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 
 171     public ImagePluginStack() {
 172         this(null, Collections.emptyList(), null,
 173                 Collections.emptyList(), null);
 174     }
 175 
 176     public ImagePluginStack(ImageBuilder imageBuilder,
 177             List<TransformerPlugin> contentPlugins,
 178             Plugin lastSorter,
 179             List<PostProcessorPlugin> postprocessingPlugins) {
 180         this(imageBuilder, contentPlugins, lastSorter,
 181             postprocessingPlugins, null);
 182     }
 183 
 184     public ImagePluginStack(ImageBuilder imageBuilder,
 185             List<TransformerPlugin> contentPlugins,
 186             Plugin lastSorter,
 187             List<PostProcessorPlugin> postprocessingPlugins,
 188             PluginContext ctxt) {
 189         Objects.requireNonNull(contentPlugins);
 190         this.lastSorter = lastSorter;
 191         for (TransformerPlugin p : contentPlugins) {
 192             Objects.requireNonNull(p);
 193             if (p instanceof ResourcePrevisitor) {
 194                 resourcePrevisitors.add((ResourcePrevisitor) p);
 195             }
 196             this.contentPlugins.add(p);
 197         }
 198         for (PostProcessorPlugin p : postprocessingPlugins) {
 199             Objects.requireNonNull(p);
 200             this.postProcessingPlugins.add(p);
 201         }
 202         this.imageBuilder = imageBuilder;
 203         this.release = ctxt != null? ctxt.getReleaseProperties() : new Properties();
 204     }
 205 
 206     public void operate(ImageProvider provider) throws Exception {
 207         ExecutableImage img = provider.retrieve(this);
 208         List<String> arguments = new ArrayList<>();
 209         for (PostProcessorPlugin plugin : postProcessingPlugins) {
 210             List<String> lst = plugin.process(img);
 211             if (lst != null) {
 212                 arguments.addAll(lst);
 213             }
 214         }
 215         img.storeLaunchArgs(arguments);
 216     }
 217 
 218     public DataOutputStream getJImageFileOutputStream() throws IOException {
 219         return imageBuilder.getJImageOutputStream();
 220     }
 221 
 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      */
 234     public PoolImpl visitResources(PoolImpl resources)
 235             throws Exception {
 236         Objects.requireNonNull(resources);
 237         resources.setReadOnly();
 238         if (resources.isEmpty()) {
 239             return new PoolImpl(resources.getByteOrder(),
 240                     resources.getStringTable());
 241         }
 242         PreVisitStrings previsit = new PreVisitStrings();
 243         for (ResourcePrevisitor p : resourcePrevisitors) {
 244             p.previsit(resources, previsit);
 245         }
 246 
 247         // Store the strings resulting from the previsit.
 248         List<String> sorted = previsit.getSortedStrings();
 249         for (String s : sorted) {
 250             resources.getStringTable().addString(s);
 251         }
 252 
 253         PoolImpl current = resources;
 254         List<Pool.ModuleData> frozenOrder = null;
 255         for (TransformerPlugin p : contentPlugins) {
 256             current.setReadOnly();
 257             PoolImpl output = null;
 258             if (p == lastSorter) {
 259                 if (frozenOrder != null) {
 260                     throw new Exception("Order of resources is already frozen. Plugin "
 261                             + p.getName() + " is badly located");
 262                 }
 263                 // Create a special Resource pool to compute the indexes.
 264                 output = new OrderedResourcePool(current.getByteOrder(),
 265                         resources.getStringTable());
 266             } else {// If we have an order, inject it
 267                 if (frozenOrder != null) {
 268                     output = new CheckOrderResourcePool(current.getByteOrder(),
 269                             frozenOrder, resources.getStringTable());
 270                 } else {
 271                     output = new PoolImpl(current.getByteOrder(),
 272                             resources.getStringTable());
 273                 }
 274             }
 275             p.visit(current, output);
 276             if (output.isEmpty()) {
 277                 throw new Exception("Invalid resource pool for plugin " + p);
 278             }
 279             if (output instanceof OrderedResourcePool) {
 280                 frozenOrder = ((OrderedResourcePool) output).getOrderedList();
 281             }
 282 
 283             current = output;
 284         }
 285         current.setReadOnly();
 286         return current;
 287     }
 288 
 289     /**
 290      * This pool wrap the original pool and automatically uncompress moduledata
 291      * if needed.
 292      */
 293     private class LastPool extends Pool {
 294         private class LastModule implements Module {
 295 
 296             private final Module module;
 297 
 298             LastModule(Module module) {
 299                 this.module = module;
 300             }
 301 
 302             @Override
 303             public String getName() {
 304                 return module.getName();
 305             }
 306 
 307             @Override
 308             public ModuleData get(String path) {
 309                 ModuleData d = module.get(path);
 310                 return getUncompressed(d);
 311             }
 312 
 313             @Override
 314             public ModuleDescriptor getDescriptor() {
 315                 return module.getDescriptor();
 316             }
 317 
 318             @Override
 319             public void add(ModuleData data) {
 320                 throw new PluginException("pool is readonly");
 321             }
 322 
 323             @Override
 324             public Set<String> getAllPackages() {
 325                 return module.getAllPackages();
 326             }
 327 
 328             @Override
 329             public String toString() {
 330                 return getName();
 331             }
 332 
 333             @Override
 334             public Collection<ModuleData> getContent() {
 335                 List<ModuleData> lst = new ArrayList<>();
 336                 for(ModuleData md : module.getContent()) {
 337                     lst.add(getUncompressed(md));
 338                 }
 339                 return lst;
 340             }
 341         }
 342         private final PoolImpl pool;
 343         Decompressor decompressor = new Decompressor();
 344         Collection<ModuleData> content;
 345 
 346         LastPool(PoolImpl pool) {
 347             this.pool = pool;
 348         }
 349 
 350         @Override
 351         public boolean isReadOnly() {
 352             return true;
 353         }
 354 
 355         @Override
 356         public void add(ModuleData resource) {
 357             throw new PluginException("pool is readonly");
 358         }
 359 
 360         /**
 361          * Retrieves the module of the provided name.
 362          *
 363          * @param name The module name
 364          * @return the module or null if the module doesn't exist.
 365          */
 366         @Override
 367         public Module getModule(String name) {
 368             Module module = pool.getModule(name);
 369             if (module != null) {
 370                 module = new LastModule(module);
 371             }
 372             return module;
 373         }
 374 
 375         /**
 376          * The collection of modules contained in this pool.
 377          *
 378          * @return The collection of modules.
 379          */
 380         @Override
 381         public Collection<Module> getModules() {
 382             List<Module> modules = new ArrayList<>();
 383             for (Module m : pool.getModules()) {
 384                 modules.add(new LastModule(m));
 385             }
 386             return modules;
 387         }
 388 
 389         /**
 390          * Get all resources contained in this pool instance.
 391          *
 392          * @return The collection of resources;
 393          */
 394         @Override
 395         public Collection<ModuleData> getContent() {
 396             if (content == null) {
 397                 content = new ArrayList<>();
 398                 for (ModuleData md : pool.getContent()) {
 399                     content.add(getUncompressed(md));
 400                 }
 401             }
 402             return content;
 403         }
 404 
 405         /**
 406          * Get the resource for the passed path.
 407          *
 408          * @param path A resource path
 409          * @return A Resource instance or null if the resource is not found
 410          */
 411         @Override
 412         public ModuleData get(String path) {
 413             Objects.requireNonNull(path);
 414             Pool.ModuleData res = pool.get(path);
 415             return getUncompressed(res);
 416         }
 417 
 418         @Override
 419         public boolean contains(ModuleData res) {
 420             return pool.contains(res);
 421         }
 422 
 423         @Override
 424         public boolean isEmpty() {
 425             return pool.isEmpty();
 426         }
 427 
 428         @Override
 429         public void visit(Visitor visitor, Pool output) {
 430             pool.visit(visitor, output);
 431         }
 432 
 433         @Override
 434         public ByteOrder getByteOrder() {
 435             return pool.getByteOrder();
 436         }
 437 
 438         private ModuleData getUncompressed(ModuleData res) {
 439             if (res != null) {
 440                 if (res instanceof PoolImpl.CompressedModuleData) {
 441                     try {
 442                         byte[] bytes = decompressor.decompressResource(getByteOrder(),
 443                                 (int offset) -> pool.getStringTable().getString(offset),
 444                                 res.getBytes());
 445                         res = Pool.newResource(res.getPath(),
 446                                 new ByteArrayInputStream(bytes),
 447                                 bytes.length);
 448                     } catch (IOException ex) {
 449                         throw new PluginException(ex);
 450                     }
 451                 }
 452             }
 453             return res;
 454         }
 455     }
 456 
 457     /**
 458      * Make the imageBuilder to store files.
 459      *
 460      * @param original
 461      * @param transformed
 462      * @param writer
 463      * @throws java.lang.Exception
 464      */
 465     public void storeFiles(PoolImpl original, PoolImpl transformed,
 466             BasicImageWriter writer)
 467             throws Exception {
 468         Objects.requireNonNull(original);
 469         try {
 470             // fill release information available from transformed "java.base" module!
 471             ModuleDescriptor desc = transformed.getModule("java.base").getDescriptor();
 472             desc.osName().ifPresent(s -> release.put("OS_NAME", s));
 473             desc.osVersion().ifPresent(s -> release.put("OS_VERSION", s));
 474             desc.osArch().ifPresent(s -> release.put("OS_ARCH", s));
 475         } catch (Exception ignored) {
 476         }
 477 
 478         imageBuilder.storeFiles(new LastPool(transformed), release);
 479     }
 480 
 481     public ExecutableImage getExecutableImage() throws IOException {
 482         return imageBuilder.getExecutableImage();
 483     }
 484 }