+/*******************************************************************************\r
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * Foster Wheeler Energia Oy - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.interop.test;\r
+\r
+import java.util.ArrayList;\r
+import java.util.Arrays;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Comparator;\r
+import java.util.List;\r
+import java.util.Stack;\r
+\r
+import org.simantics.db.Builtins;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.Statement;\r
+import org.simantics.db.exception.DoesNotContainValueException;\r
+import org.simantics.db.exception.ManyObjectsForFunctionalRelationException;\r
+import org.simantics.db.exception.ServiceException;\r
+import org.simantics.db.exception.ValidationException;\r
+import org.simantics.layer0.utils.direct.GraphUtils;\r
+import org.simantics.utils.datastructures.BijectionMap;\r
+import org.simantics.utils.datastructures.MapList;\r
+\r
+/**\r
+ * Compares two subgraphs and reports differences.\r
+ * \r
+ * Assumes that subgraphs (defined using traverse relations) are not cyclic.\r
+ * \r
+ * Assumes that properties can be used to identify objects, if relation type is not enough.\r
+ * \r
+ * \r
+ * \r
+ * @author Marko Luukkainen <marko.luukkainen@vtt.fi>\r
+ *\r
+ */\r
+public class GraphComparator {\r
+\r
+ \r
+ private List<Resource> traversed = new ArrayList<Resource>(); // list of relations that are traversed (and tested)\r
+ private List<Resource> tested = new ArrayList<Resource>(); // list of relations that are tested, but not traversed\r
+ \r
+ \r
+ private List<Statement> changes1 = new ArrayList<Statement>();\r
+ private List<Statement> changes2 = new ArrayList<Statement>();\r
+ private BijectionMap<Statement, Statement> comparable = new BijectionMap<Statement, Statement>();\r
+ \r
+ \r
+ // runtime attributes\r
+ \r
+ private ReadGraph g;\r
+ private Builtins b;\r
+ \r
+ ArrayList<Resource> rs1 = new ArrayList<Resource>();\r
+ ArrayList<Resource> rs2 = new ArrayList<Resource>();\r
+ ArrayList<Statement> ss1 = new ArrayList<Statement>();\r
+ ArrayList<Statement> ss2 = new ArrayList<Statement>();\r
+ Comparator<Statement> scomp = new StatementComparator();\r
+ Comparator<Resource> rcomp = new ResourceComparator();\r
+\r
+ \r
+ public void addTraversed(Resource rel) {\r
+ traversed.add(rel);\r
+ }\r
+ \r
+ public void addTraversed(Collection<Resource> rels) {\r
+ traversed.addAll(rels);\r
+ }\r
+ \r
+ public void addTested(Resource rel) {\r
+ tested.add(rel);\r
+ }\r
+ \r
+ public void addTested(Collection<Resource> rels) {\r
+ tested.addAll(rels);\r
+ }\r
+ \r
+ public void clearRels() {\r
+ traversed.clear();\r
+ tested.clear();\r
+ }\r
+ \r
+ public void test(ReadGraph g, Resource r1, Resource r2) throws ServiceException, DoesNotContainValueException, ValidationException {\r
+ this.g = g;\r
+ this.b = g.getBuiltins();\r
+ \r
+ changes1.clear();\r
+ changes2.clear();\r
+ \r
+ Stack<Resource> stack1 = new Stack<Resource>();\r
+ Stack<Resource> stack2 = new Stack<Resource>();\r
+ stack1.push(r1);\r
+ stack2.push(r2);\r
+ \r
+ ArrayList<Statement> ss1 = new ArrayList<Statement>();\r
+ ArrayList<Statement> ss2 = new ArrayList<Statement>();\r
+ \r
+ while (!stack1.isEmpty()) {\r
+ r1 = stack1.pop();\r
+ r2 = stack2.pop();\r
+ \r
+ System.out.println("test " + GraphUtils.getReadableName(g, r1) + " " + GraphUtils.getReadableName(g, r2));\r
+ compareProps(r1, r2);\r
+ \r
+ for (Resource rel : tested) {\r
+ filterTraversed(g.getStatements(r1, rel), ss1);\r
+ filterTraversed(g.getStatements(r2, rel), ss2);\r
+ \r
+ compareStatement(ss1, ss2, null, null);\r
+ ss1.clear();\r
+ ss2.clear();\r
+ }\r
+ \r
+ for (Resource rel : traversed) {\r
+ ss1.addAll(g.getStatements(r1, rel));\r
+ ss2.addAll(g.getStatements(r2, rel));\r
+ compareStatement(ss1, ss2, stack1, stack2);\r
+ ss1.clear();\r
+ ss2.clear();\r
+ }\r
+ }\r
+ }\r
+ \r
+ public Collection<Statement> getChanges1() {\r
+ return changes1;\r
+ }\r
+ \r
+ public Collection<Statement> getChanges2() {\r
+ return changes2;\r
+ }\r
+ \r
+ public BijectionMap<Statement, Statement> getComparable() {\r
+ return comparable;\r
+ }\r
+ \r
+ private void filterTraversed(Collection<Statement> in, Collection<Statement> out) throws ServiceException {\r
+ for (Statement s : in) {\r
+ boolean usable = true;\r
+ for (Resource r : traversed) {\r
+ if (g.isSubrelationOf(s.getPredicate(),r)) {\r
+ usable = false;\r
+ break;\r
+ }\r
+ }\r
+ if (usable) {\r
+ out.add(s);\r
+ }\r
+ \r
+ }\r
+ }\r
+ \r
+ private void compareStatement(List<Statement> ss1, List<Statement> ss2, Stack<Resource> stack1, Stack<Resource> stack2) throws ServiceException, DoesNotContainValueException, ValidationException {\r
+ Collections.sort(ss1, scomp);\r
+ Collections.sort(ss2, scomp);\r
+ \r
+ int i1 = 0;\r
+ int i2 = 0;\r
+ \r
+ while (true) {\r
+ if (i1 >= ss1.size()) {\r
+ if (i2 >= ss2.size()) {\r
+ break;\r
+ } else {\r
+ while (i2 < ss2.size()) {\r
+ changes2.add(ss2.get(i2));\r
+ i2++;\r
+ }\r
+ break;\r
+ }\r
+ } else if (i2 >= ss2.size()) {\r
+ while (i1 < ss1.size()) {\r
+ changes1.add(ss1.get(i1));\r
+ i1++;\r
+ }\r
+ break;\r
+ }\r
+ int same1 = sameRel(ss1, i1);\r
+ int same2 = sameRel(ss2, i2);\r
+ int c = rcomp.compare(ss1.get(i1).getPredicate(),ss2.get(i2).getPredicate());\r
+ if (c == 0) {\r
+ compareObject(ss1, i1, same1, ss2, i2, same2,stack1,stack2);\r
+ i1+=same1;\r
+ i2+=same2;\r
+ } else if (c < 0) {\r
+ for (int i = 0; i < same1; i++) {\r
+ changes1.add(ss1.get(i+i1));\r
+ }\r
+ i1 += same1;\r
+ } else {\r
+ for (int i = 0; i < same2; i++) {\r
+ changes2.add(ss2.get(i+i2));\r
+ }\r
+ i2 += same2;\r
+ }\r
+ \r
+ }\r
+ }\r
+ \r
+ private int sameRel(List<Statement> statements, int off) {\r
+ if (statements.size() <= off)\r
+ return 0;\r
+ int same = 1;\r
+ long id = statements.get(off).getPredicate().getResourceId();\r
+ for (int i = off+1; i <statements.size(); i++) {\r
+ if (statements.get(i).getPredicate().getResourceId() == id)\r
+ same++;\r
+ else \r
+ break;\r
+ }\r
+ return same;\r
+ \r
+ }\r
+\r
+ \r
+ private void compareObject(List<Statement> ss1, int off1, int len1, List<Statement> ss2, int off2, int len2, Stack<Resource> stack1, Stack<Resource> stack2) throws ServiceException, DoesNotContainValueException, ValidationException {\r
+ boolean[] used1 = new boolean[len1];\r
+ for (int i = 0; i < used1.length; i++) {\r
+ used1[i] = false;\r
+ }\r
+ \r
+ boolean[] used2 = new boolean[len2];\r
+ for (int i = 0; i < used2.length; i++) {\r
+ used2[i] = false;\r
+ }\r
+ \r
+ // left, right, difference\r
+ List<List<Integer>> differences = new ArrayList<List<Integer>>();\r
+ for (int i1 = off1; i1 < off1 + len1; i1++) {\r
+ Statement s1 = ss1.get(i1);\r
+ //Map<Integer,Integer> differences = new HashMap<Integer, Integer>();\r
+ List<Integer> diff = new ArrayList<Integer>();\r
+ for (int i2 = off2; i2 < off2 + len2; i2++) {\r
+ Statement s2 = ss2.get(i2);\r
+ if (!compareType(s1.getObject(), s2.getObject())) {\r
+ diff.add(Integer.MAX_VALUE);\r
+ continue;\r
+ }\r
+ diff.add(propsDiffCount(s1.getObject(), s2.getObject()));\r
+ }\r
+ differences.add(diff);\r
+ }\r
+ // difference, left\r
+ MapList<Integer, Integer> priorities = new MapList<Integer, Integer>();\r
+ for (int i = 0; i < differences.size(); i++) {\r
+ List<Integer> list = differences.get(i);\r
+ for (int j = 0; j < list.size(); j++) {\r
+ priorities.add(list.get(j), i);\r
+ }\r
+ }\r
+ \r
+ Integer[] pris = priorities.getKeys(new Integer[]{});\r
+ Arrays.sort(pris);\r
+ \r
+ for (Integer pri : pris) {\r
+ if (pri == Integer.MAX_VALUE)\r
+ continue;\r
+ List<Integer> i1s = priorities.getValues(pri);\r
+ for (Integer i1 : i1s) {\r
+ if (used1[i1])\r
+ continue;\r
+ List<Integer> i2diff = differences.get(i1);\r
+ for (int i2 = 0; i2 < i2diff.size(); i2++) {\r
+ if (i2diff.get(i2) == pri) {\r
+ if (used2[i2])\r
+ break;\r
+ used1[i1] = true;\r
+ used2[i2] = true;\r
+ if (stack1 != null) {\r
+ stack1.add(ss1.get(i1+off1).getObject());\r
+ stack2.add(ss2.get(i2+off2).getObject());\r
+ } else {\r
+ // TODO : how should we report non traversed differences\r
+ // using compareProps assumes that referenced objects are the same (references are not different)\r
+ // using propsDiffCount assumes that objects are different, and cannot report changes in referred object.\r
+ \r
+ //compareProps(ss1.get(i1+off1).getObject(), ss2.get(i2+off2).getObject());\r
+ int diff = propsDiffCount(ss1.get(i1+off1).getObject(), ss2.get(i2+off2).getObject());\r
+ if (diff != 0) {\r
+ changes1.add(ss1.get(i1+off1));\r
+ changes2.add(ss2.get(i2+off2));\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ for (int i1 = off1; i1 < off1 + len1; i1++) {\r
+ if (!used1[i1-off1])\r
+ changes1.add(ss1.get(i1));\r
+ }\r
+ for (int i2 = off2; i2 < off2 + len2; i2++) {\r
+ if (!used2[i2-off2])\r
+ changes2.add(ss2.get(i2));\r
+ }\r
+ }\r
+ \r
+ private boolean compareType(Resource r1, Resource r2) throws ServiceException, ManyObjectsForFunctionalRelationException {\r
+ rs1.addAll(g.getObjects(r1, b.InstanceOf));\r
+ rs2.addAll(g.getObjects(r2, b.InstanceOf));\r
+ if (rs1.size() != rs2.size()) {\r
+ rs1.clear();\r
+ rs2.clear();\r
+ return false;\r
+ }\r
+ Collections.sort(rs1, rcomp);\r
+ Collections.sort(rs2, rcomp);\r
+ for (int i = 0; i < rs1.size(); i++) {\r
+ int c = rcomp.compare(rs1.get(i), rs2.get(i));\r
+ if (c != 0) {\r
+ rs1.clear();\r
+ rs2.clear();\r
+ return false;\r
+ }\r
+ }\r
+ \r
+ rs1.clear();\r
+ rs2.clear();\r
+ \r
+ return true;\r
+ }\r
+ \r
+ /**\r
+ * compares properties, assumes functional relations\r
+ * @param r1\r
+ * @param r2\r
+ * @throws ManyObjectsForFunctionalRelationException\r
+ * @throws ServiceException\r
+ * @throws DoesNotContainValueException\r
+ */\r
+ private void compareProps(Resource r1, Resource r2) throws ManyObjectsForFunctionalRelationException, ServiceException, DoesNotContainValueException {\r
+ ArrayList<Statement> ss1 = new ArrayList<Statement>();\r
+ ArrayList<Statement> ss2 = new ArrayList<Statement>();\r
+ ss1.addAll(g.getStatements(r1, b.HasProperty));\r
+ ss2.addAll(g.getStatements(r2, b.HasProperty));\r
+ Collections.sort(ss1, scomp);\r
+ Collections.sort(ss2, scomp);\r
+ \r
+ int i1 = 0; \r
+ int i2 = 0;\r
+ \r
+ while (true) {\r
+ if (i1 >= ss1.size()) {\r
+ if (i2 >= ss2.size())\r
+ break;\r
+ else {\r
+ while (i2 < ss2.size()) {\r
+ changes2.add(ss2.get(i2));\r
+ i2++;\r
+ }\r
+ break;\r
+ }\r
+ } else if (i2 >= ss2.size()) {\r
+ while (i1 < ss1.size()) {\r
+ changes1.add(ss1.get(i1));\r
+ i1++;\r
+ }\r
+ break;\r
+ }\r
+ Statement s1 = ss1.get(i1);\r
+ Statement s2 = ss2.get(i2);\r
+ int c = scomp.compare(s1, s2);\r
+ switch (c) {\r
+ case 0:{\r
+ boolean b1 = g.hasValue(s1.getObject());\r
+ boolean b2 = g.hasValue(s2.getObject());\r
+ if (b1 == b2) {\r
+ if (b1) {\r
+ Object v1 = g.getValue(s1.getObject());\r
+ Object v2 = g.getValue(s2.getObject());\r
+ boolean eq = false;\r
+ if (v1 instanceof Object[] && v2 instanceof Object[])\r
+ eq = Arrays.deepEquals((Object[])v1, (Object[])v2);\r
+ else\r
+ eq = v1.equals(v2);\r
+ if (!eq) {\r
+ changes1.add(s1);\r
+ changes2.add(s2);\r
+ comparable.map(s1, s2);\r
+ }\r
+ } else {\r
+ compareProps(s1.getObject(), s2.getObject());\r
+ }\r
+ } else {\r
+ changes1.add(s1);\r
+ changes2.add(s2);\r
+ comparable.map(s1, s2);\r
+ }\r
+ i1++;\r
+ i2++;\r
+ break;\r
+ }\r
+ case -1:{\r
+ changes1.add(s1);\r
+ i1++;\r
+ break;\r
+ }\r
+ \r
+ case 1:{\r
+ changes2.add(s2);\r
+ i2++;\r
+ break;\r
+ }\r
+ }\r
+ \r
+ \r
+ }\r
+ \r
+ ss1.clear();\r
+ ss2.clear();\r
+ \r
+ }\r
+ \r
+ private int propsDiffCount(Resource r1, Resource r2) throws ServiceException, DoesNotContainValueException, ValidationException {\r
+ ArrayList<Statement> ss1 = new ArrayList<Statement>();\r
+ ArrayList<Statement> ss2 = new ArrayList<Statement>();\r
+ ss1.addAll(g.getStatements(r1, b.HasProperty));\r
+ ss2.addAll(g.getStatements(r2, b.HasProperty));\r
+ //System.out.println("Props count " + GraphUtils.getReadableName(g, r1) + " " + GraphUtils.getReadableName(g, r2));\r
+ Collections.sort(ss1, scomp);\r
+ Collections.sort(ss2, scomp);\r
+ \r
+ int count = 0;\r
+ \r
+ int i1 = 0; \r
+ int i2 = 0;\r
+ \r
+ while (true) {\r
+ if (i1 >= ss1.size()) {\r
+ if (i2 >= ss2.size())\r
+ break;\r
+ else {\r
+ while (i2 < ss2.size()) {\r
+ count++;\r
+ i2++;\r
+ }\r
+ break;\r
+ }\r
+ } else if (i2 >= ss2.size()) {\r
+ while (i1 < ss1.size()) {\r
+ count++;\r
+ i1++;\r
+ }\r
+ break;\r
+ }\r
+ Statement s1 = ss1.get(i1);\r
+ Statement s2 = ss2.get(i2);\r
+ int c = scomp.compare(s1, s2);\r
+ switch (c) {\r
+ case 0:{\r
+ boolean b1 = g.hasValue(s1.getObject());\r
+ boolean b2 = g.hasValue(s2.getObject());\r
+ if (b1 == b2) {\r
+ if (b1) {\r
+ Object v1 = g.getValue(s1.getObject());\r
+ Object v2 = g.getValue(s2.getObject());\r
+ boolean eq = false;\r
+ if (v1 instanceof Object[] && v2 instanceof Object[])\r
+ eq = Arrays.deepEquals((Object[])v1, (Object[])v2);\r
+ else\r
+ eq = v1.equals(v2);\r
+ if (!eq) {\r
+ count++;\r
+ }\r
+ //System.out.println("Prop count values " + v1 + " " + v2);\r
+ } else {\r
+ count += propsDiffCount(s1.getObject(), s2.getObject());\r
+ }\r
+ } else {\r
+ //System.out.println("Props count structural vs literal");\r
+ count++;\r
+ }\r
+ i1++;\r
+ i2++;\r
+ break;\r
+ }\r
+ case -1:{\r
+ count++;\r
+ i1++;\r
+ break;\r
+ }\r
+ \r
+ case 1:{\r
+ count++;\r
+ i2++;\r
+ break;\r
+ }\r
+ }\r
+ \r
+\r
+ }\r
+ \r
+ ss1.clear();\r
+ ss2.clear();\r
+ return count;\r
+ }\r
+\r
+ \r
+ \r
+ public class StatementComparator implements Comparator<Statement> {\r
+ @Override\r
+ public int compare(Statement o1, Statement o2) {\r
+ if (o1.getPredicate().getResourceId() < o2.getPredicate().getResourceId())\r
+ return -1;\r
+ if (o1.getPredicate().getResourceId() > o2.getPredicate().getResourceId())\r
+ return 1;\r
+ return 0;\r
+ }\r
+ }\r
+ \r
+\r
+ \r
+ public class ResourceComparator implements Comparator<Resource> {\r
+ @Override\r
+ public int compare(Resource o1, Resource o2) {\r
+ if (o1.getResourceId() < o2.getResourceId())\r
+ return -1;\r
+ if (o2.getResourceId() > o2.getResourceId())\r
+ return 1;\r
+ return 0;\r
+ }\r
+ }\r
+}\r