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