package org.simantics.databoard.container; import java.io.DataInput; import java.io.DataInputStream; import java.io.DataOutput; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.Map; import java.util.TreeMap; import org.simantics.databoard.Bindings; import org.simantics.databoard.binding.Binding; import org.simantics.databoard.binding.impl.TreeMapBinding; import org.simantics.databoard.binding.mutable.Variant; import org.simantics.databoard.serialization.Serializer; import org.simantics.databoard.type.Datatype; import org.simantics.databoard.util.binary.BinaryFile; /** * An utility class for reading files whose format follows * {@link DataContainer}. * @author Hannu Niemistö */ public class DataContainers { private static final Serializer STRING_SERIALIZER = Bindings.getSerializerUnchecked(Bindings.STRING); private static final Serializer INTEGER_SERIALIZER = Bindings.getSerializerUnchecked(Bindings.INTEGER); private static final Serializer METADATA_SERIALIZER = Bindings.getSerializerUnchecked(new TreeMapBinding(Bindings.STRING, Bindings.VARIANT)); private static final Serializer DATATYPE_SERIALIZER = Bindings.getSerializerUnchecked(Datatype.class); private static final Serializer VARIANT_SERIALIZER = Bindings.getSerializerUnchecked(Bindings.VARIANT); private static final Serializer DATA_CONTAINER_SERIALIZER = Bindings.getSerializerUnchecked(DataContainer.class); /** * Binds a format name and a version together to form a format version * identifier string. * * @param formatName * @param version * @return */ public static String toFormatString(String formatName, int version) { return formatName + ":" + version; } /** * Reads only the header of the data container. Returns * a DataContainer whose content-field is null. * @throws IOException */ public static DataContainer readHeader(DataInput input) throws IOException { String format = (String)STRING_SERIALIZER.deserialize(input); int version = (Integer)INTEGER_SERIALIZER.deserialize(input); @SuppressWarnings("unchecked") TreeMap metadata = (TreeMap)METADATA_SERIALIZER.deserialize(input); return new DataContainer(format, version, metadata, null); } /** * Consumes a header from a given stream and checks that the header satisfies the given format and version. * Returns the obtained header if the check fails and null on success. * @throws IOException */ public static DataContainer requireHeader(DataInput input, String format, int version) { try { DataContainer header = readHeader(input); if(!format.equals(header.format) || version != header.version) return header; else return null; } catch (Throwable t) { return new DataContainer("unknown", 0, null, null); } } /** * Consumes a header from a given stream and checks that the header satisfies the given format and version restrictions. * Returns the obtained header if the check fails and null on success. * @throws IOException */ public static DataContainer requireHeader(DataInput input, String... requiredFormatStrings) { try { DataContainer header = readHeader(input); String formatString = toFormatString(header.format, header.version); for (String requiredFormatString : requiredFormatStrings) { if(formatString.equals(requiredFormatString)) return null; } return header; } catch (Throwable t) { return new DataContainer("unknown", 0, null, null); } } /** * Checks that the given file satisfies the given format and version. * Returns the obtained header if the check fails and null on success. * @throws IOException */ public static DataContainer validateHeader(File file, String format, int version) throws IOException { try (InputStream stream = new FileInputStream( file )) { return DataContainers.requireHeader(new DataInputStream(stream), format, version); } } /** * Checks that the given file satisfies the given format and version. * Returns the obtained header if the check fails and null on success. * @throws IOException */ public static DataContainer validateHeader(File file, String... allowedFormatStrings) throws IOException { try (InputStream stream = new FileInputStream( file )) { return DataContainers.requireHeader(new DataInputStream(stream), allowedFormatStrings); } } /** * Reads only the header of the data container. Returns * a DataContainer whose content-field is null. * @throws IOException */ public static DataContainer readHeader(File input) throws IOException { try (BinaryFile rf = new BinaryFile( input, "r" )) { return readHeader(rf); } } /** * Reads a data container including the content data. * @throws IOException */ public static DataContainer readFile(DataInput input) throws IOException { DataContainer result = readHeader(input); result.content = (Variant)VARIANT_SERIALIZER.deserialize(input); return result; } /** * Reads a data container including the content data. * @throws IOException */ public static DataContainer readFile(DataInput input, Binding expectedBinding) throws IOException, DataFormatException { DataContainer result = readHeader(input); Datatype contentType = (Datatype) DATATYPE_SERIALIZER.deserialize(input); if (!expectedBinding.type().equals(contentType)) throw new DataFormatException( "Content type didn't match the type expected for the binding " + expectedBinding + ":\nexpected type: " + expectedBinding.type() + "\nactual type: " + contentType); Object value = Bindings.getSerializerUnchecked(expectedBinding).deserialize(input); result.content = new Variant(expectedBinding, value); return result; } /** * Process a data container using a format handler matching the format and version * of the file. * @param handlers Map of handlers. Keys are strings of form "format:version". */ public static T readFile(DataInput input, Map> handlers) throws Exception { DataContainer result = readHeader(input); FormatHandler handler = handlers.get(result.format + ":" + result.version); if(handler == null) throw new DataFormatException("Unknown data format " + result.format + " version " + result.version + "."); Binding binding = handler.getBinding(); Datatype contentType = (Datatype)DATATYPE_SERIALIZER.deserialize(input); if(!binding.type().equals(contentType)) throw new DataFormatException("Content type didn't match the type expected for the format " + result.format + " version " + result.version + "."); Object value = Bindings.getSerializerUnchecked(binding).deserialize(input); result.content = new Variant(binding, value); return handler.process(result); } /** * Reads a data container including the content data. * @throws IOException */ public static DataContainer readFile(File input) throws IOException { try (BinaryFile rf = new BinaryFile( input, "r" )) { return readFile(rf); } } /** * Reads a data container including the content data. * @throws IOException * @throws DataFormatException */ public static DataContainer readFile(File input, Binding expectedBinding) throws IOException, DataFormatException { try (BinaryFile rf = new BinaryFile( input, "r" )) { return readFile(rf, expectedBinding); } } /** * Process a data container using a format handler matching the format and version * of the file. * @param handlers Map of handlers. Keys are strings of form "format:version". */ public static T readFile(File input, Map> handlers) throws Exception { try (BinaryFile rf = new BinaryFile( input, "r" )) { return readFile(rf, handlers); } } /** * Writes header fields of a container to the given output. Content field is * ignored. * @throws IOException */ public static void writeHeader(DataOutput output, DataContainer container) throws IOException { STRING_SERIALIZER.serialize(output, container.format); INTEGER_SERIALIZER.serialize(output, container.version); METADATA_SERIALIZER.serialize(output, container.metadata); } /** * Writes a data container to the given output. * @throws IOException */ public static void writeFile(DataOutput output, DataContainer container) throws IOException { writeHeader(output, container); VARIANT_SERIALIZER.serialize(output, container.content); } /** * Writes a data container to the given file * @throws IOException */ public static void writeFile(File output, DataContainer container) throws IOException { try (BinaryFile wf = new BinaryFile(output)) { writeFile(wf, container); wf.setLength(wf.position()); } } public static byte[] writeFile(DataContainer container) throws IOException { return DATA_CONTAINER_SERIALIZER.serialize(container); } }