1 /*
  2  * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package jdk.internal.jimage;
 27 
 28 import java.nio.ByteBuffer;
 29 import java.util.List;
 30 import java.util.Objects;
 31 import java.util.function.Predicate;
 32 
 33 /**
 34  * @implNote This class needs to maintain JDK 8 source compatibility.
 35  *
 36  * It is used internally in the JDK to implement jimage/jrtfs access,
 37  * but also compiled and delivered as part of the jrtfs.jar to support access
 38  * to the jimage file provided by the shipped JDK by tools running on JDK 8.
 39  */
 40 public class ImageLocation {
 41     // Also defined in src/java.base/share/native/libjimage/imageFile.hpp
 42 
 43     /** End of attribute stream marker. */
 44     public static final int ATTRIBUTE_END = 0;
 45     /** String table offset of module name. */
 46     public static final int ATTRIBUTE_MODULE = 1;
 47     /** String table offset of resource path parent. */
 48     public static final int ATTRIBUTE_PARENT = 2;
 49     /** String table offset of resource path base. */
 50     public static final int ATTRIBUTE_BASE = 3;
 51     /** String table offset of resource path extension. */
 52     public static final int ATTRIBUTE_EXTENSION = 4;
 53     /** Container byte offset of resource. */
 54     public static final int ATTRIBUTE_OFFSET = 5;
 55     /** In-image byte size of the compressed resource. */
 56     public static final int ATTRIBUTE_COMPRESSED = 6;
 57     /** In-memory byte size of the uncompressed resource. */
 58     public static final int ATTRIBUTE_UNCOMPRESSED = 7;
 59     /** Flags relating to preview mode resources. */
 60     public static final int ATTRIBUTE_PREVIEW_FLAGS = 8;
 61     /** Number of attribute kinds. */
 62     public static final int ATTRIBUTE_COUNT = 9;
 63 
 64     // Flag masks for the ATTRIBUTE_PREVIEW_FLAGS attribute. Defined so
 65     // that zero is the overwhelmingly common case for normal resources.
 66 
 67     /**
 68      * Indicates that a non-preview location is associated with preview
 69      * resources.
 70      *
 71      * <p>This can apply to both resources and directories in the
 72      * {@code /modules/xxx/...} namespace, as well as {@code /packages/xxx}
 73      * directories.
 74      *
 75      * <p>For {@code /packages/xxx} directories, it indicates that the package
 76      * has preview resources in one of the modules in which it exists.
 77      */
 78     private static final int FLAGS_HAS_PREVIEW_VERSION = 0x1;
 79     /**
 80      * Set on all locations in the {@code /modules/xxx/META-INF/preview/...}
 81      * namespace.
 82      *
 83      * <p>This flag is mutually exclusive with {@link #FLAGS_HAS_PREVIEW_VERSION}.
 84      */
 85     private static final int FLAGS_IS_PREVIEW_VERSION = 0x2;
 86     /**
 87      * Indicates that a location only exists due to preview resources.
 88      *
 89      * <p>This can apply to both resources and directories in the
 90      * {@code /modules/xxx/...} namespace, as well as {@code /packages/xxx}
 91      * directories.
 92      *
 93      * <p>For {@code /packages/xxx} directories it indicates that, for every
 94      * module in which the package exists, it is preview only.
 95      *
 96      * <p>This flag is mutually exclusive with {@link #FLAGS_HAS_PREVIEW_VERSION}
 97      * and need not imply that {@link #FLAGS_IS_PREVIEW_VERSION} is set (i.e.
 98      * for {@code /packages/xxx} directories).
 99      */
100     private static final int FLAGS_IS_PREVIEW_ONLY = 0x4;
101 
102     // Also used in ImageReader.
103     static final String MODULES_PREFIX = "/modules";
104     static final String PACKAGES_PREFIX = "/packages";
105     static final String PREVIEW_INFIX = "/META-INF/preview";
106 
107     /**
108      * Helper function to calculate preview flags (ATTRIBUTE_PREVIEW_FLAGS).
109      *
110      * <p>Since preview flags are calculated separately for resource nodes and
111      * directory nodes (in two quite different places) it's useful to have a
112      * common helper.
113      *
114      * <p>Based on the entry name, the flags are:
115      * <ul>
116      *     <li>{@code "[/modules]/<module>/<path>"} normal resource or directory:<br>
117      *     Zero, or {@code FLAGS_HAS_PREVIEW_VERSION} if a preview entry exists.
118      *     <li>{@code "[/modules]/<module>/META-INF/preview/<path>"} preview
119      *     resource or directory:<br>
120      *     {@code FLAGS_IS_PREVIEW_VERSION}, and additionally {@code
121      *     FLAGS_IS_PREVIEW_ONLY} if no normal version of the resource exists.
122      *     <li>In all other cases, returned flags are zero (note that {@code
123      *     "/packages/xxx"} entries may have flags, but these are calculated
124      *     elsewhere).
125      * </ul>
126      *
127      * @param name the jimage name of the resource or directory.
128      * @param hasEntry a predicate for jimage names returning whether an entry
129      *     is present.
130      * @return flags for the ATTRIBUTE_PREVIEW_FLAGS attribute.
131      */
132     public static int getFlags(String name, Predicate<String> hasEntry) {
133         if (name.startsWith(PACKAGES_PREFIX + "/")) {
134             throw new IllegalArgumentException(
135                     "Package sub-directory flags handled separately: " + name);
136         }
137         // Find suffix for either '/modules/xxx/suffix' or '/xxx/suffix' paths.
138         int idx = name.startsWith(MODULES_PREFIX + "/") ? MODULES_PREFIX.length() + 1 : 1;
139         int suffixStart = name.indexOf('/', idx);
140         if (suffixStart == -1) {
141             // No flags for '[/modules]/xxx' paths (esp. '/modules', '/packages').
142             // '/packages/xxx' entries have flags, but not calculated here.
143             return 0;
144         }
145         // Prefix is either '/modules/xxx' or '/xxx', and suffix starts with '/'.
146         String prefix = name.substring(0, suffixStart);
147         String suffix = name.substring(suffixStart);
148         if (suffix.startsWith(PREVIEW_INFIX + "/")) {
149             // Preview resources/directories.
150             String nonPreviewName = prefix + suffix.substring(PREVIEW_INFIX.length());
151             return FLAGS_IS_PREVIEW_VERSION
152                     | (hasEntry.test(nonPreviewName) ? 0 : FLAGS_IS_PREVIEW_ONLY);
153         } else if (!suffix.startsWith("/META-INF/")) {
154             // Non-preview resources/directories.
155             String previewName = prefix + PREVIEW_INFIX + suffix;
156             return hasEntry.test(previewName) ? FLAGS_HAS_PREVIEW_VERSION : 0;
157         } else {
158             // Suffix is '/META-INF/xxx' and no preview version is even possible.
159             return 0;
160         }
161     }
162 
163     /**
164      * Helper function to calculate package flags for {@code "/packages/xxx"}
165      * directory entries.
166      *
167      * <p>Based on the module references, the flags are:
168      * <ul>
169      *     <li>{@code FLAGS_HAS_PREVIEW_VERSION} if <em>any</em> referenced
170      *     package has a preview version.
171      *     <li>{@code FLAGS_IS_PREVIEW_ONLY} if <em>all</em> referenced packages
172      *     are preview only.
173      * </ul>
174      *
175      * @return package flags for {@code "/packages/xxx"} directory entries.
176      */
177     public static int getPackageFlags(List<ModuleReference> moduleReferences) {
178         boolean hasPreviewVersion =
179                 moduleReferences.stream().anyMatch(ModuleReference::hasPreviewVersion);
180         boolean isPreviewOnly =
181                 moduleReferences.stream().allMatch(ModuleReference::isPreviewOnly);
182         return (hasPreviewVersion ? ImageLocation.FLAGS_HAS_PREVIEW_VERSION : 0)
183                 | (isPreviewOnly ? ImageLocation.FLAGS_IS_PREVIEW_ONLY : 0);
184     }
185 
186     /**
187      * Tests a non-preview image location's flags to see if it has preview
188      * content associated with it.
189      */
190     public static boolean hasPreviewVersion(int flags) {
191         return (flags & FLAGS_HAS_PREVIEW_VERSION) != 0;
192     }
193 
194     /**
195      * Tests an image location's flags to see if it only exists in preview mode.
196      */
197     public static boolean isPreviewOnly(int flags) {
198         return (flags & FLAGS_IS_PREVIEW_ONLY) != 0;
199     }
200 
201     public enum LocationType {
202         RESOURCE, MODULES_ROOT, MODULES_DIR, PACKAGES_ROOT, PACKAGES_DIR;
203     }
204 
205     protected final long[] attributes;
206 
207     protected final ImageStrings strings;
208 
209     public ImageLocation(long[] attributes, ImageStrings strings) {
210         this.attributes = Objects.requireNonNull(attributes);
211         this.strings = Objects.requireNonNull(strings);
212     }
213 
214     ImageStrings getStrings() {
215         return strings;
216     }
217 
218     static long[] decompress(ByteBuffer bytes, int offset) {
219         Objects.requireNonNull(bytes);
220         long[] attributes = new long[ATTRIBUTE_COUNT];
221 
222         int limit = bytes.limit();
223         while (offset < limit) {
224             int data = bytes.get(offset++) & 0xFF;
225             if (data <= 0x7) { // ATTRIBUTE_END
226                 break;
227             }
228             int kind = data >>> 3;
229             if (ATTRIBUTE_COUNT <= kind) {
230                 throw new InternalError(
231                     "Invalid jimage attribute kind: " + kind);
232             }
233 
234             int length = (data & 0x7) + 1;
235             attributes[kind] = readValue(length, bytes, offset, limit);
236             offset += length;
237         }
238         return attributes;
239     }
240 
241     public static byte[] compress(long[] attributes) {
242         Objects.requireNonNull(attributes);
243         ImageStream stream = new ImageStream(16);
244 
245         for (int kind = ATTRIBUTE_END + 1; kind < ATTRIBUTE_COUNT; kind++) {
246             long value = attributes[kind];
247 
248             if (value != 0) {
249                 int n = (63 - Long.numberOfLeadingZeros(value)) >> 3;
250                 stream.put((kind << 3) | n);
251 
252                 for (int i = n; i >= 0; i--) {
253                     stream.put((int)(value >> (i << 3)));
254                 }
255             }
256         }
257 
258         stream.put(ATTRIBUTE_END << 3);
259 
260         return stream.toArray();
261      }
262 
263     public boolean verify(String name) {
264         return verify(name, attributes, strings);
265     }
266 
267     /**
268      * A simpler verification would be {@code name.equals(getFullName())}, but
269      * by not creating the full name and enabling early returns we allocate
270      * fewer objects.
271      */
272     static boolean verify(String name, long[] attributes, ImageStrings strings) {
273         Objects.requireNonNull(name);
274         final int length = name.length();
275         int index = 0;
276         int moduleOffset = (int)attributes[ATTRIBUTE_MODULE];
277         if (moduleOffset != 0 && length >= 1) {
278             int moduleLen = strings.match(moduleOffset, name, 1);
279             index = moduleLen + 1;
280             if (moduleLen < 0
281                     || length <= index
282                     || name.charAt(0) != '/'
283                     || name.charAt(index++) != '/') {
284                 return false;
285             }
286         }
287         return verifyName(null, name, index, length, 0,
288                 (int) attributes[ATTRIBUTE_PARENT],
289                 (int) attributes[ATTRIBUTE_BASE],
290                 (int) attributes[ATTRIBUTE_EXTENSION],
291                 strings);
292     }
293 
294     static boolean verify(String module, String name, ByteBuffer locations,
295                           int locationOffset, ImageStrings strings) {
296         int moduleOffset = 0;
297         int parentOffset = 0;
298         int baseOffset = 0;
299         int extOffset = 0;
300 
301         int limit = locations.limit();
302         while (locationOffset < limit) {
303             int data = locations.get(locationOffset++) & 0xFF;
304             if (data <= 0x7) { // ATTRIBUTE_END
305                 break;
306             }
307             int kind = data >>> 3;
308             if (ATTRIBUTE_COUNT <= kind) {
309                 throw new InternalError(
310                         "Invalid jimage attribute kind: " + kind);
311             }
312 
313             int length = (data & 0x7) + 1;
314             switch (kind) {
315                 case ATTRIBUTE_MODULE:
316                     moduleOffset = (int) readValue(length, locations, locationOffset, limit);
317                     break;
318                 case ATTRIBUTE_BASE:
319                     baseOffset = (int) readValue(length, locations, locationOffset, limit);
320                     break;
321                 case ATTRIBUTE_PARENT:
322                     parentOffset = (int) readValue(length, locations, locationOffset, limit);
323                     break;
324                 case ATTRIBUTE_EXTENSION:
325                     extOffset = (int) readValue(length, locations, locationOffset, limit);
326                     break;
327             }
328             locationOffset += length;
329         }
330         return verifyName(module, name, 0, name.length(),
331                 moduleOffset, parentOffset, baseOffset, extOffset, strings);
332     }
333 
334     private static long readValue(int length, ByteBuffer buffer, int offset, int limit) {
335         long value = 0;
336         for (int j = 0; j < length; j++) {
337             value <<= 8;
338             if (offset >= limit) {
339                 throw new InternalError("Missing jimage attribute data");
340             }
341             value |= buffer.get(offset++) & 0xFF;
342         }
343         return value;
344     }
345 
346     static boolean verify(String module, String name, long[] attributes,
347             ImageStrings strings) {
348         Objects.requireNonNull(module);
349         Objects.requireNonNull(name);
350         return verifyName(module, name, 0, name.length(),
351                 (int) attributes[ATTRIBUTE_MODULE],
352                 (int) attributes[ATTRIBUTE_PARENT],
353                 (int) attributes[ATTRIBUTE_BASE],
354                 (int) attributes[ATTRIBUTE_EXTENSION],
355                 strings);
356     }
357 
358     private static boolean verifyName(String module, String name, int index, int length,
359             int moduleOffset, int parentOffset, int baseOffset, int extOffset, ImageStrings strings) {
360 
361         if (moduleOffset != 0) {
362             if (strings.match(moduleOffset, module, 0) != module.length()) {
363                 return false;
364             }
365         }
366         if (parentOffset != 0) {
367             int parentLen = strings.match(parentOffset, name, index);
368             if (parentLen < 0) {
369                 return false;
370             }
371             index += parentLen;
372             if (length <= index || name.charAt(index++) != '/') {
373                 return false;
374             }
375         }
376         int baseLen = strings.match(baseOffset, name, index);
377         if (baseLen < 0) {
378             return false;
379         }
380         index += baseLen;
381         if (extOffset != 0) {
382             if (length <= index
383                     || name.charAt(index++) != '.') {
384                 return false;
385             }
386 
387             int extLen = strings.match(extOffset, name, index);
388             if (extLen < 0) {
389                 return false;
390             }
391             index += extLen;
392         }
393         return length == index;
394     }
395 
396     long getAttribute(int kind) {
397         if (kind < ATTRIBUTE_END || ATTRIBUTE_COUNT <= kind) {
398             throw new InternalError(
399                 "Invalid jimage attribute kind: " + kind);
400         }
401         return attributes[kind];
402     }
403 
404     String getAttributeString(int kind) {
405         if (kind < ATTRIBUTE_END || ATTRIBUTE_COUNT <= kind) {
406             throw new InternalError(
407                 "Invalid jimage attribute kind: " + kind);
408         }
409         return getStrings().get((int)attributes[kind]);
410     }
411 
412     public String getModule() {
413         return getAttributeString(ATTRIBUTE_MODULE);
414     }
415 
416     public int getModuleOffset() {
417         return (int)getAttribute(ATTRIBUTE_MODULE);
418     }
419 
420     public String getBase() {
421         return getAttributeString(ATTRIBUTE_BASE);
422     }
423 
424     public int getBaseOffset() {
425         return (int)getAttribute(ATTRIBUTE_BASE);
426     }
427 
428     public String getParent() {
429         return getAttributeString(ATTRIBUTE_PARENT);
430     }
431 
432     public int getParentOffset() {
433         return (int)getAttribute(ATTRIBUTE_PARENT);
434     }
435 
436     public String getExtension() {
437         return getAttributeString(ATTRIBUTE_EXTENSION);
438     }
439 
440     public int getExtensionOffset() {
441         return (int)getAttribute(ATTRIBUTE_EXTENSION);
442     }
443 
444     public int getFlags() {
445         return (int) getAttribute(ATTRIBUTE_PREVIEW_FLAGS);
446     }
447 
448     public String getFullName() {
449         return getFullName(false);
450     }
451 
452     public String getFullName(boolean modulesPrefix) {
453         StringBuilder builder = new StringBuilder();
454 
455         if (getModuleOffset() != 0) {
456             if (modulesPrefix) {
457                 builder.append(MODULES_PREFIX);
458             }
459 
460             builder.append('/');
461             builder.append(getModule());
462             builder.append('/');
463         }
464 
465         if (getParentOffset() != 0) {
466             builder.append(getParent());
467             builder.append('/');
468         }
469 
470         builder.append(getBase());
471 
472         if (getExtensionOffset() != 0) {
473             builder.append('.');
474             builder.append(getExtension());
475         }
476 
477         return builder.toString();
478     }
479 
480     public long getContentOffset() {
481         return getAttribute(ATTRIBUTE_OFFSET);
482     }
483 
484     public long getCompressedSize() {
485         return getAttribute(ATTRIBUTE_COMPRESSED);
486     }
487 
488     public long getUncompressedSize() {
489         return getAttribute(ATTRIBUTE_UNCOMPRESSED);
490     }
491 
492     // Fast (zero allocation) type determination for locations.
493     public LocationType getType() {
494         switch (getModuleOffset()) {
495             case ImageStrings.MODULES_STRING_OFFSET:
496                 // Locations in /modules/... namespace are directory entries.
497                 return LocationType.MODULES_DIR;
498             case ImageStrings.PACKAGES_STRING_OFFSET:
499                 // Locations in /packages/... namespace are always 2-level
500                 // "/packages/xxx" directories.
501                 return LocationType.PACKAGES_DIR;
502             case ImageStrings.EMPTY_STRING_OFFSET:
503                 // Only 2 choices, either the "/modules" or "/packages" root.
504                 assert isRootDir() : "Invalid root directory: " + getFullName();
505                 return getBase().charAt(1) == 'p'
506                         ? LocationType.PACKAGES_ROOT
507                         : LocationType.MODULES_ROOT;
508             default:
509                 // Anything else is /<module>/<path> and references a resource.
510                 return LocationType.RESOURCE;
511         }
512     }
513 
514     private boolean isRootDir() {
515         if (getModuleOffset() == 0 && getParentOffset() == 0) {
516             String name = getFullName();
517             return name.equals(MODULES_PREFIX) || name.equals(PACKAGES_PREFIX);
518         }
519         return false;
520     }
521 
522     @Override
523     public String toString() {
524         // Cannot use String.format() (too early in startup for locale code).
525         return "ImageLocation[name='" + getFullName() + "', type=" + getType() + ", flags=" + getFlags() + "]";
526     }
527 
528     static ImageLocation readFrom(BasicImageReader reader, int offset) {
529         Objects.requireNonNull(reader);
530         long[] attributes = reader.getAttributes(offset);
531         ImageStringsReader strings = reader.getStrings();
532 
533         return new ImageLocation(attributes, strings);
534     }
535 }