blob: 0b0f31b7e2dae416c1e013c20b263cd2e65517dc [file] [log] [blame]
/*******************************************************************************
* 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.Iterator;
import java.util.List;
import java.util.Vector;
import org.eclipse.jdt.core.dom.ASTNode;
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.common.core.AnnotationProvider;
import org.eclipse.jpt.common.core.internal.resource.java.AbstractJavaResourceNode;
import org.eclipse.jpt.common.core.resource.java.JavaResourceCompilationUnit;
import org.eclipse.jpt.common.core.resource.java.JavaResourceNode;
import org.eclipse.jpt.common.core.resource.java.NestableAnnotation;
import org.eclipse.jpt.common.utility.internal.CollectionTools;
import org.eclipse.jpt.common.utility.internal.StringTools;
import org.eclipse.jpt.common.utility.internal.iterables.ListIterable;
import org.eclipse.jpt.common.utility.internal.iterables.LiveCloneListIterable;
/**
* Source convenience methods
*/
public abstract class SourceNode
extends AbstractJavaResourceNode
{
protected SourceNode(JavaResourceNode parent) {
super(parent);
}
public JavaResourceCompilationUnit getJavaResourceCompilationUnit() {
return (JavaResourceCompilationUnit) this.getRoot();
}
protected CompilationUnit buildASTRoot() {
return this.getJavaResourceCompilationUnit().buildASTRoot();
}
protected void nestedAnnotationAdded(String listName, int index, NestableAnnotation addedAnnotation) {
this.fireItemAdded(listName, index, addedAnnotation);
}
protected void nestedAnnotationsRemoved(String listName, int index, List<? extends NestableAnnotation> removedAnnotations) {
this.fireItemsRemoved(listName, index, removedAnnotations);
}
@Override
protected AnnotationProvider getAnnotationProvider() {
return super.getAnnotationProvider();
}
// ********** annotation container **********
/**
* A container for nested annotations. The owner of the AnnotationContainer
* needs to call
* {@link #initializeFromContainerAnnotation(org.eclipse.jdt.core.dom.Annotation)}
* on it.
* @param <A> the type of the resource nestable annotations
*/
protected abstract class AnnotationContainer<A extends NestableAnnotation> {
protected final Vector<A> nestedAnnotations = new Vector<A>();
protected AnnotationContainer() {
super();
}
/**
* Return the element name of the nested annotations
*/
protected abstract String getElementName();
/**
* Return the nested annotation name
*/
protected abstract String getNestedAnnotationName();
/**
* Return a new nested annotation at the given index
*/
protected abstract A buildNestedAnnotation(int index);
public void initializeFromContainerAnnotation(org.eclipse.jdt.core.dom.Annotation astContainerAnnotation) {
// ignore the nested AST annotations themselves
// TODO (maybe someday we can use them during initialization...)
int size = this.getNestedAstAnnotations(astContainerAnnotation).size();
for (int i = 0; i < size; i++) {
A nestedAnnotation = this.buildNestedAnnotation(i);
this.nestedAnnotations.add(i, nestedAnnotation);
nestedAnnotation.initialize((CompilationUnit) astContainerAnnotation.getRoot());
}
}
/**
* Synchronize the resource model annotations with those in the specified AST.
* Trigger the appropriate change notification.
*/
public void synchronize(org.eclipse.jdt.core.dom.Annotation astContainerAnnotation) {
ArrayList<org.eclipse.jdt.core.dom.Annotation> astAnnotations = this.getNestedAstAnnotations(astContainerAnnotation);
Iterator<org.eclipse.jdt.core.dom.Annotation> astAnnotationStream = astAnnotations.iterator();
for (A nestedAnnotation : this.getNestedAnnotations()) {
if (astAnnotationStream.hasNext()) {
// matching AST annotation is present - synchronize the nested annotation
astAnnotationStream.next(); // TODO pass this to the update
nestedAnnotation.synchronizeWith((CompilationUnit) astContainerAnnotation.getRoot());
} else {
// no more AST annotations - remove the remaining nested annotations and exit
this.syncRemoveNestedAnnotations(astAnnotations.size());
return;
}
}
// add nested annotations for any remaining AST annotations
while (astAnnotationStream.hasNext()) {
this.syncAddNestedAnnotation(astAnnotationStream.next());
}
}
public ListIterable<A> getNestedAnnotations() {
return new LiveCloneListIterable<A>(this.nestedAnnotations);
}
public int getNestedAnnotationsSize() {
return this.nestedAnnotations.size();
}
public A getNestedAnnotation(int index) {
return this.nestedAnnotations.get(index);
}
public A addNestedAnnotation(int index) {
// add a new annotation to the end of the list...
int sourceIndex = this.nestedAnnotations.size();
A nestedAnnotation = this.buildNestedAnnotation(sourceIndex);
this.nestedAnnotations.add(sourceIndex, nestedAnnotation);
nestedAnnotation.newAnnotation();
nestedAnnotation.initialize(nestedAnnotation.getJavaResourceCompilationUnit().buildASTRoot());
// ...then move it to the specified index
this.moveNestedAnnotation(index, sourceIndex);
return nestedAnnotation;
}
public A moveNestedAnnotation(int targetIndex, int sourceIndex) {
if (targetIndex != sourceIndex) {
return this.moveNestedAnnotation_(targetIndex, sourceIndex);
}
return null;
}
public A removeNestedAnnotation(int index) {
A nestedAnnotation = this.nestedAnnotations.remove(index);
nestedAnnotation.removeAnnotation();
this.syncAstAnnotationsAfterRemove(index);
return nestedAnnotation;
}
private A moveNestedAnnotation_(int targetIndex, int sourceIndex) {
A nestedAnnotation = CollectionTools.move(this.nestedAnnotations, targetIndex, sourceIndex).get(targetIndex);
this.syncAstAnnotationsAfterMove(targetIndex, sourceIndex, nestedAnnotation);
return nestedAnnotation;
}
/**
* Return a list of the nested AST annotations.
*/
private ArrayList<org.eclipse.jdt.core.dom.Annotation> getNestedAstAnnotations(org.eclipse.jdt.core.dom.Annotation astContainerAnnotation) {
ArrayList<org.eclipse.jdt.core.dom.Annotation> result = new ArrayList<org.eclipse.jdt.core.dom.Annotation>();
if (astContainerAnnotation == null || astContainerAnnotation.isMarkerAnnotation()) {
// no nested annotations
}
else if (astContainerAnnotation.isSingleMemberAnnotation()) {
if (this.getElementName().equals("value")) { //$NON-NLS-1$
Expression ex = ((SingleMemberAnnotation) astContainerAnnotation).getValue();
this.addAstAnnotationsTo(ex, result);
} else {
// no nested annotations
}
}
else if (astContainerAnnotation.isNormalAnnotation()) {
MemberValuePair pair = this.getMemberValuePair((NormalAnnotation) astContainerAnnotation);
if (pair == null) {
// no nested annotations
} else {
this.addAstAnnotationsTo(pair.getValue(), 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 void addAstAnnotationsTo(Expression expression, ArrayList<org.eclipse.jdt.core.dom.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) {
this.addAstAnnotationsTo((ArrayInitializer) expression, astAnnotations);
}
else {
org.eclipse.jdt.core.dom.Annotation astAnnotation = this.getAstAnnotation_(expression);
if (astAnnotation != null) {
astAnnotations.add(astAnnotation);
}
}
}
private void addAstAnnotationsTo(ArrayInitializer arrayInitializer, ArrayList<org.eclipse.jdt.core.dom.Annotation> astAnnotations) {
List<Expression> expressions = this.expressions(arrayInitializer);
for (Expression expression : expressions) {
org.eclipse.jdt.core.dom.Annotation astAnnotation = getAstAnnotation(expression);
if (astAnnotation != null) {
astAnnotations.add(astAnnotation);
}
}
}
// minimize scope of suppressed warnings
@SuppressWarnings("unchecked")
private List<Expression> expressions(ArrayInitializer arrayInitializer) {
return arrayInitializer.expressions();
}
/**
* If the specified expression is an annotation with the specified name, return it;
* otherwise return null.
*/
private org.eclipse.jdt.core.dom.Annotation getAstAnnotation(Expression expression) {
// not sure how the expression could be null...
return (expression == null) ? null : getAstAnnotation_(expression);
}
/**
* pre-condition: expression is not null
*/
private org.eclipse.jdt.core.dom.Annotation getAstAnnotation_(Expression expression) {
switch (expression.getNodeType()) {
case ASTNode.NORMAL_ANNOTATION:
case ASTNode.SINGLE_MEMBER_ANNOTATION:
case ASTNode.MARKER_ANNOTATION:
org.eclipse.jdt.core.dom.Annotation astAnnotation = (org.eclipse.jdt.core.dom.Annotation) expression;
if (this.getQualifiedName(astAnnotation).equals(this.getNestedAnnotationName())) {
return astAnnotation;
}
return null;
default:
return null;
}
}
private String getQualifiedName(org.eclipse.jdt.core.dom.Annotation astAnnotation) {
ITypeBinding typeBinding = astAnnotation.resolveTypeBinding();
if (typeBinding != null) {
String resolvedName = typeBinding.getQualifiedName();
if (resolvedName != null) {
return resolvedName;
}
}
return astAnnotation.getTypeName().getFullyQualifiedName();
}
private MemberValuePair getMemberValuePair(NormalAnnotation annotation) {
List<MemberValuePair> pairs = this.values(annotation);
for (MemberValuePair pair : pairs) {
if (pair.getName().getFullyQualifiedName().equals(this.getElementName())) {
return pair;
}
}
return null;
}
@SuppressWarnings("unchecked")
private List<MemberValuePair> values(NormalAnnotation na) {
return na.values();
}
/**
* 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 void syncAstAnnotationsAfterMove(int targetIndex, int sourceIndex, A nestedAnnotation) {
// move the Java annotation to the end of the list...
nestedAnnotation.moveAnnotation(this.nestedAnnotations.size());
// ...then shift the other AST annotations over one slot...
if (sourceIndex < targetIndex) {
for (int i = sourceIndex; i < targetIndex; i++) {
this.nestedAnnotations.get(i).moveAnnotation(i);
}
} else {
for (int i = sourceIndex; i > targetIndex; i-- ) {
this.nestedAnnotations.get(i).moveAnnotation(i);
}
}
// ...then move the AST annotation to the now empty slot at the target index
nestedAnnotation.moveAnnotation(targetIndex);
}
/**
* 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 void syncAstAnnotationsAfterRemove(int index) {
for (int i = index; i < this.nestedAnnotations.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
this.nestedAnnotations.get(i).moveAnnotation(i);
}
}
private void syncAddNestedAnnotation(org.eclipse.jdt.core.dom.Annotation astAnnotation) {
int index = this.nestedAnnotations.size();
A nestedAnnotation = this.buildNestedAnnotation(index);
nestedAnnotation.initialize((CompilationUnit) astAnnotation.getRoot());
this.nestedAnnotations.add(index, nestedAnnotation);
this.nestedAnnotationAdded(index, nestedAnnotation);
}
void nestedAnnotationAdded(int index, A addedAnnotation) {
SourceNode.this.nestedAnnotationAdded(this.getNestedAnnotationsListName(), index, addedAnnotation);
}
/**
* Remove the nested annotations from the specified index to the end of
* the list.
*/
void syncRemoveNestedAnnotations(int index) {
List<A> subList = this.nestedAnnotations.subList(index, this.nestedAnnotations.size());
List<A> removedAnnotations = new ArrayList<A>(subList);
subList.clear();
this.nestedAnnotationsRemoved(index, removedAnnotations);
}
void nestedAnnotationsRemoved(int index, List<A> removedAnnotations) {
SourceNode.this.nestedAnnotationsRemoved(this.getNestedAnnotationsListName(), index, removedAnnotations);
}
/**
* Return the nested annotations list name for firing property change
* notification.
*/
protected abstract String getNestedAnnotationsListName();
public boolean isEmpty() {
return this.nestedAnnotations.isEmpty();
}
AnnotationProvider getAnnotationProvider() {
return SourceNode.this.getAnnotationProvider();
}
@Override
public String toString() {
return StringTools.buildToStringFor(this, this.nestedAnnotations);
}
}
}