package org.springframework.roo.classpath.antlrjavaparser.details; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.lang3.Validate; import org.springframework.roo.classpath.PhysicalTypeCategory; import org.springframework.roo.classpath.PhysicalTypeIdentifier; import org.springframework.roo.classpath.PhysicalTypeMetadata; import org.springframework.roo.classpath.TypeLocationService; import org.springframework.roo.classpath.antlrjavaparser.CompilationUnitServices; import org.springframework.roo.classpath.antlrjavaparser.JavaParserUtils; import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails; import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetailsBuilder; import org.springframework.roo.classpath.details.ConstructorMetadata; import org.springframework.roo.classpath.details.FieldMetadata; import org.springframework.roo.classpath.details.ImportMetadataBuilder; import org.springframework.roo.classpath.details.MethodMetadata; import org.springframework.roo.classpath.details.annotations.AnnotationMetadata; import org.springframework.roo.classpath.details.comments.CommentStructure; import org.springframework.roo.metadata.MetadataService; import org.springframework.roo.model.Builder; import org.springframework.roo.model.JavaPackage; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import com.github.antlrjavaparser.api.CompilationUnit; import com.github.antlrjavaparser.api.ImportDeclaration; import com.github.antlrjavaparser.api.body.BodyDeclaration; import com.github.antlrjavaparser.api.body.ClassOrInterfaceDeclaration; import com.github.antlrjavaparser.api.body.ConstructorDeclaration; import com.github.antlrjavaparser.api.body.EnumConstantDeclaration; import com.github.antlrjavaparser.api.body.EnumDeclaration; import com.github.antlrjavaparser.api.body.FieldDeclaration; import com.github.antlrjavaparser.api.body.MethodDeclaration; import com.github.antlrjavaparser.api.body.TypeDeclaration; import com.github.antlrjavaparser.api.body.VariableDeclarator; import com.github.antlrjavaparser.api.expr.AnnotationExpr; import com.github.antlrjavaparser.api.expr.QualifiedNameExpr; import com.github.antlrjavaparser.api.type.ClassOrInterfaceType; public class JavaParserClassOrInterfaceTypeDetailsBuilder implements Builder<ClassOrInterfaceTypeDetails> { private static final Logger LOGGER = Logger .getLogger(JavaParserClassOrInterfaceTypeDetailsBuilder.class.getName()); static final String UNSUPPORTED_MESSAGE_PREFIX = "Only enum, class and interface files are supported"; /** * Factory method for this builder class * * @param compilationUnit * @param enclosingCompilationUnitServices * @param typeDeclaration * @param declaredByMetadataId * @param typeName * @param metadataService * @param typeLocationService * @return a non-<code>null</code> builder */ public static JavaParserClassOrInterfaceTypeDetailsBuilder getInstance( final CompilationUnit compilationUnit, final CompilationUnitServices enclosingCompilationUnitServices, final TypeDeclaration typeDeclaration, final String declaredByMetadataId, final JavaType typeName, final MetadataService metadataService, final TypeLocationService typeLocationService) { return new JavaParserClassOrInterfaceTypeDetailsBuilder(compilationUnit, enclosingCompilationUnitServices, typeDeclaration, declaredByMetadataId, typeName, metadataService, typeLocationService); } private final CompilationUnit compilationUnit; private JavaPackage compilationUnitPackage; private final CompilationUnitServices compilationUnitServices; private final String declaredByMetadataId; private List<ImportDeclaration> imports = new ArrayList<ImportDeclaration>(); private final List<TypeDeclaration> innerTypes = new ArrayList<TypeDeclaration>(); private final MetadataService metadataService; private JavaType name; private PhysicalTypeCategory physicalTypeCategory; private final TypeDeclaration typeDeclaration; private final TypeLocationService typeLocationService; /** * Constructor * * @param compilationUnit * @param enclosingCompilationUnitServices * @param typeDeclaration * @param declaredByMetadataId * @param typeName * @param metadataService * @param typeLocationService */ private JavaParserClassOrInterfaceTypeDetailsBuilder(final CompilationUnit compilationUnit, final CompilationUnitServices enclosingCompilationUnitServices, final TypeDeclaration typeDeclaration, final String declaredByMetadataId, final JavaType typeName, final MetadataService metadataService, final TypeLocationService typeLocationService) { // Check Validate.notNull(compilationUnit, "Compilation unit required"); Validate.notBlank(declaredByMetadataId, "Declared by metadata ID required"); Validate.notNull(typeDeclaration, "Unable to locate the class or interface declaration"); Validate.notNull(typeName, "Name required"); // Assign this.compilationUnit = compilationUnit; compilationUnitServices = enclosingCompilationUnitServices == null ? getDefaultCompilationUnitServices() : enclosingCompilationUnitServices; this.declaredByMetadataId = declaredByMetadataId; this.metadataService = metadataService; name = typeName; this.typeDeclaration = typeDeclaration; this.typeLocationService = typeLocationService; } @Override public ClassOrInterfaceTypeDetails build() { Validate.notEmpty(compilationUnit.getTypes(), "No types in compilation unit, so unable to continue parsing"); ClassOrInterfaceDeclaration clazz = null; EnumDeclaration enumClazz = null; final StringBuilder sb = new StringBuilder(compilationUnit.getPackage().getName().toString()); if (name.getEnclosingType() != null) { sb.append(".").append(name.getEnclosingType().getSimpleTypeName()); } compilationUnitPackage = new JavaPackage(sb.toString()); // Determine the type name, adding type parameters if possible final JavaType newName = JavaParserUtils.getJavaType(compilationUnitServices, typeDeclaration); // Revert back to the original type name (thus avoiding unnecessary // inferences about java.lang types; see ROO-244) name = new JavaType(newName.getFullyQualifiedTypeName(), newName.getEnclosingType(), newName.getArray(), newName.getDataType(), newName.getArgName(), newName.getParameters(), name.getModule()); final ClassOrInterfaceTypeDetailsBuilder cidBuilder = new ClassOrInterfaceTypeDetailsBuilder(declaredByMetadataId); physicalTypeCategory = PhysicalTypeCategory.CLASS; if (typeDeclaration instanceof ClassOrInterfaceDeclaration) { clazz = (ClassOrInterfaceDeclaration) typeDeclaration; if (clazz.isInterface()) { physicalTypeCategory = PhysicalTypeCategory.INTERFACE; } } else if (typeDeclaration instanceof EnumDeclaration) { enumClazz = (EnumDeclaration) typeDeclaration; physicalTypeCategory = PhysicalTypeCategory.ENUMERATION; } Validate.notNull(physicalTypeCategory, "%s (%s for %s)", UNSUPPORTED_MESSAGE_PREFIX, typeDeclaration.getClass().getSimpleName(), name); cidBuilder.setName(name); cidBuilder.setPhysicalTypeCategory(physicalTypeCategory); imports = compilationUnit.getImports(); if (imports == null) { imports = new ArrayList<ImportDeclaration>(); compilationUnit.setImports(imports); } // Verify the package declaration appears to be correct if (compilationUnitPackage.equals(name.getPackage()) != true) { String warningStr = "[Warning] Compilation unit package '" + compilationUnitPackage + "' unexpected for type '" + name.getPackage() + "', it may be a nested class."; LOGGER.log(Level.WARNING, warningStr); } for (final ImportDeclaration importDeclaration : imports) { if (importDeclaration.getName() instanceof QualifiedNameExpr) { final String qualifier = ((QualifiedNameExpr) importDeclaration.getName()).getQualifier().toString(); final String simpleName = importDeclaration.getName().getName(); final String fullName = qualifier + "." + simpleName; // We want to calculate these... final JavaType type = new JavaType(fullName); final JavaPackage typePackage = importDeclaration.isAsterisk() ? new JavaPackage(fullName) : type.getPackage(); // Process any comments for the import final CommentStructure commentStructure = new CommentStructure(); JavaParserCommentMetadataBuilder.updateCommentsToRoo(commentStructure, importDeclaration); final ImportMetadataBuilder newImport = new ImportMetadataBuilder(declaredByMetadataId, 0, typePackage, type, importDeclaration.isStatic(), importDeclaration.isAsterisk()); newImport.setCommentStructure(commentStructure); cidBuilder.add(newImport.build()); } } // Convert Java Parser modifier into JDK modifier cidBuilder.setModifier(JavaParserUtils.getJdkModifier(typeDeclaration.getModifiers())); // Type parameters final Set<JavaSymbolName> typeParameterNames = new HashSet<JavaSymbolName>(); for (final JavaType param : name.getParameters()) { final JavaSymbolName arg = param.getArgName(); // Fortunately type names can only appear at the top-level if (arg != null && !JavaType.WILDCARD_NEITHER_ARG.equals(arg) && !JavaType.WILDCARD_EXTENDS_ARG.equals(arg) && !JavaType.WILDCARD_SUPER_ARG.equals(arg)) { typeParameterNames.add(arg); } } List<ClassOrInterfaceType> implementsList; List<AnnotationExpr> annotationsList = null; List<BodyDeclaration> members = null; if (clazz != null) { final List<ClassOrInterfaceType> extendsList = clazz.getExtends(); if (extendsList != null) { for (final ClassOrInterfaceType candidate : extendsList) { final JavaType javaType = JavaParserUtils .getJavaTypeNow(compilationUnitServices, candidate, typeParameterNames); cidBuilder.addExtendsTypes(javaType); } } final List<JavaType> extendsTypes = cidBuilder.getExtendsTypes(); // Obtain the superclass, if this is a class and one is available if (physicalTypeCategory == PhysicalTypeCategory.CLASS && extendsTypes.size() == 1) { final JavaType superclass = extendsTypes.get(0); final String superclassId = typeLocationService.getPhysicalTypeIdentifier(superclass); PhysicalTypeMetadata superPtm = null; if (superclassId != null) { superPtm = (PhysicalTypeMetadata) metadataService.get(superclassId); } if (superPtm != null && superPtm.getMemberHoldingTypeDetails() != null) { cidBuilder.setSuperclass(superPtm.getMemberHoldingTypeDetails()); } } implementsList = clazz.getImplements(); if (implementsList != null) { for (final ClassOrInterfaceType candidate : implementsList) { final JavaType javaType = JavaParserUtils .getJavaTypeNow(compilationUnitServices, candidate, typeParameterNames); cidBuilder.addImplementsType(javaType); } } annotationsList = typeDeclaration.getAnnotations(); members = clazz.getMembers(); } if (enumClazz != null) { final List<EnumConstantDeclaration> constants = enumClazz.getEntries(); if (constants != null) { for (final EnumConstantDeclaration enumConstants : constants) { cidBuilder.addEnumConstant(new JavaSymbolName(enumConstants.getName())); } } implementsList = enumClazz.getImplements(); annotationsList = enumClazz.getAnnotations(); members = enumClazz.getMembers(); } if (annotationsList != null) { for (final AnnotationExpr candidate : annotationsList) { final AnnotationMetadata md = JavaParserAnnotationMetadataBuilder.getInstance(candidate, compilationUnitServices) .build(); final CommentStructure commentStructure = new CommentStructure(); JavaParserCommentMetadataBuilder.updateCommentsToRoo(commentStructure, candidate); md.setCommentStructure(commentStructure); cidBuilder.addAnnotation(md); } } if (members != null) { // Now we've finished declaring the type, we should introspect for // any inner types that can thus be referred to in other body // members // We defer this until now because it's illegal to refer to an inner // type in the signature of the enclosing type for (final BodyDeclaration bodyDeclaration : members) { if (bodyDeclaration instanceof TypeDeclaration) { // Found a type innerTypes.add((TypeDeclaration) bodyDeclaration); } } for (final BodyDeclaration member : members) { if (member instanceof FieldDeclaration) { final FieldDeclaration castMember = (FieldDeclaration) member; for (final VariableDeclarator var : castMember.getVariables()) { final FieldMetadata field = JavaParserFieldMetadataBuilder.getInstance(declaredByMetadataId, castMember, var, compilationUnitServices, typeParameterNames).build(); final CommentStructure commentStructure = new CommentStructure(); JavaParserCommentMetadataBuilder.updateCommentsToRoo(commentStructure, member); field.setCommentStructure(commentStructure); cidBuilder.addField(field); } } if (member instanceof MethodDeclaration) { final MethodDeclaration castMember = (MethodDeclaration) member; final MethodMetadata method = JavaParserMethodMetadataBuilder.getInstance(declaredByMetadataId, castMember, compilationUnitServices, typeParameterNames).build(); final CommentStructure commentStructure = new CommentStructure(); JavaParserCommentMetadataBuilder.updateCommentsToRoo(commentStructure, member); method.setCommentStructure(commentStructure); cidBuilder.addMethod(method); } if (member instanceof ConstructorDeclaration) { final ConstructorDeclaration castMember = (ConstructorDeclaration) member; final ConstructorMetadata constructor = JavaParserConstructorMetadataBuilder.getInstance(declaredByMetadataId, castMember, compilationUnitServices, typeParameterNames).build(); final CommentStructure commentStructure = new CommentStructure(); JavaParserCommentMetadataBuilder.updateCommentsToRoo(commentStructure, member); constructor.setCommentStructure(commentStructure); cidBuilder.addConstructor(constructor); } if (member instanceof TypeDeclaration) { final TypeDeclaration castMember = (TypeDeclaration) member; final JavaType innerType = new JavaType(castMember.getName(), name); final String innerTypeMetadataId = PhysicalTypeIdentifier.createIdentifier(innerType, PhysicalTypeIdentifier.getPath(declaredByMetadataId)); final ClassOrInterfaceTypeDetails cid = new JavaParserClassOrInterfaceTypeDetailsBuilder(compilationUnit, compilationUnitServices, castMember, innerTypeMetadataId, innerType, metadataService, typeLocationService).build(); cidBuilder.addInnerType(cid); } } } return cidBuilder.build(); } private CompilationUnitServices getDefaultCompilationUnitServices() { return new CompilationUnitServices() { @Override public JavaPackage getCompilationUnitPackage() { return compilationUnitPackage; } @Override public JavaType getEnclosingTypeName() { return name; } @Override public List<ImportDeclaration> getImports() { return imports; } @Override public List<TypeDeclaration> getInnerTypes() { return innerTypes; } @Override public PhysicalTypeCategory getPhysicalTypeCategory() { return physicalTypeCategory; } }; } }