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