--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2010 Association for Decentralized Information Management in\r
+ * Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.databoard.serialization;
+
+import gnu.trove.map.hash.TObjectIntHashMap;\r
+\r
+import java.io.ByteArrayInputStream;\r
+import java.io.DataInput;\r
+import java.io.DataOutput;\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.io.InputStream;\r
+import java.io.OutputStream;\r
+import java.nio.ByteBuffer;\r
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
+import org.simantics.databoard.Files;\r
+import org.simantics.databoard.util.binary.BinaryFile;\r
+import org.simantics.databoard.util.binary.BinaryReadable;\r
+import org.simantics.databoard.util.binary.ByteBufferReadable;\r
+import org.simantics.databoard.util.binary.ByteBufferWriteable;\r
+import org.simantics.databoard.util.binary.InputStreamReadable;\r
+import org.simantics.databoard.util.binary.OutputStreamWriteable;\r
+
+/**\r
+ * Databoard binary serializer. \r
+ *\r
+ * @author Toni Kalajainen <toni.kalajainen@vtt.fi>\r
+ */
+public abstract class Serializer {
+
+ /**
+ * Serialize obj to out.
+ *
+ * The identities argument is a map of identities (in the binary block) and objects
+ * that have already been serialized. Once serialized, an object is added to the map.
+ * Typically an empty map is provided. If the type has no recursion, i.e. Referable \r
+ * Records, a <code>null</code> value can be provided.
+ *
+ * @param out
+ * @param identities Thread local empty map or <code>null</code> if there is no recursion
+ * @param obj
+ * @throws IOException
+ */
+ public abstract void serialize(DataOutput out, TObjectIntHashMap<Object> identities, Object obj) throws IOException;
+ public abstract void serialize(DataOutput out, Object obj) throws IOException;
+ public void serialize(OutputStream out, Object obj) throws IOException\r
+ {\r
+ OutputStreamWriteable writ = new OutputStreamWriteable(out);\r
+ serialize(writ, obj); \r
+ }\r
+
+ /**
+ * Deserialize an object from a readable.
+ *
+ * The identities argument is a list of identities (in the binary block) of objects
+ * that have already been deserialized. Once deserialized they are added to the list.
+ * Typically an empty list is provided. If the type has no recursion, i.e. Referable\r
+ * Records, a <code>null</code> value can be provided.<p>\r
+ * \r
+ * Note, if in argument is instanceof BinaryReadable or RandomAccessBinary, \r
+ * the serializer performs extra protection against malformed data when \r
+ * deserializing arrays and maps. This prevents the serializer from \r
+ * instanting potentially out-of-memory-invoking huge arrays. For example, \r
+ * if data data says array size is 0xffffffff (-1), 4GB is allocated -> \r
+ * out of memory exception -> unhandled runtime error. BinaryReadable has \r
+ * length limit which allowes serializer to estimate whether future data is \r
+ * readable.
+ *
+ * @param in DataInput, BinaryReadable or RandomAccessBinary
+ * @param identities empty identities array or <code>null</code> if there is no recursion
+ * @return the instance
+ * @throws IOException
+ */
+ public abstract Object deserialize(DataInput in, List<Object> identities) throws IOException;
+ public abstract Object deserialize(DataInput in) throws IOException;
+\r
+ \r
+ /**\r
+ * Deserialize into an existing instance. This method writes over previous values.\r
+ * \r
+ * @param in\r
+ * @param identities\r
+ * @param dst valid object\r
+ * @throws IOException\r
+ */
+ public abstract void deserializeTo(DataInput in, List<Object> identities, Object dst) throws IOException;\r
+ public abstract void deserializeTo(DataInput in, Object dst) throws IOException;\r
+ \r
+ /**\r
+ * Attempt deserialize to existing instance. Creates new if not possible. \r
+ * \r
+ * @param in\r
+ * @param identities\r
+ * @param dst\r
+ * @return dst or new obj\r
+ * @throws IOException\r
+ */\r
+ public Object deserializeToTry(DataInput in, List<Object> identities, Object dst) throws IOException \r
+ {\r
+ deserializeTo(in, identities, dst);\r
+ return dst; \r
+ }\r
+
+ /**
+ * Deserialize the next object in an input stream.
+ * Note, if multiple objects are deserialized from the same stream, it is
+ * more efficient to instantiate InputStreamReadable and identities only once,\r
+ * and use {@link #deserialize(DataInput, List)}.
+ *
+ * @param in
+ * @return The object deserialized into a Java Object
+ * @throws IOException
+ */
+ public Object deserialize(InputStream in) throws IOException
+ {
+ // InputStreamReadable adapts InputStream to BinaryReadable&DataInput\r
+ InputStreamReadable read = new InputStreamReadable(in, Long.MAX_VALUE);
+ return deserialize(read);
+ }\r
+ \r
+ /**\r
+ * Deserialize from an input stream into an object.\r
+ * Note, if multiple objects are deserialized from the same stream, it is \r
+ * more efficient to instantiate InputStreamReadable and identities only once,\r
+ * and use {@link #deserialize(DataInput, List)}.\r
+ * \r
+ * @param in\r
+ * @param obj a valid object\r
+ * @throws IOException\r
+ */\r
+ public void deserialize(InputStream in, Object obj) throws IOException\r
+ {\r
+ // InputStreamReadable adapts InputStream to BinaryReadable&DataInput\r
+ InputStreamReadable read = new InputStreamReadable(in, Long.MAX_VALUE);\r
+ deserializeTo(read, obj);\r
+ } \r
+ \r
+ /**\r
+ * Deserialize object from a file. \r
+ * \r
+ * @param file source file\r
+ * @return the object\r
+ * @throws IOException\r
+ */\r
+ public Object deserialize(File file) throws IOException\r
+ {\r
+ BinaryFile f = new BinaryFile(file);\r
+ try {\r
+ return deserialize(f);\r
+ } finally {\r
+ f.close();\r
+ } \r
+ }\r
+ \r
+ /**\r
+ * Deserialize a file into a valid object. This method writes over previous values.\r
+ * \r
+ * @param file source file\r
+ * @param obj a dst valid object\r
+ * @throws IOException\r
+ */\r
+ public void deserialize(File file, Object obj) throws IOException\r
+ {\r
+ BinaryFile f = new BinaryFile(file);\r
+ try {\r
+ deserializeTo(f, obj);\r
+ } finally {\r
+ f.close();\r
+ } \r
+ }
+
+ /**\r
+ * Deserialize an object in byte[] format.
+ *
+ * @param data
+ * @return the instance
+ * @throws IOException
+ */
+ public Object deserialize(byte[] data) throws IOException
+ {
+ ByteBuffer buffer = ByteBuffer.wrap( data );
+ ByteBufferReadable readable = new ByteBufferReadable( buffer );
+ return deserialize(readable);
+ }\r
+ \r
+ /**\r
+ * Deserialize byte[] into a valid object.\r
+ * \r
+ * @param data\r
+ * @param obj dst valid object\r
+ * @throws IOException\r
+ */\r
+ public void deserialize(byte[] data, Object obj) throws IOException\r
+ {\r
+ ByteBuffer buffer = ByteBuffer.wrap( data );\r
+ ByteBufferReadable readable = new ByteBufferReadable( buffer );\r
+ deserializeTo(readable, obj);\r
+ }
+
+ /**
+ * Skip over an object in a stream. This method deserializes the object
+ * without producing a result or reading thru all bytes.
+ *
+ * @param in
+ * @param identities
+ * @throws IOException
+ */
+ public abstract void skip(DataInput in, List<Object> identities) throws IOException;
+ public abstract void skip(DataInput in) throws IOException;
+
+ /**
+ * Skip over an object in a stream. This method deserializes the object
+ * without producing a result or reading thru all bytes.
+ *
+ * @param in
+ * @throws IOException
+ */
+ public void skip(InputStream in) throws IOException
+ {
+ InputStreamReadable read = new InputStreamReadable(in, Long.MAX_VALUE);
+ skip(read);
+ }
+
+ /**
+ * Get constant size of the data type in its binary serialized format
+ *
+ * @return size in bytes or null if not fixed
+ */
+ public abstract Integer getConstantSize();
+
+ /**\r
+ * Get the number of bytes required to serialize an object
+ *
+ * @param obj
+ * @param identities thread local empty hash map
+ * @return number of bytes required to serialize obj
+ * @throws IOException
+ */
+ public abstract int getSize(Object obj, TObjectIntHashMap<Object> identities) throws IOException;
+ public abstract int getSize(Object obj) throws IOException;
+\r
+ public abstract int getMinSize();\r
+
+ /**
+ * Serializes an object to a byte[].
+ *
+ * @param obj
+ * @return byte array containing the obj in its serialized format.
+ * @throws IOException
+ */
+ public byte[] serialize(Object obj) throws IOException
+ {
+ TObjectIntHashMap<Object> identities = new TObjectIntHashMap<Object>();
+ int size = getSize(obj, identities);
+ identities.clear();
+ ByteBuffer buffer = ByteBuffer.allocate( size );
+ DataOutput writable = new ByteBufferWriteable( buffer );
+ serialize(writable, identities, obj);
+ buffer.rewind();
+ return buffer.array();
+ }
+
+ /**
+ * Serializes an object to an output stream.
+ * Note, if multiple objects are serialized to the same stream, it is
+ * more efficient to instantiate OutputStreamWriteable and identities only once.
+ *
+ * @param obj
+ * @param out
+ * @throws IOException
+ */
+ public void serialize(Object obj, OutputStream out) throws IOException
+ {\r
+ // OutputStreamWriteable adapts OutputStream to DataOutput&BinaryWritable
+ OutputStreamWriteable writ = new OutputStreamWriteable(out);
+ TObjectIntHashMap<Object> identities = new TObjectIntHashMap<Object>();
+ serialize(writ, identities, obj);
+ }\r
+ \r
+ /**\r
+ * Serialize an object to a file. Note the type info is not written to the\r
+ * file (unless obj is variant), and therefore is not compatible as .dbb \r
+ * file. \r
+ * \r
+ * Databoard Binary file (.dbb) is a binary file that has datatype in the \r
+ * header. To create .dbb file, serialize Datatype and then the value.\r
+ * Or use methods in {@link Files} for convenience. Variant objects are, by\r
+ * nature, .dbb compatible. \r
+ * \r
+ * @param obj\r
+ * @param file\r
+ * @throws IOException\r
+ */\r
+ public void serialize(Object obj, File file)\r
+ throws IOException\r
+ {\r
+ TObjectIntHashMap<Object> identities = new TObjectIntHashMap<Object>();\r
+ BinaryFile writable = new BinaryFile( file );\r
+ try {\r
+ serialize(writable, identities, obj);\r
+ } finally {\r
+ writable.close();\r
+ }\r
+ }
+
+ /**
+ * Get object as readable Input Stream.
+ *
+ * @param obj
+ * @return input stream
+ * @throws IOException
+ */
+ public InputStream getInputStream(Object obj) throws IOException
+ {
+ // Trivial implementation - better implementation would code bytes on-demend\r
+ // without memory consumption.
+ byte[] data = serialize(obj);
+ return new ByteArrayInputStream(data);
+ }
+
+ /**
+ * Serializer for data types that have referable objects
+ */
+ public static abstract class RecursiveSerializer extends Serializer {\r
+ \r
+ /**\r
+ * Finalize the construction of the serializer. This is called once all component\r
+ * serializers are constructed.\r
+ */\r
+ public abstract void finalizeConstruction();\r
+
+ public void serialize(DataOutput out, Object obj) throws IOException {
+ TObjectIntHashMap<Object> identities = new TObjectIntHashMap<Object>(0);
+ serialize(out, identities, obj);
+ }
+ public Object deserialize(DataInput in) throws IOException {
+ List<Object> identities = new ArrayList<Object>(0);
+ return deserialize(in, identities);
+ }
+ public void deserializeTo(DataInput in, Object obj) throws IOException {\r
+ List<Object> identities = new ArrayList<Object>(0);\r
+ deserializeTo(in, identities, obj);\r
+ }\r
+ @Override
+ public void skip(DataInput in) throws IOException {
+ List<Object> identities = new ArrayList<Object>(0);
+ skip(in, identities);
+ }
+ @Override
+ public int getSize(Object obj)
+ throws IOException {
+ TObjectIntHashMap<Object> identities = new TObjectIntHashMap<Object>(0);
+ return getSize(obj, identities);
+ }
+ }
+
+ /**
+ * Serializer for non-recursive data types
+ */
+ public static abstract class NonRecursiveSerializer extends Serializer {
+ public void serialize(DataOutput out, TObjectIntHashMap<Object> identities, Object obj) throws IOException {
+ serialize(out, obj);
+ }
+ public Object deserialize(DataInput in, List<Object> identities) throws IOException {
+ return deserialize(in);
+ }\r
+ public void deserializeTo(DataInput in, List<Object> identities, Object obj) throws IOException {\r
+ deserializeTo(in, obj);\r
+ }
+ @Override
+ public void skip(DataInput in, List<Object> identities) throws IOException {
+ skip(in);
+ }
+ @Override
+ public int getSize(Object obj, TObjectIntHashMap<Object> identities) throws IOException {
+ return getSize(obj);
+ }
+ }
+
+ /**
+ * Serializer for composite data types
+ */
+ public static abstract class CompositeSerializer extends Serializer {
+ boolean recursive;
+ protected CompositeSerializer(boolean recursive) {
+ this.recursive = recursive;
+ }\r
+ \r
+ /**\r
+ * Finalize the construction of the serializer. This is called once all component\r
+ * serializers are constructed.\r
+ */\r
+ public abstract void finalizeConstruction( );\r
+
+ public void serialize(DataOutput out, Object obj) throws IOException {
+ TObjectIntHashMap<Object> identities = recursive ? new TObjectIntHashMap<Object>(0) : null;
+ serialize(out, identities, obj);
+ }
+ public Object deserialize(DataInput in) throws IOException {\r
+ List<Object> identities = recursive ? new ArrayList<Object>(0) : null;\r
+ return deserialize(in, identities);\r
+ }\r
+ public void deserializeTo(DataInput in, Object obj) throws IOException {\r
+ List<Object> identities = recursive ? new ArrayList<Object>(0) : null;\r
+ deserializeTo(in, identities, obj);\r
+ }\r
+ @Override
+ public void skip(DataInput in) throws IOException {
+ List<Object> identities = recursive ? new ArrayList<Object>(0) : null;
+ skip(in, identities);
+ }
+ @Override
+ public int getSize(Object obj)
+ throws IOException {
+ TObjectIntHashMap<Object> identities = recursive ? new TObjectIntHashMap<Object>(0) : null;
+ return getSize(obj, identities);
+ }
+ }
+\r
+ /**\r
+ * Assert there are enough readable bytes. This method works only if input \r
+ * object is instance of BinaryReadable. DataInput cannot tell the \r
+ * number of remaining bytes. \r
+ * \r
+ * This method is used by array, map, record and union sub-classes.\r
+ * \r
+ * @param in\r
+ * @throws IOException\r
+ */\r
+ protected void assertRemainingBytes(DataInput in, long bts) throws IOException {\r
+ if (in instanceof BinaryReadable == false) return;\r
+ BinaryReadable r = (BinaryReadable) in;\r
+ if (bts > r.length() - r.position()) throw new SerializationException("Malformed data. Serialization aborted. (Wrong binding?)");\r
+ }\r
+
+}
+