]> gerrit.simantics Code Review - simantics/interop.git/blob - org.simantics.interop.update/src/org/simantics/interop/update/model/ModelUpdate.java
Minor adjustments
[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(new FilterChangesRead(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(new FilterChangesRead(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(new FilterChangesRead(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(new FilterChangesRead(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().equals(rc.getFirst())) {
244                                         comparable.map(lc, rc);
245                                         break;
246                                 }
247                         }
248                 }
249                 for (PropertyChange lc : l) {
250                         if (!comparable.containsLeft(lc))
251                                 lc.setVisible(false);
252                 }
253                 
254         }
255         
256         protected abstract Pair<GraphComparator,String> getChanges(Resource r1, Resource r2)  throws DatabaseException;
257         protected abstract UpdateTree getUpdateTree(GraphChanges changes) throws DatabaseException;
258         protected UpdateList getUpdateList(GraphChanges changes) throws DatabaseException {
259                 return new UpdateList(changes, changes.getModifications());
260         }
261         
262         public Resource getOldModel() {
263                 return oldModel;
264         }
265         
266         public Resource getNewModel() {
267                 return newModel;
268         }
269         
270         public Resource getOriginalModel() {
271                 return originalModel;
272         }
273         
274         public boolean isInit() {
275                 return init;
276         }
277         
278         public GraphChanges getChanges() {
279                 return changes;
280         }
281         public UpdateTree getUpdateTree() {
282                 return updateTree;
283         }
284         public UpdateList getUpdateList() {
285                 return updateList;
286         }
287         public GraphChanges getChanges2() {
288                 return changes2;
289         }
290         
291         public UpdateTree getUpdateTree2() {
292                 return updateTree2;
293         }
294         public UpdateList getUpdateList2() {
295                 return updateList2;
296         }
297         
298         public GraphChanges getChanges3() {
299                 return changes3;
300         }
301         
302         public UpdateTree getUpdateTree3() throws DatabaseException{
303                 if (updateTree3 == null && changes3 != null)
304                         updateTree3 = getUpdateTree(changes3);
305                 return updateTree3;
306         }
307         public UpdateList getUpdateList3() throws DatabaseException {
308                 if (updateList3 == null && changes3 != null)
309                         updateList3 = getUpdateList(changes3);
310                 return updateList3;
311         }
312
313         
314         public void applyAll(WriteGraph graph) throws DatabaseException {
315                 Layer0Utils.addCommentMetadata(graph, "Apply all model updates");
316                 graph.markUndoPoint();
317                 for (PropertyChange mod : updateList.getChanges()) {
318                         mod.apply(graph);
319                 }
320                 
321                 updateTree.getUpdateOps().applyAll(graph);
322         }
323         
324         public void applySelected(WriteGraph graph) throws DatabaseException {
325                 Layer0Utils.addCommentMetadata(graph, "Apply selected model updates");
326                 graph.markUndoPoint();
327                 for (PropertyChange mod : updateList.getChanges()) {
328                         if (mod.selected())
329                                 mod.apply(graph);
330                 }
331                 
332                 updateTree.getUpdateOps().applySelected(graph);
333         }
334         
335         
336         
337         
338         protected Session getSession() {
339                 return Simantics.getSession();
340         }
341         
342         
343         
344         public static class FilterChangesRead implements Read<GraphChanges> {
345                 private GraphChanges changes;
346                 private List<ChangeFilter> filters;
347                 
348                 public FilterChangesRead(GraphChanges changes, List<ChangeFilter> filters) {
349                         this.changes = changes;
350                         this.filters = filters;
351                 }
352                 
353                 @Override
354                 public GraphChanges perform(ReadGraph graph) throws DatabaseException {
355                         return filterChanges(graph, changes);
356                 }
357                 
358                 /**
359                  * Filters changes:
360                  * 1. Changes that are not essential for model update (changes that can be found when the models are exactly the same)
361                  * 2. Runs custom filters for value changes. 
362                  * 
363                  * @param g
364                  * @param changes
365                  * @return
366                  * @throws DatabaseException
367                  */
368                 protected GraphChanges filterChanges(ReadGraph g, GraphChanges changes) throws DatabaseException 
369             {
370                         
371                         List<Pair<Statement,Statement>> modifications = new ArrayList<Pair<Statement,Statement>>();
372                         
373                 for (Pair<Statement, Statement> mod : changes.getModifications()) {
374                         
375                         boolean accept = true;
376                         for (ChangeFilter filter : filters) {
377                                 if (!filter.accept(g, mod)) {
378                                         accept = false;
379                                         break;
380                                 }       
381                         }
382                         if (accept)
383                                 modifications.add(mod);
384                 }
385                 GraphChanges newChanges =  new GraphChanges(changes.getResource1(),changes.getResource2(),changes.getDeletions(), changes.getAdditions(), modifications, changes.getComparable());
386                 return newChanges;
387             }
388         }
389
390         
391         public interface ChangeFilter {
392                 public boolean accept(ReadGraph g, Pair<Statement, Statement> change) throws DatabaseException;
393         }
394
395         
396         /**
397          * 
398          * Filters floating point value changes (default filter is set filter when the change is less than 1%)  
399          *
400          */
401         protected class FPValueFilter implements ChangeFilter  {
402                 
403                 private double percentage = 0.01;
404                 
405                 public FPValueFilter() {
406                         
407                 }
408                 
409                 public FPValueFilter(double percentage) {
410                         if (percentage < 0.0 || percentage > 1.0)
411                                 throw new IllegalArgumentException("Percentage must be between 0.0 and 1.0.");
412                         this.percentage = percentage;
413                 }
414                 
415                 @Override
416                 public boolean accept(ReadGraph g, Pair<Statement, Statement> change) throws DatabaseException {
417                         //filter floating point values that have less than 1% difference.
418                         if (!g.hasValue(change.first.getObject()) || !g.hasValue(change.second.getObject()))
419                                 return true;
420                 Object v1 = g.getValue(change.first.getObject());
421                 Object v2 = g.getValue(change.second.getObject());
422                 
423                 if (v1 instanceof Double && v2 instanceof Double) {
424                         double d1 = (Double)v1;
425                         double d2 = (Double)v2;
426                         if (Math.abs(d1-d2) / Math.max(Math.abs(d1), Math.abs(d2)) < percentage)
427                                 return false;
428                 } else if (v1 instanceof Float && v2 instanceof Float) {
429                         float d1 = (Float)v1;
430                         float d2 = (Float)v2;
431                         if (Math.abs(d1-d2) / Math.max(Math.abs(d1), Math.abs(d2)) < percentage)
432                                 return false;
433                 }
434
435                 return true;
436                 }
437         }
438         
439         public void defaultSelections() {
440                 if (changes3 == null) {
441                         return;
442                 }
443                 // select all changes
444                 for (Entry<Resource, UpdateOp> op : updateTree.getUpdateOps().getResourceMap().entrySet()) {
445                         op.getValue().select(true);
446                 }
447                 
448                 
449                 for (PropertyChange pair : updateList.getChanges()) {
450                         pair.select(true);
451                 }
452                 
453                 // preserve user-made changes (by removing selections)
454                 for (Entry<Resource, UpdateOp> op : updateTree.getUpdateOps().getResourceMap().entrySet()) {
455                         UpdateOp op2 = updateTree2.getUpdateOps().getUpdateOp(op.getKey());
456                         if (op2 == null) {
457                                 if (changes3.getComparable().containsRight(op.getKey())){
458                                         op2 = updateTree2.getUpdateOps().getUpdateOp(changes3.getComparable().getLeft(op.getKey()));
459                                 }
460                         }
461                         if (op2 != null && op.getValue().getClass() == op2.getClass()) {
462                                 op.getValue().select(false);
463                         }
464                 }
465                 
466                 for (PropertyChange pair : updateList.getChanges()) {
467                         if (pair.getFirst() != null) {
468                                 boolean found = false;
469                                 for (PropertyChange pair2 : updateList2.getChanges()) {
470                                         if (pair.getFirst().equals(pair2.getSecond())) {
471                                                 found = true;
472                                                 break;
473                                         }
474                                 }
475                                 if (found) {
476                                         pair.select(false);
477                                 }
478                         }
479                 }
480                 
481         }
482         
483         private void showWarning(String string) {
484                 for (WarningListener l : warningListeners)
485                         l.showWarning(this, string);
486         }
487         
488         private List<WarningListener> warningListeners = new ArrayList<>();
489         
490         public static interface WarningListener {
491                 void showWarning(ModelUpdate update, String warning);
492         }
493         
494         public void addListener(WarningListener listener) {
495                 warningListeners.add(listener);
496         }
497         
498         public void removeListener(WarningListener listener) {
499                 warningListeners.remove(listener);
500         }
501 }