X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.graph.db%2Fsrc%2Forg%2Fsimantics%2Fgraph%2Fdb%2FGraphDependencyAnalyzer.java;fp=bundles%2Forg.simantics.graph.db%2Fsrc%2Forg%2Fsimantics%2Fgraph%2Fdb%2FGraphDependencyAnalyzer.java;h=5c5b1500c58836a5f2928d421b56751373778423;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git
diff --git a/bundles/org.simantics.graph.db/src/org/simantics/graph/db/GraphDependencyAnalyzer.java b/bundles/org.simantics.graph.db/src/org/simantics/graph/db/GraphDependencyAnalyzer.java
new file mode 100644
index 000000000..5c5b1500c
--- /dev/null
+++ b/bundles/org.simantics.graph.db/src/org/simantics/graph/db/GraphDependencyAnalyzer.java
@@ -0,0 +1,413 @@
+package org.simantics.graph.db;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.common.uri.UnescapedChildMapOfResource;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.exception.RuntimeDatabaseException;
+import org.simantics.db.request.Read;
+import org.simantics.graph.representation.External;
+import org.simantics.graph.representation.Identity;
+import org.simantics.graph.representation.IdentityDefinition;
+import org.simantics.graph.representation.Internal;
+import org.simantics.graph.representation.Optional;
+import org.simantics.graph.representation.Root;
+import org.simantics.graph.representation.TransferableGraph1;
+import org.simantics.utils.datastructures.Pair;
+
+import gnu.trove.map.hash.THashMap;
+import gnu.trove.map.hash.TIntIntHashMap;
+import gnu.trove.procedure.TObjectObjectProcedure;
+import gnu.trove.set.hash.THashSet;
+
+/**
+ * Analyses the dependencies between transferable graphs. Usage:
+ *
+GraphDependencyAnalyzer analyzer =
+ new GraphDependencyAnalyzer();
+for(TransferableGraph1 tg : tgs)
+ analyzer.addGraph(tg, tg);
+if(!analyzer.analyzeDependency()) {
+ analyzer.getConflicts()
+}
+else if(!analyzer.doesSatisfyExternalDependencies(graph)) {
+ analyzer.getUnsatisfiedDependencies()
+}
+else
+ for(TransferableGraph1 tg : analyzer.getSortedGraphs())
+ tg
+}
+ *
+ * @author Hannu Niemist�
+ */
+public class GraphDependencyAnalyzer> {
+
+ private ArrayList graphs = new ArrayList();
+ private IdentityNode root = new IdentityNode(null, null);
+
+ public static class IUList {
+ IU head;
+ IUList tail;
+
+ public IUList(IU head, IUList tail) {
+ this.head = head;
+ this.tail = tail;
+ }
+ }
+
+ public static Collection toCollection(IUList list) {
+ ArrayList result = new ArrayList();
+ while(list != null) {
+ result.add(list.head);
+ list = list.tail;
+ }
+ return result;
+ }
+
+ public static class IdentityNode {
+ IdentityNode parent;
+ String name;
+ IUList provides = null;
+ IUList providesOptionally = null;
+ IUList requires = null;
+ THashMap children = null;
+
+ public IdentityNode(IdentityNode parent, String name) {
+ this.parent = parent;
+ this.name = name;
+ }
+
+ public IdentityNode get(String name) {
+ if(children == null) {
+ children = new THashMap(4);
+ IdentityNode node = new IdentityNode(this, name);
+ children.put(name, node);
+ return node;
+ }
+ else {
+ IdentityNode node = children.get(name);
+ if(node == null) {
+ node = new IdentityNode(this, name);
+ children.put(name, node);
+ }
+ return node;
+ }
+ }
+
+ @Override
+ public String toString() {
+ if(parent == null || parent.parent == null) {
+ if(name.equals(""))
+ return "http:/";
+ else
+ return name;
+ }
+ return parent.toString() + "/" + name;
+ }
+
+ public IUList getRequires() {
+ return requires;
+ }
+ }
+
+ public static class IU implements Comparable {
+ final Comparable id;
+ final TransferableGraph1 tg;
+ IdentityNode[] identities;
+ THashMap tgLists = new THashMap();
+ THashSet dependencies = new THashSet();
+
+ public IU(Comparable id, TransferableGraph1 tg) {
+ this.id = id;
+ this.tg = tg;
+ }
+
+ public Object getId() {
+ return id;
+ }
+
+ public IUList pushThis(IUList node) {
+ IUList result = tgLists.get(node);
+ if(result == null) {
+ result = new IUList(this, node);
+ tgLists.put(node, result);
+ }
+ return result;
+ }
+
+ public IdentityNode defineIdentity(IdentityNode root, TIntIntHashMap identityIdToIdentityIndex, Identity identity) {
+ IdentityDefinition definition = identity.definition;
+ if(identities[identity.resource] != null) return identities[identity.resource];
+ if(definition instanceof External) {
+ External def = (External)definition;
+ IdentityNode in = identities[def.parent];
+ if(in == null)
+ in = defineIdentity(root, identityIdToIdentityIndex, tg.identities[identityIdToIdentityIndex.get(def.parent)]);
+ IdentityNode node = in.get(def.name);
+ node.requires = pushThis(node.requires);
+ identities[identity.resource] = node;
+ return node;
+ }
+ else if(definition instanceof Internal) {
+ Internal def = (Internal)definition;
+ IdentityNode in = identities[def.parent];
+ if(in == null)
+ in = defineIdentity(root, identityIdToIdentityIndex, tg.identities[identityIdToIdentityIndex.get(def.parent)]);
+ IdentityNode node = in.get(def.name);
+ node.provides = pushThis(node.provides);
+ identities[identity.resource] = node;
+ return node;
+ }
+ else if(definition instanceof Root) {
+ Root def = (Root)definition;
+ IdentityNode node = root.get(def.name);
+ identities[identity.resource] = node;
+ return node;
+ }
+ else if(definition instanceof Optional) {
+ Optional def = (Optional)definition;
+ IdentityNode node = identities[def.parent].get(def.name);
+ node.providesOptionally = pushThis(node.providesOptionally);
+ identities[identity.resource] = node;
+ return node;
+ }
+ throw new IllegalStateException("definition: " + definition);
+ }
+
+ public void findIdentities(IdentityNode root) {
+ identities = new IdentityNode[tg.resourceCount];
+ TIntIntHashMap identityIdToIdentityIndex = new TIntIntHashMap();
+ for(int i=0;i conflicts = new THashSet();
+ private THashSet handled = new THashSet();
+
+ private void addConflicts(IUList a) {
+ while(a != null) {
+ IUList b = a.tail;
+ while(b != null) {
+ conflicts.add(new IUPair(a.head, b.head));
+ b=b.tail;
+ }
+ a = a.tail;
+ }
+ }
+
+ private void addDependencies(IUList ius, IU dependency) {
+ while(ius != null) {
+ ius.head.dependencies.add(dependency);
+ ius = ius.tail;
+ }
+ }
+
+ private void analyzeDependency(IdentityNode node) {
+ if(node.provides != null) {
+ if(node.provides.tail != null) {
+ addConflicts(node.provides);
+ }
+ else {
+ if(node.requires != null && handled.add(new IUListPair(node.requires, node.provides.head)))
+ addDependencies(node.requires, node.provides.head);
+ }
+ }
+ else if(node.providesOptionally != null) {
+ if(handled.add(new IUListPair(node.requires, node.providesOptionally.head)))
+ addDependencies(node.requires, node.providesOptionally.head);
+ }
+ if(node.children != null)
+ for(IdentityNode child : node.children.values())
+ analyzeDependency(child);
+ }
+
+ /**
+ * Adds a graph to the analyzer
+ * @param id An identifier that is used when reporting the results.
+ * @param tg A tranferable graph.
+ */
+ public void addGraph(T id, TransferableGraph1 tg) {
+ assert(id != null);
+ IU iu = new IU(id, tg);
+ graphs.add(iu);
+ iu.findIdentities(root);
+ }
+
+ ArrayList sortedGraphs = new ArrayList();
+
+ class Sorter {
+ THashSet alreadyInList = new THashSet();
+ @SuppressWarnings("unchecked")
+ public void add(IU iu) {
+ if(alreadyInList.add(iu)) {
+ /*System.out.println(iu.id);
+ for(IU dep : iu.dependencies)
+ System.out.println(" " + dep.id);
+ */
+ ArrayList list = new ArrayList(iu.dependencies);
+ Collections.sort(list);
+ for(IU dep : list)
+ add(dep);
+ sortedGraphs.add((T)iu.id);
+ }
+ }
+ }
+
+ private void sortByDependency() {
+ //System.out.println("SortByDependency");
+ sortedGraphs.clear();
+ Sorter sorter = new Sorter();
+ Collections.sort(graphs);
+ for(IU iu : graphs)
+ sorter.add(iu);
+ }
+
+ /**
+ * Analyzes the dependency between transferable graphs
+ * @return true, if there are no conflicts or cyclic dependencies
+ */
+ public boolean analyzeDependency() {
+ analyzeDependency(root);
+ sortByDependency();
+ return conflicts.isEmpty();
+ }
+
+ private ArrayList unsatisfiedRequirements = new ArrayList();
+
+ private void doesSatisfyExternalDependencies(final ReadGraph g, IdentityNode node, Resource resource) {
+ try {
+ final Map childMap = g.syncRequest(new UnescapedChildMapOfResource(resource));
+ if(node.children != null)
+ node.children.forEachEntry(new TObjectObjectProcedure() {
+ @Override
+ public boolean execute(String name, IdentityNode child) {
+ Resource childResource = childMap.get(name);
+ if(childResource == null) {
+ if(child.provides == null && child.requires != null)
+ unsatisfiedRequirements.add(child);
+ }
+ else
+ doesSatisfyExternalDependencies(g, child, childResource);
+ return true;
+ }
+ });
+ } catch(DatabaseException e) {
+ throw new RuntimeDatabaseException(e);
+ }
+ }
+
+ /**
+ * Returns true if previously analyzed graphs are importable,
+ * i.e. all external dependencies are satisfied. This method can be called
+ * after {@code analyzeDependency} method.
+ */
+ public boolean doesSatisfyExternalDependencies(ReadGraph g) throws DatabaseException {
+ unsatisfiedRequirements.clear();
+ IdentityNode rootLibrary = root.get("");
+ if(rootLibrary != null)
+ try {
+ doesSatisfyExternalDependencies(g, root.get(""), g.getRootLibrary());
+ } catch(RuntimeDatabaseException e) {
+ Throwable cause = e.getCause();
+ if(cause instanceof DatabaseException)
+ throw (DatabaseException)cause;
+ else
+ throw e;
+ }
+ return unsatisfiedRequirements.isEmpty();
+ }
+
+ public Read queryExternalDependenciesSatisfied = new Read() {
+ @Override
+ public Boolean perform(ReadGraph graph) throws DatabaseException {
+ return doesSatisfyExternalDependencies(graph);
+ }
+ };
+
+ /**
+ * Returns a collection URIs of all resources not found in the database.
+ * This method can be called after {@code doesSatisfyExternalDependencies} method.
+ * @return
+ */
+ public ArrayList getUnsatisfiedDependencies() {
+ return unsatisfiedRequirements;
+ }
+
+ /**
+ * Returns all conflicting pairs of graphs. This method can be called after
+ * {@code analyzeDependency} method.
+ */
+ @SuppressWarnings("unchecked")
+ public Collection> getConflicts() {
+ ArrayList> result = new ArrayList>();
+ for(IUPair pair : conflicts)
+ result.add(Pair.make((T)pair.a.id, (T)pair.b.id));
+ return result;
+ }
+
+ /**
+ * Returns a list sorted by dependency so that if A depends on B
+ * then A is in the list later than B.
+ * @return
+ */
+ public List getSortedGraphs() {
+ return sortedGraphs;
+ }
+
+}