| /******************************************************************************* |
| * 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.wst.jsdt.internal.core; |
| |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.TextUtilities; |
| import org.eclipse.wst.jsdt.core.IJavaScriptElement; |
| import org.eclipse.wst.jsdt.core.IJavaScriptModelStatus; |
| import org.eclipse.wst.jsdt.core.IJavaScriptModelStatusConstants; |
| import org.eclipse.wst.jsdt.core.IJavaScriptProject; |
| import org.eclipse.wst.jsdt.core.IJavaScriptUnit; |
| import org.eclipse.wst.jsdt.core.IType; |
| import org.eclipse.wst.jsdt.core.JavaScriptModelException; |
| import org.eclipse.wst.jsdt.core.dom.AST; |
| import org.eclipse.wst.jsdt.core.dom.ASTNode; |
| import org.eclipse.wst.jsdt.core.dom.ASTParser; |
| import org.eclipse.wst.jsdt.core.dom.AbstractTypeDeclaration; |
| import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit; |
| import org.eclipse.wst.jsdt.core.dom.SimpleName; |
| import org.eclipse.wst.jsdt.core.dom.StructuralPropertyDescriptor; |
| import org.eclipse.wst.jsdt.core.dom.TypeDeclaration; |
| import org.eclipse.wst.jsdt.core.dom.rewrite.ASTRewrite; |
| import org.eclipse.wst.jsdt.core.formatter.IndentManipulation; |
| import org.eclipse.wst.jsdt.internal.compiler.parser.ScannerHelper; |
| |
| /** |
| * Implements functionality common to |
| * operations that create type members. |
| */ |
| public abstract class CreateTypeMemberOperation extends CreateElementInCUOperation { |
| /** |
| * The source code for the new member. |
| */ |
| protected String source = null; |
| /** |
| * The name of the <code>ASTNode</code> that may be used to |
| * create this new element. |
| * Used by the <code>CopyElementsOperation</code> for renaming |
| */ |
| protected String alteredName; |
| /** |
| * The AST node representing the element that |
| * this operation created. |
| */ |
| protected ASTNode createdNode; |
| /** |
| * When executed, this operation will create a type member |
| * in the given parent element with the specified source. |
| */ |
| public CreateTypeMemberOperation(IJavaScriptElement parentElement, String source, boolean force) { |
| super(parentElement); |
| this.source = source; |
| this.force = force; |
| } |
| protected StructuralPropertyDescriptor getChildPropertyDescriptor(ASTNode parent) { |
| switch (parent.getNodeType()) { |
| case ASTNode.JAVASCRIPT_UNIT: |
| if (createdNode instanceof AbstractTypeDeclaration) |
| return JavaScriptUnit.TYPES_PROPERTY; |
| else |
| return JavaScriptUnit.STATEMENTS_PROPERTY; |
| default: |
| return TypeDeclaration.BODY_DECLARATIONS_PROPERTY; |
| } |
| } |
| protected ASTNode generateElementAST(ASTRewrite rewriter, IDocument document, IJavaScriptUnit cu) throws JavaScriptModelException { |
| if (this.createdNode == null) { |
| this.source = removeIndentAndNewLines(this.source, document, cu); |
| ASTParser parser = ASTParser.newParser(AST.JLS3); |
| parser.setSource(this.source.toCharArray()); |
| parser.setProject(getCompilationUnit().getJavaScriptProject()); |
| parser.setKind(ASTParser.K_CLASS_BODY_DECLARATIONS); |
| ASTNode node = parser.createAST(this.progressMonitor); |
| String createdNodeSource; |
| if (node.getNodeType() == ASTNode.JAVASCRIPT_UNIT) { |
| JavaScriptUnit compilationUnit = (JavaScriptUnit) node; |
| this.createdNode = (ASTNode) compilationUnit.statements().iterator().next(); |
| createdNodeSource = this.source; |
| } |
| else if (node.getNodeType() == ASTNode.TYPE_DECLARATION) { |
| TypeDeclaration typeDeclaration = (TypeDeclaration) node; |
| this.createdNode = (ASTNode) typeDeclaration.bodyDeclarations().iterator().next(); |
| createdNodeSource = this.source; |
| } |
| else { |
| createdNodeSource = generateSyntaxIncorrectAST(); |
| if (this.createdNode == null) |
| throw new JavaScriptModelException(new JavaModelStatus(IJavaScriptModelStatusConstants.INVALID_CONTENTS)); |
| } |
| if (this.alteredName != null) { |
| SimpleName newName = this.createdNode.getAST().newSimpleName(this.alteredName); |
| SimpleName oldName = rename(this.createdNode, newName); |
| int nameStart = oldName.getStartPosition(); |
| int nameEnd = nameStart + oldName.getLength(); |
| StringBuffer newSource = new StringBuffer(); |
| if (this.source.equals(createdNodeSource)) { |
| newSource.append(createdNodeSource.substring(0, nameStart)); |
| newSource.append(this.alteredName); |
| newSource.append(createdNodeSource.substring(nameEnd)); |
| } else { |
| // syntactically incorrect source |
| int createdNodeStart = this.createdNode.getStartPosition(); |
| int createdNodeEnd = createdNodeStart + this.createdNode.getLength(); |
| newSource.append(createdNodeSource.substring(createdNodeStart, nameStart)); |
| newSource.append(this.alteredName); |
| newSource.append(createdNodeSource.substring(nameEnd, createdNodeEnd)); |
| |
| } |
| this.source = newSource.toString(); |
| } |
| } |
| if (rewriter == null) return this.createdNode; |
| // return a string place holder (instead of the created node) so has to not lose comments and formatting |
| return rewriter.createStringPlaceholder(this.source, this.createdNode.getNodeType()); |
| } |
| private String removeIndentAndNewLines(String code, IDocument document, IJavaScriptUnit cu) { |
| IJavaScriptProject project = cu.getJavaScriptProject(); |
| Map options = project.getOptions(true/*inherit JavaScriptCore options*/); |
| int tabWidth = IndentManipulation.getTabWidth(options); |
| int indentWidth = IndentManipulation.getIndentWidth(options); |
| int indent = IndentManipulation.measureIndentUnits(code, tabWidth, indentWidth); |
| int firstNonWhiteSpace = -1; |
| int length = code.length(); |
| while (firstNonWhiteSpace < length-1) |
| if (!ScannerHelper.isWhitespace(code.charAt(++firstNonWhiteSpace))) |
| break; |
| int lastNonWhiteSpace = length; |
| while (lastNonWhiteSpace > 0) |
| if (!ScannerHelper.isWhitespace(code.charAt(--lastNonWhiteSpace))) |
| break; |
| String lineDelimiter = TextUtilities.getDefaultLineDelimiter(document); |
| return IndentManipulation.changeIndent(code.substring(firstNonWhiteSpace, lastNonWhiteSpace+1), indent, tabWidth, indentWidth, "", lineDelimiter); //$NON-NLS-1$ |
| } |
| /* |
| * Renames the given node to the given name. |
| * Returns the old name. |
| */ |
| protected abstract SimpleName rename(ASTNode node, SimpleName newName); |
| /** |
| * Generates an <code>ASTNode</code> based on the source of this operation |
| * when there is likely a syntax error in the source. |
| * Returns the source used to generate this node. |
| */ |
| protected String generateSyntaxIncorrectAST() { |
| //create some dummy source to generate an ast node |
| StringBuffer buff = new StringBuffer(); |
| // IType type = getType(); |
| // String lineSeparator = org.eclipse.wst.jsdt.internal.core.util.Util.getLineSeparator(this.source, type == null ? null : type.getJavaProject()); |
| // buff.append(lineSeparator + " public class A {" + lineSeparator); //$NON-NLS-1$ |
| buff.append(this.source); |
| // buff.append(lineSeparator).append('}'); |
| ASTParser parser = ASTParser.newParser(AST.JLS3); |
| parser.setSource(buff.toString().toCharArray()); |
| JavaScriptUnit compilationUnit = (JavaScriptUnit) parser.createAST(null); |
| |
| // TypeDeclaration typeDeclaration = (TypeDeclaration) compilationUnit.types().iterator().next(); |
| List statements = compilationUnit.statements() ; |
| if (statements.size() != 0) |
| this.createdNode = (ASTNode) statements.iterator().next(); |
| return buff.toString(); |
| } |
| /** |
| * Returns the IType the member is to be created in. |
| */ |
| protected IType getType() { |
| IJavaScriptElement parentElement = getParentElement(); |
| return (parentElement instanceof IType) ? (IType)parentElement : null; |
| } |
| /** |
| * Sets the name of the <code>ASTNode</code> that will be used to |
| * create this new element. |
| * Used by the <code>CopyElementsOperation</code> for renaming |
| */ |
| protected void setAlteredName(String newName) { |
| this.alteredName = newName; |
| } |
| /** |
| * Possible failures: <ul> |
| * <li>NO_ELEMENTS_TO_PROCESS - the parent element supplied to the operation is |
| * <code>null</code>. |
| * <li>INVALID_CONTENTS - The source is <code>null</code> or has serious syntax errors. |
| * <li>NAME_COLLISION - A name collision occurred in the destination |
| * </ul> |
| */ |
| public IJavaScriptModelStatus verify() { |
| IJavaScriptModelStatus status = super.verify(); |
| if (!status.isOK()) { |
| return status; |
| } |
| if (this.source == null) { |
| return new JavaModelStatus(IJavaScriptModelStatusConstants.INVALID_CONTENTS); |
| } |
| if (!force) { |
| //check for name collisions |
| try { |
| IJavaScriptUnit cu = getCompilationUnit(); |
| generateElementAST(null, getDocument(cu), cu); |
| } catch (JavaScriptModelException jme) { |
| return jme.getJavaScriptModelStatus(); |
| } |
| return verifyNameCollision(); |
| } |
| |
| return JavaModelStatus.VERIFIED_OK; |
| } |
| /** |
| * Verify for a name collision in the destination container. |
| */ |
| protected IJavaScriptModelStatus verifyNameCollision() { |
| return JavaModelStatus.VERIFIED_OK; |
| } |
| } |