/* * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.javac.util; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.Completer; import com.sun.tools.javac.code.Symbol.CompletionFailure; import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.util.GraphUtils.DependencyKind; import com.sun.tools.javac.util.GraphUtils.DotVisitor; import com.sun.tools.javac.util.GraphUtils.NodeVisitor; import java.io.Closeable; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.EnumMap; import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Stack; import javax.tools.JavaFileObject; /** * This class is used to track dependencies in the javac symbol completion process. * * <p><b>This is NOT part of any supported API. * If you write code that depends on this, you do so at your own risk. * This code and its internal interfaces are subject to change or * deletion without notice.</b> */ public abstract class Dependencies { protected static final Context.Key<Dependencies> dependenciesKey = new Context.Key<>(); public static Dependencies instance(Context context) { Dependencies instance = context.get(dependenciesKey); if (instance == null) { //use a do-nothing implementation in case no other implementation has been set by preRegister instance = new DummyDependencies(context); } return instance; } protected Dependencies(Context context) { context.put(dependenciesKey, this); } /** * Push a new completion node on the stack. */ abstract public void push(ClassSymbol s, CompletionCause phase); /** * Remove current dependency node from the stack. */ abstract public void pop(); public enum CompletionCause implements GraphUtils.DependencyKind { CLASS_READER, HEADER_PHASE, HIERARCHY_PHASE, IMPORTS_PHASE, MEMBER_ENTER, MEMBERS_PHASE, OTHER; } /** * This class creates a graph of all dependencies as symbols are completed; * when compilation finishes, the resulting dependency graph is then dumped * onto a dot file. Several options are provided to customize the output of the graph. */ public static class GraphDependencies extends Dependencies implements Closeable, Completer { /** * set of enabled dependencies modes */ private EnumSet<DependenciesMode> dependenciesModes; /** * file in which the dependency graph should be written */ private String dependenciesFile; /** * Register a Context.Factory to create a Dependencies. */ public static void preRegister(Context context) { context.put(dependenciesKey, (Context.Factory<Dependencies>) GraphDependencies::new); } /** * Build a Dependencies instance. */ GraphDependencies(Context context) { super(context); //fetch filename Options options = Options.instance(context); String[] modes = options.get("debug.completionDeps").split(","); for (String mode : modes) { if (mode.startsWith("file=")) { dependenciesFile = mode.substring(5); } } //parse modes dependenciesModes = DependenciesMode.getDependenciesModes(modes); //add to closeables JavaCompiler compiler = JavaCompiler.instance(context); compiler.closeables = compiler.closeables.prepend(this); } enum DependenciesMode { SOURCE("source"), CLASS("class"), REDUNDANT("redundant"); final String opt; DependenciesMode(String opt) { this.opt = opt; } /** * This method is used to parse the {@code completionDeps} option. * Possible modes are separated by colon; a mode can be excluded by * prepending '-' to its name. Finally, the special mode 'all' can be used to * add all modes to the resulting enum. */ static EnumSet<DependenciesMode> getDependenciesModes(String[] modes) { EnumSet<DependenciesMode> res = EnumSet.noneOf(DependenciesMode.class); Collection<String> args = Arrays.asList(modes); if (args.contains("all")) { res = EnumSet.allOf(DependenciesMode.class); } for (DependenciesMode mode : values()) { if (args.contains(mode.opt)) { res.add(mode); } else if (args.contains("-" + mode.opt)) { res.remove(mode); } } return res; } } /** * Class representing a node in the dependency graph. */ public static abstract class Node extends GraphUtils.AbstractNode<ClassSymbol, Node> implements GraphUtils.DottableNode<ClassSymbol, Node> { /** * dependant nodes grouped by kind */ EnumMap<CompletionCause, List<Node>> depsByKind; Node(ClassSymbol value) { super(value); this.depsByKind = new EnumMap<>(CompletionCause.class); for (CompletionCause depKind : CompletionCause.values()) { depsByKind.put(depKind, new ArrayList<Node>()); } } void addDependency(DependencyKind depKind, Node dep) { List<Node> deps = depsByKind.get(depKind); if (!deps.contains(dep)) { deps.add(dep); } } @Override public boolean equals(Object obj) { return obj instanceof Node && data.equals(((Node) obj).data); } @Override public int hashCode() { return data.hashCode(); } @Override public GraphUtils.DependencyKind[] getSupportedDependencyKinds() { return CompletionCause.values(); } @Override public java.util.Collection<? extends Node> getDependenciesByKind(DependencyKind dk) { return depsByKind.get(dk); } @Override public Properties nodeAttributes() { Properties p = new Properties(); p.put("label", DotVisitor.wrap(toString())); return p; } @Override public Properties dependencyAttributes(Node to, GraphUtils.DependencyKind dk) { Properties p = new Properties(); p.put("label", dk); return p; } @Override public String toString() { return data.getQualifiedName().toString(); } } /** * This is a dependency node used to model symbol completion requests. * Completion requests can come from either source or class. */ public static class CompletionNode extends Node { /** * Completion kind (source vs. classfile) */ enum Kind { /** * Source completion request */ SOURCE("solid"), /** * Classfile completion request */ CLASS("dotted"); final String dotStyle; Kind(String dotStyle) { this.dotStyle = dotStyle; } } final Kind ck; CompletionNode(ClassSymbol sym) { super(sym); //infer completion kind by looking at the symbol fields boolean fromClass = (sym.classfile == null && sym.sourcefile == null) || (sym.classfile != null && sym.classfile.getKind() == JavaFileObject.Kind.CLASS); ck = fromClass ? CompletionNode.Kind.CLASS : CompletionNode.Kind.SOURCE; } @Override public Properties nodeAttributes() { Properties p = super.nodeAttributes(); p.put("style", ck.dotStyle); p.put("shape", "ellipse"); return p; } public ClassSymbol getClassSymbol() { return data; } } /** * stack of dependency nodes currently being processed */ Stack<Node> nodeStack = new Stack<>(); /** * map containing all dependency nodes seen so far */ Map<ClassSymbol, Node> dependencyNodeMap = new LinkedHashMap<>(); @Override public void push(ClassSymbol s, CompletionCause phase) { Node n = new CompletionNode(s); if (n == push(n, phase)) { s.completer = this; } } /** * Push a new dependency on the stack. */ protected Node push(Node newNode, CompletionCause cc) { Node cachedNode = dependencyNodeMap.get(newNode.data); if (cachedNode == null) { dependencyNodeMap.put(newNode.data, newNode); } else { newNode = cachedNode; } if (!nodeStack.isEmpty()) { Node currentNode = nodeStack.peek(); currentNode.addDependency(cc, newNode); } nodeStack.push(newNode); return newNode; } @Override public void pop() { nodeStack.pop(); } @Override public void close() throws IOException { if (!dependenciesModes.contains(DependenciesMode.REDUNDANT)) { //prune spurious edges new PruneVisitor().visit(dependencyNodeMap.values(), null); } if (!dependenciesModes.contains(DependenciesMode.CLASS)) { //filter class completions new FilterVisitor(CompletionNode.Kind.SOURCE).visit(dependencyNodeMap.values(), null); } if (!dependenciesModes.contains(DependenciesMode.SOURCE)) { //filter source completions new FilterVisitor(CompletionNode.Kind.CLASS).visit(dependencyNodeMap.values(), null); } if (dependenciesFile != null) { //write to file try (FileWriter fw = new FileWriter(dependenciesFile)) { fw.append(GraphUtils.toDot(dependencyNodeMap.values(), "CompletionDeps", "")); } } } @Override public void complete(Symbol sym) throws CompletionFailure { push((ClassSymbol)sym, CompletionCause.OTHER); pop(); sym.completer = this; } @Override public boolean isTerminal() { return true; } public Collection<Node> getNodes() { return dependencyNodeMap.values(); } /** * This visitor is used to prune the graph from spurious edges using some heuristics. */ private static class PruneVisitor extends NodeVisitor<ClassSymbol, Node, Void> { @Override public void visitNode(Node node, Void arg) { //do nothing } @Override public void visitDependency(GraphUtils.DependencyKind dk, Node from, Node to, Void arg) { //heuristic - skips dependencies that are likely to be fake if (from.equals(to)) { to.depsByKind.get(dk).remove(from); } } } /** * This visitor is used to retain only completion nodes with given kind. */ private class FilterVisitor extends NodeVisitor<ClassSymbol, Node, Void> { CompletionNode.Kind ck; private FilterVisitor(CompletionNode.Kind ck) { this.ck = ck; } @Override public void visitNode(Node node, Void arg) { if (node instanceof CompletionNode) { if (((CompletionNode) node).ck != ck) { dependencyNodeMap.remove(node.data); } } } @Override public void visitDependency(GraphUtils.DependencyKind dk, Node from, Node to, Void arg) { if (to instanceof CompletionNode) { if (((CompletionNode) to).ck != ck) { from.depsByKind.get(dk).remove(to); } } } } } /** * Dummy class to be used when dependencies options are not set. This keeps * performance cost of calling push/pop methods during completion marginally low. */ private static class DummyDependencies extends Dependencies { private DummyDependencies(Context context) { super(context); } @Override public void push(ClassSymbol s, CompletionCause phase) { //do nothing } @Override public void pop() { //do nothing } } }