com.google.devtools.cyclefinder.GraphBuilder.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.cyclefinder.GraphBuilder.java

Source

/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.devtools.cyclefinder;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.devtools.j2objc.ast.ClassInstanceCreation;
import com.google.devtools.j2objc.ast.CompilationUnit;
import com.google.devtools.j2objc.ast.CreationReference;
import com.google.devtools.j2objc.ast.ExpressionMethodReference;
import com.google.devtools.j2objc.ast.LambdaExpression;
import com.google.devtools.j2objc.ast.MethodInvocation;
import com.google.devtools.j2objc.ast.MethodReference;
import com.google.devtools.j2objc.ast.SuperMethodReference;
import com.google.devtools.j2objc.ast.TreeNode;
import com.google.devtools.j2objc.ast.TypeDeclaration;
import com.google.devtools.j2objc.ast.TypeMethodReference;
import com.google.devtools.j2objc.ast.UnitTreeVisitor;
import com.google.devtools.j2objc.util.CaptureInfo;
import com.google.devtools.j2objc.util.ElementUtil;
import com.google.devtools.j2objc.util.TypeUtil;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.IntersectionType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.TypeVisitor;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.SimpleTypeVisitor8;

/**
 * Builds the graph of possible references between types.
 *
 * @author Keith Stanger
 */
public class GraphBuilder {

    private final Map<String, TypeNode> allTypes = new HashMap<>();
    private final NameList whitelist;
    private final ReferenceGraph graph = new ReferenceGraph();
    private final Map<TypeNode, TypeNode> superclasses = new HashMap<>();
    private final SetMultimap<TypeNode, TypeNode> subtypes = HashMultimap.create();
    private final SetMultimap<TypeNode, Edge> possibleOuterEdges = HashMultimap.create();
    private final Set<TypeNode> hasOuterRef = new HashSet<>();

    public GraphBuilder(NameList whitelist) {
        this.whitelist = whitelist;
    }

    public GraphBuilder constructGraph() {
        addOuterEdges();
        addSubtypeEdges();
        addSuperclassEdges();
        return this;
    }

    public ReferenceGraph getGraph() {
        return graph;
    }

    private void addEdge(Edge e) {
        if (!e.getOrigin().equals(e.getTarget())) {
            graph.addEdge(e);
        }
    }

    private static TypeMirror getElementType(TypeMirror t) {
        while (TypeUtil.isArray(t)) {
            t = ((ArrayType) t).getComponentType();
        }
        return t;
    }

    private void addOuterEdges() {
        for (TypeNode type : hasOuterRef) {
            for (Edge e : possibleOuterEdges.get(type)) {
                addEdge(e);
            }
        }
    }

    private void addSubtypeEdges() {
        for (TypeNode type : allTypes.values()) {
            for (Edge e : ImmutableList.copyOf(graph.getEdges(type))) {
                Set<TypeNode> targetSubtypes = subtypes.get(e.getTarget());
                Set<TypeNode> whitelisted = new HashSet<>();
                String fieldName = e.getFieldQualifiedName();
                if (fieldName == null) {
                    continue; // Outer or capture field.
                }
                for (TypeNode subtype : targetSubtypes) {
                    if (whitelist.isWhitelistedTypeForField(fieldName, subtype)
                            || whitelist.containsType(subtype)) {
                        whitelisted.add(subtype);
                        whitelisted.addAll(subtypes.get(subtype));
                    }
                }
                for (TypeNode subtype : Sets.difference(targetSubtypes, whitelisted)) {
                    addEdge(Edge.newSubtypeEdge(e, subtype));
                }
            }
        }
    }

    private void addSuperclassEdges() {
        for (TypeNode type : allTypes.values()) {
            TypeNode superclassNode = superclasses.get(type);
            while (superclassNode != null) {
                for (Edge e : graph.getEdges(superclassNode)) {
                    addEdge(Edge.newSuperclassEdge(e, type, superclassNode));
                }
                superclassNode = superclasses.get(superclassNode);
            }
        }
    }

    private static final TypeVisitor<Integer, Void> TYPE_DEPTH_COUNTER = new SimpleTypeVisitor8<Integer, Void>(0) {

        private int tryVisit(TypeMirror t) {
            return t == null ? 0 : visit(t);
        }

        private int visitList(List<? extends TypeMirror> types) {
            int max = 0;
            for (TypeMirror t : types) {
                max = Math.max(max, visit(t));
            }
            return max;
        }

        @Override
        public Integer visitArray(ArrayType t, Void p) {
            return visit(t.getComponentType()) + 1;
        }

        @Override
        public Integer visitDeclared(DeclaredType t, Void p) {
            // Visit enclosing types but don't penalize them by adding +1.
            return Math.max(visit(t.getEnclosingType()), visitList(t.getTypeArguments()) + 1);
        }

        @Override
        public Integer visitTypeVariable(TypeVariable t, Void p) {
            return 1;
        }

        @Override
        public Integer visitWildcard(WildcardType t, Void p) {
            return Math.max(tryVisit(t.getExtendsBound()), tryVisit(t.getSuperBound())) + 1;
        }
    };

    private static boolean isRawType(TypeMirror type) {
        return TypeUtil.isDeclaredType(type) && !TypeUtil.asTypeElement(type).getTypeParameters().isEmpty()
                && ((DeclaredType) type).getTypeArguments().isEmpty();
    }

    public void visitAST(CompilationUnit unit) {
        new Visitor(unit).run();
    }

    private class Visitor extends UnitTreeVisitor {

        private final CaptureInfo captureInfo;
        private final NameUtil nameUtil;

        private Visitor(CompilationUnit unit) {
            super(unit);
            captureInfo = unit.getEnv().captureInfo();
            nameUtil = new NameUtil(typeUtil);
        }

        private TypeNode createNode(TypeMirror type, String signature, String name) {
            TypeNode node = new TypeNode(signature, name, NameUtil.getQualifiedName(type));
            allTypes.put(signature, node);
            followType(type, node);
            return node;
        }

        private TypeNode getOrCreateNode(TypeMirror type) {
            type = getElementType(type);
            String signature = nameUtil.getSignature(type);
            TypeNode node = allTypes.get(signature);
            if (node != null) {
                return node;
            }
            if (!TypeUtil.isReferenceType(type) || isRawType(type)) {
                return null;
            }
            if (TYPE_DEPTH_COUNTER.visit(type) > 5) {
                // Avoid infinite recursion caused by type argument cycles.
                return null;
            }
            return createNode(type, signature, NameUtil.getName(type));
        }

        private void visitType(TypeMirror type) {
            if (type == null) {
                return;
            } else if (TypeUtil.isIntersection(type)) {
                for (TypeMirror bound : ((IntersectionType) type).getBounds()) {
                    getOrCreateNode(bound);
                }
            } else {
                getOrCreateNode(type);
            }
        }

        private void followType(TypeMirror type, TypeNode node) {
            List<? extends TypeMirror> supertypes = TypeUtil.isDeclaredType(type) ? typeUtil.directSupertypes(type)
                    : typeUtil.getUpperBounds(type);
            for (TypeMirror supertype : supertypes) {
                TypeNode supertypeNode = getOrCreateNode(supertype);
                if (supertypeNode != null) {
                    subtypes.put(supertypeNode, node);
                    if (TypeUtil.isDeclaredType(supertype) && TypeUtil.getDeclaredTypeKind(supertype).isClass()) {
                        superclasses.put(node, supertypeNode);
                    }
                }
            }
            if (TypeUtil.isDeclaredType(type)) {
                followDeclaredType((DeclaredType) type, node);
            }
        }

        private void followDeclaredType(DeclaredType type, TypeNode node) {
            followEnclosingType((DeclaredType) type, node);
            followFields((DeclaredType) type, node);
            for (TypeMirror typeArg : type.getTypeArguments()) {
                visitType(typeArg);
            }
        }

        private void followFields(DeclaredType type, TypeNode node) {
            TypeElement element = (TypeElement) type.asElement();
            for (VariableElement field : ElementUtil.getDeclaredFields(element)) {
                TypeMirror fieldType = getElementType(typeUtil.asMemberOf(type, field));
                TypeNode target = getOrCreateNode(fieldType);
                String fieldName = ElementUtil.getName(field);
                if (target != null && !whitelist.containsField(node, fieldName) && !whitelist.containsType(target)
                        && !ElementUtil.isStatic(field)
                        // Exclude self-referential fields. (likely linked DS or delegate pattern)
                        && !typeUtil.isAssignable(type, fieldType) && !ElementUtil.isWeakReference(field)
                        && !ElementUtil.isRetainedWithField(field)) {
                    addEdge(Edge.newFieldEdge(node, target, fieldName));
                }
            }
        }

        private void followEnclosingType(DeclaredType type, TypeNode typeNode) {
            TypeMirror enclosingType = type.getEnclosingType();
            if (TypeUtil.isNone(enclosingType)) {
                return;
            }
            TypeNode enclosingTypeNode = getOrCreateNode(enclosingType);
            TypeElement element = (TypeElement) type.asElement();
            TypeNode declarationType = getOrCreateNode(element.asType());
            if (declarationType != null && enclosingTypeNode != null && ElementUtil.hasOuterContext(element)
                    && !elementUtil.isWeakOuterType(element) && !whitelist.containsType(enclosingTypeNode)
                    && !whitelist.hasOuterForType(typeNode)) {
                possibleOuterEdges.put(declarationType, Edge.newOuterClassEdge(typeNode, enclosingTypeNode));
            }
        }

        private void followCaptureFields(TypeElement type, TypeNode typeNode) {
            assert ElementUtil.isAnonymous(type);
            for (VariableElement capturedVarElement : captureInfo.getLocalCaptureFields(type)) {
                TypeNode targetNode = getOrCreateNode(capturedVarElement.asType());
                if (targetNode != null && !whitelist.containsType(targetNode)
                        && !ElementUtil.isWeakReference(capturedVarElement)) {
                    addEdge(Edge.newCaptureEdge(typeNode, targetNode, ElementUtil.getName(capturedVarElement)));
                }
            }
        }

        private String getTypeDeclarationName(TreeNode node, TypeElement typeElem) {
            if (node instanceof MethodReference) {
                return "methodref:" + node.getLineNumber();
            } else if (ElementUtil.isLambda(typeElem)) {
                return "lambda:" + node.getLineNumber();
            } else if (ElementUtil.isAnonymous(typeElem)) {
                return "anonymous:" + node.getLineNumber();
            } else {
                return NameUtil.getName(typeElem.asType());
            }
        }

        private void handleTypeDeclaration(TreeNode node, TypeElement typeElem) {
            TypeMirror type = typeElem.asType();
            TypeNode typeNode = createNode(type, nameUtil.getSignature(type),
                    getTypeDeclarationName(node, typeElem));
            if (captureInfo.needsOuterReference(typeElem)) {
                hasOuterRef.add(typeNode);
            }
            VariableElement receiverField = captureInfo.getReceiverField(typeElem);
            if (receiverField != null) {
                TypeNode receiverNode = getOrCreateNode(receiverField.asType());
                if (receiverNode != null) {
                    addEdge(Edge.newReceiverClassEdge(typeNode, receiverNode));
                }
            }
            if (ElementUtil.isAnonymous(typeElem)) {
                followCaptureFields(typeElem, typeNode);
            }
        }

        @Override
        public boolean visit(TypeDeclaration node) {
            handleTypeDeclaration(node, node.getTypeElement());
            return true;
        }

        @Override
        public void endVisit(LambdaExpression node) {
            handleTypeDeclaration(node, node.getTypeElement());
        }

        @Override
        public void endVisit(CreationReference node) {
            handleTypeDeclaration(node, node.getTypeElement());
        }

        @Override
        public void endVisit(ExpressionMethodReference node) {
            handleTypeDeclaration(node, node.getTypeElement());
        }

        @Override
        public void endVisit(SuperMethodReference node) {
            handleTypeDeclaration(node, node.getTypeElement());
        }

        @Override
        public void endVisit(TypeMethodReference node) {
            handleTypeDeclaration(node, node.getTypeElement());
        }

        @Override
        public boolean visit(ClassInstanceCreation node) {
            visitType(node.getTypeMirror());
            return true;
        }

        @Override
        public boolean visit(MethodInvocation node) {
            visitType(node.getTypeMirror());
            return true;
        }
    }
}