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.internal.module.ModuleInfo.Attributes;
  47 import jdk.internal.module.ModuleTarget;
  48 import jdk.tools.jlink.plugin.Plugin;
  49 import jdk.tools.jlink.builder.ImageBuilder;
  50 import jdk.tools.jlink.plugin.PluginException;
  51 import jdk.tools.jlink.plugin.ResourcePool;
  52 import jdk.tools.jlink.plugin.ResourcePoolModule;
  53 import jdk.tools.jlink.plugin.ResourcePoolEntry;
  54 import jdk.tools.jlink.internal.ResourcePoolManager.ResourcePoolImpl;
  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 OrderedResourcePoolManager extends ResourcePoolManager {
  68         class OrderedResourcePool extends ResourcePoolImpl {
  69             List<ResourcePoolEntry> getOrderedList() {
  70                 return OrderedResourcePoolManager.this.getOrderedList();
  71             }
  72         }
  73 
  74         private final List<ResourcePoolEntry> orderedList = new ArrayList<>();
  75         private final ResourcePoolImpl poolImpl = new OrderedResourcePool();
  76 
  77         public OrderedResourcePoolManager(ByteOrder order, StringTable table) {
  78             super(order, table);
  79         }
  80 
  81         @Override
  82         public ResourcePool resourcePool() {
  83             return poolImpl;
  84         }
  85 
  86         /**
  87          * Add a resource.
  88          *
  89          * @param resource The Resource to add.
  90          */
  91         @Override
  92         public void add(ResourcePoolEntry resource) {
  93             super.add(resource);
  94             orderedList.add(resource);
  95         }
  96 
  97         List<ResourcePoolEntry> getOrderedList() {
  98             return Collections.unmodifiableList(orderedList);
  99         }
 100     }
 101 
 102     private final static class CheckOrderResourcePoolManager extends ResourcePoolManager {
 103 
 104         private final List<ResourcePoolEntry> orderedList;
 105         private int currentIndex;
 106 
 107         public CheckOrderResourcePoolManager(ByteOrder order, List<ResourcePoolEntry> orderedList, StringTable table) {
 108             super(order, table);
 109             this.orderedList = Objects.requireNonNull(orderedList);
 110         }
 111 
 112         /**
 113          * Add a resource.
 114          *
 115          * @param resource The Resource to add.
 116          */
 117         @Override
 118         public void add(ResourcePoolEntry resource) {
 119             ResourcePoolEntry ordered = orderedList.get(currentIndex);
 120             if (!resource.equals(ordered)) {
 121                 throw new PluginException("Resource " + resource.path() + " not in the right order");
 122             }
 123             super.add(resource);
 124             currentIndex += 1;
 125         }
 126     }
 127 
 128     private static final class PreVisitStrings implements StringTable {
 129 
 130         private int currentid = 0;
 131         private final Map<String, Integer> stringsUsage = new HashMap<>();
 132         private final Map<String, Integer> stringsMap = new HashMap<>();
 133         private final Map<Integer, String> reverseMap = new HashMap<>();
 134 
 135         @Override
 136         public int addString(String str) {
 137             Objects.requireNonNull(str);
 138             Integer count = stringsUsage.get(str);
 139             if (count == null) {
 140                 count = 0;
 141             }
 142             count += 1;
 143             stringsUsage.put(str, count);
 144             Integer id = stringsMap.get(str);
 145             if (id == null) {
 146                 id = currentid;
 147                 stringsMap.put(str, id);
 148                 currentid += 1;
 149                 reverseMap.put(id, str);
 150             }
 151 
 152             return id;
 153         }
 154 
 155         private List<String> getSortedStrings() {
 156             Stream<java.util.Map.Entry<String, Integer>> stream
 157                     = stringsUsage.entrySet().stream();
 158             // Remove strings that have a single occurence
 159             List<String> result = stream.sorted(Comparator.comparing(e -> e.getValue(),
 160                     Comparator.reverseOrder())).filter((e) -> {
 161                         return e.getValue() > 1;
 162                     }).map(java.util.Map.Entry::getKey).
 163                     collect(Collectors.toList());
 164             return result;
 165         }
 166 
 167         @Override
 168         public String getString(int id) {
 169             return reverseMap.get(id);
 170         }
 171     }
 172 
 173     private final ImageBuilder imageBuilder;
 174     private final Plugin lastSorter;
 175     private final List<Plugin> plugins = new ArrayList<>();
 176     private final List<ResourcePrevisitor> resourcePrevisitors = new ArrayList<>();
 177     private final boolean validate;
 178 
 179     public ImagePluginStack() {
 180         this(null, Collections.emptyList(), null);
 181     }
 182 
 183     public ImagePluginStack(ImageBuilder imageBuilder,
 184             List<Plugin> plugins,
 185             Plugin lastSorter) {
 186         this(imageBuilder, plugins, lastSorter, true);
 187     }
 188 
 189     public ImagePluginStack(ImageBuilder imageBuilder,
 190             List<Plugin> plugins,
 191             Plugin lastSorter,
 192             boolean validate) {
 193         this.imageBuilder = Objects.requireNonNull(imageBuilder);
 194         this.lastSorter = lastSorter;
 195         this.plugins.addAll(Objects.requireNonNull(plugins));
 196         plugins.stream().forEach((p) -> {
 197             Objects.requireNonNull(p);
 198             if (p instanceof ResourcePrevisitor) {
 199                 resourcePrevisitors.add((ResourcePrevisitor) p);
 200             }
 201         });
 202         this.validate = validate;
 203     }
 204 
 205     public void operate(ImageProvider provider) throws Exception {
 206         ExecutableImage img = provider.retrieve(this);
 207         List<String> arguments = new ArrayList<>();
 208         plugins.stream()
 209                 .filter(PostProcessor.class::isInstance)
 210                 .map((plugin) -> ((PostProcessor)plugin).process(img))
 211                 .filter((lst) -> (lst != null))
 212                 .forEach((lst) -> {
 213                      arguments.addAll(lst);
 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 ResourcePool visitResources(ResourcePoolManager resources)
 235             throws Exception {
 236         Objects.requireNonNull(resources);
 237         if (resources.isEmpty()) {
 238             return new ResourcePoolManager(resources.byteOrder(),
 239                     resources.getStringTable()).resourcePool();
 240         }
 241         PreVisitStrings previsit = new PreVisitStrings();
 242         resourcePrevisitors.stream().forEach((p) -> {
 243             p.previsit(resources.resourcePool(), previsit);
 244         });
 245 
 246         // Store the strings resulting from the previsit.
 247         List<String> sorted = previsit.getSortedStrings();
 248         sorted.stream().forEach((s) -> {
 249             resources.getStringTable().addString(s);
 250         });
 251 
 252         ResourcePool resPool = resources.resourcePool();
 253         List<ResourcePoolEntry> frozenOrder = null;
 254         for (Plugin p : plugins) {
 255             ResourcePoolManager resMgr = null;
 256             if (p == lastSorter) {
 257                 if (frozenOrder != null) {
 258                     throw new Exception("Order of resources is already frozen. Plugin "
 259                             + p.getName() + " is badly located");
 260                 }
 261                 // Create a special Resource pool to compute the indexes.
 262                 resMgr = new OrderedResourcePoolManager(resPool.byteOrder(),
 263                         resources.getStringTable());
 264             } else {// If we have an order, inject it
 265                 if (frozenOrder != null) {
 266                     resMgr = new CheckOrderResourcePoolManager(resPool.byteOrder(),
 267                             frozenOrder, resources.getStringTable());
 268                 } else {
 269                     resMgr = new ResourcePoolManager(resPool.byteOrder(),
 270                             resources.getStringTable());
 271                 }
 272             }
 273             try {
 274                 resPool = p.transform(resPool, resMgr.resourcePoolBuilder());
 275             } catch (PluginException pe) {
 276                 if (JlinkTask.DEBUG) {
 277                     System.err.println("Plugin " + p.getName() + " threw exception during transform");
 278                     pe.printStackTrace();
 279                 }
 280                 throw pe;
 281             }
 282             if (resPool.isEmpty()) {
 283                 throw new Exception("Invalid resource pool for plugin " + p);
 284             }
 285             if (resPool instanceof OrderedResourcePoolManager.OrderedResourcePool) {
 286                 frozenOrder = ((OrderedResourcePoolManager.OrderedResourcePool)resPool).getOrderedList();
 287             }
 288         }
 289 
 290         return resPool;
 291     }
 292 
 293     /**
 294      * This pool wrap the original pool and automatically uncompress ResourcePoolEntry
 295      * if needed.
 296      */
 297     private class LastPoolManager extends ResourcePoolManager {
 298         private class LastModule implements ResourcePoolModule {
 299 
 300             final ResourcePoolModule module;
 301             // lazily initialized
 302             ModuleDescriptor descriptor;
 303             ModuleTarget target;
 304 
 305             LastModule(ResourcePoolModule module) {
 306                 this.module = module;
 307             }
 308 
 309             @Override
 310             public String name() {
 311                 return module.name();
 312             }
 313 
 314             @Override
 315             public Optional<ResourcePoolEntry> findEntry(String path) {
 316                 Optional<ResourcePoolEntry> d = module.findEntry(path);
 317                 return d.isPresent()? Optional.of(getUncompressed(d.get())) : Optional.empty();
 318             }
 319 
 320             @Override
 321             public ModuleDescriptor descriptor() {
 322                 initModuleAttributes();
 323                 return descriptor;
 324             }
 325 
 326             @Override
 327             public String osName() {
 328                 initModuleAttributes();
 329                 return target != null? target.osName() : null;
 330             }
 331 
 332             @Override
 333             public String osArch() {
 334                 initModuleAttributes();
 335                 return target != null? target.osArch() : null;
 336             }
 337 
 338             private void initModuleAttributes() {
 339                 if (this.descriptor == null) {
 340                     Attributes attr = ResourcePoolManager.readModuleAttributes(this);
 341                     this.descriptor = attr.descriptor();
 342                     this.target = attr.target();
 343                 }
 344             }
 345 
 346             @Override
 347             public Set<String> packages() {
 348                 return module.packages();
 349             }
 350 
 351             @Override
 352             public String toString() {
 353                 return name();
 354             }
 355 
 356             @Override
 357             public Stream<ResourcePoolEntry> entries() {
 358                 List<ResourcePoolEntry> lst = new ArrayList<>();
 359                 module.entries().forEach(md -> {
 360                     lst.add(getUncompressed(md));
 361                 });
 362                 return lst.stream();
 363             }
 364 
 365             @Override
 366             public int entryCount() {
 367                 return module.entryCount();
 368             }
 369         }
 370 
 371         private final ResourcePool pool;
 372         Decompressor decompressor = new Decompressor();
 373         Collection<ResourcePoolEntry> content;
 374 
 375         LastPoolManager(ResourcePool pool) {
 376             this.pool = pool;
 377         }
 378 
 379         @Override
 380         public void add(ResourcePoolEntry resource) {
 381             throw new PluginException("pool is readonly");
 382         }
 383 
 384         @Override
 385         public Optional<ResourcePoolModule> findModule(String name) {
 386             Optional<ResourcePoolModule> module = pool.moduleView().findModule(name);
 387             return module.isPresent()? Optional.of(new LastModule(module.get())) : Optional.empty();
 388         }
 389 
 390         /**
 391          * The collection of modules contained in this pool.
 392          *
 393          * @return The collection of modules.
 394          */
 395         @Override
 396         public Stream<ResourcePoolModule> modules() {
 397             List<ResourcePoolModule> modules = new ArrayList<>();
 398             pool.moduleView().modules().forEach(m -> {
 399                 modules.add(new LastModule(m));
 400             });
 401             return modules.stream();
 402         }
 403 
 404         @Override
 405         public int moduleCount() {
 406             return pool.moduleView().moduleCount();
 407         }
 408 
 409         /**
 410          * Get all resources contained in this pool instance.
 411          *
 412          * @return The stream of resources;
 413          */
 414         @Override
 415         public Stream<ResourcePoolEntry> entries() {
 416             if (content == null) {
 417                 content = new ArrayList<>();
 418                 pool.entries().forEach(md -> {
 419                     content.add(getUncompressed(md));
 420                 });
 421             }
 422             return content.stream();
 423         }
 424 
 425         @Override
 426         public int entryCount() {
 427             return pool.entryCount();
 428         }
 429 
 430         /**
 431          * Get the resource for the passed path.
 432          *
 433          * @param path A resource path
 434          * @return A Resource instance if the resource is found
 435          */
 436         @Override
 437         public Optional<ResourcePoolEntry> findEntry(String path) {
 438             Objects.requireNonNull(path);
 439             Optional<ResourcePoolEntry> res = pool.findEntry(path);
 440             return res.isPresent()? Optional.of(getUncompressed(res.get())) : Optional.empty();
 441         }
 442 
 443         @Override
 444         public Optional<ResourcePoolEntry> findEntryInContext(String path, ResourcePoolEntry context) {
 445             Objects.requireNonNull(path);
 446             Objects.requireNonNull(context);
 447             Optional<ResourcePoolEntry> res = pool.findEntryInContext(path, context);
 448             return res.map(this::getUncompressed);
 449         }
 450 
 451         @Override
 452         public boolean contains(ResourcePoolEntry res) {
 453             return pool.contains(res);
 454         }
 455 
 456         @Override
 457         public boolean isEmpty() {
 458             return pool.isEmpty();
 459         }
 460 
 461         @Override
 462         public ByteOrder byteOrder() {
 463             return pool.byteOrder();
 464         }
 465 
 466         private ResourcePoolEntry getUncompressed(ResourcePoolEntry res) {
 467             if (res != null) {
 468                 if (res instanceof ResourcePoolManager.CompressedModuleData) {
 469                     try {
 470                         byte[] bytes = decompressor.decompressResource(byteOrder(),
 471                                 (int offset) -> ((ResourcePoolImpl)pool).getStringTable().getString(offset),
 472                                 res.contentBytes());
 473                         res = res.copyWithContent(bytes);
 474                     } catch (IOException ex) {
 475                         if (JlinkTask.DEBUG) {
 476                             System.err.println("IOException while reading resource: " + res.path());
 477                             ex.printStackTrace();
 478                         }
 479                         throw new PluginException(ex);
 480                     }
 481                 }
 482             }
 483             return res;
 484         }
 485     }
 486 
 487     /**
 488      * Make the imageBuilder to store files.
 489      *
 490      * @param original
 491      * @param transformed
 492      * @param writer
 493      * @throws java.lang.Exception
 494      */
 495     public void storeFiles(ResourcePool original, ResourcePool transformed,
 496             BasicImageWriter writer)
 497             throws Exception {
 498         Objects.requireNonNull(original);
 499         Objects.requireNonNull(transformed);
 500         ResourcePool lastPool = new LastPoolManager(transformed).resourcePool();
 501         if (validate) {
 502             ResourcePoolConfiguration.validate(lastPool);
 503         }
 504         imageBuilder.storeFiles(lastPool);
 505     }
 506 
 507     public ExecutableImage getExecutableImage() throws IOException {
 508         return imageBuilder.getExecutableImage();
 509     }
 510 }