< prev index next >

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

Print this page
*** 1,7 ***
--- 1,8 ---
  /*
   * Copyright (c) 2024, Red Hat, Inc.
+  * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
   * published by the Free Software Foundation.  Oracle designates this

*** 30,11 ***
  import java.io.ByteArrayInputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.UncheckedIOException;
  import java.lang.module.ModuleFinder;
- import java.lang.module.ModuleReference;
  import java.nio.charset.StandardCharsets;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.nio.file.Paths;
  import java.security.MessageDigest;
--- 31,10 ---

*** 49,10 ***
--- 49,12 ---
  import java.util.function.Function;
  import java.util.function.Predicate;
  import java.util.stream.Collectors;
  import java.util.stream.Stream;
  
+ import jdk.internal.jimage.ResourceEntries;
+ import jdk.internal.jimage.SystemImageReader;
  import jdk.internal.util.OperatingSystem;
  import jdk.tools.jlink.internal.Archive.Entry.EntryType;
  import jdk.tools.jlink.internal.runtimelink.ResourceDiff;
  import jdk.tools.jlink.plugin.ResourcePoolEntry;
  import jdk.tools.jlink.plugin.ResourcePoolEntry.Type;

*** 61,14 ***
   * An archive implementation based on the JDK's run-time image. That is, classes
   * and resources from the modules image (lib/modules, or jimage) and other
   * associated files from the filesystem of the JDK installation.
   */
  public class JRTArchive implements Archive {
- 
      private final String module;
      private final Path path;
!     private final ModuleReference ref;
      // The collection of files of this module
      private final List<JRTFile> files = new ArrayList<>();
      // Files not part of the lib/modules image of the JDK install.
      // Thus, native libraries, binaries, legal files, etc.
      private final List<String> otherRes;
--- 63,13 ---
   * An archive implementation based on the JDK's run-time image. That is, classes
   * and resources from the modules image (lib/modules, or jimage) and other
   * associated files from the filesystem of the JDK installation.
   */
  public class JRTArchive implements Archive {
      private final String module;
      private final Path path;
!     private final ResourceEntries imageResources;
      // The collection of files of this module
      private final List<JRTFile> files = new ArrayList<>();
      // Files not part of the lib/modules image of the JDK install.
      // Thus, native libraries, binaries, legal files, etc.
      private final List<String> otherRes;

*** 97,16 ***
                 List<ResourceDiff> perModDiff,
                 TaskHelper taskHelper,
                 Set<String> upgradeableFiles) {
          this.module = module;
          this.path = path;
!         this.ref = ModuleFinder.ofSystem()
!                                .find(module)
!                                .orElseThrow(() ->
!                                     new IllegalArgumentException(
!                                             "Module " + module +
-                                             " not part of the JDK install"));
          this.errorOnModifiedFile = errorOnModifiedFile;
          this.otherRes = readModuleResourceFile(module);
          this.resDiff = Objects.requireNonNull(perModDiff).stream()
                              .collect(Collectors.toMap(ResourceDiff::getName, Function.identity()));
          this.taskHelper = taskHelper;
--- 98,15 ---
                 List<ResourceDiff> perModDiff,
                 TaskHelper taskHelper,
                 Set<String> upgradeableFiles) {
          this.module = module;
          this.path = path;
!         ModuleFinder.ofSystem()
!                 .find(module)
!                 .orElseThrow(() -> new IllegalArgumentException(
!                         "Module " + module + " not part of the JDK install"));
!         this.imageResources = SystemImageReader.getResourceEntries();
          this.errorOnModifiedFile = errorOnModifiedFile;
          this.otherRes = readModuleResourceFile(module);
          this.resDiff = Objects.requireNonNull(perModDiff).stream()
                              .collect(Collectors.toMap(ResourceDiff::getName, Function.identity()));
          this.taskHelper = taskHelper;

*** 157,56 ***
          return (obj instanceof JRTArchive other &&
                     Objects.equals(module, other.module) &&
                     Objects.equals(path, other.path));
      }
  
      private void collectFiles() throws IOException {
          if (files.isEmpty()) {
              addNonClassResources();
              // Add classes/resources from the run-time image,
              // patched with the run-time image diff
!             files.addAll(ref.open().list()
!                                    .filter(i -> {
!                                            String lookupKey = String.format("/%s/%s", module, i);
!                                            ResourceDiff rd = resDiff.get(lookupKey);
!                                            // Filter all resources with a resource diff
!                                            // that are of kind MODIFIED.
-                                            // Note that REMOVED won't happen since in
-                                            // that case the module listing won't have
-                                            // the resource anyway.
-                                            // Note as well that filter removes files
-                                            // of kind ADDED. Those files are not in
-                                            // the packaged modules, so ought not to
-                                            // get returned from the pipeline.
-                                            return (rd == null ||
-                                                    rd.getKind() == ResourceDiff.Kind.MODIFIED);
-                                    })
-                                    .map(s -> {
-                                            String lookupKey = String.format("/%s/%s", module, s);
-                                            return new JRTArchiveFile(JRTArchive.this, s,
-                                                            EntryType.CLASS_OR_RESOURCE,
-                                                            null /* hashOrTarget */,
-                                                            false /* symlink */,
-                                                            resDiff.get(lookupKey));
-                                    })
-                                    .toList());
              // Finally add all files only present in the resource diff
              // That is, removed items in the run-time image.
              files.addAll(resDiff.values().stream()
!                                          .filter(rd -> rd.getKind() == ResourceDiff.Kind.REMOVED)
!                                          .map(s -> {
!                                                  int secondSlash = s.getName().indexOf("/", 1);
-                                                  assert secondSlash != -1;
-                                                  String pathWithoutModule = s.getName().substring(secondSlash + 1);
-                                                  return new JRTArchiveFile(JRTArchive.this,
-                                                          pathWithoutModule,
-                                                          EntryType.CLASS_OR_RESOURCE,
-                                                          null  /* hashOrTarget */,
-                                                          false /* symlink */,
-                                                          s);
-                                          })
-                                          .toList());
          }
      }
  
      /*
       * no need to keep track of the warning produced since this is eagerly
--- 157,39 ---
          return (obj instanceof JRTArchive other &&
                     Objects.equals(module, other.module) &&
                     Objects.equals(path, other.path));
      }
  
+     private boolean isNormalOrModifiedDiff(String name) {
+         ResourceDiff rd = resDiff.get(name);
+         // Filter all resources with a resource diff of kind MODIFIED.
+         // Note that REMOVED won't happen since in that case the module listing
+         // won't have the resource anyway.
+         // Note as well that filter removes files of kind ADDED. Those files are
+         // not in the packaged modules, so ought not to get returned from the
+         // pipeline.
+         return (rd == null || rd.getKind() == ResourceDiff.Kind.MODIFIED);
+     }
+ 
      private void collectFiles() throws IOException {
          if (files.isEmpty()) {
              addNonClassResources();
+ 
              // Add classes/resources from the run-time image,
              // patched with the run-time image diff
!             imageResources.getEntryNames(module)
!                     .filter(this::isNormalOrModifiedDiff)
!                     .sorted()
!                     .map(name -> new JrtModuleFile(this, name, resDiff.get(name)))
!                     .forEach(files::add);
! 
              // Finally add all files only present in the resource diff
              // That is, removed items in the run-time image.
              files.addAll(resDiff.values().stream()
!                     .filter(rd -> rd.getKind() == ResourceDiff.Kind.REMOVED)
!                     .map(rd -> new JrtModuleFile(this, rd.getName(), rd))
!                     .toList());
          }
      }
  
      /*
       * no need to keep track of the warning produced since this is eagerly

*** 232,19 ***
                              } else {
                                  taskHelper.warning("err.runtime.link.modified.file", path.toString());
                              }
                          }
  
!                         return new JRTArchiveFile(JRTArchive.this,
!                                                   m.resPath,
!                                                   toEntryType(m.resType),
!                                                   m.hashOrTarget,
-                                                   m.symlink,
-                                                   /* diff only for resources */
-                                                   null);
-                  })
-                  .toList());
          }
      }
  
      /**
       * Certain files in a module are considered upgradeable. That is,
--- 215,14 ---
                              } else {
                                  taskHelper.warning("err.runtime.link.modified.file", path.toString());
                              }
                          }
  
!                         return new JrtOtherFile(
!                                 this, m.resPath, toEntryType(m.resType), m.hashOrTarget, m.symlink);
!                     })
!                     .toList());
          }
      }
  
      /**
       * Certain files in a module are considered upgradeable. That is,

*** 321,15 ***
                                   symlink ? 1 : 0,
                                   hashOrTarget,
                                   resPath);
          }
  
!         /**
           *  line: <int>|<int>|<hashOrTarget>|<path>
           *
!          *  Take the integer before '|' convert it to a Type. The second
!          *  token is an integer representing symlinks (or not). The third token is
           *  a hash sum (sha512) of the file denoted by the fourth token (path).
           */
          static ResourceFileEntry decodeFromString(String line) {
              assert !line.isEmpty();
  
--- 299,15 ---
                                   symlink ? 1 : 0,
                                   hashOrTarget,
                                   resPath);
          }
  
!         /*
           *  line: <int>|<int>|<hashOrTarget>|<path>
           *
!          *  Take the integer before '|' convert it to a Type. The second token
!          *  is an integer representing symlinks (or not). The third token is
           *  a hash sum (sha512) of the file denoted by the fourth token (path).
           */
          static ResourceFileEntry decodeFromString(String line) {
              assert !line.isEmpty();
  

*** 435,80 ***
  
      interface JRTFile {
          Entry toEntry();
      }
  
!     record JRTArchiveFile(Archive archive,
!                           String resPath,
!                           EntryType resType,
!                           String sha,
!                           boolean symlink,
!                           ResourceDiff diff) implements JRTFile {
          public Entry toEntry() {
!             return new Entry(archive,
!                              String.format("/%s/%s",
!                                            archive.moduleName(),
!                                            resPath),
!                              resPath,
!                              resType) {
                  @Override
                  public long size() {
                      try {
!                         if (resType != EntryType.CLASS_OR_RESOURCE) {
-                             // Read from the base JDK image, special casing
-                             // symlinks, which have the link target in the
-                             // hashOrTarget field
-                             if (symlink) {
-                                 return Files.size(BASE.resolve(sha));
-                             }
-                             return Files.size(BASE.resolve(resPath));
-                         } else {
-                             if (diff != null) {
-                                 // If the resource has a diff to the
-                                 // packaged modules, use the diff. Diffs of kind
-                                 // ADDED have been filtered out in collectFiles();
-                                 assert diff.getKind() != ResourceDiff.Kind.ADDED;
-                                 assert diff.getName().equals(String.format("/%s/%s",
-                                                                            archive.moduleName(),
-                                                                            resPath));
-                                 return diff.getResourceBytes().length;
-                             }
-                             // Read from the module image. This works, because
-                             // the underlying base path is a JrtPath with the
-                             // JrtFileSystem underneath which is able to handle
-                             // this size query.
-                             return Files.size(archive.getPath().resolve(resPath));
-                         }
                      } catch (IOException e) {
                          throw new UncheckedIOException(e);
                      }
                  }
  
                  @Override
                  public InputStream stream() throws IOException {
!                     if (resType != EntryType.CLASS_OR_RESOURCE) {
-                         // Read from the base JDK image.
-                         Path path = symlink ? BASE.resolve(sha) : BASE.resolve(resPath);
-                         return Files.newInputStream(path);
-                     } else {
-                         // Read from the module image. Use the diff to the
-                         // packaged modules if we have one. Diffs of kind
-                         // ADDED have been filtered out in collectFiles();
-                         if (diff != null) {
-                             assert diff.getKind() != ResourceDiff.Kind.ADDED;
-                             assert diff.getName().equals(String.format("/%s/%s",
-                                                                        archive.moduleName(),
-                                                                        resPath));
-                             return new ByteArrayInputStream(diff.getResourceBytes());
-                         }
-                         String module = archive.moduleName();
-                         ModuleReference mRef = ModuleFinder.ofSystem()
-                                                     .find(module).orElseThrow();
-                         return mRef.open().open(resPath).orElseThrow();
-                     }
                  }
- 
              };
          }
      }
  
      private static List<String> readModuleResourceFile(String modName) {
--- 413,88 ---
  
      interface JRTFile {
          Entry toEntry();
      }
  
!     record JrtModuleFile(
!             JRTArchive archive,
!             String resPath,
!             ResourceDiff diff) implements JRTFile {
!         @Override
!         public Entry toEntry() {
+             assert resPath.startsWith("/" + archive.moduleName() + "/");
+             String resName = resPath.substring(archive.moduleName().length() + 2);
+ 
+             // If the resource has a diff to the packaged modules, use the diff.
+             // Diffs of kind ADDED have been filtered out in collectFiles();
+             if (diff != null) {
+                 assert diff.getKind() != ResourceDiff.Kind.ADDED;
+                 assert diff.getName().equals(resPath);
+ 
+                 return new Entry(archive, resPath, resName, EntryType.CLASS_OR_RESOURCE) {
+                     @Override
+                     public long size() {
+                         return diff.getResourceBytes().length;
+                     }
+                     @Override
+                     public InputStream stream() {
+                         return new ByteArrayInputStream(diff.getResourceBytes());
+                     }
+                 };
+             } else {
+                 return new Entry(archive, resPath, resName, EntryType.CLASS_OR_RESOURCE) {
+                     @Override
+                     public long size() {
+                         return archive.imageResources.getSize(resPath);
+                     }
+ 
+                     @Override
+                     public InputStream stream() {
+                         // Byte content could be cached in the entry if needed.
+                         return new ByteArrayInputStream(archive.imageResources.getBytes(resPath));
+                     }
+                 };
+             }
+         }
+     }
+ 
+     record JrtOtherFile(
+             JRTArchive archive,
+             String resPath,
+             EntryType resType,
+             String sha,
+             boolean symlink) implements JRTFile {
+ 
+         // Read from the base JDK image, special casing
+         // symlinks, which have the link target in the
+         // hashOrTarget field.
+         Path targetPath() {
+             return BASE.resolve(symlink ? sha : resPath);
+         }
+ 
          public Entry toEntry() {
!             assert resType != EntryType.CLASS_OR_RESOURCE;
! 
!             return new Entry(
!                     archive,
!                     String.format("/%s/%s", archive.moduleName(), resPath),
!                     resPath,
+                     resType) {
+ 
                  @Override
                  public long size() {
                      try {
!                         return Files.size(targetPath());
                      } catch (IOException e) {
                          throw new UncheckedIOException(e);
                      }
                  }
  
                  @Override
                  public InputStream stream() throws IOException {
!                     return Files.newInputStream(targetPath());
                  }
              };
          }
      }
  
      private static List<String> readModuleResourceFile(String modName) {
< prev index next >