Dependencies.java revision 2897:524255b0bec0
1/* 2 * Copyright (c) 2014, 2015, 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.tree.JCTree; 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.HashSet; 46import java.util.LinkedHashMap; 47import java.util.List; 48import java.util.Map; 49import java.util.Properties; 50import java.util.Set; 51import java.util.Stack; 52 53import javax.tools.JavaFileObject; 54 55/** 56 * This class is used to track dependencies in the javac symbol completion process. 57 * 58 * <p><b>This is NOT part of any supported API. 59 * If you write code that depends on this, you do so at your own risk. 60 * This code and its internal interfaces are subject to change or 61 * deletion without notice.</b> 62 */ 63public abstract class Dependencies { 64 65 protected static final Context.Key<Dependencies> dependenciesKey = new Context.Key<>(); 66 67 public static Dependencies instance(Context context) { 68 Dependencies instance = context.get(dependenciesKey); 69 if (instance == null) { 70 //use a do-nothing implementation in case no other implementation has been set by preRegister 71 instance = new DummyDependencies(context); 72 } 73 return instance; 74 } 75 76 protected Dependencies(Context context) { 77 context.put(dependenciesKey, this); 78 } 79 80 /** 81 * This enum models different kinds of attribution actions triggered during 82 * symbol completion. 83 */ 84 public enum AttributionKind { 85 /** 86 * Attribution of superclass (i.e. @{code extends} clause). 87 */ 88 EXTENDS { 89 @Override 90 String format(JCTree tree) { 91 return "extends " + super.format(tree); 92 } 93 }, 94 /** 95 * Attribution of superinterface (i.e. an type in the @{code interface} clause). 96 */ 97 IMPLEMENTS { 98 @Override 99 String format(JCTree tree) { 100 return "implements " + super.format(tree); 101 } 102 }, 103 /** 104 * Attribution of an import statement 105 */ 106 IMPORT, 107 /** 108 * Attribution of type-variable bound 109 */ 110 TVAR { 111 @Override 112 String format(JCTree tree) { 113 return "<" + super.format(tree) + ">"; 114 } 115 }; 116 117 String format(JCTree tree) { 118 return tree.toString(); 119 } 120 } 121 122 /** 123 * Push a new completion node on the stack. 124 */ 125 abstract public void push(ClassSymbol s, CompletionCause phase); 126 127 /** 128 * Push a new attribution node on the stack. 129 */ 130 abstract public void push(AttributionKind ak, JCTree t); 131 132 /** 133 * Remove current dependency node from the stack. 134 */ 135 abstract public void pop(); 136 137 public enum CompletionCause { 138 CLASS_READER, 139 HEADER_PHASE, 140 HIERARCHY_PHASE, 141 IMPORTS_PHASE, 142 MEMBER_ENTER, 143 MEMBERS_PHASE; 144 } 145 146 /** 147 * This class creates a graph of all dependencies as symbols are completed; 148 * when compilation finishes, the resulting dependecy graph is then dumped 149 * onto a dot file. Several options are provided to customise the output of the graph. 150 */ 151 public static class GraphDependencies extends Dependencies implements Closeable, Completer { 152 153 /** 154 * set of enabled dependencies modes 155 */ 156 private EnumSet<DependenciesMode> dependenciesModes; 157 158 /** 159 * file in which the dependency graph should be written 160 */ 161 private String dependenciesFile; 162 163 /** 164 * Register a Context.Factory to create a Dependencies. 165 */ 166 public static void preRegister(final Context context) { 167 context.put(dependenciesKey, new Context.Factory<Dependencies>() { 168 public Dependencies make(Context c) { 169 Dependencies deps = new GraphDependencies(context); 170 return deps; 171 } 172 }); 173 } 174 175 /** 176 * Build a Dependencies instance. 177 */ 178 GraphDependencies(Context context) { 179 super(context); 180 Options options = Options.instance(context); 181 //fetch filename 182 String[] modes = options.get("completionDeps").split(","); 183 for (String mode : modes) { 184 if (mode.startsWith("file=")) { 185 dependenciesFile = mode.substring(5); 186 } 187 } 188 //parse modes 189 dependenciesModes = DependenciesMode.getDependenciesModes(modes); 190 //add to closeables 191 JavaCompiler compiler = JavaCompiler.instance(context); 192 compiler.closeables = compiler.closeables.prepend(this); 193 } 194 195 enum DependenciesMode { 196 SOURCE("source"), 197 CLASS("class"), 198 REDUNDANT("redundant"), 199 SIDE_EFFECTS("side-effects"); 200 201 final String opt; 202 203 private DependenciesMode(String opt) { 204 this.opt = opt; 205 } 206 207 /** 208 * This method is used to parse the {@code completionDeps} option. 209 * Possible modes are separated by colon; a mode can be excluded by 210 * prepending '-' to its name. Finally, the special mode 'all' can be used to 211 * add all modes to the resulting enum. 212 */ 213 static EnumSet<DependenciesMode> getDependenciesModes(String[] modes) { 214 EnumSet<DependenciesMode> res = EnumSet.noneOf(DependenciesMode.class); 215 Collection<String> args = Arrays.asList(modes); 216 if (args.contains("all")) { 217 res = EnumSet.allOf(DependenciesMode.class); 218 } 219 for (DependenciesMode mode : values()) { 220 if (args.contains(mode.opt)) { 221 res.add(mode); 222 } else if (args.contains("-" + mode.opt)) { 223 res.remove(mode); 224 } 225 } 226 return res; 227 } 228 } 229 230 /** 231 * Class representing a node in the dependency graph. Nodes are of two main 232 * kinds: (i) symbol nodes, corresponding to symbol completion requests 233 * (either from source or classfile); (ii) attribution nodes, corresponding to 234 * attribution actions triggered during (source) completion. 235 */ 236 static abstract class Node extends GraphUtils.AbstractNode<String, Node> 237 implements GraphUtils.DottableNode<String, Node> { 238 239 /** 240 * Model the dependencies between nodes. 241 */ 242 enum DependencyKind implements GraphUtils.DependencyKind { 243 /** 244 * standard dependency - i.e. completion of the source node depends 245 * on completion of the sink node. 246 */ 247 REQUIRES("solid"), 248 /** 249 * soft dependencies - i.e. completion of the source node depends 250 * on side-effects of the source node. These dependencies are meant 251 * to capture the order in which javac processes all dependants of a given node. 252 */ 253 SIDE_EFFECTS("dashed"); 254 255 final String dotStyle; 256 257 DependencyKind(String dotStyle) { 258 this.dotStyle = dotStyle; 259 } 260 } 261 262 /** 263 * dependant nodes grouped by kind 264 */ 265 EnumMap<DependencyKind, List<Node>> depsByKind; 266 267 Node(String value) { 268 super(value); 269 this.depsByKind = new EnumMap<>(DependencyKind.class); 270 for (DependencyKind depKind : DependencyKind.values()) { 271 depsByKind.put(depKind, new ArrayList<Node>()); 272 } 273 } 274 275 void addDependency(DependencyKind depKind, Node dep) { 276 List<Node> deps = depsByKind.get(depKind); 277 if (!deps.contains(dep)) { 278 deps.add(dep); 279 } 280 } 281 282 @Override 283 public boolean equals(Object obj) { 284 return obj instanceof Node && 285 data.equals(((Node) obj).data); 286 } 287 288 @Override 289 public int hashCode() { 290 return data.hashCode(); 291 } 292 293 @Override 294 public GraphUtils.DependencyKind[] getSupportedDependencyKinds() { 295 return DependencyKind.values(); 296 } 297 298 @Override 299 public java.util.Collection<? extends Node> getDependenciesByKind(GraphUtils.DependencyKind dk) { 300 List<Node> deps = depsByKind.get(dk); 301 if (dk == DependencyKind.REQUIRES) { 302 return deps; 303 } else { 304 Set<Node> temp = new HashSet<>(deps); 305 temp.removeAll(depsByKind.get(DependencyKind.REQUIRES)); 306 return temp; 307 } 308 } 309 310 @Override 311 public Properties nodeAttributes() { 312 Properties p = new Properties(); 313 p.put("label", DotVisitor.wrap(toString())); 314 return p; 315 } 316 317 @Override 318 public Properties dependencyAttributes(Node to, GraphUtils.DependencyKind dk) { 319 Properties p = new Properties(); 320 p.put("style", ((DependencyKind) dk).dotStyle); 321 return p; 322 } 323 } 324 325 /** 326 * This is a dependency node used to model symbol completion requests. 327 * Completion requests can come from either source or class. 328 */ 329 static class CompletionNode extends Node { 330 331 /** 332 * Completion kind (source vs. classfile) 333 */ 334 enum Kind { 335 /** 336 * Source completion request 337 */ 338 SOURCE("solid"), 339 /** 340 * Classfile completion request 341 */ 342 CLASS("dotted"); 343 344 final String dotStyle; 345 346 Kind(String dotStyle) { 347 this.dotStyle = dotStyle; 348 } 349 } 350 351 final Kind ck; 352 353 CompletionNode(ClassSymbol sym) { 354 super(sym.getQualifiedName().toString()); 355 //infer completion kind by looking at the symbol fields 356 boolean fromClass = (sym.classfile == null && sym.sourcefile == null) || 357 (sym.classfile != null && sym.classfile.getKind() == JavaFileObject.Kind.CLASS); 358 ck = fromClass ? 359 CompletionNode.Kind.CLASS : 360 CompletionNode.Kind.SOURCE; 361 } 362 363 @Override 364 public Properties nodeAttributes() { 365 Properties p = super.nodeAttributes(); 366 p.put("style", ck.dotStyle); 367 p.put("shape", "ellipse"); 368 return p; 369 } 370 } 371 372 /** 373 * This is a dependency node used to model attribution actions triggered during 374 * source symbol completion. The possible kinds of attribution actions are 375 * captured in {@link AttributionNode}. 376 */ 377 static class AttributionNode extends Node { 378 379 AttributionNode(AttributionKind ak, JCTree tree) { 380 super(ak.format(tree)); 381 } 382 383 @Override 384 public Properties nodeAttributes() { 385 Properties p = super.nodeAttributes(); 386 p.put("shape", "box"); 387 p.put("style", "solid"); 388 return p; 389 } 390 } 391 392 /** 393 * stack of dependency nodes currently being processed 394 */ 395 Stack<Node> nodeStack = new Stack<>(); 396 397 /** 398 * map containing all dependency nodes seen so far 399 */ 400 Map<String, Node> dependencyNodeMap = new LinkedHashMap<>(); 401 402 @Override 403 public void push(ClassSymbol s, CompletionCause phase) { 404 Node n = new CompletionNode(s); 405 if (n == push(n)) { 406 s.completer = this; 407 } 408 } 409 410 @Override 411 public void push(AttributionKind ak, JCTree t) { 412 push(new AttributionNode(ak, t)); 413 } 414 415 /** 416 * Push a new dependency on the stack. 417 */ 418 protected Node push(Node newNode) { 419 Node cachedNode = dependencyNodeMap.get(newNode.data); 420 if (cachedNode == null) { 421 dependencyNodeMap.put(newNode.data, newNode); 422 } else { 423 newNode = cachedNode; 424 } 425 if (!nodeStack.isEmpty()) { 426 Node currentNode = nodeStack.peek(); 427 currentNode.addDependency(Node.DependencyKind.REQUIRES, newNode); 428 } 429 nodeStack.push(newNode); 430 return newNode; 431 } 432 433 @Override 434 public void pop() { 435 nodeStack.pop(); 436 } 437 438 @Override 439 public void close() throws IOException { 440 if (dependenciesFile != null) { 441 if (!dependenciesModes.contains(DependenciesMode.REDUNDANT)) { 442 //prune spurious edges 443 new PruneVisitor().visit(dependencyNodeMap.values(), null); 444 } 445 if (!dependenciesModes.contains(DependenciesMode.CLASS)) { 446 //filter class completions 447 new FilterVisitor(CompletionNode.Kind.SOURCE).visit(dependencyNodeMap.values(), null); 448 } 449 if (!dependenciesModes.contains(DependenciesMode.SOURCE)) { 450 //filter source completions 451 new FilterVisitor(CompletionNode.Kind.CLASS).visit(dependencyNodeMap.values(), null); 452 } 453 if (dependenciesModes.contains(DependenciesMode.SIDE_EFFECTS)) { 454 //add side-effects edges 455 new SideEffectVisitor().visit(dependencyNodeMap.values(), null); 456 } 457 //write to file 458 try (FileWriter fw = new FileWriter(dependenciesFile)) { 459 fw.append(GraphUtils.toDot(dependencyNodeMap.values(), "CompletionDeps", "")); 460 } 461 } 462 } 463 464 @Override 465 public void complete(Symbol sym) throws CompletionFailure { 466 push((ClassSymbol) sym, null); 467 pop(); 468 sym.completer = this; 469 } 470 471 @Override 472 public boolean isTerminal() { 473 return true; 474 } 475 476 /** 477 * This visitor is used to generate the special side-effect dependencies 478 * given a graph containing only standard dependencies. 479 */ 480 private static class SideEffectVisitor extends NodeVisitor<String, Node, Void> { 481 @Override 482 public void visitNode(Node node, Void arg) { 483 //do nothing 484 } 485 486 @Override 487 public void visitDependency(GraphUtils.DependencyKind dk, Node from, Node to, Void arg) { 488 //if we are adding multiple dependencies to same node 489 //make order explicit via special 'side-effect' dependencies 490 List<Node> deps = from.depsByKind.get(dk); 491 int pos = deps.indexOf(to); 492 if (dk == Node.DependencyKind.REQUIRES && pos > 0) { 493 to.addDependency(Node.DependencyKind.SIDE_EFFECTS, deps.get(pos - 1)); 494 } 495 } 496 } 497 498 /** 499 * This visitor is used to prune the graph from spurious edges using some heuristics. 500 */ 501 private static class PruneVisitor extends NodeVisitor<String, Node, Void> { 502 @Override 503 public void visitNode(Node node, Void arg) { 504 //do nothing 505 } 506 507 @Override 508 public void visitDependency(GraphUtils.DependencyKind dk, Node from, Node to, Void arg) { 509 //heuristic - skips dependencies that are likely to be fake 510 if (from.equals(to) || 511 from.depsByKind.get(Node.DependencyKind.REQUIRES).contains(to)) { 512 to.depsByKind.get(dk).remove(from); 513 } 514 } 515 } 516 517 /** 518 * This visitor is used to retain only completion nodes with given kind. 519 */ 520 private class FilterVisitor extends NodeVisitor<String, Node, Void> { 521 522 CompletionNode.Kind ck; 523 524 private FilterVisitor(CompletionNode.Kind ck) { 525 this.ck = ck; 526 } 527 528 @Override 529 public void visitNode(Node node, Void arg) { 530 if (node instanceof CompletionNode) { 531 if (((CompletionNode) node).ck != ck) { 532 dependencyNodeMap.remove(node.data); 533 } 534 } 535 } 536 537 @Override 538 public void visitDependency(GraphUtils.DependencyKind dk, Node from, Node to, Void arg) { 539 if (to instanceof CompletionNode) { 540 if (((CompletionNode) to).ck != ck) { 541 from.depsByKind.get(dk).remove(to); 542 } 543 } 544 } 545 } 546 } 547 548 /** 549 * Dummy class to be used when dependencies options are not set. This keeps 550 * performance cost of calling push/pop methods during completion marginally low. 551 */ 552 private static class DummyDependencies extends Dependencies { 553 554 private DummyDependencies(Context context) { 555 super(context); 556 } 557 558 @Override 559 public void push(ClassSymbol s, CompletionCause phase) { 560 //do nothing 561 } 562 563 @Override 564 public void push(AttributionKind ak, JCTree t) { 565 //do nothing 566 } 567 568 @Override 569 public void pop() { 570 //do nothing 571 } 572 } 573} 574