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