/*******************************************************************************
* Copyright (c) 2000, 2009 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 java.util.List;
import java.util.Map;
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.IJavaProject;
import org.eclipse.jdt.core.IType;
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.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.formatter.IndentManipulation;
import org.eclipse.jdt.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(IJavaElement parentElement, String source, boolean force) {
super(parentElement);
this.source= source;
this.force= force;
}
protected StructuralPropertyDescriptor getChildPropertyDescriptor(ASTNode parent) {
switch (parent.getNodeType()) {
case ASTNode.COMPILATION_UNIT:
return CompilationUnit.TYPES_PROPERTY;
case ASTNode.ENUM_DECLARATION:
return EnumDeclaration.BODY_DECLARATIONS_PROPERTY;
case ASTNode.ANNOTATION_TYPE_DECLARATION:
return AnnotationTypeDeclaration.BODY_DECLARATIONS_PROPERTY;
default:
return TypeDeclaration.BODY_DECLARATIONS_PROPERTY;
}
}
protected ASTNode generateElementAST(ASTRewrite rewriter, ICompilationUnit cu) throws JavaModelException {
if (this.createdNode == null) {
this.source= removeIndentAndNewLines(this.source, cu);
ASTParser parser= ASTParser.newParser(AST.JLS3);
parser.setSource(this.source.toCharArray());
parser.setProject(getCompilationUnit().getJavaProject());
parser.setKind(ASTParser.K_CLASS_BODY_DECLARATIONS);
ASTNode node= parser.createAST(this.progressMonitor);
String createdNodeSource;
if (node.getNodeType() != ASTNode.TYPE_DECLARATION) {
createdNodeSource= generateSyntaxIncorrectAST();
if (this.createdNode == null)
throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.INVALID_CONTENTS));
} else {
TypeDeclaration typeDeclaration= (TypeDeclaration)node;
if ((typeDeclaration.getFlags() & ASTNode.MALFORMED) != 0) {
createdNodeSource= generateSyntaxIncorrectAST();
if (this.createdNode == null)
throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.INVALID_CONTENTS));
} else {
List bodyDeclarations= typeDeclaration.bodyDeclarations();
if (bodyDeclarations.size() == 0) {
throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.INVALID_CONTENTS));
}
this.createdNode= (ASTNode)bodyDeclarations.iterator().next();
createdNodeSource= this.source;
}
}
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, ICompilationUnit cu) throws JavaModelException {
IJavaProject project= cu.getJavaProject();
Map options= project.getOptions(true/*inherit JavaCore 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= cu.findRecommendedLineSeparator();
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.jdt.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());
CompilationUnit compilationUnit= (CompilationUnit)parser.createAST(null);
TypeDeclaration typeDeclaration= (TypeDeclaration)compilationUnit.types().iterator().next();
List bodyDeclarations= typeDeclaration.bodyDeclarations();
if (bodyDeclarations.size() != 0)
this.createdNode= (ASTNode)bodyDeclarations.iterator().next();
return buff.toString();
}
/**
* Returns the IType the member is to be created in.
*/
protected IType getType() {
return (IType)getParentElement();
}
/**
* 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 IJavaModelStatus verify() {
IJavaModelStatus status= super.verify();
if (!status.isOK()) {
return status;
}
if (this.source == null) {
return new JavaModelStatus(IJavaModelStatusConstants.INVALID_CONTENTS);
}
if (!this.force) {
//check for name collisions
try {
ICompilationUnit cu= getCompilationUnit();
generateElementAST(null, cu);
} catch (JavaModelException jme) {
return jme.getJavaModelStatus();
}
return verifyNameCollision();
}
return JavaModelStatus.VERIFIED_OK;
}
/**
* Verify for a name collision in the destination container.
*/
protected IJavaModelStatus verifyNameCollision() {
return JavaModelStatus.VERIFIED_OK;
}
}