Dependencies.java revision 3528:5538ba41cb97
1/* 2 * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package com.sun.tools.javac.util; 27 28import com.sun.tools.javac.code.Symbol; 29import com.sun.tools.javac.code.Symbol.ClassSymbol; 30import com.sun.tools.javac.code.Symbol.Completer; 31import com.sun.tools.javac.code.Symbol.CompletionFailure; 32import com.sun.tools.javac.main.JavaCompiler; 33import com.sun.tools.javac.util.GraphUtils.DependencyKind; 34import com.sun.tools.javac.util.GraphUtils.DotVisitor; 35import com.sun.tools.javac.util.GraphUtils.NodeVisitor; 36 37import java.io.Closeable; 38import java.io.FileWriter; 39import java.io.IOException; 40import java.util.ArrayList; 41import java.util.Arrays; 42import java.util.Collection; 43import java.util.EnumMap; 44import java.util.EnumSet; 45import java.util.LinkedHashMap; 46import java.util.List; 47import java.util.Map; 48import java.util.Properties; 49import java.util.Stack; 50 51import javax.tools.JavaFileObject; 52 53/** 54 * This class is used to track dependencies in the javac symbol completion process. 55 * 56 * <p><b>This is NOT part of any supported API. 57 * If you write code that depends on this, you do so at your own risk. 58 * This code and its internal interfaces are subject to change or 59 * deletion without notice.</b> 60 */ 61public abstract class Dependencies { 62 63 protected static final Context.Key<Dependencies> dependenciesKey = new Context.Key<>(); 64 65 public static Dependencies instance(Context context) { 66 Dependencies instance = context.get(dependenciesKey); 67 if (instance == null) { 68 //use a do-nothing implementation in case no other implementation has been set by preRegister 69 instance = new DummyDependencies(context); 70 } 71 return instance; 72 } 73 74 protected Dependencies(Context context) { 75 context.put(dependenciesKey, this); 76 } 77 78 /** 79 * Push a new completion node on the stack. 80 */ 81 abstract public void push(ClassSymbol s, CompletionCause phase); 82 83 /** 84 * Remove current dependency node from the stack. 85 */ 86 abstract public void pop(); 87 88 public enum CompletionCause implements GraphUtils.DependencyKind { 89 CLASS_READER, 90 HEADER_PHASE, 91 HIERARCHY_PHASE, 92 IMPORTS_PHASE, 93 MEMBER_ENTER, 94 MEMBERS_PHASE, 95 OTHER; 96 } 97 98 /** 99 * This class creates a graph of all dependencies as symbols are completed; 100 * when compilation finishes, the resulting dependency graph is then dumped 101 * onto a dot file. Several options are provided to customize the output of the graph. 102 */ 103 public static class GraphDependencies extends Dependencies implements Closeable, Completer { 104 105 /** 106 * set of enabled dependencies modes 107 */ 108 private EnumSet<DependenciesMode> dependenciesModes; 109 110 /** 111 * file in which the dependency graph should be written 112 */ 113 private String dependenciesFile; 114 115 /** 116 * Register a Context.Factory to create a Dependencies. 117 */ 118 public static void preRegister(Context context) { 119 context.put(dependenciesKey, (Context.Factory<Dependencies>) GraphDependencies::new); 120 } 121 122 /** 123 * Build a Dependencies instance. 124 */ 125 GraphDependencies(Context context) { 126 super(context); 127 //fetch filename 128 Options options = Options.instance(context); 129 String[] modes = options.get("debug.completionDeps").split(","); 130 for (String mode : modes) { 131 if (mode.startsWith("file=")) { 132 dependenciesFile = mode.substring(5); 133 } 134 } 135 //parse modes 136 dependenciesModes = DependenciesMode.getDependenciesModes(modes); 137 //add to closeables 138 JavaCompiler compiler = JavaCompiler.instance(context); 139 compiler.closeables = compiler.closeables.prepend(this); 140 } 141 142 enum DependenciesMode { 143 SOURCE("source"), 144 CLASS("class"), 145 REDUNDANT("redundant"); 146 147 final String opt; 148 149 DependenciesMode(String opt) { 150 this.opt = opt; 151 } 152 153 /** 154 * This method is used to parse the {@code completionDeps} option. 155 * Possible modes are separated by colon; a mode can be excluded by 156 * prepending '-' to its name. Finally, the special mode 'all' can be used to 157 * add all modes to the resulting enum. 158 */ 159 static EnumSet<DependenciesMode> getDependenciesModes(String[] modes) { 160 EnumSet<DependenciesMode> res = EnumSet.noneOf(DependenciesMode.class); 161 Collection<String> args = Arrays.asList(modes); 162 if (args.contains("all")) { 163 res = EnumSet.allOf(DependenciesMode.class); 164 } 165 for (DependenciesMode mode : values()) { 166 if (args.contains(mode.opt)) { 167 res.add(mode); 168 } else if (args.contains("-" + mode.opt)) { 169 res.remove(mode); 170 } 171 } 172 return res; 173 } 174 } 175 176 /** 177 * Class representing a node in the dependency graph. 178 */ 179 public static abstract class Node extends GraphUtils.AbstractNode<ClassSymbol, Node> 180 implements GraphUtils.DottableNode<ClassSymbol, Node> { 181 /** 182 * dependant nodes grouped by kind 183 */ 184 EnumMap<CompletionCause, List<Node>> depsByKind; 185 186 Node(ClassSymbol value) { 187 super(value); 188 this.depsByKind = new EnumMap<>(CompletionCause.class); 189 for (CompletionCause depKind : CompletionCause.values()) { 190 depsByKind.put(depKind, new ArrayList<Node>()); 191 } 192 } 193 194 void addDependency(DependencyKind depKind, Node dep) { 195 List<Node> deps = depsByKind.get(depKind); 196 if (!deps.contains(dep)) { 197 deps.add(dep); 198 } 199 } 200 201 @Override 202 public boolean equals(Object obj) { 203 return obj instanceof Node && data.equals(((Node) obj).data); 204 } 205 206 @Override 207 public int hashCode() { 208 return data.hashCode(); 209 } 210 211 @Override 212 public GraphUtils.DependencyKind[] getSupportedDependencyKinds() { 213 return CompletionCause.values(); 214 } 215 216 @Override 217 public java.util.Collection<? extends Node> getDependenciesByKind(DependencyKind dk) { 218 return depsByKind.get(dk); 219 } 220 221 @Override 222 public Properties nodeAttributes() { 223 Properties p = new Properties(); 224 p.put("label", DotVisitor.wrap(toString())); 225 return p; 226 } 227 228 @Override 229 public Properties dependencyAttributes(Node to, GraphUtils.DependencyKind dk) { 230 Properties p = new Properties(); 231 p.put("label", dk); 232 return p; 233 } 234 235 @Override 236 public String toString() { 237 return data.getQualifiedName().toString(); 238 } 239 } 240 241 /** 242 * This is a dependency node used to model symbol completion requests. 243 * Completion requests can come from either source or class. 244 */ 245 public static class CompletionNode extends Node { 246 247 /** 248 * Completion kind (source vs. classfile) 249 */ 250 enum Kind { 251 /** 252 * Source completion request 253 */ 254 SOURCE("solid"), 255 /** 256 * Classfile completion request 257 */ 258 CLASS("dotted"); 259 260 final String dotStyle; 261 262 Kind(String dotStyle) { 263 this.dotStyle = dotStyle; 264 } 265 } 266 267 final Kind ck; 268 269 CompletionNode(ClassSymbol sym) { 270 super(sym); 271 //infer completion kind by looking at the symbol fields 272 boolean fromClass = (sym.classfile == null && sym.sourcefile == null) || 273 (sym.classfile != null && sym.classfile.getKind() == JavaFileObject.Kind.CLASS); 274 ck = fromClass ? 275 CompletionNode.Kind.CLASS : 276 CompletionNode.Kind.SOURCE; 277 } 278 279 @Override 280 public Properties nodeAttributes() { 281 Properties p = super.nodeAttributes(); 282 p.put("style", ck.dotStyle); 283 p.put("shape", "ellipse"); 284 return p; 285 } 286 287 public ClassSymbol getClassSymbol() { 288 return data; 289 } 290 } 291 292 /** 293 * stack of dependency nodes currently being processed 294 */ 295 Stack<Node> nodeStack = new Stack<>(); 296 297 /** 298 * map containing all dependency nodes seen so far 299 */ 300 Map<ClassSymbol, Node> dependencyNodeMap = new LinkedHashMap<>(); 301 302 @Override 303 public void push(ClassSymbol s, CompletionCause phase) { 304 Node n = new CompletionNode(s); 305 if (n == push(n, phase)) { 306 s.completer = this; 307 } 308 } 309 310 /** 311 * Push a new dependency on the stack. 312 */ 313 protected Node push(Node newNode, CompletionCause cc) { 314 Node cachedNode = dependencyNodeMap.get(newNode.data); 315 if (cachedNode == null) { 316 dependencyNodeMap.put(newNode.data, newNode); 317 } else { 318 newNode = cachedNode; 319 } 320 if (!nodeStack.isEmpty()) { 321 Node currentNode = nodeStack.peek(); 322 currentNode.addDependency(cc, newNode); 323 } 324 nodeStack.push(newNode); 325 return newNode; 326 } 327 328 @Override 329 public void pop() { 330 nodeStack.pop(); 331 } 332 333 @Override 334 public void close() throws IOException { 335 if (!dependenciesModes.contains(DependenciesMode.REDUNDANT)) { 336 //prune spurious edges 337 new PruneVisitor().visit(dependencyNodeMap.values(), null); 338 } 339 if (!dependenciesModes.contains(DependenciesMode.CLASS)) { 340 //filter class completions 341 new FilterVisitor(CompletionNode.Kind.SOURCE).visit(dependencyNodeMap.values(), null); 342 } 343 if (!dependenciesModes.contains(DependenciesMode.SOURCE)) { 344 //filter source completions 345 new FilterVisitor(CompletionNode.Kind.CLASS).visit(dependencyNodeMap.values(), null); 346 } 347 if (dependenciesFile != null) { 348 //write to file 349 try (FileWriter fw = new FileWriter(dependenciesFile)) { 350 fw.append(GraphUtils.toDot(dependencyNodeMap.values(), "CompletionDeps", "")); 351 } 352 } 353 } 354 355 @Override 356 public void complete(Symbol sym) throws CompletionFailure { 357 push((ClassSymbol)sym, CompletionCause.OTHER); 358 pop(); 359 sym.completer = this; 360 } 361 362 @Override 363 public boolean isTerminal() { 364 return true; 365 } 366 367 public Collection<Node> getNodes() { 368 return dependencyNodeMap.values(); 369 } 370 371 /** 372 * This visitor is used to prune the graph from spurious edges using some heuristics. 373 */ 374 private static class PruneVisitor extends NodeVisitor<ClassSymbol, Node, Void> { 375 @Override 376 public void visitNode(Node node, Void arg) { 377 //do nothing 378 } 379 380 @Override 381 public void visitDependency(GraphUtils.DependencyKind dk, Node from, Node to, Void arg) { 382 //heuristic - skips dependencies that are likely to be fake 383 if (from.equals(to)) { 384 to.depsByKind.get(dk).remove(from); 385 } 386 } 387 } 388 389 /** 390 * This visitor is used to retain only completion nodes with given kind. 391 */ 392 private class FilterVisitor extends NodeVisitor<ClassSymbol, Node, Void> { 393 394 CompletionNode.Kind ck; 395 396 private FilterVisitor(CompletionNode.Kind ck) { 397 this.ck = ck; 398 } 399 400 @Override 401 public void visitNode(Node node, Void arg) { 402 if (node instanceof CompletionNode) { 403 if (((CompletionNode) node).ck != ck) { 404 dependencyNodeMap.remove(node.data); 405 } 406 } 407 } 408 409 @Override 410 public void visitDependency(GraphUtils.DependencyKind dk, Node from, Node to, Void arg) { 411 if (to instanceof CompletionNode) { 412 if (((CompletionNode) to).ck != ck) { 413 from.depsByKind.get(dk).remove(to); 414 } 415 } 416 } 417 } 418 } 419 420 /** 421 * Dummy class to be used when dependencies options are not set. This keeps 422 * performance cost of calling push/pop methods during completion marginally low. 423 */ 424 private static class DummyDependencies extends Dependencies { 425 426 private DummyDependencies(Context context) { 427 super(context); 428 } 429 430 @Override 431 public void push(ClassSymbol s, CompletionCause phase) { 432 //do nothing 433 } 434 435 @Override 436 public void pop() { 437 //do nothing 438 } 439 } 440} 441