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 boolean validate;
 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, true);
 185     }
 186 
 187     public ImagePluginStack(ImageBuilder imageBuilder,
 188             List<Plugin> plugins,
 189             Plugin lastSorter,
 190             boolean validate) {
 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.validate = validate;
 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             try {
 272                 resPool = p.transform(resPool, resMgr.resourcePoolBuilder());
 273             } catch (PluginException pe) {
 274                 if (JlinkTask.DEBUG) {
 275                     System.err.println("Plugin " + p.getName() + " threw exception during transform");
 276                     pe.printStackTrace();
 277                 }
 278                 throw pe;
 279             }
 280             if (resPool.isEmpty()) {
 281                 throw new Exception("Invalid resource pool for plugin " + p);
 282             }
 283             if (resPool instanceof OrderedResourcePoolManager.OrderedResourcePool) {
 284                 frozenOrder = ((OrderedResourcePoolManager.OrderedResourcePool)resPool).getOrderedList();
 285             }
 286         }
 287 
 288         return resPool;
 289     }
 290 
 291     /**
 292      * This pool wrap the original pool and automatically uncompress ResourcePoolEntry
 293      * if needed.
 294      */
 295     private class LastPoolManager extends ResourcePoolManager {
 296         private class LastModule implements ResourcePoolModule {
 297 
 298             final ResourcePoolModule module;
 299             // lazily initialized
 300             ModuleDescriptor descriptor;
 301 
 302             LastModule(ResourcePoolModule module) {
 303                 this.module = module;
 304             }
 305 
 306             @Override
 307             public String name() {
 308                 return module.name();
 309             }
 310 
 311             @Override
 312             public Optional<ResourcePoolEntry> findEntry(String path) {
 313                 Optional<ResourcePoolEntry> d = module.findEntry(path);
 314                 return d.isPresent()? Optional.of(getUncompressed(d.get())) : Optional.empty();
 315             }
 316 
 317             @Override
 318             public ModuleDescriptor descriptor() {
 319                 if (descriptor == null) {
 320                     descriptor = ResourcePoolManager.readModuleDescriptor(this);
 321                 }
 322                 return descriptor;
 323             }
 324 
 325             @Override
 326             public Set<String> packages() {
 327                 return module.packages();
 328             }
 329 
 330             @Override
 331             public String toString() {
 332                 return name();
 333             }
 334 
 335             @Override
 336             public Stream<ResourcePoolEntry> entries() {
 337                 List<ResourcePoolEntry> lst = new ArrayList<>();
 338                 module.entries().forEach(md -> {
 339                     lst.add(getUncompressed(md));
 340                 });
 341                 return lst.stream();
 342             }
 343 
 344             @Override
 345             public int entryCount() {
 346                 return module.entryCount();
 347             }
 348         }
 349 
 350         private final ResourcePool pool;
 351         Decompressor decompressor = new Decompressor();
 352         Collection<ResourcePoolEntry> content;
 353 
 354         LastPoolManager(ResourcePool pool) {
 355             this.pool = pool;
 356         }
 357 
 358         @Override
 359         public void add(ResourcePoolEntry resource) {
 360             throw new PluginException("pool is readonly");
 361         }
 362 
 363         @Override
 364         public Optional<ResourcePoolModule> findModule(String name) {
 365             Optional<ResourcePoolModule> module = pool.moduleView().findModule(name);
 366             return module.isPresent()? Optional.of(new LastModule(module.get())) : Optional.empty();
 367         }
 368 
 369         /**
 370          * The collection of modules contained in this pool.
 371          *
 372          * @return The collection of modules.
 373          */
 374         @Override
 375         public Stream<ResourcePoolModule> modules() {
 376             List<ResourcePoolModule> modules = new ArrayList<>();
 377             pool.moduleView().modules().forEach(m -> {
 378                 modules.add(new LastModule(m));
 379             });
 380             return modules.stream();
 381         }
 382 
 383         @Override
 384         public int moduleCount() {
 385             return pool.moduleView().moduleCount();
 386         }
 387 
 388         /**
 389          * Get all resources contained in this pool instance.
 390          *
 391          * @return The stream of resources;
 392          */
 393         @Override
 394         public Stream<ResourcePoolEntry> entries() {
 395             if (content == null) {
 396                 content = new ArrayList<>();
 397                 pool.entries().forEach(md -> {
 398                     content.add(getUncompressed(md));
 399                 });
 400             }
 401             return content.stream();
 402         }
 403 
 404         @Override
 405         public int entryCount() {
 406             return pool.entryCount();
 407         }
 408 
 409         /**
 410          * Get the resource for the passed path.
 411          *
 412          * @param path A resource path
 413          * @return A Resource instance if the resource is found
 414          */
 415         @Override
 416         public Optional<ResourcePoolEntry> findEntry(String path) {
 417             Objects.requireNonNull(path);
 418             Optional<ResourcePoolEntry> res = pool.findEntry(path);
 419             return res.isPresent()? Optional.of(getUncompressed(res.get())) : Optional.empty();
 420         }
 421 
 422         @Override
 423         public Optional<ResourcePoolEntry> findEntryInContext(String path, ResourcePoolEntry context) {
 424             Objects.requireNonNull(path);
 425             Objects.requireNonNull(context);
 426             Optional<ResourcePoolEntry> res = pool.findEntryInContext(path, context);
 427             return res.map(this::getUncompressed);
 428         }
 429 
 430         @Override
 431         public boolean contains(ResourcePoolEntry res) {
 432             return pool.contains(res);
 433         }
 434 
 435         @Override
 436         public boolean isEmpty() {
 437             return pool.isEmpty();
 438         }
 439 
 440         @Override
 441         public ByteOrder byteOrder() {
 442             return pool.byteOrder();
 443         }
 444 
 445         private ResourcePoolEntry getUncompressed(ResourcePoolEntry res) {
 446             if (res != null) {
 447                 if (res instanceof ResourcePoolManager.CompressedModuleData) {
 448                     try {
 449                         byte[] bytes = decompressor.decompressResource(byteOrder(),
 450                                 (int offset) -> ((ResourcePoolImpl)pool).getStringTable().getString(offset),
 451                                 res.contentBytes());
 452                         res = res.copyWithContent(bytes);
 453                     } catch (IOException ex) {
 454                         if (JlinkTask.DEBUG) {
 455                             System.err.println("IOException while reading resource: " + res.path());
 456                             ex.printStackTrace();
 457                         }
 458                         throw new PluginException(ex);
 459                     }
 460                 }
 461             }
 462             return res;
 463         }
 464     }
 465 
 466     /**
 467      * Make the imageBuilder to store files.
 468      *
 469      * @param original
 470      * @param transformed
 471      * @param writer
 472      * @throws java.lang.Exception
 473      */
 474     public void storeFiles(ResourcePool original, ResourcePool transformed,
 475             BasicImageWriter writer)
 476             throws Exception {
 477         Objects.requireNonNull(original);
 478         Objects.requireNonNull(transformed);
 479         ResourcePool lastPool = new LastPoolManager(transformed).resourcePool();
 480         if (validate) {
 481             ResourcePoolConfiguration.validate(lastPool);
 482         }
 483         imageBuilder.storeFiles(lastPool);
 484     }
 485 
 486     public ExecutableImage getExecutableImage() throws IOException {
 487         return imageBuilder.getExecutableImage();
 488     }
 489 }