blob: 1b2a0e867eefda7305c3879b5b02803f4dce8977 [file] [log] [blame]
/*******************************************************************************
* 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.resource.java.source;
import java.util.ArrayList;
import java.util.Iterator;
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.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ITypeBinding;
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.resource.java.AnnotationContainer;
import org.eclipse.jpt.core.resource.java.NestableAnnotation;
import org.eclipse.jpt.utility.internal.CollectionTools;
/**
* Utility methods for manipulating annotation containers.
*/
public final class AnnotationContainerTools {
/**
* Add a nested annotation to the specified annotation container
* at the specified index.
* This method modifies both the resource model annotation container and the
* AST; with <em>no</em> change notification.
*/
public static <T extends NestableAnnotation> NestableAnnotation addNestedAnnotation(int index, AnnotationContainer<T> annotationContainer) {
// add a new annotation to the end of the list...
int sourceIndex = annotationContainer.getNestedAnnotationsSize();
T nestedAnnotation = annotationContainer.addNestedAnnotation();
nestedAnnotation.newAnnotation();
// ...then move it to the specified index
moveNestedAnnotation(index, sourceIndex, annotationContainer);
return nestedAnnotation;
}
/**
* Move the nested annotation at the specified source index in the
* specified annotation container to the specified target index.
* This method modifies both the resource model annotation container and the
* AST; with <em>no</em> change notification.
*/
public static <T extends NestableAnnotation> void moveNestedAnnotation(int targetIndex, int sourceIndex, AnnotationContainer<T> annotationContainer) {
if (targetIndex != sourceIndex) {
moveNestedAnnotation_(targetIndex, sourceIndex, annotationContainer);
}
}
private static <T extends NestableAnnotation> void moveNestedAnnotation_(int targetIndex, int sourceIndex, AnnotationContainer<T> annotationContainer) {
NestableAnnotation nestedAnnotation = annotationContainer.moveNestedAnnotation(targetIndex, sourceIndex);
syncAstAnnotationsAfterMove(targetIndex, sourceIndex, annotationContainer, nestedAnnotation);
}
/**
* An annotation was moved within the specified annotation container from
* the specified source index to the specified target index.
* Synchronize the AST annotations with the resource model annotation container,
* starting with the lower index to prevent overlap.
*/
private static <T extends NestableAnnotation> void syncAstAnnotationsAfterMove(int targetIndex, int sourceIndex, AnnotationContainer<T> annotationContainer, NestableAnnotation nestedAnnotation) {
// move the Java annotation to the end of the list...
nestedAnnotation.moveAnnotation(annotationContainer.getNestedAnnotationsSize());
// ...then shift the other AST annotations over one slot...
List<T> nestableAnnotations = CollectionTools.list(annotationContainer.getNestedAnnotations());
if (sourceIndex < targetIndex) {
for (int i = sourceIndex; i < targetIndex; i++) {
nestableAnnotations.get(i).moveAnnotation(i);
}
} else {
for (int i = sourceIndex; i > targetIndex; i-- ) {
nestableAnnotations.get(i).moveAnnotation(i);
}
}
// ...then move the AST annotation to the now empty slot at the target index
nestedAnnotation.moveAnnotation(targetIndex);
}
/**
* Remove the nested annotation at the specified index in the
* specified annotation container.
* This method modifies both the resource model annotation container and the
* AST; with <em>no</em> change notification.
*/
public static <T extends NestableAnnotation> void removeNestedAnnotation(int index, AnnotationContainer<T> annotationContainer) {
T nestedAnnotation = annotationContainer.removeNestedAnnotation(index);
nestedAnnotation.removeAnnotation();
syncAstAnnotationsAfterRemove(index, annotationContainer);
}
/**
* An annotation was removed from the specified annotation container at the
* specified index.
* Synchronize the AST annotations with the resource model annotation container,
* starting at the specified index to prevent overlap.
*/
private static <T extends NestableAnnotation> void syncAstAnnotationsAfterRemove(int index, AnnotationContainer<T> annotationContainer) {
List<T> nestableAnnotations = CollectionTools.list(annotationContainer.getNestedAnnotations());
for (int i = index; i < nestableAnnotations.size(); i++) {
// the indices are the same because the model annotations are
// already in the proper locations - it's the AST annotations that
// need to be moved to the matching location
nestableAnnotations.get(i).moveAnnotation(i);
}
}
/**
* Initialize the specified resource model annotation container to be in
* sync with the specified AST. No change notification will occur.
*/
public static <T extends NestableAnnotation> void initialize(AnnotationContainer<T> annotationContainer, CompilationUnit astRoot) {
// ignore the nested AST annotations themselves
// (maybe someday we can use them during initialization...)
int size = getNestedAstAnnotations(astRoot, annotationContainer).size();
for (int i = 0; i < size; i++) {
T nestedAnnotation = annotationContainer.addNestedAnnotation();
nestedAnnotation.initialize(astRoot);
}
}
/**
* Return a list of the nested AST annotations.
*/
private static <T extends NestableAnnotation> ArrayList<Annotation> getNestedAstAnnotations(CompilationUnit astRoot, AnnotationContainer<T> annotationContainer) {
ArrayList<Annotation> result = new ArrayList<Annotation>();
Annotation astContainerAnnotation = annotationContainer.getAstAnnotation(astRoot);
if (astContainerAnnotation == null || astContainerAnnotation.isMarkerAnnotation()) {
// no nested annotations
}
else if (astContainerAnnotation.isSingleMemberAnnotation()) {
if (annotationContainer.getElementName().equals("value")) { //$NON-NLS-1$
Expression ex = ((SingleMemberAnnotation) astContainerAnnotation).getValue();
addAstAnnotationsTo(ex, annotationContainer.getNestedAnnotationName(), result);
} else {
// no nested annotations
}
}
else if (astContainerAnnotation.isNormalAnnotation()) {
MemberValuePair pair = getMemberValuePair((NormalAnnotation) astContainerAnnotation, annotationContainer.getElementName());
if (pair == null) {
// no nested annotations
} else {
addAstAnnotationsTo(pair.getValue(), annotationContainer.getNestedAnnotationName(), result);
}
}
return result;
}
/**
* Add whatever annotations are represented by the specified expression to
* the specified list. Do not add null to the list for any non-annotation expression.
*/
private static void addAstAnnotationsTo(Expression expression, String annotationName, ArrayList<Annotation> astAnnotations) {
if (expression == null) {
//do not add null to the list, not sure how we would get here...
}
else if (expression.getNodeType() == ASTNode.ARRAY_INITIALIZER) {
addAstAnnotationsTo((ArrayInitializer) expression, annotationName, astAnnotations);
}
else {
Annotation astAnnotation = getAstAnnotation_(expression, annotationName);
if (astAnnotation != null) {
astAnnotations.add(astAnnotation);
}
}
}
private static void addAstAnnotationsTo(ArrayInitializer arrayInitializer, String annotationName, ArrayList<Annotation> astAnnotations) {
@SuppressWarnings("unchecked")
List<Expression> expressions = arrayInitializer.expressions();
for (Expression expression : expressions) {
Annotation astAnnotation = getAstAnnotation(expression, annotationName);
if (astAnnotation != null) {
astAnnotations.add(astAnnotation);
}
}
}
/**
* If the specified expression is an annotation with the specified name, return it;
* otherwise return null.
*/
private static Annotation getAstAnnotation(Expression expression, String annotationName) {
// not sure how the expression could be null...
return (expression == null) ? null : getAstAnnotation_(expression, annotationName);
}
/**
* pre-condition: expression is not null
*/
private static Annotation getAstAnnotation_(Expression expression, String annotationName) {
switch (expression.getNodeType()) {
case ASTNode.NORMAL_ANNOTATION:
case ASTNode.SINGLE_MEMBER_ANNOTATION:
case ASTNode.MARKER_ANNOTATION:
Annotation astAnnotation = (Annotation) expression;
if (getQualifiedName(astAnnotation).equals(annotationName)) {
return astAnnotation;
}
return null;
default:
return null;
}
}
private static String getQualifiedName(Annotation astAnnotation) {
ITypeBinding typeBinding = astAnnotation.resolveTypeBinding();
if (typeBinding != null) {
String resolvedName = typeBinding.getQualifiedName();
if (resolvedName != null) {
return resolvedName;
}
}
return astAnnotation.getTypeName().getFullyQualifiedName();
}
private static MemberValuePair getMemberValuePair(NormalAnnotation annotation, String elementName) {
@SuppressWarnings("unchecked")
List<MemberValuePair> pairs = annotation.values();
for (MemberValuePair pair : pairs) {
if (pair.getName().getFullyQualifiedName().equals(elementName)) {
return pair;
}
}
return null;
}
/**
* Synchronize the resource model annotations in the specified annotation
* container with those in the specified AST. Trigger the appropriate change
* notification.
*/
public static <T extends NestableAnnotation> void synchronize(AnnotationContainer<T> annotationContainer, CompilationUnit astRoot) {
ArrayList<Annotation> astAnnotations = getNestedAstAnnotations(astRoot, annotationContainer);
Iterator<Annotation> astAnnotationStream = astAnnotations.iterator();
for (T nestedAnnotation : annotationContainer.getNestedAnnotations()) {
if (astAnnotationStream.hasNext()) {
// matching AST annotation is present - synchronize the nested annotation
astAnnotationStream.next(); // maybe someday we can pass this to the update
nestedAnnotation.synchronizeWith(astRoot);
} else {
// no more AST annotations - remove the remaining nested annotations and exit
annotationContainer.syncRemoveNestedAnnotations(astAnnotations.size());
return;
}
}
// add nested annotations for any remaining AST annotations
while (astAnnotationStream.hasNext()) {
annotationContainer.syncAddNestedAnnotation(astAnnotationStream.next());
}
}
private AnnotationContainerTools() {
super();
throw new UnsupportedOperationException();
}
}