Truncate file after writing DataContainer to it
[simantics/platform.git] / bundles / org.simantics.databoard / src / org / simantics / databoard / container / DataContainers.java
1 package org.simantics.databoard.container;
2
3 import java.io.DataInput;
4 import java.io.DataInputStream;
5 import java.io.DataOutput;
6 import java.io.File;
7 import java.io.FileInputStream;
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.util.Map;
11 import java.util.TreeMap;
12
13 import org.simantics.databoard.Bindings;
14 import org.simantics.databoard.binding.Binding;
15 import org.simantics.databoard.binding.impl.TreeMapBinding;
16 import org.simantics.databoard.binding.mutable.Variant;
17 import org.simantics.databoard.serialization.Serializer;
18 import org.simantics.databoard.type.Datatype;
19 import org.simantics.databoard.util.binary.BinaryFile;
20
21 /**
22  * An utility class for reading files whose format follows
23  * {@link DataContainer}.
24  * @author Hannu Niemistö
25  */
26 public class DataContainers {
27
28     private static final Serializer STRING_SERIALIZER = 
29             Bindings.getSerializerUnchecked(Bindings.STRING);
30     private static final Serializer INTEGER_SERIALIZER = 
31             Bindings.getSerializerUnchecked(Bindings.INTEGER);
32     private static final Serializer METADATA_SERIALIZER = 
33             Bindings.getSerializerUnchecked(new TreeMapBinding(Bindings.STRING, Bindings.VARIANT));
34     private static final Serializer DATATYPE_SERIALIZER =
35             Bindings.getSerializerUnchecked(Datatype.class);
36     private static final Serializer VARIANT_SERIALIZER =
37             Bindings.getSerializerUnchecked(Bindings.VARIANT);
38     private static final Serializer DATA_CONTAINER_SERIALIZER =
39             Bindings.getSerializerUnchecked(DataContainer.class);
40
41     /**
42      * Binds a format name and a version together to form a format version
43      * identifier string.
44      * 
45      * @param formatName
46      * @param version
47      * @return
48      */
49     public static String toFormatString(String formatName, int version) {
50         return formatName + ":" + version;
51     }
52
53     /**
54      * Reads only the header of the data container. Returns
55      * a DataContainer whose content-field is null.
56      * @throws IOException 
57      */
58     public static DataContainer readHeader(DataInput input) throws IOException {
59         String format = (String)STRING_SERIALIZER.deserialize(input);
60         int version = (Integer)INTEGER_SERIALIZER.deserialize(input);
61         @SuppressWarnings("unchecked")
62                 TreeMap<String,Variant> metadata = (TreeMap<String,Variant>)METADATA_SERIALIZER.deserialize(input); 
63         return new DataContainer(format, version, metadata, null);
64     }
65     
66     /**
67      * Consumes a header from a given stream and checks that the header satisfies the given format and version.
68      * Returns the obtained header if the check fails and null on success.
69      * @throws IOException 
70      */
71     public static DataContainer requireHeader(DataInput input, String format, int version) {
72                 try {
73                         DataContainer header = readHeader(input);
74                         if(!format.equals(header.format) || version != header.version)
75                                 return header;
76                         else return null;
77                 } catch (Throwable t) {
78                         return new DataContainer("unknown", 0, null, null);
79                 }
80     }
81
82     /**
83      * Consumes a header from a given stream and checks that the header satisfies the given format and version restrictions.
84      * Returns the obtained header if the check fails and null on success.
85      * @throws IOException 
86      */
87     public static DataContainer requireHeader(DataInput input, String... requiredFormatStrings) {
88         try {
89             DataContainer header = readHeader(input);
90             String formatString = toFormatString(header.format, header.version);
91             for (String requiredFormatString : requiredFormatStrings) {
92                 if(formatString.equals(requiredFormatString))
93                     return null;
94             }
95             return header;
96         } catch (Throwable t) {
97             return new DataContainer("unknown", 0, null, null);
98         }
99     }
100
101     /**
102      * Checks that the given file satisfies the given format and version. 
103      * Returns the obtained header if the check fails and null on success.
104      * @throws IOException 
105      */
106     public static DataContainer validateHeader(File file, String format, int version) throws IOException {
107
108         InputStream stream = new FileInputStream( file );
109         try {
110                 return DataContainers.requireHeader(new DataInputStream(stream), format, version);
111         } finally {
112                 stream.close();
113         }
114         
115     }
116
117     /**
118      * Checks that the given file satisfies the given format and version. 
119      * Returns the obtained header if the check fails and null on success.
120      * @throws IOException 
121      */
122     public static DataContainer validateHeader(File file, String... allowedFormatStrings) throws IOException {
123
124         InputStream stream = new FileInputStream( file );
125         try {
126             return DataContainers.requireHeader(new DataInputStream(stream), allowedFormatStrings);
127         } finally {
128             stream.close();
129         }
130         
131     }
132
133     /**
134      * Reads only the header of the data container. Returns
135      * a DataContainer whose content-field is null.
136      * @throws IOException 
137      */
138     public static DataContainer readHeader(File input) throws IOException {
139         BinaryFile rf = new BinaryFile( input, "r" );
140         try {
141             return readHeader(rf);
142         } finally {
143             rf.close();
144         }       
145     }
146     
147     /**
148      * Reads a data container including the content data.
149      * @throws IOException 
150      */
151     public static DataContainer readFile(DataInput input) throws IOException {
152         DataContainer result = readHeader(input);
153         result.content = (Variant)VARIANT_SERIALIZER.deserialize(input);
154         return result;
155     }
156
157     /**
158      * Process a data container using a format handler matching the format and version
159      * of the file. 
160      * @param handlers Map of handlers. Keys are strings of form "format:version".
161      */
162     public static <T> T readFile(DataInput input, Map<String, FormatHandler<T>> handlers) throws Exception {
163         DataContainer result = readHeader(input);
164         
165         FormatHandler<T> handler = handlers.get(result.format + ":" + result.version);
166         if(handler == null)
167             throw new DataFormatException("Unknown data format " + result.format + " version " + result.version + ".");
168         Binding binding = handler.getBinding();
169         
170         Datatype contentType = (Datatype)DATATYPE_SERIALIZER.deserialize(input);
171         if(!binding.type().equals(contentType))
172             throw new DataFormatException("Content type didn't match the type expected for the format " + result.format + " version " + result.version + ".");
173         
174         Object value = Bindings.getSerializerUnchecked(binding).deserialize(input);
175         
176         result.content = new Variant(binding, value);
177         return handler.process(result);
178     }
179     
180     /**
181      * Reads a data container including the content data.
182      * @throws IOException 
183      */
184     public static DataContainer readFile(File input) throws IOException {
185         BinaryFile rf = new BinaryFile( input, "r" );
186         try {
187             return readFile(rf);
188         } finally {
189             rf.close();
190         }       
191     }
192     
193     /**
194      * Process a data container using a format handler matching the format and version
195      * of the file. 
196      * @param handlers Map of handlers. Keys are strings of form "format:version".
197      */
198     public static <T> T readFile(File input, Map<String, FormatHandler<T>> handlers) throws Exception {
199         BinaryFile rf = new BinaryFile( input, "r" );
200         try {
201             return readFile(rf, handlers);
202         } finally {
203             rf.close();
204         }       
205     }
206     
207     /**
208      * Writes header fields of a container to the given output. Content field is
209      * ignored.
210      * @throws IOException 
211      */
212     public static void writeHeader(DataOutput output, DataContainer container) throws IOException {
213         STRING_SERIALIZER.serialize(output, container.format);
214         INTEGER_SERIALIZER.serialize(output, container.version);
215         METADATA_SERIALIZER.serialize(output, container.metadata);
216     }
217     
218     /**
219      * Writes a data container to the given output.
220      * @throws IOException 
221      */
222     public static void writeFile(DataOutput output, DataContainer container) throws IOException {
223         writeHeader(output, container);
224         VARIANT_SERIALIZER.serialize(output, container.content);
225     }
226     
227     /**
228      * Writes a data container to the given file
229      * @throws IOException 
230      */
231     public static void writeFile(File output, DataContainer container) throws IOException {
232         BinaryFile wf = new BinaryFile( output);
233         try {
234             writeFile(wf, container);
235             wf.setLength(wf.position());
236         } finally {
237             wf.close();
238         }       
239     }
240     
241     public static byte[] writeFile(DataContainer container) throws IOException {
242         return DATA_CONTAINER_SERIALIZER.serialize(container);
243     }
244 }