Skip to content
Open
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
21 changes: 19 additions & 2 deletions src/java.base/share/classes/jdk/internal/jimage/ImageHeader.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2016, 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
Expand Down Expand Up @@ -30,6 +30,23 @@
import java.util.Objects;

/**
* Defines the header and version information for jimage files.
*
* <p>Version number changes must be synced in a single change across all code
* which reads/writes jimage files, and code which tries to open a jimage file
* with an unexpected version should fail.
*
* <p>Known jimage file code which needs updating on version change:
* <ul>
* <li>src/java.base/share/native/libjimage/imageFile.hpp
* </ul>
*
* <p>Version history:
* <ul>
* <li>{@code 1.0}: Original version.
* <li>{@code 1.1}: Support preview mode with new flags.
* </ul>
*
* @implNote This class needs to maintain JDK 8 source compatibility.
*
* It is used internally in the JDK to implement jimage/jrtfs access,
Expand All @@ -39,7 +56,7 @@
public final class ImageHeader {
public static final int MAGIC = 0xCAFEDADA;
public static final int MAJOR_VERSION = 1;
public static final int MINOR_VERSION = 0;
public static final int MINOR_VERSION = 1;
private static final int HEADER_SLOTS = 7;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add a comment before these constants connecting them to imageFile.hpp.


private final int magic;
Expand Down
208 changes: 175 additions & 33 deletions src/java.base/share/classes/jdk/internal/jimage/ImageLocation.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2021, 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
Expand Down Expand Up @@ -27,6 +27,7 @@

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

/**
* @implNote This class needs to maintain JDK 8 source compatibility.
Expand All @@ -36,15 +37,146 @@
* to the jimage file provided by the shipped JDK by tools running on JDK 8.
*/
public class ImageLocation {
// Also defined in src/java.base/share/native/libjimage/imageFile.hpp

/** End of attribute stream marker. */
public static final int ATTRIBUTE_END = 0;
/** String table offset of module name. */
public static final int ATTRIBUTE_MODULE = 1;
/** String table offset of resource path parent. */
public static final int ATTRIBUTE_PARENT = 2;
/** String table offset of resource path base. */
public static final int ATTRIBUTE_BASE = 3;
/** String table offset of resource path extension. */
public static final int ATTRIBUTE_EXTENSION = 4;
/** Container byte offset of resource. */
public static final int ATTRIBUTE_OFFSET = 5;
/** In-image byte size of the compressed resource. */
public static final int ATTRIBUTE_COMPRESSED = 6;
/** In-memory byte size of the uncompressed resource. */
public static final int ATTRIBUTE_UNCOMPRESSED = 7;
public static final int ATTRIBUTE_COUNT = 8;
/** Flags relating to preview mode resources. */
public static final int ATTRIBUTE_PREVIEW_FLAGS = 8;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add a comment above these constants that the values are defined in ImageFile.hpp.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Thought really I'd say they are defined here, since this is what the code that creates the jimage file uses (so these constants are definitive). The C++ ones are copies for the C++ reading code.

/** Number of attribute kinds. */
public static final int ATTRIBUTE_COUNT = 9;

// Flag masks for the ATTRIBUTE_PREVIEW_FLAGS attribute. Defined so
// that zero is the overwhelmingly common case for normal resources.

/**
* Indicates that a non-preview location is associated with preview
* resources.
Comment on lines +67 to +68
Copy link
Collaborator

Choose a reason for hiding this comment

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

This comment would read better saying that preview resources exist related to this location.
More like FLAGS_HAS_PREVIEW_VERSION.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahh, but it's deliberately not worded like that because this flag can be set for "/packages/xxx" entries, for which "preview resources exist related to this location" is not a meaningful statement.

This is where you could add another flag (e.g. "FLAGS_PACKAGE_HAS_PREVIEW_ENTRIES"), but it felt better to use the same flag for this, since where it's calculated becomes a neat case of just propagating the flag (next PR):

            // Calculate the package's ImageLocation flags.
            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);

And the flag/method names in this code just match across this logic.

*
* <p>This can apply to both resources and directories in the
* {@code /modules/xxx/...} namespace, as well as {@code /packages/xxx}
* directories.
*
* <p>For {@code /packages/xxx} directories, it indicates that the package
* has preview resources in one of the modules in which it exists.
*/
private static final int FLAGS_HAS_PREVIEW_VERSION = 0x1;
/**
* Set on all locations in the {@code /modules/xxx/META-INF/preview/...}
* namespace.
*
* <p>This flag is mutually exclusive with {@link #FLAGS_HAS_PREVIEW_VERSION}.
*/
private static final int FLAGS_IS_PREVIEW_VERSION = 0x2;
/**
* Indicates that a location only exists due to preview resources.
*
* <p>This can apply to both resources and directories in the
* {@code /modules/xxx/...} namespace, as well as {@code /packages/xxx}
* directories.
*
* <p>For {@code /packages/xxx} directories it indicates that, for every
* module in which the package exists, it is preview only.
*
* <p>This flag is mutually exclusive with {@link #FLAGS_HAS_PREVIEW_VERSION}
* and need not imply that {@link #FLAGS_IS_PREVIEW_VERSION} is set (i.e.
* for {@code /packages/xxx} directories).
*/
private static final int FLAGS_IS_PREVIEW_ONLY = 0x4;

// Also used in ImageReader.
static final String MODULES_PREFIX = "/modules";
static final String PACKAGES_PREFIX = "/packages";
static final String PREVIEW_INFIX = "/META-INF/preview";

/**
* Helper function to calculate preview flags (ATTRIBUTE_PREVIEW_FLAGS).
*
Comment on lines +107 to +108
Copy link
Collaborator

Choose a reason for hiding this comment

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

A summary of what gets what flags might be easier to read than the code.
Or list the flags and refer to their descriptions (that would describe the paths where the flags would be found)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in latest snapshot.

* <p>Since preview flags are calculated separately for resource nodes and
* directory nodes (in two quite different places) it's useful to have a
* common helper.
*
* <p>Based on the entry name, the flags are:
* <ul>
* <li>{@code "[/modules]/<module>/<path>"} normal resource or directory:<br>
* Zero, or {@code FLAGS_HAS_PREVIEW_VERSION} if a preview entry exists.
* <li>{@code "[/modules]/<module>/META-INF/preview/<path>"} preview
* resource or directory:<br>
* {@code FLAGS_IS_PREVIEW_VERSION}, and additionally {@code
* FLAGS_IS_PREVIEW_ONLY} if no normal version of the resource exists.
* <li>In all other cases, returned flags are zero (note that {@code
* "/packages/xxx"} entries may have flags, but these are calculated
* elsewhere).
* </ul>
*
* @param name the jimage name of the resource or directory.
* @param hasEntry a predicate for jimage names returning whether an entry
* is present.
* @return flags for the ATTRIBUTE_PREVIEW_FLAGS attribute.
*/
public static int getFlags(String name, Predicate<String> hasEntry) {
if (name.startsWith(PACKAGES_PREFIX + "/")) {
throw new IllegalArgumentException(
"Package sub-directory flags handled separately: " + name);
}
// Find suffix for either '/modules/xxx/suffix' or '/xxx/suffix' paths.
int idx = name.startsWith(MODULES_PREFIX + "/") ? MODULES_PREFIX.length() + 1 : 1;
int suffixStart = name.indexOf('/', idx);
if (suffixStart == -1) {
// No flags for '[/modules]/xxx' paths (esp. '/modules', '/packages').
// '/packages/xxx' entries have flags, but not calculated here.
return 0;
}
// Prefix is either '/modules/xxx' or '/xxx', and suffix starts with '/'.
String prefix = name.substring(0, suffixStart);
String suffix = name.substring(suffixStart);
if (suffix.startsWith(PREVIEW_INFIX + "/")) {
// Preview resources/directories.
String nonPreviewName = prefix + suffix.substring(PREVIEW_INFIX.length());
return FLAGS_IS_PREVIEW_VERSION
| (hasEntry.test(nonPreviewName) ? 0 : FLAGS_IS_PREVIEW_ONLY);
} else if (!suffix.startsWith("/META-INF/")) {
// Non-preview resources/directories.
String previewName = prefix + PREVIEW_INFIX + suffix;
return hasEntry.test(previewName) ? FLAGS_HAS_PREVIEW_VERSION : 0;
} else {
// Suffix is '/META-INF/xxx' and no preview version is even possible.
return 0;
}
}

/**
* Tests a non-preview image location's flags to see if it has preview
* content associated with it.
*/
public static boolean hasPreviewVersion(int flags) {
return (flags & FLAGS_HAS_PREVIEW_VERSION) != 0;
}

/**
* Tests an image location's flags to see if it only exists in preview mode.
*/
public static boolean isPreviewOnly(int flags) {
return (flags & FLAGS_IS_PREVIEW_ONLY) != 0;
}

public enum LocationType {
RESOURCE, MODULES_ROOT, MODULES_DIR, PACKAGES_ROOT, PACKAGES_DIR;
}

protected final long[] attributes;

Expand Down Expand Up @@ -285,6 +417,10 @@ public int getExtensionOffset() {
return (int)getAttribute(ATTRIBUTE_EXTENSION);
}

public int getFlags() {
return (int) getAttribute(ATTRIBUTE_PREVIEW_FLAGS);
}

public String getFullName() {
return getFullName(false);
}
Expand All @@ -294,7 +430,7 @@ public String getFullName(boolean modulesPrefix) {

if (getModuleOffset() != 0) {
if (modulesPrefix) {
builder.append("/modules");
builder.append(MODULES_PREFIX);
}

builder.append('/');
Expand All @@ -317,36 +453,6 @@ public String getFullName(boolean modulesPrefix) {
return builder.toString();
}

String buildName(boolean includeModule, boolean includeParent,
boolean includeName) {
StringBuilder builder = new StringBuilder();

if (includeModule && getModuleOffset() != 0) {
builder.append("/modules/");
builder.append(getModule());
}

if (includeParent && getParentOffset() != 0) {
builder.append('/');
builder.append(getParent());
}

if (includeName) {
if (includeModule || includeParent) {
builder.append('/');
}

builder.append(getBase());

if (getExtensionOffset() != 0) {
builder.append('.');
builder.append(getExtension());
}
}

return builder.toString();
}

public long getContentOffset() {
return getAttribute(ATTRIBUTE_OFFSET);
}
Expand All @@ -359,6 +465,42 @@ public long getUncompressedSize() {
return getAttribute(ATTRIBUTE_UNCOMPRESSED);
}

// Fast (zero allocation) type determination for locations.
public LocationType getType() {
switch (getModuleOffset()) {
case ImageStrings.MODULES_STRING_OFFSET:
// Locations in /modules/... namespace are directory entries.
return LocationType.MODULES_DIR;
case ImageStrings.PACKAGES_STRING_OFFSET:
// Locations in /packages/... namespace are always 2-level
// "/packages/xxx" directories.
return LocationType.PACKAGES_DIR;
case ImageStrings.EMPTY_STRING_OFFSET:
// Only 2 choices, either the "/modules" or "/packages" root.
assert isRootDir() : "Invalid root directory: " + getFullName();
return getBase().charAt(1) == 'p'
? LocationType.PACKAGES_ROOT
: LocationType.MODULES_ROOT;
default:
// Anything else is /<module>/<path> and references a resource.
return LocationType.RESOURCE;
}
}

private boolean isRootDir() {
if (getModuleOffset() == 0 && getParentOffset() == 0) {
String name = getFullName();
return name.equals(MODULES_PREFIX) || name.equals(PACKAGES_PREFIX);
}
return false;
}

@Override
public String toString() {
// Cannot use String.format() (too early in startup for locale code).
return "ImageLocation[name='" + getFullName() + "', type=" + getType() + ", flags=" + getFlags() + "]";
}

static ImageLocation readFrom(BasicImageReader reader, int offset) {
Objects.requireNonNull(reader);
long[] attributes = reader.getAttributes(offset);
Expand Down
Loading