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