--- /dev/null
+package org.simantics.db.layer0.util;\r
+\r
+import java.io.Closeable;\r
+import java.io.DataInput;\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.nio.file.Files;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.HashSet;\r
+import java.util.List;\r
+import java.util.TreeMap;\r
+\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.databoard.Databoard;\r
+import org.simantics.databoard.binding.Binding;\r
+import org.simantics.databoard.binding.mutable.Variant;\r
+import org.simantics.databoard.container.DataContainer;\r
+import org.simantics.databoard.serialization.Serializer;\r
+import org.simantics.databoard.type.Datatype;\r
+import org.simantics.databoard.util.binary.ByteBufferReadable;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.common.utils.NameUtils;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.exception.RuntimeDatabaseException;\r
+import org.simantics.db.exception.ValidationException;\r
+import org.simantics.db.layer0.adapter.SubgraphExtent.ExtentStatus;\r
+import org.simantics.db.layer0.util.TransferableGraphConfiguration2.RootSpec;\r
+import org.simantics.db.service.SerialisationSupport;\r
+import org.simantics.graph.db.TransferableGraphSource;\r
+import org.simantics.graph.representation.External;\r
+import org.simantics.graph.representation.Identity;\r
+import org.simantics.graph.representation.Root;\r
+import org.simantics.graph.representation.Value;\r
+import org.simantics.layer0.Layer0;\r
+\r
+import gnu.trove.list.array.TIntArrayList;\r
+import gnu.trove.procedure.TIntIntProcedure;\r
+\r
+public class ModelTransferableGraphSource implements TransferableGraphSource {\r
+\r
+ final private TransferableGraphConfiguration2 configuration;\r
+ final private DomainProcessorState state;\r
+ final private int externalBase;\r
+ final private int resourceCount;\r
+ final private File[] files;\r
+ final private TGValueModifier valueModifier;\r
+\r
+ private volatile boolean closed = false;\r
+\r
+ TIntArrayList externalParents = new TIntArrayList();\r
+ ArrayList<String> externalNames = new ArrayList<String>();\r
+\r
+ public ModelTransferableGraphSource(final ReadGraph graph, TransferableGraphConfiguration2 configuration, final DomainProcessorState state, File ... fs) throws DatabaseException {\r
+\r
+ this.configuration = configuration;\r
+ this.state = state;\r
+ this.files = fs;\r
+ this.valueModifier = state.valueModifier;\r
+\r
+ SerialisationSupport ss = graph.getService(SerialisationSupport.class);\r
+\r
+ // At this point ids contains all internal resources. Now add roots and externals.\r
+ \r
+ // Root Library\r
+ state.ids.put(ss.getTransientId(graph.getRootLibrary()), state.id++);\r
+ \r
+ // External roots - internal roots were already processed as internal resources by domain processor\r
+ for(RootSpec rs : configuration.roots) {\r
+ if(!rs.internal) {\r
+ int resourceId = ss.getTransientId(rs.resource);\r
+ state.ids.put(resourceId, state.id++);\r
+ // The fixed roots have been seen as externals by domain processor. Now remove them from external set.\r
+ state.externals.remove(resourceId);\r
+ }\r
+ }\r
+ \r
+ this.externalBase = state.id;\r
+\r
+ final Collection<String> errors = new HashSet<String>();\r
+\r
+ // All resource considered as not internal by domain processor. Can also contain roots.\r
+ int[] externals = state.externals.toArray();\r
+ \r
+ // Build up the state.externals, externalNames and externalParents\r
+ for(int i=0;i<externals.length;i++) {\r
+ getId(graph, externals[i], errors);\r
+ }\r
+ \r
+ state.inverses.forEachEntry(new TIntIntProcedure() {\r
+ \r
+ @Override\r
+ public boolean execute(int predicate, int inverse) {\r
+ try {\r
+ getId(graph, predicate, errors);\r
+ if(inverse != 0) getId(graph, inverse, errors);\r
+ } catch (DatabaseException e) {\r
+ throw new RuntimeDatabaseException(e);\r
+ }\r
+ return true;\r
+ }\r
+ \r
+ });\r
+ \r
+ if(!errors.isEmpty()) {\r
+ ArrayList<String> sorted = new ArrayList<String>(errors);\r
+ Collections.sort(sorted);\r
+ StringBuilder message = new StringBuilder();\r
+ message.append("Errors in exported model:\n");\r
+ for(String error : sorted) {\r
+ message.append(error);\r
+ message.append("\n");\r
+ }\r
+ throw new DatabaseException(message.toString());\r
+ }\r
+ \r
+ this.resourceCount = state.id;\r
+ \r
+ }\r
+\r
+ int indent = 0;\r
+ \r
+ public boolean validateExternal(Resource ext) {\r
+ if(configuration.validate) {\r
+ ExtentStatus status = configuration.preStatus.get(ext);\r
+ if(status != null) {\r
+ if(ExtentStatus.INTERNAL.equals(status)) return false;\r
+ else if(ExtentStatus.EXCLUDED.equals(status)) return false;\r
+ }\r
+ }\r
+ return true;\r
+ }\r
+ \r
+ private Resource getResource(ReadGraph graph, int r) throws DatabaseException {\r
+ SerialisationSupport ss = graph.getService(SerialisationSupport.class);\r
+ return ss.getResource(r);\r
+ }\r
+ \r
+ final public int getExistingId(ReadGraph graph, int r) throws DatabaseException {\r
+ \r
+ int ret = state.ids.get(r);\r
+ if(ret != -1) {\r
+ return ret;\r
+ } else {\r
+ SerialisationSupport ss = graph.getService(SerialisationSupport.class);\r
+ throw new DatabaseException("Id has not been created for " + NameUtils.getSafeName(graph, ss.getResource(r)));\r
+ }\r
+\r
+ }\r
+ \r
+ /*\r
+ * \r
+ * @return -2 if r is not really external and the statement should be excluded\r
+ * \r
+ */\r
+ public int getId(ReadGraph graph, int r, Collection<String> errors) throws DatabaseException {\r
+\r
+// // First external is root library\r
+// if(r == rootId) return internalCount;\r
+ \r
+ SerialisationSupport ss = graph.getService(SerialisationSupport.class);\r
+ Layer0 L0 = Layer0.getInstance(graph);\r
+ \r
+ if(state.ids.containsKey(r)) {\r
+ int ret = state.ids.get(r);\r
+ if(ret == -1) {\r
+ for(int i=0;i<=indent;++i)\r
+ System.out.print(" ");\r
+ System.out.println("Cycle!!!"); // with " + GraphUtils.getReadableName(g, r));\r
+ }\r
+ return ret;\r
+ }\r
+ else {\r
+ Resource res = getResource(graph, r);\r
+ if(!validateExternal(res)) {\r
+ errors.add("Illegal reference to " + graph.getPossibleURI(getResource(graph, r)));\r
+ return -2;\r
+ }\r
+ Collection<Resource> parents = graph.getObjects(res, L0.PartOf); \r
+ if(parents.size() != 1) {\r
+ throw new ValidationException("Reference to external resource " \r
+ + NameUtils.getSafeName(graph, getResource(graph, r), true) + " without unique uri (" + parents.size() + " parents).");\r
+ }\r
+ int pid = 0;\r
+ for(Resource p : parents) {\r
+ ++indent;\r
+ pid = getId(graph, ss.getTransientId(p), errors);\r
+ if(pid == -2) {\r
+ errors.add("Illegal reference to " + graph.getPossibleURI(getResource(graph, r)));\r
+ return -2;\r
+ }\r
+ }\r
+ --indent;\r
+ String name = graph.getRelatedValue(res, L0.HasName);\r
+ // Record the external entry\r
+ externalParents.add(pid);\r
+ externalNames.add(name);\r
+ state.ids.put(r, state.id);\r
+ // Ensure that this resource is included into the set of externals to maintain the total number of externals \r
+ state.externals.add(r);\r
+ return state.id++;\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public DataContainer getHeader() throws Exception {\r
+ return null;\r
+ }\r
+ \r
+ @Override\r
+ public int getResourceCount() {\r
+ return resourceCount;\r
+ }\r
+ \r
+ @Override\r
+ public int getIdentityCount() {\r
+ return configuration.roots.size() + state.externals.size() + 1;\r
+ }\r
+ \r
+ @Override\r
+ public int getStatementCount() {\r
+ return state.statementCount;\r
+ }\r
+ \r
+ @Override\r
+ public int getValueCount() {\r
+ return state.valueCount;\r
+ }\r
+\r
+ @Override\r
+ public void forStatements(ReadGraph graph, TransferableGraphSourceProcedure<int[]> procedure) throws Exception {\r
+ \r
+ int[] value = new int[4];\r
+ long length = state.otherStatementsInput.length();\r
+ state.otherStatementsInput.position(0);\r
+ \r
+ while(state.otherStatementsInput.position() < length && !state.monitor.isCanceled()) {\r
+ \r
+ int s = state.otherStatementsInput.readInt();\r
+ int subjectId = state.ids.get(s);\r
+ \r
+ boolean exclude = subjectId == -1;\r
+ \r
+ int size = state.otherStatementsInput.readInt();\r
+ for(int i=0;i<size;i++) {\r
+ int p = state.otherStatementsInput.readInt();\r
+ int o = state.otherStatementsInput.readInt();\r
+ if(!exclude) {\r
+ if(state.excludedShared.contains(o)) {\r
+ System.err.println("excluding shared " + s + " " + p + " " + o);\r
+ } else {\r
+ \r
+ int objectId = getExistingId(graph, o);\r
+ // The statement can be denied still\r
+ if(objectId != -2) {\r
+ value[0] = subjectId;\r
+ value[1] = getExistingId(graph, p);\r
+ int inverse = state.inverses.get(p);\r
+ if(inverse != 0) {\r
+ value[2] = getExistingId(graph, inverse);\r
+ } else {\r
+ value[2] = -1;\r
+ }\r
+ value[3] = objectId;\r
+\r
+ procedure.execute(value);\r
+ \r
+ } else {\r
+ System.err.println("Denied (" + NameUtils.getSafeName(graph, getResource(graph, s)) + ", " + NameUtils.getSafeName(graph, getResource(graph, p)) + "," + NameUtils.getSafeName(graph, getResource(graph, o)) + ")");\r
+ } \r
+ \r
+ }\r
+ } else {\r
+ System.err.println("excluding shared " + s);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void forValues(ReadGraph graph, TransferableGraphSourceProcedure<Value> procedure) throws Exception {\r
+\r
+ Serializer variantSerializer = graph.getService(Databoard.class).getSerializerUnchecked(Bindings.VARIANT);\r
+ List<Object> idContext = new ArrayList<>();\r
+ long length = state.valueInput.length();\r
+ state.valueInput.position(0);\r
+\r
+ while(state.valueInput.position() < length && !state.monitor.isCanceled()) {\r
+\r
+ // Ignore value type tag\r
+ int s = state.valueInput.readInt();\r
+ byte valueType = state.valueInput.readByte();\r
+ switch (valueType) {\r
+ case TAG_RAW_COPY_VARIANT_VALUE:\r
+ state.valueInput.readInt();\r
+ // Intentional fallthrough.\r
+\r
+ case TAG_POTENTIALLY_MODIFIED_VARIANT_VALUE: {\r
+ idContext.clear();\r
+ Variant variant = (Variant) variantSerializer.deserialize((DataInput)state.valueInput, idContext);\r
+ if (valueModifier.mayNeedModification(variant.type())) {\r
+ Object currentObject = variant.getValue();\r
+ Object newObject = valueModifier.modify(state, variant.getBinding(), currentObject);\r
+ if (newObject != currentObject)\r
+ variant = new Variant(variant.getBinding(), newObject);\r
+ }\r
+ procedure.execute(new Value(state.ids.get(s), variant));\r
+ break;\r
+ }\r
+\r
+ default:\r
+ throw new IllegalArgumentException("Unrecognized variant value type encountered: " + valueType);\r
+ }\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public void forValues2(ReadGraph graph, TransferableGraphSourceValueProcedure procedure) throws Exception {\r
+\r
+ Serializer datatypeSerializer = graph.getService(Databoard.class).getSerializerUnchecked(Bindings.getBindingUnchecked(Datatype.class));\r
+ List<Object> idContext = new ArrayList<>();\r
+ long length = state.valueInput.length();\r
+ state.valueInput.position(0);\r
+\r
+ while(state.valueInput.position() < length && !state.monitor.isCanceled()) {\r
+\r
+ int s = state.valueInput.readInt();\r
+ byte valueType = state.valueInput.readByte();\r
+\r
+ switch (valueType) {\r
+ case TAG_RAW_COPY_VARIANT_VALUE: {\r
+ // Variant data could be copied raw, no need for modifications\r
+ // Note that the variant data is a concatenation of the\r
+ // variant datatype serialization and the value serialization.\r
+ // variantLength contains both datatype and value data.\r
+ int variantLength = state.valueInput.readInt();\r
+ procedure.rawCopy(state.ids.get(s), variantLength, state.valueInput);\r
+ break;\r
+ }\r
+\r
+ case TAG_POTENTIALLY_MODIFIED_VARIANT_VALUE: {\r
+ // Variant data may need to be modified.\r
+ // Cannot optimize this case with raw copying.\r
+ idContext.clear();\r
+ Datatype type = (Datatype)datatypeSerializer.deserialize((DataInput)state.valueInput, idContext);\r
+\r
+ if (valueModifier.mayNeedModification(type)) {\r
+ Binding binding = Bindings.getBinding(type);\r
+ Serializer serializer = Bindings.getSerializerUnchecked(binding);\r
+ Object value = serializer.deserialize((DataInput)state.valueInput);\r
+ value = valueModifier.modify(state, binding, value);\r
+ byte[] bytes = serializer.serialize(value);\r
+ procedure.execute(state.ids.get(s), type, new ByteBufferReadable(bytes));\r
+ } else {\r
+ procedure.execute(state.ids.get(s), type, state.valueInput);\r
+ }\r
+ break;\r
+ }\r
+\r
+ default:\r
+ throw new IllegalArgumentException("Unrecognized variant value type encountered: " + valueType);\r
+ }\r
+ }\r
+ }\r
+\r
+ protected Identity getRootIdentity(DomainProcessorState state, SerialisationSupport support, Resource rootLibrary) throws DatabaseException {\r
+ return new Identity(state.ids.get(support.getTransientId(rootLibrary)), new External(-1, ""));\r
+ }\r
+\r
+ @Override\r
+ public void forIdentities(ReadGraph graph, TransferableGraphSourceProcedure<Identity> procedure) throws Exception {\r
+ \r
+ SerialisationSupport support = graph.getService(SerialisationSupport.class);\r
+ Layer0 L0 = Layer0.getInstance(graph);\r
+\r
+ // TODO: this should be Root with name ""\r
+ procedure.execute(getRootIdentity(state, support, graph.getRootLibrary()));\r
+\r
+ // Declare internal and external roots\r
+ for(RootSpec r : configuration.roots) {\r
+ Resource type = graph.getPossibleType(r.resource, L0.Entity);\r
+ if(type == null) type = L0.Entity;\r
+ procedure.execute(new Identity(\r
+ state.ids.get(support.getTransientId(r.resource)),\r
+ new Root(r.name, graph.getURI(type))));\r
+ }\r
+\r
+ for(int i = 0; i < state.externals.size() ; i++) {\r
+ int parent = externalParents.get(i);\r
+ String name = externalNames.get(i);\r
+ procedure.execute(new Identity(externalBase + i, new External(parent,name)));\r
+ }\r
+ \r
+ }\r
+\r
+ @Override\r
+ public TreeMap<String, Variant> getExtensions() {\r
+ return state.extensions;\r
+ }\r
+\r
+ public File[] getFiles() {\r
+ return files;\r
+ }\r
+\r
+ private static <T> T tryClose(T c) throws IOException {\r
+ if (c != null && c instanceof Closeable)\r
+ ((Closeable) c).close();\r
+ return null;\r
+ }\r
+\r
+ public void closeStreams() throws IOException {\r
+ state.valueInput = tryClose(state.valueInput);\r
+ state.otherStatementsInput = tryClose(state.otherStatementsInput);\r
+ state.statementsOutput = tryClose(state.statementsOutput);\r
+ state.valueOutput = tryClose(state.valueOutput);\r
+ }\r
+\r
+ @Override\r
+ public void reset() throws Exception {\r
+ throw new UnsupportedOperationException();\r
+ }\r
+ \r
+ public long[] getResourceArray(ReadGraph graph) throws DatabaseException {\r
+ final SerialisationSupport ss = graph.getService(SerialisationSupport.class);\r
+ final long[] result = new long[state.ids.size()];\r
+ state.ids.forEachEntry(new TIntIntProcedure() {\r
+\r
+ @Override\r
+ public boolean execute(int a, int b) {\r
+ \r
+ try {\r
+ Resource r = ss.getResource(a);\r
+ result[b] = r.getResourceId();\r
+ } catch (DatabaseException e) {\r
+ e.printStackTrace();\r
+ }\r
+ \r
+ return true;\r
+ \r
+ }\r
+ });\r
+ return result;\r
+ }\r
+ \r
+ public DomainProcessorState getState() {\r
+ return state;\r
+ }\r
+ \r
+ public void forResourceStatements(ReadGraph graph, TransferableGraphSourceProcedure<int[]> procedure) throws Exception {\r
+ \r
+ int[] value = new int[4];\r
+ long length = state.otherStatementsInput.length();\r
+ state.otherStatementsInput.position(0);\r
+ \r
+ while(state.otherStatementsInput.position() < length) {\r
+ \r
+ int s = state.otherStatementsInput.readInt();\r
+ int subjectId = state.ids.get(s);\r
+ \r
+ boolean exclude = subjectId == -1;\r
+ \r
+ int size = state.otherStatementsInput.readInt();\r
+ for(int i=0;i<size;i++) {\r
+ int p = state.otherStatementsInput.readInt();\r
+ int o = state.otherStatementsInput.readInt();\r
+ if(!exclude) {\r
+ if(state.excludedShared.contains(o)) {\r
+ System.err.println("excluding shared " + s + " " + p + " " + o);\r
+ } else {\r
+ \r
+ int objectId = getExistingId(graph, o);\r
+ // The statement can be denied still\r
+ if(objectId != -2) {\r
+ value[0] = s;\r
+ value[1] = p;\r
+ int inverse = state.inverses.get(p);\r
+ if(inverse != 0) {\r
+ value[2] = inverse;\r
+ } else {\r
+ value[2] = -1;\r
+ }\r
+ value[3] = o;\r
+\r
+ procedure.execute(value);\r
+ \r
+ } else {\r
+ System.err.println("Denied (" + NameUtils.getSafeName(graph, getResource(graph, s)) + ", " + NameUtils.getSafeName(graph, getResource(graph, p)) + "," + NameUtils.getSafeName(graph, getResource(graph, o)) + ")");\r
+ }\r
+ \r
+ }\r
+ } else {\r
+ System.err.println("excluding shared " + s);\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ public void forValueResources(ReadGraph graph, TransferableGraphSourceProcedure<int[]> procedure) throws Exception {\r
+ int[] value = { 0 };\r
+ long length = state.valueInput.length();\r
+ while (state.valueInput.position() < length) {\r
+ value[0] = state.valueInput.readInt();\r
+ procedure.execute(value);\r
+ }\r
+ }\r
+\r
+ public TransferableGraphConfiguration2 getConfiguration() {\r
+ return configuration;\r
+ }\r
+\r
+ @Override\r
+ public void close() throws IOException {\r
+ synchronized (this) {\r
+ if (closed)\r
+ return;\r
+ closed = true;\r
+ }\r
+ closeStreams();\r
+ if (files != null) {\r
+ for (File f : files) {\r
+ Files.deleteIfExists(f.toPath());\r
+ }\r
+ }\r
+ }\r
+\r
+}
\ No newline at end of file