package org.springframework.roo.classpath.antlrjavaparser.details; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.springframework.roo.classpath.PhysicalTypeIdentifier; import org.springframework.roo.classpath.antlrjavaparser.CompilationUnitServices; import org.springframework.roo.classpath.antlrjavaparser.JavaParserUtils; import org.springframework.roo.classpath.details.ConstructorMetadata; import org.springframework.roo.classpath.details.ConstructorMetadataBuilder; import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType; import org.springframework.roo.classpath.details.annotations.AnnotationMetadata; import org.springframework.roo.classpath.details.comments.AbstractComment; import org.springframework.roo.classpath.details.comments.CommentStructure; import org.springframework.roo.classpath.details.comments.CommentStructure.CommentLocation; import org.springframework.roo.classpath.details.comments.JavadocComment; import org.springframework.roo.classpath.itd.InvocableMemberBodyBuilder; import org.springframework.roo.model.Builder; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import com.github.antlrjavaparser.JavaParser; import com.github.antlrjavaparser.ParseException; import com.github.antlrjavaparser.api.CompilationUnit; import com.github.antlrjavaparser.api.TypeParameter; import com.github.antlrjavaparser.api.body.BodyDeclaration; import com.github.antlrjavaparser.api.body.ConstructorDeclaration; import com.github.antlrjavaparser.api.body.Parameter; import com.github.antlrjavaparser.api.body.TypeDeclaration; import com.github.antlrjavaparser.api.body.VariableDeclaratorId; import com.github.antlrjavaparser.api.expr.AnnotationExpr; import com.github.antlrjavaparser.api.stmt.BlockStmt; import com.github.antlrjavaparser.api.type.ClassOrInterfaceType; import com.github.antlrjavaparser.api.type.Type; /** * Java Parser implementation of {@link ConstructorMetadata}. * * @author Ben Alex * @author Juan Carlos GarcĂ­a * @author Sergio Clares * @since 1.0 */ public class JavaParserConstructorMetadataBuilder implements Builder<ConstructorMetadata> { // TODO: Should parse the throws types from JavaParser source public static void addConstructor(final CompilationUnitServices compilationUnitServices, final List<BodyDeclaration> members, final ConstructorMetadata constructor, final Set<JavaSymbolName> typeParameters) { Validate.notNull(compilationUnitServices, "Compilation unit services required"); Validate.notNull(members, "Members required"); Validate.notNull(constructor, "Method required"); // Start with the basic constructor final ConstructorDeclaration d = new ConstructorDeclaration(); d.setModifiers(JavaParserUtils.getJavaParserModifier(constructor.getModifier())); d.setName(PhysicalTypeIdentifier.getJavaType(constructor.getDeclaredByMetadataId()) .getSimpleTypeName()); // Add any constructor-level annotations (not parameter annotations) final List<AnnotationExpr> annotations = new ArrayList<AnnotationExpr>(); d.setAnnotations(annotations); for (final AnnotationMetadata annotation : constructor.getAnnotations()) { JavaParserAnnotationMetadataBuilder.addAnnotationToList(compilationUnitServices, annotations, annotation); } // Add any constructor parameters, including their individual // annotations and type parameters final List<Parameter> parameters = new ArrayList<Parameter>(); d.setParameters(parameters); int index = -1; for (final AnnotatedJavaType constructorParameter : constructor.getParameterTypes()) { index++; // Add the parameter annotations applicable for this parameter type final List<AnnotationExpr> parameterAnnotations = new ArrayList<AnnotationExpr>(); for (final AnnotationMetadata parameterAnnotation : constructorParameter.getAnnotations()) { JavaParserAnnotationMetadataBuilder.addAnnotationToList(compilationUnitServices, parameterAnnotations, parameterAnnotation); } // Compute the parameter name final String parameterName = constructor.getParameterNames().get(index).getSymbolName(); // Compute the parameter type Type parameterType = null; if (constructorParameter.getJavaType().isPrimitive()) { parameterType = JavaParserUtils.getType(constructorParameter.getJavaType()); } else { final Type finalType = JavaParserUtils.getResolvedName(constructorParameter.getJavaType(), constructorParameter.getJavaType(), compilationUnitServices); final ClassOrInterfaceType cit = JavaParserUtils.getClassOrInterfaceType(finalType); // Add any type arguments presented for the return type if (constructorParameter.getJavaType().getParameters().size() > 0) { final List<Type> typeArgs = new ArrayList<Type>(); cit.setTypeArgs(typeArgs); for (final JavaType parameter : constructorParameter.getJavaType().getParameters()) { // NameExpr importedParameterType = // JavaParserUtils.importTypeIfRequired(compilationUnitServices.getEnclosingTypeName(), // compilationUnitServices.getImports(), parameter); // typeArgs.add(JavaParserUtils.getReferenceType(importedParameterType)); typeArgs.add(JavaParserUtils.importParametersForType( compilationUnitServices.getEnclosingTypeName(), compilationUnitServices.getImports(), parameter)); } } parameterType = finalType; } // Create a Java Parser constructor parameter and add it to the list // of parameters final Parameter p = new Parameter(parameterType, new VariableDeclaratorId(parameterName)); p.setAnnotations(parameterAnnotations); parameters.add(p); } // Set the body if (constructor.getBody() == null || constructor.getBody().length() == 0) { d.setBlock(new BlockStmt()); } else { // There is a body. // We need to make a fake constructor that we can have JavaParser // parse. // Easiest way to do that is to build a simple source class // containing the required method and re-parse it. final StringBuilder sb = new StringBuilder(); sb.append("class TemporaryClass {\n"); sb.append(" TemporaryClass() {\n"); sb.append(constructor.getBody()); sb.append("\n"); sb.append(" }\n"); sb.append("}\n"); final ByteArrayInputStream bais = new ByteArrayInputStream(sb.toString().getBytes()); CompilationUnit ci; try { ci = JavaParser.parse(bais); } catch (final IOException e) { throw new IllegalStateException("Illegal state: Unable to parse input stream", e); } catch (final ParseException pe) { throw new IllegalStateException("Illegal state: JavaParser did not parse correctly", pe); } final List<TypeDeclaration> types = ci.getTypes(); if (types == null || types.size() != 1) { throw new IllegalArgumentException("Method body invalid"); } final TypeDeclaration td = types.get(0); final List<BodyDeclaration> bodyDeclarations = td.getMembers(); if (bodyDeclarations == null || bodyDeclarations.size() != 1) { throw new IllegalStateException( "Illegal state: JavaParser did not return body declarations correctly"); } final BodyDeclaration bd = bodyDeclarations.get(0); if (!(bd instanceof ConstructorDeclaration)) { throw new IllegalStateException( "Illegal state: JavaParser did not return a method declaration correctly"); } final ConstructorDeclaration cd = (ConstructorDeclaration) bd; d.setBlock(cd.getBlock()); } // Locate where to add this constructor; also verify if this method // already exists for (final BodyDeclaration bd : members) { if (bd instanceof ConstructorDeclaration) { // Next constructor should appear after this current constructor final ConstructorDeclaration cd = (ConstructorDeclaration) bd; if (cd.getParameters().size() == d.getParameters().size()) { // Possible match, we need to consider parameter types as // well now final ConstructorMetadata constructorMetadata = new JavaParserConstructorMetadataBuilder(constructor.getDeclaredByMetadataId(), cd, compilationUnitServices, typeParameters).build(); boolean matchesFully = true; for (final AnnotatedJavaType existingParameter : constructorMetadata.getParameterTypes()) { if (!existingParameter.getJavaType().equals(constructor.getParameterTypes().get(index))) { matchesFully = false; break; } } if (matchesFully) { throw new IllegalStateException("Constructor '" + constructor.getParameterNames() + "' already exists with identical parameters"); } } } } // ROO-3834: Append Javadoc CommentStructure commentStructure = constructor.getCommentStructure(); if (constructor.getCommentStructure() != null) { // if the constructor has annotations, add JavaDoc comments to the first // annotation if (annotations != null && annotations.size() > 0) { AnnotationExpr firstAnnotation = annotations.get(0); JavaParserCommentMetadataBuilder.updateCommentsToJavaParser(firstAnnotation, commentStructure); // Otherwise, add comments to the field declaration line } else { JavaParserCommentMetadataBuilder.updateCommentsToJavaParser(d, commentStructure); } } else { // ROO-3834: Append default Javadoc if not exists a comment structure, // including constructor params CommentStructure defaultCommentStructure = new CommentStructure(); List<String> parameterNames = new ArrayList<String>(); for (JavaSymbolName name : constructor.getParameterNames()) { parameterNames.add(name.getSymbolName()); } JavadocComment javadocComment = new JavadocComment("TODO Auto-generated constructor documentation", parameterNames, null, null); defaultCommentStructure.addComment(javadocComment, CommentLocation.BEGINNING); constructor.setCommentStructure(defaultCommentStructure); // if the constructor has annotations, add JavaDoc comments to the first // annotation if (annotations != null && annotations.size() > 0) { AnnotationExpr firstAnnotation = annotations.get(0); JavaParserCommentMetadataBuilder.updateCommentsToJavaParser(firstAnnotation, defaultCommentStructure); // Otherwise, add comments to the constructor declaration line } else { JavaParserCommentMetadataBuilder.updateCommentsToJavaParser(d, defaultCommentStructure); } } // Add the constructor to the end of the compilation unit members.add(d); } public static JavaParserConstructorMetadataBuilder getInstance(final String declaredByMetadataId, final ConstructorDeclaration constructorDeclaration, final CompilationUnitServices compilationUnitServices, final Set<JavaSymbolName> typeParameterNames) { return new JavaParserConstructorMetadataBuilder(declaredByMetadataId, constructorDeclaration, compilationUnitServices, typeParameterNames); } private final List<AnnotationMetadata> annotations = new ArrayList<AnnotationMetadata>(); private String body; private final String declaredByMetadataId; private final int modifier; private final List<JavaSymbolName> parameterNames = new ArrayList<JavaSymbolName>(); private final List<AnnotatedJavaType> parameterTypes = new ArrayList<AnnotatedJavaType>(); private final List<JavaType> throwsTypes = new ArrayList<JavaType>(); private JavaParserConstructorMetadataBuilder(final String declaredByMetadataId, final ConstructorDeclaration constructorDeclaration, final CompilationUnitServices compilationUnitServices, Set<JavaSymbolName> typeParameterNames) { Validate.notBlank(declaredByMetadataId, "Declared by metadata ID required"); Validate.notNull(constructorDeclaration, "Constructor declaration is mandatory"); Validate.notNull(compilationUnitServices, "Compilation unit services are required"); // Convert Java Parser modifier into JDK modifier modifier = JavaParserUtils.getJdkModifier(constructorDeclaration.getModifiers()); this.declaredByMetadataId = declaredByMetadataId; if (typeParameterNames == null) { typeParameterNames = new HashSet<JavaSymbolName>(); } // Add method-declared type parameters (if any) to the list of type // parameters final Set<JavaSymbolName> fullTypeParameters = new HashSet<JavaSymbolName>(); fullTypeParameters.addAll(typeParameterNames); final List<TypeParameter> params = constructorDeclaration.getTypeParameters(); if (params != null) { for (final TypeParameter candidate : params) { final JavaSymbolName currentTypeParam = new JavaSymbolName(candidate.getName()); fullTypeParameters.add(currentTypeParam); } } // Get the body body = constructorDeclaration.getBlock().toString(); body = StringUtils.replace(body, "{", "", 1); body = body.substring(0, body.lastIndexOf("}")); // Lookup the parameters and their names if (constructorDeclaration.getParameters() != null) { for (final Parameter p : constructorDeclaration.getParameters()) { final Type pt = p.getType(); final JavaType parameterType = JavaParserUtils.getJavaType(compilationUnitServices, pt, fullTypeParameters); final List<AnnotationExpr> annotationsList = p.getAnnotations(); final List<AnnotationMetadata> annotations = new ArrayList<AnnotationMetadata>(); if (annotationsList != null) { for (final AnnotationExpr candidate : annotationsList) { final JavaParserAnnotationMetadataBuilder md = JavaParserAnnotationMetadataBuilder.getInstance(candidate, compilationUnitServices); annotations.add(md.build()); } } parameterTypes.add(new AnnotatedJavaType(parameterType, annotations)); parameterNames.add(new JavaSymbolName(p.getId().getName())); } } if (constructorDeclaration.getAnnotations() != null) { for (final AnnotationExpr annotation : constructorDeclaration.getAnnotations()) { annotations.add(JavaParserAnnotationMetadataBuilder.getInstance(annotation, compilationUnitServices).build()); } } } @Override public ConstructorMetadata build() { final ConstructorMetadataBuilder constructorBuilder = new ConstructorMetadataBuilder(declaredByMetadataId); constructorBuilder.setAnnotations(annotations); constructorBuilder.setBodyBuilder(InvocableMemberBodyBuilder.getInstance().append(body)); constructorBuilder.setModifier(modifier); constructorBuilder.setParameterNames(parameterNames); constructorBuilder.setParameterTypes(parameterTypes); constructorBuilder.setThrowsTypes(throwsTypes); return constructorBuilder.build(); } }