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 
 114     private static final String CMDS_OPTION = "--cmds";
 115     private static final String CONFIG_OPTION = "--config";
 116     private static final String HASH_MODULES_OPTION = "--hash-modules";
 117     private static final String LIBS_OPTION = "--libs";
 118     private static final String MODULE_VERSION_OPTION = "--module-version";
 119 
 120     private JImageGenerator() {}
 121 
 122     private static String optionsPrettyPrint(String... args) {
 123         return Stream.of(args).collect(Collectors.joining(" "));
 124     }
 125 
 126     public static File getJModsDir(File jdkHome) {
 127         File jdkjmods = new File(jdkHome, "jmods");
 128         if (!jdkjmods.exists()) {
 129             return null;
 130         }
 131         return jdkjmods;
 132     }
 133 
 134     public static Path addFiles(Path module, InMemoryFile... resources) throws IOException {
 135         Path tempFile = Files.createTempFile("jlink-test", "");
 136         try (JarInputStream in = new JarInputStream(Files.newInputStream(module));
 137              JarOutputStream out = new JarOutputStream(new FileOutputStream(tempFile.toFile()))) {
 138             ZipEntry entry;
 139             while ((entry = in.getNextEntry()) != null) {
 140                 String name = entry.getName();
 141                 out.putNextEntry(new ZipEntry(name));
 142                 copy(in, out);
 143                 out.closeEntry();
 144             }
 145             for (InMemoryFile r : resources) {
 146                 addFile(r, out);
 147             }
 148         }
 149         Files.move(tempFile, module, StandardCopyOption.REPLACE_EXISTING);
 150         return module;
 151     }
 152 
 153     private static void copy(InputStream in, OutputStream out) throws IOException {
 154         int len;
 155         byte[] buf = new byte[4096];
 156         while ((len = in.read(buf)) > 0) {
 157             out.write(buf, 0, len);
 158         }
 159     }
 160 
 161     public static JModTask getJModTask() {
 162         return new JModTask();
 163     }
 164 
 165     public static JLinkTask getJLinkTask() {
 166         return new JLinkTask();
 167     }
 168 
 169     public static JImageTask getJImageTask() {
 170         return new JImageTask();
 171     }
 172 
 173     private static void addFile(InMemoryFile resource, JarOutputStream target) throws IOException {
 174         String fileName = resource.getPath();
 175         fileName = fileName.replace("\\", "/");
 176         String[] ss = fileName.split("/");
 177         Path p = Paths.get("");
 178         for (int i = 0; i < ss.length; ++i) {
 179             if (i < ss.length - 1) {
 180                 if (!ss[i].isEmpty()) {
 181                     p = p.resolve(ss[i]);
 182                     JarEntry entry = new JarEntry(p.toString() + "/");
 183                     target.putNextEntry(entry);
 184                     target.closeEntry();
 185                 }
 186             } else {
 187                 p = p.resolve(ss[i]);
 188                 JarEntry entry = new JarEntry(p.toString());
 189                 target.putNextEntry(entry);
 190                 copy(resource.getBytes(), target);
 191                 target.closeEntry();
 192             }
 193         }
 194     }
 195 
 196     public static Path createNewFile(Path root, String pathName, String extension) {
 197         Path out = root.resolve(pathName + extension);
 198         int i = 1;
 199         while (Files.exists(out)) {
 200             out = root.resolve(pathName + "-" + (++i) + extension);
 201         }
 202         return out;
 203     }
 204 
 205     public static Path generateSources(Path output, String moduleName, List<InMemorySourceFile> sources) throws IOException {
 206         Path moduleDir = output.resolve(moduleName);
 207         Files.createDirectory(moduleDir);
 208         for (InMemorySourceFile source : sources) {
 209             Path fileDir = moduleDir;
 210             if (!source.packageName.isEmpty()) {
 211                 String dir = source.packageName.replace('.', File.separatorChar);
 212                 fileDir = moduleDir.resolve(dir);
 213                 Files.createDirectories(fileDir);
 214             }
 215             Files.write(fileDir.resolve(source.className + ".java"), source.source.getBytes());
 216         }
 217         return moduleDir;
 218     }
 219 
 220     public static Path generateSourcesFromTemplate(Path output, String moduleName, String... classNames) throws IOException {
 221         List<InMemorySourceFile> sources = new ArrayList<>();
 222         for (String className : classNames) {
 223             String packageName = getPackageName(className);
 224             String simpleName = getSimpleName(className);
 225             String content = LOAD_ALL_CLASSES_TEMPLATE
 226                     .replace("CLASS", simpleName);
 227             if (packageName.isEmpty()) {
 228                 content = content.replace("package PACKAGE;", packageName);
 229             } else {
 230                 content = content.replace("PACKAGE", packageName);
 231             }
 232             sources.add(new InMemorySourceFile(packageName, simpleName, content));
 233         }
 234         return generateSources(output, moduleName, sources);
 235     }
 236 
 237     public static void generateModuleInfo(Path moduleDir, List<String> packages, String... dependencies) throws IOException {
 238         StringBuilder moduleInfoBuilder = new StringBuilder();
 239         Path file = moduleDir.resolve("module-info.java");
 240         String moduleName = moduleDir.getFileName().toString();
 241         moduleInfoBuilder.append("module ").append(moduleName).append("{\n");
 242         for (String dep : dependencies) {
 243             moduleInfoBuilder.append("requires ").append(dep).append(";\n");
 244         }
 245         for (String pkg : packages) {
 246             if (!pkg.trim().isEmpty()) {
 247                 moduleInfoBuilder.append("exports ").append(pkg).append(";\n");
 248             }
 249         }
 250         moduleInfoBuilder.append("}");
 251         Files.write(file, moduleInfoBuilder.toString().getBytes());
 252     }
 253 
 254     public static void compileSuccess(Path source, Path destination, String... options) throws IOException {
 255         if (!compile(source, destination, options)) {
 256             throw new AssertionError("Compilation failed.");
 257         }
 258     }
 259 
 260     public static boolean compile(Path source, Path destination, String... options) throws IOException {
 261         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 262         try (StandardJavaFileManager jfm = compiler.getStandardFileManager(null, null, null)) {
 263             List<Path> sources
 264                     = Files.find(source, Integer.MAX_VALUE,
 265                     (file, attrs) -> file.toString().endsWith(".java"))
 266                     .collect(Collectors.toList());
 267 
 268             Files.createDirectories(destination);
 269             jfm.setLocationFromPaths(StandardLocation.CLASS_OUTPUT, Collections.singleton(destination));
 270 
 271             List<String> opts = Arrays.asList(options);
 272             JavaCompiler.CompilationTask task
 273                     = compiler.getTask(null, jfm, null, opts, null,
 274                     jfm.getJavaFileObjectsFromPaths(sources));
 275             List<String> list = new ArrayList<>(opts);
 276             list.addAll(sources.stream()
 277                     .map(Path::toString)
 278                     .collect(Collectors.toList()));
 279             System.err.println("javac options: " + optionsPrettyPrint(list.toArray(new String[list.size()])));
 280             return task.call();
 281         }
 282     }
 283 
 284     public static Path createJarFile(Path jarfile, Path dir) throws IOException {
 285         return createJarFile(jarfile, dir, Paths.get("."));
 286     }
 287 
 288     public static Path createJarFile(Path jarfile, Path dir, Path file) throws IOException {
 289         // create the target directory
 290         Path parent = jarfile.getParent();
 291         if (parent != null)
 292             Files.createDirectories(parent);
 293 
 294         List<Path> entries = Files.find(dir.resolve(file), Integer.MAX_VALUE,
 295                 (p, attrs) -> attrs.isRegularFile())
 296                 .map(dir::relativize)
 297                 .collect(Collectors.toList());
 298 
 299         try (OutputStream out = Files.newOutputStream(jarfile);
 300              JarOutputStream jos = new JarOutputStream(out)) {
 301             for (Path entry : entries) {
 302                 // map the file path to a name in the JAR file
 303                 Path normalized = entry.normalize();
 304                 String name = normalized
 305                         .subpath(0, normalized.getNameCount())  // drop root
 306                         .toString()
 307                         .replace(File.separatorChar, '/');
 308 
 309                 jos.putNextEntry(new JarEntry(name));
 310                 Files.copy(dir.resolve(entry), jos);
 311             }
 312         }
 313         return jarfile;
 314     }
 315 
 316     public static Set<String> getModuleContent(Path module) {
 317         Result result = JImageGenerator.getJModTask()
 318                 .jmod(module)
 319                 .list();
 320         result.assertSuccess();
 321         return Stream.of(result.getMessage().split("\r?\n"))
 322                 .collect(Collectors.toSet());
 323     }
 324 
 325     public static void checkModule(Path module, Set<String> expected) throws IOException {
 326         Set<String> actual = getModuleContent(module);
 327         if (!Objects.equals(actual, expected)) {
 328             Set<String> unexpected = new HashSet<>(actual);
 329             unexpected.removeAll(expected);
 330             Set<String> notFound = new HashSet<>(expected);
 331             notFound.removeAll(actual);
 332             System.err.println("Unexpected files:");
 333             unexpected.forEach(s -> System.err.println("\t" + s));
 334             System.err.println("Not found files:");
 335             notFound.forEach(s -> System.err.println("\t" + s));
 336             throw new AssertionError("Module check failed.");
 337         }
 338     }
 339 
 340     public static class JModTask {
 341 
 342         private final List<Path> classpath = new ArrayList<>();
 343         private final List<Path> libs = new ArrayList<>();
 344         private final List<Path> cmds = new ArrayList<>();
 345         private final List<Path> config = new ArrayList<>();
 346         private final List<Path> jars = new ArrayList<>();
 347         private final List<Path> jmods = new ArrayList<>();
 348         private final List<String> options = new ArrayList<>();
 349         private Path output;
 350         private String hashModules;
 351         private String mainClass;
 352         private String moduleVersion;
 353 
 354         public JModTask addNativeLibraries(Path cp) {
 355             this.libs.add(cp);
 356             return this;
 357         }
 358 
 359         public JModTask hashModules(String hash) {
 360             this.hashModules = hash;
 361             return this;
 362         }
 363 
 364         public JModTask addCmds(Path cp) {
 365             this.cmds.add(cp);
 366             return this;
 367         }
 368 
 369         public JModTask addClassPath(Path cp) {
 370             this.classpath.add(cp);
 371             return this;
 372         }
 373 
 374         public JModTask addConfig(Path cp) {
 375             this.config.add(cp);
 376             return this;
 377         }
 378 
 379         public JModTask addJars(Path jars) {
 380             this.jars.add(jars);
 381             return this;
 382         }
 383 
 384         public JModTask addJmods(Path jmods) {
 385             this.jmods.add(jmods);
 386             return this;
 387         }
 388 
 389         public JModTask jmod(Path output) {
 390             this.output = output;
 391             return this;
 392         }
 393 
 394         public JModTask moduleVersion(String moduleVersion) {
 395             this.moduleVersion = moduleVersion;
 396             return this;
 397         }
 398 
 399         public JModTask mainClass(String mainClass) {
 400             this.mainClass = mainClass;
 401             return this;
 402         }
 403 
 404         public JModTask option(String o) {
 405             this.options.add(o);
 406             return this;
 407         }
 408 
 409         private String modulePath() {
 410             // This is expect FIRST jmods THEN jars, if you change this, some tests could fail
 411             String jmods = toPath(this.jmods);
 412             String jars = toPath(this.jars);
 413             return jmods + File.pathSeparator + jars;
 414         }
 415 
 416         private String toPath(List<Path> paths) {
 417             return paths.stream()
 418                     .map(Path::toString)
 419                     .collect(Collectors.joining(File.pathSeparator));
 420         }
 421 
 422         private String[] optionsJMod(String cmd) {
 423             List<String> options = new ArrayList<>();
 424             options.add(cmd);
 425             if (!cmds.isEmpty()) {
 426                 options.add(CMDS_OPTION);
 427                 options.add(toPath(cmds));
 428             }
 429             if (!config.isEmpty()) {
 430                 options.add(CONFIG_OPTION);
 431                 options.add(toPath(config));
 432             }
 433             if (hashModules != null) {
 434                 options.add(HASH_MODULES_OPTION);
 435                 options.add(hashModules);
 436             }
 437             if (mainClass != null) {
 438                 options.add(MAIN_CLASS_OPTION);
 439                 options.add(mainClass);
 440             }
 441             if (!libs.isEmpty()) {
 442                 options.add(LIBS_OPTION);
 443                 options.add(toPath(libs));
 444             }
 445             if (!classpath.isEmpty()) {
 446                 options.add(CLASS_PATH_OPTION);
 447                 options.add(toPath(classpath));
 448             }
 449             if (!jars.isEmpty() || !jmods.isEmpty()) {
 450                 options.add(MODULE_PATH_OPTION);
 451                 options.add(modulePath());
 452             }
 453             if (moduleVersion != null) {
 454                 options.add(MODULE_VERSION_OPTION);
 455                 options.add(moduleVersion);
 456             }
 457             options.addAll(this.options);
 458             if (output != null) {
 459                 options.add(output.toString());
 460             }
 461             return options.toArray(new String[options.size()]);
 462         }
 463 
 464         public Result create() {
 465             return cmd("create");
 466         }
 467 
 468         public Result list() {
 469             return cmd("list");
 470         }
 471 
 472         public Result call() {
 473             return cmd("");
 474         }
 475 
 476         private Result cmd(String cmd) {
 477             String[] args = optionsJMod(cmd);
 478             System.err.println("jmod options: " + optionsPrettyPrint(args));
 479             ByteArrayOutputStream baos = new ByteArrayOutputStream();
 480             int exitCode = jdk.tools.jmod.Main.run(args, new PrintStream(baos));
 481             String msg = new String(baos.toByteArray());
 482             return new Result(exitCode, msg, output);
 483         }
 484     }
 485 
 486     public static String getPackageName(String canonicalName) {
 487         int index = canonicalName.lastIndexOf('.');
 488         return index > 0 ? canonicalName.substring(0, index) : "";
 489     }
 490 
 491     public static String getSimpleName(String canonicalName) {
 492         int index = canonicalName.lastIndexOf('.');
 493         return canonicalName.substring(index + 1);
 494     }
 495 
 496     public static class JImageTask {
 497 
 498         private final List<Path> pluginModulePath = new ArrayList<>();
 499         private final List<String> options = new ArrayList<>();
 500         private Path dir;
 501         private Path image;
 502 
 503         public JImageTask pluginModulePath(Path p) {
 504             this.pluginModulePath.add(p);
 505             return this;
 506         }
 507 
 508         public JImageTask image(Path image) {
 509             this.image = image;
 510             return this;
 511         }
 512 
 513         public JImageTask dir(Path dir) {
 514             this.dir = dir;
 515             return this;
 516         }
 517 
 518         public JImageTask option(String o) {
 519             this.options.add(o);
 520             return this;
 521         }
 522 
 523         private String toPath(List<Path> paths) {
 524             return paths.stream()
 525                     .map(Path::toString)
 526                     .collect(Collectors.joining(File.pathSeparator));
 527         }
 528 
 529         private String[] optionsJImage(String cmd) {
 530             List<String> options = new ArrayList<>();
 531             options.add(cmd);
 532             if (dir != null) {
 533                 options.add("--dir");
 534                 options.add(dir.toString());
 535             }
 536             if (!pluginModulePath.isEmpty()) {
 537                 options.add(PLUGIN_MODULE_PATH);
 538                 options.add(toPath(pluginModulePath));
 539             }
 540             options.addAll(this.options);
 541             options.add(image.toString());
 542             return options.toArray(new String[options.size()]);
 543         }
 544 
 545         private Result cmd(String cmd, Path returnPath) {
 546             String[] args = optionsJImage(cmd);
 547             System.err.println("jimage options: " + optionsPrettyPrint(args));
 548             StringWriter writer = new StringWriter();
 549             int exitCode = jdk.tools.jimage.Main.run(args, new PrintWriter(writer));
 550             return new Result(exitCode, writer.toString(), returnPath);
 551         }
 552 
 553         public Result extract() {
 554             return cmd("extract", dir);
 555         }
 556     }
 557 
 558     public static class JLinkTask {
 559 
 560         private final List<Path> jars = new ArrayList<>();
 561         private final List<Path> jmods = new ArrayList<>();
 562         private final List<Path> pluginModulePath = new ArrayList<>();
 563         private final List<String> addMods = new ArrayList<>();
 564         private final List<String> limitMods = new ArrayList<>();
 565         private final List<String> options = new ArrayList<>();
 566         private String modulePath;
 567         private Path output;
 568         private Path existing;
 569 
 570         public JLinkTask modulePath(String modulePath) {
 571             this.modulePath = modulePath;
 572             return this;
 573         }
 574 
 575         public JLinkTask addJars(Path jars) {
 576             this.jars.add(jars);
 577             return this;
 578         }
 579 
 580         public JLinkTask addJmods(Path jmods) {
 581             this.jmods.add(jmods);
 582             return this;
 583         }
 584 
 585         public JLinkTask pluginModulePath(Path p) {
 586             this.pluginModulePath.add(p);
 587             return this;
 588         }
 589 
 590         public JLinkTask addMods(String moduleName) {
 591             this.addMods.add(moduleName);
 592             return this;
 593         }
 594 
 595         public JLinkTask limitMods(String moduleName) {
 596             this.limitMods.add(moduleName);
 597             return this;
 598         }
 599 
 600         public JLinkTask output(Path output) {
 601             this.output = output;
 602             return this;
 603         }
 604 
 605         public JLinkTask existing(Path existing) {
 606             this.existing = existing;
 607             return this;
 608         }
 609 
 610         public JLinkTask option(String o) {
 611             this.options.add(o);
 612             return this;
 613         }
 614 
 615         private String modulePath() {
 616             // This is expect FIRST jmods THEN jars, if you change this, some tests could fail
 617             String jmods = toPath(this.jmods);
 618             String jars = toPath(this.jars);
 619             return jmods + File.pathSeparator + jars;
 620         }
 621 
 622         private String toPath(List<Path> paths) {
 623             return paths.stream()
 624                     .map(Path::toString)
 625                     .collect(Collectors.joining(File.pathSeparator));
 626         }
 627 
 628         private String[] optionsJLink() {
 629             List<String> options = new ArrayList<>();
 630             if (output != null) {
 631                 options.add(OUTPUT_OPTION);
 632                 options.add(output.toString());
 633             }
 634             if (!addMods.isEmpty()) {
 635                 options.add(ADD_MODULES_OPTION);
 636                 options.add(addMods.stream().collect(Collectors.joining(",")));
 637             }
 638             if (!limitMods.isEmpty()) {
 639                 options.add(LIMIT_MODULES_OPTION);
 640                 options.add(limitMods.stream().collect(Collectors.joining(",")));
 641             }
 642             if (!jars.isEmpty() || !jmods.isEmpty()) {
 643                 options.add(MODULE_PATH_OPTION);
 644                 options.add(modulePath());
 645             }
 646             if (modulePath != null) {
 647                 options.add(MODULE_PATH_OPTION);
 648                 options.add(modulePath);
 649             }
 650             if (!pluginModulePath.isEmpty()) {
 651                 options.add(PLUGIN_MODULE_PATH);
 652                 options.add(toPath(pluginModulePath));
 653             }
 654             options.addAll(this.options);
 655             return options.toArray(new String[options.size()]);
 656         }
 657 
 658         private String[] optionsPostProcessJLink() {
 659             List<String> options = new ArrayList<>();
 660             if (existing != null) {
 661                 options.add(POST_PROCESS_OPTION);
 662                 options.add(existing.toString());
 663             }
 664             options.addAll(this.options);
 665             return options.toArray(new String[options.size()]);
 666         }
 667 
 668         public Result call() {
 669             String[] args = optionsJLink();
 670             System.err.println("jlink options: " + optionsPrettyPrint(args));
 671             StringWriter writer = new StringWriter();
 672             int exitCode = jdk.tools.jlink.internal.Main.run(args, new PrintWriter(writer));
 673             return new Result(exitCode, writer.toString(), output);
 674         }
 675 
 676         public Result callPostProcess() {
 677             String[] args = optionsPostProcessJLink();
 678             System.err.println("jlink options: " + optionsPrettyPrint(args));
 679             StringWriter writer = new StringWriter();
 680             int exitCode = jdk.tools.jlink.internal.Main.run(args, new PrintWriter(writer));
 681             return new Result(exitCode, writer.toString(), output);
 682         }
 683     }
 684 
 685     public static class InMemorySourceFile {
 686         public final String packageName;
 687         public final String className;
 688         public final String source;
 689 
 690         public InMemorySourceFile(String packageName, String simpleName, String source) {
 691             this.packageName = packageName;
 692             this.className = simpleName;
 693             this.source = source;
 694         }
 695     }
 696 
 697     public static class InMemoryFile {
 698         private final String path;
 699         private final byte[] bytes;
 700 
 701         public String getPath() {
 702             return path;
 703         }
 704 
 705         public InputStream getBytes() {
 706             return new ByteArrayInputStream(bytes);
 707         }
 708 
 709         public InMemoryFile(String path, byte[] bytes) {
 710             this.path = path;
 711             this.bytes = bytes;
 712         }
 713 
 714         public InMemoryFile(String path, InputStream is) throws IOException {
 715             this(path, readAllBytes(is));
 716         }
 717     }
 718 
 719     public static byte[] readAllBytes(InputStream is) throws IOException {
 720         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 721         byte[] buf = new byte[1024];
 722         while (true) {
 723             int n = is.read(buf);
 724             if (n < 0) {
 725                 break;
 726             }
 727             baos.write(buf, 0, n);
 728         }
 729         return baos.toByteArray();
 730     }
 731 }