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