diff --git a/make/test/BuildMicrobenchmark.gmk b/make/test/BuildMicrobenchmark.gmk index 347ca44d25f39..4946263ef4e91 100644 --- a/make/test/BuildMicrobenchmark.gmk +++ b/make/test/BuildMicrobenchmark.gmk @@ -92,6 +92,7 @@ $(eval $(call SetupJavaCompilation, BUILD_JDK_MICROBENCHMARK, \ --add-exports java.base/jdk.internal.classfile.impl=ALL-UNNAMED \ --add-exports java.base/jdk.internal.event=ALL-UNNAMED \ --add-exports java.base/jdk.internal.foreign=ALL-UNNAMED \ + --add-exports java.base/jdk.internal.jimage=ALL-UNNAMED \ --add-exports java.base/jdk.internal.misc=ALL-UNNAMED \ --add-exports java.base/jdk.internal.util=ALL-UNNAMED \ --add-exports java.base/jdk.internal.vm=ALL-UNNAMED \ 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 f12c39f3e814f..154b0d5eaa0b9 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java +++ b/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, 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 @@ -25,16 +25,15 @@ package jdk.internal.jimage; import java.io.IOException; -import java.io.InputStream; import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.nio.file.Files; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.FileTime; import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -42,9 +41,34 @@ import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; /** + * An adapter for {@link BasicImageReader} to present jimage resources in a + * file system friendly way. The jimage entries (resources, module and package + * information) are mapped into a unified hierarchy of named nodes, which serve + * as the underlying structure for the {@code JrtFileSystem} and other utilities. + * + *

This class is not a conceptual subtype of {@code BasicImageReader} and + * deliberately hides types such as {@code ImageLocation} to give a focused API + * based only on the {@link Node} type. Entries in jimage are expressed as one + * of three {@link Node} types resource nodes, directory nodes and link nodes. + * + *

When remapping jimage entries, jimage location names (e.g. {@code + * "/java.base/java/lang/Integer.class"}) are prefixed with {@code "/modules"} + * to form the names of resource nodes. This aligns with the naming of module + * entries in jimage (e.g. "/modules/java.base/java/lang"), which appear as + * directory nodes in {@code ImageReader}. + * + *

Package entries (e.g. {@code "/packages/java.lang"} appear as directory + * nodes containing link nodes, which resolve back to the root directory of the + * module in which that package exists (e.g. {@code "/modules/java.base"}). + * Unlike other nodes, the jimage file does not contain explicit entries for + * link nodes, and their existence is derived only from the contents of the + * parent directory. + * * @implNote This class needs to maintain JDK 8 source compatibility. * * It is used internally in the JDK to implement jimage/jrtfs access, @@ -60,6 +84,14 @@ private ImageReader(SharedImageReader reader) { this.reader = reader; } + /** + * Opens an image reader for a jimage file at the specified path, using the + * given byte order. + * + *

Almost all callers should use {@link #open(Path)} to obtain a reader + * with the platform native byte ordering. Using a non-native ordering is + * extremely unusual. + */ public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException { Objects.requireNonNull(imagePath); Objects.requireNonNull(byteOrder); @@ -67,6 +99,13 @@ public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOExc return SharedImageReader.open(imagePath, byteOrder); } + /** + * Opens an image reader for a jimage file at the specified path, using the + * platform native byte order. + * + *

This is the expected was to open an {@code ImageReader}, and it should + * be rare for anything other than the native byte order to be needed. + */ public static ImageReader open(Path imagePath) throws IOException { return open(imagePath, ByteOrder.nativeOrder()); } @@ -92,143 +131,93 @@ private void requireOpen() { } } - // directory management interface - public Directory getRootDirectory() throws IOException { - ensureOpen(); - return reader.getRootDirectory(); - } - - + /** + * Finds the node for the given JRT file system name. + * + * @param name a JRT file system name (path) of the form + * {@code "/modules//...} or {@code "/packages//...}. + * @return a node representing a resource, directory or symbolic link. + */ public Node findNode(String name) throws IOException { ensureOpen(); return reader.findNode(name); } + /** + * Returns the content of a resource node. + * + * @throws IOException if the content cannot be returned (including if the + * given node is not a resource node). + */ public byte[] getResource(Node node) throws IOException { ensureOpen(); return reader.getResource(node); } - public byte[] getResource(Resource rs) throws IOException { - ensureOpen(); - return reader.getResource(rs); - } - - public ImageHeader getHeader() { - requireOpen(); - return reader.getHeader(); - } - + /** + * Releases a (possibly cached) {@link ByteBuffer} obtained via + * {@link #getResourceBuffer(Node)}. + * + *

Note that no testing is performed to check whether the buffer about + * to be released actually came from a call to {@code getResourceBuffer()}. + */ public static void releaseByteBuffer(ByteBuffer buffer) { BasicImageReader.releaseByteBuffer(buffer); } - public String getName() { - requireOpen(); - return reader.getName(); - } - - public ByteOrder getByteOrder() { - requireOpen(); - return reader.getByteOrder(); - } - - public Path getImagePath() { - requireOpen(); - return reader.getImagePath(); - } - - public ImageStringsReader getStrings() { - requireOpen(); - return reader.getStrings(); - } - - public ImageLocation findLocation(String mn, String rn) { - requireOpen(); - return reader.findLocation(mn, rn); - } - - public boolean verifyLocation(String mn, String rn) { - requireOpen(); - return reader.verifyLocation(mn, rn); - } - - public ImageLocation findLocation(String name) { - requireOpen(); - return reader.findLocation(name); - } - - public String[] getEntryNames() { - requireOpen(); - return reader.getEntryNames(); - } - - public String[] getModuleNames() { - requireOpen(); - int off = "/modules/".length(); - return reader.findNode("/modules") - .getChildren() - .stream() - .map(Node::getNameString) - .map(s -> s.substring(off, s.length())) - .toArray(String[]::new); - } - - public long[] getAttributes(int offset) { - requireOpen(); - return reader.getAttributes(offset); - } - - public String getString(int offset) { - requireOpen(); - return reader.getString(offset); - } - - public byte[] getResource(String name) { - requireOpen(); - return reader.getResource(name); - } - - public byte[] getResource(ImageLocation loc) { - requireOpen(); - return reader.getResource(loc); - } - - public ByteBuffer getResourceBuffer(ImageLocation loc) { - requireOpen(); - return reader.getResourceBuffer(loc); - } - - public InputStream getResourceStream(ImageLocation loc) { + /** + * Returns the content of a resource node in a possibly cached byte buffer. + * Callers of this method must call {@link #releaseByteBuffer(ByteBuffer)} + * when they are finished with it. + */ + public ByteBuffer getResourceBuffer(Node node) { requireOpen(); - return reader.getResourceStream(loc); + if (!node.isResource()) { + throw new IllegalStateException("Not a resource node: " + node); + } + return reader.getResourceBuffer(node.getLocation()); } private static final class SharedImageReader extends BasicImageReader { - static final int SIZE_OF_OFFSET = Integer.BYTES; - - static final Map OPEN_FILES = new HashMap<>(); + private static final Map OPEN_FILES = new HashMap<>(); + public static final String MODULES_ROOT = "/modules"; + public static final String PACKAGES_ROOT = "/packages"; // List of openers for this shared image. - final Set openers; + private final Set openers; - // attributes of the .jimage file. jimage file does not contain + // Attributes of the .jimage file. The jimage file does not contain // attributes for the individual resources (yet). We use attributes // of the jimage file itself (creation, modification, access times). - // Iniitalized lazily, see {@link #imageFileAttributes()}. - BasicFileAttributes imageFileAttributes; + // Initialized lazily, see imageFileAttributes(). + private BasicFileAttributes imageFileAttributes; - // directory management implementation - final HashMap nodes; - volatile Directory rootDir; - - Directory packagesDir; - Directory modulesDir; + // Guarded by synchronizing 'this' instance. + private final HashMap nodes; + // Used to classify ImageLocation instances without string comparison. + private final int modulesStringOffset; + private final int packagesStringOffset; private SharedImageReader(Path imagePath, ByteOrder byteOrder) throws IOException { super(imagePath, byteOrder); this.openers = new HashSet<>(); + // TODO (review note): Given there are ~30,000 nodes in the image, is setting an initial capacity a good idea? this.nodes = new HashMap<>(); + // TODO (review note): These should exist under all circumstances, but there's + // probably a more robust way of getting at these offsets. + this.modulesStringOffset = findLocation("/modules/java.base").getModuleOffset(); + this.packagesStringOffset = findLocation("/packages/java.lang").getModuleOffset(); + + // Node creation is very lazy, so we can just make the top-level directories + // now without the risk of triggering the building of lots of other nodes. + Directory packages = newDirectory(PACKAGES_ROOT); + nodes.put(packages.getName(), packages); + Directory modules = newDirectory(MODULES_ROOT); + nodes.put(modules.getName(), modules); + + Directory root = newDirectory("/"); + root.setChildren(Arrays.asList(packages, modules)); + nodes.put(root.getName(), root); } public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException { @@ -263,8 +252,8 @@ public void close(ImageReader image) throws IOException { if (openers.isEmpty()) { close(); + // TODO (review note): Should this be synchronized by "this" ?? nodes.clear(); - rootDir = null; if (!OPEN_FILES.remove(this.getImagePath(), this)) { throw new IOException("image file not found in open list"); @@ -273,255 +262,230 @@ public void close(ImageReader image) throws IOException { } } - void addOpener(ImageReader reader) { - synchronized (OPEN_FILES) { - openers.add(reader); + /// Returns a node in the JRT filesystem namespace, or null if no resource or + /// directory of that name exists. + /// + /// Node names are absolute, `/`-separated path strings, prefixed with + /// either "/modules" or "/packages". + /// + /// This is the only public API by which anything outside this class can access + /// `Node` instances either directly, or by resolving a symbolic link. + /// + /// Note also that there is no reentrant calling back to this method from within + /// the node handling code. + synchronized Node findNode(String name) { + Node node = nodes.get(name); + if (node == null) { + // We cannot be given the root paths ("/modules" or "/packages") + // because those nodes are already in the nodes cache. + if (name.startsWith(MODULES_ROOT + "/")) { + node = buildModulesNode(name); + } else if (name.startsWith(PACKAGES_ROOT + "/")) { + node = buildPackagesNode(name); + } + if (node != null) { + nodes.put(node.getName(), node); + } + } else if (!node.isCompleted()) { + // Only directories can be incomplete. + assert node instanceof Directory : "Invalid incomplete node: " + node; + completeDirectory((Directory) node); } + assert node == null || node.isCompleted() : "Incomplete node: " + node; + return node; } - boolean removeOpener(ImageReader reader) { - synchronized (OPEN_FILES) { - return openers.remove(reader); + /// Builds a node in the "/modules/..." namespace. + /// + /// Called by `findNode()` if a `/modules` node is not present in the cache. + Node buildModulesNode(String name) { + assert name.startsWith(MODULES_ROOT + "/") : "Invalid module node name: " + name; + // Will fail for non-directory resources, since the image path does not + // start with "/modules" (e.g. "/java.base/java/lang/Object.class"). + ImageLocation loc = findLocation(name); + if (loc != null) { + assert name.equals(loc.getFullName()) : "Mismatched location for directory: " + name; + assert isModulesSubdirectory(loc) : "Invalid modules directory: " + name; + return completeModuleDirectory(newDirectory(name), loc); } - } - - // directory management interface - Directory getRootDirectory() { - return buildRootDirectory(); - } - - /** - * Lazily build a node from a name. - */ - synchronized Node buildNode(String name) { - Node n; - boolean isPackages = name.startsWith("/packages"); - boolean isModules = !isPackages && name.startsWith("/modules"); - - if (!(isModules || isPackages)) { + // TODO (review note): This is a heuristic to avoid spending time on lookup + // in cases of failure, but is not strictly required for correct behaviour. + // If there is no ImageLocation, the given name cannot be trusted to + // belong to any resource (or even be a valid resource name). + int moduleEnd = name.indexOf('/', MODULES_ROOT.length() + 1); + if (moduleEnd == -1) { return null; } - - ImageLocation loc = findLocation(name); - - if (loc != null) { // A sub tree node - if (isPackages) { - n = handlePackages(name, loc); - } else { // modules sub tree - n = handleModulesSubTree(name, loc); - } - } else { // Asking for a resource? /modules/java.base/java/lang/Object.class - if (isModules) { - n = handleResource(name); - } else { - // Possibly ask for /packages/java.lang/java.base - // although /packages/java.base not created - n = handleModuleLink(name); - } + // Tests that the implied module name actually exists before doing + // a full lookup for the location. + ImageLocation moduleLoc = findLocation(name.substring(0, moduleEnd)); + if (moduleLoc == null || moduleLoc.getModuleOffset() == 0) { + return null; } - return n; - } - - synchronized Directory buildRootDirectory() { - Directory root = rootDir; // volatile read - if (root != null) { - return root; + // Resource paths in the image are NOT prefixed with "/modules". + ImageLocation resourceLoc = findLocation(name.substring(MODULES_ROOT.length())); + return resourceLoc != null ? newResource(name, resourceLoc) : null; + } + + /// Builds a node in the "/packages/..." namespace. + /// + /// Called by `findNode()` if a `/packages` node is not present in the cache. + private Node buildPackagesNode(String name) { + // There are only locations for the root "/packages" or "/packages/xxx" + // directories, but not the symbolic links below them (the links can be + // entirely derived from the name information in the parent directory). + // However, unlike resources this means that we do not have a constant + // time lookup for link nodes when creating them. + int packageStart = PACKAGES_ROOT.length() + 1; + int packageEnd = name.indexOf('/', packageStart); + if (packageEnd == -1) { + ImageLocation loc = findLocation(name); + return loc != null ? completePackageDirectory(newDirectory(name), loc) : null; + } else { + // We cannot assume that because the given name was not cached, the + // directory exists (the given name is untrusted and could reference + // a non-existent link). However, *if* the parent directory *is* + // present, we can conclude that the given name is not a valid link. + String dirName = name.substring(0, packageEnd); + if (!nodes.containsKey(dirName)) { + ImageLocation loc = findLocation(dirName); + if (loc != null) { + Directory dir = completePackageDirectory(newDirectory(dirName), loc); + // When the parent is created, its child nodes are cached. + nodes.put(dir.getName(), dir); + return nodes.get(name); + } + } } - - root = newDirectory(null, "/"); - root.setIsRootDir(); - - // /packages dir - packagesDir = newDirectory(root, "/packages"); - packagesDir.setIsPackagesDir(); - - // /modules dir - modulesDir = newDirectory(root, "/modules"); - modulesDir.setIsModulesDir(); - - root.setCompleted(true); - return rootDir = root; - } - - /** - * To visit sub tree resources. - */ - interface LocationVisitor { - void visit(ImageLocation loc); + return null; } - void visitLocation(ImageLocation loc, LocationVisitor visitor) { - byte[] offsets = getResource(loc); - ByteBuffer buffer = ByteBuffer.wrap(offsets); - buffer.order(getByteOrder()); - IntBuffer intBuffer = buffer.asIntBuffer(); - for (int i = 0; i < offsets.length / SIZE_OF_OFFSET; i++) { - int offset = intBuffer.get(i); - ImageLocation pkgLoc = getLocation(offset); - visitor.visit(pkgLoc); + /// Completes a directory by ensuring its child list is populated correctly. + private void completeDirectory(Directory dir) { + String name = dir.getName(); + // Since the node exists, we can assert that its name starts with + // either "/modules" or "/packages", making differentiation easy. It + // also means that the name is valid, so it must yield a location. + assert name.startsWith(MODULES_ROOT) || name.startsWith(PACKAGES_ROOT); + ImageLocation loc = findLocation(name); + assert loc != null && name.equals(loc.getFullName()) : "Invalid location for name: " + name; + // We cannot use 'isXxxSubdirectory()' methods here since we could + // be given a top-level directory (for which that test doesn't work). + // TODO (review note): I feel a bit dirty putting this test in, but it is fast and accurate. + if (name.charAt(1) == 'm') { + completeModuleDirectory(dir, loc); + } else { + completePackageDirectory(dir, loc); } - } - - void visitPackageLocation(ImageLocation loc) { - // Retrieve package name - String pkgName = getBaseExt(loc); - // Content is array of offsets in Strings table - byte[] stringsOffsets = getResource(loc); - ByteBuffer buffer = ByteBuffer.wrap(stringsOffsets); - buffer.order(getByteOrder()); - IntBuffer intBuffer = buffer.asIntBuffer(); - // For each module, create a link node. - for (int i = 0; i < stringsOffsets.length / SIZE_OF_OFFSET; i++) { - // skip empty state, useless. - intBuffer.get(i); - i++; - int offset = intBuffer.get(i); - String moduleName = getString(offset); - Node targetNode = findNode("/modules/" + moduleName); - if (targetNode != null) { - String pkgDirName = packagesDir.getName() + "/" + pkgName; - Directory pkgDir = (Directory) nodes.get(pkgDirName); - newLinkNode(pkgDir, pkgDir.getName() + "/" + moduleName, targetNode); + assert dir.isCompleted() : "Directory must be complete by now: " + dir; + } + + /// Completes a modules directory by setting the list of child nodes. + /// + /// The given directory can be the top level `/modules` directory, so + /// it is NOT safe to use `isModulesSubdirectory(loc)` here. + private Directory completeModuleDirectory(Directory dir, ImageLocation loc) { + assert dir.getName().equals(loc.getFullName()) : "Mismatched location for directory: " + dir; + List children = createChildNodes(loc, (childloc) -> { + if (isModulesSubdirectory(childloc)) { + return nodes.computeIfAbsent(childloc.getFullName(), this::newDirectory); + } else { + // Add the "/modules" prefix to image location paths to get + // Jrt file system names. + String resourceName = childloc.getFullName(true); + return nodes.computeIfAbsent(resourceName, n -> newResource(n, childloc)); } - } + }); + dir.setChildren(children); + return dir; } - Node handlePackages(String name, ImageLocation loc) { - long size = loc.getUncompressedSize(); - Node n = null; - // Only possibilities are /packages, /packages/package/module - if (name.equals("/packages")) { - visitLocation(loc, (childloc) -> { - findNode(childloc.getFullName()); - }); - packagesDir.setCompleted(true); - n = packagesDir; + /// Completes a package directory by setting the list of child nodes. + /// + /// The given directory can be the top level `/packages` directory, so + /// it is NOT safe to use `isPackagesSubdirectory(loc)` here. + private Directory completePackageDirectory(Directory dir, ImageLocation loc) { + assert dir.getName().equals(loc.getFullName()) : "Mismatched location for directory: " + dir; + // The only directories in the "/packages" namespace are "/packages" or + // "/packages/". However, unlike "/modules" directories, the + // location offsets mean different things. + List children; + if (dir.getName().equals(PACKAGES_ROOT)) { + // Top-level directory just contains a list of subdirectories. + children = createChildNodes(loc, c -> nodes.computeIfAbsent(c.getFullName(), this::newDirectory)); } else { - if (size != 0) { // children are offsets to module in StringsTable - String pkgName = getBaseExt(loc); - Directory pkgDir = newDirectory(packagesDir, packagesDir.getName() + "/" + pkgName); - visitPackageLocation(loc); - pkgDir.setCompleted(true); - n = pkgDir; - } else { // Link to module - String pkgName = loc.getParent(); - String modName = getBaseExt(loc); - Node targetNode = findNode("/modules/" + modName); - if (targetNode != null) { - String pkgDirName = packagesDir.getName() + "/" + pkgName; - Directory pkgDir = (Directory) nodes.get(pkgDirName); - Node linkNode = newLinkNode(pkgDir, pkgDir.getName() + "/" + modName, targetNode); - n = linkNode; - } + // A package directory's content is array of offset PAIRS in the + // Strings table, but we only need the 2nd value of each pair. + IntBuffer intBuffer = getOffsetBuffer(loc); + int offsetCount = intBuffer.capacity(); + children = new ArrayList<>(offsetCount / 2); + // Iterate the 2nd offset in each pair (odd indices). + for (int i = 1; i < offsetCount; i += 2) { + String moduleName = getString(intBuffer.get(i)); + children.add(nodes.computeIfAbsent( + dir.getName() + "/" + moduleName, + n -> newLinkNode(n, "/modules/" + moduleName))); } } - return n; - } - - // Asking for /packages/package/module although - // /packages// not yet created, need to create it - // prior to return the link to module node. - Node handleModuleLink(String name) { - // eg: unresolved /packages/package/module - // Build /packages/package node - Node ret = null; - String radical = "/packages/"; - String path = name; - if (path.startsWith(radical)) { - int start = radical.length(); - int pkgEnd = path.indexOf('/', start); - if (pkgEnd != -1) { - String pkg = path.substring(start, pkgEnd); - String pkgPath = radical + pkg; - Node n = findNode(pkgPath); - // If not found means that this is a symbolic link such as: - // /packages/java.util/java.base/java/util/Vector.class - // and will be done by a retry of the filesystem - for (Node child : n.getChildren()) { - if (child.name.equals(name)) { - ret = child; - break; - } - } - } - } - return ret; - } - - Node handleModulesSubTree(String name, ImageLocation loc) { - Node n; - assert (name.equals(loc.getFullName())); - Directory dir = makeDirectories(name); - visitLocation(loc, (childloc) -> { - String path = childloc.getFullName(); - if (path.startsWith("/modules")) { // a package - makeDirectories(path); - } else { // a resource - makeDirectories(childloc.buildName(true, true, false)); - // if we have already created a resource for this name previously, then don't - // recreate it - if (!nodes.containsKey(childloc.getFullName(true))) { - newResource(dir, childloc); - } - } - }); - dir.setCompleted(true); - n = dir; - return n; + // This only happens once and "completes" the directory. + dir.setChildren(children); + return dir; } - Node handleResource(String name) { - Node n = null; - if (!name.startsWith("/modules/")) { - return null; - } - // Make sure that the thing that follows "/modules/" is a module name. - int moduleEndIndex = name.indexOf('/', "/modules/".length()); - if (moduleEndIndex == -1) { - return null; - } - ImageLocation moduleLoc = findLocation(name.substring(0, moduleEndIndex)); - if (moduleLoc == null || moduleLoc.getModuleOffset() == 0) { - return null; + /// Creates the list of child nodes for a `Directory` based on a given + /// node creation function. + /// + /// Note: This cannot be used for package subdirectories as they have + /// child offsets stored differently to other directories. + private List createChildNodes(ImageLocation loc, Function newChildFn) { + IntBuffer offsets = getOffsetBuffer(loc); + int childCount = offsets.capacity(); + List children = new ArrayList<>(childCount); + for (int i = 0; i < childCount; i++) { + children.add(newChildFn.apply(getLocation(offsets.get(i)))); } + return children; + } - String locationPath = name.substring("/modules".length()); - ImageLocation resourceLoc = findLocation(locationPath); - if (resourceLoc != null) { - Directory dir = makeDirectories(resourceLoc.buildName(true, true, false)); - Resource res = newResource(dir, resourceLoc); - n = res; - } - return n; + /// Helper to extract the integer offset buffer from a directory location. + private IntBuffer getOffsetBuffer(ImageLocation dir) { + assert isDirectory(dir) : "Not a directory: " + dir.getFullName(); + byte[] offsets = getResource(dir); + ByteBuffer buffer = ByteBuffer.wrap(offsets); + buffer.order(getByteOrder()); + return buffer.asIntBuffer(); } - String getBaseExt(ImageLocation loc) { - String base = loc.getBase(); - String ext = loc.getExtension(); - if (ext != null && !ext.isEmpty()) { - base = base + "." + ext; - } - return base; + /// Determines if an image location is a directory in the `/modules` + /// namespace (if so, the location name is the Jrt filesystem name). + /// + /// In jimage, every `ImageLocation` under `/modules/` is a directory + /// and has the same value for `getModule()`, and `getModuleOffset()`. + private boolean isModulesSubdirectory(ImageLocation loc) { + return loc.getModuleOffset() == modulesStringOffset; } - synchronized Node findNode(String name) { - buildRootDirectory(); - Node n = nodes.get(name); - if (n == null || !n.isCompleted()) { - n = buildNode(name); - } - return n; + /// Determines if an image location is a directory in the `/packages`` + /// namespace (if so, the location name is the Jrt filesystem name). + /// + /// In jimage, every `ImageLocation` under `/packages/` is a directory + /// and has the same value for `getModule()`, and `getModuleOffset()`. + private boolean isPackagesSubdirectory(ImageLocation loc) { + return loc.getModuleOffset() == packagesStringOffset; } - /** - * Returns the file attributes of the image file. - */ - BasicFileAttributes imageFileAttributes() { + /// Determines if an image location represents a directory of some kind. + private boolean isDirectory(ImageLocation loc) { + return isModulesSubdirectory(loc) || isPackagesSubdirectory(loc) || loc.getModuleOffset() == 0; + } + + /// Returns the file attributes of the image file. Currently, all nodes + /// share the same attributes. + private BasicFileAttributes imageFileAttributes() { BasicFileAttributes attrs = imageFileAttributes; if (attrs == null) { try { - Path file = getImagePath(); - attrs = Files.readAttributes(file, BasicFileAttributes.class); + attrs = Files.readAttributes(getImagePath(), BasicFileAttributes.class); } catch (IOException ioe) { throw new UncheckedIOException(ioe); } @@ -530,186 +494,161 @@ BasicFileAttributes imageFileAttributes() { return attrs; } - Directory newDirectory(Directory parent, String name) { - Directory dir = Directory.create(parent, name, imageFileAttributes()); - nodes.put(dir.getName(), dir); - return dir; - } - - Resource newResource(Directory parent, ImageLocation loc) { - Resource res = Resource.create(parent, loc, imageFileAttributes()); - nodes.put(res.getName(), res); - return res; + /// Creates an "incomplete" directory node with no child nodes set. + /// Directories need to be "completed" before they are returned by + /// `findNode()`. + private Directory newDirectory(String name) { + return new Directory(name, imageFileAttributes()); } - LinkNode newLinkNode(Directory dir, String name, Node link) { - LinkNode linkNode = LinkNode.create(dir, name, link); - nodes.put(linkNode.getName(), linkNode); - return linkNode; - } - - Directory makeDirectories(String parent) { - Directory last = rootDir; - for (int offset = parent.indexOf('/', 1); - offset != -1; - offset = parent.indexOf('/', offset + 1)) { - String dir = parent.substring(0, offset); - last = makeDirectory(dir, last); - } - return makeDirectory(parent, last); - + /// Creates a new resource from an image location. This is the only case + /// where the image location name does not match the requested node name. + /// In image files, resource locations are NOT prefixed by `/modules`. + private Resource newResource(String name, ImageLocation loc) { + assert name.equals(loc.getFullName(true)) : "Mismatched location for resource: " + name; + return new Resource(name, loc, imageFileAttributes()); } - Directory makeDirectory(String dir, Directory last) { - Directory nextDir = (Directory) nodes.get(dir); - if (nextDir == null) { - nextDir = newDirectory(last, dir); - } - return nextDir; + /// Creates a new link node pointing at the given target name. + /// + /// Note that target node is resolved each time `resolve()` is called, so + /// if a link node is retained after its reader is closed, it will fail. + private LinkNode newLinkNode(String name, String targetName) { + return new LinkNode(name, () -> findNode(targetName), imageFileAttributes()); } - byte[] getResource(Node node) throws IOException { + /// Returns the content of a resource node. + private byte[] getResource(Node node) throws IOException { + // We could have been given a non-resource node here. if (node.isResource()) { return super.getResource(node.getLocation()); } throw new IOException("Not a resource: " + node); } - - byte[] getResource(Resource rs) throws IOException { - return super.getResource(rs.getLocation()); - } } - // jimage file does not store directory structure. We build nodes - // using the "path" strings found in the jimage file. - // Node can be a directory or a resource + /** + * A directory, resource or symbolic link in the JRT file system namespace. + */ public abstract static class Node { - private static final int ROOT_DIR = 0b0000_0000_0000_0001; - private static final int PACKAGES_DIR = 0b0000_0000_0000_0010; - private static final int MODULES_DIR = 0b0000_0000_0000_0100; - - private int flags; private final String name; private final BasicFileAttributes fileAttrs; - private boolean completed; + // Only visible for use by ExplodedImage. protected Node(String name, BasicFileAttributes fileAttrs) { this.name = Objects.requireNonNull(name); this.fileAttrs = Objects.requireNonNull(fileAttrs); } - /** - * A node is completed when all its direct children have been built. - * - * @return - */ - public boolean isCompleted() { - return completed; - } - - public void setCompleted(boolean completed) { - this.completed = completed; - } - - public final void setIsRootDir() { - flags |= ROOT_DIR; - } - - public final boolean isRootDir() { - return (flags & ROOT_DIR) != 0; - } - - public final void setIsPackagesDir() { - flags |= PACKAGES_DIR; - } - - public final boolean isPackagesDir() { - return (flags & PACKAGES_DIR) != 0; - } - - public final void setIsModulesDir() { - flags |= MODULES_DIR; + // A node is completed when all its direct children have been built. + // As such, non-directory nodes are always complete. + boolean isCompleted() { + return true; } - public final boolean isModulesDir() { - return (flags & MODULES_DIR) != 0; + // Only resources can return a location. + ImageLocation getLocation() { + throw new IllegalArgumentException("not a resource: " + getName()); } + /** + * Returns the JRT file system compatible name of this node (e.g. + * {@code "/modules/java.base/java/lang/Object.class"} or {@code + * "/packages/java.lang"}). + */ public final String getName() { return name; } + /** + * Returns file attributes for this node. The value returned may be the + * same for all nodes, and should not be relied upon for accuracy. + */ public final BasicFileAttributes getFileAttributes() { return fileAttrs; } - // resolve this Node (if this is a soft link, get underlying Node) + /** + * Resolves a symbolic link to its target node. If this code is not a + * symbolic link, then it resolves to itself. + */ public final Node resolveLink() { return resolveLink(false); } + /** + * Resolves a symbolic link to its target node. If this code is not a + * symbolic link, then it resolves to itself. + */ public Node resolveLink(boolean recursive) { return this; } - // is this a soft link Node? + /** Returns whether this node is a symbolic link. */ public boolean isLink() { return false; } + /** + * Returns whether this node is a directory. Directory nodes can have + * {@link #getChildNames()} invoked to get the fully qualified names + * of any child nodes. + */ public boolean isDirectory() { return false; } - public List getChildren() { - throw new IllegalArgumentException("not a directory: " + getNameString()); - } - + /** + * Returns whether this node is a resource. Resource nodes can have + * their contents obtained via {@link ImageReader#getResource(Node)} + * or {@link ImageReader#getResourceBuffer(Node)}. + */ public boolean isResource() { return false; } - public ImageLocation getLocation() { - throw new IllegalArgumentException("not a resource: " + getNameString()); + // TODO (review note): Sure this could/should be IllegalStateException? + /** + * Returns the fully qualified names of any child nodes for a directory. + * + *

If this node is not a directory ({@code isDirectory() == false}) + * then this method will throw {@link IllegalArgumentException}. + */ + public Stream getChildNames() { + throw new IllegalArgumentException("not a directory: " + getName()); } + /** + * Returns the uncompressed size of this node's content. If this node is + * not a resource, this method returns zero. + */ public long size() { return 0L; } + /** + * Returns the compressed size of this node's content. If this node is + * not a resource, this method returns zero. + */ public long compressedSize() { return 0L; } + /** + * Returns the extension string of a resource node. If this node is not + * a resource, this method returns null. + */ public String extension() { return null; } - public long contentOffset() { - return 0L; - } - - public final FileTime creationTime() { - return fileAttrs.creationTime(); - } - - public final FileTime lastAccessTime() { - return fileAttrs.lastAccessTime(); - } - - public final FileTime lastModifiedTime() { - return fileAttrs.lastModifiedTime(); - } - - public final String getNameString() { - return name; - } - @Override public final String toString() { - return getNameString(); + return getName(); } + /// TODO (review note): In general, nodes are NOT comparable by name. They + /// can differ depending on the reader they came from, and soon preview mode. @Override public final int hashCode() { return name.hashCode(); @@ -729,21 +668,35 @@ public final boolean equals(Object other) { } } - // directory node - directory has full path name without '/' at end. - static final class Directory extends Node { - private final List children; + /// Directory node (referenced from a full path, without a trailing '/'). + /// + /// Directory nodes have two distinct states: + /// * Incomplete: The child list has not been set. + /// * Complete: The child list has been set. + /// + /// When a directory node is returned by `findNode()` it is always complete, + /// but this DOES NOT mean that its child nodes are complete yet. + /// + /// To avoid users being able to access incomplete child nodes, the + /// `Node` API offers only a way to obtain child node names, forcing + /// callers to invoke {@link ImageReader#findNode(String)} if they need to + /// access the child node itself. + /// + /// This approach allows directories to be implemented lazily with respect + /// to child nodes, while retaining efficiency when child nodes are accessed + /// (since any incomplete nodes will be created and placed in the node cache + /// when the parent was first returned to the user). + private static final class Directory extends Node { + // Monotonic reference, will be set to the unmodifiable child list exactly once. + private List children = null; private Directory(String name, BasicFileAttributes fileAttrs) { super(name, fileAttrs); - children = new ArrayList<>(); } - static Directory create(Directory parent, String name, BasicFileAttributes fileAttrs) { - Directory d = new Directory(name, fileAttrs); - if (parent != null) { - parent.addChild(d); - } - return d; + @Override + boolean isCompleted() { + return children != null; } @Override @@ -752,46 +705,40 @@ public boolean isDirectory() { } @Override - public List getChildren() { - return Collections.unmodifiableList(children); - } - - void addChild(Node node) { - assert !children.contains(node) : "Child " + node + " already added"; - children.add(node); + public Stream getChildNames() { + if (children != null) { + return children.stream().map(Node::getName); + } + throw new IllegalStateException("Cannot get child nodes of an incomplete directory: " + getName()); } - public void walk(Consumer consumer) { - consumer.accept(this); - for (Node child : children) { - if (child.isDirectory()) { - ((Directory)child).walk(consumer); - } else { - consumer.accept(child); - } - } + private void setChildren(List children) { + assert this.children == null : this + ": Cannot set child nodes twice!"; + this.children = Collections.unmodifiableList(children); } } - // "resource" is .class or any other resource (compressed/uncompressed) in a jimage. - // full path of the resource is the "name" of the resource. - static class Resource extends Node { + /// Resource node (e.g. a ".class" entry, or any other data resource). + /// + /// Resources are leaf nodes referencing an underlying image location. They + /// are lightweight, and do not cache their contents. + /// + /// Unlike directories (where the node name matches the jimage path for the + /// corresponding `ImageLocation`), resource node names are NOT the same as + /// the corresponding jimage path. The difference is that node names for + /// resources are prefixed with "/modules", which is missing from the + /// equivalent jimage path. + private static class Resource extends Node { private final ImageLocation loc; - private Resource(ImageLocation loc, BasicFileAttributes fileAttrs) { - super(loc.getFullName(true), fileAttrs); + private Resource(String name, ImageLocation loc, BasicFileAttributes fileAttrs) { + super(name, fileAttrs); this.loc = loc; } - static Resource create(Directory parent, ImageLocation loc, BasicFileAttributes fileAttrs) { - Resource rs = new Resource(loc, fileAttrs); - parent.addChild(rs); - return rs; - } - @Override - public boolean isCompleted() { - return true; + ImageLocation getLocation() { + return loc; } @Override @@ -799,11 +746,6 @@ public boolean isResource() { return true; } - @Override - public ImageLocation getLocation() { - return loc; - } - @Override public long size() { return loc.getUncompressedSize(); @@ -818,36 +760,25 @@ public long compressedSize() { public String extension() { return loc.getExtension(); } - - @Override - public long contentOffset() { - return loc.getContentOffset(); - } } - // represents a soft link to another Node - static class LinkNode extends Node { - private final Node link; + /// Link node (a symbolic link to a top-level modules directory). + /// + /// Link nodes resolve their target by invoking a given supplier, and do not + /// cache the result. Since nodes are cached by the `ImageReader`, this + /// means that only the first call to `resolveLink()` could do any + /// significant work. + private static class LinkNode extends Node { + private final Supplier link; - private LinkNode(String name, Node link) { - super(name, link.getFileAttributes()); + private LinkNode(String name, Supplier link, BasicFileAttributes fileAttrs) { + super(name, fileAttrs); this.link = link; } - static LinkNode create(Directory parent, String name, Node link) { - LinkNode ln = new LinkNode(name, link); - parent.addChild(ln); - return ln; - } - - @Override - public boolean isCompleted() { - return true; - } - @Override public Node resolveLink(boolean recursive) { - return (recursive && link instanceof LinkNode) ? ((LinkNode)link).resolveLink(true) : link; + return link.get(); } @Override diff --git a/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java b/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java index e2c17f8ca2513..87a00da4393ba 100644 --- a/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java +++ b/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java @@ -119,9 +119,9 @@ byte[] getContent() throws IOException { } @Override - public List getChildren() { + public Stream getChildNames() { if (!isDirectory()) - throw new IllegalArgumentException("not a directory: " + getNameString()); + throw new IllegalArgumentException("not a directory: " + getName()); if (children == null) { List list = new ArrayList<>(); try (DirectoryStream stream = Files.newDirectoryStream(path)) { @@ -138,7 +138,7 @@ public List getChildren() { } children = list; } - return children; + return children.stream().map(Node::getName); } @Override diff --git a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java index f0804b58c1c41..ea588c05012f4 100644 --- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java +++ b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileAttributes.java @@ -49,7 +49,7 @@ final class JrtFileAttributes implements BasicFileAttributes { //-------- basic attributes -------- @Override public FileTime creationTime() { - return node.creationTime(); + return node.getFileAttributes().creationTime(); } @Override @@ -69,12 +69,12 @@ public boolean isRegularFile() { @Override public FileTime lastAccessTime() { - return node.lastAccessTime(); + return node.getFileAttributes().lastAccessTime(); } @Override public FileTime lastModifiedTime() { - return node.lastModifiedTime(); + return node.getFileAttributes().lastModifiedTime(); } @Override diff --git a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java index 9a8d9d22dfa87..6530bd1f90a6f 100644 --- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java +++ b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java @@ -54,7 +54,6 @@ import java.nio.file.attribute.FileTime; import java.nio.file.attribute.UserPrincipalLookupService; import java.nio.file.spi.FileSystemProvider; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -64,7 +63,6 @@ import java.util.Set; import java.util.regex.Pattern; import jdk.internal.jimage.ImageReader.Node; -import static java.util.stream.Collectors.toList; /** * jrt file system implementation built on System jimage files. @@ -225,19 +223,19 @@ Iterator iteratorOf(JrtPath path, DirectoryStream.Filter fil throw new NotDirectoryException(path.getName()); } if (filter == null) { - return node.getChildren() - .stream() - .map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName()))) - .iterator(); + return node.getChildNames() + .map(child -> (Path) (path.resolve(new JrtPath(this, child).getFileName()))) + .iterator(); } - return node.getChildren() - .stream() - .map(child -> (Path)(path.resolve(new JrtPath(this, child.getNameString()).getFileName()))) - .filter(p -> { try { return filter.accept(p); - } catch (IOException x) {} - return false; - }) - .iterator(); + return node.getChildNames() + .map(child -> (Path) (path.resolve(new JrtPath(this, child).getFileName()))) + .filter(p -> { + try { + return filter.accept(p); + } catch (IOException x) {} + return false; + }) + .iterator(); } // returns the content of the file resource specified by the path diff --git a/src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java b/src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java index 88cdf724e7d39..6813c7e627f2e 100644 --- a/src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java +++ b/src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java @@ -58,7 +58,6 @@ static SystemImage open() throws IOException { if (modulesImageExists) { // open a .jimage and build directory structure final ImageReader image = ImageReader.open(moduleImageFile); - image.getRootDirectory(); return new SystemImage() { @Override Node findNode(String path) throws IOException { diff --git a/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java b/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java index c520e6e636aa2..be985452314b5 100644 --- a/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java +++ b/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -34,7 +34,6 @@ import java.lang.module.ModuleReference; import java.lang.reflect.Constructor; import java.net.URI; -import java.net.URLConnection; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; @@ -54,7 +53,6 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; -import jdk.internal.jimage.ImageLocation; import jdk.internal.jimage.ImageReader; import jdk.internal.jimage.ImageReaderFactory; import jdk.internal.access.JavaNetUriAccess; @@ -210,7 +208,7 @@ public static ModuleFinder ofSystem() { } /** - * Parses the module-info.class of all module in the runtime image and + * Parses the {@code module-info.class} of all modules in the runtime image and * returns a ModuleFinder to find the modules. * * @apiNote The returned ModuleFinder is thread safe. @@ -219,20 +217,16 @@ private static ModuleFinder ofModuleInfos() { // parse the module-info.class in every module Map nameToAttributes = new HashMap<>(); Map nameToHash = new HashMap<>(); - ImageReader reader = SystemImage.reader(); - for (String mn : reader.getModuleNames()) { - ImageLocation loc = reader.findLocation(mn, "module-info.class"); - ModuleInfo.Attributes attrs - = ModuleInfo.read(reader.getResourceBuffer(loc), null); - nameToAttributes.put(mn, attrs); + allModuleAttributes().forEach(attrs -> { + nameToAttributes.put(attrs.descriptor().name(), attrs); ModuleHashes hashes = attrs.recordedHashes(); if (hashes != null) { for (String name : hashes.names()) { nameToHash.computeIfAbsent(name, k -> hashes.hashFor(name)); } } - } + }); // create a ModuleReference for each module Set mrefs = new HashSet<>(); @@ -253,6 +247,35 @@ private static ModuleFinder ofModuleInfos() { return new SystemModuleFinder(mrefs, nameToModule); } + /** + * Parses the {@code module-info.class} of all modules in the runtime image and + * returns a stream of {@link ModuleInfo.Attributes Attributes} for them. The + * returned attributes are in no specific order. + */ + private static Stream allModuleAttributes() { + // System reader is a singleton and should not be closed by callers. + ImageReader reader = SystemImage.reader(); + try { + return reader.findNode("/modules").getChildNames().map(mn -> readModuleAttributes(reader, mn)); + } catch (IOException e) { + throw new Error("Error reading root /modules entry", e); + } + } + + // Every module is required to have a valid module-info.class. + private static ModuleInfo.Attributes readModuleAttributes(ImageReader reader, String moduleName) { + Exception err = null; + try { + ImageReader.Node node = reader.findNode(moduleName + "/module-info.class"); + if (node != null && node.isResource()) { + return ModuleInfo.read(reader.getResourceBuffer(node), null); + } + } catch (IOException | UncheckedIOException e) { + err = e; + } + throw new Error("Missing or invalid module-info.class for module: " + moduleName, err); + } + /** * A ModuleFinder that finds module in an array or set of modules. */ @@ -382,34 +405,18 @@ private static class SystemModuleReader implements ModuleReader { this.module = module; } - /** - * Returns the ImageLocation for the given resource, {@code null} - * if not found. - */ - private ImageLocation findImageLocation(String name) throws IOException { - Objects.requireNonNull(name); - if (closed) - throw new IOException("ModuleReader is closed"); - ImageReader imageReader = SystemImage.reader(); - if (imageReader != null) { - return imageReader.findLocation(module, name); - } else { - // not an images build - return null; - } - } - /** * Returns {@code true} if the given resource exists, {@code false} * if not found. */ - private boolean containsImageLocation(String name) throws IOException { - Objects.requireNonNull(name); + private boolean containsResource(String resourcePath) throws IOException { + Objects.requireNonNull(resourcePath); if (closed) throw new IOException("ModuleReader is closed"); ImageReader imageReader = SystemImage.reader(); if (imageReader != null) { - return imageReader.verifyLocation(module, name); + ImageReader.Node node = imageReader.findNode("/modules" + resourcePath); + return node != null && node.isResource(); } else { // not an images build return false; @@ -418,8 +425,9 @@ private boolean containsImageLocation(String name) throws IOException { @Override public Optional find(String name) throws IOException { - if (containsImageLocation(name)) { - URI u = JNUA.create("jrt", "/" + module + "/" + name); + String resourcePath = "/" + module + "/" + name; + if (containsResource(resourcePath)) { + URI u = JNUA.create("jrt", resourcePath); return Optional.of(u); } else { return Optional.empty(); @@ -442,14 +450,24 @@ private InputStream toInputStream(ByteBuffer bb) { // ## -> ByteBuffer? } } + /** + * Returns the node for the given resource if found. If the name references + * a non-resource node, then {@code null} is returned. + */ + private ImageReader.Node findResourceNode(ImageReader reader, String name) throws IOException { + Objects.requireNonNull(name); + if (closed) { + throw new IOException("ModuleReader is closed"); + } + String nodeName = "/modules/" + module + "/" + name; + ImageReader.Node node = reader.findNode(nodeName); + return node != null && node.isResource() ? node : null; + } + @Override public Optional read(String name) throws IOException { - ImageLocation location = findImageLocation(name); - if (location != null) { - return Optional.of(SystemImage.reader().getResourceBuffer(location)); - } else { - return Optional.empty(); - } + ImageReader reader = SystemImage.reader(); + return Optional.ofNullable(findResourceNode(reader, name)).map(reader::getResourceBuffer); } @Override @@ -481,7 +499,7 @@ public void close() { private static class ModuleContentSpliterator implements Spliterator { final String moduleRoot; final Deque stack; - Iterator iterator; + Iterator iterator; ModuleContentSpliterator(String module) throws IOException { moduleRoot = "/modules/" + module; @@ -502,13 +520,10 @@ private static class ModuleContentSpliterator implements Spliterator { private String next() throws IOException { for (;;) { while (iterator.hasNext()) { - ImageReader.Node node = iterator.next(); - String name = node.getName(); + String name = iterator.next(); + ImageReader.Node node = SystemImage.reader().findNode(name); if (node.isDirectory()) { - // build node - ImageReader.Node dir = SystemImage.reader().findNode(name); - assert dir.isDirectory(); - stack.push(dir); + stack.push(node); } else { // strip /modules/$MODULE/ prefix return name.substring(moduleRoot.length() + 1); @@ -520,7 +535,7 @@ private String next() throws IOException { } else { ImageReader.Node dir = stack.poll(); assert dir.isDirectory(); - iterator = dir.getChildren().iterator(); + iterator = dir.getChildNames().iterator(); } } } diff --git a/test/jdk/jdk/internal/jimage/ImageReaderTest.java b/test/jdk/jdk/internal/jimage/ImageReaderTest.java new file mode 100644 index 0000000000000..4b4fc944421d1 --- /dev/null +++ b/test/jdk/jdk/internal/jimage/ImageReaderTest.java @@ -0,0 +1,279 @@ +/* + * 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.ImageReader; +import jdk.internal.jimage.ImageReader.Node; +import jdk.test.lib.compiler.InMemoryJavaCompiler; +import jdk.test.lib.util.JarBuilder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.opentest4j.TestSkippedException; +import tests.Helper; +import tests.JImageGenerator; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; +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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; + +/* + * @test + * @summary Tests for ImageReader. + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jimage + * @library /test/jdk/tools/lib + * /test/lib + * @build tests.* + * @run junit/othervm 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 { + + private static final Map> IMAGE_ENTRIES = Map.of( + "modfoo", Arrays.asList( + "com.foo.Alpha", + "com.foo.Beta", + "com.foo.bar.Gamma"), + "modbar", Arrays.asList( + "com.bar.One", + "com.bar.Two")); + private final Path image = buildJImage(IMAGE_ENTRIES); + + @ParameterizedTest + @ValueSource(strings = { + "/", + "/modules", + "/modules/modfoo", + "/modules/modbar", + "/modules/modfoo/com", + "/modules/modfoo/com/foo", + "/modules/modfoo/com/foo/bar"}) + public void testModuleDirectories_expected(String name) throws IOException { + try (ImageReader reader = ImageReader.open(image)) { + assertDir(reader, name); + } + } + + @ParameterizedTest + @ValueSource(strings = { + "", + "//", + "/modules/", + "/modules/unknown", + "/modules/modbar/", + "/modules/modfoo//com", + "/modules/modfoo/com/"}) + public void testModuleNodes_absent(String name) throws IOException { + try (ImageReader reader = ImageReader.open(image)) { + assertAbsent(reader, name); + } + } + + @Test + public void testModuleResources() throws IOException { + try (ImageReader reader = ImageReader.open(image)) { + assertNode(reader, "/modules/modfoo/com/foo/Alpha.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.bar.One", loader.loadAndGetToString("modbar", "com.bar.One")); + } + } + + @Test + public void testPackageDirectories() throws IOException { + try (ImageReader reader = ImageReader.open(image)) { + Node root = assertDir(reader, "/packages"); + Set pkgNames = root.getChildNames().collect(Collectors.toSet()); + assertTrue(pkgNames.contains("/packages/com")); + assertTrue(pkgNames.contains("/packages/com.foo")); + assertTrue(pkgNames.contains("/packages/com.bar")); + + // Even though no classes exist directly in the "com" package, it still + // creates a directory with links back to all the modules which contain it. + Set comLinks = assertDir(reader, "/packages/com").getChildNames().collect(Collectors.toSet()); + assertTrue(comLinks.contains("/packages/com/modfoo")); + assertTrue(comLinks.contains("/packages/com/modbar")); + } + } + + @Test + public void testPackageLinks() throws IOException { + try (ImageReader reader = ImageReader.open(image)) { + Node moduleFoo = assertDir(reader, "/modules/modfoo"); + Node moduleBar = assertDir(reader, "/modules/modbar"); + assertSame(assertLink(reader, "/packages/com.foo/modfoo").resolveLink(), moduleFoo); + assertSame(assertLink(reader, "/packages/com.bar/modbar").resolveLink(), moduleBar); + } + } + + private static ImageReader.Node assertNode(ImageReader reader, String name) throws IOException { + ImageReader.Node node = reader.findNode(name); + assertNotNull(node, "Could not find node: " + name); + return node; + } + + private static ImageReader.Node assertDir(ImageReader reader, String name) throws IOException { + ImageReader.Node dir = assertNode(reader, name); + assertTrue(dir.isDirectory(), "Node was not a directory: " + name); + return dir; + } + + 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); + return link; + } + + private static void assertAbsent(ImageReader reader, String name) throws IOException { + assertNull(reader.findNode(name), "Should not be able to find node: " + name); + } + + /// Builds a jimage file with the specified class entries. The classes in the built + /// image can be loaded and executed to return their names via `toString()` to confirm + /// the correct bytes were returned. + public static Path buildJImage(Map> entries) { + Helper helper = getHelper(); + Path outDir = helper.createNewImageDir("test"); + JImageGenerator.JLinkTask jlink = JImageGenerator.getJLinkTask() + .modulePath(helper.defaultModulePath()) + .output(outDir); + + Path jarDir = helper.getJarDir(); + IMAGE_ENTRIES.forEach((module, classes) -> { + JarBuilder jar = new JarBuilder(jarDir.resolve(module + ".jar").toString()); + String moduleInfo = "module " + module + " {}"; + jar.addEntry("module-info.class", InMemoryJavaCompiler.compile("module-info", moduleInfo)); + + classes.forEach(fqn -> { + 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"; + } + } + """, pkg, cls, fqn); + jar.addEntry(path, InMemoryJavaCompiler.compile(fqn, source)); + }); + try { + jar.build(); + } catch (IOException e) { + throw new RuntimeException(e); + } + jlink.addMods(module); + }); + return jlink.call().assertSuccess().resolve("lib", "modules"); + } + + /// Returns the helper for building JAR and jimage files. + private static Helper getHelper() { + try { + Helper helper = Helper.newHelper(); + if (helper == null) { + throw new TestSkippedException("Cannot create test helper (exploded image?)"); + } + return helper; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /// Loads and performs actions on classes stored in a given `ImageReader`. + private static class ImageClassLoader extends ClassLoader { + private final ImageReader reader; + private final Set testModules; + + private ImageClassLoader(ImageReader reader, Set testModules) { + this.reader = reader; + this.testModules = testModules; + } + + @FunctionalInterface + public interface ClassAction { + R call(Class cls) throws T; + } + + String loadAndGetToString(String module, String fqn) { + return loadAndCall(module, fqn, c -> c.getDeclaredConstructor().newInstance().toString()); + } + + R loadAndCall(String module, String fqn, ClassAction action) { + Class cls = findClass(module, fqn); + assertNotNull(cls, "Could not load class: " + module + "/" + fqn); + try { + return action.call(cls); + } catch (Exception e) { + fail("Class loading failed", e); + return null; + } + } + + @Override + protected Class findClass(String module, String fqn) { + assumeTrue(testModules.contains(module), "Can only load classes in modules: " + testModules); + String name = "/modules/" + module + "/" + fqn.replace('.', '/') + ".class"; + Class cls = findLoadedClass(fqn); + if (cls == null) { + try { + ImageReader.Node node = reader.findNode(name); + if (node != null && node.isResource()) { + byte[] classBytes = reader.getResource(node); + cls = defineClass(fqn, classBytes, 0, classBytes.length); + resolveClass(cls); + return cls; + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return null; + } + } +} diff --git a/test/jdk/jdk/internal/jimage/JImageReadTest.java b/test/jdk/jdk/internal/jimage/JImageReadTest.java index ea700d03a4f18..da5d4f19bc1d9 100644 --- a/test/jdk/jdk/internal/jimage/JImageReadTest.java +++ b/test/jdk/jdk/internal/jimage/JImageReadTest.java @@ -49,6 +49,9 @@ import org.testng.Assert; import org.testng.TestNG; +import static java.nio.ByteOrder.BIG_ENDIAN; +import static java.nio.ByteOrder.LITTLE_ENDIAN; + @Test public class JImageReadTest { @@ -333,32 +336,23 @@ static void test4_nameTooLong() throws IOException { */ @Test static void test5_imageReaderEndianness() throws IOException { - ImageReader nativeReader = ImageReader.open(imageFile); - Assert.assertEquals(nativeReader.getByteOrder(), ByteOrder.nativeOrder()); - - try { - ImageReader leReader = ImageReader.open(imageFile, ByteOrder.LITTLE_ENDIAN); - Assert.assertEquals(leReader.getByteOrder(), ByteOrder.LITTLE_ENDIAN); - leReader.close(); - } catch (IOException io) { - // IOException expected if LITTLE_ENDIAN not the nativeOrder() - Assert.assertNotEquals(ByteOrder.nativeOrder(), ByteOrder.LITTLE_ENDIAN); + // Will be opened with native byte order. + try (ImageReader nativeReader = ImageReader.open(imageFile)) { + // Just ensure something works as expected. + Assert.assertNotNull(nativeReader.findNode("/")); + } catch (IOException expected) { + Assert.fail("Reader should be openable with native byte order."); } - try { - ImageReader beReader = ImageReader.open(imageFile, ByteOrder.BIG_ENDIAN); - Assert.assertEquals(beReader.getByteOrder(), ByteOrder.BIG_ENDIAN); - beReader.close(); - } catch (IOException io) { - // IOException expected if LITTLE_ENDIAN not the nativeOrder() - Assert.assertNotEquals(ByteOrder.nativeOrder(), ByteOrder.BIG_ENDIAN); + ByteOrder otherOrder = ByteOrder.nativeOrder() == BIG_ENDIAN ? LITTLE_ENDIAN : BIG_ENDIAN; + try (ImageReader badReader = ImageReader.open(imageFile, otherOrder)) { + Assert.fail("Reader should not be openable with the wrong byte order."); + } catch (IOException expected) { } - - nativeReader.close(); } - // main method to run standalone from jtreg - @Test(enabled=false) + // main method to run standalone from jtreg + @Test(enabled = false) @Parameters({"x"}) @SuppressWarnings("raw_types") public static void main(@Optional String[] args) { diff --git a/test/jdk/tools/jimage/ImageReaderDuplicateChildNodesTest.java b/test/jdk/tools/jimage/ImageReaderDuplicateChildNodesTest.java index bec32bee0f813..8656a4a3d00f1 100644 --- a/test/jdk/tools/jimage/ImageReaderDuplicateChildNodesTest.java +++ b/test/jdk/tools/jimage/ImageReaderDuplicateChildNodesTest.java @@ -68,17 +68,17 @@ public static void main(final String[] args) throws Exception { + " in " + imagePath); } // now verify that the parent node which is a directory, doesn't have duplicate children - final List children = parent.getChildren(); - if (children == null || children.isEmpty()) { + final List childNames = parent.getChildNames().toList(); + if (childNames.isEmpty()) { throw new RuntimeException("ImageReader did not return any child resources under " + integersParentResource + " in " + imagePath); } final Set uniqueChildren = new HashSet<>(); - for (final ImageReader.Node child : children) { - final boolean unique = uniqueChildren.add(child); + for (final String childName : childNames) { + final boolean unique = uniqueChildren.add(reader.findNode(childName)); if (!unique) { throw new RuntimeException("ImageReader returned duplicate child resource " - + child + " under " + parent + " from image " + imagePath); + + childName + " under " + parent + " from image " + imagePath); } } } diff --git a/test/micro/org/openjdk/bench/jdk/internal/jrtfs/ImageReaderBenchmark.java b/test/micro/org/openjdk/bench/jdk/internal/jrtfs/ImageReaderBenchmark.java new file mode 100644 index 0000000000000..a9236b693d185 --- /dev/null +++ b/test/micro/org/openjdk/bench/jdk/internal/jrtfs/ImageReaderBenchmark.java @@ -0,0 +1,1072 @@ +/* + * 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 org.openjdk.bench.jdk.internal.jrtfs; + +import jdk.internal.jimage.ImageReader; +import jdk.internal.jimage.ImageReader.Node; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import java.io.IOException; +import java.nio.ByteOrder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + +/// Benchmarks for ImageReader. See individual benchmarks for details on what they +/// measure, and their potential applicability for real world conclusions. +@BenchmarkMode(Mode.AverageTime) +@Warmup(iterations = 5, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 5, timeUnit = TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Fork(value = 1, jvmArgs = {"--add-exports", "java.base/jdk.internal.jimage=ALL-UNNAMED"}) +public class ImageReaderBenchmark { + + private static final Path SYSTEM_IMAGE_FILE = Path.of(System.getProperty("java.home"), "lib", "modules"); + static { + if (!Files.exists(SYSTEM_IMAGE_FILE)) { + throw new IllegalStateException("Cannot locate jimage file for benchmark: " + SYSTEM_IMAGE_FILE); + } + } + + /// NOT annotated with `@State` since it needs to potentially be used as a + /// per-benchmark or a per-iteration state object. The subclasses provide + /// any lifetime annotations that are needed. + static class BaseState { + protected Path copiedImageFile; + protected ByteOrder byteOrder; + long count = 0; + + public void setUp() throws IOException { + copiedImageFile = Files.createTempFile("copied_jimage", ""); + byteOrder = ByteOrder.nativeOrder(); + Files.copy(SYSTEM_IMAGE_FILE, copiedImageFile, REPLACE_EXISTING); + } + + public void tearDown() throws IOException { + Files.deleteIfExists(copiedImageFile); + System.err.println("Result: " + count); + } + } + + @State(Scope.Benchmark) + public static class WarmStartWithImageReader extends BaseState { + ImageReader reader; + + @Setup(Level.Trial) + public void setUp() throws IOException { + super.setUp(); + reader = ImageReader.open(copiedImageFile, byteOrder); + } + + @TearDown(Level.Trial) + public void tearDown() throws IOException { + super.tearDown(); + } + } + + @State(Scope.Benchmark) + public static class ColdStart extends BaseState { + @Setup(Level.Iteration) + public void setUp() throws IOException { + super.setUp(); + } + + @TearDown(Level.Iteration) + public void tearDown() throws IOException { + super.tearDown(); + } + } + + @State(Scope.Benchmark) + public static class ColdStartWithImageReader extends BaseState { + ImageReader reader; + + @Setup(Level.Iteration) + public void setup() throws IOException { + super.setUp(); + reader = ImageReader.open(copiedImageFile, byteOrder); + } + + @TearDown(Level.Iteration) + public void tearDown() throws IOException { + reader.close(); + super.tearDown(); + } + } + + /// Benchmarks counting of all nodes in the system image *after* they have all + /// been visited at least once. Image nodes should be cached after first use, + /// so this benchmark should be fast and very stable. + @Benchmark + @BenchmarkMode(Mode.AverageTime) + public void warmCache_CountAllNodes(WarmStartWithImageReader state) throws IOException { + state.count = countAllNodes(state.reader, state.reader.findNode("/")); + } + + /// Benchmarks counting of all nodes in the system image from a "cold start". This + /// visits all nodes in depth-first order and counts them. + /// + /// This benchmark is not representative of any typical usage pattern, but can be + /// used for comparisons between versions of `ImageReader`. + @Benchmark + @BenchmarkMode(Mode.SingleShotTime) + public void coldStart_InitAndCount(ColdStart state) throws IOException { + try (var reader = ImageReader.open(state.copiedImageFile, state.byteOrder)) { + state.count = countAllNodes(reader, reader.findNode("/")); + } + } + + /// As above, but includes the time to initialize the `ImageReader`. + @Benchmark + @BenchmarkMode(Mode.SingleShotTime) + public void coldStart_CountOnly(ColdStartWithImageReader state) throws IOException { + state.count = countAllNodes(state.reader, state.reader.findNode("/")); + } + + /// Benchmarks the time taken to load the byte array contents of classes + /// representative of those loaded by javac to for the simplest `HelloWorld` + /// program. + /// + /// This benchmark is somewhat representative of the cost of class loading + /// during javac startup. It is useful for comparisons between versions of + /// `ImageReader`, but also to estimate a lower bound for any reduction or + /// increase in the real-world startup time of javac. + @Benchmark + @BenchmarkMode(Mode.SingleShotTime) + public void coldStart_LoadJavacInitClasses(Blackhole bh, ColdStart state) throws IOException { + int errors = 0; + try (var reader = ImageReader.open(state.copiedImageFile, state.byteOrder)) { + for (String path : INIT_CLASSES) { + // 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); + if (node != null) { + bh.consume(reader.getResource(node)); + } else { + errors += 1; + } + } + } + state.count = INIT_CLASSES.size(); + // Allow up to 2% missing classes before complaining. + if ((100 * errors) / INIT_CLASSES.size() >= 2) { + reportMissingClassesAndFail(state, errors); + } + } + + static long countAllNodes(ImageReader reader, Node node) { + long count = 1; + if (node.isDirectory()) { + count += node.getChildNames().mapToLong(n -> { + try { + return countAllNodes(reader, reader.findNode(n)); + } catch (IOException e) { + throw new RuntimeException(e); + } + }).sum(); + } + 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, state.byteOrder)) { + for (String path : INIT_CLASSES) { + if (reader.findNode(path) == null) { + missing.add(path); + } + } + } + throw new IllegalStateException( + String.format( + "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))); + } + + // 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"); +}