All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.koushikdutta.async.PushParser Maven / Gradle / Ivy

package com.koushikdutta.async;

import android.util.Log;
import com.koushikdutta.async.callback.DataCallback;

import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedList;

public class PushParser implements DataCallback {

    public interface ParseCallback {
        public void parsed(T data);
    }

    static abstract class Waiter {
        int length;
        public Waiter(int length) {
            this.length = length;
        }
        /**
         * Consumes received data, and/or returns next waiter to continue reading instead of this waiter.
         * @param bb received data, bb.remaining >= length
         * @return - a waiter that should continue reading right away, or null if this waiter is finished
         */
        public abstract Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb);
    }

    static class IntWaiter extends Waiter {
        ParseCallback callback;
        public IntWaiter(ParseCallback callback) {
            super(4);
            this.callback = callback;
        }

        @Override
        public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
            callback.parsed(bb.getInt());
            return null;
        }
    }

    static class ByteArrayWaiter extends Waiter {
        ParseCallback callback;
        public ByteArrayWaiter(int length, ParseCallback callback) {
            super(length);
            if (length <= 0) throw new IllegalArgumentException("length should be > 0");
            this.callback = callback;
        }

        @Override
        public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
            byte[] bytes = new byte[length];
            bb.get(bytes);
            callback.parsed(bytes);
            return null;
        }
    }

    static class LenByteArrayWaiter extends Waiter {
        private final ParseCallback callback;

        public LenByteArrayWaiter(ParseCallback callback) {
            super(4);
            this.callback = callback;
        }

        @Override
        public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
            int length = bb.getInt();
            return new ByteArrayWaiter(length, callback);
        }
    }


    static class ByteBufferListWaiter extends Waiter {
        ParseCallback callback;
        public ByteBufferListWaiter(int length, ParseCallback callback) {
            super(length);
            if (length <= 0) throw new IllegalArgumentException("length should be > 0");
            this.callback = callback;
        }

        @Override
        public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
            callback.parsed(bb.get(length));
            return null;
        }
    }

    static class LenByteBufferListWaiter extends Waiter {
        private final ParseCallback callback;

        public LenByteBufferListWaiter(ParseCallback callback) {
            super(4);
            this.callback = callback;
        }

        @Override
        public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
            int length = bb.getInt();
            return new ByteBufferListWaiter(length, callback);
        }
    }

    static class UntilWaiter extends Waiter {

        byte value;
        DataCallback callback;
        public UntilWaiter(byte value, DataCallback callback) {
            super(1);
            this.value = value;
            this.callback = callback;
        }

        @Override
        public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
            boolean found = true;
            ByteBufferList cb = new ByteBufferList();
            while (bb.size() > 0) {
                ByteBuffer b = bb.remove();
                b.mark();
                int index = 0;
                while (b.remaining() > 0 && !(found = (b.get() == value))) {
                    index++;
                }
                b.reset();
                if (found) {
                    bb.addFirst(b);
                    bb.get(cb, index);
                    // eat the one we're waiting on
                    bb.get();
                    break;
                } else {
                    cb.add(b);
                }
            }

            callback.onDataAvailable(emitter, cb);

            if (found) {
                return null;
            } else {
                return this;
            }
        }
    }

    private class TapWaiter extends Waiter {
        private final TapCallback callback;

        public TapWaiter(TapCallback callback) {
            super(0);
            this.callback = callback;
        }

        @Override
        public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
            Method method = getTap(callback);
            method.setAccessible(true);
            try {
                method.invoke(callback, args.toArray());
            } catch (Exception e) {
                Log.e("PushParser", "Error while invoking tap callback", e);
            }
            args.clear();
            return null;
        }
    }

    private Waiter noopArgWaiter = new Waiter(0) {
        @Override
        public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
            args.add(null);
            return null;
        }
    };

    private Waiter byteArgWaiter = new Waiter(1) {
        @Override
        public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
            args.add(bb.get());
            return null;
        }
    };

    private Waiter shortArgWaiter = new Waiter(2) {
        @Override
        public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
            args.add(bb.getShort());
            return null;
        }
    };

    private Waiter intArgWaiter = new Waiter(4) {
        @Override
        public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
            args.add(bb.getInt());
            return null;
        }
    };

    private Waiter longArgWaiter = new Waiter(8) {
        @Override
        public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
            args.add(bb.getLong());
            return null;
        }
    };

    private ParseCallback byteArrayArgCallback = new ParseCallback() {
        @Override
        public void parsed(byte[] data) {
            args.add(data);
        }
    };

    private ParseCallback byteBufferListArgCallback = new ParseCallback() {
        @Override
        public void parsed(ByteBufferList data) {
            args.add(data);
        }
    };

    private ParseCallback stringArgCallback = new ParseCallback() {
        @Override
        public void parsed(byte[] data) {
            args.add(new String(data));
        }
    };

    DataEmitter mEmitter;
    private LinkedList mWaiting = new LinkedList();
    private ArrayList args = new ArrayList();
    ByteOrder order = ByteOrder.BIG_ENDIAN;

    public PushParser setOrder(ByteOrder order) {
        this.order = order;
        return this;
    }

    public PushParser(DataEmitter s) {
        mEmitter = s;
        mEmitter.setDataCallback(this);
    }

    public PushParser readInt(ParseCallback callback) {
        mWaiting.add(new IntWaiter(callback));
        return this;
    }

    public PushParser readByteArray(int length, ParseCallback callback) {
        mWaiting.add(new ByteArrayWaiter(length, callback));
        return this;
    }

    public PushParser readByteBufferList(int length, ParseCallback callback) {
        mWaiting.add(new ByteBufferListWaiter(length, callback));
        return this;
    }

    public PushParser until(byte b, DataCallback callback) {
        mWaiting.add(new UntilWaiter(b, callback));
        return this;
    }

    public PushParser readByte() {
        mWaiting.add(byteArgWaiter);
        return this;
    }

    public PushParser readShort() {
        mWaiting.add(shortArgWaiter);
        return this;
    }

    public PushParser readInt() {
        mWaiting.add(intArgWaiter);
        return this;
    }

    public PushParser readLong() {
        mWaiting.add(longArgWaiter);
        return this;
    }

    public PushParser readByteArray(int length) {
        return (length == -1) ? readLenByteArray() : readByteArray(length, byteArrayArgCallback);
    }

    public PushParser readLenByteArray() {
        mWaiting.add(new LenByteArrayWaiter(byteArrayArgCallback));
        return this;
    }

    public PushParser readByteBufferList(int length) {
        return (length == -1) ? readLenByteBufferList() : readByteBufferList(length, byteBufferListArgCallback);
    }

    public PushParser readLenByteBufferList() {
        return readLenByteBufferList(byteBufferListArgCallback);
    }

    public PushParser readLenByteBufferList(ParseCallback callback) {
        mWaiting.add(new LenByteBufferListWaiter(callback));
        return this;
    }

    public PushParser readString() {
        mWaiting.add(new LenByteArrayWaiter(stringArgCallback));
        return this;
    }

    public PushParser noop() {
        mWaiting.add(noopArgWaiter);
        return this;
    }

    ByteBufferList pending = new ByteBufferList();
    @Override
    public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
        bb.get(pending);
        while (mWaiting.size() > 0 && pending.remaining() >= mWaiting.peek().length) {
            pending.order(order);
            Waiter next = mWaiting.poll().onDataAvailable(emitter, pending);
            if (next != null) mWaiting.addFirst(next);
        }
        if (mWaiting.size() == 0)
            pending.get(bb);
    }

    public void tap(TapCallback callback) {
        mWaiting.add(new TapWaiter(callback));
    }

    static Hashtable mTable = new Hashtable();
    static Method getTap(TapCallback callback) {
        Method found = mTable.get(callback.getClass());
        if (found != null)
            return found;

        for (Method method : callback.getClass().getMethods()) {
            if ("tap".equals(method.getName())) {
                mTable.put(callback.getClass(), method);
                return method;
            }
        }

        // try the proguard friendly route, take the first/only method
        // in case "tap" has been renamed
        Method[] candidates = callback.getClass().getDeclaredMethods();
        if (candidates.length == 1)
            return candidates[0];

        String fail =
            "-keep class * extends com.koushikdutta.async.TapCallback {\n" +
                    "    *;\n" +
                    "}\n";

        //null != "AndroidAsync: tap callback could not be found. Proguard? Use this in your proguard config:\n" + fail;
        throw new AssertionError(fail);
    }
}