From 30da667fdb838c6e46961a48d6095b5f64bd15ac Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Fri, 20 Jan 2017 22:43:44 +0100 Subject: [PATCH] Added comments, implemented interfaces for some added convenience. --- .../java/ch/dissem/msgpack/types/MPArray.java | 127 +++++++++++++++++- .../ch/dissem/msgpack/types/MPBinary.java | 3 + .../ch/dissem/msgpack/types/MPBoolean.java | 3 + .../ch/dissem/msgpack/types/MPDouble.java | 3 + .../java/ch/dissem/msgpack/types/MPFloat.java | 3 + .../ch/dissem/msgpack/types/MPInteger.java | 6 + .../java/ch/dissem/msgpack/types/MPMap.java | 78 ++++++++++- .../java/ch/dissem/msgpack/types/MPNil.java | 5 +- .../ch/dissem/msgpack/types/MPString.java | 80 ++++++++--- .../java/ch/dissem/msgpack/types/MPType.java | 2 +- .../java/ch/dissem/msgpack/types/Utils.java | 3 + .../java/ch/dissem/msgpack/ReaderTest.java | 6 +- 12 files changed, 291 insertions(+), 28 deletions(-) diff --git a/src/main/java/ch/dissem/msgpack/types/MPArray.java b/src/main/java/ch/dissem/msgpack/types/MPArray.java index 80bdad8..6f8c82e 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPArray.java +++ b/src/main/java/ch/dissem/msgpack/types/MPArray.java @@ -8,9 +8,19 @@ import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.*; -public class MPArray implements MPType> { +/** + * Representation of a msgpack encoded array. Uses a list to represent data internally, and implements the {@link List} + * interface for your convenience. + * + * @param + */ +public class MPArray implements MPType>, List { private List array; + public MPArray() { + this.array = new LinkedList<>(); + } + public MPArray(List array) { this.array = array; } @@ -39,6 +49,76 @@ public class MPArray implements MPType> { } } + @Override + public int size() { + return array.size(); + } + + @Override + public boolean isEmpty() { + return array.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return array.contains(o); + } + + @Override + public Iterator iterator() { + return array.iterator(); + } + + @Override + public Object[] toArray() { + return array.toArray(); + } + + @Override + public T1[] toArray(T1[] t1s) { + return array.toArray(t1s); + } + + @Override + public boolean add(T t) { + return array.add(t); + } + + @Override + public boolean remove(Object o) { + return array.remove(o); + } + + @Override + public boolean containsAll(Collection collection) { + return array.containsAll(collection); + } + + @Override + public boolean addAll(Collection collection) { + return array.addAll(collection); + } + + @Override + public boolean addAll(int i, Collection collection) { + return array.addAll(i, collection); + } + + @Override + public boolean removeAll(Collection collection) { + return array.removeAll(collection); + } + + @Override + public boolean retainAll(Collection collection) { + return array.retainAll(collection); + } + + @Override + public void clear() { + array.clear(); + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -52,6 +132,51 @@ public class MPArray implements MPType> { return Objects.hash(array); } + @Override + public T get(int i) { + return array.get(i); + } + + @Override + public T set(int i, T t) { + return array.set(i, t); + } + + @Override + public void add(int i, T t) { + array.add(i, t); + } + + @Override + public T remove(int i) { + return array.remove(i); + } + + @Override + public int indexOf(Object o) { + return array.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return array.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return array.listIterator(); + } + + @Override + public ListIterator listIterator(int i) { + return array.listIterator(i); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return array.subList(fromIndex, toIndex); + } + @Override public String toString() { StringBuilder result = new StringBuilder(); diff --git a/src/main/java/ch/dissem/msgpack/types/MPBinary.java b/src/main/java/ch/dissem/msgpack/types/MPBinary.java index 3999741..600c306 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPBinary.java +++ b/src/main/java/ch/dissem/msgpack/types/MPBinary.java @@ -8,6 +8,9 @@ import java.util.Arrays; import static ch.dissem.msgpack.types.Utils.bytes; +/** + * Representation of msgpack encoded binary data a.k.a. byte array. + */ public class MPBinary implements MPType { private byte[] value; diff --git a/src/main/java/ch/dissem/msgpack/types/MPBoolean.java b/src/main/java/ch/dissem/msgpack/types/MPBoolean.java index f54b4a8..a9e4f53 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPBoolean.java +++ b/src/main/java/ch/dissem/msgpack/types/MPBoolean.java @@ -5,6 +5,9 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.Objects; +/** + * Representation of a msgpack encoded boolean. + */ public class MPBoolean implements MPType { private final static int FALSE = 0xC2; private final static int TRUE = 0xC3; diff --git a/src/main/java/ch/dissem/msgpack/types/MPDouble.java b/src/main/java/ch/dissem/msgpack/types/MPDouble.java index 89575cf..69fb041 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPDouble.java +++ b/src/main/java/ch/dissem/msgpack/types/MPDouble.java @@ -8,6 +8,9 @@ import java.util.Objects; import static ch.dissem.msgpack.types.Utils.bytes; +/** + * Representation of a msgpack encoded float64 number. + */ public class MPDouble implements MPType { private double value; diff --git a/src/main/java/ch/dissem/msgpack/types/MPFloat.java b/src/main/java/ch/dissem/msgpack/types/MPFloat.java index 30a52a3..965cb72 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPFloat.java +++ b/src/main/java/ch/dissem/msgpack/types/MPFloat.java @@ -8,6 +8,9 @@ import java.util.Objects; import static ch.dissem.msgpack.types.Utils.bytes; +/** + * Representation of a msgpack encoded float32 number. + */ public class MPFloat implements MPType { private float value; diff --git a/src/main/java/ch/dissem/msgpack/types/MPInteger.java b/src/main/java/ch/dissem/msgpack/types/MPInteger.java index a58e8b7..7cc17a1 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPInteger.java +++ b/src/main/java/ch/dissem/msgpack/types/MPInteger.java @@ -8,6 +8,12 @@ import java.util.Objects; import static ch.dissem.msgpack.types.Utils.bytes; +/** + * Representation of a msgpack encoded integer. The encoding is automatically selected according to the value's size. + * Uses long due to the fact that the msgpack integer implementation may contain up to 64 bit numbers, corresponding + * to Java long values. Also note that uint64 values may be too large for signed long (thanks Java for not supporting + * unsigned values) and end in a negative value. + */ public class MPInteger implements MPType { private long value; diff --git a/src/main/java/ch/dissem/msgpack/types/MPMap.java b/src/main/java/ch/dissem/msgpack/types/MPMap.java index a105149..7424063 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPMap.java +++ b/src/main/java/ch/dissem/msgpack/types/MPMap.java @@ -6,14 +6,22 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Objects; +import java.util.*; -public class MPMap implements MPType> { +/** + * Representation of a msgpack encoded map. It is recommended to use a {@link LinkedHashMap} to ensure the order + * of entries. For convenience, it also implements the {@link Map} interface. + * + * @param + * @param + */ +public class MPMap implements MPType>, Map { private Map map; + public MPMap() { + this.map = new LinkedHashMap<>(); + } + public MPMap(Map map) { this.map = map; } @@ -40,6 +48,66 @@ public class MPMap implements MPType map) { + this.map.putAll(map); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public Collection values() { + return map.values(); + } + + @Override + public Set> entrySet() { + return map.entrySet(); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/ch/dissem/msgpack/types/MPNil.java b/src/main/java/ch/dissem/msgpack/types/MPNil.java index 27122e9..d77af46 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPNil.java +++ b/src/main/java/ch/dissem/msgpack/types/MPNil.java @@ -4,6 +4,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +/** + * Representation of msgpack encoded nil / null. + */ public class MPNil implements MPType { private final static int NIL = 0xC0; @@ -27,7 +30,7 @@ public class MPNil implements MPType { @Override public String toString() { - return "nil"; + return "null"; } public static class Unpacker implements MPType.Unpacker { diff --git a/src/main/java/ch/dissem/msgpack/types/MPString.java b/src/main/java/ch/dissem/msgpack/types/MPString.java index 741a137..3fc17c3 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPString.java +++ b/src/main/java/ch/dissem/msgpack/types/MPString.java @@ -4,12 +4,42 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.util.Objects; import static ch.dissem.msgpack.types.Utils.bytes; -public class MPString implements MPType { - private String value; +/** + * Representation of a msgpack encoded string. The encoding is automatically selected according to the string's length. + *

+ * The default encoding is UTF-8. + *

+ */ +public class MPString implements MPType, CharSequence { + private static final int FIXSTR_PREFIX = 0b10100000; + private static final int FIXSTR_PREFIX_FILTER = 0b11100000; + private static final int STR8_PREFIX = 0xD9; + private static final int STR8_LIMIT = 256; + private static final int STR16_PREFIX = 0xDA; + private static final int STR16_LIMIT = 65536; + private static final int STR32_PREFIX = 0xDB; + private static final int FIXSTR_FILTER = 0b00011111; + + private static Charset encoding = Charset.forName("UTF-8"); + + /** + * Use this method if for some messed up reason you really need to use something else than UTF-8. + * Ask yourself: why should I? Is this really necessary? + *

+ * It will set the encoding for all {@link MPString}s, but if you have inconsistent encoding in your + * format you're lost anyway. + *

+ */ + public static void setEncoding(Charset encoding) { + MPString.encoding = encoding; + } + + private final String value; public MPString(String value) { this.value = value; @@ -22,18 +52,18 @@ public class MPString implements MPType { public void pack(OutputStream out) throws IOException { int size = value.length(); if (size < 32) { - out.write(0b10100000 + size); - } else if (size < 256) { - out.write(0xD9); + out.write(FIXSTR_PREFIX + size); + } else if (size < STR8_LIMIT) { + out.write(STR8_PREFIX); out.write(size); - } else if (size < 65536) { - out.write(0xDA); + } else if (size < STR16_LIMIT) { + out.write(STR16_PREFIX); out.write(ByteBuffer.allocate(2).putShort((short) size).array()); } else { - out.write(0xDB); + out.write(STR32_PREFIX); out.write(ByteBuffer.allocate(4).putInt(size).array()); } - out.write(value.getBytes("UTF-8")); + out.write(value.getBytes(encoding)); } @Override @@ -49,30 +79,46 @@ public class MPString implements MPType { return Objects.hash(value); } + @Override + public int length() { + return value.length(); + } + + @Override + public char charAt(int i) { + return value.charAt(i); + } + + @Override + public CharSequence subSequence(int beginIndex, int endIndex) { + return value.subSequence(beginIndex, endIndex); + } + @Override public String toString() { - return '"' + value + '"'; // FIXME: escape value + return value; } public static class Unpacker implements MPType.Unpacker { public boolean is(int firstByte) { - return firstByte == 0xD9 || firstByte == 0xDA || firstByte == 0xDB || (firstByte & 0b11100000) == 0b10100000; + return firstByte == STR8_PREFIX || firstByte == STR16_PREFIX || firstByte == STR32_PREFIX + || (firstByte & FIXSTR_PREFIX_FILTER) == FIXSTR_PREFIX; } public MPString unpack(int firstByte, InputStream in) throws IOException { int size; - if ((firstByte & 0b11100000) == 0b10100000) { - size = firstByte & 0b00011111; - } else if (firstByte == 0xD9) { + if ((firstByte & FIXSTR_PREFIX_FILTER) == FIXSTR_PREFIX) { + size = firstByte & FIXSTR_FILTER; + } else if (firstByte == STR8_PREFIX) { size = in.read(); - } else if (firstByte == 0xDA) { + } else if (firstByte == STR16_PREFIX) { size = in.read() << 8 | in.read(); - } else if (firstByte == 0xDB) { + } else if (firstByte == STR32_PREFIX) { size = in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read(); } else { throw new IllegalArgumentException(String.format("Unexpected first byte 0x%02x", firstByte)); } - return new MPString(new String(bytes(in, size).array(), "UTF-8")); + return new MPString(new String(bytes(in, size).array(), encoding)); } } } diff --git a/src/main/java/ch/dissem/msgpack/types/MPType.java b/src/main/java/ch/dissem/msgpack/types/MPType.java index d85b379..9136223 100644 --- a/src/main/java/ch/dissem/msgpack/types/MPType.java +++ b/src/main/java/ch/dissem/msgpack/types/MPType.java @@ -5,7 +5,7 @@ import java.io.InputStream; import java.io.OutputStream; /** - * Representation for some msgpack encoded data. + * Representation of some msgpack encoded data. */ public interface MPType { interface Unpacker { diff --git a/src/main/java/ch/dissem/msgpack/types/Utils.java b/src/main/java/ch/dissem/msgpack/types/Utils.java index de7ff00..641076b 100644 --- a/src/main/java/ch/dissem/msgpack/types/Utils.java +++ b/src/main/java/ch/dissem/msgpack/types/Utils.java @@ -5,6 +5,9 @@ import java.io.InputStream; import java.nio.ByteBuffer; class Utils { + /** + * Returns a {@link ByteBuffer} containing the next count bytes from the {@link InputStream}. + */ static ByteBuffer bytes(InputStream in, int count) throws IOException { byte[] result = new byte[count]; int off = 0; diff --git a/src/test/java/ch/dissem/msgpack/ReaderTest.java b/src/test/java/ch/dissem/msgpack/ReaderTest.java index 64bbd4d..90b36f6 100644 --- a/src/test/java/ch/dissem/msgpack/ReaderTest.java +++ b/src/test/java/ch/dissem/msgpack/ReaderTest.java @@ -29,9 +29,9 @@ public class ReaderTest { @Test public void ensureDemoJsonIsEncodedCorrectly() throws Exception { - MPMap> object = new MPMap<>(new LinkedHashMap>()); - object.getValue().put(new MPString("compact"), new MPBoolean(true)); - object.getValue().put(new MPString("schema"), new MPInteger(0)); + MPMap> object = new MPMap<>(); + object.put(new MPString("compact"), new MPBoolean(true)); + object.put(new MPString("schema"), new MPInteger(0)); ByteArrayOutputStream out = new ByteArrayOutputStream(); object.pack(out); assertThat(out.toByteArray(), is(bytes("demo.mp")));