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