/* * 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); } } }