blob: 5eb22ccf9408322bd05b272d8f9f3a475878667b [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2002, 2010 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$
}
/**
* @since 1.3
*/
protected CopyParentsCopier getCopyParentsCopier() {
if (copyParentsCopierCopier == null) {
copyParentsCopierCopier = new CopyParentsCopier();
}
return copyParentsCopierCopier;
}
/**
* @since 1.3
*/
protected 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;
}
public 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;
}
}