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