blob: 96f2c739e7d25329bc09a9470750765b1be1ee5f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2010 Oracle. 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:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.core.internal.utility.jdt;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.jdt.core.IField;
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.Annotation;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jpt.core.utility.jdt.ModifiedDeclaration;
import org.eclipse.jpt.utility.internal.StringTools;
import org.eclipse.jpt.utility.internal.iterators.FilteringIterator;
import org.eclipse.jpt.utility.internal.iterators.SubIteratorWrapper;
/**
* Wrap any of the AST nodes that have modifiers (specifically, annotations);
* i.e. BodyDeclaration, SingleVariableDeclaration, VariableDeclarationExpression,
* and VariableDeclarationStatement.
*/
public class JDTModifiedDeclaration
implements ModifiedDeclaration
{
private final Adapter adapter;
// ********** constructors **********
public JDTModifiedDeclaration(Adapter adapter) {
super();
this.adapter = adapter;
}
public JDTModifiedDeclaration(BodyDeclaration declaration) {
this(new BodyDeclarationAdapter(declaration));
}
public JDTModifiedDeclaration(SingleVariableDeclaration declaration) {
this(new SingleVariableDeclarationAdapter(declaration));
}
public JDTModifiedDeclaration(VariableDeclarationExpression declaration) {
this(new VariableDeclarationExpressionAdapter(declaration));
}
public JDTModifiedDeclaration(VariableDeclarationStatement declaration) {
this(new VariableDeclarationStatementAdapter(declaration));
}
// ********** annotations **********
public Annotation getAnnotationNamed(String annotationName) {
for (Iterator<Annotation> stream = this.annotations(); stream.hasNext(); ) {
Annotation annotation = stream.next();
if (this.annotationIsNamed(annotation, annotationName)) {
return annotation;
}
}
return null;
}
public void removeAnnotationNamed(String annotationName) {
for (Iterator<IExtendedModifier> stream = this.getModifiers().iterator(); stream.hasNext(); ) {
IExtendedModifier modifier = stream.next();
if (modifier.isAnnotation()) {
if (this.annotationIsNamed((Annotation) modifier, annotationName)) {
stream.remove();
break;
}
}
}
}
public void replaceAnnotationNamed(String oldAnnotationName, Annotation newAnnotation) {
List<IExtendedModifier> modifiers = this.getModifiers();
for (ListIterator<IExtendedModifier> stream = modifiers.listIterator(); stream.hasNext(); ) {
IExtendedModifier modifier = stream.next();
if (modifier.isAnnotation()) {
if (this.annotationIsNamed((Annotation) modifier, oldAnnotationName)) {
stream.set(newAnnotation);
return;
}
}
}
this.addAnnotation(newAnnotation);
}
/**
* Add the specified annotation to the declaration.
* By convention annotations precede the "standard" (JLS2) modifiers;
* though, technically, they can be interspersed.
*/
protected void addAnnotation(Annotation annotation) {
List<IExtendedModifier> modifiers = this.getModifiers();
for (ListIterator<IExtendedModifier> stream = modifiers.listIterator(); stream.hasNext(); ) {
if (stream.next().isModifier()) {
stream.previous(); // put the annotation *before* the first "standard" (JLS2) modifier
stream.add(annotation);
return;
}
}
modifiers.add(annotation); // just tack it on to the end
}
/**
* Return the declaration's annotations.
*/
protected Iterator<Annotation> annotations() {
return new SubIteratorWrapper<IExtendedModifier, Annotation>(this.annotations_());
}
protected Iterator<IExtendedModifier> annotations_() {
return new FilteringIterator<IExtendedModifier>(this.getModifiers().iterator()) {
@Override
protected boolean accept(IExtendedModifier next) {
return next.isAnnotation();
}
};
}
// ********** add import **********
public boolean addImport(String className) {
if (className.indexOf('.') == -1) {
return true; // the class is in the default package - no need for import
}
return this.addImport(className, false);
}
public boolean addStaticImport(String enumConstantName) {
int index1 = enumConstantName.indexOf('.');
if (index1 == -1) {
throw new IllegalArgumentException(enumConstantName); // shouldn't happen?
}
int index2 = enumConstantName.indexOf('.', index1 + 1);
if (index2 == -1) {
return true; // the enum is in the default package - no need for import
}
return this.addImport(enumConstantName, true);
}
public boolean addImport(String importName, boolean staticImport) {
Boolean include = this.importsInclude(importName, staticImport);
if (include != null) {
return include.booleanValue();
}
ImportDeclaration importDeclaration = this.getAst().newImportDeclaration();
importDeclaration.setName(this.getAst().newName(importName));
importDeclaration.setStatic(staticImport);
this.getImports().add(importDeclaration);
return true;
}
/**
* Just a bit hacky:
* Return Boolean.TRUE if the import is already present.
* Return Boolean.FALSE if a colliding import is already present.
* Return null if a new import may be added.
* This hackery allows us to loop through the imports only once
* (and compose our methods).
* Pre-condition: 'importName' is not in the "default" package (i.e. it *is* qualified)
*/
protected Boolean importsInclude(String importName, boolean staticImport) {
int period = importName.lastIndexOf('.'); // should not be -1
String importNameQualifier = importName.substring(0, period);
String shortImportName = importName.substring(period + 1);
return this.importsInclude(importName, importNameQualifier, shortImportName, staticImport);
}
/**
* pre-calculate the qualifier and short name
*/
protected Boolean importsInclude(String importName, String importNameQualifier, String shortImportName, boolean staticImport) {
for (ImportDeclaration importDeclaration : this.getImports()) {
if (importDeclaration.isStatic() == staticImport) {
Boolean match = this.importMatches(importDeclaration, importName, importNameQualifier, shortImportName);
if (match != null) {
return match;
}
}
}
return null;
}
/**
* we should be able to rely on the JDT model here, since we are looking
* at objects that should not be changing underneath us...
*/
protected Boolean importMatches(ImportDeclaration importDeclaration, String importName, String importNameQualifier, String shortImportName) {
// examples:
// 'importName' is "java.util.Date"
// or
// 'importName' is "java.lang.annotation.ElementType.TYPE"
String idn = importDeclaration.getName().getFullyQualifiedName();
if (importName.equals(idn)) {
// import java.util.Date; => "Date" will resolve to "java.util.Date"
// import static java.lang.annotation.ElementType.TYPE; => "TYPE" will resolve to "java.lang.annotation.ElementType.TYPE"
return Boolean.TRUE;
}
String shortIDN = idn.substring(idn.lastIndexOf('.') + 1);
if (shortImportName.equals(shortIDN)) {
// import java.sql.Date; => ambiguous resolution of "Date"
// import static org.foo.Bar.TYPE; => ambiguous resolution of "TYPE"
return Boolean.FALSE;
}
if (importDeclaration.isOnDemand()) {
if (importNameQualifier.equals(idn)) {
// import java.util.*; => "Date" will resolve to "java.util.Date"
// import static java.lang.annotation.ElementType.*; => "TYPE" will resolve to "java.lang.annotation.ElementType.TYPE"
return Boolean.TRUE;
}
if (importDeclaration.isStatic()) {
if (this.enumResolves(idn, shortImportName)) {
// import static org.foo.Bar.*; => ambiguous resolution of "TYPE"
return Boolean.FALSE;
}
} else {
if (this.typeResolves(idn + '.' + shortImportName)) {
// import java.sql.*; => ambiguous resolution of "Date"
return Boolean.FALSE;
}
}
}
// no matches - OK to add explicit import
return null;
}
protected boolean enumResolves(String enumTypeName, String enumConstantName) {
try {
return this.enumResolves_(enumTypeName, enumConstantName);
} catch (JavaModelException ex) {
throw new RuntimeException(ex);
}
}
protected boolean enumResolves_(String enumTypeName, String enumConstantName) throws JavaModelException {
IType jdtType = this.findType_(enumTypeName);
if (jdtType == null) {
return false;
}
if ( ! jdtType.isEnum()) {
return false;
}
for (IField jdtField : jdtType.getFields()) {
if (jdtField.isEnumConstant() && jdtField.getElementName().equals(enumConstantName)) {
return true;
}
}
return false;
}
protected boolean typeResolves(String name) {
return this.findType(name) != null;
}
protected IType findType(String name) {
try {
return this.findType_(name);
} catch (JavaModelException ex) {
throw new RuntimeException(ex);
}
}
protected IType findType_(String name) throws JavaModelException {
return this.getCompilationUnit().getJavaElement().getJavaProject().findType(name);
}
protected List<ImportDeclaration> getImports() {
return this.imports(this.getCompilationUnit());
}
// minimize scope of suppressed warnings
@SuppressWarnings("unchecked")
protected List<ImportDeclaration> imports(CompilationUnit astRoot) {
return astRoot.imports();
}
// ********** annotation name resolution **********
public boolean annotationIsNamed(Annotation annotation, String name) {
return this.getQualifiedName(annotation).equals(name);
}
/**
* Simply return the annotation's unqualified name if we can't "resolve" it.
*/
protected String getQualifiedName(Annotation annotation) {
ITypeBinding typeBinding = annotation.resolveTypeBinding();
if (typeBinding != null) {
String resolvedName = typeBinding.getQualifiedName();
if (resolvedName != null) {
return resolvedName;
}
}
// hack(?): check for a matching import because when moving a stand-alone
// annotation to its container in CombinationIndexedDeclarationAnnotationAdapter
// the container's import is added but then it won't "resolve" upon
// subsequent lookups (because the parser hasn't had time to run?)... :-(
return this.convertToFullClassName(annotation.getTypeName().getFullyQualifiedName());
}
/**
* If necessary, use the declaration's imports to calculate a guess as to
* the specified name's fully-qualified form.
* Simply return the unqualified name if we can't "resolve" it.
*/
protected String convertToFullClassName(String name) {
// check for fully-qualified name
return (name.lastIndexOf('.') != -1) ? name : this.resolveAgainstImports(name, false);
}
/**
* If necessary, use the declaration's imports to calculate a guess as to
* the specified name's fully-qualified form.
* Simply return the unqualified name if we can't "resolve" it.
*/
protected String convertToFullEnumConstantName(String name) {
int index1 = name.indexOf('.');
if (index1 == -1) {
// short name, e.g. "TYPE"
// true = look for static import of enum constant
return this.resolveAgainstImports(name, true);
}
int index2 = name.indexOf('.', index1 + 1);
if (index2 == -1) {
// partially-qualified name, e.g. "ElementType.TYPE"
// false = look regular import of enum class, not static import of enum constant
return this.resolveAgainstImports(name, false);
}
// fully-qualified name, e.g. "java.lang.annotation.ElementType.TYPE"
return name;
}
/**
* Attempt to resolve the specified "short" name against the declaration's
* imports. Return the name unchanged if we can't resolve it (perhaps it is
* in the "default" package).
*/
protected String resolveAgainstImports(String shortName, boolean static_) {
for (ImportDeclaration importDeclaration : this.getImports()) {
if (importDeclaration.isStatic() == static_) {
String resolvedName = this.resolveAgainstImport(importDeclaration, shortName);
if (resolvedName != null) {
return resolvedName;
}
}
}
return shortName; // "default" package or unknown
}
/**
* Attempt to resolve the specified "short" name against the specified
* import. Return the resolved name if the import resolves it; otherwise
* return null.
*/
protected String resolveAgainstImport(ImportDeclaration importDeclaration, String shortName) {
String idn = importDeclaration.getName().getFullyQualifiedName();
if (importDeclaration.isOnDemand()) {
String candidate = idn + '.' + shortName;
if (importDeclaration.isStatic()) {
if (this.enumResolves(idn, shortName)) {
return candidate;
}
} else {
if (this.typeResolves(candidate)) {
return candidate;
}
}
// no match
return null;
}
// explicit import - see whether its end matches 'shortName'
int period = idn.length() - shortName.length() - 1;
if (period < 1) {
// something must precede period
return null;
}
if ((idn.charAt(period) == '.') && idn.endsWith(shortName)) {
return idn; // probable exact match
}
return null;
}
// ********** miscellaneous methods **********
public ASTNode getDeclaration() {
return this.adapter.getDeclaration();
}
/**
* Return the declaration's list of modifiers.
* Element type: org.eclipse.jdt.core.dom.IExtendedModifier
*/
protected List<IExtendedModifier> getModifiers() {
return this.adapter.getModifiers();
}
public AST getAst() {
return this.getDeclaration().getAST();
}
protected CompilationUnit getCompilationUnit() {
return (CompilationUnit) this.getDeclaration().getRoot();
}
@Override
public String toString() {
return StringTools.buildToStringFor(this, this.adapter.toString());
}
// ********** declaration adapter interface and implementations **********
/**
* Define common protocol among the various "declarations".
*/
public interface Adapter {
/**
* Return the adapted "declaration".
*/
ASTNode getDeclaration();
/**
* Return the "declaration"'s list of modifiers.
* Element type: org.eclipse.jdt.core.dom.IExtendedModifier
*/
List<IExtendedModifier> getModifiers();
}
public static class BodyDeclarationAdapter implements Adapter {
private final BodyDeclaration declaration;
public BodyDeclarationAdapter(BodyDeclaration declaration) {
super();
this.declaration = declaration;
}
public ASTNode getDeclaration() {
return this.declaration;
}
@SuppressWarnings("unchecked")
public List<IExtendedModifier> getModifiers() {
return this.declaration.modifiers();
}
@Override
public String toString() {
return StringTools.buildToStringFor(this, this.declaration.toString());
}
}
public static class SingleVariableDeclarationAdapter implements Adapter {
private final SingleVariableDeclaration declaration;
public SingleVariableDeclarationAdapter(SingleVariableDeclaration declaration) {
super();
this.declaration = declaration;
}
public ASTNode getDeclaration() {
return this.declaration;
}
@SuppressWarnings("unchecked")
public List<IExtendedModifier> getModifiers() {
return this.declaration.modifiers();
}
@Override
public String toString() {
return StringTools.buildToStringFor(this, this.declaration.toString());
}
}
public static class VariableDeclarationExpressionAdapter implements Adapter {
private final VariableDeclarationExpression declaration;
public VariableDeclarationExpressionAdapter(VariableDeclarationExpression declaration) {
super();
this.declaration = declaration;
}
public ASTNode getDeclaration() {
return this.declaration;
}
@SuppressWarnings("unchecked")
public List<IExtendedModifier> getModifiers() {
return this.declaration.modifiers();
}
@Override
public String toString() {
return StringTools.buildToStringFor(this, this.declaration.toString());
}
}
public static class VariableDeclarationStatementAdapter implements Adapter {
private final VariableDeclarationStatement declaration;
public VariableDeclarationStatementAdapter(VariableDeclarationStatement declaration) {
super();
this.declaration = declaration;
}
public ASTNode getDeclaration() {
return this.declaration;
}
@SuppressWarnings("unchecked")
public List<IExtendedModifier> getModifiers() {
return this.declaration.modifiers();
}
@Override
public String toString() {
return StringTools.buildToStringFor(this, this.declaration.toString());
}
}
}