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 
  26 package com.sun.tools.javac.util;
  27 
  28 import com.sun.tools.javac.code.Symbol;
  29 import com.sun.tools.javac.code.Symbol.ClassSymbol;
  30 import com.sun.tools.javac.code.Symbol.Completer;
  31 import com.sun.tools.javac.code.Symbol.CompletionFailure;
  32 import com.sun.tools.javac.main.JavaCompiler;
  33 import com.sun.tools.javac.util.GraphUtils.DependencyKind;
  34 import com.sun.tools.javac.util.GraphUtils.DotVisitor;
  35 import com.sun.tools.javac.util.GraphUtils.NodeVisitor;
  36 
  37 import java.io.Closeable;
  38 import java.io.FileWriter;
  39 import java.io.IOException;
  40 import java.util.ArrayList;
  41 import java.util.Arrays;
  42 import java.util.Collection;
  43 import java.util.EnumMap;
  44 import java.util.EnumSet;
  45 import java.util.LinkedHashMap;
  46 import java.util.List;
  47 import java.util.Map;
  48 import java.util.Properties;
  49 import java.util.Stack;
  50 
  51 import 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  */
  61 public 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         RECORD_PHASE,
  95         MEMBERS_PHASE,
  96         OTHER;
  97     }
  98 
  99     /**
 100      * This class creates a graph of all dependencies as symbols are completed;
 101      * when compilation finishes, the resulting dependency graph is then dumped
 102      * onto a dot file. Several options are provided to customize the output of the graph.
 103      */
 104     public static class GraphDependencies extends Dependencies implements Closeable, Completer {
 105 
 106         /**
 107          * set of enabled dependencies modes
 108          */
 109         private EnumSet<DependenciesMode> dependenciesModes;
 110 
 111         /**
 112          * file in which the dependency graph should be written
 113          */
 114         private String dependenciesFile;
 115 
 116         /**
 117          * Register a Context.Factory to create a Dependencies.
 118          */
 119         public static void preRegister(Context context) {
 120             context.put(dependenciesKey, (Context.Factory<Dependencies>) GraphDependencies::new);
 121         }
 122 
 123         /**
 124          * Build a Dependencies instance.
 125          */
 126         GraphDependencies(Context context) {
 127             super(context);
 128             //fetch filename
 129             Options options = Options.instance(context);
 130             String[] modes = options.get("debug.completionDeps").split(",");
 131             for (String mode : modes) {
 132                 if (mode.startsWith("file=")) {
 133                     dependenciesFile = mode.substring(5);
 134                 }
 135             }
 136             //parse modes
 137             dependenciesModes = DependenciesMode.getDependenciesModes(modes);
 138             //add to closeables
 139             JavaCompiler compiler = JavaCompiler.instance(context);
 140             compiler.closeables = compiler.closeables.prepend(this);
 141         }
 142 
 143         enum DependenciesMode {
 144             SOURCE("source"),
 145             CLASS("class"),
 146             REDUNDANT("redundant");
 147 
 148             final String opt;
 149 
 150             DependenciesMode(String opt) {
 151                 this.opt = opt;
 152             }
 153 
 154             /**
 155              * This method is used to parse the {@code completionDeps} option.
 156              * Possible modes are separated by colon; a mode can be excluded by
 157              * prepending '-' to its name. Finally, the special mode 'all' can be used to
 158              * add all modes to the resulting enum.
 159              */
 160             static EnumSet<DependenciesMode> getDependenciesModes(String[] modes) {
 161                 EnumSet<DependenciesMode> res = EnumSet.noneOf(DependenciesMode.class);
 162                 Collection<String> args = Arrays.asList(modes);
 163                 if (args.contains("all")) {
 164                     res = EnumSet.allOf(DependenciesMode.class);
 165                 }
 166                 for (DependenciesMode mode : values()) {
 167                     if (args.contains(mode.opt)) {
 168                         res.add(mode);
 169                     } else if (args.contains("-" + mode.opt)) {
 170                         res.remove(mode);
 171                     }
 172                 }
 173                 return res;
 174             }
 175         }
 176 
 177         /**
 178          * Class representing a node in the dependency graph.
 179          */
 180         public static abstract class Node extends GraphUtils.AbstractNode<ClassSymbol, Node>
 181                 implements GraphUtils.DottableNode<ClassSymbol, Node> {
 182             /**
 183              * dependant nodes grouped by kind
 184              */
 185             EnumMap<CompletionCause, List<Node>> depsByKind;
 186 
 187             Node(ClassSymbol value) {
 188                 super(value);
 189                 this.depsByKind = new EnumMap<>(CompletionCause.class);
 190                 for (CompletionCause depKind : CompletionCause.values()) {
 191                     depsByKind.put(depKind, new ArrayList<Node>());
 192                 }
 193             }
 194 
 195             void addDependency(DependencyKind depKind, Node dep) {
 196                 List<Node> deps = depsByKind.get(depKind);
 197                 if (!deps.contains(dep)) {
 198                     deps.add(dep);
 199                 }
 200             }
 201 
 202             @Override
 203             public boolean equals(Object obj) {
 204                 return obj instanceof Node && data.equals(((Node) obj).data);
 205             }
 206 
 207             @Override
 208             public int hashCode() {
 209                 return data.hashCode();
 210             }
 211 
 212             @Override
 213             public GraphUtils.DependencyKind[] getSupportedDependencyKinds() {
 214                 return CompletionCause.values();
 215             }
 216 
 217             @Override
 218             public java.util.Collection<? extends Node> getDependenciesByKind(DependencyKind dk) {
 219                 return depsByKind.get(dk);
 220             }
 221 
 222             @Override
 223             public Properties nodeAttributes() {
 224                 Properties p = new Properties();
 225                 p.put("label", DotVisitor.wrap(toString()));
 226                 return p;
 227             }
 228 
 229             @Override
 230             public Properties dependencyAttributes(Node to, GraphUtils.DependencyKind dk) {
 231                 Properties p = new Properties();
 232                 p.put("label", dk);
 233                 return p;
 234             }
 235 
 236             @Override
 237             public String toString() {
 238                 return data.getQualifiedName().toString();
 239             }
 240         }
 241 
 242         /**
 243          * This is a dependency node used to model symbol completion requests.
 244          * Completion requests can come from either source or class.
 245          */
 246         public static class CompletionNode extends Node {
 247 
 248             /**
 249              * Completion kind (source vs. classfile)
 250              */
 251             enum Kind {
 252                 /**
 253                  * Source completion request
 254                  */
 255                 SOURCE("solid"),
 256                 /**
 257                  * Classfile completion request
 258                  */
 259                 CLASS("dotted");
 260 
 261                 final String dotStyle;
 262 
 263                 Kind(String dotStyle) {
 264                     this.dotStyle = dotStyle;
 265                 }
 266             }
 267 
 268             final Kind ck;
 269 
 270             CompletionNode(ClassSymbol sym) {
 271                 super(sym);
 272                 //infer completion kind by looking at the symbol fields
 273                 boolean fromClass = (sym.classfile == null && sym.sourcefile == null) ||
 274                         (sym.classfile != null && sym.classfile.getKind() == JavaFileObject.Kind.CLASS);
 275                 ck = fromClass ?
 276                         CompletionNode.Kind.CLASS :
 277                         CompletionNode.Kind.SOURCE;
 278             }
 279 
 280             @Override
 281             public Properties nodeAttributes() {
 282                 Properties p = super.nodeAttributes();
 283                 p.put("style", ck.dotStyle);
 284                 p.put("shape", "ellipse");
 285                 return p;
 286             }
 287 
 288             public ClassSymbol getClassSymbol() {
 289                 return data;
 290             }
 291         }
 292 
 293         /**
 294          * stack of dependency nodes currently being processed
 295          */
 296         Stack<Node> nodeStack = new Stack<>();
 297 
 298         /**
 299          * map containing all dependency nodes seen so far
 300          */
 301         Map<ClassSymbol, Node> dependencyNodeMap = new LinkedHashMap<>();
 302 
 303         @Override
 304         public void push(ClassSymbol s, CompletionCause phase) {
 305             Node n = new CompletionNode(s);
 306             if (n == push(n, phase)) {
 307                 s.completer = this;
 308             }
 309         }
 310 
 311         /**
 312          * Push a new dependency on the stack.
 313          */
 314         protected Node push(Node newNode, CompletionCause cc) {
 315             Node cachedNode = dependencyNodeMap.get(newNode.data);
 316             if (cachedNode == null) {
 317                 dependencyNodeMap.put(newNode.data, newNode);
 318             } else {
 319                 newNode = cachedNode;
 320             }
 321             if (!nodeStack.isEmpty()) {
 322                 Node currentNode = nodeStack.peek();
 323                 currentNode.addDependency(cc, newNode);
 324             }
 325             nodeStack.push(newNode);
 326             return newNode;
 327         }
 328 
 329         @Override
 330         public void pop() {
 331             nodeStack.pop();
 332         }
 333 
 334         @Override
 335         public void close() throws IOException {
 336             if (!dependenciesModes.contains(DependenciesMode.REDUNDANT)) {
 337                 //prune spurious edges
 338                 new PruneVisitor().visit(dependencyNodeMap.values(), null);
 339             }
 340             if (!dependenciesModes.contains(DependenciesMode.CLASS)) {
 341                 //filter class completions
 342                 new FilterVisitor(CompletionNode.Kind.SOURCE).visit(dependencyNodeMap.values(), null);
 343             }
 344             if (!dependenciesModes.contains(DependenciesMode.SOURCE)) {
 345                 //filter source completions
 346                 new FilterVisitor(CompletionNode.Kind.CLASS).visit(dependencyNodeMap.values(), null);
 347             }
 348             if (dependenciesFile != null) {
 349                 //write to file
 350                 try (FileWriter fw = new FileWriter(dependenciesFile)) {
 351                     fw.append(GraphUtils.toDot(dependencyNodeMap.values(), "CompletionDeps", ""));
 352                 }
 353             }
 354         }
 355 
 356         @Override
 357         public void complete(Symbol sym) throws CompletionFailure {
 358             push((ClassSymbol)sym, CompletionCause.OTHER);
 359             pop();
 360             sym.completer = this;
 361         }
 362 
 363         @Override
 364         public boolean isTerminal() {
 365             return true;
 366         }
 367 
 368         public Collection<Node> getNodes() {
 369             return dependencyNodeMap.values();
 370         }
 371 
 372         /**
 373          * This visitor is used to prune the graph from spurious edges using some heuristics.
 374          */
 375         private static class PruneVisitor extends NodeVisitor<ClassSymbol, Node, Void> {
 376             @Override
 377             public void visitNode(Node node, Void arg) {
 378                 //do nothing
 379             }
 380 
 381             @Override
 382             public void visitDependency(GraphUtils.DependencyKind dk, Node from, Node to, Void arg) {
 383                 //heuristic - skips dependencies that are likely to be fake
 384                 if (from.equals(to)) {
 385                     to.depsByKind.get(dk).remove(from);
 386                 }
 387             }
 388         }
 389 
 390         /**
 391          * This visitor is used to retain only completion nodes with given kind.
 392          */
 393         private class FilterVisitor extends NodeVisitor<ClassSymbol, Node, Void> {
 394 
 395             CompletionNode.Kind ck;
 396 
 397             private FilterVisitor(CompletionNode.Kind ck) {
 398                 this.ck = ck;
 399             }
 400 
 401             @Override
 402             public void visitNode(Node node, Void arg) {
 403                 if (node instanceof CompletionNode) {
 404                     if (((CompletionNode) node).ck != ck) {
 405                         dependencyNodeMap.remove(node.data);
 406                     }
 407                 }
 408             }
 409 
 410             @Override
 411             public void visitDependency(GraphUtils.DependencyKind dk, Node from, Node to, Void arg) {
 412                 if (to instanceof CompletionNode) {
 413                     if (((CompletionNode) to).ck != ck) {
 414                         from.depsByKind.get(dk).remove(to);
 415                     }
 416                 }
 417             }
 418         }
 419     }
 420 
 421     /**
 422      * Dummy class to be used when dependencies options are not set. This keeps
 423      * performance cost of calling push/pop methods during completion marginally low.
 424      */
 425     private static class DummyDependencies extends Dependencies {
 426 
 427         private DummyDependencies(Context context) {
 428             super(context);
 429         }
 430 
 431         @Override
 432         public void push(ClassSymbol s, CompletionCause phase) {
 433             //do nothing
 434         }
 435 
 436         @Override
 437         public void pop() {
 438             //do nothing
 439         }
 440     }
 441 }