1 /*
2 * Copyright (c) 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.IntBuffer;
29 import java.util.Comparator;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.NoSuchElementException;
33 import java.util.Objects;
34 import java.util.function.Function;
35
36 /**
37 * Represents the module entries stored in the buffer of {@code "/packages/xxx"}
38 * image locations (package subdirectories). These entries use flags which are
39 * similar to, but distinct from, the {@link ImageLocation} flags, so
40 * encapsulating them here helps avoid confusion.
41 *
42 * @implNote This class needs to maintain JDK 8 source compatibility.
43 *
44 * It is used internally in the JDK to implement jimage/jrtfs access,
45 * but also compiled and delivered as part of the jrtfs.jar to support access
46 * to the jimage file provided by the shipped JDK by tools running on JDK 8.
47 */
48 public final class ModuleReference implements Comparable<ModuleReference> {
49 // These flags are additive (hence "has-content" rather than "is-empty").
50
51 /** If set, this package exists in preview mode. */
52 private static final int FLAGS_PKG_HAS_PREVIEW_VERSION = 0x1;
53 /** If set, this package exists in non-preview mode. */
54 private static final int FLAGS_PKG_HAS_NORMAL_VERSION = 0x2;
55 /** If set, the associated module has resources (in normal or preview mode). */
56 private static final int FLAGS_PKG_HAS_RESOURCES = 0x4;
57
58 /**
59 * References are ordered with preview versions first which permits early
60 * exit when processing preview entries (it's reversed because the default
61 * order for a boolean is {@code false < true}).
62 */
63 private static final Comparator<ModuleReference> PREVIEW_FIRST =
64 Comparator.comparing(ModuleReference::hasPreviewVersion).reversed()
65 .thenComparing(ModuleReference::name);
66
67 /**
68 * Returns a reference for non-empty packages (those with resources) in a
69 * given module.
70 *
71 * <p>The same reference can be used for multiple packages in the same module.
72 */
73 public static ModuleReference forPackage(String moduleName, boolean isPreview) {
74 return new ModuleReference(moduleName, FLAGS_PKG_HAS_RESOURCES | previewFlag(isPreview));
75 }
76
77 /**
78 * Returns a reference for empty packages in a given module.
79 *
80 * <p>The same reference can be used for multiple packages in the same module.
81 */
82 public static ModuleReference forEmptyPackage(String moduleName, boolean isPreview) {
83 return new ModuleReference(moduleName, previewFlag(isPreview));
84 }
85
86 private static int previewFlag(boolean isPreview) {
87 return isPreview ? FLAGS_PKG_HAS_PREVIEW_VERSION : FLAGS_PKG_HAS_NORMAL_VERSION;
88 }
89
90 /** Merges two references for the same module (combining their flags). */
91 public ModuleReference merge(ModuleReference other) {
92 if (!name.equals(other.name)) {
93 throw new IllegalArgumentException("Cannot merge " + other + " with " + this);
94 }
95 // Because flags are additive, we can just OR them here.
96 return new ModuleReference(name, flags | other.flags);
97 }
98
99 private final String name;
100 private final int flags;
101
102 private ModuleReference(String moduleName, int flags) {
103 this.name = Objects.requireNonNull(moduleName);
104 this.flags = flags;
105 }
106
107 /** Returns the module name of this reference. */
108 public String name() {
109 return name;
110 }
111
112 /**
113 * Returns whether the package associated with this reference contains
114 * resources in this reference's module.
115 *
116 * <p>An invariant of the module system is that while a package may exist
117 * under many modules, it only has resources in one.
118 */
119 public boolean hasResources() {
120 return (flags & FLAGS_PKG_HAS_RESOURCES) != 0;
121 }
122
123 /**
124 * Returns whether the package associated with this reference has a preview
125 * version (empty or otherwise) in this reference's module.
126 */
127 public boolean hasPreviewVersion() {
128 return (flags & FLAGS_PKG_HAS_PREVIEW_VERSION) != 0;
129 }
130
131 /** Returns whether this reference exists only in preview mode. */
132 public boolean isPreviewOnly() {
133 return (flags & FLAGS_PKG_HAS_NORMAL_VERSION) == 0;
134 }
135
136 @Override
137 public int compareTo(ModuleReference rhs) {
138 return PREVIEW_FIRST.compare(this, rhs);
139 }
140
141 @Override
142 public String toString() {
143 return "ModuleReference{ module=" + name + ", flags=" + flags + " }";
144 }
145
146 @Override
147 public boolean equals(Object obj) {
148 if (!(obj instanceof ModuleReference)) {
149 return false;
150 }
151 ModuleReference other = (ModuleReference) obj;
152 return name.equals(other.name) && flags == other.flags;
153 }
154
155 @Override
156 public int hashCode() {
157 return Objects.hash(name, flags);
158 }
159
160 /**
161 * Reads the content buffer of a package subdirectory to return a sequence
162 * of module name offsets in the jimage.
163 *
164 * @param buffer the content buffer of an {@link ImageLocation} with type
165 * {@link ImageLocation.LocationType#PACKAGES_DIR PACKAGES_DIR}.
166 * @param includeNormal whether to include name offsets for modules present
167 * in normal (non-preview) mode.
168 * @param includePreview whether to include name offsets for modules present
169 * in preview mode.
170 * @return an iterator of module name offsets.
171 */
172 public static Iterator<Integer> readNameOffsets(
173 IntBuffer buffer, boolean includeNormal, boolean includePreview) {
174 int bufferSize = buffer.capacity();
175 if (bufferSize == 0 || (bufferSize & 0x1) != 0) {
176 throw new IllegalArgumentException("Invalid buffer size");
177 }
178 int includeMask = (includeNormal ? FLAGS_PKG_HAS_NORMAL_VERSION : 0)
179 + (includePreview ? FLAGS_PKG_HAS_PREVIEW_VERSION : 0);
180 if (includeMask == 0) {
181 throw new IllegalArgumentException("Invalid flags");
182 }
183
184 return new Iterator<Integer>() {
185 private int idx = nextIdx(0);
186
187 int nextIdx(int idx) {
188 for (; idx < bufferSize; idx += 2) {
189 // If any of the test flags are set, include this entry.
190 if ((buffer.get(idx) & includeMask) != 0) {
191 return idx;
192 } else if (!includeNormal) {
193 // Preview entries are first in the offset buffer, so we
194 // can exit early (by returning the end index) if we are
195 // only iterating preview entries, and have run out.
196 break;
197 }
198 }
199 return bufferSize;
200 }
201
202 @Override
203 public boolean hasNext() {
204 return idx < bufferSize;
205 }
206
207 @Override
208 public Integer next() {
209 if (idx < bufferSize) {
210 int nameOffset = buffer.get(idx + 1);
211 idx = nextIdx(idx + 2);
212 return nameOffset;
213 }
214 throw new NoSuchElementException();
215 }
216 };
217 }
218
219 /**
220 * Writes a list of module references to a given buffer. The given references
221 * list is checked carefully to ensure the written buffer will be valid.
222 *
223 * <p>Entries are written in order, taking two integer slots per entry as
224 * {@code [<flags>, <encoded-name>]}.
225 *
226 * @param refs the references to write, correctly ordered.
227 * @param buffer destination buffer.
228 * @param nameEncoder encoder for module names.
229 * @throws IllegalArgumentException in the references are invalid in any way.
230 */
231 public static void write(
232 List<ModuleReference> refs, IntBuffer buffer, Function<String, Integer> nameEncoder) {
233 if (refs.isEmpty()) {
234 throw new IllegalArgumentException("References list must be non-empty");
235 }
236 int expectedCapacity = 2 * refs.size();
237 if (buffer.capacity() != expectedCapacity) {
238 throw new IllegalArgumentException(
239 "Invalid buffer capacity: expected " + expectedCapacity + ", got " + buffer.capacity());
240 }
241 // This catches exact duplicates in the list.
242 refs.stream().reduce((lhs, rhs) -> {
243 if (lhs.compareTo(rhs) >= 0) {
244 throw new IllegalArgumentException("References must be strictly ordered: " + refs);
245 }
246 return rhs;
247 });
248 // Distinct references can have the same name (but we don't allow this).
249 if (refs.stream().map(ModuleReference::name).distinct().count() != refs.size()) {
250 throw new IllegalArgumentException("Reference names must be unique: " + refs);
251 }
252 if (refs.stream().filter(ModuleReference::hasResources).count() > 1) {
253 throw new IllegalArgumentException("At most one reference can have resources: " + refs);
254 }
255 for (ModuleReference modRef : refs) {
256 buffer.put(modRef.flags);
257 buffer.put(nameEncoder.apply(modRef.name));
258 }
259 }
260 }