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