11/*
2- * Copyright (c) 2014, 2021 , Oracle and/or its affiliates. All rights reserved.
2+ * Copyright (c) 2014, 2025 , Oracle and/or its affiliates. All rights reserved.
33 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44 *
55 * This code is free software; you can redistribute it and/or modify it
2727
2828import java .nio .ByteBuffer ;
2929import java .util .Objects ;
30+ import java .util .function .Predicate ;
3031
3132/**
3233 * @implNote This class needs to maintain JDK 8 source compatibility.
@@ -44,7 +45,126 @@ public class ImageLocation {
4445 public static final int ATTRIBUTE_OFFSET = 5 ;
4546 public static final int ATTRIBUTE_COMPRESSED = 6 ;
4647 public static final int ATTRIBUTE_UNCOMPRESSED = 7 ;
47- public static final int ATTRIBUTE_COUNT = 8 ;
48+ public static final int ATTRIBUTE_PREVIEW_FLAGS = 8 ;
49+ public static final int ATTRIBUTE_COUNT = 9 ;
50+
51+ // Flag masks for the ATTRIBUTE_PREVIEW_FLAGS attribute. Defined so
52+ // that zero is the overwhelmingly common case for normal resources.
53+
54+ /**
55+ * Indicates that a non-preview location is associated with preview
56+ * resources.
57+ *
58+ * <p>This can apply to both resources and directories in the
59+ * {@code /modules/xxx/...} namespace, as well as {@code /packages/xxx}
60+ * directories.
61+ *
62+ * <p>For {@code /packages/xxx} directories, it indicates that the package
63+ * has preview resources in one of the modules in which it exists.
64+ */
65+ private static final int FLAGS_HAS_PREVIEW_VERSION = 0x1 ;
66+ /**
67+ * Set on all locations in the {@code /modules/xxx/META-INF/preview/...}
68+ * namespace.
69+ *
70+ * <p>This flag is mutually exclusive with {@link #FLAGS_HAS_PREVIEW_VERSION}.
71+ */
72+ private static final int FLAGS_IS_PREVIEW_VERSION = 0x2 ;
73+ /**
74+ * Indicates that a location only exists due to preview resources.
75+ *
76+ * <p>This can apply to both resources and directories in the
77+ * {@code /modules/xxx/...} namespace, as well as {@code /packages/xxx}
78+ * directories.
79+ *
80+ * <p>For {@code /packages/xxx} directories it indicates that, for every
81+ * module in which the package exists, it is preview only.
82+ *
83+ * <p>This flag is mutually exclusive with {@link #FLAGS_HAS_PREVIEW_VERSION}
84+ * and need not imply that {@link #FLAGS_IS_PREVIEW_VERSION} is set (i.e.
85+ * for {@code /packages/xxx} directories).
86+ */
87+ private static final int FLAGS_IS_PREVIEW_ONLY = 0x4 ;
88+
89+ // Also used in ImageReader.
90+ static final String MODULES_PREFIX = "/modules" ;
91+ static final String PACKAGES_PREFIX = "/packages" ;
92+ static final String PREVIEW_INFIX = "/META-INF/preview" ;
93+
94+ /**
95+ * Helper function to calculate preview flags (ATTRIBUTE_PREVIEW_FLAGS).
96+ *
97+ * <p>Since preview flags are calculated separately for resource nodes and
98+ * directory nodes (in two quite different places) it's useful to have a
99+ * common helper.
100+ *
101+ * <p>Based on the entry name, the flags are:
102+ * <ul>
103+ * <li>{@code "[/modules]/<module>/<path>"} normal resource or directory:<br>
104+ * Zero, or {@code FLAGS_HAS_PREVIEW_VERSION} if a preview entry exists.
105+ * <li>{@code "[/modules]/<module>/META-INF/preview/<path>"} preview
106+ * resource or directory:<br>
107+ * {@code FLAGS_IS_PREVIEW_VERSION}, and additionally {@code
108+ * FLAGS_IS_PREVIEW_ONLY} if no normal version of the resource exists.
109+ * <li>In all other cases, returned flags are zero (note that {@code
110+ * "/packages/xxx"} entries may have flags, but these are calculated
111+ * elsewhere).
112+ * </ul>
113+ *
114+ * @param name the jimage name of the resource or directory.
115+ * @param hasEntry a predicate for jimage names returning whether an entry
116+ * is present.
117+ * @return flags for the ATTRIBUTE_PREVIEW_FLAGS attribute.
118+ */
119+ public static int getFlags (String name , Predicate <String > hasEntry ) {
120+ if (name .startsWith (PACKAGES_PREFIX + "/" )) {
121+ throw new IllegalArgumentException (
122+ "Package sub-directory flags handled separately: " + name );
123+ }
124+ // Find suffix for either '/modules/xxx/suffix' or '/xxx/suffix' paths.
125+ int idx = name .startsWith (MODULES_PREFIX + "/" ) ? MODULES_PREFIX .length () + 1 : 1 ;
126+ int suffixStart = name .indexOf ('/' , idx );
127+ if (suffixStart == -1 ) {
128+ // No flags for '[/modules]/xxx' paths (esp. '/modules', '/packages').
129+ // '/packages/xxx' entries have flags, but not calculated here.
130+ return 0 ;
131+ }
132+ // Prefix is either '/modules/xxx' or '/xxx', and suffix starts with '/'.
133+ String prefix = name .substring (0 , suffixStart );
134+ String suffix = name .substring (suffixStart );
135+ if (suffix .startsWith (PREVIEW_INFIX + "/" )) {
136+ // Preview resources/directories.
137+ String nonPreviewName = prefix + suffix .substring (PREVIEW_INFIX .length ());
138+ return FLAGS_IS_PREVIEW_VERSION
139+ | (hasEntry .test (nonPreviewName ) ? 0 : FLAGS_IS_PREVIEW_ONLY );
140+ } else if (!suffix .startsWith ("/META-INF/" )) {
141+ // Non-preview resources/directories.
142+ String previewName = prefix + PREVIEW_INFIX + suffix ;
143+ return hasEntry .test (previewName ) ? FLAGS_HAS_PREVIEW_VERSION : 0 ;
144+ } else {
145+ // Suffix is '/META-INF/xxx' and no preview version is even possible.
146+ return 0 ;
147+ }
148+ }
149+
150+ /**
151+ * Tests a non-preview image location's flags to see if it has preview
152+ * content associated with it.
153+ */
154+ public static boolean hasPreviewVersion (int flags ) {
155+ return (flags & FLAGS_HAS_PREVIEW_VERSION ) != 0 ;
156+ }
157+
158+ /**
159+ * Tests an image location's flags to see if it only exists in preview mode.
160+ */
161+ public static boolean isPreviewOnly (int flags ) {
162+ return (flags & FLAGS_IS_PREVIEW_ONLY ) != 0 ;
163+ }
164+
165+ public enum LocationType {
166+ RESOURCE , MODULES_ROOT , MODULES_DIR , PACKAGES_ROOT , PACKAGES_DIR ;
167+ }
48168
49169 protected final long [] attributes ;
50170
@@ -285,6 +405,10 @@ public int getExtensionOffset() {
285405 return (int )getAttribute (ATTRIBUTE_EXTENSION );
286406 }
287407
408+ public int getFlags () {
409+ return (int ) getAttribute (ATTRIBUTE_PREVIEW_FLAGS );
410+ }
411+
288412 public String getFullName () {
289413 return getFullName (false );
290414 }
@@ -294,7 +418,7 @@ public String getFullName(boolean modulesPrefix) {
294418
295419 if (getModuleOffset () != 0 ) {
296420 if (modulesPrefix ) {
297- builder .append ("/modules" );
421+ builder .append (MODULES_PREFIX );
298422 }
299423
300424 builder .append ('/' );
@@ -317,36 +441,6 @@ public String getFullName(boolean modulesPrefix) {
317441 return builder .toString ();
318442 }
319443
320- String buildName (boolean includeModule , boolean includeParent ,
321- boolean includeName ) {
322- StringBuilder builder = new StringBuilder ();
323-
324- if (includeModule && getModuleOffset () != 0 ) {
325- builder .append ("/modules/" );
326- builder .append (getModule ());
327- }
328-
329- if (includeParent && getParentOffset () != 0 ) {
330- builder .append ('/' );
331- builder .append (getParent ());
332- }
333-
334- if (includeName ) {
335- if (includeModule || includeParent ) {
336- builder .append ('/' );
337- }
338-
339- builder .append (getBase ());
340-
341- if (getExtensionOffset () != 0 ) {
342- builder .append ('.' );
343- builder .append (getExtension ());
344- }
345- }
346-
347- return builder .toString ();
348- }
349-
350444 public long getContentOffset () {
351445 return getAttribute (ATTRIBUTE_OFFSET );
352446 }
@@ -359,6 +453,42 @@ public long getUncompressedSize() {
359453 return getAttribute (ATTRIBUTE_UNCOMPRESSED );
360454 }
361455
456+ // Fast (zero allocation) type determination for locations.
457+ public LocationType getType () {
458+ switch (getModuleOffset ()) {
459+ case ImageStrings .MODULES_STRING_OFFSET :
460+ // Locations in /modules/... namespace are directory entries.
461+ return LocationType .MODULES_DIR ;
462+ case ImageStrings .PACKAGES_STRING_OFFSET :
463+ // Locations in /packages/... namespace are always 2-level
464+ // "/packages/xxx" directories.
465+ return LocationType .PACKAGES_DIR ;
466+ case ImageStrings .EMPTY_STRING_OFFSET :
467+ // Only 2 choices, either the "/modules" or "/packages" root.
468+ assert isRootDir () : "Invalid root directory: " + getFullName ();
469+ return getBase ().charAt (1 ) == 'p'
470+ ? LocationType .PACKAGES_ROOT
471+ : LocationType .MODULES_ROOT ;
472+ default :
473+ // Anything else is /<module>/<path> and references a resource.
474+ return LocationType .RESOURCE ;
475+ }
476+ }
477+
478+ private boolean isRootDir () {
479+ if (getModuleOffset () == 0 && getParentOffset () == 0 ) {
480+ String name = getFullName ();
481+ return name .equals (MODULES_PREFIX ) || name .equals (PACKAGES_PREFIX );
482+ }
483+ return false ;
484+ }
485+
486+ @ Override
487+ public String toString () {
488+ // Cannot use String.format() (too early in startup for locale code).
489+ return "ImageLocation[name='" + getFullName () + "', type=" + getType () + ", flags=" + getFlags () + "]" ;
490+ }
491+
362492 static ImageLocation readFrom (BasicImageReader reader , int offset ) {
363493 Objects .requireNonNull (reader );
364494 long [] attributes = reader .getAttributes (offset );
0 commit comments