< prev index next >

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

Print this page

  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);
< prev index next >