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().equals(rc.getFirst())) {
244 comparable.map(lc, rc);
249 for (PropertyChange lc : l) {
250 if (!comparable.containsLeft(lc))
251 lc.setVisible(false);
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());
262 public Resource getOldModel() {
266 public Resource getNewModel() {
270 public Resource getOriginalModel() {
271 return originalModel;
274 public boolean isInit() {
278 public GraphChanges getChanges() {
281 public UpdateTree getUpdateTree() {
284 public UpdateList getUpdateList() {
287 public GraphChanges getChanges2() {
291 public UpdateTree getUpdateTree2() {
294 public UpdateList getUpdateList2() {
298 public GraphChanges getChanges3() {
302 public UpdateTree getUpdateTree3() throws DatabaseException{
303 if (updateTree3 == null && changes3 != null)
304 updateTree3 = getUpdateTree(changes3);
307 public UpdateList getUpdateList3() throws DatabaseException {
308 if (updateList3 == null && changes3 != null)
309 updateList3 = getUpdateList(changes3);
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()) {
321 updateTree.getUpdateOps().applyAll(graph);
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()) {
332 updateTree.getUpdateOps().applySelected(graph);
338 protected Session getSession() {
339 return Simantics.getSession();
342 public Read<GraphChanges> createFilterRead(GraphChanges changes, List<ChangeFilter> filters) {
343 return new FilterChangesRead(changes, filters);
348 public static class FilterChangesRead implements Read<GraphChanges> {
349 private GraphChanges changes;
350 private List<ChangeFilter> filters;
352 public FilterChangesRead(GraphChanges changes, List<ChangeFilter> filters) {
353 this.changes = changes;
354 this.filters = filters;
358 public GraphChanges perform(ReadGraph graph) throws DatabaseException {
359 return filterChanges(graph, changes);
364 * 1. Changes that are not essential for model update (changes that can be found when the models are exactly the same)
365 * 2. Runs custom filters for value changes.
370 * @throws DatabaseException
372 protected GraphChanges filterChanges(ReadGraph g, GraphChanges changes) throws DatabaseException
375 List<Pair<Statement,Statement>> modifications = new ArrayList<Pair<Statement,Statement>>();
377 for (Pair<Statement, Statement> mod : changes.getModifications()) {
379 boolean accept = true;
380 for (ChangeFilter filter : filters) {
381 if (!filter.accept(g, mod)) {
387 modifications.add(mod);
389 GraphChanges newChanges = new GraphChanges(changes.getResource1(),changes.getResource2(),changes.getDeletions(), changes.getAdditions(), modifications, changes.getComparable());
395 public interface ChangeFilter {
396 public boolean accept(ReadGraph g, Pair<Statement, Statement> change) throws DatabaseException;
402 * Filters floating point value changes (default filter is set filter when the change is less than 1%)
405 protected class FPValueFilter implements ChangeFilter {
407 private double percentage = 0.01;
409 public FPValueFilter() {
413 public FPValueFilter(double percentage) {
414 if (percentage < 0.0 || percentage > 1.0)
415 throw new IllegalArgumentException("Percentage must be between 0.0 and 1.0.");
416 this.percentage = percentage;
420 public boolean accept(ReadGraph g, Pair<Statement, Statement> change) throws DatabaseException {
421 //filter floating point values that have less than 1% difference.
422 if (!g.hasValue(change.first.getObject()) || !g.hasValue(change.second.getObject()))
424 Object v1 = g.getValue(change.first.getObject());
425 Object v2 = g.getValue(change.second.getObject());
427 if (v1 instanceof Double && v2 instanceof Double) {
428 double d1 = (Double)v1;
429 double d2 = (Double)v2;
430 if (Math.abs(d1-d2) / Math.max(Math.abs(d1), Math.abs(d2)) < percentage)
432 } else if (v1 instanceof Float && v2 instanceof Float) {
433 float d1 = (Float)v1;
434 float d2 = (Float)v2;
435 if (Math.abs(d1-d2) / Math.max(Math.abs(d1), Math.abs(d2)) < percentage)
443 public void defaultSelections() {
444 if (changes3 == null) {
447 // select all changes
448 for (Entry<Resource, UpdateOp> op : updateTree.getUpdateOps().getResourceMap().entrySet()) {
449 op.getValue().select(true);
453 for (PropertyChange pair : updateList.getChanges()) {
457 // preserve user-made changes (by removing selections)
458 for (Entry<Resource, UpdateOp> op : updateTree.getUpdateOps().getResourceMap().entrySet()) {
459 UpdateOp op2 = updateTree2.getUpdateOps().getUpdateOp(op.getKey());
461 if (changes3.getComparable().containsRight(op.getKey())){
462 op2 = updateTree2.getUpdateOps().getUpdateOp(changes3.getComparable().getLeft(op.getKey()));
465 if (op2 != null && op.getValue().getClass() == op2.getClass()) {
466 op.getValue().select(false);
470 for (PropertyChange pair : updateList.getChanges()) {
471 if (pair.getFirst() != null) {
472 boolean found = false;
473 for (PropertyChange pair2 : updateList2.getChanges()) {
474 if (pair.getFirst().equals(pair2.getSecond())) {
487 private void showWarning(String string) {
488 for (WarningListener l : warningListeners)
489 l.showWarning(this, string);
492 private List<WarningListener> warningListeners = new ArrayList<>();
494 public static interface WarningListener {
495 void showWarning(ModelUpdate update, String warning);
498 public void addListener(WarningListener listener) {
499 warningListeners.add(listener);
502 public void removeListener(WarningListener listener) {
503 warningListeners.remove(listener);