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