]> gerrit.simantics Code Review - simantics/interop.git/blobdiff - org.simantics.interop.update/src/org/simantics/interop/update/model/ModelUpdate.java
Separating model update logic from UI
[simantics/interop.git] / org.simantics.interop.update / src / org / simantics / interop / update / model / ModelUpdate.java
diff --git a/org.simantics.interop.update/src/org/simantics/interop/update/model/ModelUpdate.java b/org.simantics.interop.update/src/org/simantics/interop/update/model/ModelUpdate.java
new file mode 100644 (file)
index 0000000..0ddd4e4
--- /dev/null
@@ -0,0 +1,350 @@
+package org.simantics.interop.update.model;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map.Entry;
+
+import org.simantics.Simantics;
+import org.simantics.db.ReadGraph;
+import org.simantics.db.Resource;
+import org.simantics.db.Session;
+import org.simantics.db.Statement;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.layer0.util.Layer0Utils;
+import org.simantics.db.request.Read;
+import org.simantics.interop.test.GraphChanges;
+import org.simantics.interop.test.GraphComparator;
+import org.simantics.utils.datastructures.Pair;
+
+public abstract class ModelUpdate {
+       
+       private GraphChanges changes;
+       private UpdateTree updateTree;
+       private UpdateList updateList;
+       
+       private GraphChanges changes2;
+       private GraphChanges changes3;
+       private UpdateTree updateTree2;
+       private UpdateList updateList2;
+       
+       private List<ChangeFilter> filters = new ArrayList<ChangeFilter>();
+       
+       boolean init = false;
+       
+       public void setInput(Resource oldModel, Resource newModel) throws DatabaseException {
+               setInput(oldModel, newModel, null, false);
+       }
+       
+       public void setInput(Resource oldModel, Resource newModel, Resource originalModel, boolean newDistinct) throws DatabaseException{
+               addFilters(filters);
+               if (originalModel != null) {
+                       // tree way comparison
+                       // compare the original and the old model
+                       Pair<GraphComparator,String> result2 = getChanges(originalModel, oldModel);
+                       GraphComparator comparator2  = result2.first;
+                       if (result2.second != null)
+                               showWarning(result2.second);
+                       comparator2.test(getSession());
+                       changes2 = comparator2.getChanges();
+                       changes2 = getSession().syncRequest(new FilterChangesRead(changes2));
+                       updateTree2 = getUpdateTree(changes2);
+                       updateList2 = getUpdateList(changes2);
+                       
+                       // compare the original and the new model
+                       Pair<GraphComparator,String> result3 = getChanges(originalModel,newModel);
+                       GraphComparator comparator3  = result3.first;
+                       if (result3.second != null)
+                               showWarning(result3.second);
+                       comparator3.test(getSession());
+                       changes3 = comparator3.getChanges();
+                       changes3 = getSession().syncRequest(new FilterChangesRead(changes3));
+               }
+               
+               Pair<GraphComparator,String> result = getChanges(oldModel,newModel);
+               GraphComparator comparator  = result.first;
+               if (result.second != null)
+                       showWarning(result.second);
+               if (originalModel != null) {
+                       // three-way comparison: use change information to configure
+                       // the comparison between the old and the new model.
+                       
+                       // 1. map comparable resources
+                       for (Entry<Resource, Resource> origToOld : changes2.getComparable().getEntries()) {
+                               Resource oldR = origToOld.getValue();
+                               Resource newR = changes3.getComparable().getRight(origToOld.getKey());
+                               if (newR != null) {
+                                       comparator.addComparableResources(oldR, newR);
+                               }
+                       }
+                       // 2. mark removed resources as distinct, so that comparison does not pair them
+                       for (Statement s : changes2.getDeletions()) {
+                               if (changes3.getComparable().containsLeft(s.getObject()))
+                                       comparator.addNonMatchedRight(changes3.getComparable().getRight(s.getObject()));
+                       }
+                       
+                       for (Statement s : changes3.getDeletions()) {
+                               if (changes2.getComparable().containsLeft(s.getObject()))
+                                       comparator.addNonMatchedLeft(changes2.getComparable().getRight(s.getObject()));
+                       }
+                       if (newDistinct) {
+                               // 3. mark added resources as distinct, so that comparison does not pair them
+                               for (Statement s : changes2.getAdditions()) {
+                                       comparator.addNonMatchedLeft(s.getObject());
+                               }
+                               
+                               for (Statement s : changes3.getAdditions()) {
+                                       comparator.addNonMatchedRight(s.getObject());
+                               }
+                       }
+               }
+               comparator.test(getSession());
+               changes = comparator.getChanges();
+               changes = getSession().syncRequest(new FilterChangesRead(changes));
+               updateTree = getUpdateTree(changes);
+               updateList = getUpdateList(changes);
+               
+               
+               if (originalModel != null) {
+                       createDefaultSelections();
+               }
+               
+               init = true;
+       }
+       
+       
+       
+       protected abstract Pair<GraphComparator,String> getChanges(Resource r1, Resource r2)  throws DatabaseException;
+       protected abstract UpdateTree getUpdateTree(GraphChanges changes) throws DatabaseException;
+       protected UpdateList getUpdateList(GraphChanges changes) throws DatabaseException {
+               return new UpdateList(changes.getModifications());
+       }
+       
+       protected void addFilters(List<ChangeFilter> filters) {
+               
+       }
+       
+       public boolean isInit() {
+               return init;
+       }
+       
+       public GraphChanges getChanges() {
+               return changes;
+       }
+       public UpdateTree getUpdateTree() {
+               return updateTree;
+       }
+       public UpdateList getUpdateList() {
+               return updateList;
+       }
+       public GraphChanges getChanges2() {
+               return changes2;
+       }
+       public GraphChanges getChanges3() {
+               return changes3;
+       }
+       public UpdateTree getUpdateTree2() {
+               return updateTree2;
+       }
+       public UpdateList getUpdateList2() {
+               return updateList2;
+       }
+
+       
+       public void applyAll(WriteGraph graph) throws DatabaseException {
+               Layer0Utils.addCommentMetadata(graph, "Apply all model updates");
+               graph.markUndoPoint();
+               for (Pair<Statement, Statement> mod : updateList.getChanges()) {
+                       applyLiteralChange(graph, mod);
+               }
+               updateList.clear();
+               
+               updateTree.getUpdateOps().applyAll(graph);
+       }
+       
+       public void applySelected(WriteGraph graph) throws DatabaseException {
+               Layer0Utils.addCommentMetadata(graph, "Apply selected model updates");
+               graph.markUndoPoint();
+               HashSet<Pair<Statement, Statement>> changes = new HashSet<>(updateList.getSelected());
+               for (Pair<Statement, Statement> mod : changes) {
+                       updateList.removeChange(mod);
+                       applyLiteralChange(graph, mod);
+               }
+               
+               updateTree.getUpdateOps().applySelected(graph);
+       }
+       
+       protected void applyLiteralChange(WriteGraph graph, Pair<Statement, Statement> mod) throws DatabaseException {
+               if (mod.second == null) {
+                       graph.deny(mod.first);
+                       return;
+               } 
+               Resource s = changes.getComparable().getLeft(mod.second.getSubject());
+               Resource pred = mod.second.getPredicate();
+               if (graph.hasValue(mod.second.getObject())) {
+                       Object value = graph.getValue(mod.second.getObject());
+                       graph.claimLiteral(s, pred, value);
+               } else {
+                       graph.deny(s,pred);
+               }
+       }
+       
+       
+       protected Session getSession() {
+               return Simantics.getSession();
+       }
+       
+       
+       
+       private class FilterChangesRead implements Read<GraphChanges> {
+               private GraphChanges changes;
+               public FilterChangesRead(GraphChanges changes) {
+                       this.changes = changes;
+               }
+               
+               @Override
+               public GraphChanges perform(ReadGraph graph) throws DatabaseException {
+                       return filterChanges(graph, changes);
+               }
+       }
+       
+       /**
+        * Filters changes:
+        * 1. Changes that are not essential for model update (changes that can be found when the models are axcatly the same)
+        * 2. Runs custom filters for value changes. 
+        * 
+        * @param g
+        * @param changes
+        * @return
+        * @throws DatabaseException
+        */
+       protected GraphChanges filterChanges(ReadGraph g, GraphChanges changes) throws DatabaseException 
+    {
+               
+               List<Pair<Statement,Statement>> modifications = new ArrayList<Pair<Statement,Statement>>();
+               
+       for (Pair<Statement, Statement> mod : changes.getModifications()) {
+               
+               boolean accept = true;
+               for (ChangeFilter filter : filters) {
+                       if (!filter.accept(g, mod)) {
+                               accept = false;
+                               break;
+                       }       
+               }
+               if (accept)
+                       modifications.add(mod);
+       }
+       GraphChanges newChanges =  new GraphChanges(changes.getResource1(),changes.getResource2(),changes.getDeletions(), changes.getAdditions(), modifications, changes.getComparable());
+       return newChanges;
+    }
+       
+       public interface ChangeFilter {
+               public boolean accept(ReadGraph g, Pair<Statement, Statement> change) throws DatabaseException;
+       }
+
+       
+       /**
+        * 
+        * Filters floating point value changes (default filter is set filter when the change is less than 1%)  
+        *
+        */
+       protected class FPValueFilter implements ChangeFilter  {
+               
+               private double percentage = 0.01;
+               
+               public FPValueFilter() {
+                       
+               }
+               
+               public FPValueFilter(double percentage) {
+                       if (percentage < 0.0 || percentage > 1.0)
+                               throw new IllegalArgumentException("Percentage must be between 0.0 and 1.0.");
+                       this.percentage = percentage;
+               }
+               
+               @Override
+               public boolean accept(ReadGraph g, Pair<Statement, Statement> change) throws DatabaseException {
+                       //filter floating point values that have less than 1% difference.
+                       if (!g.hasValue(change.first.getObject()) || !g.hasValue(change.second.getObject()))
+                               return true;
+               Object v1 = g.getValue(change.first.getObject());
+               Object v2 = g.getValue(change.second.getObject());
+               
+               if (v1 instanceof Double && v2 instanceof Double) {
+                       double d1 = (Double)v1;
+                       double d2 = (Double)v2;
+                       if (Math.abs(d1-d2) / Math.max(Math.abs(d1), Math.abs(d2)) < percentage)
+                               return false;
+               } else if (v1 instanceof Float && v2 instanceof Float) {
+                       float d1 = (Float)v1;
+                       float d2 = (Float)v2;
+                       if (Math.abs(d1-d2) / Math.max(Math.abs(d1), Math.abs(d2)) < percentage)
+                               return false;
+               }
+
+               return true;
+               }
+       }
+       
+       protected void createDefaultSelections() {
+               // select all changes
+               for (Entry<Resource, UpdateOp> op : updateTree.getUpdateOps().getResourceMap().entrySet()) {
+                       op.getValue().select(true);
+               }
+               
+               
+               for (Pair<Statement, Statement> pair : updateList.getChanges()) {
+                       updateList.addSelected(pair);
+               }
+               
+               // preserve user-made changes (by removing selections)
+               for (Entry<Resource, UpdateOp> op : updateTree.getUpdateOps().getResourceMap().entrySet()) {
+                       UpdateOp op2 = updateTree2.getUpdateOps().getUpdateOp(op.getKey());
+                       if (op2 == null) {
+                               if (changes3.getComparable().containsRight(op.getKey())){
+                                       op2 = updateTree2.getUpdateOps().getUpdateOp(changes3.getComparable().getLeft(op.getKey()));
+                               }
+                       }
+                       if (op2 != null && op.getValue().getClass() == op2.getClass()) {
+                               op.getValue().select(false);
+                       }
+               }
+               
+               for (Pair<Statement, Statement> pair : updateList.getChanges()) {
+                       if (pair.first != null) {
+                               boolean found = false;
+                               for (Pair<Statement, Statement> pair2 : updateList2.getChanges()) {
+                                       if (pair.first.equals(pair2.first)) {
+                                               found = true;
+                                               break;
+                                       }
+                               }
+                               if (found) {
+                                       updateList.removeSelected(pair);
+                               }
+                       }
+               }
+               
+       }
+       
+       private void showWarning(String string) {
+               for (WarningListener l : warningListeners)
+                       l.showWarning(this, string);
+       }
+       
+       private List<WarningListener> warningListeners = new ArrayList<>();
+       
+       public static interface WarningListener {
+               void showWarning(ModelUpdate update, String warning);
+       }
+       
+       public void addListener(WarningListener listener) {
+               warningListeners.add(listener);
+       }
+       
+       public void removeListener(WarningListener listener) {
+               warningListeners.remove(listener);
+       }
+}