| /******************************************************************************* |
| * <copyright> |
| * |
| * Copyright (c) 2005, 2012 SAP AG. |
| * 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: |
| * SAP AG - initial API, implementation and documentation |
| * mwenz - Felix Velasco - Bug 374918 - Let default paste use LocalSelectionTransfer |
| * mwenz - Felix Velasco - Bug 361414 - Copy/paste : clipboard contents confuses the workbench |
| * |
| * </copyright> |
| * |
| *******************************************************************************/ |
| package org.eclipse.graphiti.ui.internal.util.clipboard; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.emf.common.command.CommandStack; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.common.util.URI; |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EClassifier; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EReference; |
| 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.EcoreUtil.Copier; |
| import org.eclipse.emf.transaction.RecordingCommand; |
| import org.eclipse.emf.transaction.TransactionalEditingDomain; |
| import org.eclipse.emf.transaction.util.TransactionUtil; |
| import org.eclipse.graphiti.ui.internal.Messages; |
| import org.eclipse.graphiti.ui.internal.T; |
| import org.eclipse.graphiti.ui.internal.services.GraphitiUiInternal; |
| import org.eclipse.graphiti.ui.internal.util.ReflectionUtil; |
| import org.eclipse.jface.util.LocalSelectionTransfer; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.swt.custom.BusyIndicator; |
| import org.eclipse.swt.dnd.Clipboard; |
| import org.eclipse.swt.dnd.FileTransfer; |
| import org.eclipse.swt.dnd.TextTransfer; |
| import org.eclipse.swt.dnd.Transfer; |
| import org.eclipse.swt.widgets.Display; |
| |
| /** |
| * Provides a clipboard-like storage of EMF-related data based on SWT |
| * {@link Clipboard}. |
| * |
| * @noinstantiate This class is not intended to be instantiated by clients. |
| * @noextend This class is not intended to be subclassed by clients. |
| */ |
| public final class ModelClipboard { |
| |
| private static final String EMPTY_TOSTRING_PLACEHOLDER = "<empty>"; //$NON-NLS-1$ |
| private static final EObject[] NO_E_OBJECTS = new EObject[0]; |
| private static final ModelClipboard INSTANCE = new ModelClipboard(); |
| |
| /** |
| * Creates a {@link ModelClipboard}. |
| */ |
| private ModelClipboard() { |
| } |
| |
| /** |
| * @return the default {@link ModelClipboard} instance to represent a global |
| * {@link ModelClipboard} to the user, which is connected to the SWT |
| * {@link Clipboard}. |
| */ |
| public static ModelClipboard getDefault() { |
| return INSTANCE; |
| } |
| |
| /** |
| * Sets the content of the {@link Clipboard} and deletes all previous data. |
| * Must be called in the UI thread. |
| * |
| * @param objects |
| * the {@link EObject} <code>objects</code> to store |
| * @throws IllegalStateException |
| * if not called from UI thread |
| * @throws IllegalArgumentException |
| * if <code>objects</code> parameter is null |
| */ |
| public synchronized void setContent(EObject[] objects) throws IllegalStateException { |
| if (objects == null) { |
| throw new IllegalArgumentException("EObject[] objects must not be null"); //$NON-NLS-1$ |
| } |
| if (objects.length == 0) { |
| return; |
| } |
| if (canUseNative()) { |
| // must run in UI thread |
| setNativeContentObjects(Arrays.asList(objects)); |
| } |
| } |
| |
| /** |
| * Returns the SWT {@link Clipboard} content in form of {@link EObject}s. |
| * |
| * @param resourceSet |
| * the ResourceSet to resolve the stored URI information |
| * @return the content as live objects |
| * @throws IllegalStateException |
| * if not called from UI thread |
| * @throws IllegalArgumentException |
| * if <code>resourceSet</code> parameter is null |
| */ |
| public synchronized EObject[] getContentAsEObjects(ResourceSet resourceSet) throws IllegalStateException { |
| if (resourceSet == null) { |
| throw new IllegalArgumentException("ResourceSet resourceSet must not be null"); //$NON-NLS-1$ |
| } |
| final List<EObject> eObjectList; |
| if (canUseNative()) { |
| eObjectList = getLocalSelectionContent(); |
| } else { |
| eObjectList = Collections.emptyList(); |
| } |
| if (eObjectList.isEmpty()) { |
| return NO_E_OBJECTS; |
| } |
| return eObjectList.toArray(new EObject[eObjectList.size()]); |
| } |
| |
| private List<EObject> getLocalSelectionContent() { |
| final Clipboard cb = new Clipboard(Display.getCurrent()); |
| try { |
| final ISelection contents = (ISelection) cb.getContents(LocalSelectionTransfer.getTransfer()); |
| if (contents instanceof IStructuredSelection && !contents.isEmpty()) { |
| List<?> list = ((IStructuredSelection) contents).toList(); |
| for (Object o : list) { |
| if (!(o instanceof EObject)) |
| return Collections.emptyList(); |
| } |
| @SuppressWarnings("unchecked") |
| List<EObject> localList = (List<EObject>) list; |
| return localList; |
| } |
| return Collections.emptyList(); |
| } finally { |
| cb.dispose(); |
| } |
| } |
| |
| /** |
| * Answers whether at least one of the given objects can be aggregated below |
| * the given parent as composite children. This generic implementation |
| * considers type compatibility and cardinalities but no additional domain |
| * specific constraints. |
| * |
| * @param parent |
| * the composite parent |
| * @param objects |
| * the objects to check |
| * @return <code>true</code> if at least one object may be a composite child |
| * of <code>parent</code> |
| */ |
| public boolean isCompositionAllowed(EObject parent, EObject[] objects) { |
| for (final EObject object : objects) { |
| // optimistic approach: if one association can aggregate one |
| // element, we are OK |
| final List<EReference> assocs = findUsableTargetAssociations(parent, object); |
| if (assocs.size() > 0) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Duplicates the clipboard's content using EMF's deep copy service. Note |
| * that only elements from the content that are {@link EObject}s are |
| * considered, pure {@link EObject}s like packages cannot be duplicated. |
| * |
| * @param target |
| * an object acting as composite parent for the copies. |
| * <code>null</code> if the copied elements should be top-level |
| * elements. |
| * @param transactionalEditingDomain |
| * the TransactionalEditingDomain to write the copies into. Must |
| * not be <code>null</code> nor dead. |
| * @return the copy result or <code>null</code> in case of an empty |
| * clipboard |
| * @throws IllegalStateException |
| * if not called from UI thread |
| * @throws IllegalArgumentException |
| * if <code>transactionalEditingDomain</code> parameter is null |
| * @throws IllegalArgumentException |
| * if <code>transactionalEditingDomain</code> parameter is not |
| * equal to the TransactionalEditingDomain of |
| * <code>target</code> parameter |
| * @see #isCompositionAllowed(EObject, EObject[]) |
| * @see #getContentAsEObjects(ResourceSet) |
| */ |
| @SuppressWarnings("unchecked") |
| public Collection<EObject> duplicateAndPaste(final Object target, TransactionalEditingDomain transactionalEditingDomain) |
| throws IllegalStateException { |
| |
| if (transactionalEditingDomain == null) { |
| throw new IllegalStateException("TransactionalEditingDomain targetConnection should not be null"); //$NON-NLS-1$ |
| } |
| final EObject parent = GraphitiUiInternal.getEmfService().getEObject(target); |
| if (parent != null) { |
| // detect TransactionalEditingDomain mismatch to prevent |
| // modification of wrong object |
| final TransactionalEditingDomain parentEditingDomain = TransactionUtil.getEditingDomain(parent); |
| if (parentEditingDomain != null && !transactionalEditingDomain.equals(parentEditingDomain)) { |
| throw new IllegalStateException( |
| "Ambiguous TransactionalEditingDomains: transactionalEditingDomain: " + transactionalEditingDomain //$NON-NLS-1$ |
| + " <-> TransactionalEditingDomain of target object: " + parentEditingDomain //$NON-NLS-1$ |
| + ". Not clear which one to use for copy."); //$NON-NLS-1$ |
| } |
| } |
| |
| EObject[] srcObjects; |
| try { |
| srcObjects = getContentAsEObjects(transactionalEditingDomain.getResourceSet()); |
| if (srcObjects.length == 0) { |
| return null; // no or no resolvable objects in clipboard |
| } |
| |
| } catch (final OperationCanceledException e) { // $JL-EXC$ |
| return null; |
| } catch (final Exception e) { // $JL-EXC$ |
| T.racer().error(e.getMessage(), e); |
| return null; |
| } |
| |
| // subsequent operations run in a command and are rolled back in case of |
| // errors |
| final CommandStack commandStack = transactionalEditingDomain.getCommandStack(); |
| final Collection<EObject>[] copyResults = new Collection[1]; |
| try { |
| final EObject[] srcObjectsFinal = srcObjects; |
| final RecordingCommand command = new CopyCommand(transactionalEditingDomain, parent, srcObjectsFinal, copyResults); |
| commandStack.execute(command); |
| // commit |
| return copyResults[0]; |
| } catch (final OperationCanceledException e) { // $JL-EXC$ |
| // user cancelled |
| rollback(transactionalEditingDomain); |
| return null; |
| } catch (final Exception e) { // $JL-EXC$ |
| // unspecific error |
| T.racer().error(e.getMessage(), e); |
| rollback(transactionalEditingDomain); |
| return null; |
| } |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Collection<EObject> deepCopy(final EObject[] srcObjects) { |
| if (srcObjects == null) { |
| throw new IllegalArgumentException("EObject[] srcObjects must not be null"); //$NON-NLS-1$ |
| } |
| if (srcObjects.length == 0) { |
| throw new IllegalArgumentException("EObject[] srcObjects.length must not be 0"); //$NON-NLS-1$ |
| } |
| final Collection<EObject>[] result = new Collection[1]; |
| // in the case of a UI |
| if (canUseUI()) { |
| BusyIndicator.showWhile(Display.getCurrent(), new Runnable() { |
| public void run() { |
| final Copier copier = new Copier(true, true); |
| result[0] = copier.copyAll(Arrays.asList(srcObjects)); |
| copier.copyReferences(); |
| } |
| }); |
| return result[0]; |
| } |
| |
| // in the non-UI case |
| final Copier copier = new Copier(true, true); |
| result[0] = copier.copyAll(Arrays.asList(srcObjects)); |
| copier.copyReferences(); |
| return result[0]; |
| } |
| |
| /** |
| * Adds the given elements to <code>parent</code> as composite children.s |
| * |
| * @param parent |
| * parent to add the objects to. Must not be <code>null</code>. |
| * @param objects |
| * the objects to add |
| * @param association |
| * an explicit association or <code>null</code> |
| */ |
| private void addToCompositeParent(EObject parent, EObject[] objects, EReference association) { |
| if (parent == null) { |
| throw new IllegalStateException("Parent must not be null"); //$NON-NLS-1$ |
| } |
| |
| for (final EObject object : objects) { |
| final EObject objectParent = object.eContainer(); |
| if (objectParent != null) { |
| if (T.racer().debug()) { |
| final String msg = "Ignoring " + toObjectString(object) //$NON-NLS-1$ |
| + " for parent assignment. Already assigned to " + toObjectString(objectParent); //$NON-NLS-1$ |
| T.racer().debug(msg); |
| } |
| continue; |
| } |
| // Find the composition relationships to use between parent and |
| // child |
| final List<EReference> assocs = findUsableTargetAssociations(parent, object); |
| EReference assoc; |
| |
| switch (assocs.size()) { |
| case 0: |
| final String msg = "No composite associations found for " //$NON-NLS-1$ |
| + toObjectString(parent.eClass()) + " -> " //$NON-NLS-1$ |
| + toObjectString(object.eClass()); |
| // Don't issue an error here. The client might already have |
| // composed the objects. |
| T.racer().debug(msg); |
| continue; |
| case 1: |
| assoc = assocs.get(0); |
| break; |
| default: // multiple associations |
| if (association != null) { |
| if (!assocs.contains(association)) { |
| throw new IllegalStateException("Given association " + association.getName() //$NON-NLS-1$ |
| + " not valid among " + toAssociationNames(assocs)); //$NON-NLS-1$ |
| } |
| assoc = association; |
| break; |
| } else { |
| throw new IllegalStateException("Multiple associations available " //$NON-NLS-1$ |
| + toAssociationNames(assocs)); |
| } |
| } |
| |
| // Use the obtained association to compose the child into the parent |
| compose(parent, object, assoc); |
| } |
| } |
| |
| private static List<String> toAssociationNames(List<EReference> assocs) { |
| final List<String> names = new ArrayList<String>(assocs.size()); |
| for (final EReference assoc : assocs) { |
| names.add(assoc.getName()); |
| } |
| return names; |
| } |
| |
| private static String toObjectString(Object o) { |
| if (o instanceof Collection<?>) { |
| return toObjectsString((Collection<?>) o); |
| } |
| return toObjectsString(Collections.singleton(o)); |
| } |
| |
| private static String toObjectsString(Collection<?> objects) { |
| final StringBuilder b = new StringBuilder(); |
| for (final Object o : objects) { |
| if (o instanceof EObject) { |
| final EObject object = (EObject) o; |
| final String name = GraphitiUiInternal.getEmfService().getObjectName(object); |
| final String type = object.eClass().getName(); |
| b.append(type).append(" '").append(name).append("'"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } else { |
| b.append(o); |
| } |
| } |
| return b.toString(); |
| } |
| |
| /** |
| * Adds the given child to the given parent in the given association. |
| */ |
| @SuppressWarnings("unchecked") |
| private static void compose(EObject parent, EObject child, EReference assoc) { |
| final Object o = parent.eGet(assoc, true); |
| if (o instanceof List<?>) { |
| final List<EObject> list = (List<EObject>) o; |
| list.add(child); |
| } else { |
| parent.eSet(assoc, child); |
| } |
| } |
| |
| /** |
| * @return all copied elements from <code>result</code> that originate from |
| * root elements in the source |
| */ |
| private Map<EObject, EObject> getTargetRootElements(Collection<EObject> result, EObject[] srcElements) { |
| final Map<EObject, EObject> elements = new LinkedHashMap<EObject, EObject>(srcElements.length); |
| EObject[] resultArray = new EObject[result.size()]; |
| resultArray = result.toArray(resultArray); |
| for (int i = 0; i < srcElements.length; i++) { |
| elements.put(resultArray[i], srcElements[i]); |
| } |
| return elements; |
| } |
| |
| /** |
| * Finds all composite associations for the given <code>parent</code> object |
| * that reference a child of type <code>child</code>. Associations that have |
| * a upper cardinality of '1' are not contained in the returned |
| * <code>List</code>. |
| * |
| * @param parent |
| * the composite parent element. |
| * @param child |
| * the <code>EObject</code> defining the type of the composite |
| * children. |
| * @return the list containing all composite associations that fulfil the |
| * selection criteria described above. |
| */ |
| static List<EReference> findUsableTargetAssociations(EObject parent, EObject child) { |
| final List<EReference> compositeAssociations = new ArrayList<EReference>(); |
| |
| final Collection<EReference> contents = getContainmentReferences(parent.eClass()); |
| for (final Iterator<EReference> iterator = contents.iterator(); iterator.hasNext();) { |
| final EReference reference = iterator.next(); |
| final EClassifier referenceType = reference.getEType(); |
| if (referenceType.isInstance(child)) { |
| final Object value = parent.eGet(reference); |
| if (reference.getUpperBound() != 1 || value == null) { |
| compositeAssociations.add(reference); |
| } |
| } |
| |
| } |
| return compositeAssociations; |
| } |
| |
| private static Collection<EReference> getContainmentReferences(EClass eclass) { |
| final Collection<EReference> assocs = new ArrayList<EReference>(); |
| final EList<EObject> contents = eclass.eContents(); |
| for (final Iterator<EObject> iterator = contents.iterator(); iterator.hasNext();) { |
| final EObject object = iterator.next(); |
| if (object instanceof EReference) { |
| final EReference reference = (EReference) object; |
| if (reference.isContainment()) { |
| assocs.add(reference); |
| } |
| } |
| } |
| |
| final EList<EClass> superTypes = eclass.getESuperTypes(); |
| for (final Iterator<EClass> iterator = superTypes.iterator(); iterator.hasNext();) { |
| assocs.addAll(getContainmentReferences(iterator.next())); |
| } |
| |
| return assocs; |
| } |
| |
| /** |
| * Reverts the active command group. |
| */ |
| private void rollback(final TransactionalEditingDomain targetTED) { |
| try { |
| targetTED.runExclusive(new Runnable() { |
| public void run() { |
| final EList<Resource> resources = targetTED.getResourceSet().getResources(); |
| for (final Iterator<Resource> iterator = resources.iterator(); iterator.hasNext();) { |
| final Resource resource = iterator.next(); |
| resource.unload(); |
| resource.setModified(false); |
| } |
| } |
| }); |
| } catch (final InterruptedException e) { |
| } |
| } |
| |
| private synchronized List<String> getContentAsStringList() { |
| List<String> strings = Collections.emptyList(); |
| if (canUseNative()) { |
| // must run in UI thread |
| strings = getNativeContent(); |
| } |
| return strings; |
| } |
| |
| /** |
| * Sets the content of the SWT {@link clipboard} with the given objects. |
| */ |
| private void setNativeContentObjects(List<EObject> objects) { |
| final Map<Transfer, Object> nativeFormat = toTransferObjects(objects); |
| final int size = nativeFormat.size(); |
| if (size > 0) { |
| final Object[] data = nativeFormat.values().toArray(new Object[size]); |
| final Transfer[] dataTypes = nativeFormat.keySet().toArray(new Transfer[size]); |
| |
| final Clipboard cb = new Clipboard(Display.getCurrent()); |
| try { |
| cb.setContents(data, dataTypes); |
| } finally { |
| cb.dispose(); |
| } |
| } |
| } |
| |
| /** |
| * @returns currently the {@link URI} content as {@link UriTransfer}, |
| * {@link TextTransfer}, {@link FileTransfer}, and Eclipse |
| * {@link ResourceTransfer}. |
| */ |
| private synchronized Map<Transfer, Object> toTransferObjects(List<EObject> objects) { |
| final Map<Transfer, Object> empty = Collections.emptyMap(); |
| final int size = objects.size(); |
| if (size == 0) { |
| return empty; |
| } |
| |
| final List<String> uriStrings = new ArrayList<String>(size); |
| final List<IResource> files = new ArrayList<IResource>(size); |
| final List<String> filePaths = new ArrayList<String>(size); |
| for (int i = 0; i < size; i++) { |
| final EObject o = objects.get(i); |
| uriStrings.add(EcoreUtil.getURI(o).toString()); |
| IFile file = null; |
| if (isSoleContent(o)) { |
| file = GraphitiUiInternal.getEmfService().getFile(o); |
| } else { |
| file = (IFile) Platform.getAdapterManager().getAdapter(o, IFile.class); |
| } |
| |
| if (file != null && file.exists() && !files.contains(file)) { |
| files.add(file); |
| filePaths.add(file.getLocation().toOSString()); |
| } |
| } |
| final Map<Transfer, Object> result = new HashMap<Transfer, Object>(7); |
| final UriTransferData data = new UriTransferData(uriStrings); |
| ISelection localSelection = new StructuredSelection(objects); |
| LocalSelectionTransfer.getTransfer().setSelection(localSelection); |
| result.put(LocalSelectionTransfer.getTransfer(), new Object()); |
| result.put(UriTransfer.getInstance(), data); |
| if (!filePaths.isEmpty()) { |
| result.put(FileTransfer.getInstance(), filePaths.toArray(new String[filePaths.size()])); |
| // Resource Transfer resides in org.eclipse.ui.ide. We need to |
| // support an RCP scenario without having this plug-in installed. |
| try { |
| Transfer resourceTransfer = ReflectionUtil.getResourceTransfer(); |
| if (resourceTransfer != null) |
| result.put(resourceTransfer, files.toArray(new IResource[files.size()])); |
| } catch (Exception e) { |
| T.racer().debug(e.getMessage()); |
| } |
| } |
| result.put(TextTransfer.getInstance(), toExtendedString(objects)); |
| return result; |
| } |
| |
| private boolean isSoleContent(final EObject o) { |
| Resource res = o.eResource(); |
| return (res != null && res.getContents().size() == 1); |
| } |
| |
| private static List<String> getNativeContent() { |
| final Clipboard cb = new Clipboard(Display.getCurrent()); |
| try { |
| final UriTransferData contents = (UriTransferData) cb.getContents(UriTransfer.getInstance()); |
| if (contents != null) { |
| return contents.getUriStrings(); |
| } |
| return Collections.emptyList(); |
| } finally { |
| cb.dispose(); |
| } |
| } |
| |
| private String toExtendedString(List<EObject> objects) { |
| final StringBuilder result = new StringBuilder(); |
| for (int i = 0; i < objects.size(); i++) { |
| final EObject o = objects.get(i); |
| GraphitiUiInternal.getEmfService().toString(o, result); |
| if (i < objects.size() - 1) { |
| result.append(UriTransferData.LINE_SEP); |
| } |
| } |
| return result.toString(); |
| } |
| |
| @Override |
| public String toString() { |
| if (!(getContentAsStringList().size() > 0)) { |
| return EMPTY_TOSTRING_PLACEHOLDER; |
| } |
| final List<String> content = getContentAsStringList(); |
| final String[] strings = content.toArray(new String[content.size()]); |
| final StringBuilder b = new StringBuilder(); |
| for (int i = 0; i < strings.length; i++) { |
| final String s = strings[i]; |
| b.append(s); |
| if (i < strings.length - 1) { |
| b.append(UriTransferData.LINE_SEP); |
| } |
| } |
| return b.toString(); |
| } |
| |
| /** |
| * @return whether the SWT {@link Clipboard} can be accessed at all and in |
| * the current thread |
| */ |
| private synchronized boolean canUseNative() { |
| final boolean result = canUseUI(); |
| if (!result) { |
| throw new IllegalStateException("ModelClipboard must be called from UI thread."); //$NON-NLS-1$ |
| } |
| return result; |
| } |
| |
| /** |
| * @return whether Ui may be raised |
| */ |
| private static boolean canUseUI() { |
| return Display.getCurrent() != null; |
| } |
| |
| private final class CopyCommand extends RecordingCommand { |
| private final EObject parent; |
| private final EObject[] srcObjectsFinal; |
| private final Collection<EObject>[] copyResults; |
| |
| private CopyCommand(TransactionalEditingDomain domain, EObject parent, EObject[] srcObjectsFinal, Collection<EObject>[] copyResults) { |
| super(domain); |
| this.parent = parent; |
| this.srcObjectsFinal = srcObjectsFinal; |
| this.copyResults = copyResults; |
| } |
| |
| @Override |
| public String getLabel() { |
| return Messages.ModelClipBoardPasteAction_0_xfld; |
| } |
| |
| @Override |
| protected void doExecute() { |
| // actual copy |
| final Collection<EObject> copyResult = deepCopy(this.srcObjectsFinal); |
| // Process along the root elements only, avoid the effect of a child |
| // element to appear before its parent could be handled. |
| final Map<EObject, EObject> targetRootElements = getTargetRootElements(copyResult, this.srcObjectsFinal); |
| final Set<EObject> targetElements = targetRootElements.keySet(); |
| |
| final EObject[] elements = targetElements.toArray(new EObject[targetElements.size()]); |
| if (this.parent != null) { |
| addToCompositeParent(this.parent, elements, null); |
| } |
| this.copyResults[0] = copyResult; |
| } |
| } |
| } |