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 
 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 = Objects.requireNonNull(imageBuilder);
 185         this.lastSorter = lastSorter;
 186         this.plugins.addAll(Objects.requireNonNull(plugins));
 187         plugins.stream().forEach((p) -> {
 188             Objects.requireNonNull(p);
 189             if (p instanceof ResourcePrevisitor) {
 190                 resourcePrevisitors.add((ResourcePrevisitor) p);
 191             }
 192         });
 193     }
 194 
 195     public void operate(ImageProvider provider) throws Exception {
 196         ExecutableImage img = provider.retrieve(this);
 197         List<String> arguments = new ArrayList<>();
 198         plugins.stream()
 199                 .filter(PostProcessor.class::isInstance)
 200                 .map((plugin) -> ((PostProcessor)plugin).process(img))
 201                 .filter((lst) -> (lst != null))
 202                 .forEach((lst) -> {
 203                      arguments.addAll(lst);
 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 ResourcePool visitResources(ResourcePoolManager resources)
 225             throws Exception {
 226         Objects.requireNonNull(resources);
 227         if (resources.isEmpty()) {
 228             return new ResourcePoolManager(resources.byteOrder(),
 229                     resources.getStringTable()).resourcePool();
 230         }
 231         PreVisitStrings previsit = new PreVisitStrings();
 232         resourcePrevisitors.stream().forEach((p) -> {
 233             p.previsit(resources.resourcePool(), previsit);
 234         });
 235 
 236         // Store the strings resulting from the previsit.
 237         List<String> sorted = previsit.getSortedStrings();
 238         sorted.stream().forEach((s) -> {
 239             resources.getStringTable().addString(s);
 240         });
 241 
 242         ResourcePool resPool = resources.resourcePool();
 243         List<ResourcePoolEntry> frozenOrder = null;
 244         for (Plugin p : plugins) {
 245             ResourcePoolManager resMgr = null;
 246             if (p == lastSorter) {
 247                 if (frozenOrder != null) {
 248                     throw new Exception("Order of resources is already frozen. Plugin "
 249                             + p.getName() + " is badly located");
 250                 }
 251                 // Create a special Resource pool to compute the indexes.
 252                 resMgr = new OrderedResourcePoolManager(resPool.byteOrder(),
 253                         resources.getStringTable());
 254             } else {// If we have an order, inject it
 255                 if (frozenOrder != null) {
 256                     resMgr = new CheckOrderResourcePoolManager(resPool.byteOrder(),
 257                             frozenOrder, resources.getStringTable());
 258                 } else {
 259                     resMgr = new ResourcePoolManager(resPool.byteOrder(),
 260                             resources.getStringTable());
 261                 }
 262             }
 263             resPool = p.transform(resPool, resMgr.resourcePoolBuilder());
 264             if (resPool.isEmpty()) {
 265                 throw new Exception("Invalid resource pool for plugin " + p);
 266             }
 267             if (resPool instanceof OrderedResourcePoolManager.OrderedResourcePool) {
 268                 frozenOrder = ((OrderedResourcePoolManager.OrderedResourcePool)resPool).getOrderedList();
 269             }
 270         }
 271         return resPool;
 272     }
 273 
 274     /**
 275      * This pool wrap the original pool and automatically uncompress ResourcePoolEntry
 276      * if needed.
 277      */
 278     private class LastPoolManager extends ResourcePoolManager {
 279         private class LastModule implements ResourcePoolModule {
 280 
 281             final ResourcePoolModule module;
 282 
 283             LastModule(ResourcePoolModule module) {
 284                 this.module = module;
 285             }
 286 
 287             @Override
 288             public String name() {
 289                 return module.name();
 290             }
 291 
 292             @Override
 293             public Optional<ResourcePoolEntry> findEntry(String path) {
 294                 Optional<ResourcePoolEntry> d = module.findEntry(path);
 295                 return d.isPresent()? Optional.of(getUncompressed(d.get())) : Optional.empty();
 296             }
 297 
 298             @Override
 299             public ModuleDescriptor descriptor() {
 300                 return module.descriptor();
 301             }
 302 
 303             @Override
 304             public Set<String> packages() {
 305                 return module.packages();
 306             }
 307 
 308             @Override
 309             public String toString() {
 310                 return name();
 311             }
 312 
 313             @Override
 314             public Stream<ResourcePoolEntry> entries() {
 315                 List<ResourcePoolEntry> lst = new ArrayList<>();
 316                 module.entries().forEach(md -> {
 317                     lst.add(getUncompressed(md));
 318                 });
 319                 return lst.stream();
 320             }
 321 
 322             @Override
 323             public int entryCount() {
 324                 return module.entryCount();
 325             }
 326         }
 327 
 328         private final ResourcePool pool;
 329         Decompressor decompressor = new Decompressor();
 330         Collection<ResourcePoolEntry> content;
 331 
 332         LastPoolManager(ResourcePool pool) {
 333             this.pool = pool;
 334         }
 335 
 336         @Override
 337         public void add(ResourcePoolEntry resource) {
 338             throw new PluginException("pool is readonly");
 339         }
 340 
 341         @Override
 342         public Optional<ResourcePoolModule> findModule(String name) {
 343             Optional<ResourcePoolModule> module = pool.moduleView().findModule(name);
 344             return module.isPresent()? Optional.of(new LastModule(module.get())) : Optional.empty();
 345         }
 346 
 347         /**
 348          * The collection of modules contained in this pool.
 349          *
 350          * @return The collection of modules.
 351          */
 352         @Override
 353         public Stream<ResourcePoolModule> modules() {
 354             List<ResourcePoolModule> modules = new ArrayList<>();
 355             pool.moduleView().modules().forEach(m -> {
 356                 modules.add(new LastModule(m));
 357             });
 358             return modules.stream();
 359         }
 360 
 361         @Override
 362         public int moduleCount() {
 363             return pool.moduleView().moduleCount();
 364         }
 365 
 366         /**
 367          * Get all resources contained in this pool instance.
 368          *
 369          * @return The stream of resources;
 370          */
 371         @Override
 372         public Stream<ResourcePoolEntry> entries() {
 373             if (content == null) {
 374                 content = new ArrayList<>();
 375                 pool.entries().forEach(md -> {
 376                     content.add(getUncompressed(md));
 377                 });
 378             }
 379             return content.stream();
 380         }
 381 
 382         @Override
 383         public int entryCount() {
 384             return pool.entryCount();
 385         }
 386 
 387         /**
 388          * Get the resource for the passed path.
 389          *
 390          * @param path A resource path
 391          * @return A Resource instance if the resource is found
 392          */
 393         @Override
 394         public Optional<ResourcePoolEntry> findEntry(String path) {
 395             Objects.requireNonNull(path);
 396             Optional<ResourcePoolEntry> res = pool.findEntry(path);
 397             return res.isPresent()? Optional.of(getUncompressed(res.get())) : Optional.empty();
 398         }
 399 
 400         @Override
 401         public Optional<ResourcePoolEntry> findEntryInContext(String path, ResourcePoolEntry context) {
 402             Objects.requireNonNull(path);
 403             Objects.requireNonNull(context);
 404             Optional<ResourcePoolEntry> res = pool.findEntryInContext(path, context);
 405             return res.map(this::getUncompressed);
 406         }
 407 
 408         @Override
 409         public boolean contains(ResourcePoolEntry res) {
 410             return pool.contains(res);
 411         }
 412 
 413         @Override
 414         public boolean isEmpty() {
 415             return pool.isEmpty();
 416         }
 417 
 418         @Override
 419         public ByteOrder byteOrder() {
 420             return pool.byteOrder();
 421         }
 422 
 423         @Override
 424         public Map<String, String> releaseProperties() {
 425             return pool.releaseProperties();
 426         }
 427 
 428         private ResourcePoolEntry getUncompressed(ResourcePoolEntry res) {
 429             if (res != null) {
 430                 if (res instanceof ResourcePoolManager.CompressedModuleData) {
 431                     try {
 432                         byte[] bytes = decompressor.decompressResource(byteOrder(),
 433                                 (int offset) -> ((ResourcePoolImpl)pool).getStringTable().getString(offset),
 434                                 res.contentBytes());
 435                         res = res.copyWithContent(bytes);
 436                     } catch (IOException ex) {
 437                         if (JlinkTask.DEBUG) {
 438                             ex.printStackTrace();
 439                         }
 440                         throw new PluginException(ex);
 441                     }
 442                 }
 443             }
 444             return res;
 445         }
 446     }
 447 
 448     /**
 449      * Make the imageBuilder to store files.
 450      *
 451      * @param original
 452      * @param transformed
 453      * @param writer
 454      * @throws java.lang.Exception
 455      */
 456     public void storeFiles(ResourcePool original, ResourcePool transformed,
 457             BasicImageWriter writer)
 458             throws Exception {
 459         Objects.requireNonNull(original);
 460         Objects.requireNonNull(transformed);
 461         Optional<ResourcePoolModule> javaBase = transformed.moduleView().findModule("java.base");
 462         javaBase.ifPresent(mod -> {
 463             try {
 464                 Map<String, String> release = transformed.releaseProperties();
 465                 // fill release information available from transformed "java.base" module!
 466                 ModuleDescriptor desc = mod.descriptor();
 467                 desc.osName().ifPresent(s -> release.put("OS_NAME", s));
 468                 desc.osVersion().ifPresent(s -> release.put("OS_VERSION", s));
 469                 desc.osArch().ifPresent(s -> release.put("OS_ARCH", s));
 470             } catch (Exception ignored) {}
 471         });
 472 
 473         imageBuilder.storeFiles(new LastPoolManager(transformed).resourcePool());
 474     }
 475 
 476     public ExecutableImage getExecutableImage() throws IOException {
 477         return imageBuilder.getExecutableImage();
 478     }
 479 }