From 48789e733b91b0c337bd64b8e053c0ee79e0a374 Mon Sep 17 00:00:00 2001 From: Marko Luukkainen Date: Tue, 3 Oct 2017 18:04:28 +0300 Subject: [PATCH] Separating model update logic from UI --- .../update/editor/ModelUpdateEditor.java | 324 ++-------------- .../interop/update/model/ModelUpdate.java | 350 ++++++++++++++++++ 2 files changed, 387 insertions(+), 287 deletions(-) create mode 100644 org.simantics.interop.update/src/org/simantics/interop/update/model/ModelUpdate.java diff --git a/org.simantics.interop.update/src/org/simantics/interop/update/editor/ModelUpdateEditor.java b/org.simantics.interop.update/src/org/simantics/interop/update/editor/ModelUpdateEditor.java index 4c17299..4e30b22 100644 --- a/org.simantics.interop.update/src/org/simantics/interop/update/editor/ModelUpdateEditor.java +++ b/org.simantics.interop.update/src/org/simantics/interop/update/editor/ModelUpdateEditor.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; -import java.util.Map.Entry; import java.util.Stack; import org.eclipse.jface.resource.ImageDescriptor; @@ -48,16 +47,16 @@ import org.simantics.db.Statement; import org.simantics.db.WriteGraph; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.exception.DatabaseException; -import org.simantics.db.layer0.util.Layer0Utils; import org.simantics.db.request.Read; import org.simantics.interop.test.GraphChanges; -import org.simantics.interop.test.GraphComparator; import org.simantics.interop.update.Activator; +import org.simantics.interop.update.model.ModelUpdate; import org.simantics.interop.update.model.UpdateList; import org.simantics.interop.update.model.UpdateNode; import org.simantics.interop.update.model.UpdateNode.Status; import org.simantics.interop.update.model.UpdateOp; import org.simantics.interop.update.model.UpdateTree; +import org.simantics.interop.update.model.ModelUpdate.WarningListener; import org.simantics.interop.utils.TableUtils; import org.simantics.ui.SimanticsUI; import org.simantics.utils.datastructures.Callback; @@ -71,22 +70,13 @@ import org.simantics.utils.ui.ExceptionUtils; * @author Marko Luukkainen * */ -public abstract class ModelUpdateEditor extends Composite{ +public abstract class ModelUpdateEditor extends Composite implements WarningListener{ private Composite errorComposite; private CheckboxTreeViewer changeBrowser; private TableViewer changeViewer; - private GraphChanges changes; - private UpdateTree updateTree; - private UpdateList updateList; - - private GraphChanges changes2; - private GraphChanges changes3; - private UpdateTree updateTree2; - private UpdateList updateList2; - private Button updateAllButton; private Button updateSelectedButton; @@ -100,10 +90,10 @@ public abstract class ModelUpdateEditor extends Composite{ private Color deletedColor; private Color addedColor; + private ModelUpdate update; - private HashSet selectedStructure = new HashSet(); - private List filters = new ArrayList(); + private HashSet selectedStructure = new HashSet(); public ModelUpdateEditor(Composite parent) { super(parent,SWT.NONE); @@ -222,12 +212,12 @@ public abstract class ModelUpdateEditor extends Composite{ selection.getColumn().addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - if (updateList.getChanges().size() > 0) { - if (updateList.getSelected().size() > 0) { - updateList.clearSelected(); + if (update.getUpdateList().getChanges().size() > 0) { + if (update.getUpdateList().getSelected().size() > 0) { + update.getUpdateList().clearSelected(); } else { - for (Pair nr : updateList.getChanges()) - updateList.addSelected(nr); + for (Pair nr : update.getUpdateList().getChanges()) + update.getUpdateList().addSelected(nr); } changeViewer.refresh(); } @@ -299,26 +289,17 @@ public abstract class ModelUpdateEditor extends Composite{ } protected abstract ColumnLabelProvider getLabelProvider(int i); - protected abstract Pair getChanges(Resource r1, Resource r2) throws DatabaseException; - protected abstract UpdateTree getUpdateTree(GraphChanges changes) throws DatabaseException; - protected UpdateList getUpdateList(GraphChanges changes) throws DatabaseException { - return new UpdateList(changes.getModifications()); - } - - protected void addFilters(List filters) { - - } public GraphChanges getChanges() { - return changes; + return update.getChanges(); } public UpdateTree getUpdateTree() { - return updateTree; + return update.getUpdateTree(); } public UpdateList getUpdateList() { - return updateList; + return update.getUpdateList(); } public CheckboxTreeViewer getChangeBrowser() { @@ -329,7 +310,7 @@ public abstract class ModelUpdateEditor extends Composite{ return changeViewer; } - private void showWarning(String text) { + public void showWarning(ModelUpdate update, String text) { errorComposite.setVisible(true); Label label = new Label(errorComposite, SWT.NONE); @@ -354,7 +335,7 @@ public abstract class ModelUpdateEditor extends Composite{ public void refreshChecked() { Stack nodeStack = new Stack(); - nodeStack.push((UpdateNode)updateTree.getRootNode()); + nodeStack.push((UpdateNode)update.getUpdateTree().getRootNode()); while (!nodeStack.isEmpty()) { UpdateNode n = nodeStack.pop(); if (n.getOp() != null) { @@ -396,88 +377,22 @@ public abstract class ModelUpdateEditor extends Composite{ changeViewer.refresh(); } + protected abstract ModelUpdate createUpdate(); + public void load(UpdateEditorInput uei) { - - addFilters(filters); Resource oldModel = uei.getR1(); // old model that is being updated, contains user made changes Resource newModel = uei.getR2(); // new model, Resource originalModel = uei.getR3(); // original old model without user made changes - + boolean newDistinct = uei.isNewDistinct(); try { - - if (originalModel != null) { - // tree way comparison - // compare the original and the old model - Pair result2 = getChanges(originalModel, oldModel); - GraphComparator comparator2 = result2.first; - if (result2.second != null) - showWarning(result2.second); - comparator2.test(getSession()); - changes2 = comparator2.getChanges(); - changes2 = getSession().syncRequest(new FilterChangesRead(changes2)); - updateTree2 = getUpdateTree(changes2); - updateList2 = getUpdateList(changes2); - - // compare the original and the new model - Pair result3 = getChanges(originalModel,newModel); - GraphComparator comparator3 = result3.first; - if (result3.second != null) - showWarning(result3.second); - comparator3.test(getSession()); - changes3 = comparator3.getChanges(); - changes3 = getSession().syncRequest(new FilterChangesRead(changes3)); - } - - Pair result = getChanges(oldModel,newModel); - GraphComparator comparator = result.first; - if (result.second != null) - showWarning(result.second); - if (originalModel != null) { - // three-way comparison: use change information to configure - // the comparison between the old and the new model. - - // 1. map comparable resources - for (Entry origToOld : changes2.getComparable().getEntries()) { - Resource oldR = origToOld.getValue(); - Resource newR = changes3.getComparable().getRight(origToOld.getKey()); - if (newR != null) { - comparator.addComparableResources(oldR, newR); - } - } - // 2. mark removed resources as distinct, so that comparison does not pair them - for (Statement s : changes2.getDeletions()) { - if (changes3.getComparable().containsLeft(s.getObject())) - comparator.addNonMatchedRight(changes3.getComparable().getRight(s.getObject())); - } - - for (Statement s : changes3.getDeletions()) { - if (changes2.getComparable().containsLeft(s.getObject())) - comparator.addNonMatchedLeft(changes2.getComparable().getRight(s.getObject())); - } - if (uei.isNewDistinct()) { - // 3. mark added resources as distinct, so that comparison does not pair them - for (Statement s : changes2.getAdditions()) { - comparator.addNonMatchedLeft(s.getObject()); - } - - for (Statement s : changes3.getAdditions()) { - comparator.addNonMatchedRight(s.getObject()); - } - } - } - comparator.test(getSession()); - changes = comparator.getChanges(); - changes = getSession().syncRequest(new FilterChangesRead(changes)); - updateTree = getUpdateTree(changes); - updateList = getUpdateList(changes); - - if (originalModel != null) { - createDefaultSelections(); - } - + if (update != null) + update.removeListener(this); + update = createUpdate(); + update.addListener(this); + update.setInput(oldModel, newModel, originalModel, newDistinct); } catch (DatabaseException e) { Text text = new Text(this, SWT.MULTI); text.setText(e.getMessage()); @@ -491,8 +406,8 @@ public abstract class ModelUpdateEditor extends Composite{ } protected void setInputs() { - changeViewer.setInput(updateList.getChanges()); - changeBrowser.setInput(updateTree); + changeViewer.setInput(update.getUpdateList().getChanges()); + changeBrowser.setInput(update.getUpdateTree()); } private void applyAll() { @@ -501,14 +416,7 @@ public abstract class ModelUpdateEditor extends Composite{ getSession().asyncRequest(new WriteRequest(){ @Override public void perform(WriteGraph graph) throws DatabaseException { - Layer0Utils.addCommentMetadata(graph, "Apply all model updates"); - graph.markUndoPoint(); - for (Pair mod : updateList.getChanges()) { - applyLiteralChange(graph, mod); - } - updateList.clear(); - - updateTree.getUpdateOps().applyAll(graph); + update.applyAll(graph); Display.getDefault().asyncExec(new Runnable() { @@ -533,38 +441,13 @@ public abstract class ModelUpdateEditor extends Composite{ }); } - protected void applyLiteralChange(WriteGraph graph, Pair mod) throws DatabaseException { - if (mod.second == null) { - graph.deny(mod.first); - return; - } - Resource s = changes.getComparable().getLeft(mod.second.getSubject()); - Resource pred = mod.second.getPredicate(); - if (graph.hasValue(mod.second.getObject())) { - Object value = graph.getValue(mod.second.getObject()); - graph.claimLiteral(s, pred, value); - } else { - graph.deny(s,pred); - } - - - } - private void applySelected() { updateAllButton.setEnabled(false); updateSelectedButton.setEnabled(false); getSession().asyncRequest(new WriteRequest(){ @Override public void perform(WriteGraph graph) throws DatabaseException { - Layer0Utils.addCommentMetadata(graph, "Apply selected model updates"); - graph.markUndoPoint(); - HashSet> changes = new HashSet<>(updateList.getSelected()); - for (Pair mod : changes) { - updateList.removeChange(mod); - applyLiteralChange(graph, mod); - } - - updateTree.getUpdateOps().applySelected(graph); + update.applySelected(graph); Display.getDefault().asyncExec(new Runnable() { @@ -578,142 +461,7 @@ public abstract class ModelUpdateEditor extends Composite{ }); } }); - } - - protected void createDefaultSelections() { - // select all changes - for (Entry op : updateTree.getUpdateOps().getResourceMap().entrySet()) { - op.getValue().select(true); - } - - - for (Pair pair : updateList.getChanges()) { - updateList.addSelected(pair); - } - - // preserve user-made changes (by removing selections) - for (Entry op : updateTree.getUpdateOps().getResourceMap().entrySet()) { - UpdateOp op2 = updateTree2.getUpdateOps().getUpdateOp(op.getKey()); - if (op2 == null) { - if (changes3.getComparable().containsRight(op.getKey())){ - op2 = updateTree2.getUpdateOps().getUpdateOp(changes3.getComparable().getLeft(op.getKey())); - } - } - if (op2 != null && op.getValue().getClass() == op2.getClass()) { - op.getValue().select(false); - } - } - - for (Pair pair : updateList.getChanges()) { - if (pair.first != null) { - boolean found = false; - for (Pair pair2 : updateList2.getChanges()) { - if (pair.first.equals(pair2.first)) { - found = true; - break; - } - } - if (found) { - updateList.removeSelected(pair); - } - } - } - - } - - - /** - * Filters changes: - * 1. Changes that are not essential for model update (changes that can be found when the models are axcatly the same) - * 2. Runs custom filters for value changes. - * - * @param g - * @param changes - * @return - * @throws DatabaseException - */ - protected GraphChanges filterChanges(ReadGraph g, GraphChanges changes) throws DatabaseException - { - - List> modifications = new ArrayList>(); - - for (Pair mod : changes.getModifications()) { - - boolean accept = true; - for (ChangeFilter filter : filters) { - if (!filter.accept(g, mod)) { - accept = false; - break; - } - } - if (accept) - modifications.add(mod); - } - GraphChanges newChanges = new GraphChanges(changes.getResource1(),changes.getResource2(),changes.getDeletions(), changes.getAdditions(), modifications, changes.getComparable()); - return newChanges; - } - - public interface ChangeFilter { - public boolean accept(ReadGraph g, Pair change) throws DatabaseException; - } - - - /** - * - * Filters floating point value changes (default filter is set filter when the change is less than 1%) - * - */ - protected class FPValueFilter implements ChangeFilter { - - private double percentage = 0.01; - - public FPValueFilter() { - - } - - public FPValueFilter(double percentage) { - if (percentage < 0.0 || percentage > 1.0) - throw new IllegalArgumentException("Percentage must be between 0.0 and 1.0."); - this.percentage = percentage; - } - - @Override - public boolean accept(ReadGraph g, Pair change) throws DatabaseException { - //filter floating point values that have less than 1% difference. - if (!g.hasValue(change.first.getObject()) || !g.hasValue(change.second.getObject())) - return true; - Object v1 = g.getValue(change.first.getObject()); - Object v2 = g.getValue(change.second.getObject()); - - if (v1 instanceof Double && v2 instanceof Double) { - double d1 = (Double)v1; - double d2 = (Double)v2; - if (Math.abs(d1-d2) / Math.max(Math.abs(d1), Math.abs(d2)) < percentage) - return false; - } else if (v1 instanceof Float && v2 instanceof Float) { - float d1 = (Float)v1; - float d2 = (Float)v2; - if (Math.abs(d1-d2) / Math.max(Math.abs(d1), Math.abs(d2)) < percentage) - return false; - } - - return true; - } - } - - - private class FilterChangesRead implements Read { - private GraphChanges changes; - public FilterChangesRead(GraphChanges changes) { - this.changes = changes; - } - - @Override - public GraphChanges perform(ReadGraph graph) throws DatabaseException { - return filterChanges(graph, changes); - } - } - + } private class ModificationListContentProvider implements IStructuredContentProvider { @@ -789,9 +537,11 @@ public abstract class ModelUpdateEditor extends Composite{ @Override public Image getImage(Object element) { - if (updateList == null) + if (update == null || !update.isInit()) return null; - if (updateList.isSelected((Pair) element)) +// if (update.getUpdateList() == null) +// return null; + if (update.getUpdateList().isSelected((Pair) element)) return checked; else return unchecked; @@ -877,20 +627,20 @@ public abstract class ModelUpdateEditor extends Composite{ @Override protected Object getValue(Object element) { - if (updateList == null) + if (update == null || !update.isInit()) return false; - return updateList.isSelected((Pair) element); + return update.getUpdateList().isSelected((Pair) element); } @SuppressWarnings("unchecked") @Override protected void setValue(Object element, Object value) { - if (updateList == null) + if (update == null || !update.isInit()) return; if (Boolean.TRUE.equals(value)) - updateList.addSelected((Pair) element); + update.getUpdateList().addSelected((Pair) element); else - updateList.removeSelected((Pair) element); + update.getUpdateList().removeSelected((Pair) element); getViewer().refresh(element); } diff --git a/org.simantics.interop.update/src/org/simantics/interop/update/model/ModelUpdate.java b/org.simantics.interop.update/src/org/simantics/interop/update/model/ModelUpdate.java new file mode 100644 index 0000000..0ddd4e4 --- /dev/null +++ b/org.simantics.interop.update/src/org/simantics/interop/update/model/ModelUpdate.java @@ -0,0 +1,350 @@ +package org.simantics.interop.update.model; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map.Entry; + +import org.simantics.Simantics; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.Session; +import org.simantics.db.Statement; +import org.simantics.db.WriteGraph; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.layer0.util.Layer0Utils; +import org.simantics.db.request.Read; +import org.simantics.interop.test.GraphChanges; +import org.simantics.interop.test.GraphComparator; +import org.simantics.utils.datastructures.Pair; + +public abstract class ModelUpdate { + + private GraphChanges changes; + private UpdateTree updateTree; + private UpdateList updateList; + + private GraphChanges changes2; + private GraphChanges changes3; + private UpdateTree updateTree2; + private UpdateList updateList2; + + private List filters = new ArrayList(); + + boolean init = false; + + public void setInput(Resource oldModel, Resource newModel) throws DatabaseException { + setInput(oldModel, newModel, null, false); + } + + public void setInput(Resource oldModel, Resource newModel, Resource originalModel, boolean newDistinct) throws DatabaseException{ + addFilters(filters); + if (originalModel != null) { + // tree way comparison + // compare the original and the old model + Pair result2 = getChanges(originalModel, oldModel); + GraphComparator comparator2 = result2.first; + if (result2.second != null) + showWarning(result2.second); + comparator2.test(getSession()); + changes2 = comparator2.getChanges(); + changes2 = getSession().syncRequest(new FilterChangesRead(changes2)); + updateTree2 = getUpdateTree(changes2); + updateList2 = getUpdateList(changes2); + + // compare the original and the new model + Pair result3 = getChanges(originalModel,newModel); + GraphComparator comparator3 = result3.first; + if (result3.second != null) + showWarning(result3.second); + comparator3.test(getSession()); + changes3 = comparator3.getChanges(); + changes3 = getSession().syncRequest(new FilterChangesRead(changes3)); + } + + Pair result = getChanges(oldModel,newModel); + GraphComparator comparator = result.first; + if (result.second != null) + showWarning(result.second); + if (originalModel != null) { + // three-way comparison: use change information to configure + // the comparison between the old and the new model. + + // 1. map comparable resources + for (Entry origToOld : changes2.getComparable().getEntries()) { + Resource oldR = origToOld.getValue(); + Resource newR = changes3.getComparable().getRight(origToOld.getKey()); + if (newR != null) { + comparator.addComparableResources(oldR, newR); + } + } + // 2. mark removed resources as distinct, so that comparison does not pair them + for (Statement s : changes2.getDeletions()) { + if (changes3.getComparable().containsLeft(s.getObject())) + comparator.addNonMatchedRight(changes3.getComparable().getRight(s.getObject())); + } + + for (Statement s : changes3.getDeletions()) { + if (changes2.getComparable().containsLeft(s.getObject())) + comparator.addNonMatchedLeft(changes2.getComparable().getRight(s.getObject())); + } + if (newDistinct) { + // 3. mark added resources as distinct, so that comparison does not pair them + for (Statement s : changes2.getAdditions()) { + comparator.addNonMatchedLeft(s.getObject()); + } + + for (Statement s : changes3.getAdditions()) { + comparator.addNonMatchedRight(s.getObject()); + } + } + } + comparator.test(getSession()); + changes = comparator.getChanges(); + changes = getSession().syncRequest(new FilterChangesRead(changes)); + updateTree = getUpdateTree(changes); + updateList = getUpdateList(changes); + + + if (originalModel != null) { + createDefaultSelections(); + } + + init = true; + } + + + + protected abstract Pair getChanges(Resource r1, Resource r2) throws DatabaseException; + protected abstract UpdateTree getUpdateTree(GraphChanges changes) throws DatabaseException; + protected UpdateList getUpdateList(GraphChanges changes) throws DatabaseException { + return new UpdateList(changes.getModifications()); + } + + protected void addFilters(List filters) { + + } + + public boolean isInit() { + return init; + } + + public GraphChanges getChanges() { + return changes; + } + public UpdateTree getUpdateTree() { + return updateTree; + } + public UpdateList getUpdateList() { + return updateList; + } + public GraphChanges getChanges2() { + return changes2; + } + public GraphChanges getChanges3() { + return changes3; + } + public UpdateTree getUpdateTree2() { + return updateTree2; + } + public UpdateList getUpdateList2() { + return updateList2; + } + + + public void applyAll(WriteGraph graph) throws DatabaseException { + Layer0Utils.addCommentMetadata(graph, "Apply all model updates"); + graph.markUndoPoint(); + for (Pair mod : updateList.getChanges()) { + applyLiteralChange(graph, mod); + } + updateList.clear(); + + updateTree.getUpdateOps().applyAll(graph); + } + + public void applySelected(WriteGraph graph) throws DatabaseException { + Layer0Utils.addCommentMetadata(graph, "Apply selected model updates"); + graph.markUndoPoint(); + HashSet> changes = new HashSet<>(updateList.getSelected()); + for (Pair mod : changes) { + updateList.removeChange(mod); + applyLiteralChange(graph, mod); + } + + updateTree.getUpdateOps().applySelected(graph); + } + + protected void applyLiteralChange(WriteGraph graph, Pair mod) throws DatabaseException { + if (mod.second == null) { + graph.deny(mod.first); + return; + } + Resource s = changes.getComparable().getLeft(mod.second.getSubject()); + Resource pred = mod.second.getPredicate(); + if (graph.hasValue(mod.second.getObject())) { + Object value = graph.getValue(mod.second.getObject()); + graph.claimLiteral(s, pred, value); + } else { + graph.deny(s,pred); + } + } + + + protected Session getSession() { + return Simantics.getSession(); + } + + + + private class FilterChangesRead implements Read { + private GraphChanges changes; + public FilterChangesRead(GraphChanges changes) { + this.changes = changes; + } + + @Override + public GraphChanges perform(ReadGraph graph) throws DatabaseException { + return filterChanges(graph, changes); + } + } + + /** + * Filters changes: + * 1. Changes that are not essential for model update (changes that can be found when the models are axcatly the same) + * 2. Runs custom filters for value changes. + * + * @param g + * @param changes + * @return + * @throws DatabaseException + */ + protected GraphChanges filterChanges(ReadGraph g, GraphChanges changes) throws DatabaseException + { + + List> modifications = new ArrayList>(); + + for (Pair mod : changes.getModifications()) { + + boolean accept = true; + for (ChangeFilter filter : filters) { + if (!filter.accept(g, mod)) { + accept = false; + break; + } + } + if (accept) + modifications.add(mod); + } + GraphChanges newChanges = new GraphChanges(changes.getResource1(),changes.getResource2(),changes.getDeletions(), changes.getAdditions(), modifications, changes.getComparable()); + return newChanges; + } + + public interface ChangeFilter { + public boolean accept(ReadGraph g, Pair change) throws DatabaseException; + } + + + /** + * + * Filters floating point value changes (default filter is set filter when the change is less than 1%) + * + */ + protected class FPValueFilter implements ChangeFilter { + + private double percentage = 0.01; + + public FPValueFilter() { + + } + + public FPValueFilter(double percentage) { + if (percentage < 0.0 || percentage > 1.0) + throw new IllegalArgumentException("Percentage must be between 0.0 and 1.0."); + this.percentage = percentage; + } + + @Override + public boolean accept(ReadGraph g, Pair change) throws DatabaseException { + //filter floating point values that have less than 1% difference. + if (!g.hasValue(change.first.getObject()) || !g.hasValue(change.second.getObject())) + return true; + Object v1 = g.getValue(change.first.getObject()); + Object v2 = g.getValue(change.second.getObject()); + + if (v1 instanceof Double && v2 instanceof Double) { + double d1 = (Double)v1; + double d2 = (Double)v2; + if (Math.abs(d1-d2) / Math.max(Math.abs(d1), Math.abs(d2)) < percentage) + return false; + } else if (v1 instanceof Float && v2 instanceof Float) { + float d1 = (Float)v1; + float d2 = (Float)v2; + if (Math.abs(d1-d2) / Math.max(Math.abs(d1), Math.abs(d2)) < percentage) + return false; + } + + return true; + } + } + + protected void createDefaultSelections() { + // select all changes + for (Entry op : updateTree.getUpdateOps().getResourceMap().entrySet()) { + op.getValue().select(true); + } + + + for (Pair pair : updateList.getChanges()) { + updateList.addSelected(pair); + } + + // preserve user-made changes (by removing selections) + for (Entry op : updateTree.getUpdateOps().getResourceMap().entrySet()) { + UpdateOp op2 = updateTree2.getUpdateOps().getUpdateOp(op.getKey()); + if (op2 == null) { + if (changes3.getComparable().containsRight(op.getKey())){ + op2 = updateTree2.getUpdateOps().getUpdateOp(changes3.getComparable().getLeft(op.getKey())); + } + } + if (op2 != null && op.getValue().getClass() == op2.getClass()) { + op.getValue().select(false); + } + } + + for (Pair pair : updateList.getChanges()) { + if (pair.first != null) { + boolean found = false; + for (Pair pair2 : updateList2.getChanges()) { + if (pair.first.equals(pair2.first)) { + found = true; + break; + } + } + if (found) { + updateList.removeSelected(pair); + } + } + } + + } + + private void showWarning(String string) { + for (WarningListener l : warningListeners) + l.showWarning(this, string); + } + + private List warningListeners = new ArrayList<>(); + + public static interface WarningListener { + void showWarning(ModelUpdate update, String warning); + } + + public void addListener(WarningListener listener) { + warningListeners.add(listener); + } + + public void removeListener(WarningListener listener) { + warningListeners.remove(listener); + } +} -- 2.47.1