class Decoder implements Parser.Decoder {

        /*package*/ BinaryReconstructor reconstructor;

        private Decoder.Callback onDecodedCallback;

        public Decoder() {
            this.reconstructor = null;
        }

        @Override
        public void add(String obj) {
            Packet packet = decodeString(obj);
            if (BINARY_EVENT == packet.type || BINARY_ACK == packet.type) {
                this.reconstructor = new BinaryReconstructor(packet);

                if (this.reconstructor.reconPack.attachments == 0) {
                    if (this.onDecodedCallback != null) {
                        this.onDecodedCallback.call(packet);
                    }
                }
            } else {
                if (this.onDecodedCallback != null) {
                    this.onDecodedCallback.call(packet);
                }
            }
        }

        @Override
        public void add(byte[] obj) {
            if (this.reconstructor == null) {
                throw new RuntimeException("got binary data when not reconstructing a packet");
            } else {
                Packet packet = this.reconstructor.takeBinaryData(obj);
                if (packet != null) {
                    this.reconstructor = null;
                    if (this.onDecodedCallback != null) {
                        this.onDecodedCallback.call(packet);
                    }
                }
            }
        }

        private static Packet decodeString(String str) {<FILL_FUNCTION_BODY>}

        private static boolean isPayloadValid(int type, Object payload) {
            switch (type) {
                case Parser.CONNECT:
                case Parser.CONNECT_ERROR:
                    return payload instanceof JSONObject;
                case Parser.DISCONNECT:
                    return payload == null;
                case Parser.EVENT:
                case Parser.BINARY_EVENT:
                    return payload instanceof JSONArray
                            && ((JSONArray) payload).length() > 0
                            && !((JSONArray) payload).isNull(0);
                case Parser.ACK:
                case Parser.BINARY_ACK:
                    return payload instanceof JSONArray;
                default:
                    return false;
            }
        }

        @Override
        public void destroy() {
            if (this.reconstructor != null) {
                this.reconstructor.finishReconstruction();
            }
            this.onDecodedCallback = null;
        }

        @Override
        public void onDecoded (Callback callback) {
            this.onDecodedCallback = callback;
        }
    }

            int i = 0;
            int length = str.length();

            Packet<Object> p = new Packet<>(Character.getNumericValue(str.charAt(0)));

            if (p.type < 0 || p.type > types.length - 1) {
                throw new DecodingException("unknown packet type " + p.type);
            }

            if (BINARY_EVENT == p.type || BINARY_ACK == p.type) {
                if (!str.contains("-") || length <= i + 1) {
                    throw new DecodingException("illegal attachments");
                }
                StringBuilder attachments = new StringBuilder();
                while (str.charAt(++i) != '-') {
                    attachments.append(str.charAt(i));
                }
                p.attachments = Integer.parseInt(attachments.toString());
            }

            if (length > i + 1 && '/' == str.charAt(i + 1)) {
                StringBuilder nsp = new StringBuilder();
                while (true) {
                    ++i;
                    char c = str.charAt(i);
                    if (',' == c) break;
                    nsp.append(c);
                    if (i + 1 == length) break;
                }
                p.nsp = nsp.toString();
            } else {
                p.nsp = "/";
            }

            if (length > i + 1){
                Character next = str.charAt(i + 1);
                if (Character.getNumericValue(next) > -1) {
                    StringBuilder id = new StringBuilder();
                    while (true) {
                        ++i;
                        char c = str.charAt(i);
                        if (Character.getNumericValue(c) < 0) {
                            --i;
                            break;
                        }
                        id.append(c);
                        if (i + 1 == length) break;
                    }
                    try {
                        p.id = Integer.parseInt(id.toString());
                    } catch (NumberFormatException e){
                        throw new DecodingException("invalid payload");
                    }
                }
            }

            if (length > i + 1){
                try {
                    str.charAt(++i);
                    p.data = new JSONTokener(str.substring(i)).nextValue();
                } catch (JSONException e) {
                    logger.log(Level.WARNING, "An error occured while retrieving data from JSONTokener", e);
                    throw new DecodingException("invalid payload");
                }
                if (!isPayloadValid(p.type, p.data)) {
                    throw new DecodingException("invalid payload");
                }
            }

            if (logger.isLoggable(Level.FINE)) {
                logger.fine(String.format("decoded %s as %s", str, p));
            }
            return p;
        
<no_super_class>