Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,12 @@
import java.nio.file.StandardOpenOption;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Random;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import jdk.internal.jimage.decompressor.Decompressor;

/**
Expand Down Expand Up @@ -326,6 +330,56 @@ public String[] getEntryNames() {
.toArray(String[]::new);
}

/**
* Returns the "raw" API for accessing underlying jimage resource entries.
*
* <p>This is only meaningful for use by code dealing directly with jimage
* files, and cannot be used to reliably lookup resources used at runtime.
*
* <p>This API remains valid until the image reader from which it was
* obtained is closed.
*/
// Package visible for use by ImageReader.
ResourceEntries getResourceEntries() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the implementation of the new, narrow, API. That's it.

return new ResourceEntries() {
@Override
public Stream<String> getEntryNames(String module) {
if (module.isEmpty() || module.equals("modules") || module.equals("packages")) {
throw new IllegalArgumentException("Invalid module name: " + module);
}
return IntStream.range(0, offsets.capacity())
.map(offsets::get)
.filter(offset -> offset != 0)
// Reusing a location instance or getting the module
// offset directly would save a lot of allocations here.
.mapToObj(offset -> ImageLocation.readFrom(BasicImageReader.this, offset))
// Reverse lookup of module offset would be faster here.
.filter(loc -> module.equals(loc.getModule()))
.map(ImageLocation::getFullName);
}

private ImageLocation getResourceLocation(String name) {
if (!name.startsWith("/modules/") && !name.startsWith("/packages/")) {
ImageLocation location = BasicImageReader.this.findLocation(name);
if (location != null) {
return location;
}
}
throw new NoSuchElementException("No such resource entry: " + name);
}

@Override
public long getSize(String name) {
return getResourceLocation(name).getUncompressedSize();
}

@Override
public byte[] getBytes(String name) {
return BasicImageReader.this.getResource(getResourceLocation(name));
}
};
}

ImageLocation getLocation(int offset) {
return ImageLocation.readFrom(this, offset);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,11 @@ public ByteBuffer getResourceBuffer(Node node) {
return reader.getResourceBuffer(node.getLocation());
}

// Package protected for use only by SystemImageReader.
ResourceEntries getResourceEntries() {
return reader.getResourceEntries();
}

private static final class SharedImageReader extends BasicImageReader {
// There are >30,000 nodes in a complete jimage tree, and even relatively
// common tasks (e.g. starting up javac) load somewhere in the region of
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a copy of the notice in BasicImageReader, so I assume the clause about the GPL is correct.
Please let me know if not.

* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.jimage;

import java.io.InputStream;
import java.util.stream.Stream;

/**
* Accesses the underlying resource entries in a jimage file.
*
* <p>This API is designed only for use by the jlink classes, which read the raw
* jimage files. Use the {@link ImageReader} API to read jimage contents at
* runtime to correctly account for preview mode.
*
* <p>This API ignores the {@code previewMode} of the {@link ImageReader} from
* which it is obtained, and returns an unmapped view of entries (e.g. allowing
* for direct access of resources in the {@code META-INF/preview/...} namespace).
*
* <p>It disallows access to resource directories (i.e. {@code "/modules/..."})
* or packages entries (i.e. {@code "/packages/..."}).
*
* @implNote This class needs to maintain JDK 8 source compatibility.
*
* It is used internally in the JDK to implement jimage/jrtfs access,
* but also compiled and delivered as part of the jrtfs.jar to support access
* to the jimage file provided by the shipped JDK by tools running on JDK 8.
*/
public interface ResourceEntries {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an API so that we can avoid using ImageReader directly in the jlink code.
ImageReader is not conceptually the right API for this, and in future we might want to decouple things even more so it's clear that ImageReader is for inspection of resource in a runtime, and something else is there to read the jimage contents directly.

/**
* Returns the jimage names for all resources in the given module, in
* random order. Entry names will always be prefixed by the given module
* name (e.g. {@code "/<module-name>/..."}).
*/
Stream<String> getEntryNames(String module);

/**
* Returns the (uncompressed) size of a resource given its jimage name.
*
* @throws java.util.NoSuchElementException if the resource does not exist.
*/
long getSize(String name);

/**
* Returns a copy of a resource's content given its jimage name.
*
* @throws java.util.NoSuchElementException if the resource does not exist.
*/
byte[] getBytes(String name);
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,15 @@ public static ImageReader get() {
return SYSTEM_IMAGE_READER;
}

/**
* Returns the "raw" API for accessing underlying jimage resource entries.
*
* <p>This is only meaningful for use by code dealing directly with jimage
* files, and cannot be used to reliably lookup resources used at runtime.
*/
public static ResourceEntries getResourceEntries() {
return get().getResourceEntries();
}

private SystemImageReader() {}
}
19 changes: 9 additions & 10 deletions src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
*/
package jdk.tools.jlink.internal;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
Expand All @@ -34,7 +35,7 @@
* An Archive of all content, classes, resources, configuration files, and
* other, for a module.
*/
public interface Archive {
public interface Archive extends Closeable {

/**
* Entry is contained in an Archive
Expand All @@ -59,11 +60,12 @@ public static enum EntryType {
private final String path;

/**
* Constructs an entry of the given archive
* @param archive archive
* @param path
* @param name an entry name that does not contain the module name
* @param type
* Constructs an entry of the given archive.
*
* @param archive the archive in which this entry exists.
* @param path the complete path of the entry, including the module.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that now I removed the path() method I added, the path passed here serves absolutely no purpose other than to appear in the toString() output. I'd vote for removing it completely and simplifying callers.

* @param name an entry name relative to its containing module.
* @param type the entry type.
*/
public Entry(Archive archive, String path, String name, EntryType type) {
this.archive = Objects.requireNonNull(archive);
Expand All @@ -72,10 +74,6 @@ public Entry(Archive archive, String path, String name, EntryType type) {
this.type = Objects.requireNonNull(type);
}

public final Archive archive() {
return archive;
}

public final EntryType type() {
return type;
}
Expand Down Expand Up @@ -134,5 +132,6 @@ public String toString() {
/*
* Close the archive
*/
@Override
void close() throws IOException;
}
Loading