/******************************************************************************* * Copyright (c) 2000, 2008 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.jdt.internal.core; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IJavaModelStatus; import org.eclipse.jdt.core.IJavaModelStatusConstants; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor; import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; import org.eclipse.jdt.core.dom.rewrite.ListRewrite; import org.eclipse.jdt.internal.core.util.Util; import org.eclipse.text.edits.TextEdit; /** * <p> * This abstract class implements behavior common to <code>CreateElementInCUOperations</code>. To * create a compilation unit, or an element contained in a compilation unit, the source code for the * entire compilation unit is updated and saved. * * <p> * The element being created can be positioned relative to an existing element in the compilation * unit via the methods <code>#createAfter</code> and <code>#createBefore</code>. By default, the * new element is positioned as the last child of its parent element. * */ public abstract class CreateElementInCUOperation extends JavaModelOperation { /** * The compilation unit AST used for this operation */ protected CompilationUnit cuAST; /** * A constant meaning to position the new element as the last child of its parent element. */ protected static final int INSERT_LAST= 1; /** * A constant meaning to position the new element after the element defined by * <code>fAnchorElement</code>. */ protected static final int INSERT_AFTER= 2; /** * A constant meaning to position the new element before the element defined by * <code>fAnchorElement</code>. */ protected static final int INSERT_BEFORE= 3; /** * One of the position constants, describing where to position the newly created element. */ protected int insertionPolicy= INSERT_LAST; /** * The element that the newly created element is positioned relative to, as described by * <code>fInsertPosition</code>, or <code>null</code> if the newly created element will be * positioned last. */ protected IJavaElement anchorElement= null; /** * A flag indicating whether creation of a new element occurred. A request for creating a * duplicate element would request in this flag being set to <code>false</code>. Ensures that no * deltas are generated when creation does not occur. */ protected boolean creationOccurred= true; /** * Constructs an operation that creates a Java Language Element with the specified parent, * contained within a compilation unit. */ public CreateElementInCUOperation(IJavaElement parentElement) { super(null, new IJavaElement[] { parentElement }); initializeDefaultPosition(); } /** * Only allow cancelling if this operation is not nested. */ protected void checkCanceled() { if (!this.isNested) { super.checkCanceled(); } } /** * Instructs this operation to position the new element after the given sibling, or to add the * new element as the last child of its parent if <code>null</code>. */ public void createAfter(IJavaElement sibling) { setRelativePosition(sibling, INSERT_AFTER); } /** * Instructs this operation to position the new element before the given sibling, or to add the * new element as the last child of its parent if <code>null</code>. */ public void createBefore(IJavaElement sibling) { setRelativePosition(sibling, INSERT_BEFORE); } /** * Execute the operation - generate new source for the compilation unit and save the results. * * @exception JavaModelException if the operation is unable to complete */ protected void executeOperation() throws JavaModelException { try { beginTask(getMainTaskName(), getMainAmountOfWork()); JavaElementDelta delta= newJavaElementDelta(); ICompilationUnit unit= getCompilationUnit(); generateNewCompilationUnitAST(unit); if (this.creationOccurred) { //a change has really occurred unit.save(null, false); boolean isWorkingCopy= unit.isWorkingCopy(); if (!isWorkingCopy) setAttribute(HAS_MODIFIED_RESOURCE_ATTR, TRUE); worked(1); this.resultElements= generateResultHandles(); if (!isWorkingCopy // if unit is working copy, then save will have already fired the delta && !Util.isExcluded(unit) && unit.getParent().exists()) { for (int i= 0; i < this.resultElements.length; i++) { delta.added(this.resultElements[i]); } addDelta(delta); } // else unit is created outside classpath // non-java resource delta will be notified by delta processor } } finally { done(); } } /* * Returns the property descriptor for the element being created. */ protected abstract StructuralPropertyDescriptor getChildPropertyDescriptor(ASTNode parent); /* * Returns an AST node for the element being created. */ protected abstract ASTNode generateElementAST(ASTRewrite rewriter, ICompilationUnit cu) throws JavaModelException; /* * Generates a new AST for this operation and applies it to the given cu */ protected void generateNewCompilationUnitAST(ICompilationUnit cu) throws JavaModelException { this.cuAST= parse(cu); AST ast= this.cuAST.getAST(); ASTRewrite rewriter= ASTRewrite.create(ast); ASTNode child= generateElementAST(rewriter, cu); if (child != null) { ASTNode parent= ((JavaElement)getParentElement()).findNode(this.cuAST); if (parent == null) parent= this.cuAST; insertASTNode(rewriter, parent, child); TextEdit edits= rewriter.rewriteAST(); applyTextEdit(cu, edits); } worked(1); } /** * Creates and returns the handle for the element this operation created. */ protected abstract IJavaElement generateResultHandle(); /** * Creates and returns the handles for the elements this operation created. */ protected IJavaElement[] generateResultHandles() { return new IJavaElement[] { generateResultHandle() }; } /** * Returns the compilation unit in which the new element is being created. */ protected ICompilationUnit getCompilationUnit() { return getCompilationUnitFor(getParentElement()); } /** * Returns the amount of work for the main task of this operation for progress reporting. */ protected int getMainAmountOfWork() { return 2; } /** * Returns the name of the main task of this operation for progress reporting. */ public abstract String getMainTaskName(); protected ISchedulingRule getSchedulingRule() { IResource resource= getCompilationUnit().getResource(); IWorkspace workspace= resource.getWorkspace(); return workspace.getRuleFactory().modifyRule(resource); } /** * Sets the default position in which to create the new type member. Operations that require a * different default position must override this method. */ protected void initializeDefaultPosition() { // By default, the new element is positioned as the // last child of the parent element in which it is created. } /** * Inserts the given child into the given AST, based on the position settings of this operation. * * @see #createAfter(IJavaElement) * @see #createBefore(IJavaElement) */ protected void insertASTNode(ASTRewrite rewriter, ASTNode parent, ASTNode child) throws JavaModelException { StructuralPropertyDescriptor propertyDescriptor= getChildPropertyDescriptor(parent); if (propertyDescriptor instanceof ChildListPropertyDescriptor) { ChildListPropertyDescriptor childListPropertyDescriptor= (ChildListPropertyDescriptor)propertyDescriptor; ListRewrite rewrite= rewriter.getListRewrite(parent, childListPropertyDescriptor); switch (this.insertionPolicy) { case INSERT_BEFORE: ASTNode element= ((JavaElement)this.anchorElement).findNode(this.cuAST); if (childListPropertyDescriptor.getElementType().isAssignableFrom(element.getClass())) rewrite.insertBefore(child, element, null); else // case of an empty import list: the anchor element is the top level type and cannot be used in insertBefore as it is not the same type rewrite.insertLast(child, null); break; case INSERT_AFTER: element= ((JavaElement)this.anchorElement).findNode(this.cuAST); if (childListPropertyDescriptor.getElementType().isAssignableFrom(element.getClass())) rewrite.insertAfter(child, element, null); else // case of an empty import list: the anchor element is the top level type and cannot be used in insertAfter as it is not the same type rewrite.insertLast(child, null); break; case INSERT_LAST: rewrite.insertLast(child, null); break; } } else { rewriter.set(parent, propertyDescriptor, child, null); } } protected CompilationUnit parse(ICompilationUnit cu) throws JavaModelException { // ensure cu is consistent (noop if already consistent) cu.makeConsistent(this.progressMonitor); // create an AST for the compilation unit ASTParser parser= ASTParser.newParser(AST.JLS3); parser.setSource(cu); return (CompilationUnit)parser.createAST(this.progressMonitor); } /** * Sets the name of the <code>DOMNode</code> that will be used to create this new element. Used * by the <code>CopyElementsOperation</code> for renaming. Only used for * <code>CreateTypeMemberOperation</code> */ protected void setAlteredName(String newName) { // implementation in CreateTypeMemberOperation } /** * Instructs this operation to position the new element relative to the given sibling, or to add * the new element as the last child of its parent if <code>null</code>. The * <code>position</code> must be one of the position constants. */ protected void setRelativePosition(IJavaElement sibling, int policy) throws IllegalArgumentException { if (sibling == null) { this.anchorElement= null; this.insertionPolicy= INSERT_LAST; } else { this.anchorElement= sibling; this.insertionPolicy= policy; } } /** * Possible failures: * <ul> * <li>NO_ELEMENTS_TO_PROCESS - the compilation unit supplied to the operation is * <code>null</code>. * <li>INVALID_NAME - no name, a name was null or not a valid import declaration name. * <li>INVALID_SIBLING - the sibling provided for positioning is not valid. * </ul> * * @see IJavaModelStatus * @see org.eclipse.jdt.core.JavaConventions */ public IJavaModelStatus verify() { if (getParentElement() == null) { return new JavaModelStatus(IJavaModelStatusConstants.NO_ELEMENTS_TO_PROCESS); } if (this.anchorElement != null) { IJavaElement domPresentParent= this.anchorElement.getParent(); if (domPresentParent.getElementType() == IJavaElement.IMPORT_CONTAINER) { domPresentParent= domPresentParent.getParent(); } if (!domPresentParent.equals(getParentElement())) { return new JavaModelStatus(IJavaModelStatusConstants.INVALID_SIBLING, this.anchorElement); } } return JavaModelStatus.VERIFIED_OK; } }