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 }