org.jboss.windup.rules.apps.java.scan.ast.VariableResolvingASTVisitor.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.windup.rules.apps.java.scan.ast.VariableResolvingASTVisitor.java

Source

/*
 * Copyright (c) 2013 Red Hat, Inc. and/or its affiliates.
 *  
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  which accompanies this distribution, and is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 *  
 *  Contributors:
 *      Brad Davis - bradsdavis@gmail.com - Initial API and implementation
 */
package org.jboss.windup.rules.apps.java.scan.ast;

import java.util.ArrayList;
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 org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.BooleanLiteral;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
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.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Name;
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.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.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.graph.model.resource.FileModel;
import org.jboss.windup.rules.apps.java.model.JavaClassModel;
import org.jboss.windup.rules.apps.java.service.JavaClassService;
import org.jboss.windup.rules.apps.java.service.TypeReferenceService;
import java.util.logging.Logger;
import org.jboss.windup.util.Logging;

/**
 * Runs through the source code and checks "type" uses against the blacklisted class entries.
 * 
 * @author bradsdavis
 */
public class VariableResolvingASTVisitor extends ASTVisitor {
    private static final Logger LOG = Logging.get(VariableResolvingASTVisitor.class);

    private final JavaClassService javaClassService;
    private final TypeReferenceService typeRefService;

    public VariableResolvingASTVisitor(GraphContext context) {
        this.javaClassService = new JavaClassService(context);
        this.typeRefService = new TypeReferenceService(context);
    }

    private CompilationUnit cu;

    /**
     * Contains all wildcard imports (import com.example.*) lines from the source file.
     * 
     * These are used for type resolution throughout the class.
     */
    private final List<String> 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<String> classNameLookedUp = new HashSet<>();
    /**
     * Contains a map of class short names (eg, MyClass) to qualified names (eg, com.example.MyClass)
     */
    private final Map<String, String> classNameToFQCN = new HashMap<>();

    /**
     * Maintains a set of all variable names that have been resolved
     */
    private final Set<String> names = new HashSet<String>();

    /**
     * Maintains a map of nameInstances to fully qualified class names.
     */
    private final Map<String, String> nameInstance = new HashMap<String, String>();

    private FileModel fileModel;

    public void init(CompilationUnit cu, FileModel fileModel) {
        this.cu = cu;
        this.fileModel = fileModel;
        this.wildcardImports.clear();
        this.classNameLookedUp.clear();
        this.classNameToFQCN.clear();
        this.names.clear();
        this.nameInstance.clear();

        PackageDeclaration packageDeclaration = cu.getPackage();
        String packageName = packageDeclaration == null ? "" : packageDeclaration.getName().getFullyQualifiedName();
        @SuppressWarnings("unchecked")
        List<TypeDeclaration> types = cu.types();
        if (!types.isEmpty()) {
            TypeDeclaration typeDeclaration = (TypeDeclaration) types.get(0);
            String className = typeDeclaration.getName().getFullyQualifiedName();
            String fqcn = packageName + "." + className;
            this.names.add("this");
            this.nameInstance.put("this", fqcn);
        }
    }

    private void processConstructor(ConstructorType interest, int lineNumber, int columnNumber, int length) {
        String text = interest.toString();
        if (TypeInterestFactory.matchesAny(text)) {
            JavaTypeReferenceModel typeRef = typeRefService.createTypeReference(fileModel,
                    TypeReferenceLocation.CONSTRUCTOR_CALL, lineNumber, columnNumber, length, text);

            LOG.finer("Candidate: " + typeRef);
        }
    }

    private void processMethod(MethodType interest, int lineNumber, int columnNumber, int length) {
        String text = interest.toString();
        if (TypeInterestFactory.matchesAny(text)) {
            JavaTypeReferenceModel typeRef = typeRefService.createTypeReference(fileModel,
                    TypeReferenceLocation.METHOD_CALL, lineNumber, columnNumber, length, text);

            LOG.finer("Candidate: " + typeRef);
        }
    }

    private void processImport(String interest, int lineNumber, int columnNumber, int length) {
        String sourceString = interest;
        if (TypeInterestFactory.matchesAny(sourceString)) {
            sourceString = resolveClassname(sourceString);

            JavaTypeReferenceModel typeRef = typeRefService.createTypeReference(fileModel,
                    TypeReferenceLocation.IMPORT, lineNumber, columnNumber, length, interest.toString());

            LOG.finer("Candidate: " + typeRef);
        }
    }

    private void processType(Type type, TypeReferenceLocation referenceLocation) {
        if (type == null)
            return;

        String sourceString = type.toString();
        sourceString = resolveClassname(sourceString);
        if (TypeInterestFactory.matchesAny(sourceString)) {
            int lineNumber = cu.getLineNumber(type.getStartPosition());
            int columnNumber = cu.getColumnNumber(type.getStartPosition());
            int length = type.getLength();

            JavaTypeReferenceModel typeRef = typeRefService.createTypeReference(fileModel, referenceLocation,
                    lineNumber, columnNumber, length, sourceString);

            LOG.finer("Prefix: " + referenceLocation);
            if (type instanceof SimpleType) {
                SimpleType sType = (SimpleType) type;
                LOG.finer("The type name is: " + sType.getName().getFullyQualifiedName() + " and " + sourceString);

            }
            LOG.finer("Candidate: " + typeRef);
        }
    }

    private void processName(Name name, TypeReferenceLocation referenceLocation, int lineNumber, int columnNumber,
            int length) {
        if (name == null)
            return;

        String sourceString = resolveClassname(name.toString());
        if (TypeInterestFactory.matchesAny(sourceString)) {
            sourceString = resolveClassname(sourceString);

            JavaTypeReferenceModel typeRef = typeRefService.createTypeReference(fileModel, referenceLocation,
                    lineNumber, columnNumber, length, sourceString);

            LOG.finer("Prefix: " + referenceLocation);
            LOG.finer("Candidate: " + typeRef);
        }
    }

    @Override
    public boolean visit(MethodDeclaration node) {
        // get a method's return type.
        Type returnType = node.getReturnType2();
        if (returnType != null) {
            processType(returnType, TypeReferenceLocation.RETURN_TYPE);
        }

        @SuppressWarnings("unchecked")
        List<SingleVariableDeclaration> parameters = (List<SingleVariableDeclaration>) node.parameters();
        if (parameters != null) {
            for (SingleVariableDeclaration type : parameters) {
                // make it fully qualified.
                String typeName = type.getType().toString();
                typeName = resolveClassname(typeName);
                // now add it as a local variable.
                this.names.add(type.getName().toString());
                this.nameInstance.put(type.getName().toString(), typeName);

                processType(type.getType(), TypeReferenceLocation.METHOD_PARAMETER);
            }
        }

        @SuppressWarnings("unchecked")
        List<Name> throwsTypes = node.thrownExceptions();
        if (throwsTypes != null) {
            for (Name name : throwsTypes) {
                processName(name, TypeReferenceLocation.THROWS_METHOD_DECLARATION,
                        cu.getLineNumber(node.getStartPosition()), cu.getColumnNumber(name.getStartPosition()),
                        name.getLength());
            }
        }

        return super.visit(node);
    }

    @Override
    public boolean visit(InstanceofExpression node) {
        Type type = node.getRightOperand();
        processType(type, TypeReferenceLocation.INSTANCE_OF);

        return super.visit(node);
    }

    public boolean visit(org.eclipse.jdt.core.dom.ThrowStatement node) {
        if (node.getExpression() instanceof ClassInstanceCreation) {
            ClassInstanceCreation cic = (ClassInstanceCreation) node.getExpression();
            processType(cic.getType(), TypeReferenceLocation.THROW_STATEMENT);
        }

        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);

        return super.visit(node);
    }

    @Override
    public boolean visit(ReturnStatement node) {
        if (node.getExpression() instanceof ClassInstanceCreation) {
            ClassInstanceCreation cic = (ClassInstanceCreation) node.getExpression();
            processType(cic.getType(), TypeReferenceLocation.CONSTRUCTOR_CALL);
        }
        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());

            processType(node.getType(), TypeReferenceLocation.FIELD_DECLARATION);
        }
        return true;
    }

    @Override
    public boolean visit(MarkerAnnotation node) {
        processName(node.getTypeName(), TypeReferenceLocation.ANNOTATION, cu.getLineNumber(node.getStartPosition()),
                cu.getColumnNumber(cu.getStartPosition()), cu.getLength());
        return super.visit(node);
    }

    @Override
    public boolean visit(NormalAnnotation node) {
        processName(node.getTypeName(), TypeReferenceLocation.ANNOTATION, cu.getLineNumber(node.getStartPosition()),
                cu.getColumnNumber(node.getStartPosition()), node.getLength());
        return super.visit(node);
    }

    @Override
    public boolean visit(SingleMemberAnnotation node) {
        processName(node.getTypeName(), TypeReferenceLocation.ANNOTATION, cu.getLineNumber(node.getStartPosition()),
                cu.getColumnNumber(node.getStartPosition()), node.getLength());
        return super.visit(node);
    }

    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) {
                        processType((SimpleType) clzInterface, TypeReferenceLocation.IMPLEMENTS_TYPE);
                    } else {
                        LOG.finer("" + clzInterface);
                    }
                }
            }
        }
        if (clzSuperClasses != null) {
            if (clzSuperClasses instanceof SimpleType) {
                processType((SimpleType) clzSuperClasses, TypeReferenceLocation.EXTENDS_TYPE);
            } else {
                LOG.finer("" + clzSuperClasses);
            }
        }

        return super.visit(node);
    }

    @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);

        return super.visit(node);
    }

    @Override
    public boolean visit(ImportDeclaration node) {
        String name = node.getName().toString();
        if (node.isOnDemand()) {
            wildcardImports.add(name);

            Iterable<JavaClassModel> classModels = javaClassService.findByJavaPackage(name);
            for (JavaClassModel classModel : classModels) {
                processImport(classModel.getQualifiedName(), cu.getLineNumber(node.getName().getStartPosition()),
                        cu.getColumnNumber(node.getName().getStartPosition()), node.getName().getLength());
            }
        } else {
            String clzName = StringUtils.substringAfterLast(name, ".");
            classNameLookedUp.add(clzName);
            classNameToFQCN.put(clzName, name);
            processImport(node.getName().toString(), cu.getLineNumber(node.getName().getStartPosition()),
                    cu.getColumnNumber(node.getName().getStartPosition()), node.getName().getLength());
        }

        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;
        }

        String nodeName = StringUtils.removeStart(node.toString(), "this.");

        List<?> arguments = node.arguments();
        List<String> resolvedParams = methodParameterGuesser(arguments);

        String objRef = StringUtils.substringBefore(nodeName, "." + node.getName().toString());

        if (nameInstance.containsKey(objRef)) {
            objRef = nameInstance.get(objRef);
        }

        objRef = resolveClassname(objRef);

        MethodType methodCall = new MethodType(objRef, node.getName().toString(), resolvedParams);
        processMethod(methodCall, cu.getLineNumber(node.getName().getStartPosition()),
                cu.getColumnNumber(node.getName().getStartPosition()), node.getName().getLength());

        return super.visit(node);
    }

    @Override
    public boolean visit(PackageDeclaration node) {
        LOG.finer("Found package: " + node.getName().toString());
        return super.visit(node);
    }

    @Override
    public boolean visit(ClassInstanceCreation node) {
        String nodeType = node.getType().toString();
        nodeType = resolveClassname(nodeType);

        List<String> resolvedParams = this.methodParameterGuesser(node.arguments());

        ConstructorType resolvedConstructor = new ConstructorType(nodeType, resolvedParams);
        processConstructor(resolvedConstructor, cu.getLineNumber(node.getType().getStartPosition()),
                cu.getColumnNumber(node.getType().getStartPosition()), node.getType().getLength());

        return super.visit(node);
    }

    private List<String> methodParameterGuesser(List<?> arguements) {
        List<String> resolvedParams = new ArrayList<String>(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 {
                LOG.finer("Unable to determine type: " + o.getClass() + ReflectionToStringBuilder.toString(o));
                resolvedParams.add("Undefined");
            }
        }

        return resolvedParams;
    }

    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;
    }

    public static class MethodType {
        private final String qualifiedName;
        private final String methodName;
        private final List<String> qualifiedParameters;

        public MethodType(String qualifiedName, String methodName, List<String> qualifiedParameters) {
            this.qualifiedName = qualifiedName;
            this.methodName = methodName;

            if (qualifiedParameters != null) {
                this.qualifiedParameters = qualifiedParameters;
            } else {
                this.qualifiedParameters = new LinkedList<String>();
            }
        }

        public String getMethodName() {
            return methodName;
        }

        public String getQualifiedName() {
            return qualifiedName;
        }

        public List<String> 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<String> qualifiedParameters;

        public ConstructorType(String qualifiedName, List<String> qualifiedParameters) {
            this.qualifiedName = qualifiedName;
            if (qualifiedParameters != null) {
                this.qualifiedParameters = qualifiedParameters;
            } else {
                this.qualifiedParameters = new LinkedList<String>();
            }

        }

        public String getQualifiedName() {
            return qualifiedName;
        }

        public List<String> 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 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 {
                // if this name has not been resolved before, go ahead and resolve it from the graph (if possible)
                classNameLookedUp.add(sourceClassname);

                // search every wildcard import for this name
                for (String wildcardImport : wildcardImports) {
                    String candidateQualifiedName = wildcardImport + "." + sourceClassname;

                    Iterable<JavaClassModel> models = javaClassService
                            .findAllByProperty(JavaClassModel.PROPERTY_QUALIFIED_NAME, candidateQualifiedName);
                    if (models.iterator().hasNext()) {
                        // we found it... put it in the map and return the result
                        classNameToFQCN.put(sourceClassname, candidateQualifiedName);
                        return candidateQualifiedName;
                    }
                }
                // nothing was found, so just return the original value
                return sourceClassname;
            }
        } else {
            return sourceClassname;
        }
    }

}