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