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