| /***************************************************************************** |
| * Copyright (c) 2013, 2017 CEA LIST. |
| * |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * CEA LIST - Initial API and implementation |
| *****************************************************************************/ |
| package org.eclipse.papyrus.cdo.internal.core.controlmode; |
| |
| import static com.google.common.collect.Iterables.concat; |
| import static com.google.common.collect.Iterables.filter; |
| import static com.google.common.collect.Iterables.transform; |
| import static org.eclipse.emf.ecore.util.EcoreUtil.getRootContainer; |
| import static org.eclipse.papyrus.cdo.internal.core.controlmode.CDOProxyManager.createPapyrusCDOURI; |
| |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Set; |
| |
| import org.eclipse.core.commands.ExecutionException; |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.emf.cdo.CDOObject; |
| import org.eclipse.emf.cdo.CDOState; |
| import org.eclipse.emf.cdo.common.id.CDOID; |
| import org.eclipse.emf.cdo.common.id.CDOIDUtil; |
| import org.eclipse.emf.cdo.eresource.EresourcePackage; |
| import org.eclipse.emf.cdo.util.CDOUtil; |
| import org.eclipse.emf.cdo.view.CDOView; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.InternalEObject; |
| import org.eclipse.emf.ecore.InternalEObject.EStore; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.ecore.resource.ResourceSet; |
| import org.eclipse.emf.ecore.util.EcoreUtil; |
| import org.eclipse.emf.ecore.util.InternalEList; |
| import org.eclipse.emf.spi.cdo.CDOStore; |
| import org.eclipse.emf.spi.cdo.InternalCDOView; |
| import org.eclipse.gmf.runtime.common.core.command.CommandResult; |
| import org.eclipse.gmf.runtime.common.core.command.ICommand; |
| import org.eclipse.gmf.runtime.common.core.command.IdentityCommand; |
| import org.eclipse.papyrus.cdo.internal.core.Activator; |
| import org.eclipse.papyrus.cdo.internal.core.CDOUtils; |
| import org.eclipse.papyrus.cdo.internal.core.l10n.Messages; |
| import org.eclipse.papyrus.infra.services.controlmode.ControlModeRequest; |
| import org.eclipse.papyrus.infra.services.controlmode.commands.AbstractControlCommand; |
| import org.eclipse.papyrus.infra.services.controlmode.participants.IControlCommandParticipant; |
| import org.eclipse.papyrus.infra.services.controlmode.participants.IControlModeParticipant; |
| import org.eclipse.papyrus.infra.services.controlmode.participants.IUncontrolCommandParticipant; |
| |
| import com.google.common.base.Function; |
| import com.google.common.base.Predicate; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| |
| |
| /** |
| * A {@link IControlModeParticipant} for CDO resources that handles replacing references to controlled |
| * elements with proxies that, even in a CDO view, must be resolved by the usual EMF mechanism. |
| */ |
| public class CDOControlModeParticipant implements IControlCommandParticipant, IUncontrolCommandParticipant { |
| |
| private static final Set<CDOState> TEMPORARY_ID_STATES = EnumSet.of(CDOState.TRANSIENT, CDOState.NEW); |
| |
| private List<EObject> objectsToClearResource; |
| |
| public CDOControlModeParticipant() { |
| super(); |
| } |
| |
| @Override |
| public String getID() { |
| return CDOControlModeParticipant.class.getName(); |
| } |
| |
| @Override |
| public int getPriority() { |
| return 255; |
| } |
| |
| @Override |
| public boolean provideControlCommand(ControlModeRequest request) { |
| return isCDOResource(request); |
| } |
| |
| private boolean isCDOResource(ControlModeRequest request) { |
| return CDOUtils.isCDOURI(request.getSourceURI()); |
| } |
| |
| @Override |
| public boolean provideUnControlCommand(ControlModeRequest request) { |
| return isCDOResource(request); |
| } |
| |
| @Override |
| public ICommand getPreControlCommand(ControlModeRequest request) { |
| return IdentityCommand.INSTANCE; |
| } |
| |
| @Override |
| public ICommand getPostControlCommand(ControlModeRequest request) { |
| return new AbstractCDOControlCommand(request) { |
| |
| @Override |
| protected void buildUpdates(ControlModeRequest request, IUpdate.Collector updates) { |
| collectProxyCrossReferenceUpdates(updates, request.getModelSet(), request.getNewURI()); |
| } |
| }; |
| } |
| |
| /** |
| * Obtains an {@linkplain IUpdate update} operation that converts external cross-references to the elements |
| * in a logical model unit indicated by the given representative {@code resource} to proxies. |
| * |
| * @param resource |
| * a resource that is a component of a logical model unit |
| * |
| * @return an operation that, when {@linkplain IUpdate#apply() applied}, will convert incoming cross-references to proxies |
| */ |
| public IUpdate getProxyCrossReferencesUpdate(Resource resource) { |
| IUpdate.Compound result = new CompoundUpdate(); |
| |
| collectProxyCrossReferenceUpdates(result, resource.getResourceSet(), resource.getURI()); |
| |
| return result; |
| } |
| |
| private void collectProxyCrossReferenceUpdates(IUpdate.Collector updates, ResourceSet resourceSet, URI unitURI) { |
| for (final EObject object : getAllPersistentSubunitContents(resourceSet, unitURI)) { |
| // replace references to the element by a proxy |
| CDOID proxy = null; |
| for (EStructuralFeature.Setting next : getExternalCrossReferences(object)) { |
| if (proxy == null) { |
| proxy = CDOIDUtil.createExternal(CDOProxyManager.createPapyrusCDOURI(object)); |
| } |
| updates.add(new ControlUpdate(next, object, proxy)); |
| } |
| } |
| } |
| |
| public IUpdate getProxyCrossReferencesUpdate(final EObject owner, final EReference crossReference) { |
| IUpdate result = IUpdate.EMPTY; |
| final CDOStore[] store = { null }; |
| |
| for (ListIterator<? extends EObject> xrefs = CDOUtils.iterator(owner, crossReference, false); xrefs.hasNext();) { |
| final int index = xrefs.nextIndex(); |
| final EObject referent = xrefs.next(); |
| |
| if (!referent.eIsProxy() && !inSameUnit(owner, referent) && inSameModel(owner, referent)) { |
| if (store[0] == null) { |
| store[0] = ((InternalCDOView) CDOUtils.getCDOObject(owner).cdoView()).getStore(); |
| } |
| |
| result = result.chain(new OneWayUpdate() { |
| |
| @Override |
| public void apply() { |
| store[0].set((InternalEObject) owner, crossReference, index, CDOIDUtil.createExternal(createPapyrusCDOURI(referent))); |
| } |
| }); |
| } |
| } |
| |
| return result; |
| } |
| |
| @Override |
| public ICommand getPreUncontrolCommand(ControlModeRequest request) { |
| return new AbstractCDOControlCommand(request) { |
| |
| @Override |
| protected CommandResult doExecuteWithResult(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { |
| objectsToClearResource = Lists.newArrayList(); |
| |
| return super.doExecuteWithResult(monitor, info); |
| } |
| |
| @Override |
| protected void buildUpdates(ControlModeRequest request, IUpdate.Collector updates) { |
| final URI sourceURI = getRequest().getSourceURI(); |
| |
| IUpdate resolveProxies = IUpdate.EMPTY; |
| |
| // update references from other units to the unit being re-integrated |
| for (final EObject object : getAllPersistentSubunitContents(getRequest().getModelSet(), sourceURI)) { |
| // replace proxy references to the element by the element or an updated proxy |
| // which will be located in the destination resource |
| CDOID sourceProxy = null; |
| URI destinationResourceURI = null; |
| CDOID proxy = null; |
| |
| // internal cross-references within the unit won't be affected, as they are all moving |
| for (final EStructuralFeature.Setting next : getExternalCrossReferences(object)) { |
| if (next.getEObject().eIsProxy()) { |
| // the cross-reference will be from a proxy only when it is a proxy owned by the cross-referenced object, |
| // by virtue of an opposite reference. In this case, it's only interesting if the proxy targets the |
| // parent (re-integrated-into) resource. So, resolve this proxy because we need to update the |
| // reference, below |
| resolveProxies = resolveProxies.chain(new OneWayUpdate() { |
| |
| @Override |
| public void apply() { |
| resolve(object, (EReference) next.getEStructuralFeature(), next.getEObject()); |
| } |
| }); |
| |
| } else { |
| if (sourceProxy == null) { |
| // create the source proxy |
| sourceProxy = CDOIDUtil.createExternal(CDOProxyManager.createPapyrusCDOURI(object)); |
| |
| // and the destination proxy |
| destinationResourceURI = request.getTargetResource(object.eResource().getURI().fileExtension()).getURI(); |
| String proxyURI = CDOProxyManager.createPapyrusCDOURI(destinationResourceURI, object); |
| proxy = CDOIDUtil.createExternal(proxyURI); |
| } |
| updates.add(new UncontrolUpdate(next, object, sourceProxy, destinationResourceURI, proxy)); |
| } |
| } |
| |
| // upon removal from their resources, root elements will nonetheless retain a |
| // reference to the resource that formerly contained them. We need to force |
| // clearing of this reference in the CDO store |
| if (((InternalEObject) object).eDirectResource() != null) { |
| objectsToClearResource.add(object); |
| } |
| } |
| |
| // resolve proxies as per above, after completing the loop, to avoid concurrent modifications |
| resolveProxies.apply(); |
| |
| // update references from the unit being re-integrated to the unit it is re-integrating into |
| for (final EObject object : getAllPersistentSubunitContents(getRequest().getModelSet(), getRequest().getNewURI())) { |
| // replace proxy references to the element by the element or an updated proxy |
| // which will be located in the destination resource |
| CDOID targetProxy = null; |
| |
| for (EStructuralFeature.Setting next : getExternalCrossReferences(object)) { |
| if (inUnit(next.getEObject(), sourceURI)) { |
| // replace proxy references from the sub-unit into the parent with a direct reference, as this will |
| // no longer be a cross-unit reference |
| if (targetProxy == null) { |
| targetProxy = CDOIDUtil.createExternal(CDOProxyManager.createPapyrusCDOURI(object)); |
| } |
| updates.add(new UncontrolUpdate(next, object, targetProxy)); |
| } |
| } |
| } |
| } |
| }; |
| } |
| |
| void resolve(EObject object, EReference reference, EObject proxy) { |
| EReference opposite = reference.getEOpposite(); |
| if (opposite != null) { |
| if (opposite.isMany()) { |
| InternalEList<?> list = (InternalEList<?>) object.eGet(opposite, false); |
| int index = list.basicIndexOf(proxy); |
| if (index >= 0) { |
| list.get(index); // resolve just this index |
| } |
| } else { |
| object.eGet(opposite, true); // resolve the scalar reference |
| } |
| } |
| } |
| |
| @Override |
| public ICommand getPostUncontrolCommand(ControlModeRequest request) { |
| return new AbstractCDOControlCommand(request) { |
| |
| @Override |
| protected CommandResult doExecuteWithResult(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { |
| CommandResult result = super.doExecuteWithResult(monitor, info); |
| |
| objectsToClearResource = null; |
| |
| return result; |
| } |
| |
| @Override |
| protected void buildUpdates(ControlModeRequest request, IUpdate.Collector updates) { |
| for (EObject next : objectsToClearResource) { |
| // some former resource roots may not be reattached (e.g., SashWindowsMgr) |
| if (CDOUtil.getCDOObject(next).cdoState() != CDOState.TRANSIENT) { |
| updates.add(new ClearResourceUpdate(next)); |
| } |
| } |
| } |
| }; |
| } |
| |
| /** |
| * Get all cross-references to an {@code object} from outside of its own controlled unit <em>within</em> the same model (cross-model references do |
| * not count). |
| * |
| * @param object |
| * an object being controlled |
| * @return references to it from outside of its (new) controlled unit |
| */ |
| Iterable<EStructuralFeature.Setting> getExternalCrossReferences(final EObject object) { |
| return Iterables.filter(CDOUtils.crossReference(object), new Predicate<EStructuralFeature.Setting>() { |
| |
| @Override |
| public boolean apply(EStructuralFeature.Setting input) { |
| boolean result = false; |
| |
| EStructuralFeature ref = input.getEStructuralFeature(); |
| if ((ref != EresourcePackage.Literals.CDO_RESOURCE__CONTENTS) && ref.isChangeable() && !ref.isDerived()) { |
| result = !inSameUnit(input.getEObject(), object) && inSameModel(input.getEObject(), object); |
| } |
| |
| return result; |
| } |
| }); |
| |
| } |
| |
| static boolean isPersistentObject(EObject object) { |
| boolean result = !object.eIsProxy(); |
| |
| if (result) { |
| CDOObject cdo = CDOUtils.getCDOObject(object); |
| result = (cdo != null) && !TEMPORARY_ID_STATES.contains(cdo.cdoState()); |
| } |
| |
| return result; |
| } |
| |
| private static boolean inSameModel(EObject object, EObject other) { |
| // is the one object in the other's logical Papyrus model? |
| URI model1 = getResourceURI(getRootContainer(object)); |
| URI model2 = getResourceURI(getRootContainer(other)); |
| |
| return (model1 != null) && (model2 != null) && model1.trimFileExtension().equals(model2.trimFileExtension()); |
| } |
| |
| private static URI getResourceURI(EObject object) { |
| URI result; |
| |
| if (object.eIsProxy()) { |
| result = ((InternalEObject) object).eProxyURI().trimFragment(); |
| } else { |
| Resource res = object.eResource(); |
| result = (res == null) ? null : res.getURI(); |
| } |
| |
| return result; |
| } |
| |
| private static boolean inSameUnit(EObject object, EObject other) { |
| // is the one object in the other's unit? |
| URI uri = getResourceURI(other); |
| return inUnit(object, uri); |
| } |
| |
| private static boolean inUnit(EObject object, URI unit) { |
| boolean result = false; |
| |
| if (unit != null) { |
| // get the extension-less model URIs |
| URI uri = getResourceURI(object); |
| |
| if (uri != null) { |
| uri = uri.trimFileExtension(); |
| URI otherURI = unit.trimFileExtension(); |
| |
| result = uri.equals(otherURI); |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Iterates all of the proper contents of the resources comprising the logical model unit |
| * indicated by the representative {@code unitURI}. |
| * |
| * @param rset |
| * a resource set in which to load and/or iterate the resource contents |
| * @param unitURI |
| * the URI of one of the resources in the logical model unit, which therefore is representative of the unit |
| * |
| * @return an iterator over the entire logical model unit's proper contents |
| */ |
| static Iterable<EObject> getAllPersistentSubunitContents(ResourceSet rset, URI unitURI) { |
| final URI base = unitURI.trimFileExtension(); |
| Iterable<Resource> resources = filter(rset.getResources(), new Predicate<Resource>() { |
| |
| @Override |
| public boolean apply(Resource input) { |
| return input.getURI().trimFileExtension().equals(base); |
| } |
| }); |
| |
| Iterable<EObject> result = concat(transform(resources, new Function<Resource, Iterable<EObject>>() { |
| |
| @Override |
| public Iterable<EObject> apply(final Resource input) { |
| return new Iterable<EObject>() { |
| |
| @Override |
| public Iterator<EObject> iterator() { |
| return EcoreUtil.getAllProperContents(input, false); |
| } |
| }; |
| } |
| })); |
| |
| result = filter(result, new Predicate<EObject>() { |
| |
| @Override |
| public boolean apply(EObject input) { |
| return isPersistentObject(input); |
| } |
| }); |
| |
| return result; |
| } |
| |
| // |
| // Nested types |
| // |
| |
| public static interface IUpdate { |
| |
| /** |
| * An update that does nothing. It is {@linkplain IUpdate#isEmpty() empty}. |
| */ |
| IUpdate EMPTY = new IUpdate() { |
| |
| @Override |
| public boolean isEmpty() { |
| return true; |
| } |
| |
| @Override |
| public void apply() { |
| // pass |
| } |
| |
| @Override |
| public void revert() { |
| // revert |
| } |
| |
| @Override |
| public IUpdate chain(IUpdate update) { |
| return (update == null) ? this : update; |
| } |
| |
| }; |
| |
| /** |
| * Queries whether I have nothing to do on {@link #apply() apply}. |
| * |
| * @return whether I am empty |
| */ |
| boolean isEmpty(); |
| |
| void apply(); |
| |
| void revert(); |
| |
| IUpdate chain(IUpdate update); |
| |
| interface Collector { |
| |
| void add(IUpdate update); |
| } |
| |
| interface Compound extends IUpdate, Collector { |
| // all methods inherited |
| } |
| |
| } |
| |
| static final class CompoundUpdate implements IUpdate.Compound { |
| |
| private final List<IUpdate> updates = Lists.newArrayList(); |
| |
| @Override |
| public void add(IUpdate update) { |
| updates.add(update); |
| } |
| |
| @Override |
| public Compound chain(IUpdate update) { |
| if ((update != null) && !update.isEmpty()) { |
| add(update); |
| } |
| |
| return this; |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return updates.isEmpty(); |
| } |
| |
| @Override |
| public void apply() { |
| for (IUpdate next : updates) { |
| next.apply(); |
| } |
| } |
| |
| @Override |
| public void revert() { |
| for (ListIterator<IUpdate> iter = updates.listIterator(updates.size()); iter.hasPrevious();) { |
| iter.previous().revert(); |
| } |
| } |
| |
| static IUpdate compose(IUpdate first, IUpdate second) { |
| IUpdate result; |
| |
| if ((second == null) || second.isEmpty()) { |
| result = (first == null) ? IUpdate.EMPTY : first; |
| } else if ((first == null) || first.isEmpty()) { |
| // we already know it's not null or empty |
| result = second; |
| } else { |
| IUpdate.Compound compound = new CompoundUpdate(); |
| compound.add(first); |
| compound.add(second); |
| result = compound; |
| } |
| |
| return result; |
| } |
| } |
| |
| private static abstract class OneWayUpdate implements IUpdate { |
| |
| @Override |
| public void revert() { |
| throw new UnsupportedOperationException("OneWayUpdate cannot be reverted"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public IUpdate chain(IUpdate update) { |
| return CompoundUpdate.compose(this, update); |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return false; |
| } |
| } |
| |
| private static abstract class Update implements IUpdate { |
| |
| final EStructuralFeature.Setting setting; |
| |
| final CDOStore store; |
| |
| Update(EStructuralFeature.Setting setting) { |
| this.setting = setting; |
| |
| InternalEObject owner = (InternalEObject) setting.getEObject(); |
| CDOObject cdoOwner = CDOUtil.getCDOObject(owner); |
| |
| InternalCDOView view = (InternalCDOView) cdoOwner.cdoView(); |
| store = view.getStore(); |
| } |
| |
| Update(EObject object) { |
| this.setting = null; |
| |
| CDOObject cdo = CDOUtil.getCDOObject(object); |
| CDOView view = cdo.cdoView(); |
| this.store = (view instanceof InternalCDOView) ? ((InternalCDOView) view).getStore() : null; |
| } |
| |
| @Override |
| public IUpdate chain(IUpdate update) { |
| return CompoundUpdate.compose(this, update); |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| // a leaf update is, by definition, not empty |
| return false; |
| } |
| } |
| |
| private static final class ControlUpdate extends Update { |
| |
| final EObject originalObject; |
| |
| final CDOID proxy; |
| |
| final int index; |
| |
| ControlUpdate(EStructuralFeature.Setting setting, EObject originalObject, CDOID proxy) { |
| super(setting); |
| this.originalObject = originalObject; |
| this.proxy = proxy; |
| |
| EStructuralFeature feature = setting.getEStructuralFeature(); |
| InternalEObject owner = (InternalEObject) setting.getEObject(); |
| |
| if (!feature.isMany()) { |
| index = EStore.NO_INDEX; |
| } else { |
| // don't go directly to the store because it may have proxies. |
| // Use the resolved view in the EObject, instead |
| index = ((EList<?>) owner.eGet(feature)).indexOf(originalObject); |
| if (index < 0) { |
| Activator.log.error("Setting does not include the object being replaced by a proxy.", null); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| @Override |
| public void apply() { |
| EStructuralFeature feature = setting.getEStructuralFeature(); |
| |
| if ((index >= 0) || !feature.isMany()) { |
| InternalEObject owner = (InternalEObject) setting.getEObject(); |
| store.set(owner, feature, index, proxy); |
| } |
| } |
| |
| @Override |
| public void revert() { |
| EStructuralFeature feature = setting.getEStructuralFeature(); |
| |
| if (index >= 0 || !feature.isMany()) { |
| InternalEObject owner = (InternalEObject) setting.getEObject(); |
| store.set(owner, feature, index, CDOUtils.getCDOID(originalObject)); |
| } |
| } |
| } |
| |
| private static final class UncontrolUpdate extends Update { |
| |
| final EObject originalObject; |
| |
| final CDOID originalProxy; |
| |
| final URI destinationURI; |
| |
| final CDOID destinationProxy; |
| |
| final int index; |
| |
| UncontrolUpdate(EStructuralFeature.Setting setting, EObject originalObject, CDOID originalProxy, URI destinationURI, CDOID destinationProxy) { |
| super(setting); |
| this.originalObject = originalObject; |
| this.originalProxy = originalProxy; |
| this.destinationURI = destinationURI; |
| this.destinationProxy = destinationProxy; |
| |
| EStructuralFeature feature = setting.getEStructuralFeature(); |
| InternalEObject owner = (InternalEObject) setting.getEObject(); |
| |
| if (!feature.isMany()) { |
| index = EStore.NO_INDEX; |
| } else { |
| // don't go directly to the store because it may have proxies. |
| // Use the resolved view in the EObject, instead |
| index = ((EList<?>) owner.eGet(feature)).indexOf(originalObject); |
| if (index < 0) { |
| Activator.log.error("Setting does not include the object being replaced by a proxy.", null); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| UncontrolUpdate(EStructuralFeature.Setting setting, EObject originalObject, CDOID originalProxy) { |
| this(setting, originalObject, originalProxy, null, null); |
| } |
| |
| @Override |
| public void apply() { |
| EStructuralFeature feature = setting.getEStructuralFeature(); |
| |
| if ((index >= 0) || !feature.isMany()) { |
| InternalEObject owner = (InternalEObject) setting.getEObject(); |
| |
| if ((destinationURI == null) || inUnit(owner, destinationURI)) { |
| // direct reference |
| store.set(owner, feature, index, CDOUtils.getCDOID(originalObject)); |
| } else { |
| // proxy reference for cross-unit |
| store.set(owner, feature, index, destinationProxy); |
| } |
| } |
| } |
| |
| @Override |
| public void revert() { |
| EStructuralFeature feature = setting.getEStructuralFeature(); |
| |
| if (index >= 0 || !feature.isMany()) { |
| InternalEObject owner = (InternalEObject) setting.getEObject(); |
| |
| // on reversion, we are processing only references that were external |
| // to the unit that was to be re-integrated, so necessarily all |
| // references must be set to the original proxies |
| store.set(owner, feature, index, originalProxy); |
| } |
| } |
| } |
| |
| private static final class ClearResourceUpdate extends Update { |
| |
| private CDOObject object; |
| |
| ClearResourceUpdate(EObject object) { |
| super(object); |
| |
| this.object = CDOUtil.getCDOObject(object); |
| } |
| |
| @Override |
| public void apply() { |
| InternalEObject object = (InternalEObject) CDOUtil.getEObject(this.object); |
| store.setContainer(object, null, object.eInternalContainer(), object.eContainerFeatureID()); |
| } |
| |
| @Override |
| public void revert() { |
| // there is no need to revert clearing the resource reference; it will be |
| // reestablished naturally by undo of the base command |
| } |
| } |
| |
| private static abstract class AbstractCDOControlCommand extends AbstractControlCommand implements IUpdate.Compound { |
| |
| AbstractCDOControlCommand(ControlModeRequest request) { |
| super(Messages.CDOControlModeParticipant_commandLabel, Collections.emptyList(), request); |
| } |
| |
| private List<IUpdate> updates; |
| |
| @Override |
| protected CommandResult doExecuteWithResult(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { |
| final ImmutableListCollector collector = new ImmutableListCollector(); |
| |
| buildUpdates(getRequest(), collector); |
| |
| this.updates = collector.close(); |
| |
| apply(); |
| |
| return CommandResult.newOKCommandResult(); |
| } |
| |
| protected abstract void buildUpdates(ControlModeRequest request, IUpdate.Collector updates); |
| |
| @Override |
| protected IStatus doUndo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { |
| IStatus result = super.doUndo(monitor, info); |
| |
| if (result.isOK()) { |
| // setting proxies in the way we did is not recorded by EMF, so we have to undo it ourselves |
| revert(); |
| } |
| |
| return result; |
| } |
| |
| @Override |
| protected IStatus doRedo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { |
| IStatus result = super.doRedo(monitor, info); |
| |
| if (result.isOK()) { |
| // setting proxies in the way we did is not recorded by EMF, so we have to redo it ourselves |
| apply(); |
| } |
| |
| return result; |
| } |
| |
| @Override |
| public boolean isEmpty() { |
| return (updates == null) || updates.isEmpty(); |
| } |
| |
| @Override |
| public void apply() { |
| for (IUpdate next : updates) { |
| next.apply(); |
| } |
| } |
| |
| @Override |
| public void revert() { |
| for (ListIterator<IUpdate> iter = updates.listIterator(updates.size()); iter.hasPrevious();) { |
| iter.previous().revert(); |
| } |
| } |
| |
| @Override |
| public void add(IUpdate update) { |
| throw new UnsupportedOperationException("AbtsractCDOControlCommand is not externally modifiable"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public Compound chain(IUpdate update) { |
| throw new UnsupportedOperationException("AbtsractCDOControlCommand is not externally modifiable"); //$NON-NLS-1$ |
| } |
| }; |
| |
| private static final class ImmutableListCollector implements IUpdate.Collector { |
| |
| private final ImmutableList.Builder<IUpdate> builder = ImmutableList.builder(); |
| |
| @Override |
| public void add(IUpdate update) { |
| builder.add(update); |
| } |
| |
| List<IUpdate> close() { |
| return builder.build(); |
| } |
| } |
| } |