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