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