/****************************************************************************** * Copyright (c) 2006, 2008 IBM Corporation 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: * IBM Corporation - initial API and implementation ****************************************************************************/ package org.eclipse.gmf.runtime.emf.type.core.requests; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.transaction.TransactionalEditingDomain; import org.eclipse.emf.transaction.util.TransactionUtil; import org.eclipse.gmf.runtime.common.core.command.CompositeCommand; import org.eclipse.gmf.runtime.common.core.command.ICommand; import org.eclipse.gmf.runtime.emf.type.core.EditHelperContext; import org.eclipse.gmf.runtime.emf.type.core.ElementTypeRegistry; import org.eclipse.gmf.runtime.emf.type.core.IClientContext; import org.eclipse.gmf.runtime.emf.type.core.IElementType; import org.eclipse.gmf.runtime.emf.type.core.internal.l10n.EMFTypeCoreMessages; import org.eclipse.gmf.runtime.emf.type.core.internal.requests.RequestCacheEntries; /** * Request to destroy the dependents of a model element. It is expected that * the request will be served primarily by advice, which can invoke the * {@link #getDestroyDependentCommand(EObject)} to obtain a command to destroy * an object dependent on the {@linkplain #getElementToDestroy() element being destroyed} * and have it added to the edit command. However, an edit helper can also * provide an "instead" command by overriding its * <code>getDestroyDependentsCommand(DestroyDependentsRequest)</code> method * to do the same. * <p> * The destruction of dependents is an edit requested of the element being * destroyed, unlike the {@link DestroyElementRequest}, which is requested of * the <em>container</em> of the element being destroyed. * </p> * * @author Christian W. Damus (cdamus) */ public class DestroyDependentsRequest extends DestroyRequest { /** * The element to destroy. */ private EObject elementToDestroy; private EObject ctorElementToDestroy; /** * Other objects dependent on the primary object that should also be destroyed. */ private Set dependentElementsToDestroy; private Set immutableViewOfDependents; /** * Constructs a new request to destroy the dependents of a model element. * * @param editingDomain * the editing domain in which I am requesting to make model * @param elementToDestroy * the element to be destroyed * @param confirmationRequired * <code>true</code> if the user should be prompted to confirm * the element deletion, <code>false</code> otherwise. */ public DestroyDependentsRequest(TransactionalEditingDomain editingDomain, EObject elementToDestroy, boolean confirmationRequired) { super(editingDomain, confirmationRequired); this.elementToDestroy = elementToDestroy; // keep it until we populate the set, beacuse someone might construct us with some element but later call // setElementToDestroy(...) with a different element and the original behaviour would have added them both to the set ctorElementToDestroy = elementToDestroy; } /** * Gets the element to be destroyed. * * @return the element to be destroyed */ public final EObject getElementToDestroy() { return elementToDestroy; } /** * Sets the element to be destroyed. * * @param elementToDestroy * the element to be destroyed */ public final void setElementToDestroy(EObject elementToDestroy) { this.elementToDestroy = elementToDestroy; if (elementToDestroy != null) { internalGetDependentElementsToDestroy().add(elementToDestroy); } } public EObject getContainer() { if (getElementToDestroy() != null) { return getElementToDestroy().eContainer(); } return null; } /* * (non-Javadoc) * * @see org.eclipse.gmf.runtime.emf.type.core.edithelper.IEditCommandRequest#getElementsToEdit() */ public List getElementsToEdit() { if (getElementToDestroy() != null) { return Collections.singletonList(getElementToDestroy()); } return super.getElementsToEdit(); } /* * (non-Javadoc) * * @see org.eclipse.gmf.runtime.emf.type.core.edithelper.AbstractEditCommandRequest#getDefaultLabel() */ protected String getDefaultLabel() { return EMFTypeCoreMessages.Request_Label_DestroyDependents; } /* * (non-Javadoc) * * @see org.eclipse.gmf.runtime.emf.type.core.edithelper.IEditCommandRequest#getEditHelperContext() */ public Object getEditHelperContext() { IClientContext context = getClientContext(); if (context == null) { return getElementToDestroy(); } else { return new EditHelperContext(getElementToDestroy(), context); } } /** * Derives the editing domain from the object to be destroyed, if it hasn't * already been specified. */ public TransactionalEditingDomain getEditingDomain() { TransactionalEditingDomain result = super.getEditingDomain(); if (result == null) { result = TransactionUtil.getEditingDomain(getElementToDestroy()); if (result != null) { setEditingDomain(result); } } return result; } /** * Obtains the <em>mutable</em> set of dependent elements to destroy. * * @return the set of dependent elements */ protected final Set internalGetDependentElementsToDestroy() { if (dependentElementsToDestroy == null) { Map cacheMaps = (Map) getParameter(RequestCacheEntries.Cache_Maps); if (cacheMaps != null) { dependentElementsToDestroy = (Set) cacheMaps .get(RequestCacheEntries.Dependent_Elements); } else { dependentElementsToDestroy = new HashSet(); } immutableViewOfDependents = Collections.unmodifiableSet(dependentElementsToDestroy); if (ctorElementToDestroy != null) { dependentElementsToDestroy.add(ctorElementToDestroy); populateCacheMap(null, ctorElementToDestroy); } ctorElementToDestroy = null; } return dependentElementsToDestroy; } /** * Obtains an immutable view of the set of dependent elements to destroy. * * @return the immutable set of dependent elements */ public final Set getDependentElementsToDestroy() { if (immutableViewOfDependents == null) { internalGetDependentElementsToDestroy();//this should ensure we are initialized } return immutableViewOfDependents; } /** * Obtains a command that destroys the specified <code>dependent</code> of * the {@linkplain #getElementToDestroy() element to be destroyed}, if it * is not already being destroyed by the processing of the current * {@link DestroyElementRequest}. This command can then be composed with * others by the edit helper processing the <code>DestroyDependentsRequest</code>. * * @param dependent an object dependent on the element being destroyed, * which must also be destroyed * * @return a command to destroy the <code>dependent</code>, or * <code>null</code> if the element is already being destroyed * * @throws IllegalArgumentException on an attempt to destroy the * {@linkplain #getElementToDestroy() element to be destroyed} (as a * dependent of itself) * @throws NullPointerException on attempt to destroy a <code>null</code> * dependent * * @see #getDestroyDependentsCommand(Collection) */ public ICommand getDestroyDependentCommand(EObject dependent) { ICommand result = null; if (addDependentElementToDestroy(dependent)) { //record the element that we are destroying, for later restoration EObject elementBeingDestroyed = getElementToDestroy(); try { DestroyElementRequest destroy = new DestroyElementRequest( getEditingDomain(), dependent, isConfirmationRequired()); // propagate my parameters destroy.addParameters(getParameters()); // propagate the dependents information to detect cycles destroy.setParameter( DestroyElementRequest.DESTROY_DEPENDENTS_REQUEST_PARAMETER, this); setElementToDestroy(dependent); Object eHelperContext = destroy.getEditHelperContext(); IElementType context = populateCacheMap(eHelperContext, dependent); if (context == null) { context = ElementTypeRegistry.getInstance().getElementType( eHelperContext); } if (context != null) { result = context.getEditCommand(destroy); } } finally { // restore the element that we are destroying setElementToDestroy(elementBeingDestroyed); } } return result; } private IElementType populateCacheMap(Object eHelperContext, EObject dependent) { IElementType context = null; Map cacheMaps = (Map) getParameter(RequestCacheEntries.Cache_Maps); if (cacheMaps != null) { //beacareful, this one here call populateCacheMap(...) if the set was null and cacheMaps exist, //so before that you should instantiate the DependentElementsToDestroy set Set dependents = internalGetDependentElementsToDestroy(); //May be this guy was a context-of-a-dependent, and we had populated its cache map already if (cacheMaps.get(dependent) == null) { Map parentMap = new HashMap(); cacheMaps.put(dependent, parentMap); RequestCacheEntries.initializeEObjCache(dependent, parentMap); } TreeIterator it = dependent.eAllContents(); while (it.hasNext()) { EObject eObj = (EObject) it.next(); dependents.add(eObj); if (cacheMaps.get(eObj) == null) { Map map = new HashMap(); cacheMaps.put(eObj, map); RequestCacheEntries.initializeEObjCache(eObj, map); } } if (eHelperContext != null) { Map eHelperMap = (Map) cacheMaps.get(eHelperContext); if (eHelperMap == null && (eHelperContext instanceof EObject)) { eHelperMap = new HashMap(); cacheMaps.put(eHelperContext, eHelperMap); RequestCacheEntries.initializeEObjCache( (EObject) eHelperContext, eHelperMap); } if (eHelperMap != null) { context = (IElementType) eHelperMap .get(RequestCacheEntries.Element_Type); } } }//if (cacheMaps != null) return context; } /** * Obtains a command that destroys the specified <code>dependents</code> of * the {@linkplain #getElementToDestroy() element to be destroyed}, if they * are not already being destroyed by the processing of the current * {@link DestroyElementRequest}. This command can then be composed with * others by the edit helper processing the <code>DestroyDependentsRequest</code>. * * @param dependents dependents of the element being destroyed * * @return a command to destroy all of the specified <code>dependents</code>, * or <code>null</code> if they are all already being destroyed * * @see #getDestroyDependentCommand(EObject) */ public ICommand getDestroyDependentsCommand(Collection dependents) { ICommand result = null; for (Iterator i = dependents.iterator(); i.hasNext();) { result = CompositeCommand.compose( result, getDestroyDependentCommand((EObject) i.next())); } return result; } /** * Indicates that the command that fulfils this request will also destroy * the specified dependent of the * {@link #getElementToDestroy() element to be destroyed}. Note that * contained elements are implicitly considered to be dependent; they need * not be handled by this mechanism. * <p> * Advice that provides a command to destroy a dependent element * <em>must</em> indicate that fact by calling this method (only after * checking whether it isn't already * {@link #isElementToBeDestroyed(EObject) being destroyed}, anyway). * </p> * * @param dependent another object to destroy, which is dependent on the * element for which we are requesting destruction * * @return <code>true</code> if the <code>dependent</code> was not already * in the set of elements being destroyed; <code>false</code>, otherwise * * @throws IllegalArgumentException on an attempt to add the * {@link #getElementToDestroy() element to be destroyed} as a dependent * of itself * @throws NullPointerException on attempt to add a <code>null</code> object * * @see #isElementToBeDestroyed(EObject) * @see #getElementToDestroy() * @see #getDestroyDependentCommand(EObject) */ protected boolean addDependentElementToDestroy(EObject dependent) { if (dependent == null) { throw new NullPointerException("dependent is null"); //$NON-NLS-1$ } if (dependent == getElementToDestroy()) { throw new IllegalArgumentException("dependent is the element being destroyed"); //$NON-NLS-1$ } if (!isElementToBeDestroyed(dependent)) { return internalGetDependentElementsToDestroy().add(dependent); } return false; } /** * Queries whether the specified element will be destroyed as a result of * the fulfillment of this request. An element will be destroyed if it * or any of its ancestors is the * {@linkplain #getElementToDestroy() element to be destroyed} or one of its * {@linkplain #getDependentElementsToDestroy() dependents}. * * @param eObject an element * * @return <code>true</code> if the command that fulfils this request * would destroy the specified element; <code>false</code> if a new * command would have to be composed with it to destroy the element */ protected boolean isElementToBeDestroyed(EObject eObject) { boolean result = false; EObject eObj = getElementToDestroy(); Set set = internalGetDependentElementsToDestroy(); while (!(result || (eObject == null))) { result = (eObject == eObj) || set.contains(eObject); eObject = eObject.eContainer(); } return result; } }