diff --git a/java-ast/addon/src/main/java/org/jboss/windup/ast/java/ASTException.java b/java-ast/addon/src/main/java/org/jboss/windup/ast/java/ASTException.java index 8b36ab86f2..e272e09600 100644 --- a/java-ast/addon/src/main/java/org/jboss/windup/ast/java/ASTException.java +++ b/java-ast/addon/src/main/java/org/jboss/windup/ast/java/ASTException.java @@ -1,7 +1,7 @@ package org.jboss.windup.ast.java; /** - * Thrown due to errors parsing Java source with the {@link ASTProcessor} + * Thrown due to errors parsing Java source with the {@link ASTReferenceResolver} * * @author Jesse Sightler * @@ -10,24 +10,28 @@ public class ASTException extends RuntimeException { private static final long serialVersionUID = 1L; + /** + * Create an exception. + */ public ASTException() { super(); } + /** + * Create an exception with the given message and cause. + */ public ASTException(String message, Throwable cause) { super(message, cause); } + /** + * Create an exception with the given message. + */ public ASTException(String message) { super(message); } - public ASTException(Throwable cause) - { - super(cause); - } - } diff --git a/java-ast/addon/src/main/java/org/jboss/windup/ast/java/ASTProcessor.java b/java-ast/addon/src/main/java/org/jboss/windup/ast/java/ASTProcessor.java index d411918cca..83263eaa61 100644 --- a/java-ast/addon/src/main/java/org/jboss/windup/ast/java/ASTProcessor.java +++ b/java-ast/addon/src/main/java/org/jboss/windup/ast/java/ASTProcessor.java @@ -2,1145 +2,62 @@ import java.io.IOException; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.Stack; -import java.util.logging.Logger; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTParser; -import org.eclipse.jdt.core.dom.ASTVisitor; -import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.ArrayInitializer; -import org.eclipse.jdt.core.dom.BooleanLiteral; -import org.eclipse.jdt.core.dom.CastExpression; -import org.eclipse.jdt.core.dom.CharacterLiteral; -import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.core.dom.Expression; -import org.eclipse.jdt.core.dom.FieldAccess; -import org.eclipse.jdt.core.dom.FieldDeclaration; -import org.eclipse.jdt.core.dom.IMethodBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.ImportDeclaration; -import org.eclipse.jdt.core.dom.InfixExpression; -import org.eclipse.jdt.core.dom.InstanceofExpression; -import org.eclipse.jdt.core.dom.MarkerAnnotation; -import org.eclipse.jdt.core.dom.MemberValuePair; -import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.eclipse.jdt.core.dom.MethodInvocation; -import org.eclipse.jdt.core.dom.NormalAnnotation; -import org.eclipse.jdt.core.dom.NumberLiteral; -import org.eclipse.jdt.core.dom.PackageDeclaration; -import org.eclipse.jdt.core.dom.QualifiedName; -import org.eclipse.jdt.core.dom.ReturnStatement; -import org.eclipse.jdt.core.dom.SimpleName; -import org.eclipse.jdt.core.dom.SimpleType; -import org.eclipse.jdt.core.dom.SingleMemberAnnotation; -import org.eclipse.jdt.core.dom.SingleVariableDeclaration; -import org.eclipse.jdt.core.dom.StringLiteral; -import org.eclipse.jdt.core.dom.Type; -import org.eclipse.jdt.core.dom.TypeDeclaration; -import org.eclipse.jdt.core.dom.TypeLiteral; -import org.eclipse.jdt.core.dom.VariableDeclarationFragment; -import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import org.jboss.windup.ast.java.data.ClassReference; -import org.jboss.windup.ast.java.data.ClassReferences; -import org.jboss.windup.ast.java.data.TypeReferenceLocation; -import org.jboss.windup.ast.java.data.annotations.AnnotationArrayValue; -import org.jboss.windup.ast.java.data.annotations.AnnotationClassReference; -import org.jboss.windup.ast.java.data.annotations.AnnotationLiteralValue; -import org.jboss.windup.ast.java.data.annotations.AnnotationValue; /** - * Provides the ability to parse a Java file and return a {@link ClassReferences} object containing the fully qualified names of all of the contained - * references. - * * @author Jesse Sightler - * */ -public class ASTProcessor extends ASTVisitor -{ - private static Logger LOG = Logger.getLogger(ASTProcessor.class.getName()); - - private final WildcardImportResolver wildcardImportResolver; - private CompilationUnit cu; - private Path javaFile; - private ASTParser parser; - - private final Set libraryPaths; - private final Set sourcePaths; - - /** - * Contains all wildcard imports (import com.example.*) lines from the source file. - * - * These are used for type resolution throughout the class. - */ - private final List wildcardImports = new ArrayList<>(); - - /** - * Indicates that we have already attempted to query the graph for this particular shortname. The shortname will exist here even if no results - * were found. - */ - private final Set classNameLookedUp = new HashSet<>(); - - /** - * Contains a map of class short names (eg, MyClass) to qualified names (eg, com.example.MyClass) - */ - private final Map classNameToFQCN = new HashMap<>(); - /** - * Maintains a set of all variable names that have been resolved - */ - private final Set names = new HashSet(); - - /** - * Maintains a map of nameInstances to fully qualified class names. - */ - private final Map nameInstance = new HashMap(); - - private ClassReferences classReferences; - +public class ASTProcessor { /** * Processes a java file using the default {@link WildcardImportResolver}. - * - * See also: {@see JavaASTProcessor#analyzeJavaFile(WildcardImportResolver, Set, Set, Path)} + * + * See also: {@see JavaASTProcessor#analyze(WildcardImportResolver, Set, Set, Path)} */ - public static ClassReferences analyzeJavaFile(Set libraryPaths, Set sourcePaths, Path sourceFile) + public static List analyze(Set libraryPaths, Set sourcePaths, Path sourceFile) { - return new ASTProcessor(new NoopWildcardImportResolver(), libraryPaths, sourcePaths).analyzeFile(sourceFile); + return analyze(new NoopWildcardImportResolver(), libraryPaths, sourcePaths, sourceFile); } /** * Parses the provided file, using the given libraryPaths and sourcePaths as context. The libraries may be either jar files or references to * directories containing class files. - * + * * The sourcePaths must be a reference to the top level directory for sources (eg, for a file src/main/java/org/example/Foo.java, the source path * would be src/main/java). - * + * * The wildcard resolver provides a fallback for processing wildcard imports that the underlying parser was unable to resolve. */ - public static ClassReferences analyzeJavaFile(WildcardImportResolver importResolver, Set libraryPaths, Set sourcePaths, - Path sourceFile) + public static List analyze(WildcardImportResolver importResolver, Set libraryPaths, Set sourcePaths, + Path sourceFile) { - return new ASTProcessor(importResolver, libraryPaths, sourcePaths).analyzeFile(sourceFile); - } - - public ASTProcessor(WildcardImportResolver importResolver, Set libraryPaths, Set sourcePaths) - { - this.wildcardImportResolver = importResolver; - this.parser = ASTParser.newParser(AST.JLS8); - this.libraryPaths = libraryPaths; - this.sourcePaths = sourcePaths; - } - - public ClassReferences analyzeFile(Path javaFile) - { - this.classReferences = new ClassReferences(); - this.wildcardImports.clear(); - this.classNameLookedUp.clear(); - this.classNameToFQCN.clear(); - this.names.clear(); - this.nameInstance.clear(); - this.javaFile = javaFile; - + ASTParser parser = ASTParser.newParser(AST.JLS8); parser.setEnvironment(libraryPaths.toArray(new String[libraryPaths.size()]), sourcePaths.toArray(new String[sourcePaths.size()]), null, true); parser.setBindingsRecovery(false); parser.setResolveBindings(true); - - String fileName = javaFile.getFileName().toString(); + Map options = JavaCore.getOptions(); + JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options); + parser.setCompilerOptions(options); + String fileName = sourceFile.getFileName().toString(); parser.setUnitName(fileName); try { - parser.setSource(FileUtils.readFileToString(javaFile.toFile()).toCharArray()); + parser.setSource(FileUtils.readFileToString(sourceFile.toFile()).toCharArray()); } catch (IOException e) { - throw new ASTException("Failed to get source for file: " + javaFile.toString() + " due to: " + e.getMessage(), e); + throw new ASTException("Failed to get source for file: " + sourceFile.toString() + " due to: " + e.getMessage(), e); } parser.setKind(ASTParser.K_COMPILATION_UNIT); - this.cu = (CompilationUnit) parser.createAST(null); - - PackageDeclaration packageDeclaration = cu.getPackage(); - String packageName = packageDeclaration == null ? "" : packageDeclaration.getName().getFullyQualifiedName(); - @SuppressWarnings("unchecked") - List types = cu.types(); - String fqcn = null; - if (!types.isEmpty()) - { - TypeDeclaration typeDeclaration = (TypeDeclaration) types.get(0); - String className = typeDeclaration.getName().getFullyQualifiedName(); - - if (packageName.equals("")) - { - fqcn = className; - } - else - { - fqcn = packageName + "." + className; - } - this.classReferences.addReference(new ClassReference(fqcn, TypeReferenceLocation.TYPE, cu.getLineNumber(typeDeclaration - .getStartPosition()), cu.getColumnNumber(cu.getStartPosition()), cu.getLength(), extractDefinitionLine(typeDeclaration - .toString()))); - - Type superclassType = typeDeclaration.getSuperclassType(); - ITypeBinding resolveBinding = null; - if (superclassType != null) - { - resolveBinding = superclassType.resolveBinding(); - } - - while (resolveBinding != null) - { - if (superclassType.resolveBinding() != null) - { - this.classReferences.addReference(new ClassReference(resolveBinding.getQualifiedName(), TypeReferenceLocation.TYPE, cu - .getLineNumber(typeDeclaration - .getStartPosition()), cu.getColumnNumber(cu.getStartPosition()), cu.getLength(), - extractDefinitionLine(typeDeclaration.toString()))); - } - resolveBinding = resolveBinding.getSuperclass(); - } - } - - this.names.add("this"); - this.nameInstance.put("this", fqcn); - - cu.accept(this); - return this.classReferences; - } - - private String extractDefinitionLine(String typeDeclaration) - { - String typeLine = ""; - String[] lines = typeDeclaration.split("\n"); - for (String line : lines) - { - typeLine = line; - if (line.contains("{")) - { - break; - } - } - return typeLine; - } - - public ClassReferences getJavaClassReferences() - { - return this.classReferences; - } - - private void processConstructor(ConstructorType interest, int lineNumber, int columnNumber, int length, String line) - { - String text = interest.toString(); - this.classReferences.addReference(new ClassReference(text, TypeReferenceLocation.CONSTRUCTOR_CALL, lineNumber, columnNumber, length, - line)); - } - - private void processMethod(MethodType interest, TypeReferenceLocation location, int lineNumber, int columnNumber, int length, String line) - { - String text = interest.toString(); - this.classReferences.addReference(new ClassReference(text, location, lineNumber, columnNumber, length, line)); - } - - private void processImport(String interest, int lineNumber, int columnNumber, int length, String line) - { - this.classReferences.addReference(new ClassReference(interest, TypeReferenceLocation.IMPORT, lineNumber, columnNumber, length, line)); - } - - private ClassReference processTypeBinding(ITypeBinding type, TypeReferenceLocation referenceLocation, int lineNumber, int columnNumber, - int length, String line) - { - if (type == null) - return null; - String sourceString = type.getQualifiedName(); - return processTypeAsString(sourceString, referenceLocation, lineNumber, columnNumber, length, line); - } - - /** - * The method determines if the type can be resolved and if not, will try to guess the qualified name using the information from the imports. - */ - private ClassReference processType(Type type, TypeReferenceLocation typeReferenceLocation, int lineNumber, int columnNumber, int length, - String line) - { - if (type == null) - return null; - - ITypeBinding resolveBinding = type.resolveBinding(); - if (resolveBinding == null) - { - return processTypeAsString(resolveClassname(type.toString()), typeReferenceLocation, lineNumber, - columnNumber, length, line); - } - else - { - return processTypeBinding(type.resolveBinding(), typeReferenceLocation, lineNumber, - columnNumber, length, line); - } - - } - - private ClassReference processTypeAsString(String sourceString, TypeReferenceLocation referenceLocation, int lineNumber, - int columnNumber, int length, String line) - { - if (sourceString == null) - return null; - line = line.replaceAll("(\\n)|(\\r)", ""); - ClassReference typeRef = new ClassReference(sourceString, referenceLocation, lineNumber, columnNumber, length, line); - this.classReferences.addReference(typeRef); - return typeRef; - } - - @Override - public boolean visit(MethodDeclaration node) - { - //register method return type - IMethodBinding resolveBinding = node.resolveBinding(); - ITypeBinding returnType = null; - if (resolveBinding != null) - { - returnType = node.resolveBinding().getReturnType(); - } - if (returnType != null) - { - processTypeBinding(returnType, TypeReferenceLocation.RETURN_TYPE, cu.getLineNumber(node.getStartPosition()), - cu.getColumnNumber(node.getStartPosition()), node.getLength(), extractDefinitionLine(node.toString())); - } - else - { - Type methodReturnType = node.getReturnType2(); - processType(methodReturnType, TypeReferenceLocation.RETURN_TYPE, cu.getLineNumber(node.getStartPosition()), - cu.getColumnNumber(node.getStartPosition()), node.getLength(), extractDefinitionLine(node.toString())); - } - // register parameters and register them for next processing - List qualifiedArguments = new ArrayList(); - @SuppressWarnings("unchecked") - List parameters = (List) node.parameters(); - if (parameters != null) - { - for (SingleVariableDeclaration type : parameters) - { - this.names.add(type.getName().toString()); - String typeName = type.getType().toString(); - typeName = resolveClassname(typeName); - qualifiedArguments.add(typeName); - this.nameInstance.put(type.getName().toString(), typeName); - processType(type.getType(), TypeReferenceLocation.METHOD_PARAMETER, cu.getLineNumber(node.getStartPosition()), - cu.getColumnNumber(node.getStartPosition()), node.getLength(), extractDefinitionLine(node.toString())); - } - } - // register thow declarations - @SuppressWarnings("unchecked") - List throwsTypes = node.thrownExceptionTypes(); - if (throwsTypes != null) - { - for (Type type : throwsTypes) - { - processType(type, TypeReferenceLocation.THROWS_METHOD_DECLARATION, - cu.getLineNumber(node.getStartPosition()), - cu.getColumnNumber(type.getStartPosition()), type.getLength(), extractDefinitionLine(node.toString())); - } - } - - // register method declaration - MethodType methodCall = new MethodType(nameInstance.get("this"), node.getName().toString(), qualifiedArguments); - processMethod(methodCall, TypeReferenceLocation.METHOD, cu.getLineNumber(node.getName().getStartPosition()), - cu.getColumnNumber(node.getName().getStartPosition()), node.getName().getLength(), extractDefinitionLine(node.toString())); - return super.visit(node); - } - - @Override - public boolean visit(InstanceofExpression node) - { - Type type = node.getRightOperand(); - processType(type, TypeReferenceLocation.INSTANCE_OF, cu.getLineNumber(node.getStartPosition()), - cu.getColumnNumber(type.getStartPosition()), type.getLength(), node.toString()); - - return super.visit(node); - } - - public boolean visit(org.eclipse.jdt.core.dom.ThrowStatement node) - { - if (node.getExpression() instanceof ClassInstanceCreation) - { - ClassInstanceCreation cic = (ClassInstanceCreation) node.getExpression(); - Type type = cic.getType(); - processType(type, TypeReferenceLocation.THROW_STATEMENT, cu.getLineNumber(node.getStartPosition()), - cu.getColumnNumber(cic.getStartPosition()), cic.getLength(), node.toString()); - } - - return super.visit(node); - } - - public boolean visit(org.eclipse.jdt.core.dom.CatchClause node) - { - Type catchType = node.getException().getType(); - processType(catchType, TypeReferenceLocation.CATCH_EXCEPTION_STATEMENT, cu.getLineNumber(node.getStartPosition()), - cu.getColumnNumber(catchType.getStartPosition()), catchType.getLength(), node.toString()); - - return super.visit(node); - } - - @Override - public boolean visit(ReturnStatement node) - { - if (node.getExpression() instanceof ClassInstanceCreation) - { - ClassInstanceCreation cic = (ClassInstanceCreation) node.getExpression(); - processTypeBinding(cic.getType().resolveBinding(), TypeReferenceLocation.CONSTRUCTOR_CALL, cu.getLineNumber(node.getStartPosition()), - cu.getColumnNumber(cic.getStartPosition()), cic.getLength(), node.toString()); - } - return super.visit(node); - } - - @Override - public boolean visit(FieldDeclaration node) - { - for (int i = 0; i < node.fragments().size(); ++i) - { - String nodeType = node.getType().toString(); - nodeType = resolveClassname(nodeType); - VariableDeclarationFragment frag = (VariableDeclarationFragment) node.fragments().get(i); - frag.resolveBinding(); - this.names.add(frag.getName().getIdentifier()); - this.nameInstance.put(frag.getName().toString(), nodeType.toString()); - - processTypeBinding(node.getType().resolveBinding(), TypeReferenceLocation.FIELD_DECLARATION, cu.getLineNumber(node.getStartPosition()), - cu.getColumnNumber(node.getStartPosition()), node.getLength(), node.toString()); - } - return true; - } - - private AnnotationValue getAnnotationValueForExpression(Expression expression) - { - AnnotationValue value; - if (expression instanceof BooleanLiteral) - value = new AnnotationLiteralValue(boolean.class, ((BooleanLiteral) expression).booleanValue()); - else if (expression instanceof CharacterLiteral) - value = new AnnotationLiteralValue(char.class, ((CharacterLiteral) expression).charValue()); - else if (expression instanceof NumberLiteral) - { - ITypeBinding binding = expression.resolveTypeBinding(); - if (binding == null) - { - value = new AnnotationLiteralValue(String.class, expression.toString()); - } - else - { - value = new AnnotationLiteralValue(resolveLiteralType(binding), ((NumberLiteral) expression).resolveConstantExpressionValue()); - } - } - else if (expression instanceof TypeLiteral) - value = new AnnotationLiteralValue(Class.class, ((TypeLiteral) expression).resolveConstantExpressionValue()); - else if (expression instanceof StringLiteral) - value = new AnnotationLiteralValue(String.class, ((StringLiteral) expression).getLiteralValue()); - else if (expression instanceof NormalAnnotation) - value = processAnnotation((NormalAnnotation) expression); - else if (expression instanceof ArrayInitializer) - { - ArrayInitializer arrayInitializer = (ArrayInitializer) expression; - List arrayValues = new ArrayList<>(arrayInitializer.expressions().size()); - for (Object arrayExpression : arrayInitializer.expressions()) - { - arrayValues.add(getAnnotationValueForExpression((Expression) arrayExpression)); - } - value = new AnnotationArrayValue(arrayValues); - } - else if (expression instanceof QualifiedName) - { - QualifiedName qualifiedName = (QualifiedName) expression; - Object expressionValue = qualifiedName.resolveConstantExpressionValue(); - if (expressionValue == null) - { - value = new AnnotationLiteralValue(String.class, qualifiedName.toString()); - } - else - { - Class expressionType = expressionValue.getClass(); - value = new AnnotationLiteralValue(expressionType, expressionValue); - } - } - else if (expression instanceof CastExpression) - { - CastExpression cast = (CastExpression) expression; - AnnotationValue castExpressionValue = getAnnotationValueForExpression(cast.getExpression()); - if (castExpressionValue instanceof AnnotationLiteralValue) - { - AnnotationLiteralValue literalValue = (AnnotationLiteralValue) castExpressionValue; - ITypeBinding binding = cast.getType().resolveBinding(); - if (binding == null) - { - value = new AnnotationLiteralValue(String.class, literalValue.getLiteralValue()); - } - else - { - Class type = resolveLiteralType(cast.getType().resolveBinding()); - value = new AnnotationLiteralValue(type, literalValue.getLiteralValue()); - } - } - else - { - value = castExpressionValue; - } - } - else if (expression instanceof SimpleName) - { - SimpleName simpleName = (SimpleName) expression; - ITypeBinding binding = simpleName.resolveTypeBinding(); - if (binding == null) - { - value = new AnnotationLiteralValue(String.class, simpleName.toString()); - } - else - { - Object expressionValue = simpleName.resolveConstantExpressionValue(); - Class expressionType = expressionValue == null ? null : expressionValue.getClass(); - value = new AnnotationLiteralValue(expressionType, expressionValue); - } - } - else - { - LOG.warning("Unexpected type: " + expression.getClass().getCanonicalName() + " in file: " + this.javaFile - + " just attempting to use it as a string value"); - value = new AnnotationLiteralValue(String.class, expression == null ? null : expression.toString()); - } - return value; - - } - - /** - * Adds parameters contained in the annotation into the annotation type reference - * - * @param typeRef - * @param node - */ - private void addAnnotationValues(AnnotationClassReference typeRef, Annotation node) - { - Map annotationValueMap = new HashMap<>(); - if (node instanceof SingleMemberAnnotation) - { - SingleMemberAnnotation singleMemberAnnotation = (SingleMemberAnnotation) node; - AnnotationValue value = getAnnotationValueForExpression(singleMemberAnnotation.getValue()); - annotationValueMap.put("value", value); - } - else if (node instanceof NormalAnnotation) - { - @SuppressWarnings("unchecked") - List annotationValues = ((NormalAnnotation) node).values(); - for (MemberValuePair annotationValue : annotationValues) - { - String key = annotationValue.getName().toString(); - Expression expression = annotationValue.getValue(); - AnnotationValue value = getAnnotationValueForExpression(expression); - annotationValueMap.put(key, value); - } - } - typeRef.setAnnotationValues(annotationValueMap); - } - - private Class resolveLiteralType(ITypeBinding binding) - { - switch (binding.getName()) - { - case "byte": - return byte.class; - case "short": - return short.class; - case "int": - return int.class; - case "long": - return long.class; - case "float": - return float.class; - case "double": - return double.class; - case "boolean": - return boolean.class; - case "char": - return char.class; - default: - throw new ASTException("Unrecognized literal type: " + binding.getName()); - } - } - - private AnnotationClassReference processAnnotation(Annotation node) - { - ITypeBinding typeBinding = node.resolveTypeBinding(); - AnnotationClassReference reference; - String qualifiedName; - if (typeBinding != null) - qualifiedName = typeBinding.getQualifiedName(); - else - qualifiedName = resolveClassname(node.getTypeName().toString()); - - reference = new AnnotationClassReference( - qualifiedName, - cu.getLineNumber(node.getStartPosition()), - cu.getColumnNumber(node.getStartPosition()), - node.getLength(), - node.toString()); - - addAnnotationValues(reference, node); - - return reference; - } - - @Override - public boolean visit(NormalAnnotation node) - { - AnnotationClassReference reference = processAnnotation(node); - this.classReferences.addReference(reference); - - // false to avoid recursively processing nested annotations (our code already handles that) - return false; - } - - @Override - public boolean visit(SingleMemberAnnotation node) - { - AnnotationClassReference reference = processAnnotation(node); - this.classReferences.addReference(reference); - return false; - } - - @Override - public boolean visit(MarkerAnnotation node) - { - AnnotationClassReference reference = processAnnotation(node); - this.classReferences.addReference(reference); - return false; - } - - public boolean visit(TypeDeclaration node) - { - Object clzInterfaces = node.getStructuralProperty(TypeDeclaration.SUPER_INTERFACE_TYPES_PROPERTY); - Object clzSuperClasses = node.getStructuralProperty(TypeDeclaration.SUPERCLASS_TYPE_PROPERTY); - - if (clzInterfaces != null) - { - if (List.class.isAssignableFrom(clzInterfaces.getClass())) - { - List clzInterfacesList = (List) clzInterfaces; - for (Object clzInterface : clzInterfacesList) - { - if (clzInterface instanceof SimpleType) - { - ITypeBinding resolvedSuperInterface = ((SimpleType) clzInterface).resolveBinding(); - Stack stack = new Stack(); - stack.push(resolvedSuperInterface); - // register all the implemented interfaces (even superinterfaces) - while (!stack.isEmpty()) - { - resolvedSuperInterface = stack.pop(); - processTypeBinding(resolvedSuperInterface, TypeReferenceLocation.IMPLEMENTS_TYPE, - cu.getLineNumber(node.getStartPosition()), - cu.getColumnNumber(node.getStartPosition()), node.getLength(), extractDefinitionLine(node.toString())); - if (resolvedSuperInterface != null) - { - ITypeBinding[] interfaces = resolvedSuperInterface.getInterfaces(); - for (ITypeBinding oneInterface : interfaces) - { - stack.push(oneInterface); - } - } - } - } - } - } - } - if (clzSuperClasses != null) - { - if (clzSuperClasses instanceof SimpleType) - { - ITypeBinding resolvedSuperClass = ((SimpleType) clzSuperClasses).resolveBinding(); - // register all the superClasses up to Object - while (resolvedSuperClass != null && !resolvedSuperClass.getQualifiedName().equals("java.lang.Object")) - { - processTypeBinding(resolvedSuperClass, TypeReferenceLocation.INHERITANCE, cu.getLineNumber(node.getStartPosition()), - cu.getColumnNumber(node.getStartPosition()), node.getLength(), extractDefinitionLine(node.toString())); - resolvedSuperClass = resolvedSuperClass.getSuperclass(); - } - } - } - - return super.visit(node); - } - - /** - * Declaration of the variable within a block - */ - @Override - public boolean visit(VariableDeclarationStatement node) - { - for (int i = 0; i < node.fragments().size(); ++i) - { - String nodeType = node.getType().toString(); - nodeType = resolveClassname(nodeType); - VariableDeclarationFragment frag = (VariableDeclarationFragment) node.fragments().get(i); - this.names.add(frag.getName().getIdentifier()); - this.nameInstance.put(frag.getName().toString(), nodeType.toString()); - } - processType(node.getType(), TypeReferenceLocation.VARIABLE_DECLARATION, - cu.getLineNumber(node.getStartPosition()), - cu.getColumnNumber(node.getStartPosition()), node.getLength(), node.toString()); - return super.visit(node); - } - - @Override - public boolean visit(ImportDeclaration node) - { - String name = node.getName().toString(); - if (node.isOnDemand()) - { - wildcardImports.add(name); - - String[] resolvedNames = this.wildcardImportResolver.resolve(name); - for (String resolvedName : resolvedNames) - { - processImport(resolvedName, cu.getLineNumber(node.getName().getStartPosition()), - cu.getColumnNumber(node.getName().getStartPosition()), node.getName().getLength(), node.toString()); - } - } - else - { - String clzName = StringUtils.substringAfterLast(name, "."); - classNameLookedUp.add(clzName); - classNameToFQCN.put(clzName, name); - processImport(name, cu.getLineNumber(node.getName().getStartPosition()), - cu.getColumnNumber(node.getName().getStartPosition()), node.getName().getLength(), node.toString()); - } - - return super.visit(node); - } - - /*** - * Takes the MethodInvocation, and attempts to resolve the types of objects passed into the method invocation. - */ - public boolean visit(MethodInvocation node) - { - if (!StringUtils.contains(node.toString(), ".")) - { - // it must be a local method. ignore. - return true; - } - List qualifiedInstances = new ArrayList(); - List argumentsQualified = new ArrayList(); - // get qualified arguments of the method - IMethodBinding resolveTypeBinding = node.resolveMethodBinding(); - if (resolveTypeBinding != null) - { - ITypeBinding[] arguments = resolveTypeBinding.getParameterTypes(); - - for (ITypeBinding type : arguments) - { - argumentsQualified.add(type.getQualifiedName()); - } - - // find the interface declaring the method - - if (resolveTypeBinding != null && resolveTypeBinding.getDeclaringClass() != null) - { - ITypeBinding declaringClass = resolveTypeBinding.getDeclaringClass(); - qualifiedInstances.add(declaringClass.getQualifiedName()); - ITypeBinding[] interfaces = declaringClass.getInterfaces(); - // Now find all the implemented interfaces having the method called. - for (ITypeBinding possibleInterface : interfaces) - { - IMethodBinding[] declaredMethods = possibleInterface.getDeclaredMethods(); - if (declaredMethods.length != 0) - { - for (IMethodBinding interfaceMethod : declaredMethods) - { - if (interfaceMethod.getName().equals(node.getName().toString())) - { - - List interfaceMethodArguments = new ArrayList(); - for (ITypeBinding type : interfaceMethod.getParameterTypes()) - { - interfaceMethodArguments.add(type.getQualifiedName()); - } - if (interfaceMethodArguments.equals(argumentsQualified)) - { - qualifiedInstances.add(possibleInterface.getQualifiedName()); - } - } - } - } - - } - } - - } - else - { - String nodeName = StringUtils.removeStart(node.toString(), "this."); - String objRef = StringUtils.substringBefore(nodeName, "." + node.getName().toString()); - if (nameInstance.containsKey(objRef)) - { - objRef = nameInstance.get(objRef); - } - objRef = resolveClassname(objRef); - - // not resolved binding - List arguments = node.arguments(); - for (Expression expression : arguments) - { - ITypeBinding argumentBinding = expression.resolveTypeBinding(); - if (argumentBinding != null) - { - argumentsQualified.add(argumentBinding.getQualifiedName()); - } - else - { - // TODO: Is toString good option? Just a name of the argument will be saved - argumentsQualified.add(expression.toString()); - } - } - qualifiedInstances.add(objRef); - } - - // register all found qualified names for this method invocation - for (String qualifiedInstance : qualifiedInstances) - { - MethodType methodCall = new MethodType(qualifiedInstance, node.getName().toString(), argumentsQualified); - processMethod(methodCall, TypeReferenceLocation.METHOD_CALL, cu.getLineNumber(node.getName().getStartPosition()), - cu.getColumnNumber(node.getName().getStartPosition()), node.getName().getLength(), node.toString()); - } - - return super.visit(node); - } - - @Override - public boolean visit(PackageDeclaration node) - { - return super.visit(node); - } - - @Override - public boolean visit(ClassInstanceCreation node) - { - IMethodBinding constructorBinding = node.resolveConstructorBinding(); - // ITypeBinding resolveTypeBinding = node.resolveTypeBinding(); - String qualifiedClass = ""; - List constructorMethodQualifiedArguments = new ArrayList(); - if (constructorBinding != null && constructorBinding.getDeclaringClass() != null) - { - ITypeBinding declaringClass = constructorBinding.getDeclaringClass(); - qualifiedClass = declaringClass.getQualifiedName(); - for (ITypeBinding type : constructorBinding.getParameterTypes()) - { - constructorMethodQualifiedArguments.add(type.getQualifiedName()); - } - } - - if (constructorMethodQualifiedArguments.isEmpty() && !node.arguments().isEmpty()) - { - List arguments = node.arguments(); - arguments.get(0).resolveTypeBinding(); - for (Expression type : arguments) - { - ITypeBinding argumentBinding = type.resolveTypeBinding(); - if (argumentBinding != null) - { - constructorMethodQualifiedArguments.add(argumentBinding.getQualifiedName()); - } - else - { - List guessedParam = methodParameterGuesser(Collections.singletonList(type)); - constructorMethodQualifiedArguments.addAll(guessedParam); - } - - } - } - - // qualified class may not be resolved in case of anonymous classes - if (qualifiedClass == null || qualifiedClass.equals("")) - { - qualifiedClass = node.getType().toString(); - qualifiedClass = resolveClassname(qualifiedClass); - } - - ConstructorType resolvedConstructor = new ConstructorType(qualifiedClass, constructorMethodQualifiedArguments); - processConstructor(resolvedConstructor, cu.getLineNumber(node.getType().getStartPosition()), - cu.getColumnNumber(node.getType().getStartPosition()), node.getType().getLength(), node.toString()); - - return super.visit(node); + CompilationUnit cu = (CompilationUnit) parser.createAST(null); + return new ASTReferenceResolver(importResolver).analyze(sourceFile.toString(), cu); } - - public static class MethodType - { - private final String qualifiedName; - private final String methodName; - private final List qualifiedParameters; - - public MethodType(String qualifiedName, String methodName, List qualifiedParameters) - { - this.qualifiedName = qualifiedName; - this.methodName = methodName; - - if (qualifiedParameters != null) - { - this.qualifiedParameters = qualifiedParameters; - } - else - { - this.qualifiedParameters = new LinkedList(); - } - } - - public String getMethodName() - { - return methodName; - } - - public String getQualifiedName() - { - return qualifiedName; - } - - public List getQualifiedParameters() - { - return qualifiedParameters; - } - - @Override - public String toString() - { - StringBuilder builder = new StringBuilder(); - builder.append(qualifiedName + "." + methodName); - builder.append("("); - - for (int i = 0, j = qualifiedParameters.size(); i < j; i++) - { - if (i > 0) - { - builder.append(", "); - } - String param = qualifiedParameters.get(i); - builder.append(param); - } - builder.append(")"); - - return builder.toString(); - } - } - - public static class ConstructorType - { - private final String qualifiedName; - private final List qualifiedParameters; - - public ConstructorType(String qualifiedName, List qualifiedParameters) - { - this.qualifiedName = qualifiedName; - if (qualifiedParameters != null) - { - this.qualifiedParameters = qualifiedParameters; - } - else - { - this.qualifiedParameters = new LinkedList(); - } - - } - - public String getQualifiedName() - { - return qualifiedName; - } - - public List getQualifiedParameters() - { - return qualifiedParameters; - } - - @Override - public String toString() - { - StringBuilder builder = new StringBuilder(); - builder.append(qualifiedName); - builder.append("("); - - for (int i = 0, j = qualifiedParameters.size(); i < j; i++) - { - if (i > 0) - { - builder.append(", "); - } - String param = qualifiedParameters.get(i); - builder.append(param); - } - builder.append(")"); - - return builder.toString(); - } - } - - private List methodParameterGuesser(List arguements) - { - List resolvedParams = new ArrayList(arguements.size()); - for (Object o : arguements) - { - if (o instanceof SimpleName) - { - String name = nameInstance.get(o.toString()); - if (name != null) - { - resolvedParams.add(name); - } - else - { - resolvedParams.add("Undefined"); - } - } - else if (o instanceof StringLiteral) - { - resolvedParams.add("java.lang.String"); - } - else if (o instanceof FieldAccess) - { - String field = ((FieldAccess) o).getName().toString(); - if (names.contains(field)) - { - resolvedParams.add(nameInstance.get(field)); - } - else - { - resolvedParams.add("Undefined"); - } - } - else if (o instanceof CastExpression) - { - String type = ((CastExpression) o).getType().toString(); - type = qualifyType(type); - resolvedParams.add(type); - } - else if (o instanceof MethodInvocation) - { - String on = ((MethodInvocation) o).getName().toString(); - if (StringUtils.equals(on, "toString")) - { - if (((MethodInvocation) o).arguments().size() == 0) - { - resolvedParams.add("java.lang.String"); - } - } - else - { - resolvedParams.add("Undefined"); - } - } - else if (o instanceof NumberLiteral) - { - if (StringUtils.endsWith(o.toString(), "L")) - { - resolvedParams.add("long"); - } - else if (StringUtils.endsWith(o.toString(), "f")) - { - resolvedParams.add("float"); - } - else if (StringUtils.endsWith(o.toString(), "d")) - { - resolvedParams.add("double"); - } - else - { - resolvedParams.add("int"); - } - } - else if (o instanceof BooleanLiteral) - { - resolvedParams.add("boolean"); - } - else if (o instanceof ClassInstanceCreation) - { - String nodeType = ((ClassInstanceCreation) o).getType().toString(); - nodeType = resolveClassname(nodeType); - resolvedParams.add(nodeType); - } - else if (o instanceof org.eclipse.jdt.core.dom.CharacterLiteral) - { - resolvedParams.add("char"); - } - else if (o instanceof InfixExpression) - { - String expression = o.toString(); - if (StringUtils.contains(expression, "\"")) - { - resolvedParams.add("java.lang.String"); - } - else - { - resolvedParams.add("Undefined"); - } - } - else - { - resolvedParams.add("Undefined"); - } - } - return resolvedParams; - } - - private String resolveClassname(String sourceClassname) - { - // If the type contains a "." assume that it is fully qualified. - // FIXME - This is a carryover from the original Windup code, and I don't think - // that this assumption is valid. - if (!StringUtils.contains(sourceClassname, ".")) - { - // Check if we have already looked this one up - if (classNameLookedUp.contains(sourceClassname)) - { - // if yes, then just use the looked up name from the map - String qualifiedName = classNameToFQCN.get(sourceClassname); - if (qualifiedName != null) - { - return qualifiedName; - } - else - { - // otherwise, just return the provided name (unchanged) - return sourceClassname; - } - } - else - { - classNameLookedUp.add(sourceClassname); - String resolvedClassName = this.wildcardImportResolver.resolve(this.wildcardImports, sourceClassname); - if (resolvedClassName != null) - { - classNameToFQCN.put(sourceClassname, resolvedClassName); - return resolvedClassName; - } - // nothing was found, so just return the original value - return sourceClassname; - } - } - else - { - return sourceClassname; - } - } - - private String qualifyType(String objRef) - { - // temporarily remove to resolve arrays - objRef = StringUtils.removeEnd(objRef, "[]"); - if (nameInstance.containsKey(objRef)) - { - objRef = nameInstance.get(objRef); - } - objRef = resolveClassname(objRef); - return objRef; - } - } diff --git a/java-ast/addon/src/main/java/org/jboss/windup/ast/java/ASTReferenceResolver.java b/java-ast/addon/src/main/java/org/jboss/windup/ast/java/ASTReferenceResolver.java new file mode 100644 index 0000000000..f703e4b3d0 --- /dev/null +++ b/java-ast/addon/src/main/java/org/jboss/windup/ast/java/ASTReferenceResolver.java @@ -0,0 +1,1104 @@ +package org.jboss.windup.ast.java; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; +import java.util.logging.Logger; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.ArrayInitializer; +import org.eclipse.jdt.core.dom.BooleanLiteral; +import org.eclipse.jdt.core.dom.CastExpression; +import org.eclipse.jdt.core.dom.CharacterLiteral; +import org.eclipse.jdt.core.dom.ClassInstanceCreation; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.Expression; +import org.eclipse.jdt.core.dom.FieldAccess; +import org.eclipse.jdt.core.dom.FieldDeclaration; +import org.eclipse.jdt.core.dom.IMethodBinding; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.ImportDeclaration; +import org.eclipse.jdt.core.dom.InfixExpression; +import org.eclipse.jdt.core.dom.InstanceofExpression; +import org.eclipse.jdt.core.dom.MarkerAnnotation; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.MethodInvocation; +import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.NumberLiteral; +import org.eclipse.jdt.core.dom.PackageDeclaration; +import org.eclipse.jdt.core.dom.QualifiedName; +import org.eclipse.jdt.core.dom.ReturnStatement; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.SimpleType; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.StringLiteral; +import org.eclipse.jdt.core.dom.Type; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.jdt.core.dom.TypeLiteral; +import org.eclipse.jdt.core.dom.VariableDeclarationFragment; +import org.eclipse.jdt.core.dom.VariableDeclarationStatement; +import org.jboss.windup.ast.java.data.ClassReference; +import org.jboss.windup.ast.java.data.TypeReferenceLocation; +import org.jboss.windup.ast.java.data.annotations.AnnotationArrayValue; +import org.jboss.windup.ast.java.data.annotations.AnnotationClassReference; +import org.jboss.windup.ast.java.data.annotations.AnnotationLiteralValue; +import org.jboss.windup.ast.java.data.annotations.AnnotationValue; + +/** + * Provides the ability to parse a Java file and return a {@link List} of {@link ClassReference} objects containing the fully qualified names of all + * of the contained references. + * + * @author Jesse Sightler + * + */ +public class ASTReferenceResolver extends ASTVisitor +{ + private static Logger LOG = Logger.getLogger(ASTReferenceResolver.class.getName()); + + private final WildcardImportResolver wildcardImportResolver; + private String path; + private CompilationUnit compilationUnit; + + /** + * Contains all wildcard imports (import com.example.*) lines from the source file. + * + * These are used for type resolution throughout the class. + */ + private final List wildcardImports = new ArrayList<>(); + + /** + * Indicates that we have already attempted to query the graph for this particular shortname. The shortname will exist here even if no results + * were found. + */ + private final Set classNameLookedUp = new HashSet<>(); + + /** + * Contains a map of class short names (eg, MyClass) to qualified names (eg, com.example.MyClass) + */ + private final Map classNameToFQCN = new HashMap<>(); + /** + * Maintains a set of all variable names that have been resolved + */ + private final Set names = new HashSet(); + + /** + * Maintains a map of nameInstances to fully qualified class names. + */ + private final Map nameInstance = new HashMap(); + + private List classReferences; + + public ASTReferenceResolver(WildcardImportResolver importResolver) + { + this.wildcardImportResolver = importResolver; + } + + public List analyze(String path, CompilationUnit compilationUnit) + { + this.compilationUnit = compilationUnit; + this.path = path; + this.classReferences = new ArrayList<>(); + this.wildcardImports.clear(); + this.classNameLookedUp.clear(); + this.classNameToFQCN.clear(); + this.names.clear(); + this.nameInstance.clear(); + + PackageDeclaration packageDeclaration = this.compilationUnit.getPackage(); + String packageName = packageDeclaration == null ? "" : packageDeclaration.getName().getFullyQualifiedName(); + @SuppressWarnings("unchecked") + List types = this.compilationUnit.types(); + String fqcn = null; + if (!types.isEmpty()) + { + AbstractTypeDeclaration typeDeclaration = (AbstractTypeDeclaration) types.get(0); + String className = typeDeclaration.getName().getFullyQualifiedName(); + + if (packageName.equals("")) + { + fqcn = className; + } + else + { + fqcn = packageName + "." + className; + } + this.classReferences.add(new ClassReference(fqcn, TypeReferenceLocation.TYPE, this.compilationUnit.getLineNumber(typeDeclaration + .getStartPosition()), this.compilationUnit.getColumnNumber(this.compilationUnit.getStartPosition()), this.compilationUnit + .getLength(), extractDefinitionLine(typeDeclaration + .toString()))); + + if (typeDeclaration instanceof TypeDeclaration) + { + Type superclassType = ((TypeDeclaration) typeDeclaration).getSuperclassType(); + ITypeBinding resolveBinding = null; + if (superclassType != null) + { + resolveBinding = superclassType.resolveBinding(); + } + + while (resolveBinding != null) + { + if (superclassType.resolveBinding() != null) + { + this.classReferences.add(new ClassReference(resolveBinding.getQualifiedName(), TypeReferenceLocation.TYPE, + this.compilationUnit + .getLineNumber(typeDeclaration + .getStartPosition()), this.compilationUnit.getColumnNumber(this.compilationUnit + .getStartPosition()), + this.compilationUnit.getLength(), + extractDefinitionLine(typeDeclaration.toString()))); + } + resolveBinding = resolveBinding.getSuperclass(); + } + } + } + + this.names.add("this"); + this.nameInstance.put("this", fqcn); + + this.compilationUnit.accept(this); + return this.classReferences; + } + + private String extractDefinitionLine(String typeDeclaration) + { + String typeLine = ""; + String[] lines = typeDeclaration.split("\n"); + for (String line : lines) + { + typeLine = line; + if (line.contains("{")) + { + break; + } + } + return typeLine; + } + + public List getJavaClassReferences() + { + return this.classReferences; + } + + private void processConstructor(ConstructorType interest, int lineNumber, int columnNumber, int length, String line) + { + String text = interest.toString(); + this.classReferences.add(new ClassReference(text, TypeReferenceLocation.CONSTRUCTOR_CALL, lineNumber, columnNumber, length, + line)); + } + + private void processMethod(MethodType interest, TypeReferenceLocation location, int lineNumber, int columnNumber, int length, String line) + { + String text = interest.toString(); + this.classReferences.add(new ClassReference(text, location, lineNumber, columnNumber, length, line)); + } + + private void processImport(String interest, int lineNumber, int columnNumber, int length, String line) + { + this.classReferences.add(new ClassReference(interest, TypeReferenceLocation.IMPORT, lineNumber, columnNumber, length, line)); + } + + private ClassReference processTypeBinding(ITypeBinding type, TypeReferenceLocation referenceLocation, int lineNumber, int columnNumber, + int length, String line) + { + if (type == null) + return null; + String sourceString = type.getQualifiedName(); + return processTypeAsString(sourceString, referenceLocation, lineNumber, columnNumber, length, line); + } + + /** + * The method determines if the type can be resolved and if not, will try to guess the qualified name using the information from the imports. + */ + private ClassReference processType(Type type, TypeReferenceLocation typeReferenceLocation, int lineNumber, int columnNumber, int length, + String line) + { + if (type == null) + return null; + + ITypeBinding resolveBinding = type.resolveBinding(); + if (resolveBinding == null) + { + return processTypeAsString(resolveClassname(type.toString()), typeReferenceLocation, lineNumber, + columnNumber, length, line); + } + else + { + return processTypeBinding(type.resolveBinding(), typeReferenceLocation, lineNumber, + columnNumber, length, line); + } + + } + + private ClassReference processTypeAsString(String sourceString, TypeReferenceLocation referenceLocation, int lineNumber, + int columnNumber, int length, String line) + { + if (sourceString == null) + return null; + line = line.replaceAll("(\\n)|(\\r)", ""); + ClassReference typeRef = new ClassReference(sourceString, referenceLocation, lineNumber, columnNumber, length, line); + this.classReferences.add(typeRef); + return typeRef; + } + + @Override + public boolean visit(MethodDeclaration node) + { + //register method return type + IMethodBinding resolveBinding = node.resolveBinding(); + ITypeBinding returnType = null; + if (resolveBinding != null) + { + returnType = node.resolveBinding().getReturnType(); + } + if (returnType != null) + { + processTypeBinding(returnType, TypeReferenceLocation.RETURN_TYPE, compilationUnit.getLineNumber(node.getStartPosition()), + compilationUnit.getColumnNumber(node.getStartPosition()), node.getLength(), extractDefinitionLine(node.toString())); + } + else + { + Type methodReturnType = node.getReturnType2(); + processType(methodReturnType, TypeReferenceLocation.RETURN_TYPE, compilationUnit.getLineNumber(node.getStartPosition()), + compilationUnit.getColumnNumber(node.getStartPosition()), node.getLength(), extractDefinitionLine(node.toString())); + } + // register parameters and register them for next processing + List qualifiedArguments = new ArrayList(); + @SuppressWarnings("unchecked") + List parameters = (List) node.parameters(); + if (parameters != null) + { + for (SingleVariableDeclaration type : parameters) + { + this.names.add(type.getName().toString()); + String typeName = type.getType().toString(); + typeName = resolveClassname(typeName); + qualifiedArguments.add(typeName); + this.nameInstance.put(type.getName().toString(), typeName); + processType(type.getType(), TypeReferenceLocation.METHOD_PARAMETER, compilationUnit.getLineNumber(node.getStartPosition()), + compilationUnit.getColumnNumber(node.getStartPosition()), node.getLength(), extractDefinitionLine(node.toString())); + } + } + // register thow declarations + @SuppressWarnings("unchecked") + List throwsTypes = node.thrownExceptionTypes(); + if (throwsTypes != null) + { + for (Type type : throwsTypes) + { + processType(type, TypeReferenceLocation.THROWS_METHOD_DECLARATION, + compilationUnit.getLineNumber(node.getStartPosition()), + compilationUnit.getColumnNumber(type.getStartPosition()), type.getLength(), extractDefinitionLine(node.toString())); + } + } + + // register method declaration + MethodType methodCall = new MethodType(nameInstance.get("this"), node.getName().toString(), qualifiedArguments); + processMethod(methodCall, TypeReferenceLocation.METHOD, compilationUnit.getLineNumber(node.getName().getStartPosition()), + compilationUnit.getColumnNumber(node.getName().getStartPosition()), node.getName().getLength(), + extractDefinitionLine(node.toString())); + return super.visit(node); + } + + @Override + public boolean visit(InstanceofExpression node) + { + Type type = node.getRightOperand(); + processType(type, TypeReferenceLocation.INSTANCE_OF, compilationUnit.getLineNumber(node.getStartPosition()), + compilationUnit.getColumnNumber(type.getStartPosition()), type.getLength(), node.toString()); + + return super.visit(node); + } + + public boolean visit(org.eclipse.jdt.core.dom.ThrowStatement node) + { + if (node.getExpression() instanceof ClassInstanceCreation) + { + ClassInstanceCreation cic = (ClassInstanceCreation) node.getExpression(); + Type type = cic.getType(); + processType(type, TypeReferenceLocation.THROW_STATEMENT, compilationUnit.getLineNumber(node.getStartPosition()), + compilationUnit.getColumnNumber(cic.getStartPosition()), cic.getLength(), node.toString()); + } + + return super.visit(node); + } + + public boolean visit(org.eclipse.jdt.core.dom.CatchClause node) + { + Type catchType = node.getException().getType(); + processType(catchType, TypeReferenceLocation.CATCH_EXCEPTION_STATEMENT, compilationUnit.getLineNumber(node.getStartPosition()), + compilationUnit.getColumnNumber(catchType.getStartPosition()), catchType.getLength(), node.toString()); + + return super.visit(node); + } + + @Override + public boolean visit(ReturnStatement node) + { + if (node.getExpression() instanceof ClassInstanceCreation) + { + ClassInstanceCreation cic = (ClassInstanceCreation) node.getExpression(); + processTypeBinding(cic.getType().resolveBinding(), TypeReferenceLocation.CONSTRUCTOR_CALL, + compilationUnit.getLineNumber(node.getStartPosition()), + compilationUnit.getColumnNumber(cic.getStartPosition()), cic.getLength(), node.toString()); + } + return super.visit(node); + } + + @Override + public boolean visit(FieldDeclaration node) + { + for (int i = 0; i < node.fragments().size(); ++i) + { + String nodeType = node.getType().toString(); + nodeType = resolveClassname(nodeType); + VariableDeclarationFragment frag = (VariableDeclarationFragment) node.fragments().get(i); + frag.resolveBinding(); + this.names.add(frag.getName().getIdentifier()); + this.nameInstance.put(frag.getName().toString(), nodeType.toString()); + + processTypeBinding(node.getType().resolveBinding(), TypeReferenceLocation.FIELD_DECLARATION, + compilationUnit.getLineNumber(node.getStartPosition()), + compilationUnit.getColumnNumber(node.getStartPosition()), node.getLength(), node.toString()); + } + return true; + } + + private AnnotationValue getAnnotationValueForExpression(Expression expression) + { + AnnotationValue value; + if (expression instanceof BooleanLiteral) + value = new AnnotationLiteralValue(boolean.class, ((BooleanLiteral) expression).booleanValue()); + else if (expression instanceof CharacterLiteral) + value = new AnnotationLiteralValue(char.class, ((CharacterLiteral) expression).charValue()); + else if (expression instanceof NumberLiteral) + { + ITypeBinding binding = expression.resolveTypeBinding(); + if (binding == null) + { + value = new AnnotationLiteralValue(String.class, expression.toString()); + } + else + { + value = new AnnotationLiteralValue(resolveLiteralType(binding), ((NumberLiteral) expression).resolveConstantExpressionValue()); + } + } + else if (expression instanceof TypeLiteral) + value = new AnnotationLiteralValue(Class.class, ((TypeLiteral) expression).resolveConstantExpressionValue()); + else if (expression instanceof StringLiteral) + value = new AnnotationLiteralValue(String.class, ((StringLiteral) expression).getLiteralValue()); + else if (expression instanceof NormalAnnotation) + value = processAnnotation((NormalAnnotation) expression); + else if (expression instanceof ArrayInitializer) + { + ArrayInitializer arrayInitializer = (ArrayInitializer) expression; + List arrayValues = new ArrayList<>(arrayInitializer.expressions().size()); + for (Object arrayExpression : arrayInitializer.expressions()) + { + arrayValues.add(getAnnotationValueForExpression((Expression) arrayExpression)); + } + value = new AnnotationArrayValue(arrayValues); + } + else if (expression instanceof QualifiedName) + { + QualifiedName qualifiedName = (QualifiedName) expression; + Object expressionValue = qualifiedName.resolveConstantExpressionValue(); + if (expressionValue == null) + { + value = new AnnotationLiteralValue(String.class, qualifiedName.toString()); + } + else + { + Class expressionType = expressionValue.getClass(); + value = new AnnotationLiteralValue(expressionType, expressionValue); + } + } + else if (expression instanceof CastExpression) + { + CastExpression cast = (CastExpression) expression; + AnnotationValue castExpressionValue = getAnnotationValueForExpression(cast.getExpression()); + if (castExpressionValue instanceof AnnotationLiteralValue) + { + AnnotationLiteralValue literalValue = (AnnotationLiteralValue) castExpressionValue; + ITypeBinding binding = cast.getType().resolveBinding(); + if (binding == null) + { + value = new AnnotationLiteralValue(String.class, literalValue.getLiteralValue()); + } + else + { + Class type = resolveLiteralType(cast.getType().resolveBinding()); + value = new AnnotationLiteralValue(type, literalValue.getLiteralValue()); + } + } + else + { + value = castExpressionValue; + } + } + else if (expression instanceof SimpleName) + { + SimpleName simpleName = (SimpleName) expression; + ITypeBinding binding = simpleName.resolveTypeBinding(); + if (binding == null) + { + value = new AnnotationLiteralValue(String.class, simpleName.toString()); + } + else + { + Object expressionValue = simpleName.resolveConstantExpressionValue(); + Class expressionType = expressionValue == null ? null : expressionValue.getClass(); + value = new AnnotationLiteralValue(expressionType, expressionValue); + } + } + else + { + LOG.warning("Unexpected type: " + expression.getClass().getCanonicalName() + " in file: " + this.path + + " just attempting to use it as a string value"); + value = new AnnotationLiteralValue(String.class, expression == null ? null : expression.toString()); + } + return value; + + } + + /** + * Adds parameters contained in the annotation into the annotation type reference + * + * @param typeRef + * @param node + */ + private void addAnnotationValues(AnnotationClassReference typeRef, Annotation node) + { + Map annotationValueMap = new HashMap<>(); + if (node instanceof SingleMemberAnnotation) + { + SingleMemberAnnotation singleMemberAnnotation = (SingleMemberAnnotation) node; + AnnotationValue value = getAnnotationValueForExpression(singleMemberAnnotation.getValue()); + annotationValueMap.put("value", value); + } + else if (node instanceof NormalAnnotation) + { + @SuppressWarnings("unchecked") + List annotationValues = ((NormalAnnotation) node).values(); + for (MemberValuePair annotationValue : annotationValues) + { + String key = annotationValue.getName().toString(); + Expression expression = annotationValue.getValue(); + AnnotationValue value = getAnnotationValueForExpression(expression); + annotationValueMap.put(key, value); + } + } + typeRef.setAnnotationValues(annotationValueMap); + } + + private Class resolveLiteralType(ITypeBinding binding) + { + switch (binding.getName()) + { + case "byte": + return byte.class; + case "short": + return short.class; + case "int": + return int.class; + case "long": + return long.class; + case "float": + return float.class; + case "double": + return double.class; + case "boolean": + return boolean.class; + case "char": + return char.class; + default: + throw new ASTException("Unrecognized literal type: " + binding.getName()); + } + } + + private AnnotationClassReference processAnnotation(Annotation node) + { + ITypeBinding typeBinding = node.resolveTypeBinding(); + AnnotationClassReference reference; + String qualifiedName; + if (typeBinding != null) + qualifiedName = typeBinding.getQualifiedName(); + else + qualifiedName = resolveClassname(node.getTypeName().toString()); + + reference = new AnnotationClassReference( + qualifiedName, + compilationUnit.getLineNumber(node.getStartPosition()), + compilationUnit.getColumnNumber(node.getStartPosition()), + node.getLength(), + node.toString()); + + addAnnotationValues(reference, node); + + return reference; + } + + @Override + public boolean visit(NormalAnnotation node) + { + AnnotationClassReference reference = processAnnotation(node); + this.classReferences.add(reference); + + // false to avoid recursively processing nested annotations (our code already handles that) + return false; + } + + @Override + public boolean visit(SingleMemberAnnotation node) + { + AnnotationClassReference reference = processAnnotation(node); + this.classReferences.add(reference); + return false; + } + + @Override + public boolean visit(MarkerAnnotation node) + { + AnnotationClassReference reference = processAnnotation(node); + this.classReferences.add(reference); + return false; + } + + public boolean visit(TypeDeclaration node) + { + Object clzInterfaces = node.getStructuralProperty(TypeDeclaration.SUPER_INTERFACE_TYPES_PROPERTY); + Object clzSuperClasses = node.getStructuralProperty(TypeDeclaration.SUPERCLASS_TYPE_PROPERTY); + + if (clzInterfaces != null) + { + if (List.class.isAssignableFrom(clzInterfaces.getClass())) + { + List clzInterfacesList = (List) clzInterfaces; + for (Object clzInterface : clzInterfacesList) + { + if (clzInterface instanceof SimpleType) + { + ITypeBinding resolvedSuperInterface = ((SimpleType) clzInterface).resolveBinding(); + Stack stack = new Stack(); + stack.push(resolvedSuperInterface); + // register all the implemented interfaces (even superinterfaces) + while (!stack.isEmpty()) + { + resolvedSuperInterface = stack.pop(); + processTypeBinding(resolvedSuperInterface, TypeReferenceLocation.IMPLEMENTS_TYPE, + compilationUnit.getLineNumber(node.getStartPosition()), + compilationUnit.getColumnNumber(node.getStartPosition()), node.getLength(), + extractDefinitionLine(node.toString())); + if (resolvedSuperInterface != null) + { + ITypeBinding[] interfaces = resolvedSuperInterface.getInterfaces(); + for (ITypeBinding oneInterface : interfaces) + { + stack.push(oneInterface); + } + } + } + } + } + } + } + if (clzSuperClasses != null) + { + if (clzSuperClasses instanceof SimpleType) + { + ITypeBinding resolvedSuperClass = ((SimpleType) clzSuperClasses).resolveBinding(); + // register all the superClasses up to Object + while (resolvedSuperClass != null && !resolvedSuperClass.getQualifiedName().equals("java.lang.Object")) + { + processTypeBinding(resolvedSuperClass, TypeReferenceLocation.INHERITANCE, compilationUnit.getLineNumber(node.getStartPosition()), + compilationUnit.getColumnNumber(node.getStartPosition()), node.getLength(), extractDefinitionLine(node.toString())); + resolvedSuperClass = resolvedSuperClass.getSuperclass(); + } + } + } + + return super.visit(node); + } + + /** + * Declaration of the variable within a block + */ + @Override + public boolean visit(VariableDeclarationStatement node) + { + for (int i = 0; i < node.fragments().size(); ++i) + { + String nodeType = node.getType().toString(); + nodeType = resolveClassname(nodeType); + VariableDeclarationFragment frag = (VariableDeclarationFragment) node.fragments().get(i); + this.names.add(frag.getName().getIdentifier()); + this.nameInstance.put(frag.getName().toString(), nodeType.toString()); + } + processType(node.getType(), TypeReferenceLocation.VARIABLE_DECLARATION, + compilationUnit.getLineNumber(node.getStartPosition()), + compilationUnit.getColumnNumber(node.getStartPosition()), node.getLength(), node.toString()); + return super.visit(node); + } + + @Override + public boolean visit(ImportDeclaration node) + { + String name = node.getName().toString(); + if (node.isOnDemand()) + { + wildcardImports.add(name); + + String[] resolvedNames = this.wildcardImportResolver.resolve(name); + for (String resolvedName : resolvedNames) + { + processImport(resolvedName, compilationUnit.getLineNumber(node.getName().getStartPosition()), + compilationUnit.getColumnNumber(node.getName().getStartPosition()), node.getName().getLength(), node.toString()); + } + } + else + { + String clzName = StringUtils.substringAfterLast(name, "."); + classNameLookedUp.add(clzName); + classNameToFQCN.put(clzName, name); + processImport(name, compilationUnit.getLineNumber(node.getName().getStartPosition()), + compilationUnit.getColumnNumber(node.getName().getStartPosition()), node.getName().getLength(), node.toString()); + } + + return super.visit(node); + } + + /*** + * Takes the MethodInvocation, and attempts to resolve the types of objects passed into the method invocation. + */ + public boolean visit(MethodInvocation node) + { + if (!StringUtils.contains(node.toString(), ".")) + { + // it must be a local method. ignore. + return true; + } + List qualifiedInstances = new ArrayList(); + List argumentsQualified = new ArrayList(); + // get qualified arguments of the method + IMethodBinding resolveTypeBinding = node.resolveMethodBinding(); + if (resolveTypeBinding != null) + { + ITypeBinding[] arguments = resolveTypeBinding.getParameterTypes(); + + for (ITypeBinding type : arguments) + { + argumentsQualified.add(type.getQualifiedName()); + } + + // find the interface declaring the method + + if (resolveTypeBinding != null && resolveTypeBinding.getDeclaringClass() != null) + { + ITypeBinding declaringClass = resolveTypeBinding.getDeclaringClass(); + qualifiedInstances.add(declaringClass.getQualifiedName()); + ITypeBinding[] interfaces = declaringClass.getInterfaces(); + // Now find all the implemented interfaces having the method called. + for (ITypeBinding possibleInterface : interfaces) + { + IMethodBinding[] declaredMethods = possibleInterface.getDeclaredMethods(); + if (declaredMethods.length != 0) + { + for (IMethodBinding interfaceMethod : declaredMethods) + { + if (interfaceMethod.getName().equals(node.getName().toString())) + { + + List interfaceMethodArguments = new ArrayList(); + for (ITypeBinding type : interfaceMethod.getParameterTypes()) + { + interfaceMethodArguments.add(type.getQualifiedName()); + } + if (interfaceMethodArguments.equals(argumentsQualified)) + { + qualifiedInstances.add(possibleInterface.getQualifiedName()); + } + } + } + } + + } + } + + } + else + { + String nodeName = StringUtils.removeStart(node.toString(), "this."); + String objRef = StringUtils.substringBefore(nodeName, "." + node.getName().toString()); + if (nameInstance.containsKey(objRef)) + { + objRef = nameInstance.get(objRef); + } + objRef = resolveClassname(objRef); + + // not resolved binding + List arguments = node.arguments(); + for (Expression expression : arguments) + { + ITypeBinding argumentBinding = expression.resolveTypeBinding(); + if (argumentBinding != null) + { + argumentsQualified.add(argumentBinding.getQualifiedName()); + } + else + { + // TODO: Is toString good option? Just a name of the argument will be saved + argumentsQualified.add(expression.toString()); + } + } + qualifiedInstances.add(objRef); + } + + // register all found qualified names for this method invocation + for (String qualifiedInstance : qualifiedInstances) + { + MethodType methodCall = new MethodType(qualifiedInstance, node.getName().toString(), argumentsQualified); + processMethod(methodCall, TypeReferenceLocation.METHOD_CALL, compilationUnit.getLineNumber(node.getName().getStartPosition()), + compilationUnit.getColumnNumber(node.getName().getStartPosition()), node.getName().getLength(), node.toString()); + } + + return super.visit(node); + } + + @Override + public boolean visit(PackageDeclaration node) + { + return super.visit(node); + } + + @Override + public boolean visit(ClassInstanceCreation node) + { + IMethodBinding constructorBinding = node.resolveConstructorBinding(); + // ITypeBinding resolveTypeBinding = node.resolveTypeBinding(); + String qualifiedClass = ""; + List constructorMethodQualifiedArguments = new ArrayList(); + if (constructorBinding != null && constructorBinding.getDeclaringClass() != null) + { + ITypeBinding declaringClass = constructorBinding.getDeclaringClass(); + qualifiedClass = declaringClass.getQualifiedName(); + for (ITypeBinding type : constructorBinding.getParameterTypes()) + { + constructorMethodQualifiedArguments.add(type.getQualifiedName()); + } + } + + if (constructorMethodQualifiedArguments.isEmpty() && !node.arguments().isEmpty()) + { + List arguments = node.arguments(); + arguments.get(0).resolveTypeBinding(); + for (Expression type : arguments) + { + ITypeBinding argumentBinding = type.resolveTypeBinding(); + if (argumentBinding != null) + { + constructorMethodQualifiedArguments.add(argumentBinding.getQualifiedName()); + } + else + { + List guessedParam = methodParameterGuesser(Collections.singletonList(type)); + constructorMethodQualifiedArguments.addAll(guessedParam); + } + + } + } + + // qualified class may not be resolved in case of anonymous classes + if (qualifiedClass == null || qualifiedClass.equals("")) + { + qualifiedClass = node.getType().toString(); + qualifiedClass = resolveClassname(qualifiedClass); + } + + ConstructorType resolvedConstructor = new ConstructorType(qualifiedClass, constructorMethodQualifiedArguments); + processConstructor(resolvedConstructor, compilationUnit.getLineNumber(node.getType().getStartPosition()), + compilationUnit.getColumnNumber(node.getType().getStartPosition()), node.getType().getLength(), node.toString()); + + return super.visit(node); + } + + public static class MethodType + { + private final String qualifiedName; + private final String methodName; + private final List qualifiedParameters; + + public MethodType(String qualifiedName, String methodName, List qualifiedParameters) + { + this.qualifiedName = qualifiedName; + this.methodName = methodName; + + if (qualifiedParameters != null) + { + this.qualifiedParameters = qualifiedParameters; + } + else + { + this.qualifiedParameters = new LinkedList(); + } + } + + public String getMethodName() + { + return methodName; + } + + public String getQualifiedName() + { + return qualifiedName; + } + + public List getQualifiedParameters() + { + return qualifiedParameters; + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append(qualifiedName + "." + methodName); + builder.append("("); + + for (int i = 0, j = qualifiedParameters.size(); i < j; i++) + { + if (i > 0) + { + builder.append(", "); + } + String param = qualifiedParameters.get(i); + builder.append(param); + } + builder.append(")"); + + return builder.toString(); + } + } + + public static class ConstructorType + { + private final String qualifiedName; + private final List qualifiedParameters; + + public ConstructorType(String qualifiedName, List qualifiedParameters) + { + this.qualifiedName = qualifiedName; + if (qualifiedParameters != null) + { + this.qualifiedParameters = qualifiedParameters; + } + else + { + this.qualifiedParameters = new LinkedList(); + } + + } + + public String getQualifiedName() + { + return qualifiedName; + } + + public List getQualifiedParameters() + { + return qualifiedParameters; + } + + @Override + public String toString() + { + StringBuilder builder = new StringBuilder(); + builder.append(qualifiedName); + builder.append("("); + + for (int i = 0, j = qualifiedParameters.size(); i < j; i++) + { + if (i > 0) + { + builder.append(", "); + } + String param = qualifiedParameters.get(i); + builder.append(param); + } + builder.append(")"); + + return builder.toString(); + } + } + + private List methodParameterGuesser(List arguements) + { + List resolvedParams = new ArrayList(arguements.size()); + for (Object o : arguements) + { + if (o instanceof SimpleName) + { + String name = nameInstance.get(o.toString()); + if (name != null) + { + resolvedParams.add(name); + } + else + { + resolvedParams.add("Undefined"); + } + } + else if (o instanceof StringLiteral) + { + resolvedParams.add("java.lang.String"); + } + else if (o instanceof FieldAccess) + { + String field = ((FieldAccess) o).getName().toString(); + if (names.contains(field)) + { + resolvedParams.add(nameInstance.get(field)); + } + else + { + resolvedParams.add("Undefined"); + } + } + else if (o instanceof CastExpression) + { + String type = ((CastExpression) o).getType().toString(); + type = qualifyType(type); + resolvedParams.add(type); + } + else if (o instanceof MethodInvocation) + { + String on = ((MethodInvocation) o).getName().toString(); + if (StringUtils.equals(on, "toString")) + { + if (((MethodInvocation) o).arguments().size() == 0) + { + resolvedParams.add("java.lang.String"); + } + } + else + { + resolvedParams.add("Undefined"); + } + } + else if (o instanceof NumberLiteral) + { + if (StringUtils.endsWith(o.toString(), "L")) + { + resolvedParams.add("long"); + } + else if (StringUtils.endsWith(o.toString(), "f")) + { + resolvedParams.add("float"); + } + else if (StringUtils.endsWith(o.toString(), "d")) + { + resolvedParams.add("double"); + } + else + { + resolvedParams.add("int"); + } + } + else if (o instanceof BooleanLiteral) + { + resolvedParams.add("boolean"); + } + else if (o instanceof ClassInstanceCreation) + { + String nodeType = ((ClassInstanceCreation) o).getType().toString(); + nodeType = resolveClassname(nodeType); + resolvedParams.add(nodeType); + } + else if (o instanceof org.eclipse.jdt.core.dom.CharacterLiteral) + { + resolvedParams.add("char"); + } + else if (o instanceof InfixExpression) + { + String expression = o.toString(); + if (StringUtils.contains(expression, "\"")) + { + resolvedParams.add("java.lang.String"); + } + else + { + resolvedParams.add("Undefined"); + } + } + else + { + resolvedParams.add("Undefined"); + } + } + return resolvedParams; + } + + private String resolveClassname(String sourceClassname) + { + // If the type contains a "." assume that it is fully qualified. + // FIXME - This is a carryover from the original Windup code, and I don't think + // that this assumption is valid. + if (!StringUtils.contains(sourceClassname, ".")) + { + // Check if we have already looked this one up + if (classNameLookedUp.contains(sourceClassname)) + { + // if yes, then just use the looked up name from the map + String qualifiedName = classNameToFQCN.get(sourceClassname); + if (qualifiedName != null) + { + return qualifiedName; + } + else + { + // otherwise, just return the provided name (unchanged) + return sourceClassname; + } + } + else + { + classNameLookedUp.add(sourceClassname); + String resolvedClassName = this.wildcardImportResolver.resolve(this.wildcardImports, sourceClassname); + if (resolvedClassName != null) + { + classNameToFQCN.put(sourceClassname, resolvedClassName); + return resolvedClassName; + } + // nothing was found, so just return the original value + return sourceClassname; + } + } + else + { + return sourceClassname; + } + } + + private String qualifyType(String objRef) + { + // temporarily remove to resolve arrays + objRef = StringUtils.removeEnd(objRef, "[]"); + if (nameInstance.containsKey(objRef)) + { + objRef = nameInstance.get(objRef); + } + objRef = resolveClassname(objRef); + return objRef; + } + +} diff --git a/java-ast/addon/src/main/java/org/jboss/windup/ast/java/BatchASTListener.java b/java-ast/addon/src/main/java/org/jboss/windup/ast/java/BatchASTListener.java new file mode 100644 index 0000000000..7edde611ab --- /dev/null +++ b/java-ast/addon/src/main/java/org/jboss/windup/ast/java/BatchASTListener.java @@ -0,0 +1,24 @@ +package org.jboss.windup.ast.java; + +import java.nio.file.Path; +import java.util.List; + +import org.jboss.windup.ast.java.data.ClassReference; + +/** + * Provides a callback to indicate that processing has completed for a particular file. + * + * @author Jesse Sightler + */ +public interface BatchASTListener +{ + /** + * Called to indicate that processing has completed on the specified file. + */ + void processed(Path filePath, List classReferences); + + /** + * Called on parse failures. Note that some failures will not trigger this method, due to limitations of JDT's batch API. + */ + void failed(Path filePath, Throwable cause); +} diff --git a/java-ast/addon/src/main/java/org/jboss/windup/ast/java/BatchASTProcessor.java b/java-ast/addon/src/main/java/org/jboss/windup/ast/java/BatchASTProcessor.java new file mode 100644 index 0000000000..87138feb78 --- /dev/null +++ b/java-ast/addon/src/main/java/org/jboss/windup/ast/java/BatchASTProcessor.java @@ -0,0 +1,91 @@ +package org.jboss.windup.ast.java; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.FileASTRequestor; +import org.jboss.windup.ast.java.data.ClassReference; + +/** + * Processes multiple files at a time in order to improve performance. + * + * @author Jesse Sightler + */ +public class BatchASTProcessor +{ + private static final int BATCH_SIZE = 1000; + + /** + * Process the given batch of files and pass the results back to the listener as each file is processed. + */ + public static void analyze(final BatchASTListener listener, WildcardImportResolver importResolver, Set libraryPaths, + Set sourcePaths, Iterable sourceFiles) + { + ASTParser parser = ASTParser.newParser(AST.JLS8); + + String[] encodings = null; + String[] bindingKeys = new String[0]; + + final ASTReferenceResolver referenceResolver = new ASTReferenceResolver(importResolver); + + FileASTRequestor requestor = new FileASTRequestor() + { + @Override + public void acceptAST(String sourcePath, CompilationUnit ast) + { + try + { + super.acceptAST(sourcePath, ast); + List references = referenceResolver.analyze(sourcePath, ast); + listener.processed(Paths.get(sourcePath), references); + } + catch (Throwable t) + { + listener.failed(Paths.get(sourcePath), t); + } + } + }; + + Iterator pathIterator = sourceFiles.iterator(); + List batch = new ArrayList<>(BATCH_SIZE); + while (pathIterator.hasNext()) + { + batch.add(pathIterator.next().toAbsolutePath().toString()); + + if (batch.size() == BATCH_SIZE || !pathIterator.hasNext()) + { + parser.setEnvironment(libraryPaths.toArray(new String[libraryPaths.size()]), sourcePaths.toArray(new String[sourcePaths.size()]), + null, true); + parser.setBindingsRecovery(false); + parser.setResolveBindings(true); + + Map options = JavaCore.getOptions(); + JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options); + + // these options seem to slightly reduce the number of times that JDT aborts on compilation errors + options.put(JavaCore.CORE_INCOMPLETE_CLASSPATH, "warning"); + options.put(JavaCore.COMPILER_PB_ENUM_IDENTIFIER, "warning"); + options.put(JavaCore.COMPILER_PB_FORBIDDEN_REFERENCE, "warning"); + options.put(JavaCore.CORE_CIRCULAR_CLASSPATH, "warning"); + options.put(JavaCore.COMPILER_PB_ASSERT_IDENTIFIER, "warning"); + options.put(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION, "warning"); + options.put(JavaCore.CORE_JAVA_BUILD_INVALID_CLASSPATH, "ignore"); + options.put(JavaCore.COMPILER_PB_NULL_ANNOTATION_INFERENCE_CONFLICT, "warning"); + options.put(JavaCore.CORE_OUTPUT_LOCATION_OVERLAPPING_ANOTHER_SOURCE, "warning"); + + parser.setCompilerOptions(options); + parser.createASTs(batch.toArray(new String[batch.size()]), encodings, bindingKeys, requestor, null); + batch.clear(); + } + } + } +} diff --git a/java-ast/addon/src/main/java/org/jboss/windup/ast/java/WildcardImportResolver.java b/java-ast/addon/src/main/java/org/jboss/windup/ast/java/WildcardImportResolver.java index 1d3a094def..246528175a 100644 --- a/java-ast/addon/src/main/java/org/jboss/windup/ast/java/WildcardImportResolver.java +++ b/java-ast/addon/src/main/java/org/jboss/windup/ast/java/WildcardImportResolver.java @@ -2,9 +2,18 @@ import java.util.List; +/** + * Provides a pluggable lookup mechanism for resolving wildcard imports. + */ public interface WildcardImportResolver { + /** + * Resolve the given name based upon the provided wildcard imports. + */ String resolve(List wildcardImports, String name); + /** + * Find all potential imports for this wildcard package name. + */ String[] resolve(String wildcardImportPackageName); } diff --git a/java-ast/addon/src/main/java/org/jboss/windup/ast/java/data/ClassReference.java b/java-ast/addon/src/main/java/org/jboss/windup/ast/java/data/ClassReference.java index e8fb7e42bc..2fac9abc89 100644 --- a/java-ast/addon/src/main/java/org/jboss/windup/ast/java/data/ClassReference.java +++ b/java-ast/addon/src/main/java/org/jboss/windup/ast/java/data/ClassReference.java @@ -30,11 +30,17 @@ public ClassReference(String qualifiedName, TypeReferenceLocation location, int this.line= line; } + /** + * Contains the raw text represented by this reference (class names are not resolved). + */ public String getLine() { return line; } + /** + * Contains the raw text represented by this reference (class names are not resolved). + */ public void setLine(String line) { this.line = line; diff --git a/java-ast/addon/src/main/java/org/jboss/windup/ast/java/data/ClassReferences.java b/java-ast/addon/src/main/java/org/jboss/windup/ast/java/data/ClassReferences.java deleted file mode 100644 index 880542a2bf..0000000000 --- a/java-ast/addon/src/main/java/org/jboss/windup/ast/java/data/ClassReferences.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.jboss.windup.ast.java.data; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Contains a list of {@link ClassReference}s, which themselves describe the contents of the Java source file, and all of the things that it - * directly references. - * - * @author Jesse Sightler - * - */ -public class ClassReferences -{ - private List references = new ArrayList<>(); - - /** - * Adds a {@link ClassReference} - */ - public void addReference(ClassReference reference) - { - this.references.add(reference); - } - - /** - * Gets the list of all {@link ClassReference}s - */ - public List getReferences() - { - return Collections.unmodifiableList(this.references); - } -} diff --git a/java-ast/tests/src/test/java/org/jboss/windup/ast/java/test/JavaASTProcessorTest.java b/java-ast/tests/src/test/java/org/jboss/windup/ast/java/test/JavaASTReferenceResolverTest.java similarity index 63% rename from java-ast/tests/src/test/java/org/jboss/windup/ast/java/test/JavaASTProcessorTest.java rename to java-ast/tests/src/test/java/org/jboss/windup/ast/java/test/JavaASTReferenceResolverTest.java index 8f43e5f2f3..25ff6515ad 100644 --- a/java-ast/tests/src/test/java/org/jboss/windup/ast/java/test/JavaASTProcessorTest.java +++ b/java-ast/tests/src/test/java/org/jboss/windup/ast/java/test/JavaASTReferenceResolverTest.java @@ -1,71 +1,71 @@ package org.jboss.windup.ast.java.test; import java.nio.file.Paths; +import java.util.List; import org.jboss.arquillian.junit.Arquillian; import org.jboss.windup.ast.java.ASTProcessor; import org.jboss.windup.ast.java.data.ClassReference; -import org.jboss.windup.ast.java.data.ClassReferences; import org.jboss.windup.ast.java.data.TypeReferenceLocation; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(Arquillian.class) -public class JavaASTProcessorTest extends AbstractJavaASTTest +public class JavaASTReferenceResolverTest extends AbstractJavaASTTest { @Test public void testHelloWorld() { - ClassReferences references = ASTProcessor.analyzeJavaFile(getLibraryPaths(), getSourcePaths(), - Paths.get("src/test/resources/testclasses/helloworld/HelloWorld.java")); + List references = ASTProcessor.analyze(getLibraryPaths(), getSourcePaths(), + Paths.get("src/test/resources/testclasses/helloworld/HelloWorld.java")); - for (ClassReference reference : references.getReferences()) + for (ClassReference reference : references) { System.out.println("Reference: " + reference); } - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("testclasses.helloworld.HelloWorld", TypeReferenceLocation.TYPE, 3, 0, 174, "public class HelloWorld {"))); - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("testclasses.helloworld.HelloWorld.main(String[])", TypeReferenceLocation.METHOD, 5, 23, 4, "public static void main(String[] argv) throws Exception {"))); - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("void", TypeReferenceLocation.RETURN_TYPE, 5, 4, 108, "public static void main(String[] argv) throws Exception {"))); - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("java.lang.String[]", TypeReferenceLocation.METHOD_PARAMETER, 5, 4, 108, "public static void main(String[] argv) throws Exception {"))); - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("java.lang.Exception", TypeReferenceLocation.THROWS_METHOD_DECLARATION, 5, 51, 9, "public static void main(String[] argv) throws Exception {"))); - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("java.io.PrintStream.println(java.lang.String)", TypeReferenceLocation.METHOD_CALL, 6, 19, 7, "System.out.println(\"Hello world!\")"))); - Assert.assertEquals(6, references.getReferences().size()); + Assert.assertEquals(6, references.size()); } @Test public void testSimpleMain() { - ClassReferences references = ASTProcessor.analyzeJavaFile(getLibraryPaths(), getSourcePaths(), - Paths.get("src/test/resources/testclasses/simple/Main.java")); + List references = ASTProcessor.analyze(getLibraryPaths(), getSourcePaths(), + Paths.get("src/test/resources/testclasses/simple/Main.java")); - for (ClassReference reference : references.getReferences()) + for (ClassReference reference : references) { System.out.println("Reference: " + reference); } - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("testclasses.simple.MyBClass", TypeReferenceLocation.VARIABLE_DECLARATION, 9, 8, 28, "MyBClass b=new MyBClass();"))); - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("testclasses.simple.MyAClass.interfaceMethod()", TypeReferenceLocation.METHOD_CALL, 12, 26, 15, "c.returnAnother().interfaceMethod()"))); - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("testclasses.simple.SomeInterface.interfaceMethod()", TypeReferenceLocation.METHOD_CALL, 12, 26, 15, "c.returnAnother().interfaceMethod()"))); - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("testclasses.simple.ClassReturningAnother.returnAnother()", TypeReferenceLocation.METHOD_CALL, 12, 10, 13, "c.returnAnother()"))); } @@ -73,18 +73,18 @@ public void testSimpleMain() @Test public void testMyBClass() { - ClassReferences references = ASTProcessor.analyzeJavaFile(getLibraryPaths(), getSourcePaths(), - Paths.get("src/test/resources/testclasses/simple/MyBClass.java")); + List references = ASTProcessor.analyze(getLibraryPaths(), getSourcePaths(), + Paths.get("src/test/resources/testclasses/simple/MyBClass.java")); - for (ClassReference reference : references.getReferences()) + for (ClassReference reference : references) { System.out.println("Reference: " + reference); } - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("testclasses.simple.MyBClass", TypeReferenceLocation.TYPE, 4, 0, 161, "public class MyBClass extends MyAClass {"))); - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("testclasses.simple.MyAClass", TypeReferenceLocation.TYPE, 4, 0, 161, "public class MyBClass extends MyAClass {"))); } @@ -92,41 +92,42 @@ public void testMyBClass() @Test public void testMyAClass() { - ClassReferences references = ASTProcessor.analyzeJavaFile(getLibraryPaths(), getSourcePaths(), - Paths.get("src/test/resources/testclasses/simple/MyAClass.java")); + List references = ASTProcessor.analyze(getLibraryPaths(), getSourcePaths(), + Paths.get("src/test/resources/testclasses/simple/MyAClass.java")); - for (ClassReference reference : references.getReferences()) + for (ClassReference reference : references) { System.out.println("Reference: " + reference); } - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("testclasses.simple.MyAClass", TypeReferenceLocation.TYPE, 3, 0, 128, "public class MyAClass implements SomeInterface {"))); - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("testclasses.simple.SomeInterface", TypeReferenceLocation.IMPLEMENTS_TYPE, 3, 0, 99, "public class MyAClass implements SomeInterface {"))); } + @Test public void testJavaLangReferences() { - ClassReferences references = ASTProcessor.analyzeJavaFile(getLibraryPaths(), getSourcePaths(), - Paths.get("src/test/resources/testclasses/javalang/JavaLangReferences.java")); + List references = ASTProcessor.analyze(getLibraryPaths(), getSourcePaths(), + Paths.get("src/test/resources/testclasses/javalang/JavaLangReferences.java")); - for (ClassReference reference : references.getReferences()) + for (ClassReference reference : references) { System.out.println("Reference: " + reference); } - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("testclasses.javalang.JavaLangReferences", TypeReferenceLocation.TYPE, 3, 0, 191, "public class JavaLangReferences {"))); - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("void", TypeReferenceLocation.RETURN_TYPE, 5, 4, 119, "public void someMethod(){"))); - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("java.lang.String", TypeReferenceLocation.VARIABLE_DECLARATION, 7, 8, 39, "String a=\"This is an example String\";"))); - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("java.lang.String", TypeReferenceLocation.VARIABLE_DECLARATION, 8, 8, 26, "String b=a.substring(1);"))); - Assert.assertTrue(references.getReferences().contains( + Assert.assertTrue(references.contains( new ClassReference("java.lang.String.substring(int)", TypeReferenceLocation.METHOD_CALL, 8, 21, 9, "a.substring(1)"))); } diff --git a/java-ast/tests/src/test/java/org/jboss/windup/ast/java/test/JavaAnnotationScanningTest.java b/java-ast/tests/src/test/java/org/jboss/windup/ast/java/test/JavaAnnotationScanningTest.java index 274dbed3ac..de342e7713 100644 --- a/java-ast/tests/src/test/java/org/jboss/windup/ast/java/test/JavaAnnotationScanningTest.java +++ b/java-ast/tests/src/test/java/org/jboss/windup/ast/java/test/JavaAnnotationScanningTest.java @@ -1,11 +1,11 @@ package org.jboss.windup.ast.java.test; import java.nio.file.Paths; +import java.util.List; import org.jboss.arquillian.junit.Arquillian; import org.jboss.windup.ast.java.ASTProcessor; import org.jboss.windup.ast.java.data.ClassReference; -import org.jboss.windup.ast.java.data.ClassReferences; import org.jboss.windup.ast.java.data.annotations.AnnotationArrayValue; import org.jboss.windup.ast.java.data.annotations.AnnotationClassReference; import org.jboss.windup.ast.java.data.annotations.AnnotationLiteralValue; @@ -21,12 +21,12 @@ public class JavaAnnotationScanningTest extends AbstractJavaASTTest @Test public void testSimpleAnnotatedClass() { - ClassReferences references = ASTProcessor.analyzeJavaFile(getLibraryPaths(), getSourcePaths(), + List references = ASTProcessor.analyze(getLibraryPaths(), getSourcePaths(), Paths.get("src/test/resources/testclasses/annotations/basic/SimpleAnnotatedClass.java")); boolean foundSimpleAnnotation = false; boolean foundSingleMemberAnnotation = false; - for (ClassReference reference : references.getReferences()) + for (ClassReference reference : references) { System.out.println("Reference: " + reference); if (reference instanceof AnnotationClassReference) @@ -55,11 +55,11 @@ else if (reference.getQualifiedName().equals("testclasses.annotations.basic.Simp @Test public void testComplexAnnotatedClass() { - ClassReferences references = ASTProcessor.analyzeJavaFile(getLibraryPaths(), getSourcePaths(), + List references = ASTProcessor.analyze(getLibraryPaths(), getSourcePaths(), Paths.get("src/test/resources/testclasses/annotations/complex/ComplexAnnotatedClass.java")); boolean foundAnnotation = false; - for (ClassReference reference : references.getReferences()) + for (ClassReference reference : references) { System.out.println("Reference: " + reference); if (reference instanceof AnnotationClassReference) diff --git a/rules-java/api/src/main/java/org/jboss/windup/rules/apps/java/scan/provider/AnalyzeJavaFilesRuleProvider.java b/rules-java/api/src/main/java/org/jboss/windup/rules/apps/java/scan/provider/AnalyzeJavaFilesRuleProvider.java index e26f244281..2e6eb2881b 100644 --- a/rules-java/api/src/main/java/org/jboss/windup/rules/apps/java/scan/provider/AnalyzeJavaFilesRuleProvider.java +++ b/rules-java/api/src/main/java/org/jboss/windup/rules/apps/java/scan/provider/AnalyzeJavaFilesRuleProvider.java @@ -1,18 +1,24 @@ package org.jboss.windup.rules.apps.java.scan.provider; -import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import javax.inject.Inject; import org.jboss.windup.ast.java.ASTProcessor; +import org.jboss.windup.ast.java.BatchASTListener; +import org.jboss.windup.ast.java.BatchASTProcessor; import org.jboss.windup.ast.java.data.ClassReference; -import org.jboss.windup.ast.java.data.ClassReferences; import org.jboss.windup.ast.java.data.TypeReferenceLocation; import org.jboss.windup.ast.java.data.annotations.AnnotationArrayValue; import org.jboss.windup.ast.java.data.annotations.AnnotationClassReference; @@ -21,13 +27,8 @@ import org.jboss.windup.config.AbstractRuleProvider; import org.jboss.windup.config.GraphRewrite; import org.jboss.windup.config.metadata.MetadataBuilder; -import org.jboss.windup.config.operation.Commit; import org.jboss.windup.config.operation.GraphOperation; -import org.jboss.windup.config.operation.Iteration; -import org.jboss.windup.config.operation.IterationProgress; -import org.jboss.windup.config.operation.iteration.AbstractIterationOperation; import org.jboss.windup.config.phase.InitialAnalysisPhase; -import org.jboss.windup.config.query.Query; import org.jboss.windup.graph.GraphContext; import org.jboss.windup.graph.model.resource.FileModel; import org.jboss.windup.graph.service.GraphService; @@ -56,9 +57,10 @@ */ public class AnalyzeJavaFilesRuleProvider extends AbstractRuleProvider { + public static final int COMMIT_INTERVAL = 750; + public static final int LOG_INTERVAL = 250; private static Logger LOG = Logging.get(AnalyzeJavaFilesRuleProvider.class); - private ASTProcessor processor; @Inject private WindupWildcardImportResolver importResolver; @@ -75,111 +77,155 @@ public Configuration getConfiguration(GraphContext context) { return ConfigurationBuilder.begin() .addRule() - .when(Query.fromType(JavaSourceFileModel.class)) - .perform( - Iteration.over().perform( - new ParseSourceOperation() - .and(IterationProgress.monitoring("Analyzed Java File: ", 250)) - .and(Commit.every(10)) - ).endIteration() - .and(new ClearClasspathCache()) - ); - } - // @formatter:on + .perform(new ParseSourceOperation()); - private final class ClearClasspathCache extends GraphOperation - { - @Override - public void perform(GraphRewrite event, EvaluationContext context) - { - processor = null; - } + + //.and(IterationProgress.monitoring("Analyzed Java File: ", 250)) + //.and(Commit.every(10)) } + // @formatter:on - private final class ParseSourceOperation extends AbstractIterationOperation + private final class ParseSourceOperation extends GraphOperation { - public void perform(GraphRewrite event, EvaluationContext context, JavaSourceFileModel payload) + public void perform(final GraphRewrite event, EvaluationContext context) { ExecutionStatistics.get().begin("AnalyzeJavaFilesRuleProvider.analyzeFile"); try { WindupJavaConfigurationService windupJavaConfigurationService = new WindupJavaConfigurationService( event.getGraphContext()); - if (!windupJavaConfigurationService.shouldScanPackage(payload.getPackageName())) + + GraphService service = new GraphService<>(event.getGraphContext(), JavaSourceFileModel.class); + Iterable allJavaSourceModels = service.findAll(); + + final Set allSourceFiles = new TreeSet<>(); + final Map sourcePathToFileModel = new TreeMap<>(); + + Set sourcePaths = new HashSet<>(); + for (JavaSourceFileModel javaFile : allJavaSourceModels) { - // should not analyze this one, skip it - return; + FileModel rootSourceFolder = javaFile.getRootSourceFolder(); + if (rootSourceFolder != null) + { + sourcePaths.add(rootSourceFolder.getFilePath()); + } + + if (windupJavaConfigurationService.shouldScanPackage(javaFile.getPackageName())) + { + Path path = Paths.get(javaFile.getFilePath()); + allSourceFiles.add(path); + sourcePathToFileModel.put(path, javaFile); + } } - GraphService service = new GraphService(event.getGraphContext(), JavaSourceFileModel.class); - if (processor == null) + GraphService libraryService = new GraphService(event.getGraphContext(), JarArchiveModel.class); + + Iterable libraries = libraryService.findAll(); + Set libraryPaths = new HashSet<>(); + for (JarArchiveModel library : libraries) { - Iterable allJavaSourceModels = service.findAll(); - Set sourcePaths = new HashSet<>(); - for (JavaSourceFileModel javaFile : allJavaSourceModels) + if (library.getUnzippedDirectory() != null) { - FileModel rootSourceFolder = javaFile.getRootSourceFolder(); - if (rootSourceFolder != null) - { - sourcePaths.add(rootSourceFolder.getFilePath()); - } + libraryPaths.add(library.getUnzippedDirectory().getFilePath()); } + else + { + libraryPaths.add(library.getFilePath()); + } + } + ExecutionStatistics.get().begin("AnalyzeJavaFilesRuleProvider.parseFiles"); + try + { + WindupWildcardImportResolver.setGraphContext(event.getGraphContext()); + final int totalToProcess = allSourceFiles.size(); + final AtomicInteger numberProcessed = new AtomicInteger(0); - GraphService libraryService = new GraphService(event.getGraphContext(), JarArchiveModel.class); - - Iterable libraries = libraryService.findAll(); - Set libraryPaths = new HashSet<>(); - for (JarArchiveModel library : libraries) + final Set processedPaths = new HashSet<>(allSourceFiles.size()); + BatchASTListener listener = new BatchASTListener() { - if (library.getUnzippedDirectory() != null) + @Override + public void processed(Path filePath, List references) { - libraryPaths.add(library.getUnzippedDirectory().getFilePath()); + processedPaths.add(filePath); + processReferences(event.getGraphContext(), sourcePathToFileModel, filePath, references); + + numberProcessed.incrementAndGet(); + if (numberProcessed.get() % LOG_INTERVAL == 0) + { + LOG.info("Analyzed Java File: " + numberProcessed.get() + " / " + totalToProcess); + } + + if (numberProcessed.get() % COMMIT_INTERVAL == 0) + { + event.getGraphContext().getGraph().getBaseGraph().commit(); + } } - else + + @Override + public void failed(Path filePath, Throwable cause) { - libraryPaths.add(library.getFilePath()); + LOG.log(Level.WARNING, "Failed to process: " + filePath + " due to: " + cause.getMessage(), cause); + ClassificationService classificationService = new ClassificationService(event.getGraphContext()); + JavaSourceFileModel sourceFileModel = sourcePathToFileModel.get(filePath); + classificationService.attachClassification(sourceFileModel, JavaSourceFileModel.UNPARSEABLE_JAVA_CLASSIFICATION, + JavaSourceFileModel.UNPARSEABLE_JAVA_DESCRIPTION); } - } - processor = new ASTProcessor(importResolver, libraryPaths, sourcePaths); - } - File sourceFile = payload.asFile(); + }; - ExecutionStatistics.get().begin("AnalyzeJavaFilesRuleProvider.parseFile"); - try - { - WindupWildcardImportResolver.setGraphContext(event.getGraphContext()); - ClassReferences references = processor.analyzeFile(sourceFile.toPath()); - TypeReferenceService typeReferenceService = new TypeReferenceService(event.getGraphContext()); - for (ClassReference reference : references.getReferences()) + Set filesToProcess = new TreeSet<>(allSourceFiles); + BatchASTProcessor.analyze(listener, importResolver, libraryPaths, sourcePaths, filesToProcess); + filesToProcess.removeAll(processedPaths); + processedPaths.clear(); + + if (!filesToProcess.isEmpty()) { - // we are always interested in types + anything that the TypeInterestFactory has registered - if (reference.getLocation() == TypeReferenceLocation.TYPE - || TypeInterestFactory.matchesAny(reference.getQualifiedName(), reference.getLocation())) + // try these one file at a time + for (Path unprocessed : filesToProcess) { - JavaTypeReferenceModel typeReference = typeReferenceService.createTypeReference(payload, reference.getLocation(), - reference.getLineNumber(), reference.getColumn(), reference.getLength(), reference.getQualifiedName(), - reference.getLine()); - if (reference instanceof AnnotationClassReference) + try + { + List references = ASTProcessor.analyze(importResolver, libraryPaths, sourcePaths, unprocessed); + processReferences(event.getGraphContext(), sourcePathToFileModel, unprocessed, references); + processedPaths.add(unprocessed); + } + catch (Exception e) { - Map annotationValues = ((AnnotationClassReference) reference).getAnnotationValues(); - addAnnotationValues(event.getGraphContext(), typeReference, annotationValues); + LOG.log(Level.WARNING, "Failed to process: " + unprocessed + " due to: " + e.getMessage(), e); + ClassificationService classificationService = new ClassificationService(event.getGraphContext()); + JavaSourceFileModel sourceFileModel = sourcePathToFileModel.get(unprocessed); + classificationService.attachClassification(sourceFileModel, JavaSourceFileModel.UNPARSEABLE_JAVA_CLASSIFICATION, + JavaSourceFileModel.UNPARSEABLE_JAVA_DESCRIPTION); } } } - ExecutionStatistics.get().end("AnalyzeJavaFilesRuleProvider.parseFile"); + filesToProcess.removeAll(processedPaths); + + if (!filesToProcess.isEmpty()) + { + ClassificationService classificationService = new ClassificationService(event.getGraphContext()); + + StringBuilder message = new StringBuilder(); + message.append("Failed to process " + filesToProcess.size() + " files:\n"); + for (Path unprocessed : filesToProcess) + { + JavaSourceFileModel sourceFileModel = sourcePathToFileModel.get(unprocessed); + message.append("\tFailed to process: " + unprocessed + "\n"); + classificationService.attachClassification(sourceFileModel, JavaSourceFileModel.UNPARSEABLE_JAVA_CLASSIFICATION, + JavaSourceFileModel.UNPARSEABLE_JAVA_DESCRIPTION); + } + LOG.warning(message.toString()); + } + + ExecutionStatistics.get().end("AnalyzeJavaFilesRuleProvider.parseFiles"); } catch (Exception e) { - LOG.log(Level.WARNING, "Could not analyze java file: " + payload.getFilePath() + " due to: " + e.getMessage(), e); - ClassificationService classificationService = new ClassificationService(event.getGraphContext()); - classificationService.attachClassification(payload, JavaSourceFileModel.UNPARSEABLE_JAVA_CLASSIFICATION, - JavaSourceFileModel.UNPARSEABLE_JAVA_DESCRIPTION); + LOG.log(Level.SEVERE, "Could not analyze java files: " + e.getMessage(), e); } finally { WindupWildcardImportResolver.setGraphContext(null); } - } finally { @@ -187,6 +233,31 @@ public void perform(GraphRewrite event, EvaluationContext context, JavaSourceFil } } + private void processReferences(GraphContext context, Map sourcePathToFileModel, Path filePath, + List references) + { + TypeReferenceService typeReferenceService = new TypeReferenceService(context); + for (ClassReference reference : references) + { + // we are always interested in types + anything that the TypeInterestFactory has registered + if (reference.getLocation() == TypeReferenceLocation.TYPE + || TypeInterestFactory.matchesAny(reference.getQualifiedName(), reference.getLocation())) + { + JavaSourceFileModel javaSourceModel = sourcePathToFileModel.get(filePath); + JavaTypeReferenceModel typeReference = typeReferenceService.createTypeReference(javaSourceModel, + reference.getLocation(), + reference.getLineNumber(), reference.getColumn(), reference.getLength(), + reference.getQualifiedName(), + reference.getLine()); + if (reference instanceof AnnotationClassReference) + { + Map annotationValues = ((AnnotationClassReference) reference).getAnnotationValues(); + addAnnotationValues(context, typeReference, annotationValues); + } + } + } + } + /** * Adds parameters contained in the annotation into the annotation type reference */ diff --git a/rules-xml/addon/src/main/java/org/jboss/windup/rules/apps/xml/service/XmlFileService.java b/rules-xml/addon/src/main/java/org/jboss/windup/rules/apps/xml/service/XmlFileService.java index b746712176..39ea30e196 100644 --- a/rules-xml/addon/src/main/java/org/jboss/windup/rules/apps/xml/service/XmlFileService.java +++ b/rules-xml/addon/src/main/java/org/jboss/windup/rules/apps/xml/service/XmlFileService.java @@ -46,20 +46,23 @@ public Document loadDocumentQuiet(XmlFileModel model) } XMLDocumentCache.Result cacheResult = XMLDocumentCache.get(model); - Document document = null; + Document document; if (cacheResult.isParseFailure()) { - LOG.log(Level.WARNING, "Not loading entity: " + model.getFilePath() + ", due to previous parse failures"); + LOG.log(Level.FINE, "Not loading entity: " + model.getFilePath() + ", due to previous parse failures"); + document = null; } else if (cacheResult.getDocument() == null) { try (InputStream is = model.asInputStream()) { document = LocationAwareXmlReader.readXML(is); + XMLDocumentCache.cache(model, document); } catch (SAXException e) { XMLDocumentCache.cacheParseFailure(model); + document = null; LOG.log(Level.WARNING, "Failed to parse xml entity: " + model.getFilePath() + ", due to: " + e.getMessage()); classificationService.attachClassification(model, XmlFileModel.UNPARSEABLE_XML_CLASSIFICATION, @@ -68,14 +71,12 @@ else if (cacheResult.getDocument() == null) catch (IOException e) { XMLDocumentCache.cacheParseFailure(model); + document = null; LOG.log(Level.WARNING, "Failed to parse xml entity: " + model.getFilePath() + ", due to: " + e.getMessage()); classificationService.attachClassification(model, XmlFileModel.UNPARSEABLE_XML_CLASSIFICATION, XmlFileModel.UNPARSEABLE_XML_DESCRIPTION); } - - if (document != null) - XMLDocumentCache.cache(model, document); } else { diff --git a/tests/src/test/java/org/jboss/windup/tests/application/WindupArchitectureSmallBinaryMode2Test.java b/tests/src/test/java/org/jboss/windup/tests/application/WindupArchitectureSmallBinaryMode2Test.java index 0284a07136..34bd9928c8 100644 --- a/tests/src/test/java/org/jboss/windup/tests/application/WindupArchitectureSmallBinaryMode2Test.java +++ b/tests/src/test/java/org/jboss/windup/tests/application/WindupArchitectureSmallBinaryMode2Test.java @@ -27,6 +27,7 @@ public class WindupArchitectureSmallBinaryMode2Test extends WindupArchitectureTe @AddonDependency(name = "org.jboss.windup.reporting:windup-reporting"), @AddonDependency(name = "org.jboss.windup.exec:windup-exec"), @AddonDependency(name = "org.jboss.windup.rules.apps:windup-rules-java"), + @AddonDependency(name = "org.jboss.windup.rules.apps:windup-rules-java-ee"), @AddonDependency(name = "org.jboss.windup.config:windup-config-groovy"), @AddonDependency(name = "org.jboss.forge.furnace.container:cdi"), })