X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.databoard%2Fsrc%2Forg%2Fsimantics%2Fdataboard%2Fserialization%2FSerializer.java;fp=bundles%2Forg.simantics.databoard%2Fsrc%2Forg%2Fsimantics%2Fdataboard%2Fserialization%2FSerializer.java;h=70829699bc2dce5b42d773c142a03537d940fc34;hp=0000000000000000000000000000000000000000;hb=969bd23cab98a79ca9101af33334000879fb60c5;hpb=866dba5cd5a3929bbeae85991796acb212338a08 diff --git a/bundles/org.simantics.databoard/src/org/simantics/databoard/serialization/Serializer.java b/bundles/org.simantics.databoard/src/org/simantics/databoard/serialization/Serializer.java new file mode 100644 index 000000000..70829699b --- /dev/null +++ b/bundles/org.simantics.databoard/src/org/simantics/databoard/serialization/Serializer.java @@ -0,0 +1,448 @@ +/******************************************************************************* + * 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?)"); + } + +} +