blob: 9606e659261e519242c6046943ca190d2a15f85a [file] [log] [blame]
/*******************************************************************************
* 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>
* &#64;Outer(name="Fred", foo=&#64;Inner) => &#64;Outer(name="Fred")
* &#64;Outer(foo=&#64;Inner) => &#64;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>
* &#64;Outer(&#64;Inner)
* </pre>
* or
* <pre>
* &#64;Outer(foo=&#64;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>
* &#64;Outer("lorem ipsum") => &#64;Outer(value="lorem ipsum", foo=&#64;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>
* &#64;Outer(bar="lorem ipsum") => &#64;Outer(bar="lorem ipsum", foo=&#64;Inner)
* </pre>
* or
* <pre>
* &#64;Outer(foo=&#64;Inner("lorem ipsum")) => &#64;Outer(foo=&#64;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);
}
}
}