1 /*
2 * Copyright (c) 2015, 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 package jdk.tools.jlink.internal;
26
27 import java.lang.module.ModuleDescriptor;
28 import java.nio.ByteBuffer;
29 import java.nio.ByteOrder;
30 import java.util.HashSet;
31 import java.util.LinkedHashMap;
32 import java.util.Map;
33 import java.util.Objects;
34 import java.util.Optional;
35 import java.util.Set;
36 import java.util.stream.Stream;
37 import jdk.internal.jimage.decompressor.CompressedResourceHeader;
38 import jdk.internal.module.Checks;
39 import jdk.internal.module.ModuleInfo;
40 import jdk.internal.module.ModuleInfo.Attributes;
41 import jdk.internal.module.ModuleTarget;
42 import jdk.tools.jlink.plugin.ResourcePool;
43 import jdk.tools.jlink.plugin.ResourcePoolBuilder;
44 import jdk.tools.jlink.plugin.ResourcePoolEntry;
45 import jdk.tools.jlink.plugin.ResourcePoolModule;
46 import jdk.tools.jlink.plugin.ResourcePoolModuleView;
47 import jdk.tools.jlink.plugin.PluginException;
48
49 /**
50 * A manager for pool of resources.
51 */
52 public class ResourcePoolManager {
53 // utility to read Module Attributes of the given ResourcePoolModule
54 static Attributes readModuleAttributes(ResourcePoolModule mod) {
55 String p = "/" + mod.name() + "/module-info.class";
56 Optional<ResourcePoolEntry> content = mod.findEntry(p);
57 if (content.isEmpty()) {
58 throw new PluginException("module-info.class not found for " +
59 mod.name() + " module");
60 }
61 ByteBuffer bb = ByteBuffer.wrap(content.get().contentBytes());
62 try {
63 return ModuleInfo.read(bb, null);
64 } catch (RuntimeException re) {
65 throw new RuntimeException("module info cannot be read for " + mod.name(), re);
66 }
67 }
68
69 /**
70 * Returns true if a resource is located in a named package.
71 */
72 public static boolean isNamedPackageResource(String name) {
73 int index = name.lastIndexOf("/");
74 if (index == -1) {
75 return false;
76 } else {
77 String pn = name.substring(0, index).replace('/', '.');
78 return Checks.isPackageName(pn);
79 }
80 }
81
82 static class ResourcePoolModuleImpl implements ResourcePoolModule {
83
84 final Map<String, ResourcePoolEntry> moduleContent = new LinkedHashMap<>();
85 // lazily initialized
86 private ModuleDescriptor descriptor;
87 private ModuleTarget target;
88
89 final String name;
90
91 private ResourcePoolModuleImpl(String name) {
92 this.name = name;
93 }
94
95 @Override
96 public String name() {
97 return name;
98 }
99
100 @Override
101 public Optional<ResourcePoolEntry> findEntry(String path) {
102 if (!path.startsWith("/")) {
103 path = "/" + path;
104 }
105 if (!path.startsWith("/" + name + "/")) {
106 path = "/" + name + path; // path already starts with '/'
107 }
108 return Optional.ofNullable(moduleContent.get(path));
109 }
110
111 @Override
112 public ModuleDescriptor descriptor() {
113 initModuleAttributes();
114 return descriptor;
115 }
116
117 @Override
118 public String targetPlatform() {
119 initModuleAttributes();
120 return target != null? target.targetPlatform() : null;
121 }
122
123 private void initModuleAttributes() {
124 if (this.descriptor == null) {
125 Attributes attr = readModuleAttributes(this);
126 this.descriptor = attr.descriptor();
127 this.target = attr.target();
128 }
129 }
130
131 @Override
132 public Set<String> packages() {
133 Set<String> pkgs = new HashSet<>();
134 moduleContent.values().stream()
135 .filter(m -> m.type() == ResourcePoolEntry.Type.CLASS_OR_RESOURCE)
136 .forEach(res -> {
137 String name = ImageFileCreator.resourceName(res.path());
138 if (isNamedPackageResource(name)) {
139 String pkg = ImageFileCreator.toPackage(name);
140 if (!pkg.isEmpty()) {
141 pkgs.add(pkg);
142 }
143 }
144 });
145 return pkgs;
146 }
147
148 @Override
149 public String toString() {
150 return name();
151 }
152
153 @Override
154 public Stream<ResourcePoolEntry> entries() {
155 return moduleContent.values().stream();
156 }
157
158 @Override
159 public int entryCount() {
160 return moduleContent.values().size();
161 }
162 }
163
164 public class ResourcePoolImpl implements ResourcePool {
165 @Override
166 public ResourcePoolModuleView moduleView() {
167 return ResourcePoolManager.this.moduleView();
168 }
169
170 @Override
171 public Stream<ResourcePoolEntry> entries() {
172 return ResourcePoolManager.this.entries();
173 }
174
175 @Override
176 public int entryCount() {
177 return ResourcePoolManager.this.entryCount();
178 }
179
180 @Override
181 public Optional<ResourcePoolEntry> findEntry(String path) {
182 return ResourcePoolManager.this.findEntry(path);
183 }
184
185 @Override
186 public Optional<ResourcePoolEntry> findEntryInContext(String path, ResourcePoolEntry context) {
187 return ResourcePoolManager.this.findEntryInContext(path, context);
188 }
189
190 @Override
191 public boolean contains(ResourcePoolEntry data) {
192 return ResourcePoolManager.this.contains(data);
193 }
194
195 @Override
196 public boolean isEmpty() {
197 return ResourcePoolManager.this.isEmpty();
198 }
199
200 @Override
201 public ByteOrder byteOrder() {
202 return ResourcePoolManager.this.byteOrder();
203 }
204
205 public StringTable getStringTable() {
206 return ResourcePoolManager.this.getStringTable();
207 }
208 }
209
210 class ResourcePoolBuilderImpl implements ResourcePoolBuilder {
211 private boolean built;
212
213 @Override
214 public void add(ResourcePoolEntry data) {
215 if (built) {
216 throw new IllegalStateException("resource pool already built!");
217 }
218 ResourcePoolManager.this.add(data);
219 }
220
221 @Override
222 public ResourcePool build() {
223 built = true;
224 return ResourcePoolManager.this.resourcePool();
225 }
226 }
227
228 class ResourcePoolModuleViewImpl implements ResourcePoolModuleView {
229 @Override
230 public Optional<ResourcePoolModule> findModule(String name) {
231 return ResourcePoolManager.this.findModule(name);
232 }
233
234 @Override
235 public Stream<ResourcePoolModule> modules() {
236 return ResourcePoolManager.this.modules();
237 }
238
239 @Override
240 public int moduleCount() {
241 return ResourcePoolManager.this.moduleCount();
242 }
243 }
244
245 private final Map<String, ResourcePoolEntry> resources = new LinkedHashMap<>();
246 private final Map<String, ResourcePoolModule> modules = new LinkedHashMap<>();
247 private final ByteOrder order;
248 private final StringTable table;
249 private final ResourcePool poolImpl;
250 private final ResourcePoolBuilder poolBuilderImpl;
251 private final ResourcePoolModuleView moduleViewImpl;
252
253 public ResourcePoolManager() {
254 this(ByteOrder.nativeOrder());
255 }
256
257 public ResourcePoolManager(ByteOrder order) {
258 this(order, new StringTable() {
259
260 @Override
261 public int addString(String str) {
262 return -1;
263 }
264
265 @Override
266 public String getString(int id) {
267 return null;
268 }
269 });
270 }
271
272 public ResourcePoolManager(ByteOrder order, StringTable table) {
273 this.order = Objects.requireNonNull(order);
274 this.table = Objects.requireNonNull(table);
275 this.poolImpl = new ResourcePoolImpl();
276 this.poolBuilderImpl = new ResourcePoolBuilderImpl();
277 this.moduleViewImpl = new ResourcePoolModuleViewImpl();
278 }
279
280 public ResourcePool resourcePool() {
281 return poolImpl;
282 }
283
284 public ResourcePoolBuilder resourcePoolBuilder() {
285 return poolBuilderImpl;
286 }
287
288 public ResourcePoolModuleView moduleView() {
289 return moduleViewImpl;
290 }
291
292 /**
293 * Add a ResourcePoolEntry.
294 *
295 * @param data The ResourcePoolEntry to add.
296 */
297 public void add(ResourcePoolEntry data) {
298 Objects.requireNonNull(data);
299 if (resources.get(data.path()) != null) {
300 throw new PluginException("Resource " + data.path()
301 + " already present");
302 }
303 String modulename = data.moduleName();
304 ResourcePoolModuleImpl m = (ResourcePoolModuleImpl)modules.get(modulename);
305 if (m == null) {
306 m = new ResourcePoolModuleImpl(modulename);
307 modules.put(modulename, m);
308 }
309 resources.put(data.path(), data);
310 m.moduleContent.put(data.path(), data);
311 }
312
313 /**
314 * Retrieves the module for the provided name.
315 *
316 * @param name The module name
317 * @return the module of matching name, if found
318 */
319 public Optional<ResourcePoolModule> findModule(String name) {
320 Objects.requireNonNull(name);
321 return Optional.ofNullable(modules.get(name));
322 }
323
324 /**
325 * The stream of modules contained in this ResourcePool.
326 *
327 * @return The stream of modules.
328 */
329 public Stream<ResourcePoolModule> modules() {
330 return modules.values().stream();
331 }
332
333 /**
334 * Return the number of ResourcePoolModule count in this ResourcePool.
335 *
336 * @return the module count.
337 */
338 public int moduleCount() {
339 return modules.size();
340 }
341
342 /**
343 * Get all ResourcePoolEntry contained in this ResourcePool instance.
344 *
345 * @return The stream of ResourcePoolModuleEntries.
346 */
347 public Stream<ResourcePoolEntry> entries() {
348 return resources.values().stream();
349 }
350
351 /**
352 * Return the number of ResourcePoolEntry count in this ResourcePool.
353 *
354 * @return the entry count.
355 */
356 public int entryCount() {
357 return resources.values().size();
358 }
359
360 /**
361 * Get the ResourcePoolEntry for the passed path.
362 *
363 * @param path A data path
364 * @return A ResourcePoolEntry instance or null if the data is not found
365 */
366 public Optional<ResourcePoolEntry> findEntry(String path) {
367 Objects.requireNonNull(path);
368 return Optional.ofNullable(resources.get(path));
369 }
370
371 /**
372 * Get the ResourcePoolEntry for the passed path restricted to supplied context.
373 *
374 * @param path A data path
375 * @param context A context of the search
376 * @return A ResourcePoolEntry instance or null if the data is not found
377 */
378 public Optional<ResourcePoolEntry> findEntryInContext(String path, ResourcePoolEntry context) {
379 Objects.requireNonNull(path);
380 Objects.requireNonNull(context);
381 ResourcePoolModule module = modules.get(context.moduleName());
382 Objects.requireNonNull(module);
383 Optional<ResourcePoolEntry> entry = module.findEntry(path);
384 // Navigating other modules via requires and exports is problematic
385 // since we cannot construct the runtime model of loaders and layers.
386 return entry;
387 }
388
389 /**
390 * Check if the ResourcePool contains the given ResourcePoolEntry.
391 *
392 * @param data The module data to check existence for.
393 * @return The module data or null if not found.
394 */
395 public boolean contains(ResourcePoolEntry data) {
396 Objects.requireNonNull(data);
397 return findEntry(data.path()).isPresent();
398 }
399
400 /**
401 * Check if the ResourcePool contains some content at all.
402 *
403 * @return True, no content, false otherwise.
404 */
405 public boolean isEmpty() {
406 return resources.isEmpty();
407 }
408
409 /**
410 * The ByteOrder currently in use when generating the jimage file.
411 *
412 * @return The ByteOrder.
413 */
414 public ByteOrder byteOrder() {
415 return order;
416 }
417
418 public StringTable getStringTable() {
419 return table;
420 }
421
422 /**
423 * A resource that has been compressed.
424 */
425 public static final class CompressedModuleData extends ByteArrayResourcePoolEntry {
426
427 final long uncompressed_size;
428
429 private CompressedModuleData(String module, String path,
430 byte[] content, long uncompressed_size) {
431 super(module, path, ResourcePoolEntry.Type.CLASS_OR_RESOURCE, content);
432 this.uncompressed_size = uncompressed_size;
433 }
434
435 public long getUncompressedSize() {
436 return uncompressed_size;
437 }
438
439 @Override
440 public boolean equals(Object other) {
441 if (!(other instanceof CompressedModuleData)) {
442 return false;
443 }
444 CompressedModuleData f = (CompressedModuleData) other;
445 return f.path().equals(path());
446 }
447
448 @Override
449 public int hashCode() {
450 return super.hashCode();
451 }
452 }
453
454 public static CompressedModuleData newCompressedResource(ResourcePoolEntry original,
455 ByteBuffer compressed,
456 String plugin,
457 StringTable strings,
458 ByteOrder order) {
459 Objects.requireNonNull(original);
460 Objects.requireNonNull(compressed);
461 Objects.requireNonNull(plugin);
462
463 boolean isTerminal = !(original instanceof CompressedModuleData);
464 long uncompressed_size = original.contentLength();
465 if (original instanceof CompressedModuleData) {
466 CompressedModuleData comp = (CompressedModuleData) original;
467 uncompressed_size = comp.getUncompressedSize();
468 }
469 int nameOffset = strings.addString(plugin);
470 CompressedResourceHeader rh
471 = new CompressedResourceHeader(compressed.limit(), original.contentLength(),
472 nameOffset, isTerminal);
473 // Merge header with content;
474 byte[] h = rh.getBytes(order);
475 ByteBuffer bb = ByteBuffer.allocate(compressed.limit() + h.length);
476 bb.order(order);
477 bb.put(h);
478 bb.put(compressed);
479 byte[] contentWithHeader = bb.array();
480
481 CompressedModuleData compressedResource
482 = new CompressedModuleData(original.moduleName(), original.path(),
483 contentWithHeader, uncompressed_size);
484 return compressedResource;
485 }
486 }