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