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 }