blob: ec38a9ee2ea2680b17cd577be71bbcf138055c79 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2011 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.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
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.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.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 (annotations)
*/
abstract class SourceAnnotatedElement<A extends AnnotatedElement>
extends SourceNode
implements JavaResourceAnnotatedElement
{
final A annotatedElement;
/**
* annotations; no duplicates (java compiler has an error for duplicates)
*/
final Vector<Annotation> annotations = new Vector<Annotation>();
/**
* Annotation containers keyed on nestable annotation name.
* This is used to store annotations that can be both standalone and nested
* and are moved back and forth between the 2.
*/
final Map<String, AnnotationContainer> annotationContainers = new HashMap<String, AnnotationContainer>();
// ********** construction/initialization **********
SourceAnnotatedElement(JavaResourceNode parent, A annotatedElement) {
super(parent);
this.annotatedElement = annotatedElement;
}
public void initialize(CompilationUnit astRoot) {
ASTNode node = this.annotatedElement.getBodyDeclaration(astRoot);
node.accept(this.buildInitialAnnotationVisitor(node));
}
private ASTVisitor buildInitialAnnotationVisitor(ASTNode node) {
return new InitialAnnotationVisitor(node);
}
/**
* called from {@link InitialAnnotationVisitor}
*/
/* private */ void addInitialAnnotation(org.eclipse.jdt.core.dom.Annotation node) {
String jdtAnnotationName = ASTTools.resolveAnnotation(node);
if (jdtAnnotationName != null) {
if(this.annotationIsValidContainer(jdtAnnotationName)) {
String nestableAnnotationName = this.getNestableAnnotationName(jdtAnnotationName);
AnnotationContainer container = new AnnotationContainer(nestableAnnotationName);
container.initialize(node);
this.annotationContainers.put(nestableAnnotationName, container);
}
else if (this.annotationIsValid(jdtAnnotationName)) {
if (this.selectAnnotationNamed(this.annotations, jdtAnnotationName) == null) { // ignore duplicates
Annotation annotation = this.buildAnnotation(jdtAnnotationName);
annotation.initialize((CompilationUnit) node.getRoot());
this.annotations.add(annotation);
}
}
else if(this.annotationIsValidNestable(jdtAnnotationName)) {
AnnotationContainer container = new AnnotationContainer(jdtAnnotationName);
container.initializeNestableAnnotation(node);
this.annotationContainers.put(jdtAnnotationName, container);
}
}
}
public void synchronizeWith(CompilationUnit astRoot) {
this.syncAnnotations(this.annotatedElement.getBodyDeclaration(astRoot));
}
// ********** annotations **********
public Iterable<Annotation> getAnnotations() {
return new LiveCloneIterable<Annotation>(this.annotations);
}
public int getAnnotationsSize() {
return this.annotations.size();
}
protected Iterable<NestableAnnotation> getNestableAnnotations() {
return new CompositeIterable<NestableAnnotation>(this.getNestableAnnotationLists());
}
private Iterable<Iterable<NestableAnnotation>> getNestableAnnotationLists() {
return new TransformationIterable<AnnotationContainer, Iterable<NestableAnnotation>>(this.annotationContainers.values()) {
@Override
protected Iterable<NestableAnnotation> transform(AnnotationContainer container) {
return container.getNestedAnnotations();
}
};
}
public Annotation getAnnotation(String annotationName) {
if (this.annotationIsValidContainer(annotationName)) {
AnnotationContainer container = this.annotationContainers.get(getAnnotationProvider().getNestableAnnotationName(annotationName));
return container == null ? null : container.getContainerAnnotation();
}
return this.selectAnnotationNamed(this.getAnnotations(), annotationName);
}
public Annotation getNonNullAnnotation(String annotationName) {
Annotation annotation = this.getAnnotation(annotationName);
return (annotation != null) ? annotation : this.buildNullAnnotation(annotationName);
}
public ListIterable<NestableAnnotation> getAnnotations(String nestableAnnotationName) {
AnnotationContainer container = this.annotationContainers.get(nestableAnnotationName);
return container != null ? container.getNestedAnnotations() : EmptyListIterable.<NestableAnnotation> instance();
}
public int getAnnotationsSize(String nestableAnnotationName) {
AnnotationContainer container = this.annotationContainers.get(nestableAnnotationName);
return container == null ? 0 : container.getNestedAnnotationsSize();
}
public NestableAnnotation getAnnotation(int index, String nestableAnnotationName) {
AnnotationContainer container = this.annotationContainers.get(nestableAnnotationName);
return container == null ? null : container.nestedAnnotationAt(index);
}
public Annotation getContainerAnnotation(String containerAnnotationName) {
AnnotationContainer container = this.annotationContainers.get(getAnnotationProvider().getNestableAnnotationName(containerAnnotationName));
return container == null ? null : container.getContainerAnnotation();
}
private String getNestableAnnotationName(String containerAnnotationName) {
return getAnnotationProvider().getNestableAnnotationName(containerAnnotationName);
}
private String getNestableElementName(String nestableAnnotationName) {
return getAnnotationProvider().getNestableElementName(nestableAnnotationName);
}
public Annotation addAnnotation(String annotationName) {
Annotation annotation = this.buildAnnotation(annotationName);
this.annotations.add(annotation);
annotation.newAnnotation();
return annotation;
}
public NestableAnnotation addAnnotation(int index, String nestableAnnotationName) {
AnnotationContainer container = this.annotationContainers.get(nestableAnnotationName);
if (container == null) {
container = new AnnotationContainer(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(String annotationName) {
Annotation annotation = this.getAnnotation(annotationName);
if (annotation != null) {
this.removeAnnotation(annotation);
}
}
private void removeAnnotation(Annotation annotation) {
this.annotations.remove(annotation);
annotation.removeAnnotation();
}
public void removeAnnotation(int index, String nestableAnnotationName) {
AnnotationContainer container = this.annotationContainers.get(nestableAnnotationName);
container.removeNestedAnnotation(index);
if (container.isEmpty()) {
this.annotationContainers.remove(nestableAnnotationName);
}
}
protected boolean annotationIsValid(String annotationName) {
return CollectionTools.contains(this.getValidAnnotationNames(), annotationName);
}
protected boolean annotationIsValidContainer(String annotationName) {
return CollectionTools.contains(this.getValidContainerAnnotationNames(), annotationName);
}
protected boolean annotationIsValidNestable(String annotationName) {
return CollectionTools.contains(this.getValidNestableAnnotationNames(), annotationName);
}
Iterable<String> getValidAnnotationNames() {
return this.getAnnotationProvider().getAnnotationNames();
}
Iterable<String> getValidContainerAnnotationNames() {
return this.getAnnotationProvider().getContainerAnnotationNames();
}
Iterable<String> getValidNestableAnnotationNames() {
return this.getAnnotationProvider().getNestableAnnotationNames();
}
Annotation buildAnnotation(String annotationName) {
return this.getAnnotationProvider().buildAnnotation(this, this.annotatedElement, annotationName);
}
Annotation buildNullAnnotation(String annotationName) {
return this.getAnnotationProvider().buildNullAnnotation(this, annotationName);
}
NestableAnnotation buildNestableAnnotation(String annotationName, int index) {
return this.getAnnotationProvider().buildAnnotation(this, this.annotatedElement, annotationName, index);
}
private void syncAnnotations(ASTNode node) {
HashSet<Annotation> annotationsToRemove = new HashSet<Annotation>(this.annotations);
HashSet<AnnotationContainer> containersToRemove = new HashSet<AnnotationContainer>(this.annotationContainers.values());
node.accept(this.buildSynchronizeAnnotationVisitor(node, annotationsToRemove, containersToRemove));
for (Annotation annotation : annotationsToRemove) {
this.removeItemFromCollection(annotation, this.annotations, ANNOTATIONS_COLLECTION);
}
for (AnnotationContainer annotationContainer : containersToRemove) {
this.annotationContainers.remove(annotationContainer.getNestedAnnotationName());
fireItemsRemoved(NESTABLE_ANNOTATIONS_COLLECTION, CollectionTools.collection(annotationContainer.getNestedAnnotations()));
}
Iterator<String> keys = this.annotationContainers.keySet().iterator();
while (keys.hasNext()) {
String annotationName = keys.next();
if (this.annotationContainers.get(annotationName).getNestedAnnotationsSize() == 0) {
keys.remove();
}
}
}
private ASTVisitor buildSynchronizeAnnotationVisitor(ASTNode node, Set<Annotation> annotationsToRemove, Set<AnnotationContainer> containersToRemove) {
return new SynchronizeAnnotationVisitor(node, annotationsToRemove, containersToRemove);
}
/**
* called from {@link SynchronizeAnnotationVisitor}
*/
/* private */ void addOrSyncAnnotation(org.eclipse.jdt.core.dom.Annotation node, Set<Annotation> annotationsToRemove, Set<AnnotationContainer> containersToRemove) {
String jdtAnnotationName = ASTTools.resolveAnnotation(node);
if (jdtAnnotationName != null) {
if (this.annotationIsValidContainer(jdtAnnotationName)) {
this.addOrSyncContainerAnnotation_(node, jdtAnnotationName, containersToRemove);
}
else if (this.annotationIsValid(jdtAnnotationName)) {
this.addOrSyncAnnotation_(node, jdtAnnotationName, annotationsToRemove);
}
else if(this.annotationIsValidNestable(jdtAnnotationName)) {
this.addOrSyncNestableAnnotation_(node, jdtAnnotationName, containersToRemove);
}
}
}
/**
* pre-condition: jdtAnnotationName is valid
*/
private void addOrSyncAnnotation_(org.eclipse.jdt.core.dom.Annotation node, String jdtAnnotationName, Set<Annotation> annotationsToRemove) {
Annotation annotation = this.selectAnnotationNamed(annotationsToRemove, jdtAnnotationName);
if (annotation != null) {
annotation.synchronizeWith((CompilationUnit) node.getRoot());
annotationsToRemove.remove(annotation);
} else {
annotation = this.buildAnnotation(jdtAnnotationName);
annotation.initialize((CompilationUnit) node.getRoot());
this.addItemToCollection(annotation, this.annotations, ANNOTATIONS_COLLECTION);
}
}
/**
* pre-condition: jdtAnnotationName is valid
*/
private void addOrSyncNestableAnnotation_(org.eclipse.jdt.core.dom.Annotation node, String nestableAnnotationName, Set<AnnotationContainer> containersToRemove) {
AnnotationContainer container = this.annotationContainers.get(nestableAnnotationName);
if (container != null) {
container.synchronizeNestableAnnotation(node);
containersToRemove.remove(container);
}
else {
container = new AnnotationContainer(nestableAnnotationName);
container.initializeNestableAnnotation(node);
this.annotationContainers.put(nestableAnnotationName, container);
this.fireItemAdded(NESTABLE_ANNOTATIONS_COLLECTION, container.nestedAnnotationAt(0));
}
}
/**
* pre-condition: node is valid container annotation
*/
private void addOrSyncContainerAnnotation_(org.eclipse.jdt.core.dom.Annotation node, String containerAnnotationName, Set<AnnotationContainer> containersToRemove) {
String nestableAnnotationName = this.getNestableAnnotationName(containerAnnotationName);
AnnotationContainer container = this.annotationContainers.get(nestableAnnotationName);
if (container == null) {
container = new AnnotationContainer(nestableAnnotationName);
container.initialize(node);
this.annotationContainers.put(nestableAnnotationName, container);
this.fireItemsAdded(NESTABLE_ANNOTATIONS_COLLECTION, CollectionTools.collection(container.getNestedAnnotations()));
}
else {
container.synchronize(node);
containersToRemove.remove(container);
}
}
// ********** miscellaneous **********
public Iterable<Annotation> getAllAnnotations() {
return new CompositeIterable<Annotation>(
getAnnotations(),
getContainerOrNestableAnnotations());
}
protected Iterable<Annotation> getContainerOrNestableAnnotations() {
return new TransformationIterable<AnnotationContainer, Annotation>(this.annotationContainers.values()) {
@Override
protected Annotation transform(AnnotationContainer o) {
return (o.getContainerAnnotation() != null) ? o.getContainerAnnotation() : CollectionTools.get(o.getNestedAnnotations(), 0);
}
};
}
public boolean isAnnotated() {
return ! (this.annotations.isEmpty() && this.annotationContainers.isEmpty());
}
public boolean isAnnotatedWith(Iterable<String> annotationNames) {
for (Annotation annotation : this.getAnnotations()) {
if (CollectionTools.contains(annotationNames, annotation.getAnnotationName())) {
return true;
}
}
for (Annotation annotation : this.getNestableAnnotations()) {
if (CollectionTools.contains(annotationNames, annotation.getAnnotationName())) {
return true;
}
}
return false;
}
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) {
Annotation containerAnnotation = getContainerAnnotation(getAnnotationProvider().getContainerAnnotationName(nestableAnnotationName));
if (containerAnnotation != null) {
return containerAnnotation.getTextRange(astRoot);
}
Annotation nestableAnnotation = getAnnotation(0, nestableAnnotationName);
return nestableAnnotation == null ? null : nestableAnnotation.getTextRange(astRoot);
}
private Annotation selectAnnotationNamed(Iterable<Annotation> list, String annotationName) {
for (Annotation annotation : list) {
if (annotation.getAnnotationName().equals(annotationName)) {
return annotation;
}
}
return null;
}
private TextRange buildTextRange(ASTNode astNode) {
return (astNode == null) ? null : ASTTools.buildTextRange(astNode);
}
// ********** AST visitors **********
/**
* annotation visitor
*/
protected static abstract class AnnotationVisitor
extends ASTVisitor
{
protected final ASTNode node;
protected AnnotationVisitor(ASTNode node) {
super();
this.node = node;
}
@Override
public boolean visit(SingleMemberAnnotation node) {
return this.visit_(node);
}
@Override
public boolean visit(NormalAnnotation node) {
return this.visit_(node);
}
@Override
public boolean visit(MarkerAnnotation node) {
return this.visit_(node);
}
protected boolean visit_(org.eclipse.jdt.core.dom.Annotation node) {
// ignore annotations for child members, only this member
if (node.getParent() == this.node) {
this.visitChildAnnotation(node);
}
return false;
}
protected abstract void visitChildAnnotation(org.eclipse.jdt.core.dom.Annotation node);
}
/**
* initial annotation visitor
*/
protected class InitialAnnotationVisitor
extends AnnotationVisitor
{
protected InitialAnnotationVisitor(ASTNode node) {
super(node);
}
@Override
protected void visitChildAnnotation(org.eclipse.jdt.core.dom.Annotation node) {
SourceAnnotatedElement.this.addInitialAnnotation(node);
}
}
/**
* synchronize annotation visitor
*/
protected class SynchronizeAnnotationVisitor
extends AnnotationVisitor
{
protected final Set<Annotation> annotationsToRemove;
protected final Set<AnnotationContainer> containersToRemove;
protected SynchronizeAnnotationVisitor(ASTNode node, Set<Annotation> annotationsToRemove, Set<AnnotationContainer> containersToRemove) {
super(node);
this.annotationsToRemove = annotationsToRemove;
this.containersToRemove = containersToRemove;
}
@Override
protected void visitChildAnnotation(org.eclipse.jdt.core.dom.Annotation node) {
SourceAnnotatedElement.this.addOrSyncAnnotation(node, this.annotationsToRemove, this.containersToRemove);
}
}
class AnnotationContainer extends SourceNode.AnnotationContainer<NestableAnnotation>
{
private final String nestableAnnotationName;
private Annotation containerAnnotation;
protected AnnotationContainer(String nestableAnnotationName) {
super();
this.nestableAnnotationName = nestableAnnotationName;
}
@Override
public void initialize(org.eclipse.jdt.core.dom.Annotation astContainerAnnotation) {
super.initialize(astContainerAnnotation);
this.containerAnnotation = this.buildContainerAnnotation(ASTTools.resolveAnnotation(astContainerAnnotation));
}
protected Annotation buildContainerAnnotation(String name) {
return getAnnotationProvider().buildAnnotation(SourceAnnotatedElement.this, SourceAnnotatedElement.this.annotatedElement, name);
}
protected Annotation getContainerAnnotation() {
return this.containerAnnotation;
}
@Override
public void synchronize(org.eclipse.jdt.core.dom.Annotation astContainerAnnotation) {
super.synchronize(astContainerAnnotation);
}
/**
* 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);
}
public void initializeNestableAnnotation(org.eclipse.jdt.core.dom.Annotation standaloneNestableAnnotation) {
NestableAnnotation nestedAnnotation = this.buildNestedAnnotation(0);
this.nestedAnnotations.add(nestedAnnotation);
nestedAnnotation.initialize((CompilationUnit) standaloneNestableAnnotation.getRoot());
}
public void synchronizeNestableAnnotation(org.eclipse.jdt.core.dom.Annotation standaloneNestableAnnotation) {
if (this.getNestedAnnotationsSize() > 1) {
//ignore the new standalone annotation as a container annotation already exists
}
else if (this.getNestedAnnotationsSize() == 1) {
this.containerAnnotation = null;
this.nestedAnnotationAt(0).synchronizeWith((CompilationUnit) standaloneNestableAnnotation.getRoot());
}
}
@Override
public NestableAnnotation addNestedAnnotation(int index) {
if (getNestedAnnotationsSize() == 1 && getContainerAnnotation() == null) {
this.containerAnnotation = buildContainerAnnotation(getAnnotationProvider().getContainerAnnotationName(getNestedAnnotationName()));
}
return super.addNestedAnnotation(index);
}
@Override
public NestableAnnotation removeNestedAnnotation(int index) {
if (getNestedAnnotationsSize() == 2) {
this.containerAnnotation = null;
}
return super.removeNestedAnnotation(index);
}
@Override
protected void fireItemAdded(int index, NestableAnnotation nestedAnnotation) {
SourceAnnotatedElement.this.fireItemAdded(NESTABLE_ANNOTATIONS_COLLECTION, nestedAnnotation);
}
@Override
protected void fireItemsRemoved(int index, List<NestableAnnotation> removedItems) {
SourceAnnotatedElement.this.fireItemsRemoved(NESTABLE_ANNOTATIONS_COLLECTION, removedItems);
}
}
}