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

Java tutorial

Introduction

Here is the source code for com.google.devtools.cyclefinder.ReferenceGraph.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.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.devtools.j2objc.translate.OuterReferenceResolver;
import com.google.devtools.j2objc.util.BindingUtil;

import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.Modifier;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Builds the graph of possible references between types and searches for
 * possible cycles.
 *
 * @author Keith Stanger
 */
public class ReferenceGraph {

    private final Map<String, ITypeBinding> allTypes;
    private final OuterReferenceResolver outerResolver;
    private final NameList whitelist;
    private final NameList blacklist;
    private SetMultimap<String, Edge> edges = HashMultimap.create();
    private List<List<Edge>> cycles = Lists.newArrayList();

    public ReferenceGraph(TypeCollector typeCollector, OuterReferenceResolver outerResolver, NameList whitelist,
            NameList blacklist) {
        this.allTypes = typeCollector.getTypes();
        this.outerResolver = outerResolver;
        this.whitelist = whitelist;
        this.blacklist = blacklist;
    }

    public List<List<Edge>> findCycles() {
        constructGraph();
        runTarjans();
        return cycles;
    }

    private void constructGraph() {
        addFieldEdges();
        addSubtypeEdges();
        addSuperclassEdges();
        addOuterClassEdges();
        // TODO(kstanger): Capture edges should be added before subtype edges.
        addAnonymousClassCaptureEdges();
    }

    private void addEdge(Edge e) {
        if (!e.getOrigin().getKey().equals(e.getTarget().getKey())) {
            edges.put(e.getOrigin().getKey(), e);
        }
    }

    private void addFieldEdges() {
        for (ITypeBinding type : allTypes.values()) {
            for (IVariableBinding field : type.getDeclaredFields()) {
                ITypeBinding fieldType = getElementType(field.getType());
                if (!whitelist.containsField(field) && !whitelist.containsType(fieldType)
                        && !fieldType.isPrimitive() && !Modifier.isStatic(field.getModifiers())
                        // Exclude self-referential fields. (likely linked DS or delegate pattern)
                        && !type.isAssignmentCompatible(fieldType) && !BindingUtil.isWeakReference(field)
                        && !BindingUtil.isRetainedWithField(field)) {
                    addEdge(Edge.newFieldEdge(type, field));
                }
            }
        }
    }

    private ITypeBinding getElementType(ITypeBinding type) {
        if (type.isArray()) {
            return type.getElementType();
        }
        return type;
    }

    private void addSubtypeEdges() {
        SetMultimap<String, String> subtypes = HashMultimap.create();
        for (ITypeBinding type : allTypes.values()) {
            collectSubtypes(type.getKey(), type, subtypes);
        }
        for (String type : allTypes.keySet()) {
            for (Edge e : ImmutableList.copyOf(edges.get(type))) {
                Set<String> targetSubtypes = subtypes.get(e.getTarget().getKey());
                Set<String> whitelistKeys = Sets.newHashSet();
                IVariableBinding field = e.getField();
                for (String subtype : targetSubtypes) {
                    ITypeBinding subtypeBinding = allTypes.get(subtype);
                    if ((field != null && field.isField()
                            && whitelist.isWhitelistedTypeForField(field, subtypeBinding))
                            || whitelist.containsType(subtypeBinding)) {
                        whitelistKeys.add(subtype);
                        whitelistKeys.addAll(subtypes.get(subtype));
                    }
                }
                for (String subtype : Sets.difference(targetSubtypes, whitelistKeys)) {
                    addEdge(Edge.newSubtypeEdge(e, allTypes.get(subtype)));
                }
            }
        }
    }

    private void collectSubtypes(String originalType, ITypeBinding type, Multimap<String, String> subtypes) {
        for (ITypeBinding interfaze : type.getInterfaces()) {
            subtypes.put(interfaze.getKey(), originalType);
            collectSubtypes(originalType, interfaze, subtypes);
        }
        if (type.getSuperclass() != null) {
            subtypes.put(type.getSuperclass().getKey(), originalType);
            collectSubtypes(originalType, type.getSuperclass(), subtypes);
        }
    }

    private void addSuperclassEdges() {
        for (ITypeBinding type : allTypes.values()) {
            ITypeBinding superclass = type.getSuperclass();
            while (superclass != null) {
                for (Edge e : edges.get(superclass.getKey())) {
                    addEdge(Edge.newSuperclassEdge(e, type, superclass));
                }
                superclass = superclass.getSuperclass();
            }
        }
    }

    private void addOuterClassEdges() {
        for (ITypeBinding type : allTypes.values()) {
            if (outerResolver.needsOuterReference(type.getTypeDeclaration())
                    && !BindingUtil.hasNamedAnnotation(type, "WeakOuter")
                    && !BindingUtil.isWeakOuterAnonymousClass(type)) {
                ITypeBinding declaringType = type.getDeclaringClass();
                if (declaringType != null && !whitelist.containsType(declaringType)
                        && !whitelist.hasOuterForType(type)) {
                    addEdge(Edge.newOuterClassEdge(type, declaringType));
                }
            }
        }
    }

    private void addAnonymousClassCaptureEdges() {
        for (ITypeBinding type : allTypes.values()) {
            if (type.isAnonymous()) {
                for (IVariableBinding capturedVar : outerResolver.getInnerFields(type.getTypeDeclaration())) {
                    ITypeBinding targetType = getElementType(capturedVar.getType());
                    if (!targetType.isPrimitive() && !whitelist.containsType(targetType)
                            && !BindingUtil.isWeakReference(capturedVar)) {
                        addEdge(Edge.newCaptureEdge(type, capturedVar));
                    }
                }
            }
        }
    }

    private void runTarjans() {
        Set<String> seedTypes = edges.keySet();
        if (blacklist != null) {
            seedTypes = Sets.newHashSet(seedTypes);
            Iterator<String> it = seedTypes.iterator();
            while (it.hasNext()) {
                if (!blacklist.containsType(allTypes.get(it.next()))) {
                    it.remove();
                }
            }
        }
        List<List<String>> stronglyConnectedComponents = Tarjans.getStronglyConnectedComponents(edges, seedTypes);
        for (List<String> component : stronglyConnectedComponents) {
            handleStronglyConnectedComponent(makeSubgraph(edges, component));
        }
    }

    private void handleStronglyConnectedComponent(SetMultimap<String, Edge> subgraph) {
        // Make sure to find at least one cycle for each type in the SCC.
        Set<String> unusedTypes = Sets.newHashSet(subgraph.keySet());
        while (!unusedTypes.isEmpty()) {
            String root = Iterables.getFirst(unusedTypes, null);
            assert root != null;
            List<Edge> cycle = runDijkstras(subgraph, root);
            if (shouldAddCycle(cycle)) {
                cycles.add(cycle);
            }
            for (Edge e : cycle) {
                unusedTypes.remove(e.getOrigin().getKey());
            }
        }
    }

    private boolean shouldAddCycle(List<Edge> cycle) {
        if (blacklist == null) {
            return true;
        }
        for (Edge e : cycle) {
            if (blacklist.containsType(e.getOrigin())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Runs a version of Dijkstra's algorithm to find a tight cycle in the given
     * strongly connected component.
     */
    private List<Edge> runDijkstras(SetMultimap<String, Edge> graph, String root) {
        Map<String, Edge> backlinks = Maps.newHashMap();
        Set<String> visited = Sets.newHashSet();
        List<String> toVisit = Lists.newArrayList(root);
        outer: while (true) {
            List<String> visitNext = Lists.newArrayList();
            for (String source : toVisit) {
                visited.add(source);
                for (Edge e : graph.get(source)) {
                    String target = e.getTarget().getKey();
                    if (!visited.contains(target)) {
                        visitNext.add(target);
                        backlinks.put(target, e);
                    } else if (target.equals(root)) {
                        backlinks.put(root, e);
                        break outer;
                    }
                }
            }
            toVisit = visitNext;
        }
        List<Edge> cycle = Lists.newArrayList();
        String curNode = root;
        while (!curNode.equals(root) || cycle.size() == 0) {
            Edge nextEdge = backlinks.get(curNode);
            cycle.add(nextEdge);
            curNode = nextEdge.getOrigin().getKey();
        }
        return Lists.newArrayList(Lists.reverse(cycle));
    }

    private static SetMultimap<String, Edge> makeSubgraph(SetMultimap<String, Edge> graph,
            Collection<String> vertices) {
        SetMultimap<String, Edge> subgraph = HashMultimap.create();
        for (String type : vertices) {
            for (Edge e : graph.get(type)) {
                if (vertices.contains(e.getTarget().getKey())) {
                    subgraph.put(type, e);
                }
            }
        }
        return subgraph;
    }
}