< prev index next >

src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageResourcesTree.java

Print this page

  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 package jdk.tools.jlink.internal;
 26 



 27 import java.io.DataOutputStream;
 28 import java.io.IOException;
 29 import java.nio.ByteBuffer;
 30 import java.util.ArrayList;
 31 import java.util.Collections;

 32 import java.util.HashMap;

 33 import java.util.List;
 34 import java.util.Map;
 35 import java.util.Objects;
 36 import java.util.Set;
 37 import java.util.TreeMap;
 38 import java.util.TreeSet;
 39 
 40 /**
 41  * A class to build a sorted tree of Resource paths as a tree of ImageLocation.
 42  *
 43  */
 44 // XXX Public only due to the JImageTask / JImageTask code duplication
 45 public final class ImageResourcesTree {
 46     public static boolean isTreeInfoResource(String path) {
 47         return path.startsWith("/packages") || path.startsWith("/modules");
 48     }
 49 
 50     /**
 51      * Path item tree node.
 52      */
 53     private static class Node {

 54 
 55         private final String name;
 56         private final Map<String, Node> children = new TreeMap<>();
 57         private final Node parent;
 58         private ImageLocationWriter loc;
 59 
 60         private Node(String name, Node parent) {
 61             this.name = name;
 62             this.parent = parent;
 63 
 64             if (parent != null) {
 65                 parent.children.put(name, this);
 66             }
 67         }
 68 








 69         public String getPath() {
 70             if (parent == null) {
 71                 return "/";
 72             }
 73             return buildPath(this);
 74         }
 75 
 76         public String getName() {
 77             return name;
 78         }
 79 
 80         public Node getChildren(String name) {
 81             Node item = children.get(name);
 82             return item;
 83         }
 84 
 85         private static String buildPath(Node item) {
 86             if (item == null) {
 87                 return null;
 88             }
 89             String path = buildPath(item.parent);
 90             if (path == null) {
 91                 return item.getName();
 92             } else {
 93                 return path + "/" + item.getName();
 94             }
 95         }
 96     }
 97 
 98     private static final class ResourceNode extends Node {

 99 
100         public ResourceNode(String name, Node parent) {
101             super(name, parent);
102         }
103     }
104 
105     private static class PackageNode extends Node {
106         /**
107          * A reference to a package. Empty packages can be located inside one or
108          * more modules. A package with classes exist in only one module.
109          */
110         static final class PackageReference {
111 
112             private final String name;
113             private final boolean isEmpty;












114 
115             PackageReference(String name, boolean isEmpty) {
116                 this.name = Objects.requireNonNull(name);
117                 this.isEmpty = isEmpty;

118             }
119 
120             @Override
121             public String toString() {
122                 return name + "[empty:" + isEmpty + "]";
123             }

124         }
125 
126         private final Map<String, PackageReference> references = new TreeMap<>();
127 
128         PackageNode(String name, Node parent) {
129             super(name, parent);
130         }
131 
132         private void addReference(String name, boolean isEmpty) {
133             PackageReference ref = references.get(name);
134             if (ref == null || ref.isEmpty) {
135                 references.put(name, new PackageReference(name, isEmpty));
136             }
137         }

138 
139         private void validate() {
140             boolean exists = false;
141             for (PackageReference ref : references.values()) {
142                 if (!ref.isEmpty) {
143                     if (exists) {
144                         throw new RuntimeException("Multiple modules to contain package "
145                                 + getName());
146                     } else {
147                         exists = true;
148                     }
149                 }
150             }
151         }



152     }
153 
154     /**
155      * Tree of nodes.
156      */
157     private static final class Tree {


158 
159         private final Map<String, Node> directAccess = new HashMap<>();
160         private final List<String> paths;
161         private final Node root;
162         private Node modules;
163         private Node packages;
164 
165         private Tree(List<String> paths) {
166             this.paths = paths;


167             root = new Node("", null);
168             buildTree();
169         }
170 
171         private void buildTree() {
172             modules = new Node("modules", root);
173             directAccess.put(modules.getPath(), modules);
174 
175             Map<String, Set<String>> moduleToPackage = new TreeMap<>();
176             Map<String, Set<String>> packageToModule = new TreeMap<>();
177 
178             for (String p : paths) {
179                 if (!p.startsWith("/")) {
180                     continue;
181                 }
182                 String[] split = p.split("/");
183                 // minimum length is 3 items: /<mod>/<pkg>
184                 if (split.length < 3) {
185                     System.err.println("Resources tree, invalid data structure, "
186                             + "skipping " + p);
187                     continue;
188                 }
189                 Node current = modules;
190                 String module = null;
191                 for (int i = 0; i < split.length; i++) {
192                     // When a non terminal node is marked as being a resource, something is wrong.
193                     // It has been observed some badly created jar file to contain
194                     // invalid directory entry marled as not directory (see 8131762)
195                     if (current instanceof ResourceNode) {
196                         System.err.println("Resources tree, invalid data structure, "
197                                 + "skipping " + p);
198                         continue;
199                     }
200                     String s = split[i];
201                     if (!s.isEmpty()) {
202                         // First item, this is the module, simply add a new node to the
203                         // tree.
204                         if (module == null) {
205                             module = s;
206                         }
207                         Node n = current.children.get(s);
208                         if (n == null) {
209                             if (i == split.length - 1) { // Leaf
210                                 n = new ResourceNode(s, current);
211                                 String pkg = toPackageName(n.parent);
212                                 //System.err.println("Adding a resource node. pkg " + pkg + ", name " + s);
213                                 if (pkg != null && !pkg.startsWith("META-INF")) {
214                                     Set<String> pkgs = moduleToPackage.get(module);
215                                     if (pkgs == null) {
216                                         pkgs = new TreeSet<>();
217                                         moduleToPackage.put(module, pkgs);
218                                     }
219                                     pkgs.add(pkg);
220                                 }
221                             } else { // put only sub trees, no leaf
222                                 n = new Node(s, current);
223                                 directAccess.put(n.getPath(), n);
224                                 String pkg = toPackageName(n);
225                                 if (pkg != null && !pkg.startsWith("META-INF")) {
226                                     Set<String> mods = packageToModule.get(pkg);
227                                     if (mods == null) {
228                                         mods = new TreeSet<>();
229                                         packageToModule.put(pkg, mods);
230                                     }
231                                     mods.add(module);
232                                 }
233                             }
234                         }
235                         current = n;
236                     }
237                 }
238             }
239             packages = new Node("packages", root);
240             directAccess.put(packages.getPath(), packages);
241             // The subset of package nodes that have some content.
242             // These packages exist only in a single module.
243             for (Map.Entry<String, Set<String>> entry : moduleToPackage.entrySet()) {
244                 for (String pkg : entry.getValue()) {
245                     PackageNode pkgNode = new PackageNode(pkg, packages);
246                     pkgNode.addReference(entry.getKey(), false);
247                     directAccess.put(pkgNode.getPath(), pkgNode);
248                 }
249             }
250 
251             // All packages
252             for (Map.Entry<String, Set<String>> entry : packageToModule.entrySet()) {
253                 // Do we already have a package node?
254                 PackageNode pkgNode = (PackageNode) packages.getChildren(entry.getKey());
255                 if (pkgNode == null) {
256                     pkgNode = new PackageNode(entry.getKey(), packages);
257                 }
258                 for (String module : entry.getValue()) {
259                     pkgNode.addReference(module, true);
260                 }



261                 directAccess.put(pkgNode.getPath(), pkgNode);
















262             }
263             // Validate that the packages are well formed.
264             for (Node n : packages.children.values()) {
265                 ((PackageNode)n).validate();








266             }
267 


























268         }
269 
270         public String toResourceName(Node node) {
271             if (!node.children.isEmpty()) {
272                 throw new RuntimeException("Node is not a resource");





273             }
274             return removeRadical(node);
275         }
276 
277         public String getModule(Node node) {
278             if (node.parent == null || node.getName().equals("modules")
279                     || node.getName().startsWith("packages")) {
280                 return null;
281             }
282             String path = removeRadical(node);
283             // "/xxx/...";
284             path = path.substring(1);
285             int i = path.indexOf("/");
286             if (i == -1) {
287                 return path;
288             } else {
289                 return path.substring(0, i);
290             }

291         }
292 
293         public String toPackageName(Node node) {
294             if (node.parent == null) {
295                 return null;
296             }
297             String path = removeRadical(node.getPath(), "/modules/");
298             String module = getModule(node);
299             if (path.equals(module)) {
300                 return null;
301             }
302             String pkg = removeRadical(path, module + "/");
303             return pkg.replace('/', '.');
304         }
305 
306         public String removeRadical(Node node) {
307             return removeRadical(node.getPath(), "/modules");
308         }
309 
310         private String removeRadical(String path, String str) {
311             if (!(path.length() < str.length())) {
312                 path = path.substring(str.length());
313             }
314             return path;
315         }
316 
317         public Node getRoot() {
318             return root;
319         }
320 
321         public Map<String, Node> getMap() {
322             return directAccess;
323         }
324     }
325 
326     private static final class LocationsAdder {
327 
328         private long offset;
329         private final List<byte[]> content = new ArrayList<>();
330         private final BasicImageWriter writer;
331         private final Tree tree;
332 
333         LocationsAdder(Tree tree, long offset, BasicImageWriter writer) {
334             this.tree = tree;
335             this.offset = offset;
336             this.writer = writer;
337             addLocations(tree.getRoot());
338         }
339 
340         private int addLocations(Node current) {
341             if (current instanceof PackageNode) {
342                 PackageNode pkgNode = (PackageNode) current;
343                 int size = pkgNode.references.size() * 8;
344                 writer.addLocation(current.getPath(), offset, 0, size);

345                 offset += size;
346             } else {
347                 int[] ret = new int[current.children.size()];
348                 int i = 0;
349                 for (java.util.Map.Entry<String, Node> entry : current.children.entrySet()) {
350                     ret[i] = addLocations(entry.getValue());
351                     i += 1;
352                 }
353                 if (current != tree.getRoot() && !(current instanceof ResourceNode)) {


354                     int size = ret.length * 4;
355                     writer.addLocation(current.getPath(), offset, 0, size);
356                     offset += size;
357                 }
358             }
359             return 0;
360         }
361 
362         private List<byte[]> computeContent() {
363             // Map used to associate Tree item with locations offset.
364             Map<String, ImageLocationWriter> outLocations = new HashMap<>();
365             for (ImageLocationWriter wr : writer.getLocations()) {
366                 outLocations.put(wr.getFullName(), wr);
367             }
368             // Attach location to node
369             for (Map.Entry<String, ImageLocationWriter> entry : outLocations.entrySet()) {
370                 Node item = tree.getMap().get(entry.getKey());
371                 if (item != null) {
372                     item.loc = entry.getValue();
373                 }
374             }
375             computeContent(tree.getRoot(), outLocations);
376             return content;
377         }
378 
379         private int computeContent(Node current, Map<String, ImageLocationWriter> outLocations) {
380             if (current instanceof PackageNode) {
381                 // /packages/<pkg name>
382                 PackageNode pkgNode = (PackageNode) current;
383                 int size = pkgNode.references.size() * 8;
384                 ByteBuffer buff = ByteBuffer.allocate(size);
385                 buff.order(writer.getByteOrder());
386                 for (PackageNode.PackageReference mod : pkgNode.references.values()) {
387                     buff.putInt(mod.isEmpty ? 1 : 0);
388                     buff.putInt(writer.addString(mod.name));
389                 }
390                 byte[] arr = buff.array();
391                 content.add(arr);
392                 current.loc = outLocations.get(current.getPath());
393             } else {
394                 int[] ret = new int[current.children.size()];
395                 int i = 0;
396                 for (java.util.Map.Entry<String, Node> entry : current.children.entrySet()) {
397                     ret[i] = computeContent(entry.getValue(), outLocations);
398                     i += 1;
399                 }
400                 if (ret.length > 0) {
401                     int size = ret.length * 4;
402                     ByteBuffer buff = ByteBuffer.allocate(size);
403                     buff.order(writer.getByteOrder());
404                     for (int val : ret) {
405                         buff.putInt(val);
406                     }
407                     byte[] arr = buff.array();
408                     content.add(arr);
409                 } else {
410                     if (current instanceof ResourceNode) {
411                         // A resource location, remove "/modules"
412                         String s = tree.toResourceName(current);
413                         current.loc = outLocations.get(s);
414                     } else {
415                         // empty "/packages" or empty "/modules" paths
416                         current.loc = outLocations.get(current.getPath());
417                     }
418                 }
419                 if (current.loc == null && current != tree.getRoot()) {
420                     System.err.println("Invalid path in metadata, skipping " + current.getPath());
421                 }
422             }
423             return current.loc == null ? 0 : current.loc.getLocationOffset();
424         }
425     }
426 
427     private final List<String> paths;
428     private final LocationsAdder adder;
429 
430     public ImageResourcesTree(long offset, BasicImageWriter writer, List<String> paths) {
431         this.paths = new ArrayList<>();
432         this.paths.addAll(paths);
433         Collections.sort(this.paths);
434         Tree tree = new Tree(this.paths);
435         adder = new LocationsAdder(tree, offset, writer);
436     }

  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 package jdk.tools.jlink.internal;
 26 
 27 import jdk.internal.jimage.ImageLocation;
 28 import jdk.internal.jimage.ModuleReference;
 29 
 30 import java.io.DataOutputStream;
 31 import java.io.IOException;
 32 import java.nio.ByteBuffer;
 33 import java.util.ArrayList;
 34 import java.util.Collections;
 35 import java.util.Comparator;
 36 import java.util.HashMap;
 37 import java.util.HashSet;
 38 import java.util.List;
 39 import java.util.Map;
 40 import java.util.Objects;
 41 import java.util.Set;
 42 import java.util.TreeMap;
 43 import java.util.stream.Collectors;
 44 
 45 /**
 46  * A class to build a sorted tree of Resource paths as a tree of ImageLocation.

 47  */
 48 // XXX Public only due to the JImageTask / JImageTask code duplication
 49 public final class ImageResourcesTree {




 50     /**
 51      * Path item tree node.
 52      */
 53     // Visible for testing only.
 54     static class Node {
 55 
 56         private final String name;
 57         private final Map<String, Node> children = new TreeMap<>();
 58         private final Node parent;
 59         private ImageLocationWriter loc;
 60 
 61         private Node(String name, Node parent) {
 62             this.name = name;
 63             this.parent = parent;
 64 
 65             if (parent != null) {
 66                 parent.children.put(name, this);
 67             }
 68         }
 69 
 70         private void setLocation(ImageLocationWriter loc) {
 71             // This *can* be called more than once, but only with the same instance.
 72             if (this.loc != null && loc != this.loc) {
 73                 throw new IllegalStateException("Cannot add different locations: " + name);
 74             }
 75             this.loc = Objects.requireNonNull(loc);
 76         }
 77 
 78         public String getPath() {
 79             if (parent == null) {
 80                 return "/";
 81             }
 82             return buildPath(this);
 83         }
 84 
 85         public String getName() {
 86             return name;
 87         }
 88 
 89         public Node getChildren(String name) {
 90             Node item = children.get(name);
 91             return item;
 92         }
 93 
 94         private static String buildPath(Node item) {
 95             if (item == null) {
 96                 return null;
 97             }
 98             String path = buildPath(item.parent);
 99             if (path == null) {
100                 return item.getName();
101             } else {
102                 return path + "/" + item.getName();
103             }
104         }
105     }
106 
107     // Visible for testing only.
108     static final class ResourceNode extends Node {
109 
110         public ResourceNode(String name, Node parent) {
111             super(name, parent);
112         }
113     }
114 
115     /**
116      * A 2nd level package directory, {@code "/packages/<package-name>"}.
117      *
118      * <p>While package paths can exist within many modules, for each package
119      * there is at most one module in which that package has resources.
120      *
121      * <p>For example, the package path {@code java/util} exists in both the
122      * {@code java.base} and {@code java.logging} modules. This means both
123      * {@code "/packages/java.util/java.base"} and
124      * {@code "/packages/java.util/java.logging"} will exist, but only
125      * {@code "java.base"} entry will be marked as having content.
126      *
127      * <p>When processing module references in non-preview mode, entries marked
128      * as {@link ModuleReference#isPreviewOnly() preview-only} must be ignored.
129      *
130      * <p>If all references in a package are preview-only, then the entire
131      * package is marked as preview-only, and must be ignored.
132      */
133     // Visible for testing only.
134     static final class PackageNode extends Node {
135         private final List<ModuleReference> moduleReferences;
136 
137         PackageNode(String name, List<ModuleReference> moduleReferences, Node parent) {
138             super(name, parent);
139             if (moduleReferences.isEmpty()) {
140                 throw new IllegalStateException("Package must be associated with modules: " + name);
141             }
142             if (moduleReferences.stream().filter(ModuleReference::hasResources).count() > 1) {
143                 throw new IllegalStateException("Multiple modules contain non-empty package: " + name);


144             }
145             this.moduleReferences = Collections.unmodifiableList(moduleReferences);
146         }
147 
148         List<ModuleReference> getModuleReferences() {
149             return moduleReferences;









150         }
151     }
152 
153     // Not serialized, and never stored in any field of any class that is.
154     @SuppressWarnings("serial")
155     private static final class InvalidTreeException extends Exception {
156         public InvalidTreeException(Node badNode) {
157             super("Resources tree, invalid data structure, skipping: " + badNode.getPath());







158         }
159         // Exception only used for program flow, not debugging.
160         @Override
161         public Throwable fillInStackTrace() {return this;}
162     }
163 
164     /**
165      * Tree of nodes.
166      */
167     // Visible for testing only.
168     static final class Tree {
169         private static final String PREVIEW_PREFIX = "META-INF/preview/";
170 
171         private final Map<String, Node> directAccess = new HashMap<>();
172         private final List<String> paths;
173         private final Node root;
174         private Node packagesRoot;

175 
176         // Visible for testing only.
177         Tree(List<String> paths) {
178             this.paths = paths.stream().sorted(Comparator.reverseOrder()).toList();
179             // Root node is not added to the directAccess map.
180             root = new Node("", null);
181             buildTree();
182         }
183 
184         private void buildTree() {
185             Node modulesRoot = new Node("modules", root);
186             directAccess.put(modulesRoot.getPath(), modulesRoot);
187             packagesRoot = new Node("packages", root);
188             directAccess.put(packagesRoot.getPath(), packagesRoot);
189 
190             // Map of dot-separated package names to module references (those
191             // in which the package appear). References are merged after to
192             // ensure each module name appears only once, but temporarily a
193             // module may have several entries per package (e.g. with-content,
194             // without-content, normal, preview-only etc..).
195             Map<String, Set<ModuleReference>> packageToModules = new TreeMap<>();
196             for (String fullPath : paths) {
197                 try {
198                     processPath(fullPath, modulesRoot, packageToModules);
199                 } catch (InvalidTreeException ex) {






200                     // It has been observed some badly created jar file to contain
201                     // invalid directory entry marked as not directory (see 8131762).
202                     System.err.println(ex.getMessage());




















































203                 }
204             }
205 
206             // We've collected information for all "packages", including the root
207             // (empty) package and anything under "META-INF". However, these should
208             // not have entries in the "/packages" directory.
209             packageToModules.keySet().removeIf(p -> p.isEmpty() || p.equals("META-INF") || p.startsWith("META-INF."));
210             packageToModules.forEach((pkgName, modRefs) -> {
211                 // Merge multiple refs for the same module.
212                 List<ModuleReference> pkgModules = modRefs.stream()
213                         .collect(Collectors.groupingBy(ModuleReference::name))
214                         .values().stream()
215                         .map(refs -> refs.stream().reduce(ModuleReference::merge).orElseThrow())
216                         .sorted()
217                         .toList();
218                 PackageNode pkgNode = new PackageNode(pkgName, pkgModules, packagesRoot);
219                 directAccess.put(pkgNode.getPath(), pkgNode);
220             });
221         }
222 
223         private void processPath(
224                 String fullPath,
225                 Node modulesRoot,
226                 Map<String, Set<ModuleReference>> packageToModules)
227                 throws InvalidTreeException {
228             // Paths are untrusted, so be careful about checking expected format.
229             if (!fullPath.startsWith("/") || fullPath.endsWith("/") || fullPath.contains("//")) {
230                 return;
231             }
232             int modEnd = fullPath.indexOf('/', 1);
233             // Ensure non-empty module name with non-empty suffix.
234             if (modEnd <= 1) {
235                 return;
236             }
237             String modName = fullPath.substring(1, modEnd);
238             String pkgPath = fullPath.substring(modEnd + 1);
239 
240             Node parentNode = getDirectoryNode(modName, modulesRoot);
241             boolean isPreviewPath = false;
242             if (pkgPath.startsWith(PREVIEW_PREFIX)) {
243                 // For preview paths, process nodes relative to the preview directory.
244                 pkgPath = pkgPath.substring(PREVIEW_PREFIX.length());
245                 Node metaInf = getDirectoryNode("META-INF", parentNode);
246                 parentNode = getDirectoryNode("preview", metaInf);
247                 isPreviewPath = true;
248             }
249 
250             int pathEnd = pkgPath.lastIndexOf('/');
251             // From invariants tested above, this must now be well-formed.
252             String fullPkgName = (pathEnd == -1) ? "" : pkgPath.substring(0, pathEnd).replace('/', '.');
253             String resourceName = pkgPath.substring(pathEnd + 1);
254             // Intermediate packages are marked "empty" (no resources). This might
255             // later be merged with a non-empty reference for the same package.
256             ModuleReference emptyRef = ModuleReference.forEmptyPackage(modName, isPreviewPath);
257 
258             // Work down through empty packages to final resource.
259             for (int i = pkgEndIndex(fullPkgName, 0); i != -1; i = pkgEndIndex(fullPkgName, i)) {
260                 // Due to invariants already checked, pkgName is non-empty.
261                 String pkgName = fullPkgName.substring(0, i);
262                 packageToModules.computeIfAbsent(pkgName, p -> new HashSet<>()).add(emptyRef);
263                 String childNodeName = pkgName.substring(pkgName.lastIndexOf('.') + 1);
264                 parentNode = getDirectoryNode(childNodeName, parentNode);
265             }
266             // Reached non-empty (leaf) package (could still be a duplicate).
267             Node resourceNode = parentNode.getChildren(resourceName);
268             if (resourceNode == null) {
269                 ModuleReference resourceRef = ModuleReference.forPackage(modName, isPreviewPath);
270                 packageToModules.computeIfAbsent(fullPkgName, p -> new HashSet<>()).add(resourceRef);
271                 // Init adds new node to parent (don't add resources to directAccess).
272                 new ResourceNode(resourceName, parentNode);
273             } else if (!(resourceNode instanceof ResourceNode)) {
274                 throw new InvalidTreeException(resourceNode);
275             }
276         }
277 
278         private Node getDirectoryNode(String name, Node parent) throws InvalidTreeException {
279             Node child = parent.getChildren(name);
280             if (child == null) {
281                 // Adds child to parent during init.
282                 child = new Node(name, parent);
283                 directAccess.put(child.getPath(), child);
284             } else if (child instanceof ResourceNode) {
285                 throw new InvalidTreeException(child);
286             }
287             return child;
288         }
289 
290         // Helper to iterate package names up to, and including, the complete name.
291         private int pkgEndIndex(String s, int i) {
292             if (i >= 0 && i < s.length()) {
293                 i = s.indexOf('.', i + 1);
294                 return i != -1 ? i : s.length();








295             }
296             return -1;
297         }
298 
299         private String toResourceName(Node node) {
300             if (!node.children.isEmpty()) {
301                 throw new RuntimeException("Node is not a resource");





302             }
303             return removeRadical(node);

304         }
305 
306         private String removeRadical(Node node) {
307             return removeRadical(node.getPath(), "/modules");
308         }
309 
310         private String removeRadical(String path, String str) {
311             if (!(path.length() < str.length())) {
312                 path = path.substring(str.length());
313             }
314             return path;
315         }
316 
317         public Node getRoot() {
318             return root;
319         }
320 
321         public Map<String, Node> getMap() {
322             return directAccess;
323         }
324     }
325 
326     private static final class LocationsAdder {
327 
328         private long offset;
329         private final List<byte[]> content = new ArrayList<>();
330         private final BasicImageWriter writer;
331         private final Tree tree;
332 
333         LocationsAdder(Tree tree, long offset, BasicImageWriter writer) {
334             this.tree = tree;
335             this.offset = offset;
336             this.writer = writer;
337             addLocations(tree.getRoot());
338         }
339 
340         private int addLocations(Node current) {
341             if (current instanceof PackageNode) {
342                 List<ModuleReference> refs = ((PackageNode) current).getModuleReferences();
343                 // "/packages/<pkg name>" entries have 8-byte entries (flags+offset).
344                 int size = refs.size() * 8;
345                 writer.addLocation(current.getPath(), offset, 0, size, ImageLocation.getPackageFlags(refs));
346                 offset += size;
347             } else {
348                 int[] ret = new int[current.children.size()];
349                 int i = 0;
350                 for (java.util.Map.Entry<String, Node> entry : current.children.entrySet()) {
351                     ret[i] = addLocations(entry.getValue());
352                     i += 1;
353                 }
354                 if (current != tree.getRoot() && !(current instanceof ResourceNode)) {
355                     int locFlags = ImageLocation.getFlags(current.getPath(), tree.directAccess::containsKey);
356                     // Normal directory entries have 4-byte entries (offset only).
357                     int size = ret.length * 4;
358                     writer.addLocation(current.getPath(), offset, 0, size, locFlags);
359                     offset += size;
360                 }
361             }
362             return 0;
363         }
364 
365         private List<byte[]> computeContent() {
366             // Map used to associate Tree item with locations offset.
367             Map<String, ImageLocationWriter> outLocations = new HashMap<>();
368             for (ImageLocationWriter wr : writer.getLocations()) {
369                 outLocations.put(wr.getFullName(), wr);
370             }
371             // Attach location to node
372             for (Map.Entry<String, ImageLocationWriter> entry : outLocations.entrySet()) {
373                 Node item = tree.getMap().get(entry.getKey());
374                 if (item != null) {
375                     item.setLocation(entry.getValue());
376                 }
377             }
378             computeContent(tree.getRoot(), outLocations);
379             return content;
380         }
381 
382         private int computeContent(Node current, Map<String, ImageLocationWriter> outLocations) {
383             if (current instanceof PackageNode) {
384                 // "/packages/<pkg name>" entries have 8-byte entries (flags+offset).
385                 List<ModuleReference> refs = ((PackageNode) current).getModuleReferences();
386                 ByteBuffer byteBuffer = ByteBuffer.allocate(8 * refs.size());
387                 byteBuffer.order(writer.getByteOrder());
388                 ModuleReference.write(refs, byteBuffer.asIntBuffer(), writer::addString);
389                 content.add(byteBuffer.array());
390                 current.setLocation(outLocations.get(current.getPath()));





391             } else {
392                 int[] ret = new int[current.children.size()];
393                 int i = 0;
394                 for (java.util.Map.Entry<String, Node> entry : current.children.entrySet()) {
395                     ret[i] = computeContent(entry.getValue(), outLocations);
396                     i += 1;
397                 }
398                 if (ret.length > 0) {
399                     int size = ret.length * 4;
400                     ByteBuffer buff = ByteBuffer.allocate(size);
401                     buff.order(writer.getByteOrder());
402                     for (int val : ret) {
403                         buff.putInt(val);
404                     }
405                     byte[] arr = buff.array();
406                     content.add(arr);
407                 } else {
408                     if (current instanceof ResourceNode) {
409                         // A resource location, remove "/modules"
410                         String s = tree.toResourceName(current);
411                         current.setLocation(outLocations.get(s));
412                     } else {
413                         // empty "/packages" or empty "/modules" paths
414                         current.setLocation(outLocations.get(current.getPath()));
415                     }
416                 }
417                 if (current.loc == null && current != tree.getRoot()) {
418                     System.err.println("Invalid path in metadata, skipping " + current.getPath());
419                 }
420             }
421             return current.loc == null ? 0 : current.loc.getLocationOffset();
422         }
423     }
424 
425     private final List<String> paths;
426     private final LocationsAdder adder;
427 
428     public ImageResourcesTree(long offset, BasicImageWriter writer, List<String> paths) {
429         this.paths = new ArrayList<>();
430         this.paths.addAll(paths);
431         Collections.sort(this.paths);
432         Tree tree = new Tree(this.paths);
433         adder = new LocationsAdder(tree, offset, writer);
434     }
< prev index next >