blob: 5dbd2b38d1848478663f401ace4192adb3b7b891 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 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.resource.java;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.ListIterator;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.BodyDeclaration;
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.core.internal.utility.jdt.ASTNodeTextRange;
import org.eclipse.jpt.core.internal.utility.jdt.JDTTools;
import org.eclipse.jpt.core.resource.java.Annotation;
import org.eclipse.jpt.core.resource.java.ContainerAnnotation;
import org.eclipse.jpt.core.resource.java.JavaResourceNode;
import org.eclipse.jpt.core.resource.java.JavaResourcePersistentMember;
import org.eclipse.jpt.core.resource.java.NestableAnnotation;
import org.eclipse.jpt.core.utility.TextRange;
import org.eclipse.jpt.core.utility.jdt.Member;
import org.eclipse.jpt.utility.MethodSignature;
import org.eclipse.jpt.utility.internal.CollectionTools;
import org.eclipse.jpt.utility.internal.iterators.CloneIterator;
import org.eclipse.jpt.utility.internal.iterators.EmptyListIterator;
import org.eclipse.jpt.utility.internal.iterators.FilteringIterator;
import org.eclipse.jpt.utility.internal.iterators.SingleElementListIterator;
public abstract class AbstractJavaResourcePersistentMember<E extends Member>
extends AbstractJavaResourceNode
implements JavaResourcePersistentMember
{
private final E member;
/**
* stores all annotations(non-mapping) except duplicates, java compiler has an error for duplicates
*/
private final Collection<Annotation> annotations;
/**
* stores all mapping annotations except duplicates, java compiler has an error for duplicates
*/
private final Collection<Annotation> mappingAnnotations;
private boolean persistable;
AbstractJavaResourcePersistentMember(JavaResourceNode parent, E member){
super(parent);
this.member = member;
this.annotations = new ArrayList<Annotation>();
this.mappingAnnotations = new ArrayList<Annotation>();
}
public void initialize(CompilationUnit astRoot) {
getMember().getBodyDeclaration(astRoot).accept(this.buildInitialAnnotationVisitor(astRoot));
this.persistable = buildPersistable(astRoot);
}
protected ASTVisitor buildInitialAnnotationVisitor(CompilationUnit astRoot) {
return new LocalASTVisitor(astRoot, this.getMember().getBodyDeclaration(astRoot)) {
@Override
protected void visitChildAnnotation(org.eclipse.jdt.core.dom.Annotation node) {
AbstractJavaResourcePersistentMember.this.addInitialAnnotation(node, this.astRoot);
}
};
}
protected void addInitialAnnotation(org.eclipse.jdt.core.dom.Annotation node, CompilationUnit astRoot) {
String qualifiedAnnotationName = JDTTools.resolveAnnotation(node);
if (qualifiedAnnotationName == null) {
return;
}
if (isPossibleAnnotation(qualifiedAnnotationName)) {
if (getAnnotation(qualifiedAnnotationName) == null) { //don't want duplicates
Annotation annotation = buildAnnotation(qualifiedAnnotationName);
annotation.initialize(astRoot);
this.annotations.add(annotation);
}
}
else if (isPossibleMappingAnnotation(qualifiedAnnotationName)) {
if (getMappingAnnotation(qualifiedAnnotationName) == null) { //don't want duplicates
Annotation annotation = buildMappingAnnotation(qualifiedAnnotationName);
annotation.initialize(astRoot);
this.mappingAnnotations.add(annotation);
}
}
}
public E getMember() {
return this.member;
}
protected abstract Annotation buildAnnotation(String annotationName);
protected abstract Annotation buildNullAnnotation(String annotationName);
protected abstract Annotation buildMappingAnnotation(String mappingAnnotationName);
protected abstract Annotation buildNullMappingAnnotation(String annotationName);
protected abstract ListIterator<String> possibleMappingAnnotationNames();
protected abstract boolean isPossibleAnnotation(String annotationName);
protected abstract boolean isPossibleMappingAnnotation(String annotationName);
protected boolean buildPersistable(CompilationUnit astRoot) {
return this.getMember().isPersistable(astRoot);
}
public Annotation getAnnotation(String annotationName) {
for (Iterator<Annotation> i = annotations(); i.hasNext(); ) {
Annotation annotation = i.next();
if (annotation.getAnnotationName().equals(annotationName)) {
return annotation;
}
}
return null;
}
public JavaResourceNode getNonNullAnnotation(String annotationName) {
Annotation annotation = getAnnotation(annotationName);
if (annotation == null) {
return buildNullAnnotation(annotationName);
}
return annotation;
}
public Annotation getMappingAnnotation(String annotationName) {
for (Iterator<Annotation> i = mappingAnnotations(); i.hasNext(); ) {
Annotation mappingAnnotation = i.next();
if (mappingAnnotation.getAnnotationName().equals(annotationName)) {
return mappingAnnotation;
}
}
return null;
}
public JavaResourceNode getNullMappingAnnotation(String annotationName) {
return buildNullMappingAnnotation(annotationName);
}
@SuppressWarnings("unchecked")
public Iterator<Annotation> annotations() {
return new CloneIterator<Annotation>(this.annotations);
}
public int annotationsSize() {
return this.annotations.size();
}
public Annotation addAnnotation(String annotationName) {
Annotation annotation = buildAnnotation(annotationName);
this.annotations.add(annotation);
annotation.newAnnotation();
this.fireItemAdded(ANNOTATIONS_COLLECTION, annotation);
return annotation;
}
@SuppressWarnings("unchecked")
protected ContainerAnnotation<NestableAnnotation> addContainerAnnotation(String containerAnnotationName) {
return (ContainerAnnotation<NestableAnnotation>) addAnnotation(containerAnnotationName);
}
protected ContainerAnnotation<NestableAnnotation> addContainerAnnotationTwoNestableAnnotations(String containerAnnotationName) {
ContainerAnnotation<NestableAnnotation> containerAnnotation = buildContainerAnnotation(containerAnnotationName);
this.annotations.add(containerAnnotation);
containerAnnotation.newAnnotation();
containerAnnotation.addInternal(0).newAnnotation();
containerAnnotation.addInternal(1).newAnnotation();
return containerAnnotation;
}
@SuppressWarnings("unchecked")
protected ContainerAnnotation<NestableAnnotation> buildContainerAnnotation(String containerAnnotationName) {
return (ContainerAnnotation<NestableAnnotation>) buildAnnotation(containerAnnotationName);
}
@SuppressWarnings("unchecked")
protected ContainerAnnotation<NestableAnnotation> getContainerAnnotation(String containerAnnotationName) {
return (ContainerAnnotation<NestableAnnotation>) getAnnotation(containerAnnotationName);
}
protected NestableAnnotation getNestableAnnotation(String nestableAnnotationName) {
return (NestableAnnotation) getAnnotation(nestableAnnotationName);
}
protected NestableAnnotation addNestableAnnotation(String nestableAnnotationName) {
return (NestableAnnotation) addAnnotation(nestableAnnotationName);
}
//TODO it seems we should be firing one change notification here, that a new nestable annotation was added.
public NestableAnnotation addAnnotation(int index, String nestableAnnotationName, String containerAnnotationName) {
NestableAnnotation nestedAnnotation = (NestableAnnotation) getAnnotation(nestableAnnotationName);
ContainerAnnotation<NestableAnnotation> containerAnnotation = getContainerAnnotation(containerAnnotationName);
if (containerAnnotation != null) {
//ignore any nestableAnnotation and just add to the plural one
NestableAnnotation newNestedAnnotation = ContainerAnnotationTools.addNestedAnnotation(index, containerAnnotation);
//TODO any event notification being fired for the add???
return newNestedAnnotation;
}
if (nestedAnnotation == null) {
//add the nestable since neither nestable or container exists
return addNestableAnnotation(nestableAnnotationName);
}
//move the nestable to a new container annotation and add to it
ContainerAnnotation<NestableAnnotation> newContainerAnnotation = addContainerAnnotationTwoNestableAnnotations(containerAnnotationName);
if (index == 0) {
newContainerAnnotation.nestedAnnotationAt(1).initializeFrom(nestedAnnotation);
}
else {
newContainerAnnotation.nestedAnnotationAt(0).initializeFrom(nestedAnnotation);
}
removeAnnotation(nestedAnnotation);
return newContainerAnnotation.nestedAnnotationAt(index);
}
public void move(int targetIndex, int sourceIndex, String containerAnnotationName) {
move(targetIndex, sourceIndex, getContainerAnnotation(containerAnnotationName));
}
protected void move(int targetIndex, int sourceIndex, ContainerAnnotation<NestableAnnotation> containerAnnotation) {
containerAnnotation.move(targetIndex, sourceIndex);
ContainerAnnotationTools.synchAnnotationsAfterMove(targetIndex, sourceIndex, containerAnnotation);
}
protected void addAnnotation(Annotation annotation) {
addItemToCollection(annotation, this.annotations, ANNOTATIONS_COLLECTION);
}
protected void removeAnnotation(Annotation annotation) {
removeAnnotation_(annotation);
//TODO looks odd that we remove the annotation here, but in addAnnotation(Annotation) we don't do the same
annotation.removeAnnotation();
}
protected void removeAnnotation_(Annotation annotation) {
removeItemFromCollection(annotation, this.annotations, ANNOTATIONS_COLLECTION);
}
protected void addMappingAnnotation(String mappingAnnotationName) {
if (getMappingAnnotation(mappingAnnotationName) != null) {
return;
}
Annotation mappingAnnotation = buildMappingAnnotation(mappingAnnotationName);
addMappingAnnotation(mappingAnnotation);
//TODO should this be done here or should creating the Annotation do this??
mappingAnnotation.newAnnotation();
}
protected void addMappingAnnotation(Annotation annotation) {
addItemToCollection(annotation, this.mappingAnnotations, MAPPING_ANNOTATIONS_COLLECTION);
}
protected void removeMappingAnnotation(Annotation annotation) {
removeMappingAnnotation_(annotation);
annotation.removeAnnotation();
}
protected void removeMappingAnnotation_(Annotation annotation) {
removeItemFromCollection(annotation, this.mappingAnnotations, MAPPING_ANNOTATIONS_COLLECTION);
}
@SuppressWarnings("unchecked")
public Iterator<Annotation> mappingAnnotations() {
return new CloneIterator<Annotation>(this.mappingAnnotations);
}
public int mappingAnnotationsSize() {
return this.mappingAnnotations.size();
}
public void removeAnnotation(String annotationName) {
Annotation annotation = getAnnotation(annotationName);
if (annotation != null) {
removeAnnotation(annotation);
}
}
public void removeAnnotation(int index, String nestableAnnotationName, String containerAnnotationName) {
ContainerAnnotation<NestableAnnotation> containerAnnotation = getContainerAnnotation(containerAnnotationName);
if (containerAnnotation == null) {
Annotation annotation = getAnnotation(nestableAnnotationName);
removeAnnotation(annotation);
}
else {
removeAnnotation(index, containerAnnotation);
}
}
protected void removeAnnotation(int index, ContainerAnnotation<NestableAnnotation> containerAnnotation) {
NestableAnnotation nestableAnnotation = containerAnnotation.nestedAnnotationAt(index);
containerAnnotation.remove(index);
//TODO move these 2 lines to the ContainerAnnotation implementation, i think
nestableAnnotation.removeAnnotation();
ContainerAnnotationTools.synchAnnotationsAfterRemove(index, containerAnnotation);
if (containerAnnotation.nestedAnnotationsSize() == 0) {
removeAnnotation(containerAnnotation);
}
else if (containerAnnotation.nestedAnnotationsSize() == 1) {
NestableAnnotation nestedAnnotation = containerAnnotation.nestedAnnotationAt(0);
this.annotations.remove(containerAnnotation);
containerAnnotation.removeAnnotation();
NestableAnnotation newAnnotation = (NestableAnnotation) buildAnnotation(containerAnnotation.getNestableAnnotationName());
this.annotations.add(newAnnotation);
newAnnotation.newAnnotation();
newAnnotation.initializeFrom(nestedAnnotation);
this.fireItemAdded(ANNOTATIONS_COLLECTION, newAnnotation);
fireItemRemoved(ANNOTATIONS_COLLECTION, containerAnnotation);
}
}
// removes all other *mapping* annotations that exist, not just the one returned by mappingAnnotation()
// mappingAnnotation() returns the first mapping annotation found in the source. if there are multiple
// mapping annotations (which is a validation error condition) then calling this api would not work
// because the new mapping annotatio would be added to the end of the list of annotations.
public void setMappingAnnotation(String annotationName) {
if (getMappingAnnotation(annotationName) != null) {
throw new IllegalStateException("The mapping annotation named " + annotationName + " already exists.");
}
Annotation oldMapping = getMappingAnnotation();
Annotation newMapping = null;
if (oldMapping != null) {
removeUnnecessaryAnnotations(annotationName);
}
if (annotationName != null) {
if (getMappingAnnotation(annotationName) != null) {
return;
}
newMapping = buildMappingAnnotation(annotationName);
this.mappingAnnotations.add(newMapping);
newMapping.newAnnotation();
}
//have to hold property change notification until the end so a project update does not occur
//before we are finished removing the old mapping and adding the new mapping
//just firing collectio changed since one or more removes and one add was completed.
//if we ever listen for specific events instead of just doing a mass update, we might need to make this more specific
fireCollectionChanged(MAPPING_ANNOTATIONS_COLLECTION);
}
/**
* Remove all mapping annotations that already exist.
* No change notification fired.
*/
protected void removeUnnecessaryAnnotations(String newMappingAnnotationName) {
for (ListIterator<String> i = possibleMappingAnnotationNames(); i.hasNext(); ) {
String mappingAnnotationName = i.next();
if (mappingAnnotationName != newMappingAnnotationName) {
Annotation mappingAnnotation = getMappingAnnotation(mappingAnnotationName);
if (mappingAnnotation != null) {
this.mappingAnnotations.remove(mappingAnnotation);
mappingAnnotation.removeAnnotation();
}
}
}
}
//TODO need property change notification on this mappingAnnotation changing
//from the context model we don't really care if their are multiple mapping annotations,
//just which one we need to use
public Annotation getMappingAnnotation() {
for (ListIterator<String> i = possibleMappingAnnotationNames(); i.hasNext(); ) {
String mappingAnnotationName = i.next();
for (Iterator<Annotation> j = mappingAnnotations(); j.hasNext(); ) {
Annotation mappingAnnotation = j.next();
if (mappingAnnotationName == mappingAnnotation.getAnnotationName()) {
return mappingAnnotation;
}
}
}
return null;
}
@SuppressWarnings("unchecked")
public ListIterator<NestableAnnotation> annotations(String nestableAnnotationName, String containerAnnotationName) {
ContainerAnnotation<NestableAnnotation> containerAnnotation = getContainerAnnotation(containerAnnotationName);
if (containerAnnotation != null) {
return containerAnnotation.nestedAnnotations();
}
NestableAnnotation nestableAnnotation = getNestableAnnotation(nestableAnnotationName);
if (nestableAnnotation != null) {
return new SingleElementListIterator<NestableAnnotation>(nestableAnnotation);
}
return EmptyListIterator.instance();
}
public void update(CompilationUnit astRoot) {
this.updateAnnotations(astRoot);
this.setPersistable(this.buildPersistable(astRoot));
}
public void resolveTypes(CompilationUnit astRoot) {
this.setPersistable(this.buildPersistable(astRoot));
}
protected void updateAnnotations(CompilationUnit astRoot) {
getMember().getBodyDeclaration(astRoot).accept(this.buildUpdateAnnotationVisitor(astRoot));
removeMappingAnnotationsNotInSource(astRoot);
removeAnnotationsNotInSource(astRoot);
}
protected void removeAnnotationsNotInSource(CompilationUnit astRoot) {
for (Annotation annotation : CollectionTools.iterable(annotations())) {
if (annotation.getJdtAnnotation(astRoot) == null) {
removeAnnotation_(annotation);
}
}
}
protected void removeMappingAnnotationsNotInSource(CompilationUnit astRoot) {
for (Annotation mappingAnnotation : CollectionTools.iterable(mappingAnnotations())) {
if (mappingAnnotation.getJdtAnnotation(astRoot) == null) {
removeMappingAnnotation_(mappingAnnotation);
}
}
}
protected ASTVisitor buildUpdateAnnotationVisitor(CompilationUnit astRoot) {
return new LocalASTVisitor(astRoot, this.getMember().getBodyDeclaration(astRoot)) {
@Override
protected void visitChildAnnotation(org.eclipse.jdt.core.dom.Annotation node) {
AbstractJavaResourcePersistentMember.this.addOrUpdateAnnotation(node, this.astRoot);
}
};
}
protected void addOrUpdateAnnotation(org.eclipse.jdt.core.dom.Annotation node, CompilationUnit astRoot) {
String qualifiedAnnotationName = JDTTools.resolveAnnotation(node);
if (qualifiedAnnotationName == null) {
return;
}
if (isPossibleAnnotation(qualifiedAnnotationName)) {
Annotation annotation = getAnnotation(qualifiedAnnotationName);
if (annotation != null) {
annotation.update(astRoot);
}
else {
annotation = buildAnnotation(qualifiedAnnotationName);
annotation.initialize(astRoot);
addAnnotation(annotation);
}
}
else if (isPossibleMappingAnnotation(qualifiedAnnotationName)) {
Annotation annotation = getMappingAnnotation(qualifiedAnnotationName);
if (annotation != null) {
annotation.update(astRoot);
}
else {
annotation = buildMappingAnnotation(qualifiedAnnotationName);
annotation.initialize(astRoot);
addMappingAnnotation(annotation);
}
}
}
public boolean isFor(String memberName, int occurrence) {
return this.member.matches(memberName, occurrence);
}
public boolean isFor(MethodSignature methodSignature, int occurrence) {
return false;
}
public boolean isPersistable() {
return this.persistable;
}
protected void setPersistable(boolean newPersistable) {
boolean oldPersistable = this.persistable;
this.persistable = newPersistable;
firePropertyChanged(PERSISTABLE_PROPERTY, oldPersistable, newPersistable);
//TODO change notification to parent so that the context model gets notification
//that the list of persistable fields has been updated
//
}
public boolean isPersisted() {
return getMappingAnnotation() != null;
}
public TextRange fullTextRange(CompilationUnit astRoot) {
return this.getTextRange(this.getMember().getBodyDeclaration(astRoot));
}
public TextRange getTextRange(CompilationUnit astRoot) {
return this.fullTextRange(astRoot);
}
public TextRange getNameTextRange(CompilationUnit astRoot) {
return this.getMember().getNameTextRange(astRoot);
}
protected TextRange getTextRange(ASTNode astNode) {
return (astNode == null) ? null : new ASTNodeTextRange(astNode);
}
protected static <T extends JavaResourcePersistentMember> Iterator<T> persistableMembers(Iterator<T> attributes) {
return new FilteringIterator<T, T>(attributes) {
@Override
protected boolean accept(T attribute) {
return attribute.isPersistable();
}
};
}
@Override
public void toString(StringBuilder sb) {
sb.append(this.getMember());
}
// ********** AST visitor **********
protected abstract class LocalASTVisitor extends ASTVisitor {
protected final CompilationUnit astRoot;
protected final BodyDeclaration bodyDeclaration;
protected LocalASTVisitor(CompilationUnit astRoot, BodyDeclaration bodyDeclaration) {
super();
this.astRoot = astRoot;
this.bodyDeclaration = bodyDeclaration;
}
@Override
public boolean visit(SingleMemberAnnotation node) {
return visit_(node);
}
@Override
public boolean visit(NormalAnnotation node) {
return visit_(node);
}
@Override
public boolean visit(MarkerAnnotation node) {
return visit_(node);
}
protected boolean visit_(org.eclipse.jdt.core.dom.Annotation node) {
// ignore annotations for child members, only this member
if (node.getParent() == this.bodyDeclaration) {
this.visitChildAnnotation(node);
}
return false;
}
protected abstract void visitChildAnnotation(org.eclipse.jdt.core.dom.Annotation node);
}
}