diff --git a/make/CompileJavaModules.gmk b/make/CompileJavaModules.gmk index 8741dba1868..cf9ba7b40fe 100644 --- a/make/CompileJavaModules.gmk +++ b/make/CompileJavaModules.gmk @@ -99,13 +99,16 @@ endif ################################################################################ # Setup the main compilation + +COMPILATION_OUTPUTDIR := $(if $($(MODULE)_BIN), $($(MODULE)_BIN), $(JDK_OUTPUTDIR)/modules) + $(eval $(call SetupJavaCompilation, $(MODULE), \ SMALL_JAVA := false, \ MODULE := $(MODULE), \ SRC := $(wildcard $(MODULE_SRC_DIRS)), \ INCLUDES := $(JDK_USER_DEFINED_FILTER), \ FAIL_NO_SRC := $(FAIL_NO_SRC), \ - BIN := $(if $($(MODULE)_BIN), $($(MODULE)_BIN), $(JDK_OUTPUTDIR)/modules), \ + BIN := $(COMPILATION_OUTPUTDIR), \ HEADERS := $(SUPPORT_OUTPUTDIR)/headers, \ CREATE_API_DIGEST := true, \ CLEAN := $(CLEAN), \ @@ -138,6 +141,15 @@ ifneq ($(COMPILER), bootjdk) MODULE_VALUECLASS_SRC_DIRS := $(call FindModuleValueClassSrcDirs, $(MODULE)) MODULE_VALUECLASS_SOURCEPATH := $(call GetModuleValueClassSrcPath) + # Temporarily compile valueclasses into a separate directory with the form: + # // + # and then copy the class files into: + # //META-INF/preview/ + # We cannot compile directly into the desired directory because it's the + # compiler which creates the original '//...' hierarchy. + VALUECLASS_OUTPUTDIR := $(SUPPORT_OUTPUTDIR)/$(VALUECLASSES_STR) + PREVIEW_OUTPUTDIR := $(COMPILATION_OUTPUTDIR)/$(MODULE)/META-INF/preview + ifneq ($(MODULE_VALUECLASS_SRC_DIRS),) $(eval $(call SetupJavaCompilation, $(MODULE)-$(VALUECLASSES_STR), \ SMALL_JAVA := false, \ @@ -145,7 +157,7 @@ ifneq ($(COMPILER), bootjdk) SRC := $(wildcard $(MODULE_VALUECLASS_SRC_DIRS)), \ INCLUDES := $(JDK_USER_DEFINED_FILTER), \ FAIL_NO_SRC := $(FAIL_NO_SRC), \ - BIN := $(SUPPORT_OUTPUTDIR)/$(VALUECLASSES_STR)/, \ + BIN := $(VALUECLASS_OUTPUTDIR)/, \ JAR := $(JDK_OUTPUTDIR)/lib/$(VALUECLASSES_STR)/$(MODULE)-$(VALUECLASSES_STR).jar, \ HEADERS := $(SUPPORT_OUTPUTDIR)/headers, \ DISABLED_WARNINGS := $(DISABLED_WARNINGS_java) preview, \ @@ -163,6 +175,14 @@ ifneq ($(COMPILER), bootjdk) TARGETS += $($(MODULE)-$(VALUECLASSES_STR)) + # Restructure the class file hierarchy from //... to /META-INF/preview//... + $(PREVIEW_OUTPUTDIR)/_copy_valueclasses.marker: $($(MODULE)-$(VALUECLASSES_STR)) + $(call MakeTargetDir) + $(CP) -R $(VALUECLASS_OUTPUTDIR)/$(MODULE)/. $(@D)/ + $(TOUCH) $@ + + TARGETS += $(PREVIEW_OUTPUTDIR)/_copy_valueclasses.marker + $(eval $(call SetupCopyFiles, $(MODULE)-copy-valueclass-jar, \ FILES := $(JDK_OUTPUTDIR)/lib/$(VALUECLASSES_STR)/$(MODULE)-$(VALUECLASSES_STR).jar, \ DEST := $(SUPPORT_OUTPUTDIR)/modules_libs/$(MODULE)/$(VALUECLASSES_STR), \ diff --git a/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java b/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java index c06c62488db..057f631266d 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java +++ b/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java @@ -37,8 +37,12 @@ import java.nio.file.StandardOpenOption; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.NoSuchElementException; import java.util.Objects; +import java.util.function.Predicate; import java.util.stream.IntStream; +import java.util.stream.Stream; + import jdk.internal.jimage.decompressor.Decompressor; /** @@ -326,6 +330,87 @@ public String[] getEntryNames() { .toArray(String[]::new); } + /** + * Returns the "raw" API for accessing underlying jimage resource entries. + * + *

This is only meaningful for use by code dealing directly with jimage + * files, and cannot be used to reliably lookup resources used at runtime. + * + *

This API remains valid until the image reader from which it was + * obtained is closed. + */ + // Package visible for use by ImageReader. + ResourceEntries getResourceEntries() { + return new ResourceEntries() { + @Override + public Stream entryNamesIn(String module) { + if (module.isEmpty() || module.equals("modules") || module.equals("packages")) { + throw new IllegalArgumentException("Invalid module name: " + module); + } + return IntStream.range(0, offsets.capacity()) + .map(offsets::get) + .filter(offset -> offset != 0) + // Reusing a location instance or getting the module + // offset directly would save a lot of allocations here. + .mapToObj(offset -> ImageLocation.readFrom(BasicImageReader.this, offset)) + // Reverse lookup of module offset would be faster here. + .filter(loc -> module.equals(loc.getModule())) + .map(ImageLocation::getFullName); + } + + private ImageLocation getResourceLocation(String name) { + // Other types of invalid name just result in no entry being found. + if (name.startsWith("/modules/") || name.startsWith("/packages/")) { + throw new IllegalArgumentException("Invalid entry name: " + name); + } + ImageLocation location = BasicImageReader.this.findLocation(name); + if (location == null) { + throw new NoSuchElementException("No such resource entry: " + name); + } + return location; + } + + @Override + public long sizeOf(String name) { + return getResourceLocation(name).getUncompressedSize(); + } + + @Override + public InputStream open(String name) { + return BasicImageReader.this.getResourceStream(getResourceLocation(name)); + } + }; + } + + /** + * Returns a sorted array of all matching entry names in the jimage file. + * + *

Entry names are of one of the following forms: + *

    + *
  • {@code "/modules//path/to/class-or-resource"} + *
  • {@code "//path/to/directory"} + *
  • {@code "/packages/"} + *
+ * + *

Note that the module names {@code "modules"} or {@code "packages"} are + * not representable in a jimage file, so can never exist. + * + *

The resulting array is sorted lexicographically, and the resulting + * order need not match that of a breadth or depth first search. + * + * @param matcher a predicate for entry names to be returned. + */ + public String[] getEntryNames(Predicate matcher) { + int[] attributeOffsets = new int[offsets.capacity()]; + offsets.get(attributeOffsets); + return IntStream.of(attributeOffsets) + .filter(o -> o != 0) + .mapToObj(o -> ImageLocation.readFrom(this, o).getFullName()) + .filter(matcher) + .sorted() + .toArray(String[]::new); + } + ImageLocation getLocation(int offset) { return ImageLocation.readFrom(this, offset); } diff --git a/src/java.base/share/classes/jdk/internal/jimage/ImageLocation.java b/src/java.base/share/classes/jdk/internal/jimage/ImageLocation.java index 0842e8e47a8..26167241835 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/ImageLocation.java +++ b/src/java.base/share/classes/jdk/internal/jimage/ImageLocation.java @@ -26,6 +26,7 @@ package jdk.internal.jimage; import java.nio.ByteBuffer; +import java.util.List; import java.util.Objects; import java.util.function.Predicate; @@ -159,6 +160,29 @@ public static int getFlags(String name, Predicate hasEntry) { } } + /** + * Helper function to calculate package flags for {@code "/packages/xxx"} + * directory entries. + * + *

Based on the module references, the flags are: + *

    + *
  • {@code FLAGS_HAS_PREVIEW_VERSION} if any referenced + * package has a preview version. + *
  • {@code FLAGS_IS_PREVIEW_ONLY} if all referenced packages + * are preview only. + *
+ * + * @return package flags for {@code "/packages/xxx"} directory entries. + */ + public static int getPackageFlags(List moduleReferences) { + boolean hasPreviewVersion = + moduleReferences.stream().anyMatch(ModuleReference::hasPreviewVersion); + boolean isPreviewOnly = + moduleReferences.stream().allMatch(ModuleReference::isPreviewOnly); + return (hasPreviewVersion ? ImageLocation.FLAGS_HAS_PREVIEW_VERSION : 0) + | (isPreviewOnly ? ImageLocation.FLAGS_IS_PREVIEW_ONLY : 0); + } + /** * Tests a non-preview image location's flags to see if it has preview * content associated with it. diff --git a/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java b/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java index a8ab2ff465b..3a58c8a5032 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java +++ b/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java @@ -230,6 +230,19 @@ public ByteBuffer getResourceBuffer(Node node) { return reader.getResourceBuffer(node.getLocation()); } + /** + * Returns the "raw" API for accessing underlying jimage resource entries. + * + *

This is only meaningful for use by code dealing directly with jimage + * files, and cannot be used to reliably lookup resources used at runtime. + * + *

This API remains valid until the image reader from which it was + * obtained is closed. + */ + public ResourceEntries getResourceEntries() { + return reader.getResourceEntries(); + } + private static final class SharedImageReader extends BasicImageReader { // There are >30,000 nodes in a complete jimage tree, and even relatively // common tasks (e.g. starting up javac) load somewhere in the region of diff --git a/src/java.base/share/classes/jdk/internal/jimage/ModuleReference.java b/src/java.base/share/classes/jdk/internal/jimage/ModuleReference.java index a013fbad947..7117d4708a1 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/ModuleReference.java +++ b/src/java.base/share/classes/jdk/internal/jimage/ModuleReference.java @@ -53,8 +53,7 @@ public final class ModuleReference implements Comparable { /** If set, this package exists in non-preview mode. */ private static final int FLAGS_PKG_HAS_NORMAL_VERSION = 0x2; /** If set, the associated module has resources (in normal or preview mode). */ - // TODO: Make this private again when image writer code is updated. - public static final int FLAGS_PKG_HAS_RESOURCES = 0x4; + private static final int FLAGS_PKG_HAS_RESOURCES = 0x4; /** * References are ordered with preview versions first which permits early @@ -118,7 +117,7 @@ public String name() { * under many modules, it only has resources in one. */ public boolean hasResources() { - return ((flags & FLAGS_PKG_HAS_RESOURCES) != 0); + return (flags & FLAGS_PKG_HAS_RESOURCES) != 0; } /** @@ -176,9 +175,9 @@ public static Iterator readNameOffsets( if (bufferSize == 0 || (bufferSize & 0x1) != 0) { throw new IllegalArgumentException("Invalid buffer size"); } - int testFlags = (includeNormal ? FLAGS_PKG_HAS_NORMAL_VERSION : 0) + int includeMask = (includeNormal ? FLAGS_PKG_HAS_NORMAL_VERSION : 0) + (includePreview ? FLAGS_PKG_HAS_PREVIEW_VERSION : 0); - if (testFlags == 0) { + if (includeMask == 0) { throw new IllegalArgumentException("Invalid flags"); } @@ -188,14 +187,7 @@ public static Iterator readNameOffsets( int nextIdx(int idx) { for (; idx < bufferSize; idx += 2) { // If any of the test flags are set, include this entry. - - // Temporarily allow for *neither* flag to be set. This is what would - // be written by a 1.0 version of the jimage flag, and indicates a - // normal resource without a preview version. - // TODO: Remove the zero-check below once image writer code is updated. - int previewFlags = - buffer.get(idx) & (FLAGS_PKG_HAS_NORMAL_VERSION | FLAGS_PKG_HAS_PREVIEW_VERSION); - if (previewFlags == 0 || (previewFlags & testFlags) != 0) { + if ((buffer.get(idx) & includeMask) != 0) { return idx; } else if (!includeNormal) { // Preview entries are first in the offset buffer, so we diff --git a/src/java.base/share/classes/jdk/internal/jimage/PreviewMode.java b/src/java.base/share/classes/jdk/internal/jimage/PreviewMode.java index 6f55add4db0..baf75261d8f 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/PreviewMode.java +++ b/src/java.base/share/classes/jdk/internal/jimage/PreviewMode.java @@ -56,6 +56,9 @@ public enum PreviewMode { * Resolves whether preview mode should be enabled for an {@link ImageReader}. */ public boolean isPreviewModeEnabled() { + if (!ENABLE_PREVIEW_MODE) { + return false; + } // A switch, instead of an abstract method, saves 3 subclasses. switch (this) { case DISABLED: @@ -83,4 +86,14 @@ public boolean isPreviewModeEnabled() { throw new IllegalStateException("Invalid mode: " + this); } } + ; + + // Temporary system property to disable preview patching and enable the new preview mode + // feature for testing/development. Once the preview mode feature is finished, the value + // will be always 'true' and this code, and all related dead-code can be removed. + private static final boolean DISABLE_PREVIEW_PATCHING_DEFAULT = false; + private static final boolean ENABLE_PREVIEW_MODE = Boolean.parseBoolean( + System.getProperty( + "DISABLE_PREVIEW_PATCHING", + Boolean.toString(DISABLE_PREVIEW_PATCHING_DEFAULT))); } diff --git a/src/java.base/share/classes/jdk/internal/jimage/ResourceEntries.java b/src/java.base/share/classes/jdk/internal/jimage/ResourceEntries.java new file mode 100644 index 00000000000..b025d5f3b73 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/jimage/ResourceEntries.java @@ -0,0 +1,41 @@ +package jdk.internal.jimage; + +import java.io.InputStream; +import java.util.stream.Stream; + +/** + * Accesses the underlying resource entries in a jimage file. + * + *

This API is designed only for use by the jlink classes, which manipulate + * jimage files directly. For inspection of runtime resources, it is vital that + * {@code previewMode} is correctly observed, making this API unsuitable. + * + *

This API ignores the {@code previewMode} of the {@link ImageReader} from + * which it is obtained, and returns an unmapped view of entries (e.g. allowing + * for direct access of resources in the {@code META-INF/preview/...} namespace). + * + *

It disallows access to resource directories (i.e. {@code "/modules/..."} + * or packages (i.e. {@code "/packages/..."}. + */ +public interface ResourceEntries { + /** + * Returns the full entry names for all resources in the given module, in + * random order. Entry names will always be prefixed by the given module + * name (e.g. "/ entryNamesIn(String module); + + /** + * Returns the (uncompressed) size of a resource given its full entry name. + * + * @throws java.util.NoSuchElementException if the resource does not exist. + */ + long sizeOf(String name); + + /** + * Returns an {@link InputStream} for a resource given its full entry name. + * + * @throws java.util.NoSuchElementException if the resource does not exist. + */ + InputStream open(String name); +} diff --git a/src/java.base/share/native/libjimage/imageFile.cpp b/src/java.base/share/native/libjimage/imageFile.cpp index 60c2cf44e79..31ac4ebc8d4 100644 --- a/src/java.base/share/native/libjimage/imageFile.cpp +++ b/src/java.base/share/native/libjimage/imageFile.cpp @@ -319,12 +319,7 @@ bool ImageFileReader::open() { !read_at((u1*)&_header, header_size, 0) || _header.magic(_endian) != IMAGE_MAGIC || _header.major_version(_endian) != MAJOR_VERSION || - // Temporarily, we allow either version (1.1 or 1.0) of the file to - // be read so this code can be committed before image writing changes - // for preview mode. Preview mode changes do not modify any structure, - // so a 1.0 file will look like a jimage without any preview resources. - // TODO: Restore equality check for MINOR_VERSION. - _header.minor_version(_endian) > MINOR_VERSION) { + _header.minor_version(_endian) != MINOR_VERSION) { close(); return false; } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java index c56346b6994..1b8abdd1bb2 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java @@ -24,6 +24,7 @@ */ package jdk.tools.jlink.internal; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; @@ -34,7 +35,7 @@ * An Archive of all content, classes, resources, configuration files, and * other, for a module. */ -public interface Archive { +public interface Archive extends Closeable { /** * Entry is contained in an Archive @@ -59,11 +60,12 @@ public static enum EntryType { private final String path; /** - * Constructs an entry of the given archive - * @param archive archive - * @param path - * @param name an entry name that does not contain the module name - * @param type + * Constructs an entry of the given archive. + * + * @param archive the archive in which this entry exists. + * @param path the complete path of the entry, including the module. + * @param name an entry name relative to its containing module. + * @param type the entry type. */ public Entry(Archive archive, String path, String name, EntryType type) { this.archive = Objects.requireNonNull(archive); @@ -72,10 +74,6 @@ public Entry(Archive archive, String path, String name, EntryType type) { this.type = Objects.requireNonNull(type); } - public final Archive archive() { - return archive; - } - public final EntryType type() { return type; } @@ -87,6 +85,11 @@ public final String name() { return name; } + /** + * Returns the path of this entry. + */ + public final String path() {return path;} + /** * Returns the name representing a ResourcePoolEntry in the form of: * /$MODULE/$ENTRY_NAME @@ -134,5 +137,6 @@ public String toString() { /* * Close the archive */ + @Override void close() throws IOException; } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/BasicImageWriter.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/BasicImageWriter.java index ca576227692..230285561ca 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/BasicImageWriter.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/BasicImageWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -29,6 +29,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; + import jdk.internal.jimage.ImageHeader; import jdk.internal.jimage.ImageStream; import jdk.internal.jimage.ImageStringsReader; @@ -36,21 +37,17 @@ public final class BasicImageWriter { public static final String MODULES_IMAGE_NAME = "modules"; - private ByteOrder byteOrder; - private ImageStringsWriter strings; + private final ByteOrder byteOrder; + private final ImageStringsWriter strings; private int length; private int[] redirect; private ImageLocationWriter[] locations; - private List input; - private ImageStream headerStream; - private ImageStream redirectStream; - private ImageStream locationOffsetStream; - private ImageStream locationStream; - private ImageStream allIndexStream; - - public BasicImageWriter() { - this(ByteOrder.nativeOrder()); - } + private final List input; + private final ImageStream headerStream; + private final ImageStream redirectStream; + private final ImageStream locationOffsetStream; + private final ImageStream locationStream; + private final ImageStream allIndexStream; public BasicImageWriter(ByteOrder byteOrder) { this.byteOrder = Objects.requireNonNull(byteOrder); @@ -75,11 +72,15 @@ public String getString(int offset) { return strings.get(offset); } - public void addLocation(String fullname, long contentOffset, - long compressedSize, long uncompressedSize) { + public void addLocation( + String fullname, + long contentOffset, + long compressedSize, + long uncompressedSize, + int previewFlags) { ImageLocationWriter location = ImageLocationWriter.newLocation(fullname, strings, - contentOffset, compressedSize, uncompressedSize); + contentOffset, compressedSize, uncompressedSize, previewFlags); input.add(location); length++; } @@ -88,13 +89,9 @@ ImageLocationWriter[] getLocations() { return locations; } - int getLocationsCount() { - return input.size(); - } - private void generatePerfectHash() { PerfectHashBuilder builder = - new PerfectHashBuilder<>( + new PerfectHashBuilder<>( PerfectHashBuilder.Entry.class, PerfectHashBuilder.Bucket.class); @@ -174,16 +171,4 @@ public byte[] getBytes() { return allIndexStream.toArray(); } - - ImageLocationWriter find(String key) { - int index = redirect[ImageStringsReader.hashCode(key) % length]; - - if (index < 0) { - index = -index - 1; - } else { - index = ImageStringsReader.hashCode(key, index) % length; - } - - return locations[index]; - } } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java index 9e05fe31aa9..89a001f5ba0 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -49,6 +49,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import jdk.internal.jimage.ImageLocation; import jdk.tools.jlink.internal.Archive.Entry; import jdk.tools.jlink.internal.Archive.Entry.EntryType; import jdk.tools.jlink.internal.JRTArchive.ResourceFileEntry; @@ -83,7 +84,7 @@ * } */ public final class ImageFileCreator { - private static final byte[] EMPTY_RESOURCE_BYTES = new byte[] {}; + private static final byte[] EMPTY_RESOURCE_BYTES = new byte[]{}; private static final String JLINK_MOD_NAME = "jdk.jlink"; private static final String RESPATH = "/" + JLINK_MOD_NAME + "/" + RESPATH_PATTERN; @@ -113,13 +114,12 @@ private ImageFileCreator(ImagePluginStack plugins, * @throws IOException */ public static ExecutableImage create(Set archives, - ByteOrder byteOrder, - ImagePluginStack plugins, - boolean generateRuntimeImage) - throws IOException - { + ByteOrder byteOrder, + ImagePluginStack plugins, + boolean generateRuntimeImage) + throws IOException { ImageFileCreator image = new ImageFileCreator(plugins, - generateRuntimeImage); + generateRuntimeImage); try { image.readAllEntries(archives); // write to modular image @@ -155,25 +155,25 @@ private void readAllEntries(Set archives) { } public static void recreateJimage(Path jimageFile, - Set archives, - ImagePluginStack pluginSupport, - boolean generateRuntimeImage) + Set archives, + ImagePluginStack pluginSupport, + boolean generateRuntimeImage) throws IOException { try { Map> entriesForModule = archives.stream().collect(Collectors.toMap( - Archive::moduleName, - a -> { - try (Stream entries = a.entries()) { - return entries.toList(); - } - })); + Archive::moduleName, + a -> { + try (Stream entries = a.entries()) { + return entries.toList(); + } + })); ByteOrder order = ByteOrder.nativeOrder(); BasicImageWriter writer = new BasicImageWriter(order); ResourcePoolManager pool = createPoolManager(archives, entriesForModule, order, writer); try (OutputStream fos = Files.newOutputStream(jimageFile); - BufferedOutputStream bos = new BufferedOutputStream(fos); - DataOutputStream out = new DataOutputStream(bos)) { + BufferedOutputStream bos = new BufferedOutputStream(fos); + DataOutputStream out = new DataOutputStream(bos)) { generateJImage(pool, writer, pluginSupport, out, generateRuntimeImage); } } finally { @@ -185,7 +185,7 @@ public static void recreateJimage(Path jimageFile, } private void writeImage(Set archives, - ByteOrder byteOrder) + ByteOrder byteOrder) throws IOException { BasicImageWriter writer = new BasicImageWriter(byteOrder); ResourcePoolManager allContent = createPoolManager(archives, @@ -222,36 +222,13 @@ private void writeImage(Set archives, * @throws IOException */ private static ResourcePool generateJImage(ResourcePoolManager allContent, - BasicImageWriter writer, - ImagePluginStack pluginSupport, - DataOutputStream out, - boolean generateRuntimeImage + BasicImageWriter writer, + ImagePluginStack pluginSupport, + DataOutputStream out, + boolean generateRuntimeImage ) throws IOException { - ResourcePool resultResources; - try { - resultResources = pluginSupport.visitResources(allContent); - if (generateRuntimeImage) { - // Keep track of non-modules resources for linking from a run-time image - resultResources = addNonClassResourcesTrackFiles(resultResources, - writer); - // Generate the diff between the input resources from packaged - // modules in 'allContent' to the plugin- or otherwise - // generated-content in 'resultResources' - resultResources = addResourceDiffFiles(allContent.resourcePool(), - resultResources, - writer); - } - } catch (PluginException pe) { - if (JlinkTask.DEBUG) { - pe.printStackTrace(); - } - throw pe; - } catch (Exception ex) { - if (JlinkTask.DEBUG) { - ex.printStackTrace(); - } - throw new IOException(ex); - } + ResourcePool resultResources = + getResourcePool(allContent, writer, pluginSupport, generateRuntimeImage); Set duplicates = new HashSet<>(); long[] offset = new long[1]; @@ -282,8 +259,10 @@ private static ResourcePool generateJImage(ResourcePoolManager allContent, offset[0] += onFileSize; return; } + int locFlags = ImageLocation.getFlags( + res.path(), p -> resultResources.findEntry(p).isPresent()); duplicates.add(path); - writer.addLocation(path, offset[0], compressedSize, uncompressedSize); + writer.addLocation(path, offset[0], compressedSize, uncompressedSize, locFlags); paths.add(path); offset[0] += onFileSize; } @@ -307,6 +286,40 @@ private static ResourcePool generateJImage(ResourcePoolManager allContent, return resultResources; } + private static ResourcePool getResourcePool( + ResourcePoolManager allContent, + BasicImageWriter writer, + ImagePluginStack pluginSupport, + boolean generateRuntimeImage) + throws IOException { + ResourcePool resultResources; + try { + resultResources = pluginSupport.visitResources(allContent); + if (generateRuntimeImage) { + // Keep track of non-modules resources for linking from a run-time image + resultResources = addNonClassResourcesTrackFiles(resultResources, + writer); + // Generate the diff between the input resources from packaged + // modules in 'allContent' to the plugin- or otherwise + // generated-content in 'resultResources' + resultResources = addResourceDiffFiles(allContent.resourcePool(), + resultResources, + writer); + } + } catch (PluginException pe) { + if (JlinkTask.DEBUG) { + pe.printStackTrace(); + } + throw pe; + } catch (Exception ex) { + if (JlinkTask.DEBUG) { + ex.printStackTrace(); + } + throw new IOException(ex); + } + return resultResources; + } + /** * Support for creating a runtime suitable for linking from the run-time * image. @@ -335,19 +348,19 @@ private static ResourcePool addResourceDiffFiles(ResourcePool jmodContent, throw new AssertionError("Failed to generate the runtime image diff", e); } Set modules = resultContent.moduleView().modules() - .map(a -> a.name()) - .collect(Collectors.toSet()); + .map(a -> a.name()) + .collect(Collectors.toSet()); // Add resource diffs for the resource files we are about to add modules.stream().forEach(m -> { String resourceName = String.format(DIFF_PATH, m); ResourceDiff.Builder builder = new ResourceDiff.Builder(); ResourceDiff d = builder.setKind(ResourceDiff.Kind.ADDED) - .setName(resourceName) - .build(); + .setName(resourceName) + .build(); diff.add(d); }); Map> perModDiffs = preparePerModuleDiffs(diff, - modules); + modules); return addDiffResourcesFiles(modules, perModDiffs, resultContent, writer); } @@ -361,7 +374,7 @@ private static Map> preparePerModuleDiffs(List perModDiff = modToDiff.computeIfAbsent(module, - a -> new ArrayList<>()); + a -> new ArrayList<>()); perModDiff.add(d); }); Map> allModsToDiff = new HashMap<>(); @@ -393,7 +406,7 @@ private static ResourcePool addDiffResourcesFiles(Set modules, ResourceDiff.write(diff, bout); } catch (IOException e) { throw new AssertionError("Failed to write resource diff file" + - " for module " + module, e); + " for module " + module, e); } out.add(ResourcePoolEntry.create(mResource, bout.toByteArray())); }); @@ -418,7 +431,7 @@ private static ResourcePool addNonClassResourcesTrackFiles(ResourcePool resultRe BasicImageWriter writer) { // Only add resources if jdk.jlink module is present in the target image Optional jdkJlink = resultResources.moduleView() - .findModule(JLINK_MOD_NAME); + .findModule(JLINK_MOD_NAME); if (jdkJlink.isPresent()) { Map> nonClassResources = recordAndFilterEntries(resultResources); return addModuleResourceEntries(resultResources, nonClassResources, writer); @@ -446,8 +459,8 @@ private static ResourcePool addModuleResourceEntries(ResourcePool resultResource Map> nonClassResEntries, BasicImageWriter writer) { Set inputModules = resultResources.moduleView().modules() - .map(rm -> rm.name()) - .collect(Collectors.toSet()); + .map(rm -> rm.name()) + .collect(Collectors.toSet()); ResourcePoolManager mgr = createPoolManager(resultResources, writer); ResourcePoolBuilder out = mgr.resourcePoolBuilder(); inputModules.stream().sorted().forEach(module -> { @@ -460,9 +473,9 @@ private static ResourcePool addModuleResourceEntries(ResourcePool resultResource out.add(ResourcePoolEntry.create(mResource, EMPTY_RESOURCE_BYTES)); } else { String mResContent = mResources.stream().sorted() - .collect(Collectors.joining("\n")); + .collect(Collectors.joining("\n")); out.add(ResourcePoolEntry.create(mResource, - mResContent.getBytes(StandardCharsets.UTF_8))); + mResContent.getBytes(StandardCharsets.UTF_8))); } }); return out.build(); @@ -491,9 +504,9 @@ private static Map> recordAndFilterEntries(ResourcePool res if (entry.type() != ResourcePoolEntry.Type.CLASS_OR_RESOURCE && entry.type() != ResourcePoolEntry.Type.TOP) { List mRes = nonClassResEntries.computeIfAbsent(entry.moduleName(), - a -> new ArrayList<>()); + a -> new ArrayList<>()); ResourceFileEntry rfEntry = ResourceFileEntry.toResourceFileEntry(entry, - platform); + platform); mRes.add(rfEntry.encodeToString()); } }); @@ -508,17 +521,17 @@ private static Platform getTargetPlatform(ResourcePool in) { } private static ResourcePoolManager createPoolManager(Set archives, - Map> entriesForModule, - ByteOrder byteOrder, - BasicImageWriter writer) throws IOException { + Map> entriesForModule, + ByteOrder byteOrder, + BasicImageWriter writer) throws IOException { ResourcePoolManager resources = createBasicResourcePoolManager(byteOrder, writer); archives.stream() .map(Archive::moduleName) .sorted() .flatMap(mn -> - entriesForModule.get(mn).stream() - .map(e -> new ArchiveEntryResourcePoolEntry(mn, - e.getResourcePoolEntryName(), e))) + entriesForModule.get(mn).stream() + .map(e -> new ArchiveEntryResourcePoolEntry(mn, + e.getResourcePoolEntryName(), e))) .forEach(resources::add); return resources; } @@ -550,7 +563,7 @@ public String getString(int id) { private static ResourcePoolManager createPoolManager(ResourcePool resultResources, BasicImageWriter writer) { ResourcePoolManager resources = createBasicResourcePoolManager(resultResources.byteOrder(), - writer); + writer); // Note that resources are already sorted in the correct order. // The underlying ResourcePoolManager keeps track of entries via // LinkedHashMap, which keeps values in insertion order. Therefore @@ -558,62 +571,4 @@ private static ResourcePoolManager createPoolManager(ResourcePool resultResource resultResources.entries().forEach(resources::add); return resources; } - - /** - * Helper method that splits a Resource path onto 3 items: module, parent - * and resource name. - * - * @param path - * @return An array containing module, parent and name. - */ - public static String[] splitPath(String path) { - Objects.requireNonNull(path); - String noRoot = path.substring(1); - int pkgStart = noRoot.indexOf("/"); - String module = noRoot.substring(0, pkgStart); - List result = new ArrayList<>(); - result.add(module); - String pkg = noRoot.substring(pkgStart + 1); - String resName; - int pkgEnd = pkg.lastIndexOf("/"); - if (pkgEnd == -1) { // No package. - resName = pkg; - } else { - resName = pkg.substring(pkgEnd + 1); - } - - pkg = toPackage(pkg, false); - result.add(pkg); - result.add(resName); - - String[] array = new String[result.size()]; - return result.toArray(array); - } - - /** - * Returns the path of the resource. - */ - public static String resourceName(String path) { - Objects.requireNonNull(path); - String s = path.substring(1); - int index = s.indexOf("/"); - return s.substring(index + 1); - } - - public static String toPackage(String name) { - return toPackage(name, false); - } - - private static String toPackage(String name, boolean log) { - int index = name.lastIndexOf('/'); - if (index > 0) { - return name.substring(0, index).replace('/', '.'); - } else { - // ## unnamed package - if (log) { - System.err.format("Warning: %s in unnamed package%n", name); - } - return ""; - } - } } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageLocationWriter.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageLocationWriter.java index f2c7f102027..c212e0f97ad 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageLocationWriter.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageLocationWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -44,7 +44,7 @@ void writeTo(ImageStream stream) { private ImageLocationWriter addAttribute(int kind, long value) { assert ATTRIBUTE_END < kind && - kind < ATTRIBUTE_COUNT : "Invalid attribute kind"; + kind < ATTRIBUTE_COUNT : "Invalid attribute kind"; attributes[kind] = value; return this; } @@ -53,9 +53,13 @@ private ImageLocationWriter addAttribute(int kind, String value) { return addAttribute(kind, strings.add(value)); } - static ImageLocationWriter newLocation(String fullName, + static ImageLocationWriter newLocation( + String fullName, ImageStringsWriter strings, - long contentOffset, long compressedSize, long uncompressedSize) { + long contentOffset, + long compressedSize, + long uncompressedSize, + int previewFlags) { String moduleName = ""; String parentName = ""; String baseName; @@ -64,7 +68,7 @@ static ImageLocationWriter newLocation(String fullName, if (fullName.startsWith("/modules/")) { moduleName = "modules"; baseName = fullName.substring("/modules/".length()); - } else if ( fullName.startsWith("/packages/")) { + } else if (fullName.startsWith("/packages/")) { moduleName = "packages"; baseName = fullName.substring("/packages/".length()); } else { @@ -90,13 +94,14 @@ static ImageLocationWriter newLocation(String fullName, } return new ImageLocationWriter(strings) - .addAttribute(ATTRIBUTE_MODULE, moduleName) - .addAttribute(ATTRIBUTE_PARENT, parentName) - .addAttribute(ATTRIBUTE_BASE, baseName) - .addAttribute(ATTRIBUTE_EXTENSION, extensionName) - .addAttribute(ATTRIBUTE_OFFSET, contentOffset) - .addAttribute(ATTRIBUTE_COMPRESSED, compressedSize) - .addAttribute(ATTRIBUTE_UNCOMPRESSED, uncompressedSize); + .addAttribute(ATTRIBUTE_MODULE, moduleName) + .addAttribute(ATTRIBUTE_PARENT, parentName) + .addAttribute(ATTRIBUTE_BASE, baseName) + .addAttribute(ATTRIBUTE_EXTENSION, extensionName) + .addAttribute(ATTRIBUTE_OFFSET, contentOffset) + .addAttribute(ATTRIBUTE_COMPRESSED, compressedSize) + .addAttribute(ATTRIBUTE_UNCOMPRESSED, uncompressedSize) + .addAttribute(ATTRIBUTE_PREVIEW_FLAGS, previewFlags); } @Override @@ -141,9 +146,9 @@ public boolean equals(Object obj) { ImageLocationWriter other = (ImageLocationWriter) obj; return getModuleOffset() == other.getModuleOffset() && - getParentOffset() == other.getParentOffset() && - getBaseOffset() == other.getBaseOffset() && - getExtensionOffset() == other.getExtensionOffset(); + getParentOffset() == other.getParentOffset() && + getBaseOffset() == other.getBaseOffset() && + getExtensionOffset() == other.getExtensionOffset(); } int getLocationOffset() { diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageResourcesTree.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageResourcesTree.java index 1d1914025ab..870978a410e 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageResourcesTree.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageResourcesTree.java @@ -24,6 +24,7 @@ */ package jdk.tools.jlink.internal; +import jdk.internal.jimage.ImageLocation; import jdk.internal.jimage.ModuleReference; import java.io.DataOutputStream; @@ -31,28 +32,30 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TreeMap; -import java.util.TreeSet; +import java.util.stream.Collectors; /** * A class to build a sorted tree of Resource paths as a tree of ImageLocation. - * */ // XXX Public only due to the JImageTask / JImageTask code duplication public final class ImageResourcesTree { public static boolean isTreeInfoResource(String path) { - return path.startsWith("/packages") || path.startsWith("/modules"); + return path.startsWith("/packages/") || path.startsWith("/modules/"); } /** * Path item tree node. */ - private static class Node { + // Visible for testing only. + static class Node { private final String name; private final Map children = new TreeMap<>(); @@ -68,6 +71,14 @@ private Node(String name, Node parent) { } } + private void setLocation(ImageLocationWriter loc) { + // This *can* be called more than once, but only with the same instance. + if (this.loc != null && loc != this.loc) { + throw new IllegalStateException("Cannot add different locations: " + name); + } + this.loc = Objects.requireNonNull(loc); + } + public String getPath() { if (parent == null) { return "/"; @@ -97,215 +108,207 @@ private static String buildPath(Node item) { } } - private static final class ResourceNode extends Node { + // Visible for testing only. + static final class ResourceNode extends Node { public ResourceNode(String name, Node parent) { super(name, parent); } } - private static class PackageNode extends Node { - /** - * A reference to a package. Empty packages can be located inside one or - * more modules. A package with classes exist in only one module. - */ - static final class PackageReference { - - private final String name; - private final boolean isEmpty; + /** + * A 2nd level package directory, {@code "/packages/"}. + * + *

While package paths can exist within many modules, for each package + * there is at most one module in which that package has resources. + * + *

For example, the package path {@code java/util} exists in both the + * {@code java.base} and {@code java.logging} modules. This means both + * {@code "/packages/java.util/java.base"} and + * {@code "/packages/java.util/java.logging"} will exist, but only + * {@code "java.base"} entry will be marked as having content. + * + *

When processing module references in non-preview mode, entries marked + * as {@link ModuleReference#isPreviewOnly() preview-only} must be ignored. + * + *

If all entries in a package are preview-only, then the package's flags + * have {@link ImageLocation#FLAGS_IS_PREVIEW_ONLY FLAGS_IS_PREVIEW_ONLY} + * set, and the entire package must be ignored. + */ + // Visible for testing only. + static final class PackageNode extends Node { + private final List moduleReferences; - PackageReference(String name, boolean isEmpty) { - this.name = Objects.requireNonNull(name); - this.isEmpty = isEmpty; + PackageNode(String name, List moduleReferences, Node parent) { + super(name, parent); + if (moduleReferences.isEmpty()) { + throw new IllegalStateException("Package must be associated with modules: " + name); } - - @Override - public String toString() { - return name + "[empty:" + isEmpty + "]"; + if (moduleReferences.stream().filter(ModuleReference::hasResources).count() > 1) { + throw new IllegalStateException("Multiple modules contain non-empty package: " + name); } + this.moduleReferences = Collections.unmodifiableList(moduleReferences); } - private final Map references = new TreeMap<>(); - - PackageNode(String name, Node parent) { - super(name, parent); - } - - private void addReference(String name, boolean isEmpty) { - PackageReference ref = references.get(name); - if (ref == null || ref.isEmpty) { - references.put(name, new PackageReference(name, isEmpty)); - } + List getModuleReferences() { + return moduleReferences; } + } - private void validate() { - boolean exists = false; - for (PackageReference ref : references.values()) { - if (!ref.isEmpty) { - if (exists) { - throw new RuntimeException("Multiple modules to contain package " - + getName()); - } else { - exists = true; - } - } - } + // Not serialized, and never stored in any field of any class that is. + @SuppressWarnings("serial") + private static final class InvalidTreeException extends Exception { + public InvalidTreeException(Node badNode) { + super("Resources tree, invalid data structure, skipping: " + badNode.getPath()); } + // Exception only used for program flow, not debugging. + @Override + public Throwable fillInStackTrace() {return this;} } /** * Tree of nodes. */ - private static final class Tree { + // Visible for testing only. + static final class Tree { + private static final String PREVIEW_PREFIX = "META-INF/preview/"; private final Map directAccess = new HashMap<>(); private final List paths; private final Node root; - private Node modules; - private Node packages; + private Node packagesRoot; - private Tree(List paths) { - this.paths = paths; + // Visible for testing only. + Tree(List paths) { + this.paths = paths.stream().sorted(Comparator.reverseOrder()).toList(); + // Root node is not added to the directAccess map. root = new Node("", null); buildTree(); } private void buildTree() { - modules = new Node("modules", root); - directAccess.put(modules.getPath(), modules); - - Map> moduleToPackage = new TreeMap<>(); - Map> packageToModule = new TreeMap<>(); - - for (String p : paths) { - if (!p.startsWith("/")) { - continue; - } - String[] split = p.split("/"); - // minimum length is 3 items: // - if (split.length < 3) { - System.err.println("Resources tree, invalid data structure, " - + "skipping " + p); - continue; - } - Node current = modules; - String module = null; - for (int i = 0; i < split.length; i++) { - // When a non terminal node is marked as being a resource, something is wrong. + Node modulesRoot = new Node("modules", root); + directAccess.put(modulesRoot.getPath(), modulesRoot); + packagesRoot = new Node("packages", root); + directAccess.put(packagesRoot.getPath(), packagesRoot); + + // Map of dot-separated package names to module references (those + // in which the package appear). References are merged after to + // ensure each module name appears only once, but temporarily a + // module may have several entries per package (e.g. with-content, + // without-content, normal, preview-only etc..). + Map> packageToModules = new TreeMap<>(); + for (String fullPath : paths) { + try { + processPath(fullPath, modulesRoot, packageToModules); + } catch (InvalidTreeException err) { // It has been observed some badly created jar file to contain - // invalid directory entry marled as not directory (see 8131762) - if (current instanceof ResourceNode) { - System.err.println("Resources tree, invalid data structure, " - + "skipping " + p); - continue; - } - String s = split[i]; - if (!s.isEmpty()) { - // First item, this is the module, simply add a new node to the - // tree. - if (module == null) { - module = s; - } - Node n = current.children.get(s); - if (n == null) { - if (i == split.length - 1) { // Leaf - n = new ResourceNode(s, current); - String pkg = toPackageName(n.parent); - //System.err.println("Adding a resource node. pkg " + pkg + ", name " + s); - if (pkg != null && !pkg.startsWith("META-INF")) { - Set pkgs = moduleToPackage.get(module); - if (pkgs == null) { - pkgs = new TreeSet<>(); - moduleToPackage.put(module, pkgs); - } - pkgs.add(pkg); - } - } else { // put only sub trees, no leaf - n = new Node(s, current); - directAccess.put(n.getPath(), n); - String pkg = toPackageName(n); - if (pkg != null && !pkg.startsWith("META-INF")) { - Set mods = packageToModule.get(pkg); - if (mods == null) { - mods = new TreeSet<>(); - packageToModule.put(pkg, mods); - } - mods.add(module); - } - } - } - current = n; - } - } - } - packages = new Node("packages", root); - directAccess.put(packages.getPath(), packages); - // The subset of package nodes that have some content. - // These packages exist only in a single module. - for (Map.Entry> entry : moduleToPackage.entrySet()) { - for (String pkg : entry.getValue()) { - PackageNode pkgNode = new PackageNode(pkg, packages); - pkgNode.addReference(entry.getKey(), false); - directAccess.put(pkgNode.getPath(), pkgNode); + // invalid directory entry marked as not directory (see 8131762). + System.err.println(err.getMessage()); } } - // All packages - for (Map.Entry> entry : packageToModule.entrySet()) { - // Do we already have a package node? - PackageNode pkgNode = (PackageNode) packages.getChildren(entry.getKey()); - if (pkgNode == null) { - pkgNode = new PackageNode(entry.getKey(), packages); - } - for (String module : entry.getValue()) { - pkgNode.addReference(module, true); - } + // We've collected information for all "packages", including the root + // (empty) package and anything under "META-INF". However, these should + // not have entries in the "/packages" directory. + packageToModules.keySet().removeIf(p -> p.isEmpty() || p.equals("META-INF") || p.startsWith("META-INF.")); + packageToModules.forEach((pkgName, modRefs) -> { + // Merge multiple refs for the same module. + List pkgModules = modRefs.stream() + .collect(Collectors.groupingBy(ModuleReference::name)) + .values().stream() + .map(refs -> refs.stream().reduce(ModuleReference::merge).orElseThrow()) + .sorted() + .toList(); + PackageNode pkgNode = new PackageNode(pkgName, pkgModules, packagesRoot); directAccess.put(pkgNode.getPath(), pkgNode); + }); + } + + private void processPath( + String fullPath, + Node modulesRoot, + Map> packageToModules) + throws InvalidTreeException { + // Paths are untrusted, so be careful about checking expected format. + if (!fullPath.startsWith("/") || fullPath.endsWith("/") || fullPath.contains("//")) { + return; + } + int modEnd = fullPath.indexOf('/', 1); + // Ensure non-empty module name with non-empty suffix. + if (modEnd <= 1) { + return; } - // Validate that the packages are well formed. - for (Node n : packages.children.values()) { - ((PackageNode)n).validate(); + String modName = fullPath.substring(1, modEnd); + String pkgPath = fullPath.substring(modEnd + 1); + + Node parentNode = getDirectoryNode(modName, modulesRoot); + boolean isPreviewPath = false; + if (pkgPath.startsWith(PREVIEW_PREFIX)) { + // For preview paths, process nodes relative to the preview directory. + pkgPath = pkgPath.substring(PREVIEW_PREFIX.length()); + Node metaInf = getDirectoryNode("META-INF", parentNode); + parentNode = getDirectoryNode("preview", metaInf); + isPreviewPath = true; } + int pathEnd = pkgPath.lastIndexOf('/'); + // From invariants tested above, this must now be well-formed. + String fullPkgName = (pathEnd == -1) ? "" : pkgPath.substring(0, pathEnd).replace('/', '.'); + String resourceName = pkgPath.substring(pathEnd + 1); + // Intermediate packages are marked "empty" (no resources). This might + // later be merged with a non-empty reference for the same package. + ModuleReference emptyRef = ModuleReference.forEmptyPackageIn(modName, isPreviewPath); + + // Work down through empty packages to final resource. + for (int i = pkgEndIndex(fullPkgName, 0); i != -1; i = pkgEndIndex(fullPkgName, i)) { + // Due to invariants already checked, pkgName is non-empty. + String pkgName = fullPkgName.substring(0, i); + packageToModules.computeIfAbsent(pkgName, p -> new HashSet<>()).add(emptyRef); + String childNodeName = pkgName.substring(pkgName.lastIndexOf('.') + 1); + parentNode = getDirectoryNode(childNodeName, parentNode); + } + // Reached non-empty (leaf) package (could still be a duplicate). + Node resourceNode = parentNode.getChildren(resourceName); + if (resourceNode == null) { + ModuleReference resourceRef = ModuleReference.forPackageIn(modName, isPreviewPath); + packageToModules.computeIfAbsent(fullPkgName, p -> new HashSet<>()).add(resourceRef); + // Init adds new node to parent (don't add resources to directAccess). + new ResourceNode(resourceName, parentNode); + } else if (!(resourceNode instanceof ResourceNode)) { + throw new InvalidTreeException(resourceNode); + } } - public String toResourceName(Node node) { - if (!node.children.isEmpty()) { - throw new RuntimeException("Node is not a resource"); + private Node getDirectoryNode(String name, Node parent) throws InvalidTreeException { + Node child = parent.getChildren(name); + if (child == null) { + // Adds child to parent during init. + child = new Node(name, parent); + directAccess.put(child.getPath(), child); + } else if (child instanceof ResourceNode) { + throw new InvalidTreeException(child); } - return removeRadical(node); + return child; } - public String getModule(Node node) { - if (node.parent == null || node.getName().equals("modules") - || node.getName().startsWith("packages")) { - return null; - } - String path = removeRadical(node); - // "/xxx/..."; - path = path.substring(1); - int i = path.indexOf("/"); - if (i == -1) { - return path; - } else { - return path.substring(0, i); + // Helper to iterate package names up to, and including, the complete name. + private int pkgEndIndex(String s, int i) { + if (i >= 0 && i < s.length()) { + i = s.indexOf('.', i + 1); + return i != -1 ? i : s.length(); } + return -1; } - public String toPackageName(Node node) { - if (node.parent == null) { - return null; - } - String path = removeRadical(node.getPath(), "/modules/"); - String module = getModule(node); - if (path.equals(module)) { - return null; + private String toResourceName(Node node) { + if (!node.children.isEmpty()) { + throw new RuntimeException("Node is not a resource"); } - String pkg = removeRadical(path, module + "/"); - return pkg.replace('/', '.'); + return removeRadical(node); } - public String removeRadical(Node node) { + private String removeRadical(Node node) { return removeRadical(node.getPath(), "/modules"); } @@ -341,9 +344,10 @@ private static final class LocationsAdder { private int addLocations(Node current) { if (current instanceof PackageNode) { - PackageNode pkgNode = (PackageNode) current; - int size = pkgNode.references.size() * 8; - writer.addLocation(current.getPath(), offset, 0, size); + List refs = ((PackageNode) current).getModuleReferences(); + // "/packages/" entries have 8-byte entries (flags+offset). + int size = refs.size() * 8; + writer.addLocation(current.getPath(), offset, 0, size, ImageLocation.getPackageFlags(refs)); offset += size; } else { int[] ret = new int[current.children.size()]; @@ -353,8 +357,10 @@ private int addLocations(Node current) { i += 1; } if (current != tree.getRoot() && !(current instanceof ResourceNode)) { + int locFlags = ImageLocation.getFlags(current.getPath(), tree.directAccess::containsKey); + // Normal directory entries have 4-byte entries (offset only). int size = ret.length * 4; - writer.addLocation(current.getPath(), offset, 0, size); + writer.addLocation(current.getPath(), offset, 0, size, locFlags); offset += size; } } @@ -371,7 +377,7 @@ private List computeContent() { for (Map.Entry entry : outLocations.entrySet()) { Node item = tree.getMap().get(entry.getKey()); if (item != null) { - item.loc = entry.getValue(); + item.setLocation(entry.getValue()); } } computeContent(tree.getRoot(), outLocations); @@ -380,18 +386,13 @@ private List computeContent() { private int computeContent(Node current, Map outLocations) { if (current instanceof PackageNode) { - // /packages/ - PackageNode pkgNode = (PackageNode) current; - int size = pkgNode.references.size() * 8; - ByteBuffer buff = ByteBuffer.allocate(size); - buff.order(writer.getByteOrder()); - for (PackageNode.PackageReference mod : pkgNode.references.values()) { - buff.putInt(mod.isEmpty ? 0 : ModuleReference.FLAGS_PKG_HAS_RESOURCES); - buff.putInt(writer.addString(mod.name)); - } - byte[] arr = buff.array(); - content.add(arr); - current.loc = outLocations.get(current.getPath()); + // "/packages/" entries have 8-byte entries (flags+offset). + List refs = ((PackageNode) current).getModuleReferences(); + ByteBuffer byteBuffer = ByteBuffer.allocate(8 * refs.size()); + byteBuffer.order(writer.getByteOrder()); + ModuleReference.write(refs, byteBuffer.asIntBuffer(), writer::addString); + content.add(byteBuffer.array()); + current.setLocation(outLocations.get(current.getPath())); } else { int[] ret = new int[current.children.size()]; int i = 0; @@ -412,10 +413,10 @@ private int computeContent(Node current, Map outLoc if (current instanceof ResourceNode) { // A resource location, remove "/modules" String s = tree.toResourceName(current); - current.loc = outLocations.get(s); + current.setLocation(outLocations.get(s)); } else { // empty "/packages" or empty "/modules" paths - current.loc = outLocations.get(current.getPath()); + current.setLocation(outLocations.get(current.getPath())); } } if (current.loc == null && current != tree.getRoot()) { diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java index aac220e5b94..0badcf3b923 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java @@ -51,6 +51,8 @@ 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; @@ -63,10 +65,9 @@ * 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; + private final ResourceEntries imageResources; // The collection of files of this module private final List files = new ArrayList<>(); // Files not part of the lib/modules image of the JDK install. @@ -99,12 +100,11 @@ public class JRTArchive implements Archive { Set upgradeableFiles) { this.module = module; this.path = path; - this.ref = ModuleFinder.ofSystem() - .find(module) - .orElseThrow(() -> - new IllegalArgumentException( - "Module " + module + - " not part of the JDK install")); + ModuleFinder.ofSystem() + .find(module) + .orElseThrow(() -> new IllegalArgumentException( + "Module " + module + " not part of the JDK install")); + this.imageResources = SystemImageReader.get().getResourceEntries(); this.errorOnModifiedFile = errorOnModifiedFile; this.otherRes = readModuleResourceFile(module); this.resDiff = Objects.requireNonNull(perModDiff).stream() @@ -159,52 +159,35 @@ public boolean equals(Object obj) { 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 - 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()); + imageResources.entryNamesIn(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(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()); + .filter(rd -> rd.getKind() == ResourceDiff.Kind.REMOVED) + .map(rd -> new JrtModuleFile(this, rd.getName(), rd)) + .toList()); } } @@ -234,15 +217,10 @@ private void addNonClassResources() { } } - return new JRTArchiveFile(JRTArchive.this, - m.resPath, - toEntryType(m.resType), - m.hashOrTarget, - m.symlink, - /* diff only for resources */ - null); - }) - .toList()); + return new JrtOtherFile( + this, m.resPath, toEntryType(m.resType), m.hashOrTarget, m.symlink); + }) + .toList()); } } @@ -324,9 +302,9 @@ public String encodeToString() { } /** - * line: ||| + * line: {@code |||} * - * Take the integer before '|' convert it to a Type. The second + *

Take the integer before {@code '|'} 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). */ @@ -437,47 +415,74 @@ interface JRTFile { Entry toEntry(); } - record JRTArchiveFile(Archive archive, - String resPath, - EntryType resType, - String sha, - boolean symlink, - ResourceDiff diff) implements JRTFile { + 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.sizeOf(path()); + } + + @Override + public InputStream stream() { + return archive.imageResources.open(path()); + } + }; + } + } + } + + 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() { - return new Entry(archive, - String.format("/%s/%s", - archive.moduleName(), - resPath), - resPath, - resType) { + assert resType != EntryType.CLASS_OR_RESOURCE; + + 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)); - } + return Files.size(targetPath()); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -485,28 +490,8 @@ public long size() { @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(); - } + return Files.newInputStream(targetPath()); } - }; } } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java index 928b9a47934..af77c25c28b 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java @@ -349,21 +349,22 @@ public static void createImage(JlinkConfiguration config, plugins = plugins == null ? new PluginsConfiguration() : plugins; // First create the image provider - ImageProvider imageProvider = - createImageProvider(config, - null, - IGNORE_SIGNING_DEFAULT, - false, - null, - false, - new OptionsValues(), - null); - - // Then create the Plugin Stack - ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(plugins); - - //Ask the stack to proceed; - stack.operate(imageProvider); + try (ImageHelper imageProvider = + createImageProvider(config, + null, + IGNORE_SIGNING_DEFAULT, + false, + null, + false, + new OptionsValues(), + null)) { + + // Then create the Plugin Stack + ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(plugins); + + //Ask the stack to proceed; + stack.operate(imageProvider); + } } // the token for "all modules on the module path" @@ -486,22 +487,25 @@ private void createImage(JlinkConfiguration config) throws Exception { } // First create the image provider - ImageHelper imageProvider = createImageProvider(config, - options.packagedModulesPath, - options.ignoreSigning, - options.bindServices, - options.endian, - options.verbose, - options, - log); - - // Then create the Plugin Stack - ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration( - taskHelper.getPluginsConfig(options.output, options.launchers, - imageProvider.targetPlatform)); - - //Ask the stack to proceed - stack.operate(imageProvider); + try (ImageHelper imageProvider = createImageProvider(config, + options.packagedModulesPath, + options.ignoreSigning, + options.bindServices, + options.endian, + options.verbose, + options, + log)) { + // Then create the Plugin Stack + ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration( + taskHelper.getPluginsConfig( + options.output, + options.launchers, + imageProvider.targetPlatform)); + + //Ask the stack to proceed + stack.operate(imageProvider); + } + } /** @@ -1027,10 +1031,11 @@ private String getSaveOpts() { return sb.toString(); } - private static record ImageHelper(Set archives, - Platform targetPlatform, - Path packagedModulesPath, - boolean generateRuntimeImage) implements ImageProvider { + private record ImageHelper(Set archives, + Platform targetPlatform, + Path packagedModulesPath, + boolean generateRuntimeImage) + implements ImageProvider, AutoCloseable { @Override public ExecutableImage retrieve(ImagePluginStack stack) throws IOException { ExecutableImage image = ImageFileCreator.create(archives, @@ -1046,5 +1051,12 @@ public ExecutableImage retrieve(ImagePluginStack stack) throws IOException { } return image; } + + @Override + public void close() throws IOException { + for (Archive archive : archives) { + archive.close(); + } + } } } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolManager.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolManager.java index ba04b9db014..a45eb16d61c 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolManager.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolManager.java @@ -66,20 +66,8 @@ static Attributes readModuleAttributes(ResourcePoolModule mod) { } } - /** - * Returns true if a resource is located in a named package. - */ - public static boolean isNamedPackageResource(String name) { - int index = name.lastIndexOf("/"); - if (index == -1) { - return false; - } else { - String pn = name.substring(0, index).replace('/', '.'); - return Checks.isPackageName(pn); - } - } - static class ResourcePoolModuleImpl implements ResourcePoolModule { + private static final String PREVIEW_PREFIX = "META-INF/preview/"; final Map moduleContent = new LinkedHashMap<>(); // lazily initialized @@ -132,16 +120,8 @@ private void initModuleAttributes() { public Set packages() { Set pkgs = new HashSet<>(); moduleContent.values().stream() - .filter(m -> m.type() == ResourcePoolEntry.Type.CLASS_OR_RESOURCE) - .forEach(res -> { - String name = ImageFileCreator.resourceName(res.path()); - if (isNamedPackageResource(name)) { - String pkg = ImageFileCreator.toPackage(name); - if (!pkg.isEmpty()) { - pkgs.add(pkg); - } - } - }); + .filter(m -> m.type() == ResourcePoolEntry.Type.CLASS_OR_RESOURCE) + .forEach(res -> inferPackageName(res).ifPresent(pkgs::add)); return pkgs; } @@ -159,6 +139,39 @@ public Stream entries() { public int entryCount() { return moduleContent.values().size(); } + + /** + * Returns a valid non-empty package name, inferred from a resource pool + * entry's path. + * + *

If the resource pool entry is for a preview resource (i.e. with + * path {@code "/mod-name/META-INF/preview/pkg-path/resource-name"}) + * the package name is the non-preview name based on {@code "pkg-path"}. + * + * @return the inferred package name, or {@link Optional#empty() empty} + * if no name could be inferred. + */ + private static Optional inferPackageName(ResourcePoolEntry res) { + // Expect entry paths to be "/mod-name/pkg-path/resource-name", but + // may also get "/mod-name/META-INF/preview/pkg-path/resource-name" + String name = res.path(); + if (name.charAt(0) == '/') { + int pkgStart = name.indexOf('/', 1) + 1; + int pkgEnd = name.lastIndexOf('/'); + if (pkgStart > 0 && pkgEnd > pkgStart) { + String pkgPath = name.substring(pkgStart, pkgEnd); + // Handle preview paths by removing the prefix. + if (pkgPath.startsWith(PREVIEW_PREFIX)) { + pkgPath = pkgPath.substring(PREVIEW_PREFIX.length()); + } + String pkgName = pkgPath.replace('/', '.'); + if (Checks.isPackageName(pkgName)) { + return Optional.of(pkgName); + } + } + } + return Optional.empty(); + } } public class ResourcePoolImpl implements ResourcePool { diff --git a/test/jdk/jdk/internal/jimage/ImageLocationTest.java b/test/jdk/jdk/internal/jimage/ImageLocationTest.java new file mode 100644 index 00000000000..66a76328ae9 --- /dev/null +++ b/test/jdk/jdk/internal/jimage/ImageLocationTest.java @@ -0,0 +1,136 @@ +/* + * 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. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +import jdk.internal.jimage.ImageLocation; +import jdk.internal.jimage.ModuleReference; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/* + * @test + * @summary Tests for ImageLocation. + * @modules java.base/jdk.internal.jimage + * @run junit/othervm -esa -DDISABLE_PREVIEW_PATCHING=false ImageLocationTest + */ +public class ImageLocationTest { + + @ParameterizedTest + @ValueSource(strings = { + "/modules/modfoo/com", + "/modules/modfoo/com/foo/Foo.class"}) + public void getFlags_resourceNames(String name) { + String previewName = previewName(name); + + int noPreviewFlags = + ImageLocation.getFlags(name, Set.of(name)::contains); + assertEquals(0, noPreviewFlags); + assertFalse(ImageLocation.hasPreviewVersion(noPreviewFlags)); + assertFalse(ImageLocation.isPreviewOnly(noPreviewFlags)); + + int withPreviewFlags = + ImageLocation.getFlags(name, Set.of(name, previewName)::contains); + assertTrue(ImageLocation.hasPreviewVersion(withPreviewFlags)); + assertFalse(ImageLocation.isPreviewOnly(withPreviewFlags)); + + int previewOnlyFlags = ImageLocation.getFlags(previewName, Set.of(previewName)::contains); + assertFalse(ImageLocation.hasPreviewVersion(previewOnlyFlags)); + assertTrue(ImageLocation.isPreviewOnly(previewOnlyFlags)); + } + + @ParameterizedTest + @ValueSource(strings = { + "/modules", + "/packages", + "/modules/modfoo", + "/modules/modfoo/META-INF", + "/modules/modfoo/META-INF/module-info.class"}) + public void getFlags_zero(String name) { + assertEquals(0, ImageLocation.getFlags(name, Set.of(name)::contains)); + } + + @Test + public void getFlags_packageFlags() { + assertThrows( + IllegalArgumentException.class, + () -> ImageLocation.getFlags("/packages/pkgname", p -> true)); + } + + @Test + public void getPackageFlags_noPreview() { + List refs = List.of( + ModuleReference.forPackage("modfoo", false), + ModuleReference.forEmptyPackage("modbar", false), + ModuleReference.forEmptyPackage("modbaz", false)); + int noPreviewFlags = ImageLocation.getPackageFlags(refs); + assertEquals(0, noPreviewFlags); + assertFalse(ImageLocation.hasPreviewVersion(noPreviewFlags)); + assertFalse(ImageLocation.isPreviewOnly(noPreviewFlags)); + } + + @Test + public void getPackageFlags_withPreview() { + List refs = List.of( + ModuleReference.forPackage("modfoo", true), + ModuleReference.forEmptyPackage("modbar", false), + ModuleReference.forEmptyPackage("modbaz", true)); + int withPreviewFlags = ImageLocation.getPackageFlags(refs); + assertTrue(ImageLocation.hasPreviewVersion(withPreviewFlags)); + assertFalse(ImageLocation.isPreviewOnly(withPreviewFlags)); + } + + @Test + public void getPackageFlags_previewOnly() { + List refs = List.of( + ModuleReference.forPackage("modfoo", true), + ModuleReference.forEmptyPackage("modbar", true), + ModuleReference.forEmptyPackage("modbaz", true)); + int previewOnlyFlags = ImageLocation.getPackageFlags(refs); + // Note the asymmetry between this and the getFlags() case. Unlike + // module resources, there is no concept of a separate package directory + // existing in the preview namespace, so a single entry serves both + // purposes, and hasPreviewVersion() and isPreviewOnly() can both be set. + assertTrue(ImageLocation.hasPreviewVersion(previewOnlyFlags)); + assertTrue(ImageLocation.isPreviewOnly(previewOnlyFlags)); + } + + private static final Pattern MODULES_PATH = Pattern.compile("/modules/([^/]+)/(.+)"); + + private static String previewName(String name) { + var m = MODULES_PATH.matcher(name); + if (m.matches() && !m.group(2).startsWith("/META-INF/preview/")) { + return "/modules/" + m.group(1) + "/META-INF/preview/" + m.group(2); + } + throw new IllegalStateException("Invalid modules name: " + name); + } +} diff --git a/test/jdk/jdk/internal/jimage/ImageReaderTest.java b/test/jdk/jdk/internal/jimage/ImageReaderTest.java index 8246f7640ab..e835f68f23a 100644 --- a/test/jdk/jdk/internal/jimage/ImageReaderTest.java +++ b/test/jdk/jdk/internal/jimage/ImageReaderTest.java @@ -44,6 +44,7 @@ import java.util.Set; import java.util.stream.Collectors; +import static java.util.stream.Collectors.toSet; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -64,22 +65,33 @@ * @library /test/jdk/tools/lib * /test/lib * @build tests.* - * @run junit/othervm ImageReaderTest + * @run junit/othervm -esa -DDISABLE_PREVIEW_PATCHING=false ImageReaderTest */ /// Using PER_CLASS lifecycle means the (expensive) image file is only build once. /// There is no mutable test instance state to worry about. @TestInstance(PER_CLASS) public class ImageReaderTest { - + // The '@' prefix marks the entry as a preview entry which will be placed in + // the '/modules//META-INF/preview/...' namespace. private static final Map> IMAGE_ENTRIES = Map.of( "modfoo", Arrays.asList( - "com.foo.Alpha", - "com.foo.Beta", - "com.foo.bar.Gamma"), + "com.foo.HasPreviewVersion", + "com.foo.NormalFoo", + "com.foo.bar.NormalBar", + // Replaces original class in preview mode. + "@com.foo.HasPreviewVersion", + // New class in existing package in preview mode. + "@com.foo.bar.IsPreviewOnly"), "modbar", Arrays.asList( "com.bar.One", - "com.bar.Two")); + "com.bar.Two", + // Two new packages in preview mode (new symbolic links). + "@com.bar.preview.stuff.Foo", + "@com.bar.preview.stuff.Bar"), + "modgus", Arrays.asList( + // A second module with a preview-only empty package (preview). + "@com.bar.preview.other.Gus")); private final Path image = buildJImage(IMAGE_ENTRIES); @ParameterizedTest @@ -115,20 +127,20 @@ public void testModuleNodes_absent(String name) throws IOException { @Test public void testModuleResources() throws IOException { try (ImageReader reader = ImageReader.open(image, PreviewMode.DISABLED)) { - assertNode(reader, "/modules/modfoo/com/foo/Alpha.class"); + assertNode(reader, "/modules/modfoo/com/foo/HasPreviewVersion.class"); assertNode(reader, "/modules/modbar/com/bar/One.class"); ImageClassLoader loader = new ImageClassLoader(reader, IMAGE_ENTRIES.keySet()); - assertEquals("Class: com.foo.Alpha", loader.loadAndGetToString("modfoo", "com.foo.Alpha")); - assertEquals("Class: com.foo.Beta", loader.loadAndGetToString("modfoo", "com.foo.Beta")); - assertEquals("Class: com.foo.bar.Gamma", loader.loadAndGetToString("modfoo", "com.foo.bar.Gamma")); + assertEquals("Class: com.foo.HasPreviewVersion", loader.loadAndGetToString("modfoo", "com.foo.HasPreviewVersion")); + assertEquals("Class: com.foo.NormalFoo", loader.loadAndGetToString("modfoo", "com.foo.NormalFoo")); + assertEquals("Class: com.foo.bar.NormalBar", loader.loadAndGetToString("modfoo", "com.foo.bar.NormalBar")); assertEquals("Class: com.bar.One", loader.loadAndGetToString("modbar", "com.bar.One")); } } @ParameterizedTest @CsvSource(delimiter = ':', value = { - "modfoo:com/foo/Alpha.class", + "modfoo:com/foo/HasPreviewVersion.class", "modbar:com/bar/One.class", }) public void testResource_present(String modName, String resPath) throws IOException { @@ -148,15 +160,15 @@ public void testResource_present(String modName, String resPath) throws IOExcept "modfoo:/com/bar/One.class", // Resource in wrong module. "modfoo:com/bar/One.class", - "modbar:com/foo/Alpha.class", + "modbar:com/foo/HasPreviewVersion.class", // Directories are not returned. "modfoo:com/foo", "modbar:com/bar", // JImage entries exist for these, but they are not resources. - "modules:modfoo/com/foo/Alpha.class", + "modules:modfoo/com/foo/HasPreviewVersion.class", "packages:com.foo/modfoo", // Empty module names/paths do not find resources. - "'':modfoo/com/foo/Alpha.class", + "'':modfoo/com/foo/HasPreviewVersion.class", "modfoo:''"}) public void testResource_absent(String modName, String resPath) throws IOException { try (ImageReader reader = ImageReader.open(image, PreviewMode.DISABLED)) { @@ -176,7 +188,7 @@ public void testResource_absent(String modName, String resPath) throws IOExcepti // Don't permit module names to contain paths. "modfoo/com/bar:One.class", "modfoo/com:bar/One.class", - "modules/modfoo/com:foo/Alpha.class", + "modules/modfoo/com:foo/HasPreviewVersion.class", }) public void testResource_invalid(String modName, String resPath) throws IOException { try (ImageReader reader = ImageReader.open(image, PreviewMode.DISABLED)) { @@ -189,7 +201,7 @@ public void testResource_invalid(String modName, String resPath) throws IOExcept public void testPackageDirectories() throws IOException { try (ImageReader reader = ImageReader.open(image, PreviewMode.DISABLED)) { Node root = assertDir(reader, "/packages"); - Set pkgNames = root.getChildNames().collect(Collectors.toSet()); + Set pkgNames = root.getChildNames().collect(toSet()); assertTrue(pkgNames.contains("/packages/com")); assertTrue(pkgNames.contains("/packages/com.foo")); assertTrue(pkgNames.contains("/packages/com.bar")); @@ -212,6 +224,123 @@ public void testPackageLinks() throws IOException { } } + @Test + public void testPreviewResources_disabled() throws IOException { + try (ImageReader reader = ImageReader.open(image, PreviewMode.DISABLED)) { + ImageClassLoader loader = new ImageClassLoader(reader, IMAGE_ENTRIES.keySet()); + + // No preview classes visible. + assertEquals("Class: com.foo.HasPreviewVersion", loader.loadAndGetToString("modfoo", "com.foo.HasPreviewVersion")); + assertEquals("Class: com.foo.NormalFoo", loader.loadAndGetToString("modfoo", "com.foo.NormalFoo")); + assertEquals("Class: com.foo.bar.NormalBar", loader.loadAndGetToString("modfoo", "com.foo.bar.NormalBar")); + + // NormalBar exists but IsPreviewOnly doesn't. + assertResource(reader, "modfoo", "com/foo/bar/NormalBar.class"); + assertAbsent(reader, "/modules/modfoo/com/foo/bar/IsPreviewOnly.class"); + assertDirContents(reader, "/modules/modfoo/com/foo", "HasPreviewVersion.class", "NormalFoo.class", "bar"); + assertDirContents(reader, "/modules/modfoo/com/foo/bar", "NormalBar.class"); + } + } + + @Test + public void testPreviewResources_enabled() throws IOException { + try (ImageReader reader = ImageReader.open(image, PreviewMode.ENABLED)) { + ImageClassLoader loader = new ImageClassLoader(reader, IMAGE_ENTRIES.keySet()); + + // Preview version of classes either overwrite existing entries or are added to directories. + assertEquals("Preview: com.foo.HasPreviewVersion", loader.loadAndGetToString("modfoo", "com.foo.HasPreviewVersion")); + assertEquals("Class: com.foo.NormalFoo", loader.loadAndGetToString("modfoo", "com.foo.NormalFoo")); + assertEquals("Class: com.foo.bar.NormalBar", loader.loadAndGetToString("modfoo", "com.foo.bar.NormalBar")); + assertEquals("Preview: com.foo.bar.IsPreviewOnly", loader.loadAndGetToString("modfoo", "com.foo.bar.IsPreviewOnly")); + + // Both NormalBar and IsPreviewOnly exist (direct lookup and as child nodes). + assertResource(reader, "modfoo", "com/foo/bar/NormalBar.class"); + assertResource(reader, "modfoo", "com/foo/bar/IsPreviewOnly.class"); + assertDirContents(reader, "/modules/modfoo/com/foo", "HasPreviewVersion.class", "NormalFoo.class", "bar"); + assertDirContents(reader, "/modules/modfoo/com/foo/bar", "NormalBar.class", "IsPreviewOnly.class"); + } + } + + @Test + public void testPreviewOnlyPackages_disabled() throws IOException { + try (ImageReader reader = ImageReader.open(image, PreviewMode.DISABLED)) { + ImageClassLoader loader = new ImageClassLoader(reader, IMAGE_ENTRIES.keySet()); + + // No 'preview' package or anything inside it. + assertDirContents(reader, "/modules/modbar/com/bar", "One.class", "Two.class"); + assertAbsent(reader, "/modules/modbar/com/bar/preview"); + assertAbsent(reader, "/modules/modbar/com/bar/preview/stuff/Foo.class"); + + // And no package link. + assertAbsent(reader, "/packages/com.bar.preview"); + } + } + + @Test + public void testPreviewOnlyPackages_enabled() throws IOException { + try (ImageReader reader = ImageReader.open(image, PreviewMode.ENABLED)) { + ImageClassLoader loader = new ImageClassLoader(reader, IMAGE_ENTRIES.keySet()); + + // In preview mode 'preview' package exists with preview only content. + assertDirContents(reader, "/modules/modbar/com/bar", "One.class", "Two.class", "preview"); + assertDirContents(reader, "/modules/modbar/com/bar/preview/stuff", "Foo.class", "Bar.class"); + assertResource(reader, "modbar", "com/bar/preview/stuff/Foo.class"); + + // And package links exists. + assertDirContents(reader, "/packages/com.bar.preview", "modbar", "modgus"); + } + } + + @Test + public void testPreviewModeLinks_disabled() throws IOException { + try (ImageReader reader = ImageReader.open(image, PreviewMode.DISABLED)) { + assertDirContents(reader, "/packages/com.bar", "modbar"); + // Missing symbolic link and directory when not in preview mode. + assertAbsent(reader, "/packages/com.bar.preview"); + assertAbsent(reader, "/packages/com.bar.preview.stuff"); + assertAbsent(reader, "/modules/modbar/com/bar/preview"); + assertAbsent(reader, "/modules/modbar/com/bar/preview/stuff"); + } + } + + @Test + public void testPreviewModeLinks_enabled() throws IOException { + try (ImageReader reader = ImageReader.open(image, PreviewMode.ENABLED)) { + // In preview mode there is a new preview-only module visible. + assertDirContents(reader, "/packages/com.bar", "modbar", "modgus"); + // And additional packages are present. + assertDirContents(reader, "/packages/com.bar.preview", "modbar", "modgus"); + assertDirContents(reader, "/packages/com.bar.preview.stuff", "modbar"); + assertDirContents(reader, "/packages/com.bar.preview.other", "modgus"); + // And the preview-only content appears as we expect. + assertDirContents(reader, "/modules/modbar/com/bar", "One.class", "Two.class", "preview"); + assertDirContents(reader, "/modules/modbar/com/bar/preview", "stuff"); + assertDirContents(reader, "/modules/modbar/com/bar/preview/stuff", "Foo.class", "Bar.class"); + // In both modules in which it was added. + assertDirContents(reader, "/modules/modgus/com/bar", "preview"); + assertDirContents(reader, "/modules/modgus/com/bar/preview", "other"); + assertDirContents(reader, "/modules/modgus/com/bar/preview/other", "Gus.class"); + } + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + public void testPreviewEntriesAlwaysHidden(boolean previewMode) throws IOException { + try (ImageReader reader = ImageReader.open(image, previewMode ? PreviewMode.ENABLED : PreviewMode.DISABLED)) { + // The META-INF directory exists, but does not contain the preview directory. + Node dir = assertDir(reader, "/modules/modfoo/META-INF"); + assertEquals(0, dir.getChildNames().filter(n -> n.endsWith("/preview")).count()); + // Neither the preview directory, nor anything in it, can be looked-up directly. + assertAbsent(reader, "/modules/modfoo/META-INF/preview"); + assertAbsent(reader, "/modules/modfoo/META-INF/preview/com/foo"); + // HasPreviewVersion.class is a preview class in the test data, and thus appears in + // two places in the jimage). Ensure the preview version is always hidden. + String alphaPath = "com/foo/HasPreviewVersion.class"; + assertNode(reader, "/modules/modfoo/" + alphaPath); + assertAbsent(reader, "/modules/modfoo/META-INF/preview/" + alphaPath); + } + } + private static ImageReader.Node assertNode(ImageReader reader, String name) throws IOException { ImageReader.Node node = reader.findNode(name); assertNotNull(node, "Could not find node: " + name); @@ -224,9 +353,30 @@ private static ImageReader.Node assertDir(ImageReader reader, String name) throw return dir; } + private static void assertDirContents(ImageReader reader, String name, String... expectedChildNames) throws IOException { + Node dir = assertDir(reader, name); + Set localChildNames = dir.getChildNames() + .peek(s -> assertTrue(s.startsWith(name + "/"))) + .map(s -> s.substring(name.length() + 1)) + .collect(toSet()); + assertEquals( + Set.of(expectedChildNames), + localChildNames, + String.format("Unexpected child names in directory '%s'", name)); + } + + private static void assertResource(ImageReader reader, String modName, String resPath) throws IOException { + assertTrue(reader.containsResource(modName, resPath), "Resource should exist: " + modName + "/" + resPath); + Node resNode = reader.findResourceNode(modName, resPath); + assertTrue(resNode.isResource(), "Node should be a resource: " + resNode.getName()); + String nodeName = "/modules/" + modName + "/" + resPath; + assertEquals(nodeName, resNode.getName()); + assertSame(resNode, reader.findNode(nodeName)); + } + private static ImageReader.Node assertLink(ImageReader reader, String name) throws IOException { ImageReader.Node link = assertNode(reader, name); - assertTrue(link.isLink(), "Node was not a symbolic link: " + name); + assertTrue(link.isLink(), "Node should be a symbolic link: " + link.getName()); return link; } @@ -251,20 +401,23 @@ public static Path buildJImage(Map> entries) { jar.addEntry("module-info.class", InMemoryJavaCompiler.compile("module-info", moduleInfo)); classes.forEach(fqn -> { + boolean isPreviewEntry = fqn.startsWith("@"); + if (isPreviewEntry) { + fqn = fqn.substring(1); + } int lastDot = fqn.lastIndexOf('.'); String pkg = fqn.substring(0, lastDot); String cls = fqn.substring(lastDot + 1); - - String path = fqn.replace('.', '/') + ".class"; String source = String.format( """ package %s; public class %s { public String toString() { - return "Class: %s"; + return "%s: %s"; } } - """, pkg, cls, fqn); + """, pkg, cls, isPreviewEntry ? "Preview" : "Class", fqn); + String path = (isPreviewEntry ? "META-INF/preview/" : "") + fqn.replace('.', '/') + ".class"; jar.addEntry(path, InMemoryJavaCompiler.compile(fqn, source)); }); try { diff --git a/test/jdk/jdk/internal/jimage/ModuleReferenceTest.java b/test/jdk/jdk/internal/jimage/ModuleReferenceTest.java index 8f625b3e605..996a86ee0c8 100644 --- a/test/jdk/jdk/internal/jimage/ModuleReferenceTest.java +++ b/test/jdk/jdk/internal/jimage/ModuleReferenceTest.java @@ -129,7 +129,7 @@ public void writeBuffer_badCapacity() { } @Test - public void writeBuffer_multipleContent() { + public void writeBuffer_multiplePackagesWithResources() { // Only one module reference (at most) can have resources. List refs = Arrays.asList( forPackageIn("alpha", false), diff --git a/test/jdk/tools/jlink/ResourcePoolTest.java b/test/jdk/tools/jlink/ResourcePoolTest.java index 9c6aa2c9fae..e8227e9061d 100644 --- a/test/jdk/tools/jlink/ResourcePoolTest.java +++ b/test/jdk/tools/jlink/ResourcePoolTest.java @@ -21,21 +21,11 @@ * questions. */ -/* - * @test - * @summary Test a pool containing jimage resources and classes. - * @author Jean-Francois Denise - * @modules jdk.jlink/jdk.tools.jlink.internal - * jdk.jlink/jdk.tools.jlink.plugin - * @run build ResourcePoolTest - * @run main ResourcePoolTest - */ - import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; @@ -43,22 +33,27 @@ import jdk.tools.jlink.plugin.ResourcePool; import jdk.tools.jlink.plugin.ResourcePoolModule; import jdk.tools.jlink.plugin.ResourcePoolEntry; +import org.junit.jupiter.api.Test; -public class ResourcePoolTest { - - public static void main(String[] args) throws Exception { - new ResourcePoolTest().test(); - } - - public void test() throws Exception { - checkResourceAdding(); - checkResourceVisitor(); - checkResourcesAfterCompression(); - } +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +/* + * @test + * @summary Test a pool containing jimage resources and classes. + * @author Jean-Francois Denise + * @modules jdk.jlink/jdk.tools.jlink.internal + * jdk.jlink/jdk.tools.jlink.plugin + * @run junit ResourcePoolTest + */ +public class ResourcePoolTest { private static final String SUFFIX = "END"; - private void checkResourceVisitor() throws Exception { + @Test + public void resourceVisitor() throws Exception { ResourcePoolManager input = new ResourcePoolManager(); for (int i = 0; i < 1000; ++i) { String module = "/module" + (i / 10); @@ -69,22 +64,12 @@ private void checkResourceVisitor() throws Exception { ResourcePoolManager output = new ResourcePoolManager(); ResourceVisitor visitor = new ResourceVisitor(); input.resourcePool().transformAndCopy(visitor, output.resourcePoolBuilder()); - if (visitor.getAmountBefore() == 0) { - throw new AssertionError("Resources not found"); - } - if (visitor.getAmountBefore() != input.entryCount()) { - throw new AssertionError("Number of visited resources. Expected: " + - visitor.getAmountBefore() + ", got: " + input.entryCount()); - } - if (visitor.getAmountAfter() != output.entryCount()) { - throw new AssertionError("Number of added resources. Expected: " + - visitor.getAmountAfter() + ", got: " + output.entryCount()); - } + assertNotEquals(0, visitor.getAmountBefore(), "Resources not found"); + assertEquals(visitor.getAmountBefore(), input.entryCount(), "Number of visited resources"); + assertEquals(visitor.getAmountAfter(), output.entryCount(), "Number of added resources"); output.entries().forEach(outResource -> { String path = outResource.path().replaceAll(SUFFIX + "$", ""); - if (!input.findEntry(path).isPresent()) { - throw new AssertionError("Unknown resource: " + path); - } + assertTrue(input.findEntry(path).isPresent(), "Unknown resource: " + path); }); } @@ -117,14 +102,11 @@ public int getAmountBefore() { } } - private void checkResourceAdding() { - List samples = new ArrayList<>(); - samples.add("java.base"); - samples.add("java/lang/Object"); - samples.add("java.base"); - samples.add("java/lang/String"); - samples.add("java.management"); - samples.add("javax/management/ObjectName"); + @Test + public void resourceAdding() { + Map> samples = Map.of( + "java.base", List.of("java/lang/Object", "java/lang/String"), + "java.management", List.of("javax/management/ObjectName")); test(samples, (resources, module, path) -> { try { resources.add(ResourcePoolEntry.create(path, new byte[0])); @@ -144,55 +126,84 @@ private void checkResourceAdding() { }); } - private void test(List samples, ResourceAdder adder) { - if (samples.isEmpty()) { - throw new AssertionError("No sample to test"); - } - ResourcePoolManager resources = new ResourcePoolManager(); - Set modules = new HashSet<>(); - for (int i = 0; i < samples.size(); i++) { - String module = samples.get(i); - modules.add(module); - i++; - String clazz = samples.get(i); - String path = "/" + module + "/" + clazz + ".class"; - adder.add(resources, module, path); - } - for (int i = 0; i < samples.size(); i++) { - String module = samples.get(i); - i++; - String clazz = samples.get(i); - String path = "/" + module + "/" + clazz + ".class"; - Optional res = resources.findEntry(path); - if (!res.isPresent()) { - throw new AssertionError("Resource not found " + path); + @Test + public void packageInference() { + Map> samples = Map.of( + "java.base", List.of("NoPackage", "java/lang/String", "java/util/List")); + ResourcePoolManager manager = test(samples, (resources, module, path) -> { + try { + resources.add(ResourcePoolEntry.create(path, new byte[0])); + } catch (Exception ex) { + throw new RuntimeException(ex); } - checkModule(resources.resourcePool(), res.get()); - if (resources.findEntry(clazz).isPresent()) { - throw new AssertionError("Resource found " + clazz); + }); + + Optional modBase = manager.moduleView().findModule("java.base"); + assertTrue(modBase.isPresent()); + // Empty packages are not included (and should normally not exist). + assertEquals(Set.of("java.lang", "java.util"), modBase.get().packages()); + } + + @Test + public void packageInference_previewOnly() { + Map> samples = Map.of( + "java.base", List.of( + "java/lang/Object", + "java/lang/String", + "java/util/List", + "META-INF/preview/java/lang/String", + "META-INF/preview/java/extra/PreviewOnly")); + ResourcePoolManager manager = test(samples, (resources, module, path) -> { + try { + resources.add(ResourcePoolEntry.create(path, new byte[0])); + } catch (Exception ex) { + throw new RuntimeException(ex); } - } - if (resources.entryCount() != samples.size() / 2) { - throw new AssertionError("Invalid number of resources"); - } + }); + + Optional modBase = manager.moduleView().findModule("java.base"); + assertTrue(modBase.isPresent()); + // Preview only package is included, and no packages start with 'META-INF'. + assertEquals(Set.of("java.lang", "java.util", "java.extra"), modBase.get().packages()); + // But the preview resources exist in the META-INF/preview namespace. + assertTrue(modBase.get().findEntry("/java.base/META-INF/preview/java/extra/PreviewOnly.class").isPresent()); + } + + private ResourcePoolManager test(Map> samples, ResourceAdder adder) { + assertFalse(samples.isEmpty(), "No sample to test"); + ResourcePoolManager resources = new ResourcePoolManager(); + samples.forEach((module, clazzes) -> { + clazzes.forEach(clazz -> { + String path = "/" + module + "/" + clazz + ".class"; + adder.add(resources, module, path); + }); + }); + samples.forEach((module, clazzes) -> { + clazzes.forEach(clazz -> { + String path = "/" + module + "/" + clazz + ".class"; + Optional res = resources.findEntry(path); + assertTrue(res.isPresent(), "Resource not found " + path); + checkModule(resources.resourcePool(), res.get()); + assertTrue(resources.findEntry(clazz).isEmpty(), "Resource found " + clazz); + }); + }); + long resourcesCount = samples.values().stream().mapToInt(List::size).sum(); + assertEquals(resourcesCount, resources.entryCount(), "Invalid number of resources"); + assertEquals(samples.size(), resources.moduleCount(), "Invalid number of modules"); + return resources; } private void checkModule(ResourcePool resources, ResourcePoolEntry res) { Optional optMod = resources.moduleView().findModule(res.moduleName()); - if (!optMod.isPresent()) { - throw new AssertionError("No module " + res.moduleName()); - } + assertTrue(optMod.isPresent(), "No module " + res.moduleName()); ResourcePoolModule m = optMod.get(); - if (!m.name().equals(res.moduleName())) { - throw new AssertionError("Not right module name " + res.moduleName()); - } - if (!m.findEntry(res.path()).isPresent()) { - throw new AssertionError("resource " + res.path() - + " not in module " + m.name()); - } + assertEquals(res.moduleName(), m.name(), "Not right module name " + res.moduleName()); + assertTrue(m.findEntry(res.path()).isPresent(), + "resource " + res.path() + " not in module " + m.name()); } - private void checkResourcesAfterCompression() throws Exception { + @Test + public void resourcesAfterCompression() throws Exception { ResourcePoolManager resources1 = new ResourcePoolManager(); ResourcePoolEntry res1 = ResourcePoolEntry.create("/module1/toto1", new byte[0]); ResourcePoolEntry res2 = ResourcePoolEntry.create("/module2/toto1", new byte[0]); @@ -215,36 +226,17 @@ private void checkResources(ResourcePoolManager resources, ResourcePoolEntry... modules.add(m.name()); }); for (ResourcePoolEntry res : expected) { - if (!resources.contains(res)) { - throw new AssertionError("Resource not found: " + res); - } - - if (!resources.findEntry(res.path()).isPresent()) { - throw new AssertionError("Resource not found: " + res); - } - - if (!modules.contains(res.moduleName())) { - throw new AssertionError("Module not found: " + res.moduleName()); - } - - if (!resources.contains(res)) { - throw new AssertionError("Resources not found: " + res); - } - - try { - resources.add(res); - throw new AssertionError(res + " already present, but an exception is not thrown"); - } catch (Exception ex) { - // Expected - } + assertTrue(resources.contains(res), "Resource not found: " + res); + assertTrue(resources.findEntry(res.path()).isPresent(), "Resource not found: " + res); + assertTrue(modules.contains(res.moduleName()), "Module not found: " + res.moduleName()); + assertTrue(modules.contains(res.moduleName()), "Module not found: " + res.moduleName()); + assertThrows(RuntimeException.class, () -> resources.add(res), + res + " already present, but an exception is not thrown"); } - try { - resources.add(ResourcePoolEntry.create("/module2/toto1", new byte[0])); - throw new AssertionError("ResourcePool is read-only, but an exception is not thrown"); - } catch (Exception ex) { - // Expected - } + ResourcePoolEntry toAdd = ResourcePoolEntry.create("/module2/toto1", new byte[0]); + assertThrows(RuntimeException.class, () -> resources.add(toAdd), + "ResourcePool is read-only, but an exception is not thrown"); } interface ResourceAdder { diff --git a/test/jdk/tools/jlink/runtimeImage/PackagedModulesVsRuntimeImageLinkTest.java b/test/jdk/tools/jlink/runtimeImage/PackagedModulesVsRuntimeImageLinkTest.java index ef7030d2e62..ec0b38d3d06 100644 --- a/test/jdk/tools/jlink/runtimeImage/PackagedModulesVsRuntimeImageLinkTest.java +++ b/test/jdk/tools/jlink/runtimeImage/PackagedModulesVsRuntimeImageLinkTest.java @@ -31,6 +31,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import tests.Helper; import tests.JImageGenerator; @@ -47,9 +48,10 @@ * jdk.jlink/jdk.tools.jlink.internal * jdk.jlink/jdk.tools.jlink.plugin * jdk.jlink/jdk.tools.jimage + * jdk.jlink * @build tests.* jdk.test.lib.process.OutputAnalyzer * jdk.test.lib.process.ProcessTools - * @run main/othervm/timeout=1200 -Xmx1g PackagedModulesVsRuntimeImageLinkTest + * @run main/othervm/timeout=1200 -ea -esa -DDISABLE_PREVIEW_PATCHING=false -Xmx1g PackagedModulesVsRuntimeImageLinkTest */ public class PackagedModulesVsRuntimeImageLinkTest extends AbstractLinkableRuntimeTest { @@ -70,7 +72,6 @@ void runTest(Helper helper, boolean isLinkableRuntime) throws Exception { builder.setLinkableRuntime(); } Path javaSEruntimeLink = createJavaImageRuntimeLink(builder.build()); - // create a java.se using packaged modules (jmod-full) Path javaSEJmodFull = JImageGenerator.getJLinkTask() .output(helper.createNewImageDir("java-se-jmodfull")) @@ -120,9 +121,8 @@ private static void compareRecursively(Path javaSEJmodLess, Path jimageJmodFull = javaSEJmodFull.resolve(Path.of("lib")).resolve(Path.of("modules")); List jimageContentJmodLess = JImageHelper.listContents(jimageJmodLess); List jimageContentJmodFull = JImageHelper.listContents(jimageJmodFull); - if (jimageContentJmodLess.size() != jimageContentJmodFull.size()) { - throw new AssertionError(String.format("Size of jimage content differs for jmod-less (%d) v. jmod-full (%d)", jimageContentJmodLess.size(), jimageContentJmodFull.size())); - } + assertSameContent("jmod-less", jimageContentJmodLess, "jmod-full", jimageContentJmodFull); + // Both lists are same size, with same names, so enumerate either. for (int i = 0; i < jimageContentJmodFull.size(); i++) { if (!jimageContentJmodFull.get(i).equals(jimageContentJmodLess.get(i))) { throw new AssertionError(String.format("Jimage content differs at index %d: jmod-full was: '%s' jmod-less was: '%s'", @@ -145,8 +145,55 @@ private static void compareRecursively(Path javaSEJmodLess, } } + // Helper to assert the content of two jimage files are the same and provide + // useful debug information otherwise. + private static void assertSameContent( + String lhsLabel, List lhsNames, String rhsLabel, List rhsNames) { + int lhsSize = lhsNames.size(); + int rhsSize = rhsNames.size(); + // Both input lists are sorted, enumerate the differences for debugging. + List lhsOnly = new ArrayList<>(); + List rhsOnly = new ArrayList<>(); + int i = 0; + int j = 0; + while (i < lhsSize && j < rhsSize) { + String lhsName = lhsNames.get(i); + String rhsName = rhsNames.get(j); + int signum = lhsName.compareTo(rhsName); + if (signum == 0) { + i += 1; + j += 1; + } else if (signum < 0) { + lhsOnly.add(lhsName); + i += 1; + } else { + rhsOnly.add(rhsName); + j += 1; + } + } + lhsOnly.addAll(lhsNames.subList(i, lhsSize)); + rhsOnly.addAll(rhsNames.subList(j, rhsSize)); + if (!lhsOnly.isEmpty() || !rhsOnly.isEmpty()) { + String message = String.format( + "jimage content differs for %s (%d) v. %s (%d)", lhsLabel, lhsSize, rhsLabel, rhsSize); + if (!lhsOnly.isEmpty()) { + message += "\nOnly in " + lhsLabel + ":\n\t" + String.join("\n\t", lhsOnly); + } + if (!rhsOnly.isEmpty()) { + message += "\nOnly in " + rhsLabel + ":\n\t" + String.join("\n\t", rhsOnly); + } + throw new AssertionError(message); + } + } + private static boolean isTreeInfoResource(String path) { - return path.startsWith("/packages") || path.startsWith("/modules"); + return pathStartsWith(path, "/packages") || pathStartsWith(path, "/modules"); + } + + // Handle both "" and "/...". + private static boolean pathStartsWith(String path, String prefix) { + int plen = prefix.length(); + return path.startsWith(prefix) && (path.length() == plen || path.charAt(plen) == '/'); } private static void handleFileMismatch(Path a, Path b) { diff --git a/test/jdk/tools/jlink/whitebox/ImageResourcesTreeTestDriver.java b/test/jdk/tools/jlink/whitebox/ImageResourcesTreeTestDriver.java new file mode 100644 index 00000000000..225cc637c39 --- /dev/null +++ b/test/jdk/tools/jlink/whitebox/ImageResourcesTreeTestDriver.java @@ -0,0 +1,31 @@ +/* + * 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. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Whitebox tests for ImageResourcesTree. + * @modules jdk.jlink/jdk.tools.jlink.internal + * @build jdk.jlink/jdk.tools.jlink.internal.ImageResourcesTreeTest + * @run junit/othervm -ea -esa jdk.jlink/jdk.tools.jlink.internal.ImageResourcesTreeTest + */ +public class ImageResourcesTreeTestDriver {} diff --git a/test/jdk/tools/jlink/whitebox/TEST.properties b/test/jdk/tools/jlink/whitebox/TEST.properties new file mode 100644 index 00000000000..35196da49bf --- /dev/null +++ b/test/jdk/tools/jlink/whitebox/TEST.properties @@ -0,0 +1,3 @@ +modules = \ + jdk.jlink/jdk.tools.jlink.internal +bootclasspath.dirs=. diff --git a/test/jdk/tools/jlink/whitebox/jdk.jlink/jdk/tools/jlink/internal/ImageResourcesTreeTest.java b/test/jdk/tools/jlink/whitebox/jdk.jlink/jdk/tools/jlink/internal/ImageResourcesTreeTest.java new file mode 100644 index 00000000000..37999136985 --- /dev/null +++ b/test/jdk/tools/jlink/whitebox/jdk.jlink/jdk/tools/jlink/internal/ImageResourcesTreeTest.java @@ -0,0 +1,261 @@ +/* + * 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. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.tools.jlink.internal; + +import jdk.internal.jimage.ModuleReference; +import jdk.tools.jlink.internal.ImageResourcesTree.Node; +import jdk.tools.jlink.internal.ImageResourcesTree.PackageNode; +import jdk.tools.jlink.internal.ImageResourcesTree.ResourceNode; +import jdk.tools.jlink.internal.ImageResourcesTree.Tree; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ImageResourcesTreeTest { + + private static final String MODULES_PREFIX = "/modules/"; + private static final String PACKAGES_PREFIX = "/packages/"; + + @Test + public void directoryNodes() { + List paths = List.of( + "/java.base/java/util/SomeClass.class", + "/java.base/java/util/SomeOtherClass.class", + "/java.base/java/util/resource.txt", + "/java.logging/java/util/logging/LoggingClass.class", + "/java.logging/java/util/logging/OtherLoggingClass.class"); + + Tree tree = new Tree(paths); + Map nodes = tree.getMap(); + + // All paths from the root (but not the root itself). + assertTrue(nodes.containsKey("/modules")); + assertTrue(nodes.containsKey("/modules/java.base")); + assertTrue(nodes.containsKey("/modules/java.base/java")); + assertTrue(nodes.containsKey("/modules/java.base/java/util")); + assertFalse(nodes.containsKey("/")); + + // Check for mismatched modules. + assertTrue(nodes.containsKey("/modules/java.logging/java/util/logging")); + assertFalse(nodes.containsKey("/modules/java.base/java/util/logging")); + + Set dirPaths = nodes.keySet().stream() + .filter(p -> p.startsWith(MODULES_PREFIX)) + .collect(Collectors.toSet()); + for (String path : dirPaths) { + Node dir = nodes.get(path); + assertFalse(dir instanceof ResourceNode, "Unexpected resource: " + dir); + assertEquals(path, dir.getPath()); + assertTrue(path.endsWith("/" + dir.getName()), "Unexpected directory name: " + dir); + } + } + + @Test + public void resourceNodes() { + List paths = List.of( + "/java.base/java/util/SomeClass.class", + "/java.base/java/util/SomeOtherClass.class", + "/java.base/java/util/resource.txt", + "/java.logging/java/util/logging/LoggingClass.class", + "/java.logging/java/util/logging/OtherLoggingClass.class"); + + Tree tree = new Tree(paths); + // This map *does not* contain the resources, only the "directory" nodes. + Map nodes = tree.getMap(); + + assertContainsResources( + nodes.get("/modules/java.base/java/util"), + "SomeClass.class", "SomeOtherClass.class", "resource.txt"); + + assertContainsResources( + nodes.get("/modules/java.logging/java/util/logging"), + "LoggingClass.class", "OtherLoggingClass.class"); + } + + @Test + public void expectedPackages() { + // Paths are only to resources. Packages are inferred. + List paths = List.of( + "/java.base/java/util/SomeClass.class", + "/java.logging/java/util/logging/SomeClass.class"); + + Tree tree = new Tree(paths); + Map nodes = tree.getMap(); + Node packages = nodes.get("/packages"); + List pkgNames = nodes.keySet().stream() + .filter(p -> p.startsWith(PACKAGES_PREFIX)) + .map(p -> p.substring(PACKAGES_PREFIX.length())) + .sorted() + .toList(); + + assertEquals(List.of("java", "java.util", "java.util.logging"), pkgNames); + for (String pkgName : pkgNames) { + PackageNode pkgNode = assertInstanceOf(PackageNode.class, packages.getChildren(pkgName)); + assertSame(nodes.get(PACKAGES_PREFIX + pkgNode.getName()), pkgNode); + } + } + + @Test + public void expectedPackageEntries() { + List paths = List.of( + "/java.base/java/util/SomeClass.class", + "/java.logging/java/util/logging/SomeClass.class"); + + Tree tree = new Tree(paths); + Map nodes = tree.getMap(); + PackageNode pkgUtil = getPackageNode(nodes, "java.util"); + List modRefs = pkgUtil.getModuleReferences(); + assertEquals(2, modRefs.size()); + + List modNames = modRefs.stream().map(ModuleReference::name).toList(); + assertEquals(List.of("java.base", "java.logging"), modNames); + + // Ordered by name. + assertNonEmptyRef(modRefs.get(0), "java.base"); + assertEmptyRef(modRefs.get(1), "java.logging"); + } + + @Test + public void expectedPackageEntries_withPreviewResources() { + List paths = List.of( + "/java.base/java/util/SomeClass.class", + "/java.base/java/util/OtherClass.class", + "/java.base/META-INF/preview/java/util/OtherClass.class", + "/java.logging/java/util/logging/SomeClass.class"); + + Tree tree = new Tree(paths); + Map nodes = tree.getMap(); + PackageNode pkgUtil = getPackageNode(nodes, "java.util"); + List modRefs = pkgUtil.getModuleReferences(); + + ModuleReference baseRef = modRefs.get(0); + assertNonEmptyRef(baseRef, "java.base"); + assertTrue(baseRef.hasPreviewVersion()); + } + + @Test + public void expectedPackageEntries_withPreviewOnlyPackages() { + List paths = List.of( + "/java.base/java/util/SomeClass.class", + "/java.base/META-INF/preview/java/util/preview/only/PreviewClass.class"); + + Tree tree = new Tree(paths); + Map nodes = tree.getMap(); + + // Preview only package (with content). + PackageNode nonEmptyPkg = getPackageNode(nodes, "java.util.preview.only"); + ModuleReference nonEmptyRef = nonEmptyPkg.getModuleReferences().getFirst(); + assertNonEmptyPreviewOnlyRef(nonEmptyRef, "java.base"); + + // Preview only packages can be empty. + PackageNode emptyPkg = getPackageNode(nodes, "java.util.preview"); + ModuleReference emptyRef = emptyPkg.getModuleReferences().getFirst(); + assertEmptyPreviewOnlyRef(emptyRef, "java.base"); + } + + @Test + public void expectedPackageOrder_sharedPackage() { + // Resource in many modules define the same package (java.shared), but + // this only has content in one module (java.content). + // Order of test data is shuffled to show reordering in entry list. + // "java.moduleN" would sort before after "java.previewN" if it were + // only sorted by name, but preview entries come first. + // Expect: preview{1..3) -> content -> module{1..3} + List paths = List.of( + // Module with content in "java.shared". + "/java.content/java/shared/MainPackageClass.class", + // Other resources (in other modules) which implicitly define "java.shared". + "/java.module3/java/shared/three/SomeClass.class", + "/java.module2/java/shared/two/SomeClass.class", + "/java.module1/java/shared/one/SomeClass.class", + // Preview resources in other modules which implicitly define "java.shared". + "/java.preview3/META-INF/preview/java/shared/baz/SomeClass.class", + "/java.preview2/META-INF/preview/java/shared/bar/SomeClass.class", + "/java.preview1/META-INF/preview/java/shared/foo/SomeClass.class"); + + Tree tree = new Tree(paths); + Map nodes = tree.getMap(); + + PackageNode sharedPkg = getPackageNode(nodes, "java.shared"); + List refs = sharedPkg.getModuleReferences(); + + // Preview packages first, by name. + int n = 1; + for (ModuleReference ref : refs.subList(0, 3)) { + assertEmptyPreviewOnlyRef(ref, "java.preview" + (n++)); + } + // The content package (simply due to its name). + assertNonEmptyRef(refs.get(3), "java.content"); + // And the non-preview empty packages after. + n = 1; + for (ModuleReference ref : refs.subList(4, 7)) { + assertEmptyRef(ref, "java.module" + (n++)); + } + } + + static PackageNode getPackageNode(Map nodes, String pkgName) { + return assertInstanceOf(PackageNode.class, nodes.get(PACKAGES_PREFIX + pkgName)); + } + + static void assertContainsResources(Node dirNode, String... resourceNames) { + for (String name : resourceNames) { + Node node = assertInstanceOf(ResourceNode.class, dirNode.getChildren(name)); + assertEquals(name, node.getName()); + assertEquals(dirNode.getPath() + "/" + name, node.getPath()); + } + } + + static void assertNonEmptyRef(ModuleReference ref, String modName) { + assertEquals(modName, ref.name(), "Unexpected module name: " + ref); + assertTrue(ref.hasResources(), "Expected non-empty reference: " + ref); + assertFalse(ref.isPreviewOnly(), "Expected not preview-only: " + ref); + } + + static void assertEmptyRef(ModuleReference ref, String modName) { + assertEquals(modName, ref.name(), "Unexpected module name: " + ref); + assertFalse(ref.hasResources(), "Expected empty reference: " + ref); + assertFalse(ref.isPreviewOnly(), "Expected not preview-only: " + ref); + } + + static void assertNonEmptyPreviewOnlyRef(ModuleReference ref, String modName) { + assertEquals(modName, ref.name(), "Unexpected module name: " + ref); + assertTrue(ref.hasResources(), "Expected empty reference: " + ref); + assertTrue(ref.isPreviewOnly(), "Expected preview-only: " + ref); + } + + static void assertEmptyPreviewOnlyRef(ModuleReference ref, String modName) { + assertEquals(modName, ref.name(), "Unexpected module name: " + ref); + assertFalse(ref.hasResources(), "Expected empty reference: " + ref); + assertTrue(ref.isPreviewOnly(), "Expected preview-only: " + ref); + } +} diff --git a/test/micro/org/openjdk/bench/jdk/internal/jrtfs/ImageReaderBenchmark.java b/test/micro/org/openjdk/bench/jdk/internal/jrtfs/ImageReaderBenchmark.java index 89952805417..f2e30bea2a1 100644 --- a/test/micro/org/openjdk/bench/jdk/internal/jrtfs/ImageReaderBenchmark.java +++ b/test/micro/org/openjdk/bench/jdk/internal/jrtfs/ImageReaderBenchmark.java @@ -32,6 +32,7 @@ import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; @@ -44,10 +45,16 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toList; /// Benchmarks for ImageReader. See individual benchmarks for details on what they /// measure, and their potential applicability for real world conclusions. @@ -67,7 +74,7 @@ public class ImageReaderBenchmark { } /// NOT annotated with `@State` since it needs to potentially be used as a - /// per-benchmark or a per-iteration state object. The subclasses provide + /// per-trial or a per-iteration state object. The subclasses provide /// any lifetime annotations that are needed. static class BaseState { protected Path copiedImageFile; @@ -84,14 +91,18 @@ public void tearDown() throws IOException { } } + /// A {@link Level#Trial per-trial} state which provides an image reader, + /// suitable for {@link Mode#AverageTime average time} benchmarks. @State(Scope.Benchmark) - public static class WarmStartWithImageReader extends BaseState { + public static class WarmStart extends BaseState { + @Param({"DISABLED", "ENABLED"}) + PreviewMode previewMode; ImageReader reader; @Setup(Level.Trial) public void setUp() throws IOException { super.setUp(); - reader = ImageReader.open(copiedImageFile, PreviewMode.DISABLED); + reader = ImageReader.open(copiedImageFile, previewMode); } @TearDown(Level.Trial) @@ -100,8 +111,23 @@ public void tearDown() throws IOException { } } + @State(Scope.Benchmark) + public static class WarmStartWithCachedNodes extends WarmStart { + @Setup(Level.Trial) + public void setUp() throws IOException { + super.setUp(); + countAllNodes(reader, reader.findNode("/")); + } + } + + /// A {@link Level#Iteration per-iteration} state suitable for + /// {@link Mode#SingleShotTime single shot} benchmarks. Unlike + /// {@link WarmStart}, this state does not provide a reader instance. @State(Scope.Benchmark) public static class ColdStart extends BaseState { + @Param({"DISABLED", "ENABLED"}) + PreviewMode previewMode; + @Setup(Level.Iteration) public void setUp() throws IOException { super.setUp(); @@ -114,13 +140,13 @@ public void tearDown() throws IOException { } @State(Scope.Benchmark) - public static class ColdStartWithImageReader extends BaseState { + public static class ColdStartWithImageReader extends ColdStart { ImageReader reader; @Setup(Level.Iteration) public void setup() throws IOException { super.setUp(); - reader = ImageReader.open(copiedImageFile, PreviewMode.DISABLED); + reader = ImageReader.open(copiedImageFile, previewMode); } @TearDown(Level.Iteration) @@ -135,10 +161,49 @@ public void tearDown() throws IOException { /// so this benchmark should be fast and very stable. @Benchmark @BenchmarkMode(Mode.AverageTime) - public void warmCache_CountAllNodes(WarmStartWithImageReader state) throws IOException { + public void warmStart_CountAllNodes(WarmStartWithCachedNodes state) throws IOException { state.count = countAllNodes(state.reader, state.reader.findNode("/")); } + /// Benchmarks {@link ImageReader#containsResource(String, String)} when no + /// nodes have been cached in the {@link ImageReader}. In non-preview mode, + /// this should be identical to the case where nodes are cached (because the + /// cache isn't used) but in preview mode, the cache will be tested for + /// preview resources, and thus differ depending on whether nodes are present. + /// + /// This doesn't need to be a cold start because it never modifies the nodes + /// cache. + @Benchmark + @BenchmarkMode(Mode.AverageTime) + public void warmStart_ContainsResource_emptyNodeCache(WarmStart state) throws IOException { + state.count = countContainsResource(state.reader, ClassList.pathMap()); + } + + /// As above, but the nodes cache has been filled, giving preview mode a + /// different code path. + @Benchmark + @BenchmarkMode(Mode.AverageTime) + public void warmStart_ContainsResource_fullNodeCache(WarmStartWithCachedNodes state) throws IOException { + state.count = countContainsResource(state.reader, ClassList.pathMap()); + } + + /// As {@link #warmStart_ContainsResource_emptyNodeCache}, but tests + /// {@link ImageReader#findResourceNode(String, String)}. + @Benchmark + @BenchmarkMode(Mode.AverageTime) + public void warmStart_FindResourceNode_emptyNodeCache(WarmStart state) throws IOException { + state.count = countFindResourceNode(state.reader, ClassList.pathMap()); + } + + /// As {@link #warmStart_ContainsResource_fullNodeCache}, but tests + /// {@link ImageReader#findResourceNode(String, String)}. + @Benchmark + @BenchmarkMode(Mode.AverageTime) + public void warmStart_FindResourceNode_fullNodeCache(WarmStartWithCachedNodes state) throws IOException { + state.count = countFindResourceNode(state.reader, ClassList.pathMap()); + } + + /// Benchmarks counting of all nodes in the system image from a "cold start". This /// visits all nodes in depth-first order and counts them. /// @@ -147,12 +212,12 @@ public void warmCache_CountAllNodes(WarmStartWithImageReader state) throws IOExc @Benchmark @BenchmarkMode(Mode.SingleShotTime) public void coldStart_InitAndCount(ColdStart state) throws IOException { - try (var reader = ImageReader.open(state.copiedImageFile, PreviewMode.DISABLED)) { + try (var reader = ImageReader.open(state.copiedImageFile, state.previewMode)) { state.count = countAllNodes(reader, reader.findNode("/")); } } - /// As above, but includes the time to initialize the `ImageReader`. + /// As above, but excludes the time to initialize the `ImageReader`. @Benchmark @BenchmarkMode(Mode.SingleShotTime) public void coldStart_CountOnly(ColdStartWithImageReader state) throws IOException { @@ -171,8 +236,8 @@ public void coldStart_CountOnly(ColdStartWithImageReader state) throws IOExcepti @BenchmarkMode(Mode.SingleShotTime) public void coldStart_LoadJavacInitClasses(Blackhole bh, ColdStart state) throws IOException { int errors = 0; - try (var reader = ImageReader.open(state.copiedImageFile, PreviewMode.DISABLED)) { - for (String path : INIT_CLASSES) { + try (var reader = ImageReader.open(state.copiedImageFile, state.previewMode)) { + for (String path : ClassList.names()) { // Path determination isn't perfect so there can be a few "misses" in here. // Report the count of bad paths as the "result", which should be < 20 or so. Node node = reader.findNode(path); @@ -183,9 +248,9 @@ public void coldStart_LoadJavacInitClasses(Blackhole bh, ColdStart state) throws } } } - state.count = INIT_CLASSES.size(); + state.count = ClassList.count(); // Allow up to 2% missing classes before complaining. - if ((100 * errors) / INIT_CLASSES.size() >= 2) { + if ((100 * errors) / ClassList.count() >= 2) { reportMissingClassesAndFail(state, errors); } } @@ -204,12 +269,39 @@ static long countAllNodes(ImageReader reader, Node node) { return count; } + static long countContainsResource(ImageReader reader, Map> modToPaths) + throws IOException { + long count = 0; + for (Map.Entry> e : modToPaths.entrySet()) { + String mod = e.getKey(); + for (String path : e.getValue()) { + if (reader.containsResource(mod, path)) { + count++; + } + } + } + return count; + } + + static long countFindResourceNode(ImageReader reader, Map> modToPaths) throws IOException { + long count = 0; + for (Map.Entry> e : modToPaths.entrySet()) { + String mod = e.getKey(); + for (String path : e.getValue()) { + if (reader.findResourceNode(mod, path) != null) { + count++; + } + } + } + return count; + } + // Run if the INIT_CLASSES list below is sufficiently out-of-date. // DO NOT run this before the benchmark, as it will cache all the nodes! private static void reportMissingClassesAndFail(ColdStart state, int errors) throws IOException { List missing = new ArrayList<>(errors); - try (var reader = ImageReader.open(state.copiedImageFile, PreviewMode.DISABLED)) { - for (String path : INIT_CLASSES) { + try (var reader = ImageReader.open(state.copiedImageFile, state.previewMode)) { + for (String path : ClassList.names()) { if (reader.findNode(path) == null) { missing.add(path); } @@ -220,858 +312,891 @@ private static void reportMissingClassesAndFail(ColdStart state, int errors) thr "Too many missing classes (%d of %d) in the hardcoded benchmark list.\n" + "Please regenerate it according to instructions in the source code.\n" + "Missing classes:\n\t%s", - errors, INIT_CLASSES.size(), String.join("\n\t", missing))); + errors, ClassList.count(), String.join("\n\t", missing))); } - // Note: This list is inherently a little fragile and may end up being more - // trouble than it's worth to maintain. If it turns out that it needs to be - // regenerated often when this benchmark is run, then a new approach should - // be considered, such as: - // * Limit the list of classes to non-internal ones. - // * Calculate the list dynamically based on the running JVM. - // - // Created by running "java -verbose:class", throwing away anonymous inner - // classes and anything without a reliable name, and grouping by the stated - // source. It's not perfect, but it's representative. - // - // /bin/java -verbose:class HelloWorld 2>&1 \ - // | fgrep '[class,load]' | cut -d' ' -f2 \ - // | tr '.' '/' \ - // | egrep -v '\$[0-9$]' \ - // | fgrep -v 'HelloWorld' \ - // | fgrep -v '/META-INF/preview/' \ - // | while read f ; do echo "${f}.class" ; done \ - // > initclasses.txt - // - // Output: - // java/lang/Object.class - // java/io/Serializable.class - // ... - // - // jimage list /images/jdk/lib/modules \ - // | awk '/^Module: */ { MOD=$2 }; /^ */ { print "/modules/"MOD"/"$1 }' \ - // > fullpaths.txt - // - // Output: - // ... - // /modules/java.base/java/lang/Object.class - // /modules/java.base/java/lang/OutOfMemoryError.class - // ... - // - // while read c ; do grep "/$c" fullpaths.txt ; done < initclasses.txt \ - // | while read c ; do printf ' "%s",\n' "$c" ; done \ - // > initpaths.txt - // - // Output: - private static final Set INIT_CLASSES = Set.of( - "/modules/java.base/java/lang/Object.class", - "/modules/java.base/java/io/Serializable.class", - "/modules/java.base/java/lang/Comparable.class", - "/modules/java.base/java/lang/CharSequence.class", - "/modules/java.base/java/lang/constant/Constable.class", - "/modules/java.base/java/lang/constant/ConstantDesc.class", - "/modules/java.base/java/lang/String.class", - "/modules/java.base/java/lang/reflect/AnnotatedElement.class", - "/modules/java.base/java/lang/reflect/GenericDeclaration.class", - "/modules/java.base/java/lang/reflect/Type.class", - "/modules/java.base/java/lang/invoke/TypeDescriptor.class", - "/modules/java.base/java/lang/invoke/TypeDescriptor$OfField.class", - "/modules/java.base/java/lang/Class.class", - "/modules/java.base/java/lang/Cloneable.class", - "/modules/java.base/java/lang/ClassLoader.class", - "/modules/java.base/java/lang/System.class", - "/modules/java.base/java/lang/Throwable.class", - "/modules/java.base/java/lang/Error.class", - "/modules/java.base/java/lang/Exception.class", - "/modules/java.base/java/lang/RuntimeException.class", - "/modules/java.base/java/security/ProtectionDomain.class", - "/modules/java.base/java/security/SecureClassLoader.class", - "/modules/java.base/java/lang/ReflectiveOperationException.class", - "/modules/java.base/java/lang/ClassNotFoundException.class", - "/modules/java.base/java/lang/Record.class", - "/modules/java.base/java/lang/LinkageError.class", - "/modules/java.base/java/lang/NoClassDefFoundError.class", - "/modules/java.base/java/lang/ClassCastException.class", - "/modules/java.base/java/lang/ArrayStoreException.class", - "/modules/java.base/java/lang/VirtualMachineError.class", - "/modules/java.base/java/lang/InternalError.class", - "/modules/java.base/java/lang/OutOfMemoryError.class", - "/modules/java.base/java/lang/StackOverflowError.class", - "/modules/java.base/java/lang/IllegalMonitorStateException.class", - "/modules/java.base/java/lang/ref/Reference.class", - "/modules/java.base/java/lang/IllegalCallerException.class", - "/modules/java.base/java/lang/ref/SoftReference.class", - "/modules/java.base/java/lang/ref/WeakReference.class", - "/modules/java.base/java/lang/ref/FinalReference.class", - "/modules/java.base/java/lang/ref/PhantomReference.class", - "/modules/java.base/java/lang/ref/Finalizer.class", - "/modules/java.base/java/lang/Runnable.class", - "/modules/java.base/java/lang/Thread.class", - "/modules/java.base/java/lang/Thread$FieldHolder.class", - "/modules/java.base/java/lang/Thread$Constants.class", - "/modules/java.base/java/lang/Thread$UncaughtExceptionHandler.class", - "/modules/java.base/java/lang/ThreadGroup.class", - "/modules/java.base/java/lang/BaseVirtualThread.class", - "/modules/java.base/java/lang/VirtualThread.class", - "/modules/java.base/java/lang/ThreadBuilders$BoundVirtualThread.class", - "/modules/java.base/java/util/Map.class", - "/modules/java.base/java/util/Dictionary.class", - "/modules/java.base/java/util/Hashtable.class", - "/modules/java.base/java/util/Properties.class", - "/modules/java.base/java/lang/Module.class", - "/modules/java.base/java/lang/reflect/AccessibleObject.class", - "/modules/java.base/java/lang/reflect/Member.class", - "/modules/java.base/java/lang/reflect/Field.class", - "/modules/java.base/java/lang/reflect/Parameter.class", - "/modules/java.base/java/lang/reflect/Executable.class", - "/modules/java.base/java/lang/reflect/Method.class", - "/modules/java.base/java/lang/reflect/Constructor.class", - "/modules/java.base/jdk/internal/vm/ContinuationScope.class", - "/modules/java.base/jdk/internal/vm/Continuation.class", - "/modules/java.base/jdk/internal/vm/StackChunk.class", - "/modules/java.base/jdk/internal/reflect/MethodAccessor.class", - "/modules/java.base/jdk/internal/reflect/MethodAccessorImpl.class", - "/modules/java.base/jdk/internal/reflect/ConstantPool.class", - "/modules/java.base/java/lang/annotation/Annotation.class", - "/modules/java.base/jdk/internal/reflect/CallerSensitive.class", - "/modules/java.base/jdk/internal/reflect/ConstructorAccessor.class", - "/modules/java.base/jdk/internal/reflect/ConstructorAccessorImpl.class", - "/modules/java.base/jdk/internal/reflect/DirectConstructorHandleAccessor$NativeAccessor.class", - "/modules/java.base/java/lang/invoke/MethodHandle.class", - "/modules/java.base/java/lang/invoke/DirectMethodHandle.class", - "/modules/java.base/java/lang/invoke/VarHandle.class", - "/modules/java.base/java/lang/invoke/MemberName.class", - "/modules/java.base/java/lang/invoke/ResolvedMethodName.class", - "/modules/java.base/java/lang/invoke/MethodHandleNatives.class", - "/modules/java.base/java/lang/invoke/LambdaForm.class", - "/modules/java.base/java/lang/invoke/TypeDescriptor$OfMethod.class", - "/modules/java.base/java/lang/invoke/MethodType.class", - "/modules/java.base/java/lang/BootstrapMethodError.class", - "/modules/java.base/java/lang/invoke/CallSite.class", - "/modules/java.base/jdk/internal/foreign/abi/NativeEntryPoint.class", - "/modules/java.base/jdk/internal/foreign/abi/ABIDescriptor.class", - "/modules/java.base/jdk/internal/foreign/abi/VMStorage.class", - "/modules/java.base/jdk/internal/foreign/abi/UpcallLinker$CallRegs.class", - "/modules/java.base/java/lang/invoke/ConstantCallSite.class", - "/modules/java.base/java/lang/invoke/MutableCallSite.class", - "/modules/java.base/java/lang/invoke/VolatileCallSite.class", - "/modules/java.base/java/lang/AssertionStatusDirectives.class", - "/modules/java.base/java/lang/Appendable.class", - "/modules/java.base/java/lang/AbstractStringBuilder.class", - "/modules/java.base/java/lang/StringBuffer.class", - "/modules/java.base/java/lang/StringBuilder.class", - "/modules/java.base/jdk/internal/misc/UnsafeConstants.class", - "/modules/java.base/jdk/internal/misc/Unsafe.class", - "/modules/java.base/jdk/internal/module/Modules.class", - "/modules/java.base/java/lang/AutoCloseable.class", - "/modules/java.base/java/io/Closeable.class", - "/modules/java.base/java/io/InputStream.class", - "/modules/java.base/java/io/ByteArrayInputStream.class", - "/modules/java.base/java/net/URL.class", - "/modules/java.base/java/lang/Enum.class", - "/modules/java.base/java/util/jar/Manifest.class", - "/modules/java.base/jdk/internal/loader/BuiltinClassLoader.class", - "/modules/java.base/jdk/internal/loader/ClassLoaders.class", - "/modules/java.base/jdk/internal/loader/ClassLoaders$AppClassLoader.class", - "/modules/java.base/jdk/internal/loader/ClassLoaders$PlatformClassLoader.class", - "/modules/java.base/java/security/CodeSource.class", - "/modules/java.base/java/util/concurrent/ConcurrentMap.class", - "/modules/java.base/java/util/AbstractMap.class", - "/modules/java.base/java/util/concurrent/ConcurrentHashMap.class", - "/modules/java.base/java/lang/Iterable.class", - "/modules/java.base/java/util/Collection.class", - "/modules/java.base/java/util/SequencedCollection.class", - "/modules/java.base/java/util/List.class", - "/modules/java.base/java/util/RandomAccess.class", - "/modules/java.base/java/util/AbstractCollection.class", - "/modules/java.base/java/util/AbstractList.class", - "/modules/java.base/java/util/ArrayList.class", - "/modules/java.base/java/lang/StackTraceElement.class", - "/modules/java.base/java/nio/Buffer.class", - "/modules/java.base/java/lang/StackWalker.class", - "/modules/java.base/java/lang/StackStreamFactory$AbstractStackWalker.class", - "/modules/java.base/java/lang/StackWalker$StackFrame.class", - "/modules/java.base/java/lang/ClassFrameInfo.class", - "/modules/java.base/java/lang/StackFrameInfo.class", - "/modules/java.base/java/lang/LiveStackFrame.class", - "/modules/java.base/java/lang/LiveStackFrameInfo.class", - "/modules/java.base/java/util/concurrent/locks/AbstractOwnableSynchronizer.class", - "/modules/java.base/java/lang/Boolean.class", - "/modules/java.base/java/lang/Character.class", - "/modules/java.base/java/lang/Number.class", - "/modules/java.base/java/lang/Float.class", - "/modules/java.base/java/lang/Double.class", - "/modules/java.base/java/lang/Byte.class", - "/modules/java.base/java/lang/Short.class", - "/modules/java.base/java/lang/Integer.class", - "/modules/java.base/java/lang/Long.class", - "/modules/java.base/java/lang/Void.class", - "/modules/java.base/java/util/Iterator.class", - "/modules/java.base/java/lang/reflect/RecordComponent.class", - "/modules/java.base/jdk/internal/vm/vector/VectorSupport.class", - "/modules/java.base/jdk/internal/vm/vector/VectorSupport$VectorPayload.class", - "/modules/java.base/jdk/internal/vm/vector/VectorSupport$Vector.class", - "/modules/java.base/jdk/internal/vm/vector/VectorSupport$VectorMask.class", - "/modules/java.base/jdk/internal/vm/vector/VectorSupport$VectorShuffle.class", - "/modules/java.base/jdk/internal/vm/FillerObject.class", - "/modules/java.base/java/lang/NullPointerException.class", - "/modules/java.base/java/lang/ArithmeticException.class", - "/modules/java.base/java/lang/IndexOutOfBoundsException.class", - "/modules/java.base/java/lang/ArrayIndexOutOfBoundsException.class", - "/modules/java.base/java/io/ObjectStreamField.class", - "/modules/java.base/java/util/Comparator.class", - "/modules/java.base/java/lang/String$CaseInsensitiveComparator.class", - "/modules/java.base/jdk/internal/misc/VM.class", - "/modules/java.base/java/lang/Module$ArchivedData.class", - "/modules/java.base/jdk/internal/misc/CDS.class", - "/modules/java.base/java/util/Set.class", - "/modules/java.base/java/util/ImmutableCollections$AbstractImmutableCollection.class", - "/modules/java.base/java/util/ImmutableCollections$AbstractImmutableSet.class", - "/modules/java.base/java/util/ImmutableCollections$Set12.class", - "/modules/java.base/java/util/Objects.class", - "/modules/java.base/java/util/ImmutableCollections.class", - "/modules/java.base/java/util/ImmutableCollections$AbstractImmutableList.class", - "/modules/java.base/java/util/ImmutableCollections$ListN.class", - "/modules/java.base/java/util/ImmutableCollections$SetN.class", - "/modules/java.base/java/util/ImmutableCollections$AbstractImmutableMap.class", - "/modules/java.base/java/util/ImmutableCollections$MapN.class", - "/modules/java.base/jdk/internal/access/JavaLangReflectAccess.class", - "/modules/java.base/java/lang/reflect/ReflectAccess.class", - "/modules/java.base/jdk/internal/access/SharedSecrets.class", - "/modules/java.base/jdk/internal/reflect/ReflectionFactory.class", - "/modules/java.base/java/io/ObjectStreamClass.class", - "/modules/java.base/java/lang/Math.class", - "/modules/java.base/jdk/internal/reflect/ReflectionFactory$Config.class", - "/modules/java.base/jdk/internal/access/JavaLangRefAccess.class", - "/modules/java.base/java/lang/ref/ReferenceQueue.class", - "/modules/java.base/java/lang/ref/ReferenceQueue$Null.class", - "/modules/java.base/java/lang/ref/ReferenceQueue$Lock.class", - "/modules/java.base/jdk/internal/access/JavaLangAccess.class", - "/modules/java.base/jdk/internal/util/SystemProps.class", - "/modules/java.base/jdk/internal/util/SystemProps$Raw.class", - "/modules/java.base/java/nio/charset/Charset.class", - "/modules/java.base/java/nio/charset/spi/CharsetProvider.class", - "/modules/java.base/sun/nio/cs/StandardCharsets.class", - "/modules/java.base/java/lang/StringLatin1.class", - "/modules/java.base/sun/nio/cs/HistoricallyNamedCharset.class", - "/modules/java.base/sun/nio/cs/Unicode.class", - "/modules/java.base/sun/nio/cs/UTF_8.class", - "/modules/java.base/java/util/HashMap.class", - "/modules/java.base/java/lang/StrictMath.class", - "/modules/java.base/jdk/internal/util/ArraysSupport.class", - "/modules/java.base/java/util/Map$Entry.class", - "/modules/java.base/java/util/HashMap$Node.class", - "/modules/java.base/java/util/LinkedHashMap$Entry.class", - "/modules/java.base/java/util/HashMap$TreeNode.class", - "/modules/java.base/java/lang/StringConcatHelper.class", - "/modules/java.base/java/lang/VersionProps.class", - "/modules/java.base/java/lang/Runtime.class", - "/modules/java.base/java/util/concurrent/locks/Lock.class", - "/modules/java.base/java/util/concurrent/locks/ReentrantLock.class", - "/modules/java.base/java/util/concurrent/ConcurrentHashMap$Segment.class", - "/modules/java.base/java/util/concurrent/ConcurrentHashMap$CounterCell.class", - "/modules/java.base/java/util/concurrent/ConcurrentHashMap$Node.class", - "/modules/java.base/java/util/concurrent/locks/LockSupport.class", - "/modules/java.base/java/util/concurrent/ConcurrentHashMap$ReservationNode.class", - "/modules/java.base/java/util/AbstractSet.class", - "/modules/java.base/java/util/HashMap$EntrySet.class", - "/modules/java.base/java/util/HashMap$HashIterator.class", - "/modules/java.base/java/util/HashMap$EntryIterator.class", - "/modules/java.base/jdk/internal/util/StaticProperty.class", - "/modules/java.base/java/io/FileInputStream.class", - "/modules/java.base/java/lang/System$In.class", - "/modules/java.base/java/io/FileDescriptor.class", - "/modules/java.base/jdk/internal/access/JavaIOFileDescriptorAccess.class", - "/modules/java.base/java/io/Flushable.class", - "/modules/java.base/java/io/OutputStream.class", - "/modules/java.base/java/io/FileOutputStream.class", - "/modules/java.base/java/lang/System$Out.class", - "/modules/java.base/java/io/FilterInputStream.class", - "/modules/java.base/java/io/BufferedInputStream.class", - "/modules/java.base/java/io/FilterOutputStream.class", - "/modules/java.base/java/io/PrintStream.class", - "/modules/java.base/java/io/BufferedOutputStream.class", - "/modules/java.base/java/io/Writer.class", - "/modules/java.base/java/io/OutputStreamWriter.class", - "/modules/java.base/sun/nio/cs/StreamEncoder.class", - "/modules/java.base/java/nio/charset/CharsetEncoder.class", - "/modules/java.base/sun/nio/cs/UTF_8$Encoder.class", - "/modules/java.base/java/nio/charset/CodingErrorAction.class", - "/modules/java.base/java/util/Arrays.class", - "/modules/java.base/java/nio/ByteBuffer.class", - "/modules/java.base/jdk/internal/misc/ScopedMemoryAccess.class", - "/modules/java.base/java/util/function/Function.class", - "/modules/java.base/jdk/internal/util/Preconditions.class", - "/modules/java.base/java/util/function/BiFunction.class", - "/modules/java.base/jdk/internal/access/JavaNioAccess.class", - "/modules/java.base/java/nio/HeapByteBuffer.class", - "/modules/java.base/java/nio/PreviewMode.DISABLED.class", - "/modules/java.base/java/io/BufferedWriter.class", - "/modules/java.base/java/lang/Terminator.class", - "/modules/java.base/jdk/internal/misc/Signal$Handler.class", - "/modules/java.base/jdk/internal/misc/Signal.class", - "/modules/java.base/java/util/Hashtable$Entry.class", - "/modules/java.base/jdk/internal/misc/Signal$NativeHandler.class", - "/modules/java.base/java/lang/Integer$IntegerCache.class", - "/modules/java.base/jdk/internal/misc/OSEnvironment.class", - "/modules/java.base/java/lang/Thread$State.class", - "/modules/java.base/java/lang/ref/Reference$ReferenceHandler.class", - "/modules/java.base/java/lang/Thread$ThreadIdentifiers.class", - "/modules/java.base/java/lang/ref/Finalizer$FinalizerThread.class", - "/modules/java.base/jdk/internal/ref/Cleaner.class", - "/modules/java.base/java/util/Collections.class", - "/modules/java.base/java/util/Collections$EmptySet.class", - "/modules/java.base/java/util/Collections$EmptyList.class", - "/modules/java.base/java/util/Collections$EmptyMap.class", - "/modules/java.base/java/lang/IllegalArgumentException.class", - "/modules/java.base/java/lang/invoke/MethodHandleStatics.class", - "/modules/java.base/java/lang/reflect/ClassFileFormatVersion.class", - "/modules/java.base/java/lang/CharacterData.class", - "/modules/java.base/java/lang/CharacterDataLatin1.class", - "/modules/java.base/jdk/internal/util/ClassFileDumper.class", - "/modules/java.base/java/util/HexFormat.class", - "/modules/java.base/java/lang/Character$CharacterCache.class", - "/modules/java.base/java/util/concurrent/atomic/AtomicInteger.class", - "/modules/java.base/jdk/internal/module/ModuleBootstrap.class", - "/modules/java.base/java/lang/module/ModuleDescriptor.class", - "/modules/java.base/java/lang/invoke/MethodHandles.class", - "/modules/java.base/java/lang/invoke/MemberName$Factory.class", - "/modules/java.base/jdk/internal/reflect/Reflection.class", - "/modules/java.base/java/lang/invoke/MethodHandles$Lookup.class", - "/modules/java.base/java/util/ImmutableCollections$MapN$MapNIterator.class", - "/modules/java.base/java/util/KeyValueHolder.class", - "/modules/java.base/sun/invoke/util/VerifyAccess.class", - "/modules/java.base/java/lang/reflect/Modifier.class", - "/modules/java.base/jdk/internal/access/JavaLangModuleAccess.class", - "/modules/java.base/java/io/File.class", - "/modules/java.base/java/io/DefaultFileSystem.class", - "/modules/java.base/java/io/FileSystem.class", - "/modules/java.base/java/io/UnixFileSystem.class", - "/modules/java.base/jdk/internal/util/DecimalDigits.class", - "/modules/java.base/jdk/internal/module/ModulePatcher.class", - "/modules/java.base/jdk/internal/module/ModuleBootstrap$IllegalNativeAccess.class", - "/modules/java.base/java/util/HashSet.class", - "/modules/java.base/jdk/internal/module/ModuleLoaderMap.class", - "/modules/java.base/jdk/internal/module/ModuleLoaderMap$Modules.class", - "/modules/java.base/jdk/internal/module/ModuleBootstrap$Counters.class", - "/modules/java.base/jdk/internal/module/ArchivedBootLayer.class", - "/modules/java.base/jdk/internal/module/ArchivedModuleGraph.class", - "/modules/java.base/jdk/internal/module/SystemModuleFinders.class", - "/modules/java.base/java/net/URI.class", - "/modules/java.base/jdk/internal/access/JavaNetUriAccess.class", - "/modules/java.base/jdk/internal/module/SystemModulesMap.class", - "/modules/java.base/jdk/internal/module/SystemModules.class", - "/modules/java.base/jdk/internal/module/ExplodedSystemModules.class", - "/modules/java.base/java/nio/file/Watchable.class", - "/modules/java.base/java/nio/file/Path.class", - "/modules/java.base/java/nio/file/FileSystems.class", - "/modules/java.base/sun/nio/fs/DefaultFileSystemProvider.class", - "/modules/java.base/java/nio/file/spi/FileSystemProvider.class", - "/modules/java.base/sun/nio/fs/AbstractFileSystemProvider.class", - "/modules/java.base/sun/nio/fs/UnixFileSystemProvider.class", - "/modules/java.base/sun/nio/fs/LinuxFileSystemProvider.class", - "/modules/java.base/java/nio/file/OpenOption.class", - "/modules/java.base/java/nio/file/StandardOpenOption.class", - "/modules/java.base/java/nio/file/FileSystem.class", - "/modules/java.base/sun/nio/fs/UnixFileSystem.class", - "/modules/java.base/sun/nio/fs/LinuxFileSystem.class", - "/modules/java.base/sun/nio/fs/UnixPath.class", - "/modules/java.base/sun/nio/fs/Util.class", - "/modules/java.base/java/lang/StringCoding.class", - "/modules/java.base/sun/nio/fs/UnixNativeDispatcher.class", - "/modules/java.base/jdk/internal/loader/BootLoader.class", - "/modules/java.base/java/lang/Module$EnableNativeAccess.class", - "/modules/java.base/jdk/internal/loader/NativeLibraries.class", - "/modules/java.base/jdk/internal/loader/ClassLoaderHelper.class", - "/modules/java.base/java/util/concurrent/ConcurrentHashMap$CollectionView.class", - "/modules/java.base/java/util/concurrent/ConcurrentHashMap$KeySetView.class", - "/modules/java.base/jdk/internal/loader/NativeLibraries$LibraryPaths.class", - "/modules/java.base/java/io/File$PathStatus.class", - "/modules/java.base/jdk/internal/loader/NativeLibraries$CountedLock.class", - "/modules/java.base/java/util/concurrent/locks/AbstractQueuedSynchronizer.class", - "/modules/java.base/java/util/concurrent/locks/ReentrantLock$Sync.class", - "/modules/java.base/java/util/concurrent/locks/ReentrantLock$NonfairSync.class", - "/modules/java.base/jdk/internal/loader/NativeLibraries$NativeLibraryContext.class", - "/modules/java.base/java/util/Queue.class", - "/modules/java.base/java/util/Deque.class", - "/modules/java.base/java/util/ArrayDeque.class", - "/modules/java.base/java/util/ArrayDeque$DeqIterator.class", - "/modules/java.base/jdk/internal/loader/NativeLibrary.class", - "/modules/java.base/jdk/internal/loader/NativeLibraries$NativeLibraryImpl.class", - "/modules/java.base/java/security/cert/Certificate.class", - "/modules/java.base/java/util/concurrent/ConcurrentHashMap$ValuesView.class", - "/modules/java.base/java/util/Enumeration.class", - "/modules/java.base/java/util/concurrent/ConcurrentHashMap$Traverser.class", - "/modules/java.base/java/util/concurrent/ConcurrentHashMap$BaseIterator.class", - "/modules/java.base/java/util/concurrent/ConcurrentHashMap$ValueIterator.class", - "/modules/java.base/java/nio/file/attribute/BasicFileAttributes.class", - "/modules/java.base/java/nio/file/attribute/PosixFileAttributes.class", - "/modules/java.base/sun/nio/fs/UnixFileAttributes.class", - "/modules/java.base/sun/nio/fs/UnixFileStoreAttributes.class", - "/modules/java.base/sun/nio/fs/UnixMountEntry.class", - "/modules/java.base/java/nio/file/CopyOption.class", - "/modules/java.base/java/nio/file/LinkOption.class", - "/modules/java.base/java/nio/file/Files.class", - "/modules/java.base/sun/nio/fs/NativeBuffers.class", - "/modules/java.base/java/lang/ThreadLocal.class", - "/modules/java.base/jdk/internal/misc/CarrierThreadLocal.class", - "/modules/java.base/jdk/internal/misc/TerminatingThreadLocal.class", - "/modules/java.base/java/lang/ThreadLocal$ThreadLocalMap.class", - "/modules/java.base/java/lang/ThreadLocal$ThreadLocalMap$Entry.class", - "/modules/java.base/java/util/IdentityHashMap.class", - "/modules/java.base/java/util/Collections$SetFromMap.class", - "/modules/java.base/java/util/IdentityHashMap$KeySet.class", - "/modules/java.base/sun/nio/fs/NativeBuffer.class", - "/modules/java.base/jdk/internal/ref/CleanerFactory.class", - "/modules/java.base/java/util/concurrent/ThreadFactory.class", - "/modules/java.base/java/lang/ref/Cleaner.class", - "/modules/java.base/jdk/internal/ref/CleanerImpl.class", - "/modules/java.base/jdk/internal/ref/CleanerImpl$CleanableList.class", - "/modules/java.base/jdk/internal/ref/CleanerImpl$CleanableList$Node.class", - "/modules/java.base/java/lang/ref/Cleaner$Cleanable.class", - "/modules/java.base/jdk/internal/ref/PhantomCleanable.class", - "/modules/java.base/jdk/internal/ref/CleanerImpl$CleanerCleanable.class", - "/modules/java.base/jdk/internal/misc/InnocuousThread.class", - "/modules/java.base/sun/nio/fs/NativeBuffer$Deallocator.class", - "/modules/java.base/jdk/internal/ref/CleanerImpl$PhantomCleanableRef.class", - "/modules/java.base/java/lang/module/ModuleFinder.class", - "/modules/java.base/jdk/internal/module/ModulePath.class", - "/modules/java.base/java/util/jar/Attributes$Name.class", - "/modules/java.base/java/lang/reflect/Array.class", - "/modules/java.base/jdk/internal/perf/PerfCounter.class", - "/modules/java.base/jdk/internal/perf/Perf.class", - "/modules/java.base/sun/nio/ch/DirectBuffer.class", - "/modules/java.base/java/nio/MappedByteBuffer.class", - "/modules/java.base/java/nio/DirectByteBuffer.class", - "/modules/java.base/java/nio/Bits.class", - "/modules/java.base/java/util/concurrent/atomic/AtomicLong.class", - "/modules/java.base/jdk/internal/misc/VM$BufferPool.class", - "/modules/java.base/java/nio/LongBuffer.class", - "/modules/java.base/java/nio/DirectLongBufferU.class", - "/modules/java.base/java/util/zip/ZipConstants.class", - "/modules/java.base/java/util/zip/ZipFile.class", - "/modules/java.base/java/util/jar/JarFile.class", - "/modules/java.base/java/util/BitSet.class", - "/modules/java.base/jdk/internal/access/JavaUtilZipFileAccess.class", - "/modules/java.base/jdk/internal/access/JavaUtilJarAccess.class", - "/modules/java.base/java/util/jar/JavaUtilJarAccessImpl.class", - "/modules/java.base/java/lang/Runtime$Version.class", - "/modules/java.base/java/util/ImmutableCollections$List12.class", - "/modules/java.base/java/util/Optional.class", - "/modules/java.base/java/nio/file/attribute/DosFileAttributes.class", - "/modules/java.base/java/nio/file/attribute/AttributeView.class", - "/modules/java.base/java/nio/file/attribute/FileAttributeView.class", - "/modules/java.base/java/nio/file/attribute/BasicFileAttributeView.class", - "/modules/java.base/java/nio/file/attribute/DosFileAttributeView.class", - "/modules/java.base/java/nio/file/attribute/UserDefinedFileAttributeView.class", - "/modules/java.base/sun/nio/fs/UnixFileAttributeViews.class", - "/modules/java.base/sun/nio/fs/DynamicFileAttributeView.class", - "/modules/java.base/sun/nio/fs/AbstractBasicFileAttributeView.class", - "/modules/java.base/sun/nio/fs/UnixFileAttributeViews$Basic.class", - "/modules/java.base/sun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes.class", - "/modules/java.base/java/nio/file/DirectoryStream$Filter.class", - "/modules/java.base/java/nio/file/Files$AcceptAllFilter.class", - "/modules/java.base/java/nio/file/DirectoryStream.class", - "/modules/java.base/java/nio/file/SecureDirectoryStream.class", - "/modules/java.base/sun/nio/fs/UnixSecureDirectoryStream.class", - "/modules/java.base/sun/nio/fs/UnixDirectoryStream.class", - "/modules/java.base/java/util/concurrent/locks/ReadWriteLock.class", - "/modules/java.base/java/util/concurrent/locks/ReentrantReadWriteLock.class", - "/modules/java.base/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.class", - "/modules/java.base/java/util/concurrent/locks/ReentrantReadWriteLock$Sync.class", - "/modules/java.base/java/util/concurrent/locks/ReentrantReadWriteLock$FairSync.class", - "/modules/java.base/java/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter.class", - "/modules/java.base/java/util/concurrent/locks/ReentrantReadWriteLock$ReadLock.class", - "/modules/java.base/java/util/concurrent/locks/ReentrantReadWriteLock$WriteLock.class", - "/modules/java.base/sun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator.class", - "/modules/java.base/java/nio/file/attribute/FileAttribute.class", - "/modules/java.base/sun/nio/fs/UnixFileModeAttribute.class", - "/modules/java.base/sun/nio/fs/UnixChannelFactory.class", - "/modules/java.base/sun/nio/fs/UnixChannelFactory$Flags.class", - "/modules/java.base/java/util/Collections$EmptyIterator.class", - "/modules/java.base/java/nio/channels/Channel.class", - "/modules/java.base/java/nio/channels/ReadableByteChannel.class", - "/modules/java.base/java/nio/channels/WritableByteChannel.class", - "/modules/java.base/java/nio/channels/ByteChannel.class", - "/modules/java.base/java/nio/channels/SeekableByteChannel.class", - "/modules/java.base/java/nio/channels/GatheringByteChannel.class", - "/modules/java.base/java/nio/channels/ScatteringByteChannel.class", - "/modules/java.base/java/nio/channels/InterruptibleChannel.class", - "/modules/java.base/java/nio/channels/spi/AbstractInterruptibleChannel.class", - "/modules/java.base/java/nio/channels/FileChannel.class", - "/modules/java.base/sun/nio/ch/FileChannelImpl.class", - "/modules/java.base/sun/nio/ch/NativeDispatcher.class", - "/modules/java.base/sun/nio/ch/FileDispatcher.class", - "/modules/java.base/sun/nio/ch/UnixFileDispatcherImpl.class", - "/modules/java.base/sun/nio/ch/FileDispatcherImpl.class", - "/modules/java.base/sun/nio/ch/IOUtil.class", - "/modules/java.base/sun/nio/ch/Interruptible.class", - "/modules/java.base/sun/nio/ch/NativeThreadSet.class", - "/modules/java.base/sun/nio/ch/FileChannelImpl$Closer.class", - "/modules/java.base/java/nio/channels/Channels.class", - "/modules/java.base/sun/nio/ch/Streams.class", - "/modules/java.base/sun/nio/ch/SelChImpl.class", - "/modules/java.base/java/nio/channels/NetworkChannel.class", - "/modules/java.base/java/nio/channels/SelectableChannel.class", - "/modules/java.base/java/nio/channels/spi/AbstractSelectableChannel.class", - "/modules/java.base/java/nio/channels/SocketChannel.class", - "/modules/java.base/sun/nio/ch/SocketChannelImpl.class", - "/modules/java.base/sun/nio/ch/ChannelInputStream.class", - "/modules/java.base/java/lang/invoke/LambdaMetafactory.class", - "/modules/java.base/java/util/function/Supplier.class", - "/modules/java.base/jdk/internal/util/ReferencedKeySet.class", - "/modules/java.base/jdk/internal/util/ReferencedKeyMap.class", - "/modules/java.base/jdk/internal/util/ReferenceKey.class", - "/modules/java.base/jdk/internal/util/StrongReferenceKey.class", - "/modules/java.base/java/lang/invoke/MethodTypeForm.class", - "/modules/java.base/jdk/internal/util/WeakReferenceKey.class", - "/modules/java.base/sun/invoke/util/Wrapper.class", - "/modules/java.base/sun/invoke/util/Wrapper$Format.class", - "/modules/java.base/java/lang/constant/ConstantDescs.class", - "/modules/java.base/java/lang/constant/ClassDesc.class", - "/modules/java.base/jdk/internal/constant/ClassOrInterfaceDescImpl.class", - "/modules/java.base/jdk/internal/constant/ArrayClassDescImpl.class", - "/modules/java.base/jdk/internal/constant/ConstantUtils.class", - "/modules/java.base/java/lang/constant/DirectMethodHandleDesc$Kind.class", - "/modules/java.base/java/lang/constant/MethodTypeDesc.class", - "/modules/java.base/jdk/internal/constant/MethodTypeDescImpl.class", - "/modules/java.base/java/lang/constant/MethodHandleDesc.class", - "/modules/java.base/java/lang/constant/DirectMethodHandleDesc.class", - "/modules/java.base/jdk/internal/constant/DirectMethodHandleDescImpl.class", - "/modules/java.base/java/lang/constant/DynamicConstantDesc.class", - "/modules/java.base/jdk/internal/constant/PrimitiveClassDescImpl.class", - "/modules/java.base/java/lang/constant/DynamicConstantDesc$AnonymousDynamicConstantDesc.class", - "/modules/java.base/java/lang/invoke/LambdaForm$NamedFunction.class", - "/modules/java.base/java/lang/invoke/DirectMethodHandle$Holder.class", - "/modules/java.base/sun/invoke/util/ValueConversions.class", - "/modules/java.base/java/lang/invoke/MethodHandleImpl.class", - "/modules/java.base/java/lang/invoke/Invokers.class", - "/modules/java.base/java/lang/invoke/LambdaForm$Kind.class", - "/modules/java.base/java/lang/NoSuchMethodException.class", - "/modules/java.base/java/lang/invoke/LambdaForm$BasicType.class", - "/modules/java.base/java/lang/classfile/TypeKind.class", - "/modules/java.base/java/lang/invoke/LambdaForm$Name.class", - "/modules/java.base/java/lang/invoke/LambdaForm$Holder.class", - "/modules/java.base/java/lang/invoke/InvokerBytecodeGenerator.class", - "/modules/java.base/java/lang/classfile/AnnotationElement.class", - "/modules/java.base/java/lang/classfile/Annotation.class", - "/modules/java.base/java/lang/classfile/constantpool/ConstantPool.class", - "/modules/java.base/java/lang/classfile/constantpool/ConstantPoolBuilder.class", - "/modules/java.base/jdk/internal/classfile/impl/TemporaryConstantPool.class", - "/modules/java.base/java/lang/classfile/constantpool/PoolEntry.class", - "/modules/java.base/java/lang/classfile/constantpool/AnnotationConstantValueEntry.class", - "/modules/java.base/java/lang/classfile/constantpool/Utf8Entry.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$Utf8EntryImpl.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$Utf8EntryImpl$State.class", - "/modules/java.base/jdk/internal/classfile/impl/AnnotationImpl.class", - "/modules/java.base/java/lang/classfile/ClassFileElement.class", - "/modules/java.base/java/lang/classfile/Attribute.class", - "/modules/java.base/java/lang/classfile/ClassElement.class", - "/modules/java.base/java/lang/classfile/MethodElement.class", - "/modules/java.base/java/lang/classfile/FieldElement.class", - "/modules/java.base/java/lang/classfile/attribute/RuntimeVisibleAnnotationsAttribute.class", - "/modules/java.base/jdk/internal/classfile/impl/Util$Writable.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractElement.class", - "/modules/java.base/jdk/internal/classfile/impl/UnboundAttribute.class", - "/modules/java.base/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeVisibleAnnotationsAttribute.class", - "/modules/java.base/java/lang/classfile/Attributes.class", - "/modules/java.base/java/lang/classfile/AttributeMapper.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractAttributeMapper.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeVisibleAnnotationsMapper.class", - "/modules/java.base/java/lang/classfile/AttributeMapper$AttributeStability.class", - "/modules/java.base/java/lang/invoke/MethodHandleImpl$Intrinsic.class", - "/modules/java.base/jdk/internal/classfile/impl/SplitConstantPool.class", - "/modules/java.base/java/lang/classfile/BootstrapMethodEntry.class", - "/modules/java.base/jdk/internal/classfile/impl/BootstrapMethodEntryImpl.class", - "/modules/java.base/jdk/internal/classfile/impl/EntryMap.class", - "/modules/java.base/jdk/internal/classfile/impl/Util.class", - "/modules/java.base/java/lang/classfile/constantpool/LoadableConstantEntry.class", - "/modules/java.base/java/lang/classfile/constantpool/ClassEntry.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractRefEntry.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractNamedEntry.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$ClassEntryImpl.class", - "/modules/java.base/java/util/function/Consumer.class", - "/modules/java.base/java/lang/classfile/ClassFile.class", - "/modules/java.base/jdk/internal/classfile/impl/ClassFileImpl.class", - "/modules/java.base/java/lang/classfile/ClassFileBuilder.class", - "/modules/java.base/java/lang/classfile/ClassBuilder.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractDirectBuilder.class", - "/modules/java.base/jdk/internal/classfile/impl/DirectClassBuilder.class", - "/modules/java.base/jdk/internal/classfile/impl/AttributeHolder.class", - "/modules/java.base/java/lang/classfile/Superclass.class", - "/modules/java.base/jdk/internal/classfile/impl/SuperclassImpl.class", - "/modules/java.base/java/lang/classfile/attribute/SourceFileAttribute.class", - "/modules/java.base/jdk/internal/classfile/impl/UnboundAttribute$UnboundSourceFileAttribute.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractAttributeMapper$SourceFileMapper.class", - "/modules/java.base/jdk/internal/classfile/impl/BoundAttribute.class", - "/modules/java.base/java/lang/classfile/MethodBuilder.class", - "/modules/java.base/jdk/internal/classfile/impl/MethodInfo.class", - "/modules/java.base/jdk/internal/classfile/impl/TerminalMethodBuilder.class", - "/modules/java.base/jdk/internal/classfile/impl/DirectMethodBuilder.class", - "/modules/java.base/java/lang/classfile/constantpool/NameAndTypeEntry.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractRefsEntry.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$NameAndTypeEntryImpl.class", - "/modules/java.base/java/lang/classfile/constantpool/MemberRefEntry.class", - "/modules/java.base/java/lang/classfile/constantpool/FieldRefEntry.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractMemberRefEntry.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$FieldRefEntryImpl.class", - "/modules/java.base/java/lang/invoke/InvokerBytecodeGenerator$ClassData.class", - "/modules/java.base/java/lang/classfile/CodeBuilder.class", - "/modules/java.base/jdk/internal/classfile/impl/LabelContext.class", - "/modules/java.base/jdk/internal/classfile/impl/TerminalCodeBuilder.class", - "/modules/java.base/jdk/internal/classfile/impl/DirectCodeBuilder.class", - "/modules/java.base/java/lang/classfile/CodeElement.class", - "/modules/java.base/java/lang/classfile/PseudoInstruction.class", - "/modules/java.base/java/lang/classfile/instruction/CharacterRange.class", - "/modules/java.base/java/lang/classfile/instruction/LocalVariable.class", - "/modules/java.base/java/lang/classfile/instruction/LocalVariableType.class", - "/modules/java.base/jdk/internal/classfile/impl/DirectCodeBuilder$DeferredLabel.class", - "/modules/java.base/java/lang/classfile/BufWriter.class", - "/modules/java.base/jdk/internal/classfile/impl/BufWriterImpl.class", - "/modules/java.base/java/lang/classfile/Label.class", - "/modules/java.base/java/lang/classfile/instruction/LabelTarget.class", - "/modules/java.base/jdk/internal/classfile/impl/LabelImpl.class", - "/modules/java.base/sun/invoke/util/VerifyType.class", - "/modules/java.base/java/lang/classfile/Opcode.class", - "/modules/java.base/java/lang/classfile/Opcode$Kind.class", - "/modules/java.base/java/lang/classfile/constantpool/MethodRefEntry.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$MethodRefEntryImpl.class", - "/modules/java.base/sun/invoke/empty/Empty.class", - "/modules/java.base/jdk/internal/classfile/impl/BytecodeHelpers.class", - "/modules/java.base/jdk/internal/classfile/impl/UnboundAttribute$AdHocAttribute.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractAttributeMapper$CodeMapper.class", - "/modules/java.base/java/lang/classfile/FieldBuilder.class", - "/modules/java.base/jdk/internal/classfile/impl/TerminalFieldBuilder.class", - "/modules/java.base/jdk/internal/classfile/impl/DirectFieldBuilder.class", - "/modules/java.base/java/lang/classfile/CustomAttribute.class", - "/modules/java.base/jdk/internal/classfile/impl/AnnotationReader.class", - "/modules/java.base/java/util/ListIterator.class", - "/modules/java.base/java/util/ImmutableCollections$ListItr.class", - "/modules/java.base/jdk/internal/classfile/impl/StackMapGenerator.class", - "/modules/java.base/jdk/internal/classfile/impl/StackMapGenerator$Frame.class", - "/modules/java.base/jdk/internal/classfile/impl/StackMapGenerator$Type.class", - "/modules/java.base/jdk/internal/classfile/impl/RawBytecodeHelper.class", - "/modules/java.base/jdk/internal/classfile/impl/RawBytecodeHelper$CodeRange.class", - "/modules/java.base/jdk/internal/classfile/impl/ClassHierarchyImpl.class", - "/modules/java.base/java/lang/classfile/ClassHierarchyResolver.class", - "/modules/java.base/jdk/internal/classfile/impl/ClassHierarchyImpl$ClassLoadingClassHierarchyResolver.class", - "/modules/java.base/jdk/internal/classfile/impl/ClassHierarchyImpl$CachedClassHierarchyResolver.class", - "/modules/java.base/java/lang/classfile/ClassHierarchyResolver$ClassHierarchyInfo.class", - "/modules/java.base/jdk/internal/classfile/impl/ClassHierarchyImpl$ClassHierarchyInfoImpl.class", - "/modules/java.base/java/lang/classfile/ClassReader.class", - "/modules/java.base/jdk/internal/classfile/impl/ClassReaderImpl.class", - "/modules/java.base/jdk/internal/util/ModifiedUtf.class", - "/modules/java.base/java/lang/invoke/MethodHandles$Lookup$ClassDefiner.class", - "/modules/java.base/java/lang/IncompatibleClassChangeError.class", - "/modules/java.base/java/lang/NoSuchMethodError.class", - "/modules/java.base/java/lang/invoke/BootstrapMethodInvoker.class", - "/modules/java.base/java/lang/invoke/AbstractValidatingLambdaMetafactory.class", - "/modules/java.base/java/lang/invoke/InnerClassLambdaMetafactory.class", - "/modules/java.base/java/lang/invoke/MethodHandleInfo.class", - "/modules/java.base/java/lang/invoke/InfoFromMemberName.class", - "/modules/java.base/java/util/ImmutableCollections$Access.class", - "/modules/java.base/jdk/internal/access/JavaUtilCollectionAccess.class", - "/modules/java.base/java/lang/classfile/Interfaces.class", - "/modules/java.base/jdk/internal/classfile/impl/InterfacesImpl.class", - "/modules/java.base/java/lang/invoke/TypeConvertingMethodAdapter.class", - "/modules/java.base/java/lang/invoke/DirectMethodHandle$Constructor.class", - "/modules/java.base/jdk/internal/access/JavaLangInvokeAccess.class", - "/modules/java.base/java/lang/invoke/VarHandle$AccessMode.class", - "/modules/java.base/java/lang/invoke/VarHandle$AccessType.class", - "/modules/java.base/java/lang/invoke/Invokers$Holder.class", - "/modules/java.base/jdk/internal/module/ModuleInfo.class", - "/modules/java.base/java/io/DataInput.class", - "/modules/java.base/java/io/DataInputStream.class", - "/modules/java.base/jdk/internal/module/ModuleInfo$CountingDataInput.class", - "/modules/java.base/sun/nio/ch/NativeThread.class", - "/modules/java.base/jdk/internal/misc/Blocker.class", - "/modules/java.base/sun/nio/ch/Util.class", - "/modules/java.base/sun/nio/ch/Util$BufferCache.class", - "/modules/java.base/sun/nio/ch/IOStatus.class", - "/modules/java.base/jdk/internal/util/ByteArray.class", - "/modules/java.base/java/lang/invoke/VarHandles.class", - "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsShorts$ByteArrayViewVarHandle.class", - "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsShorts$ArrayHandle.class", - "/modules/java.base/java/lang/invoke/VarHandleGuards.class", - "/modules/java.base/java/lang/invoke/VarForm.class", - "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsChars$ByteArrayViewVarHandle.class", - "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsChars$ArrayHandle.class", - "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsInts$ByteArrayViewVarHandle.class", - "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsInts$ArrayHandle.class", - "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsFloats$ByteArrayViewVarHandle.class", - "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsFloats$ArrayHandle.class", - "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsLongs$ByteArrayViewVarHandle.class", - "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsLongs$ArrayHandle.class", - "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsDoubles$ByteArrayViewVarHandle.class", - "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsDoubles$ArrayHandle.class", - "/modules/java.base/java/lang/invoke/VarHandle$AccessDescriptor.class", - "/modules/java.base/jdk/internal/module/ModuleInfo$ConstantPool.class", - "/modules/java.base/jdk/internal/module/ModuleInfo$ConstantPool$Entry.class", - "/modules/java.base/jdk/internal/module/ModuleInfo$ConstantPool$IndexEntry.class", - "/modules/java.base/java/nio/charset/StandardCharsets.class", - "/modules/java.base/sun/nio/cs/US_ASCII.class", - "/modules/java.base/sun/nio/cs/ISO_8859_1.class", - "/modules/java.base/sun/nio/cs/UTF_16BE.class", - "/modules/java.base/sun/nio/cs/UTF_16LE.class", - "/modules/java.base/sun/nio/cs/UTF_16.class", - "/modules/java.base/sun/nio/cs/UTF_32BE.class", - "/modules/java.base/sun/nio/cs/UTF_32LE.class", - "/modules/java.base/sun/nio/cs/UTF_32.class", - "/modules/java.base/jdk/internal/module/ModuleInfo$ConstantPool$ValueEntry.class", - "/modules/java.base/java/lang/module/ModuleDescriptor$Builder.class", - "/modules/java.base/java/lang/module/ModuleDescriptor$Modifier.class", - "/modules/java.base/java/lang/reflect/AccessFlag.class", - "/modules/java.base/java/lang/reflect/AccessFlag$Location.class", - "/modules/java.base/java/lang/module/ModuleDescriptor$Requires$Modifier.class", - "/modules/java.base/java/lang/module/ModuleDescriptor$Requires.class", - "/modules/java.base/java/util/HashMap$KeySet.class", - "/modules/java.base/java/util/HashMap$KeyIterator.class", - "/modules/java.base/jdk/internal/module/Checks.class", - "/modules/java.base/java/util/ArrayList$Itr.class", - "/modules/java.base/java/lang/module/ModuleDescriptor$Provides.class", - "/modules/java.base/java/util/Collections$UnmodifiableCollection.class", - "/modules/java.base/java/util/Collections$UnmodifiableSet.class", - "/modules/java.base/java/util/HashMap$Values.class", - "/modules/java.base/java/util/HashMap$ValueIterator.class", - "/modules/java.base/java/util/ImmutableCollections$SetN$SetNIterator.class", - "/modules/java.base/jdk/internal/module/ModuleInfo$Attributes.class", - "/modules/java.base/jdk/internal/module/ModuleReferences.class", - "/modules/java.base/java/lang/module/ModuleReader.class", - "/modules/java.base/sun/nio/fs/UnixUriUtils.class", - "/modules/java.base/java/net/URI$Parser.class", - "/modules/java.base/java/lang/module/ModuleReference.class", - "/modules/java.base/jdk/internal/module/ModuleReferenceImpl.class", - "/modules/java.base/java/lang/module/ModuleDescriptor$Exports.class", - "/modules/java.base/java/lang/module/ModuleDescriptor$Opens.class", - "/modules/java.base/sun/nio/fs/UnixException.class", - "/modules/java.base/java/io/IOException.class", - "/modules/java.base/jdk/internal/loader/ArchivedClassLoaders.class", - "/modules/java.base/jdk/internal/loader/ClassLoaders$BootClassLoader.class", - "/modules/java.base/java/lang/ClassLoader$ParallelLoaders.class", - "/modules/java.base/java/util/WeakHashMap.class", - "/modules/java.base/java/util/WeakHashMap$Entry.class", - "/modules/java.base/java/util/WeakHashMap$KeySet.class", - "/modules/java.base/java/security/Principal.class", - "/modules/java.base/jdk/internal/loader/URLClassPath.class", - "/modules/java.base/java/net/URLStreamHandlerFactory.class", - "/modules/java.base/java/net/URL$DefaultFactory.class", - "/modules/java.base/jdk/internal/access/JavaNetURLAccess.class", - "/modules/java.base/sun/net/www/ParseUtil.class", - "/modules/java.base/java/net/URLStreamHandler.class", - "/modules/java.base/sun/net/www/protocol/file/Handler.class", - "/modules/java.base/sun/net/util/IPAddressUtil.class", - "/modules/java.base/sun/net/util/IPAddressUtil$MASKS.class", - "/modules/java.base/sun/net/www/protocol/jar/Handler.class", - "/modules/java.base/jdk/internal/module/ServicesCatalog.class", - "/modules/java.base/jdk/internal/loader/AbstractClassLoaderValue.class", - "/modules/java.base/jdk/internal/loader/ClassLoaderValue.class", - "/modules/java.base/jdk/internal/loader/BuiltinClassLoader$LoadedModule.class", - "/modules/java.base/jdk/internal/module/DefaultRoots.class", - "/modules/java.base/java/util/Spliterator.class", - "/modules/java.base/java/util/HashMap$HashMapSpliterator.class", - "/modules/java.base/java/util/HashMap$ValueSpliterator.class", - "/modules/java.base/java/util/stream/StreamSupport.class", - "/modules/java.base/java/util/stream/BaseStream.class", - "/modules/java.base/java/util/stream/Stream.class", - "/modules/java.base/java/util/stream/PipelineHelper.class", - "/modules/java.base/java/util/stream/AbstractPipeline.class", - "/modules/java.base/java/util/stream/ReferencePipeline.class", - "/modules/java.base/java/util/stream/ReferencePipeline$Head.class", - "/modules/java.base/java/util/stream/StreamOpFlag.class", - "/modules/java.base/java/util/stream/StreamOpFlag$Type.class", - "/modules/java.base/java/util/stream/StreamOpFlag$MaskBuilder.class", - "/modules/java.base/java/util/EnumMap.class", - "/modules/java.base/java/lang/Class$ReflectionData.class", - "/modules/java.base/java/lang/Class$Atomic.class", - "/modules/java.base/java/lang/PublicMethods$MethodList.class", - "/modules/java.base/java/lang/PublicMethods$Key.class", - "/modules/java.base/sun/reflect/annotation/AnnotationParser.class", - "/modules/java.base/jdk/internal/reflect/MethodHandleAccessorFactory.class", - "/modules/java.base/jdk/internal/reflect/MethodHandleAccessorFactory$LazyStaticHolder.class", - "/modules/java.base/java/lang/invoke/BoundMethodHandle.class", - "/modules/java.base/java/lang/invoke/ClassSpecializer.class", - "/modules/java.base/java/lang/invoke/BoundMethodHandle$Specializer.class", - "/modules/java.base/jdk/internal/vm/annotation/Stable.class", - "/modules/java.base/java/lang/invoke/ClassSpecializer$SpeciesData.class", - "/modules/java.base/java/lang/invoke/BoundMethodHandle$SpeciesData.class", - "/modules/java.base/java/lang/invoke/ClassSpecializer$Factory.class", - "/modules/java.base/java/lang/invoke/BoundMethodHandle$Specializer$Factory.class", - "/modules/java.base/java/lang/invoke/SimpleMethodHandle.class", - "/modules/java.base/java/lang/NoSuchFieldException.class", - "/modules/java.base/java/lang/invoke/BoundMethodHandle$Species_L.class", - "/modules/java.base/java/lang/invoke/DirectMethodHandle$Accessor.class", - "/modules/java.base/java/lang/invoke/DelegatingMethodHandle.class", - "/modules/java.base/java/lang/invoke/DelegatingMethodHandle$Holder.class", - "/modules/java.base/java/lang/invoke/LambdaFormEditor.class", - "/modules/java.base/java/lang/invoke/LambdaFormEditor$TransformKey.class", - "/modules/java.base/java/lang/invoke/LambdaFormBuffer.class", - "/modules/java.base/java/lang/invoke/LambdaFormEditor$Transform.class", - "/modules/java.base/jdk/internal/reflect/DirectMethodHandleAccessor.class", - "/modules/java.base/java/util/stream/Collectors.class", - "/modules/java.base/java/util/stream/Collector$Characteristics.class", - "/modules/java.base/java/util/EnumSet.class", - "/modules/java.base/java/util/RegularEnumSet.class", - "/modules/java.base/java/util/stream/Collector.class", - "/modules/java.base/java/util/stream/Collectors$CollectorImpl.class", - "/modules/java.base/java/util/function/BiConsumer.class", - "/modules/java.base/java/lang/invoke/DirectMethodHandle$Interface.class", - "/modules/java.base/java/lang/classfile/constantpool/InterfaceMethodRefEntry.class", - "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$InterfaceMethodRefEntryImpl.class", - "/modules/java.base/java/util/function/BinaryOperator.class", - "/modules/java.base/java/util/stream/ReduceOps.class", - "/modules/java.base/java/util/stream/TerminalOp.class", - "/modules/java.base/java/util/stream/ReduceOps$ReduceOp.class", - "/modules/java.base/java/util/stream/StreamShape.class", - "/modules/java.base/java/util/stream/Sink.class", - "/modules/java.base/java/util/stream/TerminalSink.class", - "/modules/java.base/java/util/stream/ReduceOps$AccumulatingSink.class", - "/modules/java.base/java/util/stream/ReduceOps$Box.class", - "/modules/java.base/java/util/HashMap$KeySpliterator.class", - "/modules/java.base/java/util/function/Predicate.class", - "/modules/java.base/java/util/stream/ReferencePipeline$StatelessOp.class", - "/modules/java.base/java/util/stream/Sink$ChainedReference.class", - "/modules/java.base/jdk/internal/module/ModuleResolution.class", - "/modules/java.base/java/util/stream/FindOps.class", - "/modules/java.base/java/util/stream/FindOps$FindSink.class", - "/modules/java.base/java/util/stream/FindOps$FindSink$OfRef.class", - "/modules/java.base/java/util/stream/FindOps$FindOp.class", - "/modules/java.base/java/util/Spliterators.class", - "/modules/java.base/java/util/Spliterators$IteratorSpliterator.class", - "/modules/java.base/java/lang/module/Configuration.class", - "/modules/java.base/java/lang/module/Resolver.class", - "/modules/java.base/java/lang/ModuleLayer.class", - "/modules/java.base/java/util/SequencedSet.class", - "/modules/java.base/java/util/LinkedHashSet.class", - "/modules/java.base/java/util/SequencedMap.class", - "/modules/java.base/java/util/LinkedHashMap.class", - "/modules/java.base/java/lang/module/ResolvedModule.class", - "/modules/java.base/jdk/internal/module/ModuleLoaderMap$Mapper.class", - "/modules/java.base/jdk/internal/loader/AbstractClassLoaderValue$Memoizer.class", - "/modules/java.base/jdk/internal/module/ServicesCatalog$ServiceProvider.class", - "/modules/java.base/java/util/concurrent/CopyOnWriteArrayList.class", - "/modules/java.base/java/lang/ModuleLayer$Controller.class", - "/modules/java.base/jdk/internal/module/ModuleBootstrap$SafeModuleFinder.class", - "/modules/java.base/jdk/internal/vm/ContinuationSupport.class", - "/modules/java.base/jdk/internal/vm/Continuation$Pinned.class", - "/modules/java.base/sun/launcher/LauncherHelper.class", - "/modules/java.base/sun/net/util/URLUtil.class", - "/modules/java.base/jdk/internal/loader/URLClassPath$Loader.class", - "/modules/java.base/jdk/internal/loader/URLClassPath$FileLoader.class", - "/modules/java.base/jdk/internal/loader/Resource.class", - "/modules/java.base/java/io/FileCleanable.class", - "/modules/java.base/sun/nio/ByteBuffered.class", - "/modules/java.base/java/security/SecureClassLoader$CodeSourceKey.class", - "/modules/java.base/java/security/PermissionCollection.class", - "/modules/java.base/java/security/Permissions.class", - "/modules/java.base/java/lang/NamedPackage.class", - "/modules/java.base/jdk/internal/misc/MethodFinder.class", - "/modules/java.base/java/lang/Readable.class", - "/modules/java.base/java/nio/CharBuffer.class", - "/modules/java.base/java/nio/HeapCharBuffer.class", - "/modules/java.base/java/nio/charset/CoderResult.class", - "/modules/java.base/java/util/IdentityHashMap$IdentityHashMapIterator.class", - "/modules/java.base/java/util/IdentityHashMap$KeyIterator.class", - "/modules/java.base/java/lang/Shutdown.class", - "/modules/java.base/java/lang/Shutdown$Lock.class"); + /// Note: This list is inherently a little fragile and may end up being more + /// trouble than it's worth to maintain. If it turns out that it needs to be + /// regenerated often when this benchmark is run, then a new approach should + /// be considered, such as: + /// * Limit the list of classes to non-internal ones. + /// * Calculate the list dynamically based on the running JVM. + /// * Build a custom jimage file similar to ImageReaderTest + private static final class ClassList { + /// Returns the names of resource nodes expected to be present in the + /// reader, excluding preview mode paths (i.e. "/META-INF/preview/"). + private static Set names() { + return INIT_CLASSES; + } + + /// Returns the number of resources present. + private static int count() { + return INIT_CLASSES.size(); + } + + /// Returns the resource nodes represented as a map from module name to + /// resource path. This is suitable for testing functions like + /// {@link ImageReader#containsResource(String, String)} without the + /// overhead of splitting resource names during the trial. + private static Map> pathMap() { + return MODULE_TO_PATHS; + } + + // Created by running "java -verbose:class", throwing away anonymous inner + // classes and anything without a reliable name, and grouping by the stated + // source. It's not perfect, but it's representative. + // + // /bin/java -verbose:class HelloWorld 2>&1 \ + // | fgrep '[class,load]' | cut -d' ' -f2 \ + // | tr '.' '/' \ + // | egrep -v '\$[0-9$]' \ + // | fgrep -v 'HelloWorld' \ + // | fgrep -v '/META-INF/preview/' \ + // | while read f ; do echo "${f}.class" ; done \ + // > initclasses.txt + // + // Output: + // java/lang/Object.class + // java/io/Serializable.class + // ... + // + // jimage list /images/jdk/lib/modules \ + // | awk '/^Module: */ { MOD=$2 }; /^ */ { print "/modules/"MOD"/"$1 }' \ + // > fullpaths.txt + // + // Output: + // ... + // /modules/java.base/java/lang/Object.class + // /modules/java.base/java/lang/OutOfMemoryError.class + // ... + // + // while read c ; do grep "/$c" fullpaths.txt ; done < initclasses.txt \ + // | while read c ; do printf ' "%s",\n' "$c" ; done \ + // > initpaths.txt + // + // Output: + private static final Set INIT_CLASSES = Set.of( + "/modules/java.base/java/lang/Object.class", + "/modules/java.base/java/io/Serializable.class", + "/modules/java.base/java/lang/Comparable.class", + "/modules/java.base/java/lang/CharSequence.class", + "/modules/java.base/java/lang/constant/Constable.class", + "/modules/java.base/java/lang/constant/ConstantDesc.class", + "/modules/java.base/java/lang/String.class", + "/modules/java.base/java/lang/reflect/AnnotatedElement.class", + "/modules/java.base/java/lang/reflect/GenericDeclaration.class", + "/modules/java.base/java/lang/reflect/Type.class", + "/modules/java.base/java/lang/invoke/TypeDescriptor.class", + "/modules/java.base/java/lang/invoke/TypeDescriptor$OfField.class", + "/modules/java.base/java/lang/Class.class", + "/modules/java.base/java/lang/Cloneable.class", + "/modules/java.base/java/lang/ClassLoader.class", + "/modules/java.base/java/lang/System.class", + "/modules/java.base/java/lang/Throwable.class", + "/modules/java.base/java/lang/Error.class", + "/modules/java.base/java/lang/Exception.class", + "/modules/java.base/java/lang/RuntimeException.class", + "/modules/java.base/java/security/ProtectionDomain.class", + "/modules/java.base/java/security/SecureClassLoader.class", + "/modules/java.base/java/lang/ReflectiveOperationException.class", + "/modules/java.base/java/lang/ClassNotFoundException.class", + "/modules/java.base/java/lang/Record.class", + "/modules/java.base/java/lang/LinkageError.class", + "/modules/java.base/java/lang/NoClassDefFoundError.class", + "/modules/java.base/java/lang/ClassCastException.class", + "/modules/java.base/java/lang/ArrayStoreException.class", + "/modules/java.base/java/lang/VirtualMachineError.class", + "/modules/java.base/java/lang/InternalError.class", + "/modules/java.base/java/lang/OutOfMemoryError.class", + "/modules/java.base/java/lang/StackOverflowError.class", + "/modules/java.base/java/lang/IllegalMonitorStateException.class", + "/modules/java.base/java/lang/ref/Reference.class", + "/modules/java.base/java/lang/IllegalCallerException.class", + "/modules/java.base/java/lang/ref/SoftReference.class", + "/modules/java.base/java/lang/ref/WeakReference.class", + "/modules/java.base/java/lang/ref/FinalReference.class", + "/modules/java.base/java/lang/ref/PhantomReference.class", + "/modules/java.base/java/lang/ref/Finalizer.class", + "/modules/java.base/java/lang/Runnable.class", + "/modules/java.base/java/lang/Thread.class", + "/modules/java.base/java/lang/Thread$FieldHolder.class", + "/modules/java.base/java/lang/Thread$Constants.class", + "/modules/java.base/java/lang/Thread$UncaughtExceptionHandler.class", + "/modules/java.base/java/lang/ThreadGroup.class", + "/modules/java.base/java/lang/BaseVirtualThread.class", + "/modules/java.base/java/lang/VirtualThread.class", + "/modules/java.base/java/lang/ThreadBuilders$BoundVirtualThread.class", + "/modules/java.base/java/util/Map.class", + "/modules/java.base/java/util/Dictionary.class", + "/modules/java.base/java/util/Hashtable.class", + "/modules/java.base/java/util/Properties.class", + "/modules/java.base/java/lang/Module.class", + "/modules/java.base/java/lang/reflect/AccessibleObject.class", + "/modules/java.base/java/lang/reflect/Member.class", + "/modules/java.base/java/lang/reflect/Field.class", + "/modules/java.base/java/lang/reflect/Parameter.class", + "/modules/java.base/java/lang/reflect/Executable.class", + "/modules/java.base/java/lang/reflect/Method.class", + "/modules/java.base/java/lang/reflect/Constructor.class", + "/modules/java.base/jdk/internal/vm/ContinuationScope.class", + "/modules/java.base/jdk/internal/vm/Continuation.class", + "/modules/java.base/jdk/internal/vm/StackChunk.class", + "/modules/java.base/jdk/internal/reflect/MethodAccessor.class", + "/modules/java.base/jdk/internal/reflect/MethodAccessorImpl.class", + "/modules/java.base/jdk/internal/reflect/ConstantPool.class", + "/modules/java.base/java/lang/annotation/Annotation.class", + "/modules/java.base/jdk/internal/reflect/CallerSensitive.class", + "/modules/java.base/jdk/internal/reflect/ConstructorAccessor.class", + "/modules/java.base/jdk/internal/reflect/ConstructorAccessorImpl.class", + "/modules/java.base/jdk/internal/reflect/DirectConstructorHandleAccessor$NativeAccessor.class", + "/modules/java.base/java/lang/invoke/MethodHandle.class", + "/modules/java.base/java/lang/invoke/DirectMethodHandle.class", + "/modules/java.base/java/lang/invoke/VarHandle.class", + "/modules/java.base/java/lang/invoke/MemberName.class", + "/modules/java.base/java/lang/invoke/ResolvedMethodName.class", + "/modules/java.base/java/lang/invoke/MethodHandleNatives.class", + "/modules/java.base/java/lang/invoke/LambdaForm.class", + "/modules/java.base/java/lang/invoke/TypeDescriptor$OfMethod.class", + "/modules/java.base/java/lang/invoke/MethodType.class", + "/modules/java.base/java/lang/BootstrapMethodError.class", + "/modules/java.base/java/lang/invoke/CallSite.class", + "/modules/java.base/jdk/internal/foreign/abi/NativeEntryPoint.class", + "/modules/java.base/jdk/internal/foreign/abi/ABIDescriptor.class", + "/modules/java.base/jdk/internal/foreign/abi/VMStorage.class", + "/modules/java.base/jdk/internal/foreign/abi/UpcallLinker$CallRegs.class", + "/modules/java.base/java/lang/invoke/ConstantCallSite.class", + "/modules/java.base/java/lang/invoke/MutableCallSite.class", + "/modules/java.base/java/lang/invoke/VolatileCallSite.class", + "/modules/java.base/java/lang/AssertionStatusDirectives.class", + "/modules/java.base/java/lang/Appendable.class", + "/modules/java.base/java/lang/AbstractStringBuilder.class", + "/modules/java.base/java/lang/StringBuffer.class", + "/modules/java.base/java/lang/StringBuilder.class", + "/modules/java.base/jdk/internal/misc/UnsafeConstants.class", + "/modules/java.base/jdk/internal/misc/Unsafe.class", + "/modules/java.base/jdk/internal/module/Modules.class", + "/modules/java.base/java/lang/AutoCloseable.class", + "/modules/java.base/java/io/Closeable.class", + "/modules/java.base/java/io/InputStream.class", + "/modules/java.base/java/io/ByteArrayInputStream.class", + "/modules/java.base/java/net/URL.class", + "/modules/java.base/java/lang/Enum.class", + "/modules/java.base/java/util/jar/Manifest.class", + "/modules/java.base/jdk/internal/loader/BuiltinClassLoader.class", + "/modules/java.base/jdk/internal/loader/ClassLoaders.class", + "/modules/java.base/jdk/internal/loader/ClassLoaders$AppClassLoader.class", + "/modules/java.base/jdk/internal/loader/ClassLoaders$PlatformClassLoader.class", + "/modules/java.base/java/security/CodeSource.class", + "/modules/java.base/java/util/concurrent/ConcurrentMap.class", + "/modules/java.base/java/util/AbstractMap.class", + "/modules/java.base/java/util/concurrent/ConcurrentHashMap.class", + "/modules/java.base/java/lang/Iterable.class", + "/modules/java.base/java/util/Collection.class", + "/modules/java.base/java/util/SequencedCollection.class", + "/modules/java.base/java/util/List.class", + "/modules/java.base/java/util/RandomAccess.class", + "/modules/java.base/java/util/AbstractCollection.class", + "/modules/java.base/java/util/AbstractList.class", + "/modules/java.base/java/util/ArrayList.class", + "/modules/java.base/java/lang/StackTraceElement.class", + "/modules/java.base/java/nio/Buffer.class", + "/modules/java.base/java/lang/StackWalker.class", + "/modules/java.base/java/lang/StackStreamFactory$AbstractStackWalker.class", + "/modules/java.base/java/lang/StackWalker$StackFrame.class", + "/modules/java.base/java/lang/ClassFrameInfo.class", + "/modules/java.base/java/lang/StackFrameInfo.class", + "/modules/java.base/java/lang/LiveStackFrame.class", + "/modules/java.base/java/lang/LiveStackFrameInfo.class", + "/modules/java.base/java/util/concurrent/locks/AbstractOwnableSynchronizer.class", + "/modules/java.base/java/lang/Boolean.class", + "/modules/java.base/java/lang/Character.class", + "/modules/java.base/java/lang/Number.class", + "/modules/java.base/java/lang/Float.class", + "/modules/java.base/java/lang/Double.class", + "/modules/java.base/java/lang/Byte.class", + "/modules/java.base/java/lang/Short.class", + "/modules/java.base/java/lang/Integer.class", + "/modules/java.base/java/lang/Long.class", + "/modules/java.base/java/lang/Void.class", + "/modules/java.base/java/util/Iterator.class", + "/modules/java.base/java/lang/reflect/RecordComponent.class", + "/modules/java.base/jdk/internal/vm/vector/VectorSupport.class", + "/modules/java.base/jdk/internal/vm/vector/VectorSupport$VectorPayload.class", + "/modules/java.base/jdk/internal/vm/vector/VectorSupport$Vector.class", + "/modules/java.base/jdk/internal/vm/vector/VectorSupport$VectorMask.class", + "/modules/java.base/jdk/internal/vm/vector/VectorSupport$VectorShuffle.class", + "/modules/java.base/jdk/internal/vm/FillerObject.class", + "/modules/java.base/java/lang/NullPointerException.class", + "/modules/java.base/java/lang/ArithmeticException.class", + "/modules/java.base/java/lang/IndexOutOfBoundsException.class", + "/modules/java.base/java/lang/ArrayIndexOutOfBoundsException.class", + "/modules/java.base/java/io/ObjectStreamField.class", + "/modules/java.base/java/util/Comparator.class", + "/modules/java.base/java/lang/String$CaseInsensitiveComparator.class", + "/modules/java.base/jdk/internal/misc/VM.class", + "/modules/java.base/java/lang/Module$ArchivedData.class", + "/modules/java.base/jdk/internal/misc/CDS.class", + "/modules/java.base/java/util/Set.class", + "/modules/java.base/java/util/ImmutableCollections$AbstractImmutableCollection.class", + "/modules/java.base/java/util/ImmutableCollections$AbstractImmutableSet.class", + "/modules/java.base/java/util/ImmutableCollections$Set12.class", + "/modules/java.base/java/util/Objects.class", + "/modules/java.base/java/util/ImmutableCollections.class", + "/modules/java.base/java/util/ImmutableCollections$AbstractImmutableList.class", + "/modules/java.base/java/util/ImmutableCollections$ListN.class", + "/modules/java.base/java/util/ImmutableCollections$SetN.class", + "/modules/java.base/java/util/ImmutableCollections$AbstractImmutableMap.class", + "/modules/java.base/java/util/ImmutableCollections$MapN.class", + "/modules/java.base/jdk/internal/access/JavaLangReflectAccess.class", + "/modules/java.base/java/lang/reflect/ReflectAccess.class", + "/modules/java.base/jdk/internal/access/SharedSecrets.class", + "/modules/java.base/jdk/internal/reflect/ReflectionFactory.class", + "/modules/java.base/java/io/ObjectStreamClass.class", + "/modules/java.base/java/lang/Math.class", + "/modules/java.base/jdk/internal/reflect/ReflectionFactory$Config.class", + "/modules/java.base/jdk/internal/access/JavaLangRefAccess.class", + "/modules/java.base/java/lang/ref/ReferenceQueue.class", + "/modules/java.base/java/lang/ref/ReferenceQueue$Null.class", + "/modules/java.base/java/lang/ref/ReferenceQueue$Lock.class", + "/modules/java.base/jdk/internal/access/JavaLangAccess.class", + "/modules/java.base/jdk/internal/util/SystemProps.class", + "/modules/java.base/jdk/internal/util/SystemProps$Raw.class", + "/modules/java.base/java/nio/charset/Charset.class", + "/modules/java.base/java/nio/charset/spi/CharsetProvider.class", + "/modules/java.base/sun/nio/cs/StandardCharsets.class", + "/modules/java.base/java/lang/StringLatin1.class", + "/modules/java.base/sun/nio/cs/HistoricallyNamedCharset.class", + "/modules/java.base/sun/nio/cs/Unicode.class", + "/modules/java.base/sun/nio/cs/UTF_8.class", + "/modules/java.base/java/util/HashMap.class", + "/modules/java.base/java/lang/StrictMath.class", + "/modules/java.base/jdk/internal/util/ArraysSupport.class", + "/modules/java.base/java/util/Map$Entry.class", + "/modules/java.base/java/util/HashMap$Node.class", + "/modules/java.base/java/util/LinkedHashMap$Entry.class", + "/modules/java.base/java/util/HashMap$TreeNode.class", + "/modules/java.base/java/lang/StringConcatHelper.class", + "/modules/java.base/java/lang/VersionProps.class", + "/modules/java.base/java/lang/Runtime.class", + "/modules/java.base/java/util/concurrent/locks/Lock.class", + "/modules/java.base/java/util/concurrent/locks/ReentrantLock.class", + "/modules/java.base/java/util/concurrent/ConcurrentHashMap$Segment.class", + "/modules/java.base/java/util/concurrent/ConcurrentHashMap$CounterCell.class", + "/modules/java.base/java/util/concurrent/ConcurrentHashMap$Node.class", + "/modules/java.base/java/util/concurrent/locks/LockSupport.class", + "/modules/java.base/java/util/concurrent/ConcurrentHashMap$ReservationNode.class", + "/modules/java.base/java/util/AbstractSet.class", + "/modules/java.base/java/util/HashMap$EntrySet.class", + "/modules/java.base/java/util/HashMap$HashIterator.class", + "/modules/java.base/java/util/HashMap$EntryIterator.class", + "/modules/java.base/jdk/internal/util/StaticProperty.class", + "/modules/java.base/java/io/FileInputStream.class", + "/modules/java.base/java/lang/System$In.class", + "/modules/java.base/java/io/FileDescriptor.class", + "/modules/java.base/jdk/internal/access/JavaIOFileDescriptorAccess.class", + "/modules/java.base/java/io/Flushable.class", + "/modules/java.base/java/io/OutputStream.class", + "/modules/java.base/java/io/FileOutputStream.class", + "/modules/java.base/java/lang/System$Out.class", + "/modules/java.base/java/io/FilterInputStream.class", + "/modules/java.base/java/io/BufferedInputStream.class", + "/modules/java.base/java/io/FilterOutputStream.class", + "/modules/java.base/java/io/PrintStream.class", + "/modules/java.base/java/io/BufferedOutputStream.class", + "/modules/java.base/java/io/Writer.class", + "/modules/java.base/java/io/OutputStreamWriter.class", + "/modules/java.base/sun/nio/cs/StreamEncoder.class", + "/modules/java.base/java/nio/charset/CharsetEncoder.class", + "/modules/java.base/sun/nio/cs/UTF_8$Encoder.class", + "/modules/java.base/java/nio/charset/CodingErrorAction.class", + "/modules/java.base/java/util/Arrays.class", + "/modules/java.base/java/nio/ByteBuffer.class", + "/modules/java.base/jdk/internal/misc/ScopedMemoryAccess.class", + "/modules/java.base/java/util/function/Function.class", + "/modules/java.base/jdk/internal/util/Preconditions.class", + "/modules/java.base/java/util/function/BiFunction.class", + "/modules/java.base/jdk/internal/access/JavaNioAccess.class", + "/modules/java.base/java/nio/HeapByteBuffer.class", + "/modules/java.base/java/nio/ByteOrder.class", + "/modules/java.base/java/io/BufferedWriter.class", + "/modules/java.base/java/lang/Terminator.class", + "/modules/java.base/jdk/internal/misc/Signal$Handler.class", + "/modules/java.base/jdk/internal/misc/Signal.class", + "/modules/java.base/java/util/Hashtable$Entry.class", + "/modules/java.base/jdk/internal/misc/Signal$NativeHandler.class", + "/modules/java.base/java/lang/Integer$IntegerCache.class", + "/modules/java.base/jdk/internal/misc/OSEnvironment.class", + "/modules/java.base/java/lang/Thread$State.class", + "/modules/java.base/java/lang/ref/Reference$ReferenceHandler.class", + "/modules/java.base/java/lang/Thread$ThreadIdentifiers.class", + "/modules/java.base/java/lang/ref/Finalizer$FinalizerThread.class", + "/modules/java.base/jdk/internal/ref/Cleaner.class", + "/modules/java.base/java/util/Collections.class", + "/modules/java.base/java/util/Collections$EmptySet.class", + "/modules/java.base/java/util/Collections$EmptyList.class", + "/modules/java.base/java/util/Collections$EmptyMap.class", + "/modules/java.base/java/lang/IllegalArgumentException.class", + "/modules/java.base/java/lang/invoke/MethodHandleStatics.class", + "/modules/java.base/java/lang/reflect/ClassFileFormatVersion.class", + "/modules/java.base/java/lang/CharacterData.class", + "/modules/java.base/java/lang/CharacterDataLatin1.class", + "/modules/java.base/jdk/internal/util/ClassFileDumper.class", + "/modules/java.base/java/util/HexFormat.class", + "/modules/java.base/java/lang/Character$CharacterCache.class", + "/modules/java.base/java/util/concurrent/atomic/AtomicInteger.class", + "/modules/java.base/jdk/internal/module/ModuleBootstrap.class", + "/modules/java.base/java/lang/module/ModuleDescriptor.class", + "/modules/java.base/java/lang/invoke/MethodHandles.class", + "/modules/java.base/java/lang/invoke/MemberName$Factory.class", + "/modules/java.base/jdk/internal/reflect/Reflection.class", + "/modules/java.base/java/lang/invoke/MethodHandles$Lookup.class", + "/modules/java.base/java/util/ImmutableCollections$MapN$MapNIterator.class", + "/modules/java.base/java/util/KeyValueHolder.class", + "/modules/java.base/sun/invoke/util/VerifyAccess.class", + "/modules/java.base/java/lang/reflect/Modifier.class", + "/modules/java.base/jdk/internal/access/JavaLangModuleAccess.class", + "/modules/java.base/java/io/File.class", + "/modules/java.base/java/io/DefaultFileSystem.class", + "/modules/java.base/java/io/FileSystem.class", + "/modules/java.base/java/io/UnixFileSystem.class", + "/modules/java.base/jdk/internal/util/DecimalDigits.class", + "/modules/java.base/jdk/internal/module/ModulePatcher.class", + "/modules/java.base/jdk/internal/module/ModuleBootstrap$IllegalNativeAccess.class", + "/modules/java.base/java/util/HashSet.class", + "/modules/java.base/jdk/internal/module/ModuleLoaderMap.class", + "/modules/java.base/jdk/internal/module/ModuleLoaderMap$Modules.class", + "/modules/java.base/jdk/internal/module/ModuleBootstrap$Counters.class", + "/modules/java.base/jdk/internal/module/ArchivedBootLayer.class", + "/modules/java.base/jdk/internal/module/ArchivedModuleGraph.class", + "/modules/java.base/jdk/internal/module/SystemModuleFinders.class", + "/modules/java.base/java/net/URI.class", + "/modules/java.base/jdk/internal/access/JavaNetUriAccess.class", + "/modules/java.base/jdk/internal/module/SystemModulesMap.class", + "/modules/java.base/jdk/internal/module/SystemModules.class", + "/modules/java.base/jdk/internal/module/ExplodedSystemModules.class", + "/modules/java.base/java/nio/file/Watchable.class", + "/modules/java.base/java/nio/file/Path.class", + "/modules/java.base/java/nio/file/FileSystems.class", + "/modules/java.base/sun/nio/fs/DefaultFileSystemProvider.class", + "/modules/java.base/java/nio/file/spi/FileSystemProvider.class", + "/modules/java.base/sun/nio/fs/AbstractFileSystemProvider.class", + "/modules/java.base/sun/nio/fs/UnixFileSystemProvider.class", + "/modules/java.base/sun/nio/fs/LinuxFileSystemProvider.class", + "/modules/java.base/java/nio/file/OpenOption.class", + "/modules/java.base/java/nio/file/StandardOpenOption.class", + "/modules/java.base/java/nio/file/FileSystem.class", + "/modules/java.base/sun/nio/fs/UnixFileSystem.class", + "/modules/java.base/sun/nio/fs/LinuxFileSystem.class", + "/modules/java.base/sun/nio/fs/UnixPath.class", + "/modules/java.base/sun/nio/fs/Util.class", + "/modules/java.base/java/lang/StringCoding.class", + "/modules/java.base/sun/nio/fs/UnixNativeDispatcher.class", + "/modules/java.base/jdk/internal/loader/BootLoader.class", + "/modules/java.base/java/lang/Module$EnableNativeAccess.class", + "/modules/java.base/jdk/internal/loader/NativeLibraries.class", + "/modules/java.base/jdk/internal/loader/ClassLoaderHelper.class", + "/modules/java.base/java/util/concurrent/ConcurrentHashMap$CollectionView.class", + "/modules/java.base/java/util/concurrent/ConcurrentHashMap$KeySetView.class", + "/modules/java.base/jdk/internal/loader/NativeLibraries$LibraryPaths.class", + "/modules/java.base/java/io/File$PathStatus.class", + "/modules/java.base/jdk/internal/loader/NativeLibraries$CountedLock.class", + "/modules/java.base/java/util/concurrent/locks/AbstractQueuedSynchronizer.class", + "/modules/java.base/java/util/concurrent/locks/ReentrantLock$Sync.class", + "/modules/java.base/java/util/concurrent/locks/ReentrantLock$NonfairSync.class", + "/modules/java.base/jdk/internal/loader/NativeLibraries$NativeLibraryContext.class", + "/modules/java.base/java/util/Queue.class", + "/modules/java.base/java/util/Deque.class", + "/modules/java.base/java/util/ArrayDeque.class", + "/modules/java.base/java/util/ArrayDeque$DeqIterator.class", + "/modules/java.base/jdk/internal/loader/NativeLibrary.class", + "/modules/java.base/jdk/internal/loader/NativeLibraries$NativeLibraryImpl.class", + "/modules/java.base/java/security/cert/Certificate.class", + "/modules/java.base/java/util/concurrent/ConcurrentHashMap$ValuesView.class", + "/modules/java.base/java/util/Enumeration.class", + "/modules/java.base/java/util/concurrent/ConcurrentHashMap$Traverser.class", + "/modules/java.base/java/util/concurrent/ConcurrentHashMap$BaseIterator.class", + "/modules/java.base/java/util/concurrent/ConcurrentHashMap$ValueIterator.class", + "/modules/java.base/java/nio/file/attribute/BasicFileAttributes.class", + "/modules/java.base/java/nio/file/attribute/PosixFileAttributes.class", + "/modules/java.base/sun/nio/fs/UnixFileAttributes.class", + "/modules/java.base/sun/nio/fs/UnixFileStoreAttributes.class", + "/modules/java.base/sun/nio/fs/UnixMountEntry.class", + "/modules/java.base/java/nio/file/CopyOption.class", + "/modules/java.base/java/nio/file/LinkOption.class", + "/modules/java.base/java/nio/file/Files.class", + "/modules/java.base/sun/nio/fs/NativeBuffers.class", + "/modules/java.base/java/lang/ThreadLocal.class", + "/modules/java.base/jdk/internal/misc/CarrierThreadLocal.class", + "/modules/java.base/jdk/internal/misc/TerminatingThreadLocal.class", + "/modules/java.base/java/lang/ThreadLocal$ThreadLocalMap.class", + "/modules/java.base/java/lang/ThreadLocal$ThreadLocalMap$Entry.class", + "/modules/java.base/java/util/IdentityHashMap.class", + "/modules/java.base/java/util/Collections$SetFromMap.class", + "/modules/java.base/java/util/IdentityHashMap$KeySet.class", + "/modules/java.base/sun/nio/fs/NativeBuffer.class", + "/modules/java.base/jdk/internal/ref/CleanerFactory.class", + "/modules/java.base/java/util/concurrent/ThreadFactory.class", + "/modules/java.base/java/lang/ref/Cleaner.class", + "/modules/java.base/jdk/internal/ref/CleanerImpl.class", + "/modules/java.base/jdk/internal/ref/CleanerImpl$CleanableList.class", + "/modules/java.base/jdk/internal/ref/CleanerImpl$CleanableList$Node.class", + "/modules/java.base/java/lang/ref/Cleaner$Cleanable.class", + "/modules/java.base/jdk/internal/ref/PhantomCleanable.class", + "/modules/java.base/jdk/internal/ref/CleanerImpl$CleanerCleanable.class", + "/modules/java.base/jdk/internal/misc/InnocuousThread.class", + "/modules/java.base/sun/nio/fs/NativeBuffer$Deallocator.class", + "/modules/java.base/jdk/internal/ref/CleanerImpl$PhantomCleanableRef.class", + "/modules/java.base/java/lang/module/ModuleFinder.class", + "/modules/java.base/jdk/internal/module/ModulePath.class", + "/modules/java.base/java/util/jar/Attributes$Name.class", + "/modules/java.base/java/lang/reflect/Array.class", + "/modules/java.base/jdk/internal/perf/PerfCounter.class", + "/modules/java.base/jdk/internal/perf/Perf.class", + "/modules/java.base/sun/nio/ch/DirectBuffer.class", + "/modules/java.base/java/nio/MappedByteBuffer.class", + "/modules/java.base/java/nio/DirectByteBuffer.class", + "/modules/java.base/java/nio/Bits.class", + "/modules/java.base/java/util/concurrent/atomic/AtomicLong.class", + "/modules/java.base/jdk/internal/misc/VM$BufferPool.class", + "/modules/java.base/java/nio/LongBuffer.class", + "/modules/java.base/java/nio/DirectLongBufferU.class", + "/modules/java.base/java/util/zip/ZipConstants.class", + "/modules/java.base/java/util/zip/ZipFile.class", + "/modules/java.base/java/util/jar/JarFile.class", + "/modules/java.base/java/util/BitSet.class", + "/modules/java.base/jdk/internal/access/JavaUtilZipFileAccess.class", + "/modules/java.base/jdk/internal/access/JavaUtilJarAccess.class", + "/modules/java.base/java/util/jar/JavaUtilJarAccessImpl.class", + "/modules/java.base/java/lang/Runtime$Version.class", + "/modules/java.base/java/util/ImmutableCollections$List12.class", + "/modules/java.base/java/util/Optional.class", + "/modules/java.base/java/nio/file/attribute/DosFileAttributes.class", + "/modules/java.base/java/nio/file/attribute/AttributeView.class", + "/modules/java.base/java/nio/file/attribute/FileAttributeView.class", + "/modules/java.base/java/nio/file/attribute/BasicFileAttributeView.class", + "/modules/java.base/java/nio/file/attribute/DosFileAttributeView.class", + "/modules/java.base/java/nio/file/attribute/UserDefinedFileAttributeView.class", + "/modules/java.base/sun/nio/fs/UnixFileAttributeViews.class", + "/modules/java.base/sun/nio/fs/DynamicFileAttributeView.class", + "/modules/java.base/sun/nio/fs/AbstractBasicFileAttributeView.class", + "/modules/java.base/sun/nio/fs/UnixFileAttributeViews$Basic.class", + "/modules/java.base/sun/nio/fs/UnixFileAttributes$UnixAsBasicFileAttributes.class", + "/modules/java.base/java/nio/file/DirectoryStream$Filter.class", + "/modules/java.base/java/nio/file/Files$AcceptAllFilter.class", + "/modules/java.base/java/nio/file/DirectoryStream.class", + "/modules/java.base/java/nio/file/SecureDirectoryStream.class", + "/modules/java.base/sun/nio/fs/UnixSecureDirectoryStream.class", + "/modules/java.base/sun/nio/fs/UnixDirectoryStream.class", + "/modules/java.base/java/util/concurrent/locks/ReadWriteLock.class", + "/modules/java.base/java/util/concurrent/locks/ReentrantReadWriteLock.class", + "/modules/java.base/java/util/concurrent/locks/AbstractQueuedLongSynchronizer.class", + "/modules/java.base/java/util/concurrent/locks/ReentrantReadWriteLock$Sync.class", + "/modules/java.base/java/util/concurrent/locks/ReentrantReadWriteLock$FairSync.class", + "/modules/java.base/java/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter.class", + "/modules/java.base/java/util/concurrent/locks/ReentrantReadWriteLock$ReadLock.class", + "/modules/java.base/java/util/concurrent/locks/ReentrantReadWriteLock$WriteLock.class", + "/modules/java.base/sun/nio/fs/UnixDirectoryStream$UnixDirectoryIterator.class", + "/modules/java.base/java/nio/file/attribute/FileAttribute.class", + "/modules/java.base/sun/nio/fs/UnixFileModeAttribute.class", + "/modules/java.base/sun/nio/fs/UnixChannelFactory.class", + "/modules/java.base/sun/nio/fs/UnixChannelFactory$Flags.class", + "/modules/java.base/java/util/Collections$EmptyIterator.class", + "/modules/java.base/java/nio/channels/Channel.class", + "/modules/java.base/java/nio/channels/ReadableByteChannel.class", + "/modules/java.base/java/nio/channels/WritableByteChannel.class", + "/modules/java.base/java/nio/channels/ByteChannel.class", + "/modules/java.base/java/nio/channels/SeekableByteChannel.class", + "/modules/java.base/java/nio/channels/GatheringByteChannel.class", + "/modules/java.base/java/nio/channels/ScatteringByteChannel.class", + "/modules/java.base/java/nio/channels/InterruptibleChannel.class", + "/modules/java.base/java/nio/channels/spi/AbstractInterruptibleChannel.class", + "/modules/java.base/java/nio/channels/FileChannel.class", + "/modules/java.base/sun/nio/ch/FileChannelImpl.class", + "/modules/java.base/sun/nio/ch/NativeDispatcher.class", + "/modules/java.base/sun/nio/ch/FileDispatcher.class", + "/modules/java.base/sun/nio/ch/UnixFileDispatcherImpl.class", + "/modules/java.base/sun/nio/ch/FileDispatcherImpl.class", + "/modules/java.base/sun/nio/ch/IOUtil.class", + "/modules/java.base/sun/nio/ch/Interruptible.class", + "/modules/java.base/sun/nio/ch/NativeThreadSet.class", + "/modules/java.base/sun/nio/ch/FileChannelImpl$Closer.class", + "/modules/java.base/java/nio/channels/Channels.class", + "/modules/java.base/sun/nio/ch/Streams.class", + "/modules/java.base/sun/nio/ch/SelChImpl.class", + "/modules/java.base/java/nio/channels/NetworkChannel.class", + "/modules/java.base/java/nio/channels/SelectableChannel.class", + "/modules/java.base/java/nio/channels/spi/AbstractSelectableChannel.class", + "/modules/java.base/java/nio/channels/SocketChannel.class", + "/modules/java.base/sun/nio/ch/SocketChannelImpl.class", + "/modules/java.base/sun/nio/ch/ChannelInputStream.class", + "/modules/java.base/java/lang/invoke/LambdaMetafactory.class", + "/modules/java.base/java/util/function/Supplier.class", + "/modules/java.base/jdk/internal/util/ReferencedKeySet.class", + "/modules/java.base/jdk/internal/util/ReferencedKeyMap.class", + "/modules/java.base/jdk/internal/util/ReferenceKey.class", + "/modules/java.base/jdk/internal/util/StrongReferenceKey.class", + "/modules/java.base/java/lang/invoke/MethodTypeForm.class", + "/modules/java.base/jdk/internal/util/WeakReferenceKey.class", + "/modules/java.base/sun/invoke/util/Wrapper.class", + "/modules/java.base/sun/invoke/util/Wrapper$Format.class", + "/modules/java.base/java/lang/constant/ConstantDescs.class", + "/modules/java.base/java/lang/constant/ClassDesc.class", + "/modules/java.base/jdk/internal/constant/ClassOrInterfaceDescImpl.class", + "/modules/java.base/jdk/internal/constant/ArrayClassDescImpl.class", + "/modules/java.base/jdk/internal/constant/ConstantUtils.class", + "/modules/java.base/java/lang/constant/DirectMethodHandleDesc$Kind.class", + "/modules/java.base/java/lang/constant/MethodTypeDesc.class", + "/modules/java.base/jdk/internal/constant/MethodTypeDescImpl.class", + "/modules/java.base/java/lang/constant/MethodHandleDesc.class", + "/modules/java.base/java/lang/constant/DirectMethodHandleDesc.class", + "/modules/java.base/jdk/internal/constant/DirectMethodHandleDescImpl.class", + "/modules/java.base/java/lang/constant/DynamicConstantDesc.class", + "/modules/java.base/jdk/internal/constant/PrimitiveClassDescImpl.class", + "/modules/java.base/java/lang/constant/DynamicConstantDesc$AnonymousDynamicConstantDesc.class", + "/modules/java.base/java/lang/invoke/LambdaForm$NamedFunction.class", + "/modules/java.base/java/lang/invoke/DirectMethodHandle$Holder.class", + "/modules/java.base/sun/invoke/util/ValueConversions.class", + "/modules/java.base/java/lang/invoke/MethodHandleImpl.class", + "/modules/java.base/java/lang/invoke/Invokers.class", + "/modules/java.base/java/lang/invoke/LambdaForm$Kind.class", + "/modules/java.base/java/lang/NoSuchMethodException.class", + "/modules/java.base/java/lang/invoke/LambdaForm$BasicType.class", + "/modules/java.base/java/lang/classfile/TypeKind.class", + "/modules/java.base/java/lang/invoke/LambdaForm$Name.class", + "/modules/java.base/java/lang/invoke/LambdaForm$Holder.class", + "/modules/java.base/java/lang/invoke/InvokerBytecodeGenerator.class", + "/modules/java.base/java/lang/classfile/AnnotationElement.class", + "/modules/java.base/java/lang/classfile/Annotation.class", + "/modules/java.base/java/lang/classfile/constantpool/ConstantPool.class", + "/modules/java.base/java/lang/classfile/constantpool/ConstantPoolBuilder.class", + "/modules/java.base/jdk/internal/classfile/impl/TemporaryConstantPool.class", + "/modules/java.base/java/lang/classfile/constantpool/PoolEntry.class", + "/modules/java.base/java/lang/classfile/constantpool/AnnotationConstantValueEntry.class", + "/modules/java.base/java/lang/classfile/constantpool/Utf8Entry.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$Utf8EntryImpl.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$Utf8EntryImpl$State.class", + "/modules/java.base/jdk/internal/classfile/impl/AnnotationImpl.class", + "/modules/java.base/java/lang/classfile/ClassFileElement.class", + "/modules/java.base/java/lang/classfile/Attribute.class", + "/modules/java.base/java/lang/classfile/ClassElement.class", + "/modules/java.base/java/lang/classfile/MethodElement.class", + "/modules/java.base/java/lang/classfile/FieldElement.class", + "/modules/java.base/java/lang/classfile/attribute/RuntimeVisibleAnnotationsAttribute.class", + "/modules/java.base/jdk/internal/classfile/impl/Util$Writable.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractElement.class", + "/modules/java.base/jdk/internal/classfile/impl/UnboundAttribute.class", + "/modules/java.base/jdk/internal/classfile/impl/UnboundAttribute$UnboundRuntimeVisibleAnnotationsAttribute.class", + "/modules/java.base/java/lang/classfile/Attributes.class", + "/modules/java.base/java/lang/classfile/AttributeMapper.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractAttributeMapper.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractAttributeMapper$RuntimeVisibleAnnotationsMapper.class", + "/modules/java.base/java/lang/classfile/AttributeMapper$AttributeStability.class", + "/modules/java.base/java/lang/invoke/MethodHandleImpl$Intrinsic.class", + "/modules/java.base/jdk/internal/classfile/impl/SplitConstantPool.class", + "/modules/java.base/java/lang/classfile/BootstrapMethodEntry.class", + "/modules/java.base/jdk/internal/classfile/impl/BootstrapMethodEntryImpl.class", + "/modules/java.base/jdk/internal/classfile/impl/EntryMap.class", + "/modules/java.base/jdk/internal/classfile/impl/Util.class", + "/modules/java.base/java/lang/classfile/constantpool/LoadableConstantEntry.class", + "/modules/java.base/java/lang/classfile/constantpool/ClassEntry.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractRefEntry.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractNamedEntry.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$ClassEntryImpl.class", + "/modules/java.base/java/util/function/Consumer.class", + "/modules/java.base/java/lang/classfile/ClassFile.class", + "/modules/java.base/jdk/internal/classfile/impl/ClassFileImpl.class", + "/modules/java.base/java/lang/classfile/ClassFileBuilder.class", + "/modules/java.base/java/lang/classfile/ClassBuilder.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractDirectBuilder.class", + "/modules/java.base/jdk/internal/classfile/impl/DirectClassBuilder.class", + "/modules/java.base/jdk/internal/classfile/impl/AttributeHolder.class", + "/modules/java.base/java/lang/classfile/Superclass.class", + "/modules/java.base/jdk/internal/classfile/impl/SuperclassImpl.class", + "/modules/java.base/java/lang/classfile/attribute/SourceFileAttribute.class", + "/modules/java.base/jdk/internal/classfile/impl/UnboundAttribute$UnboundSourceFileAttribute.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractAttributeMapper$SourceFileMapper.class", + "/modules/java.base/jdk/internal/classfile/impl/BoundAttribute.class", + "/modules/java.base/java/lang/classfile/MethodBuilder.class", + "/modules/java.base/jdk/internal/classfile/impl/MethodInfo.class", + "/modules/java.base/jdk/internal/classfile/impl/TerminalMethodBuilder.class", + "/modules/java.base/jdk/internal/classfile/impl/DirectMethodBuilder.class", + "/modules/java.base/java/lang/classfile/constantpool/NameAndTypeEntry.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractRefsEntry.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$NameAndTypeEntryImpl.class", + "/modules/java.base/java/lang/classfile/constantpool/MemberRefEntry.class", + "/modules/java.base/java/lang/classfile/constantpool/FieldRefEntry.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$AbstractMemberRefEntry.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$FieldRefEntryImpl.class", + "/modules/java.base/java/lang/invoke/InvokerBytecodeGenerator$ClassData.class", + "/modules/java.base/java/lang/classfile/CodeBuilder.class", + "/modules/java.base/jdk/internal/classfile/impl/LabelContext.class", + "/modules/java.base/jdk/internal/classfile/impl/TerminalCodeBuilder.class", + "/modules/java.base/jdk/internal/classfile/impl/DirectCodeBuilder.class", + "/modules/java.base/java/lang/classfile/CodeElement.class", + "/modules/java.base/java/lang/classfile/PseudoInstruction.class", + "/modules/java.base/java/lang/classfile/instruction/CharacterRange.class", + "/modules/java.base/java/lang/classfile/instruction/LocalVariable.class", + "/modules/java.base/java/lang/classfile/instruction/LocalVariableType.class", + "/modules/java.base/jdk/internal/classfile/impl/DirectCodeBuilder$DeferredLabel.class", + "/modules/java.base/java/lang/classfile/BufWriter.class", + "/modules/java.base/jdk/internal/classfile/impl/BufWriterImpl.class", + "/modules/java.base/java/lang/classfile/Label.class", + "/modules/java.base/java/lang/classfile/instruction/LabelTarget.class", + "/modules/java.base/jdk/internal/classfile/impl/LabelImpl.class", + "/modules/java.base/sun/invoke/util/VerifyType.class", + "/modules/java.base/java/lang/classfile/Opcode.class", + "/modules/java.base/java/lang/classfile/Opcode$Kind.class", + "/modules/java.base/java/lang/classfile/constantpool/MethodRefEntry.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$MethodRefEntryImpl.class", + "/modules/java.base/sun/invoke/empty/Empty.class", + "/modules/java.base/jdk/internal/classfile/impl/BytecodeHelpers.class", + "/modules/java.base/jdk/internal/classfile/impl/UnboundAttribute$AdHocAttribute.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractAttributeMapper$CodeMapper.class", + "/modules/java.base/java/lang/classfile/FieldBuilder.class", + "/modules/java.base/jdk/internal/classfile/impl/TerminalFieldBuilder.class", + "/modules/java.base/jdk/internal/classfile/impl/DirectFieldBuilder.class", + "/modules/java.base/java/lang/classfile/CustomAttribute.class", + "/modules/java.base/jdk/internal/classfile/impl/AnnotationReader.class", + "/modules/java.base/java/util/ListIterator.class", + "/modules/java.base/java/util/ImmutableCollections$ListItr.class", + "/modules/java.base/jdk/internal/classfile/impl/StackMapGenerator.class", + "/modules/java.base/jdk/internal/classfile/impl/StackMapGenerator$Frame.class", + "/modules/java.base/jdk/internal/classfile/impl/StackMapGenerator$Type.class", + "/modules/java.base/jdk/internal/classfile/impl/RawBytecodeHelper.class", + "/modules/java.base/jdk/internal/classfile/impl/RawBytecodeHelper$CodeRange.class", + "/modules/java.base/jdk/internal/classfile/impl/ClassHierarchyImpl.class", + "/modules/java.base/java/lang/classfile/ClassHierarchyResolver.class", + "/modules/java.base/jdk/internal/classfile/impl/ClassHierarchyImpl$ClassLoadingClassHierarchyResolver.class", + "/modules/java.base/jdk/internal/classfile/impl/ClassHierarchyImpl$CachedClassHierarchyResolver.class", + "/modules/java.base/java/lang/classfile/ClassHierarchyResolver$ClassHierarchyInfo.class", + "/modules/java.base/jdk/internal/classfile/impl/ClassHierarchyImpl$ClassHierarchyInfoImpl.class", + "/modules/java.base/java/lang/classfile/ClassReader.class", + "/modules/java.base/jdk/internal/classfile/impl/ClassReaderImpl.class", + "/modules/java.base/jdk/internal/util/ModifiedUtf.class", + "/modules/java.base/java/lang/invoke/MethodHandles$Lookup$ClassDefiner.class", + "/modules/java.base/java/lang/IncompatibleClassChangeError.class", + "/modules/java.base/java/lang/NoSuchMethodError.class", + "/modules/java.base/java/lang/invoke/BootstrapMethodInvoker.class", + "/modules/java.base/java/lang/invoke/AbstractValidatingLambdaMetafactory.class", + "/modules/java.base/java/lang/invoke/InnerClassLambdaMetafactory.class", + "/modules/java.base/java/lang/invoke/MethodHandleInfo.class", + "/modules/java.base/java/lang/invoke/InfoFromMemberName.class", + "/modules/java.base/java/util/ImmutableCollections$Access.class", + "/modules/java.base/jdk/internal/access/JavaUtilCollectionAccess.class", + "/modules/java.base/java/lang/classfile/Interfaces.class", + "/modules/java.base/jdk/internal/classfile/impl/InterfacesImpl.class", + "/modules/java.base/java/lang/invoke/TypeConvertingMethodAdapter.class", + "/modules/java.base/java/lang/invoke/DirectMethodHandle$Constructor.class", + "/modules/java.base/jdk/internal/access/JavaLangInvokeAccess.class", + "/modules/java.base/java/lang/invoke/VarHandle$AccessMode.class", + "/modules/java.base/java/lang/invoke/VarHandle$AccessType.class", + "/modules/java.base/java/lang/invoke/Invokers$Holder.class", + "/modules/java.base/jdk/internal/module/ModuleInfo.class", + "/modules/java.base/java/io/DataInput.class", + "/modules/java.base/java/io/DataInputStream.class", + "/modules/java.base/jdk/internal/module/ModuleInfo$CountingDataInput.class", + "/modules/java.base/sun/nio/ch/NativeThread.class", + "/modules/java.base/jdk/internal/misc/Blocker.class", + "/modules/java.base/sun/nio/ch/Util.class", + "/modules/java.base/sun/nio/ch/Util$BufferCache.class", + "/modules/java.base/sun/nio/ch/IOStatus.class", + "/modules/java.base/jdk/internal/util/ByteArray.class", + "/modules/java.base/java/lang/invoke/VarHandles.class", + "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsShorts$ByteArrayViewVarHandle.class", + "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsShorts$ArrayHandle.class", + "/modules/java.base/java/lang/invoke/VarHandleGuards.class", + "/modules/java.base/java/lang/invoke/VarForm.class", + "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsChars$ByteArrayViewVarHandle.class", + "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsChars$ArrayHandle.class", + "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsInts$ByteArrayViewVarHandle.class", + "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsInts$ArrayHandle.class", + "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsFloats$ByteArrayViewVarHandle.class", + "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsFloats$ArrayHandle.class", + "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsLongs$ByteArrayViewVarHandle.class", + "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsLongs$ArrayHandle.class", + "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsDoubles$ByteArrayViewVarHandle.class", + "/modules/java.base/java/lang/invoke/VarHandleByteArrayAsDoubles$ArrayHandle.class", + "/modules/java.base/java/lang/invoke/VarHandle$AccessDescriptor.class", + "/modules/java.base/jdk/internal/module/ModuleInfo$ConstantPool.class", + "/modules/java.base/jdk/internal/module/ModuleInfo$ConstantPool$Entry.class", + "/modules/java.base/jdk/internal/module/ModuleInfo$ConstantPool$IndexEntry.class", + "/modules/java.base/java/nio/charset/StandardCharsets.class", + "/modules/java.base/sun/nio/cs/US_ASCII.class", + "/modules/java.base/sun/nio/cs/ISO_8859_1.class", + "/modules/java.base/sun/nio/cs/UTF_16BE.class", + "/modules/java.base/sun/nio/cs/UTF_16LE.class", + "/modules/java.base/sun/nio/cs/UTF_16.class", + "/modules/java.base/sun/nio/cs/UTF_32BE.class", + "/modules/java.base/sun/nio/cs/UTF_32LE.class", + "/modules/java.base/sun/nio/cs/UTF_32.class", + "/modules/java.base/jdk/internal/module/ModuleInfo$ConstantPool$ValueEntry.class", + "/modules/java.base/java/lang/module/ModuleDescriptor$Builder.class", + "/modules/java.base/java/lang/module/ModuleDescriptor$Modifier.class", + "/modules/java.base/java/lang/reflect/AccessFlag.class", + "/modules/java.base/java/lang/reflect/AccessFlag$Location.class", + "/modules/java.base/java/lang/module/ModuleDescriptor$Requires$Modifier.class", + "/modules/java.base/java/lang/module/ModuleDescriptor$Requires.class", + "/modules/java.base/java/util/HashMap$KeySet.class", + "/modules/java.base/java/util/HashMap$KeyIterator.class", + "/modules/java.base/jdk/internal/module/Checks.class", + "/modules/java.base/java/util/ArrayList$Itr.class", + "/modules/java.base/java/lang/module/ModuleDescriptor$Provides.class", + "/modules/java.base/java/util/Collections$UnmodifiableCollection.class", + "/modules/java.base/java/util/Collections$UnmodifiableSet.class", + "/modules/java.base/java/util/HashMap$Values.class", + "/modules/java.base/java/util/HashMap$ValueIterator.class", + "/modules/java.base/java/util/ImmutableCollections$SetN$SetNIterator.class", + "/modules/java.base/jdk/internal/module/ModuleInfo$Attributes.class", + "/modules/java.base/jdk/internal/module/ModuleReferences.class", + "/modules/java.base/java/lang/module/ModuleReader.class", + "/modules/java.base/sun/nio/fs/UnixUriUtils.class", + "/modules/java.base/java/net/URI$Parser.class", + "/modules/java.base/java/lang/module/ModuleReference.class", + "/modules/java.base/jdk/internal/module/ModuleReferenceImpl.class", + "/modules/java.base/java/lang/module/ModuleDescriptor$Exports.class", + "/modules/java.base/java/lang/module/ModuleDescriptor$Opens.class", + "/modules/java.base/sun/nio/fs/UnixException.class", + "/modules/java.base/java/io/IOException.class", + "/modules/java.base/jdk/internal/loader/ArchivedClassLoaders.class", + "/modules/java.base/jdk/internal/loader/ClassLoaders$BootClassLoader.class", + "/modules/java.base/java/lang/ClassLoader$ParallelLoaders.class", + "/modules/java.base/java/util/WeakHashMap.class", + "/modules/java.base/java/util/WeakHashMap$Entry.class", + "/modules/java.base/java/util/WeakHashMap$KeySet.class", + "/modules/java.base/java/security/Principal.class", + "/modules/java.base/jdk/internal/loader/URLClassPath.class", + "/modules/java.base/java/net/URLStreamHandlerFactory.class", + "/modules/java.base/java/net/URL$DefaultFactory.class", + "/modules/java.base/jdk/internal/access/JavaNetURLAccess.class", + "/modules/java.base/sun/net/www/ParseUtil.class", + "/modules/java.base/java/net/URLStreamHandler.class", + "/modules/java.base/sun/net/www/protocol/file/Handler.class", + "/modules/java.base/sun/net/util/IPAddressUtil.class", + "/modules/java.base/sun/net/util/IPAddressUtil$MASKS.class", + "/modules/java.base/sun/net/www/protocol/jar/Handler.class", + "/modules/java.base/jdk/internal/module/ServicesCatalog.class", + "/modules/java.base/jdk/internal/loader/AbstractClassLoaderValue.class", + "/modules/java.base/jdk/internal/loader/ClassLoaderValue.class", + "/modules/java.base/jdk/internal/loader/BuiltinClassLoader$LoadedModule.class", + "/modules/java.base/jdk/internal/module/DefaultRoots.class", + "/modules/java.base/java/util/Spliterator.class", + "/modules/java.base/java/util/HashMap$HashMapSpliterator.class", + "/modules/java.base/java/util/HashMap$ValueSpliterator.class", + "/modules/java.base/java/util/stream/StreamSupport.class", + "/modules/java.base/java/util/stream/BaseStream.class", + "/modules/java.base/java/util/stream/Stream.class", + "/modules/java.base/java/util/stream/PipelineHelper.class", + "/modules/java.base/java/util/stream/AbstractPipeline.class", + "/modules/java.base/java/util/stream/ReferencePipeline.class", + "/modules/java.base/java/util/stream/ReferencePipeline$Head.class", + "/modules/java.base/java/util/stream/StreamOpFlag.class", + "/modules/java.base/java/util/stream/StreamOpFlag$Type.class", + "/modules/java.base/java/util/stream/StreamOpFlag$MaskBuilder.class", + "/modules/java.base/java/util/EnumMap.class", + "/modules/java.base/java/lang/Class$ReflectionData.class", + "/modules/java.base/java/lang/Class$Atomic.class", + "/modules/java.base/java/lang/PublicMethods$MethodList.class", + "/modules/java.base/java/lang/PublicMethods$Key.class", + "/modules/java.base/sun/reflect/annotation/AnnotationParser.class", + "/modules/java.base/jdk/internal/reflect/MethodHandleAccessorFactory.class", + "/modules/java.base/jdk/internal/reflect/MethodHandleAccessorFactory$LazyStaticHolder.class", + "/modules/java.base/java/lang/invoke/BoundMethodHandle.class", + "/modules/java.base/java/lang/invoke/ClassSpecializer.class", + "/modules/java.base/java/lang/invoke/BoundMethodHandle$Specializer.class", + "/modules/java.base/jdk/internal/vm/annotation/Stable.class", + "/modules/java.base/java/lang/invoke/ClassSpecializer$SpeciesData.class", + "/modules/java.base/java/lang/invoke/BoundMethodHandle$SpeciesData.class", + "/modules/java.base/java/lang/invoke/ClassSpecializer$Factory.class", + "/modules/java.base/java/lang/invoke/BoundMethodHandle$Specializer$Factory.class", + "/modules/java.base/java/lang/invoke/SimpleMethodHandle.class", + "/modules/java.base/java/lang/NoSuchFieldException.class", + "/modules/java.base/java/lang/invoke/BoundMethodHandle$Species_L.class", + "/modules/java.base/java/lang/invoke/DirectMethodHandle$Accessor.class", + "/modules/java.base/java/lang/invoke/DelegatingMethodHandle.class", + "/modules/java.base/java/lang/invoke/DelegatingMethodHandle$Holder.class", + "/modules/java.base/java/lang/invoke/LambdaFormEditor.class", + "/modules/java.base/java/lang/invoke/LambdaFormEditor$TransformKey.class", + "/modules/java.base/java/lang/invoke/LambdaFormBuffer.class", + "/modules/java.base/java/lang/invoke/LambdaFormEditor$Transform.class", + "/modules/java.base/jdk/internal/reflect/DirectMethodHandleAccessor.class", + "/modules/java.base/java/util/stream/Collectors.class", + "/modules/java.base/java/util/stream/Collector$Characteristics.class", + "/modules/java.base/java/util/EnumSet.class", + "/modules/java.base/java/util/RegularEnumSet.class", + "/modules/java.base/java/util/stream/Collector.class", + "/modules/java.base/java/util/stream/Collectors$CollectorImpl.class", + "/modules/java.base/java/util/function/BiConsumer.class", + "/modules/java.base/java/lang/invoke/DirectMethodHandle$Interface.class", + "/modules/java.base/java/lang/classfile/constantpool/InterfaceMethodRefEntry.class", + "/modules/java.base/jdk/internal/classfile/impl/AbstractPoolEntry$InterfaceMethodRefEntryImpl.class", + "/modules/java.base/java/util/function/BinaryOperator.class", + "/modules/java.base/java/util/stream/ReduceOps.class", + "/modules/java.base/java/util/stream/TerminalOp.class", + "/modules/java.base/java/util/stream/ReduceOps$ReduceOp.class", + "/modules/java.base/java/util/stream/StreamShape.class", + "/modules/java.base/java/util/stream/Sink.class", + "/modules/java.base/java/util/stream/TerminalSink.class", + "/modules/java.base/java/util/stream/ReduceOps$AccumulatingSink.class", + "/modules/java.base/java/util/stream/ReduceOps$Box.class", + "/modules/java.base/java/util/HashMap$KeySpliterator.class", + "/modules/java.base/java/util/function/Predicate.class", + "/modules/java.base/java/util/stream/ReferencePipeline$StatelessOp.class", + "/modules/java.base/java/util/stream/Sink$ChainedReference.class", + "/modules/java.base/jdk/internal/module/ModuleResolution.class", + "/modules/java.base/java/util/stream/FindOps.class", + "/modules/java.base/java/util/stream/FindOps$FindSink.class", + "/modules/java.base/java/util/stream/FindOps$FindSink$OfRef.class", + "/modules/java.base/java/util/stream/FindOps$FindOp.class", + "/modules/java.base/java/util/Spliterators.class", + "/modules/java.base/java/util/Spliterators$IteratorSpliterator.class", + "/modules/java.base/java/lang/module/Configuration.class", + "/modules/java.base/java/lang/module/Resolver.class", + "/modules/java.base/java/lang/ModuleLayer.class", + "/modules/java.base/java/util/SequencedSet.class", + "/modules/java.base/java/util/LinkedHashSet.class", + "/modules/java.base/java/util/SequencedMap.class", + "/modules/java.base/java/util/LinkedHashMap.class", + "/modules/java.base/java/lang/module/ResolvedModule.class", + "/modules/java.base/jdk/internal/module/ModuleLoaderMap$Mapper.class", + "/modules/java.base/jdk/internal/loader/AbstractClassLoaderValue$Memoizer.class", + "/modules/java.base/jdk/internal/module/ServicesCatalog$ServiceProvider.class", + "/modules/java.base/java/util/concurrent/CopyOnWriteArrayList.class", + "/modules/java.base/java/lang/ModuleLayer$Controller.class", + "/modules/java.base/jdk/internal/module/ModuleBootstrap$SafeModuleFinder.class", + "/modules/java.base/jdk/internal/vm/ContinuationSupport.class", + "/modules/java.base/jdk/internal/vm/Continuation$Pinned.class", + "/modules/java.base/sun/launcher/LauncherHelper.class", + "/modules/java.base/sun/net/util/URLUtil.class", + "/modules/java.base/jdk/internal/loader/URLClassPath$Loader.class", + "/modules/java.base/jdk/internal/loader/URLClassPath$FileLoader.class", + "/modules/java.base/jdk/internal/loader/Resource.class", + "/modules/java.base/java/io/FileCleanable.class", + "/modules/java.base/sun/nio/ByteBuffered.class", + "/modules/java.base/java/security/SecureClassLoader$CodeSourceKey.class", + "/modules/java.base/java/security/PermissionCollection.class", + "/modules/java.base/java/security/Permissions.class", + "/modules/java.base/java/lang/NamedPackage.class", + "/modules/java.base/jdk/internal/misc/MethodFinder.class", + "/modules/java.base/java/lang/Readable.class", + "/modules/java.base/java/nio/CharBuffer.class", + "/modules/java.base/java/nio/HeapCharBuffer.class", + "/modules/java.base/java/nio/charset/CoderResult.class", + "/modules/java.base/java/util/IdentityHashMap$IdentityHashMapIterator.class", + "/modules/java.base/java/util/IdentityHashMap$KeyIterator.class", + "/modules/java.base/java/lang/Shutdown.class", + "/modules/java.base/java/lang/Shutdown$Lock.class"); + + private static final Pattern SPLIT_MODULE_AND_PATH = Pattern.compile("/modules/([^/]+)/(.*)"); + + private static final Map> MODULE_TO_PATHS = INIT_CLASSES.stream() + .map(name -> { + Matcher m = SPLIT_MODULE_AND_PATH.matcher(name); + if (!m.matches()) { + throw new IllegalArgumentException("Bad resource name: " + name); + } + return m.toMatchResult(); + }) + .collect(groupingBy(m -> m.group(1), mapping(m -> m.group(2), toList()))); + } }