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