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<T> { 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<Integer> callback; public IntWaiter(ParseCallback<Integer> 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<byte[]> callback; public ByteArrayWaiter(int length, ParseCallback<byte[]> 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<byte[]> callback; public LenByteArrayWaiter(ParseCallback<byte[]> callback) { super(4); this.callback = callback; } @Override public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) { int length = bb.getInt(); if (length == 0) { callback.parsed(new byte[0]); return null; } return new ByteArrayWaiter(length, callback); } } static class ByteBufferListWaiter extends Waiter { ParseCallback<ByteBufferList> callback; public ByteBufferListWaiter(int length, ParseCallback<ByteBufferList> 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<ByteBufferList> callback; public LenByteBufferListWaiter(ParseCallback<ByteBufferList> 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<byte[]> byteArrayArgCallback = new ParseCallback<byte[]>() { @Override public void parsed(byte[] data) { args.add(data); } }; private ParseCallback<ByteBufferList> byteBufferListArgCallback = new ParseCallback<ByteBufferList>() { @Override public void parsed(ByteBufferList data) { args.add(data); } }; private ParseCallback<byte[]> stringArgCallback = new ParseCallback<byte[]>() { @Override public void parsed(byte[] data) { args.add(new String(data)); } }; DataEmitter mEmitter; private LinkedList<Waiter> mWaiting = new LinkedList<Waiter>(); private ArrayList<Object> args = new ArrayList<Object>(); 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<Integer> callback) { mWaiting.add(new IntWaiter(callback)); return this; } public PushParser readByteArray(int length, ParseCallback<byte[]> callback) { mWaiting.add(new ByteArrayWaiter(length, callback)); return this; } public PushParser readByteBufferList(int length, ParseCallback<ByteBufferList> 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<ByteBufferList> 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<Class, Method> mTable = new Hashtable<Class, Method>(); 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); } }