Skip to content
24 changes: 22 additions & 2 deletions make/CompileJavaModules.gmk
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,16 @@ endif

################################################################################
# Setup the main compilation

COMPILATION_OUTPUTDIR := $(if $($(MODULE)_BIN), $($(MODULE)_BIN), $(JDK_OUTPUTDIR)/modules)

$(eval $(call SetupJavaCompilation, $(MODULE), \
SMALL_JAVA := false, \
MODULE := $(MODULE), \
SRC := $(wildcard $(MODULE_SRC_DIRS)), \
INCLUDES := $(JDK_USER_DEFINED_FILTER), \
FAIL_NO_SRC := $(FAIL_NO_SRC), \
BIN := $(if $($(MODULE)_BIN), $($(MODULE)_BIN), $(JDK_OUTPUTDIR)/modules), \
BIN := $(COMPILATION_OUTPUTDIR), \
HEADERS := $(SUPPORT_OUTPUTDIR)/headers, \
CREATE_API_DIGEST := true, \
CLEAN := $(CLEAN), \
Expand Down Expand Up @@ -138,14 +141,23 @@ ifneq ($(COMPILER), bootjdk)
MODULE_VALUECLASS_SRC_DIRS := $(call FindModuleValueClassSrcDirs, $(MODULE))
MODULE_VALUECLASS_SOURCEPATH := $(call GetModuleValueClassSrcPath)

# Temporarily compile valueclasses into a separate directory with the form:
# <tempdir>/<module>/<classpath>
# and then copy the class files into:
# <outdir>/<module>/META-INF/preview/<classpath>
# We cannot compile directly into the desired directory because it's the
# compiler which creates the original '<module>/<classpath>/...' hierarchy.
VALUECLASS_OUTPUTDIR := $(SUPPORT_OUTPUTDIR)/$(VALUECLASSES_STR)
PREVIEW_OUTPUTDIR := $(COMPILATION_OUTPUTDIR)/$(MODULE)/META-INF/preview

ifneq ($(MODULE_VALUECLASS_SRC_DIRS),)
$(eval $(call SetupJavaCompilation, $(MODULE)-$(VALUECLASSES_STR), \
SMALL_JAVA := false, \
MODULE := $(MODULE), \
SRC := $(wildcard $(MODULE_VALUECLASS_SRC_DIRS)), \
INCLUDES := $(JDK_USER_DEFINED_FILTER), \
FAIL_NO_SRC := $(FAIL_NO_SRC), \
BIN := $(SUPPORT_OUTPUTDIR)/$(VALUECLASSES_STR)/, \
BIN := $(VALUECLASS_OUTPUTDIR)/, \
JAR := $(JDK_OUTPUTDIR)/lib/$(VALUECLASSES_STR)/$(MODULE)-$(VALUECLASSES_STR).jar, \
HEADERS := $(SUPPORT_OUTPUTDIR)/headers, \
DISABLED_WARNINGS := $(DISABLED_WARNINGS_java) preview, \
Expand All @@ -163,6 +175,14 @@ ifneq ($(COMPILER), bootjdk)

TARGETS += $($(MODULE)-$(VALUECLASSES_STR))

# Restructure the class file hierarchy from <module>/<classpath>/... to <module>/META-INF/preview/<classpath>/...
$(PREVIEW_OUTPUTDIR)/_copy_valueclasses.marker: $($(MODULE)-$(VALUECLASSES_STR))
$(call MakeTargetDir)
$(CP) -R $(VALUECLASS_OUTPUTDIR)/$(MODULE)/. $(@D)/
$(TOUCH) $@

TARGETS += $(PREVIEW_OUTPUTDIR)/_copy_valueclasses.marker

Comment on lines +178 to +185
Copy link
Collaborator

Choose a reason for hiding this comment

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

I expect build reviewer will suggest using the SetupCopyFiles mechanism to copy files.

$(eval $(call SetupCopyFiles, $(MODULE)-copy-valueclass-jar, \
FILES := $(JDK_OUTPUTDIR)/lib/$(VALUECLASSES_STR)/$(MODULE)-$(VALUECLASSES_STR).jar, \
DEST := $(SUPPORT_OUTPUTDIR)/modules_libs/$(MODULE)/$(VALUECLASSES_STR), \
Expand Down
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,87 @@ 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() {
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);
}
ImageLocation location = BasicImageReader.this.findLocation(name);
if (location == null) {
throw new NoSuchElementException("No such resource entry: " + name);
}
return location;
}

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

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

/**
* Returns a sorted array of all matching entry names in the jimage file.
*
* <p>Entry names are of one of the following forms:
* <ul>
* <li>{@code "/modules/<mod-name>/path/to/class-or-resource"}
* <li>{@code "/<mod-name>/path/to/directory"}
* <li>{@code "/packages/<package-name>"}
* </ul>
*
* <p>Note that the module names {@code "modules"} or {@code "packages"} are
* not representable in a jimage file, so can never exist.
*
* <p>The resulting array is sorted lexicographically, and the resulting
* order need not match that of a breadth or depth first search.
*
* @param matcher a predicate for entry names to be returned.
*/
public String[] getEntryNames(Predicate<String> matcher) {
int[] attributeOffsets = new int[offsets.capacity()];
offsets.get(attributeOffsets);
return IntStream.of(attributeOffsets)
.filter(o -> o != 0)
.mapToObj(o -> ImageLocation.readFrom(this, o).getFullName())
.filter(matcher)
.sorted()
.toArray(String[]::new);
}

ImageLocation getLocation(int offset) {
return ImageLocation.readFrom(this, offset);
}
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
13 changes: 13 additions & 0 deletions src/java.base/share/classes/jdk/internal/jimage/ImageReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,19 @@ public ByteBuffer getResourceBuffer(Node node) {
return reader.getResourceBuffer(node.getLocation());
}

/**
* 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.
*/
public ResourceEntries getResourceEntries() {
return reader.getResourceEntries();
}

private static final class SharedImageReader extends BasicImageReader {
// There are >30,000 nodes in a complete jimage tree, and even relatively
// common tasks (e.g. starting up javac) load somewhere in the region of
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.
*
* <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 {
/**
* 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);

/**
* 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);
}
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
Loading