/******************************************************************************* * Copyright (c) 2010 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.databoard.serialization; import gnu.trove.map.hash.TObjectIntHashMap; import java.io.ByteArrayInputStream; import java.io.DataInput; import java.io.DataOutput; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import org.simantics.databoard.Files; import org.simantics.databoard.util.binary.BinaryFile; import org.simantics.databoard.util.binary.BinaryReadable; import org.simantics.databoard.util.binary.ByteBufferReadable; import org.simantics.databoard.util.binary.ByteBufferWriteable; import org.simantics.databoard.util.binary.InputStreamReadable; import org.simantics.databoard.util.binary.OutputStreamWriteable; /** * Databoard binary serializer. * * @author Toni Kalajainen */ 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 * Records, a null value can be provided. * * @param out * @param identities Thread local empty map or null if there is no recursion * @param obj * @throws IOException */ public abstract void serialize(DataOutput out, TObjectIntHashMap identities, Object obj) throws IOException; public abstract void serialize(DataOutput out, Object obj) throws IOException; public void serialize(OutputStream out, Object obj) throws IOException { OutputStreamWriteable writ = new OutputStreamWriteable(out); serialize(writ, obj); } /** * 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 * Records, a null value can be provided.

* * Note, if in argument is instanceof BinaryReadable or RandomAccessBinary, * the serializer performs extra protection against malformed data when * deserializing arrays and maps. This prevents the serializer from * instanting potentially out-of-memory-invoking huge arrays. For example, * if data data says array size is 0xffffffff (-1), 4GB is allocated -> * out of memory exception -> unhandled runtime error. BinaryReadable has * length limit which allowes serializer to estimate whether future data is * readable. * * @param in DataInput, BinaryReadable or RandomAccessBinary * @param identities empty identities array or null if there is no recursion * @return the instance * @throws IOException */ public abstract Object deserialize(DataInput in, List identities) throws IOException; public abstract Object deserialize(DataInput in) throws IOException; /** * Deserialize into an existing instance. This method writes over previous values. * * @param in * @param identities * @param dst valid object * @throws IOException */ public abstract void deserializeTo(DataInput in, List identities, Object dst) throws IOException; public abstract void deserializeTo(DataInput in, Object dst) throws IOException; /** * Attempt deserialize to existing instance. Creates new if not possible. * * @param in * @param identities * @param dst * @return dst or new obj * @throws IOException */ public Object deserializeToTry(DataInput in, List identities, Object dst) throws IOException { deserializeTo(in, identities, dst); return dst; } /** * 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, * 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 InputStreamReadable read = new InputStreamReadable(in, Long.MAX_VALUE); return deserialize(read); } /** * Deserialize from an input stream into an object. * Note, if multiple objects are deserialized from the same stream, it is * more efficient to instantiate InputStreamReadable and identities only once, * and use {@link #deserialize(DataInput, List)}. * * @param in * @param obj a valid object * @throws IOException */ public void deserialize(InputStream in, Object obj) throws IOException { // InputStreamReadable adapts InputStream to BinaryReadable&DataInput InputStreamReadable read = new InputStreamReadable(in, Long.MAX_VALUE); deserializeTo(read, obj); } /** * Deserialize object from a file. * * @param file source file * @return the object * @throws IOException */ public Object deserialize(File file) throws IOException { BinaryFile f = new BinaryFile(file); try { return deserialize(f); } finally { f.close(); } } /** * Deserialize a file into a valid object. This method writes over previous values. * * @param file source file * @param obj a dst valid object * @throws IOException */ public void deserialize(File file, Object obj) throws IOException { BinaryFile f = new BinaryFile(file); try { deserializeTo(f, obj); } finally { f.close(); } } /** * 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); } /** * Deserialize byte[] into a valid object. * * @param data * @param obj dst valid object * @throws IOException */ public void deserialize(byte[] data, Object obj) throws IOException { ByteBuffer buffer = ByteBuffer.wrap( data ); ByteBufferReadable readable = new ByteBufferReadable( buffer ); deserializeTo(readable, obj); } /** * 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 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(); /** * 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 identities) throws IOException; public abstract int getSize(Object obj) throws IOException; public abstract int getMinSize(); /** * 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 identities = new TObjectIntHashMap(); 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 { // OutputStreamWriteable adapts OutputStream to DataOutput&BinaryWritable OutputStreamWriteable writ = new OutputStreamWriteable(out); TObjectIntHashMap identities = new TObjectIntHashMap(); serialize(writ, identities, obj); } /** * Serialize an object to a file. Note the type info is not written to the * file (unless obj is variant), and therefore is not compatible as .dbb * file. * * Databoard Binary file (.dbb) is a binary file that has datatype in the * header. To create .dbb file, serialize Datatype and then the value. * Or use methods in {@link Files} for convenience. Variant objects are, by * nature, .dbb compatible. * * @param obj * @param file * @throws IOException */ public void serialize(Object obj, File file) throws IOException { TObjectIntHashMap identities = new TObjectIntHashMap(); BinaryFile writable = new BinaryFile( file ); try { serialize(writable, identities, obj); } finally { writable.close(); } } /** * 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 // 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 { /** * Finalize the construction of the serializer. This is called once all component * serializers are constructed. */ public abstract void finalizeConstruction(); public void serialize(DataOutput out, Object obj) throws IOException { TObjectIntHashMap identities = new TObjectIntHashMap(0); serialize(out, identities, obj); } public Object deserialize(DataInput in) throws IOException { List identities = new ArrayList(0); return deserialize(in, identities); } public void deserializeTo(DataInput in, Object obj) throws IOException { List identities = new ArrayList(0); deserializeTo(in, identities, obj); } @Override public void skip(DataInput in) throws IOException { List identities = new ArrayList(0); skip(in, identities); } @Override public int getSize(Object obj) throws IOException { TObjectIntHashMap identities = new TObjectIntHashMap(0); return getSize(obj, identities); } } /** * Serializer for non-recursive data types */ public static abstract class NonRecursiveSerializer extends Serializer { public void serialize(DataOutput out, TObjectIntHashMap identities, Object obj) throws IOException { serialize(out, obj); } public Object deserialize(DataInput in, List identities) throws IOException { return deserialize(in); } public void deserializeTo(DataInput in, List identities, Object obj) throws IOException { deserializeTo(in, obj); } @Override public void skip(DataInput in, List identities) throws IOException { skip(in); } @Override public int getSize(Object obj, TObjectIntHashMap 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; } /** * Finalize the construction of the serializer. This is called once all component * serializers are constructed. */ public abstract void finalizeConstruction( ); public void serialize(DataOutput out, Object obj) throws IOException { TObjectIntHashMap identities = recursive ? new TObjectIntHashMap(0) : null; serialize(out, identities, obj); } public Object deserialize(DataInput in) throws IOException { List identities = recursive ? new ArrayList(0) : null; return deserialize(in, identities); } public void deserializeTo(DataInput in, Object obj) throws IOException { List identities = recursive ? new ArrayList(0) : null; deserializeTo(in, identities, obj); } @Override public void skip(DataInput in) throws IOException { List identities = recursive ? new ArrayList(0) : null; skip(in, identities); } @Override public int getSize(Object obj) throws IOException { TObjectIntHashMap identities = recursive ? new TObjectIntHashMap(0) : null; return getSize(obj, identities); } } /** * Assert there are enough readable bytes. This method works only if input * object is instance of BinaryReadable. DataInput cannot tell the * number of remaining bytes. * * This method is used by array, map, record and union sub-classes. * * @param in * @throws IOException */ protected void assertRemainingBytes(DataInput in, long bts) throws IOException { if (in instanceof BinaryReadable == false) return; BinaryReadable r = (BinaryReadable) in; if (bts > r.length() - r.position()) throw new SerializationException("Malformed data. Serialization aborted. (Wrong binding?)"); } }