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