--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageResourcesTree.java Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,444 @@
+/*
+ * Copyright (c) 2014, 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.tools.jlink.internal;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * A class to build a sorted tree of Resource paths as a tree of ImageLocation.
+ *
+ */
+// XXX Public only due to the JImageTask / JImageTask code duplication
+public final class ImageResourcesTree {
+ public static boolean isTreeInfoResource(String path) {
+ return path.startsWith("/packages") || path.startsWith("/modules");
+ }
+
+ /**
+ * Path item tree node.
+ */
+ private static class Node {
+
+ private final String name;
+ private final Map<String, Node> children = new TreeMap<>();
+ private final Node parent;
+ private ImageLocationWriter loc;
+
+ private Node(String name, Node parent) {
+ this.name = name;
+ this.parent = parent;
+
+ if (parent != null) {
+ parent.children.put(name, this);
+ }
+ }
+
+ public String getPath() {
+ if (parent == null) {
+ return "/";
+ }
+ return buildPath(this);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Node getChildren(String name) {
+ Node item = children.get(name);
+ return item;
+ }
+
+ private static String buildPath(Node item) {
+ if (item == null) {
+ return null;
+ }
+ String path = buildPath(item.parent);
+ if (path == null) {
+ return item.getName();
+ } else {
+ return path + "/" + item.getName();
+ }
+ }
+ }
+
+ private static final class ResourceNode extends Node {
+
+ public ResourceNode(String name, Node parent) {
+ super(name, parent);
+ }
+ }
+
+ private static class PackageNode extends Node {
+ /**
+ * A reference to a package. Empty packages can be located inside one or
+ * more modules. A package with classes exist in only one module.
+ */
+ final static class PackageReference {
+
+ private final String name;
+ private final boolean isEmpty;
+
+ PackageReference(String name, boolean isEmpty) {
+ this.name = Objects.requireNonNull(name);
+ this.isEmpty = isEmpty;
+ }
+
+ @Override
+ public String toString() {
+ return name + "[empty:" + isEmpty + "]";
+ }
+ }
+
+ private final Map<String, PackageReference> references = new TreeMap<>();
+
+ PackageNode(String name, Node parent) {
+ super(name, parent);
+ }
+
+ private void addReference(String name, boolean isEmpty) {
+ PackageReference ref = references.get(name);
+ if (ref == null || ref.isEmpty) {
+ references.put(name, new PackageReference(name, isEmpty));
+ }
+ }
+
+ private void validate() {
+ boolean exists = false;
+ for (PackageReference ref : references.values()) {
+ if (!ref.isEmpty) {
+ if (exists) {
+ throw new RuntimeException("Multiple modules to contain package "
+ + getName());
+ } else {
+ exists = true;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Tree of nodes.
+ */
+ private static final class Tree {
+
+ private final Map<String, Node> directAccess = new HashMap<>();
+ private final List<String> paths;
+ private final Node root;
+ private Node modules;
+ private Node packages;
+
+ private Tree(List<String> paths) {
+ this.paths = paths;
+ root = new Node("", null);
+ buildTree();
+ }
+
+ private void buildTree() {
+ modules = new Node("modules", root);
+ directAccess.put(modules.getPath(), modules);
+
+ Map<String, Set<String>> moduleToPackage = new TreeMap<>();
+ Map<String, Set<String>> packageToModule = new TreeMap<>();
+
+ for (String p : paths) {
+ if (!p.startsWith("/")) {
+ continue;
+ }
+ String[] split = p.split("/");
+ // minimum length is 3 items: /<mod>/<pkg>
+ if (split.length < 3) {
+ System.err.println("Resources tree, invalid data structure, "
+ + "skipping " + p);
+ continue;
+ }
+ Node current = modules;
+ String module = null;
+ for (int i = 0; i < split.length; i++) {
+ // When a non terminal node is marked as being a resource, something is wrong.
+ // It has been observed some badly created jar file to contain
+ // invalid directory entry marled as not directory (see 8131762)
+ if (current instanceof ResourceNode) {
+ System.err.println("Resources tree, invalid data structure, "
+ + "skipping " + p);
+ continue;
+ }
+ String s = split[i];
+ if (!s.isEmpty()) {
+ // First item, this is the module, simply add a new node to the
+ // tree.
+ if (module == null) {
+ module = s;
+ }
+ Node n = current.children.get(s);
+ if (n == null) {
+ if (i == split.length - 1) { // Leaf
+ n = new ResourceNode(s, current);
+ String pkg = toPackageName(n.parent);
+ //System.err.println("Adding a resource node. pkg " + pkg + ", name " + s);
+ if (pkg != null && !pkg.startsWith("META-INF")) {
+ Set<String> pkgs = moduleToPackage.get(module);
+ if (pkgs == null) {
+ pkgs = new TreeSet<>();
+ moduleToPackage.put(module, pkgs);
+ }
+ pkgs.add(pkg);
+ }
+ } else { // put only sub trees, no leaf
+ n = new Node(s, current);
+ directAccess.put(n.getPath(), n);
+ String pkg = toPackageName(n);
+ if (pkg != null && !pkg.startsWith("META-INF")) {
+ Set<String> mods = packageToModule.get(pkg);
+ if (mods == null) {
+ mods = new TreeSet<>();
+ packageToModule.put(pkg, mods);
+ }
+ mods.add(module);
+ }
+ }
+ }
+ current = n;
+ }
+ }
+ }
+ packages = new Node("packages", root);
+ directAccess.put(packages.getPath(), packages);
+ // The subset of package nodes that have some content.
+ // These packages exist only in a single module.
+ for (Map.Entry<String, Set<String>> entry : moduleToPackage.entrySet()) {
+ for (String pkg : entry.getValue()) {
+ PackageNode pkgNode = new PackageNode(pkg, packages);
+ pkgNode.addReference(entry.getKey(), false);
+ directAccess.put(pkgNode.getPath(), pkgNode);
+ }
+ }
+
+ // All packages
+ for (Map.Entry<String, Set<String>> entry : packageToModule.entrySet()) {
+ // Do we already have a package node?
+ PackageNode pkgNode = (PackageNode) packages.getChildren(entry.getKey());
+ if (pkgNode == null) {
+ pkgNode = new PackageNode(entry.getKey(), packages);
+ }
+ for (String module : entry.getValue()) {
+ pkgNode.addReference(module, true);
+ }
+ directAccess.put(pkgNode.getPath(), pkgNode);
+ }
+ // Validate that the packages are well formed.
+ for (Node n : packages.children.values()) {
+ ((PackageNode)n).validate();
+ }
+
+ }
+
+ public String toResourceName(Node node) {
+ if (!node.children.isEmpty()) {
+ throw new RuntimeException("Node is not a resource");
+ }
+ return removeRadical(node);
+ }
+
+ public String getModule(Node node) {
+ if (node.parent == null || node.getName().equals("modules")
+ || node.getName().startsWith("packages")) {
+ return null;
+ }
+ String path = removeRadical(node);
+ // "/xxx/...";
+ path = path.substring(1);
+ int i = path.indexOf("/");
+ if (i == -1) {
+ return path;
+ } else {
+ return path.substring(0, i);
+ }
+ }
+
+ public String toPackageName(Node node) {
+ if (node.parent == null) {
+ return null;
+ }
+ String path = removeRadical(node.getPath(), "/modules/");
+ String module = getModule(node);
+ if (path.equals(module)) {
+ return null;
+ }
+ String pkg = removeRadical(path, module + "/");
+ return pkg.replaceAll("/", ".");
+ }
+
+ public String removeRadical(Node node) {
+ return removeRadical(node.getPath(), "/modules");
+ }
+
+ private String removeRadical(String path, String str) {
+ if (!(path.length() < str.length())) {
+ path = path.substring(str.length());
+ }
+ return path;
+ }
+
+ public Node getRoot() {
+ return root;
+ }
+
+ public Map<String, Node> getMap() {
+ return directAccess;
+ }
+ }
+
+ private static final class LocationsAdder {
+
+ private long offset;
+ private final List<byte[]> content = new ArrayList<>();
+ private final BasicImageWriter writer;
+ private final Tree tree;
+
+ LocationsAdder(Tree tree, long offset, BasicImageWriter writer) {
+ this.tree = tree;
+ this.offset = offset;
+ this.writer = writer;
+ addLocations(tree.getRoot());
+ }
+
+ private int addLocations(Node current) {
+ if (current instanceof PackageNode) {
+ PackageNode pkgNode = (PackageNode) current;
+ int size = pkgNode.references.size() * 8;
+ writer.addLocation(current.getPath(), offset, 0, size);
+ offset += size;
+ } else {
+ int[] ret = new int[current.children.size()];
+ int i = 0;
+ for (java.util.Map.Entry<String, Node> entry : current.children.entrySet()) {
+ ret[i] = addLocations(entry.getValue());
+ i += 1;
+ }
+ if (current != tree.getRoot() && !(current instanceof ResourceNode)) {
+ int size = ret.length * 4;
+ writer.addLocation(current.getPath(), offset, 0, size);
+ offset += size;
+ }
+ }
+ return 0;
+ }
+
+ private List<byte[]> computeContent() {
+ // Map used to associate Tree item with locations offset.
+ Map<String, ImageLocationWriter> outLocations = new HashMap<>();
+ for (ImageLocationWriter wr : writer.getLocations()) {
+ outLocations.put(wr.getFullName(), wr);
+ }
+ // Attach location to node
+ for (Map.Entry<String, ImageLocationWriter> entry : outLocations.entrySet()) {
+ Node item = tree.getMap().get(entry.getKey());
+ if (item != null) {
+ item.loc = entry.getValue();
+ }
+ }
+ computeContent(tree.getRoot(), outLocations);
+ return content;
+ }
+
+ private int computeContent(Node current, Map<String, ImageLocationWriter> outLocations) {
+ if (current instanceof PackageNode) {
+ // /packages/<pkg name>
+ PackageNode pkgNode = (PackageNode) current;
+ int size = pkgNode.references.size() * 8;
+ ByteBuffer buff = ByteBuffer.allocate(size);
+ buff.order(writer.getByteOrder());
+ for (PackageNode.PackageReference mod : pkgNode.references.values()) {
+ buff.putInt(mod.isEmpty ? 1 : 0);
+ buff.putInt(writer.addString(mod.name));
+ }
+ byte[] arr = buff.array();
+ content.add(arr);
+ current.loc = outLocations.get(current.getPath());
+ } else {
+ int[] ret = new int[current.children.size()];
+ int i = 0;
+ for (java.util.Map.Entry<String, Node> entry : current.children.entrySet()) {
+ ret[i] = computeContent(entry.getValue(), outLocations);
+ i += 1;
+ }
+ if (ret.length > 0) {
+ int size = ret.length * 4;
+ ByteBuffer buff = ByteBuffer.allocate(size);
+ buff.order(writer.getByteOrder());
+ for (int val : ret) {
+ buff.putInt(val);
+ }
+ byte[] arr = buff.array();
+ content.add(arr);
+ } else {
+ if (current instanceof ResourceNode) {
+ // A resource location, remove "/modules"
+ String s = tree.toResourceName(current);
+ current.loc = outLocations.get(s);
+ } else {
+ // empty "/packages" or empty "/modules" paths
+ current.loc = outLocations.get(current.getPath());
+ }
+ }
+ if (current.loc == null && current != tree.getRoot()) {
+ System.err.println("Invalid path in metadata, skipping " + current.getPath());
+ }
+ }
+ return current.loc == null ? 0 : current.loc.getLocationOffset();
+ }
+ }
+
+ private final List<String> paths;
+ private final LocationsAdder adder;
+
+ public ImageResourcesTree(long offset, BasicImageWriter writer, List<String> paths) {
+ this.paths = new ArrayList<>();
+ this.paths.addAll(paths);
+ Collections.sort(this.paths);
+ Tree tree = new Tree(this.paths);
+ adder = new LocationsAdder(tree, offset, writer);
+ }
+
+ public void addContent(DataOutputStream out) throws IOException {
+ List<byte[]> content = adder.computeContent();
+ for (byte[] c : content) {
+ out.write(c, 0, c.length);
+ }
+ }
+}