|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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.util.ArrayDeque; |
|
import java.util.Collections; |
|
import java.util.Deque; |
|
import java.util.HashMap; |
|
import java.util.HashSet; |
|
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.*; |
|
|
|
|
|
|
|
*/ |
|
public class ModuleHashesBuilder { |
|
private final Configuration configuration; |
|
private final Set<String> hashModuleCandidates; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public ModuleHashesBuilder(Configuration config, Set<String> modules) { |
|
this.configuration = config; |
|
this.hashModuleCandidates = modules; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
public Map<String, ModuleHashes> computeHashes(Set<String> roots) { |
|
// build a graph containing the packaged modules and |
|
|
|
Graph.Builder<String> builder = new Graph.Builder<>(); |
|
Deque<ResolvedModule> todo = new ArrayDeque<>(configuration.modules()); |
|
Set<ResolvedModule> visited = new HashSet<>(); |
|
ResolvedModule rm; |
|
while ((rm = todo.poll()) != null) { |
|
if (visited.add(rm)) { |
|
builder.addNode(rm.name()); |
|
for (ResolvedModule dm : rm.reads()) { |
|
if (!visited.contains(dm)) { |
|
todo.push(dm); |
|
} |
|
builder.addEdge(rm.name(), dm.name()); |
|
} |
|
} |
|
} |
|
|
|
// each node in a transposed graph is a matching packaged module |
|
|
|
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 |
|
|
|
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 |
|
|
|
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 = Path.of(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; |
|
} |
|
|
|
|
|
|
|
*/ |
|
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); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public Stream<T> orderedNodes() { |
|
TopoSorter<T> sorter = new TopoSorter<>(this); |
|
return sorter.result.stream(); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public void ordered(Consumer<T> action) { |
|
TopoSorter<T> sorter = new TopoSorter<>(this); |
|
sorter.ordered(action); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public void reverse(Consumer<T> action) { |
|
TopoSorter<T> sorter = new TopoSorter<>(this); |
|
sorter.reverse(action); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public Graph<T> transpose() { |
|
Builder<T> builder = new Builder<>(); |
|
nodes.forEach(builder::addNode); |
|
|
|
edges.keySet().forEach(u -> { |
|
edges.get(u).forEach(v -> builder.addEdge(v, u)); |
|
}); |
|
return builder.build(); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public Set<T> dfs(T root) { |
|
return dfs(Set.of(root)); |
|
} |
|
|
|
|
|
|
|
*/ |
|
public Set<T> dfs(Set<T> roots) { |
|
ArrayDeque<T> todo = new ArrayDeque<>(roots); |
|
Set<T> visited = new HashSet<>(); |
|
T u; |
|
while ((u = todo.poll()) != null) { |
|
if (visited.add(u) && contains(u)) { |
|
adjacentNodes(u).stream() |
|
.filter(v -> !visited.contains(v)) |
|
.forEach(todo::push); |
|
} |
|
} |
|
return visited; |
|
} |
|
|
|
public void printGraph(PrintStream out) { |
|
out.println("graph for " + nodes); |
|
nodes |
|
.forEach(u -> adjacentNodes(u) |
|
.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.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); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
private static class TopoSorter<T> { |
|
final Deque<T> result = new ArrayDeque<>(); |
|
final Graph<T> graph; |
|
|
|
TopoSorter(Graph<T> graph) { |
|
this.graph = graph; |
|
sort(); |
|
} |
|
|
|
public void ordered(Consumer<T> action) { |
|
result.forEach(action); |
|
} |
|
|
|
public void reverse(Consumer<T> action) { |
|
result.descendingIterator().forEachRemaining(action); |
|
} |
|
|
|
private void sort() { |
|
Set<T> visited = new HashSet<>(); |
|
Deque<T> stack = new ArrayDeque<>(); |
|
graph.nodes.forEach(node -> visit(node, visited, stack)); |
|
} |
|
|
|
private Set<T> children(T node) { |
|
return graph.edges().get(node); |
|
} |
|
|
|
private void visit(T node, Set<T> visited, Deque<T> stack) { |
|
if (visited.add(node)) { |
|
stack.push(node); |
|
children(node).forEach(child -> visit(child, visited, stack)); |
|
stack.pop(); |
|
result.addLast(node); |
|
} |
|
else if (stack.contains(node)) { |
|
throw new IllegalArgumentException( |
|
"Cycle detected: " + node + " -> " + children(node)); |
|
} |
|
} |
|
} |
|
} |