1 /*
2  * Copyright (c) 2015, 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.io.DataOutputStream;
28 import java.io.IOException;
29 import java.lang.module.ModuleDescriptor;
30 import java.nio.ByteOrder;
31 import java.util.ArrayList;
32 import java.util.Collection;
33 import java.util.Collections;
34 import java.util.Comparator;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Objects;
39 import java.util.Optional;
40 import java.util.Set;
41 import java.util.function.Function;
42 import java.util.stream.Collectors;
43 import java.util.stream.Stream;
44 
45 import jdk.internal.jimage.decompressor.Decompressor;
46 import jdk.internal.module.ModuleInfo.Attributes;
47 import jdk.internal.module.ModuleTarget;
48 import jdk.tools.jlink.plugin.Plugin;
49 import jdk.tools.jlink.builder.ImageBuilder;
50 import jdk.tools.jlink.plugin.PluginException;
51 import jdk.tools.jlink.plugin.ResourcePool;
52 import jdk.tools.jlink.plugin.ResourcePoolModule;
53 import jdk.tools.jlink.plugin.ResourcePoolEntry;
54 import jdk.tools.jlink.internal.ResourcePoolManager.ResourcePoolImpl;
55 
56 /**
57  * Plugins Stack. Plugins entry point to apply transformations onto resources
58  * and files.
59  */
60 public final class ImagePluginStack {
61 
62     public interface ImageProvider {
63 
retrieve(ImagePluginStack stack)64         ExecutableImage retrieve(ImagePluginStack stack) throws IOException;
65     }
66 
67     public static final class OrderedResourcePoolManager extends ResourcePoolManager {
68         class OrderedResourcePool extends ResourcePoolImpl {
getOrderedList()69             List<ResourcePoolEntry> getOrderedList() {
70                 return OrderedResourcePoolManager.this.getOrderedList();
71             }
72         }
73 
74         private final List<ResourcePoolEntry> orderedList = new ArrayList<>();
75         private final ResourcePoolImpl poolImpl = new OrderedResourcePool();
76 
OrderedResourcePoolManager(ByteOrder order, StringTable table)77         public OrderedResourcePoolManager(ByteOrder order, StringTable table) {
78             super(order, table);
79         }
80 
81         @Override
resourcePool()82         public ResourcePool resourcePool() {
83             return poolImpl;
84         }
85 
86         /**
87          * Add a resource.
88          *
89          * @param resource The Resource to add.
90          */
91         @Override
add(ResourcePoolEntry resource)92         public void add(ResourcePoolEntry resource) {
93             super.add(resource);
94             orderedList.add(resource);
95         }
96 
getOrderedList()97         List<ResourcePoolEntry> getOrderedList() {
98             return Collections.unmodifiableList(orderedList);
99         }
100     }
101 
102     private final static class CheckOrderResourcePoolManager extends ResourcePoolManager {
103 
104         private final List<ResourcePoolEntry> orderedList;
105         private int currentIndex;
106 
CheckOrderResourcePoolManager(ByteOrder order, List<ResourcePoolEntry> orderedList, StringTable table)107         public CheckOrderResourcePoolManager(ByteOrder order, List<ResourcePoolEntry> orderedList, StringTable table) {
108             super(order, table);
109             this.orderedList = Objects.requireNonNull(orderedList);
110         }
111 
112         /**
113          * Add a resource.
114          *
115          * @param resource The Resource to add.
116          */
117         @Override
add(ResourcePoolEntry resource)118         public void add(ResourcePoolEntry resource) {
119             ResourcePoolEntry ordered = orderedList.get(currentIndex);
120             if (!resource.equals(ordered)) {
121                 throw new PluginException("Resource " + resource.path() + " not in the right order");
122             }
123             super.add(resource);
124             currentIndex += 1;
125         }
126     }
127 
128     private static final class PreVisitStrings implements StringTable {
129 
130         private int currentid = 0;
131         private final Map<String, Integer> stringsUsage = new HashMap<>();
132         private final Map<String, Integer> stringsMap = new HashMap<>();
133         private final Map<Integer, String> reverseMap = new HashMap<>();
134 
135         @Override
addString(String str)136         public int addString(String str) {
137             Objects.requireNonNull(str);
138             Integer count = stringsUsage.get(str);
139             if (count == null) {
140                 count = 0;
141             }
142             count += 1;
143             stringsUsage.put(str, count);
144             Integer id = stringsMap.get(str);
145             if (id == null) {
146                 id = currentid;
147                 stringsMap.put(str, id);
148                 currentid += 1;
149                 reverseMap.put(id, str);
150             }
151 
152             return id;
153         }
154 
getSortedStrings()155         private List<String> getSortedStrings() {
156             Stream<java.util.Map.Entry<String, Integer>> stream
157                     = stringsUsage.entrySet().stream();
158             // Remove strings that have a single occurence
159             List<String> result = stream.sorted(Comparator.comparing(e -> e.getValue(),
160                     Comparator.reverseOrder())).filter((e) -> {
161                         return e.getValue() > 1;
162                     }).map(java.util.Map.Entry::getKey).
163                     collect(Collectors.toList());
164             return result;
165         }
166 
167         @Override
getString(int id)168         public String getString(int id) {
169             return reverseMap.get(id);
170         }
171     }
172 
173     private final ImageBuilder imageBuilder;
174     private final Plugin lastSorter;
175     private final List<Plugin> plugins = new ArrayList<>();
176     private final List<ResourcePrevisitor> resourcePrevisitors = new ArrayList<>();
177     private final boolean validate;
178 
ImagePluginStack()179     public ImagePluginStack() {
180         this(null, Collections.emptyList(), null);
181     }
182 
ImagePluginStack(ImageBuilder imageBuilder, List<Plugin> plugins, Plugin lastSorter)183     public ImagePluginStack(ImageBuilder imageBuilder,
184             List<Plugin> plugins,
185             Plugin lastSorter) {
186         this(imageBuilder, plugins, lastSorter, true);
187     }
188 
ImagePluginStack(ImageBuilder imageBuilder, List<Plugin> plugins, Plugin lastSorter, boolean validate)189     public ImagePluginStack(ImageBuilder imageBuilder,
190             List<Plugin> plugins,
191             Plugin lastSorter,
192             boolean validate) {
193         this.imageBuilder = Objects.requireNonNull(imageBuilder);
194         this.lastSorter = lastSorter;
195         this.plugins.addAll(Objects.requireNonNull(plugins));
196         plugins.stream().forEach((p) -> {
197             Objects.requireNonNull(p);
198             if (p instanceof ResourcePrevisitor) {
199                 resourcePrevisitors.add((ResourcePrevisitor) p);
200             }
201         });
202         this.validate = validate;
203     }
204 
operate(ImageProvider provider)205     public void operate(ImageProvider provider) throws Exception {
206         ExecutableImage img = provider.retrieve(this);
207         List<String> arguments = new ArrayList<>();
208         plugins.stream()
209                 .filter(PostProcessor.class::isInstance)
210                 .map((plugin) -> ((PostProcessor)plugin).process(img))
211                 .filter((lst) -> (lst != null))
212                 .forEach((lst) -> {
213                      arguments.addAll(lst);
214                 });
215         img.storeLaunchArgs(arguments);
216     }
217 
getJImageFileOutputStream()218     public DataOutputStream getJImageFileOutputStream() throws IOException {
219         return imageBuilder.getJImageOutputStream();
220     }
221 
getImageBuilder()222     public ImageBuilder getImageBuilder() {
223         return imageBuilder;
224     }
225 
226     /**
227      * Resource Plugins stack entry point. All resources are going through all
228      * the plugins.
229      *
230      * @param resources The set of resources to visit
231      * @return The result of the visit.
232      * @throws IOException
233      */
visitResources(ResourcePoolManager resources)234     public ResourcePool visitResources(ResourcePoolManager resources)
235             throws Exception {
236         Objects.requireNonNull(resources);
237         if (resources.isEmpty()) {
238             return new ResourcePoolManager(resources.byteOrder(),
239                     resources.getStringTable()).resourcePool();
240         }
241         PreVisitStrings previsit = new PreVisitStrings();
242         resourcePrevisitors.stream().forEach((p) -> {
243             p.previsit(resources.resourcePool(), previsit);
244         });
245 
246         // Store the strings resulting from the previsit.
247         List<String> sorted = previsit.getSortedStrings();
248         sorted.stream().forEach((s) -> {
249             resources.getStringTable().addString(s);
250         });
251 
252         ResourcePool resPool = resources.resourcePool();
253         List<ResourcePoolEntry> frozenOrder = null;
254         for (Plugin p : plugins) {
255             ResourcePoolManager resMgr = null;
256             if (p == lastSorter) {
257                 if (frozenOrder != null) {
258                     throw new Exception("Order of resources is already frozen. Plugin "
259                             + p.getName() + " is badly located");
260                 }
261                 // Create a special Resource pool to compute the indexes.
262                 resMgr = new OrderedResourcePoolManager(resPool.byteOrder(),
263                         resources.getStringTable());
264             } else {// If we have an order, inject it
265                 if (frozenOrder != null) {
266                     resMgr = new CheckOrderResourcePoolManager(resPool.byteOrder(),
267                             frozenOrder, resources.getStringTable());
268                 } else {
269                     resMgr = new ResourcePoolManager(resPool.byteOrder(),
270                             resources.getStringTable());
271                 }
272             }
273             try {
274                 resPool = p.transform(resPool, resMgr.resourcePoolBuilder());
275             } catch (PluginException pe) {
276                 if (JlinkTask.DEBUG) {
277                     System.err.println("Plugin " + p.getName() + " threw exception during transform");
278                     pe.printStackTrace();
279                 }
280                 throw pe;
281             }
282             if (resPool.isEmpty()) {
283                 throw new Exception("Invalid resource pool for plugin " + p);
284             }
285             if (resPool instanceof OrderedResourcePoolManager.OrderedResourcePool) {
286                 frozenOrder = ((OrderedResourcePoolManager.OrderedResourcePool)resPool).getOrderedList();
287             }
288         }
289 
290         return resPool;
291     }
292 
293     /**
294      * This pool wrap the original pool and automatically uncompress ResourcePoolEntry
295      * if needed.
296      */
297     private class LastPoolManager extends ResourcePoolManager {
298         private class LastModule implements ResourcePoolModule {
299 
300             final ResourcePoolModule module;
301             // lazily initialized
302             ModuleDescriptor descriptor;
303             ModuleTarget target;
304 
LastModule(ResourcePoolModule module)305             LastModule(ResourcePoolModule module) {
306                 this.module = module;
307             }
308 
309             @Override
name()310             public String name() {
311                 return module.name();
312             }
313 
314             @Override
findEntry(String path)315             public Optional<ResourcePoolEntry> findEntry(String path) {
316                 Optional<ResourcePoolEntry> d = module.findEntry(path);
317                 return d.isPresent()? Optional.of(getUncompressed(d.get())) : Optional.empty();
318             }
319 
320             @Override
descriptor()321             public ModuleDescriptor descriptor() {
322                 initModuleAttributes();
323                 return descriptor;
324             }
325 
326             @Override
targetPlatform()327             public String targetPlatform() {
328                 initModuleAttributes();
329                 return target != null? target.targetPlatform() : null;
330             }
331 
initModuleAttributes()332             private void initModuleAttributes() {
333                 if (this.descriptor == null) {
334                     Attributes attr = ResourcePoolManager.readModuleAttributes(this);
335                     this.descriptor = attr.descriptor();
336                     this.target = attr.target();
337                 }
338             }
339 
340             @Override
packages()341             public Set<String> packages() {
342                 return module.packages();
343             }
344 
345             @Override
toString()346             public String toString() {
347                 return name();
348             }
349 
350             @Override
entries()351             public Stream<ResourcePoolEntry> entries() {
352                 List<ResourcePoolEntry> lst = new ArrayList<>();
353                 module.entries().forEach(md -> {
354                     lst.add(getUncompressed(md));
355                 });
356                 return lst.stream();
357             }
358 
359             @Override
entryCount()360             public int entryCount() {
361                 return module.entryCount();
362             }
363         }
364 
365         private final ResourcePool pool;
366         Decompressor decompressor = new Decompressor();
367         Collection<ResourcePoolEntry> content;
368 
LastPoolManager(ResourcePool pool)369         LastPoolManager(ResourcePool pool) {
370             this.pool = pool;
371         }
372 
373         @Override
add(ResourcePoolEntry resource)374         public void add(ResourcePoolEntry resource) {
375             throw new PluginException("pool is readonly");
376         }
377 
378         @Override
findModule(String name)379         public Optional<ResourcePoolModule> findModule(String name) {
380             Optional<ResourcePoolModule> module = pool.moduleView().findModule(name);
381             return module.isPresent()? Optional.of(new LastModule(module.get())) : Optional.empty();
382         }
383 
384         /**
385          * The collection of modules contained in this pool.
386          *
387          * @return The collection of modules.
388          */
389         @Override
modules()390         public Stream<ResourcePoolModule> modules() {
391             List<ResourcePoolModule> modules = new ArrayList<>();
392             pool.moduleView().modules().forEach(m -> {
393                 modules.add(new LastModule(m));
394             });
395             return modules.stream();
396         }
397 
398         @Override
moduleCount()399         public int moduleCount() {
400             return pool.moduleView().moduleCount();
401         }
402 
403         /**
404          * Get all resources contained in this pool instance.
405          *
406          * @return The stream of resources;
407          */
408         @Override
entries()409         public Stream<ResourcePoolEntry> entries() {
410             if (content == null) {
411                 content = new ArrayList<>();
412                 pool.entries().forEach(md -> {
413                     content.add(getUncompressed(md));
414                 });
415             }
416             return content.stream();
417         }
418 
419         @Override
entryCount()420         public int entryCount() {
421             return pool.entryCount();
422         }
423 
424         /**
425          * Get the resource for the passed path.
426          *
427          * @param path A resource path
428          * @return A Resource instance if the resource is found
429          */
430         @Override
findEntry(String path)431         public Optional<ResourcePoolEntry> findEntry(String path) {
432             Objects.requireNonNull(path);
433             Optional<ResourcePoolEntry> res = pool.findEntry(path);
434             return res.isPresent()? Optional.of(getUncompressed(res.get())) : Optional.empty();
435         }
436 
437         @Override
findEntryInContext(String path, ResourcePoolEntry context)438         public Optional<ResourcePoolEntry> findEntryInContext(String path, ResourcePoolEntry context) {
439             Objects.requireNonNull(path);
440             Objects.requireNonNull(context);
441             Optional<ResourcePoolEntry> res = pool.findEntryInContext(path, context);
442             return res.map(this::getUncompressed);
443         }
444 
445         @Override
contains(ResourcePoolEntry res)446         public boolean contains(ResourcePoolEntry res) {
447             return pool.contains(res);
448         }
449 
450         @Override
isEmpty()451         public boolean isEmpty() {
452             return pool.isEmpty();
453         }
454 
455         @Override
byteOrder()456         public ByteOrder byteOrder() {
457             return pool.byteOrder();
458         }
459 
getUncompressed(ResourcePoolEntry res)460         private ResourcePoolEntry getUncompressed(ResourcePoolEntry res) {
461             if (res != null) {
462                 if (res instanceof ResourcePoolManager.CompressedModuleData) {
463                     try {
464                         byte[] bytes = decompressor.decompressResource(byteOrder(),
465                                 (int offset) -> ((ResourcePoolImpl)pool).getStringTable().getString(offset),
466                                 res.contentBytes());
467                         res = res.copyWithContent(bytes);
468                     } catch (IOException ex) {
469                         if (JlinkTask.DEBUG) {
470                             System.err.println("IOException while reading resource: " + res.path());
471                             ex.printStackTrace();
472                         }
473                         throw new PluginException(ex);
474                     }
475                 }
476             }
477             return res;
478         }
479     }
480 
481     /**
482      * Make the imageBuilder to store files.
483      *
484      * @param original
485      * @param transformed
486      * @param writer
487      * @throws java.lang.Exception
488      */
storeFiles(ResourcePool original, ResourcePool transformed, BasicImageWriter writer)489     public void storeFiles(ResourcePool original, ResourcePool transformed,
490             BasicImageWriter writer)
491             throws Exception {
492         Objects.requireNonNull(original);
493         Objects.requireNonNull(transformed);
494         ResourcePool lastPool = new LastPoolManager(transformed).resourcePool();
495         if (validate) {
496             ResourcePoolConfiguration.validate(lastPool);
497         }
498         imageBuilder.storeFiles(lastPool);
499     }
500 
getExecutableImage()501     public ExecutableImage getExecutableImage() throws IOException {
502         return imageBuilder.getExecutableImage();
503     }
504 }
505