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