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.lang.module.ModuleDescriptor;
  28 import java.nio.ByteBuffer;
  29 import java.nio.ByteOrder;
  30 import java.util.HashSet;
  31 import java.util.LinkedHashMap;
  32 import java.util.Map;
  33 import java.util.Objects;
  34 import java.util.Optional;
  35 import java.util.Set;
  36 import java.util.stream.Stream;
  37 import jdk.internal.jimage.decompressor.CompressedResourceHeader;
  38 import jdk.tools.jlink.plugin.ResourcePool;
  39 import jdk.tools.jlink.plugin.ResourcePoolBuilder;
  40 import jdk.tools.jlink.plugin.ResourcePoolEntry;
  41 import jdk.tools.jlink.plugin.ResourcePoolModule;
  42 import jdk.tools.jlink.plugin.ResourcePoolModuleView;
  43 import jdk.tools.jlink.plugin.PluginException;
  44 
  45 /**
  46  * A manager for pool of resources.
  47  */
  48 public class ResourcePoolManager {
  49     // utility to read ModuleDescriptor of the given ResourcePoolModule
  50     static ModuleDescriptor readModuleDescriptor(ResourcePoolModule mod) {
  51         String p = "/" + mod.name() + "/module-info.class";
  52         Optional<ResourcePoolEntry> content = mod.findEntry(p);
  53         if (!content.isPresent()) {
  54               throw new PluginException("No module-info for " + mod.name()
  55                       + " module");
  56         }
  57         ByteBuffer bb = ByteBuffer.wrap(content.get().contentBytes());
  58         return ModuleDescriptor.read(bb);
  59     }
  60 
  61     class ResourcePoolModuleImpl implements ResourcePoolModule {
  62 
  63         final Map<String, ResourcePoolEntry> moduleContent = new LinkedHashMap<>();
  64         // lazily initialized
  65         private ModuleDescriptor descriptor;
  66         final String name;
  67 
  68         private ResourcePoolModuleImpl(String name) {
  69             this.name = name;
  70         }
  71 
  72         @Override
  73         public String name() {
  74             return name;
  75         }
  76 
  77         @Override
  78         public Optional<ResourcePoolEntry> findEntry(String path) {
  79             if (!path.startsWith("/")) {
  80                 path = "/" + path;
  81             }
  82             if (!path.startsWith("/" + name + "/")) {
  83                 path = "/" + name + path; // path already starts with '/'
  84             }
  85             return Optional.ofNullable(moduleContent.get(path));
  86         }
  87 
  88         @Override
  89         public ModuleDescriptor descriptor() {
  90             if (descriptor == null) {
  91                 descriptor = readModuleDescriptor(this);
  92             }
  93             return descriptor;
  94         }
  95 
  96         @Override
  97         public Set<String> packages() {
  98             Set<String> pkgs = new HashSet<>();
  99             moduleContent.values().stream()
 100                 .filter(m -> m.type() == ResourcePoolEntry.Type.CLASS_OR_RESOURCE)
 101                 .forEach(res -> {
 102                     String name = ImageFileCreator.resourceName(res.path());
 103                     if (name.endsWith(".class") && !name.endsWith("module-info.class")) {
 104                         String pkg = ImageFileCreator.toPackage(name);
 105                         if (!pkg.isEmpty()) {
 106                             pkgs.add(pkg);
 107                         }
 108                     }
 109                 });
 110             return pkgs;
 111         }
 112 
 113         @Override
 114         public String toString() {
 115             return name();
 116         }
 117 
 118         @Override
 119         public Stream<ResourcePoolEntry> entries() {
 120             return moduleContent.values().stream();
 121         }
 122 
 123         @Override
 124         public int entryCount() {
 125             return moduleContent.values().size();
 126         }
 127     }
 128 
 129     public class ResourcePoolImpl implements ResourcePool {
 130         @Override
 131         public ResourcePoolModuleView moduleView() {
 132             return ResourcePoolManager.this.moduleView();
 133         }
 134 
 135         @Override
 136         public Stream<ResourcePoolEntry> entries() {
 137             return ResourcePoolManager.this.entries();
 138         }
 139 
 140         @Override
 141         public int entryCount() {
 142             return ResourcePoolManager.this.entryCount();
 143         }
 144 
 145         @Override
 146         public Optional<ResourcePoolEntry> findEntry(String path) {
 147             return ResourcePoolManager.this.findEntry(path);
 148         }
 149 
 150         @Override
 151         public Optional<ResourcePoolEntry> findEntryInContext(String path, ResourcePoolEntry context) {
 152             return ResourcePoolManager.this.findEntryInContext(path, context);
 153         }
 154 
 155         @Override
 156         public boolean contains(ResourcePoolEntry data) {
 157             return ResourcePoolManager.this.contains(data);
 158         }
 159 
 160         @Override
 161         public boolean isEmpty() {
 162             return ResourcePoolManager.this.isEmpty();
 163         }
 164 
 165         @Override
 166         public ByteOrder byteOrder() {
 167             return ResourcePoolManager.this.byteOrder();
 168         }
 169 
 170         public StringTable getStringTable() {
 171             return ResourcePoolManager.this.getStringTable();
 172         }
 173     }
 174 
 175     class ResourcePoolBuilderImpl implements ResourcePoolBuilder {
 176         private boolean built;
 177 
 178         @Override
 179         public void add(ResourcePoolEntry data) {
 180             if (built) {
 181                 throw new IllegalStateException("resource pool already built!");
 182             }
 183             ResourcePoolManager.this.add(data);
 184         }
 185 
 186         @Override
 187         public ResourcePool build() {
 188             built = true;
 189             return ResourcePoolManager.this.resourcePool();
 190         }
 191     }
 192 
 193     class ResourcePoolModuleViewImpl implements ResourcePoolModuleView {
 194         @Override
 195         public Optional<ResourcePoolModule> findModule(String name) {
 196             return ResourcePoolManager.this.findModule(name);
 197         }
 198 
 199         @Override
 200         public Stream<ResourcePoolModule> modules() {
 201             return ResourcePoolManager.this.modules();
 202         }
 203 
 204         @Override
 205         public int moduleCount() {
 206             return ResourcePoolManager.this.moduleCount();
 207         }
 208     }
 209 
 210     private final Map<String, ResourcePoolEntry> resources = new LinkedHashMap<>();
 211     private final Map<String, ResourcePoolModule> modules = new LinkedHashMap<>();
 212     private final ByteOrder order;
 213     private final StringTable table;
 214     private final ResourcePool poolImpl;
 215     private final ResourcePoolBuilder poolBuilderImpl;
 216     private final ResourcePoolModuleView moduleViewImpl;
 217 
 218     public ResourcePoolManager() {
 219         this(ByteOrder.nativeOrder());
 220     }
 221 
 222     public ResourcePoolManager(ByteOrder order) {
 223         this(order, new StringTable() {
 224 
 225             @Override
 226             public int addString(String str) {
 227                 return -1;
 228             }
 229 
 230             @Override
 231             public String getString(int id) {
 232                 return null;
 233             }
 234         });
 235     }
 236 
 237     public ResourcePoolManager(ByteOrder order, StringTable table) {
 238         this.order = Objects.requireNonNull(order);
 239         this.table = Objects.requireNonNull(table);
 240         this.poolImpl = new ResourcePoolImpl();
 241         this.poolBuilderImpl = new ResourcePoolBuilderImpl();
 242         this.moduleViewImpl = new ResourcePoolModuleViewImpl();
 243     }
 244 
 245     public ResourcePool resourcePool() {
 246         return poolImpl;
 247     }
 248 
 249     public ResourcePoolBuilder resourcePoolBuilder() {
 250         return poolBuilderImpl;
 251     }
 252 
 253     public ResourcePoolModuleView moduleView() {
 254         return moduleViewImpl;
 255     }
 256 
 257     /**
 258      * Add a ResourcePoolEntry.
 259      *
 260      * @param data The ResourcePoolEntry to add.
 261      */
 262     public void add(ResourcePoolEntry data) {
 263         Objects.requireNonNull(data);
 264         if (resources.get(data.path()) != null) {
 265             throw new PluginException("Resource " + data.path()
 266                     + " already present");
 267         }
 268         String modulename = data.moduleName();
 269         ResourcePoolModuleImpl m = (ResourcePoolModuleImpl)modules.get(modulename);
 270         if (m == null) {
 271             m = new ResourcePoolModuleImpl(modulename);
 272             modules.put(modulename, m);
 273         }
 274         resources.put(data.path(), data);
 275         m.moduleContent.put(data.path(), data);
 276     }
 277 
 278     /**
 279      * Retrieves the module for the provided name.
 280      *
 281      * @param name The module name
 282      * @return the module of matching name, if found
 283      */
 284     public Optional<ResourcePoolModule> findModule(String name) {
 285         Objects.requireNonNull(name);
 286         return Optional.ofNullable(modules.get(name));
 287     }
 288 
 289     /**
 290      * The stream of modules contained in this ResourcePool.
 291      *
 292      * @return The stream of modules.
 293      */
 294     public Stream<ResourcePoolModule> modules() {
 295         return modules.values().stream();
 296     }
 297 
 298     /**
 299      * Return the number of ResourcePoolModule count in this ResourcePool.
 300      *
 301      * @return the module count.
 302      */
 303     public int moduleCount() {
 304         return modules.size();
 305     }
 306 
 307     /**
 308      * Get all ResourcePoolEntry contained in this ResourcePool instance.
 309      *
 310      * @return The stream of ResourcePoolModuleEntries.
 311      */
 312     public Stream<ResourcePoolEntry> entries() {
 313         return resources.values().stream();
 314     }
 315 
 316     /**
 317      * Return the number of ResourcePoolEntry count in this ResourcePool.
 318      *
 319      * @return the entry count.
 320      */
 321     public int entryCount() {
 322         return resources.values().size();
 323     }
 324 
 325     /**
 326      * Get the ResourcePoolEntry for the passed path.
 327      *
 328      * @param path A data path
 329      * @return A ResourcePoolEntry instance or null if the data is not found
 330      */
 331     public Optional<ResourcePoolEntry> findEntry(String path) {
 332         Objects.requireNonNull(path);
 333         return Optional.ofNullable(resources.get(path));
 334     }
 335 
 336     /**
 337      * Get the ResourcePoolEntry for the passed path restricted to supplied context.
 338      *
 339      * @param path A data path
 340      * @param context A context of the search
 341      * @return A ResourcePoolEntry instance or null if the data is not found
 342      */
 343     public Optional<ResourcePoolEntry> findEntryInContext(String path, ResourcePoolEntry context) {
 344         Objects.requireNonNull(path);
 345         Objects.requireNonNull(context);
 346         ResourcePoolModule module = modules.get(context.moduleName());
 347         Objects.requireNonNull(module);
 348         Optional<ResourcePoolEntry> entry = module.findEntry(path);
 349         // Navigating other modules via requires and exports is problematic
 350         // since we cannot construct the runtime model of loaders and layers.
 351         return entry;
 352      }
 353 
 354     /**
 355      * Check if the ResourcePool contains the given ResourcePoolEntry.
 356      *
 357      * @param data The module data to check existence for.
 358      * @return The module data or null if not found.
 359      */
 360     public boolean contains(ResourcePoolEntry data) {
 361         Objects.requireNonNull(data);
 362         return findEntry(data.path()).isPresent();
 363     }
 364 
 365     /**
 366      * Check if the ResourcePool contains some content at all.
 367      *
 368      * @return True, no content, false otherwise.
 369      */
 370     public boolean isEmpty() {
 371         return resources.isEmpty();
 372     }
 373 
 374     /**
 375      * The ByteOrder currently in use when generating the jimage file.
 376      *
 377      * @return The ByteOrder.
 378      */
 379     public ByteOrder byteOrder() {
 380         return order;
 381     }
 382 
 383     public StringTable getStringTable() {
 384         return table;
 385     }
 386 
 387     /**
 388      * A resource that has been compressed.
 389      */
 390     public static final class CompressedModuleData extends ByteArrayResourcePoolEntry {
 391 
 392         final long uncompressed_size;
 393 
 394         private CompressedModuleData(String module, String path,
 395                 byte[] content, long uncompressed_size) {
 396             super(module, path, ResourcePoolEntry.Type.CLASS_OR_RESOURCE, content);
 397             this.uncompressed_size = uncompressed_size;
 398         }
 399 
 400         public long getUncompressedSize() {
 401             return uncompressed_size;
 402         }
 403 
 404         @Override
 405         public boolean equals(Object other) {
 406             if (!(other instanceof CompressedModuleData)) {
 407                 return false;
 408             }
 409             CompressedModuleData f = (CompressedModuleData) other;
 410             return f.path().equals(path());
 411         }
 412 
 413         @Override
 414         public int hashCode() {
 415             return super.hashCode();
 416         }
 417     }
 418 
 419     public static CompressedModuleData newCompressedResource(ResourcePoolEntry original,
 420             ByteBuffer compressed,
 421             String plugin, String pluginConfig, StringTable strings,
 422             ByteOrder order) {
 423         Objects.requireNonNull(original);
 424         Objects.requireNonNull(compressed);
 425         Objects.requireNonNull(plugin);
 426 
 427         boolean isTerminal = !(original instanceof CompressedModuleData);
 428         long uncompressed_size = original.contentLength();
 429         if (original instanceof CompressedModuleData) {
 430             CompressedModuleData comp = (CompressedModuleData) original;
 431             uncompressed_size = comp.getUncompressedSize();
 432         }
 433         int nameOffset = strings.addString(plugin);
 434         int configOffset = -1;
 435         if (pluginConfig != null) {
 436             configOffset = strings.addString(plugin);
 437         }
 438         CompressedResourceHeader rh
 439                 = new CompressedResourceHeader(compressed.limit(), original.contentLength(),
 440                         nameOffset, configOffset, isTerminal);
 441         // Merge header with content;
 442         byte[] h = rh.getBytes(order);
 443         ByteBuffer bb = ByteBuffer.allocate(compressed.limit() + h.length);
 444         bb.order(order);
 445         bb.put(h);
 446         bb.put(compressed);
 447         byte[] contentWithHeader = bb.array();
 448 
 449         CompressedModuleData compressedResource
 450                 = new CompressedModuleData(original.moduleName(), original.path(),
 451                         contentWithHeader, uncompressed_size);
 452         return compressedResource;
 453     }
 454 }