]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.databoard/src/org/simantics/databoard/serialization/Serializer.java
Streaming serialization of values, debugger for corrupted values
[simantics/platform.git] / bundles / org.simantics.databoard / src / org / simantics / databoard / serialization / Serializer.java
1 /*******************************************************************************
2  *  Copyright (c) 2010 Association for Decentralized Information Management in
3  *  Industry THTH ry.
4  *  All rights reserved. This program and the accompanying materials
5  *  are made available under the terms of the Eclipse Public License v1.0
6  *  which accompanies this distribution, and is available at
7  *  http://www.eclipse.org/legal/epl-v10.html
8  *
9  *  Contributors:
10  *      VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.databoard.serialization;
13
14 import gnu.trove.map.hash.TObjectIntHashMap;
15
16 import java.io.ByteArrayInputStream;
17 import java.io.DataInput;
18 import java.io.DataOutput;
19 import java.io.File;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.io.OutputStream;
23 import java.nio.ByteBuffer;
24 import java.util.ArrayList;
25 import java.util.List;
26
27 import org.simantics.databoard.Files;
28 import org.simantics.databoard.util.binary.BinaryFile;
29 import org.simantics.databoard.util.binary.BinaryReadable;
30 import org.simantics.databoard.util.binary.ByteBufferReadable;
31 import org.simantics.databoard.util.binary.ByteBufferWriteable;
32 import org.simantics.databoard.util.binary.InputStreamReadable;
33 import org.simantics.databoard.util.binary.OutputStreamWriteable;
34
35 /**
36  * Databoard binary serializer. 
37  *
38  * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
39  */
40 public abstract class Serializer {
41
42         /**
43          * Serialize obj to out.
44          * 
45          * The identities argument is a map of identities (in the binary block) and objects
46          * that have already been serialized. Once serialized, an object is added to the map.
47          * Typically an empty map is provided. If the type has no recursion, i.e. Referable 
48          * Records, a <code>null</code> value can be provided.  
49          * 
50          * @param out
51          * @param identities Thread local empty map or <code>null</code> if there is no recursion
52          * @param obj
53          * @throws IOException
54          */
55         public abstract void serialize(DataOutput out, TObjectIntHashMap<Object> identities, Object obj) throws IOException;
56         public abstract void serialize(DataOutput out, Object obj) throws IOException;
57         public void serialize(OutputStream out, Object obj) throws IOException
58         {
59                 OutputStreamWriteable writ = new OutputStreamWriteable(out);
60                 serialize(writ, obj);           
61         }
62         
63         /**
64          * Deserialize an object from a readable.
65          * 
66          * The identities argument is a list of identities (in the binary block) of objects
67          * that have already been deserialized. Once deserialized they are added to the list.
68          * Typically an empty list is provided. If the type has no recursion, i.e. Referable
69          * Records, a <code>null</code> value can be provided.<p>
70          * 
71          * Note, if in argument is instanceof BinaryReadable or RandomAccessBinary, 
72          * the serializer performs extra protection against malformed data when 
73          * deserializing arrays and maps. This prevents the serializer from 
74          * instanting potentially out-of-memory-invoking huge arrays. For example, 
75          * if data data says array size is 0xffffffff (-1), 4GB is allocated -> 
76          * out of memory exception -> unhandled runtime error. BinaryReadable has 
77          * length limit which allowes serializer to estimate whether future data is 
78          * readable.  
79          * 
80          * @param in DataInput, BinaryReadable or RandomAccessBinary
81          * @param identities empty identities array or <code>null</code> if there is no recursion
82          * @return the instance
83          * @throws IOException
84          */
85         public abstract Object deserialize(DataInput in, List<Object> identities) throws IOException;
86         public abstract Object deserialize(DataInput in) throws IOException;
87
88         
89         /**
90          * Deserialize into an existing instance. This method writes over previous values.
91          * 
92          * @param in
93          * @param identities
94          * @param dst valid object
95          * @throws IOException
96          */
97         public abstract void deserializeTo(DataInput in, List<Object> identities, Object dst) throws IOException;
98         public abstract void deserializeTo(DataInput in, Object dst) throws IOException;
99         
100         /**
101          * Attempt deserialize to existing instance. Creates new if not possible.  
102          * 
103          * @param in
104          * @param identities
105          * @param dst
106          * @return dst or new obj
107          * @throws IOException
108          */
109         public Object deserializeToTry(DataInput in, List<Object> identities, Object dst) throws IOException 
110         {
111                 deserializeTo(in, identities, dst);
112                 return dst;                             
113         }
114         
115         /**
116          * Deserialize the next object in an input stream.
117          * Note, if multiple objects are deserialized from the same stream, it is 
118          * more efficient to instantiate InputStreamReadable and identities only once,
119          * and use {@link #deserialize(DataInput, List)}.
120          * 
121          * @param in
122          * @return The object deserialized into a Java Object
123          * @throws IOException
124          */
125         public Object deserialize(InputStream in) throws IOException
126         {
127                 // InputStreamReadable adapts InputStream to BinaryReadable&DataInput
128                 InputStreamReadable read = new InputStreamReadable(in, Long.MAX_VALUE);
129                 return deserialize(read);
130         }
131         
132         /**
133          * Deserialize from an input stream into an object.
134          * Note, if multiple objects are deserialized from the same stream, it is 
135          * more efficient to instantiate InputStreamReadable and identities only once,
136          * and use {@link #deserialize(DataInput, List)}.
137          * 
138          * @param in
139          * @param obj a valid object
140          * @throws IOException
141          */
142         public void deserialize(InputStream in, Object obj) throws IOException
143         {
144                 // InputStreamReadable adapts InputStream to BinaryReadable&DataInput
145                 InputStreamReadable read = new InputStreamReadable(in, Long.MAX_VALUE);
146                 deserializeTo(read, obj);
147         }       
148         
149         /**
150          * Deserialize object from a file.  
151          * 
152          * @param file source file
153          * @return the object
154          * @throws IOException
155          */
156         public Object deserialize(File file) throws IOException
157         {
158                 BinaryFile f = new BinaryFile(file);
159                 try {
160                         return deserialize(f);
161                 } finally {
162                         f.close();
163                 }               
164         }
165         
166         /**
167          * Deserialize a file into a valid object. This method writes over previous values.
168          * 
169          * @param file source file
170          * @param obj a dst valid object
171          * @throws IOException
172          */
173         public void deserialize(File file, Object obj) throws IOException
174         {
175                 BinaryFile f = new BinaryFile(file);
176                 try {
177                         deserializeTo(f, obj);
178                 } finally {
179                         f.close();
180                 }               
181         }       
182         
183         /**
184          * Deserialize an object in byte[] format.
185          * 
186          * @param data
187          * @return the instance
188          * @throws IOException
189          */
190         public Object deserialize(byte[] data) throws IOException
191         {
192                 ByteBuffer buffer = ByteBuffer.wrap( data );
193                 ByteBufferReadable readable = new ByteBufferReadable( buffer );
194                 return deserialize(readable);
195         }
196         
197         /**
198          * Deserialize byte[] into a valid object.
199          * 
200          * @param data
201          * @param obj dst valid object
202          * @throws IOException
203          */
204         public void deserialize(byte[] data, Object obj) throws IOException
205         {
206                 ByteBuffer buffer = ByteBuffer.wrap( data );
207                 ByteBufferReadable readable = new ByteBufferReadable( buffer );
208                 deserializeTo(readable, obj);
209         }       
210
211         /**
212          * Skip over an object in a stream. This method deserializes the object 
213          * without producing a result or reading thru all bytes. 
214          * 
215          * @param in
216          * @param identities
217          * @throws IOException
218          */
219         public abstract void skip(DataInput in, List<Object> identities) throws IOException;
220         public abstract void skip(DataInput in) throws IOException;
221         
222         /**
223          * Skip over an object in a stream. This method deserializes the object 
224          * without producing a result or reading thru all bytes. 
225          * 
226          * @param in
227          * @throws IOException
228          */     
229         public void skip(InputStream in) throws IOException
230         {
231                 InputStreamReadable read = new InputStreamReadable(in, Long.MAX_VALUE);
232                 skip(read);
233         }
234         
235         /**
236          * Get constant size of the data type in its binary serialized format 
237          * 
238          * @return size in bytes or null if not fixed
239          */
240         public abstract Integer getConstantSize();
241         
242         /**
243          * Get the number of bytes required to serialize an object
244          * 
245          * @param obj
246          * @param identities thread local empty hash map
247          * @return number of bytes required to serialize obj
248          * @throws IOException 
249          */
250         public abstract int getSize(Object obj, TObjectIntHashMap<Object> identities) throws IOException;
251         public abstract int getSize(Object obj) throws IOException;
252
253         public abstract int getMinSize();
254         
255         /**
256          * Serializes an object to a byte[].
257          * 
258          * @param obj
259          * @return byte array containing the obj in its serialized format. 
260          * @throws IOException
261          */
262         public byte[] serialize(Object obj) throws IOException
263         {
264                 TObjectIntHashMap<Object> identities = new TObjectIntHashMap<Object>();
265                 int size = getSize(obj, identities);
266                 identities.clear();
267                 ByteBuffer buffer = ByteBuffer.allocate( size );
268                 DataOutput writable = new ByteBufferWriteable( buffer );
269                 serialize(writable, identities, obj);
270                 buffer.rewind();
271                 return buffer.array();
272         }
273         
274         /**
275          * Serializes an object to an output stream.
276          * Note, if multiple objects are serialized to the same stream, it is 
277          * more efficient to instantiate OutputStreamWriteable and identities only once.
278          * 
279          * @param obj
280          * @param out
281          * @throws IOException
282          */
283         public void serialize(Object obj, OutputStream out) throws IOException
284         {
285                 // OutputStreamWriteable adapts OutputStream to DataOutput&BinaryWritable
286                 OutputStreamWriteable writ = new OutputStreamWriteable(out);
287                 TObjectIntHashMap<Object> identities = new TObjectIntHashMap<Object>();
288                 serialize(writ, identities, obj);
289         }
290         
291         /**
292          * Serialize an object to a file. Note the type info is not written to the
293          * file (unless obj is variant), and therefore is not compatible as .dbb 
294          * file. 
295          * 
296          * Databoard Binary file (.dbb) is a binary file that has datatype in the 
297          * header. To create .dbb file, serialize Datatype and then the value.
298          * Or use methods in {@link Files} for convenience. Variant objects are, by
299          * nature, .dbb compatible.   
300          * 
301          * @param obj
302          * @param file
303          * @throws IOException
304          */
305         public void serialize(Object obj, File file)
306         throws IOException
307         {
308                 TObjectIntHashMap<Object> identities = new TObjectIntHashMap<Object>();
309                 BinaryFile writable = new BinaryFile( file );
310                 try {
311                         serialize(writable, identities, obj);
312                 } finally {
313                         writable.close();
314                 }
315         }       
316
317         /**
318          * Get object as readable Input Stream.
319          * 
320          * @param obj
321          * @return input stream
322          * @throws IOException
323          */
324         public InputStream getInputStream(Object obj) throws IOException
325         {
326                 // Trivial implementation - better implementation would code bytes on-demend
327                 // without memory consumption.
328                 byte[] data = serialize(obj);
329                 return new ByteArrayInputStream(data);
330         }
331         
332         /**
333          * Serializer for data types that have referable objects
334          */
335         public static abstract class RecursiveSerializer extends Serializer {
336                 
337                 /**
338                  * Finalize the construction of the serializer. This is called once all component
339                  * serializers are constructed.
340                  */
341                 public abstract void finalizeConstruction();
342                 
343                 public void serialize(DataOutput out, Object obj) throws IOException {
344                         TObjectIntHashMap<Object> identities = new TObjectIntHashMap<Object>(0);
345                         serialize(out, identities, obj);
346                 }
347                 public Object deserialize(DataInput in) throws IOException {
348                         List<Object> identities = new ArrayList<Object>(0);
349                         return deserialize(in, identities);
350                 }
351                 public void deserializeTo(DataInput in, Object obj) throws IOException {
352                         List<Object> identities = new ArrayList<Object>(0);
353                         deserializeTo(in, identities, obj);
354                 }
355                 @Override
356                 public void skip(DataInput in) throws IOException {
357                         List<Object> identities = new ArrayList<Object>(0);
358                         skip(in, identities);
359                 }
360                 @Override
361                 public int getSize(Object obj)
362                                 throws IOException {
363                         TObjectIntHashMap<Object> identities = new TObjectIntHashMap<Object>(0);
364                         return getSize(obj, identities);
365                 }
366         }
367         
368         /**
369          * Serializer for non-recursive data types
370          */
371         public static abstract class NonRecursiveSerializer extends Serializer {
372                 public void serialize(DataOutput out, TObjectIntHashMap<Object> identities, Object obj) throws IOException {
373                         serialize(out, obj);
374                 }
375                 public Object deserialize(DataInput in, List<Object> identities) throws IOException {
376                         return deserialize(in);
377                 }
378                 public void deserializeTo(DataInput in, List<Object> identities, Object obj) throws IOException {
379                         deserializeTo(in, obj);
380                 }               
381                 @Override
382                 public void skip(DataInput in, List<Object> identities) throws IOException {
383                         skip(in);
384                 }
385                 @Override
386                 public int getSize(Object obj, TObjectIntHashMap<Object> identities) throws IOException {
387                         return getSize(obj);
388                 }
389         }
390         
391         /**
392          * Serializer for composite data types
393          */
394         public static abstract class CompositeSerializer extends Serializer {
395                 boolean recursive;
396                 protected CompositeSerializer(boolean recursive) {
397                         this.recursive = recursive;
398                 }
399                 
400                 /**
401                  * Finalize the construction of the serializer. This is called once all component
402                  * serializers are constructed.
403                  */
404                 public abstract void finalizeConstruction( );
405                 
406                 public void serialize(DataOutput out, Object obj) throws IOException {
407                         TObjectIntHashMap<Object> identities = recursive ? new TObjectIntHashMap<Object>(0) : null;
408                         serialize(out, identities, obj);
409                 }
410                 public Object deserialize(DataInput in) throws IOException {
411                         List<Object> identities = recursive ? new ArrayList<Object>(0) : null;
412                         return deserialize(in, identities);
413                 }
414                 public void deserializeTo(DataInput in, Object obj) throws IOException {
415                         List<Object> identities = recursive ? new ArrayList<Object>(0) : null;
416                         deserializeTo(in, identities, obj);
417                 }
418                 @Override
419                 public void skip(DataInput in) throws IOException {
420                         List<Object> identities = recursive ? new ArrayList<Object>(0) : null;
421                         skip(in, identities);
422                 }
423                 @Override
424                 public int getSize(Object obj)
425                                 throws IOException {
426                         TObjectIntHashMap<Object> identities = recursive ? new TObjectIntHashMap<Object>(0) : null;
427                         return getSize(obj, identities);
428                 }
429         }
430
431         /**
432          * Assert there are enough readable bytes. This method works only if input 
433          * object is instance of BinaryReadable. DataInput cannot tell the 
434          * number of remaining bytes.   
435          * 
436          * This method is used by array, map, record and union sub-classes.
437          * 
438          * @param in
439          * @throws IOException
440          */
441         protected void assertRemainingBytes(DataInput in, long bts) throws IOException {
442                 if (in instanceof BinaryReadable == false) return;
443                 BinaryReadable r = (BinaryReadable) in;
444                 if (bts > r.length() - r.position()) throw new SerializationException("Malformed data. Serialization aborted. (Wrong binding?)");
445         }
446         
447 }
448