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.Deque;
8 import java.util.Map.Entry;
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.exception.DatabaseException;
17 import org.simantics.db.layer0.util.Layer0Utils;
18 import org.simantics.db.request.Read;
19 import org.simantics.interop.test.GraphChanges;
20 import org.simantics.interop.test.GraphComparator;
21 import org.simantics.utils.datastructures.BijectionMap;
22 import org.simantics.utils.datastructures.Pair;
24 public abstract class ModelUpdate {
26 private Resource oldModel; // old model that is going to be updated (User modified model)
27 private Resource newModel; // new model that contains the updates (New design model)
28 private Resource originalModel; // original model (optional) that is used for detecting and retaining user made changes (Old design model)
30 private GraphChanges changes; // changes between old /new
31 private UpdateTree updateTree;
32 private UpdateList updateList;
34 private GraphChanges changes2; // changes between original / old
35 private UpdateTree updateTree2;
36 private UpdateList updateList2;
38 private GraphChanges changes3; // changes between original / new
39 private UpdateTree updateTree3;
40 private UpdateList updateList3;
42 private List<ChangeFilter> filters = new ArrayList<ChangeFilter>();
43 private List<ChangeFilter> userFilters = new ArrayList<ChangeFilter>();
47 public void setInput(Resource oldModel, Resource newModel) throws DatabaseException {
48 setInput(oldModel, newModel, null, false);
52 * Initialises the ModelUpdate with given input
53 * @param oldModel the model that is going to be updated (User modified model)
54 * @param newModel the model containing updates (New design model)
55 * @param originalModel the model that is used for detecting and retaining user made changes (Old design model). Parameter can be null.
56 * @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.
57 * @throws DatabaseException
59 public void setInput(Resource oldModel, Resource newModel, Resource originalModel, boolean newDistinct) throws DatabaseException{
60 this.oldModel = oldModel;
61 this.newModel = newModel;
62 this.originalModel = originalModel;
63 // addFilters(filters);
64 if (originalModel != null) {
65 // tree way comparison
66 // compare the original and the old model
67 Pair<GraphComparator,String> result2 = getChanges(originalModel, oldModel);
68 GraphComparator comparator2 = result2.first;
69 if (result2.second != null)
70 showWarning(result2.second);
71 comparator2.test(getSession());
72 changes2 = comparator2.getChanges();
73 changes2 = getSession().syncRequest(new FilterChangesRead(changes2, filters));
74 updateTree2 = getUpdateTree(changes2);
75 updateList2 = getUpdateList(changes2);
77 // compare the original and the new model
78 Pair<GraphComparator,String> result3 = getChanges(originalModel,newModel);
79 GraphComparator comparator3 = result3.first;
80 if (result3.second != null)
81 showWarning(result3.second);
82 comparator3.test(getSession());
83 changes3 = comparator3.getChanges();
84 changes3 = getSession().syncRequest(new FilterChangesRead(changes3, filters));
87 Pair<GraphComparator,String> result = getChanges(oldModel,newModel);
88 GraphComparator comparator = result.first;
89 if (result.second != null)
90 showWarning(result.second);
91 if (originalModel != null) {
92 // three-way comparison: use change information to configure
93 // the comparison between the old and the new model.
95 // 1. map comparable resources
96 for (Entry<Resource, Resource> origToOld : changes2.getComparable().getEntries()) {
97 Resource oldR = origToOld.getValue();
98 Resource newR = changes3.getComparable().getRight(origToOld.getKey());
100 comparator.addComparableResources(oldR, newR);
103 // 2. mark removed resources as distinct, so that comparison does not pair them
104 for (Statement s : changes2.getDeletions()) {
105 if (changes3.getComparable().containsLeft(s.getObject()))
106 comparator.addNonMatchedRight(changes3.getComparable().getRight(s.getObject()));
109 for (Statement s : changes3.getDeletions()) {
110 if (changes2.getComparable().containsLeft(s.getObject()))
111 comparator.addNonMatchedLeft(changes2.getComparable().getRight(s.getObject()));
114 // 3. mark added resources as distinct, so that comparison does not pair them
115 for (Statement s : changes2.getAdditions()) {
116 comparator.addNonMatchedLeft(s.getObject());
119 for (Statement s : changes3.getAdditions()) {
120 comparator.addNonMatchedRight(s.getObject());
124 comparator.test(getSession());
125 changes = comparator.getChanges();
126 changes = getSession().syncRequest(new FilterChangesRead(changes, filters));
127 updateTree = getUpdateTree(changes);
128 updateList = getUpdateList(changes);
129 if (userFilters.size() != 0) {
130 refreshUserFilters();
134 if (originalModel != null) {
141 public void addFilter(ChangeFilter filter) {
143 throw new IllegalStateException("ModelUpdate has been initialized, adjusting filters is no longer possible.");
149 * Adds an user filter. Use refreshUserFilters() to apply the changes.
152 public void addUserFilter(ChangeFilter filter) {
153 userFilters.add(filter);
157 * Removes an user filter. Use refreshUserFilters() to apply the changes.
160 public void removeUserFilter(ChangeFilter filter) {
161 userFilters.remove(filter);
165 * Clears user filters. Use refreshUserFilters() to apply the changes.
167 public void clearUserFilters() {
171 public void refreshUserFilters() throws DatabaseException{
172 // use user filters to set visible flags of changes.
173 // First, set all changes visible.
174 Deque<UpdateNode> stack = new ArrayDeque<>();
175 stack.push(updateTree.getRootNode());
176 while (!stack.isEmpty()) {
177 UpdateNode n = stack.pop();
179 stack.addAll(n.getChildren());
181 for (PropertyChange pc : updateList.getChanges()) {
184 if (userFilters.size() > 0) {
185 // Create filtered changes
186 List<ChangeFilter> combined = new ArrayList<>(filters);
187 combined.addAll(userFilters);
188 GraphChanges filteredChanges = getSession().syncRequest(new FilterChangesRead(changes, combined));
189 UpdateTree updateTreeF = getUpdateTree(filteredChanges);
190 UpdateList updateListF = getUpdateList(filteredChanges);
191 // hide changes that are not contained within the filtered changes.
192 applyVisibleFlags(updateTree.getRootNode(), updateTreeF.getRootNode());
193 applyVisibleFlags(updateList.getChanges(), updateListF.getChanges());
197 private void applyVisibleFlags(UpdateNode l, UpdateNode r) {
198 BijectionMap<UpdateNode, UpdateNode> comparable = new BijectionMap<>();
199 for (UpdateNode lc : l.getChildren()) {
200 for (UpdateNode rc : r.getChildren()) {
201 if (comparable.containsRight(rc))
203 if (lc.getResource() != null) {
204 if (lc.getResource().equals(rc.getResource())) {
206 comparable.map(lc, rc);
209 } else if (rc.getResource() == null){
210 UpdateOp lop = lc.getOp();
211 UpdateOp rop = rc.getOp();
212 if (lop.getStatement() != null && lop.getStatement().equals(rop.getStatement())) {
213 comparable.map(lc, rc);
219 for (UpdateNode lc : l.getChildren()) {
220 if (!comparable.containsLeft(lc))
221 lc.setVisible(false);
223 for (Entry<UpdateNode, UpdateNode> entry : comparable.getEntries()) {
224 applyVisibleFlags(entry.getKey(), entry.getValue());
228 private void applyVisibleFlags(Collection<PropertyChange> l, Collection<PropertyChange> r) {
229 BijectionMap<PropertyChange, PropertyChange> comparable = new BijectionMap<>();
230 for (PropertyChange lc : l) {
231 for (PropertyChange rc : r) {
232 if (comparable.containsRight(rc))
234 if (lc.getFirst().equals(rc.getFirst())) {
235 comparable.map(lc, rc);
239 for (PropertyChange lc : l) {
240 if (!comparable.containsLeft(lc))
241 lc.setVisible(false);
246 protected abstract Pair<GraphComparator,String> getChanges(Resource r1, Resource r2) throws DatabaseException;
247 protected abstract UpdateTree getUpdateTree(GraphChanges changes) throws DatabaseException;
248 protected UpdateList getUpdateList(GraphChanges changes) throws DatabaseException {
249 return new UpdateList(changes, changes.getModifications());
252 public Resource getOldModel() {
256 public Resource getNewModel() {
260 public Resource getOriginalModel() {
261 return originalModel;
264 public boolean isInit() {
268 public GraphChanges getChanges() {
271 public UpdateTree getUpdateTree() {
274 public UpdateList getUpdateList() {
277 public GraphChanges getChanges2() {
281 public UpdateTree getUpdateTree2() {
284 public UpdateList getUpdateList2() {
288 public GraphChanges getChanges3() {
292 public UpdateTree getUpdateTree3() throws DatabaseException{
293 if (updateTree3 == null && changes3 != null)
294 updateTree3 = getUpdateTree(changes3);
297 public UpdateList getUpdateList3() throws DatabaseException {
298 if (updateList3 == null && changes3 != null)
299 updateList3 = getUpdateList(changes3);
304 public void applyAll(WriteGraph graph) throws DatabaseException {
305 Layer0Utils.addCommentMetadata(graph, "Apply all model updates");
306 graph.markUndoPoint();
307 for (PropertyChange mod : updateList.getChanges()) {
311 updateTree.getUpdateOps().applyAll(graph);
314 public void applySelected(WriteGraph graph) throws DatabaseException {
315 Layer0Utils.addCommentMetadata(graph, "Apply selected model updates");
316 graph.markUndoPoint();
317 for (PropertyChange mod : updateList.getChanges()) {
322 updateTree.getUpdateOps().applySelected(graph);
328 protected Session getSession() {
329 return Simantics.getSession();
334 private class FilterChangesRead implements Read<GraphChanges> {
335 private GraphChanges changes;
336 private List<ChangeFilter> filters;
338 public FilterChangesRead(GraphChanges changes, List<ChangeFilter> filters) {
339 this.changes = changes;
340 this.filters = filters;
344 public GraphChanges perform(ReadGraph graph) throws DatabaseException {
345 return filterChanges(graph, changes);
350 * 1. Changes that are not essential for model update (changes that can be found when the models are axcatly the same)
351 * 2. Runs custom filters for value changes.
356 * @throws DatabaseException
358 protected GraphChanges filterChanges(ReadGraph g, GraphChanges changes) throws DatabaseException
361 List<Pair<Statement,Statement>> modifications = new ArrayList<Pair<Statement,Statement>>();
363 for (Pair<Statement, Statement> mod : changes.getModifications()) {
365 boolean accept = true;
366 for (ChangeFilter filter : filters) {
367 if (!filter.accept(g, mod)) {
373 modifications.add(mod);
375 GraphChanges newChanges = new GraphChanges(changes.getResource1(),changes.getResource2(),changes.getDeletions(), changes.getAdditions(), modifications, changes.getComparable());
381 public interface ChangeFilter {
382 public boolean accept(ReadGraph g, Pair<Statement, Statement> change) throws DatabaseException;
388 * Filters floating point value changes (default filter is set filter when the change is less than 1%)
391 protected class FPValueFilter implements ChangeFilter {
393 private double percentage = 0.01;
395 public FPValueFilter() {
399 public FPValueFilter(double percentage) {
400 if (percentage < 0.0 || percentage > 1.0)
401 throw new IllegalArgumentException("Percentage must be between 0.0 and 1.0.");
402 this.percentage = percentage;
406 public boolean accept(ReadGraph g, Pair<Statement, Statement> change) throws DatabaseException {
407 //filter floating point values that have less than 1% difference.
408 if (!g.hasValue(change.first.getObject()) || !g.hasValue(change.second.getObject()))
410 Object v1 = g.getValue(change.first.getObject());
411 Object v2 = g.getValue(change.second.getObject());
413 if (v1 instanceof Double && v2 instanceof Double) {
414 double d1 = (Double)v1;
415 double d2 = (Double)v2;
416 if (Math.abs(d1-d2) / Math.max(Math.abs(d1), Math.abs(d2)) < percentage)
418 } else if (v1 instanceof Float && v2 instanceof Float) {
419 float d1 = (Float)v1;
420 float d2 = (Float)v2;
421 if (Math.abs(d1-d2) / Math.max(Math.abs(d1), Math.abs(d2)) < percentage)
429 public void defaultSelections() {
430 if (changes3 == null) {
433 // select all changes
434 for (Entry<Resource, UpdateOp> op : updateTree.getUpdateOps().getResourceMap().entrySet()) {
435 op.getValue().select(true);
439 for (PropertyChange pair : updateList.getChanges()) {
443 // preserve user-made changes (by removing selections)
444 for (Entry<Resource, UpdateOp> op : updateTree.getUpdateOps().getResourceMap().entrySet()) {
445 UpdateOp op2 = updateTree2.getUpdateOps().getUpdateOp(op.getKey());
447 if (changes3.getComparable().containsRight(op.getKey())){
448 op2 = updateTree2.getUpdateOps().getUpdateOp(changes3.getComparable().getLeft(op.getKey()));
451 if (op2 != null && op.getValue().getClass() == op2.getClass()) {
452 op.getValue().select(false);
456 for (PropertyChange pair : updateList.getChanges()) {
457 if (pair.getFirst() != null) {
458 boolean found = false;
459 for (PropertyChange pair2 : updateList2.getChanges()) {
460 if (pair.getFirst().equals(pair2.getSecond())) {
473 private void showWarning(String string) {
474 for (WarningListener l : warningListeners)
475 l.showWarning(this, string);
478 private List<WarningListener> warningListeners = new ArrayList<>();
480 public static interface WarningListener {
481 void showWarning(ModelUpdate update, String warning);
484 public void addListener(WarningListener listener) {
485 warningListeners.add(listener);
488 public void removeListener(WarningListener listener) {
489 warningListeners.remove(listener);