package org.simantics.graph.representation; import java.io.DataInput; import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.channels.ReadableByteChannel; import java.util.ArrayList; import java.util.List; import org.simantics.databoard.Bindings; import org.simantics.databoard.Datatypes; import org.simantics.databoard.binding.Binding; import org.simantics.databoard.binding.error.RuntimeDatatypeConstructionException; import org.simantics.databoard.binding.mutable.Variant; import org.simantics.databoard.container.DataContainer; import org.simantics.databoard.container.DataContainers; import org.simantics.databoard.serialization.RuntimeSerializerConstructionException; import org.simantics.databoard.serialization.Serializer; import org.simantics.databoard.type.Datatype; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * It is recommended to use {@link #read(File)} and {@link #read(InputStream)} * for reading to ensure proper resource handling. * *

* It is important to use the correct setting for * {@link #sharedValueContext(boolean)} which depends on how the TG was * serialized to begin with. See {@link #DEFAULT_SHARED_VALUE_CONTEXT} for more * information on this. */ final public class TransferableGraphFileReader extends ByteFileReader { /** * Serializing TransferableGraph1 structures using the default {@link Binding} * will use shared context for serializing the values Variant array. Thus all TG * files produced by the graph compiler use a shared value context which means * this class must be used with {@link #sharedValueContext(boolean)} set to * true. As an example, true must be used if the corresponding TG * file is written e.g. like this: *

     * DataContainers.writeFile(location 
     *     new DataContainer(format,
     *                       version,
     *                       metadata,
     *                       new Variant(TransferableGraph1.BINDING, tg)));
     * 
* *

* On the other hand, any TG files serialized using more optimized methods like * ModelTransferableGraphSource do not use a shared value context * when writing the file. This means those files cannot be read safely using * standard {@link Binding} at all, and when using this class, * {@link #sharedValueContext(boolean)} must be set to false to prevent the * import from corrupting datatype values because the referable parts of * datatypes may get bound to the wrong existing types if the data is read using * shared context. * *

* true is the default setting. */ public static final boolean DEFAULT_SHARED_VALUE_CONTEXT = true; private static final Logger LOGGER = LoggerFactory.getLogger(TransferableGraphFileReader.class); InputStream in = new InputStream() { @Override public int read() throws IOException { return getByte(); } @Override public int read(byte[] b) throws IOException { // FIXME not correctly implemented System.arraycopy(safeBytes(b.length), 0, b, 0, b.length); return b.length; } @Override public int read(byte[] b, int off, int len) throws IOException { // FIXME not correctly implemented System.arraycopy(safeBytes(len), 0, b, off, len); return len; } }; private static boolean init = true; final private static int SIZE = 1<<18; final private static int HEADER = headerSize(); final private int header; private boolean shareValueContext = true; /** * Reads a {@link DataContainer} containing a {@link TransferableGraph1} * structure from the specified {@link File}. * * @param file the file to read from * @return the TG contained by the file * @throws IOException */ public static TransferableGraph1 read(File file) throws IOException { return read(file, DEFAULT_SHARED_VALUE_CONTEXT); } /** * Reads a {@link DataContainer} containing a {@link TransferableGraph1} * structure from the specified InputStream. Note that this implementation does * not close the specified input stream, it is expected to be * closed by the caller. * * @param input the input stream to read from * @return the TG contained by the stream * @throws IOException */ public static TransferableGraph1 read(InputStream input) throws IOException { return read(input, DEFAULT_SHARED_VALUE_CONTEXT); } /** * Reads a {@link DataContainer} containing a {@link TransferableGraph1} * structure from the specified {@link File}. * * @param file the file to read from * @return the TG contained by the file * @throws IOException */ public static TransferableGraph1 read(File file, boolean sharedValueContext) throws IOException { try (TransferableGraphFileReader reader = new TransferableGraphFileReader(file)) { return reader.sharedValueContext(sharedValueContext).readTG(); } } /** * Reads a {@link DataContainer} containing a {@link TransferableGraph1} * structure from the specified InputStream. Note that this implementation does * not close the specified input stream, it is expected to be * closed by the caller. * * @param input the input stream to read from * @return the TG contained by the stream * @throws IOException */ public static TransferableGraph1 read(InputStream input, boolean sharedValueContext) throws IOException { try (TransferableGraphFileReader reader = new TransferableGraphFileReader(input)) { return reader.sharedValueContext(sharedValueContext).readTG(); } } public TransferableGraphFileReader(File file) throws IOException { super(file, SIZE); if(init) { init=false; TransferableGraphFileReader r = new TransferableGraphFileReader(file, 0); for(int i=0;i<40000;i++) r.readTG(); } this.header = HEADER; } public TransferableGraphFileReader(InputStream stream) throws IOException { super(null, new InputChannel(stream), SIZE); if(init) { init=false; TransferableGraphFileReader r = new TransferableGraphFileReader(stream, 0); for(int i=0;i<40000;i++) r.readTG(); } this.header = 0; } public TransferableGraphFileReader(ReadableByteChannel channel) throws IOException { super(null, channel, SIZE); if(init) { init=false; TransferableGraphFileReader r = new TransferableGraphFileReader(channel, 0); for(int i=0;i<40000;i++) r.readTG(); } this.header = 0; } public TransferableGraphFileReader(ReadableByteChannel channel, int size) throws IOException { super(null, channel, SIZE); this.header = 0; } public TransferableGraphFileReader(InputStream stream, int size) throws IOException { super(null, new InputChannel(stream), size); this.header = 0; } public TransferableGraphFileReader(File file, int size) throws IOException { super(file, size); this.header = HEADER; } public TransferableGraphFileReader sharedValueContext(boolean share) { this.shareValueContext = share; return this; } private static int headerSize() { try { return Bindings.getSerializerUnchecked(Datatype.class).serialize(Datatypes.getDatatypeUnchecked(TransferableGraph1.class)).length; } catch (RuntimeSerializerConstructionException e) { throw new Error("Failed to determine TransferableGraph1 header size. ", e); } catch (RuntimeDatatypeConstructionException e) { throw new Error("Failed to determine TransferableGraph1 header size. ", e); } catch (IOException e) { throw new Error("Failed to determine TransferableGraph1 header size. ", e); } } public TransferableGraph1 readTG() throws IOException { if(getSize() == 0) return null; // long start = System.nanoTime(); final byte[] bytes = getBytes(); // byteIndex = header; DataInputStream dis = new DataInputStream(in); // Header DataContainers.readHeader(dis); // Content variant data type Bindings.getSerializerUnchecked(Datatype.class).deserialize((DataInput)dis); int resourceCount = safeInt(); List idcontext = new ArrayList<>(); dis = new DataInputStream(in); Extensions extensions = (Extensions)Bindings.getSerializerUnchecked(Extensions.class).deserialize((DataInput)dis, idcontext); int identities = safeInt(); Identity[] ids = new Identity[identities]; // LOGGER.warn("rc: " + resourceCount); // LOGGER.warn("ids: " + identities); // long duration = System.nanoTime() - start; // LOGGER.warn("start in " + 1e-9*duration + "s."); // start = System.nanoTime(); for(int i=0;i> 2; int allowed = Math.min(stmLength-stmIndex, avail); for(int index = byteIndex, i=0;i