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 * @author Juan Carlos GarcĂ­a * @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"); // ROO-3678: Add-on which include new annotation should be the responsible to check if annotation // exists, not JavaParser. /*// 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, compilationUnitServices); // 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, CompilationUnitServices compilationUnitServices) { 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().getSimpleTypeName())); } else if (castValue.getValue().getAttributeNames().size() == 1 && (castValue.getValue().getAttributeNames().get(0) == null || "value".equals(castValue .getValue().getAttributeNames().get(0).getSymbolName()))) { annotationExpr = new SingleMemberAnnotationExpr(JavaParserUtils.getNameExpr(nestedAnnotation .getAnnotationType().getSimpleTypeName()), convert( nestedAnnotation.getAttribute(nestedAnnotation.getAttributeNames().get(0)), compilationUnitServices).getValue()); } else { final List<MemberValuePair> memberValuePairs = new ArrayList<MemberValuePair>(); for (final JavaSymbolName attributeName : nestedAnnotation.getAttributeNames()) { memberValuePairs.add(convert(nestedAnnotation.getAttribute(attributeName), compilationUnitServices)); } annotationExpr = new NormalAnnotationExpr(JavaParserUtils.getNameExpr(nestedAnnotation .getAnnotationType().getSimpleTypeName()), memberValuePairs); } // Add imports for nested annotation types JavaParserUtils.importTypeIfRequired(compilationUnitServices.getEnclosingTypeName(), compilationUnitServices.getImports(), nestedAnnotation.getAnnotationType()); // 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.importTypeIfRequired(compilationUnitServices.getEnclosingTypeName(), compilationUnitServices.getImports(), castValue); 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, compilationUnitServices); 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() + "'"); } }