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>
Comment on lines +39 to +42
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are there any java source files that have to be updated on a version change?

*
* <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;

private final int magic;
Expand Down
178 changes: 145 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 @@ -44,7 +45,102 @@ public class ImageLocation {
public static final int ATTRIBUTE_OFFSET = 5;
public static final int ATTRIBUTE_COMPRESSED = 6;
public static final int ATTRIBUTE_UNCOMPRESSED = 7;
public static final int ATTRIBUTE_COUNT = 8;
public static final int ATTRIBUTE_PREVIEW_FLAGS = 8;
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 +55 to +56
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.

*
* <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.
*/
public 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}.
*/
public static final int FLAGS_IS_PREVIEW_VERSION = 0x2;
Comment on lines +66 to +72
Copy link
Collaborator

Choose a reason for hiding this comment

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

Seems to be redundant with HAS_PREVIEW_VERSION when seen in a preview location.

/**
* 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).
*/
public static final int FLAGS_IS_PREVIEW_ONLY = 0x4;
Comment on lines +73 to +87
Copy link
Collaborator

Choose a reason for hiding this comment

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

The preview-only case is expected to be the empty set. There seems to be a lot of code dedicated to it.

/**
* This flag identifies the unique {@code "/packages"} location, and
* is used to determine the {@link LocationType} without additional
* string comparison.
*
* <p>This flag is mutually exclusive with all other flags.
*/
public static final int FLAGS_IS_PACKAGE_ROOT = 0x8;

// 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 +103 to +104
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)

* <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.
*
* @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);
}
String start = name.startsWith(MODULES_PREFIX + "/") ? MODULES_PREFIX + "/" : "/";
int idx = name.indexOf('/', start.length());
if (idx == -1) {
// Special case for "/packages" root, but otherwise, no flags.
return name.equals(PACKAGES_PREFIX) ? FLAGS_IS_PACKAGE_ROOT : 0;
}
String prefix = name.substring(0, idx);
String suffix = name.substring(idx);
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 {
// Edge case for things META-INF/module-info.class etc.
return 0;
}
}

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

protected final long[] attributes;

Expand Down Expand Up @@ -285,6 +381,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 +394,7 @@ public String getFullName(boolean modulesPrefix) {

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

builder.append('/');
Expand All @@ -317,36 +417,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 +429,48 @@ 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();

// Temporary logic to handle package root classification until new
// image reader code is committed which sets FLAGS_IS_PACKAGE_ROOT.
// Base name is "/packages" or "/modules" (NOT "packages" and "modules").
// TODO: Uncomment the FLAGS_IS_PACKAGE_ROOT test below.
// return (getFlags() & FLAGS_IS_PACKAGE_ROOT) != 0
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