/*
 * Copyright (c) 2014, 2019, 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.

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.

/** * 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.
/** * Push a new completion node on the stack. */
abstract public void push(ClassSymbol s, CompletionCause phase);
Remove current dependency node from the stack.
/** * 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, RECORD_PHASE, 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.
/** * 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
/** * set of enabled dependencies modes */
private EnumSet<DependenciesMode> dependenciesModes;
file in which the dependency graph should be written
/** * file in which the dependency graph should be written */
private String dependenciesFile;
Register a Context.Factory to create a Dependencies.
/** * 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.
/** * 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 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.
/** * 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.
/** * 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
/** * 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.
/** * 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)
/** * Completion kind (source vs. classfile) */
enum Kind {
Source completion request
/** * Source completion request */
SOURCE("solid"),
Classfile completion request
/** * 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 of dependency nodes currently being processed */
Stack<Node> nodeStack = new Stack<>();
map containing all dependency nodes seen so far
/** * 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.
/** * 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.
/** * 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.
/** * 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.
/** * 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 } } }