diff options
4 files changed, 243 insertions, 23 deletions
diff --git a/AndroidAsync/src/com/koushikdutta/async/PushParser.java b/AndroidAsync/src/com/koushikdutta/async/PushParser.java index 784729f..bfb6b74 100644 --- a/AndroidAsync/src/com/koushikdutta/async/PushParser.java +++ b/AndroidAsync/src/com/koushikdutta/async/PushParser.java @@ -1,15 +1,27 @@ package com.koushikdutta.async; +import android.util.Log; import com.koushikdutta.async.callback.DataCallback; +import java.lang.reflect.InvocationTargetException; +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 @@ -19,24 +31,24 @@ public class PushParser implements DataCallback { } static class IntWaiter extends Waiter { - TapCallback<Integer> callback; - public IntWaiter(TapCallback<Integer> callback) { + ParseCallback<Integer> callback; + public IntWaiter(ParseCallback<Integer> callback) { + super(4); this.callback = callback; - this.length = 4; } @Override public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) { - callback.tap(bb.getInt()); + callback.parsed(bb.getInt()); return null; } } static class BufferWaiter extends Waiter { - TapCallback<byte[]> callback; - public BufferWaiter(int length, TapCallback<byte[]> callback) { + ParseCallback<byte[]> callback; + public BufferWaiter(int length, ParseCallback<byte[]> callback) { + super(length); if (length <= 0) throw new IllegalArgumentException("length should be > 0"); - this.length = length; this.callback = callback; } @@ -44,17 +56,32 @@ public class PushParser implements DataCallback { public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) { byte[] bytes = new byte[length]; bb.get(bytes); - callback.tap(bytes); + callback.parsed(bytes); return null; } } + static class LenBufferWaiter extends Waiter { + private final ParseCallback<byte[]> callback; + + public LenBufferWaiter(ParseCallback<byte[]> callback) { + super(4); + this.callback = callback; + } + + @Override + public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + int length = bb.getInt(); + return new BufferWaiter(length, callback); + } + } + static class UntilWaiter extends Waiter { byte value; DataCallback callback; public UntilWaiter(byte value, DataCallback callback) { - this.length = 1; + super(1); this.value = value; this.callback = callback; } @@ -90,11 +117,48 @@ public class PushParser implements DataCallback { return this; } } + } + + private class TapArgCallback<T> implements ParseCallback<T> { + @Override + public void parsed(T data) { + args.add(data); + } + } + + 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 noopWaiter = new Waiter(0) { + @Override + public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + args.add(null); + return null; + } + }; + DataEmitter mEmitter; private LinkedList<Waiter> mWaiting = new LinkedList<Waiter>(); + private ArrayList<Object> args = new ArrayList<Object>(); ByteOrder order = ByteOrder.BIG_ENDIAN; public PushParser(DataEmitter s) { @@ -102,12 +166,12 @@ public class PushParser implements DataCallback { mEmitter.setDataCallback(this); } - public PushParser readInt(TapCallback<Integer> callback) { + public PushParser readInt(ParseCallback<Integer> callback) { mWaiting.add(new IntWaiter(callback)); return this; } - public PushParser readBuffer(int length, TapCallback<byte[]> callback) { + public PushParser readBuffer(int length, ParseCallback<byte[]> callback) { mWaiting.add(new BufferWaiter(length, callback)); return this; } @@ -117,6 +181,74 @@ public class PushParser implements DataCallback { return this; } + public PushParser readByte() { + mWaiting.add(new Waiter(1) { + @Override + public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + args.add(bb.get()); + return null; + } + }); + return this; + } + + public PushParser readShort() { + mWaiting.add(new Waiter(2) { + @Override + public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + args.add(bb.getShort()); + return null; + } + }); + return this; + } + + public PushParser readInt() { + mWaiting.add(new Waiter(4) { + @Override + public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + args.add(bb.getInt()); + return null; + } + }); + return this; + } + + public PushParser readLong() { + mWaiting.add(new Waiter(8) { + @Override + public Waiter onDataAvailable(DataEmitter emitter, ByteBufferList bb) { + args.add(bb.getLong()); + return null; + } + }); + return this; + } + + public PushParser readBuffer(int length) { + return (length == -1) ? readLenBuffer() : readBuffer(length, new TapArgCallback<byte[]>()); + } + + public PushParser readLenBuffer() { + mWaiting.add(new LenBufferWaiter(new TapArgCallback<byte[]>())); + return this; + } + + public PushParser readString() { + mWaiting.add(new LenBufferWaiter(new ParseCallback<byte[]>() { + @Override + public void parsed(byte[] data) { + args.add(new String(data)); + } + })); + return this; + } + + public PushParser noop() { + mWaiting.add(noopWaiter); + return this; + } + @Override public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { @@ -126,4 +258,37 @@ public class PushParser implements DataCallback { if (next != null) mWaiting.addFirst(next); } } + + 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; + assert false; + return null; + } } diff --git a/AndroidAsync/src/com/koushikdutta/async/TapCallback.java b/AndroidAsync/src/com/koushikdutta/async/TapCallback.java index d6252f2..d8d7844 100644 --- a/AndroidAsync/src/com/koushikdutta/async/TapCallback.java +++ b/AndroidAsync/src/com/koushikdutta/async/TapCallback.java @@ -1,6 +1,5 @@ package com.koushikdutta.async; -public interface TapCallback<T> { - public void tap(T data); +public interface TapCallback { } diff --git a/AndroidAsync/src/com/koushikdutta/async/http/filter/GZIPInputFilter.java b/AndroidAsync/src/com/koushikdutta/async/http/filter/GZIPInputFilter.java index 1bbe5c4..1ee83d3 100644 --- a/AndroidAsync/src/com/koushikdutta/async/http/filter/GZIPInputFilter.java +++ b/AndroidAsync/src/com/koushikdutta/async/http/filter/GZIPInputFilter.java @@ -3,6 +3,7 @@ package com.koushikdutta.async.http.filter; import com.koushikdutta.async.*; import com.koushikdutta.async.callback.DataCallback; import com.koushikdutta.async.http.libcore.Memory; +import com.koushikdutta.async.PushParser.ParseCallback; import java.io.IOException; import java.nio.ByteBuffer; @@ -38,10 +39,10 @@ public class GZIPInputFilter extends InflaterInputFilter { public void onDataAvailable(final DataEmitter emitter, ByteBufferList bb) { if (mNeedsHeader) { final PushParser parser = new PushParser(emitter); - parser.readBuffer(10, new TapCallback<byte[]>() { + parser.readBuffer(10, new ParseCallback<byte[]>() { int flags; boolean hcrc; - public void tap(byte[] header) { + public void parsed(byte[] header) { short magic = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN); if (magic != (short) GZIPInputStream.GZIP_MAGIC) { report(new IOException(String.format("unknown format (magic number %x)", magic))); @@ -54,14 +55,14 @@ public class GZIPInputFilter extends InflaterInputFilter { crc.update(header, 0, header.length); } if ((flags & FEXTRA) != 0) { - parser.readBuffer(2, new TapCallback<byte[]>() { - public void tap(byte[] header) { + parser.readBuffer(2, new ParseCallback<byte[]>() { + public void parsed(byte[] header) { if (hcrc) { crc.update(header, 0, 2); } int length = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN) & 0xffff; - parser.readBuffer(length, new TapCallback<byte[]>() { - public void tap(byte[] buf) { + parser.readBuffer(length, new ParseCallback<byte[]>() { + public void parsed(byte[] buf) { if (hcrc) { crc.update(buf, 0, buf.length); } @@ -95,8 +96,8 @@ public class GZIPInputFilter extends InflaterInputFilter { parser.until((byte)0, summer); } if (hcrc) { - parser.readBuffer(2, new TapCallback<byte[]>() { - public void tap(byte[] header) { + parser.readBuffer(2, new ParseCallback<byte[]>() { + public void parsed(byte[] header) { short crc16 = Memory.peekShort(header, 0, ByteOrder.LITTLE_ENDIAN); if ((short) crc.getValue() != crc16) { report(new IOException("CRC mismatch")); diff --git a/AndroidAsyncTest/src/com/koushikdutta/async/test/ByteUtilTests.java b/AndroidAsyncTest/src/com/koushikdutta/async/test/ByteUtilTests.java index 8fd8b6a..0d2f5df 100644 --- a/AndroidAsyncTest/src/com/koushikdutta/async/test/ByteUtilTests.java +++ b/AndroidAsyncTest/src/com/koushikdutta/async/test/ByteUtilTests.java @@ -24,8 +24,8 @@ public class ByteUtilTests extends TestCase { }; new PushParser(mock) .until((byte)0, new NullDataCallback()) - .readInt(new TapCallback<Integer>() { - public void tap(Integer arg) { + .readInt(new PushParser.ParseCallback<Integer>() { + public void parsed(Integer arg) { valRead = arg; } }); @@ -33,4 +33,59 @@ public class ByteUtilTests extends TestCase { Util.emitAllData(mock, new ByteBufferList(bytes)); assertEquals(valRead, 0x0A050505); } + + public void testPushParserTapUntil() { + valRead = 0; + FilteredDataEmitter mock = new FilteredDataEmitter() { + @Override + public boolean isPaused() { + return false; + } + }; + new PushParser(mock) + .until((byte)0, new NullDataCallback()) + .readInt() + .tap(new TapCallback() { + public void parsed(int arg) { + valRead = arg; + } + }); + byte[] bytes = new byte[] { 5, 5, 5, 5, 0, 10, 5, 5, 5 }; + Util.emitAllData(mock, new ByteBufferList(bytes)); + assertEquals(valRead, 0x0A050505); + } + + int readInt; + byte readByte; + String readString; + + public void testTapCallback() { + readInt = 0; + readByte = 0; + readString = ""; + + FilteredDataEmitter mock = new FilteredDataEmitter() { + @Override + public boolean isPaused() { + return false; + } + }; + new PushParser(mock) + .readInt() + .readByte() + .readString() + .tap(new TapCallback() { + void tap(int i, byte b, String s) { + readInt = i; + readByte = b; + readString = s; + } + }); + + byte[] bytes = new byte[] { 10, 5, 5, 5, 3, 0, 0, 0, 4, 116, 101, 115, 116 }; + Util.emitAllData(mock, new ByteBufferList(bytes)); + assertEquals(readInt, 0x0A050505); + assertEquals(readByte, (byte) 3); + assertEquals(readString, "test"); + } } |