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 }