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 ByteOrder order; 217 private final StringTable table; 218 private final ResourcePool poolImpl; 219 private final ResourcePoolBuilder poolBuilderImpl; 220 private final ResourcePoolModuleView moduleViewImpl; 221 222 public ResourcePoolManager() { 223 this(ByteOrder.nativeOrder()); 224 } 225 226 public ResourcePoolManager(ByteOrder order) { 227 this(order, new StringTable() { 228 229 @Override 230 public int addString(String str) { 231 return -1; 232 } 233 234 @Override 235 public String getString(int id) { 236 return null; 237 } 238 }); 239 } 240 241 public ResourcePoolManager(ByteOrder order, StringTable table) { 242 this.order = Objects.requireNonNull(order); 243 this.table = Objects.requireNonNull(table); 244 this.poolImpl = new ResourcePoolImpl(); 245 this.poolBuilderImpl = new ResourcePoolBuilderImpl(); 246 this.moduleViewImpl = new ResourcePoolModuleViewImpl(); 247 } 248 249 public ResourcePool resourcePool() { 250 return poolImpl; 251 } 252 253 public ResourcePoolBuilder resourcePoolBuilder() { 254 return poolBuilderImpl; 255 } 256 257 public ResourcePoolModuleView moduleView() { 258 return moduleViewImpl; 259 } 260 261 /** 262 * Add a ResourcePoolEntry. 263 * 264 * @param data The ResourcePoolEntry to add. 265 */ 266 public void add(ResourcePoolEntry data) { 267 Objects.requireNonNull(data); 268 if (resources.get(data.path()) != null) { 269 throw new PluginException("Resource " + data.path() 270 + " already present"); 271 } 272 String modulename = data.moduleName(); 273 ResourcePoolModuleImpl m = (ResourcePoolModuleImpl)modules.get(modulename); 274 if (m == null) { 275 m = new ResourcePoolModuleImpl(modulename); 276 modules.put(modulename, m); 277 } 278 resources.put(data.path(), data); 279 m.moduleContent.put(data.path(), data); 280 } 281 282 /** 283 * Retrieves the module for the provided name. 284 * 285 * @param name The module name 286 * @return the module of matching name, if found 287 */ 288 public Optional<ResourcePoolModule> findModule(String name) { 289 Objects.requireNonNull(name); 290 return Optional.ofNullable(modules.get(name)); 291 } 292 293 /** 294 * The stream of modules contained in this ResourcePool. 295 * 296 * @return The stream of modules. 297 */ 298 public Stream<ResourcePoolModule> modules() { 299 return modules.values().stream(); 300 } 301 302 /** 303 * Return the number of ResourcePoolModule count in this ResourcePool. 304 * 305 * @return the module count. 306 */ 307 public int moduleCount() { 308 return modules.size(); 309 } 310 311 /** 312 * Get all ResourcePoolEntry contained in this ResourcePool instance. 313 * 314 * @return The stream of ResourcePoolModuleEntries. 315 */ 316 public Stream<ResourcePoolEntry> entries() { 317 return resources.values().stream(); 318 } 319 320 /** 321 * Return the number of ResourcePoolEntry count in this ResourcePool. 322 * 323 * @return the entry count. 324 */ 325 public int entryCount() { 326 return resources.values().size(); 327 } 328 329 /** 330 * Get the ResourcePoolEntry for the passed path. 331 * 332 * @param path A data path 333 * @return A ResourcePoolEntry instance or null if the data is not found 334 */ 335 public Optional<ResourcePoolEntry> findEntry(String path) { 336 Objects.requireNonNull(path); 337 return Optional.ofNullable(resources.get(path)); 338 } 339 340 /** 341 * Get the ResourcePoolEntry for the passed path restricted to supplied context. 342 * 343 * @param path A data path 344 * @param context A context of the search 345 * @return A ResourcePoolEntry instance or null if the data is not found 346 */ 347 public Optional<ResourcePoolEntry> findEntryInContext(String path, ResourcePoolEntry context) { 348 Objects.requireNonNull(path); 349 Objects.requireNonNull(context); 350 ResourcePoolModule module = modules.get(context.moduleName()); 351 Objects.requireNonNull(module); 352 Optional<ResourcePoolEntry> entry = module.findEntry(path); 353 // Navigating other modules via requires and exports is problematic 354 // since we cannot construct the runtime model of loaders and layers. 355 return entry; 356 } 357 358 /** 359 * Check if the ResourcePool contains the given ResourcePoolEntry. 360 * 361 * @param data The module data to check existence for. 362 * @return The module data or null if not found. 363 */ 364 public boolean contains(ResourcePoolEntry data) { 365 Objects.requireNonNull(data); 366 return findEntry(data.path()).isPresent(); 367 } 368 369 /** 370 * Check if the ResourcePool contains some content at all. 371 * 372 * @return True, no content, false otherwise. 373 */ 374 public boolean isEmpty() { 375 return resources.isEmpty(); 376 } 377 378 /** 379 * The ByteOrder currently in use when generating the jimage file. 380 * 381 * @return The ByteOrder. 382 */ 383 public ByteOrder byteOrder() { 384 return order; 385 } 386 387 public StringTable getStringTable() { 388 return table; 389 } 390 391 /** 392 * A resource that has been compressed. 393 */ 394 public static final class CompressedModuleData extends ByteArrayResourcePoolEntry { 395 396 final long uncompressed_size; 397 398 private CompressedModuleData(String module, String path, 399 byte[] content, long uncompressed_size) { 400 super(module, path, ResourcePoolEntry.Type.CLASS_OR_RESOURCE, content); 401 this.uncompressed_size = uncompressed_size; 402 } 403 404 public long getUncompressedSize() { 405 return uncompressed_size; 406 } 407 408 @Override 409 public boolean equals(Object other) { 410 if (!(other instanceof CompressedModuleData)) { 411 return false; 412 } 413 CompressedModuleData f = (CompressedModuleData) other; 414 return f.path().equals(path()); 415 } 416 417 @Override 418 public int hashCode() { 419 return super.hashCode(); 420 } 421 } 422 423 public static CompressedModuleData newCompressedResource(ResourcePoolEntry original, 424 ByteBuffer compressed, 425 String plugin, String pluginConfig, StringTable strings, 426 ByteOrder order) { 427 Objects.requireNonNull(original); 428 Objects.requireNonNull(compressed); 429 Objects.requireNonNull(plugin); 430 431 boolean isTerminal = !(original instanceof CompressedModuleData); 432 long uncompressed_size = original.contentLength(); 433 if (original instanceof CompressedModuleData) { 434 CompressedModuleData comp = (CompressedModuleData) original; 435 uncompressed_size = comp.getUncompressedSize(); 436 } 437 int nameOffset = strings.addString(plugin); 438 int configOffset = -1; 439 if (pluginConfig != null) { 440 configOffset = strings.addString(plugin); 441 } 442 CompressedResourceHeader rh 443 = new CompressedResourceHeader(compressed.limit(), original.contentLength(), 444 nameOffset, configOffset, isTerminal); 445 // Merge header with content; 446 byte[] h = rh.getBytes(order); 447 ByteBuffer bb = ByteBuffer.allocate(compressed.limit() + h.length); 448 bb.order(order); 449 bb.put(h); 450 bb.put(compressed); 451 byte[] contentWithHeader = bb.array(); 452 453 CompressedModuleData compressedResource 454 = new CompressedModuleData(original.moduleName(), original.path(), 455 contentWithHeader, uncompressed_size); 456 return compressedResource; 457 } 458 }