/******************************************************************************* * 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.HashSet; import java.util.List; import java.util.Set; import java.util.Stack; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.Statement; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.exception.DoesNotContainValueException; import org.simantics.db.exception.ServiceException; import org.simantics.db.exception.ValidationException; import org.simantics.layer0.Layer0; import org.simantics.utils.datastructures.BijectionMap; import org.simantics.utils.datastructures.MapList; import org.simantics.utils.datastructures.Pair; /** * 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 Resource r1; private Resource r2; 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 nonTraversed = new ArrayList(); // list of relations that are not traversed private List nonTested = new ArrayList(); // list of relations that are not tested private List changes1 = new ArrayList(); private List changes2 = new ArrayList(); private List> modifications = new ArrayList>(); private Set changes1Set = new HashSet(); private Set changes2Set = new HashSet(); private Set> modificationsSet = new HashSet>(); private BijectionMap comparableStatements = new BijectionMap(); private BijectionMap comparableResources = new BijectionMap(); private ObjectComparator comparator; // runtime attributes private ReadGraph g; private Layer0 b; public GraphComparator() { comparator = new TypeComparator(); } public GraphComparator(ObjectComparator comparator) { this.comparator = comparator; } ArrayList ss1 = new ArrayList(); ArrayList ss2 = new ArrayList(); Comparator scomp = new PredicateComparator(); Comparator rcomp = new ResourceComparator(); public void addTraversed(Resource rel) { traversed.add(rel); } public void addTraversed(Collection rels) { traversed.addAll(rels); } public void addNonTraversed(Resource rel) { nonTraversed.add(rel); } public void addNonTraversed(Collection rels) { nonTraversed.addAll(rels); } public void addTested(Resource rel) { tested.add(rel); } public void addTested(Collection rels) { tested.addAll(rels); } public void addNonTested(Resource rel) { nonTested.add(rel); } public void addNonTested(Collection rels) { nonTested.addAll(rels); } public void clearRels() { traversed.clear(); tested.clear(); } public void test(ReadGraph g, Resource r1, Resource r2) throws DatabaseException { this.g = g; this.b = Layer0.getInstance(g); this.r1 = r1; this.r2 = r2; changes1Set.clear(); changes2Set.clear(); modificationsSet.clear(); changes1.clear(); changes2.clear(); modifications.clear(); comparableResources.clear(); Stack stack1 = new Stack(); Stack stack2 = new Stack(); stack1.push(r1); stack2.push(r2); List ss1 = new ArrayList(); List ss2 = new ArrayList(); while (!stack1.isEmpty()) { r1 = stack1.pop(); r2 = stack2.pop(); if (comparableResources.contains(r1, r2)) { System.out.println("already tested " + NameUtils.getSafeName(g, r1) + " " + NameUtils.getSafeName(g, r2)); continue; } comparableResources.map(r1, r2); System.out.println("test " + NameUtils.getSafeName(g, r1) + " " + NameUtils.getSafeName(g, r2)); compareProps(r1, r2); for (Resource rel : tested) { ss1.addAll(g.getStatements(r1, rel)); ss2.addAll(g.getStatements(r2, rel)); ss1 = filterAsserted(r1, ss1); ss2 = filterAsserted(r2, ss2); ss1 = filterTraversed(ss1); ss2 = filterTraversed(ss2); ss1 = filterNonTested(ss1); ss2 = filterNonTested(ss2); compareStatements(ss1, ss2, null, null); ss1.clear(); ss2.clear(); } for (Resource rel : traversed) { ss1.addAll(g.getStatements(r1, rel)); ss2.addAll(g.getStatements(r2, rel)); ss1 = filterAsserted(r1, ss1); ss2 = filterAsserted(r2, ss2); compareStatements(ss1, ss2, stack1, stack2); ss1.clear(); ss2.clear(); } } } public BijectionMap getComparableStatements() { return comparableStatements; } public GraphChanges getChanges() { return new GraphChanges(r1,r2,changes1,changes2,modifications); } private List filterAsserted(Resource r, Collection in) throws ServiceException { List out = new ArrayList(); for (Statement s : in) { if (!s.isAsserted(r)) out.add(s); } return out; } private String printStatement(ReadGraph graph, Statement s) throws ValidationException, ServiceException { return NameUtils.getSafeName(graph, s.getSubject()) + " " + NameUtils.getSafeName(graph, s.getPredicate()) + " " + NameUtils.getSafeName(graph, s.getObject()); } private List filterTraversed(List in) throws ServiceException { return filter(traversed, in); } private List filterNonTested(List in) throws ServiceException { return filter(nonTested, in); } private List filter(Collection toFilter, List in) throws ServiceException { if (toFilter.size() == 0) return in; List out = new ArrayList(); for (Statement s : in) { boolean usable = true; for (Resource r : toFilter) { if (g.isSubrelationOf(s.getPredicate(),r)) { usable = false; break; } } if (usable) { out.add(s); } } return out; } private void addDeletion(Statement s) { if (!changes1Set.contains(s)) { changes1Set.add(s); changes1.add(s); } } private void addAddition(Statement s) { if (!changes2Set.contains(s)) { changes2Set.add(s); changes2.add(s); } } private void addModification(Statement s1, Statement s2) { Pair mod = new Pair(s1,s2); if (!modificationsSet.contains(mod)) { modificationsSet.add(mod); modifications.add(mod); } } private void compareStatements(List ss1, List ss2, Stack stack1, Stack stack2) throws DatabaseException { 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()) { System.out.println("Compare Statement diff2 " + printStatement(g,ss2.get(i2))); addAddition(ss2.get(i2)); i2++; } break; } } else if (i2 >= ss2.size()) { while (i1 < ss1.size()) { System.out.println("Compare Statement diff1 " + printStatement(g,ss1.get(i1))); addDeletion(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) { compareStatements(ss1, i1, same1, ss2, i2, same2,stack1,stack2); i1+=same1; i2+=same2; } else if (c < 0) { for (int i = 0; i < same1; i++) { System.out.println("Compare Statement diff1 " + printStatement(g,ss1.get(i+i1))); addDeletion(ss1.get(i+i1)); } i1 += same1; } else { for (int i = 0; i < same2; i++) { System.out.println("Compare Statement diff2 " + printStatement(g,ss2.get(i+i2))); addAddition(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 DatabaseException { 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); List diff = new ArrayList(); for (int i2 = off2; i2 < off2 + len2; i2++) { Statement s2 = ss2.get(i2); diff.add(compareObject(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]) continue; used1[i1] = true; used2[i2] = true; Statement s1 = ss1.get(i1+off1); Statement s2 = ss2.get(i2+off2); if (stack1 != null) { stack1.add(s1.getObject()); stack2.add(s2.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. // 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)); // modifies.add(new Pair(ss1.get(i1+off1), ss2.get(i2+off2))); // } } comparableStatements.map(s1, s2); //comparableResources.map(s1.getObject(), s2.getObject()); break; } } } } for (int i1 = off1; i1 < off1 + len1; i1++) { if (!used1[i1-off1]) { System.out.println("Compare Object diff1 " + printStatement(g,ss1.get(i1))); addDeletion(ss1.get(i1)); } } for (int i2 = off2; i2 < off2 + len2; i2++) { if (!used2[i2-off2]) { System.out.println("Compare Object diff2 " + printStatement(g,ss2.get(i2))); addAddition(ss2.get(i2)); } } } /** * compares properties, assumes functional relations * @param r1 * @param r2 * @throws ServiceException * @throws DoesNotContainValueException * @throws ValidationException */ private void compareProps(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)); 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()) { System.out.println("Compare Prop diff2 " + printStatement(g,ss2.get(i2))); addAddition(ss2.get(i2)); i2++; } break; } } else if (i2 >= ss2.size()) { while (i1 < ss1.size()) { System.out.println("Compare Prop diff1 " + printStatement(g,ss1.get(i1))); addDeletion(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 = compareValue(v1, v2); if (!eq) { addModification(s1, s2); comparableStatements.map(s1, s2); comparableResources.map(s1.getObject(),s2.getObject()); } } else { compareProps(s1.getObject(), s2.getObject()); } } else { addModification(s1, s2); comparableStatements.map(s1, s2); comparableResources.map(s1.getObject(),s2.getObject()); } i1++; i2++; break; } case -1:{ System.out.println("Compare Prop diff1s " + printStatement(g,s1)); addDeletion(s1); i1++; break; } case 1:{ System.out.println("Compare Prop diff2s " + printStatement(g,s2)); addAddition(s2); i2++; break; } } } ss1.clear(); ss2.clear(); } public static boolean compareValue(Object v1, Object v2) { if (v1 instanceof Object[] && v2 instanceof Object[]) return Arrays.deepEquals((Object[])v1, (Object[])v2); else if (v1 instanceof int[] && v2 instanceof int[]) return Arrays.equals((int[])v1, (int[])v2); else if (v1 instanceof float[] && v2 instanceof float[]) return Arrays.equals((float[])v1, (float[])v2); else if (v1 instanceof double[] && v2 instanceof double[]) return Arrays.equals((double[])v1, (double[])v2); else if (v1 instanceof long[] && v2 instanceof long[]) return Arrays.equals((long[])v1, (long[])v2); else if (v1 instanceof byte[] && v2 instanceof byte[]) return Arrays.equals((byte[])v1, (byte[])v2); else if (v1 instanceof boolean[] && v2 instanceof boolean[]) return Arrays.equals((boolean[])v1, (boolean[])v2); else return v1.equals(v2); } public static class PredicateComparator 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 static class FullStatementComparator implements Comparator { @Override public int compare(Statement o1, Statement o2) { if (o1.getSubject().getResourceId() < o2.getSubject().getResourceId()) return -1; if (o1.getSubject().getResourceId() > o2.getSubject().getResourceId()) return 1; if (o1.getPredicate().getResourceId() < o2.getPredicate().getResourceId()) return -1; if (o1.getPredicate().getResourceId() > o2.getPredicate().getResourceId()) return 1; if (o1.getObject().getResourceId() < o2.getObject().getResourceId()) return -1; if (o1.getObject().getResourceId() > o2.getObject().getResourceId()) return 1; return 0; } } public static class ResourceComparator implements Comparator { @Override public int compare(Resource o1, Resource o2) { if (o1.getResourceId() < o2.getResourceId()) return -1; if (o1.getResourceId() > o2.getResourceId()) return 1; return 0; } } }