/******************************************************************************* * Copyright (c) 2000, 2016 QNX Software Systems 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: * QNX Software Systems - Initial API and implementation * Anton Leherbauer (Wind River Systems) - Fixed bug 141484 *******************************************************************************/ package org.eclipse.cdt.internal.core.model; import org.eclipse.cdt.core.model.CModelException; import org.eclipse.cdt.core.model.IBuffer; import org.eclipse.cdt.core.model.ICElement; import org.eclipse.cdt.core.model.ICModelStatus; import org.eclipse.cdt.core.model.ICModelStatusConstants; import org.eclipse.cdt.core.model.IParent; import org.eclipse.cdt.core.model.ISourceRange; import org.eclipse.cdt.core.model.ISourceReference; import org.eclipse.cdt.core.model.ITranslationUnit; /** * This operation copies/moves a collection of elements from their current * container to a new container, optionally renaming the * elements. * <p>Notes:<ul> * <li>If there is already an element with the same name in * the new container, the operation either overwrites or aborts, * depending on the collision policy setting. The default setting is * abort. * * <li>When constructors are copied to a type, the constructors * are automatically renamed to the name of the destination * type. * * <li>When main types are renamed (move within the same parent), * the compilation unit and constructors are automatically renamed * * <li>The collection of elements being copied must all share the * same type of container (for example, must all be type members). * * <li>The elements are inserted in the new container in the order given. * * <li>The elements can be positioned in the new container - see #setInsertBefore. * By default, the elements are inserted based on the default positions as specified in * the creation operation for that element type. * * <li>This operation can be used to copy and rename elements within * the same container. * * <li>This operation only copies elements contained within compilation units. * </ul> * */ public class CopyElementsOperation extends MultiOperation { /** * When executed, this operation will copy the given elements to the * given containers. The elements and destination containers must be in * the correct order. If there is > 1 destination, the number of destinations * must be the same as the number of elements being copied/moved/renamed. */ public CopyElementsOperation(ICElement[] elementsToCopy, ICElement[] destContainers, boolean force) { super(elementsToCopy, destContainers, force); } /** * When executed, this operation will copy the given elements to the * given container. */ public CopyElementsOperation(ICElement[] elementsToCopy, ICElement destContainer, boolean force) { this(elementsToCopy, new ICElement[]{destContainer}, force); } /** * Returns the <code>String</code> to use as the main task name * for progress monitoring. */ @Override protected String getMainTaskName() { return CoreModelMessages.getString("operation.copyElementProgress"); //$NON-NLS-1$ } /** * Returns the nested operation to use for processing this element */ protected CModelOperation getNestedOperation(ICElement element) { ICElement parentElement = getDestinationParent(element); String name = element.getElementName(); int type = element.getElementType(); return new CreateSourceReferenceOperation(parentElement, name, type, getSourceFor(element)); } protected ITranslationUnit getDestinationTranslationUnit(ICElement element) { ICElement dest = getDestinationParent(element); return (ITranslationUnit)dest.getAncestor(ICElement.C_UNIT); } protected ITranslationUnit getSourceTranslationUnit(ICElement element) { return (ITranslationUnit)element.getAncestor(ICElement.C_UNIT); } /** * Returns the cached source for this element or compute it if not already cached. */ private String getSourceFor(ICElement element) { if (element instanceof ISourceReference) { // TODO: remove this hack when we have ASTRewrite and doit properly try { ISourceReference source = (ISourceReference)element; ISourceRange range = source.getSourceRange(); String contents = source.getSource(); StringBuilder sb = new StringBuilder(contents); // Copy the extra spaces and newLines like it is part of // the element. Note: the DeleteElementAction is doing the same. IBuffer buffer = getSourceTranslationUnit(element).getBuffer(); boolean newLineFound = false; for (int offset = range.getStartPos() + range.getLength();;++offset) { try { char c = buffer.getChar(offset); // TODO:Bug in the Parser, it does not give the semicolon if (c == ';') { sb.append(c) ; } else if (c == '\r' || c == '\n') { newLineFound = true; sb.append(c) ; } else if (!newLineFound && c == ' ') { // Do not include the spaces after the newline sb.append(c) ; } else { break; } } catch (Exception e) { break; } } contents = sb.toString(); if (! contents.endsWith(Util.LINE_SEPARATOR)) { contents += Util.LINE_SEPARATOR; } return contents; } catch (CModelException e) { // } } return ""; //$NON-NLS-1$ } /** * Copy/move the element from the source to destination, renaming * the elements as specified, honoring the collision policy. * * @exception CModelException if the operation is unable to * be completed */ @Override protected void processElement(ICElement element) throws CModelException { CModelOperation op = getNestedOperation(element); if (op == null) { return; } boolean isInTUOperation = op instanceof CreateElementInTUOperation; if (isInTUOperation && isMove()) { DeleteElementsOperation deleteOp = new DeleteElementsOperation(new ICElement[] { element }, fForce); executeNestedOperation(deleteOp, 1); } if (isInTUOperation) { CreateElementInTUOperation inTUop = (CreateElementInTUOperation)op; ICElement sibling = fInsertBeforeElements.get(element); if (sibling != null) { (inTUop).setRelativePosition(sibling, CreateElementInTUOperation.INSERT_BEFORE); } else if (isRename()) { ICElement anchor = resolveRenameAnchor(element); if (anchor != null) { inTUop.setRelativePosition(anchor, CreateElementInTUOperation.INSERT_AFTER); // insert after so that the anchor is found before when deleted below } } String newName = getNewNameFor(element); if (newName != null) { inTUop.setAlteredName(newName); } } executeNestedOperation(op, 1); ITranslationUnit destUnit = getDestinationTranslationUnit(element); if (!destUnit.isWorkingCopy()) { destUnit.close(); } } /** * Returns the anchor used for positioning in the destination for * the element being renamed. For renaming, if no anchor has * explicitly been provided, the element is anchored in the same position. */ private ICElement resolveRenameAnchor(ICElement element) throws CModelException { IParent parent = (IParent) element.getParent(); ICElement[] children = parent.getChildren(); for (ICElement child : children) { if (child.equals(element)) { return child; } } return null; } /** * Possible failures: * <ul> * <li>NO_ELEMENTS_TO_PROCESS - no elements supplied to the operation * <li>INDEX_OUT_OF_BOUNDS - the number of renamings supplied to the operation * does not match the number of elements that were supplied. * </ul> */ @Override protected ICModelStatus verify() { ICModelStatus status = super.verify(); if (!status.isOK()) { return status; } if (fRenamingsList != null && fRenamingsList.length != fElementsToProcess.length) { return new CModelStatus(ICModelStatusConstants.INDEX_OUT_OF_BOUNDS); } return CModelStatus.VERIFIED_OK; } /** * @see MultiOperation * * Possible failure codes: * <ul> * * <li>ELEMENT_DOES_NOT_EXIST - <code>element</code> or its specified destination is * is <code>null</code> or does not exist. If a <code>null</code> element is * supplied, no element is provided in the status, otherwise, the non-existant element * is supplied in the status. * <li>INVALID_ELEMENT_TYPES - <code>element</code> is not contained within a compilation unit. * This operation only operates on elements contained within compilation units. * <li>READ_ONLY - <code>element</code> is read only. * <li>INVALID_DESTINATION - The destination parent specified for <code>element</code> * is of an incompatible type. The destination for a package declaration or import declaration must * be a compilation unit; the destination for a type must be a type or compilation * unit; the destinaion for any type member (other than a type) must be a type. When * this error occurs, the element provided in the operation status is the <code>element</code>. * <li>INVALID_NAME - the new name for <code>element</code> does not have valid syntax. * In this case the element and name are provided in the status. * </ul> */ @Override protected void verify(ICElement element) throws CModelException { if (element == null || !element.exists()) error(ICModelStatusConstants.ELEMENT_DOES_NOT_EXIST, element); else { if (element.getElementType() < ICElement.C_UNIT) error(ICModelStatusConstants.INVALID_ELEMENT_TYPES, element); if (element.isReadOnly()) error(ICModelStatusConstants.READ_ONLY, element); ICElement dest = getDestinationParent(element); verifyDestination(element, dest); verifySibling(element, dest); if (fRenamingsList != null) { verifyRenaming(element); } } } }