| /******************************************************************************* |
| * Copyright (c) 2010, 2012 Oracle. All rights reserved. |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v1.0, which accompanies this distribution |
| * and is available at http://www.eclipse.org/legal/epl-v10.html. |
| * |
| * Contributors: |
| * Oracle - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.jpt.common.core.internal.resource.java.source; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import org.eclipse.jdt.core.dom.ASTNode; |
| import org.eclipse.jdt.core.dom.ASTVisitor; |
| import org.eclipse.jdt.core.dom.CompilationUnit; |
| 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.MarkerAnnotation; |
| import org.eclipse.jdt.core.dom.NormalAnnotation; |
| import org.eclipse.jdt.core.dom.SingleMemberAnnotation; |
| import org.eclipse.jpt.common.core.internal.utility.jdt.ASTTools; |
| import org.eclipse.jpt.common.core.resource.java.Annotation; |
| import org.eclipse.jpt.common.core.resource.java.JavaResourceAnnotatedElement; |
| import org.eclipse.jpt.common.core.resource.java.JavaResourceNode; |
| import org.eclipse.jpt.common.core.resource.java.NestableAnnotation; |
| import org.eclipse.jpt.common.core.utility.TextRange; |
| import org.eclipse.jpt.common.core.utility.jdt.AnnotatedElement; |
| import org.eclipse.jpt.common.utility.internal.CollectionTools; |
| import org.eclipse.jpt.common.utility.internal.StringTools; |
| import org.eclipse.jpt.common.utility.internal.Transformer; |
| import org.eclipse.jpt.common.utility.internal.TransformerAdapter; |
| import org.eclipse.jpt.common.utility.internal.iterables.CompositeIterable; |
| import org.eclipse.jpt.common.utility.internal.iterables.EmptyListIterable; |
| import org.eclipse.jpt.common.utility.internal.iterables.ListIterable; |
| import org.eclipse.jpt.common.utility.internal.iterables.LiveCloneIterable; |
| import org.eclipse.jpt.common.utility.internal.iterables.TransformationIterable; |
| |
| /** |
| * Java source annotated element |
| */ |
| abstract class SourceAnnotatedElement<E extends AnnotatedElement> |
| extends SourceNode |
| implements JavaResourceAnnotatedElement |
| { |
| final E annotatedElement; |
| |
| /** |
| * Annotations keyed by annotation name; |
| * no duplicates (the Java compiler does not allow duplicate annotations). |
| */ |
| private final Hashtable<String, Annotation> annotations = new Hashtable<String, Annotation>(); |
| |
| /** |
| * Cache the null annotation objects or they will be rebuilt on every access. |
| * Make this a HashMap for performance, not concerned with duplicate creation and |
| * unlikely that multiple threads will access this. |
| */ |
| private final HashMap<String, Annotation> nullAnnotationCache = new HashMap<String, Annotation>(); |
| |
| /** |
| * Annotation containers keyed by <em>nestable</em> annotation name. |
| * This is used to store annotations that can be both standalone and nested |
| * and are moved back and forth between the two. |
| */ |
| private final Hashtable<String, CombinationAnnotationContainer> annotationContainers = new Hashtable<String, CombinationAnnotationContainer>(); |
| |
| |
| // ********** construction/initialization ********** |
| |
| SourceAnnotatedElement(JavaResourceNode parent, E annotatedElement) { |
| super(parent); |
| this.annotatedElement = annotatedElement; |
| } |
| |
| /** |
| * There are 2 initialize calls, 1 for ASTNode and one for IBinding. |
| * This is a performance enhancement because finding a MethodDeclaration |
| * for a SourceMethod is very non-performant. SourceMethod actually overrides |
| * this as unsupported and instead uses initialize(MethodDeclaration). |
| * TODO continue the pattern in SourceMethod with the other classes in this hierarchy |
| * trying not to change much API in 3.2M7 |
| * |
| * This is also to handle multiple fields declared in a single statement: |
| * private int foo, bar; |
| * JDTFieldAttribute.getBodyDeclaration(CompilationUnit) returns the FieldDeclaration |
| * in the call to getBodyDeclaration, this is the ASTNode for a field and |
| * has the annotations on it. |
| * JDTFieldAttribute.getBinding(CompiltationUnit) returns the IVariableBinding of the |
| * VariableDeclarationFragment which is the ASTNode for the particular field. |
| */ |
| public void initialize(CompilationUnit astRoot) { |
| this.initialize(this.annotatedElement.getBodyDeclaration(astRoot)); |
| this.initialize(this.annotatedElement.getBinding(astRoot)); |
| } |
| |
| protected void initialize(IBinding binding) { |
| //do nothing |
| } |
| |
| /** |
| * Gather up all the significant AST annotations |
| * and build the corresponding Dali annotations. |
| */ |
| protected void initialize(ASTNode node) { |
| AnnotationVisitor visitor = new AnnotationVisitor(node); |
| node.accept(visitor); |
| this.initializeAnnotations(visitor.astAnnotations); |
| // container annotations take precedence over... |
| this.initializeContainerAnnotations(visitor.astContainerAnnotations); |
| // ...standalone nestable annotations |
| this.initializeStandaloneNestableAnnotations(visitor.astStandaloneNestableAnnotations); |
| } |
| |
| private void initializeAnnotations(HashMap<String, org.eclipse.jdt.core.dom.Annotation> astAnnotations) { |
| for (Map.Entry<String, org.eclipse.jdt.core.dom.Annotation> entry : astAnnotations.entrySet()) { |
| String annotationName = entry.getKey(); |
| org.eclipse.jdt.core.dom.Annotation astAnnotation = entry.getValue(); |
| Annotation annotation = this.buildAnnotation(annotationName); |
| annotation.initialize((CompilationUnit) astAnnotation.getRoot()); // TODO pass the AST annotation! |
| this.annotations.put(annotationName, annotation); |
| } |
| } |
| |
| private void initializeContainerAnnotations(HashMap<String, org.eclipse.jdt.core.dom.Annotation> astContainerAnnotations) { |
| for (Map.Entry<String, org.eclipse.jdt.core.dom.Annotation> entry : astContainerAnnotations.entrySet()) { |
| String containerAnnotationName = entry.getKey(); |
| org.eclipse.jdt.core.dom.Annotation astAnnotation = entry.getValue(); |
| String nestableAnnotationName = this.getNestableAnnotationName(containerAnnotationName); |
| CombinationAnnotationContainer container = new CombinationAnnotationContainer(nestableAnnotationName, containerAnnotationName); |
| container.initializeFromContainerAnnotation(astAnnotation); |
| this.annotationContainers.put(nestableAnnotationName, container); |
| } |
| } |
| |
| private void initializeStandaloneNestableAnnotations(HashMap<String, org.eclipse.jdt.core.dom.Annotation> astStandaloneNestableAnnotations) { |
| for (Map.Entry<String, org.eclipse.jdt.core.dom.Annotation> entry : astStandaloneNestableAnnotations.entrySet()) { |
| String nestableAnnotationName = entry.getKey(); |
| org.eclipse.jdt.core.dom.Annotation astAnnotation = entry.getValue(); |
| // if we already have an annotation container (because there was a container annotation) |
| // ignore the standalone nestable annotation |
| if (this.annotationContainers.get(nestableAnnotationName) == null) { |
| CombinationAnnotationContainer container = new CombinationAnnotationContainer(nestableAnnotationName); |
| container.initializeFromStandaloneAnnotation(astAnnotation); |
| this.annotationContainers.put(nestableAnnotationName, container); |
| } |
| } |
| } |
| |
| /** |
| * @see #initialize(CompilationUnit) |
| */ |
| public void synchronizeWith(CompilationUnit astRoot) { |
| this.synchronizeWith(this.annotatedElement.getBodyDeclaration(astRoot)); |
| this.synchronizeWith(this.annotatedElement.getBinding(astRoot)); |
| } |
| |
| protected void synchronizeWith(ASTNode bodyDeclaration) { |
| this.syncAnnotations(bodyDeclaration); |
| } |
| |
| protected void synchronizeWith(IBinding binding) { |
| //do nothing |
| } |
| |
| |
| // ********** annotations ********** |
| |
| public Iterable<Annotation> getAnnotations() { |
| return new LiveCloneIterable<Annotation>(this.annotations.values()); |
| } |
| |
| public int getAnnotationsSize() { |
| return this.annotations.size(); |
| } |
| |
| public Annotation getAnnotation(String annotationName) { |
| // TODO one reason we search the containers is validation, we need to have separate API for getting the container annotation. |
| //The validation in org.eclipse.jpt.jaxb.core.internal.context.java.GenericJavaXmlAnyElementMapping is an example |
| if (this.annotationIsValidContainer(annotationName)) { |
| CombinationAnnotationContainer container = this.annotationContainers.get(this.getAnnotationProvider().getNestableAnnotationName(annotationName)); |
| return (container == null) ? null : container.getContainerAnnotation(); |
| } |
| return this.annotations.get(annotationName); |
| } |
| |
| public Annotation getNonNullAnnotation(String annotationName) { |
| Annotation annotation = this.performantGetAnnotation(annotationName); |
| return (annotation != null) ? annotation : this.getNullAnnotation(annotationName); |
| } |
| |
| /** |
| * TODO performance - hack for performance so we don't have to break API in M7. |
| * Our calls to getNonNullAnnotation are never used for container annotations, so |
| * I can use this method to avoid the expensive {@link #annotationIsValidContainer(String)} check |
| * in {@link #getAnnotation(String)}. In the next release I think we need |
| * to change the genAnnotation api to not check for container annotations and instead have |
| * separate API. |
| */ |
| protected Annotation performantGetAnnotation(String annotationName) { |
| return this.annotations.get(annotationName); |
| } |
| |
| private Annotation getNullAnnotation(String annotationName) { |
| Annotation annotation = this.nullAnnotationCache.get(annotationName); |
| if (annotation == null) { |
| annotation = this.buildNullAnnotation(annotationName); |
| this.nullAnnotationCache.put(annotationName, annotation); |
| } |
| return annotation; |
| } |
| |
| private Annotation buildNullAnnotation(String annotationName) { |
| return this.getAnnotationProvider().buildNullAnnotation(this, annotationName); |
| } |
| |
| public Annotation addAnnotation(String annotationName) { |
| Annotation annotation = this.buildAnnotation(annotationName); |
| this.annotations.put(annotationName, annotation); |
| annotation.newAnnotation(); |
| return annotation; |
| } |
| |
| public void removeAnnotation(String annotationName) { |
| Annotation annotation = this.annotations.remove(annotationName); |
| if (annotation != null) { |
| annotation.removeAnnotation(); |
| } |
| } |
| |
| /* CU private */ boolean annotationIsValid(String annotationName) { |
| return CollectionTools.contains(this.getAnnotationProvider().getAnnotationNames(), annotationName); |
| } |
| |
| /* CU private */ Annotation buildAnnotation(String annotationName) { |
| return this.getAnnotationProvider().buildAnnotation(this, this.annotatedElement, annotationName); |
| } |
| |
| |
| // ********** combination annotations ********** |
| |
| private Iterable<NestableAnnotation> getNestableAnnotations() { |
| return new CompositeIterable<NestableAnnotation>(this.getNestableAnnotationLists()); |
| } |
| |
| private Iterable<Iterable<NestableAnnotation>> getNestableAnnotationLists() { |
| return new TransformationIterable<CombinationAnnotationContainer_, Iterable<NestableAnnotation>>(this.getAnnotationContainers(), ANNOTATION_CONTAINER_NESTED_ANNOTATIONS_TRANSFORMER); |
| } |
| |
| private static final Transformer<CombinationAnnotationContainer_, Iterable<NestableAnnotation>> ANNOTATION_CONTAINER_NESTED_ANNOTATIONS_TRANSFORMER = new AnnotationContainerNestedAnnotationsTransformer(); |
| /* CU private */ static final class AnnotationContainerNestedAnnotationsTransformer |
| extends TransformerAdapter<CombinationAnnotationContainer_, Iterable<NestableAnnotation>> |
| { |
| @Override |
| public Iterable<NestableAnnotation> transform(CombinationAnnotationContainer_ container) { |
| return container.getNestedAnnotations(); |
| } |
| } |
| |
| private Iterable<CombinationAnnotationContainer> getAnnotationContainers() { |
| return new LiveCloneIterable<CombinationAnnotationContainer>(this.annotationContainers.values()); |
| } |
| |
| public ListIterable<NestableAnnotation> getAnnotations(String nestableAnnotationName) { |
| CombinationAnnotationContainer container = this.annotationContainers.get(nestableAnnotationName); |
| return (container != null) ? container.getNestedAnnotations() : EmptyListIterable.<NestableAnnotation> instance(); |
| } |
| |
| public int getAnnotationsSize(String nestableAnnotationName) { |
| CombinationAnnotationContainer container = this.annotationContainers.get(nestableAnnotationName); |
| return (container == null) ? 0 : container.getNestedAnnotationsSize(); |
| } |
| |
| public NestableAnnotation getAnnotation(int index, String nestableAnnotationName) { |
| CombinationAnnotationContainer container = this.annotationContainers.get(nestableAnnotationName); |
| if (container == null) { |
| throw new ArrayIndexOutOfBoundsException("size: 0 index: " + index); //$NON-NLS-1$ |
| } |
| return container.getNestedAnnotation(index); |
| } |
| |
| private String getNestableAnnotationName(String containerAnnotationName) { |
| return this.getAnnotationProvider().getNestableAnnotationName(containerAnnotationName); |
| } |
| |
| /* CU private */ String getContainerAnnotationName(String nestableAnnotationName) { |
| return this.getAnnotationProvider().getContainerAnnotationName(nestableAnnotationName); |
| } |
| |
| /* CU private */ String getNestableElementName(String nestableAnnotationName) { |
| return this.getAnnotationProvider().getNestableElementName(nestableAnnotationName); |
| } |
| |
| public NestableAnnotation addAnnotation(int index, String nestableAnnotationName) { |
| CombinationAnnotationContainer container = this.annotationContainers.get(nestableAnnotationName); |
| if (container == null) { |
| container = new CombinationAnnotationContainer(nestableAnnotationName); |
| this.annotationContainers.put(nestableAnnotationName, container); |
| } |
| return container.addNestedAnnotation(index); |
| } |
| |
| public void moveAnnotation(int targetIndex, int sourceIndex, String nestableAnnotationName) { |
| this.annotationContainers.get(nestableAnnotationName).moveNestedAnnotation(targetIndex, sourceIndex); |
| } |
| |
| public void removeAnnotation(int index, String nestableAnnotationName) { |
| CombinationAnnotationContainer container = this.annotationContainers.get(nestableAnnotationName); |
| container.removeNestedAnnotation(index); |
| if (container.isEmpty()) { |
| this.annotationContainers.remove(nestableAnnotationName); |
| } |
| } |
| |
| /* CU private */ boolean annotationIsValidContainer(String annotationName) { |
| return CollectionTools.contains(this.getAnnotationProvider().getContainerAnnotationNames(), annotationName); |
| } |
| |
| /* CU private */ boolean annotationIsValidNestable(String annotationName) { |
| return CollectionTools.contains(this.getAnnotationProvider().getNestableAnnotationNames(), annotationName); |
| } |
| |
| /* CU private */ NestableAnnotation buildNestableAnnotation(String annotationName, int index) { |
| return this.getAnnotationProvider().buildAnnotation(this, this.annotatedElement, annotationName, index); |
| } |
| |
| /* CU private */ void nestedAnnotationAdded(String collectionName, NestableAnnotation addedAnnotation) { |
| this.fireItemAdded(collectionName, addedAnnotation); |
| } |
| |
| /* CU private */ void nestedAnnotationsRemoved(String collectionName, Collection<? extends NestableAnnotation> removedAnnotations) { |
| this.fireItemsRemoved(collectionName, removedAnnotations); |
| } |
| |
| |
| // ***** all annotations ***** |
| |
| Annotation setPrimaryAnnotation(String primaryAnnotationName, Iterable<String> supportingAnnotationNames) { |
| // clear out extraneous annotations |
| HashSet<String> annotationNames = new HashSet<String>(); |
| CollectionTools.addAll(annotationNames, supportingAnnotationNames); |
| if (primaryAnnotationName != null) { |
| annotationNames.add(primaryAnnotationName); |
| } |
| this.retainAnnotations(annotationNames); |
| this.retainAnnotationContainers(annotationNames); |
| |
| // add the primary annotation |
| if (primaryAnnotationName == null) { |
| return null; |
| } |
| Annotation primaryAnnotation = this.getAnnotation(primaryAnnotationName); |
| if (primaryAnnotation == null) { |
| primaryAnnotation = this.buildAnnotation(primaryAnnotationName); |
| this.annotations.put(primaryAnnotationName, primaryAnnotation); |
| primaryAnnotation.newAnnotation(); |
| } |
| return primaryAnnotation; |
| } |
| |
| private void retainAnnotations(HashSet<String> annotationNames) { |
| synchronized (this.annotations) { |
| for (Iterator<Map.Entry<String, Annotation>> stream = this.annotations.entrySet().iterator(); stream.hasNext(); ) { |
| Map.Entry<String, Annotation> entry = stream.next(); |
| String annotationName = entry.getKey(); |
| Annotation annotation = entry.getValue(); |
| if ( ! annotationNames.contains(annotationName)) { |
| stream.remove(); |
| annotation.removeAnnotation(); |
| } |
| } |
| } |
| } |
| |
| private void retainAnnotationContainers(HashSet<String> annotationNames) { |
| synchronized (this.annotationContainers) { |
| for (Iterator<Map.Entry<String, CombinationAnnotationContainer>> stream = this.annotationContainers.entrySet().iterator(); stream.hasNext(); ) { |
| Map.Entry<String, CombinationAnnotationContainer> entry = stream.next(); |
| String nestableAnnotationName = entry.getKey(); |
| CombinationAnnotationContainer container = entry.getValue(); |
| Annotation containerAnnotation = container.getContainerAnnotation(); |
| if (containerAnnotation != null) { |
| if ( ! annotationNames.contains(container.getContainerAnnotationName())) { |
| stream.remove(); |
| containerAnnotation.removeAnnotation(); |
| } |
| } else { |
| // standalone "nestable" annotation |
| if ( ! annotationNames.contains(nestableAnnotationName)) { |
| stream.remove(); |
| container.getNestedAnnotation(0).removeAnnotation(); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Gather up all the significant AST annotations |
| * and add or sync the corresponding Dali annotations. |
| */ |
| private void syncAnnotations(ASTNode node) { |
| AnnotationVisitor visitor = new AnnotationVisitor(node); |
| node.accept(visitor); |
| this.syncAnnotations(visitor.astAnnotations); |
| this.syncAnnotationContainers(visitor.astContainerAnnotations, visitor.astStandaloneNestableAnnotations); |
| } |
| |
| private void syncAnnotations(HashMap<String, org.eclipse.jdt.core.dom.Annotation> astAnnotations) { |
| HashMap<String, Annotation> annotationsToRemove = new HashMap<String, Annotation>(this.annotations); |
| HashMap<String, Annotation> annotationsToAdd = new HashMap<String, Annotation>(); |
| for (Map.Entry<String, org.eclipse.jdt.core.dom.Annotation> entry : astAnnotations.entrySet()) { |
| String annotationName = entry.getKey(); |
| org.eclipse.jdt.core.dom.Annotation astAnnotation = entry.getValue(); |
| Annotation annotation = annotationsToRemove.remove(annotationName); |
| if (annotation == null) { |
| annotation = this.buildAnnotation(annotationName); |
| annotation.initialize((CompilationUnit) astAnnotation.getRoot()); // TODO pass the AST annotation! |
| annotationsToAdd.put(annotationName, annotation); |
| } else { |
| annotation.synchronizeWith((CompilationUnit) astAnnotation.getRoot()); // TODO pass the AST annotation! |
| } |
| } |
| |
| for (String annotationName : annotationsToRemove.keySet()) { |
| this.annotations.remove(annotationName); |
| } |
| this.fireItemsRemoved(ANNOTATIONS_COLLECTION, annotationsToRemove.values()); |
| |
| this.annotations.putAll(annotationsToAdd); |
| this.fireItemsAdded(ANNOTATIONS_COLLECTION, annotationsToAdd.values()); |
| } |
| |
| private void syncAnnotationContainers(HashMap<String, org.eclipse.jdt.core.dom.Annotation> astContainerAnnotations, HashMap<String, org.eclipse.jdt.core.dom.Annotation> astStandaloneNestableAnnotations) { |
| HashMap<String, CombinationAnnotationContainer> containersToRemove = new HashMap<String, CombinationAnnotationContainer>(this.annotationContainers); |
| HashMap<String, CombinationAnnotationContainer> containersToAdd = new HashMap<String, CombinationAnnotationContainer>(); |
| |
| for (Map.Entry<String, org.eclipse.jdt.core.dom.Annotation> entry : astContainerAnnotations.entrySet()) { |
| String containerAnnotationName = entry.getKey(); |
| org.eclipse.jdt.core.dom.Annotation astContainerAnnotation = entry.getValue(); |
| String nestableAnnotationName = this.getNestableAnnotationName(containerAnnotationName); |
| CombinationAnnotationContainer container = containersToRemove.remove(nestableAnnotationName); |
| if (container == null) { |
| container = new CombinationAnnotationContainer(nestableAnnotationName, containerAnnotationName); |
| container.initializeFromContainerAnnotation(astContainerAnnotation); |
| containersToAdd.put(nestableAnnotationName, container); |
| } else { |
| container.synchronize(astContainerAnnotation); |
| } |
| // if it exists, strip out the standalone annotation |
| // corresponding to the current container annotation |
| astStandaloneNestableAnnotations.remove(nestableAnnotationName); |
| } |
| |
| for (Map.Entry<String, org.eclipse.jdt.core.dom.Annotation> entry : astStandaloneNestableAnnotations.entrySet()) { |
| String nestableAnnotationName = entry.getKey(); |
| org.eclipse.jdt.core.dom.Annotation astNestableAnnotation = entry.getValue(); |
| CombinationAnnotationContainer container = containersToRemove.remove(nestableAnnotationName); |
| if (container == null) { |
| container = new CombinationAnnotationContainer(nestableAnnotationName); |
| container.initializeFromStandaloneAnnotation(astNestableAnnotation); |
| containersToAdd.put(nestableAnnotationName, container); |
| } else { |
| container.synchronizeNestableAnnotation(astNestableAnnotation); |
| } |
| } |
| |
| ArrayList<NestableAnnotation> removedNestableAnnotations = new ArrayList<NestableAnnotation>(); |
| for (String nestableAnnotationName : containersToRemove.keySet()) { |
| CombinationAnnotationContainer container = this.annotationContainers.remove(nestableAnnotationName); |
| CollectionTools.addAll(removedNestableAnnotations, container.getNestedAnnotations()); |
| } |
| this.fireItemsRemoved(NESTABLE_ANNOTATIONS_COLLECTION, removedNestableAnnotations); |
| |
| ArrayList<NestableAnnotation> addedNestableAnnotations = new ArrayList<NestableAnnotation>(); |
| for (Map.Entry<String, CombinationAnnotationContainer> entry : containersToAdd.entrySet()) { |
| String nestableAnnotationName = entry.getKey(); |
| CombinationAnnotationContainer container = entry.getValue(); |
| this.annotationContainers.put(nestableAnnotationName, container); |
| CollectionTools.addAll(addedNestableAnnotations, container.getNestedAnnotations()); |
| } |
| this.fireItemsAdded(NESTABLE_ANNOTATIONS_COLLECTION, addedNestableAnnotations); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public Iterable<Annotation> getTopLevelAnnotations() { |
| return new CompositeIterable<Annotation>( |
| this.getAnnotations(), |
| this.getContainerOrStandaloneNestableAnnotations() |
| ); |
| } |
| |
| private Iterable<Annotation> getContainerOrStandaloneNestableAnnotations() { |
| return new TransformationIterable<CombinationAnnotationContainer_, Annotation>(this.getAnnotationContainers(), TOP_LEVEL_ANNOTATION_CONTAINER_TRANSFORMER); |
| } |
| |
| private static final Transformer<CombinationAnnotationContainer_, Annotation> TOP_LEVEL_ANNOTATION_CONTAINER_TRANSFORMER = new TopLevelAnnotationContainerTransformer(); |
| static final class TopLevelAnnotationContainerTransformer |
| extends TransformerAdapter<CombinationAnnotationContainer_, Annotation> |
| { |
| @Override |
| public Annotation transform(CombinationAnnotationContainer_ container) { |
| Annotation containerAnnotation = container.getContainerAnnotation(); |
| return (containerAnnotation != null) ? containerAnnotation : container.getNestedAnnotation(0); |
| } |
| } |
| |
| public boolean isAnnotated() { |
| return ! this.isUnannotated(); |
| } |
| |
| public boolean isUnannotated() { |
| return this.annotations.isEmpty() && this.annotationContainers.isEmpty(); |
| } |
| |
| public boolean isAnnotatedWithAnyOf(Iterable<String> annotationNames) { |
| for (Annotation annotation : this.getSignificantAnnotations()) { |
| if (CollectionTools.contains(annotationNames, annotation.getAnnotationName())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Return the "significant" annotations; |
| * i.e. ignore the container annotations (they have no semantics). |
| */ |
| @SuppressWarnings("unchecked") |
| private Iterable<Annotation> getSignificantAnnotations() { |
| return new CompositeIterable<Annotation>( |
| this.getAnnotations(), |
| this.getNestableAnnotations() |
| ); |
| } |
| |
| |
| // ********** misc ********** |
| |
| public TextRange getTextRange(CompilationUnit astRoot) { |
| // the AST is null for virtual Java attributes |
| // TODO remove the AST null check once we start storing text ranges |
| // in the resource model |
| return (astRoot == null) ? null : this.buildTextRange(this.annotatedElement.getBodyDeclaration(astRoot)); |
| } |
| |
| public TextRange getNameTextRange(CompilationUnit astRoot) { |
| // the AST is null for virtual Java attributes |
| // TODO remove the AST null check once we start storing text ranges |
| // in the resource model |
| return (astRoot == null) ? null : this.annotatedElement.getNameTextRange(astRoot); |
| } |
| |
| public TextRange getTextRange(String nestableAnnotationName, CompilationUnit astRoot) { |
| CombinationAnnotationContainer container = this.annotationContainers.get(nestableAnnotationName); |
| if (container == null) { |
| return null; |
| } |
| Annotation annotation = container.getContainerAnnotation(); |
| if (annotation == null) { |
| annotation = container.getNestedAnnotation(0); |
| } |
| return annotation.getTextRange(astRoot); |
| } |
| |
| private TextRange buildTextRange(ASTNode astNode) { |
| return (astNode == null) ? null : ASTTools.buildTextRange(astNode); |
| } |
| |
| |
| // ********** AST visitor ********** |
| |
| /** |
| * This annotation visitor gathers up all the <em>significant</em> |
| * (i.e. non-duplicate with a valid name) AST annotations |
| * container annotations and standalone nestable annotations for its |
| * {@link #node}. |
| */ |
| /* CU private */ class AnnotationVisitor |
| extends ASTVisitor |
| { |
| private final ASTNode node; |
| final HashMap<String, org.eclipse.jdt.core.dom.Annotation> astAnnotations = new HashMap<String, org.eclipse.jdt.core.dom.Annotation>(); |
| final HashMap<String, org.eclipse.jdt.core.dom.Annotation> astContainerAnnotations = new HashMap<String, org.eclipse.jdt.core.dom.Annotation>(); |
| final HashMap<String, org.eclipse.jdt.core.dom.Annotation> astStandaloneNestableAnnotations = new HashMap<String, org.eclipse.jdt.core.dom.Annotation>(); |
| |
| AnnotationVisitor(ASTNode node) { |
| super(); |
| this.node = node; |
| } |
| |
| @Override |
| public boolean visit(SingleMemberAnnotation annotation) { |
| return this.visit_(annotation); |
| } |
| |
| @Override |
| public boolean visit(NormalAnnotation annotation) { |
| return this.visit_(annotation); |
| } |
| |
| @Override |
| public boolean visit(MarkerAnnotation annotation) { |
| return this.visit_(annotation); |
| } |
| |
| /** |
| * Process only the annotations for the {@link #node}; ignore any children. |
| */ |
| private boolean visit_(org.eclipse.jdt.core.dom.Annotation astAnnotation) { |
| if (astAnnotation.getParent() == this.node) { |
| this.visitChildAnnotation(astAnnotation); |
| } |
| return false; // => do *not* visit children |
| } |
| |
| /** |
| * For each annotation name we save only the first one |
| * and ignore duplicates. |
| */ |
| private void visitChildAnnotation(org.eclipse.jdt.core.dom.Annotation astAnnotation) { |
| String astAnnotationName = this.resolveAnnotationName(astAnnotation); |
| if (astAnnotationName == null) { |
| return; |
| } |
| // check whether the annotation is a valid container annotation first |
| // because container validations are also valid annotations |
| // TODO remove container annotations from list of annotations??? |
| if (SourceAnnotatedElement.this.annotationIsValidContainer(astAnnotationName)) { |
| if (this.astContainerAnnotations.get(astAnnotationName) == null) { |
| this.astContainerAnnotations.put(astAnnotationName, astAnnotation); |
| } |
| } |
| else if (SourceAnnotatedElement.this.annotationIsValid(astAnnotationName)) { |
| if (this.astAnnotations.get(astAnnotationName) == null) { |
| this.astAnnotations.put(astAnnotationName, astAnnotation); |
| } |
| } |
| else if (SourceAnnotatedElement.this.annotationIsValidNestable(astAnnotationName)) { |
| if (this.astStandaloneNestableAnnotations.get(astAnnotationName) == null) { |
| this.astStandaloneNestableAnnotations.put(astAnnotationName, astAnnotation); |
| } |
| } |
| } |
| |
| /** |
| * Return the specified annotation's (fully-qualified) class name. |
| */ |
| private String resolveAnnotationName(org.eclipse.jdt.core.dom.Annotation astAnnotation) { |
| IAnnotationBinding annotationBinding = astAnnotation.resolveAnnotationBinding(); |
| if (annotationBinding == null) { |
| return null; |
| } |
| ITypeBinding annotationTypeBinding = annotationBinding.getAnnotationType(); |
| return (annotationTypeBinding == null) ? null : annotationTypeBinding.getQualifiedName(); |
| } |
| |
| @Override |
| public String toString() { |
| return StringTools.buildToStringFor(this, node); |
| } |
| } |
| |
| |
| // ********** annotation container ********** |
| |
| /** |
| * Use this interface to make static references to the annotation container. |
| * Sort of a hack.... |
| * @see AnnotationContainerNestedAnnotationsTransformer |
| * @see TopLevelAnnotationContainerTransformer |
| */ |
| private interface CombinationAnnotationContainer_ { |
| Annotation getContainerAnnotation(); |
| ListIterable<NestableAnnotation> getNestedAnnotations(); |
| NestableAnnotation getNestedAnnotation(int index); |
| } |
| |
| |
| /** |
| * Annotation container for top-level "combination" annotations that allow |
| * a single nestable annotation to stand alone, outside of its standard |
| * container annotation, and represent a single-element array. |
| */ |
| /* CU private */ class CombinationAnnotationContainer |
| extends AnnotationContainer<NestableAnnotation> |
| implements CombinationAnnotationContainer_ |
| { |
| /** |
| * The name of the nestable annotation that be either a top-level |
| * standalone annotation or nested within the container annotation. |
| */ |
| private final String nestableAnnotationName; |
| |
| /** |
| * The name of the container annotation, used to build the |
| * {@link #containerAnnotation container annotation} as necessary. |
| */ |
| private final String containerAnnotationName; |
| |
| /** |
| * This is <code>null</code> if the container annotation does not exist |
| * but the standalone nestable annotation does. |
| */ |
| private Annotation containerAnnotation; |
| |
| |
| CombinationAnnotationContainer(String nestableAnnotationName) { |
| this(nestableAnnotationName, SourceAnnotatedElement.this.getContainerAnnotationName(nestableAnnotationName)); |
| } |
| |
| CombinationAnnotationContainer(String nestableAnnotationName, String containerAnnotationName) { |
| super(); |
| if ((nestableAnnotationName == null) || (containerAnnotationName == null)) { |
| throw new NullPointerException(); |
| } |
| this.nestableAnnotationName = nestableAnnotationName; |
| this.containerAnnotationName = containerAnnotationName; |
| } |
| |
| @Override |
| public void initializeFromContainerAnnotation(org.eclipse.jdt.core.dom.Annotation astContainerAnnotation) { |
| super.initializeFromContainerAnnotation(astContainerAnnotation); |
| this.containerAnnotation = this.buildContainerAnnotation(this.containerAnnotationName); |
| } |
| |
| private Annotation buildContainerAnnotation(String name) { |
| return SourceAnnotatedElement.this.buildAnnotation(name); |
| } |
| |
| public Annotation getContainerAnnotation() { |
| return this.containerAnnotation; |
| } |
| |
| String getContainerAnnotationName() { |
| return this.containerAnnotationName; |
| } |
| |
| /** |
| * Return the element name of the nested annotations |
| */ |
| @Override |
| protected String getElementName() { |
| return SourceAnnotatedElement.this.getNestableElementName(this.nestableAnnotationName); |
| } |
| |
| /** |
| * Return the nested annotation name |
| */ |
| @Override |
| protected String getNestedAnnotationName() { |
| return this.nestableAnnotationName; |
| } |
| |
| /** |
| * Return a new nested annotation at the given index |
| */ |
| @Override |
| protected NestableAnnotation buildNestedAnnotation(int index) { |
| return SourceAnnotatedElement.this.buildNestableAnnotation(this.nestableAnnotationName, index); |
| } |
| |
| void initializeFromStandaloneAnnotation(org.eclipse.jdt.core.dom.Annotation astStandaloneNestableAnnotation) { |
| NestableAnnotation nestedAnnotation = this.buildNestedAnnotation(0); |
| this.nestedAnnotations.add(nestedAnnotation); |
| nestedAnnotation.initialize((CompilationUnit) astStandaloneNestableAnnotation.getRoot()); // TODO pass the AST annotation! |
| } |
| |
| /** |
| * If we get here, the container annotation does <em>not</em> exist but |
| * the standalone nestable annotation does. |
| */ |
| void synchronizeNestableAnnotation(org.eclipse.jdt.core.dom.Annotation astStandaloneNestableAnnotation) { |
| if (this.nestedAnnotations.size() == 0) { |
| throw new IllegalStateException(); // should not get here... |
| } |
| |
| this.containerAnnotation = null; |
| this.nestedAnnotations.get(0).synchronizeWith((CompilationUnit) astStandaloneNestableAnnotation.getRoot()); // TODO pass the AST annotation! |
| // remove any remaining nested annotations |
| this.syncRemoveNestedAnnotations(1); |
| } |
| |
| @Override |
| public NestableAnnotation addNestedAnnotation(int index) { |
| if ((this.nestedAnnotations.size() == 1) && (this.containerAnnotation == null)) { |
| this.containerAnnotation = this.buildContainerAnnotation(this.containerAnnotationName); |
| } |
| return super.addNestedAnnotation(index); |
| } |
| |
| @Override |
| public NestableAnnotation removeNestedAnnotation(int index) { |
| if (this.nestedAnnotations.size() == 2) { |
| this.containerAnnotation = null; |
| } |
| return super.removeNestedAnnotation(index); |
| } |
| |
| /** |
| * <strong>NB:</strong> This is a <em>collection</em> name. |
| * @see #nestedAnnotationAdded(int, NestableAnnotation) |
| * @see #nestedAnnotationsRemoved(int, List) |
| */ |
| @Override |
| protected String getNestedAnnotationsListName() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * <strong>NB:</strong> Convert to a <em>collection</em> change. |
| */ |
| @Override |
| void nestedAnnotationAdded(int index, NestableAnnotation addedAnnotation) { |
| SourceAnnotatedElement.this.nestedAnnotationAdded(NESTABLE_ANNOTATIONS_COLLECTION, addedAnnotation); |
| } |
| |
| /** |
| * <strong>NB:</strong> Convert to a <em>collection</em> change. |
| */ |
| @Override |
| void nestedAnnotationsRemoved(int index, List<NestableAnnotation> removedAnnotations) { |
| SourceAnnotatedElement.this.nestedAnnotationsRemoved(NESTABLE_ANNOTATIONS_COLLECTION, removedAnnotations); |
| } |
| } |
| } |