Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/infra/misc/org.eclipse.papyrus.infra.sync/src/org/eclipse/papyrus/infra/sync/EStructuralFeatureSyncFeature.java')
-rw-r--r--plugins/infra/misc/org.eclipse.papyrus.infra.sync/src/org/eclipse/papyrus/infra/sync/EStructuralFeatureSyncFeature.java535
1 files changed, 535 insertions, 0 deletions
diff --git a/plugins/infra/misc/org.eclipse.papyrus.infra.sync/src/org/eclipse/papyrus/infra/sync/EStructuralFeatureSyncFeature.java b/plugins/infra/misc/org.eclipse.papyrus.infra.sync/src/org/eclipse/papyrus/infra/sync/EStructuralFeatureSyncFeature.java
new file mode 100644
index 00000000000..f47ca4a5ecc
--- /dev/null
+++ b/plugins/infra/misc/org.eclipse.papyrus.infra.sync/src/org/eclipse/papyrus/infra/sync/EStructuralFeatureSyncFeature.java
@@ -0,0 +1,535 @@
+/*****************************************************************************
+ * Copyright (c) 2015 Christian W. Damus and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Christian W. Damus - Initial API and implementation
+ * Christian W. Damus - bug 465416
+ *
+ *****************************************************************************/
+
+package org.eclipse.papyrus.infra.sync;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.emf.common.command.AbstractCommand;
+import org.eclipse.emf.common.command.Command;
+import org.eclipse.emf.common.command.CommandWrapper;
+import org.eclipse.emf.common.command.CompoundCommand;
+import org.eclipse.emf.common.command.IdentityCommand;
+import org.eclipse.emf.common.notify.Notification;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EStructuralFeature;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.ObjectArrays;
+
+/**
+ * A synchronization feature for {@link EStructuralFeature}s of {@link EObject}s.
+ *
+ * @author Laurent Wouters
+ *
+ * @param <M>
+ * The type of the underlying model element common to all synchronized items in a single bucket
+ * @param <T>
+ * The type of the backend element to synchronize
+ */
+public abstract class EStructuralFeatureSyncFeature<M extends EObject, T> extends SyncFeature<M, T, Notification> {
+
+ final EStructuralFeature[] features;
+
+ /**
+ * The active listeners
+ */
+ private EMFDispatchManager<Dispatcher> dispatchMgr = createMultipleDispatchManager();
+
+ /**
+ * Initialized this feature
+ *
+ * @param bucket
+ * The bucket doing the synchronization
+ */
+ public EStructuralFeatureSyncFeature(SyncBucket<M, T, Notification> bucket, EStructuralFeature feature, EStructuralFeature... more) {
+ super(bucket);
+
+ this.features = ObjectArrays.concat(feature, more);
+ }
+
+ /**
+ * Callback for sub-classes of this feature when a new target has been added to a synchronized item
+ *
+ * @param from
+ * the source time
+ * @param source
+ * the corresponding source object
+ * @param to
+ * The target item
+ * @param target
+ * The added target
+ *
+ * @return a command to append to the current synchronization operation
+ */
+ protected Command onTargetAdded(SyncItem<M, T> from, EObject source, SyncItem<M, T> to, T target) {
+ // by default, do nothing
+ return null;
+ }
+
+ /**
+ * Callback for sub-classes of this feature when a new child has been removed from a target synchronized item
+ *
+ * @param to
+ * The target item
+ * @param target
+ * The removed target
+ *
+ * @return a command to append to the current synchronization operation
+ */
+ protected Command onTargetRemoved(SyncItem<M, T> to, T target) {
+ // by default, do nothing
+ return null;
+ }
+
+ /**
+ * Finds and returns the model object in the {@code to} side of a synchronization object that corresponds to
+ * the given source object in the {@code from} side. By default, the {@code sourceModel} is returned as is
+ * for the simple case of synchronizing multiple visualizations on the same model content.
+ *
+ * @param from
+ * the source sync-item of a synchronization operation
+ * @param to
+ * the target sync-item of a synchronization operation
+ * @param sourceModel
+ * an object added to the {@link SyncItem#getModel() model} of the {@code from} item
+ * @return the corresponding object in the {@code model} of the {@code to} item
+ */
+ protected EObject getTargetModel(SyncItem<M, T> from, SyncItem<M, T> to, EObject sourceModel) {
+ return sourceModel;
+ }
+
+ @Override
+ public void observe(SyncItem<M, T> item) {
+ for (EStructuralFeature next : features) {
+ dispatchMgr.add(item, new Dispatcher(item, next));
+ }
+ }
+
+ @Override
+ public void unobserve(SyncItem<M, T> item) {
+ dispatchMgr.remove(item);
+ }
+
+ @Override
+ protected void onClear() {
+ dispatchMgr.removeAll();
+ }
+
+ @Override
+ public void synchronize(SyncItem<M, T> from, SyncItem<M, T> to, Notification message) {
+ if (message == null) {
+ synchronizeInit(from, to);
+ } else {
+ synchronizeIncrement(from, to, message);
+ }
+ }
+
+ protected abstract Iterable<? extends T> getContents(T backend);
+
+ protected boolean match(EObject sourceModel, EObject targetModel) {
+ return sourceModel == targetModel;
+ }
+
+ /**
+ * Completely synchronizes the target item with the origin item
+ *
+ * @param from
+ * The origin item
+ * @param to
+ * The target item
+ */
+ private void synchronizeInit(SyncItem<M, T> from, SyncItem<M, T> to) {
+ Iterable<? extends T> childrenFrom = getContents(from.getBackend());
+ List<? extends T> childrenTo = Lists.newArrayList(getContents(to.getBackend()));
+ List<EObject> toAdd = new ArrayList<EObject>();
+
+ Command additionalCommand = null;
+
+ // find the missing children and the supplementary children in the target (to) item
+ for (T source : childrenFrom) {
+ EObject model = getModelOf(source);
+ boolean found = false;
+ for (Iterator<? extends T> iter = childrenTo.iterator(); iter.hasNext();) {
+ T target = iter.next();
+ EObject potential = getTargetModel(from, to, getModelOf(target));
+ if (match(model, potential)) {
+ found = true;
+ iter.remove();
+
+ // Hook it up for its own synchronization
+ Command additional = onTargetAdded(from, model, to, target);
+ if (additional != null) {
+ additionalCommand = (additionalCommand == null) ? additional : additionalCommand.chain(additional);
+ }
+ break;
+ }
+ }
+ if (!found) {
+ toAdd.add(model);
+ }
+ }
+
+ Command command = null;
+
+ if (!childrenTo.isEmpty() || !toAdd.isEmpty()) {
+ // compute the initial sync command
+ CompoundCommand compound = new CompoundCommand("Initial Sync Command");
+ command = compound;
+
+ // remove the supplementary children of the target item
+ for (int i = 0; i < childrenTo.size(); i++) {
+ compound.append(getRemoveCommand(from, null, to, childrenTo.get(i)));
+ }
+ // add the missing ones
+ for (int i = 0; i < toAdd.size(); i++) {
+ EObject newSource = toAdd.get(i);
+ if (shouldAdd(from, to, newSource)) {
+ Command add = getAddCommand(from, to, newSource);
+ if (add != null) {
+ compound.append(add);
+ }
+ }
+ }
+ }
+
+ if (additionalCommand != null) {
+ command = (command == null) ? additionalCommand : command.chain(additionalCommand);
+ }
+
+ if (command != null) {
+ execute(command);
+ }
+ }
+
+ /**
+ * Synchronizes the target item with the specified change
+ *
+ * @param to
+ * The target item
+ * @param message
+ * The change message
+ */
+ private void synchronizeIncrement(SyncItem<M, T> from, SyncItem<M, T> to, Notification message) {
+ Dispatcher dispatcher = dispatchMgr.getDispatcher(from, message.getFeature());
+
+ switch (message.getEventType()) {
+ case Notification.ADD:
+ dispatcher.handleAdd(from, to, (EObject) message.getNewValue());
+ break;
+ case Notification.ADD_MANY:
+ for (Object next : (Iterable<?>) message.getNewValue()) {
+ dispatcher.handleAdd(from, to, (EObject) next);
+ }
+ break;
+ case Notification.REMOVE:
+ dispatcher.handleRemove(from, to, (EObject) message.getOldValue());
+ break;
+ case Notification.REMOVE_MANY:
+ for (Object next : (Iterable<?>) message.getOldValue()) {
+ dispatcher.handleRemove(from, to, (EObject) next);
+ }
+ break;
+ case Notification.SET:
+ if (message.getOldValue() != null) {
+ dispatcher.handleRemove(from, to, (EObject) message.getOldValue());
+ }
+ if (message.getNewValue() != null) {
+ dispatcher.handleAdd(from, to, (EObject) message.getNewValue());
+ }
+ break;
+ case Notification.UNSET:
+ // A single-valued feature may be unset
+ if (message.getOldValue() instanceof EObject) {
+ dispatcher.handleRemove(from, to, (EObject) message.getOldValue());
+ }
+ break;
+ }
+ }
+
+ /**
+ * Queries whether a new source object should be added {@code to} the synchronization target in synchronization
+ * {@code from} a synchronization source. The default implementation always adds.
+ *
+ * @param from
+ * the source of a synchronization operation
+ * @param to
+ * the target of a synchronization operation
+ * @param newSource
+ * the new element added to the source
+ *
+ * @return whether the new element should be synchronized to the target
+ */
+ protected boolean shouldAdd(SyncItem<M, T> from, SyncItem<M, T> to, EObject newSource) {
+ return true;
+ }
+
+ /**
+ * Gets the synchronization command for adding a new back-end to the specified target to correspond to a new source model element.
+ *
+ * @param from
+ * The source item of the synchronization
+ * @param to
+ * The target item to synchronize
+ * @param newSource
+ * The new source object for which a corresponding target is to be added
+ * @return The command
+ */
+ protected Command getAddCommand(final SyncItem<M, T> from, final SyncItem<M, T> to, final EObject newSource) {
+ return new CommandWrapper(doGetAddCommand(from, to, newSource)) {
+ private T addedTarget;
+
+ @Override
+ public void execute() {
+ super.execute();
+
+ @SuppressWarnings("unchecked")
+ T newTarget = (T) getCommand().getResult().iterator().next();
+ addedTarget = newTarget;
+
+ Command additional = onTargetAdded(from, newSource, to, addedTarget);
+ if (additional != null) {
+ additional.execute();
+ }
+ }
+
+ @Override
+ public void undo() {
+ super.undo();
+ Command additional = onTargetRemoved(to, addedTarget);
+ if (additional != null) {
+ additional.execute();
+ }
+ }
+
+ @Override
+ public void redo() {
+ super.redo();
+ Command additional = onTargetAdded(from, newSource, to, addedTarget);
+ if (additional != null) {
+ additional.execute();
+ }
+ }
+ };
+ }
+
+ protected Command doGetAddCommand(SyncItem<M, T> from, SyncItem<M, T> to, EObject newSource) {
+ throw new UnsupportedOperationException("doGetAddCommand"); //$NON-NLS-1$
+ }
+
+ /**
+ * Gets the synchronization command for removing a back-end from the specified target on removal of its corresponding source model.
+ *
+ * @param from
+ * The source item of the synchronization
+ * @param oldSource
+ * the source object to which the {@code oldTarget} had corresponded. May be {@code null} in the case of an
+ * initial synchronization where we have found targets that have no corresponding sources and that thus need to be removed
+ * @param to
+ * The target item to synchronize
+ * @param oldTarget
+ * The old back-end object that is to be removed
+ * @return The command
+ */
+ protected Command getRemoveCommand(final SyncItem<M, T> from, final EObject oldSource, final SyncItem<M, T> to, final T oldTarget) {
+ return new CommandWrapper(doGetRemoveCommand(from, oldSource, to, oldTarget)) {
+ @Override
+ public void execute() {
+ super.execute();
+ Command additional = onTargetRemoved(to, oldTarget);
+ if (additional != null) {
+ additional.execute();
+ }
+ }
+
+ @Override
+ public void undo() {
+ super.undo();
+
+ // Only notify of add if we had done that in the first place (this is not an initial sync that is being undone)
+ if (oldSource != null) {
+ Command additional = onTargetAdded(from, oldSource, to, oldTarget);
+ if (additional != null) {
+ additional.execute();
+ }
+ }
+ }
+
+ @Override
+ public void redo() {
+ super.redo();
+ Command additional = onTargetRemoved(to, oldTarget);
+ if (additional != null) {
+ additional.execute();
+ }
+ }
+ };
+ }
+
+ protected Command doGetRemoveCommand(SyncItem<M, T> from, EObject oldSource, SyncItem<M, T> to, T oldTarget) {
+ throw new UnsupportedOperationException("doGetRemoveCommand"); //$NON-NLS-1$
+ }
+
+ /**
+ * Gets the model element associated to the specified back-end object, or <code>null</code> if none is found
+ *
+ * @param backend
+ * the synchronization back-end
+ * @return The associated model element
+ */
+ protected EObject getModelOf(T backend) {
+ EObject notifier = getNotifier(backend);
+ return (notifier == null) ? null : getModelOfNotifier(notifier);
+ }
+
+ protected abstract EObject getNotifier(T backend);
+
+ protected abstract EObject getModelOfNotifier(EObject backendNotifier);
+
+ /**
+ * Obtains a command that wraps a given {@code command} in a command registers a back-end element for synchronization
+ * on execute and redo and deregisters it on undo.
+ *
+ * @param registry
+ * the synchronization registry in which to register the back-end for synchronization
+ * @param backend
+ * the element to synchronize
+ * @param command
+ * the command to wrap. May be {@code null}, in which case we only perform the registration changes
+ *
+ * @return the wrapper command (never {@code null})
+ */
+ protected Command synchronizingWrapper(final SyncRegistry<?, ? super T, Notification> registry, final T backend, Command command) {
+ class Wrapper extends CommandWrapper {
+ Wrapper() {
+ super();
+ }
+
+ Wrapper(Command command) {
+ super(command);
+ }
+
+ @Override
+ public void execute() {
+ super.execute();
+
+ registry.synchronize(backend);
+ }
+
+ @Override
+ public void undo() {
+ registry.desynchronize(backend);
+
+ super.undo();
+ }
+
+ @Override
+ public void redo() {
+ super.redo();
+
+ registry.synchronize(backend);
+ }
+ }
+
+ class CleanWrapper extends Wrapper implements AbstractCommand.NonDirtying {
+
+ CleanWrapper() {
+ super();
+ }
+
+ @Override
+ protected Command createCommand() {
+ // When we don't have a command to wrap
+ return IdentityCommand.INSTANCE;
+ }
+
+ }
+
+ return (command == null) ? new CleanWrapper() : new Wrapper(command);
+ }
+
+ //
+ // Nested types
+ //
+
+ /**
+ * Represents a dispatcher for this feature
+ *
+ * @author Laurent Wouters
+ */
+ private class Dispatcher extends EStructuralFeatureSyncDispatcher<M, T> {
+ public Dispatcher(SyncItem<M, T> item, EStructuralFeature feature) {
+ super(item, feature);
+ }
+
+ @Override
+ public EObject getNotifier() {
+ return EStructuralFeatureSyncFeature.this.getNotifier(getItem().getBackend());
+ }
+
+ @Override
+ public void onClear() {
+ // clears the parent bucket
+ getBucket().clear();
+ }
+
+ @Override
+ public void onChange(Notification notification) {
+ EStructuralFeatureSyncFeature.this.onChange(getItem(), notification);
+ }
+
+ EObject getModelOf(EObject notifier) {
+ return EStructuralFeatureSyncFeature.this.getModelOfNotifier(notifier);
+ }
+
+ boolean handleAdd(SyncItem<M, T> from, SyncItem<M, T> to, EObject object) {
+ boolean result = true; // In case the target has no children at all, yet
+
+ EObject model = getModelOf(object);
+ Iterable<? extends T> children = getContents(to.getBackend());
+ for (Iterator<? extends T> iter = children.iterator(); !result && iter.hasNext();) {
+ T potential = iter.next();
+ result = !match(model, EStructuralFeatureSyncFeature.this.getModelOf(potential));
+ }
+
+ if (result && shouldAdd(from, to, model)) {
+ Command add = getAddCommand(from, to, model);
+ if (add != null) {
+ react(add);
+ }
+ }
+
+ return result;
+ }
+
+ boolean handleRemove(SyncItem<M, T> from, SyncItem<M, T> to, EObject object) {
+ boolean result = false;
+
+ EObject model = getModelOf(object);
+ Iterable<? extends T> children = getContents(to.getBackend());
+ for (Iterator<? extends T> iter = children.iterator(); !result && iter.hasNext();) {
+ T potential = iter.next();
+ result = match(model, EStructuralFeatureSyncFeature.this.getModelOf(potential));
+ if (result) {
+ react(getRemoveCommand(from, model, to, potential));
+ }
+ }
+
+ return result;
+ }
+ }
+
+}

Back to the top