| /******************************************************************************* |
| * 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$ |
| } |
| }; |
| |
| } |