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