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