From: luukkainen Date: Fri, 21 May 2010 13:11:43 +0000 (+0000) Subject: Subgraph difference tester X-Git-Tag: v1.31.0~139 X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=commitdiff_plain;h=ebdc4f0b3aff0d1684efc80e167ad4cd710c7915;p=simantics%2Finterop.git Subgraph difference tester git-svn-id: https://www.simantics.org/svn/simantics/interoperability/trunk@15820 ac1ea38d-2e2b-0410-8846-a27921b304fc --- diff --git a/org.simantics.interop/plugin.xml b/org.simantics.interop/plugin.xml index a745faf..250fcbc 100644 --- a/org.simantics.interop/plugin.xml +++ b/org.simantics.interop/plugin.xml @@ -21,5 +21,15 @@ + + + + diff --git a/org.simantics.interop/src/org/simantics/interop/test/GraphComparator.java b/org.simantics.interop/src/org/simantics/interop/test/GraphComparator.java new file mode 100644 index 0000000..10f1265 --- /dev/null +++ b/org.simantics.interop/src/org/simantics/interop/test/GraphComparator.java @@ -0,0 +1,531 @@ +/******************************************************************************* + * 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.Builtins; +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.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 Builtins 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 = g.getBuiltins(); + + 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; + } + } +} diff --git a/org.simantics.interop/src/org/simantics/interop/test/GraphComparatorViewer.java b/org.simantics.interop/src/org/simantics/interop/test/GraphComparatorViewer.java new file mode 100644 index 0000000..0f8dce0 --- /dev/null +++ b/org.simantics.interop/src/org/simantics/interop/test/GraphComparatorViewer.java @@ -0,0 +1,275 @@ +/******************************************************************************* + * 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.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.DropTarget; +import org.eclipse.swt.dnd.DropTargetAdapter; +import org.eclipse.swt.dnd.DropTargetEvent; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.part.ViewPart; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.Session; +import org.simantics.db.Statement; +import org.simantics.db.common.ResourceArray; +import org.simantics.db.common.request.ReadRequest; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.exception.InvalidResourceReferenceException; +import org.simantics.db.exception.ServiceException; +import org.simantics.db.exception.ValidationException; +import org.simantics.db.request.Read; +import org.simantics.db.service.SerialisationSupport; +import org.simantics.layer0.utils.direct.GraphUtils; +import org.simantics.ui.SimanticsUI; +import org.simantics.ui.dnd.LocalObjectTransfer; +import org.simantics.ui.dnd.ResourceReferenceTransfer; +import org.simantics.ui.dnd.ResourceTransferUtils; +import org.simantics.ui.utils.ResourceAdaptionUtils; +import org.simantics.utils.datastructures.BijectionMap; + +/** + * Simple multi line text viewer for seeing differences in two subgraphs. + * + * @author Marko Luukkainen + * + */ +public class GraphComparatorViewer extends ViewPart{ + + private Session session; + + private Composite composite; + + private Label resourceText1; + private Label resourceText2; + + private Text text1; + private Text text2; + + private GraphComparator comparator = new GraphComparator(); + + @Override + public void createPartControl(Composite parent) { + composite = new Composite(parent, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + + session = SimanticsUI.getSession(); + + Composite topComposite = new Composite(composite, SWT.BORDER); + topComposite.setLayout(new GridLayout(3, false)); + text1 = new Text(composite, SWT.MULTI|SWT.V_SCROLL); + text2 = new Text(composite, SWT.MULTI|SWT.V_SCROLL); + + GridDataFactory.fillDefaults().align(SWT.FILL, SWT.TOP).grab(true, false).span(2, 1).applyTo(topComposite); + GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true).applyTo(text1); + GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true).applyTo(text2); + + resourceText1 = createDropLabel(topComposite); + resourceText2 = createDropLabel(topComposite); + + Button button = new Button(topComposite, SWT.PUSH); + button.setText("Compare"); + button.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + compare(); + } + }); + + } + + + + private void compare() { + text1.setText(""); + text2.setText(""); + final Resource r1 = (Resource)resourceText1.getData(); + final Resource r2 = (Resource)resourceText2.getData(); + if (r1 == null || r2 == null) { + if (r1 == null) + text1.setText("Missing input!"); + if (r2 == null) + text2.setText("Missing input!"); + return; + } + + session.asyncRequest(new ReadRequest() { + + @Override + public void run(final ReadGraph graph) throws DatabaseException { + comparator.clearRels(); + comparator.addTraversed(graph.getBuiltins().ConsistsOf); +// comparator.addTested(graph.getBuiltins().IsWeaklyRelatedTo); + comparator.test(graph, r1, r2); + BijectionMap map = comparator.getComparable(); + Map indices = new HashMap(); + final StringBuilder sb1 = new StringBuilder(); + int index = 0; + for (Statement s : comparator.getChanges1()) { + String sub; + try { + + sub = GraphUtils.getReadableName(graph, s.getSubject()); + String pre = GraphUtils.getReadableName(graph, s.getPredicate()); + String obj = GraphUtils.getReadableName(graph, s.getObject()); + if (map.containsLeft(s)) { + index++; + indices.put(s, index); + sb1.append("["+index + "] "); + } + sb1.append(sub + " - " + pre + " - " + obj + "\n"); + } catch (ValidationException e) { + e.printStackTrace(); + } catch (ServiceException e) { + e.printStackTrace(); + } + + } + final StringBuilder sb2 = new StringBuilder(); + for (Statement s : comparator.getChanges2()) { + String sub; + try { + sub = GraphUtils.getReadableName(graph, s.getSubject()); + String pre = GraphUtils.getReadableName(graph, s.getPredicate()); + String obj = GraphUtils.getReadableName(graph, s.getObject()); + if (map.containsRight(s)) { + index = indices.get(map.getLeft(s)); + sb2.append("["+index + "] "); + } + sb2.append(sub + " - " + pre + " - " + obj + "\n"); + } catch (ValidationException e) { + e.printStackTrace(); + } catch (ServiceException e) { + e.printStackTrace(); + } + + } + Display.getDefault().asyncExec(new Runnable() { + @Override + public void run() { + text1.setText(sb1.toString()); + text2.setText(sb2.toString()); + + } + }); + } + }); + } + + @Override + public void setFocus() { + composite.setFocus(); + } + + @Override + public void dispose() { + super.dispose(); + + } + + + // copy-paste from GraphDebugger + public Label createDropLabel(Composite parent) { + final Label label = new Label(parent, SWT.BORDER); + label.setAlignment(SWT.CENTER); + label.setText("Drag a resource here to examine it in this debugger!"); + label.setForeground(parent.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY)); + GridData data = new GridData(SWT.FILL, SWT.TOP, true, false); + data.heightHint = 16; + label.setLayoutData(data); + + // Add resource id drop support to the drop-area. + DropTarget dropTarget = new DropTarget(label, DND.DROP_LINK | DND.DROP_COPY); + dropTarget.setTransfer(new Transfer[] { TextTransfer.getInstance(), ResourceReferenceTransfer.getInstance(), LocalObjectTransfer.getTransfer() }); + dropTarget.addDropListener(new DropTargetAdapter() { + @Override + public void dragEnter(DropTargetEvent event) { + event.detail = DND.DROP_LINK; + //label.setBackground(green); + return; + } + @Override + public void dragLeave(DropTargetEvent event) { + label.setBackground(null); + } + + @Override + public void drop(DropTargetEvent event) { + label.setBackground(null); + ResourceArray[] data = parseEventData(event); + if (data == null || data.length != 1) { + event.detail = DND.DROP_NONE; + return; + } + final ResourceArray array = data[0]; + final Resource r = array.resources[array.resources.length - 1]; + + label.setData(r); + try { + label.setText(session.syncRequest(new Read() { + @Override + public String perform(ReadGraph graph) + throws DatabaseException { + return GraphUtils.getReadableName(graph, r); + } + })); + } catch (DatabaseException e) { + e.printStackTrace(); + } + } + + private ResourceArray[] parseEventData(DropTargetEvent event) { + //System.out.println("DATA: " + event.data); + if (event.data instanceof String) { + try { + SerialisationSupport support = session.getService(SerialisationSupport.class); + return ResourceTransferUtils.readStringTransferable(support.getResourceSerializer(), (String) event.data).toResourceArrayArray(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (InvalidResourceReferenceException e) { + e.printStackTrace(); + } + } + ResourceArray[] ret = ResourceAdaptionUtils.toResourceArrays(event.data); + if (ret.length > 0) + return ret; + return null; + } + }); + + return label; + } + + + + + + +}