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