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