Graph.java revision 3792:d516975e8110
1/*
2 * Copyright (c) 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 */
25package com.sun.tools.jdeps;
26
27import java.io.PrintWriter;
28import java.lang.module.ModuleDescriptor;
29import java.lang.module.ModuleFinder;
30import java.lang.module.ModuleReference;
31import java.util.Collections;
32import java.util.Deque;
33import java.util.HashMap;
34import java.util.HashSet;
35import java.util.LinkedList;
36import java.util.Map;
37import java.util.Set;
38import java.util.function.Consumer;
39import java.util.function.Predicate;
40import java.util.stream.Collectors;
41import java.util.stream.Stream;
42
43public final class Graph<T> {
44    private final Set<T> nodes;
45    private final Map<T, Set<T>> edges;
46
47    public Graph(Set<T> nodes, Map<T, Set<T>> edges) {
48        this.nodes = Collections.unmodifiableSet(nodes);
49        this.edges = Collections.unmodifiableMap(edges);
50    }
51
52    public Set<T> nodes() {
53        return nodes;
54    }
55
56    public Map<T, Set<T>> edges() {
57        return edges;
58    }
59
60    public Set<T> adjacentNodes(T u) {
61        return edges.get(u);
62    }
63
64    public boolean contains(T u) {
65        return nodes.contains(u);
66    }
67
68    public Set<Edge<T>> edgesFrom(T u) {
69        return edges.get(u).stream()
70                    .map(v -> new Edge<T>(u, v))
71                    .collect(Collectors.toSet());
72    }
73
74    /**
75     * Returns a new Graph after transitive reduction
76     */
77    public Graph<T> reduce() {
78        Builder<T> builder = new Builder<>();
79        nodes.stream()
80                .forEach(u -> {
81                    builder.addNode(u);
82                    edges.get(u).stream()
83                         .filter(v -> !pathExists(u, v, false))
84                         .forEach(v -> builder.addEdge(u, v));
85                });
86        return builder.build();
87    }
88
89    /**
90     * Returns a new Graph after transitive reduction.  All edges in
91     * the given g takes precedence over this graph.
92     *
93     * @throw IllegalArgumentException g must be a subgraph this graph
94     */
95    public Graph<T> reduce(Graph<T> g) {
96        boolean subgraph = nodes.containsAll(g.nodes) &&
97                g.edges.keySet().stream()
98                       .allMatch(u -> adjacentNodes(u).containsAll(g.adjacentNodes(u)));
99        if (!subgraph) {
100            throw new IllegalArgumentException(g + " is not a subgraph of " + this);
101        }
102
103        Builder<T> builder = new Builder<>();
104        nodes.stream()
105                .forEach(u -> {
106                    builder.addNode(u);
107                    // filter the edge if there exists a path from u to v in the given g
108                    // or there exists another path from u to v in this graph
109                    edges.get(u).stream()
110                         .filter(v -> !g.pathExists(u, v) && !pathExists(u, v, false))
111                         .forEach(v -> builder.addEdge(u, v));
112                });
113
114        // add the overlapped edges from this graph and the given g
115        g.edges().keySet().stream()
116                .forEach(u -> g.adjacentNodes(u).stream()
117                                .filter(v -> isAdjacent(u, v))
118                                .forEach(v -> builder.addEdge(u, v)));
119        return builder.build();
120    }
121
122    /**
123     * Returns nodes sorted in topological order.
124     */
125    public Stream<T> orderedNodes() {
126        TopoSorter<T> sorter = new TopoSorter<>(this);
127        return sorter.result.stream();
128    }
129
130    /**
131     * Traverse this graph and performs the given action in topological order
132     */
133    public void ordered(Consumer<T> action) {
134        TopoSorter<T> sorter = new TopoSorter<>(this);
135        sorter.ordered(action);
136    }
137
138    /**
139     * Traverses this graph and performs the given action in reverse topological order
140     */
141    public void reverse(Consumer<T> action) {
142        TopoSorter<T> sorter = new TopoSorter<>(this);
143        sorter.reverse(action);
144    }
145
146    /**
147     * Returns a transposed graph from this graph
148     */
149    public Graph<T> transpose() {
150        Builder<T> builder = new Builder<>();
151        builder.addNodes(nodes);
152        // reverse edges
153        edges.keySet().forEach(u -> {
154            edges.get(u).stream()
155                .forEach(v -> builder.addEdge(v, u));
156        });
157        return builder.build();
158    }
159
160    /**
161     * Returns all nodes reachable from the given set of roots.
162     */
163    public Set<T> dfs(Set<T> roots) {
164        Deque<T> deque = new LinkedList<>(roots);
165        Set<T> visited = new HashSet<>();
166        while (!deque.isEmpty()) {
167            T u = deque.pop();
168            if (!visited.contains(u)) {
169                visited.add(u);
170                if (contains(u)) {
171                    adjacentNodes(u).stream()
172                        .filter(v -> !visited.contains(v))
173                        .forEach(deque::push);
174                }
175            }
176        }
177        return visited;
178    }
179
180    private boolean isAdjacent(T u, T v) {
181        return edges.containsKey(u) && edges.get(u).contains(v);
182    }
183
184    private boolean pathExists(T u, T v) {
185        return pathExists(u, v, true);
186    }
187
188    /**
189     * Returns true if there exists a path from u to v in this graph.
190     * If includeAdjacent is false, it returns true if there exists
191     * another path from u to v of distance > 1
192     */
193    private boolean pathExists(T u, T v, boolean includeAdjacent) {
194        if (!nodes.contains(u) || !nodes.contains(v)) {
195            return false;
196        }
197        if (includeAdjacent && isAdjacent(u, v)) {
198            return true;
199        }
200        Deque<T> stack = new LinkedList<>();
201        Set<T> visited = new HashSet<>();
202        stack.push(u);
203        while (!stack.isEmpty()) {
204            T node = stack.pop();
205            if (node.equals(v)) {
206                return true;
207            }
208            if (!visited.contains(node)) {
209                visited.add(node);
210                edges.get(node).stream()
211                     .filter(e -> includeAdjacent || !node.equals(u) || !e.equals(v))
212                     .forEach(e -> stack.push(e));
213            }
214        }
215        assert !visited.contains(v);
216        return false;
217    }
218
219    public void printGraph(PrintWriter out) {
220        out.println("graph for " + nodes);
221        nodes.stream()
222             .forEach(u -> adjacentNodes(u).stream()
223                               .forEach(v -> out.format("  %s -> %s%n", u, v)));
224    }
225
226    @Override
227    public String toString() {
228        return nodes.toString();
229    }
230
231    static class Edge<T> {
232        final T u;
233        final T v;
234        Edge(T u, T v) {
235            this.u = u;
236            this.v = v;
237        }
238
239        @Override
240        public String toString() {
241            return String.format("%s -> %s", u, v);
242        }
243
244        @Override
245        public boolean equals(Object o) {
246            if (this == o) return true;
247            if (o == null || !(o instanceof Edge))
248                return false;
249
250            @SuppressWarnings("unchecked")
251            Edge<T> edge = (Edge<T>) o;
252
253            return u.equals(edge.u) && v.equals(edge.v);
254        }
255
256        @Override
257        public int hashCode() {
258            int result = u.hashCode();
259            result = 31 * result + v.hashCode();
260            return result;
261        }
262    }
263
264    static class Builder<T> {
265        final Set<T> nodes = new HashSet<>();
266        final Map<T, Set<T>> edges = new HashMap<>();
267
268        public void addNode(T node) {
269            if (nodes.contains(node)) {
270                return;
271            }
272            nodes.add(node);
273            edges.computeIfAbsent(node, _e -> new HashSet<>());
274        }
275
276        public void addNodes(Set<T> nodes) {
277            nodes.addAll(nodes);
278        }
279
280        public void addEdge(T u, T v) {
281            addNode(u);
282            addNode(v);
283            edges.get(u).add(v);
284        }
285
286        public Graph<T> build() {
287            return new Graph<T>(nodes, edges);
288        }
289    }
290
291    /**
292     * Topological sort
293     */
294    static class TopoSorter<T> {
295        final Deque<T> result = new LinkedList<>();
296        final Deque<T> nodes;
297        final Graph<T> graph;
298        TopoSorter(Graph<T> graph) {
299            this.graph = graph;
300            this.nodes = new LinkedList<>(graph.nodes);
301            sort();
302        }
303
304        public void ordered(Consumer<T> action) {
305            result.iterator().forEachRemaining(action);
306        }
307
308        public void reverse(Consumer<T> action) {
309            result.descendingIterator().forEachRemaining(action);
310        }
311
312        private void sort() {
313            Deque<T> visited = new LinkedList<>();
314            Deque<T> done = new LinkedList<>();
315            T node;
316            while ((node = nodes.poll()) != null) {
317                if (!visited.contains(node)) {
318                    visit(node, visited, done);
319                }
320            }
321        }
322
323        private void visit(T node, Deque<T> visited, Deque<T> done) {
324            if (visited.contains(node)) {
325                if (!done.contains(node)) {
326                    throw new IllegalArgumentException("Cyclic detected: " +
327                        node + " " + graph.edges().get(node));
328                }
329                return;
330            }
331            visited.add(node);
332            graph.edges().get(node).stream()
333                .forEach(x -> visit(x, visited, done));
334            done.add(node);
335            result.addLast(node);
336        }
337    }
338
339    public static class DotGraph {
340        static final String ORANGE = "#e76f00";
341        static final String BLUE = "#437291";
342        static final String GRAY = "#dddddd";
343
344        static final String REEXPORTS = "";
345        static final String REQUIRES = "style=\"dashed\"";
346        static final String REQUIRES_BASE = "color=\"" + GRAY + "\"";
347
348        static final Set<String> javaModules = modules(name ->
349            (name.startsWith("java.") && !name.equals("java.smartcardio")));
350        static final Set<String> jdkModules = modules(name ->
351            (name.startsWith("java.") ||
352                name.startsWith("jdk.") ||
353                name.startsWith("javafx.")) && !javaModules.contains(name));
354
355        private static Set<String> modules(Predicate<String> predicate) {
356            return ModuleFinder.ofSystem().findAll()
357                               .stream()
358                               .map(ModuleReference::descriptor)
359                               .map(ModuleDescriptor::name)
360                               .filter(predicate)
361                               .collect(Collectors.toSet());
362        }
363
364        static void printAttributes(PrintWriter out) {
365            out.format("  size=\"25,25\";%n");
366            out.format("  nodesep=.5;%n");
367            out.format("  ranksep=1.5;%n");
368            out.format("  pencolor=transparent;%n");
369            out.format("  node [shape=plaintext, fontname=\"DejaVuSans\", fontsize=36, margin=\".2,.2\"];%n");
370            out.format("  edge [penwidth=4, color=\"#999999\", arrowhead=open, arrowsize=2];%n");
371        }
372
373        static void printNodes(PrintWriter out, Graph<String> graph) {
374            out.format("  subgraph se {%n");
375            graph.nodes().stream()
376                 .filter(javaModules::contains)
377                 .forEach(mn -> out.format("  \"%s\" [fontcolor=\"%s\", group=%s];%n",
378                                           mn, ORANGE, "java"));
379            out.format("  }%n");
380            graph.nodes().stream()
381                 .filter(jdkModules::contains)
382                 .forEach(mn -> out.format("    \"%s\" [fontcolor=\"%s\", group=%s];%n",
383                                           mn, BLUE, "jdk"));
384
385            graph.nodes().stream()
386                 .filter(mn -> !javaModules.contains(mn) && !jdkModules.contains(mn))
387                 .forEach(mn -> out.format("  \"%s\";%n", mn));
388        }
389
390        static void printEdges(PrintWriter out, Graph<String> graph,
391                               String node, Set<String> requiresTransitive) {
392            graph.adjacentNodes(node).forEach(dn -> {
393                String attr = dn.equals("java.base") ? REQUIRES_BASE
394                        : (requiresTransitive.contains(dn) ? REEXPORTS : REQUIRES);
395                out.format("  \"%s\" -> \"%s\" [%s];%n", node, dn, attr);
396            });
397        }
398    }
399
400
401}
402