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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package tests; 24 25 import java.io.ByteArrayInputStream; 26 import java.io.ByteArrayOutputStream; 27 import java.io.File; 28 import java.io.FileOutputStream; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.io.OutputStream; 32 import java.io.PrintStream; 33 import java.io.PrintWriter; 34 import java.io.StringWriter; 35 import java.nio.file.Files; 36 import java.nio.file.Path; 37 import java.nio.file.Paths; 38 import java.nio.file.StandardCopyOption; 39 import java.util.ArrayList; 40 import java.util.Arrays; 41 import java.util.Collections; 42 import java.util.HashSet; 43 import java.util.List; 44 import java.util.Objects; 45 import java.util.Set; 46 import java.util.jar.JarEntry; 47 import java.util.jar.JarInputStream; 48 import java.util.jar.JarOutputStream; 49 import java.util.stream.Collectors; 50 import java.util.stream.Stream; 51 import java.util.zip.ZipEntry; 52 53 import javax.tools.JavaCompiler; 54 import javax.tools.StandardJavaFileManager; 55 import javax.tools.StandardLocation; 56 import javax.tools.ToolProvider; 57 58 /** 59 * 60 * A generator for jmods, jars and images. 61 */ 62 public class JImageGenerator { 63 64 public static final String LOAD_ALL_CLASSES_TEMPLATE = "package PACKAGE;\n" 65 + "\n" 66 + "import java.net.URI;\n" 67 + "import java.nio.file.FileSystems;\n" 68 + "import java.nio.file.Files;\n" 69 + "import java.nio.file.Path;\n" 70 + "import java.util.function.Function;\n" 71 + "\n" 72 + "public class CLASS {\n" 73 + " private static long total_time;\n" 74 + " private static long num_classes;\n" 75 + " public static void main(String[] args) throws Exception {\n" 76 + " Function<Path, String> formatter = (path) -> {\n" 77 + " String clazz = path.toString().substring(\"modules/\".length()+1, path.toString().lastIndexOf(\".\"));\n" 78 + " clazz = clazz.substring(clazz.indexOf(\"/\") + 1);\n" 79 + " return clazz.replaceAll(\"/\", \"\\\\.\");\n" 80 + " };\n" 81 + " Files.walk(FileSystems.getFileSystem(URI.create(\"jrt:/\")).getPath(\"/modules/\")).\n" 82 + " filter((p) -> {\n" 83 + " return Files.isRegularFile(p) && p.toString().endsWith(\".class\")\n" 84 + " && !p.toString().endsWith(\"module-info.class\");\n" 85 + " }).\n" 86 + " map(formatter).forEach((clazz) -> {\n" 87 + " try {\n" 88 + " long t = System.currentTimeMillis();\n" 89 + " Class.forName(clazz, false, Thread.currentThread().getContextClassLoader());\n" 90 + " total_time+= System.currentTimeMillis()-t;\n" 91 + " num_classes+=1;\n" 92 + " } catch (IllegalAccessError ex) {\n" 93 + " // Security exceptions can occur, this is not what we are testing\n" 94 + " System.err.println(\"Access error, OK \" + clazz);\n" 95 + " } catch (Exception ex) {\n" 96 + " System.err.println(\"ERROR \" + clazz);\n" 97 + " throw new RuntimeException(ex);\n" 98 + " }\n" 99 + " });\n" 100 + " double res = (double) total_time / num_classes;\n" 101 + " // System.out.println(\"Total time \" + total_time + \" num classes \" + num_classes + \" average \" + res);\n" 102 + " }\n" 103 + "}\n"; 104 105 private static final String OUTPUT_OPTION = "--output"; 106 private static final String POST_PROCESS_OPTION = "--post-process-path"; 107 private static final String MAIN_CLASS_OPTION = "--main-class"; 108 private static final String CLASS_PATH_OPTION = "--class-path"; 109 private static final String MODULE_PATH_OPTION = "--module-path"; 110 private static final String ADD_MODULES_OPTION = "--add-modules"; 111 private static final String LIMIT_MODULES_OPTION = "--limit-modules"; 112 private static final String PLUGIN_MODULE_PATH = "--plugin-module-path"; 113 private static final String LAUNCHER = "--launcher"; 114 115 private static final String CMDS_OPTION = "--cmds"; 116 private static final String CONFIG_OPTION = "--config"; 117 private static final String HASH_MODULES_OPTION = "--hash-modules"; 118 private static final String LIBS_OPTION = "--libs"; 119 private static final String MODULE_VERSION_OPTION = "--module-version"; 120 JImageGenerator()121 private JImageGenerator() {} 122 optionsPrettyPrint(String... args)123 private static String optionsPrettyPrint(String... args) { 124 return Stream.of(args).collect(Collectors.joining(" ")); 125 } 126 getJModsDir(File jdkHome)127 public static File getJModsDir(File jdkHome) { 128 File jdkjmods = new File(jdkHome, "jmods"); 129 if (!jdkjmods.exists()) { 130 return null; 131 } 132 return jdkjmods; 133 } 134 addFiles(Path module, InMemoryFile... resources)135 public static Path addFiles(Path module, InMemoryFile... resources) throws IOException { 136 Path tempFile = Files.createTempFile("jlink-test", ""); 137 try (JarInputStream in = new JarInputStream(Files.newInputStream(module)); 138 JarOutputStream out = new JarOutputStream(new FileOutputStream(tempFile.toFile()))) { 139 ZipEntry entry; 140 while ((entry = in.getNextEntry()) != null) { 141 String name = entry.getName(); 142 out.putNextEntry(new ZipEntry(name)); 143 copy(in, out); 144 out.closeEntry(); 145 } 146 for (InMemoryFile r : resources) { 147 addFile(r, out); 148 } 149 } 150 Files.move(tempFile, module, StandardCopyOption.REPLACE_EXISTING); 151 return module; 152 } 153 copy(InputStream in, OutputStream out)154 private static void copy(InputStream in, OutputStream out) throws IOException { 155 int len; 156 byte[] buf = new byte[4096]; 157 while ((len = in.read(buf)) > 0) { 158 out.write(buf, 0, len); 159 } 160 } 161 getJModTask()162 public static JModTask getJModTask() { 163 return new JModTask(); 164 } 165 getJLinkTask()166 public static JLinkTask getJLinkTask() { 167 return new JLinkTask(); 168 } 169 getJImageTask()170 public static JImageTask getJImageTask() { 171 return new JImageTask(); 172 } 173 addFile(InMemoryFile resource, JarOutputStream target)174 private static void addFile(InMemoryFile resource, JarOutputStream target) throws IOException { 175 String fileName = resource.getPath(); 176 fileName = fileName.replace("\\", "/"); 177 String[] ss = fileName.split("/"); 178 Path p = Paths.get(""); 179 for (int i = 0; i < ss.length; ++i) { 180 if (i < ss.length - 1) { 181 if (!ss[i].isEmpty()) { 182 p = p.resolve(ss[i]); 183 JarEntry entry = new JarEntry(p.toString() + "/"); 184 target.putNextEntry(entry); 185 target.closeEntry(); 186 } 187 } else { 188 p = p.resolve(ss[i]); 189 JarEntry entry = new JarEntry(p.toString()); 190 target.putNextEntry(entry); 191 copy(resource.getBytes(), target); 192 target.closeEntry(); 193 } 194 } 195 } 196 createNewFile(Path root, String pathName, String extension)197 public static Path createNewFile(Path root, String pathName, String extension) { 198 Path out = root.resolve(pathName + extension); 199 int i = 1; 200 while (Files.exists(out)) { 201 out = root.resolve(pathName + "-" + (++i) + extension); 202 } 203 return out; 204 } 205 generateSources(Path output, String moduleName, List<InMemorySourceFile> sources)206 public static Path generateSources(Path output, String moduleName, List<InMemorySourceFile> sources) throws IOException { 207 Path moduleDir = output.resolve(moduleName); 208 Files.createDirectory(moduleDir); 209 for (InMemorySourceFile source : sources) { 210 Path fileDir = moduleDir; 211 if (!source.packageName.isEmpty()) { 212 String dir = source.packageName.replace('.', File.separatorChar); 213 fileDir = moduleDir.resolve(dir); 214 Files.createDirectories(fileDir); 215 } 216 Files.write(fileDir.resolve(source.className + ".java"), source.source.getBytes()); 217 } 218 return moduleDir; 219 } 220 generateSourcesFromTemplate(Path output, String moduleName, String... classNames)221 public static Path generateSourcesFromTemplate(Path output, String moduleName, String... classNames) throws IOException { 222 List<InMemorySourceFile> sources = new ArrayList<>(); 223 for (String className : classNames) { 224 String packageName = getPackageName(className); 225 String simpleName = getSimpleName(className); 226 String content = LOAD_ALL_CLASSES_TEMPLATE 227 .replace("CLASS", simpleName); 228 if (packageName.isEmpty()) { 229 content = content.replace("package PACKAGE;", packageName); 230 } else { 231 content = content.replace("PACKAGE", packageName); 232 } 233 sources.add(new InMemorySourceFile(packageName, simpleName, content)); 234 } 235 return generateSources(output, moduleName, sources); 236 } 237 generateModuleInfo(Path moduleDir, List<String> packages, String... dependencies)238 public static void generateModuleInfo(Path moduleDir, List<String> packages, String... dependencies) throws IOException { 239 StringBuilder moduleInfoBuilder = new StringBuilder(); 240 Path file = moduleDir.resolve("module-info.java"); 241 String moduleName = moduleDir.getFileName().toString(); 242 moduleInfoBuilder.append("module ").append(moduleName).append("{\n"); 243 for (String dep : dependencies) { 244 moduleInfoBuilder.append("requires ").append(dep).append(";\n"); 245 } 246 for (String pkg : packages) { 247 if (!pkg.trim().isEmpty()) { 248 moduleInfoBuilder.append("exports ").append(pkg).append(";\n"); 249 } 250 } 251 moduleInfoBuilder.append("}"); 252 Files.write(file, moduleInfoBuilder.toString().getBytes()); 253 } 254 compileSuccess(Path source, Path destination, String... options)255 public static void compileSuccess(Path source, Path destination, String... options) throws IOException { 256 if (!compile(source, destination, options)) { 257 throw new AssertionError("Compilation failed."); 258 } 259 } 260 compile(Path source, Path destination, String... options)261 public static boolean compile(Path source, Path destination, String... options) throws IOException { 262 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 263 try (StandardJavaFileManager jfm = compiler.getStandardFileManager(null, null, null)) { 264 List<Path> sources 265 = Files.find(source, Integer.MAX_VALUE, 266 (file, attrs) -> file.toString().endsWith(".java")) 267 .collect(Collectors.toList()); 268 269 Files.createDirectories(destination); 270 jfm.setLocationFromPaths(StandardLocation.CLASS_OUTPUT, Collections.singleton(destination)); 271 272 List<String> opts = Arrays.asList(options); 273 JavaCompiler.CompilationTask task 274 = compiler.getTask(null, jfm, null, opts, null, 275 jfm.getJavaFileObjectsFromPaths(sources)); 276 List<String> list = new ArrayList<>(opts); 277 list.addAll(sources.stream() 278 .map(Path::toString) 279 .collect(Collectors.toList())); 280 System.err.println("javac options: " + optionsPrettyPrint(list.toArray(new String[list.size()]))); 281 return task.call(); 282 } 283 } 284 createJarFile(Path jarfile, Path dir)285 public static Path createJarFile(Path jarfile, Path dir) throws IOException { 286 return createJarFile(jarfile, dir, Paths.get(".")); 287 } 288 createJarFile(Path jarfile, Path dir, Path file)289 public static Path createJarFile(Path jarfile, Path dir, Path file) throws IOException { 290 // create the target directory 291 Path parent = jarfile.getParent(); 292 if (parent != null) 293 Files.createDirectories(parent); 294 295 List<Path> entries = Files.find(dir.resolve(file), Integer.MAX_VALUE, 296 (p, attrs) -> attrs.isRegularFile()) 297 .map(dir::relativize) 298 .collect(Collectors.toList()); 299 300 try (OutputStream out = Files.newOutputStream(jarfile); 301 JarOutputStream jos = new JarOutputStream(out)) { 302 for (Path entry : entries) { 303 // map the file path to a name in the JAR file 304 Path normalized = entry.normalize(); 305 String name = normalized 306 .subpath(0, normalized.getNameCount()) // drop root 307 .toString() 308 .replace(File.separatorChar, '/'); 309 310 jos.putNextEntry(new JarEntry(name)); 311 Files.copy(dir.resolve(entry), jos); 312 } 313 } 314 return jarfile; 315 } 316 getModuleContent(Path module)317 public static Set<String> getModuleContent(Path module) { 318 Result result = JImageGenerator.getJModTask() 319 .jmod(module) 320 .list(); 321 result.assertSuccess(); 322 return Stream.of(result.getMessage().split("\r?\n")) 323 .collect(Collectors.toSet()); 324 } 325 checkModule(Path module, Set<String> expected)326 public static void checkModule(Path module, Set<String> expected) throws IOException { 327 Set<String> actual = getModuleContent(module); 328 if (!Objects.equals(actual, expected)) { 329 Set<String> unexpected = new HashSet<>(actual); 330 unexpected.removeAll(expected); 331 Set<String> notFound = new HashSet<>(expected); 332 notFound.removeAll(actual); 333 System.err.println("Unexpected files:"); 334 unexpected.forEach(s -> System.err.println("\t" + s)); 335 System.err.println("Not found files:"); 336 notFound.forEach(s -> System.err.println("\t" + s)); 337 throw new AssertionError("Module check failed."); 338 } 339 } 340 341 public static class JModTask { 342 static final java.util.spi.ToolProvider JMOD_TOOL = 343 java.util.spi.ToolProvider.findFirst("jmod").orElseThrow(() -> 344 new RuntimeException("jmod tool not found") 345 ); 346 347 private final List<Path> classpath = new ArrayList<>(); 348 private final List<Path> libs = new ArrayList<>(); 349 private final List<Path> cmds = new ArrayList<>(); 350 private final List<Path> config = new ArrayList<>(); 351 private final List<Path> jars = new ArrayList<>(); 352 private final List<Path> jmods = new ArrayList<>(); 353 private final List<String> options = new ArrayList<>(); 354 private Path output; 355 private String hashModules; 356 private String mainClass; 357 private String moduleVersion; 358 addNativeLibraries(Path cp)359 public JModTask addNativeLibraries(Path cp) { 360 this.libs.add(cp); 361 return this; 362 } 363 hashModules(String hash)364 public JModTask hashModules(String hash) { 365 this.hashModules = hash; 366 return this; 367 } 368 addCmds(Path cp)369 public JModTask addCmds(Path cp) { 370 this.cmds.add(cp); 371 return this; 372 } 373 addClassPath(Path cp)374 public JModTask addClassPath(Path cp) { 375 this.classpath.add(cp); 376 return this; 377 } 378 addConfig(Path cp)379 public JModTask addConfig(Path cp) { 380 this.config.add(cp); 381 return this; 382 } 383 addJars(Path jars)384 public JModTask addJars(Path jars) { 385 this.jars.add(jars); 386 return this; 387 } 388 addJmods(Path jmods)389 public JModTask addJmods(Path jmods) { 390 this.jmods.add(jmods); 391 return this; 392 } 393 jmod(Path output)394 public JModTask jmod(Path output) { 395 this.output = output; 396 return this; 397 } 398 moduleVersion(String moduleVersion)399 public JModTask moduleVersion(String moduleVersion) { 400 this.moduleVersion = moduleVersion; 401 return this; 402 } 403 mainClass(String mainClass)404 public JModTask mainClass(String mainClass) { 405 this.mainClass = mainClass; 406 return this; 407 } 408 option(String o)409 public JModTask option(String o) { 410 this.options.add(o); 411 return this; 412 } 413 modulePath()414 private String modulePath() { 415 // This is expect FIRST jmods THEN jars, if you change this, some tests could fail 416 String jmods = toPath(this.jmods); 417 String jars = toPath(this.jars); 418 return jmods + File.pathSeparator + jars; 419 } 420 toPath(List<Path> paths)421 private String toPath(List<Path> paths) { 422 return paths.stream() 423 .map(Path::toString) 424 .collect(Collectors.joining(File.pathSeparator)); 425 } 426 optionsJMod(String cmd)427 private String[] optionsJMod(String cmd) { 428 List<String> options = new ArrayList<>(); 429 options.add(cmd); 430 if (!cmds.isEmpty()) { 431 options.add(CMDS_OPTION); 432 options.add(toPath(cmds)); 433 } 434 if (!config.isEmpty()) { 435 options.add(CONFIG_OPTION); 436 options.add(toPath(config)); 437 } 438 if (hashModules != null) { 439 options.add(HASH_MODULES_OPTION); 440 options.add(hashModules); 441 } 442 if (mainClass != null) { 443 options.add(MAIN_CLASS_OPTION); 444 options.add(mainClass); 445 } 446 if (!libs.isEmpty()) { 447 options.add(LIBS_OPTION); 448 options.add(toPath(libs)); 449 } 450 if (!classpath.isEmpty()) { 451 options.add(CLASS_PATH_OPTION); 452 options.add(toPath(classpath)); 453 } 454 if (!jars.isEmpty() || !jmods.isEmpty()) { 455 options.add(MODULE_PATH_OPTION); 456 options.add(modulePath()); 457 } 458 if (moduleVersion != null) { 459 options.add(MODULE_VERSION_OPTION); 460 options.add(moduleVersion); 461 } 462 options.addAll(this.options); 463 if (output != null) { 464 options.add(output.toString()); 465 } 466 return options.toArray(new String[options.size()]); 467 } 468 create()469 public Result create() { 470 return cmd("create"); 471 } 472 list()473 public Result list() { 474 return cmd("list"); 475 } 476 call()477 public Result call() { 478 return cmd(""); 479 } 480 cmd(String cmd)481 private Result cmd(String cmd) { 482 String[] args = optionsJMod(cmd); 483 System.err.println("jmod options: " + optionsPrettyPrint(args)); 484 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 485 PrintStream ps = new PrintStream(baos); 486 int exitCode = JMOD_TOOL.run(ps, ps, args); 487 String msg = new String(baos.toByteArray()); 488 return new Result(exitCode, msg, output); 489 } 490 } 491 getPackageName(String canonicalName)492 public static String getPackageName(String canonicalName) { 493 int index = canonicalName.lastIndexOf('.'); 494 return index > 0 ? canonicalName.substring(0, index) : ""; 495 } 496 getSimpleName(String canonicalName)497 public static String getSimpleName(String canonicalName) { 498 int index = canonicalName.lastIndexOf('.'); 499 return canonicalName.substring(index + 1); 500 } 501 502 public static class JImageTask { 503 504 private final List<Path> pluginModulePath = new ArrayList<>(); 505 private final List<String> options = new ArrayList<>(); 506 private Path dir; 507 private Path image; 508 pluginModulePath(Path p)509 public JImageTask pluginModulePath(Path p) { 510 this.pluginModulePath.add(p); 511 return this; 512 } 513 image(Path image)514 public JImageTask image(Path image) { 515 this.image = image; 516 return this; 517 } 518 dir(Path dir)519 public JImageTask dir(Path dir) { 520 this.dir = dir; 521 return this; 522 } 523 option(String o)524 public JImageTask option(String o) { 525 this.options.add(o); 526 return this; 527 } 528 toPath(List<Path> paths)529 private String toPath(List<Path> paths) { 530 return paths.stream() 531 .map(Path::toString) 532 .collect(Collectors.joining(File.pathSeparator)); 533 } 534 optionsJImage(String cmd)535 private String[] optionsJImage(String cmd) { 536 List<String> options = new ArrayList<>(); 537 options.add(cmd); 538 if (dir != null) { 539 options.add("--dir"); 540 options.add(dir.toString()); 541 } 542 if (!pluginModulePath.isEmpty()) { 543 options.add(PLUGIN_MODULE_PATH); 544 options.add(toPath(pluginModulePath)); 545 } 546 options.addAll(this.options); 547 options.add(image.toString()); 548 return options.toArray(new String[options.size()]); 549 } 550 cmd(String cmd, Path returnPath)551 private Result cmd(String cmd, Path returnPath) { 552 String[] args = optionsJImage(cmd); 553 System.err.println("jimage options: " + optionsPrettyPrint(args)); 554 StringWriter writer = new StringWriter(); 555 int exitCode = jdk.tools.jimage.Main.run(args, new PrintWriter(writer)); 556 return new Result(exitCode, writer.toString(), returnPath); 557 } 558 extract()559 public Result extract() { 560 return cmd("extract", dir); 561 } 562 } 563 564 public static class JLinkTask { 565 static final java.util.spi.ToolProvider JLINK_TOOL = 566 java.util.spi.ToolProvider.findFirst("jlink").orElseThrow(() -> 567 new RuntimeException("jlink tool not found") 568 ); 569 570 private final List<Path> jars = new ArrayList<>(); 571 private final List<Path> jmods = new ArrayList<>(); 572 private final List<Path> pluginModulePath = new ArrayList<>(); 573 private final List<String> addMods = new ArrayList<>(); 574 private final List<String> limitMods = new ArrayList<>(); 575 private final List<String> options = new ArrayList<>(); 576 private String modulePath; 577 // if you want to specifiy repeated --module-path option 578 private String repeatedModulePath; 579 // if you want to specifiy repeated --limit-modules option 580 private String repeatedLimitMods; 581 private Path output; 582 private Path existing; 583 private String launcher; // optional 584 modulePath(String modulePath)585 public JLinkTask modulePath(String modulePath) { 586 this.modulePath = modulePath; 587 return this; 588 } 589 launcher(String cmd)590 public JLinkTask launcher(String cmd) { 591 launcher = Objects.requireNonNull(cmd); 592 return this; 593 } 594 repeatedModulePath(String modulePath)595 public JLinkTask repeatedModulePath(String modulePath) { 596 this.repeatedModulePath = modulePath; 597 return this; 598 } 599 addJars(Path jars)600 public JLinkTask addJars(Path jars) { 601 this.jars.add(jars); 602 return this; 603 } 604 addJmods(Path jmods)605 public JLinkTask addJmods(Path jmods) { 606 this.jmods.add(jmods); 607 return this; 608 } 609 pluginModulePath(Path p)610 public JLinkTask pluginModulePath(Path p) { 611 this.pluginModulePath.add(p); 612 return this; 613 } 614 addMods(String moduleName)615 public JLinkTask addMods(String moduleName) { 616 this.addMods.add(moduleName); 617 return this; 618 } 619 limitMods(String moduleName)620 public JLinkTask limitMods(String moduleName) { 621 this.limitMods.add(moduleName); 622 return this; 623 } 624 repeatedLimitMods(String modules)625 public JLinkTask repeatedLimitMods(String modules) { 626 this.repeatedLimitMods = modules; 627 return this; 628 } 629 output(Path output)630 public JLinkTask output(Path output) { 631 this.output = output; 632 return this; 633 } 634 existing(Path existing)635 public JLinkTask existing(Path existing) { 636 this.existing = existing; 637 return this; 638 } 639 option(String o)640 public JLinkTask option(String o) { 641 this.options.add(o); 642 return this; 643 } 644 modulePath()645 private String modulePath() { 646 // This is expect FIRST jmods THEN jars, if you change this, some tests could fail 647 String jmods = toPath(this.jmods); 648 String jars = toPath(this.jars); 649 return jmods + File.pathSeparator + jars; 650 } 651 toPath(List<Path> paths)652 private String toPath(List<Path> paths) { 653 return paths.stream() 654 .map(Path::toString) 655 .collect(Collectors.joining(File.pathSeparator)); 656 } 657 optionsJLink()658 private String[] optionsJLink() { 659 List<String> options = new ArrayList<>(); 660 if (output != null) { 661 options.add(OUTPUT_OPTION); 662 options.add(output.toString()); 663 } 664 if (!addMods.isEmpty()) { 665 options.add(ADD_MODULES_OPTION); 666 options.add(addMods.stream().collect(Collectors.joining(","))); 667 } 668 if (!limitMods.isEmpty()) { 669 options.add(LIMIT_MODULES_OPTION); 670 options.add(limitMods.stream().collect(Collectors.joining(","))); 671 } 672 if (repeatedLimitMods != null) { 673 options.add(LIMIT_MODULES_OPTION); 674 options.add(repeatedLimitMods); 675 } 676 if (!jars.isEmpty() || !jmods.isEmpty()) { 677 options.add(MODULE_PATH_OPTION); 678 options.add(modulePath()); 679 } 680 if (modulePath != null) { 681 options.add(MODULE_PATH_OPTION); 682 options.add(modulePath); 683 } 684 if (repeatedModulePath != null) { 685 options.add(MODULE_PATH_OPTION); 686 options.add(repeatedModulePath); 687 } 688 if (!pluginModulePath.isEmpty()) { 689 options.add(PLUGIN_MODULE_PATH); 690 options.add(toPath(pluginModulePath)); 691 } 692 if (launcher != null && !launcher.isEmpty()) { 693 options.add(LAUNCHER); 694 options.add(launcher); 695 } 696 options.addAll(this.options); 697 return options.toArray(new String[options.size()]); 698 } 699 optionsPostProcessJLink()700 private String[] optionsPostProcessJLink() { 701 List<String> options = new ArrayList<>(); 702 if (existing != null) { 703 options.add(POST_PROCESS_OPTION); 704 options.add(existing.toString()); 705 } 706 options.addAll(this.options); 707 return options.toArray(new String[options.size()]); 708 } 709 call()710 public Result call() { 711 String[] args = optionsJLink(); 712 System.err.println("jlink options: " + optionsPrettyPrint(args)); 713 StringWriter writer = new StringWriter(); 714 PrintWriter pw = new PrintWriter(writer); 715 int exitCode = JLINK_TOOL.run(pw, pw, args); 716 return new Result(exitCode, writer.toString(), output); 717 } 718 callPostProcess()719 public Result callPostProcess() { 720 String[] args = optionsPostProcessJLink(); 721 System.err.println("jlink options: " + optionsPrettyPrint(args)); 722 StringWriter writer = new StringWriter(); 723 PrintWriter pw = new PrintWriter(writer); 724 int exitCode = JLINK_TOOL.run(pw, pw, args); 725 return new Result(exitCode, writer.toString(), output); 726 } 727 } 728 729 public static class InMemorySourceFile { 730 public final String packageName; 731 public final String className; 732 public final String source; 733 InMemorySourceFile(String packageName, String simpleName, String source)734 public InMemorySourceFile(String packageName, String simpleName, String source) { 735 this.packageName = packageName; 736 this.className = simpleName; 737 this.source = source; 738 } 739 } 740 741 public static class InMemoryFile { 742 private final String path; 743 private final byte[] bytes; 744 getPath()745 public String getPath() { 746 return path; 747 } 748 getBytes()749 public InputStream getBytes() { 750 return new ByteArrayInputStream(bytes); 751 } 752 InMemoryFile(String path, byte[] bytes)753 public InMemoryFile(String path, byte[] bytes) { 754 this.path = path; 755 this.bytes = bytes; 756 } 757 InMemoryFile(String path, InputStream is)758 public InMemoryFile(String path, InputStream is) throws IOException { 759 this(path, readAllBytes(is)); 760 } 761 } 762 readAllBytes(InputStream is)763 public static byte[] readAllBytes(InputStream is) throws IOException { 764 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 765 byte[] buf = new byte[1024]; 766 while (true) { 767 int n = is.read(buf); 768 if (n < 0) { 769 break; 770 } 771 baos.write(buf, 0, n); 772 } 773 return baos.toByteArray(); 774 } 775 } 776