AgeDiagram.java
/**
* Copyright (c) 2004-2025 Carnegie Mellon University and others. (see Contributors file).
* All Rights Reserved.
*
* NO WARRANTY. ALL MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY
* KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE
* OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT
* MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
*
* This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
* SPDX-License-Identifier: EPL-2.0
*
* Created, in part, with funding and support from the United States Government. (see Acknowledgments file).
*
* This program includes and/or can make use of certain third party source code, object code, documentation and other
* files ("Third Party Software"). The Third Party Software that is used by this program is dependent upon your system
* configuration. By using this program, You agree to comply with any and all relevant Third Party Software terms and
* conditions contained in any such Third Party Software or separate license file distributed with such Third Party
* Software. The parties who own the Third Party Software ("Third Party Licensors") are intended third party benefici-
* aries to this license with respect to the terms applicable to their Third Party Software. Third Party Software li-
* censes only apply to the Third Party Software and not any other portion of this program or this program as a whole.
*/
package org.osate.ge.internal.diagram.runtime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import org.osate.ge.GraphicalConfiguration;
import org.osate.ge.RelativeBusinessObjectReference;
import org.osate.ge.aadl2.internal.diagramtypes.CustomDiagramType;
import org.osate.ge.businessobjecthandling.BusinessObjectHandler;
import org.osate.ge.graphics.Dimension;
import org.osate.ge.graphics.Point;
import org.osate.ge.graphics.Style;
import org.osate.ge.internal.GraphicalEditorException;
import org.osate.ge.internal.diagram.runtime.updating.Completeness;
import org.osate.ge.internal.model.EmbeddedBusinessObject;
import org.osate.ge.internal.services.ActionExecutor;
import org.osate.ge.internal.services.AgeAction;
import org.osate.ge.internal.services.impl.SimpleActionExecutor;
import com.google.common.collect.Lists;
/**
* This class is the in-memory data structure for the diagram.
* Represents the state of the diagram independent of the UI library being used to present the diagram.
* Listeners are used to provide notifications when the diagram state changes. For example: when an element is moved listeners are notified.
*/
public class AgeDiagram extends DiagramNode {
private final List<DiagramModificationListener> modificationListeners = new CopyOnWriteArrayList<>();
private DiagramConfiguration diagramConfiguration;
private final DiagramElementCollection elements = new DiagramElementCollection();
private ActionExecutor actionExecutor = new SimpleActionExecutor();
private boolean actionExecutorSet = false;
private DiagramModification currentModification;
private int changeNumber = 0;
/**
* Creates a new instance
*/
public AgeDiagram() {
this.diagramConfiguration = new DiagramConfiguration(new CustomDiagramType(), null, Collections.emptySet(),
true);
}
/**
* Returns the diagram configuration
* @return the diagram configuration
*/
public DiagramConfiguration getConfiguration() {
return diagramConfiguration;
}
@Override
public DiagramNode getParent() {
return null;
}
@Override
public Collection<DiagramElement> getChildren() {
return Collections.unmodifiableCollection(elements);
}
@Override
public DiagramElementCollection getModifiableChildren() {
return elements;
}
@Override
public DiagramElement getChildByRelativeReference(final RelativeBusinessObjectReference ref) {
return elements.getByRelativeReference(ref);
}
/**
* Adds a listener which is notified when the diagram is modified
* @param listener the listener to notify
* @see #removeModificationListener(DiagramModificationListener)
*/
public void addModificationListener(final DiagramModificationListener listener) {
this.modificationListeners.add(Objects.requireNonNull(listener, "listener must not be null"));
}
/**
* Removes a listener from the collection of listeners which are notified when the diagram is modified
* @param listener the listener to remove. If the instance is not in the listener collection, it is ignored.
* @see #addModificationListener(DiagramModificationListener)
*/
public void removeModificationListener(final DiagramModificationListener listener) {
this.modificationListeners.remove(Objects.requireNonNull(listener, "listener must not be null"));
}
/**
* Sets the action executor to be used when performing modifications.
* Must be set at most once. An exception will be thrown if this method is called more than once without resetting the action listener.
* If the action executor is not set, a default action executor which simply executes the action will be used.
* @param actionExecutor the action executor to use when performing modifications
* @see #resetActionExecutor()
*/
public void setActionExecutor(final ActionExecutor actionExecutor) {
if (actionExecutorSet) {
throw new GraphicalEditorException("The action executor for the diagram must not be set multiple times");
}
this.actionExecutor = Objects.requireNonNull(actionExecutor, "actionExecutor must not be null");
actionExecutorSet = true;
}
/**
* Resets the action executor. Sets the action executor to a default value which simply executes the action. Once this method has been
* called, {@link #setActionExecutor(ActionExecutor)} may be called again. The set -> reset mechanism is used to detect errors which
* would occur if the action executor was replaced. Diagrams editors typically create the action executor and the same action executor
* should be used for all actions related to the diagram.
* @see #setActionExecutor(ActionExecutor)
*/
public void resetActionExecutor() {
this.actionExecutor = new SimpleActionExecutor();
actionExecutorSet = false;
}
/**
* Calls a modifier to modify the diagram. If a modification is not in progress, a new action will be executed using the current action executor.
* If a modification is in progress, the modification will simply be performed as part of the current modification.
* @param label the label for the action. If a modification is already in progress, then it will be ignored.
* @param modifier the modifier which performs the modification.
* @see #setActionExecutor(ActionExecutor)
*/
public synchronized void modify(final String label, final DiagramModifier modifier) {
final boolean modificationInProgress = currentModification != null;
if (modificationInProgress) {
modifier.modify(currentModification);
} else {
try {
final AgeDiagramModification m = new AgeDiagramModification();
currentModification = m;
actionExecutor.execute(label, ActionExecutor.ExecutionMode.NORMAL,
new AgeDiagramModificationAction(this, m, modifier));
} finally {
currentModification = null;
}
}
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
String indention = "\t";
sb.append("{");
sb.append(System.lineSeparator());
sb.append(indention);
sb.append("diagram configuration: ");
sb.append(diagramConfiguration);
sb.append(System.lineSeparator());
if (elements.size() > 0) {
elements.toString(sb, indention);
}
sb.append("}");
return sb.toString();
}
// The diagram does not have a business object. However, the diagram configuration may provide a business object to scope the diagram.
@Override
public Object getBusinessObject() {
return null;
}
/**
* Searches for a diagram element using the specified ID
* @param id the ID of the element to return
* @return the element with the specified ID. Returns null if the element cannot be returned.
* @see DiagramElement#getId()
*/
public DiagramElement findElementById(final UUID id) {
return findDescendantById(this, id);
}
/**
* Returns the change number. The change number is an Incrementing number that is not persisted which indicates the diagram has been
* modified in a way which would affect the persisted version of the diagram. Used to determine whether a diagram is "dirty".
* @return the change number
*/
public int getCurrentChangeNumber() {
return changeNumber;
}
private static DiagramElement findDescendantById(final DiagramNode container, final UUID id) {
for (final DiagramElement child : container.getChildren()) {
if (Objects.equals(id, child.getId())) {
return child;
}
final DiagramElement result = findDescendantById(child, id);
if (result != null) {
return result;
}
}
return null;
}
/**
* {@link DiagramModification} implementation which modifies diagram elements. It also tracks changes to allow undo and redo.
*/
private class AgeDiagramModification implements DiagramModification {
private DiagramElement addedElement;
private DiagramElement updatedElement;
private EnumSet<ModifiableField> updates = EnumSet.noneOf(ModifiableField.class);
private DiagramElement removedElement;
private ArrayList<DiagramChange> changes = new ArrayList<>(); // Used for undoing the modification
@Override
public AgeDiagram getDiagram() {
return AgeDiagram.this;
}
@Override
public void setDiagramConfiguration(final DiagramConfiguration config) {
Objects.requireNonNull(config, "config must not be null");
final boolean valuesAreEqual = getConfiguration().equals(config);
if (!valuesAreEqual) {
storeFieldChange(null, ModifiableField.DIAGRAM_CONFIGURATION, AgeDiagram.this.diagramConfiguration,
config);
}
// The reference is changed even if the configuration is logically equal. The case of the context reference may have changed that
// requires an update or indicator but needs to stored so that it will be serialized during the next save.
AgeDiagram.this.diagramConfiguration = config;
if (!valuesAreEqual) {
// Notify listeners. Diagram configuration doesn't get the usual update event. It gets a special one for diagram configuration.
final DiagramConfigurationChangedEvent event = new DiagramConfigurationChangedEvent();
for (final DiagramModificationListener ml : modificationListeners) {
ml.diagramConfigurationChanged(event);
}
}
}
@Override
public void updateBusinessObject(final DiagramElement e, final Object bo,
final RelativeBusinessObjectReference relativeReference) {
Objects.requireNonNull(e, "e must not be null");
Objects.requireNonNull(bo, "bo must not be null");
Objects.requireNonNull(relativeReference, "relativeReference must not be null");
setBusinessObject(e, bo);
setRelativeReference(e, relativeReference);
}
private void setRelativeReference(final DiagramElement e,
final RelativeBusinessObjectReference relativeReference) {
Objects.requireNonNull(e, "e must not be null");
Objects.requireNonNull(relativeReference, "relativeReference must not be null");
final boolean valuesAreEqual = Objects.equals(relativeReference, e.getRelativeReference());
boolean wasRemoved = false;
if (!valuesAreEqual) {
wasRemoved = e.getParent().getModifiableChildren().remove(e);
storeFieldChange(e, ModifiableField.RELATIVE_REFERENCE, e.getRelativeReference(), relativeReference);
}
// The relative reference is updated even when the objects are equal so that the case of the relative reference will be preserved.
// However, it will not be recorded as a change.
e.setRelativeReference(relativeReference);
if (!valuesAreEqual) {
if (wasRemoved) {
e.getParent().getModifiableChildren().add(e);
}
afterUpdate(e, ModifiableField.RELATIVE_REFERENCE);
}
}
@Override
public void updateBusinessObjectWithSameRelativeReference(final DiagramElement e, final Object bo) {
setBusinessObject(e, bo);
}
private void setBusinessObject(final DiagramElement e, final Object bo) {
Objects.requireNonNull(e, "e must not be null");
// Special handling for embedded business objects.
if (bo instanceof EmbeddedBusinessObject) {
// Conversion from non-embedded to an embedded object is not supported.
if (!(e.getBusinessObject() instanceof EmbeddedBusinessObject)) {
throw new GraphicalEditorException(
"Invalid case. Conversion from non-embeedded to embedded business object");
}
final String oldData = ((EmbeddedBusinessObject) e.getBusinessObject()).getData();
final String newData = ((EmbeddedBusinessObject) bo).getData();
// This does not consider the UUID of the embedded object. That should be handled by updating the relative reference
if (!Objects.equals(oldData, newData)) {
storeFieldChange(e, ModifiableField.EMBEDDED_BUSINESS_OBJECT, e.getBusinessObject(), bo);
e.setBusinessObject(bo);
afterUpdate(e, ModifiableField.EMBEDDED_BUSINESS_OBJECT);
}
} else {
e.setBusinessObject(bo);
}
}
@Override
public void setBusinessObjectHandler(final DiagramElement e, final BusinessObjectHandler boh) {
e.setBusinessObjectHandler(boh);
// Do not notify listeners
}
@Override
public void setCompleteness(final DiagramElement e, final Completeness value) {
if (!value.equals(e.getCompleteness())) {
storeFieldChange(e, ModifiableField.COMPLETENESS, e.getCompleteness(), value);
e.setCompleteness(value);
afterUpdate(e, ModifiableField.COMPLETENESS);
}
}
@Override
public void setLabelName(final DiagramElement e, final String value) {
if (value == null && e.getLabelName() == null) {
return;
}
if (value == null || !value.equals(e.getLabelName())) {
storeFieldChange(e, ModifiableField.LABEL_NAME, e.getLabelName(), value);
e.setLabelName(value);
afterUpdate(e, ModifiableField.LABEL_NAME);
}
}
@Override
public void setUserInterfaceName(final DiagramElement e, final String value) {
if (!Objects.equals(e.getUserInterfaceName(), value)) {
storeFieldChange(e, ModifiableField.USER_INTERFACE_NAME, e.getUserInterfaceName(), value);
e.setUserInterfaceName(value);
afterUpdate(e, ModifiableField.USER_INTERFACE_NAME);
}
}
@Override
public void setSize(final DiagramElement e, final Dimension value) {
if (value == null && e.getSize() == null) {
return;
}
if (value == null || !value.equals(e.getSize())) {
storeFieldChange(e, ModifiableField.SIZE, e.getSize(), value);
e.setSize(value);
afterUpdate(e, ModifiableField.SIZE);
}
}
@Override
public void setPosition(final DiagramElement e, final Point value) {
if (!Objects.equals(e.getPosition(), value)) {
storeFieldChange(e, ModifiableField.POSITION, e.getPosition(), value);
e.setPosition(value);
afterUpdate(e, ModifiableField.POSITION);
}
}
@Override
public void setGraphicalConfiguration(final DiagramElement e, final GraphicalConfiguration value) {
if (!Objects.equals(value, e.getGraphicalConfiguration())) {
storeFieldChange(e, ModifiableField.GRAPHICAL_CONFIGURATION, e.getGraphicalConfiguration(), value);
e.setGraphicalConfiguration(value);
afterUpdate(e, ModifiableField.GRAPHICAL_CONFIGURATION);
}
}
@Override
public void setDockArea(final DiagramElement e, final DockArea value) {
if (value != e.getDockArea()) {
storeFieldChange(e, ModifiableField.DOCK_AREA, e.getDockArea(), value);
e.setDockArea(value);
afterUpdate(e, ModifiableField.DOCK_AREA);
}
}
@Override
public void setBendpoints(final DiagramElement e, final List<Point> value) {
if (value == null && !e.isBendpointsSet()) {
return;
}
// Set the bendpoints even if the returned bendpoints are equal if the bendpoints is being set for the first time or unset.
if (value == null || !e.isBendpointsSet() || !value.equals(e.getBendpoints())) {
// Make copy of values because lists are not immutable.
storeFieldChange(e, ModifiableField.BENDPOINTS, new ArrayList<>(e.getBendpoints()),
value == null ? Collections.emptyList() : new ArrayList<>(value));
e.setBendpoints(value);
afterUpdate(e, ModifiableField.BENDPOINTS);
}
}
@Override
public void setConnectionPrimaryLabelPosition(final DiagramElement e, final Point value) {
if (value == null && e.getConnectionPrimaryLabelPosition() == null) {
return;
}
if (value == null || !value.equals(e.getConnectionPrimaryLabelPosition())) {
storeFieldChange(e, ModifiableField.CONNECTION_PRIMARY_LABEL_POSITION,
e.getConnectionPrimaryLabelPosition(), value);
e.setConnectionPrimaryLabelPosition(value);
afterUpdate(e, ModifiableField.CONNECTION_PRIMARY_LABEL_POSITION);
}
}
@Override
public void setStyle(final DiagramElement e, final Style value) {
if (!value.equals(e.getStyle())) {
storeFieldChange(e, ModifiableField.STYLE, e.getStyle(), value);
e.setStyle(value);
afterUpdate(e, ModifiableField.STYLE);
}
}
// Notifies listeners and manages change tracking state after a field has been updated.
private void afterUpdate(final DiagramElement e, final ModifiableField c) {
// Notify listeners of the previous modification
if ((addedElement != null && addedElement != e) || (updatedElement != null && updatedElement != e)
|| removedElement != null) {
notifyListeners();
}
// Don't track updates on an element that has a pending add event
if (addedElement != e) {
updatedElement = e;
updates.add(c);
}
}
@Override
public void removeElement(final DiagramElement e) {
Objects.requireNonNull(e, "e must not be null");
// Notify listeners of the previous modification
if (addedElement != null || updatedElement != null || removedElement != null) {
notifyListeners();
}
e.getParent().getModifiableChildren().remove(e);
removedElement = e;
changes.add(new RemoveElementChange(e));
}
@Override
public void addElement(final DiagramElement e) {
// Notify listeners of the previous modification
if (addedElement != null || updatedElement != null || removedElement != null) {
notifyListeners();
}
e.getParent().getModifiableChildren().add(e);
addedElement = e;
changes.add(new AddElementChange(e));
}
private void sendBeforeCompletedNotifications() {
// Send any pending events
notifyListeners();
// Send the before modifications complete event
if (modificationListeners.size() > 0) {
final BeforeModificationsCompletedEvent beforeCompletedEvent = new BeforeModificationsCompletedEvent(
AgeDiagram.this, this);
for (final DiagramModificationListener ml : modificationListeners) {
ml.beforeModificationsCompleted(beforeCompletedEvent);
}
}
}
private void sendCompleteNotifications() {
// Send the modifications complete event
if (modificationListeners.size() > 0) {
final ModificationsCompletedEvent completedEvent = new ModificationsCompletedEvent(AgeDiagram.this);
for (final DiagramModificationListener ml : modificationListeners) {
ml.modificationsCompleted(completedEvent);
}
}
}
private void notifyListeners() {
// Notify Listeners
if (modificationListeners.size() > 0) {
if (addedElement != null) {
final ElementAddedEvent event = new ElementAddedEvent(addedElement);
for (final DiagramModificationListener ml : modificationListeners) {
ml.elementAdded(event);
}
addedElement = null;
}
if (updatedElement != null) {
final ElementUpdatedEvent event = new ElementUpdatedEvent(updatedElement, updates);
for (final DiagramModificationListener ml : modificationListeners) {
ml.elementUpdated(event);
}
updatedElement = null;
updates.clear();
}
if (removedElement != null) {
final ElementRemovedEvent event = new ElementRemovedEvent(removedElement);
for (final DiagramModificationListener ml : modificationListeners) {
ml.elementRemoved(event);
}
removedElement = null;
}
}
}
@Override
public void undoModification(final DiagramModification modification) {
if (modification instanceof AgeDiagramModification) {
final AgeDiagramModification modificationToUndo = ((AgeDiagramModification) modification);
for (final DiagramChange change : Lists.reverse(modificationToUndo.changes)) {
change.undo(this);
}
}
}
@Override
public void redoModification(final DiagramModification modification) {
if (modification instanceof AgeDiagramModification) {
final AgeDiagramModification modificationToRedo = ((AgeDiagramModification) modification);
for (final DiagramChange change : modificationToRedo.changes) {
change.redo(this);
}
}
}
/**
* Stores the current value so that changes can be undone/redone
*/
private void storeFieldChange(final DiagramElement element, final ModifiableField field,
final Object currentValue, final Object newValue) {
changes.add(new FieldChange(element, field, currentValue, newValue));
}
}
private static interface DiagramChange {
void undo(final AgeDiagramModification m);
void redo(final AgeDiagramModification m);
default boolean affectsChangeNumber() {
return true;
}
}
private static class AddElementChange implements DiagramChange {
private DiagramElement diagramElement;
public AddElementChange(final DiagramElement diagramElement) {
this.diagramElement = Objects.requireNonNull(diagramElement, "diagramElement must not be null");
}
@Override
public void undo(AgeDiagramModification m) {
m.removeElement(diagramElement);
}
@Override
public void redo(AgeDiagramModification m) {
m.addElement(diagramElement);
}
}
private static class RemoveElementChange implements DiagramChange {
private DiagramElement diagramElement;
public RemoveElementChange(final DiagramElement diagramElement) {
this.diagramElement = Objects.requireNonNull(diagramElement, "diagramElement must not be null");
}
@Override
public void undo(AgeDiagramModification m) {
m.addElement(diagramElement);
}
@Override
public void redo(AgeDiagramModification m) {
m.removeElement(diagramElement);
}
}
/**
* Holds previous values to allow modifications to be undone.
*
*/
private static class FieldChange implements DiagramChange {
public final DiagramElement element;
public final ModifiableField field;
public final Object previousValue;
public final Object newValue;
public FieldChange(final DiagramElement element, final ModifiableField field, final Object previousValue,
final Object newValue) {
this.element = element;
this.field = field;
this.previousValue = previousValue;
this.newValue = newValue;
}
@Override
public void undo(final AgeDiagramModification m) {
setValue(m, previousValue);
}
@Override
public void redo(final AgeDiagramModification m) {
setValue(m, newValue);
}
@SuppressWarnings("unchecked")
private void setValue(final AgeDiagramModification m, final Object value) {
switch (field) {
case COMPLETENESS:
m.setCompleteness(element, (Completeness) value);
break;
case LABEL_NAME:
m.setLabelName(element, (String) value);
break;
case USER_INTERFACE_NAME:
m.setUserInterfaceName(element, (String) value);
break;
case BENDPOINTS:
m.setBendpoints(element, (List<Point>) value);
break;
case DOCK_AREA:
m.setDockArea(element, (DockArea) value);
break;
case GRAPHICAL_CONFIGURATION:
m.setGraphicalConfiguration(element, (GraphicalConfiguration) value);
break;
case POSITION:
m.setPosition(element, (Point) value);
break;
case SIZE:
m.setSize(element, (Dimension) value);
break;
case CONNECTION_PRIMARY_LABEL_POSITION:
m.setConnectionPrimaryLabelPosition(element, (Point) value);
break;
case STYLE:
m.setStyle(element, (Style) value);
break;
case DIAGRAM_CONFIGURATION:
m.setDiagramConfiguration((DiagramConfiguration) value);
break;
case RELATIVE_REFERENCE:
((AgeDiagramModification) m).setRelativeReference(element, (RelativeBusinessObjectReference) value);
break;
case EMBEDDED_BUSINESS_OBJECT:
m.updateBusinessObjectWithSameRelativeReference(element, value);
break;
default:
break;
}
}
@Override
public boolean affectsChangeNumber() {
if (field == ModifiableField.COMPLETENESS || field == ModifiableField.USER_INTERFACE_NAME
|| field == ModifiableField.LABEL_NAME || field == ModifiableField.GRAPHICAL_CONFIGURATION) {
return false;
}
if (field == ModifiableField.SIZE && !DiagramElementPredicates.isResizeable(element)) {
return false;
}
if (field == ModifiableField.POSITION && !DiagramElementPredicates.isMoveable(element)) {
return false;
}
if (field == ModifiableField.DOCK_AREA && element.getDockArea() == DockArea.GROUP) {
return false;
}
return true;
}
}
private static class AgeDiagramModificationAction implements AgeAction {
private final AgeDiagram ageDiagram;
private final AgeDiagramModification mod;
private final DiagramModifier modifier;
private final AgeAction undoAction;
private final AgeAction redoAction;
private int originalVersionNumber;
private int newVersionNumber;
public AgeDiagramModificationAction(final AgeDiagram ageDiagram, final AgeDiagramModification mod,
final DiagramModifier modifier) {
this.ageDiagram = Objects.requireNonNull(ageDiagram, "ageDiagram must not be null");
this.mod = Objects.requireNonNull(mod, "mod must not be null");
this.modifier = Objects.requireNonNull(modifier, "modifier must not be null");
this.undoAction = () -> {
ageDiagram.modify("Undo Diagram Modification", newMod -> {
newMod.undoModification(mod);
});
ageDiagram.changeNumber = originalVersionNumber;
return getRedoAction();
};
this.redoAction = () -> {
ageDiagram.modify("Redo Diagram Modification", newMod -> newMod.redoModification(mod));
ageDiagram.changeNumber = newVersionNumber;
return undoAction;
};
}
// Needed to allow implementation of undo action since the formatter convers the undo action into a lambda.
private AgeAction getRedoAction() {
return redoAction;
}
private static boolean affectsChangeNumber(Collection<DiagramChange> changes) {
return changes.stream().anyMatch(DiagramChange::affectsChangeNumber);
}
@Override
public AgeAction execute() {
modifier.modify(mod);
mod.sendBeforeCompletedNotifications();
if (mod.changes.size() > 0) {
if (affectsChangeNumber(mod.changes)) {
for (final DiagramChange c : mod.changes) {
if (c.affectsChangeNumber()) {
break;
}
}
originalVersionNumber = ageDiagram.changeNumber;
ageDiagram.changeNumber++;
newVersionNumber = ageDiagram.changeNumber;
}
mod.sendCompleteNotifications();
return undoAction;
} else {
mod.sendCompleteNotifications();
return null;
}
}
}
}