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 retrieve(ImagePluginStack stack)64 ExecutableImage retrieve(ImagePluginStack stack) throws IOException; 65 } 66 67 public static final class OrderedResourcePoolManager extends ResourcePoolManager { 68 class OrderedResourcePool extends ResourcePoolImpl { getOrderedList()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 OrderedResourcePoolManager(ByteOrder order, StringTable table)77 public OrderedResourcePoolManager(ByteOrder order, StringTable table) { 78 super(order, table); 79 } 80 81 @Override resourcePool()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 add(ResourcePoolEntry resource)92 public void add(ResourcePoolEntry resource) { 93 super.add(resource); 94 orderedList.add(resource); 95 } 96 getOrderedList()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 CheckOrderResourcePoolManager(ByteOrder order, List<ResourcePoolEntry> orderedList, StringTable table)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 add(ResourcePoolEntry resource)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 addString(String str)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 getSortedStrings()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 getString(int id)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 ImagePluginStack()179 public ImagePluginStack() { 180 this(null, Collections.emptyList(), null); 181 } 182 ImagePluginStack(ImageBuilder imageBuilder, List<Plugin> plugins, Plugin lastSorter)183 public ImagePluginStack(ImageBuilder imageBuilder, 184 List<Plugin> plugins, 185 Plugin lastSorter) { 186 this(imageBuilder, plugins, lastSorter, true); 187 } 188 ImagePluginStack(ImageBuilder imageBuilder, List<Plugin> plugins, Plugin lastSorter, boolean validate)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 operate(ImageProvider provider)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 getJImageFileOutputStream()218 public DataOutputStream getJImageFileOutputStream() throws IOException { 219 return imageBuilder.getJImageOutputStream(); 220 } 221 getImageBuilder()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 */ visitResources(ResourcePoolManager resources)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 LastModule(ResourcePoolModule module)305 LastModule(ResourcePoolModule module) { 306 this.module = module; 307 } 308 309 @Override name()310 public String name() { 311 return module.name(); 312 } 313 314 @Override findEntry(String path)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 descriptor()321 public ModuleDescriptor descriptor() { 322 initModuleAttributes(); 323 return descriptor; 324 } 325 326 @Override targetPlatform()327 public String targetPlatform() { 328 initModuleAttributes(); 329 return target != null? target.targetPlatform() : null; 330 } 331 initModuleAttributes()332 private void initModuleAttributes() { 333 if (this.descriptor == null) { 334 Attributes attr = ResourcePoolManager.readModuleAttributes(this); 335 this.descriptor = attr.descriptor(); 336 this.target = attr.target(); 337 } 338 } 339 340 @Override packages()341 public Set<String> packages() { 342 return module.packages(); 343 } 344 345 @Override toString()346 public String toString() { 347 return name(); 348 } 349 350 @Override entries()351 public Stream<ResourcePoolEntry> entries() { 352 List<ResourcePoolEntry> lst = new ArrayList<>(); 353 module.entries().forEach(md -> { 354 lst.add(getUncompressed(md)); 355 }); 356 return lst.stream(); 357 } 358 359 @Override entryCount()360 public int entryCount() { 361 return module.entryCount(); 362 } 363 } 364 365 private final ResourcePool pool; 366 Decompressor decompressor = new Decompressor(); 367 Collection<ResourcePoolEntry> content; 368 LastPoolManager(ResourcePool pool)369 LastPoolManager(ResourcePool pool) { 370 this.pool = pool; 371 } 372 373 @Override add(ResourcePoolEntry resource)374 public void add(ResourcePoolEntry resource) { 375 throw new PluginException("pool is readonly"); 376 } 377 378 @Override findModule(String name)379 public Optional<ResourcePoolModule> findModule(String name) { 380 Optional<ResourcePoolModule> module = pool.moduleView().findModule(name); 381 return module.isPresent()? Optional.of(new LastModule(module.get())) : Optional.empty(); 382 } 383 384 /** 385 * The collection of modules contained in this pool. 386 * 387 * @return The collection of modules. 388 */ 389 @Override modules()390 public Stream<ResourcePoolModule> modules() { 391 List<ResourcePoolModule> modules = new ArrayList<>(); 392 pool.moduleView().modules().forEach(m -> { 393 modules.add(new LastModule(m)); 394 }); 395 return modules.stream(); 396 } 397 398 @Override moduleCount()399 public int moduleCount() { 400 return pool.moduleView().moduleCount(); 401 } 402 403 /** 404 * Get all resources contained in this pool instance. 405 * 406 * @return The stream of resources; 407 */ 408 @Override entries()409 public Stream<ResourcePoolEntry> entries() { 410 if (content == null) { 411 content = new ArrayList<>(); 412 pool.entries().forEach(md -> { 413 content.add(getUncompressed(md)); 414 }); 415 } 416 return content.stream(); 417 } 418 419 @Override entryCount()420 public int entryCount() { 421 return pool.entryCount(); 422 } 423 424 /** 425 * Get the resource for the passed path. 426 * 427 * @param path A resource path 428 * @return A Resource instance if the resource is found 429 */ 430 @Override findEntry(String path)431 public Optional<ResourcePoolEntry> findEntry(String path) { 432 Objects.requireNonNull(path); 433 Optional<ResourcePoolEntry> res = pool.findEntry(path); 434 return res.isPresent()? Optional.of(getUncompressed(res.get())) : Optional.empty(); 435 } 436 437 @Override findEntryInContext(String path, ResourcePoolEntry context)438 public Optional<ResourcePoolEntry> findEntryInContext(String path, ResourcePoolEntry context) { 439 Objects.requireNonNull(path); 440 Objects.requireNonNull(context); 441 Optional<ResourcePoolEntry> res = pool.findEntryInContext(path, context); 442 return res.map(this::getUncompressed); 443 } 444 445 @Override contains(ResourcePoolEntry res)446 public boolean contains(ResourcePoolEntry res) { 447 return pool.contains(res); 448 } 449 450 @Override isEmpty()451 public boolean isEmpty() { 452 return pool.isEmpty(); 453 } 454 455 @Override byteOrder()456 public ByteOrder byteOrder() { 457 return pool.byteOrder(); 458 } 459 getUncompressed(ResourcePoolEntry res)460 private ResourcePoolEntry getUncompressed(ResourcePoolEntry res) { 461 if (res != null) { 462 if (res instanceof ResourcePoolManager.CompressedModuleData) { 463 try { 464 byte[] bytes = decompressor.decompressResource(byteOrder(), 465 (int offset) -> ((ResourcePoolImpl)pool).getStringTable().getString(offset), 466 res.contentBytes()); 467 res = res.copyWithContent(bytes); 468 } catch (IOException ex) { 469 if (JlinkTask.DEBUG) { 470 System.err.println("IOException while reading resource: " + res.path()); 471 ex.printStackTrace(); 472 } 473 throw new PluginException(ex); 474 } 475 } 476 } 477 return res; 478 } 479 } 480 481 /** 482 * Make the imageBuilder to store files. 483 * 484 * @param original 485 * @param transformed 486 * @param writer 487 * @throws java.lang.Exception 488 */ storeFiles(ResourcePool original, ResourcePool transformed, BasicImageWriter writer)489 public void storeFiles(ResourcePool original, ResourcePool transformed, 490 BasicImageWriter writer) 491 throws Exception { 492 Objects.requireNonNull(original); 493 Objects.requireNonNull(transformed); 494 ResourcePool lastPool = new LastPoolManager(transformed).resourcePool(); 495 if (validate) { 496 ResourcePoolConfiguration.validate(lastPool); 497 } 498 imageBuilder.storeFiles(lastPool); 499 } 500 getExecutableImage()501 public ExecutableImage getExecutableImage() throws IOException { 502 return imageBuilder.getExecutableImage(); 503 } 504 } 505