package org.springframework.roo.classpath.antlrjavaparser.details;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.Validate;
import org.springframework.roo.classpath.antlrjavaparser.CompilationUnitServices;
import org.springframework.roo.classpath.antlrjavaparser.JavaParserUtils;
import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.ArrayAttributeValue;
import org.springframework.roo.classpath.details.annotations.BooleanAttributeValue;
import org.springframework.roo.classpath.details.annotations.CharAttributeValue;
import org.springframework.roo.classpath.details.annotations.ClassAttributeValue;
import org.springframework.roo.classpath.details.annotations.DoubleAttributeValue;
import org.springframework.roo.classpath.details.annotations.EnumAttributeValue;
import org.springframework.roo.classpath.details.annotations.IntegerAttributeValue;
import org.springframework.roo.classpath.details.annotations.LongAttributeValue;
import org.springframework.roo.classpath.details.annotations.NestedAnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.StringAttributeValue;
import org.springframework.roo.classpath.details.comments.CommentStructure;
import org.springframework.roo.model.Builder;
import org.springframework.roo.model.EnumDetails;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import com.github.antlrjavaparser.api.expr.AnnotationExpr;
import com.github.antlrjavaparser.api.expr.ArrayInitializerExpr;
import com.github.antlrjavaparser.api.expr.BinaryExpr;
import com.github.antlrjavaparser.api.expr.BooleanLiteralExpr;
import com.github.antlrjavaparser.api.expr.CharLiteralExpr;
import com.github.antlrjavaparser.api.expr.ClassExpr;
import com.github.antlrjavaparser.api.expr.DoubleLiteralExpr;
import com.github.antlrjavaparser.api.expr.Expression;
import com.github.antlrjavaparser.api.expr.FieldAccessExpr;
import com.github.antlrjavaparser.api.expr.IntegerLiteralExpr;
import com.github.antlrjavaparser.api.expr.LongLiteralExpr;
import com.github.antlrjavaparser.api.expr.MarkerAnnotationExpr;
import com.github.antlrjavaparser.api.expr.MemberValuePair;
import com.github.antlrjavaparser.api.expr.NameExpr;
import com.github.antlrjavaparser.api.expr.NormalAnnotationExpr;
import com.github.antlrjavaparser.api.expr.SingleMemberAnnotationExpr;
import com.github.antlrjavaparser.api.expr.StringLiteralExpr;
import com.github.antlrjavaparser.api.expr.UnaryExpr;
import com.github.antlrjavaparser.api.expr.UnaryExpr.Operator;
import com.github.antlrjavaparser.api.type.Type;
/**
* Java Parser implementation of {@link AnnotationMetadata}.
*
* @author Ben Alex
* @author Andrew Swan
* @since 1.0
*/
public class JavaParserAnnotationMetadataBuilder implements
Builder<AnnotationMetadata> {
/**
* Facilitates the addition of the annotation to the presented type.
*
* @param compilationUnitServices to use (required)
* @param annotations to add to the end of (required)
* @param annotation to add (required)
*/
public static void addAnnotationToList(
final CompilationUnitServices compilationUnitServices,
final List<AnnotationExpr> annotations,
final AnnotationMetadata annotation) {
Validate.notNull(compilationUnitServices,
"Compilation unit services required");
Validate.notNull(annotations, "Annotations required");
Validate.notNull(annotation, "Annotation metadata required");
// Create a holder for the annotation we're going to create
boolean foundExisting = false;
// Search for an existing annotation of this type
for (final AnnotationExpr candidate : annotations) {
NameExpr existingName = null;
if (candidate instanceof NormalAnnotationExpr) {
existingName = ((NormalAnnotationExpr) candidate).getName();
}
else if (candidate instanceof MarkerAnnotationExpr) {
existingName = ((MarkerAnnotationExpr) candidate).getName();
}
else if (candidate instanceof SingleMemberAnnotationExpr) {
existingName = ((SingleMemberAnnotationExpr) candidate)
.getName();
}
// Convert the candidate annotation type's into a JavaType
final JavaType javaType = JavaParserUtils.getJavaType(
compilationUnitServices, existingName, null);
if (annotation.getAnnotationType().equals(javaType)) {
foundExisting = true;
break;
}
}
Validate.isTrue(!foundExisting,
"Found an existing annotation for type '%s'",
annotation.getAnnotationType());
// Import the annotation type, if needed
final NameExpr nameToUse = JavaParserUtils.importTypeIfRequired(
compilationUnitServices.getEnclosingTypeName(),
compilationUnitServices.getImports(),
annotation.getAnnotationType());
// Create member-value pairs in accordance with Java Parser requirements
final List<MemberValuePair> memberValuePairs = new ArrayList<MemberValuePair>();
for (final JavaSymbolName attributeName : annotation
.getAttributeNames()) {
final AnnotationAttributeValue<?> value = annotation
.getAttribute(attributeName);
Validate.notNull(value,
"Unable to acquire value '%s' from annotation",
attributeName);
final MemberValuePair memberValuePair = convert(value);
// Validate.notNull(memberValuePair,
// "Member value pair should have been set");
if (memberValuePair != null) {
memberValuePairs.add(memberValuePair);
}
}
// Create the AnnotationExpr; it varies depending on how many
// member-value pairs we need to present
AnnotationExpr annotationExpression = null;
if (memberValuePairs.isEmpty()) {
annotationExpression = new MarkerAnnotationExpr(nameToUse);
}
else if (memberValuePairs.size() == 1
&& (memberValuePairs.get(0).getName() == null || "value"
.equals(memberValuePairs.get(0).getName()))) {
final Expression toUse = JavaParserUtils
.importExpressionIfRequired(
compilationUnitServices.getEnclosingTypeName(),
compilationUnitServices.getImports(),
memberValuePairs.get(0).getValue());
annotationExpression = new SingleMemberAnnotationExpr(nameToUse,
toUse);
}
else {
// We have a number of pairs being presented
annotationExpression = new NormalAnnotationExpr(nameToUse,
new ArrayList<MemberValuePair>());
}
// Add our AnnotationExpr to the actual annotations that will eventually
// be flushed through to the compilation unit
JavaParserCommentMetadataBuilder.updateCommentsToJavaParser(
annotationExpression, annotation.getCommentStructure());
annotations.add(annotationExpression);
// Add member-value pairs to our AnnotationExpr
if (!memberValuePairs.isEmpty()) {
// Have to check here for cases where we need to change an existing
// MarkerAnnotationExpr to a NormalAnnotationExpr or
// SingleMemberAnnotationExpr
if (annotationExpression instanceof MarkerAnnotationExpr) {
final MarkerAnnotationExpr mae = (MarkerAnnotationExpr) annotationExpression;
annotations.remove(mae);
if (memberValuePairs.size() == 1
&& (memberValuePairs.get(0).getName() == null || "value"
.equals(memberValuePairs.get(0).getName()))) {
final Expression toUse = JavaParserUtils
.importExpressionIfRequired(compilationUnitServices
.getEnclosingTypeName(),
compilationUnitServices.getImports(),
memberValuePairs.get(0).getValue());
annotationExpression = new SingleMemberAnnotationExpr(
nameToUse, toUse);
JavaParserCommentMetadataBuilder
.updateCommentsToJavaParser(annotationExpression,
annotation.getCommentStructure());
annotations.add(annotationExpression);
}
else {
// We have a number of pairs being presented
annotationExpression = new NormalAnnotationExpr(nameToUse,
new ArrayList<MemberValuePair>());
JavaParserCommentMetadataBuilder
.updateCommentsToJavaParser(annotationExpression,
annotation.getCommentStructure());
annotations.add(annotationExpression);
}
}
if (annotationExpression instanceof SingleMemberAnnotationExpr) {
// Potentially upgrade this expression to a NormalAnnotationExpr
final SingleMemberAnnotationExpr smae = (SingleMemberAnnotationExpr) annotationExpression;
if (memberValuePairs.size() == 1
&& memberValuePairs.get(0).getName() == null
|| memberValuePairs.get(0).getName().equals("value")
|| memberValuePairs.get(0).getName().equals("")) {
// They specified only a single member-value pair, and it is
// the default anyway, so we need not do anything except
// update the value
final Expression toUse = JavaParserUtils
.importExpressionIfRequired(compilationUnitServices
.getEnclosingTypeName(),
compilationUnitServices.getImports(),
memberValuePairs.get(0).getValue());
smae.setMemberValue(toUse);
return;
}
// There is > 1 expression, or they have provided some sort of
// non-default value, so it's time to upgrade the expression
// (whilst retaining any potentially existing expression values)
final Expression existingValue = smae.getMemberValue();
annotationExpression = new NormalAnnotationExpr(smae.getName(),
new ArrayList<MemberValuePair>());
((NormalAnnotationExpr) annotationExpression).getPairs().add(
new MemberValuePair("value", existingValue));
}
Validate.isInstanceOf(
NormalAnnotationExpr.class,
annotationExpression,
"Attempting to add >1 annotation member-value pair requires an existing normal annotation expression");
final List<MemberValuePair> annotationPairs = ((NormalAnnotationExpr) annotationExpression)
.getPairs();
annotationPairs.clear();
for (final MemberValuePair pair : memberValuePairs) {
final Expression toUse = JavaParserUtils
.importExpressionIfRequired(
compilationUnitServices.getEnclosingTypeName(),
compilationUnitServices.getImports(),
pair.getValue());
pair.setValue(toUse);
annotationPairs.add(pair);
}
}
}
@SuppressWarnings("unchecked")
private static MemberValuePair convert(
final AnnotationAttributeValue<?> value) {
if (value instanceof NestedAnnotationAttributeValue) {
final NestedAnnotationAttributeValue castValue = (NestedAnnotationAttributeValue) value;
AnnotationExpr annotationExpr;
final AnnotationMetadata nestedAnnotation = castValue.getValue();
if (castValue.getValue().getAttributeNames().size() == 0) {
annotationExpr = new MarkerAnnotationExpr(
JavaParserUtils.getNameExpr(nestedAnnotation
.getAnnotationType()
.getFullyQualifiedTypeName()));
}
else if (castValue.getValue().getAttributeNames().size() == 1) {
annotationExpr = new SingleMemberAnnotationExpr(
JavaParserUtils.getNameExpr(nestedAnnotation
.getAnnotationType()
.getFullyQualifiedTypeName()), convert(
nestedAnnotation.getAttribute(nestedAnnotation
.getAttributeNames().get(0)))
.getValue());
}
else {
final List<MemberValuePair> memberValuePairs = new ArrayList<MemberValuePair>();
for (final JavaSymbolName attributeName : nestedAnnotation
.getAttributeNames()) {
memberValuePairs.add(convert(nestedAnnotation
.getAttribute(attributeName)));
}
annotationExpr = new NormalAnnotationExpr(
JavaParserUtils.getNameExpr(nestedAnnotation
.getAnnotationType()
.getFullyQualifiedTypeName()), memberValuePairs);
}
// Rely on the nested instance to know its member value pairs
return new MemberValuePair(value.getName().getSymbolName(),
annotationExpr);
}
if (value instanceof BooleanAttributeValue) {
final boolean castValue = ((BooleanAttributeValue) value)
.getValue();
final BooleanLiteralExpr convertedValue = new BooleanLiteralExpr(
castValue);
return new MemberValuePair(value.getName().getSymbolName(),
convertedValue);
}
if (value instanceof CharAttributeValue) {
final char castValue = ((CharAttributeValue) value).getValue();
final CharLiteralExpr convertedValue = new CharLiteralExpr(
new String(new char[] { castValue }));
return new MemberValuePair(value.getName().getSymbolName(),
convertedValue);
}
if (value instanceof LongAttributeValue) {
final Long castValue = ((LongAttributeValue) value).getValue();
final LongLiteralExpr convertedValue = new LongLiteralExpr(
castValue.toString() + "L");
return new MemberValuePair(value.getName().getSymbolName(),
convertedValue);
}
if (value instanceof IntegerAttributeValue) {
final Integer castValue = ((IntegerAttributeValue) value)
.getValue();
final IntegerLiteralExpr convertedValue = new IntegerLiteralExpr(
castValue.toString());
return new MemberValuePair(value.getName().getSymbolName(),
convertedValue);
}
if (value instanceof DoubleAttributeValue) {
final DoubleAttributeValue doubleAttributeValue = (DoubleAttributeValue) value;
final Double castValue = doubleAttributeValue.getValue();
DoubleLiteralExpr convertedValue;
if (doubleAttributeValue.isFloatingPrecisionOnly()) {
convertedValue = new DoubleLiteralExpr(castValue.toString()
+ "F");
}
else {
convertedValue = new DoubleLiteralExpr(castValue.toString()
+ "D");
}
return new MemberValuePair(value.getName().getSymbolName(),
convertedValue);
}
if (value instanceof StringAttributeValue) {
final String castValue = ((StringAttributeValue) value).getValue();
final StringLiteralExpr convertedValue = new StringLiteralExpr(
castValue.toString());
return new MemberValuePair(value.getName().getSymbolName(),
convertedValue);
}
if (value instanceof EnumAttributeValue) {
final EnumDetails castValue = ((EnumAttributeValue) value)
.getValue();
// This isn't as elegant as it could be (ie loss of type
// parameters), but it will do for now
final FieldAccessExpr convertedValue = new FieldAccessExpr(
JavaParserUtils.getNameExpr(castValue.getType()
.getFullyQualifiedTypeName()), castValue.getField()
.getSymbolName());
return new MemberValuePair(value.getName().getSymbolName(),
convertedValue);
}
if (value instanceof ClassAttributeValue) {
final JavaType castValue = ((ClassAttributeValue) value).getValue();
// This doesn't preserve type parameters
final NameExpr nameExpr = JavaParserUtils.getNameExpr(castValue
.getFullyQualifiedTypeName());
final ClassExpr convertedValue = new ClassExpr(
JavaParserUtils.getReferenceType(nameExpr));
return new MemberValuePair(value.getName().getSymbolName(),
convertedValue);
}
if (value instanceof ArrayAttributeValue) {
final ArrayAttributeValue<AnnotationAttributeValue<?>> castValue = (ArrayAttributeValue<AnnotationAttributeValue<?>>) value;
final List<Expression> arrayElements = new ArrayList<Expression>();
for (final AnnotationAttributeValue<?> v : castValue.getValue()) {
final MemberValuePair converted = convert(v);
if (converted != null) {
arrayElements.add(converted.getValue());
}
}
return new MemberValuePair(value.getName().getSymbolName(),
new ArrayInitializerExpr(arrayElements));
}
throw new UnsupportedOperationException("Unsupported attribute value '"
+ value.getName() + "' of type '" + value.getClass().getName()
+ "'");
}
public static JavaParserAnnotationMetadataBuilder getInstance(
final AnnotationExpr annotationExpr,
final CompilationUnitServices compilationUnitServices) {
return new JavaParserAnnotationMetadataBuilder(annotationExpr,
compilationUnitServices);
}
private final JavaType annotationType;
private final List<AnnotationAttributeValue<?>> attributeValues;
private final CommentStructure commentStructure;
/**
* Factory method
*
* @param annotationExpr
* @param compilationUnitServices
* @return a non-<code>null</code> instance
* @since 1.2.0
*/
private JavaParserAnnotationMetadataBuilder(
final AnnotationExpr annotationExpr,
final CompilationUnitServices compilationUnitServices) {
Validate.notNull(annotationExpr, "Annotation expression required");
Validate.notNull(compilationUnitServices,
"Compilation unit services required");
// Obtain the annotation type name from the assorted types of
// annotations we might have received (ie marker annotations, single
// member annotations, normal annotations etc)
final NameExpr nameToFind = JavaParserUtils.getNameExpr(annotationExpr);
// Compute the actual annotation type, having regard to the compilation
// unit package and imports
annotationType = JavaParserUtils.getJavaType(compilationUnitServices,
nameToFind, null);
// Generate some member-value pairs for subsequent parsing
List<MemberValuePair> annotationPairs = new ArrayList<MemberValuePair>();
if (annotationExpr instanceof MarkerAnnotationExpr) {
// A marker annotation has no values, so we can have no pairs to add
}
else if (annotationExpr instanceof SingleMemberAnnotationExpr) {
final SingleMemberAnnotationExpr a = (SingleMemberAnnotationExpr) annotationExpr;
// Add the "value=" member-value pair.
if (a.getMemberValue() != null) {
annotationPairs.add(new MemberValuePair("value", a
.getMemberValue()));
}
}
else if (annotationExpr instanceof NormalAnnotationExpr) {
final NormalAnnotationExpr a = (NormalAnnotationExpr) annotationExpr;
// Must iterate over the expressions
if (a.getPairs() != null) {
annotationPairs = a.getPairs();
}
}
// Iterate over the annotation attributes, creating our parsed
// attributes map
final List<AnnotationAttributeValue<?>> attributeValues = new ArrayList<AnnotationAttributeValue<?>>();
for (final MemberValuePair p : annotationPairs) {
final JavaSymbolName annotationName = new JavaSymbolName(
p.getName());
final AnnotationAttributeValue<?> value = convert(annotationName,
p.getValue(), compilationUnitServices);
attributeValues.add(value);
}
this.attributeValues = attributeValues;
commentStructure = new CommentStructure();
JavaParserCommentMetadataBuilder.updateCommentsToRoo(commentStructure,
annotationExpr);
}
@Override
public AnnotationMetadata build() {
final AnnotationMetadataBuilder annotationMetadataBuilder = new AnnotationMetadataBuilder(
annotationType, attributeValues);
final AnnotationMetadata md = annotationMetadataBuilder.build();
md.setCommentStructure(commentStructure);
return md;
}
private AnnotationAttributeValue<?> convert(JavaSymbolName annotationName,
final Expression expression,
final CompilationUnitServices compilationUnitServices) {
if (annotationName == null) {
annotationName = new JavaSymbolName("__ARRAY_ELEMENT__");
}
if (expression instanceof AnnotationExpr) {
final AnnotationExpr annotationExpr = (AnnotationExpr) expression;
final AnnotationMetadata value = getInstance(annotationExpr,
compilationUnitServices).build();
return new NestedAnnotationAttributeValue(annotationName, value);
}
if (expression instanceof BooleanLiteralExpr) {
final boolean value = ((BooleanLiteralExpr) expression).getValue();
return new BooleanAttributeValue(annotationName, value);
}
if (expression instanceof CharLiteralExpr) {
final String value = ((CharLiteralExpr) expression).getValue();
Validate.isTrue(
value.length() == 1,
"Expected a char expression, but instead received '%s' for attribute '%s'",
value, annotationName);
final char c = value.charAt(0);
return new CharAttributeValue(annotationName, c);
}
if (expression instanceof LongLiteralExpr) {
String value = ((LongLiteralExpr) expression).getValue();
Validate.isTrue(
value.toUpperCase().endsWith("L"),
"Expected long literal expression '%s' to end in 'l' or 'L'",
value);
value = value.substring(0, value.length() - 1);
final long l = new Long(value);
return new LongAttributeValue(annotationName, l);
}
if (expression instanceof IntegerLiteralExpr) {
final String value = ((IntegerLiteralExpr) expression).getValue();
final int i = new Integer(value);
return new IntegerAttributeValue(annotationName, i);
}
if (expression instanceof DoubleLiteralExpr) {
String value = ((DoubleLiteralExpr) expression).getValue();
boolean floatingPrecisionOnly = false;
if (value.toUpperCase().endsWith("F")) {
value = value.substring(0, value.length() - 1);
floatingPrecisionOnly = true;
}
if (value.toUpperCase().endsWith("D")) {
value = value.substring(0, value.length() - 1);
}
final double d = new Double(value);
return new DoubleAttributeValue(annotationName, d,
floatingPrecisionOnly);
}
if (expression instanceof BinaryExpr) {
String result = "";
BinaryExpr current = (BinaryExpr) expression;
while (current != null) {
String right = "";
if (current.getRight() instanceof StringLiteralExpr) {
right = ((StringLiteralExpr) current.getRight()).getValue();
}
else if (current.getRight() instanceof NameExpr) {
right = ((NameExpr) current.getRight()).getName();
}
result = right + result;
if (current.getLeft() instanceof StringLiteralExpr) {
final String left = ((StringLiteralExpr) current.getLeft())
.getValue();
result = left + result;
}
if (current.getLeft() instanceof BinaryExpr) {
current = (BinaryExpr) current.getLeft();
}
else {
current = null;
}
}
return new StringAttributeValue(annotationName, result);
}
if (expression instanceof StringLiteralExpr) {
final String value = ((StringLiteralExpr) expression).getValue();
return new StringAttributeValue(annotationName, value);
}
if (expression instanceof FieldAccessExpr) {
final FieldAccessExpr field = (FieldAccessExpr) expression;
final String fieldName = field.getField();
// Determine the type
final Expression scope = field.getScope();
NameExpr nameToFind = null;
if (scope instanceof FieldAccessExpr) {
final FieldAccessExpr fScope = (FieldAccessExpr) scope;
nameToFind = JavaParserUtils.getNameExpr(fScope.toString());
}
else if (scope instanceof NameExpr) {
nameToFind = (NameExpr) scope;
}
else {
throw new UnsupportedOperationException(
"A FieldAccessExpr for '"
+ field.getScope()
+ "' should return a NameExpr or FieldAccessExpr (was "
+ field.getScope().getClass().getName() + ")");
}
final JavaType fieldType = JavaParserUtils.getJavaType(
compilationUnitServices, nameToFind, null);
final EnumDetails enumDetails = new EnumDetails(fieldType,
new JavaSymbolName(fieldName));
return new EnumAttributeValue(annotationName, enumDetails);
}
if (expression instanceof NameExpr) {
final NameExpr field = (NameExpr) expression;
final String name = field.getName();
// As we have no way of finding out the real type
final JavaType fieldType = new JavaType("unknown.Object");
final EnumDetails enumDetails = new EnumDetails(fieldType,
new JavaSymbolName(name));
return new EnumAttributeValue(annotationName, enumDetails);
}
if (expression instanceof ClassExpr) {
final ClassExpr clazz = (ClassExpr) expression;
final Type nameToFind = clazz.getType();
final JavaType javaType = JavaParserUtils.getJavaType(
compilationUnitServices, nameToFind, null);
return new ClassAttributeValue(annotationName, javaType);
}
if (expression instanceof ArrayInitializerExpr) {
final ArrayInitializerExpr castExp = (ArrayInitializerExpr) expression;
final List<AnnotationAttributeValue<?>> arrayElements = new ArrayList<AnnotationAttributeValue<?>>();
for (final Expression e : castExp.getValues()) {
arrayElements.add(convert(null, e, compilationUnitServices));
}
return new ArrayAttributeValue<AnnotationAttributeValue<?>>(
annotationName, arrayElements);
}
if (expression instanceof UnaryExpr) {
final UnaryExpr castExp = (UnaryExpr) expression;
if (castExp.getOperator() == Operator.negative) {
String value = castExp.toString();
value = value.toUpperCase().endsWith("L") ? value.substring(0,
value.length() - 1) : value;
final long l = new Long(value);
return new LongAttributeValue(annotationName, l);
}
else {
throw new UnsupportedOperationException(
"Only negative operator in UnaryExpr is supported");
}
}
throw new UnsupportedOperationException(
"Unable to parse annotation attribute '" + annotationName
+ "' due to unsupported annotation expression '"
+ expression.getClass().getName() + "'");
}
}