package org.springframework.roo.classpath.antlrjavaparser; import static org.springframework.roo.model.JavaType.OBJECT; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.springframework.roo.classpath.PhysicalTypeCategory; import org.springframework.roo.classpath.TypeLocationService; import org.springframework.roo.classpath.TypeParsingService; import org.springframework.roo.classpath.antlrjavaparser.details.JavaParserAnnotationMetadataBuilder; import org.springframework.roo.classpath.antlrjavaparser.details.JavaParserClassOrInterfaceTypeDetailsBuilder; import org.springframework.roo.classpath.antlrjavaparser.details.JavaParserCommentMetadataBuilder; import org.springframework.roo.classpath.antlrjavaparser.details.JavaParserConstructorMetadataBuilder; import org.springframework.roo.classpath.antlrjavaparser.details.JavaParserFieldMetadataBuilder; import org.springframework.roo.classpath.antlrjavaparser.details.JavaParserMethodMetadataBuilder; import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails; import org.springframework.roo.classpath.details.ConstructorMetadata; import org.springframework.roo.classpath.details.FieldMetadata; import org.springframework.roo.classpath.details.ImportMetadata; 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.classpath.details.comments.CommentStructure.CommentLocation; import org.springframework.roo.classpath.details.comments.JavadocComment; import org.springframework.roo.metadata.MetadataService; import org.springframework.roo.model.JavaPackage; import org.springframework.roo.model.JavaSymbolName; import org.springframework.roo.model.JavaType; import com.github.antlrjavaparser.ASTHelper; import com.github.antlrjavaparser.JavaParser; import com.github.antlrjavaparser.ParseException; import com.github.antlrjavaparser.api.Comment; import com.github.antlrjavaparser.api.CompilationUnit; import com.github.antlrjavaparser.api.ImportDeclaration; import com.github.antlrjavaparser.api.PackageDeclaration; import com.github.antlrjavaparser.api.TypeParameter; import com.github.antlrjavaparser.api.body.BodyDeclaration; import com.github.antlrjavaparser.api.body.ClassOrInterfaceDeclaration; import com.github.antlrjavaparser.api.body.EnumConstantDeclaration; import com.github.antlrjavaparser.api.body.EnumDeclaration; import com.github.antlrjavaparser.api.body.TypeDeclaration; import com.github.antlrjavaparser.api.expr.AnnotationExpr; import com.github.antlrjavaparser.api.expr.NameExpr; import com.github.antlrjavaparser.api.expr.QualifiedNameExpr; import com.github.antlrjavaparser.api.type.ClassOrInterfaceType; @Component @Service public class JavaParserTypeParsingService implements TypeParsingService { @Reference MetadataService metadataService; @Reference TypeLocationService typeLocationService; private void addEnumConstant(final List<EnumConstantDeclaration> constants, final JavaSymbolName name) { // Determine location to insert for (final EnumConstantDeclaration constant : constants) { if (constant.getName().equals(name.getSymbolName())) { throw new IllegalArgumentException("Enum constant '" + name.getSymbolName() + "' already exists"); } } final EnumConstantDeclaration newEntry = new EnumConstantDeclaration(name.getSymbolName()); constants.add(constants.size(), newEntry); } @Override public final String getCompilationUnitContents(final ClassOrInterfaceTypeDetails cid) { Validate.notNull(cid, "Class or interface type details are required"); // Create a compilation unit to store the type to be created final CompilationUnit compilationUnit = new CompilationUnit(); // NB: this import list is replaced at the end of this method by a // sorted version compilationUnit.setImports(new ArrayList<ImportDeclaration>()); if (!cid.getName().isDefaultPackage()) { compilationUnit.setPackage(new PackageDeclaration(ASTHelper.createNameExpr(cid.getName() .getPackage().getFullyQualifiedPackageName()))); } // Add the class of interface declaration to the compilation unit final List<TypeDeclaration> types = new ArrayList<TypeDeclaration>(); compilationUnit.setTypes(types); updateOutput(compilationUnit, null, cid, null); return compilationUnit.toString(); } @Override public ClassOrInterfaceTypeDetails getTypeAtLocation(final String fileIdentifier, final String declaredByMetadataId, final JavaType typeName) { Validate.notBlank(fileIdentifier, "Compilation unit path required"); Validate.notBlank(declaredByMetadataId, "Declaring metadata ID required"); Validate.notNull(typeName, "Java type to locate required"); final File file = new File(fileIdentifier); String typeContents = ""; try { typeContents = FileUtils.readFileToString(file); } catch (final IOException ignored) { } if (StringUtils.isBlank(typeContents)) { return null; } return getTypeFromString(typeContents, declaredByMetadataId, typeName); } @Override public ClassOrInterfaceTypeDetails getTypeFromString(final String fileContents, final String declaredByMetadataId, final JavaType typeName) { if (StringUtils.isBlank(fileContents)) { return null; } Validate.notBlank(declaredByMetadataId, "Declaring metadata ID required"); Validate.notNull(typeName, "Java type to locate required"); try { final CompilationUnit compilationUnit = JavaParser.parse(new ByteArrayInputStream(fileContents.getBytes())); final TypeDeclaration typeDeclaration = JavaParserUtils.locateTypeDeclaration(compilationUnit, typeName); if (typeDeclaration == null) { return null; } return JavaParserClassOrInterfaceTypeDetailsBuilder.getInstance(compilationUnit, null, typeDeclaration, declaredByMetadataId, typeName, metadataService, typeLocationService) .build(); } catch (final IOException e) { throw new IllegalStateException(e); } catch (final ParseException e) { throw new IllegalStateException("Failed to parse " + typeName + " : " + e.getMessage()); } } /** * Appends the presented class to the end of the presented body * declarations. The body declarations appear within the presented * compilation unit. This is used to progressively build inner types. * * @param compilationUnit the work-in-progress compilation unit (required) * @param enclosingCompilationUnitServices * @param cid the new class to add (required) * @param parent the class body declarations a subclass should be added to * (may be null, which denotes a top-level type within the * compilation unit) */ private void updateOutput(final CompilationUnit compilationUnit, CompilationUnitServices enclosingCompilationUnitServices, final ClassOrInterfaceTypeDetails cid, final List<BodyDeclaration> parent) { // Append the new imports this class declares Validate.notNull(compilationUnit.getImports(), "Compilation unit imports should be non-null when producing type '%s'", cid.getName()); for (final ImportMetadata importType : cid.getRegisteredImports()) { ImportDeclaration importDeclaration; if (!importType.isAsterisk()) { NameExpr typeToImportExpr; if (importType.getImportType().getEnclosingType() == null) { typeToImportExpr = new QualifiedNameExpr(new NameExpr(importType.getImportType().getPackage() .getFullyQualifiedPackageName()), importType.getImportType().getSimpleTypeName()); } else { typeToImportExpr = new QualifiedNameExpr(new NameExpr(importType.getImportType().getEnclosingType() .getFullyQualifiedTypeName()), importType.getImportType().getSimpleTypeName()); } importDeclaration = new ImportDeclaration(typeToImportExpr, importType.isStatic(), false); } else { importDeclaration = new ImportDeclaration(new NameExpr(importType.getImportPackage() .getFullyQualifiedPackageName()), importType.isStatic(), importType.isAsterisk()); } JavaParserCommentMetadataBuilder.updateCommentsToJavaParser(importDeclaration, importType.getCommentStructure()); compilationUnit.getImports().add(importDeclaration); } // Create a class or interface declaration to represent this actual type final int javaParserModifier = JavaParserUtils.getJavaParserModifier(cid.getModifier()); TypeDeclaration typeDeclaration; ClassOrInterfaceDeclaration classOrInterfaceDeclaration; // Implements handling final List<ClassOrInterfaceType> implementsList = new ArrayList<ClassOrInterfaceType>(); for (final JavaType current : cid.getImplementsTypes()) { implementsList.add(JavaParserUtils.getResolvedName(cid.getName(), current, compilationUnit)); } if (cid.getPhysicalTypeCategory() == PhysicalTypeCategory.INTERFACE || cid.getPhysicalTypeCategory() == PhysicalTypeCategory.CLASS) { final boolean isInterface = cid.getPhysicalTypeCategory() == PhysicalTypeCategory.INTERFACE; if (parent == null) { // Top level type typeDeclaration = new ClassOrInterfaceDeclaration(javaParserModifier, isInterface, cid.getName() .getNameIncludingTypeParameters() .replace(cid.getName().getPackage().getFullyQualifiedPackageName() + ".", "")); classOrInterfaceDeclaration = (ClassOrInterfaceDeclaration) typeDeclaration; } else { // Inner type typeDeclaration = new ClassOrInterfaceDeclaration(javaParserModifier, isInterface, cid.getName() .getSimpleTypeName()); classOrInterfaceDeclaration = (ClassOrInterfaceDeclaration) typeDeclaration; if (cid.getName().getParameters().size() > 0) { classOrInterfaceDeclaration.setTypeParameters(new ArrayList<TypeParameter>()); for (final JavaType param : cid.getName().getParameters()) { NameExpr pNameExpr = JavaParserUtils.importTypeIfRequired(cid.getName(), compilationUnit.getImports(), param); final String tempName = StringUtils.replace(pNameExpr.toString(), param.getArgName() + " extends ", "", 1); pNameExpr = new NameExpr(tempName); final ClassOrInterfaceType pResolvedName = JavaParserUtils.getClassOrInterfaceType(pNameExpr); classOrInterfaceDeclaration.getTypeParameters().add( new TypeParameter(param.getArgName().getSymbolName(), Collections .singletonList(pResolvedName))); } } } // Superclass handling final List<ClassOrInterfaceType> extendsList = new ArrayList<ClassOrInterfaceType>(); for (final JavaType current : cid.getExtendsTypes()) { if (!OBJECT.equals(current)) { extendsList.add(JavaParserUtils.getResolvedName(cid.getName(), current, compilationUnit)); } } if (extendsList.size() > 0) { classOrInterfaceDeclaration.setExtends(extendsList); } // Implements handling if (implementsList.size() > 0) { classOrInterfaceDeclaration.setImplements(implementsList); } } else { typeDeclaration = new EnumDeclaration(javaParserModifier, cid.getName().getSimpleTypeName()); } typeDeclaration.setMembers(new ArrayList<BodyDeclaration>()); Validate.notNull(typeDeclaration.getName(), "Missing type declaration name for '%s'", cid.getName()); // If adding a new top-level type, must add it to the compilation unit // types Validate.notNull(compilationUnit.getTypes(), "Compilation unit types must not be null when attempting to add '%s'", cid.getName()); if (parent == null) { // Top-level class compilationUnit.getTypes().add(typeDeclaration); } else { // Inner class parent.add(typeDeclaration); } // If the enclosing CompilationUnitServices was not provided a default // CompilationUnitServices needs to be created if (enclosingCompilationUnitServices == null) { // Create a compilation unit so that we can use JavaType*Metadata // static methods directly enclosingCompilationUnitServices = new CompilationUnitServices() { @Override public JavaPackage getCompilationUnitPackage() { return cid.getName().getPackage(); } @Override public JavaType getEnclosingTypeName() { return cid.getName(); } @Override public List<ImportDeclaration> getImports() { return compilationUnit.getImports(); } @Override public List<TypeDeclaration> getInnerTypes() { return compilationUnit.getTypes(); } @Override public PhysicalTypeCategory getPhysicalTypeCategory() { return cid.getPhysicalTypeCategory(); } }; } final CompilationUnitServices finalCompilationUnitServices = enclosingCompilationUnitServices; // A hybrid CompilationUnitServices must be provided that references the // enclosing types imports and package final CompilationUnitServices compilationUnitServices = new CompilationUnitServices() { @Override public JavaPackage getCompilationUnitPackage() { return finalCompilationUnitServices.getCompilationUnitPackage(); } @Override public JavaType getEnclosingTypeName() { return cid.getName(); } @Override public List<ImportDeclaration> getImports() { return finalCompilationUnitServices.getImports(); } @Override public List<TypeDeclaration> getInnerTypes() { return compilationUnit.getTypes(); } @Override public PhysicalTypeCategory getPhysicalTypeCategory() { return cid.getPhysicalTypeCategory(); } }; // Add type annotations final List<AnnotationExpr> annotations = new ArrayList<AnnotationExpr>(); typeDeclaration.setAnnotations(annotations); for (final AnnotationMetadata candidate : cid.getAnnotations()) { JavaParserAnnotationMetadataBuilder.addAnnotationToList(compilationUnitServices, annotations, candidate); } // ROO-3834: Generating default Javadoc inside class if class doesn't contains JavaDoc List<Comment> classComments = compilationUnit.getComments(); if (classComments == null || classComments.isEmpty()) { CommentStructure defaultCommentStructure = new CommentStructure(); String defaultComment = "= ".concat(cid.getType().getSimpleTypeName()).concat( "\n \nTODO Auto-generated class documentation"); defaultCommentStructure.addComment(new JavadocComment(defaultComment), CommentLocation.BEGINNING); if (annotations.isEmpty()) { JavaParserCommentMetadataBuilder.updateCommentsToJavaParser(typeDeclaration, defaultCommentStructure); } else { // If exists some annotation, include comment before the existing annotations AnnotationExpr firstAnnotation = annotations.get(0); JavaParserCommentMetadataBuilder.updateCommentsToJavaParser(firstAnnotation, defaultCommentStructure); } } // Add enum constants and interfaces if (typeDeclaration instanceof EnumDeclaration && cid.getEnumConstants().size() > 0) { final EnumDeclaration enumDeclaration = (EnumDeclaration) typeDeclaration; final List<EnumConstantDeclaration> constants = new ArrayList<EnumConstantDeclaration>(); enumDeclaration.setEntries(constants); for (final JavaSymbolName constant : cid.getEnumConstants()) { addEnumConstant(constants, constant); } // Implements handling if (implementsList.size() > 0) { enumDeclaration.setImplements(implementsList); } } // Add fields for (final FieldMetadata candidate : cid.getDeclaredFields()) { JavaParserFieldMetadataBuilder.addField(compilationUnitServices, typeDeclaration.getMembers(), candidate); } // Add constructors for (final ConstructorMetadata candidate : cid.getDeclaredConstructors()) { JavaParserConstructorMetadataBuilder.addConstructor(compilationUnitServices, typeDeclaration.getMembers(), candidate, null); } // Add methods for (final MethodMetadata candidate : cid.getDeclaredMethods()) { JavaParserMethodMetadataBuilder.addMethod(compilationUnitServices, typeDeclaration.getMembers(), candidate, null); } // Add inner types for (final ClassOrInterfaceTypeDetails candidate : cid.getDeclaredInnerTypes()) { updateOutput(compilationUnit, compilationUnitServices, candidate, typeDeclaration.getMembers()); } final HashSet<String> imported = new HashSet<String>(); final ArrayList<ImportDeclaration> imports = new ArrayList<ImportDeclaration>(); for (final ImportDeclaration importDeclaration : compilationUnit.getImports()) { JavaPackage importPackage = null; JavaType importType = null; if (importDeclaration.isAsterisk()) { importPackage = new JavaPackage(importDeclaration.getName().toString()); } else { importType = new JavaType(importDeclaration.getName().toString()); importPackage = importType.getPackage(); } if (importPackage.equals(cid.getName().getPackage()) && importDeclaration.isAsterisk()) { continue; } if (importPackage.equals(cid.getName().getPackage()) && importType != null && importType.getEnclosingType() == null) { continue; } if (importType != null && importType.equals(cid.getName())) { continue; } if (!imported.contains(importDeclaration.getName().toString())) { imports.add(importDeclaration); imported.add(importDeclaration.getName().toString()); } } Collections.sort(imports, new Comparator<ImportDeclaration>() { @Override public int compare(final ImportDeclaration importDeclaration, final ImportDeclaration importDeclaration1) { return importDeclaration.getName().toString() .compareTo(importDeclaration1.getName().toString()); } }); compilationUnit.setImports(imports); } @Override public String updateAndGetCompilationUnitContents(final String fileIdentifier, final ClassOrInterfaceTypeDetails cid) { // Validate parameters Validate.notBlank(fileIdentifier, "Oringinal unit path required"); Validate.notNull(cid, "Type details required"); // Load original compilation unit from file final File file = new File(fileIdentifier); String fileContents = ""; try { fileContents = FileUtils.readFileToString(file); } catch (final IOException ignored) { } if (StringUtils.isBlank(fileContents)) { return getCompilationUnitContents(cid); } CompilationUnit compilationUnit; try { compilationUnit = JavaParser.parse(new ByteArrayInputStream(fileContents.getBytes())); } catch (final IOException e) { throw new IllegalStateException(e); } catch (final ParseException e) { throw new IllegalStateException(e); } // Load new compilation unit from cid information final String cidContents = getCompilationUnitContents(cid); CompilationUnit cidCompilationUnit; try { cidCompilationUnit = JavaParser.parse(new ByteArrayInputStream(cidContents.getBytes())); } catch (final IOException e) { throw new IllegalStateException(e); } catch (final ParseException e) { throw new IllegalStateException(e); } // Update package if (!compilationUnit.getPackage().getName().getName() .equals(cidCompilationUnit.getPackage().getName().getName())) { compilationUnit.setPackage(cidCompilationUnit.getPackage()); } // Update imports UpdateCompilationUnitUtils.updateCompilationUnitImports(compilationUnit, cidCompilationUnit); // Update types UpdateCompilationUnitUtils.updateCompilationUnitTypes(compilationUnit, cidCompilationUnit); // Return new contents return compilationUnit.toString(); } }