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