1 /*
2 * Copyright (c) 2024, Red Hat, Inc.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
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
26 package jdk.tools.jlink.internal;
27
28 import static jdk.tools.jlink.internal.LinkableRuntimeImage.RESPATH_PATTERN;
29
30 import java.io.ByteArrayInputStream;
31 import java.io.IOException;
32 import java.io.InputStream;
33 import java.io.UncheckedIOException;
34 import java.lang.module.ModuleFinder;
35 import java.lang.module.ModuleReference;
36 import java.nio.charset.StandardCharsets;
37 import java.nio.file.Files;
38 import java.nio.file.Path;
39 import java.nio.file.Paths;
40 import java.security.MessageDigest;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Collections;
44 import java.util.HexFormat;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Objects;
48 import java.util.Set;
49 import java.util.function.Function;
50 import java.util.function.Predicate;
51 import java.util.stream.Collectors;
52 import java.util.stream.Stream;
53
54 import jdk.internal.util.OperatingSystem;
55 import jdk.tools.jlink.internal.Archive.Entry.EntryType;
56 import jdk.tools.jlink.internal.runtimelink.ResourceDiff;
57 import jdk.tools.jlink.plugin.ResourcePoolEntry;
58 import jdk.tools.jlink.plugin.ResourcePoolEntry.Type;
59
60 /**
61 * An archive implementation based on the JDK's run-time image. That is, classes
62 * and resources from the modules image (lib/modules, or jimage) and other
63 * associated files from the filesystem of the JDK installation.
64 */
65 public class JRTArchive implements Archive {
66
67 private final String module;
68 private final Path path;
69 private final ModuleReference ref;
70 // The collection of files of this module
71 private final List<JRTFile> files = new ArrayList<>();
72 // Files not part of the lib/modules image of the JDK install.
73 // Thus, native libraries, binaries, legal files, etc.
74 private final List<String> otherRes;
75 // Maps a module resource path to the corresponding diff to packaged
76 // modules for that resource (if any)
77 private final Map<String, ResourceDiff> resDiff;
78 private final boolean errorOnModifiedFile;
79 private final TaskHelper taskHelper;
80 private final Set<String> upgradeableFiles;
81
82 /**
83 * JRTArchive constructor
84 *
85 * @param module The module name this archive refers to
86 * @param path The JRT filesystem path.
87 * @param errorOnModifiedFile Whether or not modified files of the JDK
88 * install aborts the link.
89 * @param perModDiff The lib/modules (a.k.a jimage) diff for this module,
90 * possibly an empty list if there are no differences.
91 * @param taskHelper The task helper instance.
92 * @param upgradeableFiles The set of files that are allowed for upgrades.
93 */
94 JRTArchive(String module,
95 Path path,
96 boolean errorOnModifiedFile,
97 List<ResourceDiff> perModDiff,
98 TaskHelper taskHelper,
99 Set<String> upgradeableFiles) {
100 this.module = module;
101 this.path = path;
102 this.ref = ModuleFinder.ofSystem()
103 .find(module)
104 .orElseThrow(() ->
105 new IllegalArgumentException(
106 "Module " + module +
107 " not part of the JDK install"));
108 this.errorOnModifiedFile = errorOnModifiedFile;
109 this.otherRes = readModuleResourceFile(module);
110 this.resDiff = Objects.requireNonNull(perModDiff).stream()
111 .collect(Collectors.toMap(ResourceDiff::getName, Function.identity()));
112 this.taskHelper = taskHelper;
113 this.upgradeableFiles = upgradeableFiles;
114 }
115
116 @Override
117 public String moduleName() {
118 return module;
119 }
120
121 @Override
122 public Path getPath() {
123 return path;
124 }
125
126 @Override
127 public Stream<Entry> entries() {
142
143 @Override
144 public void close() throws IOException {
145 if (!files.isEmpty()) {
146 files.clear();
147 }
148 }
149
150 @Override
151 public int hashCode() {
152 return Objects.hash(module, path);
153 }
154
155 @Override
156 public boolean equals(Object obj) {
157 return (obj instanceof JRTArchive other &&
158 Objects.equals(module, other.module) &&
159 Objects.equals(path, other.path));
160 }
161
162 private void collectFiles() throws IOException {
163 if (files.isEmpty()) {
164 addNonClassResources();
165 // Add classes/resources from the run-time image,
166 // patched with the run-time image diff
167 files.addAll(ref.open().list()
168 .filter(i -> {
169 String lookupKey = String.format("/%s/%s", module, i);
170 ResourceDiff rd = resDiff.get(lookupKey);
171 // Filter all resources with a resource diff
172 // that are of kind MODIFIED.
173 // Note that REMOVED won't happen since in
174 // that case the module listing won't have
175 // the resource anyway.
176 // Note as well that filter removes files
177 // of kind ADDED. Those files are not in
178 // the packaged modules, so ought not to
179 // get returned from the pipeline.
180 return (rd == null ||
181 rd.getKind() == ResourceDiff.Kind.MODIFIED);
182 })
183 .map(s -> {
184 String lookupKey = String.format("/%s/%s", module, s);
185 return new JRTArchiveFile(JRTArchive.this, s,
186 EntryType.CLASS_OR_RESOURCE,
187 null /* hashOrTarget */,
188 false /* symlink */,
189 resDiff.get(lookupKey));
190 })
191 .toList());
192 // Finally add all files only present in the resource diff
193 // That is, removed items in the run-time image.
194 files.addAll(resDiff.values().stream()
195 .filter(rd -> rd.getKind() == ResourceDiff.Kind.REMOVED)
196 .map(s -> {
197 int secondSlash = s.getName().indexOf("/", 1);
198 assert secondSlash != -1;
199 String pathWithoutModule = s.getName().substring(secondSlash + 1);
200 return new JRTArchiveFile(JRTArchive.this,
201 pathWithoutModule,
202 EntryType.CLASS_OR_RESOURCE,
203 null /* hashOrTarget */,
204 false /* symlink */,
205 s);
206 })
207 .toList());
208 }
209 }
210
211 /*
212 * no need to keep track of the warning produced since this is eagerly
213 * checked once.
214 */
215 private void addNonClassResources() {
216 // Not all modules will have other resources like bin, lib, legal etc.
217 // files. In that case the list will be empty.
218 if (!otherRes.isEmpty()) {
219 files.addAll(otherRes.stream()
220 .filter(Predicate.not(String::isEmpty))
221 .map(s -> {
222 ResourceFileEntry m = ResourceFileEntry.decodeFromString(s);
223
224 // Read from the base JDK image.
225 Path path = BASE.resolve(m.resPath);
226 if (!isUpgradeableFile(m.resPath) &&
227 shaSumMismatch(path, m.hashOrTarget, m.symlink)) {
228 if (errorOnModifiedFile) {
229 String msg = taskHelper.getMessage("err.runtime.link.modified.file", path.toString());
230 IOException cause = new IOException(msg);
231 throw new UncheckedIOException(cause);
232 } else {
233 taskHelper.warning("err.runtime.link.modified.file", path.toString());
234 }
235 }
236
237 return new JRTArchiveFile(JRTArchive.this,
238 m.resPath,
239 toEntryType(m.resType),
240 m.hashOrTarget,
241 m.symlink,
242 /* diff only for resources */
243 null);
244 })
245 .toList());
246 }
247 }
248
249 /**
250 * Certain files in a module are considered upgradeable. That is,
251 * their hash sums aren't checked.
252 *
253 * @param resPath The resource path of the file to check for upgradeability.
254 * @return {@code true} if the file is upgradeable. {@code false} otherwise.
255 */
256 private boolean isUpgradeableFile(String resPath) {
257 return upgradeableFiles.contains(resPath);
258 }
259
260 static boolean shaSumMismatch(Path res, String expectedSha, boolean isSymlink) {
261 if (isSymlink) {
262 return false;
263 }
264 // handle non-symlink resources
265 try {
306 // Where fields are:
307 //
308 // (1) The resource type as specified by ResourcePoolEntry.type()
309 // (2) Symlink designator. 0 => regular resource, 1 => symlinked resource
310 // (3) The SHA-512 sum of the resources' content. The link to the target
311 // for symlinked resources.
312 // (4) The relative file path of the resource
313 private static final String TYPE_FILE_FORMAT = "%d|%d|%s|%s";
314
315 private static final Map<Integer, Type> typeMap = Arrays.stream(Type.values())
316 .collect(Collectors.toMap(Type::ordinal, Function.identity()));
317
318 public String encodeToString() {
319 return String.format(TYPE_FILE_FORMAT,
320 resType.ordinal(),
321 symlink ? 1 : 0,
322 hashOrTarget,
323 resPath);
324 }
325
326 /**
327 * line: <int>|<int>|<hashOrTarget>|<path>
328 *
329 * Take the integer before '|' convert it to a Type. The second
330 * token is an integer representing symlinks (or not). The third token is
331 * a hash sum (sha512) of the file denoted by the fourth token (path).
332 */
333 static ResourceFileEntry decodeFromString(String line) {
334 assert !line.isEmpty();
335
336 String[] tokens = line.split("\\|", 4);
337 Type type = null;
338 int symlinkNum = -1;
339 try {
340 Integer typeInt = Integer.valueOf(tokens[0]);
341 type = typeMap.get(typeInt);
342 if (type == null) {
343 throw new AssertionError("Illegal type ordinal: " + typeInt);
344 }
345 symlinkNum = Integer.valueOf(tokens[1]);
346 } catch (NumberFormatException e) {
347 throw new AssertionError(e); // must not happen
348 }
349 if (symlinkNum < 0 || symlinkNum > 1) {
350 throw new AssertionError(
420 // precondition: Native lib, windows platform
421 if (resPath.endsWith(".dll") || resPath.endsWith(".diz")
422 || resPath.endsWith(".pdb") || resPath.endsWith(".map")) {
423 if (resPath.startsWith(LIB_DIRNAME + "/")) {
424 return BIN_DIRNAME + "/" +
425 resPath.substring((LIB_DIRNAME + "/").length());
426 }
427 }
428 return resPath;
429 }
430 private static final String BIN_DIRNAME = "bin";
431 private static final String LIB_DIRNAME = "lib";
432 }
433
434 private static final Path BASE = Paths.get(System.getProperty("java.home"));
435
436 interface JRTFile {
437 Entry toEntry();
438 }
439
440 record JRTArchiveFile(Archive archive,
441 String resPath,
442 EntryType resType,
443 String sha,
444 boolean symlink,
445 ResourceDiff diff) implements JRTFile {
446 public Entry toEntry() {
447 return new Entry(archive,
448 String.format("/%s/%s",
449 archive.moduleName(),
450 resPath),
451 resPath,
452 resType) {
453 @Override
454 public long size() {
455 try {
456 if (resType != EntryType.CLASS_OR_RESOURCE) {
457 // Read from the base JDK image, special casing
458 // symlinks, which have the link target in the
459 // hashOrTarget field
460 if (symlink) {
461 return Files.size(BASE.resolve(sha));
462 }
463 return Files.size(BASE.resolve(resPath));
464 } else {
465 if (diff != null) {
466 // If the resource has a diff to the
467 // packaged modules, use the diff. Diffs of kind
468 // ADDED have been filtered out in collectFiles();
469 assert diff.getKind() != ResourceDiff.Kind.ADDED;
470 assert diff.getName().equals(String.format("/%s/%s",
471 archive.moduleName(),
472 resPath));
473 return diff.getResourceBytes().length;
474 }
475 // Read from the module image. This works, because
476 // the underlying base path is a JrtPath with the
477 // JrtFileSystem underneath which is able to handle
478 // this size query.
479 return Files.size(archive.getPath().resolve(resPath));
480 }
481 } catch (IOException e) {
482 throw new UncheckedIOException(e);
483 }
484 }
485
486 @Override
487 public InputStream stream() throws IOException {
488 if (resType != EntryType.CLASS_OR_RESOURCE) {
489 // Read from the base JDK image.
490 Path path = symlink ? BASE.resolve(sha) : BASE.resolve(resPath);
491 return Files.newInputStream(path);
492 } else {
493 // Read from the module image. Use the diff to the
494 // packaged modules if we have one. Diffs of kind
495 // ADDED have been filtered out in collectFiles();
496 if (diff != null) {
497 assert diff.getKind() != ResourceDiff.Kind.ADDED;
498 assert diff.getName().equals(String.format("/%s/%s",
499 archive.moduleName(),
500 resPath));
501 return new ByteArrayInputStream(diff.getResourceBytes());
502 }
503 String module = archive.moduleName();
504 ModuleReference mRef = ModuleFinder.ofSystem()
505 .find(module).orElseThrow();
506 return mRef.open().open(resPath).orElseThrow();
507 }
508 }
509
510 };
511 }
512 }
513
514 private static List<String> readModuleResourceFile(String modName) {
515 String resName = String.format(RESPATH_PATTERN, modName);
516 try {
517 try (InputStream inStream = JRTArchive.class.getModule()
518 .getResourceAsStream(resName)) {
519 String input = new String(inStream.readAllBytes(), StandardCharsets.UTF_8);
520 if (input.isEmpty()) {
521 // Not all modules have non-class resources
522 return Collections.emptyList();
523 } else {
524 return Arrays.asList(input.split("\n"));
525 }
526 }
527 } catch (IOException e) {
528 throw new UncheckedIOException("Failed to process resources from the " +
529 "run-time image for module " + modName, e);
|
1 /*
2 * Copyright (c) 2024, Red Hat, Inc.
3 * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 *
6 * This code is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License version 2 only, as
8 * published by the Free Software Foundation. Oracle designates this
9 * particular file as subject to the "Classpath" exception as provided
10 * by Oracle in the LICENSE file that accompanied this code.
11 *
12 * This code is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * version 2 for more details (a copy is included in the LICENSE file that
16 * accompanied this code).
17 *
18 * You should have received a copy of the GNU General Public License version
19 * 2 along with this work; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21 *
22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23 * or visit www.oracle.com if you need additional information or have any
24 * questions.
25 */
26
27 package jdk.tools.jlink.internal;
28
29 import static jdk.tools.jlink.internal.LinkableRuntimeImage.RESPATH_PATTERN;
30
31 import java.io.ByteArrayInputStream;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.UncheckedIOException;
35 import java.lang.module.ModuleFinder;
36 import java.nio.charset.StandardCharsets;
37 import java.nio.file.Files;
38 import java.nio.file.Path;
39 import java.nio.file.Paths;
40 import java.security.MessageDigest;
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.Collections;
44 import java.util.HexFormat;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Objects;
48 import java.util.Set;
49 import java.util.function.Function;
50 import java.util.function.Predicate;
51 import java.util.stream.Collectors;
52 import java.util.stream.Stream;
53
54 import jdk.internal.jimage.ResourceEntries;
55 import jdk.internal.jimage.SystemImageReader;
56 import jdk.internal.util.OperatingSystem;
57 import jdk.tools.jlink.internal.Archive.Entry.EntryType;
58 import jdk.tools.jlink.internal.runtimelink.ResourceDiff;
59 import jdk.tools.jlink.plugin.ResourcePoolEntry;
60 import jdk.tools.jlink.plugin.ResourcePoolEntry.Type;
61
62 /**
63 * An archive implementation based on the JDK's run-time image. That is, classes
64 * and resources from the modules image (lib/modules, or jimage) and other
65 * associated files from the filesystem of the JDK installation.
66 */
67 public class JRTArchive implements Archive {
68 private final String module;
69 private final Path path;
70 private final ResourceEntries imageResources;
71 // The collection of files of this module
72 private final List<JRTFile> files = new ArrayList<>();
73 // Files not part of the lib/modules image of the JDK install.
74 // Thus, native libraries, binaries, legal files, etc.
75 private final List<String> otherRes;
76 // Maps a module resource path to the corresponding diff to packaged
77 // modules for that resource (if any)
78 private final Map<String, ResourceDiff> resDiff;
79 private final boolean errorOnModifiedFile;
80 private final TaskHelper taskHelper;
81 private final Set<String> upgradeableFiles;
82
83 /**
84 * JRTArchive constructor
85 *
86 * @param module The module name this archive refers to
87 * @param path The JRT filesystem path.
88 * @param errorOnModifiedFile Whether or not modified files of the JDK
89 * install aborts the link.
90 * @param perModDiff The lib/modules (a.k.a jimage) diff for this module,
91 * possibly an empty list if there are no differences.
92 * @param taskHelper The task helper instance.
93 * @param upgradeableFiles The set of files that are allowed for upgrades.
94 */
95 JRTArchive(String module,
96 Path path,
97 boolean errorOnModifiedFile,
98 List<ResourceDiff> perModDiff,
99 TaskHelper taskHelper,
100 Set<String> upgradeableFiles) {
101 this.module = module;
102 this.path = path;
103 ModuleFinder.ofSystem()
104 .find(module)
105 .orElseThrow(() -> new IllegalArgumentException(
106 "Module " + module + " not part of the JDK install"));
107 this.imageResources = SystemImageReader.getResourceEntries();
108 this.errorOnModifiedFile = errorOnModifiedFile;
109 this.otherRes = readModuleResourceFile(module);
110 this.resDiff = Objects.requireNonNull(perModDiff).stream()
111 .collect(Collectors.toMap(ResourceDiff::getName, Function.identity()));
112 this.taskHelper = taskHelper;
113 this.upgradeableFiles = upgradeableFiles;
114 }
115
116 @Override
117 public String moduleName() {
118 return module;
119 }
120
121 @Override
122 public Path getPath() {
123 return path;
124 }
125
126 @Override
127 public Stream<Entry> entries() {
142
143 @Override
144 public void close() throws IOException {
145 if (!files.isEmpty()) {
146 files.clear();
147 }
148 }
149
150 @Override
151 public int hashCode() {
152 return Objects.hash(module, path);
153 }
154
155 @Override
156 public boolean equals(Object obj) {
157 return (obj instanceof JRTArchive other &&
158 Objects.equals(module, other.module) &&
159 Objects.equals(path, other.path));
160 }
161
162 private boolean isNormalOrModifiedDiff(String name) {
163 ResourceDiff rd = resDiff.get(name);
164 // Filter all resources with a resource diff of kind MODIFIED.
165 // Note that REMOVED won't happen since in that case the module listing
166 // won't have the resource anyway.
167 // Note as well that filter removes files of kind ADDED. Those files are
168 // not in the packaged modules, so ought not to get returned from the
169 // pipeline.
170 return (rd == null || rd.getKind() == ResourceDiff.Kind.MODIFIED);
171 }
172
173 private void collectFiles() throws IOException {
174 if (files.isEmpty()) {
175 addNonClassResources();
176
177 // Add classes/resources from the run-time image,
178 // patched with the run-time image diff
179 imageResources.getEntryNames(module)
180 .filter(this::isNormalOrModifiedDiff)
181 .sorted()
182 .map(name -> new JrtModuleFile(this, name, resDiff.get(name)))
183 .forEach(files::add);
184
185 // Finally add all files only present in the resource diff
186 // That is, removed items in the run-time image.
187 files.addAll(resDiff.values().stream()
188 .filter(rd -> rd.getKind() == ResourceDiff.Kind.REMOVED)
189 .map(rd -> new JrtModuleFile(this, rd.getName(), rd))
190 .toList());
191 }
192 }
193
194 /*
195 * no need to keep track of the warning produced since this is eagerly
196 * checked once.
197 */
198 private void addNonClassResources() {
199 // Not all modules will have other resources like bin, lib, legal etc.
200 // files. In that case the list will be empty.
201 if (!otherRes.isEmpty()) {
202 files.addAll(otherRes.stream()
203 .filter(Predicate.not(String::isEmpty))
204 .map(s -> {
205 ResourceFileEntry m = ResourceFileEntry.decodeFromString(s);
206
207 // Read from the base JDK image.
208 Path path = BASE.resolve(m.resPath);
209 if (!isUpgradeableFile(m.resPath) &&
210 shaSumMismatch(path, m.hashOrTarget, m.symlink)) {
211 if (errorOnModifiedFile) {
212 String msg = taskHelper.getMessage("err.runtime.link.modified.file", path.toString());
213 IOException cause = new IOException(msg);
214 throw new UncheckedIOException(cause);
215 } else {
216 taskHelper.warning("err.runtime.link.modified.file", path.toString());
217 }
218 }
219
220 return new JrtOtherFile(
221 this, m.resPath, toEntryType(m.resType), m.hashOrTarget, m.symlink);
222 })
223 .toList());
224 }
225 }
226
227 /**
228 * Certain files in a module are considered upgradeable. That is,
229 * their hash sums aren't checked.
230 *
231 * @param resPath The resource path of the file to check for upgradeability.
232 * @return {@code true} if the file is upgradeable. {@code false} otherwise.
233 */
234 private boolean isUpgradeableFile(String resPath) {
235 return upgradeableFiles.contains(resPath);
236 }
237
238 static boolean shaSumMismatch(Path res, String expectedSha, boolean isSymlink) {
239 if (isSymlink) {
240 return false;
241 }
242 // handle non-symlink resources
243 try {
284 // Where fields are:
285 //
286 // (1) The resource type as specified by ResourcePoolEntry.type()
287 // (2) Symlink designator. 0 => regular resource, 1 => symlinked resource
288 // (3) The SHA-512 sum of the resources' content. The link to the target
289 // for symlinked resources.
290 // (4) The relative file path of the resource
291 private static final String TYPE_FILE_FORMAT = "%d|%d|%s|%s";
292
293 private static final Map<Integer, Type> typeMap = Arrays.stream(Type.values())
294 .collect(Collectors.toMap(Type::ordinal, Function.identity()));
295
296 public String encodeToString() {
297 return String.format(TYPE_FILE_FORMAT,
298 resType.ordinal(),
299 symlink ? 1 : 0,
300 hashOrTarget,
301 resPath);
302 }
303
304 /*
305 * line: <int>|<int>|<hashOrTarget>|<path>
306 *
307 * Take the integer before '|' convert it to a Type. The second token
308 * is an integer representing symlinks (or not). The third token is
309 * a hash sum (sha512) of the file denoted by the fourth token (path).
310 */
311 static ResourceFileEntry decodeFromString(String line) {
312 assert !line.isEmpty();
313
314 String[] tokens = line.split("\\|", 4);
315 Type type = null;
316 int symlinkNum = -1;
317 try {
318 Integer typeInt = Integer.valueOf(tokens[0]);
319 type = typeMap.get(typeInt);
320 if (type == null) {
321 throw new AssertionError("Illegal type ordinal: " + typeInt);
322 }
323 symlinkNum = Integer.valueOf(tokens[1]);
324 } catch (NumberFormatException e) {
325 throw new AssertionError(e); // must not happen
326 }
327 if (symlinkNum < 0 || symlinkNum > 1) {
328 throw new AssertionError(
398 // precondition: Native lib, windows platform
399 if (resPath.endsWith(".dll") || resPath.endsWith(".diz")
400 || resPath.endsWith(".pdb") || resPath.endsWith(".map")) {
401 if (resPath.startsWith(LIB_DIRNAME + "/")) {
402 return BIN_DIRNAME + "/" +
403 resPath.substring((LIB_DIRNAME + "/").length());
404 }
405 }
406 return resPath;
407 }
408 private static final String BIN_DIRNAME = "bin";
409 private static final String LIB_DIRNAME = "lib";
410 }
411
412 private static final Path BASE = Paths.get(System.getProperty("java.home"));
413
414 interface JRTFile {
415 Entry toEntry();
416 }
417
418 record JrtModuleFile(
419 JRTArchive archive,
420 String resPath,
421 ResourceDiff diff) implements JRTFile {
422 @Override
423 public Entry toEntry() {
424 assert resPath.startsWith("/" + archive.moduleName() + "/");
425 String resName = resPath.substring(archive.moduleName().length() + 2);
426
427 // If the resource has a diff to the packaged modules, use the diff.
428 // Diffs of kind ADDED have been filtered out in collectFiles();
429 if (diff != null) {
430 assert diff.getKind() != ResourceDiff.Kind.ADDED;
431 assert diff.getName().equals(resPath);
432
433 return new Entry(archive, resPath, resName, EntryType.CLASS_OR_RESOURCE) {
434 @Override
435 public long size() {
436 return diff.getResourceBytes().length;
437 }
438 @Override
439 public InputStream stream() {
440 return new ByteArrayInputStream(diff.getResourceBytes());
441 }
442 };
443 } else {
444 return new Entry(archive, resPath, resName, EntryType.CLASS_OR_RESOURCE) {
445 @Override
446 public long size() {
447 return archive.imageResources.getSize(resPath);
448 }
449
450 @Override
451 public InputStream stream() {
452 // Byte content could be cached in the entry if needed.
453 return new ByteArrayInputStream(archive.imageResources.getBytes(resPath));
454 }
455 };
456 }
457 }
458 }
459
460 record JrtOtherFile(
461 JRTArchive archive,
462 String resPath,
463 EntryType resType,
464 String sha,
465 boolean symlink) implements JRTFile {
466
467 // Read from the base JDK image, special casing
468 // symlinks, which have the link target in the
469 // hashOrTarget field.
470 Path targetPath() {
471 return BASE.resolve(symlink ? sha : resPath);
472 }
473
474 public Entry toEntry() {
475 assert resType != EntryType.CLASS_OR_RESOURCE;
476
477 return new Entry(
478 archive,
479 String.format("/%s/%s", archive.moduleName(), resPath),
480 resPath,
481 resType) {
482
483 @Override
484 public long size() {
485 try {
486 return Files.size(targetPath());
487 } catch (IOException e) {
488 throw new UncheckedIOException(e);
489 }
490 }
491
492 @Override
493 public InputStream stream() throws IOException {
494 return Files.newInputStream(targetPath());
495 }
496 };
497 }
498 }
499
500 private static List<String> readModuleResourceFile(String modName) {
501 String resName = String.format(RESPATH_PATTERN, modName);
502 try {
503 try (InputStream inStream = JRTArchive.class.getModule()
504 .getResourceAsStream(resName)) {
505 String input = new String(inStream.readAllBytes(), StandardCharsets.UTF_8);
506 if (input.isEmpty()) {
507 // Not all modules have non-class resources
508 return Collections.emptyList();
509 } else {
510 return Arrays.asList(input.split("\n"));
511 }
512 }
513 } catch (IOException e) {
514 throw new UncheckedIOException("Failed to process resources from the " +
515 "run-time image for module " + modName, e);
|