package org.eclipse.jdt.internal.core; | |
/* | |
* (c) Copyright IBM Corp. 2000, 2001. | |
* All Rights Reserved. | |
*/ | |
import java.util.HashMap; | |
import java.util.Map; | |
import org.eclipse.core.resources.*; | |
import org.eclipse.core.runtime.IStatus; | |
import org.eclipse.jdt.core.*; | |
/** | |
* This class is used to perform operations on multiple <code>IJavaElement</code>. | |
* It is responible for running each operation in turn, collecting | |
* the errors and merging the corresponding <code>JavaElementDelta</code>s. | |
* <p> | |
* If several errors occured, they are collected in a multi-status | |
* <code>JavaModelStatus</code>. Otherwise, a simple <code>JavaModelStatus</code> | |
* is thrown. | |
*/ | |
public abstract class MultiOperation extends JavaModelOperation { | |
/** | |
* The list of renamings supplied to the operation | |
*/ | |
protected String[] fRenamingsList= null; | |
/** | |
* Table specifying the new parent for elements being | |
* copied/moved/renamed. | |
* Keyed by elements being processed, and | |
* values are the corresponding destination parent. | |
*/ | |
protected Map fParentElements; | |
/** | |
* Table specifying insertion positions for elements being | |
* copied/moved/renamed. Keyed by elements being processed, and | |
* values are the corresponding insertion point. | |
* @see processElements(IProgressMonitor) | |
*/ | |
protected Map fInsertBeforeElements= new HashMap(1); | |
/** | |
* This table presents the data in <code>fRenamingList</code> in a more | |
* convenient way. | |
*/ | |
protected Map fRenamings; | |
/** | |
* Creates a new <code>MultiOperation</code>. | |
*/ | |
protected MultiOperation(IJavaElement[] elementsToProcess, IJavaElement[] parentElements, boolean force) { | |
super(elementsToProcess, parentElements, force); | |
fParentElements = new HashMap(elementsToProcess.length); | |
if (elementsToProcess.length == parentElements.length) { | |
for (int i = 0; i < elementsToProcess.length; i++) { | |
fParentElements.put(elementsToProcess[i], parentElements[i]); | |
} | |
} else { //same destination for all elements to be moved/copied/renamed | |
for (int i = 0; i < elementsToProcess.length; i++) { | |
fParentElements.put(elementsToProcess[i], parentElements[0]); | |
} | |
} | |
} | |
/** | |
* Creates a new <code>MultiOperation</code> on <code>elementsToProcess</code>. | |
*/ | |
protected MultiOperation(IJavaElement[] elementsToProcess, boolean force) { | |
super(elementsToProcess, force); | |
} | |
/** | |
* Convenience method to create a <code>JavaModelException</code> | |
* embending a <code>JavaModelStatus</code>. | |
*/ | |
protected void error(int code, IJavaElement element) throws JavaModelException { | |
throw new JavaModelException(new JavaModelStatus(code, element)); | |
} | |
/** | |
* Executes the operation. | |
* | |
* @exception JavaModelException if one or several errors occured during the operation. | |
* If multiple errors occured, the corresponding <code>JavaModelStatus</code> is a | |
* multi-status. Otherwise, it is a simple one. | |
*/ | |
protected void executeOperation() throws JavaModelException { | |
try { | |
processElements(); | |
} catch (JavaModelException jme) { | |
throw jme; | |
} finally { | |
mergeDeltas(); | |
} | |
} | |
/** | |
* Returns the parent of the element being copied/moved/renamed. | |
*/ | |
protected IJavaElement getDestinationParent(IJavaElement child) { | |
return (IJavaElement)fParentElements.get(child); | |
} | |
/** | |
* Returns the name to be used by the progress monitor. | |
*/ | |
protected abstract String getMainTaskName(); | |
/** | |
* Returns the new name for <code>element</code>, or <code>null</code> | |
* if there are no renamings specified. | |
*/ | |
protected String getNewNameFor(IJavaElement element) { | |
if (fRenamings != null) | |
return (String) fRenamings.get(element); | |
else | |
return null; | |
} | |
/** | |
* Sets up the renamings hashtable - keys are the elements and | |
* values are the new name. | |
*/ | |
private void initializeRenamings() { | |
if (fRenamingsList != null && fRenamingsList.length == fElementsToProcess.length) { | |
fRenamings = new HashMap(fRenamingsList.length); | |
for (int i = 0; i < fRenamingsList.length; i++) { | |
if (fRenamingsList[i] != null) { | |
fRenamings.put(fElementsToProcess[i], fRenamingsList[i]); | |
} | |
} | |
} | |
} | |
/** | |
* Returns <code>true</code> if this operation represents a move or rename, <code>false</code> | |
* if this operation represents a copy.<br> | |
* Note: a rename is just a move within the same parent with a name change. | |
*/ | |
protected boolean isMove() { | |
return false; | |
} | |
/** | |
* Returns <code>true</code> if this operation represents a rename, <code>false</code> | |
* if this operation represents a copy or move. | |
*/ | |
protected boolean isRename() { | |
return false; | |
} | |
/** | |
* Process all of the changed deltas generated by these operations. | |
*/ | |
protected void mergeDeltas() { | |
if (fDeltas != null) { | |
JavaElementDelta rootDelta = newJavaElementDelta(); | |
boolean insertedTree = false; | |
for (int i = 0; i < fDeltas.length; i++) { | |
IJavaElementDelta delta = fDeltas[i]; | |
IJavaElementDelta[] children = delta.getAffectedChildren(); | |
for (int j = 0; j < children.length; j++) { | |
JavaElementDelta projectDelta = (JavaElementDelta) children[j]; | |
rootDelta.insertDeltaTree(projectDelta.getElement(), projectDelta); | |
insertedTree = true; | |
} | |
} | |
if (insertedTree) | |
fDeltas = new IJavaElementDelta[] {rootDelta}; | |
else | |
fDeltas = null; | |
} | |
} | |
/** | |
* Subclasses must implement this method to process a given <code>IJavaElement</code>. | |
*/ | |
protected abstract void processElement(IJavaElement element) throws JavaModelException; | |
/** | |
* Processes all the <code>IJavaElement</code>s in turn, collecting errors | |
* and updating the progress monitor. | |
* | |
* @exception JavaModelException if one or several operation(s) was unable to | |
* be completed. | |
*/ | |
protected void processElements() throws JavaModelException { | |
beginTask(getMainTaskName(), fElementsToProcess.length); | |
IJavaModelStatus[] errors = new IJavaModelStatus[3]; | |
int errorsCounter = 0; | |
for (int i = 0; i < fElementsToProcess.length; i++) { | |
try { | |
verify(fElementsToProcess[i]); | |
processElement(fElementsToProcess[i]); | |
} catch (JavaModelException jme) { | |
if (errorsCounter == errors.length) { | |
// resize | |
System.arraycopy(errors, 0, (errors = new IJavaModelStatus[errorsCounter*2]), 0, errorsCounter); | |
} | |
errors[errorsCounter++] = jme.getJavaModelStatus(); | |
} finally { | |
worked(1); | |
} | |
} | |
done(); | |
if (errorsCounter == 1) { | |
throw new JavaModelException(errors[0]); | |
} else if (errorsCounter > 1) { | |
if (errorsCounter != errors.length) { | |
// resize | |
System.arraycopy(errors, 0, (errors = new IJavaModelStatus[errorsCounter]), 0, errorsCounter); | |
} | |
throw new JavaModelException(JavaModelStatus.newMultiStatus(errors)); | |
} | |
} | |
/** | |
* Sets the insertion position in the new container for the modified element. The element | |
* being modified will be inserted before the specified new sibling. The given sibling | |
* must be a child of the destination container specified for the modified element. | |
* The default is <code>null</code>, which indicates that the element is to be | |
* inserted at the end of the container. | |
*/ | |
public void setInsertBefore(IJavaElement modifiedElement, IJavaElement newSibling) { | |
fInsertBeforeElements.put(modifiedElement, newSibling); | |
} | |
/** | |
* Sets the new names to use for each element being copied. The renamings | |
* correspond to the elements being processed, and the number of | |
* renamings must match the number of elements being processed. | |
* A <code>null</code> entry in the list indicates that an element | |
* is not to be renamed. | |
* | |
* <p>Note that some renamings may not be used. If both a parent | |
* and a child have been selected for copy/move, only the parent | |
* is changed. Therefore, if a new name is specified for the child, | |
* the child's name will not be changed. | |
*/ | |
public void setRenamings(String[] renamings) { | |
fRenamingsList = renamings; | |
initializeRenamings(); | |
} | |
/** | |
* This method is called for each <code>IJavaElement</code> before | |
* <code>processElement</code>. It should check that this <code>element</code> | |
* can be processed. | |
*/ | |
protected abstract void verify(IJavaElement element) throws JavaModelException; | |
/** | |
* Verifies that the <code>destination</code> specified for the <code>element</code> is valid for the types of the | |
* <code>element</code> and <code>destination</code>. | |
*/ | |
protected void verifyDestination(IJavaElement element, IJavaElement destination) throws JavaModelException { | |
if (destination == null || !destination.exists()) | |
error(IJavaModelStatusConstants.ELEMENT_DOES_NOT_EXIST, destination); | |
int destType = destination.getElementType(); | |
switch (element.getElementType()) { | |
case IJavaElement.PACKAGE_DECLARATION : | |
case IJavaElement.IMPORT_DECLARATION : | |
if (destType != IJavaElement.COMPILATION_UNIT) | |
error(IJavaModelStatusConstants.INVALID_DESTINATION, element); | |
break; | |
case IJavaElement.TYPE : | |
if (destType != IJavaElement.COMPILATION_UNIT && destType != IJavaElement.TYPE) | |
error(IJavaModelStatusConstants.INVALID_DESTINATION, element); | |
break; | |
case IJavaElement.METHOD : | |
case IJavaElement.FIELD : | |
case IJavaElement.INITIALIZER : | |
if (destType != IJavaElement.TYPE || destination instanceof BinaryType) | |
error(IJavaModelStatusConstants.INVALID_DESTINATION, element); | |
break; | |
case IJavaElement.COMPILATION_UNIT : | |
if (destType != IJavaElement.PACKAGE_FRAGMENT) | |
error(IJavaModelStatusConstants.INVALID_DESTINATION, element); | |
else if (isMove() && ((ICompilationUnit) element).isWorkingCopy()) | |
error(IJavaModelStatusConstants.INVALID_ELEMENT_TYPES, element); | |
break; | |
case IJavaElement.PACKAGE_FRAGMENT : | |
IPackageFragment fragment = (IPackageFragment) element; | |
IJavaElement parent = fragment.getParent(); | |
if (parent.isReadOnly()) | |
error(IJavaModelStatusConstants.READ_ONLY, element); | |
else if (destType != IJavaElement.PACKAGE_FRAGMENT_ROOT) | |
error(IJavaModelStatusConstants.INVALID_DESTINATION, element); | |
break; | |
default : | |
error(IJavaModelStatusConstants.INVALID_ELEMENT_TYPES, element); | |
} | |
} | |
/** | |
* Verify that the new name specified for <code>element</code> is | |
* valid for that type of Java element. | |
*/ | |
protected void verifyRenaming(IJavaElement element) throws JavaModelException { | |
String newName = getNewNameFor(element); | |
boolean isValid = true; | |
switch (element.getElementType()) { | |
case IJavaElement.PACKAGE_FRAGMENT : | |
if (element.getElementName().equals(IPackageFragment.DEFAULT_PACKAGE_NAME)) { | |
// don't allow renaming of default package (see PR #1G47GUM) | |
throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.NAME_COLLISION, element)); | |
} | |
isValid = JavaConventions.validatePackageName(newName).getSeverity() != IStatus.ERROR; | |
break; | |
case IJavaElement.COMPILATION_UNIT : | |
isValid = JavaConventions.validateCompilationUnitName(newName).getSeverity() != IStatus.ERROR; | |
break; | |
case IJavaElement.INITIALIZER : | |
isValid = false; //cannot rename initializers | |
break; | |
default : | |
isValid = JavaConventions.validateIdentifier(newName).getSeverity() != IStatus.ERROR; | |
break; | |
} | |
if (!isValid) { | |
throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.INVALID_NAME, element, newName)); | |
} | |
} | |
/** | |
* Verifies that the positioning sibling specified for the <code>element</code> is exists and | |
* its parent is the destination container of this <code>element</code>. | |
*/ | |
protected void verifySibling(IJavaElement element, IJavaElement destination) throws JavaModelException { | |
IJavaElement insertBeforeElement = (IJavaElement) fInsertBeforeElements.get(element); | |
if (insertBeforeElement != null) { | |
if (!insertBeforeElement.exists() || !insertBeforeElement.getParent().equals(destination)) { | |
error(IJavaModelStatusConstants.INVALID_SIBLING, insertBeforeElement); | |
} | |
} | |
} | |
} |