//------------------------------------------------------------------------------
// Copyright (c) 2005, 2006 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 implementation
//------------------------------------------------------------------------------
package org.eclipse.epf.library.edit.command;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EventObject;
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 java.util.Map.Entry;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandWrapper;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.util.AbstractTreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.WrappedException;
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.resource.Resource;
import org.eclipse.emf.ecore.util.EContentsEList;
import org.eclipse.emf.edit.provider.IWrapperItemProvider;
import org.eclipse.emf.edit.provider.ItemProviderAdapter;
import org.eclipse.epf.common.serviceability.MsgBox;
import org.eclipse.epf.common.utils.StrUtil;
import org.eclipse.epf.library.edit.ICommandListener;
import org.eclipse.epf.library.edit.IReferencer;
import org.eclipse.epf.library.edit.IStatefulItemProvider;
import org.eclipse.epf.library.edit.LibraryEditPlugin;
import org.eclipse.epf.library.edit.LibraryEditResources;
import org.eclipse.epf.library.edit.Providers;
import org.eclipse.epf.library.edit.ui.UserInteractionHelper;
import org.eclipse.epf.library.edit.util.TngUtil;
import org.eclipse.epf.uma.CustomCategory;
import org.eclipse.epf.uma.MethodElement;
import org.eclipse.epf.uma.UmaPackage;
import org.eclipse.epf.uma.ecore.impl.MultiResourceEObject;
import org.eclipse.epf.uma.util.AssociationHelper;
import org.eclipse.epf.uma.util.ContentDescriptionFactory;
import org.eclipse.epf.uma.util.UmaUtil;
import org.eclipse.epf.uma.util.IMethodLibraryPersister.FailSafeMethodLibraryPersister;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

/**
 * This command is used to delete a method element permanently. This involves deleting
 * the storage content and removing all references to this element.
 * 
 * @author Phong Nguyen Le
 * @since 1.0
 */
public class DeleteMethodElementCommand extends CommandWrapper {
	

	protected Collection elements;

	private boolean refRemoved;

	public boolean executed = false;

	private Collection commandListeners;

	private FailSafeMethodLibraryPersister persister;

	protected ArrayList elementsToDeleteContent;

	protected Set modifiedResources;

	// Map of element to Map of its referencer to features list
	//
	private HashMap elementToRemovedRefsMap;

	/**
	 * List of Reference objects
	 * 
	 * @see Reference
	 */
	protected ArrayList removedReferences;

	public boolean failed;

	private Collection objectsToDelete;

	/**
	 * @param command
	 * @param elements
	 *            MethodElement objects to be permanently deleted.
	 */
	public DeleteMethodElementCommand(Command command, Collection elements) {
		super(command);
		this.elements = elements;
		commandListeners = new ArrayList();
	}

	protected void superExecute() {
		super.execute();
	}
	
	private void notifyPreExecute() {
		List commandListeners = Providers.getCommandListeners(DeleteMethodElementCommand.class);
		if(commandListeners != null && !commandListeners.isEmpty()) {
			for (Iterator iter = commandListeners.iterator(); iter.hasNext();) {
				ICommandListener cmdListener = (ICommandListener) iter.next();
				try {
					cmdListener.preExecute(this);
				}
				catch(Exception e) {
					LibraryEditPlugin.getDefault().getLogger().logError(e);
				}
			}
		}
	}
	
	private void notifyPreUndo() {
		List commandListeners = Providers.getCommandListeners(DeleteMethodElementCommand.class);
		if(commandListeners != null && !commandListeners.isEmpty()) {
			for (Iterator iter = commandListeners.iterator(); iter.hasNext();) {
				ICommandListener cmdListener = (ICommandListener) iter.next();
				try {
					cmdListener.preUndo(this);
				}
				catch(Exception e) {
					LibraryEditPlugin.getDefault().getLogger().logError(e);
				}
			}
		}
	}

	public void execute() {
		notifyPreExecute();

		elementsToDeleteContent = new ArrayList();

		for (Iterator iter = elements.iterator(); iter.hasNext();) {
			Object element = TngUtil.unwrap(iter.next());
			if (element instanceof MethodElement) {
				collectObjectsToDeleteContent(elementsToDeleteContent, (MethodElement) element);
			}
		}

		IRunnableWithProgress runnable = new IRunnableWithProgress() {

			public void run(IProgressMonitor monitor)
			throws InvocationTargetException, InterruptedException {
				prepareRemovingReferences();
			}

		};
		UserInteractionHelper.runWithProgress(runnable, LibraryEditResources.processingReferencesTask_name); //$NON-NLS-1$

		// confirm with user before removing illegal references
		//
		if (!elementToRemovedRefsMap.isEmpty()) {
			// collect set of referencers
			HashSet referencers = new HashSet();
			for (Iterator iter = elementToRemovedRefsMap.values().iterator(); iter
			.hasNext();) {
				Map referencerToFeaturesMap = (Map) iter.next();
				// collect only referencer with an unidirectional or containment relationship
				//
				for (Iterator iterator = referencerToFeaturesMap.entrySet().iterator(); iterator
				.hasNext();) {
					Map.Entry entry = (Map.Entry) iterator.next();
					
					// Check if the key is Predefined Element, then show as reference
					// in delete confirm dialog
					boolean predefined = false;
					Object key  = entry.getKey();
					if(key instanceof MethodElement && TngUtil.isPredefined(
							(MethodElement)key)){
						predefined = true;
					}
					if(!predefined)
					{
						Collection features = (Collection) entry.getValue();
						boolean canCollect = false;
						check_ref:
							for (Iterator iterator1 = features.iterator(); iterator1
							.hasNext();) {
								Object f = iterator1.next();
								if(f instanceof EReference) {
									EReference ref = (EReference) f;
									if(ref.isContainment() || ref.getEOpposite() == null) {
										canCollect = true;
										break check_ref;
									}
								}
							}
						if(canCollect) {
							referencers.add(entry.getKey());
						}
					}
				}
			}

			MultiStatus multiStatus = new MultiStatus(
					LibraryEditPlugin.INSTANCE.getSymbolicName(), 0, "", null); //$NON-NLS-1$
			for (Iterator iter = referencers.iterator(); iter.hasNext();) {
				MethodElement e = (MethodElement) iter.next();
				String msg = NLS.bind(LibraryEditResources.elementType_text, e.eClass().getName(), TngUtil.getLabelWithPath(e)); 
				IStatus status = new Status(IStatus.INFO,
						LibraryEditPlugin.INSTANCE.getSymbolicName(), 0, msg,
						null);
				multiStatus.add(status);
			}

			if (LibraryEditPlugin
					.getDefault()
					.getMsgDialog()
					.displayConfirmation(
							LibraryEditResources.deleteReferencesDialog_title, //$NON-NLS-1$
							LibraryEditResources.deleteReferencesDialog_text, //$NON-NLS-1$
							multiStatus) == Dialog.CANCEL) { //$NON-NLS-1$
				return;
			}

			// check if the referencers can be changed
			//
			for (Iterator iter = referencers.iterator(); iter.hasNext();) {
				IStatus status = UserInteractionHelper.checkModify((EObject) iter.next(), MsgBox.getDefaultShell());
				if (!status.isOK()) {
					LibraryEditPlugin.getDefault().getMsgDialog().displayError(
							LibraryEditResources.deleteDialog_title, //$NON-NLS-1$
							LibraryEditResources.deleteElementError_msg, //$NON-NLS-1$
							status);
					return;
				}
			}
		}

		modifiedResources = new HashSet();

		// get the owner resources before the elements got removed from
		// container in superExecute()
		//
		collectOwnerResources(modifiedResources);

		superExecute();

		// get resources of the objects that have been affected by this command until now
		//
		collectResources(modifiedResources, super.getAffectedObjects());

		final Exception[] exceptions = new Exception[1];
		BusyIndicator.showWhile(Display.getCurrent(), new Runnable() {

			public void run() {
				try {
					removeReferences();
				} catch (Exception e) {
					exceptions[0] = e;
				}
			}

		});

		if (exceptions[0] != null) {
			LibraryEditPlugin
			.getDefault()
			.getMsgDialog()
			.displayError(
					LibraryEditResources.deleteDialog_title, //$NON-NLS-1$
					LibraryEditResources.deleteElementError_msg, //$NON-NLS-1$
					LibraryEditResources.deleteReferencesError_reason, //$NON-NLS-1$
					exceptions[0]);
			undo();
			return;
		}

		modifiedResources.addAll(getReferencingResources());

		// check affected resources for unmodifiable
		//
		IStatus status = UserInteractionHelper.checkModify(modifiedResources,
				MsgBox.getDefaultShell());
		if (!status.isOK()) {
			LibraryEditPlugin.getDefault().getMsgDialog().displayError(
					LibraryEditResources.deleteDialog_title, //$NON-NLS-1$
					LibraryEditResources.deleteElementError_msg, //$NON-NLS-1$
					status);
			undo();
			return;
		}

		for (Iterator iter = elementsToDeleteContent.iterator(); iter.hasNext();) {
			EObject element = (EObject) iter.next();
			if (element.eContainer() != null) {
				iter.remove();
			}
		}

		elementsToDeleteContent.addAll(elements);
		runnable = new IRunnableWithProgress() {

			public void run(IProgressMonitor monitor)
			throws InvocationTargetException, InterruptedException {
				monitor.beginTask("", 3);
				getPersister();

				// save resources that had been changed after references to the
				// deleted elements
				// had been removed
				//
				try {
					monitor.subTask(LibraryEditResources.deletingElementsTask_name);
					monitor.worked(1);
					deleteContent();					

					// save modified resources
					//
					monitor.subTask("Saving resources");
					monitor.worked(1);
					for (Iterator iter = modifiedResources.iterator(); iter
					.hasNext();) {
						Resource resource = (Resource) iter.next();
						if (resource.isLoaded()) {							
							persister.save(resource);
						}
					}

					persister.commit();

					executed = true;

					removeAdapters();
				} catch (Exception e) {
					LibraryEditPlugin.INSTANCE.log(e);
					try {
						persister.rollback();
					} catch (Exception ex) {
						failed = true;
					}
					if (e instanceof RuntimeException) {
						throw (RuntimeException) e;
					} else {
						throw new WrappedException(e);
					}
				}
			}

		};

//		if (!UserInteractionHelper.runWithProgress(runnable,
//		LibraryEditResources.deletingElementsTask_name)) { //$NON-NLS-1$
//		if (failed) {
//		notifyFailure();
//		} else {
//		undo();
//		}
//		return;
//		}

		UserInteractionHelper.runInUI(runnable, (Shell)null);

		if (executed) {
			notifyExecuted();
			List warnings = persister.getWarnings();
			if(!warnings.isEmpty()) {
				String title = LibraryEditResources.deleteDialog_title; 
				String msg = LibraryEditResources.DeleteMethodElementCommand_warningMsg; 
				StringBuffer reason = new StringBuffer();
				for (Iterator iter = warnings.iterator(); iter.hasNext();) {
					Exception e = (Exception) iter.next();
					String str = e.getMessage();
					if(!StrUtil.isBlank(str)) {
						reason.append(str).append('\n');
					}
				}

				LibraryEditPlugin.getDefault().getMsgDialog().displayWarning(title, msg, reason.toString());
			}
		}
		else {
			if (failed) {
				notifyFailure();
			} else {
				undo();
			}
		}
	}

	/**
	 * Disposes all stateful adapters, then removes all adapters that are
	 * attached to the given element
	 */
	private static void removeAdapters(EObject element) {
		for (Iterator iterator = new ArrayList(element.eAdapters()).iterator(); iterator
				.hasNext();) {
			Object adapter = iterator.next();
			if (adapter instanceof IStatefulItemProvider) {
				((IStatefulItemProvider) adapter).dispose();
				if (adapter instanceof ItemProviderAdapter) {
					AdapterFactory adapterFactory = ((ItemProviderAdapter) adapter)
							.getAdapterFactory();
					if (adapterFactory instanceof IReferencer) {
						((IReferencer) adapterFactory).remove(adapter);
					}
				}
			}
		}
		element.eAdapters().clear();
	}

	/**
	 * Disposes all stateful adapters, then removes all adapters that are
	 * attached to the deleted elements
	 */
	protected void removeAdapters() {
		for (Iterator iter = elements.iterator(); iter.hasNext();) {
			Object obj = TngUtil.unwrap(iter.next());
			if (obj instanceof EObject) {
				EObject element = (EObject) obj;
				if (element.eContainer() == null) {
					for (Iterator iterator = element.eAllContents(); iterator
							.hasNext();) {
						EObject e = (EObject) iterator.next();
						removeAdapters(e);
					}
					removeAdapters(element);
				}
			}
		}
	}

	/**
	 * 
	 */
	private void notifyFailure() {
		EventObject eventObject = new EventObject(this);

		for (Iterator iter = commandListeners.iterator(); iter.hasNext();) {
			CommandListener listener = (CommandListener) iter.next();
			listener.notifyFailure(eventObject);
		}
	}

	/**
	 * @return
	 * 
	 */
	protected FailSafeMethodLibraryPersister getPersister() {
		if (persister == null) {
			persister = ContentDescriptionFactory.getMethodLibraryPersister()
					.getFailSafePersister();
		}
		return persister;
	}

	private void notifyExecuted() {
		EventObject eventObject = new EventObject(this);

		for (Iterator iter = this.commandListeners.iterator(); iter.hasNext();) {
			CommandListener listener = (CommandListener) iter.next();
			listener.notifyExecuted(eventObject);
		}
		
		List commandListeners = Providers.getCommandListeners(DeleteMethodElementCommand.class);
		if(commandListeners != null && !commandListeners.isEmpty()) {
			for (Iterator iter = commandListeners.iterator(); iter.hasNext();) {
				ICommandListener cmdListener = (ICommandListener) iter.next();
				try {
					cmdListener.notifyExecuted(this);
				}
				catch(Exception e) {
					LibraryEditPlugin.getDefault().getLogger().logError(e);
				}
			}
		}
	}

	private Collection getReferencingResources() {
		HashSet referrers = new HashSet();

		// for (Iterator iter = removedReferencesMap.values().iterator();
		// iter.hasNext();) {
		// Map referrerToFeaturesMap = (Map) iter.next();
		// referrers.addAll(referrerToFeaturesMap.keySet());
		// }

		for (Iterator iter = removedReferences.iterator(); iter.hasNext();) {
			Reference ref = (Reference) iter.next();
			referrers.add(ref.owner);
		}

		HashSet resources = new HashSet();
		for (Iterator iter = referrers.iterator(); iter.hasNext();) {
			MethodElement element = (MethodElement) iter.next();
			if (element.eResource() != null) {
				resources.add(element.eResource());
			}
		}
		return resources;
	}

	private void collectOwnerResources(Set resources) {
		for (Iterator iter = elements.iterator(); iter.hasNext();) {
			Object element = TngUtil.unwrap(iter.next());
			if (element instanceof MethodElement) {
				EObject container = ((MethodElement) element).eContainer();
				if (container.eResource() != null) {
					resources.add(container.eResource());
				}
			}
		}
	}
	
	private static void collectResources(Set resources, Collection objects) {
		for (Iterator iter = objects.iterator(); iter.hasNext();) {
			Object element = TngUtil.unwrap(iter.next());
			if (element instanceof EObject) {
				EObject eObj = (MethodElement) element;
				if (eObj.eResource() != null) {
					resources.add(eObj.eResource());
				}
			}		
		}
	}

	public void redo() {

		super.redo();
		removeReferences();

	}

	protected void deleteContent() throws Exception {
		for (Iterator iter = elementsToDeleteContent.iterator(); iter.hasNext();) {
			Object element = TngUtil.unwrap(iter.next());
			if (element instanceof MethodElement) {
				MethodElement e = (MethodElement) element;
				if (e.eContainer() == null) {
					persister.delete(e);
				}
			}
		}
	}

	public Collection getElementsToRemoveReferences() {
		Collection list = new ArrayList();
		for (Iterator iter = elements.iterator(); iter.hasNext();) {
			Object element = TngUtil.unwrap(iter.next());
			if (element instanceof EObject) {
				list.add(element);
			}
		}
		return list;
	}

	protected boolean canRemoveReferences(MethodElement e) {
		// if e is one of the deleted elements, make sure that it actually got
		// deleted by checking its container
		//
		for (Iterator iter = elements.iterator(); iter.hasNext();) {
			Object obj = TngUtil.unwrap(iter.next());
			if (e == obj) {
				return e.eContainer() == null;
			}
		}
		return true;
	}

	/**
	 * Loads all opposite features of the elements to be deleted
	 * 
	 * @return list of elements whose opposite features are loaded
	 */
	public List loadOppositeFeatures() {
		HashSet oppositeFeatures = new HashSet();
		HashSet deletedGUIDs = new HashSet();
		ArrayList elements = new ArrayList();

		for (Iterator iter = getElementsToRemoveReferences().iterator(); iter
				.hasNext();) {
			Object obj = iter.next();
			if (obj instanceof MethodElement) {
				MethodElement e = (MethodElement) obj;
				for (Iterator iterator = e.eAllContents(); iterator.hasNext();) {
					Object element = iterator.next();
					if (element instanceof MethodElement) {
						elements.add(element);
						Map oppositeFeatureMap = ((MultiResourceEObject) element)
								.getOppositeFeatureMap();
						if (oppositeFeatureMap != null
								&& !oppositeFeatureMap.isEmpty()) {
							oppositeFeatures
									.addAll(oppositeFeatureMap.keySet());
							deletedGUIDs.add(((MethodElement) element)
									.getGuid());
						}
					}
				}
				elements.add(e);
				Map oppositeFeatureMap = ((MultiResourceEObject) e)
						.getOppositeFeatureMap();
				if (oppositeFeatureMap != null && !oppositeFeatureMap.isEmpty()) {
					oppositeFeatures.addAll(oppositeFeatureMap.keySet());
					deletedGUIDs.add(((MethodElement) e).getGuid());
				}
			}
		}

		loadOppositeFeatures(new ArrayList(oppositeFeatures), deletedGUIDs);

		return elements;
	}

	private boolean isContainedByDeletedElement(EObject e) {
		for (Iterator iter = elements.iterator(); iter.hasNext();) {
			Object deleted = TngUtil.unwrap(iter.next());
			if (deleted instanceof EObject && UmaUtil.isContainedBy(e, deleted)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Collects objects that should be removed if <code>elementToDelete</code> will be removed from 
	 * <code>references</code> of <code>referencer</code>.
	 * 
	 * @param objectsToRemove output 
	 * @param referencer element that references to elementToDelete
	 * @param references collection of {@link EReference} that contains elementToDelete
	 * @return true if one of the collected objects is the <code>referencer</code> or the container of <code>referencer</code>
	 */
	protected boolean collectObjectsToRemove(Collection objectsToRemove, EObject elementToDelete, EObject referencer, Collection references) {
		boolean ret = false;
		List commandListeners = Providers.getCommandListeners(DeleteMethodElementCommand.class);
		if(commandListeners != null && !commandListeners.isEmpty()) {
			for (Iterator iter = commandListeners.iterator(); iter.hasNext();) {
				Object cmdListener = iter.next();
				if(cmdListener instanceof IDeleteMethodElementCommandListener) {
					try {
						boolean b = ((IDeleteMethodElementCommandListener)cmdListener).collectObjectsToRemove(objectsToRemove, elementToDelete, referencer, references);
						if(b) {
							ret = true;
						}
					}
					catch(Exception e) {
						LibraryEditPlugin.getDefault().getLogger().logError(e);
					}
				}
			}
		}
		
		return ret;
	}

	private void prepareRemovingReferences() {
		List elements = loadOppositeFeatures();

		elementToRemovedRefsMap = new HashMap();
		HashSet objectsToRemove = new HashSet();
		if(objectsToDelete == null) {
			objectsToDelete = new HashSet();
		}
		else {
			objectsToDelete.clear();
		}
		for (Iterator iter = elements.iterator(); iter.hasNext();) {
			MethodElement element = (MethodElement) iter.next();
			Map refMap = AssociationHelper.getReferenceMap(element);
			if (!refMap.isEmpty()) {
				for (Iterator iterator = refMap.entrySet().iterator(); iterator
						.hasNext();) {
					Map.Entry entry = (Entry) iterator.next();
					EObject referencer = (EObject) entry.getKey();
					Collection references = (Collection) entry.getValue();
					boolean b = collectObjectsToRemove(objectsToRemove, element, referencer, references);
					if (b || elements.contains(referencer)
							|| isContainedByDeletedElement(referencer)) 
					{
						iterator.remove();
					}
				}
				if (!refMap.isEmpty())
					elementToRemovedRefsMap.put(element, refMap);
			}
		}
		
		// add entries for objectsToRemove to elementToRemovedRefsMap
		//
		for (Iterator iter = objectsToRemove.iterator(); iter.hasNext();) {
			EObject obj = (EObject) iter.next();
			Map map = (Map) elementToRemovedRefsMap.get(obj);
			if(map == null) {
				map = new HashMap();				
				elementToRemovedRefsMap.put(obj, map);
			}
			EObject container = obj.eContainer();
			EReference containmentFeature = obj.eContainmentFeature();
			Collection refs = (Collection) map.get(container);
			if(refs == null) {
				refs = new ArrayList();
				refs.add(containmentFeature);
				map.put(container, refs);
			}
			else {
				if(!refs.contains(containmentFeature)) {
					refs.add(containmentFeature);
				}
			}
		}		

		// remove all bi-directional relationships
		//
		for (Iterator iter = elements.iterator(); iter.hasNext();) {
			Object element = iter.next();
			if(element instanceof EObject) {
				EObject eObject = (EObject) element;
				Map objToRefsMap = new HashMap(); 
				for (EContentsEList.FeatureIterator featureIterator = 
					(EContentsEList.FeatureIterator)eObject.eCrossReferences().iterator();
				featureIterator.hasNext(); )
				{
					EObject eObj = (EObject)featureIterator.next();
					EReference eReference = (EReference)featureIterator.feature();
					if(eReference.getEOpposite() != null) {
						List refs = (List) objToRefsMap.get(eObj);
						if(refs == null) {
							refs = new ArrayList();
							objToRefsMap.put(eObj, refs);
						}
						refs.add(eReference.getEOpposite());
					}
				}
				Map map = (Map) elementToRemovedRefsMap.get(eObject);
				if(map == null) {
					elementToRemovedRefsMap.put(eObject, objToRefsMap);
				}
				else {
					// merge objToRefsMap to map
					//
					for (Iterator iterator = objToRefsMap.entrySet().iterator(); iterator
							.hasNext();) {
						Map.Entry entry = (Map.Entry) iterator.next();
						Object obj = entry.getKey();
						Collection refs = (Collection) entry.getValue();
						Collection existingRefs = (Collection) map.get(obj);
						if(existingRefs != null) {
							for (Iterator itor = refs.iterator(); itor
							.hasNext();) {
								Object ref = (Object) itor.next();
								if(!existingRefs.contains(ref)) {
									existingRefs.add(ref);
								}
							}
						}
						else {
							map.put(obj, refs);
						}
					}
				}
			}			
		}

	}

	/**
	 * @param collectedObjects
	 * @param element the element that will be deleted by this command
	 */
	protected void collectObjectsToDeleteContent(Collection collectedObjects, MethodElement element) {	
		if (element instanceof CustomCategory) {
			// have to handle CustomCategory specially since deleting a
			// CustomCategory might triger deleting
			// its subcategories even the relationship between a CustomCategory and
			// its subcategories is
			// non-containment reference
			//		
			Iterator iter1 = new AbstractTreeIterator(element, false) {

				private static final long serialVersionUID = -6285969923138781437L;
				protected Iterator getChildren(Object object) {
					ArrayList children = new ArrayList();
					Collection catElements = ((CustomCategory) object)
					.getCategorizedElements();
					for (Iterator iterator = catElements.iterator(); iterator
					.hasNext();) {
						Object e = iterator.next();
						if (e instanceof CustomCategory) {
							children.add(e);
						}
					}
					return children.iterator();
				}

			};
			while (iter1.hasNext()) {
				collectedObjects.add(iter1.next());
			}
		}
		
		List commandListeners = Providers.getCommandListeners(DeleteMethodElementCommand.class);
		if(commandListeners != null && !commandListeners.isEmpty()) {
			for (Iterator iter = commandListeners.iterator(); iter.hasNext();) {
				Object cmdListener = iter.next();
				if(cmdListener instanceof IDeleteMethodElementCommandListener) {
					try {
						((IDeleteMethodElementCommandListener)cmdListener).collectObjectsToDeleteContent(collectedObjects, element);
					}
					catch(Exception e) {
						LibraryEditPlugin.getDefault().getLogger().logError(e);
					}
				}
			}
		}
	}

	protected void removeReferences() {
		if (refRemoved)
			return;
		
		if (removedReferences == null) {
			removedReferences = new ArrayList();
		} else {
			removedReferences.clear();
		}
		for (Iterator iter = elementToRemovedRefsMap.entrySet().iterator(); iter
				.hasNext();) {
			Map.Entry entry = (Map.Entry) iter.next();
			MethodElement referenced = (MethodElement) entry.getKey();
			if (canRemoveReferences(referenced)) {
				Map removedRefMap = (Map) entry.getValue();
				for (Iterator iterator = removedRefMap.entrySet().iterator(); iterator
						.hasNext();) {
					Map.Entry ent = (Map.Entry) iterator.next();
					EObject referencer = (EObject) ent.getKey();
					Collection features = (Collection) ent.getValue();
					for (Iterator iter1 = features.iterator(); iter1.hasNext();) {
						EStructuralFeature feature = (EStructuralFeature) iter1
								.next();
						if (feature.isMany()) {
							List list = ((List) referencer.eGet(feature));

							int index = list.indexOf(referenced);
							// list.remove(index);
							if (index != -1) {
								list.remove(index);
								removedReferences.add(new Reference(referencer,
										feature, referenced, index));
							} else {
								if (TngUtil.DEBUG) {
									System.out
											.println("DeleteMethodElementCommand.removeReferences(): index=" + index + ", size=" + list.size() + ", referencer=" + referencer + ", referenced=" + referenced + ", feature=" + feature); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
								}
								// work-around: try to find the proxy and remove
								// it.
								// TODO: kind of hack, needs revisit
								//
								String guid = ((MethodElement) referenced)
										.getGuid();
								find_proxy: for (int i = 0; i < list.size(); i++) {
									InternalEObject ref = (InternalEObject) list
											.get(i);
									URI uri = ref.eProxyURI();
									if (uri != null
											&& guid.equals(uri.fragment())) {
										list.remove(i);
										removedReferences.add(new Reference(
												referencer, feature,
												referenced, i));
										break find_proxy;
									}
								}
							}
						} else {
							referencer.eSet(feature, null);
							removedReferences.add(new Reference(referencer,
									feature, referenced, -1));
						}
					}
				}
			}
		}

		if (TngUtil.DEBUG) {
			System.out
					.println("removedReferences: size=" + removedReferences.size()); //$NON-NLS-1$
		}

		refRemoved = true;
	}

	/**
	 * Subclass should override this method to resolve all target features so
	 * opposite features of the given MethodElement are fully loaded.
	 * 
	 * @param deletedGUIDs
	 * 
	 * @param e
	 */
	protected void loadOppositeFeatures(List oppositeFeatures, Set deletedGUIDs) {
		//
	}
	
	protected static void restoreReferences(List removedReferences) {
		for (int i = removedReferences.size() - 1; i > -1; i--) {
			Reference ref = (Reference) removedReferences.get(i);
			if (ref.feature.isMany()) {
				List list = (List) ref.owner.eGet(ref.feature);
				if (ref.index != -1) {
					// TODO: need revisits
					//
					if (!list.contains(ref.value)) {
						if (ref.index < list.size()) {
							list.add(ref.index, ref.value);
						} else {
							if (TngUtil.DEBUG) {
								System.out
										.println("DeleteMethodElementCommand.removeReferences(): index=" + ref.index + ", size=" + list.size() + ", referencer=" + ref.owner + ", referenced=" + ref.value + ", feature=" + ref.feature); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
							}
							list.add(ref.value);
						}
					} else {
						if (TngUtil.DEBUG) {
							System.out
									.println("DeleteMethodElementCommand.removeReferences(): reference already exists: referencer=" + ref.owner + ", referenced=" + ref.value + ", feature=" + ref.feature); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
						}

					}
				} else {
					list.add(ref.value);
				}
			} else {
				ref.owner.eSet(ref.feature, ref.value);
			}
		}
	}

	protected void restoreReferences() {
		if (!refRemoved)
			return;

		restoreReferences(removedReferences);

		refRemoved = false;
	}

	public void undo() {
		notifyPreUndo();
		try {
			super.undo();
			restoreReferences();
		} catch (Exception e) {
			LibraryEditPlugin.INSTANCE.log(e);
			notifyFailure();
		}
	}

	public Collection getAffectedObjects() {
		if (executed) {
			return super.getAffectedObjects();
		}
		return elements;
	}

	public void addCommandListener(CommandListener listener) {
		if (!commandListeners.contains(listener)) {
			commandListeners.add(listener);
		}
	}

	public void removeCommandListener(CommandListener listener) {
		commandListeners.remove(listener);
	}

	public static interface CommandListener {
		void notifyExecuted(EventObject eventObject);

		void notifyFailure(EventObject eventObject);
	}

}
