8048890: Add option to keep track of symbol completion dependencies
Summary: Generate dot file with representation of javac on-demand symbol completion dependencies
Reviewed-by: jjg, jlahoda
--- a/langtools/src/share/classes/com/sun/tools/javac/code/ClassFinder.java Wed Jul 23 09:19:23 2014 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/code/ClassFinder.java Thu Jul 24 13:11:03 2014 +0100
@@ -103,6 +103,10 @@
*/
private final JavaFileManager fileManager;
+ /** Dependency tracker
+ */
+ private final Dependencies dependencies;
+
/** Factory for diagnostics
*/
JCDiagnostic.Factory diagFactory;
@@ -150,6 +154,7 @@
names = Names.instance(context);
syms = Symtab.instance(context);
fileManager = context.get(JavaFileManager.class);
+ dependencies = Dependencies.instance(context);
if (fileManager == null)
throw new AssertionError("FileManager initialization error");
diagFactory = JCDiagnostic.Factory.instance(context);
@@ -179,18 +184,23 @@
*/
private void complete(Symbol sym) throws CompletionFailure {
if (sym.kind == TYP) {
- ClassSymbol c = (ClassSymbol)sym;
- c.members_field = new Scope.ErrorScope(c); // make sure it's always defined
- annotate.enterStart();
try {
- completeOwners(c.owner);
- completeEnclosing(c);
+ ClassSymbol c = (ClassSymbol) sym;
+ dependencies.push(c);
+ c.members_field = new Scope.ErrorScope(c); // make sure it's always defined
+ annotate.enterStart();
+ try {
+ completeOwners(c.owner);
+ completeEnclosing(c);
+ } finally {
+ // The flush needs to happen only after annotations
+ // are filled in.
+ annotate.enterDoneWithoutFlush();
+ }
+ fillIn(c);
} finally {
- // The flush needs to happen only after annotations
- // are filled in.
- annotate.enterDoneWithoutFlush();
+ dependencies.pop();
}
- fillIn(c);
} else if (sym.kind == PCK) {
PackageSymbol p = (PackageSymbol)sym;
try {
@@ -257,7 +267,6 @@
+ classfile.toUri());
}
}
- return;
} finally {
currentClassFile = previousClassFile;
}
--- a/langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java Wed Jul 23 09:19:23 2014 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java Thu Jul 24 13:11:03 2014 +0100
@@ -49,6 +49,7 @@
import com.sun.tools.javac.tree.JCTree.*;
import com.sun.tools.javac.tree.JCTree.JCPolyExpression.*;
import com.sun.tools.javac.util.*;
+import com.sun.tools.javac.util.Dependencies.AttributionKind;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.List;
import static com.sun.tools.javac.code.Flags.*;
@@ -94,6 +95,7 @@
final Annotate annotate;
final DeferredLintHandler deferredLintHandler;
final TypeEnvs typeEnvs;
+ final Dependencies dependencies;
public static Attr instance(Context context) {
Attr instance = context.get(attrKey);
@@ -123,6 +125,7 @@
annotate = Annotate.instance(context);
deferredLintHandler = DeferredLintHandler.instance(context);
typeEnvs = TypeEnvs.instance(context);
+ dependencies = Dependencies.instance(context);
Options options = Options.instance(context);
@@ -695,6 +698,7 @@
*/
void attribTypeVariables(List<JCTypeParameter> typarams, Env<AttrContext> env) {
for (JCTypeParameter tvar : typarams) {
+ dependencies.push(AttributionKind.TVAR, tvar);
TypeVar a = (TypeVar)tvar.type;
a.tsym.flags_field |= UNATTRIBUTED;
a.bound = Type.noType;
@@ -710,6 +714,7 @@
types.setBounds(a, List.of(syms.objectType));
}
a.tsym.flags_field &= ~UNATTRIBUTED;
+ dependencies.pop();
}
for (JCTypeParameter tvar : typarams) {
chk.checkNonCyclic(tvar.pos(), (TypeVar)tvar.type);
--- a/langtools/src/share/classes/com/sun/tools/javac/comp/Infer.java Wed Jul 23 09:19:23 2014 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/comp/Infer.java Thu Jul 24 13:11:03 2014 +0100
@@ -29,6 +29,7 @@
import com.sun.tools.javac.tree.JCTree.JCTypeCast;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.tools.javac.util.*;
+import com.sun.tools.javac.util.GraphUtils.DottableNode;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.code.*;
@@ -40,9 +41,9 @@
import com.sun.tools.javac.comp.Infer.GraphSolver.InferenceGraph.Node;
import com.sun.tools.javac.comp.Resolve.InapplicableMethodException;
import com.sun.tools.javac.comp.Resolve.VerboseResolutionMode;
-import com.sun.tools.javac.util.GraphUtils.TarjanNode;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
@@ -50,6 +51,7 @@
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
+import java.util.Properties;
import java.util.Set;
import static com.sun.tools.javac.code.TypeTag.*;
@@ -1607,11 +1609,6 @@
private DependencyKind(String dotSyle) {
this.dotSyle = dotSyle;
}
-
- @Override
- public String getDotStyle() {
- return dotSyle;
- }
}
/**
@@ -1684,7 +1681,7 @@
* updates on the structure of the graph this node belongs to (used to
* keep dependencies in sync).
*/
- class Node extends GraphUtils.TarjanNode<ListBuffer<Type>> {
+ class Node extends GraphUtils.TarjanNode<ListBuffer<Type>, Node> implements DottableNode<ListBuffer<Type>, Node> {
/** map listing all dependencies (grouped by kind) */
EnumMap<DependencyKind, Set<Node>> deps;
@@ -1699,33 +1696,12 @@
return DependencyKind.values();
}
- @Override
- public String getDependencyName(GraphUtils.Node<ListBuffer<Type>> to, GraphUtils.DependencyKind dk) {
- if (dk == DependencyKind.STUCK) return "";
- else {
- StringBuilder buf = new StringBuilder();
- String sep = "";
- for (Type from : data) {
- UndetVar uv = (UndetVar)inferenceContext.asUndetVar(from);
- for (Type bound : uv.getBounds(InferenceBound.values())) {
- if (bound.containsAny(List.from(to.data))) {
- buf.append(sep);
- buf.append(bound);
- sep = ",";
- }
- }
- }
- return buf.toString();
- }
- }
-
- @Override
public Iterable<? extends Node> getAllDependencies() {
return getDependencies(DependencyKind.values());
}
@Override
- public Iterable<? extends TarjanNode<ListBuffer<Type>>> getDependenciesByKind(GraphUtils.DependencyKind dk) {
+ public Collection<? extends Node> getDependenciesByKind(GraphUtils.DependencyKind dk) {
return getDependencies((DependencyKind)dk);
}
@@ -1855,6 +1831,36 @@
}
}
}
+
+ @Override
+ public Properties nodeAttributes() {
+ Properties p = new Properties();
+ p.put("label", toString());
+ return p;
+ }
+
+ @Override
+ public Properties dependencyAttributes(Node sink, GraphUtils.DependencyKind dk) {
+ Properties p = new Properties();
+ p.put("style", ((DependencyKind)dk).dotSyle);
+ if (dk == DependencyKind.STUCK) return p;
+ else {
+ StringBuilder buf = new StringBuilder();
+ String sep = "";
+ for (Type from : data) {
+ UndetVar uv = (UndetVar)inferenceContext.asUndetVar(from);
+ for (Type bound : uv.getBounds(InferenceBound.values())) {
+ if (bound.containsAny(List.from(sink.data))) {
+ buf.append(sep);
+ buf.append(bound);
+ sep = ",";
+ }
+ }
+ }
+ p.put("label", buf.toString());
+ }
+ return p;
+ }
}
/** the nodes in the inference graph */
--- a/langtools/src/share/classes/com/sun/tools/javac/comp/MemberEnter.java Wed Jul 23 09:19:23 2014 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/comp/MemberEnter.java Thu Jul 24 13:11:03 2014 +0100
@@ -55,6 +55,7 @@
import static com.sun.tools.javac.code.TypeTag.TYPEVAR;
import static com.sun.tools.javac.tree.JCTree.Tag.*;
+import com.sun.tools.javac.util.Dependencies.AttributionKind;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
@@ -90,6 +91,7 @@
private final DeferredLintHandler deferredLintHandler;
private final Lint lint;
private final TypeEnvs typeEnvs;
+ private final Dependencies dependencies;
public static MemberEnter instance(Context context) {
MemberEnter instance = context.get(memberEnterKey);
@@ -116,6 +118,7 @@
deferredLintHandler = DeferredLintHandler.instance(context);
lint = Lint.instance(context);
typeEnvs = TypeEnvs.instance(context);
+ dependencies = Dependencies.instance(context);
allowTypeAnnos = source.allowTypeAnnotations();
}
@@ -555,6 +558,7 @@
// process the non-static imports and the static imports of types.
public void visitImport(JCImport tree) {
+ dependencies.push(AttributionKind.IMPORT, tree);
JCFieldAccess imp = (JCFieldAccess)tree.qualid;
Name name = TreeInfo.name(imp);
@@ -581,6 +585,7 @@
importNamed(tree.pos(), c, env);
}
}
+ dependencies.pop();
}
public void visitMethodDef(JCMethodDecl tree) {
@@ -952,6 +957,8 @@
JavaFileObject prev = log.useSource(env.toplevel.sourcefile);
DiagnosticPosition prevLintPos = deferredLintHandler.setPos(tree.pos());
try {
+ dependencies.push(c);
+
// Save class environment for later member enter (2) processing.
halfcompleted.append(env);
@@ -990,16 +997,21 @@
Type supertype;
if (tree.extending != null) {
- supertype = attr.attribBase(tree.extending, baseEnv,
- true, false, true);
- if (sym.isAnonymous()) {
- annotate.annotateAnonClassDefLater(tree.extending,
- tree.mods.annotations,
- baseEnv, sym, tree.pos(),
- annotate.extendsCreator);
- } else {
- annotate.annotateTypeLater(tree.extending, baseEnv, sym,
- tree.pos(), annotate.extendsCreator);
+ dependencies.push(AttributionKind.EXTENDS, tree.extending);
+ try {
+ supertype = attr.attribBase(tree.extending, baseEnv,
+ true, false, true);
+ if (sym.isAnonymous()) {
+ annotate.annotateAnonClassDefLater(tree.extending,
+ tree.mods.annotations,
+ baseEnv, sym, tree.pos(),
+ annotate.extendsCreator);
+ } else {
+ annotate.annotateTypeLater(tree.extending, baseEnv, sym,
+ tree.pos(), annotate.extendsCreator);
+ }
+ } finally {
+ dependencies.pop();
}
} else {
supertype = ((tree.mods.flags & Flags.ENUM) != 0)
@@ -1018,29 +1030,34 @@
List<JCExpression> interfaceTrees = tree.implementing;
int i = 0;
for (JCExpression iface : interfaceTrees) {
- Type it = attr.attribBase(iface, baseEnv, false, true, true);
- if (it.hasTag(CLASS)) {
- interfaces.append(it);
- if (all_interfaces != null) all_interfaces.append(it);
- chk.checkNotRepeated(iface.pos(), types.erasure(it), interfaceSet);
- } else {
- if (all_interfaces == null)
- all_interfaces = new ListBuffer<Type>().appendList(interfaces);
- all_interfaces.append(modelMissingTypes(it, iface, true));
+ dependencies.push(AttributionKind.IMPLEMENTS, iface);
+ try {
+ Type it = attr.attribBase(iface, baseEnv, false, true, true);
+ if (it.hasTag(CLASS)) {
+ interfaces.append(it);
+ if (all_interfaces != null) all_interfaces.append(it);
+ chk.checkNotRepeated(iface.pos(), types.erasure(it), interfaceSet);
+ } else {
+ if (all_interfaces == null)
+ all_interfaces = new ListBuffer<Type>().appendList(interfaces);
+ all_interfaces.append(modelMissingTypes(it, iface, true));
+ }
+ if (sym.isAnonymous()) {
+ // Note: if an anonymous class ever has more than
+ // one supertype for some reason, this will
+ // incorrectly attach tree.mods.annotations to ALL
+ // supertypes, not just the first.
+ annotate.annotateAnonClassDefLater(iface, tree.mods.annotations,
+ baseEnv, sym, tree.pos(),
+ annotate.implementsCreator(i++));
+ } else {
+ annotate.annotateTypeLater(iface, baseEnv, sym, tree.pos(),
+ annotate.implementsCreator(i++));
+ }
+ } finally {
+ dependencies.pop();
}
- if (sym.isAnonymous()) {
- // Note: if an anonymous class ever has more than
- // one supertype for some reason, this will
- // incorrectly attach tree.mods.annotations to ALL
- // supertypes, not just the first.
- annotate.annotateAnonClassDefLater(iface, tree.mods.annotations,
- baseEnv, sym, tree.pos(),
- annotate.implementsCreator(i++));
- } else {
- annotate.annotateTypeLater(iface, baseEnv, sym, tree.pos(),
- annotate.implementsCreator(i++));
- }
}
if ((c.flags_field & ANNOTATION) != 0) {
@@ -1157,6 +1174,7 @@
} finally {
deferredLintHandler.setPos(prevLintPos);
log.useSource(prev);
+ dependencies.pop();
}
// Enter all member fields and methods of a set of half completed
--- a/langtools/src/share/classes/com/sun/tools/javac/main/Main.java Wed Jul 23 09:19:23 2014 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/main/Main.java Thu Jul 24 13:11:03 2014 +0100
@@ -488,6 +488,10 @@
}
}
+ if (options.isSet("completionDeps")) {
+ Dependencies.GraphDependencies.preRegister(context);
+ }
+
comp = JavaCompiler.instance(context);
// FIXME: this code will not be invoked if using JavacTask.parse/analyze/generate
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/src/share/classes/com/sun/tools/javac/util/Dependencies.java Thu Jul 24 13:11:03 2014 +0100
@@ -0,0 +1,559 @@
+/*
+ * Copyright (c) 2014, 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.tree.JCTree;
+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.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+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;
+ }
+
+ Dependencies(Context context) {
+ context.put(dependenciesKey, this);
+ }
+
+ /**
+ * This enum models different kinds of attribution actions triggered during
+ * symbol completion.
+ */
+ public enum AttributionKind {
+ /**
+ * Attribution of superclass (i.e. @{code extends} clause).
+ */
+ EXTENDS {
+ @Override
+ String format(JCTree tree) {
+ return "extends " + super.format(tree);
+ }
+ },
+ /**
+ * Attribution of superinterface (i.e. an type in the @{code interface} clause).
+ */
+ IMPLEMENTS {
+ @Override
+ String format(JCTree tree) {
+ return "implements " + super.format(tree);
+ }
+ },
+ /**
+ * Attribution of an import statement
+ */
+ IMPORT,
+ /**
+ * Attribution of type-variable bound
+ */
+ TVAR {
+ @Override
+ String format(JCTree tree) {
+ return "<" + super.format(tree) + ">";
+ }
+ };
+
+ String format(JCTree tree) {
+ return tree.toString();
+ }
+ }
+
+ /**
+ * Push a new completion node on the stack.
+ */
+ abstract public void push(ClassSymbol s);
+
+ /**
+ * Push a new attribution node on the stack.
+ */
+ abstract public void push(AttributionKind ak, JCTree t);
+
+ /**
+ * Remove current dependency node from the stack.
+ */
+ abstract public void pop();
+
+ /**
+ * This class creates a graph of all dependencies as symbols are completed;
+ * when compilation finishes, the resulting dependecy graph is then dumped
+ * onto a dot file. Several options are provided to customise 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(final Context context) {
+ context.put(dependenciesKey, new Context.Factory<Dependencies>() {
+ public Dependencies make(Context c) {
+ Dependencies deps = new GraphDependencies(context);
+ return deps;
+ }
+ });
+ }
+
+ /**
+ * Build a Dependencies instance.
+ */
+ GraphDependencies(Context context) {
+ super(context);
+ Options options = Options.instance(context);
+ //fetch filename
+ String[] modes = options.get("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"),
+ SIDE_EFFECTS("side-effects");
+
+ final String opt;
+
+ private 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. Nodes are of two main
+ * kinds: (i) symbol nodes, corresponding to symbol completion requests
+ * (either from source or classfile); (ii) attribution nodes, corresponding to
+ * attribution actions triggered during (source) completion.
+ */
+ static abstract class Node extends GraphUtils.AbstractNode<String, Node>
+ implements GraphUtils.DottableNode<String, Node> {
+
+ /**
+ * Model the dependencies between nodes.
+ */
+ enum DependencyKind implements GraphUtils.DependencyKind {
+ /**
+ * standard dependency - i.e. completion of the source node depends
+ * on completion of the sink node.
+ */
+ REQUIRES("solid"),
+ /**
+ * soft dependencies - i.e. completion of the source node depends
+ * on side-effects of the source node. These dependencies are meant
+ * to capture the order in which javac processes all dependants of a given node.
+ */
+ SIDE_EFFECTS("dashed");
+
+ final String dotStyle;
+
+ DependencyKind(String dotStyle) {
+ this.dotStyle = dotStyle;
+ }
+ }
+
+ /**
+ * dependant nodes grouped by kind
+ */
+ EnumMap<DependencyKind, List<Node>> depsByKind;
+
+ Node(String value) {
+ super(value);
+ this.depsByKind = new EnumMap<>(DependencyKind.class);
+ for (DependencyKind depKind : DependencyKind.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 DependencyKind.values();
+ }
+
+ @Override
+ public java.util.Collection<? extends Node> getDependenciesByKind(GraphUtils.DependencyKind dk) {
+ List<Node> deps = depsByKind.get(dk);
+ if (dk == DependencyKind.REQUIRES) {
+ return deps;
+ } else {
+ Set<Node> temp = new HashSet<>(deps);
+ temp.removeAll(depsByKind.get(DependencyKind.REQUIRES));
+ return temp;
+ }
+ }
+
+ @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("style", ((DependencyKind) dk).dotStyle);
+ return p;
+ }
+ }
+
+ /**
+ * This is a dependency node used to model symbol completion requests.
+ * Completion requests can come from either source or class.
+ */
+ 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.getQualifiedName().toString());
+ //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;
+ }
+ }
+
+ /**
+ * This is a dependency node used to model attribution actions triggered during
+ * source symbol completion. The possible kinds of attribution actions are
+ * captured in {@link AttributionNode}.
+ */
+ static class AttributionNode extends Node {
+
+ AttributionNode(AttributionKind ak, JCTree tree) {
+ super(ak.format(tree));
+ }
+
+ @Override
+ public Properties nodeAttributes() {
+ Properties p = super.nodeAttributes();
+ p.put("shape", "box");
+ p.put("style", "solid");
+ return p;
+ }
+ }
+
+ /**
+ * stack of dependency nodes currently being processed
+ */
+ Stack<Node> nodeStack = new Stack<>();
+
+ /**
+ * map containing all dependency nodes seen so far
+ */
+ Map<String, Node> dependencyNodeMap = new LinkedHashMap<>();
+
+ @Override
+ public void push(ClassSymbol s) {
+ Node n = new CompletionNode(s);
+ if (n == push(n)) {
+ s.completer = this;
+ }
+ }
+
+ @Override
+ public void push(AttributionKind ak, JCTree t) {
+ push(new AttributionNode(ak, t));
+ }
+
+ /**
+ * Push a new dependency on the stack.
+ */
+ protected Node push(Node newNode) {
+ 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(Node.DependencyKind.REQUIRES, newNode);
+ }
+ nodeStack.push(newNode);
+ return newNode;
+ }
+
+ @Override
+ public void pop() {
+ nodeStack.pop();
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (dependenciesFile != null) {
+ 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 (dependenciesModes.contains(DependenciesMode.SIDE_EFFECTS)) {
+ //add side-effects edges
+ new SideEffectVisitor().visit(dependencyNodeMap.values(), 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);
+ pop();
+ sym.completer = this;
+ }
+
+ /**
+ * This visitor is used to generate the special side-effect dependencies
+ * given a graph containing only standard dependencies.
+ */
+ private static class SideEffectVisitor extends NodeVisitor<String, 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) {
+ //if we are adding multiple dependencies to same node
+ //make order explicit via special 'side-effect' dependencies
+ List<Node> deps = from.depsByKind.get(dk);
+ int pos = deps.indexOf(to);
+ if (dk == Node.DependencyKind.REQUIRES && pos > 0) {
+ to.addDependency(Node.DependencyKind.SIDE_EFFECTS, deps.get(pos - 1));
+ }
+ }
+ }
+
+ /**
+ * This visitor is used to prune the graph from spurious edges using some heuristics.
+ */
+ private static class PruneVisitor extends NodeVisitor<String, 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) ||
+ from.depsByKind.get(Node.DependencyKind.REQUIRES).contains(to)) {
+ to.depsByKind.get(dk).remove(from);
+ }
+ }
+ }
+
+ /**
+ * This visitor is used to retain only completion nodes with given kind.
+ */
+ private class FilterVisitor extends NodeVisitor<String, 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) {
+ //do nothing
+ }
+
+ @Override
+ public void push(AttributionKind ak, JCTree t) {
+ //do nothing
+ }
+
+ @Override
+ public void pop() {
+ //do nothing
+ }
+ }
+}
--- a/langtools/src/share/classes/com/sun/tools/javac/util/GraphUtils.java Wed Jul 23 09:19:23 2014 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/util/GraphUtils.java Thu Jul 24 13:11:03 2014 +0100
@@ -25,6 +25,10 @@
package com.sun.tools.javac.util;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Properties;
+
/** <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
@@ -33,25 +37,65 @@
public class GraphUtils {
/**
- * Basic interface for defining various dependency kinds. All dependency kinds
- * must at least support basic capabilities to tell the DOT engine how to render them.
+ * Basic interface for defining various dependency kinds.
+ */
+ public interface DependencyKind { }
+
+ /**
+ * Common superinterfaces to all graph nodes.
*/
- public interface DependencyKind {
+ public interface Node<D, N extends Node<D, N>> {
+ /**
+ * visitor method.
+ */
+ <A> void accept(NodeVisitor<D, N, A> visitor, A arg);
+ }
+
+ /**
+ * Visitor for graph nodes.
+ */
+ static abstract class NodeVisitor<D, N extends Node<D, N>, A> {
+ /**
+ * Visitor action for nodes.
+ */
+ public abstract void visitNode(N node, A arg);
/**
- * Returns the DOT representation (to be used in a {@code style} attribute
- * that's most suited for this dependency kind.
+ * Visitor action for a dependency between 'from' and 'to' with given kind.
+ */
+ public abstract void visitDependency(DependencyKind dk, N from, N to, A arg);
+
+ /**
+ * Visitor entry point.
*/
- String getDotStyle();
+ public void visit(Collection<? extends N> nodes, A arg) {
+ for (N n : new ArrayList<>(nodes)) {
+ n.accept(this, arg);
+ }
+ }
+ }
+
+ /**
+ * Optional interface for nodes supporting dot-based representation.
+ */
+ public interface DottableNode<D, N extends DottableNode<D, N>> extends Node<D, N> {
+ /**
+ * Retrieves the set of dot attributes associated with the node.
+ */
+ Properties nodeAttributes();
+ /**
+ * Retrieves the set of dot attributes associated with a given dependency.
+ */
+ Properties dependencyAttributes(N to, DependencyKind dk);
}
/**
* This class is a basic abstract class for representing a node.
* A node is associated with a given data.
*/
- public static abstract class Node<D> {
+ public static abstract class AbstractNode<D, N extends AbstractNode<D, N>> implements Node<D, N> {
public final D data;
- public Node(D data) {
+ public AbstractNode(D data) {
this.data = data;
}
@@ -61,26 +105,32 @@
public abstract DependencyKind[] getSupportedDependencyKinds();
/**
- * Get all dependencies, regardless of their kind.
+ * Get all dependencies of a given kind
*/
- public abstract Iterable<? extends Node<D>> getAllDependencies();
-
- /**
- * Get a name for the dependency (of given kind) linking this node to a given node
- */
- public abstract String getDependencyName(Node<D> to, DependencyKind dk);
+ public abstract Collection<? extends N> getDependenciesByKind(DependencyKind dk);
@Override
public String toString() {
return data.toString();
}
+
+ @SuppressWarnings("unchecked")
+ public <A> void accept(NodeVisitor<D, N, A> visitor, A arg) {
+ visitor.visitNode((N)this, arg);
+ for (DependencyKind dk : getSupportedDependencyKinds()) {
+ for (N dep : new ArrayList<>(getDependenciesByKind(dk))) {
+ visitor.visitDependency(dk, (N)this, dep, arg);
+ }
+ }
+ }
}
/**
* This class specialized Node, by adding elements that are required in order
* to perform Tarjan computation of strongly connected components.
*/
- public static abstract class TarjanNode<D> extends Node<D> implements Comparable<TarjanNode<D>> {
+ public static abstract class TarjanNode<D, N extends TarjanNode<D, N>> extends AbstractNode<D, N>
+ implements Comparable<N> {
int index = -1;
int lowlink;
boolean active;
@@ -89,11 +139,9 @@
super(data);
}
- public abstract Iterable<? extends TarjanNode<D>> getAllDependencies();
+ public abstract Iterable<? extends N> getAllDependencies();
- public abstract Iterable<? extends TarjanNode<D>> getDependenciesByKind(DependencyKind dk);
-
- public int compareTo(TarjanNode<D> o) {
+ public int compareTo(N o) {
return (index < o.index) ? -1 : (index == o.index) ? 0 : 1;
}
}
@@ -102,7 +150,7 @@
* Tarjan's algorithm to determine strongly connected components of a
* directed graph in linear time. Works on TarjanNode.
*/
- public static <D, N extends TarjanNode<D>> List<? extends List<? extends N>> tarjan(Iterable<? extends N> nodes) {
+ public static <D, N extends TarjanNode<D, N>> List<? extends List<? extends N>> tarjan(Iterable<? extends N> nodes) {
ListBuffer<List<N>> cycles = new ListBuffer<>();
ListBuffer<N> stack = new ListBuffer<>();
int index = 0;
@@ -114,15 +162,13 @@
return cycles.toList();
}
- private static <D, N extends TarjanNode<D>> int tarjan(N v, int index, ListBuffer<N> stack, ListBuffer<List<N>> cycles) {
+ private static <D, N extends TarjanNode<D, N>> int tarjan(N v, int index, ListBuffer<N> stack, ListBuffer<List<N>> cycles) {
v.index = index;
v.lowlink = index;
index++;
stack.prepend(v);
v.active = true;
- for (TarjanNode<D> nd: v.getAllDependencies()) {
- @SuppressWarnings("unchecked")
- N n = (N)nd;
+ for (N n: v.getAllDependencies()) {
if (n.index == -1) {
tarjan(n, index, stack, cycles);
v.lowlink = Math.min(v.lowlink, n.lowlink);
@@ -149,24 +195,45 @@
* and {@code Node.printDependency} to display edge labels. The resulting
* representation is also customizable with a graph name and a header.
*/
- public static <D> String toDot(Iterable<? extends TarjanNode<D>> nodes, String name, String header) {
+ public static <D, N extends DottableNode<D, N>> String toDot(Collection<? extends N> nodes, String name, String header) {
StringBuilder buf = new StringBuilder();
buf.append(String.format("digraph %s {\n", name));
- buf.append(String.format("label = \"%s\";\n", header));
- //dump nodes
- for (TarjanNode<D> n : nodes) {
- buf.append(String.format("%s [label = \"%s\"];\n", n.hashCode(), n.toString()));
- }
- //dump arcs
- for (TarjanNode<D> from : nodes) {
- for (DependencyKind dk : from.getSupportedDependencyKinds()) {
- for (TarjanNode<D> to : from.getDependenciesByKind(dk)) {
- buf.append(String.format("%s -> %s [label = \" %s \" style = %s ];\n",
- from.hashCode(), to.hashCode(), from.getDependencyName(to, dk), dk.getDotStyle()));
- }
- }
- }
+ buf.append(String.format("label = %s;\n", DotVisitor.wrap(header)));
+ DotVisitor<D, N> dotVisitor = new DotVisitor<>();
+ dotVisitor.visit(nodes, buf);
buf.append("}\n");
return buf.toString();
}
+
+ /**
+ * This visitor is used to dump the contents of a set of nodes of type {@link DottableNode}
+ * onto a string builder.
+ */
+ public static class DotVisitor<D, N extends DottableNode<D, N>> extends NodeVisitor<D, N, StringBuilder> {
+
+ @Override
+ public void visitDependency(DependencyKind dk, N from, N to, StringBuilder buf) {
+ buf.append(String.format("%s -> %s", from.hashCode(), to.hashCode()));
+ buf.append(formatProperties(from.dependencyAttributes(to, dk)));
+ buf.append('\n');
+ }
+
+ @Override
+ public void visitNode(N node, StringBuilder buf) {
+ buf.append(String.format("%s ", node.hashCode()));
+ buf.append(formatProperties(node.nodeAttributes()));
+ buf.append('\n');
+ }
+
+ protected String formatProperties(Properties p) {
+ return p.toString().replaceAll(",", " ")
+ .replaceAll("\\{", "[")
+ .replaceAll("\\}", "]");
+ }
+
+ protected static String wrap(String s) {
+ String res = "\"" + s + "\"";
+ return res.replaceAll("\n", "");
+ }
+ }
}