Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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.function.Predicate;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import jdk.internal.jimage.decompressor.Decompressor;

/**
Expand Down Expand Up @@ -326,6 +330,58 @@ 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> entryNamesIn(String module) {
if (module.isEmpty() || module.equals("modules") || module.equals("packages")) {
throw new IllegalArgumentException("Invalid module name: " + module);
}
return IntStream.range(0, offsets.capacity())
.map(offsets::get)
.filter(offset -> offset != 0)
// Reusing a location instance or getting the module
// offset directly would save a lot of allocations here.
.mapToObj(offset -> ImageLocation.readFrom(BasicImageReader.this, offset))
// Reverse lookup of module offset would be faster here.
.filter(loc -> module.equals(loc.getModule()))
.map(ImageLocation::getFullName);
}

private ImageLocation getResourceLocation(String name) {
// Other types of invalid name just result in no entry being found.
if (name.startsWith("/modules/") || name.startsWith("/packages/")) {
throw new IllegalArgumentException("Invalid entry name: " + name);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is already covered by the == null case below. The distinguished exception isn't necessary.

ImageLocation location = BasicImageReader.this.findLocation(name);
if (location == null) {
throw new NoSuchElementException("No such resource entry: " + name);
}
return location;
}

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

@Override
public InputStream open(String name) {
return BasicImageReader.this.getResourceStream(getResourceLocation(name));
}
};
}

ImageLocation getLocation(int offset) {
return ImageLocation.readFrom(this, offset);
}
Expand Down
24 changes: 24 additions & 0 deletions src/java.base/share/classes/jdk/internal/jimage/ImageLocation.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
package jdk.internal.jimage;

import java.nio.ByteBuffer;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;

Expand Down Expand Up @@ -159,6 +160,29 @@ public static int getFlags(String name, Predicate<String> hasEntry) {
}
}

/**
* Helper function to calculate package flags for {@code "/packages/xxx"}
* directory entries.
*
* <p>Based on the module references, the flags are:
* <ul>
* <li>{@code FLAGS_HAS_PREVIEW_VERSION} if <em>any</em> referenced
* package has a preview version.
* <li>{@code FLAGS_IS_PREVIEW_ONLY} if <em>all</em> referenced packages
* are preview only.
* </ul>
*
* @return package flags for {@code "/packages/xxx"} directory entries.
*/
public static int getPackageFlags(List<ModuleReference> moduleReferences) {
boolean hasPreviewVersion =
moduleReferences.stream().anyMatch(ModuleReference::hasPreviewVersion);
boolean isPreviewOnly =
moduleReferences.stream().allMatch(ModuleReference::isPreviewOnly);
return (hasPreviewVersion ? ImageLocation.FLAGS_HAS_PREVIEW_VERSION : 0)
| (isPreviewOnly ? ImageLocation.FLAGS_IS_PREVIEW_ONLY : 0);
}

/**
* Tests a non-preview image location's flags to see if it has preview
* content associated with it.
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
Expand Up @@ -53,8 +53,7 @@ public final class ModuleReference implements Comparable<ModuleReference> {
/** If set, this package exists in non-preview mode. */
private static final int FLAGS_PKG_HAS_NORMAL_VERSION = 0x2;
/** If set, the associated module has resources (in normal or preview mode). */
// TODO: Make this private again when image writer code is updated.
public static final int FLAGS_PKG_HAS_RESOURCES = 0x4;
private static final int FLAGS_PKG_HAS_RESOURCES = 0x4;

/**
* References are ordered with preview versions first which permits early
Expand Down Expand Up @@ -118,7 +117,7 @@ public String name() {
* under many modules, it only has resources in one.
*/
public boolean hasResources() {
return ((flags & FLAGS_PKG_HAS_RESOURCES) != 0);
return (flags & FLAGS_PKG_HAS_RESOURCES) != 0;
}

/**
Expand Down Expand Up @@ -176,9 +175,9 @@ public static Iterator<Integer> readNameOffsets(
if (bufferSize == 0 || (bufferSize & 0x1) != 0) {
throw new IllegalArgumentException("Invalid buffer size");
}
int testFlags = (includeNormal ? FLAGS_PKG_HAS_NORMAL_VERSION : 0)
int includeMask = (includeNormal ? FLAGS_PKG_HAS_NORMAL_VERSION : 0)
+ (includePreview ? FLAGS_PKG_HAS_PREVIEW_VERSION : 0);
if (testFlags == 0) {
if (includeMask == 0) {
throw new IllegalArgumentException("Invalid flags");
}

Expand All @@ -188,14 +187,7 @@ public static Iterator<Integer> readNameOffsets(
int nextIdx(int idx) {
for (; idx < bufferSize; idx += 2) {
// If any of the test flags are set, include this entry.

// Temporarily allow for *neither* flag to be set. This is what would
// be written by a 1.0 version of the jimage flag, and indicates a
// normal resource without a preview version.
// TODO: Remove the zero-check below once image writer code is updated.
int previewFlags =
buffer.get(idx) & (FLAGS_PKG_HAS_NORMAL_VERSION | FLAGS_PKG_HAS_PREVIEW_VERSION);
if (previewFlags == 0 || (previewFlags & testFlags) != 0) {
if ((buffer.get(idx) & includeMask) != 0) {
return idx;
} else if (!includeNormal) {
// Preview entries are first in the offset buffer, so we
Expand Down
13 changes: 13 additions & 0 deletions src/java.base/share/classes/jdk/internal/jimage/PreviewMode.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ public enum PreviewMode {
* Resolves whether preview mode should be enabled for an {@link ImageReader}.
*/
public boolean isPreviewModeEnabled() {
if (!ENABLE_PREVIEW_MODE) {
return false;
}
// A switch, instead of an abstract method, saves 3 subclasses.
switch (this) {
case DISABLED:
Expand Down Expand Up @@ -83,4 +86,14 @@ public boolean isPreviewModeEnabled() {
throw new IllegalStateException("Invalid mode: " + this);
}
}
;

// Temporary system property to disable preview patching and enable the new preview mode
// feature for testing/development. Once the preview mode feature is finished, the value
// will be always 'true' and this code, and all related dead-code can be removed.
private static final boolean DISABLE_PREVIEW_PATCHING_DEFAULT = false;
private static final boolean ENABLE_PREVIEW_MODE = Boolean.parseBoolean(
System.getProperty(
"DISABLE_PREVIEW_PATCHING",
Boolean.toString(DISABLE_PREVIEW_PATCHING_DEFAULT)));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package jdk.internal.jimage;

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

/**
* Accesses the underlying resource entries in a jimage file.
*
* <p>This API is designed only for use by the jlink classes, which manipulate
* jimage files directly. For inspection of runtime resources, it is vital that
* {@code previewMode} is correctly observed, making this API unsuitable.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This would be more clearly stated as use API xxx to access the classes and resources of a runtime.

*
* <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 (i.e. {@code "/packages/..."}.
*/
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 full entry names for all resources in the given module, in
* random order. Entry names will always be prefixed by the given module
* name (e.g. "/<module-name/...").
*/
Stream<String> entryNamesIn(String module);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are these names useful with any ImageReader api other than sizeOf or open?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Those extra words at the end of the method name are not consistent with naming conventions in openjdk.
Drop the "In".


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

/**
* Returns an {@link InputStream} for a resource given its full entry name.
*
* @throws java.util.NoSuchElementException if the resource does not exist.
*/
InputStream open(String name);
}
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() {}
}
7 changes: 1 addition & 6 deletions src/java.base/share/native/libjimage/imageFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,7 @@ bool ImageFileReader::open() {
!read_at((u1*)&_header, header_size, 0) ||
_header.magic(_endian) != IMAGE_MAGIC ||
_header.major_version(_endian) != MAJOR_VERSION ||
// Temporarily, we allow either version (1.1 or 1.0) of the file to
// be read so this code can be committed before image writing changes
// for preview mode. Preview mode changes do not modify any structure,
// so a 1.0 file will look like a jimage without any preview resources.
// TODO: Restore equality check for MINOR_VERSION.
_header.minor_version(_endian) > MINOR_VERSION) {
_header.minor_version(_endian) != MINOR_VERSION) {
close();
return false;
}
Expand Down
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