/*******************************************************************************
* 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);
}
}
}
}