1 package org.simantics.interop.update.model;
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;
9 import java.util.Map.Entry;
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;
26 public abstract class ModelUpdate {
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)
32 private GraphChanges changes; // changes between old /new
33 private UpdateTree updateTree;
34 private UpdateList updateList;
36 private GraphChanges changes2; // changes between original / old
37 private UpdateTree updateTree2;
38 private UpdateList updateList2;
40 private GraphChanges changes3; // changes between original / new
41 private UpdateTree updateTree3;
42 private UpdateList updateList3;
44 private List<ChangeFilter> filters = new ArrayList<ChangeFilter>();
45 private List<ChangeFilter> userFilters = new ArrayList<ChangeFilter>();
49 public void setInput(Resource oldModel, Resource newModel) throws DatabaseException {
50 setInput(oldModel, newModel, null, false);
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
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);
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));
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.
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());
102 comparator.addComparableResources(oldR, newR);
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()));
111 for (Statement s : changes3.getDeletions()) {
112 if (changes2.getComparable().containsLeft(s.getObject()))
113 comparator.addNonMatchedLeft(changes2.getComparable().getRight(s.getObject()));
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());
121 for (Statement s : changes3.getAdditions()) {
122 comparator.addNonMatchedRight(s.getObject());
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();
136 if (originalModel != null) {
143 public void addFilter(ChangeFilter filter) {
145 throw new IllegalStateException("ModelUpdate has been initialized, adjusting filters is no longer possible.");
150 public List<ChangeFilter> getFilters() {
151 return Collections.unmodifiableList(filters);
155 * Adds an user filter. Use refreshUserFilters() to apply the changes.
158 public void addUserFilter(ChangeFilter filter) {
159 userFilters.add(filter);
163 * Removes an user filter. Use refreshUserFilters() to apply the changes.
166 public void removeUserFilter(ChangeFilter filter) {
167 userFilters.remove(filter);
171 * Clears user filters. Use refreshUserFilters() to apply the changes.
173 public void clearUserFilters() {
177 public List<ChangeFilter> getUserFilters() {
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();
189 stack.addAll(n.getChildren());
191 for (PropertyChange pc : updateList.getChanges()) {
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());
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))
213 if (lc.getResource() != null) {
214 if (lc.getResource().equals(rc.getResource())) {
216 comparable.map(lc, rc);
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);
229 for (UpdateNode lc : l.getChildren()) {
230 if (!comparable.containsLeft(lc))
231 lc.setVisible(false);
233 for (Entry<UpdateNode, UpdateNode> entry : comparable.getEntries()) {
234 applyVisibleFlags(entry.getKey(), entry.getValue());
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))
244 if (lc.getFirst() != null && lc.getFirst().equals(rc.getFirst())) {
245 comparable.map(lc, rc);
248 if (lc.getSecond() != null && lc.getSecond().equals(rc.getSecond())) {
249 comparable.map(lc, rc);
254 for (PropertyChange lc : l) {
255 if (!comparable.containsLeft(lc))
256 lc.setVisible(false);
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());
267 public Resource getOldModel() {
271 public Resource getNewModel() {
275 public Resource getOriginalModel() {
276 return originalModel;
279 public boolean isInit() {
283 public GraphChanges getChanges() {
286 public UpdateTree getUpdateTree() {
289 public UpdateList getUpdateList() {
292 public GraphChanges getChanges2() {
296 public UpdateTree getUpdateTree2() {
299 public UpdateList getUpdateList2() {
303 public GraphChanges getChanges3() {
307 public UpdateTree getUpdateTree3() throws DatabaseException{
308 if (updateTree3 == null && changes3 != null)
309 updateTree3 = getUpdateTree(changes3);
312 public UpdateList getUpdateList3() throws DatabaseException {
313 if (updateList3 == null && changes3 != null)
314 updateList3 = getUpdateList(changes3);
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()) {
326 updateTree.getUpdateOps().applyAll(graph);
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()) {
337 updateTree.getUpdateOps().applySelected(graph);
343 protected Session getSession() {
344 return Simantics.getSession();
347 public Read<GraphChanges> createFilterRead(GraphChanges changes, List<ChangeFilter> filters) {
348 return new FilterChangesRead(changes, filters);
353 public static class FilterChangesRead implements Read<GraphChanges> {
354 private GraphChanges changes;
355 private List<ChangeFilter> filters;
357 public FilterChangesRead(GraphChanges changes, List<ChangeFilter> filters) {
358 this.changes = changes;
359 this.filters = filters;
363 public GraphChanges perform(ReadGraph graph) throws DatabaseException {
364 return filterChanges(graph, 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.
375 * @throws DatabaseException
377 protected GraphChanges filterChanges(ReadGraph g, GraphChanges changes) throws DatabaseException
380 List<Modification> modifications = new ArrayList<Modification>();
382 for (Modification mod : changes.getModifications()) {
384 boolean accept = true;
385 for (ChangeFilter filter : filters) {
386 if (!filter.accept(g, mod)) {
392 modifications.add(mod);
394 GraphChanges newChanges = new GraphChanges(changes.getResource1(),changes.getResource2(),changes.getDeletions(), changes.getAdditions(), modifications, changes.getComparable());
400 public interface ChangeFilter {
401 public boolean accept(ReadGraph g, Modification change) throws DatabaseException;
407 * Filters floating point value changes (default filter is set filter when the change is less than 1%)
410 protected class FPValueFilter implements ChangeFilter {
412 private double percentage = 0.01;
414 public FPValueFilter() {
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;
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()))
429 Object v1 = g.getValue(change.getLeftStm().getObject());
430 Object v2 = g.getValue(change.getRightStm().getObject());
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)
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)
448 public void defaultSelections() {
449 if (changes3 == null) {
452 // select all changes
453 for (Entry<Resource, UpdateOp> op : updateTree.getUpdateOps().getResourceMap().entrySet()) {
454 op.getValue().select(true);
458 for (PropertyChange pair : updateList.getChanges()) {
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());
466 if (changes3.getComparable().containsRight(op.getKey())){
467 op2 = updateTree2.getUpdateOps().getUpdateOp(changes3.getComparable().getLeft(op.getKey()));
470 if (op2 != null && op.getValue().getClass() == op2.getClass()) {
471 op.getValue().select(false);
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())) {
492 private void showWarning(String string) {
493 for (WarningListener l : warningListeners)
494 l.showWarning(this, string);
497 private List<WarningListener> warningListeners = new ArrayList<>();
499 public static interface WarningListener {
500 void showWarning(ModelUpdate update, String warning);
503 public void addListener(WarningListener listener) {
504 warningListeners.add(listener);
507 public void removeListener(WarningListener listener) {
508 warningListeners.remove(listener);