diff options
Diffstat (limited to 'jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt')
43 files changed, 7122 insertions, 0 deletions
diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ASTNodeTextRange.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ASTNodeTextRange.java new file mode 100644 index 0000000000..1cb404987c --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ASTNodeTextRange.java @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jpt.core.utility.AbstractTextRange; + +/** + * Adapt an ASTNode to the TextRange interface. + */ +public class ASTNodeTextRange extends AbstractTextRange { + private final ASTNode astNode; + + public ASTNodeTextRange(ASTNode astNode) { + super(); + this.astNode = astNode; + } + + public int getOffset() { + return this.astNode.getStartPosition(); + } + + public int getLength() { + return this.astNode.getLength(); + } + + public int getLineNumber() { + return ((CompilationUnit) this.astNode.getRoot()).getLineNumber(this.getOffset()); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ASTTools.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ASTTools.java new file mode 100644 index 0000000000..531ae81be7 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ASTTools.java @@ -0,0 +1,206 @@ +/******************************************************************************* + * Copyright (c) 2005, 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.HashSet; +import java.util.List; + +import org.eclipse.jdt.core.ICompilationUnit; +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.Annotation; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.IAnnotationBinding; +import org.eclipse.jdt.core.dom.IBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.TypeLiteral; +import org.eclipse.jpt.utility.JavaType; +import org.eclipse.jpt.utility.MethodSignature; +import org.eclipse.jpt.utility.internal.SimpleJavaType; +import org.eclipse.jpt.utility.internal.SimpleMethodSignature; + +/** + * Convenience methods for dealing with JDT ASTs. + */ +public class ASTTools { + + /** + * Build an AST without method bodies for the specified compilation unit + * with its bindings resolved (and the resultant performance hit). + */ + public static CompilationUnit buildASTRoot(ICompilationUnit compilationUnit) { + ASTParser parser = ASTParser.newParser(AST.JLS3); + parser.setSource(compilationUnit); + parser.setIgnoreMethodBodies(true); // we don't need method bodies + parser.setResolveBindings(true); + parser.setBindingsRecovery(true); // see bugs 196200, 222735 + return (CompilationUnit) parser.createAST(null); + } + + + // ********** JDT DOM ********** + + public static String resolveEnum(Expression expression) { + if (expression == null) { + return null; + } + switch (expression.getNodeType()) { + case ASTNode.QUALIFIED_NAME: + case ASTNode.SIMPLE_NAME: + return resolveEnum((Name) expression); + default: + return null; + } + } + + public static String resolveEnum(Name enumExpression) { + IBinding binding = enumExpression.resolveBinding(); + if (binding == null) { + return null; // TODO figure out why this is null sometimes + } + if (binding.getKind() != IBinding.VARIABLE) { + return null; + } + IVariableBinding variableBinding = (IVariableBinding) binding; + return variableBinding.getType().getQualifiedName() + '.' + variableBinding.getName(); + } + + /** + * Return the fully-qualified name of the specified node's annotation + * class. + */ + public static String resolveAnnotation(Annotation node) { + IAnnotationBinding annotationBinding = node.resolveAnnotationBinding(); + if (annotationBinding == null) { + return null; + } + ITypeBinding annotationTypeBinding = annotationBinding.getAnnotationType(); + return (annotationTypeBinding == null) ? null : annotationTypeBinding.getQualifiedName(); + } + + /** + * If the specified expression is a type literal, return the type's fully- + * qualified name. Return null otherwise. + */ + public static String resolveFullyQualifiedName(Expression expression) { + ITypeBinding resolvedTypeBinding = resolveTypeBinding(expression); + return (resolvedTypeBinding == null) ? null : resolvedTypeBinding.getQualifiedName(); + } + + /** + * If the specified expression is a type literal, return the corresponding + * type binding. + */ + public static ITypeBinding resolveTypeBinding(Expression expression) { + if (expression.getNodeType() == ASTNode.TYPE_LITERAL) { + return ((TypeLiteral) expression).getType().resolveBinding(); + } + return null; + } + + public static MethodSignature buildMethodSignature(MethodDeclaration methodDeclaration) { + return new SimpleMethodSignature( + methodDeclaration.getName().getFullyQualifiedName(), + buildParameterTypes(methodDeclaration) + ); + } + + public static JavaType[] buildParameterTypes(MethodDeclaration methodDeclaration) { + List<SingleVariableDeclaration> parameters = parameters(methodDeclaration); + int len = parameters.size(); + JavaType[] parameterTypes = new JavaType[len]; + for (int i = 0; i < len; i++) { + ITypeBinding type = parameters.get(i).getType().resolveBinding(); + parameterTypes[i] = new SimpleJavaType(type.getQualifiedName(), type.getDimensions()); + } + return parameterTypes; + } + + // minimize scope of suppressed warnings + @SuppressWarnings("unchecked") + private static List<SingleVariableDeclaration> parameters(MethodDeclaration methodDeclaration) { + return methodDeclaration.parameters(); + } + + /** + * Return whether the specified expression is a type literal and the type binding + * corresponding to the specified type name exists in the type + * literal's inheritance hierarchy (superclasses and interfaces). + * Return null otherwise. + */ + public static boolean typeIsSubTypeOf(Expression expression, String searchTypeName) { + return findTypeInHierarchy(expression, searchTypeName) != null; + } + + /** + * If the specified expression is a type literal, return the type binding + * corresponding to the specified type name if it exists in the type + * literal's inheritance hierarchy (superclasses and interfaces). + * Return null otherwise. + */ + public static ITypeBinding findTypeInHierarchy(Expression expression, String searchTypeName) { + ITypeBinding typeBinding = resolveTypeBinding(expression); + return (typeBinding == null) ? null : findTypeInHierarchy(typeBinding, searchTypeName); + } + + /** + * Return whether a type binding with the specified type name exists in + * the specified type binding's inheritance hierarchy (superclasses + * and interfaces). + */ + public static boolean typeIsSubTypeOf(ITypeBinding typeBinding, String searchTypeName) { + return findTypeInHierarchy(typeBinding, searchTypeName) != null; + } + + /** + * Return the type binding corresponding to the specified type name if it + * exists in the specified type binding's inheritance hierarchy (superclasses + * and interfaces). Return null otherwise. + */ + public static ITypeBinding findTypeInHierarchy(ITypeBinding typeBinding, String searchTypeName) { + return findTypeInHierarchy(typeBinding, searchTypeName, new HashSet<String>()); + } + + private static ITypeBinding findTypeInHierarchy(ITypeBinding typeBinding, String searchTypeName, HashSet<String> visited) { + String typeName = typeBinding.getQualifiedName(); + if (visited.contains(typeName)) { + return null; + } + if (typeName.equals(searchTypeName)) { + return typeBinding; + } + visited.add(typeName); + + ITypeBinding[] interfaceBindings = typeBinding.getInterfaces(); + for (ITypeBinding interfaceBinding : interfaceBindings) { // recurse up interfaces + ITypeBinding result = findTypeInHierarchy(interfaceBinding, searchTypeName, visited); + if (result != null) { + return result; + } + } + + ITypeBinding superBinding = typeBinding.getSuperclass(); + if (superBinding != null) { // recurse up superclasses + ITypeBinding result = findTypeInHierarchy(superBinding, searchTypeName, visited); + if (result != null) { + return result; + } + } + + return null; + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/AbstractAnnotationAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/AbstractAnnotationAdapter.java new file mode 100644 index 0000000000..09631dee2e --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/AbstractAnnotationAdapter.java @@ -0,0 +1,165 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jpt.core.utility.jdt.AnnotationAdapter; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationAdapter; +import org.eclipse.jpt.core.utility.jdt.Member; +import org.eclipse.jpt.core.utility.jdt.ModifiedDeclaration; +import org.eclipse.jpt.utility.internal.StringTools; + +/** + * Adapt a member and a declaration annotation adapter. + */ +public abstract class AbstractAnnotationAdapter implements AnnotationAdapter { + private final Member member; + private final DeclarationAnnotationAdapter daa; + + + // ********** constructor ********** + + public AbstractAnnotationAdapter(Member member, DeclarationAnnotationAdapter daa) { + super(); + this.member = member; + this.daa = daa; + } + + + // ********** AnnotationAdapter implementation ********** + + public Annotation getAnnotation(CompilationUnit astRoot) { + return this.daa.getAnnotation(this.member.getModifiedDeclaration(astRoot)); + } + + public void newMarkerAnnotation() { + this.edit(this.buildNewMarkerAnnotationEditor()); + } + + public void newSingleMemberAnnotation() { + this.edit(this.buildNewSingleMemberAnnotationEditor()); + } + + public void newNormalAnnotation() { + this.edit(this.buildNewNormalAnnotationEditor()); + } + + public void removeAnnotation() { + this.edit(this.buildRemoveAnnotationEditor()); + } + + public ASTNode getAstNode(CompilationUnit astRoot) { + return this.daa.getAstNode(this.member.getModifiedDeclaration(astRoot)); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.daa); + } + + + // ********** internal methods ********** + + protected void edit(Member.Editor editor) { + this.member.edit(editor); + } + + + // ********** factory methods ********** + + protected Member.Editor buildNewMarkerAnnotationEditor() { + return new NewMarkerAnnotationEditor(this.daa); + } + + protected Member.Editor buildNewSingleMemberAnnotationEditor() { + return new NewSingleMemberAnnotationEditor(this.daa); + } + + protected Member.Editor buildNewNormalAnnotationEditor() { + return new NewNormalAnnotationEditor(this.daa); + } + + protected Member.Editor buildRemoveAnnotationEditor() { + return new RemoveAnnotationEditor(this.daa); + } + + + // ********** member classes ********** + + protected static class NewMarkerAnnotationEditor implements Member.Editor { + private final DeclarationAnnotationAdapter daa; + + NewMarkerAnnotationEditor(DeclarationAnnotationAdapter daa) { + super(); + this.daa = daa; + } + public void edit(ModifiedDeclaration declaration) { + this.daa.newMarkerAnnotation(declaration); + } + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + } + + + protected static class NewSingleMemberAnnotationEditor implements Member.Editor { + private final DeclarationAnnotationAdapter daa; + + NewSingleMemberAnnotationEditor(DeclarationAnnotationAdapter daa) { + super(); + this.daa = daa; + } + public void edit(ModifiedDeclaration declaration) { + this.daa.newSingleMemberAnnotation(declaration); + } + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + } + + + protected static class NewNormalAnnotationEditor implements Member.Editor { + private final DeclarationAnnotationAdapter daa; + + NewNormalAnnotationEditor(DeclarationAnnotationAdapter daa) { + super(); + this.daa = daa; + } + public void edit(ModifiedDeclaration declaration) { + this.daa.newNormalAnnotation(declaration); + } + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + } + + + protected static class RemoveAnnotationEditor implements Member.Editor { + private final DeclarationAnnotationAdapter daa; + + RemoveAnnotationEditor(DeclarationAnnotationAdapter daa) { + super(); + this.daa = daa; + } + public void edit(ModifiedDeclaration declaration) { + this.daa.removeAnnotation(declaration); + } + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/AbstractDeclarationAnnotationAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/AbstractDeclarationAnnotationAdapter.java new file mode 100644 index 0000000000..85c4983f16 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/AbstractDeclarationAnnotationAdapter.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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.List; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.MarkerAnnotation; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationAdapter; +import org.eclipse.jpt.core.utility.jdt.ModifiedDeclaration; +import org.eclipse.jpt.utility.internal.StringTools; + +/** + * + */ +public abstract class AbstractDeclarationAnnotationAdapter implements DeclarationAnnotationAdapter { + private final String annotationName; + + + // ********** constructors ********** + + protected AbstractDeclarationAnnotationAdapter(String annotationName) { + super(); + this.annotationName = annotationName; + } + + + // ********** DeclarationAnnotationAdapter implementation ********** + + public MarkerAnnotation newMarkerAnnotation(ModifiedDeclaration declaration) { + MarkerAnnotation annotation = this.newMarkerAnnotation(declaration.getAst()); + this.addAnnotationAndImport(declaration, annotation); + return annotation; + } + + public SingleMemberAnnotation newSingleMemberAnnotation(ModifiedDeclaration declaration) { + SingleMemberAnnotation annotation = this.newSingleMemberAnnotation(declaration.getAst()); + this.addAnnotationAndImport(declaration, annotation); + return annotation; + } + + public NormalAnnotation newNormalAnnotation(ModifiedDeclaration declaration) { + NormalAnnotation annotation = this.newNormalAnnotation(declaration.getAst()); + this.addAnnotationAndImport(declaration, annotation); + return annotation; + } + + /** + * Add the appropriate import statement, then shorten the annotation's + * name before adding it to the declaration. + */ + protected void addAnnotationAndImport(ModifiedDeclaration declaration, Annotation annotation) { + annotation.setTypeName(declaration.getAst().newName(this.getSourceCodeAnnotationName(declaration))); + this.addAnnotation(declaration, annotation); + } + + /** + * Return the annotation's name as it can be used in source code; + * i.e. if we can add it to the compilation unit's imports, return the short + * name; if we can't (because of a collision), return the fully-qualified name. + * NB: an import may be added as a side-effect :-( + */ + protected String getSourceCodeAnnotationName(ModifiedDeclaration declaration) { + return declaration.addImport(this.annotationName) ? this.getShortAnnotationName() : this.annotationName; + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.annotationName); + } + + /** + * Add the specified annotation to the specified declaration, + * replacing the original annotation if present. + */ + protected abstract void addAnnotation(ModifiedDeclaration declaration, Annotation annotation); + + + // ********** public methods ********** + + /** + * This is 'public' because we use it in CombinationIndexedDeclarationAnnotationAdapter + * to get the annotation name from a NestedIndexedDeclarationAnnotationAdapter. + */ + public String getAnnotationName() { + return this.annotationName; + } + + + // ********** helper methods ********** + + protected boolean nameMatches(ModifiedDeclaration declaration, Annotation annotation) { + return this.nameMatches(declaration, annotation, this.annotationName); + } + + protected boolean nameMatches(ModifiedDeclaration declaration, Annotation annotation, String name) { + return declaration.annotationIsNamed(annotation, name); + } + + protected MarkerAnnotation newMarkerAnnotation(AST ast) { + return this.newMarkerAnnotation(ast, this.annotationName); + } + + protected MarkerAnnotation newMarkerAnnotation(AST ast, String name) { + MarkerAnnotation annotation = ast.newMarkerAnnotation(); + annotation.setTypeName(ast.newName(name)); + return annotation; + } + + protected SingleMemberAnnotation newSingleMemberAnnotation(AST ast) { + return this.newSingleMemberAnnotation(ast, this.annotationName); + } + + protected SingleMemberAnnotation newSingleMemberAnnotation(AST ast, String name) { + SingleMemberAnnotation annotation = ast.newSingleMemberAnnotation(); + annotation.setTypeName(ast.newName(name)); + return annotation; + } + + protected NormalAnnotation newNormalAnnotation(AST ast) { + return this.newNormalAnnotation(ast, this.annotationName); + } + + protected NormalAnnotation newNormalAnnotation(AST ast, String name) { + NormalAnnotation annotation = ast.newNormalAnnotation(); + annotation.setTypeName(ast.newName(name)); + return annotation; + } + + protected String getShortAnnotationName() { + return convertToShortName(this.annotationName); + } + + protected static String convertToShortName(String name) { + return name.substring(name.lastIndexOf('.') + 1); + } + + @SuppressWarnings("unchecked") + protected List<MemberValuePair> values(NormalAnnotation na) { + return na.values(); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/AbstractExpressionConverter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/AbstractExpressionConverter.java new file mode 100644 index 0000000000..a4bde672a8 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/AbstractExpressionConverter.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jpt.core.utility.jdt.ExpressionConverter; +import org.eclipse.jpt.utility.internal.StringTools; + +/** + * Gather together the common implementation behavior. + * T is the type of the object to be converted to and from an expression. + * + * We're still figuring out Java Generics here.... The methods in this abstract + * class work fine with any subclass of Expression E; but a ClassCastException + * will occur as soon as we call any method implemented by a subclass + * (e.g. StringExpressionConverter) that expects a particular subclass of + * Expression (e.g. StringLiteral). + */ +public abstract class AbstractExpressionConverter<T> + implements ExpressionConverter<T> +{ + + protected AbstractExpressionConverter() { + super(); + } + + + // ********** object -> expression ********** + + public Expression convert(T object, AST ast) { + return (object == null) ? this.convertNull(ast) : this.convertObject(object, ast); + } + + /** + * Return the expression for a null object. By default, a null object will + * be converted into a null expression. + */ + protected Expression convertNull(@SuppressWarnings("unused") AST ast) { + return null; + } + + /** + * The specified object is not null. + * @see #convert(T, AST) + */ + protected abstract Expression convertObject(T object, AST ast); + + + // ********** expression -> object ********** + + public T convert(Expression expression) { + return (expression == null) ? this.convertNull() : this.convertExpression(expression); + } + + /** + * Return the object for a null expression. By default, a null expression will + * be converted into a null object. + */ + protected T convertNull() { + return null; + } + + /** + * The specified expression is not null. + * @see #convert(Expression) + */ + protected abstract T convertExpression(Expression expression); + + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/AbstractNestedDeclarationAnnotationAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/AbstractNestedDeclarationAnnotationAdapter.java new file mode 100644 index 0000000000..9606e65926 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/AbstractNestedDeclarationAnnotationAdapter.java @@ -0,0 +1,422 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 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.Expression; +import org.eclipse.jdt.core.dom.MarkerAnnotation; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationAdapter; +import org.eclipse.jpt.core.utility.jdt.ModifiedDeclaration; + +/** + * Pull together some of the behavior common to NestedDeclarationAnnotationAdapter + * and IndexedNestedDeclarationAnnotationAdapter + */ +public abstract class AbstractNestedDeclarationAnnotationAdapter extends AbstractDeclarationAnnotationAdapter { + private final DeclarationAnnotationAdapter outerAnnotationAdapter; + private final String elementName; + private final boolean removeOuterAnnotationWhenEmpty; + + // reduce NLS checks + protected static final String VALUE = "value"; //$NON-NLS-1$ + + + // ********** constructors ********** + + /** + * default element name is "value"; + * default behavior is to remove the outer annotation when it is empty + */ + protected AbstractNestedDeclarationAnnotationAdapter(DeclarationAnnotationAdapter outerAnnotationAdapter, String annotationName) { + this(outerAnnotationAdapter, VALUE, annotationName); + } + + /** + * default behavior is to remove the outer annotation when it is empty + */ + protected AbstractNestedDeclarationAnnotationAdapter(DeclarationAnnotationAdapter outerAnnotationAdapter, String elementName, String annotationName) { + this(outerAnnotationAdapter, elementName, annotationName, true); + } + + protected AbstractNestedDeclarationAnnotationAdapter(DeclarationAnnotationAdapter outerAnnotationAdapter, String elementName, String annotationName, boolean removeOuterAnnotationWhenEmpty) { + super(annotationName); + this.outerAnnotationAdapter = outerAnnotationAdapter; + this.elementName = elementName; + this.removeOuterAnnotationWhenEmpty = removeOuterAnnotationWhenEmpty; + } + + + // ********** DeclarationAnnotationAdapter implementation ********** + + public Annotation getAnnotation(ModifiedDeclaration declaration) { + Annotation outer = this.outerAnnotationAdapter.getAnnotation(declaration); + if (outer == null) { + return null; + } + Expression value = this.elementValue(outer); + if (value == null) { + return null; + } + Annotation inner = this.getAnnotation(value); + if (inner == null) { + return null; + } + // return the annotation only if it has a matching name(?) + return this.nameMatches(declaration, inner) ? inner : null; + } + + public void removeAnnotation(ModifiedDeclaration declaration) { + Annotation outer = this.outerAnnotationAdapter.getAnnotation(declaration); + if (outer == null) { + return; + } + Expression value = this.elementValue(outer); + if (value == null) { + return; + } + // hack to allow short-circuit when the value is an array initializer + if (this.removeAnnotation(declaration, outer, value)) { + return; + } + Annotation inner = this.annotationValue(value); + if (inner == null) { + return; + } + // remove the annotation only if it has a matching name(?) + if (this.nameMatches(declaration, inner)) { + this.removeElementAndNormalize(declaration, outer); + } + } + + public ASTNode getAstNode(ModifiedDeclaration declaration) { + // if the annotation is missing, return the outer annotation's node + Annotation annotation = this.getAnnotation(declaration); + return (annotation != null) ? annotation : this.outerAnnotationAdapter.getAstNode(declaration); + } + + @Override + protected void addAnnotation(ModifiedDeclaration declaration, Annotation inner) { + Annotation outer = this.outerAnnotationAdapter.getAnnotation(declaration); + if (outer == null) { + this.buildNewOuterAnnotation(declaration, inner); + } else if (outer.isMarkerAnnotation()) { + this.modifyAnnotation(declaration, (MarkerAnnotation) outer, inner); + } else if (outer.isSingleMemberAnnotation()) { + this.modifyAnnotation(declaration, (SingleMemberAnnotation) outer, inner); + } else if (outer.isNormalAnnotation()) { + this.modifyAnnotation(declaration, (NormalAnnotation) outer, inner); + } else { + throw new IllegalStateException("unknown annotation type: " + outer); //$NON-NLS-1$ + } + } + + + // ********** abstract methods ********** + + /** + * Return an annotation extracted from the specified expression, + * which is the value of the adapter's element. + */ + protected abstract Annotation getAnnotation(Expression value); + + /** + * Remove the annotation from the specified expression, + * which is the value of the adapter's element. + * Return whether the removal was successful. + */ + protected abstract boolean removeAnnotation(ModifiedDeclaration declaration, Annotation outer, Expression value); + + /** + * Set the value of the specified outer annotation to the + * specified inner annotation. + */ + protected abstract void modifyAnnotationValue(SingleMemberAnnotation outer, Annotation inner); + + /** + * Set the value of the specified member value pair to the + * specified inner annotation. + */ + protected abstract void modifyMemberValuePair(MemberValuePair pair, Annotation inner); + + + // ********** public methods ********** + + public DeclarationAnnotationAdapter getOuterAnnotationAdapter() { + return this.outerAnnotationAdapter; + } + + public String getElementName() { + return this.elementName; + } + + + // ********** internal methods ********** + + /** + * If the specified expression is an annotation, cast it to an annotation; + * otherwise return null. + */ + protected Annotation annotationValue(Expression expression) { + switch (expression.getNodeType()) { + case ASTNode.NORMAL_ANNOTATION: + case ASTNode.SINGLE_MEMBER_ANNOTATION: + case ASTNode.MARKER_ANNOTATION: + return (Annotation) expression; + default: + return null; + } + } + + /** + * Remove the *first* annotation element with the specified name + * from the specified annotation, converting the annotation as appropriate. + */ + protected void removeElementAndNormalize(ModifiedDeclaration declaration, Annotation outer) { + if (outer.isNormalAnnotation()) { + this.removeElementAndNormalize(declaration, (NormalAnnotation) outer); + } else if (outer.isSingleMemberAnnotation()) { + this.removeElementAndNormalize(declaration, (SingleMemberAnnotation) outer); + } else if (outer.isMarkerAnnotation()) { + this.removeElementAndNormalize(declaration, (MarkerAnnotation) outer); + } else { + throw new IllegalArgumentException("unknown annotation type: " + outer); //$NON-NLS-1$ + } + } + + /** + * Remove the *first* annotation element with the adapter's element name + * from the specified annotation. Convert the annotation to + * a marker annotation or single member annotation if appropriate. + * <pre> + * @Outer(name="Fred", foo=@Inner) => @Outer(name="Fred") + * @Outer(foo=@Inner) => @Outer + * </pre> + */ + protected void removeElementAndNormalize(ModifiedDeclaration declaration, NormalAnnotation outer) { + this.removeElement(outer); + this.normalizeAnnotation(declaration, outer); + } + + /** + * Remove from the specified annotation the element with + * the adapter's element name. + */ + protected void removeElement(NormalAnnotation annotation) { + for (Iterator<MemberValuePair> stream = this.values(annotation).iterator(); stream.hasNext(); ) { + MemberValuePair pair = stream.next(); + if (pair.getName().getFullyQualifiedName().equals(this.elementName)) { + stream.remove(); + break; + } + } + } + + /** + * Convert the specified normal annotation to a marker annotation or + * single member annotation if appropriate. + */ + protected void normalizeAnnotation(ModifiedDeclaration declaration, NormalAnnotation outer) { + List<MemberValuePair> values = this.values(outer); + switch (values.size()) { + case 0: + // if the elements are all gone, remove the annotation or convert it to a marker annotation + if (this.removeOuterAnnotationWhenEmpty) { + this.outerAnnotationAdapter.removeAnnotation(declaration); + } else { + // convert the annotation to a marker annotation + this.outerAnnotationAdapter.newMarkerAnnotation(declaration); + } + break; + case 1: + MemberValuePair pair = values.get(0); + if (pair.getName().getFullyQualifiedName().equals(VALUE)) { + // if the last remaining element is 'value', convert the annotation to a single member annotation + Expression vv = pair.getValue(); + vv = (Expression) ASTNode.copySubtree(vv.getAST(), vv); + this.outerAnnotationAdapter.newSingleMemberAnnotation(declaration).setValue(vv); + } + break; + default: + // do nothing + break; + } + } + + /** + * Convert the specified single member annotation to a marker annotation + * if the adapter's element name is "value". + */ + protected void removeElementAndNormalize(ModifiedDeclaration declaration, @SuppressWarnings("unused") SingleMemberAnnotation outer) { + if (this.elementName.equals(VALUE)) { + if (this.removeOuterAnnotationWhenEmpty) { + this.outerAnnotationAdapter.removeAnnotation(declaration); + } else { + // convert the annotation to a marker annotation + this.outerAnnotationAdapter.newMarkerAnnotation(declaration); + } + } + } + + protected void removeElementAndNormalize(ModifiedDeclaration declaration, @SuppressWarnings("unused") MarkerAnnotation outer) { + if (this.removeOuterAnnotationWhenEmpty) { + this.outerAnnotationAdapter.removeAnnotation(declaration); + } + } + + /** + * Return the value of the *first* annotation element + * with the adapter's element name. + * Return null if the annotation has no such element. + * (An element name of "value" will return the value of a single + * member annotation.) + */ + protected Expression elementValue(Annotation annotation) { + if (annotation.isNormalAnnotation()) { + return this.elementValue((NormalAnnotation) annotation); + } + if (annotation.isSingleMemberAnnotation()) { + return this.elementValue((SingleMemberAnnotation) annotation); + } + return null; + } + + protected Expression elementValue(NormalAnnotation annotation) { + MemberValuePair pair = this.memberValuePair(annotation); + return (pair == null) ? null : pair.getValue(); + } + + /** + * If the adapter's element name is "value", return the value of the + * annotation, otherwise return null. + */ + protected Expression elementValue(SingleMemberAnnotation annotation) { + return this.elementName.equals(VALUE) ? annotation.getValue() : null; + } + + /** + * Return the *first* member value pair for the adapter's element name. + * Return null if the specified annotation has no such element. + */ + protected MemberValuePair memberValuePair(NormalAnnotation annotation) { + for (MemberValuePair pair : this.values(annotation)) { + if (pair.getName().getFullyQualifiedName().equals(this.elementName)) { + return pair; + } + } + return null; + } + + /** + * Build a new outer annotation and add the specified + * inner annotation to it: + * <pre> + * @Outer(@Inner) + * </pre> + * or + * <pre> + * @Outer(foo=@Inner) + * </pre> + */ + protected void buildNewOuterAnnotation(ModifiedDeclaration declaration, Annotation inner) { + if (this.elementName.equals(VALUE)) { + this.outerAnnotationAdapter.newSingleMemberAnnotation(declaration).setValue(this.buildNewInnerExpression(inner)); + } else { + List<MemberValuePair> values = this.values(this.outerAnnotationAdapter.newNormalAnnotation(declaration)); + values.add(this.newMemberValuePair(this.buildNewInnerExpression(inner))); + } + } + + /** + * Build an expression to be added to a new outer annotation + * for the specified inner annotation. + */ + protected abstract Expression buildNewInnerExpression(Annotation inner); + + /** + * Build a new member value pair with the specified name and value. + */ + protected MemberValuePair newMemberValuePair(String name, Expression value) { + AST ast = value.getAST(); + MemberValuePair pair = ast.newMemberValuePair(); + pair.setName(ast.newSimpleName(name)); + pair.setValue(value); + return pair; + } + + /** + * Build a new member value pair with the adapter's element name + * and the specified inner annotation. + */ + protected MemberValuePair newMemberValuePair(Expression value) { + return this.newMemberValuePair(this.elementName, value); + } + + /** + * Add the specified inner annotation to the marker annotation. + */ + protected void modifyAnnotation(ModifiedDeclaration declaration, @SuppressWarnings("unused") MarkerAnnotation outer, Annotation inner) { + this.buildNewOuterAnnotation(declaration, inner); + } + + /** + * Add the specified inner annotation to the single member annotation. + */ + protected void modifyAnnotation(ModifiedDeclaration declaration, SingleMemberAnnotation outer, Annotation inner) { + if (this.elementName.equals(VALUE)) { + this.modifyAnnotationValue(outer, inner); + } else { + this.modifyAnnotationNonValue(declaration, outer, inner); + } + } + + /** + * Add the specified inner annotation to the single member annotation, + * converting it to a normal annotation: + * <pre> + * @Outer("lorem ipsum") => @Outer(value="lorem ipsum", foo=@Inner) + * </pre> + */ + protected void modifyAnnotationNonValue(ModifiedDeclaration declaration, SingleMemberAnnotation outer, Annotation inner) { + Expression vv = outer.getValue(); + vv = (Expression) ASTNode.copySubtree(vv.getAST(), vv); + NormalAnnotation newOuter = this.outerAnnotationAdapter.newNormalAnnotation(declaration); + List<MemberValuePair> values = this.values(newOuter); + values.add(this.newMemberValuePair(VALUE, vv)); + values.add(this.newMemberValuePair(this.buildNewInnerExpression(inner))); + } + + /** + * Add the specified inner annotation to the normal annotation: + * <pre> + * @Outer(bar="lorem ipsum") => @Outer(bar="lorem ipsum", foo=@Inner) + * </pre> + * or + * <pre> + * @Outer(foo=@Inner("lorem ipsum")) => @Outer(foo=@Inner) + * </pre> + */ + protected void modifyAnnotation(@SuppressWarnings("unused") ModifiedDeclaration declaration, NormalAnnotation outer, Annotation inner) { + MemberValuePair pair = this.memberValuePair(outer); + if (pair == null) { + List<MemberValuePair> values = this.values(outer); + values.add(this.newMemberValuePair(inner)); + } else { + this.modifyMemberValuePair(pair, inner); + } + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/AnnotationStringArrayExpressionConverter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/AnnotationStringArrayExpressionConverter.java new file mode 100644 index 0000000000..889b59ea63 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/AnnotationStringArrayExpressionConverter.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 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 org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ArrayInitializer; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jpt.core.utility.jdt.ExpressionConverter; + +/** + * Convert an array initializer or single expression to/from an array of + * strings (e.g. {"text0", "text1"}). + * E is the type of the expressions to be found either standalone or + * as elements in the array initializer. + */ +public class AnnotationStringArrayExpressionConverter + extends AbstractExpressionConverter<String[]> +{ + private final ExpressionConverter<String> elementConverter; + private final StringArrayExpressionConverter arrayConverter; + + + /** + * The default behavior is to remove the array initializer if it is empty. + */ + public AnnotationStringArrayExpressionConverter(ExpressionConverter<String> elementConverter) { + this(elementConverter, true); + } + + public AnnotationStringArrayExpressionConverter(ExpressionConverter<String> elementConverter, boolean removeArrayInitializerWhenEmpty) { + super(); + this.elementConverter = elementConverter; + this.arrayConverter = new StringArrayExpressionConverter(elementConverter, removeArrayInitializerWhenEmpty); + } + + /** + * if we only have a single string in the array return the single expression, + * without braces, instead of an array initializer + */ + @Override + protected Expression convertObject(String[] strings, AST ast) { + return (strings.length == 1) ? + this.elementConverter.convert(strings[0], ast) + : + this.arrayConverter.convertObject(strings, ast); + } + + @Override + protected String[] convertNull() { + return this.arrayConverter.convertNull(); + } + + /** + * check for a single expression with no surrounding braces, implying a + * single-entry array + */ + @Override + protected String[] convertExpression(Expression expression) { + return (expression.getNodeType() == ASTNode.ARRAY_INITIALIZER) ? + this.arrayConverter.convertArrayInitializer((ArrayInitializer) expression) + : + new String[] {this.elementConverter.convert(expression)}; + } + + + // ********** factory methods ********** + + /** + * Build an expression converter for an annotation element of type String[]. + * @Foo(bar={"text0", "text1"}) + * or + * @Foo(bar="text0") + */ + public static AnnotationStringArrayExpressionConverter forStrings() { + return new AnnotationStringArrayExpressionConverter(StringExpressionConverter.instance()); + } + + /** + * Build an expression converter for an annotation element of type <enum>[]. + * @Foo(bar={BAZ, BAT}) + * or + * @Foo(bar=BAZ) + */ + public static AnnotationStringArrayExpressionConverter forNames() { + return new AnnotationStringArrayExpressionConverter(NameStringExpressionConverter.instance()); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/BooleanExpressionConverter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/BooleanExpressionConverter.java new file mode 100644 index 0000000000..d6fc40d886 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/BooleanExpressionConverter.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.BooleanLiteral; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jpt.core.utility.jdt.ExpressionConverter; + +/** + * Convert a boolean literal to/from a Boolean + * (e.g. Boolean.TRUE). + */ +public final class BooleanExpressionConverter + extends AbstractExpressionConverter<Boolean> +{ + private static final ExpressionConverter<Boolean> INSTANCE = new BooleanExpressionConverter(); + + /** + * Return the singleton. + */ + public static ExpressionConverter<Boolean> instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private BooleanExpressionConverter() { + super(); + } + + @Override + protected BooleanLiteral convertObject(Boolean booleanObject, AST ast) { + return ast.newBooleanLiteral(booleanObject.booleanValue()); + } + + @Override + protected Boolean convertExpression(Expression expression) { + Object value = expression.resolveConstantExpressionValue(); + return (value instanceof Boolean) ? ((Boolean) value) : null; + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/CharacterStringExpressionConverter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/CharacterStringExpressionConverter.java new file mode 100644 index 0000000000..1da206b188 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/CharacterStringExpressionConverter.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.CharacterLiteral; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jpt.core.utility.jdt.ExpressionConverter; + +/** + * Convert a character literal to/from a string representation of a character + * (e.g. "A"). + */ +public final class CharacterStringExpressionConverter + extends AbstractExpressionConverter<String> +{ + private static final ExpressionConverter<String> INSTANCE = new CharacterStringExpressionConverter(); + + /** + * Return the singleton. + */ + public static ExpressionConverter<String> instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private CharacterStringExpressionConverter() { + super(); + } + + @Override + protected CharacterLiteral convertObject(String string, AST ast) { + CharacterLiteral characterLiteral = ast.newCharacterLiteral(); + characterLiteral.setCharValue(string.charAt(0)); + return characterLiteral; + } + + @Override + protected String convertExpression(Expression expression) { + Object value = expression.resolveConstantExpressionValue(); + return (value instanceof Character) ? ((Character) value).toString() : null; + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/CombinationIndexedDeclarationAnnotationAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/CombinationIndexedDeclarationAnnotationAdapter.java new file mode 100644 index 0000000000..e43a30e97c --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/CombinationIndexedDeclarationAnnotationAdapter.java @@ -0,0 +1,496 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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.List; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.MarkerAnnotation; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationAdapter; +import org.eclipse.jpt.core.utility.jdt.IndexedDeclarationAnnotationAdapter; +import org.eclipse.jpt.core.utility.jdt.ModifiedDeclaration; +import org.eclipse.jpt.utility.internal.StringTools; + +/** + * Manipulate an annotation that either occurs stand-alone, e.g. + * <pre> + * @Inner("zero") + * private int id; + * </pre> + * + * or is embedded in an element array within another annotation, e.g. + * <pre> + * @Outer(foo={@Inner("zero"), @Inner("one"), @Inner("two")}) + * private int id; + * + * annotationName = "Inner" + * containerAnnotationName = "Outer" + * elementName = "foo" + * index = 0-2 + * </pre> + * + * This is a useful pattern because a declaration cannot have more + * than one annotation of the same type, and allowing the stand-alone + * configuration reduces clutter. + * <br> + * NB: This configuration only makes sense for "top-level" annotations, as + * opposed to "nested" annotations. This is because annotation elements + * can only be declared with a type of a single annotation and annotations + * cannot be part of an inheritance hierarchy. + * For example, the following configurations cannot *both* be supported: + * <pre> + * @Foo(bar=@Outer(...)) + * private int id; + * + * @Foo(bar=@Inner(...)) // not allowed + * private int id; + * </pre> + * + * NB: Behavior is undefined when both the stand-alone and the nested + * configurations annotate the same declaration, e.g. + * <pre> + * // undefined behavior + * @Inner("zero") + * @Outer(foo={@Inner("zero"), @Inner("one"), @Inner("two")}) + * private int id; + * </pre> + */ +public class CombinationIndexedDeclarationAnnotationAdapter + implements IndexedDeclarationAnnotationAdapter +{ + + /** + * this adapter is used when the annotation is "stand-alone": + * <pre> + * @Inner("zero") + * </pre> + * and is only used when the index is 0 or 1 + */ + private final SimpleDeclarationAnnotationAdapter standAloneAnnotationAdapter; + + /** + * this adapter is used when the annotation is "nested": + * <pre> + * @Outer(foo={@Inner("zero"), @Inner("one")}) + * </pre> + */ + private final NestedIndexedDeclarationAnnotationAdapter nestedAnnotationAdapter; + + /** + * this adapter is for the "nested" annotation at the zero index; + * and is only used when the index is 1 + */ + private final NestedIndexedDeclarationAnnotationAdapter zeroNestedAnnotationAdapter; + + // reduce NLS checks + protected static final String VALUE = "value"; //$NON-NLS-1$ + + + // ********** constructors ********** + + /** + * default element name is "value" + * <pre> + * @Inner("zero") + * @Outer({@Inner("zero"), @Inner("one")}) + * </pre> + */ + public CombinationIndexedDeclarationAnnotationAdapter(String annotationName, String containerAnnotationName, int index) { + this(annotationName, containerAnnotationName, VALUE, index); + } + + public CombinationIndexedDeclarationAnnotationAdapter(String annotationName, String containerAnnotationName, String elementName, int index) { + this(new SimpleDeclarationAnnotationAdapter(annotationName), new SimpleDeclarationAnnotationAdapter(containerAnnotationName), elementName, index, annotationName); + } + + /** + * default element name is "value" + */ + public CombinationIndexedDeclarationAnnotationAdapter( + SimpleDeclarationAnnotationAdapter standAloneAnnotationAdapter, + SimpleDeclarationAnnotationAdapter containerAnnotationAdapter, + int index, + String nestedAnnotationName + ) { + this(standAloneAnnotationAdapter, containerAnnotationAdapter, VALUE, index, nestedAnnotationName); + } + + public CombinationIndexedDeclarationAnnotationAdapter( + SimpleDeclarationAnnotationAdapter standAloneAnnotationAdapter, + SimpleDeclarationAnnotationAdapter containerAnnotationAdapter, + String elementName, + int index, + String nestedAnnotationName + ) { + super(); + this.standAloneAnnotationAdapter = standAloneAnnotationAdapter; + this.nestedAnnotationAdapter = new NestedIndexedDeclarationAnnotationAdapter(containerAnnotationAdapter, elementName, index, nestedAnnotationName); + this.zeroNestedAnnotationAdapter = new NestedIndexedDeclarationAnnotationAdapter(containerAnnotationAdapter, elementName, 0, nestedAnnotationName); + } + + + // ********** DeclarationAnnotationAdapter implementation ********** + + public Annotation getAnnotation(ModifiedDeclaration declaration) { + if (this.getIndex() == 0) { + // check for the stand-alone annotation + Annotation standAloneAnnotation = this.getStandAloneAnnotation(declaration); + if (standAloneAnnotation != null) { + return standAloneAnnotation; + } + } + return this.getNestedAnnotation(declaration); + } + + /** + * <pre> + * [none] => @Inner + * or + * @Inner("lorem ipsum") => @Inner + * or + * @Inner(text="lorem ipsum") => @Inner + * or + * @Outer(foo={@Inner, @Inner}) => @Outer(foo={@Inner, @Inner, @Inner}) + * or + * @Outer(foo=@Inner) => @Outer(foo={@Inner, @Inner}) + * or + * @Inner => @Outer(foo={@Inner, @Inner}) + * etc. + * </pre> + */ + public MarkerAnnotation newMarkerAnnotation(ModifiedDeclaration declaration) { + return (MarkerAnnotation) this.newAnnotation(MARKER_ANNOTATION_FACTORY, declaration); + } + + public SingleMemberAnnotation newSingleMemberAnnotation(ModifiedDeclaration declaration) { + return (SingleMemberAnnotation) this.newAnnotation(SINGLE_MEMBER_ANNOTATION_FACTORY, declaration); + } + + public NormalAnnotation newNormalAnnotation(ModifiedDeclaration declaration) { + return (NormalAnnotation) this.newAnnotation(NORMAL_ANNOTATION_FACTORY, declaration); + } + + public void removeAnnotation(ModifiedDeclaration declaration) { + if (this.getIndex() == 0) { + // check for the stand-alone annotation + if (this.standAloneAnnotationIsPresent(declaration)) { + this.removeStandAloneAnnotation(declaration); + return; + } + } + this.removeNestedAnnotation(declaration); + if (this.nestedElementCanBeConvertedToStandAlone(declaration)) { + this.convertLastElementAnnotationToStandAloneAnnotation(declaration); + } + } + + public ASTNode getAstNode(ModifiedDeclaration declaration) { + // if the annotation is missing, delegate to the nested annotation adapter + Annotation annotation = this.getAnnotation(declaration); + return (annotation != null) ? annotation : this.nestedAnnotationAdapter.getAstNode(declaration); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.getAnnotationName()); + } + + + // ********** IndexedDeclarationAnnotationAdapter implementation ********** + + public int getIndex() { + return this.nestedAnnotationAdapter.getIndex(); + } + + public void moveAnnotation(int newIndex, ModifiedDeclaration declaration) { + int oldIndex = this.getIndex(); + if (newIndex == oldIndex) { + return; + } + + Annotation standAloneAnnotation = this.getStandAloneAnnotation(declaration); + if (standAloneAnnotation == null) { + this.moveNestedAnnotation(newIndex, declaration); + if (this.nestedElementCanBeConvertedToStandAlone(declaration)) { + this.convertLastElementAnnotationToStandAloneAnnotation(declaration); + } + } else { + if ((oldIndex == 0) && (newIndex == 1)) { + // this is one of two situations where we transition from standalone to container + this.moveStandAloneAnnotationToContainerAnnotation(standAloneAnnotation, declaration); + this.moveNestedAnnotation(newIndex, declaration); + } else if (newIndex == 0) { + // we are moving a 'null' entry on top of the standalone, so remove it + this.removeStandAloneAnnotation(declaration); + } else { + throw new IllegalStateException("old index = " + oldIndex + "; new index = " + newIndex); //$NON-NLS-1$ //$NON-NLS-2$ + } + } + } + + + // ********** internal methods ********** + + /** + * build the appropriate new annotation, + * which may require moving the 0th annotation from "stand-alone" to "nested" + */ + private Annotation newAnnotation(AnnotationFactory annotationFactory, ModifiedDeclaration declaration) { + if (this.getIndex() == 0) { + return this.newZeroAnnotation(annotationFactory, declaration); + } + if (this.zeroNestedAnnotationIsPresent(declaration)) { + // manipulate the container annotation - ignore the stand-alone annotation(?) + // @Outer(foo=@Inner("zero")) => @Outer(foo={@Inner("zero"), @Inner}) + // or + // @Outer(foo={@Inner("zero"), @Inner("one")}) => @Outer(foo={@Inner("zero"), @Inner}) + return annotationFactory.newAnnotation(this.nestedAnnotationAdapter, declaration); + } + + // this is one of two situations where we transition from standalone to container + this.moveStandAloneAnnotationToContainerAnnotation(declaration); + // once the stand-alone annotation is moved to index=0, build the new annotation at index=1 + return annotationFactory.newAnnotation(this.nestedAnnotationAdapter, declaration); + } + + /** + * the index is 0 - build the appropriate new annotation, + * which may be either "stand-alone" or "nested" + */ + private Annotation newZeroAnnotation(AnnotationFactory annotationFactory, ModifiedDeclaration declaration) { + if (this.standAloneAnnotationIsPresent(declaration)) { + // replace the stand-alone annotation - ignore the container annotation(?) + // @Inner(text="lorem ipsum") => @Inner + return annotationFactory.newAnnotation(this.standAloneAnnotationAdapter, declaration); + } + if (this.containerAnnotationIsPresent(declaration)) { + // manipulate the container annotation + // @Outer(foo=@Inner(text="lorem ipsum")) => @Outer(foo=@Inner) + return annotationFactory.newAnnotation(this.nestedAnnotationAdapter, declaration); + } + // neither annotation is present - add a new stand-alone annotation + return annotationFactory.newAnnotation(this.standAloneAnnotationAdapter, declaration); + } + + /** + * move the stand-alone annotation to the container annotation at index=0 + */ + private void moveStandAloneAnnotationToContainerAnnotation(ModifiedDeclaration declaration) { + Annotation standAloneAnnotation = this.getStandAloneAnnotation(declaration); + if (standAloneAnnotation == null) { + throw new IllegalStateException("the stand-alone annotation is missing"); //$NON-NLS-1$ + } + this.moveStandAloneAnnotationToContainerAnnotation(standAloneAnnotation, declaration); + } + + /** + * move the specified, non-null, stand-alone annotation to + * the container annotation at index=0 + */ + private void moveStandAloneAnnotationToContainerAnnotation(Annotation standAloneAnnotation, ModifiedDeclaration declaration) { + if (standAloneAnnotation.isMarkerAnnotation()) { + this.zeroNestedAnnotationAdapter.newMarkerAnnotation(declaration); + } else if (standAloneAnnotation.isSingleMemberAnnotation()) { + Expression vv = ((SingleMemberAnnotation) standAloneAnnotation).getValue(); + vv = (Expression) ASTNode.copySubtree(vv.getAST(), vv); + this.zeroNestedAnnotationAdapter.newSingleMemberAnnotation(declaration).setValue(vv); + } else if (standAloneAnnotation.isNormalAnnotation()) { + NormalAnnotation newNA = this.zeroNestedAnnotationAdapter.newNormalAnnotation(declaration); + List<MemberValuePair> values = this.values(newNA); + for (MemberValuePair pair : this.values((NormalAnnotation) standAloneAnnotation)) { + values.add((MemberValuePair) ASTNode.copySubtree(pair.getAST(), pair)); + } + } else { + throw new IllegalStateException("unknown annotation type: " + standAloneAnnotation); //$NON-NLS-1$ + } + this.removeStandAloneAnnotation(declaration); + } + + /** + * return whether the "nested" annotation container has been reduced to + * a single element (and the array initializer is converted to just + * the single remaining element) and can be further converted to the + * "stand-alone" annotation: + * <pre> + * @Outer(foo={@Inner("zero"), @Inner("one")}) => + * @Outer(foo=@Inner("zero")) => + * @Inner("zero") + * </pre> + */ + private boolean nestedElementCanBeConvertedToStandAlone(ModifiedDeclaration declaration) { + Annotation containerAnnotation = this.getContainerAnnotation(declaration); + if (containerAnnotation == null) { + return false; + } + if (containerAnnotation.isMarkerAnnotation()) { + return false; + } + if (containerAnnotation.isSingleMemberAnnotation()) { + if (this.getElementName().equals(VALUE)) { + return (((SingleMemberAnnotation) containerAnnotation).getValue().getNodeType() != ASTNode.ARRAY_INITIALIZER) + && (this.zeroNestedAnnotationAdapter.getAnnotation(declaration) != null); + } + return false; + } + if (containerAnnotation.isNormalAnnotation()) { + NormalAnnotation na = (NormalAnnotation) containerAnnotation; + if (na.values().size() == 0) { + return false; // there are no elements present + } + if (na.values().size() != 1) { + return false; // there are other elements present - leave them all alone + } + MemberValuePair pair = (MemberValuePair) na.values().get(0); + if (this.getElementName().equals(pair.getName().getFullyQualifiedName())) { + return (pair.getValue().getNodeType() != ASTNode.ARRAY_INITIALIZER) + && (this.zeroNestedAnnotationAdapter.getAnnotation(declaration) != null); + } + return false; + } + throw new IllegalStateException("unknown annotation type: " + containerAnnotation); //$NON-NLS-1$ + } + + /** + * move the annotation in the container annotation at index=0 + * to the stand-alone annotation + */ + private void convertLastElementAnnotationToStandAloneAnnotation(ModifiedDeclaration declaration) { + Annotation last = this.zeroNestedAnnotationAdapter.getAnnotation(declaration); + if (last == null) { + throw new IllegalStateException("the last nested annotation is missing"); //$NON-NLS-1$ + } else if (last.isMarkerAnnotation()) { + this.newStandAloneMarkerAnnotation(declaration); + } else if (last.isSingleMemberAnnotation()) { + Expression vv = ((SingleMemberAnnotation) last).getValue(); + vv = (Expression) ASTNode.copySubtree(vv.getAST(), vv); + this.newStandAloneSingleMemberAnnotation(declaration).setValue(vv); + } else if (last.isNormalAnnotation()) { + NormalAnnotation newNA = this.newStandAloneNormalAnnotation(declaration); + List<MemberValuePair> values = this.values(newNA); + for (MemberValuePair pair : this.values((NormalAnnotation) last)) { + values.add((MemberValuePair) ASTNode.copySubtree(pair.getAST(), pair)); + } + } else { + throw new IllegalStateException("unknown annotation type: " + last); //$NON-NLS-1$ + } + this.zeroNestedAnnotationAdapter.removeAnnotation(declaration); + } + + private boolean standAloneAnnotationIsPresent(ModifiedDeclaration declaration) { + return this.getStandAloneAnnotation(declaration) != null; + } + + private Annotation getStandAloneAnnotation(ModifiedDeclaration declaration) { + return this.standAloneAnnotationAdapter.getAnnotation(declaration); + } + + private MarkerAnnotation newStandAloneMarkerAnnotation(ModifiedDeclaration declaration) { + return this.standAloneAnnotationAdapter.newMarkerAnnotation(declaration); + } + + private SingleMemberAnnotation newStandAloneSingleMemberAnnotation(ModifiedDeclaration declaration) { + return this.standAloneAnnotationAdapter.newSingleMemberAnnotation(declaration); + } + + private NormalAnnotation newStandAloneNormalAnnotation(ModifiedDeclaration declaration) { + return this.standAloneAnnotationAdapter.newNormalAnnotation(declaration); + } + + private void removeStandAloneAnnotation(ModifiedDeclaration declaration) { + this.standAloneAnnotationAdapter.removeAnnotation(declaration); + } + + private Annotation getNestedAnnotation(ModifiedDeclaration declaration) { + return this.nestedAnnotationAdapter.getAnnotation(declaration); + } + + private void moveNestedAnnotation(int newIndex, ModifiedDeclaration declaration) { + this.nestedAnnotationAdapter.moveAnnotation(newIndex, declaration); + } + + private void removeNestedAnnotation(ModifiedDeclaration declaration) { + this.nestedAnnotationAdapter.removeAnnotation(declaration); + } + + private boolean containerAnnotationIsPresent(ModifiedDeclaration declaration) { + return this.getContainerAnnotation(declaration) != null; + } + + private Annotation getContainerAnnotation(ModifiedDeclaration declaration) { + return this.nestedAnnotationAdapter.getOuterAnnotationAdapter().getAnnotation(declaration); + } + + private boolean zeroNestedAnnotationIsPresent(ModifiedDeclaration declaration) { + return this.getZeroNestedAnnotation(declaration) != null; + } + + private Annotation getZeroNestedAnnotation(ModifiedDeclaration declaration) { + return this.zeroNestedAnnotationAdapter.getAnnotation(declaration); + } + + private String getAnnotationName() { + return this.nestedAnnotationAdapter.getAnnotationName(); + } + + private String getElementName() { + return this.nestedAnnotationAdapter.getElementName(); + } + + @SuppressWarnings("unchecked") + protected List<MemberValuePair> values(NormalAnnotation na) { + return na.values(); + } + + + // ********** annotation factories ********** + + /** + * define interface that allows us to "re-use" the nasty code in + * #newAnnotation(AnnotationFactory, ModifiedDeclaration) + */ + private interface AnnotationFactory { + Annotation newAnnotation(DeclarationAnnotationAdapter adapter, ModifiedDeclaration declaration); + } + + private static final AnnotationFactory MARKER_ANNOTATION_FACTORY = new AnnotationFactory() { + public Annotation newAnnotation(DeclarationAnnotationAdapter adapter, ModifiedDeclaration declaration) { + return adapter.newMarkerAnnotation(declaration); + } + @Override + public String toString() { + return "MarkerAnnotationFactory"; //$NON-NLS-1$ + } + }; + + private static final AnnotationFactory SINGLE_MEMBER_ANNOTATION_FACTORY = new AnnotationFactory() { + public Annotation newAnnotation(DeclarationAnnotationAdapter adapter, ModifiedDeclaration declaration) { + return adapter.newSingleMemberAnnotation(declaration); + } + @Override + public String toString() { + return "SingleMemberAnnotationFactory"; //$NON-NLS-1$ + } + }; + + private static final AnnotationFactory NORMAL_ANNOTATION_FACTORY = new AnnotationFactory() { + public Annotation newAnnotation(DeclarationAnnotationAdapter adapter, ModifiedDeclaration declaration) { + return adapter.newNormalAnnotation(declaration); + } + @Override + public String toString() { + return "NormalAnnotationFactory"; //$NON-NLS-1$ + } + }; + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ConversionDeclarationAnnotationElementAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ConversionDeclarationAnnotationElementAdapter.java new file mode 100644 index 0000000000..8a70c2230c --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ConversionDeclarationAnnotationElementAdapter.java @@ -0,0 +1,203 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationAdapter; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationElementAdapter; +import org.eclipse.jpt.core.utility.jdt.ExpressionConverter; +import org.eclipse.jpt.core.utility.jdt.ModifiedDeclaration; +import org.eclipse.jpt.utility.internal.StringTools; + +/** + * Wrap a declaration annotation element adapter that deals with AST + * expressions, converting them to/from various other objects. + * T is the type of the object to be passed to and returned by the adapter. + */ +public class ConversionDeclarationAnnotationElementAdapter<T> + implements DeclarationAnnotationElementAdapter<T> +{ + /** + * The wrapped adapter that returns and takes AST expressions. + */ + private final DeclarationAnnotationElementAdapter<Expression> adapter; + + /** + * The converter that converts AST expressions to other objects + * (e.g. Strings). + */ + private final ExpressionConverter<T> converter; + + + // ********** constructors ********** + + /** + * The default element name is "value"; the default behavior is to + * remove the annotation when the last element is removed. + */ + public ConversionDeclarationAnnotationElementAdapter(DeclarationAnnotationAdapter annotationAdapter, ExpressionConverter<T> converter) { + this(new ExpressionDeclarationAnnotationElementAdapter<Expression>(annotationAdapter), converter); + } + + /** + * The default behavior is to remove the annotation when the last + * element is removed. + */ + public ConversionDeclarationAnnotationElementAdapter(DeclarationAnnotationAdapter annotationAdapter, String elementName, ExpressionConverter<T> converter) { + this(new ExpressionDeclarationAnnotationElementAdapter<Expression>(annotationAdapter, elementName), converter); + } + + public ConversionDeclarationAnnotationElementAdapter(DeclarationAnnotationAdapter annotationAdapter, String elementName, boolean removeAnnotationWhenEmpty, ExpressionConverter<T> converter) { + this(new ExpressionDeclarationAnnotationElementAdapter<Expression>(annotationAdapter, elementName, removeAnnotationWhenEmpty), converter); + } + + public ConversionDeclarationAnnotationElementAdapter(DeclarationAnnotationElementAdapter<Expression> adapter, ExpressionConverter<T> converter) { + super(); + this.adapter = adapter; + this.converter = converter; + } + + + // ********** DeclarationAnnotationElementAdapter implementation ********** + + public T getValue(ModifiedDeclaration declaration) { + Expression expression = this.adapter.getValue(declaration); + return this.converter.convert(expression); + } + + public void setValue(T value, ModifiedDeclaration declaration) { + Expression expression; + try { + expression = this.converter.convert(value, declaration.getAst()); + } catch (IllegalArgumentException ex) { + // if there is a problem converting the 'value' to an Expression we get this exception + return; // don't set the value if it is "illegal" + } + this.adapter.setValue(expression, declaration); + } + + public Expression getExpression(ModifiedDeclaration declaration) { + return this.adapter.getExpression(declaration); + } + + public ASTNode getAstNode(ModifiedDeclaration declaration) { + return this.adapter.getAstNode(declaration); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.adapter); + } + + + // ********** factory static methods ********** + + /** + * The default element name is "value"; the default behavior is to + * remove the annotation when the last element is removed; + * the default expression converter expects string constant expressions. + */ + public static ConversionDeclarationAnnotationElementAdapter<String> forStrings(DeclarationAnnotationAdapter annotationAdapter) { + return new ConversionDeclarationAnnotationElementAdapter<String>(annotationAdapter, StringExpressionConverter.instance()); + } + + /** + * The default behavior is to remove the annotation when the last + * element is removed; the default expression converter expects + * string constant expressions. + */ + public static ConversionDeclarationAnnotationElementAdapter<String> forStrings(DeclarationAnnotationAdapter annotationAdapter, String elementName) { + return new ConversionDeclarationAnnotationElementAdapter<String>(annotationAdapter, elementName, StringExpressionConverter.instance()); + } + + /** + * The default expression converter expects string constant expressions. + */ + public static ConversionDeclarationAnnotationElementAdapter<String> forStrings(DeclarationAnnotationAdapter annotationAdapter, String elementName, boolean removeAnnotationWhenEmpty) { + return new ConversionDeclarationAnnotationElementAdapter<String>(annotationAdapter, elementName, removeAnnotationWhenEmpty, StringExpressionConverter.instance()); + } + + /** + * The default element name is "value"; the default behavior is to + * remove the annotation when the last element is removed; + * the default expression converter expects number constant expressions. + */ + public static ConversionDeclarationAnnotationElementAdapter<Integer> forNumbers(DeclarationAnnotationAdapter annotationAdapter) { + return new ConversionDeclarationAnnotationElementAdapter<Integer>(annotationAdapter, NumberIntegerExpressionConverter.instance()); + } + + /** + * The default behavior is to remove the annotation when the last + * element is removed; the default expression converter expects + * number constant expressions. + */ + public static ConversionDeclarationAnnotationElementAdapter<Integer> forNumbers(DeclarationAnnotationAdapter annotationAdapter, String elementName) { + return new ConversionDeclarationAnnotationElementAdapter<Integer>(annotationAdapter, elementName, NumberIntegerExpressionConverter.instance()); + } + + /** + * The default expression converter expects number constant expressions. + */ + public static ConversionDeclarationAnnotationElementAdapter<Integer> forNumbers(DeclarationAnnotationAdapter annotationAdapter, String elementName, boolean removeAnnotationWhenEmpty) { + return new ConversionDeclarationAnnotationElementAdapter<Integer>(annotationAdapter, elementName, removeAnnotationWhenEmpty, NumberIntegerExpressionConverter.instance()); + } + + /** + * The default element name is "value"; the default behavior is to + * remove the annotation when the last element is removed; + * the default expression converter expects boolean constant expressions. + */ + public static ConversionDeclarationAnnotationElementAdapter<Boolean> forBooleans(DeclarationAnnotationAdapter annotationAdapter) { + return new ConversionDeclarationAnnotationElementAdapter<Boolean>(annotationAdapter, BooleanExpressionConverter.instance()); + } + + /** + * The default behavior is to remove the annotation when the last + * element is removed; the default expression converter expects + * boolean constant expressions. + */ + public static ConversionDeclarationAnnotationElementAdapter<Boolean> forBooleans(DeclarationAnnotationAdapter annotationAdapter, String elementName) { + return new ConversionDeclarationAnnotationElementAdapter<Boolean>(annotationAdapter, elementName, BooleanExpressionConverter.instance()); + } + + /** + * The default expression converter expects boolean constant expressions. + */ + public static ConversionDeclarationAnnotationElementAdapter<Boolean> forBooleans(DeclarationAnnotationAdapter annotationAdapter, String elementName, boolean removeAnnotationWhenEmpty) { + return new ConversionDeclarationAnnotationElementAdapter<Boolean>(annotationAdapter, elementName, removeAnnotationWhenEmpty, BooleanExpressionConverter.instance()); + } + + /** + * The default element name is "value"; the default behavior is to + * remove the annotation when the last element is removed; + * the default expression converter expects character constant expressions. + */ + public static ConversionDeclarationAnnotationElementAdapter<String> forCharacters(DeclarationAnnotationAdapter annotationAdapter) { + return new ConversionDeclarationAnnotationElementAdapter<String>(annotationAdapter, CharacterStringExpressionConverter.instance()); + } + + /** + * The default behavior is to remove the annotation when the last + * element is removed; the default expression converter expects + * character constant expressions. + */ + public static ConversionDeclarationAnnotationElementAdapter<String> forCharacters(DeclarationAnnotationAdapter annotationAdapter, String elementName) { + return new ConversionDeclarationAnnotationElementAdapter<String>(annotationAdapter, elementName, CharacterStringExpressionConverter.instance()); + } + + /** + * The default expression converter expects character constant expressions. + */ + public static ConversionDeclarationAnnotationElementAdapter<String> forCharacters(DeclarationAnnotationAdapter annotationAdapter, String elementName, boolean removeAnnotationWhenEmpty) { + return new ConversionDeclarationAnnotationElementAdapter<String>(annotationAdapter, elementName, removeAnnotationWhenEmpty, CharacterStringExpressionConverter.instance()); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/DefaultAnnotationEditFormatter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/DefaultAnnotationEditFormatter.java new file mode 100644 index 0000000000..0f6cfd917d --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/DefaultAnnotationEditFormatter.java @@ -0,0 +1,219 @@ +/******************************************************************************* + * Copyright (c) 2005, 2008 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 org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jpt.core.utility.jdt.AnnotationEditFormatter; +import org.eclipse.text.edits.InsertEdit; +import org.eclipse.text.edits.MalformedTreeException; +import org.eclipse.text.edits.MultiTextEdit; +import org.eclipse.text.edits.ReplaceEdit; +import org.eclipse.text.edits.TextEdit; + +/** + * This implementation will clean up some of the nasty Eclipse annotation + * formatting (or lack thereof); e.g. arrays of annotations. + */ +public final class DefaultAnnotationEditFormatter + implements AnnotationEditFormatter +{ + private static final DefaultAnnotationEditFormatter INSTANCE = new DefaultAnnotationEditFormatter(); + + /** + * Return the singleton. + */ + public static DefaultAnnotationEditFormatter instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private DefaultAnnotationEditFormatter() { + super(); + } + + /** + * TODO + */ + public void format(IDocument doc, TextEdit editTree) throws MalformedTreeException, BadLocationException { + TextEdit[] edits = editTree.getChildren(); + int len = edits.length; + if (len == 0) { + return; + } + + MultiTextEdit extraEdits = new MultiTextEdit(); + for (int i = 0; i < len; i++) { + TextEdit edit1 = edits[i]; + if ( ! (edit1 instanceof InsertEdit)) { + continue; // if the edit is not an insert, skip to the next edit + } + InsertEdit insert1 = (InsertEdit) edit1; + int j = i + 1; + if (j < len) { + TextEdit edit2 = edits[j]; + if (edit2 instanceof InsertEdit) { + InsertEdit insert2 = (InsertEdit) edit2; + String text1 = insert1.getText(); + String text2 = insert2.getText(); + int offset1 = insert1.getOffset(); + int offset2 = insert2.getOffset(); + if (this.stringIsAnnotation(text1) && text2.equals(" ")) { + // an annotation was inserted before something on the same line; + // replace the trailing space with a newline and appropriate indent + extraEdits.addChild(new ReplaceEdit(offset2, 1, this.buildCR(doc, offset2))); + i++; // jump the index past 'edit2' + continue; // go to the next edit + } + int comma1Length = this.commaLength(text1); + if ((comma1Length != 0) && this.stringIsAnnotation(text2)) { + // an annotation was inserted in an array initializer on the + // same line as the previous array element; + // replace the preceding space with a newline and appropriate indent + extraEdits.addChild(new ReplaceEdit(offset1 + comma1Length, text1.length() - comma1Length, this.buildCR(doc, offset1))); + i++; // jump the index past 'edit2' + continue; // go to the next edit + } + } + } + this.formatArrayInitializer(doc, insert1, extraEdits); + } + extraEdits.apply(doc, TextEdit.NONE); + } + + /** + * If the insert edit is inserting an annotation containing an array of annotations as + * its value then format them nicely. + */ + private void formatArrayInitializer(IDocument doc, InsertEdit insertEdit, MultiTextEdit extraEdits) throws BadLocationException { + String s = insertEdit.getText(); + if ( ! this.stringIsAnnotation(s)) { + return; + } + int len = s.length(); + int pos = 1; // skip '@' + while (pos < len) { + char c = s.charAt(pos); + pos++; // bump to just past first '(' + if (c == '(') { + break; + } + } + if (pos == len) { + return; // reached end of string + } + while (pos < len) { + char c = s.charAt(pos); + pos++; // bump to just past first '{' + if (c == '{') { + break; + } + if (c != ' ') { + return; + } + } + if (pos == len) { + return; // reached end of string + } + // now look for '@' not inside parentheses and put in + // line delimeter and indent string before each + int offset = insertEdit.getOffset(); + String indent = null; + int parenDepth = 0; + while (pos < len) { + switch (s.charAt(pos)) { + case '(' : + parenDepth++; + break; + case ')' : + parenDepth--; + break; + case '@' : + if (parenDepth == 0) { + if (indent == null) { + indent = this.buildCR(doc, offset, "\t"); // TODO use tab preference? + } + extraEdits.addChild(new InsertEdit(offset + pos, indent)); + } + break; + case '}' : + if (parenDepth == 0) { + extraEdits.addChild(new InsertEdit(offset + pos, this.buildCR(doc, offset))); + } + break; + } + pos++; + } + } + + /** + * Build a string containing a line delimeter and indenting characters + * matching the indent level of the line containing the character offset + * (i.e. the new line's indent matches the current line). + */ + private String buildCR(IDocument doc, int offset) throws BadLocationException { + return this.buildCR(doc, offset, ""); + } + + private String buildCR(IDocument doc, int offset, String suffix) throws BadLocationException { + int line = doc.getLineOfOffset(offset); + StringBuilder sb = new StringBuilder(); + sb.append(doc.getLineDelimiter(line)); // use same CR as current line + + int o = doc.getLineOffset(line); // match the whitespace of the current line + char c = doc.getChar(o++); + while ((c == ' ') || (c == '\t')) { + sb.append(c); + c = doc.getChar(o++); + } + sb.append(suffix); + return sb.toString(); + } + + /** + * Return whether the specified string is an annotation. + */ + private boolean stringIsAnnotation(String string) { + return (string.length() > 1) && string.charAt(0) == '@'; + } + + /** + * If the specified string is a single comma, possibly surrounded by + * spaces, return the length of the substring containing the + * initial spaces and the comma. + */ + private int commaLength(String string) { + boolean comma = false; + int len = string.length(); + int result = 0; + for (int i = 0; i < len; i++) { + switch (string.charAt(i)) { + case ' ' : + if ( ! comma) { + result++; // space preceding comma + } + break; + case ',' : + if (comma) { + return 0; // second comma! + } + comma = true; + result++; + break; + default: + return 0; // non-comma, non-space char + } + } + return result; + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/EnumArrayDeclarationAnnotationElementAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/EnumArrayDeclarationAnnotationElementAdapter.java new file mode 100644 index 0000000000..92c8728bc5 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/EnumArrayDeclarationAnnotationElementAdapter.java @@ -0,0 +1,159 @@ +/******************************************************************************* + * Copyright (c) 2007, 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.List; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ArrayInitializer; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationAdapter; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationElementAdapter; +import org.eclipse.jpt.core.utility.jdt.ExpressionConverter; +import org.eclipse.jpt.core.utility.jdt.ModifiedDeclaration; + +/** + * Wrap a declaration annotation element adapter and simply + * add an import for the enums when necessary. + */ +public class EnumArrayDeclarationAnnotationElementAdapter + implements DeclarationAnnotationElementAdapter<String[]> +{ + /** + * The wrapped adapter that returns and takes name strings (enums). + */ + private final ConversionDeclarationAnnotationElementAdapter<String[]> adapter; + + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + + + // ********** constructors ********** + + /** + * The default element name is "value"; the default behavior is to + * remove the annotation when the last element is removed. + */ + public EnumArrayDeclarationAnnotationElementAdapter(DeclarationAnnotationAdapter annotationAdapter) { + this(annotationAdapter, VALUE); + } + + /** + * The default behavior is to remove the annotation when the last + * element is removed and remove the array initializer if it is empty. + */ + public EnumArrayDeclarationAnnotationElementAdapter(DeclarationAnnotationAdapter annotationAdapter, String elementName) { + this(annotationAdapter, elementName, true); + } + + /** + * The default behavior is to remove the array initializer if it is empty. + */ + public EnumArrayDeclarationAnnotationElementAdapter(DeclarationAnnotationAdapter annotationAdapter, String elementName, boolean removeAnnotationWhenEmpty) { + this(annotationAdapter, elementName, removeAnnotationWhenEmpty, true); + } + + public EnumArrayDeclarationAnnotationElementAdapter(DeclarationAnnotationAdapter annotationAdapter, String elementName, boolean removeAnnotationWhenEmpty, boolean removeArrayInitializerWhenEmpty) { + this(new ConversionDeclarationAnnotationElementAdapter<String[]>(annotationAdapter, elementName, removeAnnotationWhenEmpty, buildExpressionConverter(removeArrayInitializerWhenEmpty))); + } + + private static ExpressionConverter<String[]> buildExpressionConverter(boolean removeArrayInitializerWhenEmpty) { + return new AnnotationStringArrayExpressionConverter(NameStringExpressionConverter.instance(), removeArrayInitializerWhenEmpty); + } + + protected EnumArrayDeclarationAnnotationElementAdapter(ConversionDeclarationAnnotationElementAdapter<String[]> adapter) { + super(); + this.adapter = adapter; + } + + + // ********** DeclarationAnnotationElementAdapter implementation ********** + + public String[] getValue(ModifiedDeclaration declaration) { + // ignore the adapter's getValue() - we want the expression + return this.resolve(this.adapter.getExpression(declaration), declaration); + } + + public void setValue(String[] value, ModifiedDeclaration declaration) { + this.adapter.setValue(this.convertToSourceCodeNames(value, declaration), declaration); + } + + public Expression getExpression(ModifiedDeclaration declaration) { + return this.adapter.getExpression(declaration); + } + + public ASTNode getAstNode(ModifiedDeclaration declaration) { + return this.adapter.getAstNode(declaration); + } + + + // ********** internal methods ********** + + /** + * resolve the enums, which can be + * null + * or + * {FOO, BAR, BAZ} + * or + * FOO + */ + protected String[] resolve(Expression expression, ModifiedDeclaration declaration) { + if (expression == null) { + return EMPTY_STRING_ARRAY; + } else if (expression.getNodeType() == ASTNode.ARRAY_INITIALIZER) { + return this.resolveArray((ArrayInitializer) expression, declaration); + } else { + return this.resolveSingleElement(expression, declaration); + } + } + + protected String[] resolveArray(ArrayInitializer ai, @SuppressWarnings("unused") ModifiedDeclaration declaration) { + List<Expression> expressions = this.expressions(ai); + int len = expressions.size(); + String[] enums = new String[len]; + for (int i = len; i-- > 0; ) { + enums[i] = this.resolveEnum(expressions.get(i)); + } + return enums; + } + + protected String[] resolveSingleElement(Expression enumExpression, @SuppressWarnings("unused") ModifiedDeclaration declaration) { + return new String[] {this.resolveEnum(enumExpression)}; + } + + protected String resolveEnum(Expression expression) { + return ASTTools.resolveEnum(expression); + } + + // minimize scope of suppressd warnings + @SuppressWarnings("unchecked") + private List<Expression> expressions(ArrayInitializer arrayInitializer) { + return arrayInitializer.expressions(); + } + + /** + * convert the fully-qualified enums to names that can be inserted in source code + * NB: imports may be added as a side-effect :-( + */ + protected String[] convertToSourceCodeNames(String[] enums, ModifiedDeclaration declaration) { + if (enums == null) { + return null; + } + int len = enums.length; + String[] sourceCodeNames = new String[len]; + for (int i = 0; i < len; i++) { + sourceCodeNames[i] = this.convertToSourceCodeName(enums[i], declaration); + } + return sourceCodeNames; + } + + protected String convertToSourceCodeName(String enum_, ModifiedDeclaration declaration) { + return EnumDeclarationAnnotationElementAdapter.convertToSourceCodeName(enum_, declaration); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/EnumDeclarationAnnotationElementAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/EnumDeclarationAnnotationElementAdapter.java new file mode 100644 index 0000000000..c9ccb8d0a0 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/EnumDeclarationAnnotationElementAdapter.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * 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 org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationAdapter; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationElementAdapter; +import org.eclipse.jpt.core.utility.jdt.ModifiedDeclaration; + +/** + * Wrap a declaration annotation element adapter and simply + * add an import for the enum when necessary. + */ +public class EnumDeclarationAnnotationElementAdapter + implements DeclarationAnnotationElementAdapter<String> +{ + /** + * The wrapped adapter that returns and takes name strings (enums). + */ + private final ConversionDeclarationAnnotationElementAdapter<String> adapter; + + + // ********** constructors ********** + + /** + * The default element name is "value"; the default behavior is to + * remove the annotation when the last element is removed. + */ + public EnumDeclarationAnnotationElementAdapter(DeclarationAnnotationAdapter annotationAdapter) { + this(annotationAdapter, VALUE); + } + + /** + * The default behavior is to remove the annotation when the last + * element is removed. + */ + public EnumDeclarationAnnotationElementAdapter(DeclarationAnnotationAdapter annotationAdapter, String elementName) { + this(annotationAdapter, elementName, true); + } + + public EnumDeclarationAnnotationElementAdapter(DeclarationAnnotationAdapter annotationAdapter, String elementName, boolean removeAnnotationWhenEmpty) { + this(new ConversionDeclarationAnnotationElementAdapter<String>(annotationAdapter, elementName, removeAnnotationWhenEmpty, NameStringExpressionConverter.instance())); + } + + protected EnumDeclarationAnnotationElementAdapter(ConversionDeclarationAnnotationElementAdapter<String> adapter) { + super(); + this.adapter = adapter; + } + + + // ********** DeclarationAnnotationElementAdapter implementation ********** + + public String getValue(ModifiedDeclaration declaration) { + return this.resolve(this.adapter.getExpression(declaration)); + } + + public void setValue(String value, ModifiedDeclaration declaration) { + this.adapter.setValue(convertToSourceCodeName(value, declaration), declaration); + } + + public Expression getExpression(ModifiedDeclaration declaration) { + return this.adapter.getExpression(declaration); + } + + public ASTNode getAstNode(ModifiedDeclaration declaration) { + return this.adapter.getAstNode(declaration); + } + + + // ********** internal methods ********** + + /** + * resolve the enum + */ + protected String resolve(Expression expression) { + return ASTTools.resolveEnum(expression); + } + + /** + * convert the fully-qualified enum constant to a static import and the constant's short name, e.g. + * static import javax.persistence.FetchType.EAGER; + * return "EAGER" + * if that doesn't work, convert to a normal import and the constant's partially-qualified name, e.g. + * import javax.persistence.FetchType; + * return "FetchType.EAGER" + * if that doesn't work, simply return the constant's fully-qualified name, e.g. + * return "javax.persistence.FetchType.EAGER" + * NB: an import may be added as a side-effect :-( + */ + protected static String convertToSourceCodeName(String enumConstantName, ModifiedDeclaration declaration) { + return (enumConstantName == null) ? null : convertToSourceCodeName_(enumConstantName, declaration); + } + + /** + * pre-condition: enum constant name is non-null; + * convert it to its short version if we can add a static import etc. + */ + protected static String convertToSourceCodeName_(String enumConstantName, ModifiedDeclaration declaration) { + if (declaration.addStaticImport(enumConstantName)) { + return convertToShortName(enumConstantName); + } + if (declaration.addImport(convertToTypeName(enumConstantName))) { + return convertToPartiallyQualifiedName(enumConstantName); + } + return enumConstantName; + } + + protected static String convertToShortName(String name) { + return name.substring(name.lastIndexOf('.') + 1); + } + + protected static String convertToTypeName(String name) { + return name.substring(0, name.lastIndexOf('.')); + } + + protected static String convertToPartiallyQualifiedName(String name) { + return name.substring(name.lastIndexOf('.', name.lastIndexOf('.') - 1) + 1); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ExpressionDeclarationAnnotationElementAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ExpressionDeclarationAnnotationElementAdapter.java new file mode 100644 index 0000000000..b96a7de027 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ExpressionDeclarationAnnotationElementAdapter.java @@ -0,0 +1,362 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 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.Expression; +import org.eclipse.jdt.core.dom.MarkerAnnotation; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationAdapter; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationElementAdapter; +import org.eclipse.jpt.core.utility.jdt.ModifiedDeclaration; +import org.eclipse.jpt.utility.internal.StringTools; + +/** + * Most obvious implementation of the interface. + * Assume the element's value is an Expression. + */ +public class ExpressionDeclarationAnnotationElementAdapter<E extends Expression> + implements DeclarationAnnotationElementAdapter<E> +{ + /** + * Adapter used to manipulate the element's annotation. + */ + private final DeclarationAnnotationAdapter annotationAdapter; + + /** + * The name of the relevant annotation element. + */ + private final String elementName; + + /** + * Flag to indicate whether the element's annotation is to be + * completely removed if, when the element itself is removed, + * the annotation has no remaining elements. + */ + private final boolean removeAnnotationWhenEmpty; + + + // ********** constructors ********** + + /** + * The default element name is "value"; the default behavior is to + * remove the annotation when the last element is removed. + */ + public ExpressionDeclarationAnnotationElementAdapter(DeclarationAnnotationAdapter annotationAdapter) { + this(annotationAdapter, VALUE); + } + + /** + * The default element name is "value". + */ + public ExpressionDeclarationAnnotationElementAdapter(DeclarationAnnotationAdapter annotationAdapter, boolean removeAnnotationWhenEmpty) { + this(annotationAdapter, VALUE, removeAnnotationWhenEmpty); + } + + /** + * The default behavior is to remove the annotation when the last + * element is removed. + */ + public ExpressionDeclarationAnnotationElementAdapter(DeclarationAnnotationAdapter annotationAdapter, String elementName) { + this(annotationAdapter, elementName, true); + } + + public ExpressionDeclarationAnnotationElementAdapter(DeclarationAnnotationAdapter annotationAdapter, String elementName, boolean removeAnnotationWhenEmpty) { + super(); + this.annotationAdapter = annotationAdapter; + this.elementName = elementName; + this.removeAnnotationWhenEmpty = removeAnnotationWhenEmpty; + } + + + // ********** DeclarationAnnotationElementAdapter implementation ********** + + public E getValue(ModifiedDeclaration declaration) { + // return the expression unmodified + return this.getExpression(declaration); + } + + public void setValue(E value, ModifiedDeclaration declaration) { + this.setValue(value, this.annotationAdapter.getAnnotation(declaration), declaration); + } + + public E getExpression(ModifiedDeclaration declaration) { + return this.expression(this.annotationAdapter.getAnnotation(declaration)); + } + + public ASTNode getAstNode(ModifiedDeclaration declaration) { + Expression exp = this.getExpression(declaration); + return (exp != null) ? exp : this.annotationAdapter.getAstNode(declaration); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.elementName); + } + + + // ********** expression ********** + + /** + * Return the expression value of the *first* annotation element + * with the adapter's element name. + * Return null if the annotation has no such element. + * (An element name of "value" will return the value of a single + * member annotation.) + */ + protected E expression(Annotation annotation) { + if (annotation == null) { + return this.expressionNoAnnotation(); + } + if (annotation.isMarkerAnnotation()) { + return this.expressionMarkerAnnotation((MarkerAnnotation) annotation); + } + if (annotation.isSingleMemberAnnotation()) { + return this.expressionSingleMemberAnnotation((SingleMemberAnnotation) annotation); + } + if (annotation.isNormalAnnotation()) { + return this.expressionNormalAnnotation((NormalAnnotation) annotation); + } + throw new IllegalArgumentException("unknown annotation type: " + annotation); //$NON-NLS-1$ + } + + protected E expressionNoAnnotation() { + return null; + } + + /** + * Return the expression value of the *first* annotation element + * with the adapter's element name. + * Return null if the annotation has no such element. + */ + protected E expressionMarkerAnnotation(@SuppressWarnings("unused") MarkerAnnotation annotation) { + return null; + } + + /** + * Return the expression value of the *first* annotation element + * with the adapter's element name. + * Return null if the annotation has no such element. + */ + protected E expressionSingleMemberAnnotation(SingleMemberAnnotation annotation) { + return this.downcast(this.elementName.equals(VALUE) ? annotation.getValue() : null); + } + + @SuppressWarnings("unchecked") + private E downcast(Expression e) { + return (E) e; + } + + /** + * Return the expression value of the *first* annotation element + * with the adapter's element name. + * Return null if the annotation has no such element. + */ + protected E expressionNormalAnnotation(NormalAnnotation annotation) { + MemberValuePair pair = this.memberValuePair(annotation); + return this.downcast((pair == null) ? null : pair.getValue()); + } + + + // ********** set value ********** + + /** + * set non-null, non-empty value + */ + protected void setValue(Expression value, Annotation annotation, ModifiedDeclaration declaration) { + if (value == null) { + this.removeElement(annotation, declaration); + } + else if (annotation == null) { + this.setValueNoAnnotation(value, declaration); + } + else if (annotation.isMarkerAnnotation()) { + this.setValueMarkerAnnotation(value, (MarkerAnnotation) annotation, declaration); + } + else if (annotation.isSingleMemberAnnotation()) { + this.setValueSingleMemberAnnotation(value, (SingleMemberAnnotation) annotation, declaration); + } + else if (annotation.isNormalAnnotation()) { + this.setValueNormalAnnotation(value, (NormalAnnotation) annotation, declaration); + } + else { + throw new IllegalArgumentException("unknown annotation type: " + annotation); //$NON-NLS-1$ + } + } + + /** + * add non-null, non-empty value + */ + protected void setValueNoAnnotation(Expression value, ModifiedDeclaration declaration) { + if (this.elementName.equals(VALUE)) { + // @Foo("xxx") + this.annotationAdapter.newSingleMemberAnnotation(declaration).setValue(value); + } else { + // @Foo(bar="xxx") + this.addValue(value, this.annotationAdapter.newNormalAnnotation(declaration)); + } + } + + protected void addValue(Expression value, NormalAnnotation annotation) { + this.addValue(value, annotation, this.elementName); + } + + protected void addValue(Expression value, NormalAnnotation annotation, String annotationElementName) { + AST ast = annotation.getAST(); + MemberValuePair pair = ast.newMemberValuePair(); + pair.setName(ast.newSimpleName(annotationElementName)); + pair.setValue(value); + List<MemberValuePair> values = this.values(annotation); + values.add(pair); + } + + protected void setValueMarkerAnnotation(Expression value, @SuppressWarnings("unused") MarkerAnnotation annotation, ModifiedDeclaration declaration) { + // @Foo => @Foo("xxx") + // or + // @Foo => @Foo(bar="xxx") + this.setValueNoAnnotation(value, declaration); + } + + protected void setValueSingleMemberAnnotation(Expression value, SingleMemberAnnotation annotation, ModifiedDeclaration declaration) { + if (this.elementName.equals(VALUE)) { + // @Foo("yyy") => @Foo("xxx") + annotation.setValue(value); + } else { + // @Foo("yyy") => @Foo(value="yyy", bar="xxx") + Expression vv = annotation.getValue(); + vv = (Expression) ASTNode.copySubtree(vv.getAST(), vv); + NormalAnnotation normalAnnotation = this.annotationAdapter.newNormalAnnotation(declaration); + this.addValue(vv, normalAnnotation, VALUE); + this.addValue(value, normalAnnotation); + } + } + + protected void setValueNormalAnnotation(Expression value, NormalAnnotation annotation, @SuppressWarnings("unused") ModifiedDeclaration declaration) { + MemberValuePair pair = this.memberValuePair(annotation); + if (pair == null) { + this.addValue(value, annotation); + } else { + pair.setValue(value); + } + } + + + // ********** remove element ********** + + protected void removeElement(Annotation annotation, ModifiedDeclaration declaration) { + if (annotation == null) { + this.removeElementNoAnnotation(declaration); + } + else if (annotation.isMarkerAnnotation()) { + this.removeElementMarkerAnnotation((MarkerAnnotation) annotation, declaration); + } + else if (annotation.isSingleMemberAnnotation()) { + this.removeElementSingleMemberAnnotation((SingleMemberAnnotation) annotation, declaration); + } + else if (annotation.isNormalAnnotation()) { + this.removeElementNormalAnnotation((NormalAnnotation) annotation, declaration); + } + else { + throw new IllegalArgumentException("unknown annotation type: " + annotation); //$NON-NLS-1$ + } + } + + protected void removeElementNoAnnotation(@SuppressWarnings("unused") ModifiedDeclaration declaration) { + // the element is already gone (?) + } + + protected void removeElementMarkerAnnotation(@SuppressWarnings("unused") MarkerAnnotation annotation, @SuppressWarnings("unused") ModifiedDeclaration declaration) { + // the element is already gone (?) + } + + protected void removeElementSingleMemberAnnotation(@SuppressWarnings("unused") SingleMemberAnnotation annotation, ModifiedDeclaration declaration) { + if (this.elementName.equals(VALUE)) { + if (this.removeAnnotationWhenEmpty) { + // @Foo("xxx") => + this.annotationAdapter.removeAnnotation(declaration); + } else { + // @Foo("xxx") => @Foo + this.annotationAdapter.newMarkerAnnotation(declaration); + } + } else { + // the [non-'value'] element is already gone (?) + } + } + + protected void removeElementNormalAnnotation(NormalAnnotation annotation, ModifiedDeclaration declaration) { + List<MemberValuePair> values = this.values(annotation); + if ((values.size() == 1) && values.get(0).getName().getFullyQualifiedName().equals(this.elementName)) { + if (this.removeAnnotationWhenEmpty) { + // @Foo(bar="xxx") => + this.annotationAdapter.removeAnnotation(declaration); + } else { + // @Foo(bar="xxx") => @Foo + this.annotationAdapter.newMarkerAnnotation(declaration); + } + } else { + this.removeElement(annotation); + if (values.size() == 1) { + MemberValuePair pair = values.get(0); + if (pair.getName().getFullyQualifiedName().equals(VALUE)) { + // @Foo(bar="xxx", value="yyy") => @Foo("yyy") + Expression vv = pair.getValue(); + vv = (Expression) ASTNode.copySubtree(vv.getAST(), vv); + this.annotationAdapter.newSingleMemberAnnotation(declaration).setValue(vv); + } else { + // @Foo(bar="xxx", baz="yyy") => @Foo(baz="yyy") + } + } else { + // @Foo(bar="xxx", baz="yyy", joo="xxx") => @Foo(baz="yyy", joo="xxx") + } + } + } + + /** + * Remove the *first* member value pair from the specified annotation element + * with the adapter's element name. + */ + protected void removeElement(NormalAnnotation annotation) { + for (Iterator<MemberValuePair> stream = this.values(annotation).iterator(); stream.hasNext(); ) { + MemberValuePair pair = stream.next(); + if (pair.getName().getFullyQualifiedName().equals(this.elementName)) { + stream.remove(); + } + } + } + + + // ********** convenience methods ********** + + /** + * Return the *first* member value pair for the specified annotation element + * with the adapter's element name. + * Return null if the annotation has no such element. + */ + protected MemberValuePair memberValuePair(NormalAnnotation annotation) { + for (MemberValuePair pair : this.values(annotation)) { + if (pair.getName().getFullyQualifiedName().equals(this.elementName)) { + return pair; + } + } + return null; + } + + @SuppressWarnings("unchecked") + protected List<MemberValuePair> values(NormalAnnotation na) { + return na.values(); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/GenericVisitor.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/GenericVisitor.java new file mode 100644 index 0000000000..284d615e26 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/GenericVisitor.java @@ -0,0 +1,791 @@ +/******************************************************************************* + * 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.jpt.core.internal.utility.jdt; + +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; +import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; +import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; +import org.eclipse.jdt.core.dom.ArrayAccess; +import org.eclipse.jdt.core.dom.ArrayCreation; +import org.eclipse.jdt.core.dom.ArrayInitializer; +import org.eclipse.jdt.core.dom.ArrayType; +import org.eclipse.jdt.core.dom.AssertStatement; +import org.eclipse.jdt.core.dom.Assignment; +import org.eclipse.jdt.core.dom.Block; +import org.eclipse.jdt.core.dom.BlockComment; +import org.eclipse.jdt.core.dom.BooleanLiteral; +import org.eclipse.jdt.core.dom.BreakStatement; +import org.eclipse.jdt.core.dom.CastExpression; +import org.eclipse.jdt.core.dom.CatchClause; +import org.eclipse.jdt.core.dom.CharacterLiteral; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.ConditionalExpression; +import org.eclipse.jdt.core.dom.ConstructorInvocation; +import org.eclipse.jdt.core.dom.ContinueStatement; +import org.eclipse.jdt.core.dom.DoStatement; +import org.eclipse.jdt.core.dom.EmptyStatement; +import org.eclipse.jdt.core.dom.EnhancedForStatement; +import org.eclipse.jdt.core.dom.EnumConstantDeclaration; +import org.eclipse.jdt.core.dom.EnumDeclaration; +import org.eclipse.jdt.core.dom.ExpressionStatement; +import org.eclipse.jdt.core.dom.FieldAccess; +import org.eclipse.jdt.core.dom.FieldDeclaration; +import org.eclipse.jdt.core.dom.ForStatement; +import org.eclipse.jdt.core.dom.IfStatement; +import org.eclipse.jdt.core.dom.ImportDeclaration; +import org.eclipse.jdt.core.dom.InfixExpression; +import org.eclipse.jdt.core.dom.Initializer; +import org.eclipse.jdt.core.dom.InstanceofExpression; +import org.eclipse.jdt.core.dom.Javadoc; +import org.eclipse.jdt.core.dom.LabeledStatement; +import org.eclipse.jdt.core.dom.LineComment; +import org.eclipse.jdt.core.dom.MarkerAnnotation; +import org.eclipse.jdt.core.dom.MemberRef; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.MethodRef; +import org.eclipse.jdt.core.dom.MethodRefParameter; +import org.eclipse.jdt.core.dom.Modifier; +import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.NullLiteral; +import org.eclipse.jdt.core.dom.NumberLiteral; +import org.eclipse.jdt.core.dom.PackageDeclaration; +import org.eclipse.jdt.core.dom.ParameterizedType; +import org.eclipse.jdt.core.dom.ParenthesizedExpression; +import org.eclipse.jdt.core.dom.PostfixExpression; +import org.eclipse.jdt.core.dom.PrefixExpression; +import org.eclipse.jdt.core.dom.PrimitiveType; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.QualifiedType; +import org.eclipse.jdt.core.dom.ReturnStatement; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SimpleType; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.jdt.core.dom.SuperConstructorInvocation; +import org.eclipse.jdt.core.dom.SuperFieldAccess; +import org.eclipse.jdt.core.dom.SuperMethodInvocation; +import org.eclipse.jdt.core.dom.SwitchCase; +import org.eclipse.jdt.core.dom.SwitchStatement; +import org.eclipse.jdt.core.dom.SynchronizedStatement; +import org.eclipse.jdt.core.dom.TagElement; +import org.eclipse.jdt.core.dom.TextElement; +import org.eclipse.jdt.core.dom.ThisExpression; +import org.eclipse.jdt.core.dom.ThrowStatement; +import org.eclipse.jdt.core.dom.TryStatement; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.TypeDeclarationStatement; +import org.eclipse.jdt.core.dom.TypeLiteral; +import org.eclipse.jdt.core.dom.TypeParameter; +import org.eclipse.jdt.core.dom.VariableDeclarationExpression; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.dom.VariableDeclarationStatement; +import org.eclipse.jdt.core.dom.WhileStatement; +import org.eclipse.jdt.core.dom.WildcardType; + +/** + * copied from org.eclipse.jdt.internal.corext.dom.GenericVisitor + */ +public class GenericVisitor extends ASTVisitor { + + public GenericVisitor() { + super(); + } + + public GenericVisitor(boolean visitJavadocTags) { + super(visitJavadocTags); + } + + // ********** hooks for subclasses ********** + + protected boolean visit_(@SuppressWarnings("unused") ASTNode node) { + return true; + } + + protected void endVisit_(@SuppressWarnings("unused") ASTNode node) { + // do nothing + } + + // ********** overrides ********** + + @Override + public boolean visit(AnonymousClassDeclaration node) { + return visit_(node); + } + @Override + public boolean visit(ArrayAccess node) { + return visit_(node); + } + @Override + public boolean visit(ArrayCreation node) { + return visit_(node); + } + @Override + public boolean visit(ArrayInitializer node) { + return visit_(node); + } + @Override + public boolean visit(ArrayType node) { + return visit_(node); + } + @Override + public boolean visit(AssertStatement node) { + return visit_(node); + } + @Override + public boolean visit(Assignment node) { + return visit_(node); + } + @Override + public boolean visit(Block node) { + return visit_(node); + } + @Override + public boolean visit(BooleanLiteral node) { + return visit_(node); + } + @Override + public boolean visit(BreakStatement node) { + return visit_(node); + } + @Override + public boolean visit(CastExpression node) { + return visit_(node); + } + @Override + public boolean visit(CatchClause node) { + return visit_(node); + } + @Override + public boolean visit(CharacterLiteral node) { + return visit_(node); + } + @Override + public boolean visit(ClassInstanceCreation node) { + return visit_(node); + } + @Override + public boolean visit(CompilationUnit node) { + return visit_(node); + } + @Override + public boolean visit(ConditionalExpression node) { + return visit_(node); + } + @Override + public boolean visit(ConstructorInvocation node) { + return visit_(node); + } + @Override + public boolean visit(ContinueStatement node) { + return visit_(node); + } + @Override + public boolean visit(DoStatement node) { + return visit_(node); + } + @Override + public boolean visit(EmptyStatement node) { + return visit_(node); + } + @Override + public boolean visit(ExpressionStatement node) { + return visit_(node); + } + @Override + public boolean visit(FieldAccess node) { + return visit_(node); + } + @Override + public boolean visit(FieldDeclaration node) { + return visit_(node); + } + @Override + public boolean visit(ForStatement node) { + return visit_(node); + } + @Override + public boolean visit(IfStatement node) { + return visit_(node); + } + @Override + public boolean visit(ImportDeclaration node) { + return visit_(node); + } + @Override + public boolean visit(InfixExpression node) { + return visit_(node); + } + @Override + public boolean visit(InstanceofExpression node) { + return visit_(node); + } + @Override + public boolean visit(Initializer node) { + return visit_(node); + } + @Override + public boolean visit(Javadoc node) { + return (super.visit(node)) ? visit_(node) : false; + } + @Override + public boolean visit(LabeledStatement node) { + return visit_(node); + } + @Override + public boolean visit(MethodDeclaration node) { + return visit_(node); + } + @Override + public boolean visit(MethodInvocation node) { + return visit_(node); + } + @Override + public boolean visit(NullLiteral node) { + return visit_(node); + } + @Override + public boolean visit(NumberLiteral node) { + return visit_(node); + } + @Override + public boolean visit(PackageDeclaration node) { + return visit_(node); + } + @Override + public boolean visit(ParenthesizedExpression node) { + return visit_(node); + } + @Override + public boolean visit(PostfixExpression node) { + return visit_(node); + } + @Override + public boolean visit(PrefixExpression node) { + return visit_(node); + } + @Override + public boolean visit(PrimitiveType node) { + return visit_(node); + } + @Override + public boolean visit(QualifiedName node) { + return visit_(node); + } + @Override + public boolean visit(ReturnStatement node) { + return visit_(node); + } + @Override + public boolean visit(SimpleName node) { + return visit_(node); + } + @Override + public boolean visit(SimpleType node) { + return visit_(node); + } + @Override + public boolean visit(StringLiteral node) { + return visit_(node); + } + @Override + public boolean visit(SuperConstructorInvocation node) { + return visit_(node); + } + @Override + public boolean visit(SuperFieldAccess node) { + return visit_(node); + } + @Override + public boolean visit(SuperMethodInvocation node) { + return visit_(node); + } + @Override + public boolean visit(SwitchCase node) { + return visit_(node); + } + @Override + public boolean visit(SwitchStatement node) { + return visit_(node); + } + @Override + public boolean visit(SynchronizedStatement node) { + return visit_(node); + } + @Override + public boolean visit(ThisExpression node) { + return visit_(node); + } + @Override + public boolean visit(ThrowStatement node) { + return visit_(node); + } + @Override + public boolean visit(TryStatement node) { + return visit_(node); + } + @Override + public boolean visit(TypeDeclaration node) { + return visit_(node); + } + @Override + public boolean visit(TypeDeclarationStatement node) { + return visit_(node); + } + @Override + public boolean visit(TypeLiteral node) { + return visit_(node); + } + @Override + public boolean visit(SingleVariableDeclaration node) { + return visit_(node); + } + @Override + public boolean visit(VariableDeclarationExpression node) { + return visit_(node); + } + @Override + public boolean visit(VariableDeclarationStatement node) { + return visit_(node); + } + @Override + public boolean visit(VariableDeclarationFragment node) { + return visit_(node); + } + @Override + public boolean visit(WhileStatement node) { + return visit_(node); + } + @Override + public boolean visit(AnnotationTypeDeclaration node) { + return visit_(node); + } + @Override + public boolean visit(AnnotationTypeMemberDeclaration node) { + return visit_(node); + } + @Override + public boolean visit(BlockComment node) { + return visit_(node); + } + @Override + public boolean visit(EnhancedForStatement node) { + return visit_(node); + } + @Override + public boolean visit(EnumConstantDeclaration node) { + return visit_(node); + } + @Override + public boolean visit(EnumDeclaration node) { + return visit_(node); + } + @Override + public boolean visit(LineComment node) { + return visit_(node); + } + @Override + public boolean visit(MarkerAnnotation node) { + return visit_(node); + } + @Override + public boolean visit(MemberRef node) { + return visit_(node); + } + @Override + public boolean visit(MemberValuePair node) { + return visit_(node); + } + @Override + public boolean visit(MethodRef node) { + return visit_(node); + } + @Override + public boolean visit(MethodRefParameter node) { + return visit_(node); + } + @Override + public boolean visit(Modifier node) { + return visit_(node); + } + @Override + public boolean visit(NormalAnnotation node) { + return visit_(node); + } + @Override + public boolean visit(ParameterizedType node) { + return visit_(node); + } + @Override + public boolean visit(QualifiedType node) { + return visit_(node); + } + @Override + public boolean visit(SingleMemberAnnotation node) { + return visit_(node); + } + @Override + public boolean visit(TagElement node) { + return visit_(node); + } + @Override + public boolean visit(TextElement node) { + return visit_(node); + } + @Override + public boolean visit(TypeParameter node) { + return visit_(node); + } + @Override + public boolean visit(WildcardType node) { + return visit_(node); + } + + @Override + public void endVisit(AnonymousClassDeclaration node) { + endVisit_(node); + } + @Override + public void endVisit(ArrayAccess node) { + endVisit_(node); + } + @Override + public void endVisit(ArrayCreation node) { + endVisit_(node); + } + @Override + public void endVisit(ArrayInitializer node) { + endVisit_(node); + } + @Override + public void endVisit(ArrayType node) { + endVisit_(node); + } + @Override + public void endVisit(AssertStatement node) { + endVisit_(node); + } + @Override + public void endVisit(Assignment node) { + endVisit_(node); + } + @Override + public void endVisit(Block node) { + endVisit_(node); + } + @Override + public void endVisit(BooleanLiteral node) { + endVisit_(node); + } + @Override + public void endVisit(BreakStatement node) { + endVisit_(node); + } + @Override + public void endVisit(CastExpression node) { + endVisit_(node); + } + @Override + public void endVisit(CatchClause node) { + endVisit_(node); + } + @Override + public void endVisit(CharacterLiteral node) { + endVisit_(node); + } + @Override + public void endVisit(ClassInstanceCreation node) { + endVisit_(node); + } + @Override + public void endVisit(CompilationUnit node) { + endVisit_(node); + } + @Override + public void endVisit(ConditionalExpression node) { + endVisit_(node); + } + @Override + public void endVisit(ConstructorInvocation node) { + endVisit_(node); + } + @Override + public void endVisit(ContinueStatement node) { + endVisit_(node); + } + @Override + public void endVisit(DoStatement node) { + endVisit_(node); + } + @Override + public void endVisit(EmptyStatement node) { + endVisit_(node); + } + @Override + public void endVisit(ExpressionStatement node) { + endVisit_(node); + } + @Override + public void endVisit(FieldAccess node) { + endVisit_(node); + } + @Override + public void endVisit(FieldDeclaration node) { + endVisit_(node); + } + @Override + public void endVisit(ForStatement node) { + endVisit_(node); + } + @Override + public void endVisit(IfStatement node) { + endVisit_(node); + } + @Override + public void endVisit(ImportDeclaration node) { + endVisit_(node); + } + @Override + public void endVisit(InfixExpression node) { + endVisit_(node); + } + @Override + public void endVisit(InstanceofExpression node) { + endVisit_(node); + } + @Override + public void endVisit(Initializer node) { + endVisit_(node); + } + @Override + public void endVisit(Javadoc node) { + endVisit_(node); + } + @Override + public void endVisit(LabeledStatement node) { + endVisit_(node); + } + @Override + public void endVisit(MethodDeclaration node) { + endVisit_(node); + } + @Override + public void endVisit(MethodInvocation node) { + endVisit_(node); + } + @Override + public void endVisit(NullLiteral node) { + endVisit_(node); + } + @Override + public void endVisit(NumberLiteral node) { + endVisit_(node); + } + @Override + public void endVisit(PackageDeclaration node) { + endVisit_(node); + } + @Override + public void endVisit(ParenthesizedExpression node) { + endVisit_(node); + } + @Override + public void endVisit(PostfixExpression node) { + endVisit_(node); + } + @Override + public void endVisit(PrefixExpression node) { + endVisit_(node); + } + @Override + public void endVisit(PrimitiveType node) { + endVisit_(node); + } + @Override + public void endVisit(QualifiedName node) { + endVisit_(node); + } + @Override + public void endVisit(ReturnStatement node) { + endVisit_(node); + } + @Override + public void endVisit(SimpleName node) { + endVisit_(node); + } + @Override + public void endVisit(SimpleType node) { + endVisit_(node); + } + @Override + public void endVisit(StringLiteral node) { + endVisit_(node); + } + @Override + public void endVisit(SuperConstructorInvocation node) { + endVisit_(node); + } + @Override + public void endVisit(SuperFieldAccess node) { + endVisit_(node); + } + @Override + public void endVisit(SuperMethodInvocation node) { + endVisit_(node); + } + @Override + public void endVisit(SwitchCase node) { + endVisit_(node); + } + @Override + public void endVisit(SwitchStatement node) { + endVisit_(node); + } + @Override + public void endVisit(SynchronizedStatement node) { + endVisit_(node); + } + @Override + public void endVisit(ThisExpression node) { + endVisit_(node); + } + @Override + public void endVisit(ThrowStatement node) { + endVisit_(node); + } + @Override + public void endVisit(TryStatement node) { + endVisit_(node); + } + @Override + public void endVisit(TypeDeclaration node) { + endVisit_(node); + } + @Override + public void endVisit(TypeDeclarationStatement node) { + endVisit_(node); + } + @Override + public void endVisit(TypeLiteral node) { + endVisit_(node); + } + @Override + public void endVisit(SingleVariableDeclaration node) { + endVisit_(node); + } + @Override + public void endVisit(VariableDeclarationExpression node) { + endVisit_(node); + } + @Override + public void endVisit(VariableDeclarationStatement node) { + endVisit_(node); + } + @Override + public void endVisit(VariableDeclarationFragment node) { + endVisit_(node); + } + @Override + public void endVisit(WhileStatement node) { + endVisit_(node); + } + @Override + public void endVisit(AnnotationTypeDeclaration node) { + endVisit_(node); + } + @Override + public void endVisit(AnnotationTypeMemberDeclaration node) { + endVisit_(node); + } + @Override + public void endVisit(BlockComment node) { + endVisit_(node); + } + @Override + public void endVisit(EnhancedForStatement node) { + endVisit_(node); + } + @Override + public void endVisit(EnumConstantDeclaration node) { + endVisit_(node); + } + @Override + public void endVisit(EnumDeclaration node) { + endVisit_(node); + } + @Override + public void endVisit(LineComment node) { + endVisit_(node); + } + @Override + public void endVisit(MarkerAnnotation node) { + endVisit_(node); + } + @Override + public void endVisit(MemberRef node) { + endVisit_(node); + } + @Override + public void endVisit(MemberValuePair node) { + endVisit_(node); + } + @Override + public void endVisit(MethodRef node) { + endVisit_(node); + } + @Override + public void endVisit(MethodRefParameter node) { + endVisit_(node); + } + @Override + public void endVisit(Modifier node) { + endVisit_(node); + } + @Override + public void endVisit(NormalAnnotation node) { + endVisit_(node); + } + @Override + public void endVisit(ParameterizedType node) { + endVisit_(node); + } + @Override + public void endVisit(QualifiedType node) { + endVisit_(node); + } + @Override + public void endVisit(SingleMemberAnnotation node) { + endVisit_(node); + } + @Override + public void endVisit(TagElement node) { + endVisit_(node); + } + @Override + public void endVisit(TextElement node) { + endVisit_(node); + } + @Override + public void endVisit(TypeParameter node) { + endVisit_(node); + } + @Override + public void endVisit(WildcardType node) { + endVisit_(node); + } + +} + diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTAttribute.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTAttribute.java new file mode 100644 index 0000000000..342aebf7e0 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTAttribute.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 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 org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jpt.core.utility.jdt.AnnotationEditFormatter; +import org.eclipse.jpt.core.utility.jdt.Attribute; +import org.eclipse.jpt.core.utility.jdt.Type; +import org.eclipse.jpt.utility.CommandExecutor; + +/** + * Combine behavior common to JDTFieldAttribute and JDTMethodAttribute. + * Not so sure this is useful.... + */ +public abstract class JDTAttribute + extends JDTMember + implements Attribute +{ + + // ********** constructors ********** + + protected JDTAttribute( + Type declaringType, + String name, + int occurrence, + ICompilationUnit compilationUnit, + CommandExecutor modifySharedDocumentCommandExecutor) { + super(declaringType, name, occurrence, compilationUnit, modifySharedDocumentCommandExecutor); + } + + protected JDTAttribute( + Type declaringType, + String name, + int occurrence, + ICompilationUnit compilationUnit, + CommandExecutor modifySharedDocumentCommandExecutor, + AnnotationEditFormatter annotationEditFormatter) { + super(declaringType, name, occurrence, compilationUnit, modifySharedDocumentCommandExecutor, annotationEditFormatter); + } + + + // ********** Member/Attribute implementation ********** + + public boolean isField() { + return false; + } + + + // ********** internal ********** + + protected TypeDeclaration getDeclaringTypeDeclaration(CompilationUnit astRoot) { + // assume the declaring type is not an enum or annotation + // since they do not have field or method declarations + return this.getDeclaringType().getBodyDeclaration(astRoot); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTFieldAttribute.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTFieldAttribute.java new file mode 100644 index 0000000000..1c18f297cb --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTFieldAttribute.java @@ -0,0 +1,205 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 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.List; + +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.FieldDeclaration; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jpt.core.utility.TextRange; +import org.eclipse.jpt.core.utility.jdt.AnnotationEditFormatter; +import org.eclipse.jpt.core.utility.jdt.FieldAttribute; +import org.eclipse.jpt.core.utility.jdt.Type; +import org.eclipse.jpt.utility.CommandExecutor; + +/** + * Adapt and extend a JDT field. + * Attribute based on a Java field, e.g. + * private int foo; + */ +public class JDTFieldAttribute + extends JDTAttribute + implements FieldAttribute +{ + + // ********** constructors ********** + + public JDTFieldAttribute( + Type declaringType, + String name, + int occurrence, + ICompilationUnit compilationUnit, + CommandExecutor modifySharedDocumentCommandExecutor) { + this(declaringType, name, occurrence, compilationUnit, modifySharedDocumentCommandExecutor, DefaultAnnotationEditFormatter.instance()); + } + + public JDTFieldAttribute( + Type declaringType, + String name, + int occurrence, + ICompilationUnit compilationUnit, + CommandExecutor modifySharedDocumentCommandExecutor, + AnnotationEditFormatter annotationEditFormatter) { + super(declaringType, name, occurrence, compilationUnit, modifySharedDocumentCommandExecutor, annotationEditFormatter); + } + + /** + * constructor for testing + */ + public JDTFieldAttribute(Type declaringType, String name, int occurrence, ICompilationUnit compilationUnit) { + this(declaringType, name, occurrence, compilationUnit, CommandExecutor.Default.instance(), DefaultAnnotationEditFormatter.instance()); + } + + + // ********** Member/Attribute/FieldAttribute implementation ********** + + public IVariableBinding getBinding(CompilationUnit astRoot) { + return this.getFragment(astRoot).resolveBinding(); + } + + public FieldDeclaration getBodyDeclaration(CompilationUnit astRoot) { + return this.getSelectedDeclaration(astRoot, FIELD_DECLARATION_SELECTOR); + } + + public TextRange getNameTextRange(CompilationUnit astRoot) { + return new ASTNodeTextRange(this.getFragment(astRoot).getName()); + } + + public String getAttributeName() { + return this.getName_(); + } + + public ITypeBinding getTypeBinding(CompilationUnit astRoot) { + return this.getBodyDeclaration(astRoot).getType().resolveBinding(); + } + + @Override + public boolean isField() { + return true; + } + + public boolean isPersistable(CompilationUnit astRoot) { + IVariableBinding binding = this.getBinding(astRoot); + return (binding == null) ? false : JPTTools.fieldIsPersistable(new JPTToolsAdapter(binding)); + } + + + // ********** internal ********** + + protected VariableDeclarationFragment getFragment(CompilationUnit astRoot) { + return this.getSelectedDeclaration(astRoot, VARIABLE_DECLARATION_FRAGMENT_SELECTOR); + } + + /** + * return either a FieldDeclaration or a VariableDeclarationFragment, + * depending on the specified selector; + * + * handle multiple fields declared in a single statement: + * private int foo, bar; + */ + protected <T extends ASTNode> T getSelectedDeclaration(CompilationUnit astRoot, Selector<T> selector) { + String name = this.getName_(); + int occurrence = this.getOccurrence(); + int count = 0; + for (FieldDeclaration fieldDeclaration : this.getDeclaringTypeFieldDeclarations(astRoot)) { + for (VariableDeclarationFragment fragment : fragments(fieldDeclaration)) { + if (fragment.getName().getFullyQualifiedName().equals(name)) { + count++; + if (count == occurrence) { + return selector.select(fieldDeclaration, fragment); + } + } + } + } + // return null if the field is no longer in the source code; + // this can happen when the context model has not yet + // been synchronized with the resource model but is still + // asking for an ASTNode (e.g. during a selection event) + return null; + } + + protected FieldDeclaration[] getDeclaringTypeFieldDeclarations(CompilationUnit astRoot) { + return this.getDeclaringTypeDeclaration(astRoot).getFields(); + } + + // minimize scope of suppressed warnings + @SuppressWarnings("unchecked") + protected static List<VariableDeclarationFragment> fragments(FieldDeclaration fd) { + return fd.fragments(); + } + + + // ********** Selector ********** + + // I'm not quite sure this interface is worth the resulting obfuscation, + // but, then, I kept changing both methods, so... ~bjv + protected interface Selector<T extends ASTNode> { + T select(FieldDeclaration fieldDeclaration, VariableDeclarationFragment variableDeclarationFragment); + String getDescription(); + } + + protected static final Selector<FieldDeclaration> FIELD_DECLARATION_SELECTOR = + new Selector<FieldDeclaration>() { + public FieldDeclaration select(FieldDeclaration fieldDeclaration, VariableDeclarationFragment variableDeclarationFragment) { + return fieldDeclaration; + } + public String getDescription() { + return "field declaration"; //$NON-NLS-1$ + } + @Override + public String toString() { + return "FIELD_DECLARATION_SELECTOR"; //$NON-NLS-1$ + } + }; + + protected static final Selector<VariableDeclarationFragment> VARIABLE_DECLARATION_FRAGMENT_SELECTOR = + new Selector<VariableDeclarationFragment>() { + public VariableDeclarationFragment select(FieldDeclaration fieldDeclaration, VariableDeclarationFragment variableDeclarationFragment) { + return variableDeclarationFragment; + } + public String getDescription() { + return "variable declaration fragment"; //$NON-NLS-1$ + } + @Override + public String toString() { + return "VARIABLE_DECLARATION_FRAGMENT_SELECTOR"; //$NON-NLS-1$ + } + }; + + + // ********** JPTTools adapter ********** + + /** + * JPTTools needs an adapter so it can work with either an IField + * or an IVariableBinding etc. + */ + protected static class JPTToolsAdapter implements JPTTools.FieldAdapter { + private final IVariableBinding fieldBinding; + + protected JPTToolsAdapter(IVariableBinding fieldBinding) { + super(); + if (fieldBinding == null) { + throw new NullPointerException(); + } + this.fieldBinding = fieldBinding; + } + + public int getModifiers() { + return this.fieldBinding.getModifiers(); + } + + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTMember.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTMember.java new file mode 100644 index 0000000000..c7c5ebbdb6 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTMember.java @@ -0,0 +1,236 @@ +/******************************************************************************* + * Copyright (c) 2005, 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 org.eclipse.core.filebuffers.FileBuffers; +import org.eclipse.core.filebuffers.ITextFileBuffer; +import org.eclipse.core.filebuffers.LocationKind; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jpt.core.utility.jdt.AnnotationEditFormatter; +import org.eclipse.jpt.core.utility.jdt.Member; +import org.eclipse.jpt.core.utility.jdt.ModifiedDeclaration; +import org.eclipse.jpt.core.utility.jdt.Type; +import org.eclipse.jpt.utility.Command; +import org.eclipse.jpt.utility.CommandExecutor; +import org.eclipse.jpt.utility.internal.StringTools; +import org.eclipse.text.edits.MalformedTreeException; +import org.eclipse.text.edits.TextEdit; + +/** + * Adapt and extend a JDT member with simplified annotation handling. + */ +public abstract class JDTMember + implements Member +{ + /** this will be null for the primary type */ + private final Type declaringType; + + /** the member's name (duh) */ + private final String name; + + /** + * members can occur more than once in non-compiling source; + * count starts at 1; the primary type will have occurrence 1 + */ + private final int occurrence; + + /** + * the compilation unit (file) containing the member; + * used for building an AST when we modify the member + */ + private final ICompilationUnit compilationUnit; + + /** + * this allows clients to provide a way to modify the compilation unit + * (file) when it is open in an editor and should be modified on the UI + * thread + */ + private final CommandExecutor modifySharedDocumentCommandExecutor; + + /** this will format the member's annotations a bit */ + private final AnnotationEditFormatter annotationEditFormatter; + + + // ********** constructors ********** + + protected JDTMember( + Type declaringType, + String name, + int occurrence, + ICompilationUnit compilationUnit, + CommandExecutor modifySharedDocumentCommandExecutor) { + this(declaringType, name, occurrence, compilationUnit, modifySharedDocumentCommandExecutor, DefaultAnnotationEditFormatter.instance()); + } + + protected JDTMember( + Type declaringType, + String name, + int occurrence, + ICompilationUnit compilationUnit, + CommandExecutor modifySharedDocumentCommandExecutor, + AnnotationEditFormatter annotationEditFormatter) { + super(); + this.declaringType = declaringType; + this.name = name; + this.occurrence = occurrence; + this.compilationUnit = compilationUnit; + this.modifySharedDocumentCommandExecutor = modifySharedDocumentCommandExecutor; + this.annotationEditFormatter = annotationEditFormatter; + } + + + // ********** Member implementation ********** + + public ModifiedDeclaration getModifiedDeclaration(CompilationUnit astRoot) { + return new JDTModifiedDeclaration(this.getBodyDeclaration(astRoot)); + } + + public ModifiedDeclaration getModifiedDeclaration() { + return this.getModifiedDeclaration(this.buildASTRoot()); + } + + public boolean matches(String memberName, int occur) { + return memberName.equals(this.name) && (occur == this.occurrence); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.name); + } + + + // ********** internal ********** + + protected String getName_() { + return this.name; + } + + protected int getOccurrence() { + return this.occurrence; + } + + /** + * this will return null for a top-level type + */ + protected Type getDeclaringType() { + return this.declaringType; + } + + + // ********** editing ********** + + /** + * Edit the member with the specified editor. + * The editor will be invoked once the member's compilation unit + * is in an editable state. + */ + public void edit(Editor editor) { + try { + this.edit_(editor); + } catch (JavaModelException ex) { + throw new RuntimeException(ex); + } catch (BadLocationException ex) { + throw new RuntimeException(ex); + } + } + + /** + * NB: Be careful changing this method. + * Things to look out for: + * - when editing via the JavaEditor there is no need to create a working copy + * - when editing without an editor or via a simple text editor, a "working copy" must be created. + * (at least as far as I can tell ~kfm) + * - sharedDocument is only ever false in tests (headless mode). In the UI, even if the file + * is not open in an editor, sharedDocument is still true (buffer is not null) + * - if a working copy is created, then we must discard it + */ + protected void edit_(Editor editor) throws JavaModelException, BadLocationException { + boolean createWorkingCopy = ! this.compilationUnit.isWorkingCopy(); + if (createWorkingCopy) { + this.compilationUnit.becomeWorkingCopy(null); + } + + ITextFileBuffer buffer = FileBuffers.getTextFileBufferManager().getTextFileBuffer(this.compilationUnit.getResource().getFullPath(), LocationKind.NORMALIZE); + boolean sharedDocument = (buffer != null); // documents are typically shared when they are already open in an editor + IDocument doc = sharedDocument ? + buffer.getDocument() : + new Document(this.compilationUnit.getBuffer().getContents()); + + try { + CompilationUnit astRoot = this.buildASTRoot(); + astRoot.recordModifications(); + + editor.edit(this.getModifiedDeclaration(astRoot)); + + TextEdit edits = astRoot.rewrite(doc, this.compilationUnit.getJavaProject().getOptions(true)); + if (sharedDocument) { + this.modifySharedDocumentCommandExecutor.execute(new ModifySharedDocumentCommand(edits, doc)); + } else { + this.applyEdits(edits, doc); + } + } + finally { + if (createWorkingCopy) { + //discardWorkingCopy must be called every time becomeWorkingCopy is called. + this.compilationUnit.getBuffer().setContents(doc.get()); + this.compilationUnit.commitWorkingCopy(true, null); // true="force" + this.compilationUnit.discardWorkingCopy(); + } + } + } + + /** + * apply the specified edits to the specified document, + * reformatting the document if necessary + */ + protected void applyEdits(TextEdit edits, IDocument doc) throws MalformedTreeException, BadLocationException { + edits.apply(doc, TextEdit.UPDATE_REGIONS); + this.annotationEditFormatter.format(doc, edits); + } + + protected CompilationUnit buildASTRoot() { + return ASTTools.buildASTRoot(this.compilationUnit); + } + + + // ********** modify shared document command class ********** + + /** + * simple command that calls back to the member to apply the edits + * in the same way as if the document were not shared + */ + protected class ModifySharedDocumentCommand implements Command { + private final TextEdit edits; + private final IDocument doc; + + protected ModifySharedDocumentCommand(TextEdit edits, IDocument doc) { + super(); + this.edits = edits; + this.doc = doc; + } + + public void execute() { + try { + JDTMember.this.applyEdits(this.edits, this.doc); + } catch (MalformedTreeException ex) { + throw new RuntimeException(ex); + } catch (BadLocationException ex) { + throw new RuntimeException(ex); + } + } + + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTMethodAttribute.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTMethodAttribute.java new file mode 100644 index 0000000000..4316d6c198 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTMethodAttribute.java @@ -0,0 +1,256 @@ +/******************************************************************************* + * Copyright (c) 2005, 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.Arrays; +import java.util.List; + +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jpt.core.utility.TextRange; +import org.eclipse.jpt.core.utility.jdt.AnnotationEditFormatter; +import org.eclipse.jpt.core.utility.jdt.MethodAttribute; +import org.eclipse.jpt.core.utility.jdt.Type; +import org.eclipse.jpt.utility.CommandExecutor; +import org.eclipse.jpt.utility.JavaType; +import org.eclipse.jpt.utility.MethodSignature; +import org.eclipse.jpt.utility.internal.NameTools; +import org.eclipse.jpt.utility.internal.SimpleMethodSignature; + +/** + * Adapt and extend a JDT method. + * Attribute based on a Java property, e.g. + * private int getFoo() { + * return foo; + * } + * private void setFoo(int foo) { + * this.foo = foo; + * } + */ +public class JDTMethodAttribute + extends JDTAttribute + implements MethodAttribute +{ + /** we need the parameter types to build the method signature */ + private final JavaType[] parameterTypes; + + + // ********** constructors ********** + + public static JDTMethodAttribute newInstance( + Type declaringType, + MethodSignature signature, + int occurrence, + ICompilationUnit compilationUnit, + CommandExecutor modifySharedDocumentCommandExecutor) { + return newInstance(declaringType, signature, occurrence, compilationUnit, modifySharedDocumentCommandExecutor, DefaultAnnotationEditFormatter.instance()); + } + + public static JDTMethodAttribute newInstance( + Type declaringType, + MethodSignature signature, + int occurrence, + ICompilationUnit compilationUnit, + CommandExecutor modifySharedDocumentCommandExecutor, + AnnotationEditFormatter annotationEditFormatter) { + return new JDTMethodAttribute(declaringType, signature, occurrence, compilationUnit, modifySharedDocumentCommandExecutor, annotationEditFormatter); + } + + public JDTMethodAttribute( + Type declaringType, + MethodSignature methodSignature, + int occurrence, + ICompilationUnit compilationUnit, + CommandExecutor modifySharedDocumentCommandExecutor) { + this(declaringType, methodSignature, occurrence, compilationUnit, modifySharedDocumentCommandExecutor, DefaultAnnotationEditFormatter.instance()); + } + + public JDTMethodAttribute( + Type declaringType, + MethodSignature methodSignature, + int occurrence, + ICompilationUnit compilationUnit, + CommandExecutor modifySharedDocumentCommandExecutor, + AnnotationEditFormatter annotationEditFormatter) { + super(declaringType, methodSignature.getName(), occurrence, compilationUnit, modifySharedDocumentCommandExecutor, annotationEditFormatter); + this.parameterTypes = methodSignature.getParameterTypes(); + } + + /** + * constructor for testing + */ + public JDTMethodAttribute(Type declaringType, String name, String[] parameterTypeNames, int occurrence, ICompilationUnit compilationUnit) { + this(declaringType, new SimpleMethodSignature(name, parameterTypeNames), occurrence, compilationUnit, CommandExecutor.Default.instance(), DefaultAnnotationEditFormatter.instance()); + } + + + // ********** Member/Attribute/MethodAttribute implementation ********** + + public IMethodBinding getBinding(CompilationUnit astRoot) { + return this.getBodyDeclaration(astRoot).resolveBinding(); + } + + public MethodDeclaration getBodyDeclaration(CompilationUnit astRoot) { + int count = 0; + for (MethodDeclaration methodDeclaration : this.getDeclaringTypeMethodDeclarations(astRoot)) { + if (this.matches(methodDeclaration)) { + count++; + if (count == this.getOccurrence()) { + return methodDeclaration; + } + } + } + // return null if the method is no longer in the source code; + // this can happen when the context model has not yet + // been synchronized with the resource model but is still + // asking for an ASTNode (e.g. during a selection event) + return null; + } + + public boolean matches(MethodSignature signature, int occurrence) { + return this.matches(signature) && (occurrence == this.getOccurrence()); + } + + protected boolean matches(MethodSignature signature) { + return signature.getName().equals(this.getName_()) + && Arrays.equals(this.parameterTypes, signature.getParameterTypes()); + } + + protected boolean matches(MethodDeclaration methodDeclaration) { + return this.matches(ASTTools.buildMethodSignature(methodDeclaration)); + } + + // minimize scope of suppressed warnings + @SuppressWarnings("unchecked") + protected static List<SingleVariableDeclaration> parameters(MethodDeclaration methodDeclaration) { + return methodDeclaration.parameters(); + } + + @Override + public boolean matches(String memberName, int occurrence) { + throw new UnsupportedOperationException("Use #matches(MethodSignature, int)."); //$NON-NLS-1$ + } + + public TextRange getNameTextRange(CompilationUnit astRoot) { + return new ASTNodeTextRange(this.getBodyDeclaration(astRoot).getName()); + } + + /** + * return "foo" for a method named "getFoo" or "isFoo" + */ + public String getAttributeName() { + return NameTools.convertGetterMethodNameToPropertyName(this.getName_()); + } + + public ITypeBinding getTypeBinding(CompilationUnit astRoot) { + IMethodBinding methodBinding = getBodyDeclaration(astRoot).resolveBinding(); + return (methodBinding == null) ? null : methodBinding.getReturnType(); + } + + public boolean isPersistable(CompilationUnit astRoot) { + IMethodBinding binding = this.getBinding(astRoot); + return (binding == null) ? false : JPTTools.methodIsPersistablePropertyGetter(new JPTToolsAdapter(binding)); + } + + + // ********** internal ********** + + protected MethodDeclaration[] getDeclaringTypeMethodDeclarations(CompilationUnit astRoot) { + return this.getDeclaringTypeDeclaration(astRoot).getMethods(); + } + + + // ********** JPTTools adapter ********** + + /** + * JPTTools needs an adapter so it can work with either an IMethod + * or an IMethodBinding etc. + */ + protected static class SimpleJPTToolsAdapter + implements JPTTools.SimpleMethodAdapter + { + protected final IMethodBinding methodBinding; + + protected SimpleJPTToolsAdapter(IMethodBinding methodBinding) { + super(); + if (methodBinding == null) { + throw new NullPointerException(); + } + this.methodBinding = methodBinding; + } + + public int getModifiers() { + return this.methodBinding.getModifiers(); + } + + public String getReturnTypeErasureName() { + ITypeBinding returnType = this.methodBinding.getReturnType(); + return (returnType == null) ? null : returnType.getTypeDeclaration().getErasure().getQualifiedName(); + } + + public boolean isConstructor() { + return this.methodBinding.isConstructor(); + } + + } + + protected static class JPTToolsAdapter + extends SimpleJPTToolsAdapter + implements JPTTools.MethodAdapter + { + protected JPTToolsAdapter(IMethodBinding methodBinding) { + super(methodBinding); + } + + public String getName() { + return this.methodBinding.getName(); + } + + public int getParametersLength() { + return this.methodBinding.getParameterTypes().length; + } + + public JPTTools.SimpleMethodAdapter getSibling(String name) { + ITypeBinding typeBinding = this.methodBinding.getDeclaringClass(); + if (typeBinding == null) { + return null; + } + for (IMethodBinding sibling : typeBinding.getDeclaredMethods()) { + if ((sibling.getParameterTypes().length == 0) + && sibling.getName().equals(name)) { + return new SimpleJPTToolsAdapter(sibling); + } + } + return null; + } + + public JPTTools.SimpleMethodAdapter getSibling(String name, String parameterTypeErasureName) { + ITypeBinding typeBinding = this.methodBinding.getDeclaringClass(); + if (typeBinding == null) { + return null; + } + for (IMethodBinding sibling : typeBinding.getDeclaredMethods()) { + ITypeBinding[] siblingParmTypes = sibling.getParameterTypes(); + if ((siblingParmTypes.length == 1) + && sibling.getName().equals(name) + && siblingParmTypes[0].getTypeDeclaration().getErasure().getQualifiedName().equals(parameterTypeErasureName)) { + return new SimpleJPTToolsAdapter(sibling); + } + } + return null; + } + + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTModifiedDeclaration.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTModifiedDeclaration.java new file mode 100644 index 0000000000..96f2c739e7 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTModifiedDeclaration.java @@ -0,0 +1,537 @@ +/******************************************************************************* + * 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()); + } + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTTools.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTTools.java new file mode 100644 index 0000000000..bf1e5661a8 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTTools.java @@ -0,0 +1,92 @@ +/******************************************************************************* +* Copyright (c) 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 org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jpt.core.JptCorePlugin; +import org.eclipse.jpt.utility.Filter; +import org.eclipse.jpt.utility.internal.iterables.ArrayIterable; +import org.eclipse.jpt.utility.internal.iterables.EmptyIterable; +import org.eclipse.jpt.utility.internal.iterables.FilteringIterable; + +/** + * Convenience methods for dealing with JDT core + */ +public final class JDTTools +{ + + public static boolean packageFragmentRootIsSourceFolder(IPackageFragmentRoot pfr) { + try { + return packageFragmentRootIsSourceFolder_(pfr); + } catch (JavaModelException ex) { + JptCorePlugin.log(ex); + return false; + } + } + + protected static boolean packageFragmentRootIsSourceFolder_(IPackageFragmentRoot pfr) throws JavaModelException { + return pfr.exists() && (pfr.getKind() == IPackageFragmentRoot.K_SOURCE); + } + + //TODO move this method to JpaProject once API freeze is over + public static Iterable<IPackageFragmentRoot> getJavaSourceFolders(IJavaProject javaProject) { + try { + return new FilteringIterable<IPackageFragmentRoot>( + getPackageFragmentRoots(javaProject), + SOURCE_PACKAGE_FRAGMENT_ROOT_FILTER + ); + } catch (JavaModelException ex) { + JptCorePlugin.log(ex); + return EmptyIterable.instance(); + } + } + + /** + * This returns the first package fragment root found on this project. + * I am not completely sure why, but the JavaTypeCompletionProcessor works with this. + */ + //TODO move this method to JpaProject once API freeze is over + public static IPackageFragmentRoot getCodeCompletionContextRoot(IJavaProject javaProject) { + try { + return javaProject.getPackageFragmentRoots()[0]; + } + catch (JavaModelException e) { + JptCorePlugin.log(e); + return null; + } + } + + protected static Iterable<IPackageFragmentRoot> getJavaSourceFolders_(IJavaProject javaProject) throws JavaModelException { + return new FilteringIterable<IPackageFragmentRoot>( + getPackageFragmentRoots(javaProject), + SOURCE_PACKAGE_FRAGMENT_ROOT_FILTER + ); + } + + protected static final Filter<IPackageFragmentRoot> SOURCE_PACKAGE_FRAGMENT_ROOT_FILTER = + new Filter<IPackageFragmentRoot>() { + public boolean accept(IPackageFragmentRoot pfr) { + try { + return this.accept_(pfr); + } catch (JavaModelException ex) { + return false; + } + } + private boolean accept_(IPackageFragmentRoot pfr) throws JavaModelException { + return packageFragmentRootIsSourceFolder_(pfr); + } + }; + + protected static Iterable<IPackageFragmentRoot> getPackageFragmentRoots(IJavaProject javaProject) throws JavaModelException { + return new ArrayIterable<IPackageFragmentRoot>(javaProject.getPackageFragmentRoots()); + } +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTType.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTType.java new file mode 100644 index 0000000000..f3438cfe8f --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JDTType.java @@ -0,0 +1,238 @@ +/******************************************************************************* + * Copyright (c) 2005, 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.List; + +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.FieldDeclaration; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jpt.core.utility.TextRange; +import org.eclipse.jpt.core.utility.jdt.AnnotationEditFormatter; +import org.eclipse.jpt.core.utility.jdt.Type; +import org.eclipse.jpt.utility.CommandExecutor; + +/** + * Adapt and extend a JDT type. + */ +public class JDTType + extends JDTMember + implements Type +{ + + /** + * constructor for the compilation unit's primary type + */ + public JDTType( + TypeDeclaration typeDeclaration, // exclude annotations and enums + ICompilationUnit compilationUnit, + CommandExecutor modifySharedDocumentCommandExecutor) { + this(typeDeclaration, compilationUnit, modifySharedDocumentCommandExecutor, DefaultAnnotationEditFormatter.instance()); + } + + /** + * constructor for the compilation unit's primary type + */ + public JDTType( + TypeDeclaration typeDeclaration, // exclude annotations and enums + ICompilationUnit compilationUnit, + CommandExecutor modifySharedDocumentCommandExecutor, + AnnotationEditFormatter annotationEditFormatter) { + this(null, typeDeclaration, 1, compilationUnit, modifySharedDocumentCommandExecutor, annotationEditFormatter); + } + + /** + * constructor for nested types + */ + public JDTType( + Type declaringType, + TypeDeclaration typeDeclaration, // exclude annotations and enums + int occurrence, + ICompilationUnit compilationUnit, + CommandExecutor modifySharedDocumentCommandExecutor) { + this(declaringType, typeDeclaration, occurrence, compilationUnit, modifySharedDocumentCommandExecutor, DefaultAnnotationEditFormatter.instance()); + } + + /** + * constructor for nested types + */ + public JDTType( + Type declaringType, + TypeDeclaration typeDeclaration, // exclude annotations and enums + int occurrence, + ICompilationUnit compilationUnit, + CommandExecutor modifySharedDocumentCommandExecutor, + AnnotationEditFormatter annotationEditFormatter) { + super(declaringType, typeDeclaration.getName().getFullyQualifiedName(), occurrence, compilationUnit, modifySharedDocumentCommandExecutor, annotationEditFormatter); + } + + /** + * constructor for testing + */ + public JDTType(Type declaringType, String name, int occurrence, ICompilationUnit compilationUnit) { + super(declaringType, name, occurrence, compilationUnit, CommandExecutor.Default.instance(), DefaultAnnotationEditFormatter.instance()); + } + + + // ********** Member/Type implementation ********** + + public ITypeBinding getBinding(CompilationUnit astRoot) { + TypeDeclaration td = this.getBodyDeclaration(astRoot); + return (td == null) ? null : td.resolveBinding(); + } + + /** + * find the type's body declaration in the specified AST + */ + public TypeDeclaration getBodyDeclaration(CompilationUnit astRoot) { + Type declaringType = this.getDeclaringType(); + if (declaringType == null) { + return this.getTopLevelTypeDeclaration(astRoot); + } + TypeDeclaration typeDeclaration = declaringType.getBodyDeclaration(astRoot); + // the type declaration can be null when the source is completely hosed + return (typeDeclaration == null) ? null : this.getNestedTypeDeclaration(typeDeclaration); + } + + public boolean isPersistable(CompilationUnit astRoot) { + ITypeBinding binding = this.getBinding(astRoot); + return (binding == null) ? false : JPTTools.typeIsPersistable(new JPTToolsAdapter(binding)); + } + + public TextRange getNameTextRange(CompilationUnit astRoot) { + return new ASTNodeTextRange(this.getBodyDeclaration(astRoot).getName()); + } + + public TypeDeclaration[] getTypes(CompilationUnit astRoot) { + return this.getBodyDeclaration(astRoot).getTypes(); + } + + public FieldDeclaration[] getFields(CompilationUnit astRoot) { + return this.getBodyDeclaration(astRoot).getFields(); + } + + public MethodDeclaration[] getMethods(CompilationUnit astRoot) { + return this.getBodyDeclaration(astRoot).getMethods(); + } + + + // ********** internal ********** + + /** + * return the first top-level type in the specified AST with a matching name + */ + protected TypeDeclaration getTopLevelTypeDeclaration(CompilationUnit astRoot) { + return this.getTypeDeclaration(types(astRoot)); + } + + protected TypeDeclaration getTypeDeclaration(List<AbstractTypeDeclaration> typeDeclarations) { + return this.getTypeDeclaration(typeDeclarations.toArray(new AbstractTypeDeclaration[typeDeclarations.size()])); + } + + /** + * return the nested type with a matching name and occurrence + */ + protected TypeDeclaration getNestedTypeDeclaration(TypeDeclaration declaringTypeDeclaration) { + return this.getTypeDeclaration(declaringTypeDeclaration.getTypes()); + } + + /** + * return the type declaration corresponding to the type from the specified + * set of type declarations (match name and occurrence) + */ + protected TypeDeclaration getTypeDeclaration(AbstractTypeDeclaration[] typeDeclarations) { + String name = this.getName_(); + int occurrence = this.getOccurrence(); + int count = 0; + for (AbstractTypeDeclaration typeDeclaration : typeDeclarations) { + if (typeDeclaration.getName().getFullyQualifiedName().equals(name)) { + count++; + if (count == occurrence) { + return (typeDeclaration.getNodeType() == ASTNode.TYPE_DECLARATION) ? (TypeDeclaration) typeDeclaration : null; + } + } + } + // return null if the type is no longer in the source code; + // this can happen when the context model has not yet + // been synchronized with the resource model but is still + // asking for an ASTNode (e.g. during a selection event) + return null; + } + + /** + * we only instantiate a single top-level, non-enum, non-annotation + * type per compilation unit (i.e. a class or interface); and, since + * enums and annotations can only be top-level types (i.e. they cannot + * be nested within another type) we will always have TypeDeclarations + * in the CompilationUnit + */ + // minimize scope of suppressed warnings + @SuppressWarnings("unchecked") + protected static List<AbstractTypeDeclaration> types(CompilationUnit astRoot) { + return astRoot.types(); + } + + + // ********** JPT tools adapter ********** + + protected static class JPTToolsAdapter implements JPTTools.TypeAdapter { + private final ITypeBinding typeBinding; + protected JPTToolsAdapter(ITypeBinding typeBinding) { + super(); + if (typeBinding == null) { + throw new NullPointerException(); + } + this.typeBinding = typeBinding; + } + + public int getModifiers() { + return this.typeBinding.getModifiers(); + } + + public boolean isAnnotation() { + return this.typeBinding.isAnnotation(); + } + + public boolean isAnonymous() { + return this.typeBinding.isAnonymous(); + } + + public boolean isArray() { + return this.typeBinding.isArray(); + } + + public boolean isEnum() { + return this.typeBinding.isEnum(); + } + + public boolean isInterface() { + return this.typeBinding.isInterface(); + } + + public boolean isLocal() { + return this.typeBinding.isLocal(); + } + + public boolean isMember() { + return this.typeBinding.isMember(); + } + + public boolean isPrimitive() { + return this.typeBinding.isPrimitive(); + } + + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JPTTools.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JPTTools.java new file mode 100644 index 0000000000..73b19d863b --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/JPTTools.java @@ -0,0 +1,341 @@ +/******************************************************************************* + * Copyright (c) 2005, 2009 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.lang.reflect.Modifier; +import java.util.Iterator; +import org.eclipse.jpt.core.resource.java.AccessType; +import org.eclipse.jpt.core.resource.java.JavaResourcePersistentAttribute; +import org.eclipse.jpt.core.resource.java.JavaResourcePersistentType; + +/** + * Convenience methods for JPA-related queries concerning JDT objects. + */ +public class JPTTools { + + // ********** type ********** + + /** + * Return whether the specified type can be "persisted", i.e. marked as + * Entity, MappedSuperclass, Embeddable + */ + // TODO check for no-arg constructor (or should that just be validation?) + // TODO move other checks to validation (e.g. 'final', 'static')? + public static boolean typeIsPersistable(TypeAdapter typeAdapter) { + if (typeAdapter.isInterface()) { + return false; + } + if (typeAdapter.isAnnotation()) { + return false; + } + if (typeAdapter.isEnum()) { + return false; + } + if (typeAdapter.isLocal()) { + return false; + } + if (typeAdapter.isAnonymous()) { + return false; + } + if (typeAdapter.isPrimitive()) { + return false; // should never get here(?) + } + if (typeAdapter.isArray()) { + return false; // should never get here(?) + } + int modifiers = typeAdapter.getModifiers(); + if (typeAdapter.isMember()) { + if ( ! Modifier.isStatic(modifiers)) { + return false; + } + } + return true; + } + + /** + * Queries needed to calculate whether a type is "persistable". + * Adapted to ITypeBinding and IType. + */ + public interface TypeAdapter { + int getModifiers(); + boolean isAnnotation(); + boolean isAnonymous(); + boolean isArray(); + boolean isEnum(); + boolean isInterface(); + boolean isLocal(); + boolean isMember(); + boolean isPrimitive(); + } + + + // ********** field ********** + + /** + * Return whether the specified field may be "persisted". + * According to the spec, "All non-transient instance variables that are not + * annotated with the Transient annotation are persistent." + */ + public static boolean fieldIsPersistable(FieldAdapter fieldAdapter) { + int modifiers = fieldAdapter.getModifiers(); + if (Modifier.isStatic(modifiers)) { + return false; + } + if (Modifier.isTransient(modifiers)) { + return false; + } + return true; + } + + /** + * Queries needed to calculate whether a field is "persistable". + * Adapted to IVariableBinding and IField. + */ + public interface FieldAdapter { + /** + * Return the field's modifiers. We use these to check whether the + * field is static or transient. + */ + int getModifiers(); + } + + + // ********** method ********** + + /** + * Return whether the specified method is a "getter" method that + * represents a property that may be "persisted". + */ + public static boolean methodIsPersistablePropertyGetter(MethodAdapter methodAdapter) { + if (methodHasInvalidModifiers(methodAdapter)) { + return false; + } + if (methodAdapter.isConstructor()) { + return false; + } + + String returnTypeName = methodAdapter.getReturnTypeErasureName(); + if (returnTypeName == null) { + return false; // DOM method bindings can have a null name + } + if (returnTypeName.equals("void")) { //$NON-NLS-1$ + return false; + } + if (methodHasParameters(methodAdapter)) { + return false; + } + + String name = methodAdapter.getName(); + int beginIndex = 0; + boolean booleanGetter = false; + if (name.startsWith("is")) { //$NON-NLS-1$ + if (returnTypeName.equals("boolean")) { //$NON-NLS-1$ + beginIndex = 2; + } else { + return false; + } + } else if (name.startsWith("get")) { //$NON-NLS-1$ + beginIndex = 3; + if (returnTypeName.equals("boolean")) { //$NON-NLS-1$ + booleanGetter = true; + } + } else { + return false; + } + + String capitalizedAttributeName = name.substring(beginIndex); + // if the type has both methods: + // boolean isProperty() + // boolean getProperty() + // then #isProperty() takes precedence and we ignore #getProperty(); + // but only having #getProperty() is OK too + // (see the JavaBeans spec 1.01) + if (booleanGetter && methodHasValidSiblingIsMethod(methodAdapter, capitalizedAttributeName)) { + return false; // since the type also defines #isProperty(), ignore #getProperty() + } + return methodHasValidSiblingSetMethod(methodAdapter, capitalizedAttributeName, returnTypeName); + } + + /** + * Return whether the method's modifiers prevent it + * from being a getter or setter for a "persistent" property. + */ + private static boolean methodHasInvalidModifiers(SimpleMethodAdapter methodAdapter) { + int modifiers = methodAdapter.getModifiers(); + if (Modifier.isStatic(modifiers)) { + return true; + } + if (Modifier.isFinal(modifiers)) { + return true; + } + if ( ! (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers))) { + return true; + } + return false; + } + + private static boolean methodHasParameters(MethodAdapter methodAdapter) { + return methodAdapter.getParametersLength() != 0; + } + + /** + * Return whether the method has a sibling "is" method for the specified + * property and that method is valid for a "persistable" property. + * Pre-condition: the method is a "boolean getter" (e.g. 'public boolean getProperty()'); + * this prevents us from returning true when the method itself is an + * "is" method. + */ + private static boolean methodHasValidSiblingIsMethod(MethodAdapter methodAdapter, String capitalizedAttributeName) { + SimpleMethodAdapter isMethodAdapter = methodAdapter.getSibling("is" + capitalizedAttributeName); //$NON-NLS-1$ + return methodIsValidSibling(isMethodAdapter, "boolean"); //$NON-NLS-1$ + } + + /** + * Return whether the method has a sibling "set" method + * and that method is valid for a "persistable" property. + */ + private static boolean methodHasValidSiblingSetMethod(MethodAdapter methodAdapter, String capitalizedAttributeName, String parameterTypeErasureName) { + SimpleMethodAdapter setMethodAdapter = methodAdapter.getSibling("set" + capitalizedAttributeName, parameterTypeErasureName); //$NON-NLS-1$ + return methodIsValidSibling(setMethodAdapter, "void"); //$NON-NLS-1$ + } + + /** + * Return whether the specified method is a valid sibling with the + * specified return type. + */ + private static boolean methodIsValidSibling(SimpleMethodAdapter methodAdapter, String returnTypeName) { + if (methodAdapter == null) { + return false; + } + if (methodHasInvalidModifiers(methodAdapter)) { + return false; + } + if (methodAdapter.isConstructor()) { + return false; + } + String rtName = methodAdapter.getReturnTypeErasureName(); + if (rtName == null) { + return false; // DOM method bindings can have a null name + } + return rtName.equals(returnTypeName); + } + + /** + * Queries needed to calculate whether a method is "persistable". + * Adapted to IMethodBinding and IMethod. + */ + public interface SimpleMethodAdapter { + /** + * Return the method's modifiers. + * We use these to check whether the method is static, final, etc. + */ + int getModifiers(); + + /** + * Return the name of the method's return type erasure. + * We use this to check for + * - boolean getters + * - void return types + * - matching getters and setters + */ + String getReturnTypeErasureName(); + + /** + * Return whether the method is a constructor. + */ + boolean isConstructor(); + } + + /** + * Queries needed to calculate whether a method is "persistable". + * Adapted to IMethodBinding and IMethod. + */ + public interface MethodAdapter extends SimpleMethodAdapter { + /** + * Return the method's name. + * We use this to determine + * - whether the method is a "getter" + * - the property name implied by the getter's name + */ + String getName(); + + /** + * Return the number of paramters declared by the method. + * We use this to determine whether the method is a "getter". + */ + int getParametersLength(); + + /** + * Return the method's "sibling" with the specified name and no parameters. + * We use this to find an "is" boolean getter that would take precedence + * over a "get" boolean getter. + */ + SimpleMethodAdapter getSibling(String name); + + /** + * Return the method's "sibling" with the specified name and single parameter. + * We use this to find a matching "setter" for a possible "getter". + */ + SimpleMethodAdapter getSibling(String name, String parameterTypeErasureName); + } + + + // ********** Access type ********** + + /** + * Return the AccessType currently implied by the Java source code + * or class file: + * - if only Fields are annotated => FIELD + * - if only Properties are annotated => PROPERTY + * - if both Fields and Properties are annotated => FIELD + * - if nothing is annotated + * - and fields exist => FIELD + * - and properties exist, but no fields exist => PROPERTY + * - and neither fields nor properties exist => null at this level (FIELD in the context model) + */ + public static AccessType buildAccess(JavaResourcePersistentType jrpType) { + boolean hasPersistableFields = false; + for (Iterator<JavaResourcePersistentAttribute> stream = jrpType.persistableFields(); stream.hasNext(); ) { + hasPersistableFields = true; + if (stream.next().isAnnotated()) { + // any field is annotated => FIELD + return AccessType.FIELD; + } + } + + boolean hasPersistableProperties = false; + for (Iterator<JavaResourcePersistentAttribute> stream = jrpType.persistableProperties(); stream.hasNext(); ) { + hasPersistableProperties = true; + if (stream.next().isAnnotated()) { + // none of the fields are annotated and a getter is annotated => PROPERTY + return AccessType.PROPERTY; + } + } + + if (hasPersistableProperties && ! hasPersistableFields) { + return AccessType.PROPERTY; + } + + // if no annotations exist, access is null at the resource model level + return null; + } + + + // ********** suppressed constructor ********** + + /** + * Suppress default constructor, ensuring non-instantiability. + */ + private JPTTools() { + super(); + throw new UnsupportedOperationException(); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/MemberAnnotationAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/MemberAnnotationAdapter.java new file mode 100644 index 0000000000..5b92fa57d8 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/MemberAnnotationAdapter.java @@ -0,0 +1,27 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationAdapter; +import org.eclipse.jpt.core.utility.jdt.Member; + +/** + * Adapt a member and a declaration annotation adapter. + */ +public class MemberAnnotationAdapter extends AbstractAnnotationAdapter { + + + // ********** constructor ********** + + public MemberAnnotationAdapter(Member member, DeclarationAnnotationAdapter daa) { + super(member, daa); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/MemberAnnotationElementAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/MemberAnnotationElementAdapter.java new file mode 100644 index 0000000000..89f854f2e5 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/MemberAnnotationElementAdapter.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jpt.core.utility.jdt.AnnotationElementAdapter; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationElementAdapter; +import org.eclipse.jpt.core.utility.jdt.Member; +import org.eclipse.jpt.core.utility.jdt.ModifiedDeclaration; +import org.eclipse.jpt.utility.internal.StringTools; + + /** + * Adapt a member and a declaration annotation element adapter. + */ +public class MemberAnnotationElementAdapter<T> + implements AnnotationElementAdapter<T> +{ + private final Member member; + private final DeclarationAnnotationElementAdapter<T> daea; + + + // ********** constructor ********** + + public MemberAnnotationElementAdapter(Member member, DeclarationAnnotationElementAdapter<T> daea) { + super(); + this.member = member; + this.daea = daea; + } + + + // ********** AnnotationElementAdapter implementation ********** + + public T getValue() { + return this.daea.getValue(this.member.getModifiedDeclaration()); + } + + public T getValue(CompilationUnit astRoot) { + return this.daea.getValue(this.member.getModifiedDeclaration(astRoot)); + } + + public void setValue(T value) { + this.edit(this.buildSetValueEditor(value)); + } + + public Expression getExpression(CompilationUnit astRoot) { + return this.daea.getExpression(this.member.getModifiedDeclaration(astRoot)); + } + + public ASTNode getAstNode(CompilationUnit astRoot) { + return this.daea.getAstNode(this.member.getModifiedDeclaration(astRoot)); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.daea); + } + + + // ********** internal methods ********** + + protected void edit(Member.Editor editor) { + this.member.edit(editor); + } + + protected Member.Editor buildSetValueEditor(T value) { + return new SetValueEditor<T>(value, this.daea); + } + + + // ********** member classes ********** + + protected static class SetValueEditor<T> implements Member.Editor { + private final DeclarationAnnotationElementAdapter<T> daea; + private final T value; + + SetValueEditor(T value, DeclarationAnnotationElementAdapter<T> daea) { + super(); + this.value = value; + this.daea = daea; + } + public void edit(ModifiedDeclaration declaration) { + this.daea.setValue(this.value, declaration); + } + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/MemberIndexedAnnotationAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/MemberIndexedAnnotationAdapter.java new file mode 100644 index 0000000000..aceeb48616 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/MemberIndexedAnnotationAdapter.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 org.eclipse.jpt.core.utility.jdt.IndexedAnnotationAdapter; +import org.eclipse.jpt.core.utility.jdt.IndexedDeclarationAnnotationAdapter; +import org.eclipse.jpt.core.utility.jdt.Member; +import org.eclipse.jpt.core.utility.jdt.ModifiedDeclaration; +import org.eclipse.jpt.utility.internal.StringTools; + +/** + * Adapt a member and an indexed declaration annotation adapter. + */ +public class MemberIndexedAnnotationAdapter + extends AbstractAnnotationAdapter + implements IndexedAnnotationAdapter +{ + private final IndexedDeclarationAnnotationAdapter idaa; + + + // ********** constructor ********** + + public MemberIndexedAnnotationAdapter(Member member, IndexedDeclarationAnnotationAdapter idaa) { + super(member, idaa); + this.idaa = idaa; + } + + + // ********** IndexedAnnotationAdapter implementation ********** + + public int getIndex() { + return this.idaa.getIndex(); + } + + public void moveAnnotation(int newIndex) { + this.edit(this.buildMoveAnnotationEditor(newIndex)); + } + + + // ********** factory methods ********** + + protected Member.Editor buildMoveAnnotationEditor(int newIndex) { + return new MoveAnnotationEditor(this.idaa, newIndex); + } + + + // ********** member classes ********** + + protected static class MoveAnnotationEditor implements Member.Editor { + private final IndexedDeclarationAnnotationAdapter idaa; + private int index; + + MoveAnnotationEditor(IndexedDeclarationAnnotationAdapter idaa, int index) { + super(); + this.idaa = idaa; + this.index = index; + } + public void edit(ModifiedDeclaration declaration) { + this.idaa.moveAnnotation(this.index, declaration); + } + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NameStringExpressionConverter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NameStringExpressionConverter.java new file mode 100644 index 0000000000..56fd65a386 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NameStringExpressionConverter.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jpt.core.utility.jdt.ExpressionConverter; + +/** + * Convert a name to/from a string representation of a name/identifier + * (e.g. "com.xxx.Foo.VALUE1" or "value"). + */ +public final class NameStringExpressionConverter + extends AbstractExpressionConverter<String> +{ + private static final ExpressionConverter<String> INSTANCE = new NameStringExpressionConverter(); + + /** + * Return the singleton. + */ + public static ExpressionConverter<String> instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private NameStringExpressionConverter() { + super(); + } + + @Override + protected Name convertObject(String string, AST ast) { + return ast.newName(string); + } + + @Override + protected String convertExpression(Expression expression) { + switch (expression.getNodeType()) { + case ASTNode.QUALIFIED_NAME: + case ASTNode.SIMPLE_NAME: + return ((Name) expression).getFullyQualifiedName(); + default: + return null; + } + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NestedDeclarationAnnotationAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NestedDeclarationAnnotationAdapter.java new file mode 100644 index 0000000000..2b991a7c2e --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NestedDeclarationAnnotationAdapter.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationAdapter; +import org.eclipse.jpt.core.utility.jdt.ModifiedDeclaration; + +/** + * Manipulate an annotation that is embedded as an element within + * another annotation, e.g. + * <pre> + * @Outer(foo=@Inner) + * private int id; + * outerAnnotationAdapter = AnnotationAdapter<@Outer> + * elementName = "foo" + * annotationName = "Inner" + * </pre> + */ +public class NestedDeclarationAnnotationAdapter extends AbstractNestedDeclarationAnnotationAdapter { + + + // ********** constructors ********** + + /** + * default element name is "value"; + * default behavior is to remove the outer annotation when it is empty + */ + public NestedDeclarationAnnotationAdapter(DeclarationAnnotationAdapter outerAnnotationAdapter, String annotationName) { + super(outerAnnotationAdapter, annotationName); + } + + /** + * default behavior is to remove the outer annotation when it is empty + */ + public NestedDeclarationAnnotationAdapter(DeclarationAnnotationAdapter outerAnnotationAdapter, String elementName, String annotationName) { + super(outerAnnotationAdapter, elementName, annotationName); + } + + public NestedDeclarationAnnotationAdapter(DeclarationAnnotationAdapter outerAnnotationAdapter, String elementName, String annotationName, boolean removeOuterAnnotationWhenEmpty) { + super(outerAnnotationAdapter, elementName, annotationName, removeOuterAnnotationWhenEmpty); + } + + + // ********** AbstractNestedDeclarationAnnotationAdapter implementation ********** + + @Override + protected Annotation getAnnotation(Expression value) { + return this.annotationValue(value); + } + + @Override + protected Expression buildNewInnerExpression(Annotation inner) { + return inner; + } + + /** + * the annotation is the expression itself, so the annotation cannot be + * "removed" from itself - return 'false' + */ + @Override + protected boolean removeAnnotation(ModifiedDeclaration declaration, Annotation outer, Expression value) { + return false; + } + + /** + * <pre> + * @Outer("lorem ipsum") => @Outer(@Inner) + * </pre> + */ + @Override + protected void modifyAnnotationValue(SingleMemberAnnotation outer, Annotation inner) { + // replace(?) the current element value + outer.setValue(inner); + } + + /** + * Simply set the pair's value. + */ + @Override + protected void modifyMemberValuePair(MemberValuePair pair, Annotation inner) { + pair.setValue(inner); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NestedIndexedDeclarationAnnotationAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NestedIndexedDeclarationAnnotationAdapter.java new file mode 100644 index 0000000000..8f39485e0e --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NestedIndexedDeclarationAnnotationAdapter.java @@ -0,0 +1,334 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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.List; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.ArrayInitializer; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationAdapter; +import org.eclipse.jpt.core.utility.jdt.IndexedDeclarationAnnotationAdapter; +import org.eclipse.jpt.core.utility.jdt.ModifiedDeclaration; + +/** + * Manipulate an annotation that is embedded in an element array within + * another annotation, e.g. + * <pre> + * @Outer(foo={@Inner("zero"), @Inner("one"), @Inner("two")}) + * private int id; + * outerAnnotationAdapter = AnnotationAdapter<@Outer> + * elementName = "foo" + * index = 0-2 + * annotationName = "Inner" + * </pre> + */ +public class NestedIndexedDeclarationAnnotationAdapter + extends AbstractNestedDeclarationAnnotationAdapter + implements IndexedDeclarationAnnotationAdapter +{ + private int index; + + + // ********** constructors ********** + + /** + * default element name is "value"; + * default behavior is to remove the outer annotation when it is empty + */ + public NestedIndexedDeclarationAnnotationAdapter(DeclarationAnnotationAdapter annotationAdapter, int index, String annotationName) { + super(annotationAdapter, annotationName); + this.index = index; + } + + /** + * default behavior is to remove the outer annotation when it is empty + */ + public NestedIndexedDeclarationAnnotationAdapter(DeclarationAnnotationAdapter annotationAdapter, String elementName, int index, String annotationName) { + super(annotationAdapter, elementName, annotationName); + this.index = index; + } + + public NestedIndexedDeclarationAnnotationAdapter(DeclarationAnnotationAdapter annotationAdapter, String elementName, int index, String annotationName, boolean removeOuterAnnotationWhenEmpty) { + super(annotationAdapter, elementName, annotationName, removeOuterAnnotationWhenEmpty); + this.index = index; + } + + + // ********** AbstractNestedDeclarationAnnotationAdapter implementation ********** + + @Override + protected Annotation getAnnotation(Expression value) { + if (value.getNodeType() == ASTNode.ARRAY_INITIALIZER) { + return this.annotation((ArrayInitializer) value); + } + return (this.index == 0) ? this.annotationValue(value) : null; + } + + @Override + protected Expression buildNewInnerExpression(Annotation inner) { + return (this.index == 0) ? inner : (Expression) this.buildNewInnerArrayInitializer(inner); + } + + @Override + protected boolean removeAnnotation(ModifiedDeclaration declaration, Annotation outer, Expression value) { + if (value.getNodeType() == ASTNode.ARRAY_INITIALIZER) { + this.removeAnnotation(declaration, outer, (ArrayInitializer) value); + return true; + } + // if our index is greater than zero, but we don't have an array, + // then the annotation must already be gone + return (this.index != 0); + } + + /** + * <pre> + * @Outer({@Inner(0), @Inner(1)}) => @Outer({@Inner(0), @Inner(1), @Inner(2)}) + * or + * @Outer("lorem ipsum") => @Outer(@Inner(0)) + * or + * @Outer(@Inner(0)) => @Outer({@Inner(0), @Inner(1)}) + * </pre> + */ + @Override + protected void modifyAnnotationValue(SingleMemberAnnotation outer, Annotation inner) { + this.modifyExpression(outer, SINGLE_MEMBER_ANNOTATION_EXPRESSION_PROVIDER, inner); + } + + /** + * <pre> + * @Outer(text="lorem ipsum") => @Outer(text="lorem ipsum", foo=@Inner(0)) + * or + * @Outer(foo={@Inner(0), @Inner(1)}) => @Outer(foo={@Inner(0), @Inner(1), @Inner(2)}) + * or + * @Outer(foo="lorem ipsum") => @Outer(foo=@Inner(0)) + * or + * @Outer(foo=@NotInner) => @Outer(foo=@Inner(0)) + * or + * @Outer(foo=@Inner(0)) => @Outer(foo={@Inner(0), @Inner(1)}) + * </pre> + */ + @Override + protected void modifyMemberValuePair(MemberValuePair pair, Annotation inner) { + this.modifyExpression(pair, MEMBER_VALUE_PAIR_EXPRESSION_PROVIDER, inner); + } + + + // ********** IndexedDeclarationAnnotationAdapter implementation ********** + + public int getIndex() { + return this.index; + } + + /** + * Move the annotation to the specified index, leaving its original + * position cleared out. + */ + public void moveAnnotation(int newIndex, ModifiedDeclaration declaration) { + int oldIndex = this.index; + if (newIndex == oldIndex) { + return; + } + + Annotation original = this.getAnnotation(declaration); + if (original == null) { + this.index = newIndex; + this.removeAnnotation(declaration); // clear out the new location (?) + } else { + Annotation copy = (Annotation) ASTNode.copySubtree(original.getAST(), original); + this.index = newIndex; + this.addAnnotation(declaration, copy); // install the copy in the new location + this.index = oldIndex; + this.removeAnnotation(declaration); // go back and clear out the original location (AFTER the move) + this.index = newIndex; + } + } + + + // ********** internal methods ********** + + /** + * Return the adapter's annotation from the specified array initializer. + */ + private Annotation annotation(ArrayInitializer value) { + List<Expression> expressions = this.expressions(value); + return (this.index >= expressions.size()) ? null : this.annotationValue(expressions.get(this.index)); + } + + /** + * Build a new array initializer to hold the specified annotation, + * padding it with 'null' literals as necessary + */ + private ArrayInitializer buildNewInnerArrayInitializer(Annotation inner) { + ArrayInitializer ai = inner.getAST().newArrayInitializer(); + this.addInnerToExpressions(inner, this.expressions(ai)); + return ai; + } + + /** + * Add the specified annotation to the specified array initializer, + * padding it with 'null' literals as necessary + */ + private void addInnerToExpressions(Annotation inner, List<Expression> expressions) { + if (expressions.size() > this.index) { + throw new IllegalStateException("expressions size is greater than index - size: " + expressions.size() + " - index: " + this.index); //$NON-NLS-1$ //$NON-NLS-2$ + } + while (expressions.size() < this.index) { + expressions.add(inner.getAST().newNullLiteral()); + } + expressions.add(inner); + } + + /** + * Remove the adapter's annotation from the specified array initializer. + */ + private void removeAnnotation(ModifiedDeclaration declaration, Annotation outer, ArrayInitializer value) { + List<Expression> expressions = this.expressions(value); + if (this.index >= expressions.size()) { + return; // avoid IndexOutOfBoundsException(?) + } + Annotation inner = this.annotationValue(expressions.get(this.index)); + if (inner == null) { + return; + } + if ( ! this.nameMatches(declaration, inner)) { + return; + } + if (this.index == (expressions.size() - 1)) { + expressions.remove(this.index); + } else { + expressions.set(this.index, value.getAST().newNullLiteral()); + } + this.trimExpressions(declaration, outer, expressions); + } + + /** + * Strip all the null literals off the end of the specified list of expressions + * and normalize the specified outer annotation. + */ + private void trimExpressions(ModifiedDeclaration declaration, Annotation outer, List<Expression> expressions) { + // start at the end of the list + for (int i = expressions.size(); i-- > 0; ) { + if (expressions.get(i).getNodeType() == ASTNode.NULL_LITERAL) { + expressions.remove(i); + } else { + break; // stop with the first non-null literal encountered + } + } + switch (expressions.size()) { + case 0: + this.removeElementAndNormalize(declaration, outer); + break; + case 1: + this.convertArrayToLastRemainingExpression(outer, expressions.get(0)); + break; + default: + break; + } + } + + /** + * When there is only a single element in an array initializer, convert the + * expression to be just the single element; e.g. + * <pre> + * @Foo(xxx={"abc"}) => @Foo(xxx="abc") + * or + * @Foo({"abc"}) => @Foo("abc") + * </pre> + */ + private void convertArrayToLastRemainingExpression(Annotation outer, Expression lastValue) { + lastValue = (Expression) ASTNode.copySubtree(lastValue.getAST(), lastValue); + if (outer.isNormalAnnotation()) { + this.memberValuePair((NormalAnnotation) outer).setValue(lastValue); + } else if (outer.isSingleMemberAnnotation()) { + ((SingleMemberAnnotation) outer).setValue(lastValue); + } else { + throw new IllegalArgumentException("unexpected annotation type: " + outer); //$NON-NLS-1$ + } + } + + /** + * Manipulate the specified expression appropriately. + * If it is an array initializer, add the specified annotation to it. + * If it is not, replace the expression or convert it into an array + * initializer. + */ + private void modifyExpression(ASTNode node, ExpressionProvider expProvider, Annotation inner) { + Expression value = expProvider.getExpression(node); + if (value.getNodeType() == ASTNode.ARRAY_INITIALIZER) { + // ignore the other entries in the array initializer(?) - they may not be matching Annotations... + List<Expression> expressions = this.expressions((ArrayInitializer) value); + if (this.index >= expressions.size()) { + this.addInnerToExpressions(inner, expressions); + } else { + expressions.set(this.index, inner); + } + } else { + if (this.index == 0) { + // replace whatever was there before + expProvider.setExpression(node, inner); + } else { + // convert to an array + ArrayInitializer ai = inner.getAST().newArrayInitializer(); + List<Expression> expressions = this.expressions(ai); + expressions.add((Expression) ASTNode.copySubtree(value.getAST(), value)); + this.addInnerToExpressions(inner, expressions); + expProvider.setExpression(node, ai); + } + } + } + + @SuppressWarnings("unchecked") + protected List<Expression> expressions(ArrayInitializer ai) { + return ai.expressions(); + } + + + // ********** expression providers ********** + + /** + * define interface that allows us to "re-use" the code in + * #modifyExpression(ASTNode, ExpressionProvider, Annotation) + */ + private interface ExpressionProvider { + Expression getExpression(ASTNode node); + void setExpression(ASTNode node, Expression expression); + } + + private static final ExpressionProvider MEMBER_VALUE_PAIR_EXPRESSION_PROVIDER = new ExpressionProvider() { + public Expression getExpression(ASTNode node) { + return ((MemberValuePair) node).getValue(); + } + public void setExpression(ASTNode node, Expression expression) { + ((MemberValuePair) node).setValue(expression); + } + @Override + public String toString() { + return "MemberValuePairExpressionProvider"; //$NON-NLS-1$ + } + }; + + private static final ExpressionProvider SINGLE_MEMBER_ANNOTATION_EXPRESSION_PROVIDER = new ExpressionProvider() { + public Expression getExpression(ASTNode node) { + return ((SingleMemberAnnotation) node).getValue(); + } + public void setExpression(ASTNode node, Expression expression) { + ((SingleMemberAnnotation) node).setValue(expression); + } + @Override + public String toString() { + return "SingleMemberAnnotationExpressionProvider"; //$NON-NLS-1$ + } + }; + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NullAnnotationEditFormatter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NullAnnotationEditFormatter.java new file mode 100644 index 0000000000..3c632f36c3 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NullAnnotationEditFormatter.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 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 org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jpt.core.utility.jdt.AnnotationEditFormatter; +import org.eclipse.text.edits.MalformedTreeException; +import org.eclipse.text.edits.TextEdit; + +public final class NullAnnotationEditFormatter + implements AnnotationEditFormatter +{ + + private static final NullAnnotationEditFormatter INSTANCE = new NullAnnotationEditFormatter(); + + /** + * Return the singleton. + */ + public static AnnotationEditFormatter instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private NullAnnotationEditFormatter() { + super(); + } + + public void format(IDocument doc, TextEdit editTree) throws MalformedTreeException, BadLocationException { + // no formatting + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NullDeclarationAnnotationAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NullDeclarationAnnotationAdapter.java new file mode 100644 index 0000000000..d81448ce1b --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NullDeclarationAnnotationAdapter.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 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 org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.MarkerAnnotation; +import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jpt.core.utility.jdt.IndexedDeclarationAnnotationAdapter; +import org.eclipse.jpt.core.utility.jdt.ModifiedDeclaration; + +/** + * Behaviorless implementation. + */ +public final class NullDeclarationAnnotationAdapter + implements IndexedDeclarationAnnotationAdapter +{ + + // singleton + private static final NullDeclarationAnnotationAdapter INSTANCE = new NullDeclarationAnnotationAdapter(); + + /** + * Return the singleton. + */ + public static IndexedDeclarationAnnotationAdapter instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private NullDeclarationAnnotationAdapter() { + super(); + } + + + // ********** DeclarationAnnotationAdapter implementation ********** + + public Annotation getAnnotation(ModifiedDeclaration declaration) { + return null; + } + + public MarkerAnnotation newMarkerAnnotation(ModifiedDeclaration declaration) { + return null; + } + + public SingleMemberAnnotation newSingleMemberAnnotation(ModifiedDeclaration declaration) { + return null; + } + + public NormalAnnotation newNormalAnnotation(ModifiedDeclaration declaration) { + return null; + } + + public void removeAnnotation(ModifiedDeclaration declaration) { + // do nothing + } + + public ASTNode getAstNode(ModifiedDeclaration declaration) { + return declaration.getDeclaration(); + } + + + // ********** IndexedDeclarationAnnotationAdapter implementation ********** + + public int getIndex() { + return -1; + } + + public void moveAnnotation(int newIndex, ModifiedDeclaration declaration) { + // do nothing + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NullDeclarationAnnotationElementAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NullDeclarationAnnotationElementAdapter.java new file mode 100644 index 0000000000..c53a69546a --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NullDeclarationAnnotationElementAdapter.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 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 org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationElementAdapter; +import org.eclipse.jpt.core.utility.jdt.ModifiedDeclaration; + +/** + * Behaviorless implementation. + */ +public class NullDeclarationAnnotationElementAdapter<T> + implements DeclarationAnnotationElementAdapter<T> +{ + + // singleton + @SuppressWarnings("unchecked") + private static final DeclarationAnnotationElementAdapter INSTANCE + = new NullDeclarationAnnotationElementAdapter(); + + /** + * Return the singleton. + */ + @SuppressWarnings("unchecked") + public static <S> DeclarationAnnotationElementAdapter<S> instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private NullDeclarationAnnotationElementAdapter() { + super(); + } + + public T getValue(ModifiedDeclaration declaration) { + return null; + } + + public void setValue(T value, ModifiedDeclaration declaration) { + // do nothing + } + + public ASTNode getAstNode(ModifiedDeclaration declaration) { + return declaration.getDeclaration(); + } + + public Expression getExpression(ModifiedDeclaration declaration) { + return null; + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NullExpressionConverter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NullExpressionConverter.java new file mode 100644 index 0000000000..803b983910 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NullExpressionConverter.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jpt.core.utility.jdt.ExpressionConverter; +import org.eclipse.jpt.utility.internal.StringTools; + +/** + * No conversion. + */ +public final class NullExpressionConverter + implements ExpressionConverter<Expression> +{ + + // singleton + private static final ExpressionConverter<Expression> INSTANCE = new NullExpressionConverter(); + + /** + * Return the singleton. + */ + public static ExpressionConverter<Expression> instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private NullExpressionConverter() { + super(); + } + + public Expression convert(Expression expression, AST ast) { + return expression; + } + + public Expression convert(Expression expression) { + return expression; + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NumberIntegerExpressionConverter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NumberIntegerExpressionConverter.java new file mode 100644 index 0000000000..526e1fe4aa --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/NumberIntegerExpressionConverter.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.NumberLiteral; +import org.eclipse.jpt.core.utility.jdt.ExpressionConverter; + +/** + * Convert a number literal to/from an Integer + * (e.g. 5). + */ +public final class NumberIntegerExpressionConverter + extends AbstractExpressionConverter<Integer> +{ + private static final ExpressionConverter<Integer> INSTANCE = new NumberIntegerExpressionConverter(); + + /** + * Return the singleton. + */ + public static ExpressionConverter<Integer> instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private NumberIntegerExpressionConverter() { + super(); + } + + @Override + protected NumberLiteral convertObject(Integer integer, AST ast) { + return ast.newNumberLiteral(integer.toString()); + } + + @Override + protected Integer convertExpression(Expression expression) { + Object value = expression.resolveConstantExpressionValue(); + return (value instanceof Integer) ? ((Integer) value) : null; + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/PrimitiveTypeStringExpressionConverter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/PrimitiveTypeStringExpressionConverter.java new file mode 100644 index 0000000000..3f9990680f --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/PrimitiveTypeStringExpressionConverter.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.PrimitiveType; +import org.eclipse.jdt.core.dom.TypeLiteral; +import org.eclipse.jpt.core.utility.jdt.ExpressionConverter; + +/** + * Convert a type literal to/from a string representation of a primitive type + * (e.g. "int"). + */ +public final class PrimitiveTypeStringExpressionConverter + extends AbstractExpressionConverter<String> +{ + private static final ExpressionConverter<String> INSTANCE = new PrimitiveTypeStringExpressionConverter(); + + /** + * Return the singleton. + */ + public static ExpressionConverter<String> instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private PrimitiveTypeStringExpressionConverter() { + super(); + } + + @Override + protected TypeLiteral convertObject(String string, AST ast) { + org.eclipse.jdt.core.dom.Type type = ast.newPrimitiveType(PrimitiveType.toCode(string)); + TypeLiteral typeLiteral = ast.newTypeLiteral(); + typeLiteral.setType(type); + return typeLiteral; + } + + @Override + protected String convertExpression(Expression expression) { + if (expression.getNodeType() == ASTNode.TYPE_LITERAL) { + org.eclipse.jdt.core.dom.Type type = ((TypeLiteral) expression).getType(); + if (type.getNodeType() == ASTNode.PRIMITIVE_TYPE) { + return ((PrimitiveType) type).getPrimitiveTypeCode().toString(); + } + } + return null; + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ShortCircuitAnnotationElementAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ShortCircuitAnnotationElementAdapter.java new file mode 100644 index 0000000000..44c27535c8 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ShortCircuitAnnotationElementAdapter.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2006, 2009 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 org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jpt.core.utility.jdt.AnnotationElementAdapter; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationElementAdapter; +import org.eclipse.jpt.core.utility.jdt.Member; +import org.eclipse.jpt.utility.internal.StringTools; + +/** + * Wrap another annotation element adapter and short-circuit the + * #setValue method if the value has not changed. + */ +public class ShortCircuitAnnotationElementAdapter<T> + implements AnnotationElementAdapter<T> +{ + /** the wrapped adapter */ + private final AnnotationElementAdapter<T> adapter; + + + // ********** constructor ********** + + public ShortCircuitAnnotationElementAdapter(Member member, DeclarationAnnotationElementAdapter<T> daea) { + this(new MemberAnnotationElementAdapter<T>(member, daea)); + } + + public ShortCircuitAnnotationElementAdapter(AnnotationElementAdapter<T> adapter) { + super(); + this.adapter = adapter; + } + + + // ********** AnnotationElementAdapter implementation ********** + + public T getValue() { + return this.adapter.getValue(); + } + + public T getValue(CompilationUnit astRoot) { + return this.adapter.getValue(astRoot); + } + + public void setValue(T value) { + this.setValue(this.adapter.getValue(), value); + } + + public Expression getExpression(CompilationUnit astRoot) { + return this.adapter.getExpression(astRoot); + } + + public ASTNode getAstNode(CompilationUnit astRoot) { + return this.adapter.getAstNode(astRoot); + } + + @Override + public String toString() { + return StringTools.buildToStringFor(this, this.adapter); + } + + + // ********** internal methods ********** + + /** + * set the adapter's value to the specified new value if it + * is different from the specified old value + */ + protected void setValue(T oldValue, T newValue) { + if (oldValue == null) { + if (newValue == null) { // null => null + // do nothing + } else { // null => object + this.adapter.setValue(newValue); + } + } else { + if (newValue == null) { // object => null + this.adapter.setValue(null); + } else { // object => object + if (this.valuesAreEqual(oldValue, newValue)) { + // do nothing + } else { + this.adapter.setValue(newValue); + } + } + } + } + + /** + * both values are non-null when this method is called + */ + protected boolean valuesAreEqual(T oldValue, T newValue) { + return newValue.equals(oldValue); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ShortCircuitArrayAnnotationElementAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ShortCircuitArrayAnnotationElementAdapter.java new file mode 100644 index 0000000000..01952fe059 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/ShortCircuitArrayAnnotationElementAdapter.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 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.Arrays; +import org.eclipse.jpt.core.utility.jdt.AnnotationElementAdapter; +import org.eclipse.jpt.core.utility.jdt.DeclarationAnnotationElementAdapter; +import org.eclipse.jpt.core.utility.jdt.Member; + +/** + * Wrap another annotation element adapter and short-circuit the + * #setValue method if the value has not changed. Overrides #valuesAreEqual() + * to check equality on arrays + */ +public class ShortCircuitArrayAnnotationElementAdapter<T> + extends ShortCircuitAnnotationElementAdapter<T[]> +{ + // ********** constructor ********** + + public ShortCircuitArrayAnnotationElementAdapter(Member member, DeclarationAnnotationElementAdapter<T[]> daea) { + super(member, daea); + } + + public ShortCircuitArrayAnnotationElementAdapter(AnnotationElementAdapter<T[]> adapter) { + super(adapter); + } + + + // ********** AnnotationElementAdapter implementation ********** + + @Override + protected boolean valuesAreEqual(T[] oldValue, T[] newValue) { + return Arrays.equals(newValue, oldValue); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/SimpleDeclarationAnnotationAdapter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/SimpleDeclarationAnnotationAdapter.java new file mode 100644 index 0000000000..16a657437b --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/SimpleDeclarationAnnotationAdapter.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jpt.core.utility.jdt.ModifiedDeclaration; + +/** + * Manipulate an annotation with a specific name, e.g. + * <pre> + * @Foo + * private int id; + * </pre> + * + * NB: + * If the declaration contains more than one annotation with the same + * name, the adapter will correspond to the first annotation with the specified + * name. (Also note that the compiler will not allow a declaration to be + * modified by multiple annotations with the same name, i.e. of the same type; + * so if there *are* multiple annotations of the same type, there are bigger + * problems to worry about than which annotation the adapter manipulates.) + */ +public class SimpleDeclarationAnnotationAdapter extends AbstractDeclarationAnnotationAdapter { + + + // ********** constructors ********** + + public SimpleDeclarationAnnotationAdapter(String annotationName) { + super(annotationName); + } + + + // ********** DeclarationAnnotationAdapter implementation ********** + + public Annotation getAnnotation(ModifiedDeclaration declaration) { + return declaration.getAnnotationNamed(this.getAnnotationName()); + } + + public void removeAnnotation(ModifiedDeclaration declaration) { + declaration.removeAnnotationNamed(this.getAnnotationName()); + } + + @Override + protected void addAnnotation(ModifiedDeclaration declaration, Annotation annotation) { + declaration.replaceAnnotationNamed(this.getAnnotationName(), annotation); + } + + public ASTNode getAstNode(ModifiedDeclaration declaration) { + // if the annotation is missing, return the declaration + Annotation annotation = this.getAnnotation(declaration); + return (annotation != null) ? annotation : declaration.getDeclaration(); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/SimpleTypeStringExpressionConverter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/SimpleTypeStringExpressionConverter.java new file mode 100644 index 0000000000..f622562554 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/SimpleTypeStringExpressionConverter.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.Name; +import org.eclipse.jdt.core.dom.SimpleType; +import org.eclipse.jdt.core.dom.TypeLiteral; +import org.eclipse.jpt.core.utility.jdt.ExpressionConverter; + +/** + * Convert a type literal to/from a string representation of a simple type + * (e.g. "java.lang.Object"). + */ +public final class SimpleTypeStringExpressionConverter + extends AbstractExpressionConverter<String> +{ + private static final ExpressionConverter<String> INSTANCE = new SimpleTypeStringExpressionConverter(); + + /** + * Return the singleton. + */ + public static ExpressionConverter<String> instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private SimpleTypeStringExpressionConverter() { + super(); + } + + @Override + protected TypeLiteral convertObject(String string, AST ast) { + Name name = ast.newName(string); + org.eclipse.jdt.core.dom.Type type = ast.newSimpleType(name); + TypeLiteral typeLiteral = ast.newTypeLiteral(); + typeLiteral.setType(type); + return typeLiteral; + } + + @Override + protected String convertExpression(Expression expression) { + if (expression.getNodeType() == ASTNode.TYPE_LITERAL) { + org.eclipse.jdt.core.dom.Type type = ((TypeLiteral) expression).getType(); + if (type.getNodeType() == ASTNode.SIMPLE_TYPE) { + return ((SimpleType) type).getName().getFullyQualifiedName(); + } + } + return null; + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/StringArrayExpressionConverter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/StringArrayExpressionConverter.java new file mode 100644 index 0000000000..35751f6e52 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/StringArrayExpressionConverter.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 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.List; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ArrayInitializer; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jpt.core.utility.jdt.ExpressionConverter; + +/** + * Convert an array initializer to/from an array of strings (e.g. {"text0", "text1"}). + * + * Do NOT use this class for converting array initializers in annotation elements. + * Java5 has a bit of syntactic sugar that allows a single-element array + * initializer to not have curly braces. This converter will return null if it encounters + * anything other than an array initializer. + * + * Invalid entries in the array initializer will result in null elements in the + * resulting string array. This allows clients to manipulate elements at + * the appropriate index. + */ +public class StringArrayExpressionConverter + extends AbstractExpressionConverter<String[]> +{ + private final ExpressionConverter<String> elementConverter; + private final boolean removeArrayInitializerWhenEmpty; + + private static final String[] EMPTY_STRING_ARRAY = new String[0]; + + + /** + * The default behavior is to remove the array initializer if it is empty. + */ + public StringArrayExpressionConverter(ExpressionConverter<String> elementConverter) { + this(elementConverter, true); + } + + public StringArrayExpressionConverter(ExpressionConverter<String> elementConverter, boolean removeArrayInitializerWhenEmpty) { + super(); + this.elementConverter = elementConverter; + this.removeArrayInitializerWhenEmpty = removeArrayInitializerWhenEmpty; + } + + @Override + /* + * this method is 'public' so it can be called by + * AnnotationStringArrayExpressionConverter + */ + public ArrayInitializer convertObject(String[] strings, AST ast) { + if ((strings.length == 0) && this.removeArrayInitializerWhenEmpty) { + return null; + } + ArrayInitializer arrayInitializer = ast.newArrayInitializer(); + List<Expression> expressions = this.expressions(arrayInitializer); + for (String string : strings) { + expressions.add(this.elementConverter.convert(string, ast)); + } + return arrayInitializer; + } + + @SuppressWarnings("unchecked") + private List<Expression> expressions(ArrayInitializer arrayInitializer) { + return arrayInitializer.expressions(); + } + + /* + * this method is 'public' so it can be called by + * AnnotationStringArrayExpressionConverter + */ + @Override + public String[] convertNull() { + return EMPTY_STRING_ARRAY; + } + + @Override + protected String[] convertExpression(Expression expression) { + return (expression.getNodeType() == ASTNode.ARRAY_INITIALIZER) ? + this.convertArrayInitializer((ArrayInitializer) expression) + : + EMPTY_STRING_ARRAY; + } + + /* + * this method is 'public' so it can be called by + * AnnotationStringArrayExpressionConverter + */ + public String[] convertArrayInitializer(ArrayInitializer arrayInitializer) { + List<Expression> expressions = this.downcastExpressions(arrayInitializer); + int len = expressions.size(); + String[] strings = new String[len]; + for (int i = len; i-- > 0; ) { + strings[i] = this.elementConverter.convert(expressions.get(i)); + } + return strings; + } + + @SuppressWarnings("unchecked") + private List<Expression> downcastExpressions(ArrayInitializer arrayInitializer) { + return arrayInitializer.expressions(); + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/StringExpressionConverter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/StringExpressionConverter.java new file mode 100644 index 0000000000..b5fee1e52d --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/StringExpressionConverter.java @@ -0,0 +1,52 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 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 org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.jpt.core.utility.jdt.ExpressionConverter; + +/** + * Convert a string literal to/from a string (e.g. "text"). + */ +public final class StringExpressionConverter + extends AbstractExpressionConverter<String> +{ + private static final ExpressionConverter<String> INSTANCE = new StringExpressionConverter(); + + /** + * Return the singleton. + */ + public static ExpressionConverter<String> instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private StringExpressionConverter() { + super(); + } + + @Override + protected StringLiteral convertObject(String string, AST ast) { + StringLiteral stringLiteral = ast.newStringLiteral(); + stringLiteral.setLiteralValue(string); + return stringLiteral; + } + + @Override + protected String convertExpression(Expression expression) { + Object value = expression.resolveConstantExpressionValue(); + return (value instanceof String) ? (String) value : null; + } + +} diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/TypeStringExpressionConverter.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/TypeStringExpressionConverter.java new file mode 100644 index 0000000000..4ca6248449 --- /dev/null +++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/internal/utility/jdt/TypeStringExpressionConverter.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2008 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 org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.PrimitiveType; +import org.eclipse.jdt.core.dom.TypeLiteral; +import org.eclipse.jpt.core.utility.jdt.ExpressionConverter; + +/** + * Convert a type literal to/from a string representation of a + * simple type (e.g. "java.lang.Object") or primitive type (e.g. "int"). + */ +public final class TypeStringExpressionConverter + extends AbstractExpressionConverter<String> +{ + private static final ExpressionConverter<String> INSTANCE = new TypeStringExpressionConverter(); + + /** + * Return the singleton. + */ + public static ExpressionConverter<String> instance() { + return INSTANCE; + } + + /** + * Ensure single instance. + */ + private TypeStringExpressionConverter() { + super(); + } + + @Override + protected TypeLiteral convertObject(String string, AST ast) { + if (PrimitiveType.toCode(string) != null) { + return (TypeLiteral) PrimitiveTypeStringExpressionConverter.instance().convert(string, ast); + } + return (TypeLiteral) SimpleTypeStringExpressionConverter.instance().convert(string, ast); + } + + @Override + protected String convertExpression(Expression expression) { + String name = SimpleTypeStringExpressionConverter.instance().convert(expression); + return name != null ? name : PrimitiveTypeStringExpressionConverter.instance().convert(expression); + } + +} |