import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import java.nio.ByteBuffer;
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.
+ *
+ * <p>
+ * 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, <code>true</code> must be used if the corresponding TG
+ * file is written e.g. like this:
+ * <pre>
+ * DataContainers.writeFile(location
+ * new DataContainer(format,
+ * version,
+ * metadata,
+ * new Variant(TransferableGraph1.BINDING, tg)));
+ * </pre>
+ *
+ * <p>
+ * On the other hand, any TG files serialized using more optimized methods like
+ * <code>ModelTransferableGraphSource</code> 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.
+ *
+ * <p>
+ * <code>true</code> 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
}
};
-
-
- final static class InputChannel implements ReadableByteChannel {
- final private InputStream stream;
-
- public InputChannel(InputStream stream) {
- this.stream = stream;
- }
-
- @Override
- public boolean isOpen() {
- return true;
- }
+ private static boolean init = true;
- @Override
- public void close() throws IOException {
+ 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 <code>input</code> 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();
}
+ }
- @Override
- public int read(ByteBuffer dst) throws IOException {
- int pos = dst.position();
- int limit = dst.limit();
- int i=stream.read(dst.array(), pos, limit-pos);
- //System.err.println("Read " + i + " (expected " + dst.array().length + ")");
- return i;
+ /**
+ * Reads a {@link DataContainer} containing a {@link TransferableGraph1}
+ * structure from the specified InputStream. Note that this implementation does
+ * not close the specified <code>input</code> 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();
}
-
}
-
- private static boolean init = true;
-
- final private static int SIZE = 1<<18;
- final private static int HEADER = headerSize();
- final private int header;
-
+
public TransferableGraphFileReader(File file) throws IOException {
super(file, SIZE);
if(init) {
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;
throw new Error("Failed to determine TransferableGraph1 header size. ", e);
}
}
-
+
public TransferableGraph1 readTG() throws IOException {
if(getSize() == 0) return null;
int resourceCount = safeInt();
- List<Object> idcontext = new ArrayList<Object>();
+ List<Object> 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];
-// System.err.println("rc: " + resourceCount);
-// System.err.println("ids: " + identities);
+// LOGGER.warn("rc: " + resourceCount);
+// LOGGER.warn("ids: " + identities);
// long duration = System.nanoTime() - start;
-// System.err.println("start in " + 1e-9*duration + "s.");
+// LOGGER.warn("start in " + 1e-9*duration + "s.");
// start = System.nanoTime();
-
+
for(int i=0;i<identities;i++) {
int rid = safeInt();
byte type = bytes[byteIndex++];
// External
if(type == 1) {
-
+
int parent = safeInt();
int nameLen = getDynamicUInt32();
-
+
if(byteIndex+nameLen < SIZE) {
ids[i] = new Identity(rid, new External(parent, utf(bytes, byteIndex, byteIndex + nameLen)));
byteIndex += nameLen;
} else {
ids[i] = new Identity(rid, new External(parent, utf(safeBytes(nameLen), 0, nameLen)));
}
-
+
}
// Internal
else if(type == 3) {
-
+
int parent = safeInt();
int nameLen = getDynamicUInt32();
if(byteIndex+nameLen < SIZE) {
} else if(type == 2) {
throw new UnsupportedOperationException();
+ } else {
+ throw new IllegalStateException();
}
}
-// for(Identity id : ids) System.err.println("id: " + id);
+// for(Identity id : ids) LOGGER.warn("id: " + id);
// duration = System.nanoTime() - start;
-// System.err.println("ids in " + 1e-9*duration + "s.");
+// LOGGER.warn("ids in " + 1e-9*duration + "s.");
// start = System.nanoTime();
int stmLength = safeInt();
-// System.err.println("statements: " + stmLength + " (" + byteIndex + ")");
+// LOGGER.warn("statements: " + stmLength + " (" + byteIndex + ")");
int[] statements = new int[stmLength];
}
// duration = System.nanoTime() - start;
-// System.err.println("stms in " + 1e-9*duration + "s.");
+// LOGGER.warn("stms in " + 1e-9*duration + "s.");
//
// start = System.nanoTime();
int valueLength = safeInt();
-// System.err.println("values: " + valueLength + " (" + byteIndex + ")");
+// LOGGER.warn("values: " + valueLength + " (" + byteIndex + ")");
Value[] values = new Value[valueLength];
Serializer variantSerializer = Bindings.getSerializerUnchecked(Bindings.VARIANT);
- idcontext = new ArrayList<Object>();
dis = new DataInputStream(in);
for(int i=0;i<valueLength;i++) {
int resource = safeInt();
+ if (!shareValueContext)
+ idcontext.clear();
Variant value = (Variant)variantSerializer
.deserialize((DataInput)dis, idcontext);
values[i] = new Value(resource, value);
- //System.err.println("read variant[" + resource + "]: " + value.toString().substring(0, Math.min(100, value.toString().length())));
+ //LOGGER.warn("read variant[" + resource + "]: " + value.toString().substring(0, Math.min(100, value.toString().length())));
}
// duration = System.nanoTime() - start;
-// System.err.println("values in " + 1e-9*duration + "s.");
+// LOGGER.warn("values in " + 1e-9*duration + "s.");
return new TransferableGraph1(resourceCount, ids, statements, values, extensions.map);
long s = System.nanoTime();
reader.readTG();
long d = System.nanoTime() - s;
- System.err.println("Duration=" + 1e-9*d + "s.");
+ LOGGER.warn("Duration=" + 1e-9*d + "s.");
} catch (Throwable t) {