src/jdk.jdeps/share/classes/com/sun/tools/jdeps/Graph.java
changeset 47216 71c04702a3d5
parent 43873 705d732d3715
child 48253 82767203606e
equal deleted inserted replaced
47215:4ebc2e2fb97c 47216:71c04702a3d5
       
     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  */
       
    25 package com.sun.tools.jdeps;
       
    26 
       
    27 import java.io.PrintWriter;
       
    28 import java.lang.module.ModuleDescriptor;
       
    29 import java.lang.module.ModuleFinder;
       
    30 import java.lang.module.ModuleReference;
       
    31 import java.util.Collections;
       
    32 import java.util.Deque;
       
    33 import java.util.HashMap;
       
    34 import java.util.HashSet;
       
    35 import java.util.LinkedList;
       
    36 import java.util.Map;
       
    37 import java.util.Set;
       
    38 import java.util.function.Consumer;
       
    39 import java.util.function.Predicate;
       
    40 import java.util.stream.Collectors;
       
    41 import java.util.stream.Stream;
       
    42 
       
    43 public 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().reduce();
       
   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(stack::push);
       
   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             this.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 }