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.GraphComparator;
22 import org.simantics.utils.datastructures.BijectionMap;
23 import org.simantics.utils.datastructures.Pair;
25 public abstract class ModelUpdate {
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)
31 private GraphChanges changes; // changes between old /new
32 private UpdateTree updateTree;
33 private UpdateList updateList;
35 private GraphChanges changes2; // changes between original / old
36 private UpdateTree updateTree2;
37 private UpdateList updateList2;
39 private GraphChanges changes3; // changes between original / new
40 private UpdateTree updateTree3;
41 private UpdateList updateList3;
43 private List<ChangeFilter> filters = new ArrayList<ChangeFilter>();
44 private List<ChangeFilter> userFilters = new ArrayList<ChangeFilter>();
48 public void setInput(Resource oldModel, Resource newModel) throws DatabaseException {
49 setInput(oldModel, newModel, null, false);
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
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);
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));
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.
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());
101 comparator.addComparableResources(oldR, newR);
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()));
110 for (Statement s : changes3.getDeletions()) {
111 if (changes2.getComparable().containsLeft(s.getObject()))
112 comparator.addNonMatchedLeft(changes2.getComparable().getRight(s.getObject()));
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());
120 for (Statement s : changes3.getAdditions()) {
121 comparator.addNonMatchedRight(s.getObject());
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();
135 if (originalModel != null) {
142 public void addFilter(ChangeFilter filter) {
144 throw new IllegalStateException("ModelUpdate has been initialized, adjusting filters is no longer possible.");
149 public List<ChangeFilter> getFilters() {
150 return Collections.unmodifiableList(filters);
154 * Adds an user filter. Use refreshUserFilters() to apply the changes.
157 public void addUserFilter(ChangeFilter filter) {
158 userFilters.add(filter);
162 * Removes an user filter. Use refreshUserFilters() to apply the changes.
165 public void removeUserFilter(ChangeFilter filter) {
166 userFilters.remove(filter);
170 * Clears user filters. Use refreshUserFilters() to apply the changes.
172 public void clearUserFilters() {
176 public List<ChangeFilter> getUserFilters() {
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();
188 stack.addAll(n.getChildren());
190 for (PropertyChange pc : updateList.getChanges()) {
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());
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))
212 if (lc.getResource() != null) {
213 if (lc.getResource().equals(rc.getResource())) {
215 comparable.map(lc, rc);
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);
228 for (UpdateNode lc : l.getChildren()) {
229 if (!comparable.containsLeft(lc))
230 lc.setVisible(false);
232 for (Entry<UpdateNode, UpdateNode> entry : comparable.getEntries()) {
233 applyVisibleFlags(entry.getKey(), entry.getValue());
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))
243 if (lc.getFirst() != null && lc.getFirst().equals(rc.getFirst())) {
244 comparable.map(lc, rc);
247 if (lc.getSecond() != null && lc.getSecond().equals(rc.getSecond())) {
248 comparable.map(lc, rc);
253 for (PropertyChange lc : l) {
254 if (!comparable.containsLeft(lc))
255 lc.setVisible(false);
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());
266 public Resource getOldModel() {
270 public Resource getNewModel() {
274 public Resource getOriginalModel() {
275 return originalModel;
278 public boolean isInit() {
282 public GraphChanges getChanges() {
285 public UpdateTree getUpdateTree() {
288 public UpdateList getUpdateList() {
291 public GraphChanges getChanges2() {
295 public UpdateTree getUpdateTree2() {
298 public UpdateList getUpdateList2() {
302 public GraphChanges getChanges3() {
306 public UpdateTree getUpdateTree3() throws DatabaseException{
307 if (updateTree3 == null && changes3 != null)
308 updateTree3 = getUpdateTree(changes3);
311 public UpdateList getUpdateList3() throws DatabaseException {
312 if (updateList3 == null && changes3 != null)
313 updateList3 = getUpdateList(changes3);
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()) {
325 updateTree.getUpdateOps().applyAll(graph);
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()) {
336 updateTree.getUpdateOps().applySelected(graph);
342 protected Session getSession() {
343 return Simantics.getSession();
346 public Read<GraphChanges> createFilterRead(GraphChanges changes, List<ChangeFilter> filters) {
347 return new FilterChangesRead(changes, filters);
352 public static class FilterChangesRead implements Read<GraphChanges> {
353 private GraphChanges changes;
354 private List<ChangeFilter> filters;
356 public FilterChangesRead(GraphChanges changes, List<ChangeFilter> filters) {
357 this.changes = changes;
358 this.filters = filters;
362 public GraphChanges perform(ReadGraph graph) throws DatabaseException {
363 return filterChanges(graph, 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.
374 * @throws DatabaseException
376 protected GraphChanges filterChanges(ReadGraph g, GraphChanges changes) throws DatabaseException
379 List<Pair<Statement,Statement>> modifications = new ArrayList<Pair<Statement,Statement>>();
381 for (Pair<Statement, Statement> mod : changes.getModifications()) {
383 boolean accept = true;
384 for (ChangeFilter filter : filters) {
385 if (!filter.accept(g, mod)) {
391 modifications.add(mod);
393 GraphChanges newChanges = new GraphChanges(changes.getResource1(),changes.getResource2(),changes.getDeletions(), changes.getAdditions(), modifications, changes.getComparable());
399 public interface ChangeFilter {
400 public boolean accept(ReadGraph g, Pair<Statement, Statement> change) throws DatabaseException;
406 * Filters floating point value changes (default filter is set filter when the change is less than 1%)
409 protected class FPValueFilter implements ChangeFilter {
411 private double percentage = 0.01;
413 public FPValueFilter() {
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;
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()))
428 Object v1 = g.getValue(change.first.getObject());
429 Object v2 = g.getValue(change.second.getObject());
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)
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)
447 public void defaultSelections() {
448 if (changes3 == null) {
451 // select all changes
452 for (Entry<Resource, UpdateOp> op : updateTree.getUpdateOps().getResourceMap().entrySet()) {
453 op.getValue().select(true);
457 for (PropertyChange pair : updateList.getChanges()) {
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());
465 if (changes3.getComparable().containsRight(op.getKey())){
466 op2 = updateTree2.getUpdateOps().getUpdateOp(changes3.getComparable().getLeft(op.getKey()));
469 if (op2 != null && op.getValue().getClass() == op2.getClass()) {
470 op.getValue().select(false);
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())) {
491 private void showWarning(String string) {
492 for (WarningListener l : warningListeners)
493 l.showWarning(this, string);
496 private List<WarningListener> warningListeners = new ArrayList<>();
498 public static interface WarningListener {
499 void showWarning(ModelUpdate update, String warning);
502 public void addListener(WarningListener listener) {
503 warningListeners.add(listener);
506 public void removeListener(WarningListener listener) {
507 warningListeners.remove(listener);