]> gerrit.simantics Code Review - simantics/interop.git/blob - org.simantics.interop.update/src/org/simantics/interop/update/model/ModelUpdate.java
Added support for filtering changes
[simantics/interop.git] / org.simantics.interop.update / src / org / simantics / interop / update / model / ModelUpdate.java
1 package org.simantics.interop.update.model;
2
3 import java.util.ArrayDeque;
4 import java.util.ArrayList;
5 import java.util.Collection;
6 import java.util.Deque;
7 import java.util.List;
8 import java.util.Map.Entry;
9
10 import org.simantics.Simantics;
11 import org.simantics.db.ReadGraph;
12 import org.simantics.db.Resource;
13 import org.simantics.db.Session;
14 import org.simantics.db.Statement;
15 import org.simantics.db.WriteGraph;
16 import org.simantics.db.exception.DatabaseException;
17 import org.simantics.db.layer0.util.Layer0Utils;
18 import org.simantics.db.request.Read;
19 import org.simantics.interop.test.GraphChanges;
20 import org.simantics.interop.test.GraphComparator;
21 import org.simantics.utils.datastructures.BijectionMap;
22 import org.simantics.utils.datastructures.Pair;
23
24 public abstract class ModelUpdate {
25         
26         private Resource oldModel;      // old model that is going to be updated (User modified model)
27         private Resource newModel;      // new model that contains the updates   (New design model)
28         private Resource originalModel; // original model (optional) that is used for detecting and retaining user made changes (Old design model)
29         
30         private GraphChanges changes;   // changes between old /new
31         private UpdateTree updateTree;
32         private UpdateList updateList;
33         
34         private GraphChanges changes2;  // changes between original / old
35         private UpdateTree updateTree2;
36         private UpdateList updateList2;
37         
38         private GraphChanges changes3;  // changes between original / new
39         private UpdateTree updateTree3;
40         private UpdateList updateList3;
41         
42         private List<ChangeFilter> filters = new ArrayList<ChangeFilter>();
43         private List<ChangeFilter> userFilters = new ArrayList<ChangeFilter>();
44         
45         boolean init = false;
46         
47         public void setInput(Resource oldModel, Resource newModel) throws DatabaseException {
48                 setInput(oldModel, newModel, null, false);
49         }
50         
51         /**
52          * Initialises the ModelUpdate with given input
53          * @param oldModel the model that is going to be updated (User modified model)
54          * @param newModel the model containing updates (New design model)
55          * @param originalModel the model that is used for detecting and retaining user made changes (Old design model). Parameter can be null.
56          * @param newDistinct when originalModel is given, additions to the old and the new model (when compared to the original model) are forced to be distinct. 
57          * @throws DatabaseException
58          */
59         public void setInput(Resource oldModel, Resource newModel, Resource originalModel, boolean newDistinct) throws DatabaseException{
60                 this.oldModel = oldModel;
61                 this.newModel = newModel;
62                 this.originalModel = originalModel;
63 //              addFilters(filters);
64                 if (originalModel != null) {
65                         // tree way comparison
66                         // compare the original and the old model
67                         Pair<GraphComparator,String> result2 = getChanges(originalModel, oldModel);
68                         GraphComparator comparator2  = result2.first;
69                         if (result2.second != null)
70                                 showWarning(result2.second);
71                         comparator2.test(getSession());
72                         changes2 = comparator2.getChanges();
73                         changes2 = getSession().syncRequest(new FilterChangesRead(changes2, filters));
74                         updateTree2 = getUpdateTree(changes2);
75                         updateList2 = getUpdateList(changes2);
76                         
77                         // compare the original and the new model
78                         Pair<GraphComparator,String> result3 = getChanges(originalModel,newModel);
79                         GraphComparator comparator3  = result3.first;
80                         if (result3.second != null)
81                                 showWarning(result3.second);
82                         comparator3.test(getSession());
83                         changes3 = comparator3.getChanges();
84                         changes3 = getSession().syncRequest(new FilterChangesRead(changes3, filters));
85                 }
86                 
87                 Pair<GraphComparator,String> result = getChanges(oldModel,newModel);
88                 GraphComparator comparator  = result.first;
89                 if (result.second != null)
90                         showWarning(result.second);
91                 if (originalModel != null) {
92                         // three-way comparison: use change information to configure
93                         // the comparison between the old and the new model.
94                         
95                         // 1. map comparable resources
96                         for (Entry<Resource, Resource> origToOld : changes2.getComparable().getEntries()) {
97                                 Resource oldR = origToOld.getValue();
98                                 Resource newR = changes3.getComparable().getRight(origToOld.getKey());
99                                 if (newR != null) {
100                                         comparator.addComparableResources(oldR, newR);
101                                 }
102                         }
103                         // 2. mark removed resources as distinct, so that comparison does not pair them
104                         for (Statement s : changes2.getDeletions()) {
105                                 if (changes3.getComparable().containsLeft(s.getObject()))
106                                         comparator.addNonMatchedRight(changes3.getComparable().getRight(s.getObject()));
107                         }
108                         
109                         for (Statement s : changes3.getDeletions()) {
110                                 if (changes2.getComparable().containsLeft(s.getObject()))
111                                         comparator.addNonMatchedLeft(changes2.getComparable().getRight(s.getObject()));
112                         }
113                         if (newDistinct) {
114                                 // 3. mark added resources as distinct, so that comparison does not pair them
115                                 for (Statement s : changes2.getAdditions()) {
116                                         comparator.addNonMatchedLeft(s.getObject());
117                                 }
118                                 
119                                 for (Statement s : changes3.getAdditions()) {
120                                         comparator.addNonMatchedRight(s.getObject());
121                                 }
122                         }
123                 }
124                 comparator.test(getSession());
125                 changes = comparator.getChanges();
126                 changes = getSession().syncRequest(new FilterChangesRead(changes, filters));
127                 updateTree = getUpdateTree(changes);
128                 updateList = getUpdateList(changes);
129                 if (userFilters.size() != 0) {
130                         refreshUserFilters();
131                 }
132                 
133                 
134                 if (originalModel != null) {
135                         defaultSelections();
136                 }
137                 
138                 init = true;
139         }
140         
141         public void addFilter(ChangeFilter filter) {
142                 if (init)
143                         throw new IllegalStateException("ModelUpdate has been initialized, adjusting filters is no longer possible.");
144                 filters.add(filter);
145                 
146         }
147         
148         /**
149          * Adds an user filter. Use refreshUserFilters() to apply the changes.
150          * @param filter
151          */
152         public void addUserFilter(ChangeFilter filter) {
153                 userFilters.add(filter);
154         }
155         
156         /**
157          * Removes an user filter. Use refreshUserFilters() to apply the changes.
158          * @param filter
159          */
160         public void removeUserFilter(ChangeFilter filter) {
161                 userFilters.remove(filter);
162         }
163         
164         /**
165          * Clears user filters.  Use refreshUserFilters() to apply the changes.
166          */
167         public void clearUserFilters() {
168                 userFilters.clear();
169         }
170         
171         public void refreshUserFilters() throws DatabaseException{
172                 // use user filters to set visible flags of changes.
173                 // First, set all changes visible.
174                 Deque<UpdateNode> stack = new ArrayDeque<>();
175                 stack.push(updateTree.getRootNode());
176                 while (!stack.isEmpty()) {
177                         UpdateNode n = stack.pop();
178                         n.setVisible(true);
179                         stack.addAll(n.getChildren());
180                 }
181                 for (PropertyChange pc : updateList.getChanges()) {
182                         pc.setVisible(true);
183                 }
184                 if (userFilters.size() > 0) {
185                         // Create filtered changes
186                         List<ChangeFilter> combined = new ArrayList<>(filters);
187                         combined.addAll(userFilters);
188                         GraphChanges filteredChanges = getSession().syncRequest(new FilterChangesRead(changes, combined));
189                         UpdateTree updateTreeF = getUpdateTree(filteredChanges);
190                         UpdateList updateListF = getUpdateList(filteredChanges);
191                         // hide changes that are not contained within the filtered changes.
192                         applyVisibleFlags(updateTree.getRootNode(), updateTreeF.getRootNode());
193                         applyVisibleFlags(updateList.getChanges(), updateListF.getChanges());
194                 }
195         }
196         
197         private void applyVisibleFlags(UpdateNode l, UpdateNode r) {
198                 BijectionMap<UpdateNode, UpdateNode> comparable = new BijectionMap<>();
199                 for (UpdateNode lc : l.getChildren()) {
200                         for (UpdateNode rc : r.getChildren()) {
201                                 if (comparable.containsRight(rc))
202                                         continue;
203                                 if (lc.getResource() != null) {
204                                         if (lc.getResource().equals(rc.getResource())) {
205                                 
206                                                 comparable.map(lc, rc);
207                                                 break;
208                                         }
209                                 } else if (rc.getResource() == null){
210                                         UpdateOp lop = lc.getOp();
211                                         UpdateOp rop = rc.getOp();
212                                         if (lop.getStatement() != null && lop.getStatement().equals(rop.getStatement())) {
213                                                 comparable.map(lc, rc);
214                                                 break;
215                                         }
216                                 }
217                         }
218                 }
219                 for (UpdateNode lc : l.getChildren()) {
220                         if (!comparable.containsLeft(lc))
221                                 lc.setVisible(false);
222                 }
223                 for (Entry<UpdateNode, UpdateNode> entry : comparable.getEntries()) {
224                         applyVisibleFlags(entry.getKey(), entry.getValue());
225                 }       
226         }
227         
228         private void applyVisibleFlags(Collection<PropertyChange> l, Collection<PropertyChange> r) {
229                 BijectionMap<PropertyChange, PropertyChange> comparable = new BijectionMap<>();
230                 for (PropertyChange lc : l) {
231                         for (PropertyChange rc : r) {
232                                 if (comparable.containsRight(rc))
233                                         continue;
234                                 if (lc.getFirst().equals(rc.getFirst())) {
235                                         comparable.map(lc, rc);
236                                 }
237                         }
238                 }
239                 for (PropertyChange lc : l) {
240                         if (!comparable.containsLeft(lc))
241                                 lc.setVisible(false);
242                 }
243                 
244         }
245         
246         protected abstract Pair<GraphComparator,String> getChanges(Resource r1, Resource r2)  throws DatabaseException;
247         protected abstract UpdateTree getUpdateTree(GraphChanges changes) throws DatabaseException;
248         protected UpdateList getUpdateList(GraphChanges changes) throws DatabaseException {
249                 return new UpdateList(changes, changes.getModifications());
250         }
251         
252         public Resource getOldModel() {
253                 return oldModel;
254         }
255         
256         public Resource getNewModel() {
257                 return newModel;
258         }
259         
260         public Resource getOriginalModel() {
261                 return originalModel;
262         }
263         
264         public boolean isInit() {
265                 return init;
266         }
267         
268         public GraphChanges getChanges() {
269                 return changes;
270         }
271         public UpdateTree getUpdateTree() {
272                 return updateTree;
273         }
274         public UpdateList getUpdateList() {
275                 return updateList;
276         }
277         public GraphChanges getChanges2() {
278                 return changes2;
279         }
280         
281         public UpdateTree getUpdateTree2() {
282                 return updateTree2;
283         }
284         public UpdateList getUpdateList2() {
285                 return updateList2;
286         }
287         
288         public GraphChanges getChanges3() {
289                 return changes3;
290         }
291         
292         public UpdateTree getUpdateTree3() throws DatabaseException{
293                 if (updateTree3 == null && changes3 != null)
294                         updateTree3 = getUpdateTree(changes3);
295                 return updateTree3;
296         }
297         public UpdateList getUpdateList3() throws DatabaseException {
298                 if (updateList3 == null && changes3 != null)
299                         updateList3 = getUpdateList(changes3);
300                 return updateList3;
301         }
302
303         
304         public void applyAll(WriteGraph graph) throws DatabaseException {
305                 Layer0Utils.addCommentMetadata(graph, "Apply all model updates");
306                 graph.markUndoPoint();
307                 for (PropertyChange mod : updateList.getChanges()) {
308                         mod.apply(graph);
309                 }
310                 
311                 updateTree.getUpdateOps().applyAll(graph);
312         }
313         
314         public void applySelected(WriteGraph graph) throws DatabaseException {
315                 Layer0Utils.addCommentMetadata(graph, "Apply selected model updates");
316                 graph.markUndoPoint();
317                 for (PropertyChange mod : updateList.getChanges()) {
318                         if (mod.selected())
319                                 mod.apply(graph);
320                 }
321                 
322                 updateTree.getUpdateOps().applySelected(graph);
323         }
324         
325         
326         
327         
328         protected Session getSession() {
329                 return Simantics.getSession();
330         }
331         
332         
333         
334         private class FilterChangesRead implements Read<GraphChanges> {
335                 private GraphChanges changes;
336                 private List<ChangeFilter> filters;
337                 
338                 public FilterChangesRead(GraphChanges changes, List<ChangeFilter> filters) {
339                         this.changes = changes;
340                         this.filters = filters;
341                 }
342                 
343                 @Override
344                 public GraphChanges perform(ReadGraph graph) throws DatabaseException {
345                         return filterChanges(graph, changes);
346                 }
347                 
348                 /**
349                  * Filters changes:
350                  * 1. Changes that are not essential for model update (changes that can be found when the models are axcatly the same)
351                  * 2. Runs custom filters for value changes. 
352                  * 
353                  * @param g
354                  * @param changes
355                  * @return
356                  * @throws DatabaseException
357                  */
358                 protected GraphChanges filterChanges(ReadGraph g, GraphChanges changes) throws DatabaseException 
359             {
360                         
361                         List<Pair<Statement,Statement>> modifications = new ArrayList<Pair<Statement,Statement>>();
362                         
363                 for (Pair<Statement, Statement> mod : changes.getModifications()) {
364                         
365                         boolean accept = true;
366                         for (ChangeFilter filter : filters) {
367                                 if (!filter.accept(g, mod)) {
368                                         accept = false;
369                                         break;
370                                 }       
371                         }
372                         if (accept)
373                                 modifications.add(mod);
374                 }
375                 GraphChanges newChanges =  new GraphChanges(changes.getResource1(),changes.getResource2(),changes.getDeletions(), changes.getAdditions(), modifications, changes.getComparable());
376                 return newChanges;
377             }
378         }
379
380         
381         public interface ChangeFilter {
382                 public boolean accept(ReadGraph g, Pair<Statement, Statement> change) throws DatabaseException;
383         }
384
385         
386         /**
387          * 
388          * Filters floating point value changes (default filter is set filter when the change is less than 1%)  
389          *
390          */
391         protected class FPValueFilter implements ChangeFilter  {
392                 
393                 private double percentage = 0.01;
394                 
395                 public FPValueFilter() {
396                         
397                 }
398                 
399                 public FPValueFilter(double percentage) {
400                         if (percentage < 0.0 || percentage > 1.0)
401                                 throw new IllegalArgumentException("Percentage must be between 0.0 and 1.0.");
402                         this.percentage = percentage;
403                 }
404                 
405                 @Override
406                 public boolean accept(ReadGraph g, Pair<Statement, Statement> change) throws DatabaseException {
407                         //filter floating point values that have less than 1% difference.
408                         if (!g.hasValue(change.first.getObject()) || !g.hasValue(change.second.getObject()))
409                                 return true;
410                 Object v1 = g.getValue(change.first.getObject());
411                 Object v2 = g.getValue(change.second.getObject());
412                 
413                 if (v1 instanceof Double && v2 instanceof Double) {
414                         double d1 = (Double)v1;
415                         double d2 = (Double)v2;
416                         if (Math.abs(d1-d2) / Math.max(Math.abs(d1), Math.abs(d2)) < percentage)
417                                 return false;
418                 } else if (v1 instanceof Float && v2 instanceof Float) {
419                         float d1 = (Float)v1;
420                         float d2 = (Float)v2;
421                         if (Math.abs(d1-d2) / Math.max(Math.abs(d1), Math.abs(d2)) < percentage)
422                                 return false;
423                 }
424
425                 return true;
426                 }
427         }
428         
429         public void defaultSelections() {
430                 if (changes3 == null) {
431                         return;
432                 }
433                 // select all changes
434                 for (Entry<Resource, UpdateOp> op : updateTree.getUpdateOps().getResourceMap().entrySet()) {
435                         op.getValue().select(true);
436                 }
437                 
438                 
439                 for (PropertyChange pair : updateList.getChanges()) {
440                         pair.select(true);
441                 }
442                 
443                 // preserve user-made changes (by removing selections)
444                 for (Entry<Resource, UpdateOp> op : updateTree.getUpdateOps().getResourceMap().entrySet()) {
445                         UpdateOp op2 = updateTree2.getUpdateOps().getUpdateOp(op.getKey());
446                         if (op2 == null) {
447                                 if (changes3.getComparable().containsRight(op.getKey())){
448                                         op2 = updateTree2.getUpdateOps().getUpdateOp(changes3.getComparable().getLeft(op.getKey()));
449                                 }
450                         }
451                         if (op2 != null && op.getValue().getClass() == op2.getClass()) {
452                                 op.getValue().select(false);
453                         }
454                 }
455                 
456                 for (PropertyChange pair : updateList.getChanges()) {
457                         if (pair.getFirst() != null) {
458                                 boolean found = false;
459                                 for (PropertyChange pair2 : updateList2.getChanges()) {
460                                         if (pair.getFirst().equals(pair2.getSecond())) {
461                                                 found = true;
462                                                 break;
463                                         }
464                                 }
465                                 if (found) {
466                                         pair.select(false);
467                                 }
468                         }
469                 }
470                 
471         }
472         
473         private void showWarning(String string) {
474                 for (WarningListener l : warningListeners)
475                         l.showWarning(this, string);
476         }
477         
478         private List<WarningListener> warningListeners = new ArrayList<>();
479         
480         public static interface WarningListener {
481                 void showWarning(ModelUpdate update, String warning);
482         }
483         
484         public void addListener(WarningListener listener) {
485                 warningListeners.add(listener);
486         }
487         
488         public void removeListener(WarningListener listener) {
489                 warningListeners.remove(listener);
490         }
491 }