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