Dependencies.java revision 3528:5538ba41cb97
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
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.util.GraphUtils.DependencyKind;
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.LinkedHashMap;
46import java.util.List;
47import java.util.Map;
48import java.util.Properties;
49import java.util.Stack;
50
51import 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 */
61public 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}
441