| /****************************************************************************** |
| * Copyright (c) 2002, 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 API and implementation |
| ****************************************************************************/ |
| |
| package org.eclipse.gmf.runtime.emf.clipboard.core; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.util.ArrayList; |
| 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.core.runtime.IProgressMonitor; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.ecore.EAttribute; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.util.EcoreUtil; |
| import org.eclipse.emf.ecore.util.FeatureMapUtil; |
| |
| import org.eclipse.gmf.runtime.emf.clipboard.core.internal.ResourceInfoProcessor; |
| import org.eclipse.gmf.runtime.emf.clipboard.core.internal.SavingEMFResource; |
| import org.eclipse.gmf.runtime.emf.clipboard.core.internal.SerializationEMFResource; |
| |
| /** |
| * The default copy operation. Extensibility is provided by the callback |
| * methods of the {@link IClipboardSupport} implementation for the specific |
| * EMF metamodel. |
| * <p> |
| * <b>Note</b> that this class is not intended to be extended "directly" |
| * by clients. Clients may not instantiate it. Clients that need to extend |
| * or override this default copy operation should extend the |
| * {@link OverrideCopyOperation} class, instead. |
| * </p> |
| * |
| * @see OverrideCopyOperation |
| * |
| * @author Yasser Lulu |
| */ |
| public class CopyOperation |
| extends ClipboardOperation { |
| |
| private static final int KILOBYTE = 1024; |
| |
| private static final int BUFFER_SIZE = 128 * KILOBYTE; |
| |
| /** String constant for the value "copy". */ |
| public static final String COPY = "copy"; //$NON-NLS-1$ |
| |
| private static final URI COPY_URI = URI.createFileURI(""); //$NON-NLS-1$ |
| |
| private Map hintMap; |
| |
| private Collection eObjects; |
| |
| private CopyParentsCopier copyParentsCopierCopier; |
| |
| private OverrideCopyOperation overrideCopyOperation; |
| |
| /** |
| * Initializes me. |
| * |
| * @param monitor the optional progress monitor (can be <code>null</code>) |
| * @param clipboardSupport the clipboard support for the EMF metamodel |
| * @param eObjects the {@link EObject}s to copy |
| * @param hintMap the map of copy hints, possibly empty. Hints are defined |
| * by the {@link org.eclipse.gmf.runtime.emf.clipboard.core.ClipboardUtil} class |
| * |
| * @see org.eclipse.gmf.runtime.emf.clipboard.core.ClipboardUtil#RECYCLE_HINT_ID |
| * @see org.eclipse.gmf.runtime.emf.clipboard.core.ClipboardUtil#IGNORE_RECYCLE_HINT_ID |
| */ |
| public CopyOperation(IProgressMonitor monitor, |
| IClipboardSupport clipboardSupport, Collection eObjects, |
| Map hintMap) { |
| super(monitor, clipboardSupport); |
| this.eObjects = eObjects; |
| this.hintMap = hintMap; |
| } |
| |
| /** |
| * Obtains my hint map. |
| * |
| * @return my hint map |
| */ |
| protected Map getEObjectsHintMap() { |
| return hintMap; |
| } |
| |
| /** |
| * Performs the copy operation. |
| * |
| * @return the serialized form of the copied objects, suitable for placing |
| * on the system clipboard for later pasting |
| * @throws Exception if anything goes wrong in performing the copy |
| */ |
| public String copy() throws Exception { |
| if (getClipboardOperationHelper().shouldOverrideCopyOperation( |
| getEObjects(), getEObjectsHintMap())) { |
| overrideCopyOperation = getClipboardOperationHelper() |
| .getOverrideCopyOperation(this); |
| //by returning a null op, it means disallow coying, thus return |
| // empty string |
| return (overrideCopyOperation != null) ? overrideCopyOperation |
| .copy() : ""; //$NON-NLS-1$ |
| } |
| return doCopy(); |
| |
| } |
| |
| protected String doCopy() throws Exception{ |
| CopyObjects copyObjects = getAuxiliaryObjects(); |
| if (isCancelled()) { |
| throwCancelException(); |
| } |
| |
| return saveEObjects(copyObjects, COPY_URI, "UTF-8", //$NON-NLS-1$ |
| SerializationEMFResource.SAVE_OPTIONS, getEObjectsHintMap()); |
| } |
| |
| |
| private String saveEObjects(CopyObjects copyObjects, URI uri, |
| String encoding, Map saveOptions, Map hints) throws Exception { |
| SavingEMFResource res = null; |
| try { |
| try { |
| res = new SavingEMFResource(uri, encoding, saveOptions, |
| getCopyParentsCopier().getCopy2ObjectMap(), copyObjects, |
| getClipboardOperationHelper()); |
| } catch (Exception ex) { |
| //if the ctor of the saving resource threw an exception, then |
| //something is wrong with elements we have, usually a problem with a |
| // detached view-element that renderes it un-copy-able. |
| throwUncopyableException(); |
| } |
| ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE); |
| res.save(out, null); |
| String dataStr = out.toString(encoding); |
| String infoStr = ResourceInfoProcessor.getResourceInfo(dataStr |
| .length(), encoding, res, hints, copyObjects); |
| return dataStr + infoStr; |
| } finally { |
| if (res != null) { |
| res.unload(); |
| } |
| } |
| } |
| |
| private void extractCopyAlwaysObjects(Collection mainObjects, |
| Set combinedCopyAlwaysSet, Map copyAlwaysMap) { |
| Iterator mainEObjectIt = mainObjects.iterator(); |
| EObject originalEObject = null; |
| Set copyAlwaysSet = new HashSet(); |
| while (mainEObjectIt.hasNext()) { |
| originalEObject = (EObject) mainEObjectIt.next(); |
| getCopyAlwaysObjects(originalEObject, copyAlwaysSet, |
| combinedCopyAlwaysSet); |
| //now get copy-always for the originalObject's children, |
| // recursively. Use eAllContents() instead of |
| // EcoreUtil.getAllProperContents() because we really need all |
| // of the model sub-tree |
| Iterator childrenIt = originalEObject.eAllContents(); |
| EObject directChild = null; |
| while (childrenIt.hasNext()) { |
| directChild = (EObject) childrenIt.next(); |
| getCopyAlwaysObjects(directChild, copyAlwaysSet, |
| combinedCopyAlwaysSet); |
| } |
| if (copyAlwaysSet.isEmpty() == false) { |
| copyAlwaysMap.put(originalEObject, copyAlwaysSet); |
| copyAlwaysSet = new HashSet(); |
| } |
| } |
| |
| //just to be safe, remove non-copyables |
| removeNonCopyableObject(combinedCopyAlwaysSet); |
| |
| // we are doing this because copyAlways objects are copied with all |
| // their |
| //content recursively (i.e., treated as original objects |
| removeEmbeddedChildren(new ArrayList(combinedCopyAlwaysSet), |
| combinedCopyAlwaysSet); |
| } |
| |
| private void extractCopyAlwaysObjects(CopyObjects copyObjects) { |
| extractCopyAlwaysObjects(copyObjects.originalObjects, |
| copyObjects.combinedCopyAlwaysSet, copyObjects.copyAlwaysMap); |
| |
| //Now, remove copy always objects that are copied anyways |
| copyObjects.combinedCopyAlwaysSet |
| .removeAll(copyObjects.originalObjects); |
| removeEmbeddedChildren(copyObjects.originalObjects, |
| copyObjects.combinedCopyAlwaysSet); |
| |
| if (copyObjects.combinedCopyAlwaysSet.isEmpty()) { |
| return; |
| } |
| |
| Set nonCheckedCopyAlwaysSet = new HashSet( |
| copyObjects.combinedCopyAlwaysSet); |
| do { |
| Set startingCopyAlwaysSet = new HashSet( |
| copyObjects.combinedCopyAlwaysSet); |
| extractCopyAlwaysObjects(nonCheckedCopyAlwaysSet, |
| copyObjects.combinedCopyAlwaysSet, copyObjects.copyAlwaysMap); |
| Set newCopyAlwaysSet = new HashSet( |
| copyObjects.combinedCopyAlwaysSet); |
| newCopyAlwaysSet.removeAll(startingCopyAlwaysSet); |
| nonCheckedCopyAlwaysSet = newCopyAlwaysSet; |
| } while (nonCheckedCopyAlwaysSet.isEmpty() == false); |
| } |
| |
| private Map removeEmbeddedChildren(Collection parents, Collection childs) { |
| Map removedEmbeddedChild2Parent = new HashMap(); |
| Iterator parentsIt = parents.iterator(); |
| EObject parent = null; |
| EObject child = null; |
| while (parentsIt.hasNext()) { |
| parent = (EObject) parentsIt.next(); |
| Iterator childIt = childs.iterator(); |
| while (childIt.hasNext()) { |
| child = (EObject) childIt.next(); |
| if (ClipboardSupportUtil.isChild(parent, child)) { |
| childIt.remove(); |
| removedEmbeddedChild2Parent.put(child, parent); |
| } |
| } |
| } |
| return removedEmbeddedChild2Parent; |
| } |
| |
| private void extractCopyParentsObjects(CopyObjects copyObjects) { |
| List list = new ArrayList(); |
| list.addAll(copyObjects.originalObjects); |
| list.addAll(copyObjects.combinedCopyAlwaysSet); |
| |
| Iterator it = list.iterator(); |
| EObject eObject = null; |
| EObject copyParent = null; |
| while (it.hasNext()) { |
| eObject = (EObject) it.next(); |
| copyParent = getTopMostCopyParentObject(eObject); |
| if (copyParent != null) { |
| Set objectSet = (Set) copyObjects.copyParent2ObjectsMap |
| .get(copyParent); |
| if (objectSet == null) { |
| objectSet = new HashSet(); |
| copyObjects.copyParent2ObjectsMap |
| .put(copyParent, objectSet); |
| } |
| objectSet.add(eObject); |
| //build initial objectsToCopyParentMap |
| copyObjects.objectsToCopyParentMap.put(eObject, copyParent); |
| } |
| } |
| |
| if (copyObjects.copyParent2ObjectsMap.isEmpty()) { |
| return; |
| } |
| |
| Set copyParentSet = copyObjects.copyParent2ObjectsMap.keySet(); |
| removeEmbeddedChildren(copyObjects.originalObjects, copyParentSet); |
| Set copyParentSetCopy = new HashSet(copyParentSet); |
| //collapse the copyParent2ObjectsMap by removing embedded copy-parents |
| Map removedEmbeddedChild2Parent = removeEmbeddedChildren( |
| copyParentSetCopy, copyParentSet); |
| |
| //build the ancestry path from each object to its top-most copyParent |
| Set embeddedCopyParents = new HashSet(); |
| buildEmbeddedCopyParentChain(copyObjects.objectsToCopyParentMap |
| .keySet(), embeddedCopyParents, copyParentSet); |
| |
| //add those who we won't indirectly copy by copier |
| list.addAll(copyParentSet); |
| |
| Iterator copyParentsIt = copyParentSet.iterator(); |
| copyParent = null; |
| EObject parentCopy = null; |
| while (copyParentsIt.hasNext()) { |
| copyParent = (EObject) copyParentsIt.next(); |
| parentCopy = getCopyParentsCopier().copyParent(copyParent, |
| embeddedCopyParents, list); |
| copyObjects.copyParent2CopyMap.put(copyParent, parentCopy); |
| } |
| //now copy refs |
| getCopyParentsCopier().copyReferences(); |
| |
| //re-build objectsToCopyParentMap |
| list.removeAll(copyParentSet); |
| it = list.iterator(); |
| while (it.hasNext()) { |
| eObject = (EObject) it.next(); |
| copyParent = (EObject) copyObjects.objectsToCopyParentMap |
| .get(eObject); |
| EObject replacementCopyParent = (EObject) removedEmbeddedChild2Parent |
| .get(copyParent); |
| if (replacementCopyParent != null) { |
| copyObjects.objectsToCopyParentMap.put(eObject, |
| replacementCopyParent); |
| } |
| } |
| } |
| |
| private void buildEmbeddedCopyParentChain(Set objectsWithCopyParent, |
| Set embeddedCopyParents, Set copyParentSet) { |
| Iterator it = objectsWithCopyParent.iterator(); |
| EObject eObject = null; |
| while (it.hasNext()) { |
| eObject = (EObject) it.next(); |
| while (copyParentSet.contains(eObject.eContainer()) == false) { |
| embeddedCopyParents.add(eObject.eContainer()); |
| eObject = eObject.eContainer(); |
| } |
| } |
| } |
| |
| /** |
| * Obtains my complete set of objects that must always be copied, that are |
| * related to the original selection but not of it. |
| * |
| * @return my combined set of "copy always" objects; may be empty |
| */ |
| public Set getCombinedCopyAlwaysSet() { |
| return getAuxiliaryObjects().combinedCopyAlwaysSet; |
| } |
| |
| protected CopyObjects getAuxiliaryObjects() { |
| CopyObjects copyObjects = new CopyObjects(getEObjects()); |
| extractCopyAlwaysObjects(copyObjects); |
| extractCopyParentsObjects(copyObjects); |
| if (isCancelled()) { |
| throwCancelException(); |
| } |
| |
| //we are doing this because copyAlways objects are copied with all |
| // their |
| //content recursively (i.e., treated as original objects), so if a |
| //copyAlways happen to be a parent of an original object, then there is |
| //no need to copy the original.We will use a copy of the original |
| // object |
| //list to remove the embedded children from so that the list of |
| // originals |
| //is available for us later on. |
| List originalObjectsCopy = new ArrayList(copyObjects.originalObjects); |
| removeEmbeddedChildren(copyObjects.combinedCopyAlwaysSet, |
| originalObjectsCopy); |
| |
| //now combine them all |
| copyObjects.totalCopyObjects.addAll(originalObjectsCopy); |
| copyObjects.totalCopyObjects.addAll(copyObjects.copyParent2CopyMap |
| .values()); |
| copyObjects.totalCopyObjects.addAll(copyObjects.combinedCopyAlwaysSet); |
| return copyObjects; |
| } |
| |
| private void removeNonCopyableObject(Collection collection) { |
| // model roots are not copyable |
| Iterator it = collection.iterator(); |
| EObject eObject = null; |
| while (it.hasNext()) { |
| eObject = (EObject) it.next(); |
| if (eObject.eContainer() == null) { |
| it.remove(); |
| } |
| } |
| } |
| |
| private EObject getTopMostCopyParentObject(EObject eChild) { |
| if ((eChild == null) || (eChild.eContainmentFeature() == null) |
| || (eChild.eContainer() == null)) { |
| return null; |
| } |
| EObject copyParent = null; |
| if (getClipboardOperationHelper().hasPasteOption(eChild, |
| eChild.eContainmentFeature(), PasteOption.PARENT)) { |
| copyParent = getTopMostCopyParentObject(eChild.eContainer()); |
| if (copyParent == null) { |
| if ((eChild.eContainer() != null) |
| && (eChild.eContainer().eContainer() != null)) { |
| copyParent = eChild.eContainer(); |
| } |
| } |
| } |
| return copyParent; |
| } |
| |
| private void getCopyAlwaysObjects(EObject eObject, Set copyAlwaysObjects, |
| Set combinedCopyAlwaysSet) { |
| Iterator refIt = eObject.eClass().getEAllReferences().iterator(); |
| EReference ref = null; |
| while (refIt.hasNext()) { |
| ref = (EReference) refIt.next(); |
| if ((ref.isContainment() == false) && (eObject.eIsSet(ref))) { |
| Object value = eObject.eGet(ref, true); |
| if (getClipboardOperationHelper().isCopyAlways(eObject, ref, |
| value)) { |
| if (FeatureMapUtil.isMany(eObject,ref)) { |
| List vals = (List) value; |
| if (vals.isEmpty() == false) { |
| Iterator valIt = vals.iterator(); |
| while (valIt.hasNext()) { |
| EObject val = (EObject) valIt.next(); |
| if (combinedCopyAlwaysSet.contains(val) == false) { |
| copyAlwaysObjects.add(val); |
| combinedCopyAlwaysSet.add(val); |
| } |
| } |
| } |
| } else if (value != null) { |
| if (combinedCopyAlwaysSet.contains(value) == false) { |
| copyAlwaysObjects.add(value); |
| combinedCopyAlwaysSet.add(value); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private void throwCancelException() { |
| throwCancelException(COPY); |
| } |
| |
| private void throwUncopyableException() { |
| throwException(COPY, |
| new IllegalArgumentException("Uncopyable Object")); //$NON-NLS-1$ |
| } |
| |
| private CopyParentsCopier getCopyParentsCopier() { |
| if (copyParentsCopierCopier == null) { |
| copyParentsCopierCopier = new CopyParentsCopier(); |
| } |
| return copyParentsCopierCopier; |
| |
| } |
| |
| private class CopyParentsCopier |
| extends EcoreUtil.Copier { |
| |
| private static final long serialVersionUID = 6178429496879264655L; |
| |
| private Map copyToObjectMap = new HashMap(); |
| |
| private Collection doNotCopyEObjects; |
| |
| private Collection embeddedCopyParents; |
| |
| EObject copyParent(EObject eParent, |
| Collection embeddedCopyParentsCollection, |
| Collection doNotCopyEObjectsCollection) { |
| this.embeddedCopyParents = embeddedCopyParentsCollection; |
| this.doNotCopyEObjects = doNotCopyEObjectsCollection; |
| EObject copiedParent = copy(eParent); |
| this.doNotCopyEObjects = null; |
| embeddedCopyParents = null; |
| return copiedParent; |
| } |
| |
| public EObject copy(EObject eObject) { |
| EObject copiedObject = (EObject) get(eObject); |
| if (copiedObject != null) { |
| return copiedObject; |
| } |
| copiedObject = super.copy(eObject); |
| copyToObjectMap.put(copiedObject, eObject); |
| return copiedObject; |
| } |
| |
| Map getObject2CopyMap() { |
| return this; |
| } |
| |
| Map getCopy2ObjectMap() { |
| return copyToObjectMap; |
| } |
| |
| private Collection copyAll(EObject eOwnerObject, EReference eReference) { |
| if (!eOwnerObject.eIsSet(eReference)) |
| return Collections.EMPTY_LIST; |
| |
| List list = (List) eOwnerObject.eGet(eReference); |
| Collection result = new ArrayList(list.size()); |
| int lowerBound = eReference.getLowerBound(); |
| List noneCopied = new ArrayList(list.size()); |
| Iterator it = list.iterator(); |
| while (it.hasNext()) { |
| EObject eObject = (EObject) it.next(); |
| if ((doNotCopyEObjects != null) |
| && (doNotCopyEObjects.contains(eObject))) { |
| continue; |
| } |
| if ((embeddedCopyParents != null) |
| && (embeddedCopyParents.contains(eObject))) { |
| result.add(copy(eObject)); |
| if ((lowerBound != 0) && (result.size() == lowerBound)) { |
| return result; |
| } |
| continue; |
| } |
| noneCopied.add(eObject); |
| } |
| |
| it = noneCopied.iterator(); |
| while (it.hasNext()) { |
| EObject eObject = (EObject) it.next(); |
| result.add(copy(eObject)); |
| if ((lowerBound != 0) && (result.size() == lowerBound)) { |
| return result; |
| } |
| } |
| return result; |
| } |
| |
| protected void copyAttribute(EAttribute eAttribute, EObject eObject, |
| EObject copiedEObject) { |
| //we don't test EAttributes for isCritical... |
| super.copyAttribute(eAttribute, eObject, copiedEObject); |
| } |
| |
| protected void copyContainment(EReference eReference, EObject eObject, |
| EObject copiedEObject) { |
| if (eObject.eIsSet(eReference)) { |
| //do not copy the child that originated the copyParent request |
| // (copy-parent-request <-> non-null-child) |
| if (doNotCopyEObjects != null) { |
| //many-refs will be handled in copyAll function |
| if (!FeatureMapUtil.isMany(eObject,eReference)) { |
| if (doNotCopyEObjects |
| .contains(eObject.eGet(eReference))) { |
| return; |
| } |
| } |
| } |
| if (isCritical(eObject, eReference)) { |
| if (FeatureMapUtil.isMany(eObject,eReference)) { |
| ((Collection) copiedEObject.eGet(eReference)) |
| .addAll(copyAll(eObject, eReference)); |
| } else { |
| EObject childEObject = (EObject) eObject |
| .eGet(eReference); |
| copiedEObject.eSet(eReference, |
| childEObject == null ? null : copy(childEObject)); |
| } |
| } |
| } |
| } |
| |
| protected void copyReference(EReference eReference, EObject eObject, |
| EObject copiedEObject) { |
| if (isCritical(eObject, eReference)) { |
| super.copyReference(eReference, eObject, copiedEObject); |
| } |
| } |
| |
| private boolean isCritical(EObject eObject, EReference eReference) { |
| int lowerBound = eReference.getLowerBound(); |
| int upperBound = eReference.getUpperBound(); |
| if (upperBound == 0) { |
| return false; |
| } |
| if (FeatureMapUtil.isMany(eObject, eReference) && !eObject.eIsSet(eReference)) |
| return false; |
| Object value = eObject.eGet(eReference); |
| if (value == null) { |
| return false; |
| } |
| if (eReference.isMany()) { |
| List list = (List) value; |
| if (list.isEmpty()) { |
| return false; |
| } |
| if (embeddedCopyParents != null) { |
| if (ClipboardSupportUtil.containsAny(list, embeddedCopyParents)) { |
| return true; |
| } |
| } |
| if (lowerBound == upperBound) { |
| return true; |
| } |
| if (lowerBound > 0) { |
| return true; |
| } |
| if (eReference.isChangeable() == false) { |
| return true; |
| } |
| } else if ((eReference.isUnsettable() == false) |
| || ((embeddedCopyParents != null) && (embeddedCopyParents |
| .contains(value)))) { |
| return true; |
| } |
| return getClipboardOperationHelper().hasPasteOption(eObject, |
| eReference, PasteOption.CRITICAL); |
| } |
| } //CopyParentsCopier |
| |
| /** |
| * @return Returns the eObjectList. |
| */ |
| protected Collection getEObjects() { |
| return eObjects; |
| } |
| } |