--- /dev/null 2017-01-13 16:40:23.000000000 -0800 +++ new/src/java.base/share/classes/jdk/internal/module/ModuleHashesBuilder.java 2017-01-13 16:40:23.000000000 -0800 @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.module; + +import java.io.PrintStream; +import java.lang.module.Configuration; +import java.lang.module.ResolvedModule; +import java.net.URI; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Stream; +import static java.util.stream.Collectors.*; + +/** + * A Builder to compute ModuleHashes from a given configuration + */ +public class ModuleHashesBuilder { + private final Configuration configuration; + private final Set hashModuleCandidates; + + /** + * Constructs a ModuleHashesBuilder that finds the packaged modules + * from the location of ModuleReference found from the given Configuration. + * + * @param config Configuration for building module hashes + * @param modules the candidate modules to be hashed + */ + public ModuleHashesBuilder(Configuration config, Set modules) { + this.configuration = config; + this.hashModuleCandidates = modules; + } + + /** + * Returns a map of a module M to ModuleHashes for the modules + * that depend upon M directly or indirectly. + * + * The key for each entry in the returned map is a module M that has + * no outgoing edges to any of the candidate modules to be hashed + * i.e. M is a leaf node in a connected subgraph containing M and + * other candidate modules from the module graph filtering + * the outgoing edges from M to non-candidate modules. + */ + public Map computeHashes(Set roots) { + // build a graph containing the the packaged modules and + // its transitive dependences matching --hash-modules + Graph.Builder builder = new Graph.Builder<>(); + Deque deque = new ArrayDeque<>(configuration.modules()); + Set visited = new HashSet<>(); + while (!deque.isEmpty()) { + ResolvedModule rm = deque.pop(); + if (!visited.contains(rm)) { + visited.add(rm); + builder.addNode(rm.name()); + for (ResolvedModule dm : rm.reads()) { + if (!visited.contains(dm)) { + deque.push(dm); + } + builder.addEdge(rm.name(), dm.name()); + } + } + } + + // each node in a transposed graph is a matching packaged module + // in which the hash of the modules that depend upon it is recorded + Graph transposedGraph = builder.build().transpose(); + + // traverse the modules in topological order that will identify + // the modules to record the hashes - it is the first matching + // module and has not been hashed during the traversal. + Set mods = new HashSet<>(); + Map hashes = new HashMap<>(); + builder.build() + .orderedNodes() + .filter(mn -> roots.contains(mn) && !mods.contains(mn)) + .forEach(mn -> { + // Compute hashes of the modules that depend on mn directly and + // indirectly excluding itself. + Set ns = transposedGraph.dfs(mn) + .stream() + .filter(n -> !n.equals(mn) && hashModuleCandidates.contains(n)) + .collect(toSet()); + mods.add(mn); + mods.addAll(ns); + + if (!ns.isEmpty()) { + Map moduleToPath = ns.stream() + .collect(toMap(Function.identity(), this::moduleToPath)); + hashes.put(mn, ModuleHashes.generate(moduleToPath, "SHA-256")); + } + }); + return hashes; + } + + private Path moduleToPath(String name) { + ResolvedModule rm = configuration.findModule(name).orElseThrow( + () -> new InternalError("Selected module " + name + " not on module path")); + + URI uri = rm.reference().location().get(); + Path path = Paths.get(uri); + String fn = path.getFileName().toString(); + if (!fn.endsWith(".jar") && !fn.endsWith(".jmod")) { + throw new UnsupportedOperationException(path + " is not a modular JAR or jmod file"); + } + return path; + } + + /* + * Utilty class + */ + static class Graph { + private final Set nodes; + private final Map> edges; + + public Graph(Set nodes, Map> edges) { + this.nodes = Collections.unmodifiableSet(nodes); + this.edges = Collections.unmodifiableMap(edges); + } + + public Set nodes() { + return nodes; + } + + public Map> edges() { + return edges; + } + + public Set adjacentNodes(T u) { + return edges.get(u); + } + + public boolean contains(T u) { + return nodes.contains(u); + } + + /** + * Returns nodes sorted in topological order. + */ + public Stream orderedNodes() { + TopoSorter sorter = new TopoSorter<>(this); + return sorter.result.stream(); + } + + /** + * Traverse this graph and performs the given action in topological order + */ + public void ordered(Consumer action) { + TopoSorter sorter = new TopoSorter<>(this); + sorter.ordered(action); + } + + /** + * Traverses this graph and performs the given action in reverse topological order + */ + public void reverse(Consumer action) { + TopoSorter sorter = new TopoSorter<>(this); + sorter.reverse(action); + } + + /** + * Returns a transposed graph from this graph + */ + public Graph transpose() { + Builder builder = new Builder<>(); + nodes.stream().forEach(builder::addNode); + // reverse edges + edges.keySet().forEach(u -> { + edges.get(u).stream() + .forEach(v -> builder.addEdge(v, u)); + }); + return builder.build(); + } + + /** + * Returns all nodes reachable from the given root. + */ + public Set dfs(T root) { + return dfs(Set.of(root)); + } + + /** + * Returns all nodes reachable from the given set of roots. + */ + public Set dfs(Set roots) { + Deque deque = new LinkedList<>(roots); + Set visited = new HashSet<>(); + while (!deque.isEmpty()) { + T u = deque.pop(); + if (!visited.contains(u)) { + visited.add(u); + if (contains(u)) { + adjacentNodes(u).stream() + .filter(v -> !visited.contains(v)) + .forEach(deque::push); + } + } + } + return visited; + } + + public void printGraph(PrintStream out) { + out.println("graph for " + nodes); + nodes.stream() + .forEach(u -> adjacentNodes(u).stream() + .forEach(v -> out.format(" %s -> %s%n", u, v))); + } + + static class Builder { + final Set nodes = new HashSet<>(); + final Map> edges = new HashMap<>(); + + public void addNode(T node) { + if (nodes.contains(node)) { + return; + } + nodes.add(node); + edges.computeIfAbsent(node, _e -> new HashSet<>()); + } + + public void addEdge(T u, T v) { + addNode(u); + addNode(v); + edges.get(u).add(v); + } + + public Graph build() { + return new Graph(nodes, edges); + } + } + } + + /** + * Topological sort + */ + private static class TopoSorter { + final Deque result = new LinkedList<>(); + final Deque nodes; + final Graph graph; + + TopoSorter(Graph graph) { + this.graph = graph; + this.nodes = new LinkedList<>(graph.nodes); + sort(); + } + + public void ordered(Consumer action) { + result.iterator().forEachRemaining(action); + } + + public void reverse(Consumer action) { + result.descendingIterator().forEachRemaining(action); + } + + private void sort() { + Deque visited = new LinkedList<>(); + Deque done = new LinkedList<>(); + T node; + while ((node = nodes.poll()) != null) { + if (!visited.contains(node)) { + visit(node, visited, done); + } + } + } + + private void visit(T node, Deque visited, Deque done) { + if (visited.contains(node)) { + if (!done.contains(node)) { + throw new IllegalArgumentException("Cyclic detected: " + + node + " " + graph.edges().get(node)); + } + return; + } + visited.add(node); + graph.edges().get(node).stream() + .forEach(x -> visit(x, visited, done)); + done.add(node); + result.addLast(node); + } + } +}