| /******************************************************************************* |
| * 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.common.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.PackageDeclaration; |
| 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.common.core.utility.jdt.ModifiedDeclaration; |
| import org.eclipse.jpt.common.utility.internal.StringTools; |
| import org.eclipse.jpt.common.utility.internal.iterators.FilteringIterator; |
| import org.eclipse.jpt.common.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(PackageDeclaration declaration) { |
| this(new PackageDeclarationAdapter(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 PackageDeclarationAdapter implements Adapter { |
| private final PackageDeclaration declaration; |
| public PackageDeclarationAdapter(PackageDeclaration declaration) { |
| super(); |
| this.declaration = declaration; |
| } |
| public ASTNode getDeclaration() { |
| return this.declaration; |
| } |
| @SuppressWarnings("unchecked") |
| public List<IExtendedModifier> getModifiers() { |
| return this.declaration.annotations(); |
| } |
| @Override |
| public String toString() { |
| return StringTools.buildToStringFor(this, this.declaration.toString()); |
| } |
| } |
| |
| /*public static class ASTNodeAdapter implements Adapter { |
| private final ASTNode declaration; |
| public ASTNodeAdapter(ASTNode declaration) { |
| super(); |
| this.declaration = declaration; |
| } |
| public ASTNode getDeclaration() { |
| return this.declaration; |
| } |
| @SuppressWarnings("unchecked") |
| public List<IExtendedModifier> getModifiers() { |
| if (declaration instanceof BodyDeclaration) { |
| return ((BodyDeclaration) declaration).modifiers(); |
| } else if (declaration instanceof PackageDeclaration) { |
| return ((PackageDeclaration) declaration).annotations(); |
| } |
| return Collections.emptyList(); |
| } |
| @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()); |
| } |
| } |
| |
| } |