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.io.DataOutputStream; 28 import java.io.IOException; 29 import java.lang.module.ModuleDescriptor; 30 import java.nio.ByteOrder; 31 import java.util.ArrayList; 32 import java.util.Collection; 33 import java.util.Collections; 34 import java.util.Comparator; 35 import java.util.HashMap; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Objects; 39 import java.util.Optional; 40 import java.util.Set; 41 import java.util.function.Function; 42 import java.util.stream.Collectors; 43 import java.util.stream.Stream; 44 45 import jdk.internal.jimage.decompressor.Decompressor; 46 import jdk.internal.module.ModuleInfo.Attributes; 47 import jdk.internal.module.ModuleTarget; 48 import jdk.tools.jlink.plugin.Plugin; 49 import jdk.tools.jlink.builder.ImageBuilder; 50 import jdk.tools.jlink.plugin.PluginException; 51 import jdk.tools.jlink.plugin.ResourcePool; 52 import jdk.tools.jlink.plugin.ResourcePoolModule; 53 import jdk.tools.jlink.plugin.ResourcePoolEntry; 54 import jdk.tools.jlink.internal.ResourcePoolManager.ResourcePoolImpl; 55 56 /** 57 * Plugins Stack. Plugins entry point to apply transformations onto resources 58 * and files. 59 */ 60 public final class ImagePluginStack { 61 62 public interface ImageProvider { 63 64 ExecutableImage retrieve(ImagePluginStack stack) throws IOException; 65 } 66 67 public static final class OrderedResourcePoolManager extends ResourcePoolManager { 68 class OrderedResourcePool extends ResourcePoolImpl { 69 List<ResourcePoolEntry> getOrderedList() { 70 return OrderedResourcePoolManager.this.getOrderedList(); 71 } 72 } 73 74 private final List<ResourcePoolEntry> orderedList = new ArrayList<>(); 75 private final ResourcePoolImpl poolImpl = new OrderedResourcePool(); 76 77 public OrderedResourcePoolManager(ByteOrder order, StringTable table) { 78 super(order, table); 79 } 80 81 @Override 82 public ResourcePool resourcePool() { 83 return poolImpl; 84 } 85 86 /** 87 * Add a resource. 88 * 89 * @param resource The Resource to add. 90 */ 91 @Override 92 public void add(ResourcePoolEntry resource) { 93 super.add(resource); 94 orderedList.add(resource); 95 } 96 97 List<ResourcePoolEntry> getOrderedList() { 98 return Collections.unmodifiableList(orderedList); 99 } 100 } 101 102 private final static class CheckOrderResourcePoolManager extends ResourcePoolManager { 103 104 private final List<ResourcePoolEntry> orderedList; 105 private int currentIndex; 106 107 public CheckOrderResourcePoolManager(ByteOrder order, List<ResourcePoolEntry> orderedList, StringTable table) { 108 super(order, table); 109 this.orderedList = Objects.requireNonNull(orderedList); 110 } 111 112 /** 113 * Add a resource. 114 * 115 * @param resource The Resource to add. 116 */ 117 @Override 118 public void add(ResourcePoolEntry resource) { 119 ResourcePoolEntry ordered = orderedList.get(currentIndex); 120 if (!resource.equals(ordered)) { 121 throw new PluginException("Resource " + resource.path() + " not in the right order"); 122 } 123 super.add(resource); 124 currentIndex += 1; 125 } 126 } 127 128 private static final class PreVisitStrings implements StringTable { 129 130 private int currentid = 0; 131 private final Map<String, Integer> stringsUsage = new HashMap<>(); 132 private final Map<String, Integer> stringsMap = new HashMap<>(); 133 private final Map<Integer, String> reverseMap = new HashMap<>(); 134 135 @Override 136 public int addString(String str) { 137 Objects.requireNonNull(str); 138 Integer count = stringsUsage.get(str); 139 if (count == null) { 140 count = 0; 141 } 142 count += 1; 143 stringsUsage.put(str, count); 144 Integer id = stringsMap.get(str); 145 if (id == null) { 146 id = currentid; 147 stringsMap.put(str, id); 148 currentid += 1; 149 reverseMap.put(id, str); 150 } 151 152 return id; 153 } 154 155 private List<String> getSortedStrings() { 156 Stream<java.util.Map.Entry<String, Integer>> stream 157 = stringsUsage.entrySet().stream(); 158 // Remove strings that have a single occurence 159 List<String> result = stream.sorted(Comparator.comparing(e -> e.getValue(), 160 Comparator.reverseOrder())).filter((e) -> { 161 return e.getValue() > 1; 162 }).map(java.util.Map.Entry::getKey). 163 collect(Collectors.toList()); 164 return result; 165 } 166 167 @Override 168 public String getString(int id) { 169 return reverseMap.get(id); 170 } 171 } 172 173 private final ImageBuilder imageBuilder; 174 private final Plugin lastSorter; 175 private final List<Plugin> plugins = new ArrayList<>(); 176 private final List<ResourcePrevisitor> resourcePrevisitors = new ArrayList<>(); 177 private final boolean validate; 178 179 public ImagePluginStack() { 180 this(null, Collections.emptyList(), null); 181 } 182 183 public ImagePluginStack(ImageBuilder imageBuilder, 184 List<Plugin> plugins, 185 Plugin lastSorter) { 186 this(imageBuilder, plugins, lastSorter, true); 187 } 188 189 public ImagePluginStack(ImageBuilder imageBuilder, 190 List<Plugin> plugins, 191 Plugin lastSorter, 192 boolean validate) { 193 this.imageBuilder = Objects.requireNonNull(imageBuilder); 194 this.lastSorter = lastSorter; 195 this.plugins.addAll(Objects.requireNonNull(plugins)); 196 plugins.stream().forEach((p) -> { 197 Objects.requireNonNull(p); 198 if (p instanceof ResourcePrevisitor) { 199 resourcePrevisitors.add((ResourcePrevisitor) p); 200 } 201 }); 202 this.validate = validate; 203 } 204 205 public void operate(ImageProvider provider) throws Exception { 206 ExecutableImage img = provider.retrieve(this); 207 List<String> arguments = new ArrayList<>(); 208 plugins.stream() 209 .filter(PostProcessor.class::isInstance) 210 .map((plugin) -> ((PostProcessor)plugin).process(img)) 211 .filter((lst) -> (lst != null)) 212 .forEach((lst) -> { 213 arguments.addAll(lst); 214 }); 215 img.storeLaunchArgs(arguments); 216 } 217 218 public DataOutputStream getJImageFileOutputStream() throws IOException { 219 return imageBuilder.getJImageOutputStream(); 220 } 221 222 public ImageBuilder getImageBuilder() { 223 return imageBuilder; 224 } 225 226 /** 227 * Resource Plugins stack entry point. All resources are going through all 228 * the plugins. 229 * 230 * @param resources The set of resources to visit 231 * @return The result of the visit. 232 * @throws IOException 233 */ 234 public ResourcePool visitResources(ResourcePoolManager resources) 235 throws Exception { 236 Objects.requireNonNull(resources); 237 if (resources.isEmpty()) { 238 return new ResourcePoolManager(resources.byteOrder(), 239 resources.getStringTable()).resourcePool(); 240 } 241 PreVisitStrings previsit = new PreVisitStrings(); 242 resourcePrevisitors.stream().forEach((p) -> { 243 p.previsit(resources.resourcePool(), previsit); 244 }); 245 246 // Store the strings resulting from the previsit. 247 List<String> sorted = previsit.getSortedStrings(); 248 sorted.stream().forEach((s) -> { 249 resources.getStringTable().addString(s); 250 }); 251 252 ResourcePool resPool = resources.resourcePool(); 253 List<ResourcePoolEntry> frozenOrder = null; 254 for (Plugin p : plugins) { 255 ResourcePoolManager resMgr = null; 256 if (p == lastSorter) { 257 if (frozenOrder != null) { 258 throw new Exception("Order of resources is already frozen. Plugin " 259 + p.getName() + " is badly located"); 260 } 261 // Create a special Resource pool to compute the indexes. 262 resMgr = new OrderedResourcePoolManager(resPool.byteOrder(), 263 resources.getStringTable()); 264 } else {// If we have an order, inject it 265 if (frozenOrder != null) { 266 resMgr = new CheckOrderResourcePoolManager(resPool.byteOrder(), 267 frozenOrder, resources.getStringTable()); 268 } else { 269 resMgr = new ResourcePoolManager(resPool.byteOrder(), 270 resources.getStringTable()); 271 } 272 } 273 try { 274 resPool = p.transform(resPool, resMgr.resourcePoolBuilder()); 275 } catch (PluginException pe) { 276 if (JlinkTask.DEBUG) { 277 System.err.println("Plugin " + p.getName() + " threw exception during transform"); 278 pe.printStackTrace(); 279 } 280 throw pe; 281 } 282 if (resPool.isEmpty()) { 283 throw new Exception("Invalid resource pool for plugin " + p); 284 } 285 if (resPool instanceof OrderedResourcePoolManager.OrderedResourcePool) { 286 frozenOrder = ((OrderedResourcePoolManager.OrderedResourcePool)resPool).getOrderedList(); 287 } 288 } 289 290 return resPool; 291 } 292 293 /** 294 * This pool wrap the original pool and automatically uncompress ResourcePoolEntry 295 * if needed. 296 */ 297 private class LastPoolManager extends ResourcePoolManager { 298 private class LastModule implements ResourcePoolModule { 299 300 final ResourcePoolModule module; 301 // lazily initialized 302 ModuleDescriptor descriptor; 303 ModuleTarget target; 304 305 LastModule(ResourcePoolModule module) { 306 this.module = module; 307 } 308 309 @Override 310 public String name() { 311 return module.name(); 312 } 313 314 @Override 315 public Optional<ResourcePoolEntry> findEntry(String path) { 316 Optional<ResourcePoolEntry> d = module.findEntry(path); 317 return d.isPresent()? Optional.of(getUncompressed(d.get())) : Optional.empty(); 318 } 319 320 @Override 321 public ModuleDescriptor descriptor() { 322 initModuleAttributes(); 323 return descriptor; 324 } 325 326 @Override 327 public String osName() { 328 initModuleAttributes(); 329 return target != null? target.osName() : null; 330 } 331 332 @Override 333 public String osArch() { 334 initModuleAttributes(); 335 return target != null? target.osArch() : null; 336 } 337 338 private void initModuleAttributes() { 339 if (this.descriptor == null) { 340 Attributes attr = ResourcePoolManager.readModuleAttributes(this); 341 this.descriptor = attr.descriptor(); 342 this.target = attr.target(); 343 } 344 } 345 346 @Override 347 public Set<String> packages() { 348 return module.packages(); 349 } 350 351 @Override 352 public String toString() { 353 return name(); 354 } 355 356 @Override 357 public Stream<ResourcePoolEntry> entries() { 358 List<ResourcePoolEntry> lst = new ArrayList<>(); 359 module.entries().forEach(md -> { 360 lst.add(getUncompressed(md)); 361 }); 362 return lst.stream(); 363 } 364 365 @Override 366 public int entryCount() { 367 return module.entryCount(); 368 } 369 } 370 371 private final ResourcePool pool; 372 Decompressor decompressor = new Decompressor(); 373 Collection<ResourcePoolEntry> content; 374 375 LastPoolManager(ResourcePool pool) { 376 this.pool = pool; 377 } 378 379 @Override 380 public void add(ResourcePoolEntry resource) { 381 throw new PluginException("pool is readonly"); 382 } 383 384 @Override 385 public Optional<ResourcePoolModule> findModule(String name) { 386 Optional<ResourcePoolModule> module = pool.moduleView().findModule(name); 387 return module.isPresent()? Optional.of(new LastModule(module.get())) : Optional.empty(); 388 } 389 390 /** 391 * The collection of modules contained in this pool. 392 * 393 * @return The collection of modules. 394 */ 395 @Override 396 public Stream<ResourcePoolModule> modules() { 397 List<ResourcePoolModule> modules = new ArrayList<>(); 398 pool.moduleView().modules().forEach(m -> { 399 modules.add(new LastModule(m)); 400 }); 401 return modules.stream(); 402 } 403 404 @Override 405 public int moduleCount() { 406 return pool.moduleView().moduleCount(); 407 } 408 409 /** 410 * Get all resources contained in this pool instance. 411 * 412 * @return The stream of resources; 413 */ 414 @Override 415 public Stream<ResourcePoolEntry> entries() { 416 if (content == null) { 417 content = new ArrayList<>(); 418 pool.entries().forEach(md -> { 419 content.add(getUncompressed(md)); 420 }); 421 } 422 return content.stream(); 423 } 424 425 @Override 426 public int entryCount() { 427 return pool.entryCount(); 428 } 429 430 /** 431 * Get the resource for the passed path. 432 * 433 * @param path A resource path 434 * @return A Resource instance if the resource is found 435 */ 436 @Override 437 public Optional<ResourcePoolEntry> findEntry(String path) { 438 Objects.requireNonNull(path); 439 Optional<ResourcePoolEntry> res = pool.findEntry(path); 440 return res.isPresent()? Optional.of(getUncompressed(res.get())) : Optional.empty(); 441 } 442 443 @Override 444 public Optional<ResourcePoolEntry> findEntryInContext(String path, ResourcePoolEntry context) { 445 Objects.requireNonNull(path); 446 Objects.requireNonNull(context); 447 Optional<ResourcePoolEntry> res = pool.findEntryInContext(path, context); 448 return res.map(this::getUncompressed); 449 } 450 451 @Override 452 public boolean contains(ResourcePoolEntry res) { 453 return pool.contains(res); 454 } 455 456 @Override 457 public boolean isEmpty() { 458 return pool.isEmpty(); 459 } 460 461 @Override 462 public ByteOrder byteOrder() { 463 return pool.byteOrder(); 464 } 465 466 private ResourcePoolEntry getUncompressed(ResourcePoolEntry res) { 467 if (res != null) { 468 if (res instanceof ResourcePoolManager.CompressedModuleData) { 469 try { 470 byte[] bytes = decompressor.decompressResource(byteOrder(), 471 (int offset) -> ((ResourcePoolImpl)pool).getStringTable().getString(offset), 472 res.contentBytes()); 473 res = res.copyWithContent(bytes); 474 } catch (IOException ex) { 475 if (JlinkTask.DEBUG) { 476 System.err.println("IOException while reading resource: " + res.path()); 477 ex.printStackTrace(); 478 } 479 throw new PluginException(ex); 480 } 481 } 482 } 483 return res; 484 } 485 } 486 487 /** 488 * Make the imageBuilder to store files. 489 * 490 * @param original 491 * @param transformed 492 * @param writer 493 * @throws java.lang.Exception 494 */ 495 public void storeFiles(ResourcePool original, ResourcePool transformed, 496 BasicImageWriter writer) 497 throws Exception { 498 Objects.requireNonNull(original); 499 Objects.requireNonNull(transformed); 500 ResourcePool lastPool = new LastPoolManager(transformed).resourcePool(); 501 if (validate) { 502 ResourcePoolConfiguration.validate(lastPool); 503 } 504 imageBuilder.storeFiles(lastPool); 505 } 506 507 public ExecutableImage getExecutableImage() throws IOException { 508 return imageBuilder.getExecutableImage(); 509 } 510 }