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