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