/*
* 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
/**
* A Builder to compute ModuleHashes from a given configuration
*/
public class ModuleHashesBuilder {
private final Configuration configuration;
private final Set<String> hashModuleCandidates;
Constructs a ModuleHashesBuilder that finds the packaged modules
from the location of ModuleReference found from the given Configuration.
Params: - config – Configuration for building module hashes
- modules – the candidate modules to be hashed
/**
* 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<String> 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.
/**
* 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<String, ModuleHashes> computeHashes(Set<String> roots) {
// build a graph containing the packaged modules and
// its transitive dependences matching --hash-modules
Graph.Builder<String> builder = new Graph.Builder<>();
Deque<ResolvedModule> deque = new ArrayDeque<>(configuration.modules());
Set<ResolvedModule> 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<String> 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<String> mods = new HashSet<>();
Map<String, ModuleHashes> 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<String> 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<String, Path> 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;
}
/*
* Utility class
*/
static class Graph<T> {
private final Set<T> nodes;
private final Map<T, Set<T>> edges;
public Graph(Set<T> nodes, Map<T, Set<T>> edges) {
this.nodes = Collections.unmodifiableSet(nodes);
this.edges = Collections.unmodifiableMap(edges);
}
public Set<T> nodes() {
return nodes;
}
public Map<T, Set<T>> edges() {
return edges;
}
public Set<T> adjacentNodes(T u) {
return edges.get(u);
}
public boolean contains(T u) {
return nodes.contains(u);
}
Returns nodes sorted in topological order.
/**
* Returns nodes sorted in topological order.
*/
public Stream<T> orderedNodes() {
TopoSorter<T> sorter = new TopoSorter<>(this);
return sorter.result.stream();
}
Traverse this graph and performs the given action in topological order
/**
* Traverse this graph and performs the given action in topological order
*/
public void ordered(Consumer<T> action) {
TopoSorter<T> sorter = new TopoSorter<>(this);
sorter.ordered(action);
}
Traverses this graph and performs the given action in reverse topological order
/**
* Traverses this graph and performs the given action in reverse topological order
*/
public void reverse(Consumer<T> action) {
TopoSorter<T> sorter = new TopoSorter<>(this);
sorter.reverse(action);
}
Returns a transposed graph from this graph
/**
* Returns a transposed graph from this graph
*/
public Graph<T> transpose() {
Builder<T> 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.
/**
* Returns all nodes reachable from the given root.
*/
public Set<T> dfs(T root) {
return dfs(Set.of(root));
}
Returns all nodes reachable from the given set of roots.
/**
* Returns all nodes reachable from the given set of roots.
*/
public Set<T> dfs(Set<T> roots) {
Deque<T> deque = new LinkedList<>(roots);
Set<T> 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<T> {
final Set<T> nodes = new HashSet<>();
final Map<T, Set<T>> 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<T> build() {
return new Graph<T>(nodes, edges);
}
}
}
Topological sort
/**
* Topological sort
*/
private static class TopoSorter<T> {
final Deque<T> result = new LinkedList<>();
final Deque<T> nodes;
final Graph<T> graph;
TopoSorter(Graph<T> graph) {
this.graph = graph;
this.nodes = new LinkedList<>(graph.nodes);
sort();
}
public void ordered(Consumer<T> action) {
result.iterator().forEachRemaining(action);
}
public void reverse(Consumer<T> action) {
result.descendingIterator().forEachRemaining(action);
}
private void sort() {
Deque<T> visited = new LinkedList<>();
Deque<T> done = new LinkedList<>();
T node;
while ((node = nodes.poll()) != null) {
if (!visited.contains(node)) {
visit(node, visited, done);
}
}
}
private void visit(T node, Deque<T> visited, Deque<T> 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);
}
}
}