/*
* 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
}
}
}