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