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 }
|