diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 diff --git a/README.md b/README.md index 85893db09..2d7689968 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,41 @@ # MessagePack for Java -An implementation of [MessagePack](http://msgpack.org/) for Java. +An *alternative* implementation of [MessagePack](http://msgpack.org/) for Java. + +It is based on the [original Java implementation](https://github.com/msgpack/msgpack-java) + +The primary goals of this alternative implementation are to keep the original tightness of MessagePack, while serving additionally the following use cases: + +* Enable long-term persistence of data, under the assumption that the full schema (but not the original code) will still be available in the future. +* Enable easy on-the-fly data-migration, when reading data using an old schema. +* Enable partial/incremental de-serialisation. +* Enable serializing *changes* to an object graph, rather the the whole graph itself. +* Enable each type to choose a different MessagePack representation (array/map/raw) +* Enable embedding of other serialisation streams. +* Enable cycles in the object graph. +* Enable serialisation of immutable objects (might not work together with "cycles") +* Enable compatibility with GWT and Hadoop +* Enamble compatibility with HPPC collections (more third-party APIs compatibility to come) + +One limitation of the core MessagePack API that causes some inefficiencies is the fact that MessagePack does not nativelly support true streaming of data. What I mean is that data can put into raw, arrays or maps, but all of them require the API user to know how many bytes or values will be written a priory, which goes against the principle of "streaming". There is currently work to make MessagePack an official Internet standard, and as part of this effort, there is work to make the API extensible. This, I hope, will allow use to create raw/arrays/maps of unknown size. + +There are generally a few patterns to serializing objects. They can either be written as a bunch of bytes (raw), where the API cannot recognize the actual content of the bytes, and the code deserializing the data must have the full knowledge. The second possibility is that each object's filed is written as a "value" that the API recognize, and so objects are always written in the same format, independent of the content. The third option is for the object to only write it's "non-default" fields to the stream. The last option requires that at least some field ID precedes each field data, so that the object can be reconstructed when de-serializing. + +Most serialisation API require force you to use a single one of those patterns for each and every type you want to persist. This API lets you choose, although MessagePack itself puts limitation on "raw" objects, which currently requires wrapping them in an array first, therefore causing one additional byte of overhead per object. + +The general design of the object protocol works like this: + +* All objects are written and read using an object-specific template. +* At the moment, all templates must be creted manually. +* The (optional) separation of object creation and obect reading allows both for the usage of immutable types and cycles, but both might not work together. +* The ObjectPacker and ObjectUnpacker are hardly more then wrappers for the templates. +* If the object is null, the you write nil +* If the object is a string, the you write a raw +* If the objects wants to be saved as an array, you open the array at size ("object size" + 1), and store the object's type as an integer ID in the first array position. +* If the objects wants to be saved as a map, you open the map at size ("object size" + 1), and store the object's type as an integer ID in the first map entry key (first map entry value might optionally be used by the object's template). +* If the objects wants to be saved as a raw (and is not a string), you create an array at size 2, and store the object's type as an integer ID in the first array position, and the raw at the second position. +* Under certain circumstances, arrays of objects might be able to skip the individual array and type header overhead. +* Being compatable with Java's DataOutput/DataInput interfaces bot for the underlying stream and the MessagePack core API should maximize this API's compatibility to existing code. ## Installation diff --git a/pom.xml b/pom.xml index 252152c43..dc6b94f49 100644 --- a/pom.xml +++ b/pom.xml @@ -1,15 +1,13 @@ 4.0.0 - org.msgpack + com.blockwithme msgpack MessagePack for Java MessagePack for Java is a binary-based efficient object serialization library in Java. - 0.6.8-SNAPSHOT + 1.0.0 bundle - http://msgpack.org/ - The Apache Software License, Version 2.0 @@ -17,28 +15,21 @@ repo - - scm:git:git://github.com/msgpack/msgpack-java.git - scm:git:git@github.com:msgpack/msgpack-java.git - scm:git:git://github.com/msgpack/msgpack-java.git + scm:git:git://github.com/skunkiferous/msgpack-java.git + scm:git:git@github.com:skunkiferous/msgpack-java.git + scm:git:git://github.com/skunkiferous/msgpack-java.git - GitHub - https://github.com/msgpack/msgpack-java/issues + https://github.com/skunkiferous/msgpack-java/issues - frsyuki - Sadayuki Furuhashi - frsyuki@users.sourceforge.jp - - - muga - Muga Nishizawa - muga.nishizawa@gmail.com + skunkiferous + Sebastien Diot + s.diot@eurodata.de @@ -66,6 +57,16 @@ 4.8.2 test + + com.blockwithme + Common + [0.0,1) + + + com.blockwithme + hppc + 0.4.1 + @@ -90,8 +91,8 @@ maven-compiler-plugin 2.3.2 - 1.6 - 1.6 + 1.7 + 1.7 UTF-8 @@ -190,4 +191,11 @@ + + + blockwithme-mvn-repo + https://raw.github.com/skunkiferous/Maven/master + + + diff --git a/src/main/java/com/blockwithme/msgpack/Helper.java b/src/main/java/com/blockwithme/msgpack/Helper.java new file mode 100644 index 000000000..34d8bf06d --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/Helper.java @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2013 Sebastien Diot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blockwithme.msgpack; + +import java.io.DataOutput; +import java.io.IOException; + +import com.blockwithme.msgpack.impl.MessagePackPacker; +import com.blockwithme.msgpack.impl.MessagePackUnpacker; +import com.blockwithme.msgpack.impl.ObjectPackerImpl; +import com.blockwithme.msgpack.impl.ObjectUnpackerImpl; +import com.blockwithme.msgpack.schema.SchemaManager; +import com.blockwithme.msgpack.templates.PackerContext; +import com.blockwithme.msgpack.templates.UnpackerContext; +import com.blockwithme.util.DataInputBuffer; + +/** + * Helper class for the MessagePack API. + * + * @author monster + */ +public class Helper { + /** The format and schema versions. */ + public static final class Version { + /** The format version. */ + public final int format; + /** The schema version. */ + public final int schema; + + /** Constructor */ + public Version(final int format, final int schema) { + this.format = format; + this.schema = schema; + } + + /** toString */ + @Override + public String toString() { + return "Version(" + format + "," + schema + ")"; + } + } + + /** Creates a new packer. */ + public static Packer newPacker(final DataOutput out) { + return new MessagePackPacker(out); + } + + /** Creates a new object packer. + * @throws IOException */ + public static ObjectPacker newObjectPacker(final DataOutput out, + final PackerContext context) throws IOException { + return new ObjectPackerImpl(newPacker(out), context); + } + + /** Creates a new object packer. + * @throws IOException */ + public static ObjectPacker newObjectPacker(final DataOutput out, + final SchemaManager schemaManager, final int schema) + throws IOException { + final PackerContext pc = new PackerContext(schemaManager); + pc.schemaID = schema; + return newObjectPacker(out, pc); + } + + /** Creates a new Unpacker for the bytes. */ + public static Unpacker newUnpacker(final byte[] bytes) { + return new MessagePackUnpacker(new DataInputBuffer(bytes)); + } + + /** Creates a new ObjectUnpacker for the bytes. + * @throws IOException */ + public static ObjectUnpacker newObjectUnpacker(final byte[] bytes, + final UnpackerContext context) throws IOException { + return new ObjectUnpackerImpl(new MessagePackUnpacker( + new DataInputBuffer(bytes)), context); + } + + /** Creates a new ObjectUnpacker for the bytes. + * @throws IOException */ + public static ObjectUnpacker newObjectUnpacker(final byte[] bytes, + final SchemaManager schemaManager) throws IOException { + return newObjectUnpacker(bytes, new UnpackerContext(schemaManager)); + } + + /** Returns the format version, and the schema version, for the given byte array. + * @throws IOException */ + public static Version getVersion(final byte[] bytes) throws IOException { + final Unpacker u = newUnpacker(bytes); + final int format = u.readIndex(); + final int schema = u.readIndex(); + return new Version(format, schema); + } + + /** Writes a boolean array. */ + public static void writeBooleanArray(final Packer packer, + final boolean[] target) throws IOException { + if (target == null) { + packer.writeNil(); + + } + packer.writeArrayBegin(target.length); + for (final boolean a : target) { + packer.writeBoolean(a); + } + packer.writeArrayEnd(); + + } + + /** Writes a short array. */ + public static void writeShortArray(final Packer packer, final short[] target) + throws IOException { + if (target == null) { + packer.writeNil(); + + } + packer.writeArrayBegin(target.length); + for (final short a : target) { + packer.writeShort(a); + } + packer.writeArrayEnd(); + + } + + /** Writes a char array. */ + public static void writeCharArray(final Packer packer, final char[] target) + throws IOException { + if (target == null) { + packer.writeNil(); + + } + packer.writeArrayBegin(target.length); + for (final char a : target) { + packer.writeChar(a); + } + packer.writeArrayEnd(); + + } + + /** Writes a int array. */ + public static void writeIntArray(final Packer packer, final int[] target) + throws IOException { + if (target == null) { + packer.writeNil(); + + } + packer.writeArrayBegin(target.length); + for (final int a : target) { + packer.writeInt(a); + } + packer.writeArrayEnd(); + + } + + /** Writes a long array. */ + public static void writeLongArray(final Packer packer, final long[] target) + throws IOException { + if (target == null) { + packer.writeNil(); + + } + packer.writeArrayBegin(target.length); + for (final long a : target) { + packer.writeLong(a); + } + packer.writeArrayEnd(); + + } + + /** Writes a float array. */ + public static void writeFloatArray(final Packer packer, final float[] target) + throws IOException { + if (target == null) { + packer.writeNil(); + + } + packer.writeArrayBegin(target.length); + for (final float a : target) { + packer.writeFloat(a); + } + packer.writeArrayEnd(); + + } + + /** Writes a double array. */ + public static void writeDoubleArray(final Packer packer, + final double[] target) throws IOException { + if (target == null) { + packer.writeNil(); + + } + packer.writeArrayBegin(target.length); + for (final double a : target) { + packer.writeDouble(a); + } + packer.writeArrayEnd(); + + } + + /** Writes a boolean array. */ + public static void writeBooleanArray(final Packer packer, + final boolean[] target, final int offset, final int length) + throws IOException { + if (target == null) { + packer.writeNil(); + + } + packer.writeArrayBegin(length); + final int end = (offset + length); + for (int i = offset; i < end; i++) { + final boolean a = target[i]; + packer.writeBoolean(a); + } + packer.writeArrayEnd(); + + } + + /** Writes a short array. */ + public static void writeShortArray(final Packer packer, + final short[] target, final int offset, final int length) + throws IOException { + if (target == null) { + packer.writeNil(); + + } + packer.writeArrayBegin(length); + final int end = (offset + length); + for (int i = offset; i < end; i++) { + final short a = target[i]; + packer.writeShort(a); + } + packer.writeArrayEnd(); + + } + + /** Writes a char array. */ + public static void writeCharArray(final Packer packer, final char[] target, + final int offset, final int length) throws IOException { + if (target == null) { + packer.writeNil(); + + } + packer.writeArrayBegin(length); + final int end = (offset + length); + for (int i = offset; i < end; i++) { + final char a = target[i]; + packer.writeChar(a); + } + packer.writeArrayEnd(); + + } + + /** Writes a int array. */ + public static void writeIntArray(final Packer packer, final int[] target, + final int offset, final int length) throws IOException { + if (target == null) { + packer.writeNil(); + + } + packer.writeArrayBegin(length); + final int end = (offset + length); + for (int i = offset; i < end; i++) { + final int a = target[i]; + packer.writeInt(a); + } + packer.writeArrayEnd(); + + } + + /** Writes a long array. */ + public static void writeLongArray(final Packer packer, final long[] target, + final int offset, final int length) throws IOException { + if (target == null) { + packer.writeNil(); + + } + packer.writeArrayBegin(length); + final int end = (offset + length); + for (int i = offset; i < end; i++) { + final long a = target[i]; + packer.writeLong(a); + } + packer.writeArrayEnd(); + + } + + /** Writes a float array. */ + public static void writeFloatArray(final Packer packer, + final float[] target, final int offset, final int length) + throws IOException { + if (target == null) { + packer.writeNil(); + + } + packer.writeArrayBegin(length); + final int end = (offset + length); + for (int i = offset; i < end; i++) { + final float a = target[i]; + packer.writeFloat(a); + } + packer.writeArrayEnd(); + + } + + /** Writes a double array. */ + public static void writeDoubleArray(final Packer packer, + final double[] target, final int offset, final int length) + throws IOException { + if (target == null) { + packer.writeNil(); + + } + packer.writeArrayBegin(length); + final int end = (offset + length); + for (int i = offset; i < end; i++) { + final double a = target[i]; + packer.writeDouble(a); + } + packer.writeArrayEnd(); + + } + + /** Reads a boolean array. */ + public static boolean[] readBooleanArray(final Unpacker unpacker) + throws IOException { + if (unpacker.trySkipNil()) { + return null; + } + final int n = unpacker.readArrayBegin(); + final boolean[] result = new boolean[n]; + for (int i = 0; i < n; i++) { + result[i] = unpacker.readBoolean(); + } + unpacker.readArrayEnd(); + return result; + } + + /** Reads a short array. */ + public static short[] readShortArray(final Unpacker unpacker) + throws IOException { + if (unpacker.trySkipNil()) { + return null; + } + final int n = unpacker.readArrayBegin(); + final short[] result = new short[n]; + for (int i = 0; i < n; i++) { + result[i] = unpacker.readShort(); + } + unpacker.readArrayEnd(); + return result; + } + + /** Reads a char array. */ + public static char[] readCharArray(final Unpacker unpacker) + throws IOException { + if (unpacker.trySkipNil()) { + return null; + } + final int n = unpacker.readArrayBegin(); + final char[] result = new char[n]; + for (int i = 0; i < n; i++) { + result[i] = unpacker.readChar(); + } + unpacker.readArrayEnd(); + return result; + } + + /** Reads a int array. */ + public static int[] readIntArray(final Unpacker unpacker) + throws IOException { + if (unpacker.trySkipNil()) { + return null; + } + final int n = unpacker.readArrayBegin(); + final int[] result = new int[n]; + for (int i = 0; i < n; i++) { + result[i] = unpacker.readInt(); + } + unpacker.readArrayEnd(); + return result; + } + + /** Reads a long array. */ + public static long[] readLongArray(final Unpacker unpacker) + throws IOException { + if (unpacker.trySkipNil()) { + return null; + } + final int n = unpacker.readArrayBegin(); + final long[] result = new long[n]; + for (int i = 0; i < n; i++) { + result[i] = unpacker.readLong(); + } + unpacker.readArrayEnd(); + return result; + } + + /** Reads a float array. */ + public static float[] readFloatArray(final Unpacker unpacker) + throws IOException { + if (unpacker.trySkipNil()) { + return null; + } + final int n = unpacker.readArrayBegin(); + final float[] result = new float[n]; + for (int i = 0; i < n; i++) { + result[i] = unpacker.readFloat(); + } + unpacker.readArrayEnd(); + return result; + } + + /** Reads a double array. */ + public static double[] readDoubleArray(final Unpacker unpacker) + throws IOException { + if (unpacker.trySkipNil()) { + return null; + } + final int n = unpacker.readArrayBegin(); + final double[] result = new double[n]; + for (int i = 0; i < n; i++) { + result[i] = unpacker.readDouble(); + } + unpacker.readArrayEnd(); + return result; + } +} diff --git a/src/main/java/org/msgpack/MessageTypeException.java b/src/main/java/com/blockwithme/msgpack/MessageTypeException.java similarity index 72% rename from src/main/java/org/msgpack/MessageTypeException.java rename to src/main/java/com/blockwithme/msgpack/MessageTypeException.java index 926838f51..36c4bcaad 100644 --- a/src/main/java/org/msgpack/MessageTypeException.java +++ b/src/main/java/com/blockwithme/msgpack/MessageTypeException.java @@ -15,23 +15,27 @@ // See the License for the specific language governing permissions and // limitations under the License. // -package org.msgpack; +package com.blockwithme.msgpack; +/** + * Denotes certain MessagePack Exceptions, generally related to inconsistent + * API usage or stream content. + */ @SuppressWarnings("serial") public class MessageTypeException extends RuntimeException { public MessageTypeException() { super(); } - public MessageTypeException(String message) { + public MessageTypeException(final String message) { super(message); } - public MessageTypeException(String message, Throwable cause) { + public MessageTypeException(final String message, final Throwable cause) { super(message, cause); } - public MessageTypeException(Throwable cause) { + public MessageTypeException(final Throwable cause) { super(cause); } } diff --git a/src/main/java/com/blockwithme/msgpack/ObjectPacker.java b/src/main/java/com/blockwithme/msgpack/ObjectPacker.java new file mode 100644 index 000000000..1f6a4f572 --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/ObjectPacker.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2013 Sebastien Diot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blockwithme.msgpack; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Date; + +import com.blockwithme.msgpack.templates.Template; + +/** + * ObjectPacker can pack any object with a registered template. + * + * It does NOT extends Packer by design. To serialize primitive value, go + * directly to the underlying Packer, with the packer() method. + * + * Please realize the difference between the write(int[]) of Packer and our + * writeObject(int[]); ours stores additional information to allow for generic object + * serialization (the type), therefore the two are not compatible. Also, all + * the methods here take into consideration that an object might be serialized + * multiple times, and so writing the same int[] with an ObjectPacker will + * result in a single copy on the stream, while it would result in multiple + * copies if done using Unpacker. + * + * @author monster + */ +public interface ObjectPacker { + + /** Returns the underlying packer. */ + Packer packer(); + + /** Writes a Boolean. */ + ObjectPacker writeObject(final Boolean o) throws IOException; + + /** Writes a Byte. */ + ObjectPacker writeObject(final Byte o) throws IOException; + + /** Writes a Short. */ + ObjectPacker writeObject(final Short o) throws IOException; + + /** Writes a Character. */ + ObjectPacker writeObject(final Character o) throws IOException; + + /** Writes a Integer. */ + ObjectPacker writeObject(final Integer o) throws IOException; + + /** Writes a Long. */ + ObjectPacker writeObject(final Long o) throws IOException; + + /** Writes a Float. */ + ObjectPacker writeObject(final Float o) throws IOException; + + /** Writes a Double. */ + ObjectPacker writeObject(final Double o) throws IOException; + + /** Writes a BigInteger. */ + ObjectPacker writeObject(final BigInteger o) throws IOException; + + /** Writes a BigDecimal. */ + ObjectPacker writeObject(final BigDecimal o) throws IOException; + + /** Writes a Date out. */ + ObjectPacker writeObject(final Date o) throws IOException; + + /** Writes a boolean array. */ + ObjectPacker writeObject(final boolean[] o) throws IOException; + + /** Writes a byte array. */ + ObjectPacker writeObject(final byte[] o) throws IOException; + + /** Writes a short array. */ + ObjectPacker writeObject(final short[] o) throws IOException; + + /** Writes a char array. */ + ObjectPacker writeObject(final char[] o) throws IOException; + + /** Writes a int array. */ + ObjectPacker writeObject(final int[] o) throws IOException; + + /** Writes a long array. */ + ObjectPacker writeObject(final long[] o) throws IOException; + + /** Writes a float array. */ + ObjectPacker writeObject(final float[] o) throws IOException; + + /** Writes a double array. */ + ObjectPacker writeObject(final double[] o) throws IOException; + + /** Writes a String. */ + ObjectPacker writeObject(final String o) throws IOException; + + /** Writes a ByteBuffer. */ + ObjectPacker writeObject(final ByteBuffer o) throws IOException; + + /** Writes a Class out. */ + ObjectPacker writeObject(final Class o) throws IOException; + + /** Writes a slice of a byte[]. */ + ObjectPacker writeObject(final byte[] o, final int off, final int len) + throws IOException; + + /** + * Writes an Object out. Template is determined based on object type. + * Pass along a template for faster results. + */ + ObjectPacker writeObject(final Object o) throws IOException; + + /** + * Writes an Object out. Object must be compatible with non-null template. + * Note that we cannot link the Object type with the template type, since + * we could use the template to also write an *array* of the object type. + */ + ObjectPacker writeObject(final Object o, final Template template) + throws IOException; + + /** + * Writes an Object out. Template is determined based on object type. + * Pass along a template for faster results. + */ + ObjectPacker writeObject(final Object o, + final boolean ifObjectArrayCanContainNullValue) throws IOException; + + /** + * Writes an Object out. Object must be compatible with non-null template. + * Note that we cannot link the Object type with the template type, since + * we could use the template to also write an *array* of the object type. + */ + ObjectPacker writeObject(final Object o, final Template template, + final boolean ifObjectArrayCanContainNullValue) throws IOException; +} diff --git a/src/main/java/com/blockwithme/msgpack/ObjectUnpacker.java b/src/main/java/com/blockwithme/msgpack/ObjectUnpacker.java new file mode 100644 index 000000000..c7a74bc05 --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/ObjectUnpacker.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2013 Sebastien Diot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blockwithme.msgpack; + +import java.io.Closeable; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Date; + +import com.blockwithme.msgpack.templates.Template; + +/** + * ObjectUnpacker can unpack any object with a registered template. + * It does NOT extends Unpacker by design. To serialize primitive value, go + * directly to the underlying Unpacker, with the unpacker() method. + * + * Please realize the difference between the int[] readIntArray() of Unpacker + * and ours; ours stores additional information to allow for generic object + * serialization (the type), therefore the two are not compatible. Also, all + * the methods here take into consideration that an object might be serialized + * multiple times, and so writing the same int[] with an ObjectPacker will + * result in a single copy on the stream, while it would result in multiple + * copies if done using Unpacker. + * + * @author monster + */ +public interface ObjectUnpacker extends Closeable { + + /** Returns the underlying unpacker. */ + Unpacker unpacker(); + + /** Reads any Object. */ + Object readObject() throws IOException; + + /** + * Reads any Object. Fails if the template is not null, and the Object type + * does not match template type. Since templates are used to read not only + * objects, but also array, we cannot assume that for a Template, the + * return type will be T; it could also be T[] ... + */ + Object readObject(final Template template) throws IOException; + + /** Reads any Object. */ + Object readObject(final boolean ifObjectArrayCanContainNullValue) + throws IOException; + + /** + * Reads any Object. Fails if the template is not null, and the Object type + * does not match template type. Since templates are used to read not only + * objects, but also array, we cannot assume that for a Template, the + * return type will be T; it could also be T[] ... + */ + Object readObject(final Template template, + final boolean ifObjectArrayCanContainNullValue) throws IOException; + + /** Reads a boolean. */ + Boolean readBoolean() throws IOException; + + /** Reads a byte. */ + Byte readByte() throws IOException; + + /** Reads a short. */ + Short readShort() throws IOException; + + /** Reads a char. */ + Character readChar() throws IOException; + + /** Reads an int. */ + Integer readInt() throws IOException; + + /** Reads a long. */ + Long readLong() throws IOException; + + /** Reads a float. */ + Float readFloat() throws IOException; + + /** Reads a double. */ + Double readDouble() throws IOException; + + /** Reads a String. */ + String readString() throws IOException; + + /** Reads a BigInteger. */ + BigInteger readBigInteger() throws IOException; + + /** Reads a BigDecimal. */ + BigDecimal readBigDecimal() throws IOException; + + /** Reads a Date. */ + Date readDate() throws IOException; + + /** Reads a Class. */ + Class readClass() throws IOException; + + /** Reads a boolean array. */ + boolean[] readBooleanArray() throws IOException; + + /** Reads a byte array. */ + byte[] readByteArray() throws IOException; + + /** Reads a short array. */ + short[] readShortArray() throws IOException; + + /** Reads a char array. */ + char[] readCharArray() throws IOException; + + /** Reads a int array. */ + int[] readIntArray() throws IOException; + + /** Reads a long array. */ + long[] readLongArray() throws IOException; + + /** Reads a float array. */ + float[] readFloatArray() throws IOException; + + /** Reads a double array. */ + double[] readDoubleArray() throws IOException; + + /** Reads a ByteBuffer. */ + ByteBuffer readByteBuffer() throws IOException; +} diff --git a/src/main/java/com/blockwithme/msgpack/Packer.java b/src/main/java/com/blockwithme/msgpack/Packer.java new file mode 100644 index 000000000..a4d8ae7dc --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/Packer.java @@ -0,0 +1,223 @@ +// +// MessagePack for Java +// +// Copyright (C) 2009 - 2013 FURUHASHI Sadayuki +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package com.blockwithme.msgpack; + +import java.io.Closeable; +import java.io.DataOutput; +import java.io.Flushable; +import java.io.IOException; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Date; + +/** + * Standard serializer in MessagePack for Java. It allows users to serialize + * objects like String, byte[], primitive types and so on. + * Things List, Map ... must be built on top of it. + * + * It also implements DataOutput, for easier migration of code, but it is + * unlikely that code using DataOutput can be ported without modifications. + */ +public interface Packer extends Closeable, Flushable, DataOutput { + /** Writes a boolean. */ + @Override + void writeBoolean(final boolean o) throws IOException; + + /** Writes a byte. */ + void writeByte(final byte o) throws IOException; + + /** Writes a short. */ + void writeShort(final short o) throws IOException; + + /** Writes a char. */ + void writeChar(final char o) throws IOException; + + /** Writes a int. */ + @Override + void writeInt(final int o) throws IOException; + + /** Writes a long. */ + @Override + void writeLong(final long o) throws IOException; + + /** Writes a float. */ + @Override + void writeFloat(final float o) throws IOException; + + /** Writes a double. */ + @Override + void writeDouble(final double o) throws IOException; + + /** Writes a byte array. */ + @Override + void write(final byte[] o) throws IOException; + + /** Writes a Boolean. */ + void writeBoolean(final Boolean o) throws IOException; + + /** Writes a Byte. */ + void writeByte(final Byte o) throws IOException; + + /** Writes a Short. */ + void writeShort(final Short o) throws IOException; + + /** Writes a Character. */ + void writeCharacter(final Character o) throws IOException; + + /** Writes a Integer. */ + void writeInteger(final Integer o) throws IOException; + + /** Writes a Long. */ + void writeLong(final Long o) throws IOException; + + /** Writes a Float. */ + void writeFloat(final Float o) throws IOException; + + /** Writes a Double. */ + void writeDouble(final Double o) throws IOException; + + /** Writes a BigInteger. */ + void writeBigInteger(final BigInteger o) throws IOException; + + /** Writes a BigDecimal. */ + void writeBigDecimal(final BigDecimal o) throws IOException; + + /** Writes a String. */ + @Override + void writeUTF(final String o) throws IOException; + + /** Writes a Date out. */ + void writeDate(final Date o) throws IOException; + + /** Writes a ByteBuffer. */ + void writeByteBuffer(final ByteBuffer o) throws IOException; + + /** + * Deduce 31 from the index, so it is more likely to be saved as one byte. + * This methods should be used for normally non-negative integers. + * -1 also stores as one byte. + */ + void writeIndex(final int index) throws IOException; + + /** Writes a slice of a byte[]. */ + @Override + void write(final byte[] o, final int off, final int len) throws IOException; + + /** Writes nil/null. */ + void writeNil() throws IOException; + + /** Writes an array begin. */ + void writeArrayBegin(final int size) throws IOException; + + /** Writes an array end. */ + void writeArrayEnd(final boolean check) throws IOException; + + /** Writes an array end. */ + void writeArrayEnd() throws IOException; + + /** Writes a map begin. */ + void writeMapBegin(final int size) throws IOException; + + /** Writes a map end. */ + void writeMapEnd(final boolean check) throws IOException; + + /** Writes a map end. */ + void writeMapEnd() throws IOException; + + /** Writes an raw begin. */ + void writeRawBegin(final int size) throws IOException; + + /** Writes an raw end. */ + void writeRawEnd() throws IOException; + + /** + * Returns the underlying DataOutput: use with extreme care! + * + * Before using the underlying DataOutput, you must first call + * writeRawBegin(size) (which implies you must already know how many bytes + * you will write. While writing, or after you are done, you must call + * rawWritten(written), so the Packer knows how much you wrote. When you are + * done, you must call writeRawEnd(). If the wrong amount of data was + * written, according to rawWritten(), writeRawEnd() will fail. + * @throws IOException + */ + DataOutput dataOutput() throws IOException; + + /** + * Returns the underlying OutputStream: use with extreme care! + * + * If the underlying DataOutput is an OutputStream, then it will be + * returned. If not, the a wrapper OutputStream on top of the DataOutput + * will be written instead. + * + * @see dataOutput() for usage restrictions. + * + * @throws IOException + */ + OutputStream outputStream() throws IOException; + + /** + * Writes a ByteBuffer *content*, between writeRawBegin(size) and + * writeRawEnd(). It can be used together with dataOutput(). Note that + * writePartial(ByteBuffer) calls rawWritten() directly, while you need to + * do this yourself if using dataOutput(). + * + * This method is just an helper, since DataOutput does not directly + * support ByteBuffers. + */ + void writePartial(final ByteBuffer o) throws IOException; + + /** + * Indicate that 'written' bytes were written to the underlying DataOutput. + * You must call this method, when accessing the underlying DataOutput directly. + * @throws IOException + */ + void rawWritten(final int written) throws IOException; + + /** Writes a byte. */ + @Override + @Deprecated + void writeByte(final int o) throws IOException; + + /** Writes a *byte*. */ + @Override + @Deprecated + void write(final int o) throws IOException; + + /** Writes a short. */ + @Override + @Deprecated + void writeShort(final int o) throws IOException; + + /** Writes a char. */ + @Override + @Deprecated + void writeChar(final int o) throws IOException; + + /** Just delegates to writeUTF(String) */ + @Override + @Deprecated + void writeBytes(final String s) throws IOException; + + /** Just delegates to writeUTF(String) */ + @Override + @Deprecated + void writeChars(final String s) throws IOException; +} diff --git a/src/main/java/com/blockwithme/msgpack/PostDeserListener.java b/src/main/java/com/blockwithme/msgpack/PostDeserListener.java new file mode 100644 index 000000000..3a1e8dfcd --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/PostDeserListener.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2013 Sebastien Diot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blockwithme.msgpack; + +import com.blockwithme.msgpack.templates.UnpackerContext; + +/** + * We assume that if there is anything to be done right before serializing, or + * right after it, the Template can take care of it. Also, the template can + * do whatever is needed right after de-serializing. So what is missing it to + * be informed that the whole de-serialisation is over. + * + * @author monster + */ +public interface PostDeserListener { + /** Called after the graph was de-serialized. */ + void postDeser(final UnpackerContext context); +} diff --git a/src/main/java/com/blockwithme/msgpack/Unpacker.java b/src/main/java/com/blockwithme/msgpack/Unpacker.java new file mode 100644 index 000000000..c57f2c2d9 --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/Unpacker.java @@ -0,0 +1,212 @@ +// +// MessagePack for Java +// +// Copyright (C) 2009 - 2013 FURUHASHI Sadayuki +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package com.blockwithme.msgpack; + +import java.io.Closeable; +import java.io.DataInput; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Date; + +/** + * Standard deserializer, implementing the core Message-Pack protocol. + * + * It also implements DataInput, for easier migration of code, but it is + * unlikely that code using DataInput can be ported without modifications. + */ +public interface Unpacker extends Closeable, DataInput { + + /** Reads a boolean. */ + @Override + boolean readBoolean() throws IOException; + + /** Reads a byte. */ + @Override + byte readByte() throws IOException; + + /** Reads a short. */ + @Override + short readShort() throws IOException; + + /** Reads a char. */ + @Override + char readChar() throws IOException; + + /** Reads an int. */ + @Override + int readInt() throws IOException; + + /** Reads a long. */ + @Override + long readLong() throws IOException; + + /** Reads a float. */ + @Override + float readFloat() throws IOException; + + /** Reads a double. */ + @Override + double readDouble() throws IOException; + + /** Reads a String. */ + @Override + String readUTF() throws IOException; + + /** Reads a BigInteger. */ + BigInteger readBigInteger() throws IOException; + + /** Reads a BigDecimal. */ + BigDecimal readBigDecimal() throws IOException; + + /** Reads a Date. */ + Date readDate() throws IOException; + + /** Reads a byte array. */ + byte[] readByteArray() throws IOException; + + /** Reads a ByteBuffer. */ + ByteBuffer readByteBuffer() throws IOException; + + /** Reads an index written with Packer.writeIndex(int). */ + int readIndex() throws IOException; + + /** Returns the type of the next value to be read. */ + ValueType getNextType() throws IOException; + + /** Set the raw size limit. */ + void setRawSizeLimit(final int size); + + /** Set the array size limit. */ + void setArraySizeLimit(final int size); + + /** Set the map size limit. */ + void setMapSizeLimit(final int size); + + /** Skip the next value. */ + void skip() throws IOException; + + /** Reads an array begin. */ + int readArrayBegin() throws IOException; + + /** Reads an array end. */ + void readArrayEnd(final boolean check) throws IOException; + + /** Reads an array end. */ + void readArrayEnd() throws IOException; + + /** Reads a map begin. */ + int readMapBegin() throws IOException; + + /** Reads a map end. */ + void readMapEnd(final boolean check) throws IOException; + + /** Reads a map end. */ + void readMapEnd() throws IOException; + + /** Reads a nil/null. */ + void readNil() throws IOException; + + /** Returns true, if a nil/null could be read. */ + boolean trySkipNil() throws IOException; + + /** Reads a raw begin and return the size. */ + int readRawBegin() throws IOException; + + /** Reads a raw end. */ + void readRawEnd() throws IOException; + + /** + * Returns the underlying DataInput: use with extreme care! + * + * Before using the underlying DataInput, you must first call readRawBegin() + * which will tell you how many bytes you must read. While reading, or after + * you are done, you must call rawRead(read), so the Unpacker knows how much + * you read. When you are done, you must call readRawEnd(). If the wrong + * amount of data was read, according to rawRead(), readRawEnd() will fail. + * @throws IOException + */ + DataInput dataInput() throws IOException; + + /** + * Returns the underlying InputStream: use with extreme care! + * + * If the underlying DataInput is an InputStream, then it will be + * returned. If not, the a wrapper InputStream on top of the DataInput + * will be written instead. + * + * @see dataInput() for usage restrictions. + * + * @throws IOException + */ + InputStream inputStream() throws IOException; + + /** + * Reads a ByteBuffer *content*, between readRawBegin() and + * readRawEnd(). It can be used together with dataInput(). Note that + * readPartialByteBuffer() calls rawRead() directly, while you need to + * do this yourself if using dataInput(). + * + * This method is just an helper, since DataInput does not directly support + * ByteBuffers. + */ + ByteBuffer readPartialByteBuffer(final int bytes) throws IOException; + + /** + * Indicate that 'read' bytes were read to the underlying DataInput. + * You must call this method, when accessing the underlying DataInput directly. + * @throws IOException + */ + void rawRead(final int read) throws IOException; + + // For DataInput compatibility; not recommended for new code. + + /** readByte() & 0xFF */ + @Override + @Deprecated + int readUnsignedByte() throws IOException; + + /** readShort() & 0xFFFF */ + @Override + @Deprecated + int readUnsignedShort() throws IOException; + + /** Just delegates to readUTF() */ + @Override + @Deprecated + String readLine() throws IOException; + + /** Just delegates to readFully(byte[], 0, byte[].length) */ + @Override + @Deprecated + void readFully(final byte b[]) throws IOException; + + /** Reads a raw into b. The raw must be exactly of length len. */ + @Override + @Deprecated + void readFully(final byte b[], final int off, final int length) + throws IOException; + + /** Skips a raw. n is ignored. The raw size is returned. */ + @Override + @Deprecated + int skipBytes(final int n) throws IOException; + +} diff --git a/src/main/java/org/msgpack/type/ValueType.java b/src/main/java/com/blockwithme/msgpack/ValueType.java similarity index 87% rename from src/main/java/org/msgpack/type/ValueType.java rename to src/main/java/com/blockwithme/msgpack/ValueType.java index 2651567e2..ad111c3a7 100644 --- a/src/main/java/org/msgpack/type/ValueType.java +++ b/src/main/java/com/blockwithme/msgpack/ValueType.java @@ -15,8 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. // -package org.msgpack.type; +package com.blockwithme.msgpack; +/** All the supported data-type of the core Message-Pack protocol. */ public enum ValueType { NIL, BOOLEAN, INTEGER, FLOAT, ARRAY, MAP, RAW; } diff --git a/src/main/java/com/blockwithme/msgpack/impl/AbstractPacker.java b/src/main/java/com/blockwithme/msgpack/impl/AbstractPacker.java new file mode 100644 index 000000000..ef30d66cb --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/impl/AbstractPacker.java @@ -0,0 +1,282 @@ +// +// MessagePack for Java +// +// Copyright (C) 2009 - 2013 FURUHASHI Sadayuki +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package com.blockwithme.msgpack.impl; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Date; + +import com.blockwithme.msgpack.Packer; + +/** + * The Abstract Packer reuses the code form the original Java implementation + * of the Abstract Packer. + * + * It is limited to the core protocol implementation, without support for Java + * objects. + * + * @author monster + */ +public abstract class AbstractPacker implements Packer { + + /** Deduce from index to optimize storage. */ + public static final int INDEX_OFFSET = 31; + + @Override + public void writeBoolean(final Boolean o) throws IOException { + if (o == null) { + writeNil(); + } else { + writeBoolean(o); + } + + } + + @Override + public void writeByte(final Byte o) throws IOException { + if (o == null) { + writeNil(); + } else { + writeByte(o); + } + + } + + @Override + public void writeShort(final Short o) throws IOException { + if (o == null) { + writeNil(); + } else { + writeShort(o); + } + + } + + @Override + public void writeCharacter(final Character o) throws IOException { + if (o == null) { + writeNil(); + } else { + writeChar(o); + } + + } + + @Override + public void writeInteger(final Integer o) throws IOException { + if (o == null) { + writeNil(); + } else { + writeInt(o); + } + + } + + @Override + public void writeLong(final Long o) throws IOException { + if (o == null) { + writeNil(); + } else { + writeLong(o); + } + + } + + @Override + public void writeBigInteger(final BigInteger o) throws IOException { + if (o == null) { + writeNil(); + } else { + writeBigInteger(o, true); + } + + } + + @Override + public void writeBigDecimal(final BigDecimal o) throws IOException { + if (o == null) { + writeNil(); + } else { + writeBigInteger(o.unscaledValue(), false); + writeInt(o.scale()); + } + + } + + @Override + public void writeFloat(final Float o) throws IOException { + if (o == null) { + writeNil(); + } else { + writeFloat(o); + } + + } + + @Override + public void writeDouble(final Double o) throws IOException { + if (o == null) { + writeNil(); + } else { + writeDouble(o); + } + + } + + /** Writes a Date out. */ + @Override + public void writeDate(final Date o) throws IOException { + if (o == null) { + writeNil(); + } else { + writeLong(o.getTime()); + } + + } + + @Override + public void write(final byte[] o) throws IOException { + if (o == null) { + writeNil(); + } else { + writeByteArray(o); + } + + } + + @Override + public void write(final byte[] o, final int off, final int len) + throws IOException { + if (o == null) { + writeNil(); + } else { + writeByteArray(o, off, len); + } + + } + + @Override + public void writeByteBuffer(final ByteBuffer o) throws IOException { + if (o == null) { + writeNil(); + } else { + writeByteBuffer2(o); + } + + } + + @Override + public void writeUTF(final String o) throws IOException { + if (o == null) { + writeNil(); + } else { + writeString(o); + } + + } + + @Override + public void writeArrayEnd() throws IOException { + writeArrayEnd(true); + + } + + @Override + public void writeMapEnd() throws IOException { + writeMapEnd(true); + + } + + /** + * Deduce 31 from the index, so it is more likely to be saved as one byte. + * This methods should be used for normally non-negative integers. + * -1 also stores as one byte. + */ + @Override + public void writeIndex(final int index) throws IOException { + writeInt(index - INDEX_OFFSET); + + } + + /** Writes a byte. */ + @Override + @Deprecated + public void writeByte(final int o) throws IOException { + writeByte((byte) o); + } + + /** Writes a short. */ + @Override + @Deprecated + public void writeShort(final int o) throws IOException { + writeShort((short) o); + } + + /** Writes a char. */ + @Override + @Deprecated + public void writeChar(final int o) throws IOException { + writeChar((char) o); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Packer#writeBytes(java.lang.String) + */ + @Override + @Deprecated + public void writeBytes(final String s) throws IOException { + writeUTF(s); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Packer#writeChars(java.lang.String) + */ + @Override + @Deprecated + public void writeChars(final String s) throws IOException { + writeUTF(s); + } + + /** Writes a *byte*. */ + @Override + @Deprecated + public void write(final int o) throws IOException { + writeByte((byte) o); + } + + @Override + public void close() throws IOException { + } + + abstract protected void writeBigInteger(final BigInteger v, + final boolean countAaValue) throws IOException; + + protected void writeByteArray(final byte[] b) throws IOException { + writeByteArray(b, 0, b.length); + } + + abstract protected void writeByteArray(final byte[] b, final int off, + final int len) throws IOException; + + abstract protected void writeByteBuffer2(final ByteBuffer bb) + throws IOException; + + abstract protected void writeString(final String s) throws IOException; +} diff --git a/src/main/java/com/blockwithme/msgpack/impl/AbstractUnpacker.java b/src/main/java/com/blockwithme/msgpack/impl/AbstractUnpacker.java new file mode 100644 index 000000000..5b396da6c --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/impl/AbstractUnpacker.java @@ -0,0 +1,178 @@ +// +// MessagePack for Java +// +// Copyright (C) 2009 - 2013 FURUHASHI Sadayuki +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package com.blockwithme.msgpack.impl; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Date; + +import com.blockwithme.msgpack.Unpacker; + +/** + * The Abstract Unpacker reuses the code form the original Java implementation + * of the Abstract Unpacker. + * + * It is limited to the core protocol implementation, without support for Java + * objects. + * + * @author monster + */ +public abstract class AbstractUnpacker implements Unpacker { + protected int rawSizeLimit = 134217728; + + protected int arraySizeLimit = 4194304; + + protected int mapSizeLimit = 2097152; + + /** Reads a Date. */ + @Override + public Date readDate() throws IOException { + if (trySkipNil()) { + return null; + } + return new Date(readLong()); + } + + @Override + public ByteBuffer readByteBuffer() throws IOException { + return ByteBuffer.wrap(readByteArray()); + } + + @Override + public void readArrayEnd() throws IOException { + readArrayEnd(false); + } + + @Override + public void readMapEnd() throws IOException { + readMapEnd(false); + } + + @Override + public void setRawSizeLimit(final int size) { + if (size < 32) { + rawSizeLimit = 32; + } else { + rawSizeLimit = size; + } + } + + @Override + public void setArraySizeLimit(final int size) { + if (size < 16) { + arraySizeLimit = 16; + } else { + arraySizeLimit = size; + } + } + + @Override + public void setMapSizeLimit(final int size) { + if (size < 16) { + mapSizeLimit = 16; + } else { + mapSizeLimit = size; + } + } + + @Override + public BigDecimal readBigDecimal() throws IOException { + final BigInteger unscaledVal = readBigInteger(false); + if (unscaledVal == null) { + return null; + } + final int scale = readInt(); + return new BigDecimal(unscaledVal, scale); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readUnsignedByte() + */ + @Override + @Deprecated + public int readUnsignedByte() throws IOException { + return readByte() & 0xFF; + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readUnsignedShort() + */ + @Override + @Deprecated + public int readUnsignedShort() throws IOException { + return readShort() & 0xFFFF; + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readLine() + */ + @Override + @Deprecated + public String readLine() throws IOException { + return readUTF(); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#skipBytes(int) + */ + @Override + @Deprecated + public int skipBytes(final int n) throws IOException { + final byte[] raw = readByteArray(); + return (raw == null) ? -1 : raw.length; + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readFully(byte[]) + */ + @Override + @Deprecated + public void readFully(final byte[] b) throws IOException { + readFully(b, 0, b.length); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readFully(byte[], int, int) + */ + @Override + @Deprecated + public void readFully(final byte[] b, final int off, final int length) + throws IOException { + final byte[] raw = readByteArray(); + if ((raw == null) || (raw.length != length)) { + throw new IOException("Could not read " + length + " bytes raw"); + } + System.arraycopy(raw, 0, b, off, length); + } + + /** Reads an index written with Packer.writeIndex(int). */ + @Override + public int readIndex() throws IOException { + return readInt() + AbstractPacker.INDEX_OFFSET; + } + + /** Checks if we are within a raw read, and fails if not. */ + protected abstract void checkInRawRead() throws IOException; + + protected abstract BigInteger readBigInteger(final boolean countAaValue) + throws IOException; + + protected abstract boolean tryReadNil() throws IOException; +} diff --git a/src/main/java/com/blockwithme/msgpack/impl/ByteArraySlice.java b/src/main/java/com/blockwithme/msgpack/impl/ByteArraySlice.java new file mode 100644 index 000000000..3b06e2f31 --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/impl/ByteArraySlice.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2013 Sebastien Diot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blockwithme.msgpack.impl; + +/** + * Small wrapper object, needed to pass a byte[] slice to a Template as a + * single object. + * + * @author monster + */ +public class ByteArraySlice { + + public final byte[] o; + public final int off; + public final int len; + + /** Creates a byte[] slice */ + public ByteArraySlice(final byte[] o, final int off, final int len) { + this.o = o; + this.off = off; + this.len = len; + } +} diff --git a/src/main/java/com/blockwithme/msgpack/impl/DataInputStreamWrapper.java b/src/main/java/com/blockwithme/msgpack/impl/DataInputStreamWrapper.java new file mode 100644 index 000000000..cb0fb8982 --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/impl/DataInputStreamWrapper.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2013 Sebastien Diot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blockwithme.msgpack.impl; + +import java.io.Closeable; +import java.io.DataInput; +import java.io.IOException; +import java.io.InputStream; +import java.util.Objects; + +/** + * Wraps an DataInput into an InputStream. + * + * @author monster + */ +public class DataInputStreamWrapper extends InputStream { + /** The DataInput */ + private final DataInput dataInput; + + /** Wraps a DataInput */ + public DataInputStreamWrapper(final DataInput dataInput) { + this.dataInput = Objects.requireNonNull(dataInput); + } + + @Override + public int read() throws IOException { + return dataInput.readByte(); + } + + @Override + public int read(final byte b[]) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(final byte b[], final int off, final int len) + throws IOException { + dataInput.readFully(b, off, len); + return len; + } + + @Override + public long skip(final long n) throws IOException { + if (n > Integer.MAX_VALUE) { + return dataInput.skipBytes(Integer.MAX_VALUE); + } + return dataInput.skipBytes((int) n); + } + + @Override + public void close() throws IOException { + if (dataInput instanceof Closeable) { + ((Closeable) dataInput).close(); + } + } +} diff --git a/src/main/java/com/blockwithme/msgpack/impl/DataOutputWrapperStream.java b/src/main/java/com/blockwithme/msgpack/impl/DataOutputWrapperStream.java new file mode 100644 index 000000000..14444806f --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/impl/DataOutputWrapperStream.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2013 Sebastien Diot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blockwithme.msgpack.impl; + +import java.io.Closeable; +import java.io.DataOutput; +import java.io.Flushable; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Objects; + +/** + * DataOutputWrapperStream is an OutputStream that wraps a DataOutput. + * + * @author monster + */ +public class DataOutputWrapperStream extends OutputStream { + + /** The DataOutput */ + private final DataOutput dataOutput; + + /** Wraps a DataOutput. */ + public DataOutputWrapperStream(final DataOutput dataOutput) { + this.dataOutput = Objects.requireNonNull(dataOutput); + } + + @Override + public void write(final int b) throws IOException { + dataOutput.write(b); + } + + @Override + public void write(final byte b[]) throws IOException { + dataOutput.write(b); + } + + @Override + public void write(final byte b[], final int off, final int len) + throws IOException { + dataOutput.write(b, off, len); + } + + @Override + public void flush() throws IOException { + if (dataOutput instanceof Flushable) { + ((Flushable) dataOutput).flush(); + } + } + + @Override + public void close() throws IOException { + if (dataOutput instanceof Closeable) { + ((Closeable) dataOutput).close(); + } + } +} diff --git a/src/main/java/com/blockwithme/msgpack/impl/MessagePackPacker.java b/src/main/java/com/blockwithme/msgpack/impl/MessagePackPacker.java new file mode 100644 index 000000000..fe4ee7184 --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/impl/MessagePackPacker.java @@ -0,0 +1,519 @@ +// +// MessagePack for Java +// +// Copyright (C) 2009 - 2013 FURUHASHI Sadayuki +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +package com.blockwithme.msgpack.impl; + +import java.io.Closeable; +import java.io.DataOutput; +import java.io.Flushable; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Objects; + +/** + * The MessagePack Packer reuses the code form the original Java implementation + * of the MessagePack Packer. + * + * It is limited to the core protocol implementation, without support for Java + * objects. + * + * @author monster + */ +public class MessagePackPacker extends AbstractPacker { + protected final DataOutput out; + protected final OutputStream outputStream; + + /** Amounts of raw bytes still to be written. */ + private int rawToWrite; + + /** True if we are in a raw write. */ + private boolean inRawWrite; + + private final PackerStack stack = new PackerStack(); + + public MessagePackPacker(final DataOutput out) { + this.out = Objects.requireNonNull(out); + if (out instanceof OutputStream) { + outputStream = (OutputStream) out; + } else { + outputStream = new DataOutputWrapperStream(out); + } + } + + @Override + public void writeByte(final byte d) throws IOException { + if (d < -(1 << 5)) { + out.writeByte((byte) 0xd0); + out.writeByte(d); + } else { + out.writeByte(d); + } + stack.reduceCount(); + } + + @Override + public void writeShort(final short d) throws IOException { + if (d < -(1 << 5)) { + if (d < -(1 << 7)) { + // signed 16 + out.writeByte((byte) 0xd1); + out.writeShort(d); + } else { + // signed 8 + out.writeByte((byte) 0xd0); + out.writeByte((byte) d); + } + } else if (d < (1 << 7)) { + // fixnum + out.writeByte((byte) d); + } else { + if (d < (1 << 8)) { + // unsigned 8 + out.writeByte((byte) 0xcc); + out.writeByte((byte) d); + } else { + // unsigned 16 + out.writeByte((byte) 0xcd); + out.writeShort(d); + } + } + stack.reduceCount(); + } + + @Override + public void writeChar(final char d) throws IOException { + if (d < -(1 << 5)) { + if (d < -(1 << 7)) { + // signed 16 + out.writeByte((byte) 0xd1); + out.writeShort((short) d); + } else { + // signed 8 + out.writeByte((byte) 0xd0); + out.writeByte((byte) d); + } + } else if (d < (1 << 7)) { + // fixnum + out.writeByte((byte) d); + } else { + if (d < (1 << 8)) { + // unsigned 8 + out.writeByte((byte) 0xcc); + out.writeByte((byte) d); + } else { + // unsigned 16 + out.writeByte((byte) 0xcd); + out.writeShort((short) d); + } + } + stack.reduceCount(); + } + + @Override + public void writeInt(final int d) throws IOException { + if (d < -(1 << 5)) { + if (d < -(1 << 15)) { + // signed 32 + out.writeByte((byte) 0xd2); + out.writeInt(d); + } else if (d < -(1 << 7)) { + // signed 16 + out.writeByte((byte) 0xd1); + out.writeShort((short) d); + } else { + // signed 8 + out.writeByte((byte) 0xd0); + out.writeByte((byte) d); + } + } else if (d < (1 << 7)) { + // fixnum + out.writeByte((byte) d); + } else { + if (d < (1 << 8)) { + // unsigned 8 + out.writeByte((byte) 0xcc); + out.writeByte((byte) d); + } else if (d < (1 << 16)) { + // unsigned 16 + out.writeByte((byte) 0xcd); + out.writeShort((short) d); + } else { + // unsigned 32 + out.writeByte((byte) 0xce); + out.writeInt(d); + } + } + stack.reduceCount(); + } + + @Override + public void writeLong(final long d) throws IOException { + if (d < -(1L << 5)) { + if (d < -(1L << 15)) { + if (d < -(1L << 31)) { + // signed 64 + out.writeByte((byte) 0xd3); + out.writeLong(d); + } else { + // signed 32 + out.writeByte((byte) 0xd2); + out.writeInt((int) d); + } + } else { + if (d < -(1 << 7)) { + // signed 16 + out.writeByte((byte) 0xd1); + out.writeShort((short) d); + } else { + // signed 8 + out.writeByte((byte) 0xd0); + out.writeByte((byte) d); + } + } + } else if (d < (1 << 7)) { + // fixnum + out.writeByte((byte) d); + } else { + if (d < (1L << 16)) { + if (d < (1 << 8)) { + // unsigned 8 + out.writeByte((byte) 0xcc); + out.writeByte((byte) d); + } else { + // unsigned 16 + out.writeByte((byte) 0xcd); + out.writeShort((short) d); + } + } else { + if (d < (1L << 32)) { + // unsigned 32 + out.writeByte((byte) 0xce); + out.writeInt((int) d); + } else { + // unsigned 64 + out.writeByte((byte) 0xcf); + out.writeLong(d); + } + } + } + stack.reduceCount(); + } + + @Override + protected void writeBigInteger(final BigInteger d, + final boolean countAaValue) throws IOException { + if (d.bitLength() <= 63) { + writeLong(d.longValue()); + if (countAaValue) { + stack.reduceCount(); + } + } else if (d.bitLength() == 64 && d.signum() == 1) { + // unsigned 64 + out.writeByte((byte) 0xcf); + out.writeLong(d.longValue()); + if (countAaValue) { + stack.reduceCount(); + } + } else { + throw new IOException( + "MessagePack can't serialize BigInteger larger than (2^64)-1"); + } + } + + @Override + public void writeFloat(final float d) throws IOException { + out.writeByte((byte) 0xca); + out.writeFloat(d); + stack.reduceCount(); + } + + @Override + public void writeDouble(final double d) throws IOException { + out.writeByte((byte) 0xcb); + out.writeDouble(d); + stack.reduceCount(); + } + + @Override + public void writeBoolean(final boolean d) throws IOException { + if (d) { + // true + out.writeByte((byte) 0xc3); + } else { + // false + out.writeByte((byte) 0xc2); + } + stack.reduceCount(); + } + + @Override + protected void writeByteArray(final byte[] b, final int off, final int len) + throws IOException { + writeRawBegin(len); + out.write(b, off, len); + rawWritten(len); + writeRawEnd(); + } + + /** + * Writes a ByteBuffer *content*, between writeRawBegin(size) and + * writeRawEnd(). It can be used together with dataOutput(). Note that + * writePartial(ByteBuffer) calls rawWritten() directly, while you need to + * do this yourself if using dataOutput(). + */ + @Override + public void writePartial(final ByteBuffer bb) throws IOException { + checkInRawWrite(); + final int len = bb.remaining(); + final int pos = bb.position(); + try { + if (bb.hasArray()) { + final byte[] array = bb.array(); + final int offset = bb.arrayOffset(); + out.write(array, offset, len); + } else { + final byte[] buf = new byte[len]; + bb.get(buf); + out.write(buf); + } + rawWritten(len); + } finally { + bb.position(pos); + } + + } + + @Override + protected void writeByteBuffer2(final ByteBuffer bb) throws IOException { + final int len = bb.remaining(); + writeRawBegin(len); + writePartial(bb); + writeRawEnd(); + } + + @Override + protected void writeString(final String s) throws IOException { + byte[] b; + try { + // TODO encoding error? + b = s.getBytes("UTF-8"); + } catch (final UnsupportedEncodingException ex) { + throw new IOException(ex); + } + writeByteArray(b, 0, b.length); + } + + @Override + public void writeNil() throws IOException { + out.writeByte((byte) 0xc0); + stack.reduceCount(); + + } + + @Override + public void writeArrayBegin(final int size) throws IOException { + // TODO check size < 0? + if (size < 16) { + // FixArray + out.writeByte((byte) (0x90 | size)); + } else if (size < 65536) { + out.writeByte((byte) 0xdc); + out.writeShort((short) size); + } else { + out.writeByte((byte) 0xdd); + out.writeInt(size); + } + stack.reduceCount(); + stack.pushArray(size); + + } + + @Override + public void writeArrayEnd(final boolean check) throws IOException { + if (!stack.topIsArray()) { + throw new IOException( + "writeArrayEnd() is called but writeArrayBegin() is not called"); + } + + final int remain = stack.getTopCount(); + if (remain > 0) { + if (check) { + throw new IOException( + "writeArrayEnd(check=true) is called but the array is not end: " + + remain); + } + for (int i = 0; i < remain; i++) { + writeNil(); + } + } + stack.pop(); + + } + + @Override + public void writeMapBegin(final int size) throws IOException { + // TODO check size < 0? + if (size < 16) { + // FixMap + out.writeByte((byte) (0x80 | size)); + } else if (size < 65536) { + out.writeByte((byte) 0xde); + out.writeShort((short) size); + } else { + out.writeByte((byte) 0xdf); + out.writeInt(size); + } + stack.reduceCount(); + stack.pushMap(size); + + } + + @Override + public void writeMapEnd(final boolean check) throws IOException { + if (!stack.topIsMap()) { + throw new IOException( + "writeMapEnd() is called but writeMapBegin() is not called"); + } + + final int remain = stack.getTopCount(); + if (remain > 0) { + if (check) { + throw new IOException( + "writeMapEnd(check=true) is called but the map is not end: " + + remain); + } + for (int i = 0; i < remain; i++) { + writeNil(); + } + } + stack.pop(); + + } + + /** + * Returns the underlying DataOutput: use with extreme care! + * + * Before using the underlying DataOutput, you must first call + * writeRawBegin(size) (which implies you must already know how many bytes + * you will write. While writing, or after you are done, you must call + * rawWritten(written), so the Packer knows how much you wrote. When you are + * done, you must call writeRawEnd(). If the wrong amount of data was + * written, according to rawWritten(), writeRawEnd() will fail. + * @throws IOException + */ + @Override + public DataOutput dataOutput() throws IOException { + checkInRawWrite(); + return out; + } + + /** + * Returns the underlying OutputStream: use with extreme care! + * + * If the underlying DataOutput is an OutputStream, then it will be + * returned. If not, the a wrapper OutputStream on top of the DataOutput + * will be written instead. + * + * @throws IOException + */ + @Override + public OutputStream outputStream() throws IOException { + checkInRawWrite(); + return outputStream; + } + + /** + * Indicate that 'written' bytes were written to the underlying DataOutput. + * You must call this method, when accessing the underlying DataOutput directly. + * @throws IOException + */ + @Override + public void rawWritten(final int written) throws IOException { + checkInRawWrite(); + rawToWrite -= written; + } + + /** Writes an raw begin. */ + @Override + public void writeRawBegin(final int len) throws IOException { + if (inRawWrite) { + throw new IOException("Last raw write not terminated!"); + } + if (len < 32) { + out.writeByte((byte) (0xa0 | len)); + } else if (len < 65536) { + out.writeByte((byte) 0xda); + out.writeShort((short) len); + } else { + out.writeByte((byte) 0xdb); + out.writeInt(len); + } + rawToWrite = len; + stack.reduceCount(); + stack.pushRaw(); + inRawWrite = true; + } + + /** Writes an raw end. */ + @Override + public void writeRawEnd() throws IOException { + checkInRawWrite(); + // We moved the single raw reduceCOunt() here. + stack.reduceCount(); + if (!stack.topIsRaw()) { + throw new IOException( + "writeRawEnd() is called but writeRawBegin() is not called"); + } + + if (rawToWrite > 0) { + throw new IOException("Last raw write not terminated!"); + } + if (rawToWrite < 0) { + throw new IOException("Last raw write too big!"); + } + stack.pop(); + inRawWrite = false; + } + + public void reset() { + stack.clear(); + } + + @Override + public void flush() throws IOException { + if (out instanceof Flushable) { + ((Flushable) out).flush(); + } + } + + @Override + public void close() throws IOException { + if (out instanceof Closeable) { + ((Closeable) out).close(); + } + } + + /** Checks if we are within a raw write, and fails if not. */ + protected void checkInRawWrite() throws IOException { + if (!inRawWrite) { + throw new IOException("Not in raw write"); + } + } +} diff --git a/src/main/java/org/msgpack/unpacker/MessagePackUnpacker.java b/src/main/java/com/blockwithme/msgpack/impl/MessagePackUnpacker.java similarity index 58% rename from src/main/java/org/msgpack/unpacker/MessagePackUnpacker.java rename to src/main/java/com/blockwithme/msgpack/impl/MessagePackUnpacker.java index f704f1717..93ce14475 100644 --- a/src/main/java/org/msgpack/unpacker/MessagePackUnpacker.java +++ b/src/main/java/com/blockwithme/msgpack/impl/MessagePackUnpacker.java @@ -15,24 +15,43 @@ // See the License for the specific language governing permissions and // limitations under the License. // -package org.msgpack.unpacker; +package com.blockwithme.msgpack.impl; +import java.io.Closeable; +import java.io.DataInput; import java.io.IOException; -import java.io.EOFException; import java.io.InputStream; import java.math.BigInteger; -import org.msgpack.io.Input; -import org.msgpack.io.StreamInput; -import org.msgpack.io.BufferReferer; -import org.msgpack.MessagePack; -import org.msgpack.MessageTypeException; -import org.msgpack.packer.Unconverter; -import org.msgpack.type.ValueType; - +import java.nio.ByteBuffer; +import java.util.Objects; + +import com.blockwithme.msgpack.ValueType; +import com.blockwithme.msgpack.impl.accept.Accept; +import com.blockwithme.msgpack.impl.accept.ArrayAccept; +import com.blockwithme.msgpack.impl.accept.BigIntegerAccept; +import com.blockwithme.msgpack.impl.accept.ByteArrayAccept; +import com.blockwithme.msgpack.impl.accept.DoubleAccept; +import com.blockwithme.msgpack.impl.accept.IntAccept; +import com.blockwithme.msgpack.impl.accept.LongAccept; +import com.blockwithme.msgpack.impl.accept.MapAccept; +import com.blockwithme.msgpack.impl.accept.SkipAccept; +import com.blockwithme.msgpack.impl.accept.StringAccept; +import com.blockwithme.util.DataInputBuffer; + +/** + * The MessagePack Unpacker reuses the code form the original Java implementation + * of the MessagePack Unpacker. + * + * It is limited to the core protocol implementation, without support for Java + * objects. + * + * @author monster + */ public class MessagePackUnpacker extends AbstractUnpacker { private static final byte REQUIRE_TO_READ_HEAD = (byte) 0xc6; - protected final Input in; + private final DataInput in; + private final InputStream inputStream; private final UnpackerStack stack = new UnpackerStack(); private byte headByte = REQUIRE_TO_READ_HEAD; @@ -48,16 +67,27 @@ public class MessagePackUnpacker extends AbstractUnpacker { private final StringAccept stringAccept = new StringAccept(); private final ArrayAccept arrayAccept = new ArrayAccept(); private final MapAccept mapAccept = new MapAccept(); - private final ValueAccept valueAccept = new ValueAccept(); private final SkipAccept skipAccept = new SkipAccept(); - public MessagePackUnpacker(MessagePack msgpack, InputStream stream) { - this(msgpack, new StreamInput(stream)); - } + /** Are we in read raw? */ + private boolean inReadRaw; + + /** Bytes to read in read raw. */ + private int readRawToRead; + + /** Temporary DataInput, used only during raw read. */ + private DataInput tempRawReadDataInput; + + /** Temporary InputStream, used only during raw read. */ + private InputStream tempInputStream; - protected MessagePackUnpacker(MessagePack msgpack, Input in) { - super(msgpack); - this.in = in; + public MessagePackUnpacker(final DataInput in) { + this.in = Objects.requireNonNull(in); + if (in instanceof InputStream) { + inputStream = (InputStream) in; + } else { + inputStream = new DataInputStreamWrapper(in); + } } private byte getHeadByte() throws IOException { @@ -68,14 +98,14 @@ private byte getHeadByte() throws IOException { return b; } - final void readOne(Accept a) throws IOException { + final void readOne(final Accept a) throws IOException { stack.checkCount(); if (readOneWithoutStack(a)) { stack.reduceCount(); } } - final boolean readOneWithoutStack(Accept a) throws IOException { + final boolean readOneWithoutStack(final Accept a) throws IOException { if (raw != null) { readRawBodyCont(); a.acceptRaw(raw); @@ -84,7 +114,7 @@ final boolean readOneWithoutStack(Accept a) throws IOException { return true; } - final int b = (int) getHeadByte(); + final int b = getHeadByte(); if ((b & 0x80) == 0) { // Positive Fixnum // System.out.println("positive fixnum "+b); @@ -101,23 +131,21 @@ final boolean readOneWithoutStack(Accept a) throws IOException { } if ((b & 0xe0) == 0xa0) { // FixRaw - int count = b & 0x1f; + final int count = b & 0x1f; if (count == 0) { a.acceptEmptyRaw(); headByte = REQUIRE_TO_READ_HEAD; return true; } - if (!tryReferRawBody(a, count)) { - readRawBody(count); - a.acceptRaw(raw); - raw = null; - } + readRawBody(count); + a.acceptRaw(raw); + raw = null; headByte = REQUIRE_TO_READ_HEAD; return true; } if ((b & 0xf0) == 0x90) { // FixArray - int count = b & 0x0f; + final int count = b & 0x0f; // System.out.println("fixarray count:"+count); a.acceptArray(count); stack.reduceCount(); @@ -127,7 +155,7 @@ final boolean readOneWithoutStack(Accept a) throws IOException { } if ((b & 0xf0) == 0x80) { // FixMap - int count = b & 0x0f; + final int count = b & 0x0f; // System.out.println("fixmap count:"+count/2); a.acceptMap(count); stack.reduceCount(); @@ -139,7 +167,7 @@ final boolean readOneWithoutStack(Accept a) throws IOException { return readOneWithoutStackLarge(a, b); } - private boolean readOneWithoutStackLarge(Accept a, final int b) + private boolean readOneWithoutStackLarge(final Accept a, final int b) throws IOException { switch (b & 0xff) { case 0xc0: // nil @@ -155,164 +183,142 @@ private boolean readOneWithoutStackLarge(Accept a, final int b) headByte = REQUIRE_TO_READ_HEAD; return true; case 0xca: // float - a.acceptFloat(in.getFloat()); - in.advance(); + a.acceptFloat(in.readFloat()); headByte = REQUIRE_TO_READ_HEAD; return true; case 0xcb: // double - a.acceptDouble(in.getDouble()); - in.advance(); + a.acceptDouble(in.readDouble()); headByte = REQUIRE_TO_READ_HEAD; return true; case 0xcc: // unsigned int 8 - a.acceptUnsignedInteger(in.getByte()); - in.advance(); + a.acceptUnsignedInteger(in.readByte()); headByte = REQUIRE_TO_READ_HEAD; return true; case 0xcd: // unsigned int 16 - a.acceptUnsignedInteger(in.getShort()); - in.advance(); + a.acceptUnsignedInteger(in.readShort()); headByte = REQUIRE_TO_READ_HEAD; return true; case 0xce: // unsigned int 32 - a.acceptUnsignedInteger(in.getInt()); - in.advance(); + a.acceptUnsignedInteger(in.readInt()); headByte = REQUIRE_TO_READ_HEAD; return true; case 0xcf: // unsigned int 64 - a.acceptUnsignedInteger(in.getLong()); - in.advance(); + a.acceptUnsignedInteger(in.readLong()); headByte = REQUIRE_TO_READ_HEAD; return true; case 0xd0: // signed int 8 - a.acceptInteger(in.getByte()); - in.advance(); + a.acceptInteger(in.readByte()); headByte = REQUIRE_TO_READ_HEAD; return true; case 0xd1: // signed int 16 - a.acceptInteger(in.getShort()); - in.advance(); + a.acceptInteger(in.readShort()); headByte = REQUIRE_TO_READ_HEAD; return true; case 0xd2: // signed int 32 - a.acceptInteger(in.getInt()); - in.advance(); + a.acceptInteger(in.readInt()); headByte = REQUIRE_TO_READ_HEAD; return true; case 0xd3: // signed int 64 - a.acceptInteger(in.getLong()); - in.advance(); + a.acceptInteger(in.readLong()); headByte = REQUIRE_TO_READ_HEAD; return true; case 0xda: // raw 16 { - int count = in.getShort() & 0xffff; + final int count = in.readShort() & 0xffff; if (count == 0) { a.acceptEmptyRaw(); - in.advance(); headByte = REQUIRE_TO_READ_HEAD; return true; } if (count >= rawSizeLimit) { - String reason = String.format( - "Size of raw (%d) over limit at %d", - new Object[] { count, rawSizeLimit }); - throw new SizeLimitException(reason); - } - in.advance(); - if (!tryReferRawBody(a, count)) { - readRawBody(count); - a.acceptRaw(raw); - raw = null; + final String reason = String.format( + "Size of raw (%d) over limit at %d", new Object[] { + count, rawSizeLimit }); + throw new IOException(reason); } + readRawBody(count); + a.acceptRaw(raw); + raw = null; headByte = REQUIRE_TO_READ_HEAD; return true; } case 0xdb: // raw 32 { - int count = in.getInt(); + final int count = in.readInt(); if (count == 0) { a.acceptEmptyRaw(); - in.advance(); headByte = REQUIRE_TO_READ_HEAD; return true; } if (count < 0 || count >= rawSizeLimit) { - String reason = String.format( - "Size of raw (%d) over limit at %d", - new Object[] { count, rawSizeLimit }); - throw new SizeLimitException(reason); - } - in.advance(); - if (!tryReferRawBody(a, count)) { - readRawBody(count); - a.acceptRaw(raw); - raw = null; + final String reason = String.format( + "Size of raw (%d) over limit at %d", new Object[] { + count, rawSizeLimit }); + throw new IOException(reason); } + readRawBody(count); + a.acceptRaw(raw); + raw = null; headByte = REQUIRE_TO_READ_HEAD; return true; } case 0xdc: // array 16 { - int count = in.getShort() & 0xffff; + final int count = in.readShort() & 0xffff; if (count >= arraySizeLimit) { - String reason = String.format( - "Size of array (%d) over limit at %d", - new Object[] { count, arraySizeLimit }); - throw new SizeLimitException(reason); + final String reason = String.format( + "Size of array (%d) over limit at %d", new Object[] { + count, arraySizeLimit }); + throw new IOException(reason); } a.acceptArray(count); stack.reduceCount(); stack.pushArray(count); - in.advance(); headByte = REQUIRE_TO_READ_HEAD; return false; } case 0xdd: // array 32 { - int count = in.getInt(); + final int count = in.readInt(); if (count < 0 || count >= arraySizeLimit) { - String reason = String.format( - "Size of array (%d) over limit at %d", - new Object[] { count, arraySizeLimit }); - throw new SizeLimitException(reason); + final String reason = String.format( + "Size of array (%d) over limit at %d", new Object[] { + count, arraySizeLimit }); + throw new IOException(reason); } a.acceptArray(count); stack.reduceCount(); stack.pushArray(count); - in.advance(); headByte = REQUIRE_TO_READ_HEAD; return false; } case 0xde: // map 16 { - int count = in.getShort() & 0xffff; + final int count = in.readShort() & 0xffff; if (count >= mapSizeLimit) { - String reason = String.format( - "Size of map (%d) over limit at %d", - new Object[] { count, mapSizeLimit }); - throw new SizeLimitException(reason); + final String reason = String.format( + "Size of map (%d) over limit at %d", new Object[] { + count, mapSizeLimit }); + throw new IOException(reason); } a.acceptMap(count); stack.reduceCount(); stack.pushMap(count); - in.advance(); headByte = REQUIRE_TO_READ_HEAD; return false; } case 0xdf: // map 32 { - int count = in.getInt(); + final int count = in.readInt(); if (count < 0 || count >= mapSizeLimit) { - String reason = String.format( - "Size of map (%d) over limit at %d", - new Object[] { count, mapSizeLimit }); - throw new SizeLimitException(reason); + final String reason = String.format( + "Size of map (%d) over limit at %d", new Object[] { + count, mapSizeLimit }); + throw new IOException(reason); } a.acceptMap(count); stack.reduceCount(); stack.pushMap(count); - in.advance(); headByte = REQUIRE_TO_READ_HEAD; return false; } @@ -324,28 +330,22 @@ private boolean readOneWithoutStackLarge(Accept a, final int b) } } - private boolean tryReferRawBody(BufferReferer referer, int size) throws IOException { - return in.tryRefer(referer, size); - } - - private void readRawBody(int size) throws IOException { + private void readRawBody(final int size) throws IOException { raw = new byte[size]; rawFilled = 0; readRawBodyCont(); } private void readRawBodyCont() throws IOException { - int len = in.read(raw, rawFilled, raw.length - rawFilled); + final int len = raw.length - rawFilled; + in.readFully(raw, rawFilled, len); rawFilled += len; - if (rawFilled < raw.length) { - throw new EOFException(); - } } @Override protected boolean tryReadNil() throws IOException { stack.checkCount(); - int b = getHeadByte() & 0xff; + final int b = getHeadByte() & 0xff; if (b == 0xc0) { // nil is read stack.reduceCount(); @@ -363,7 +363,7 @@ public boolean trySkipNil() throws IOException { return true; } - int b = getHeadByte() & 0xff; + final int b = getHeadByte() & 0xff; if (b == 0xc0) { // nil is skipped stack.reduceCount(); @@ -378,20 +378,20 @@ public boolean trySkipNil() throws IOException { public void readNil() throws IOException { // optimized not to allocate nilAccept stack.checkCount(); - int b = getHeadByte() & 0xff; + final int b = getHeadByte() & 0xff; if (b == 0xc0) { stack.reduceCount(); headByte = REQUIRE_TO_READ_HEAD; return; } - throw new MessageTypeException("Expected nil but got not nil value"); + throw new IOException("Expected nil but got not nil value"); } @Override public boolean readBoolean() throws IOException { // optimized not to allocate booleanAccept stack.checkCount(); - int b = getHeadByte() & 0xff; + final int b = getHeadByte() & 0xff; if (b == 0xc2) { stack.reduceCount(); headByte = REQUIRE_TO_READ_HEAD; @@ -401,8 +401,7 @@ public boolean readBoolean() throws IOException { headByte = REQUIRE_TO_READ_HEAD; return true; } - throw new MessageTypeException( - "Expected Boolean but got not boolean value"); + throw new IOException("Expected Boolean but got not boolean value"); } @Override @@ -410,9 +409,9 @@ public byte readByte() throws IOException { // optimized not to allocate byteAccept stack.checkCount(); readOneWithoutStack(intAccept); - int value = intAccept.value; - if (value < (int) Byte.MIN_VALUE || value > (int) Byte.MAX_VALUE) { - throw new MessageTypeException(); // TODO message + final int value = intAccept.value; + if (value < Byte.MIN_VALUE || value > Byte.MAX_VALUE) { + throw new IOException(); // TODO message } stack.reduceCount(); return (byte) value; @@ -423,14 +422,27 @@ public short readShort() throws IOException { // optimized not to allocate shortAccept stack.checkCount(); readOneWithoutStack(intAccept); - int value = intAccept.value; - if (value < (int) Short.MIN_VALUE || value > (int) Short.MAX_VALUE) { - throw new MessageTypeException(); // TODO message + final int value = intAccept.value; + if (value < Short.MIN_VALUE || value > Short.MAX_VALUE) { + throw new IOException(); // TODO message } stack.reduceCount(); return (short) value; } + @Override + public char readChar() throws IOException { + // optimized not to allocate shortAccept + stack.checkCount(); + readOneWithoutStack(intAccept); + final int value = intAccept.value; + if (value < Character.MIN_VALUE || value > Character.MAX_VALUE) { + throw new IOException(); // TODO message + } + stack.reduceCount(); + return (char) value; + } + @Override public int readInt() throws IOException { readOne(intAccept); @@ -449,6 +461,17 @@ public BigInteger readBigInteger() throws IOException { return bigIntegerAccept.value; } + @Override + protected BigInteger readBigInteger(final boolean countAsValue) + throws IOException { + readOne(bigIntegerAccept); + final BigInteger result = bigIntegerAccept.value; + if ((result != null) && !countAsValue) { + stack.raiseCount(); + } + return result; + } + @Override public float readFloat() throws IOException { readOne(doubleAccept); @@ -468,7 +491,7 @@ public byte[] readByteArray() throws IOException { } @Override - public String readString() throws IOException { + public String readUTF() throws IOException { readOne(stringAccept); return stringAccept.value; } @@ -480,16 +503,16 @@ public int readArrayBegin() throws IOException { } @Override - public void readArrayEnd(boolean check) throws IOException { + public void readArrayEnd(final boolean check) throws IOException { if (!stack.topIsArray()) { - throw new MessageTypeException( + throw new IOException( "readArrayEnd() is called but readArrayBegin() is not called"); } - int remain = stack.getTopCount(); + final int remain = stack.getTopCount(); if (remain > 0) { if (check) { - throw new MessageTypeException( + throw new IOException( "readArrayEnd(check=true) is called but the array is not end"); } for (int i = 0; i < remain; i++) { @@ -506,16 +529,16 @@ public int readMapBegin() throws IOException { } @Override - public void readMapEnd(boolean check) throws IOException { + public void readMapEnd(final boolean check) throws IOException { if (!stack.topIsMap()) { - throw new MessageTypeException( + throw new IOException( "readMapEnd() is called but readMapBegin() is not called"); } - int remain = stack.getTopCount(); + final int remain = stack.getTopCount(); if (remain > 0) { if (check) { - throw new MessageTypeException( + throw new IOException( "readMapEnd(check=true) is called but the map is not end"); } for (int i = 0; i < remain; i++) { @@ -525,41 +548,6 @@ public void readMapEnd(boolean check) throws IOException { stack.pop(); } - @Override - protected void readValue(Unconverter uc) throws IOException { - if (uc.getResult() != null) { - uc.resetResult(); - } - valueAccept.setUnconverter(uc); - - stack.checkCount(); - if (readOneWithoutStack(valueAccept)) { - stack.reduceCount(); - if (uc.getResult() != null) { - return; - } - } - while (true) { - while (stack.getTopCount() == 0) { - if (stack.topIsArray()) { - uc.writeArrayEnd(true); - stack.pop(); - // stack.reduceCount(); - } else if (stack.topIsMap()) { - uc.writeMapEnd(true); - stack.pop(); - // stack.reduceCount(); - } else { - throw new RuntimeException("invalid stack"); // FIXME error? - } - if (uc.getResult() != null) { - return; - } - } - readOne(valueAccept); - } - } - @Override public void skip() throws IOException { stack.checkCount(); @@ -567,7 +555,7 @@ public void skip() throws IOException { stack.reduceCount(); return; } - int targetDepth = stack.getDepth() - 1; + final int targetDepth = stack.getDepth() - 1; while (true) { while (stack.getTopCount() == 0) { stack.pop(); @@ -579,8 +567,9 @@ public void skip() throws IOException { } } + @Override public ValueType getNextType() throws IOException { - final int b = (int) getHeadByte(); + final int b = getHeadByte(); if ((b & 0x80) == 0) { // Positive Fixnum return ValueType.INTEGER; } @@ -633,17 +622,111 @@ public void reset() { stack.clear(); } + @Override public void close() throws IOException { - in.close(); + if (in instanceof Closeable) { + ((Closeable) in).close(); + } + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#dataInput() + */ + @Override + public DataInput dataInput() throws IOException { + checkInRawRead(); + if (inReadRaw) { + if (tempRawReadDataInput == null) { + tempRawReadDataInput = new DataInputBuffer( + byteArrayAccept.value); + } + return tempRawReadDataInput; + } + return in; + } + + /** + * Returns the underlying InputStream: use with extreme care! + * + * If the underlying DataInput is an InputStream, then it will be + * returned. If not, the a wrapper InputStream on top of the DataInput + * will be written instead. + * + * @see dataInput() for usage restrictions. + * + * @throws IOException + */ + @Override + public InputStream inputStream() throws IOException { + if (inReadRaw) { + if (tempInputStream == null) { + tempInputStream = new DataInputStreamWrapper(dataInput()); + } + return tempInputStream; + } + checkInRawRead(); + return inputStream; + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readRawBegin() + */ + @Override + public int readRawBegin() throws IOException { + if (inReadRaw) { + throw new IOException("Already in read raw!"); + } + readOne(byteArrayAccept); + inReadRaw = true; + return (readRawToRead = byteArrayAccept.value.length); } + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readRawEnd() + */ @Override - public int getReadByteCount() { - return in.getReadByteCount(); + public void readRawEnd() throws IOException { + checkInRawRead(); + if (readRawToRead != 0) { + throw new IOException("Wrong number of bytes read"); + } + inReadRaw = false; + tempRawReadDataInput = null; + tempInputStream = null; } + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#rawRead(int) + */ @Override - public void resetReadByteCount() { - in.resetReadByteCount(); + public void rawRead(final int read) throws IOException { + checkInRawRead(); + readRawToRead -= read; + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readPartialByteBuffer(int) + */ + @Override + public ByteBuffer readPartialByteBuffer(final int bytes) throws IOException { + checkInRawRead(); + if ((readRawToRead == bytes) + && (readRawToRead == byteArrayAccept.value.length)) { + readRawToRead = 0; + return ByteBuffer.wrap(byteArrayAccept.value); + } + rawRead(bytes); + final int offset = byteArrayAccept.value.length - readRawToRead; + return ByteBuffer.wrap(byteArrayAccept.value, offset, bytes); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.impl.AbstractUnpacker#checkInRawRead() + */ + @Override + protected void checkInRawRead() throws IOException { + if (!inReadRaw) { + throw new IOException("Not in read raw"); + } } } diff --git a/src/main/java/com/blockwithme/msgpack/impl/ObjectPackerImpl.java b/src/main/java/com/blockwithme/msgpack/impl/ObjectPackerImpl.java new file mode 100644 index 000000000..abbf5711c --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/impl/ObjectPackerImpl.java @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2013 Sebastien Diot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blockwithme.msgpack.impl; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Date; +import java.util.Objects; + +import com.blockwithme.msgpack.ObjectPacker; +import com.blockwithme.msgpack.Packer; +import com.blockwithme.msgpack.schema.Schema; +import com.blockwithme.msgpack.templates.AbstractTemplate; +import com.blockwithme.msgpack.templates.BasicTemplates; +import com.blockwithme.msgpack.templates.PackerContext; +import com.blockwithme.msgpack.templates.Template; + +/** + * ObjectPacker implementation. All the hard work is done in: + * + * AbstractTemplate.writeObject(PackerContext, Object, Template); + * + * @author monster + */ +public class ObjectPackerImpl implements ObjectPacker { + + /** The real Packer. */ + protected final Packer packer; + + /** The context */ + protected final PackerContext context; + + /** The BasicTemplates */ + protected final BasicTemplates basicTemplates; + + /** + * Creates an ObjectPackerImpl + * + * @param packer + * @throws IOException + */ + public ObjectPackerImpl(final Packer packer, final PackerContext context) + throws IOException { + this.packer = Objects.requireNonNull(packer); + this.context = Objects.requireNonNull(context); + context.packer = packer; + context.objectPacker = this; + final Schema schema = context.getSchema(); + basicTemplates = schema.basicTemplates; + packer.writeIndex(schema.format); + packer.writeIndex(schema.schema); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#packer() + */ + @Override + public Packer packer() { + return packer; + } + + /** Writes an Object out. Object must be compatible with template. */ + @Override + public ObjectPacker writeObject(final Object o, + @SuppressWarnings("rawtypes") final Template template, + final boolean ifObjectArrayCanContainNullValue) throws IOException { + AbstractTemplate.writeObject(context, o, template, + ifObjectArrayCanContainNullValue); + return this; + } + + /** + * Writes an Object out. Template is determined based on object type. + * Pass along a template for faster results. + */ + @Override + public ObjectPacker writeObject(final Object o, + final boolean ifObjectArrayCanContainNullValue) throws IOException { + return writeObject(o, null, ifObjectArrayCanContainNullValue); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.OwriteObject(acker#writeObject(java.lang.Class) + */ + @Override + public ObjectPacker writeObject(final Class o) throws IOException { + return writeObject(o, basicTemplates.CLASS); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(java.lang.Boolean) + */ + @Override + public ObjectPacker writeObject(final Boolean o) throws IOException { + return writeObject(o, basicTemplates.BOOLEAN); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(java.lang.Byte) + */ + @Override + public ObjectPacker writeObject(final Byte o) throws IOException { + return writeObject(o, basicTemplates.BYTE); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(java.lang.Short) + */ + @Override + public ObjectPacker writeObject(final Short o) throws IOException { + return writeObject(o, basicTemplates.SHORT); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(java.lang.Character) + */ + @Override + public ObjectPacker writeObject(final Character o) throws IOException { + return writeObject(o, basicTemplates.CHARACTER); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(java.lang.Integer) + */ + @Override + public ObjectPacker writeObject(final Integer o) throws IOException { + return writeObject(o, basicTemplates.INTEGER); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(java.lang.Long) + */ + @Override + public ObjectPacker writeObject(final Long o) throws IOException { + return writeObject(o, basicTemplates.LONG); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(java.lang.Float) + */ + @Override + public ObjectPacker writeObject(final Float o) throws IOException { + return writeObject(o, basicTemplates.FLOAT); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(java.lang.Double) + */ + @Override + public ObjectPacker writeObject(final Double o) throws IOException { + return writeObject(o, basicTemplates.DOUBLE); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(java.math.BigInteger) + */ + @Override + public ObjectPacker writeObject(final BigInteger o) throws IOException { + return writeObject(o, basicTemplates.BIG_INTEGER); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(java.math.BigDecimal) + */ + @Override + public ObjectPacker writeObject(final BigDecimal o) throws IOException { + return writeObject(o, basicTemplates.BIG_DECIMAL); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(java.lang.String) + */ + @Override + public ObjectPacker writeObject(final String o) throws IOException { + return writeObject(o, basicTemplates.STRING); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(boolean[]) + */ + @Override + public ObjectPacker writeObject(final boolean[] o) throws IOException { + return writeObject(o, basicTemplates.BOOLEAN_ARRAY); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(byte[]) + */ + @Override + public ObjectPacker writeObject(final byte[] o) throws IOException { + return writeObject(o, basicTemplates.BYTE_ARRAY); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(short[]) + */ + @Override + public ObjectPacker writeObject(final short[] o) throws IOException { + return writeObject(o, basicTemplates.SHORT_ARRAY); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(char[]) + */ + @Override + public ObjectPacker writeObject(final char[] o) throws IOException { + return writeObject(o, basicTemplates.CHAR_ARRAY); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(int[]) + */ + @Override + public ObjectPacker writeObject(final int[] o) throws IOException { + return writeObject(o, basicTemplates.INT_ARRAY); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(long[]) + */ + @Override + public ObjectPacker writeObject(final long[] o) throws IOException { + return writeObject(o, basicTemplates.LONG_ARRAY); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(float[]) + */ + @Override + public ObjectPacker writeObject(final float[] o) throws IOException { + return writeObject(o, basicTemplates.FLOAT_ARRAY); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(double[]) + */ + @Override + public ObjectPacker writeObject(final double[] o) throws IOException { + return writeObject(o, basicTemplates.DOUBLE_ARRAY); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(java.util.Date) + */ + @Override + public ObjectPacker writeObject(final Date o) throws IOException { + return writeObject(o, basicTemplates.DATE); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(java.nio.ByteBuffer) + */ + @Override + public ObjectPacker writeObject(final ByteBuffer o) throws IOException { + return writeObject(o, basicTemplates.BYTE_BUFFER); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(byte[], int, int) + */ + @Override + public ObjectPacker writeObject(final byte[] o, final int off, final int len) + throws IOException { + return writeObject(new ByteArraySlice(o, off, len), + basicTemplates.BYTE_ARRAY_SLICE); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(java.lang.Object) + */ + @Override + public ObjectPacker writeObject(final Object o) throws IOException { + return writeObject(o, true); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectPacker#writeObject(java.lang.Object, com.blockwithme.msgpack.templates.Template) + */ + @Override + public ObjectPacker writeObject(final Object o, final Template template) + throws IOException { + return writeObject(o, template, true); + } +} diff --git a/src/main/java/com/blockwithme/msgpack/impl/ObjectUnpackerImpl.java b/src/main/java/com/blockwithme/msgpack/impl/ObjectUnpackerImpl.java new file mode 100644 index 000000000..788b9c02a --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/impl/ObjectUnpackerImpl.java @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2013 Sebastien Diot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blockwithme.msgpack.impl; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Date; +import java.util.Objects; + +import com.blockwithme.msgpack.ObjectUnpacker; +import com.blockwithme.msgpack.PostDeserListener; +import com.blockwithme.msgpack.Unpacker; +import com.blockwithme.msgpack.schema.Schema; +import com.blockwithme.msgpack.templates.AbstractTemplate; +import com.blockwithme.msgpack.templates.BasicTemplates; +import com.blockwithme.msgpack.templates.Template; +import com.blockwithme.msgpack.templates.UnpackerContext; + +/** + * The ObjectUnpacker implementation. All the hard work happens in: + * + * AbstractTemplate.readObject(UnpackerContext, Template); + * + * @author monster + */ +public class ObjectUnpackerImpl implements ObjectUnpacker { + + /** The real Unpacker */ + protected final Unpacker unpacker; + + /** The context */ + protected final UnpackerContext context; + + /** The BasicTemplates */ + protected final BasicTemplates basicTemplates; + + /** + * Creates a new ObjectUnpackerImpl. + * @param unpacker + * @throws IOException + */ + public ObjectUnpackerImpl(final Unpacker unpacker, + final UnpackerContext context) throws IOException { + this.unpacker = Objects.requireNonNull(unpacker); + this.context = Objects.requireNonNull(context); + context.unpacker = unpacker; + context.objectUnpacker = this; + context.format = unpacker.readIndex(); + context.schemaID = unpacker.readIndex(); + final Schema schema = context.getSchema(); + basicTemplates = schema.basicTemplates; + } + + /** Returns the underlying unpacker, if any, otherwise self. */ + @Override + public Unpacker unpacker() { + return unpacker; + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readBoolean() + */ + @Override + public Boolean readBoolean() throws IOException { + return (Boolean) readObject(basicTemplates.BOOLEAN); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readByte() + */ + @Override + public Byte readByte() throws IOException { + return (Byte) readObject(basicTemplates.BYTE); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readShort() + */ + @Override + public Short readShort() throws IOException { + return (Short) readObject(basicTemplates.SHORT); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readChar() + */ + @Override + public Character readChar() throws IOException { + return (Character) readObject(basicTemplates.CHARACTER); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readInt() + */ + @Override + public Integer readInt() throws IOException { + return (Integer) readObject(basicTemplates.INTEGER); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readLong() + */ + @Override + public Long readLong() throws IOException { + return (Long) readObject(basicTemplates.LONG); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readFloat() + */ + @Override + public Float readFloat() throws IOException { + return (Float) readObject(basicTemplates.FLOAT); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readDouble() + */ + @Override + public Double readDouble() throws IOException { + return (Double) readObject(basicTemplates.DOUBLE); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readBigInteger() + */ + @Override + public BigInteger readBigInteger() throws IOException { + return (BigInteger) readObject(basicTemplates.BIG_INTEGER); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readBigDecimal() + */ + @Override + public BigDecimal readBigDecimal() throws IOException { + return (BigDecimal) readObject(basicTemplates.BIG_DECIMAL); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readBooleanArray() + */ + @Override + public boolean[] readBooleanArray() throws IOException { + return (boolean[]) readObject(basicTemplates.BOOLEAN_ARRAY); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readByteArray() + */ + @Override + public byte[] readByteArray() throws IOException { + return (byte[]) readObject(basicTemplates.BYTE_ARRAY); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readShortArray() + */ + @Override + public short[] readShortArray() throws IOException { + return (short[]) readObject(basicTemplates.SHORT_ARRAY); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readCharArray() + */ + @Override + public char[] readCharArray() throws IOException { + return (char[]) readObject(basicTemplates.CHAR_ARRAY); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readIntArray() + */ + @Override + public int[] readIntArray() throws IOException { + return (int[]) readObject(basicTemplates.INT_ARRAY); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readLongArray() + */ + @Override + public long[] readLongArray() throws IOException { + return (long[]) readObject(basicTemplates.LONG_ARRAY); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readFloatArray() + */ + @Override + public float[] readFloatArray() throws IOException { + return (float[]) readObject(basicTemplates.FLOAT_ARRAY); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readDoubleArray() + */ + @Override + public double[] readDoubleArray() throws IOException { + return (double[]) readObject(basicTemplates.DOUBLE_ARRAY); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readString() + */ + @Override + public String readString() throws IOException { + return (String) readObject(basicTemplates.STRING); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.Unpacker#readByteBuffer() + */ + @Override + public ByteBuffer readByteBuffer() throws IOException { + return (ByteBuffer) readObject(basicTemplates.BYTE_BUFFER); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.ObjectUnpacker#readDate() + */ + @Override + public Date readDate() throws IOException { + return (Date) readObject(basicTemplates.DATE); + } + + @Override + public Class readClass() throws IOException { + return (Class) readObject(basicTemplates.CLASS); + } + + /** Reads any Object. */ + @Override + public Object readObject() throws IOException { + return AbstractTemplate.readObject(context, null, true); + } + + /** Reads any Object. Fails if Object type does not match template type. */ + @Override + public Object readObject(final Template template) throws IOException { + return AbstractTemplate.readObject(context, template, true); + } + + /** Reads any Object. */ + @Override + public Object readObject(final boolean ifObjectArrayCanContainNullValue) + throws IOException { + return AbstractTemplate.readObject(context, null, + ifObjectArrayCanContainNullValue); + } + + /** Reads any Object. Fails if Object type does not match template type. */ + @Override + public Object readObject(final Template template, + final boolean ifObjectArrayCanContainNullValue) throws IOException { + return AbstractTemplate.readObject(context, template, + ifObjectArrayCanContainNullValue); + } + + /* (non-Javadoc) + * @see java.io.Closeable#close() + */ + @Override + public void close() throws IOException { + for (final Object o : context.previous) { + if (o instanceof PostDeserListener) { + final PostDeserListener pd = (PostDeserListener) o; + pd.postDeser(context); + } + } + } +} diff --git a/src/main/java/org/msgpack/packer/PackerStack.java b/src/main/java/com/blockwithme/msgpack/impl/PackerStack.java similarity index 73% rename from src/main/java/org/msgpack/packer/PackerStack.java rename to src/main/java/com/blockwithme/msgpack/impl/PackerStack.java index 9114ebac2..b3c435140 100644 --- a/src/main/java/org/msgpack/packer/PackerStack.java +++ b/src/main/java/com/blockwithme/msgpack/impl/PackerStack.java @@ -15,20 +15,25 @@ // See the License for the specific language governing permissions and // limitations under the License. // -package org.msgpack.packer; +package com.blockwithme.msgpack.impl; -import org.msgpack.MessageTypeException; +import com.blockwithme.msgpack.MessageTypeException; +/** + * The Packer stack keeps track of what is currently being packed, to help + * detect inconsistencies. + */ public final class PackerStack { private int top; - private byte[] types; - private int[] counts; + private final byte[] types; + private final int[] counts; - public static final int MAX_STACK_SIZE = 128; + public static final int MAX_STACK_SIZE = 256; private static final byte TYPE_INVALID = 0; private static final byte TYPE_ARRAY = 1; private static final byte TYPE_MAP = 2; + private static final byte TYPE_RAW = 3; public PackerStack() { this.top = 0; @@ -37,18 +42,24 @@ public PackerStack() { this.types[0] = TYPE_INVALID; } - public void pushArray(int size) { + public void pushArray(final int size) { top++; types[top] = TYPE_ARRAY; counts[top] = size; } - public void pushMap(int size) { + public void pushMap(final int size) { top++; types[top] = TYPE_MAP; counts[top] = size * 2; } + public void pushRaw() { + top++; + types[top] = TYPE_RAW; + counts[top] = 1; + } + public void checkCount() { if (counts[top] > 0) { return; @@ -62,6 +73,10 @@ public void checkCount() { throw new MessageTypeException( "Map is end but writeMapEnd() is not called"); + } else if (types[top] == TYPE_RAW) { + throw new MessageTypeException( + "Raw is end but writeRawEnd() is not called"); + } else { // empty return; @@ -92,6 +107,10 @@ public boolean topIsMap() { return types[top] == TYPE_MAP; } + public boolean topIsRaw() { + return types[top] == TYPE_RAW; + } + public void clear() { top = 0; } diff --git a/src/main/java/org/msgpack/unpacker/UnpackerStack.java b/src/main/java/com/blockwithme/msgpack/impl/UnpackerStack.java similarity index 82% rename from src/main/java/org/msgpack/unpacker/UnpackerStack.java rename to src/main/java/com/blockwithme/msgpack/impl/UnpackerStack.java index 9ed67afff..8b5a57e7f 100644 --- a/src/main/java/org/msgpack/unpacker/UnpackerStack.java +++ b/src/main/java/com/blockwithme/msgpack/impl/UnpackerStack.java @@ -15,16 +15,20 @@ // See the License for the specific language governing permissions and // limitations under the License. // -package org.msgpack.unpacker; +package com.blockwithme.msgpack.impl; -import org.msgpack.MessageTypeException; +import com.blockwithme.msgpack.MessageTypeException; +/** + * The Unpacker stack keeps track of what is currently being unpacked, to help + * detect inconsistencies. + */ public final class UnpackerStack { private int top; - private byte[] types; - private int[] counts; + private final byte[] types; + private final int[] counts; - public static final int MAX_STACK_SIZE = 128; + public static final int MAX_STACK_SIZE = 256; private static final byte TYPE_INVALID = 0; private static final byte TYPE_ARRAY = 1; @@ -37,13 +41,13 @@ public UnpackerStack() { this.types[0] = TYPE_INVALID; } - public void pushArray(int size) { + public void pushArray(final int size) { top++; types[top] = TYPE_ARRAY; counts[top] = size; } - public void pushMap(int size) { + public void pushMap(final int size) { top++; types[top] = TYPE_MAP; counts[top] = size * 2; @@ -72,6 +76,10 @@ public void reduceCount() { counts[top]--; } + public void raiseCount() { + counts[top]++; + } + public void pop() { top--; } diff --git a/src/main/java/org/msgpack/unpacker/Accept.java b/src/main/java/com/blockwithme/msgpack/impl/accept/Accept.java similarity index 63% rename from src/main/java/org/msgpack/unpacker/Accept.java rename to src/main/java/com/blockwithme/msgpack/impl/accept/Accept.java index 9d2f4ba34..09ecd3e04 100644 --- a/src/main/java/org/msgpack/unpacker/Accept.java +++ b/src/main/java/com/blockwithme/msgpack/impl/accept/Accept.java @@ -15,47 +15,52 @@ // See the License for the specific language governing permissions and // limitations under the License. // -package org.msgpack.unpacker; +package com.blockwithme.msgpack.impl.accept; import java.io.IOException; import java.nio.ByteBuffer; -import org.msgpack.io.BufferReferer; -import org.msgpack.MessageTypeException; -abstract class Accept implements BufferReferer { - void acceptBoolean(boolean v) throws IOException { +import com.blockwithme.msgpack.MessageTypeException; + +/** + * Part of the original low-level Message-Pack Java implementation. + * + * @author monster + */ +public abstract class Accept { + public void acceptBoolean(final boolean v) throws IOException { throw new MessageTypeException("Unexpected boolean value"); } - void acceptInteger(byte v) throws IOException { + public void acceptInteger(final byte v) throws IOException { throw new MessageTypeException("Unexpected integer value"); } - void acceptInteger(short v) throws IOException { + public void acceptInteger(final short v) throws IOException { throw new MessageTypeException("Unexpected integer value"); } - void acceptInteger(int v) throws IOException { + public void acceptInteger(final int v) throws IOException { throw new MessageTypeException("Unexpected integer value"); } - void acceptInteger(long v) throws IOException { + public void acceptInteger(final long v) throws IOException { throw new MessageTypeException("Unexpected integer value"); } - void acceptUnsignedInteger(byte v) throws IOException { + public void acceptUnsignedInteger(final byte v) throws IOException { throw new MessageTypeException("Unexpected integer value"); } - void acceptUnsignedInteger(short v) throws IOException { + public void acceptUnsignedInteger(final short v) throws IOException { throw new MessageTypeException("Unexpected integer value"); } - void acceptUnsignedInteger(int v) throws IOException { + public void acceptUnsignedInteger(final int v) throws IOException { throw new MessageTypeException("Unexpected integer value"); } - void acceptUnsignedInteger(long v) throws IOException { + public void acceptUnsignedInteger(final long v) throws IOException { throw new MessageTypeException("Unexpected integer value"); } @@ -63,11 +68,11 @@ void acceptUnsignedInteger(long v) throws IOException { // throw new MessageTypeException("Unexpected raw value"); // } - void acceptRaw(byte[] raw) throws IOException { + public void acceptRaw(final byte[] raw) throws IOException { throw new MessageTypeException("Unexpected raw value"); } - void acceptEmptyRaw() throws IOException { + public void acceptEmptyRaw() throws IOException { throw new MessageTypeException("Unexpected raw value"); } @@ -75,7 +80,7 @@ void acceptEmptyRaw() throws IOException { // throw new MessageTypeException("Unexpected array value"); // } - void acceptArray(int size) throws IOException { + public void acceptArray(final int size) throws IOException { throw new MessageTypeException("Unexpected array value"); } @@ -83,23 +88,24 @@ void acceptArray(int size) throws IOException { // throw new MessageTypeException("Unexpected map value"); // } - void acceptMap(int size) throws IOException { + public void acceptMap(final int size) throws IOException { throw new MessageTypeException("Unexpected map value"); } - void acceptNil() throws IOException { + public void acceptNil() throws IOException { throw new MessageTypeException("Unexpected nil value"); } - void acceptFloat(float v) throws IOException { + public void acceptFloat(final float v) throws IOException { throw new MessageTypeException("Unexpected float value"); } - void acceptDouble(double v) throws IOException { + public void acceptDouble(final double v) throws IOException { throw new MessageTypeException("Unexpected float value"); } - public void refer(ByteBuffer bb, boolean gift) throws IOException { + public void refer(final ByteBuffer bb, final boolean gift) + throws IOException { throw new MessageTypeException("Unexpected raw value"); } } diff --git a/src/main/java/org/msgpack/unpacker/ArrayAccept.java b/src/main/java/com/blockwithme/msgpack/impl/accept/ArrayAccept.java similarity index 73% rename from src/main/java/org/msgpack/unpacker/ArrayAccept.java rename to src/main/java/com/blockwithme/msgpack/impl/accept/ArrayAccept.java index db2285d85..4bc5710f1 100644 --- a/src/main/java/org/msgpack/unpacker/ArrayAccept.java +++ b/src/main/java/com/blockwithme/msgpack/impl/accept/ArrayAccept.java @@ -15,13 +15,18 @@ // See the License for the specific language governing permissions and // limitations under the License. // -package org.msgpack.unpacker; +package com.blockwithme.msgpack.impl.accept; -final class ArrayAccept extends Accept { - int size; +/** + * Part of the original low-level Message-Pack Java implementation. + * + * @author monster + */ +public final class ArrayAccept extends Accept { + public int size; @Override - void acceptArray(int size) { + public void acceptArray(final int size) { this.size = size; } } diff --git a/src/main/java/org/msgpack/unpacker/BigIntegerAccept.java b/src/main/java/com/blockwithme/msgpack/impl/accept/BigIntegerAccept.java similarity index 53% rename from src/main/java/org/msgpack/unpacker/BigIntegerAccept.java rename to src/main/java/com/blockwithme/msgpack/impl/accept/BigIntegerAccept.java index accc8367e..8f40bb94f 100644 --- a/src/main/java/org/msgpack/unpacker/BigIntegerAccept.java +++ b/src/main/java/com/blockwithme/msgpack/impl/accept/BigIntegerAccept.java @@ -15,54 +15,59 @@ // See the License for the specific language governing permissions and // limitations under the License. // -package org.msgpack.unpacker; +package com.blockwithme.msgpack.impl.accept; import java.math.BigInteger; -final class BigIntegerAccept extends Accept { - BigInteger value; +/** + * Part of the original low-level Message-Pack Java implementation. + * + * @author monster + */ +public final class BigIntegerAccept extends Accept { + public BigInteger value; @Override - void acceptInteger(byte v) { - this.value = BigInteger.valueOf((long) v); + public void acceptInteger(final byte v) { + this.value = BigInteger.valueOf(v); } @Override - void acceptInteger(short v) { - this.value = BigInteger.valueOf((long) v); + public void acceptInteger(final short v) { + this.value = BigInteger.valueOf(v); } @Override - void acceptInteger(int v) { - this.value = BigInteger.valueOf((long) v); + public void acceptInteger(final int v) { + this.value = BigInteger.valueOf(v); } @Override - void acceptInteger(long v) { + public void acceptInteger(final long v) { this.value = BigInteger.valueOf(v); } @Override - void acceptUnsignedInteger(byte v) { - BigInteger.valueOf((long) (v & 0xff)); + public void acceptUnsignedInteger(final byte v) { + this.value = BigInteger.valueOf(v & 0xff); } @Override - void acceptUnsignedInteger(short v) { - BigInteger.valueOf((long) (v & 0xffff)); + public void acceptUnsignedInteger(final short v) { + this.value = BigInteger.valueOf(v & 0xffff); } @Override - void acceptUnsignedInteger(int v) { + public void acceptUnsignedInteger(final int v) { if (v < 0) { - this.value = BigInteger.valueOf((long) (v & 0x7fffffff) + 0x80000000L); + this.value = BigInteger.valueOf((v & 0x7fffffff) + 0x80000000L); } else { - this.value = BigInteger.valueOf((long) v); + this.value = BigInteger.valueOf(v); } } @Override - void acceptUnsignedInteger(long v) { + public void acceptUnsignedInteger(final long v) { if (v < 0L) { this.value = BigInteger.valueOf(v + Long.MAX_VALUE + 1L).setBit(63); } else { diff --git a/src/main/java/org/msgpack/unpacker/ByteArrayAccept.java b/src/main/java/com/blockwithme/msgpack/impl/accept/ByteArrayAccept.java similarity index 70% rename from src/main/java/org/msgpack/unpacker/ByteArrayAccept.java rename to src/main/java/com/blockwithme/msgpack/impl/accept/ByteArrayAccept.java index 18d34d84f..ee07f8ee5 100644 --- a/src/main/java/org/msgpack/unpacker/ByteArrayAccept.java +++ b/src/main/java/com/blockwithme/msgpack/impl/accept/ByteArrayAccept.java @@ -15,26 +15,32 @@ // See the License for the specific language governing permissions and // limitations under the License. // -package org.msgpack.unpacker; +package com.blockwithme.msgpack.impl.accept; import java.io.IOException; import java.nio.ByteBuffer; -final class ByteArrayAccept extends Accept { - byte[] value; +/** + * Part of the original low-level Message-Pack Java implementation. + * + * @author monster + */ +public final class ByteArrayAccept extends Accept { + public byte[] value; @Override - void acceptRaw(byte[] raw) { + public void acceptRaw(final byte[] raw) { this.value = raw; } @Override - void acceptEmptyRaw() { + public void acceptEmptyRaw() { this.value = new byte[0]; } @Override - public void refer(ByteBuffer bb, boolean gift) throws IOException { + public void refer(final ByteBuffer bb, final boolean gift) + throws IOException { // TODO gift this.value = new byte[bb.remaining()]; bb.get(value); diff --git a/src/main/java/org/msgpack/unpacker/DoubleAccept.java b/src/main/java/com/blockwithme/msgpack/impl/accept/DoubleAccept.java similarity index 66% rename from src/main/java/org/msgpack/unpacker/DoubleAccept.java rename to src/main/java/com/blockwithme/msgpack/impl/accept/DoubleAccept.java index a0324654f..6e642bdf6 100644 --- a/src/main/java/org/msgpack/unpacker/DoubleAccept.java +++ b/src/main/java/com/blockwithme/msgpack/impl/accept/DoubleAccept.java @@ -15,16 +15,23 @@ // See the License for the specific language governing permissions and // limitations under the License. // -package org.msgpack.unpacker; +package com.blockwithme.msgpack.impl.accept; -final class DoubleAccept extends Accept { - double value; +/** + * Part of the original low-level Message-Pack Java implementation. + * + * @author monster + */ +public final class DoubleAccept extends Accept { + public double value; - void acceptFloat(float v) { - this.value = (double) v; + @Override + public void acceptFloat(final float v) { + this.value = v; } - void acceptDouble(double v) { + @Override + public void acceptDouble(final double v) { this.value = v; } } diff --git a/src/main/java/org/msgpack/unpacker/IntAccept.java b/src/main/java/com/blockwithme/msgpack/impl/accept/IntAccept.java similarity index 59% rename from src/main/java/org/msgpack/unpacker/IntAccept.java rename to src/main/java/com/blockwithme/msgpack/impl/accept/IntAccept.java index 71bc6839b..c11435fae 100644 --- a/src/main/java/org/msgpack/unpacker/IntAccept.java +++ b/src/main/java/com/blockwithme/msgpack/impl/accept/IntAccept.java @@ -15,48 +15,54 @@ // See the License for the specific language governing permissions and // limitations under the License. // -package org.msgpack.unpacker; +package com.blockwithme.msgpack.impl.accept; -import org.msgpack.MessageTypeException; +import com.blockwithme.msgpack.MessageTypeException; -final class IntAccept extends Accept { - int value; +/** + * Part of the original low-level Message-Pack Java implementation. + * + * @author monster + */ +public final class IntAccept extends Accept { + public int value; @Override - void acceptInteger(byte v) { - this.value = (int) v; + public void acceptInteger(final byte v) { + this.value = v; } @Override - void acceptInteger(short v) { - this.value = (int) v; + public void acceptInteger(final short v) { + this.value = v; } @Override - void acceptInteger(int v) { + public void acceptInteger(final int v) { this.value = v; } @Override - void acceptInteger(long v) { - if (value < (long) Integer.MIN_VALUE || value > (long) Integer.MAX_VALUE) { + public void acceptInteger(final long v) { + if (value < (long) Integer.MIN_VALUE + || value > (long) Integer.MAX_VALUE) { throw new MessageTypeException(); // TODO message } this.value = (int) v; } @Override - void acceptUnsignedInteger(byte v) { + public void acceptUnsignedInteger(final byte v) { this.value = v & 0xff; } @Override - void acceptUnsignedInteger(short v) { + public void acceptUnsignedInteger(final short v) { this.value = v & 0xffff; } @Override - void acceptUnsignedInteger(int v) { + public void acceptUnsignedInteger(final int v) { if (v < 0) { throw new MessageTypeException(); // TODO message } @@ -64,8 +70,8 @@ void acceptUnsignedInteger(int v) { } @Override - void acceptUnsignedInteger(long v) { - if (v < 0 || v > (long) Integer.MAX_VALUE) { + public void acceptUnsignedInteger(final long v) { + if (v < 0 || v > Integer.MAX_VALUE) { throw new MessageTypeException(); // TODO message } this.value = (int) v; diff --git a/src/main/java/org/msgpack/unpacker/LongAccept.java b/src/main/java/com/blockwithme/msgpack/impl/accept/LongAccept.java similarity index 53% rename from src/main/java/org/msgpack/unpacker/LongAccept.java rename to src/main/java/com/blockwithme/msgpack/impl/accept/LongAccept.java index 215dc67c1..d26cecf2f 100644 --- a/src/main/java/org/msgpack/unpacker/LongAccept.java +++ b/src/main/java/com/blockwithme/msgpack/impl/accept/LongAccept.java @@ -15,54 +15,59 @@ // See the License for the specific language governing permissions and // limitations under the License. // -package org.msgpack.unpacker; +package com.blockwithme.msgpack.impl.accept; -import org.msgpack.MessageTypeException; +import com.blockwithme.msgpack.MessageTypeException; -final class LongAccept extends Accept { - long value; +/** + * Part of the original low-level Message-Pack Java implementation. + * + * @author monster + */ +public final class LongAccept extends Accept { + public long value; @Override - void acceptInteger(byte v) { - this.value = (long) v; + public void acceptInteger(final byte v) { + this.value = v; } @Override - void acceptInteger(short v) { - this.value = (long) v; + public void acceptInteger(final short v) { + this.value = v; } @Override - void acceptInteger(int v) { - this.value = (long) v; + public void acceptInteger(final int v) { + this.value = v; } @Override - void acceptInteger(long v) { + public void acceptInteger(final long v) { this.value = v; } @Override - void acceptUnsignedInteger(byte v) { - this.value = (long) (v & 0xff); + public void acceptUnsignedInteger(final byte v) { + this.value = v & 0xff; } @Override - void acceptUnsignedInteger(short v) { - this.value = (long) (v & 0xffff); + public void acceptUnsignedInteger(final short v) { + this.value = v & 0xffff; } @Override - void acceptUnsignedInteger(int v) { + public void acceptUnsignedInteger(final int v) { if (v < 0) { - this.value = (long) (v & 0x7fffffff) + 0x80000000L; + this.value = (v & 0x7fffffff) + 0x80000000L; } else { - this.value = (long) v; + this.value = v; } } @Override - void acceptUnsignedInteger(long v) { + public void acceptUnsignedInteger(final long v) { if (v < 0L) { throw new MessageTypeException(); // TODO message } diff --git a/src/main/java/org/msgpack/unpacker/MapAccept.java b/src/main/java/com/blockwithme/msgpack/impl/accept/MapAccept.java similarity index 73% rename from src/main/java/org/msgpack/unpacker/MapAccept.java rename to src/main/java/com/blockwithme/msgpack/impl/accept/MapAccept.java index a687b9678..16343b306 100644 --- a/src/main/java/org/msgpack/unpacker/MapAccept.java +++ b/src/main/java/com/blockwithme/msgpack/impl/accept/MapAccept.java @@ -15,13 +15,18 @@ // See the License for the specific language governing permissions and // limitations under the License. // -package org.msgpack.unpacker; +package com.blockwithme.msgpack.impl.accept; -final class MapAccept extends Accept { - int size; +/** + * Part of the original low-level Message-Pack Java implementation. + * + * @author monster + */ +public final class MapAccept extends Accept { + public int size; @Override - void acceptMap(int size) { + public void acceptMap(final int size) { this.size = size; } } diff --git a/src/main/java/org/msgpack/unpacker/SkipAccept.java b/src/main/java/com/blockwithme/msgpack/impl/accept/SkipAccept.java similarity index 51% rename from src/main/java/org/msgpack/unpacker/SkipAccept.java rename to src/main/java/com/blockwithme/msgpack/impl/accept/SkipAccept.java index 7e3a98c54..07ba2b0e3 100644 --- a/src/main/java/org/msgpack/unpacker/SkipAccept.java +++ b/src/main/java/com/blockwithme/msgpack/impl/accept/SkipAccept.java @@ -15,77 +15,83 @@ // See the License for the specific language governing permissions and // limitations under the License. // -package org.msgpack.unpacker; +package com.blockwithme.msgpack.impl.accept; import java.io.IOException; import java.nio.ByteBuffer; -final class SkipAccept extends Accept { +/** + * Part of the original low-level Message-Pack Java implementation. + * + * @author monster + */ +public final class SkipAccept extends Accept { @Override - void acceptBoolean(boolean v) { + public void acceptBoolean(final boolean v) { } @Override - void acceptInteger(byte v) { + public void acceptInteger(final byte v) { } @Override - void acceptInteger(short v) { + public void acceptInteger(final short v) { } @Override - void acceptInteger(int v) { + public void acceptInteger(final int v) { } @Override - void acceptInteger(long v) { + public void acceptInteger(final long v) { } @Override - void acceptUnsignedInteger(byte v) { + public void acceptUnsignedInteger(final byte v) { } @Override - void acceptUnsignedInteger(short v) { + public void acceptUnsignedInteger(final short v) { } @Override - void acceptUnsignedInteger(int v) { + public void acceptUnsignedInteger(final int v) { } @Override - void acceptUnsignedInteger(long v) { + public void acceptUnsignedInteger(final long v) { } @Override - void acceptRaw(byte[] raw) { + public void acceptRaw(final byte[] raw) { } @Override - void acceptEmptyRaw() { + public void acceptEmptyRaw() { } @Override - public void refer(ByteBuffer bb, boolean gift) throws IOException { + public void refer(final ByteBuffer bb, final boolean gift) + throws IOException { } @Override - void acceptArray(int size) { + public void acceptArray(final int size) { } @Override - void acceptMap(int size) { + public void acceptMap(final int size) { } @Override - void acceptNil() { + public void acceptNil() { } @Override - void acceptFloat(float v) { + public void acceptFloat(final float v) { } @Override - void acceptDouble(double v) { + public void acceptDouble(final double v) { } } diff --git a/src/main/java/org/msgpack/unpacker/StringAccept.java b/src/main/java/com/blockwithme/msgpack/impl/accept/StringAccept.java similarity index 71% rename from src/main/java/org/msgpack/unpacker/StringAccept.java rename to src/main/java/com/blockwithme/msgpack/impl/accept/StringAccept.java index a50053ceb..e36fc1d99 100644 --- a/src/main/java/org/msgpack/unpacker/StringAccept.java +++ b/src/main/java/com/blockwithme/msgpack/impl/accept/StringAccept.java @@ -15,19 +15,25 @@ // See the License for the specific language governing permissions and // limitations under the License. // -package org.msgpack.unpacker; +package com.blockwithme.msgpack.impl.accept; import java.io.IOException; import java.nio.ByteBuffer; -import java.nio.charset.Charset; import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; -import org.msgpack.MessageTypeException; -final class StringAccept extends Accept { - String value; - private CharsetDecoder decoder; +import com.blockwithme.msgpack.MessageTypeException; + +/** + * Part of the original low-level Message-Pack Java implementation. + * + * @author monster + */ +public final class StringAccept extends Accept { + public String value; + private final CharsetDecoder decoder; public StringAccept() { this.decoder = Charset.forName("UTF-8").newDecoder() @@ -36,24 +42,25 @@ public StringAccept() { } @Override - void acceptRaw(byte[] raw) { + public void acceptRaw(final byte[] raw) { try { this.value = decoder.decode(ByteBuffer.wrap(raw)).toString(); - } catch (CharacterCodingException ex) { + } catch (final CharacterCodingException ex) { throw new MessageTypeException(ex); } } @Override - void acceptEmptyRaw() { + public void acceptEmptyRaw() { this.value = ""; } @Override - public void refer(ByteBuffer bb, boolean gift) throws IOException { + public void refer(final ByteBuffer bb, final boolean gift) + throws IOException { try { this.value = decoder.decode(bb).toString(); - } catch (CharacterCodingException ex) { + } catch (final CharacterCodingException ex) { throw new MessageTypeException(ex); } } diff --git a/src/main/java/com/blockwithme/msgpack/schema/BasicSchemaManager.java b/src/main/java/com/blockwithme/msgpack/schema/BasicSchemaManager.java new file mode 100644 index 000000000..db558d028 --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/schema/BasicSchemaManager.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2013 Sebastien Diot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blockwithme.msgpack.schema; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.blockwithme.msgpack.templates.Template; + +/** + * BasicSchemaManager assumes templates never get change ID. + * + * @author monster + */ +public abstract class BasicSchemaManager extends SchemaManagerBase { + + /** The user templates */ + private final Template[] userTemplates; + + /** Creates a BasicSchemaManager */ + protected BasicSchemaManager(final Template[] theUserTemplates) { + userTemplates = Objects.requireNonNull(theUserTemplates); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.schema.SchemaManagerBase#createSchema(int, int) + */ + @Override + protected Schema createSchema(final int format, final int schemaID) { + final List> list = new ArrayList>( + userTemplates.length); + int lastNotNull = 0; + for (int i = 0; i < userTemplates.length; i++) { + if (userTemplates[i].isSchemaSupported(schemaID)) { + list.add(userTemplates[i]); + lastNotNull = i; + } else { + list.add(null); + } + } + while (list.size() > lastNotNull + 1) { + list.remove(list.size() - 1); + } + return createSchema(format, schemaID, + list.toArray(new Template[list.size()]), + getBasicTemplateCount(schemaID)); + } + + /** Returns the basicTemplates count, for the given schema. */ + protected abstract int getBasicTemplateCount(final int schemaID); +} diff --git a/src/main/java/com/blockwithme/msgpack/schema/Schema.java b/src/main/java/com/blockwithme/msgpack/schema/Schema.java new file mode 100644 index 000000000..1e16a3814 --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/schema/Schema.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2013 Sebastien Diot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blockwithme.msgpack.schema; + +import java.util.Map; + +import com.blockwithme.msgpack.templates.BasicTemplates; +import com.blockwithme.msgpack.templates.Template; + +/** + * Represents a serialisation schema. + * + * TODO toString ... + * + * @author monster + */ +public class Schema { + + /** The current format version. */ + public static final int FORMAT = 0; + + /** The format version for the current (de)serialisation. */ + public final int format; + + /** The schema version for the current (de)serialisation. */ + public final int schema; + + /** The basic templates. */ + public final BasicTemplates basicTemplates; + + /** Maps IDs to Class templates */ + public final Template[] idToTemplate; + + /** Maps Class to Template. */ + public final Map, Template> classToTemplate; + + /** The "fallback" (catch-all) templates */ + public final Template[] fallbackTemplates; + + /** Constructor */ + public Schema(final int theFormat, final int theSchema, + final BasicTemplates theBasicTemplates, + final Template[] theIdToTemplate, + final Map, Template> theClassToTemplate, + final Template[] theFallbackTemplates) { + format = theFormat; + schema = theSchema; + basicTemplates = theBasicTemplates; + idToTemplate = theIdToTemplate; + classToTemplate = theClassToTemplate; + fallbackTemplates = theFallbackTemplates; + } + +} diff --git a/src/main/java/com/blockwithme/msgpack/schema/SchemaManager.java b/src/main/java/com/blockwithme/msgpack/schema/SchemaManager.java new file mode 100644 index 000000000..b0ba04ef9 --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/schema/SchemaManager.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2013 Sebastien Diot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blockwithme.msgpack.schema; + +/** + * SchemaManager creates and cache schemas. + * + * @author monster + */ +public interface SchemaManager { + /** Returns a schema for the given schema in. */ + Schema getSchema(final int format, final int schema); +} diff --git a/src/main/java/com/blockwithme/msgpack/schema/SchemaManagerBase.java b/src/main/java/com/blockwithme/msgpack/schema/SchemaManagerBase.java new file mode 100644 index 000000000..7bc46bc75 --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/schema/SchemaManagerBase.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2013 Sebastien Diot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blockwithme.msgpack.schema; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import com.blockwithme.msgpack.templates.BasicTemplates; +import com.blockwithme.msgpack.templates.Template; +import com.blockwithme.msgpack.templates._Template; + +/** + * Base implementation of SchemaManager. + * + * @author monster + */ +public abstract class SchemaManagerBase implements SchemaManager { + + /** Map ID to schema. */ + private final Map schemas = new HashMap(); + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.schema.SchemaManager#getSchema(int) + */ + @Override + public final Schema getSchema(final int format, final int schemaID) { + if (format < 0) { + throw new IllegalArgumentException("format: " + format); + } + if (schemaID < 1) { + throw new IllegalArgumentException("schemaID: " + schemaID); + } + final Integer key = schemaID; + Schema result; + synchronized (schemas) { + result = schemas.get(key); + if (result == null) { + result = createSchema(format, schemaID); + schemas.put(key, result); + } + } + return result; + } + + /** + * Creates and returns a new schema, given the user templates, and the + * number of basic templates. + * + * @param schemaID + * @return + */ + protected final Schema createSchema(final int format, final int schemaID, + final Template[] userTemplates, final int basicTemplateCount) { + final BasicTemplates basicTemplates = new BasicTemplates(); + final Map, Template> classToTemplate = new HashMap, Template>(); + final Template[] bt = basicTemplates + .getBasicTemplates(basicTemplateCount); + final Template[] idToTemplate = new Template[bt.length + + userTemplates.length]; + System.arraycopy(bt, 0, idToTemplate, 0, bt.length); + System.arraycopy(userTemplates, 0, idToTemplate, bt.length, + userTemplates.length); + for (int i = 0; i < idToTemplate.length; i++) { + final Template template = idToTemplate[i]; + if (template != null) { + Objects.requireNonNull(template.getType(), "idToTemplate[" + i + + "].getType()"); + ((_Template) template).setID(i); + if (template.isMainTemplate() + && classToTemplate.put(template.getType(), template) != null) { + throw new IllegalArgumentException( + "Multiple main templates for " + template.getType()); + } + } + } + final List> fallBack = new ArrayList>(); + for (int i = 0; i < idToTemplate.length; i++) { + if (idToTemplate[i].isFallBackTemplate()) { + fallBack.add(idToTemplate[i]); + } + } + final Template[] fallbackTemplates = fallBack + .toArray(new Template[fallBack.size()]); + final Schema result = new Schema(format, schemaID, basicTemplates, + idToTemplate, classToTemplate, fallbackTemplates); + for (int i = 0; i < idToTemplate.length; i++) { + ((_Template) idToTemplate[i]).resolve(result); + } + return result; + } + + /** + * Creates and returns a new schema. + * + * @param format + * @param schemaID + * @return + */ + protected abstract Schema createSchema(final int format, final int schemaID); + +} diff --git a/src/main/java/com/blockwithme/msgpack/templates/AbstractTemplate.java b/src/main/java/com/blockwithme/msgpack/templates/AbstractTemplate.java new file mode 100644 index 000000000..390f3ffd8 --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/templates/AbstractTemplate.java @@ -0,0 +1,806 @@ +/* + * Copyright (C) 2013 Sebastien Diot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blockwithme.msgpack.templates; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Objects; + +import com.blockwithme.msgpack.ObjectPacker; +import com.blockwithme.msgpack.Packer; +import com.blockwithme.msgpack.Unpacker; +import com.blockwithme.msgpack.ValueType; +import com.blockwithme.msgpack.schema.Schema; + +/** + * Object template, for anything beyond primitive types. + * + * All the code related to serializing Java Objects, as opposed to just data, + * is located here. This part is not based on the original MessagePack + * implementation. It makes a strong assumption, that the complete schema is + * known at de-serialization. + * + * @author monster + */ +public abstract class AbstractTemplate implements Template, _Template, + Cloneable { + + /** The ClassNameConverter */ + private static volatile ClassNameConverter CLASS_NAME_CONVERTER = new DefaultClassNameConverter(); + + /** The template ID (should not be negative). */ + protected int id = -1; + + /** The type that is supported. */ + protected final Class type; + + /** The template name. The Main template has the name of the type. */ + protected final String name; + + /** First schema introduction. */ + protected final int firstSchemaIntroduction; + + /** The 1D array type that is supported. */ + protected final Class type1D; + + /** The 2D array type that is supported. */ + protected final Class type2D; + + /** Is this the "main" template for this type, or an "alternate" template? */ + protected final boolean mainTemplate; + + /** Is this the "fallback" (catch-all) template for this type? */ + protected final boolean isFallBackTemplate; + + /** True if the type is final, or a primitive array. */ + protected final boolean isFinalOrPrimitiveArray; + + /** + * Most objects must be stored in "containers" (list or map). What is the + * format that should be used for this type? List is the default/usual. + */ + protected final ObjectType objectType; + + /** Returns the TrackingType of instances of this type. */ + protected final TrackingType trackingType; + + /** A non-negative value, if this type has a "fixed size" */ + protected final int fixedSize; + + /** Sets the ClassNameConverter; required for multi-version support in OSGi. */ + public static void setClassNameConverter(final ClassNameConverter cnc) { + CLASS_NAME_CONVERTER = Objects.requireNonNull(cnc); + } + + /** Returns the ClassNameConverter. */ + public static ClassNameConverter getClassNameConverter() { + return CLASS_NAME_CONVERTER; + } + + /** Reads any Object. We assume the template is valid. */ + private static T readNewNonNullObject(final UnpackerContext context, + final Template template, final int size) throws IOException { + final ArrayList previous = context.previous; + final int objID = previous.size(); + // pre-creation before read allows support for cycles. + T result = template.preCreate(size); + // Often null ... + previous.add(result); + result = template.readData(context, result, size); + // in case preCreate() returns null + previous.set(objID, result); + return result; + } + + /** Returns a previously read object. + * @return */ + private static Object readPrevious(final Unpacker unpacker, + final Template template, final ArrayList previous) + throws IOException { + final int id = unpacker.readIndex(); + if (id >= previous.size()) { + throw new IllegalStateException("Object with ID " + id + + " not read yet"); + } + final Object result = previous.get(id); + if (result == null) { + throw new IllegalStateException("Object with ID " + id + + " not (fully?) read yet"); + } + if ((template != null) && !template.accept(result)) { + throw new IllegalStateException("Object with ID " + id + + " of type " + result.getClass() + + " not supported by template " + template); + } + return result; + } + + /** Reads any 1D Object array. We assume the template is valid. */ + private static T[] readNewNonNull1DArray(final UnpackerContext context, + final Template template, int size, + final boolean ifObjectArrayCanContainNullValue) throws IOException { + final int fixedSize = template.getFixedSize(); + if (template.isFinalOrPrimitiveArray() && (fixedSize > 0) + && !ifObjectArrayCanContainNullValue) { + size /= fixedSize; + } + @SuppressWarnings("unchecked") + // pre-creation before read allows support for cycles. + final T[] result = (T[]) template.preCreateArray(1, size); + context.previous.add(result); + template.read1DArray(context, result, size, + ifObjectArrayCanContainNullValue); + return result; + } + + /** Reads any 2D Object array. We assume the template is valid. */ + private static T[][] readNewNonNull2DArray( + final UnpackerContext context, final Template template, + final int size) throws IOException { + @SuppressWarnings("unchecked") + // pre-creation before read allows support for cycles. + final T[][] result = (T[][]) template.preCreateArray(2, size); + context.previous.add(result); + template.read2DArray(context, result, size); + return result; + } + + /** Reads any 3D Object array. We assume the template is valid. */ + private static T[][][] readNewNonNull3DArray( + final UnpackerContext context, final Template template, + final int size) throws IOException { + @SuppressWarnings("unchecked") + // pre-creation before read allows support for cycles. + final T[][][] result = (T[][][]) template.preCreateArray(3, size); + context.previous.add(result); + template.read3DArray(context, result, size); + return result; + } + + /** Reads an "array" object. (Not to be confused with an array OF objects) + * @return */ + private static Object readArrayObject(final UnpackerContext context, + Template template, final boolean ifObjectArrayCanContainNullValue) + throws IOException { + final Unpacker unpacker = context.unpacker; + final int size = unpacker.readArrayBegin(); + final int tidPlusDimension = unpacker.readIndex(); + // The template ID + final int tid = tidPlusDimension / 4; + // The array dimension (0 for normal objects) + final int dimension = tidPlusDimension % 4; + if (template != null) { + if (tid != template.getID()) { + throw new IllegalArgumentException("Expected: " + template + + " but was " + context.getTemplate(tid)); + } + } else { + template = context.getTemplate(tid); + } + final Object result; + if (dimension == 0) { + if (template.getObjectType() == ObjectType.MAP) { + throw new IllegalStateException("Template " + template + + " does not support ARRAYs"); + } + result = readNewNonNullObject(context, template, size - 1); + } else if (dimension == 1) { + result = readNewNonNull1DArray(context, template, size - 1, + ifObjectArrayCanContainNullValue); + } else if (dimension == 2) { + result = readNewNonNull2DArray(context, template, size - 1); + } else { + result = readNewNonNull3DArray(context, template, size - 1); + } + unpacker.readArrayEnd(); + return result; + } + + /** Reads an "map" object. + * @return */ + @SuppressWarnings("unchecked") + private static T readMapObject(final UnpackerContext context, + Template template) throws IOException { + final Unpacker unpacker = context.unpacker; + final int size = unpacker.readMapBegin(); + // We do not specify "dimensions" for "map" objects + final int tid = unpacker.readIndex(); + if (template != null) { + if (tid != template.getID()) { + throw new IllegalArgumentException("Expected: " + template + + " but was " + context.getTemplate(tid)); + } + } else { + template = (Template) context.getTemplate(tid); + } + if (template.getObjectType() != ObjectType.MAP) { + throw new IllegalStateException("Template " + template + + " does not support MAPs"); + } + final T result = readNewNonNullObject(context, template, size - 1); + unpacker.readMapEnd(); + return result; + } + + /** + * Reads any Object. Fails if we specified the template, and the Object type + * does not match template type. + */ + public static Object readObject(final UnpackerContext context, + final Template template, + final boolean ifObjectArrayCanContainNullValue) throws IOException { + final Unpacker unpacker = context.unpacker; + final ArrayList previous = context.previous; + final ValueType type = unpacker.getNextType(); + // null? + if (type == ValueType.NIL) { + return null; + } + // previous object? + if (type == ValueType.INTEGER) { + return readPrevious(unpacker, template, previous); + } + // New object + if (type == ValueType.ARRAY) { + return readArrayObject(context, template, + ifObjectArrayCanContainNullValue); + } + if (type == ValueType.MAP) { + return readMapObject(context, template); + } + if (type == ValueType.RAW) { + if (template == null) { + // Must be string + return readNewNonNullObject(context, + context.getSchema().basicTemplates.STRING, -1); + } + return readNewNonNullObject(context, template, -1); + } + throw new IllegalStateException("Unexpected value type: " + type + + " for template " + template); + } + + /** + * Writes an Object out. Object must be compatible with template, if + * specified. + */ + @SuppressWarnings("unchecked") + public static void writeObject(final PackerContext context, final Object o, + @SuppressWarnings("rawtypes") Template template, + final boolean ifObjectArrayCanContainNullValue) throws IOException { + final Packer packer = context.packer; + if (o == null) { + if (context.required) { + throw new IOException("Attempted to write null when required"); + } + packer.writeNil(); + return; + } + int depth = -1; + if (template == null) { + if (o instanceof Packable) { + template = ((Packable) o).getTemplate(); + } + if (template == null) { + // Discover template ... + Class c = o.getClass(); + depth = 0; + Class cc; + while (c.isArray() + && !(cc = c.getComponentType()).isPrimitive()) { + depth++; + c = cc; + } + template = context.getTemplate(c); + if (o instanceof Packable) { + ((Packable) o).setTemplate(template); + } + } + } + template = template.replaceSelf(o); + // Check if new object + final TrackingType tt = template.getTrackingType(); + final int pos = (tt == TrackingType.DO_NOT_TRACK) ? -1 + : context.tracker.track(o, (tt == TrackingType.EQUALITY)); + if (pos == -1) { + // New Object! + if (depth == -1) { + depth = getArrayDepth(o.getClass()); + } + if (depth == 0) { + template.writeNonArrayObject(context, o); + } else if (depth == 1) { + template.write1DArray(context, (Object[]) o, + ifObjectArrayCanContainNullValue); + } else if (depth == 2) { + template.write2DArray(context, (Object[][]) o, + ifObjectArrayCanContainNullValue); + } else if (depth == 3) { + template.write3DArray(context, (Object[][][]) o, + ifObjectArrayCanContainNullValue); + } else { + throw new IOException( + "Maximum non-primitive (+1 for primitives) array dimention is 3, but got " + + depth); + } + } else { + // Previous object + packer.writeIndex(pos); + } + } + + /** Computes the array-depth of a class. */ + public static int getArrayDepth(Class c) { + int depth = 0; + Class cc; + while (c.isArray() && !(cc = c.getComponentType()).isPrimitive()) { + depth++; + c = cc; + } + return depth; + } + + /** + * Converts a boolean to the usual tracking types: EQUALITY (true) + * and IDENTITY (false) + */ + public static TrackingType toTrackingType(final boolean isMergeable) { + return isMergeable ? TrackingType.EQUALITY : TrackingType.IDENTITY; + } + + /** Constructor. */ + protected AbstractTemplate(final String name, final Class type, + final int firstSchemaIntroduction, final ObjectType objectType, + final boolean isMergeable) { + this(name, type, firstSchemaIntroduction, objectType, + toTrackingType(isMergeable), -1); + } + + /** Constructor. */ + protected AbstractTemplate(final String name, final Class type, + final int firstSchemaIntroduction, final ObjectType objectType, + final boolean isMergeable, final int fixedSize) { + this(name, type, firstSchemaIntroduction, objectType, + toTrackingType(isMergeable), fixedSize); + } + + /** Constructor. */ + protected AbstractTemplate(final String name, final Class type, + final int firstSchemaIntroduction, final ObjectType objectType, + final TrackingType trackingType, final int fixedSize) { + this(name, type, firstSchemaIntroduction, objectType, trackingType, + fixedSize, false); + } + + /** Constructor. */ + @SuppressWarnings("unchecked") + protected AbstractTemplate(final String name, final Class type, + final int firstSchemaIntroduction, final ObjectType objectType, + final TrackingType trackingType, final int fixedSize, + final boolean isFallBackTemplate) { + final String typeName = type.getName(); + this.name = (name == null) ? typeName : name; + this.mainTemplate = typeName.equals(this.name); + this.isFallBackTemplate = isFallBackTemplate; + this.firstSchemaIntroduction = firstSchemaIntroduction; + this.type = Objects.requireNonNull(type); + // We need those to optimize array creation + type1D = (Class) Array.newInstance(type, 0).getClass(); + type2D = (Class) Array.newInstance(type1D, 0).getClass(); + // We are not interested in actually final classes, but rather in + // "effectively" final classes. A class is "effectively" final, when + // no other class extends it. This applies to primitive arrays too. + // but realize that inheritance exists among Object arrays. + isFinalOrPrimitiveArray = CLASS_NAME_CONVERTER.isFinal(type); + this.objectType = Objects.requireNonNull(objectType); + this.trackingType = Objects.requireNonNull(trackingType); + this.fixedSize = (fixedSize >= 0) ? fixedSize : -1; + } + + /** toString */ + @Override + public String toString() { + return getClass().getSimpleName() + "(id=" + id + ", name=" + name + + ",type=" + type.getName() + ")"; + } + + /** Returns true if the template supports the given schema. */ + @Override + public boolean isSchemaSupported(final int schemaID) { + return (schemaID >= firstSchemaIntroduction); + } + + /** Returns a copy of the template */ + @Override + public final Template copy() { + try { + final AbstractTemplate result = (AbstractTemplate) clone(); + result.id = -1; + return result; + } catch (final CloneNotSupportedException e) { + throw new InternalError("Impossible!"); + } + } + + /** Returns the template ID. */ + @Override + public final int getID() { + if (id == -1) { + throw new IllegalStateException("id not set"); + } + return id; + } + + /** Sets the ID. This can only be called once. */ + @Override + public void setID(final int newID) { + if (newID < 0) { + throw new IllegalArgumentException("newID=" + newID); + } + if (id >= 0) { + throw new IllegalStateException("id already set to " + id); + } + id = newID; + } + + /** Returns the type that is supported. */ + @Override + public final Class getType() { + return type; + } + + /** Returns the template name. The Main template has the name of the type. */ + @Override + public final String getName() { + return name; + } + + /** Is this the "main" template for this type, or an "alternate" template? */ + @Override + public final boolean isMainTemplate() { + return mainTemplate; + } + + /** Is this the "fallback" (catch-all) template for this type? */ + @Override + public final boolean isFallBackTemplate() { + return isFallBackTemplate; + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.templates.Template#getTrackingType() + */ + @Override + public final TrackingType getTrackingType() { + return trackingType; + } + + /** Returns true, if the template would support reading/writing objects of this type. */ + @Override + public final boolean accept(final Object o) { + if (o == null) { + return true; + } + Class c = o.getClass(); + Class cc; + while (c.isArray() && !(cc = c.getComponentType()).isPrimitive()) { + c = cc; + } + return c == type; + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.templates.Template#isFinalOrPrimitiveArray() + */ + @Override + public final boolean isFinalOrPrimitiveArray() { + return isFinalOrPrimitiveArray; + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.templates.Template#getObjectType() + */ + @Override + public final ObjectType getObjectType() { + return objectType; + } + + /** + * Returns a non-negative value, if this type has a "fixed size" + * + * @see getSpaceRequired() + */ + @Override + public final int getFixedSize() { + return fixedSize; + } + + /** Writes the type ID. Not used by "map" (or raw) objects. */ + private void writeID(final Packer packer, final int dimensions) + throws IOException { + // We encode both the actual template ID, and the "array dimension" + // In the Id, to save space (hopefully!) + packer.writeIndex(4 * id + dimensions); + } + + /** Write an Object as a list/array. */ + private void writeList(final PackerContext context, final T v, + final int size) throws IOException { + final Packer packer = context.packer; + packer.writeArrayBegin(size + 1); + writeID(packer, 0); + writeData(context, size, v); + packer.writeArrayEnd(true); + } + + /** Writes an Object as a map. */ + private void writeMap(final PackerContext context, final T v, final int size) + throws IOException { + final Packer packer = context.packer; + packer.writeMapBegin(size + 1); + // Map entry key: we do not use "dimensions" for "map" objects + packer.writeIndex(id); + // Map entry value + writeMapHeaderValue(context, v, size); + writeData(context, size, v); + packer.writeMapEnd(true); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.templates.Template#writeNonArrayObject(com.blockwithme.msgpack.templates.PackerContext, com.blockwithme.msgpack.MetaData, java.lang.Object) + */ + @Override + public final void writeNonArrayObject(final PackerContext context, final T v) + throws IOException { + if (v == null) { + context.packer.writeNil(); + } else { + final int size = getSpaceRequired(context, v); + if (objectType == ObjectType.MAP) { + writeMap(context, v, size); + } else { + // Both ARRAY and RAW + writeList(context, v, size); + } + } + } + + /** Final type fixed-size non-null write */ + private void writeArrayAsMyNonNullObjectsOfFixedSize( + final PackerContext context, final T[] v) throws IOException { + final Packer packer = context.packer; + // We store the objects "inline" therefore saving space, by not + // wrapping them in sub-arrays. + packer.writeArrayBegin(v.length * fixedSize + 1); + writeID(packer, 1); + for (int i = 0; i < v.length; i++) { + final T t = v[i]; + if (t == null) { + throw new IllegalStateException("v[" + i + "] was null!"); + } + writeData(context, fixedSize, t); + } + packer.writeArrayEnd(true); + } + + /** Writes an array of anything */ + private void writeArrayDataAsRandomObjects(final PackerContext context, + final Object[] v) throws IOException { + final ObjectPacker op = context.objectPacker; + for (int i = 0; i < v.length; i++) { + op.writeObject(v[i], true); + } + } + + /** Final type write */ + private void writeArrayAsMyObjects(final PackerContext context, final T[] v) + throws IOException { + final Packer packer = context.packer; + packer.writeArrayBegin(v.length + 1); + writeID(packer, 1); + for (int i = 0; i < v.length; i++) { + writeNonArrayObject(context, v[i]); + } + packer.writeArrayEnd(true); + } + + /** Non-final type write */ + private void writeArrayAsRandomObjects(final PackerContext context, + final T[] v) throws IOException { + final Packer packer = context.packer; + packer.writeArrayBegin(v.length + 1); + writeID(packer, 1); + writeArrayDataAsRandomObjects(context, v); + packer.writeArrayEnd(true); + } + + /** Writes an 1D array of T */ + @Override + public final void write1DArray(final PackerContext context, final T[] v, + final boolean canContainNullValue) throws IOException { + if (v == null) { + context.packer.writeNil(); + } else { + if (isFinalOrPrimitiveArray) { + if ((fixedSize > 0) && !canContainNullValue) { + // A primitive array will not have a fixed size. + writeArrayAsMyNonNullObjectsOfFixedSize(context, v); + } else { + writeArrayAsMyObjects(context, v); + } + } else { + writeArrayAsRandomObjects(context, v); + } + } + } + + /** Writes a 2D array of T */ + @Override + public final void write2DArray(final PackerContext context, final T[][] v, + final boolean canContainNullValue) throws IOException { + final Packer packer = context.packer; + if (v == null) { + packer.writeNil(); + } else { + packer.writeArrayBegin(v.length + 1); + writeID(packer, 2); + if (isFinalOrPrimitiveArray) { + for (int i = 0; i < v.length; i++) { + write1DArray(context, v[i], canContainNullValue); + } + } else { + writeArrayDataAsRandomObjects(context, v); + } + packer.writeArrayEnd(true); + } + } + + /** Writes a 3D array of T */ + @Override + public final void write3DArray(final PackerContext context, + final T[][][] v, final boolean canContainNullValue) + throws IOException { + final Packer packer = context.packer; + if (v == null) { + packer.writeNil(); + } else { + packer.writeArrayBegin(v.length + 1); + writeID(packer, 3); + if (isFinalOrPrimitiveArray) { + for (int i = 0; i < v.length; i++) { + write2DArray(context, v[i], canContainNullValue); + } + } else { + writeArrayDataAsRandomObjects(context, v); + } + packer.writeArrayEnd(true); + } + } + + /** + * Creating and returning an empty instance before reading enable cycles in + * the object graph. This method must not return null. + */ + @SuppressWarnings("unchecked") + @Override + public final T preCreateArray(final int arrayDepth, final int size) { + if (arrayDepth == 1) { + return (T) Array.newInstance(type, size); + } + if (arrayDepth == 2) { + return (T) Array.newInstance(type1D, size); + } + if (arrayDepth == 3) { + return (T) Array.newInstance(type2D, size); + } + throw new IllegalArgumentException("Unsupposted array depth: " + + arrayDepth); + } + + /** Reads an 1D array of T */ + @SuppressWarnings("unchecked") + @Override + public final void read1DArray(final UnpackerContext context, + final T[] preCreated, final int size, + final boolean canContainNullValue) throws IOException { + if (isFinalOrPrimitiveArray && (fixedSize > 0) && !canContainNullValue) { + // A primitive array will not have a fixed size. + for (int i = 0; i < size; i++) { + preCreated[i] = readData(context, preCreate(fixedSize), + fixedSize); + } + } else { + final Template template = isFinalOrPrimitiveArray ? this : null; + for (int i = 0; i < size; i++) { + preCreated[i] = (T) readObject(context, template, true); + } + } + } + + /** Reads an 2D array of T */ + @SuppressWarnings("unchecked") + @Override + public final void read2DArray(final UnpackerContext context, + final T[][] preCreated, final int size) throws IOException { + final Template template = isFinalOrPrimitiveArray ? this : null; + for (int i = 0; i < size; i++) { + preCreated[i] = (T[]) readObject(context, template, true); + } + } + + /** Reads an 3D array of T */ + @SuppressWarnings("unchecked") + @Override + public final void read3DArray(final UnpackerContext context, + final T[][][] preCreated, final int size) throws IOException { + final Template template = isFinalOrPrimitiveArray ? this : null; + for (int i = 0; i < size; i++) { + preCreated[i] = (T[][]) readObject(context, template, true); + } + } + + /** Writes the Map-Object header-entry value. Normally unused, and hence nil. */ + protected void writeMapHeaderValue(final PackerContext context, final T v, + final int size) throws IOException { + context.packer.writeNil(); + } + + /** + * Skips the "unused header value" by default. + * But you could use it for something ... + * + * @see AbstractTemplate.writeMapHeaderValue(PackerContext, T, int) + */ + protected void readHeaderValue(final UnpackerContext context, + final T preCreated, final int size) throws IOException { + context.unpacker.skip(); + } + + /** + * Returns the number of values to write for this (non-null) value. + * If the preferred container type is a map, this number must be even, so + * that it can be divided by 2 to give the required map size. + */ + @Override + public int getSpaceRequired(final PackerContext context, final T v) { + if (fixedSize >= 0) { + return fixedSize; + } + throw new IllegalStateException("Must be implemented!"); + } + + /** + * Creating and returning an empty instance before reading enable cycles in + * the object graph. If possible, it is strongly encouraged to implement it. + */ + @Override + public T preCreate(final int size) { + return null; + } + + /** Called after the Schema was created, but before the serialisation starts. */ + @Override + public void resolve(final Schema schema) { + // NOP + } + + /** Allows the template to replace itself with another one before writing. */ + @Override + public Template replaceSelf(final T o) { + return this; + } +} diff --git a/src/main/java/com/blockwithme/msgpack/templates/BasicTemplates.java b/src/main/java/com/blockwithme/msgpack/templates/BasicTemplates.java new file mode 100644 index 000000000..4fb68ce27 --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/templates/BasicTemplates.java @@ -0,0 +1,883 @@ +/* + * Copyright (C) 2013 Sebastien Diot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blockwithme.msgpack.templates; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.blockwithme.msgpack.ObjectPacker; +import com.blockwithme.msgpack.ObjectUnpacker; +import com.blockwithme.msgpack.Packer; +import com.blockwithme.msgpack.Unpacker; +import com.blockwithme.msgpack.impl.ByteArraySlice; + +/** + * This class contains all the basic templates, which only need to delegate to + * the Packer/Unpacker. + * + * They are defined as internal classes, to simplify the package structure. + * + * WARNING: Basic templates never get deleted or moved. Only new templates can + * get added. + * + * TODO Add (more) Collection implementation classes + * TODO Check for other JDK Classes (EnumSet, StringBuffer, TimeZone, ...) + * TODO Reserve Class ID up to at least 1024 for JDK and other critical API collections. + * + * @author monster + */ +public class BasicTemplates { + + /** + * Own AbstractTemplate extension, which allow us to easily track all the + * Templates defined within BasicTemplates. + */ + private abstract class MyAbstractTemplate extends AbstractTemplate { + + /** + * @param name + * @param type + * @param isListType + * @param isMergeable + */ + protected MyAbstractTemplate(final String name, final Class type, + final ObjectType objectType, final boolean isMergeable) { + this(name, type, objectType, isMergeable, -1); + } + + /** + * @param name + * @param type + * @param isListType + * @param isMergeable + * @param fixedSize + */ + protected MyAbstractTemplate(final String name, final Class type, + final ObjectType objectType, final boolean isMergeable, + final int fixedSize) { + this(name, type, objectType, isMergeable, fixedSize, false); + } + + /** + * @param name + * @param type + * @param isListType + * @param isMergeable + * @param fixedSize + */ + protected MyAbstractTemplate(final String name, final Class type, + final ObjectType objectType, final boolean isMergeable, + final int fixedSize, final boolean isFallBackTemplate) { + super(name, type, 0, objectType, toTrackingType(isMergeable), + fixedSize, isFallBackTemplate); + allList.add(this); + } + } + + /** + * AbstractTemplate for primitive arrays. + * + * Primitive array templates do NOT pre-create, because by definition, + * primitive arrays cannot cause cycles. + */ + private abstract class MyPrimitiveArrayAbstractTemplate extends + MyAbstractTemplate { + + /** + * @param id + * @param type + */ + protected MyPrimitiveArrayAbstractTemplate(final String name, + final Class type) { + super(name, type, ObjectType.ARRAY, false); + } + } + + /** + * The Abstract Collection template. + * + * It is a complete Collection Template implementation, except for + * preCreate(int) + * + * The collection is stored as an "array" object. + */ + @SuppressWarnings("rawtypes") + private abstract class AbstractCollectionTemplate + extends MyAbstractTemplate { + + /** + * @param id + * @param type + */ + protected AbstractCollectionTemplate(final String name, + final Class type) { + super(name, type, ObjectType.ARRAY, false); + } + + /** Writes the collection. */ + @Override + public final void writeData(final PackerContext context, + final int size, final C value) throws IOException { + final ObjectPacker p = context.objectPacker; + for (final Object o : value) { + p.writeObject(o); + } + } + + /** Reads the collection. */ + @SuppressWarnings("unchecked") + @Override + public final C readData(final UnpackerContext context, + final C preCreated, final int size) throws IOException { + final ObjectUnpacker ou = context.objectUnpacker; + final Collection c = preCreated; + for (int i = 0; i < size; i++) { + c.add(ou.readObject()); + } + return preCreated; + } + + /** Computes the size. */ + @Override + public final int getSpaceRequired(final PackerContext context, final C v) { + return v.size(); + } + + /** The only thing you need to implement. */ + @Override + public abstract C preCreate(final int size); + }; + + /** + * The Abstract Map template. + * + * It is a complete Map Template implementation, except for + * preCreate(int) + * + * The Map is stored as a "map" object. + */ + @SuppressWarnings("rawtypes") + private abstract class AbstractMapTemplate extends + MyAbstractTemplate { + + /** + * @param id + * @param type + */ + protected AbstractMapTemplate(final String name, final Class type) { + super(name, type, ObjectType.MAP, false); + } + + /** Writes the Map */ + @SuppressWarnings("unchecked") + @Override + public final void writeData(final PackerContext context, + final int size, final M value) throws IOException { + final ObjectPacker p = context.objectPacker; + for (final Map.Entry o : (Set) value.entrySet()) { + p.writeObject(o.getKey()); + p.writeObject(o.getValue()); + } + } + + /** Reads the Map */ + @SuppressWarnings("unchecked") + @Override + public final M readData(final UnpackerContext context, + final M preCreated, final int size) throws IOException { + readHeaderValue(context, preCreated, size); + final ObjectUnpacker ou = context.objectUnpacker; + final Map c = preCreated; + for (int i = 0; i < size; i++) { + final Object key = ou.readObject(); + final Object value = ou.readObject(); + c.put(key, value); + } + return preCreated; + } + + /** Computes the size; (key+value) counts as *1*. */ + @Override + public final int getSpaceRequired(final PackerContext context, final M v) { + return v.size(); + } + + /** The only thing you need to implement. */ + @Override + public abstract M preCreate(final int size); + }; + + /** + * AbstractTemplate for primitive arrays. + * + * Primitive array templates do NOT pre-create, because by definition, + * primitive arrays cannot cause cycles. + */ + private final class MyNonSerialisableTemplate extends + MyAbstractTemplate { + + /** + * @param id + * @param type + */ + protected MyNonSerialisableTemplate(final Class type) { + super(null, type, ObjectType.ARRAY, true); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.templates.Template#writeData(com.blockwithme.msgpack.templates.PackerContext, int, java.lang.Object) + */ + @Override + public void writeData(final PackerContext context, final int size, + final T v) throws IOException { + throw new IOException(this + " does not support serialisation!"); + } + + /* (non-Javadoc) + * @see com.blockwithme.msgpack.templates.Template#readData(com.blockwithme.msgpack.templates.UnpackerContext, java.lang.Object, int) + */ + @Override + public T readData(final UnpackerContext context, final T preCreated, + final int size) throws IOException { + throw new IOException(this + " does not support serialisation!"); + } + } + + /** Never instantiated. */ + public BasicTemplates() { + // NOP + } + + /** Registers a non-serialisable type. */ + private void registerTypeID(final Class type) { + new MyNonSerialisableTemplate(type); + } + + /** List of all basic templates. */ + private final List> allList = new ArrayList>(); + + /** All templates. */ + private Template[] ALL; + + /** + * The Object template. + * + * WARNING: This is the (this.getClass() == java.lang.Object.class) template, + * not the "anything and everything" template!!! + */ + public final Template OBJECT = new MyAbstractTemplate(null, + Object.class, ObjectType.ARRAY, false) { + @Override + public int getSpaceRequired(final PackerContext context, final Object v) { + return 0; + } + + @Override + public void writeData(final PackerContext context, final int size, + final Object v) throws IOException { + // Objects have no data, therefore, there is nothing to write! + } + + @Override + public Object readData(final UnpackerContext context, + final Object preCreated, final int size) throws IOException { + // Objects have no data, therefore, there is nothing to read! + return new Object(); + } + }; + + /** The Class template. */ + @SuppressWarnings("rawtypes") + public final Template CLASS = new MyAbstractTemplate(null, + Class.class, ObjectType.ARRAY, true) { + + @SuppressWarnings("unchecked") + @Override + public int getSpaceRequired(final PackerContext context, final Class v) { + // Space depends on if this is a class that we use while serializing. + return (context.findTemplate(v) == null) ? 3 : 1; + } + + @SuppressWarnings("unchecked") + @Override + public void writeData(final PackerContext context, final int size, + final Class value) throws IOException { + final Template t = context.findTemplate(value); + if (t == null) { + // Not a serialisable class! + context.packer.writeNil(); + // We split package and name, so that we hope for reuse of + // package name, and therefore space saving. + final String fqname = AbstractTemplate.getClassNameConverter() + .getName(value); + final int index = fqname.lastIndexOf('.'); + if (index > 0) { + context.objectPacker + .writeObject(fqname.substring(0, index)); + context.objectPacker.writeObject(fqname + .substring(index + 1)); + } else { + // Primitive type?!? + context.objectPacker.writeObject(""); + context.objectPacker.writeObject(fqname); + } + } else { + // A serializable class + final int depth = AbstractTemplate.getArrayDepth(value); + context.packer.writeIndex(t.getID() * 4 + depth); + } + } + + @Override + public Class readData(final UnpackerContext context, + final Class preCreated, final int size) throws IOException { + if (context.unpacker.trySkipNil()) { + // OK, a non-serializable Class + final String pkg = context.objectUnpacker.readString(); + final String cls = context.objectUnpacker.readString(); + if (pkg.isEmpty()) { + return AbstractTemplate.getClassNameConverter().getClass( + cls); + } + return AbstractTemplate.getClassNameConverter().getClass( + pkg + '.' + cls); + } + // A serialisable class + final int id = context.unpacker.readIndex(); + return context.getTemplate(id / 4).getType(); + } + }; + + /** The Boolean Wrapper template. */ + public final Template BOOLEAN = new MyAbstractTemplate( + null, Boolean.class, ObjectType.ARRAY, true, 1) { + @Override + public void writeData(final PackerContext context, final int size, + final Boolean value) throws IOException { + context.packer.writeBoolean(value.booleanValue()); + } + + @Override + public Boolean readData(final UnpackerContext context, + final Boolean preCreated, final int size) throws IOException { + return context.unpacker.readBoolean(); + } + }; + + /** The Byte Wrapper template. */ + public final Template BYTE = new MyAbstractTemplate(null, + Byte.class, ObjectType.ARRAY, true, 1) { + @Override + public void writeData(final PackerContext context, final int size, + final Byte value) throws IOException { + context.packer.writeByte(value.byteValue()); + } + + @Override + public Byte readData(final UnpackerContext context, + final Byte preCreated, final int size) throws IOException { + return context.unpacker.readByte(); + } + }; + + /** The Short Wrapper template. */ + public final Template SHORT = new MyAbstractTemplate(null, + Short.class, ObjectType.ARRAY, true, 1) { + @Override + public void writeData(final PackerContext context, final int size, + final Short value) throws IOException { + context.packer.writeShort(value.shortValue()); + } + + @Override + public Short readData(final UnpackerContext context, + final Short preCreated, final int size) throws IOException { + return context.unpacker.readShort(); + } + }; + + /** The Character Wrapper template. */ + public final Template CHARACTER = new MyAbstractTemplate( + null, Character.class, ObjectType.ARRAY, true, 1) { + @Override + public void writeData(final PackerContext context, final int size, + final Character value) throws IOException { + context.packer.writeChar(value.charValue()); + } + + @Override + public Character readData(final UnpackerContext context, + final Character preCreated, final int size) throws IOException { + return context.unpacker.readChar(); + } + }; + + /** The Integer Wrapper template. */ + public final Template INTEGER = new MyAbstractTemplate( + null, Integer.class, ObjectType.ARRAY, true, 1) { + @Override + public void writeData(final PackerContext context, final int size, + final Integer value) throws IOException { + context.packer.writeInt(value.intValue()); + } + + @Override + public Integer readData(final UnpackerContext context, + final Integer preCreated, final int size) throws IOException { + return context.unpacker.readInt(); + } + }; + + /** The Long Wrapper template. */ + public final Template LONG = new MyAbstractTemplate(null, + Long.class, ObjectType.ARRAY, true, 1) { + @Override + public void writeData(final PackerContext context, final int size, + final Long value) throws IOException { + context.packer.writeLong(value.longValue()); + } + + @Override + public Long readData(final UnpackerContext context, + final Long preCreated, final int size) throws IOException { + return context.unpacker.readLong(); + } + }; + + /** The Float Wrapper template. */ + public final Template FLOAT = new MyAbstractTemplate(null, + Float.class, ObjectType.ARRAY, true, 1) { + @Override + public void writeData(final PackerContext context, final int size, + final Float value) throws IOException { + context.packer.writeFloat(value.floatValue()); + } + + @Override + public Float readData(final UnpackerContext context, + final Float preCreated, final int size) throws IOException { + return context.unpacker.readFloat(); + } + }; + + /** The Double Wrapper template. */ + public final Template DOUBLE = new MyAbstractTemplate(null, + Double.class, ObjectType.ARRAY, true, 1) { + @Override + public void writeData(final PackerContext context, final int size, + final Double value) throws IOException { + context.packer.writeDouble(value.doubleValue()); + } + + @Override + public Double readData(final UnpackerContext context, + final Double preCreated, final int size) throws IOException { + return context.unpacker.readDouble(); + } + }; + + /** The BigInteger template. */ + public final Template BIG_INTEGER = new MyAbstractTemplate( + null, BigInteger.class, ObjectType.ARRAY, true, 1) { + @Override + public void writeData(final PackerContext context, final int size, + final BigInteger value) throws IOException { + context.packer.writeBigInteger(value); + } + + @Override + public BigInteger readData(final UnpackerContext context, + final BigInteger preCreated, final int size) throws IOException { + return context.unpacker.readBigInteger(); + } + }; + + /** The BigDecimal template. */ + public final Template BIG_DECIMAL = new MyAbstractTemplate( + null, BigDecimal.class, ObjectType.ARRAY, true, 1) { + @Override + public void writeData(final PackerContext context, final int size, + final BigDecimal value) throws IOException { + context.packer.writeBigDecimal(value); + } + + @Override + public BigDecimal readData(final UnpackerContext context, + final BigDecimal preCreated, final int size) throws IOException { + return context.unpacker.readBigDecimal(); + } + }; + + /** The String template. */ + public final Template STRING = new MyAbstractTemplate(null, + String.class, ObjectType.RAW, true, 1) { + @Override + public void writeData(final PackerContext context, final int size, + final String value) throws IOException { + context.packer.writeUTF(value); + } + + @Override + public String readData(final UnpackerContext context, + final String preCreated, final int size) throws IOException { + return context.unpacker.readUTF(); + } + }; + + /** The ByteBuffer template. */ + public final Template BYTE_BUFFER = new MyAbstractTemplate( + null, ByteBuffer.class, ObjectType.RAW, true, 1) { + @Override + public void writeData(final PackerContext context, final int size, + final ByteBuffer value) throws IOException { + context.packer.writeByteBuffer(value); + } + + @Override + public ByteBuffer readData(final UnpackerContext context, + final ByteBuffer preCreated, final int size) throws IOException { + return context.unpacker.readByteBuffer(); + } + }; + + /** The Date template. */ + public final Template DATE = new MyAbstractTemplate(null, + Date.class, ObjectType.ARRAY, true, 1) { + @Override + public void writeData(final PackerContext context, final int size, + final Date value) throws IOException { + context.packer.writeDate(value); + } + + @Override + public Date readData(final UnpackerContext context, + final Date preCreated, final int size) throws IOException { + return new Date(context.unpacker.readLong()); + } + }; + + /** The Boolean array template. */ + public final Template BOOLEAN_ARRAY = new MyPrimitiveArrayAbstractTemplate( + null, boolean[].class) { + @Override + public void writeData(final PackerContext context, final int size, + final boolean[] value) throws IOException { + final Packer p = context.packer; + for (final boolean a : value) { + p.writeBoolean(a); + } + } + + @Override + public boolean[] readData(final UnpackerContext context, + final boolean[] preCreated, final int size) throws IOException { + final Unpacker u = context.unpacker; + final boolean[] result = new boolean[size]; + for (int i = 0; i < size; i++) { + result[i] = u.readBoolean(); + } + return result; + } + + @Override + public int getSpaceRequired(final PackerContext context, + final boolean[] v) { + return v.length; + } + }; + + /** The Byte array template. */ + public final Template BYTE_ARRAY = new MyAbstractTemplate( + null, byte[].class, ObjectType.RAW, false, 1) { + @Override + public void writeData(final PackerContext context, final int size, + final byte[] value) throws IOException { + context.packer.write(value); + } + + @Override + public byte[] readData(final UnpackerContext context, + final byte[] preCreated, final int size) throws IOException { + return context.unpacker.readByteArray(); + } + }; + + /** The Short array template. */ + public final Template SHORT_ARRAY = new MyPrimitiveArrayAbstractTemplate( + null, short[].class) { + @Override + public void writeData(final PackerContext context, final int size, + final short[] value) throws IOException { + final Packer p = context.packer; + for (final short a : value) { + p.writeShort(a); + } + } + + @Override + public short[] readData(final UnpackerContext context, + final short[] preCreated, final int size) throws IOException { + final Unpacker u = context.unpacker; + final short[] result = new short[size]; + for (int i = 0; i < size; i++) { + result[i] = u.readShort(); + } + return result; + } + + @Override + public int getSpaceRequired(final PackerContext context, final short[] v) { + return v.length; + } + }; + + /** The Character array template. */ + public final Template CHAR_ARRAY = new MyPrimitiveArrayAbstractTemplate( + null, char[].class) { + @Override + public void writeData(final PackerContext context, final int size, + final char[] value) throws IOException { + final Packer p = context.packer; + for (final char a : value) { + p.writeChar(a); + } + } + + @Override + public char[] readData(final UnpackerContext context, + final char[] preCreated, final int size) throws IOException { + final Unpacker u = context.unpacker; + final char[] result = new char[size]; + for (int i = 0; i < size; i++) { + result[i] = u.readChar(); + } + return result; + } + + @Override + public int getSpaceRequired(final PackerContext context, final char[] v) { + return v.length; + } + }; + + /** The Integer array template. */ + public final Template INT_ARRAY = new MyPrimitiveArrayAbstractTemplate( + null, int[].class) { + @Override + public void writeData(final PackerContext context, final int size, + final int[] value) throws IOException { + final Packer p = context.packer; + for (final int a : value) { + p.writeInt(a); + } + } + + @Override + public int[] readData(final UnpackerContext context, + final int[] preCreated, final int size) throws IOException { + final Unpacker u = context.unpacker; + final int[] result = new int[size]; + for (int i = 0; i < size; i++) { + result[i] = u.readInt(); + } + return result; + } + + @Override + public int getSpaceRequired(final PackerContext context, final int[] v) { + return v.length; + } + }; + + /** The Long array template. */ + public final Template LONG_ARRAY = new MyPrimitiveArrayAbstractTemplate( + null, long[].class) { + @Override + public void writeData(final PackerContext context, final int size, + final long[] value) throws IOException { + final Packer p = context.packer; + for (final long a : value) { + p.writeLong(a); + } + } + + @Override + public long[] readData(final UnpackerContext context, + final long[] preCreated, final int size) throws IOException { + final Unpacker u = context.unpacker; + final long[] result = new long[size]; + for (int i = 0; i < size; i++) { + result[i] = u.readLong(); + } + return result; + } + + @Override + public int getSpaceRequired(final PackerContext context, final long[] v) { + return v.length; + } + }; + + /** The Float array template. */ + public final Template FLOAT_ARRAY = new MyPrimitiveArrayAbstractTemplate( + null, float[].class) { + @Override + public void writeData(final PackerContext context, final int size, + final float[] value) throws IOException { + final Packer p = context.packer; + for (final float a : value) { + p.writeFloat(a); + } + } + + @Override + public float[] readData(final UnpackerContext context, + final float[] preCreated, final int size) throws IOException { + final Unpacker u = context.unpacker; + final float[] result = new float[size]; + for (int i = 0; i < size; i++) { + result[i] = u.readFloat(); + } + return result; + } + + @Override + public int getSpaceRequired(final PackerContext context, final float[] v) { + return v.length; + } + }; + + /** The Double array template. */ + public final Template DOUBLE_ARRAY = new MyPrimitiveArrayAbstractTemplate( + null, double[].class) { + @Override + public void writeData(final PackerContext context, final int size, + final double[] value) throws IOException { + final Packer p = context.packer; + for (final double a : value) { + p.writeDouble(a); + } + } + + @Override + public double[] readData(final UnpackerContext context, + final double[] preCreated, final int size) throws IOException { + final Unpacker u = context.unpacker; + final double[] result = new double[size]; + for (int i = 0; i < size; i++) { + result[i] = u.readDouble(); + } + return result; + } + + @Override + public int getSpaceRequired(final PackerContext context, + final double[] v) { + return v.length; + } + }; + + /** + * The Byte array slice template. + * + * The generics cannot be used here, because we receive/write ByteArraySlice, + * but return/read byte[] + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public final Template BYTE_ARRAY_SLICE = new MyAbstractTemplate(null, + ByteArraySlice.class, ObjectType.RAW, false, 1) { + @Override + public void writeData(final PackerContext context, final int size, + final Object value) throws IOException { + if (value instanceof ByteArraySlice) { + final ByteArraySlice slice = (ByteArraySlice) value; + context.packer.write(slice.o, slice.off, slice.len); + } else { + throw new IllegalArgumentException( + "Expected ByteArraySlice but got " + value); + } + } + + @Override + public Object readData(final UnpackerContext context, + final Object preCreated, final int size) throws IOException { + return context.unpacker.readByteArray(); + } + }; + + /** The ArrayList template. */ + @SuppressWarnings("rawtypes") + public final Template JAVA_UTIL_ARRAY_LIST = new AbstractCollectionTemplate( + null, ArrayList.class) { + + @Override + public ArrayList preCreate(final int size) { + return new ArrayList(size); + } + }; + + /** The HashSet template. */ + @SuppressWarnings("rawtypes") + public final Template JAVA_UTIL_HASH_SET = new AbstractCollectionTemplate( + null, HashSet.class) { + + @Override + public HashSet preCreate(final int size) { + return new HashSet(size); + } + }; + + /** The HashMap template. */ + @SuppressWarnings("rawtypes") + public final Template JAVA_UTIL_HASH_MAP = new AbstractMapTemplate( + null, HashMap.class) { + + @Override + public HashMap preCreate(final int size) { + return new HashMap(size); + } + }; + + /** Returns count basic templates. */ + public synchronized Template[] getBasicTemplates(final int count) { + if (count < 0) { + throw new IllegalArgumentException("count: " + count); + } + if (ALL == null) { + registerTypeID(CharSequence.class); + ALL = allList.toArray(new Template[allList.size()]); + } + if (count == ALL.length) { + return ALL; + } + if (count > ALL.length) { + throw new IllegalArgumentException("count: " + count + " max: " + + ALL.length); + } + return Arrays.copyOf(ALL, count); + } +} diff --git a/src/main/java/com/blockwithme/msgpack/templates/ClassNameConverter.java b/src/main/java/com/blockwithme/msgpack/templates/ClassNameConverter.java new file mode 100644 index 000000000..75b81a712 --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/templates/ClassNameConverter.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2013 Sebastien Diot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blockwithme.msgpack.templates; + +/** + * Convert a Class to it's name and back. + * + * This type is required, to deal with OSGi deployments. + * + * @author monster + */ +public interface ClassNameConverter { + /** Returns the Name of a Class, which cannot be null. */ + String getName(final Class cls); + + /** Returns the Class for a name, which cannot be null. */ + Class getClass(final String name); + + /** + * Returns true if the type is (effectively) final. + * + * A class is effectively final if no other class being used extends it. + */ + boolean isFinal(final Class cls); +} diff --git a/src/main/java/com/blockwithme/msgpack/templates/Context.java b/src/main/java/com/blockwithme/msgpack/templates/Context.java new file mode 100644 index 000000000..eb0e6cd76 --- /dev/null +++ b/src/main/java/com/blockwithme/msgpack/templates/Context.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2013 Sebastien Diot. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blockwithme.msgpack.templates; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.blockwithme.msgpack.schema.Schema; +import com.blockwithme.msgpack.schema.SchemaManager; + +/** + * Represents the context information for this serialization. + * + * It is used mostly, to allow third-party extensions, that would give context + * information required by custom templates. + * + * @author monster + */ +public class Context { + + /** Is this a required field? (Currently unused) */ + public boolean required; + + /** The format version for the current (de)serialisation. */ + public int format = Schema.FORMAT; + + /** The schema version for the current (de)serialisation. */ + public int schemaID = -1; + + /** The schema manager */ + private final SchemaManager schemaManager; + + /** The schema */ + private Schema schema; + + /** + * All the templates, each at the right position. + * + * @param idToTemplate + */ + protected Context(final SchemaManager theSchemaManager) { + // Validate input + Objects.requireNonNull(theSchemaManager); + schemaManager = theSchemaManager; + } + + /** The schema */ + public final Schema getSchema() { + if (schema == null) { + schema = schemaManager.getSchema(format, schemaID); + } + return schema; + } + + /** Returns the Template for an ID, or fails. */ + public Template getTemplate(final int id) { + try { + final Template result = getSchema().idToTemplate[id]; + if (result != null) { + return result; + } + } catch (final ArrayIndexOutOfBoundsException e) { + // NOP + } + throw new IllegalArgumentException("Template not found for id: " + id); + } + + /** Un-array a class. */ + private Class unArray(final Class cls) { + if (cls == null) { + throw new IllegalArgumentException("Class cannot be null"); + } + // Object array is a special case + Class c = cls; + Class cc; + while (c.isArray() && !(cc = c.getComponentType()).isPrimitive()) { + c = cc; + } + return c; + } + + /** Returns the ID for a Class, or null if not found. */ + @SuppressWarnings("unchecked") + public Template findTemplate(final Class cls) { + final Template result = (Template) getSchema().classToTemplate + .get(unArray(cls)); + if (result == null) { + for (final Template f : getSchema().fallbackTemplates) { + if (f.getType().isAssignableFrom(cls)) { + return (Template) f; + } + } + } + return result; + } + + /** Returns the ID for a Class. */ + public Template getTemplate(final Class cls) { + final Template t = findTemplate(cls); + if (t == null) { + throw new IllegalArgumentException("Template not found: " + cls); + } + return t; + } + + /** Returns the ID for a Class, or null if not found. */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Template[] findTemplates(final Class cls) { + final Class c = unArray(cls); + final List