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 */
23package tests;
24
25import java.io.ByteArrayInputStream;
26import java.io.ByteArrayOutputStream;
27import java.io.File;
28import java.io.FileOutputStream;
29import java.io.IOException;
30import java.io.InputStream;
31import java.io.OutputStream;
32import java.io.PrintStream;
33import java.io.PrintWriter;
34import java.io.StringWriter;
35import java.nio.file.Files;
36import java.nio.file.Path;
37import java.nio.file.Paths;
38import java.nio.file.StandardCopyOption;
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.Collections;
42import java.util.HashSet;
43import java.util.List;
44import java.util.Objects;
45import java.util.Set;
46import java.util.jar.JarEntry;
47import java.util.jar.JarInputStream;
48import java.util.jar.JarOutputStream;
49import java.util.stream.Collectors;
50import java.util.stream.Stream;
51import java.util.zip.ZipEntry;
52
53import javax.tools.JavaCompiler;
54import javax.tools.StandardJavaFileManager;
55import javax.tools.StandardLocation;
56import javax.tools.ToolProvider;
57
58/**
59 *
60 * A generator for jmods, jars and images.
61 */
62public 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        static final java.util.spi.ToolProvider JMOD_TOOL =
342            java.util.spi.ToolProvider.findFirst("jmod").orElseThrow(() ->
343                new RuntimeException("jmod tool not found")
344            );
345
346        private final List<Path> classpath = new ArrayList<>();
347        private final List<Path> libs = new ArrayList<>();
348        private final List<Path> cmds = new ArrayList<>();
349        private final List<Path> config = new ArrayList<>();
350        private final List<Path> jars = new ArrayList<>();
351        private final List<Path> jmods = new ArrayList<>();
352        private final List<String> options = new ArrayList<>();
353        private Path output;
354        private String hashModules;
355        private String mainClass;
356        private String moduleVersion;
357
358        public JModTask addNativeLibraries(Path cp) {
359            this.libs.add(cp);
360            return this;
361        }
362
363        public JModTask hashModules(String hash) {
364            this.hashModules = hash;
365            return this;
366        }
367
368        public JModTask addCmds(Path cp) {
369            this.cmds.add(cp);
370            return this;
371        }
372
373        public JModTask addClassPath(Path cp) {
374            this.classpath.add(cp);
375            return this;
376        }
377
378        public JModTask addConfig(Path cp) {
379            this.config.add(cp);
380            return this;
381        }
382
383        public JModTask addJars(Path jars) {
384            this.jars.add(jars);
385            return this;
386        }
387
388        public JModTask addJmods(Path jmods) {
389            this.jmods.add(jmods);
390            return this;
391        }
392
393        public JModTask jmod(Path output) {
394            this.output = output;
395            return this;
396        }
397
398        public JModTask moduleVersion(String moduleVersion) {
399            this.moduleVersion = moduleVersion;
400            return this;
401        }
402
403        public JModTask mainClass(String mainClass) {
404            this.mainClass = mainClass;
405            return this;
406        }
407
408        public JModTask option(String o) {
409            this.options.add(o);
410            return this;
411        }
412
413        private String modulePath() {
414            // This is expect FIRST jmods THEN jars, if you change this, some tests could fail
415            String jmods = toPath(this.jmods);
416            String jars = toPath(this.jars);
417            return jmods + File.pathSeparator + jars;
418        }
419
420        private String toPath(List<Path> paths) {
421            return paths.stream()
422                    .map(Path::toString)
423                    .collect(Collectors.joining(File.pathSeparator));
424        }
425
426        private String[] optionsJMod(String cmd) {
427            List<String> options = new ArrayList<>();
428            options.add(cmd);
429            if (!cmds.isEmpty()) {
430                options.add(CMDS_OPTION);
431                options.add(toPath(cmds));
432            }
433            if (!config.isEmpty()) {
434                options.add(CONFIG_OPTION);
435                options.add(toPath(config));
436            }
437            if (hashModules != null) {
438                options.add(HASH_MODULES_OPTION);
439                options.add(hashModules);
440            }
441            if (mainClass != null) {
442                options.add(MAIN_CLASS_OPTION);
443                options.add(mainClass);
444            }
445            if (!libs.isEmpty()) {
446                options.add(LIBS_OPTION);
447                options.add(toPath(libs));
448            }
449            if (!classpath.isEmpty()) {
450                options.add(CLASS_PATH_OPTION);
451                options.add(toPath(classpath));
452            }
453            if (!jars.isEmpty() || !jmods.isEmpty()) {
454                options.add(MODULE_PATH_OPTION);
455                options.add(modulePath());
456            }
457            if (moduleVersion != null) {
458                options.add(MODULE_VERSION_OPTION);
459                options.add(moduleVersion);
460            }
461            options.addAll(this.options);
462            if (output != null) {
463                options.add(output.toString());
464            }
465            return options.toArray(new String[options.size()]);
466        }
467
468        public Result create() {
469            return cmd("create");
470        }
471
472        public Result list() {
473            return cmd("list");
474        }
475
476        public Result call() {
477            return cmd("");
478        }
479
480        private Result cmd(String cmd) {
481            String[] args = optionsJMod(cmd);
482            System.err.println("jmod options: " + optionsPrettyPrint(args));
483            ByteArrayOutputStream baos = new ByteArrayOutputStream();
484            PrintStream ps = new PrintStream(baos);
485            int exitCode = JMOD_TOOL.run(ps, ps, args);
486            String msg = new String(baos.toByteArray());
487            return new Result(exitCode, msg, output);
488        }
489    }
490
491    public static String getPackageName(String canonicalName) {
492        int index = canonicalName.lastIndexOf('.');
493        return index > 0 ? canonicalName.substring(0, index) : "";
494    }
495
496    public static String getSimpleName(String canonicalName) {
497        int index = canonicalName.lastIndexOf('.');
498        return canonicalName.substring(index + 1);
499    }
500
501    public static class JImageTask {
502
503        private final List<Path> pluginModulePath = new ArrayList<>();
504        private final List<String> options = new ArrayList<>();
505        private Path dir;
506        private Path image;
507
508        public JImageTask pluginModulePath(Path p) {
509            this.pluginModulePath.add(p);
510            return this;
511        }
512
513        public JImageTask image(Path image) {
514            this.image = image;
515            return this;
516        }
517
518        public JImageTask dir(Path dir) {
519            this.dir = dir;
520            return this;
521        }
522
523        public JImageTask option(String o) {
524            this.options.add(o);
525            return this;
526        }
527
528        private String toPath(List<Path> paths) {
529            return paths.stream()
530                    .map(Path::toString)
531                    .collect(Collectors.joining(File.pathSeparator));
532        }
533
534        private String[] optionsJImage(String cmd) {
535            List<String> options = new ArrayList<>();
536            options.add(cmd);
537            if (dir != null) {
538                options.add("--dir");
539                options.add(dir.toString());
540            }
541            if (!pluginModulePath.isEmpty()) {
542                options.add(PLUGIN_MODULE_PATH);
543                options.add(toPath(pluginModulePath));
544            }
545            options.addAll(this.options);
546            options.add(image.toString());
547            return options.toArray(new String[options.size()]);
548        }
549
550        private Result cmd(String cmd, Path returnPath) {
551            String[] args = optionsJImage(cmd);
552            System.err.println("jimage options: " + optionsPrettyPrint(args));
553            StringWriter writer = new StringWriter();
554            int exitCode = jdk.tools.jimage.Main.run(args, new PrintWriter(writer));
555            return new Result(exitCode, writer.toString(), returnPath);
556        }
557
558        public Result extract() {
559            return cmd("extract", dir);
560        }
561    }
562
563    public static class JLinkTask {
564        static final java.util.spi.ToolProvider JLINK_TOOL =
565            java.util.spi.ToolProvider.findFirst("jlink").orElseThrow(() ->
566                new RuntimeException("jlink tool not found")
567            );
568
569        private final List<Path> jars = new ArrayList<>();
570        private final List<Path> jmods = new ArrayList<>();
571        private final List<Path> pluginModulePath = new ArrayList<>();
572        private final List<String> addMods = new ArrayList<>();
573        private final List<String> limitMods = new ArrayList<>();
574        private final List<String> options = new ArrayList<>();
575        private String modulePath;
576        // if you want to specifiy repeated --module-path option
577        private String repeatedModulePath;
578        // if you want to specifiy repeated --limit-modules option
579        private String repeatedLimitMods;
580        private Path output;
581        private Path existing;
582
583        public JLinkTask modulePath(String modulePath) {
584            this.modulePath = modulePath;
585            return this;
586        }
587
588        public JLinkTask repeatedModulePath(String modulePath) {
589            this.repeatedModulePath = modulePath;
590            return this;
591        }
592
593        public JLinkTask addJars(Path jars) {
594            this.jars.add(jars);
595            return this;
596        }
597
598        public JLinkTask addJmods(Path jmods) {
599            this.jmods.add(jmods);
600            return this;
601        }
602
603        public JLinkTask pluginModulePath(Path p) {
604            this.pluginModulePath.add(p);
605            return this;
606        }
607
608        public JLinkTask addMods(String moduleName) {
609            this.addMods.add(moduleName);
610            return this;
611        }
612
613        public JLinkTask limitMods(String moduleName) {
614            this.limitMods.add(moduleName);
615            return this;
616        }
617
618        public JLinkTask repeatedLimitMods(String modules) {
619            this.repeatedLimitMods = modules;
620            return this;
621        }
622
623        public JLinkTask output(Path output) {
624            this.output = output;
625            return this;
626        }
627
628        public JLinkTask existing(Path existing) {
629            this.existing = existing;
630            return this;
631        }
632
633        public JLinkTask option(String o) {
634            this.options.add(o);
635            return this;
636        }
637
638        private String modulePath() {
639            // This is expect FIRST jmods THEN jars, if you change this, some tests could fail
640            String jmods = toPath(this.jmods);
641            String jars = toPath(this.jars);
642            return jmods + File.pathSeparator + jars;
643        }
644
645        private String toPath(List<Path> paths) {
646            return paths.stream()
647                    .map(Path::toString)
648                    .collect(Collectors.joining(File.pathSeparator));
649        }
650
651        private String[] optionsJLink() {
652            List<String> options = new ArrayList<>();
653            if (output != null) {
654                options.add(OUTPUT_OPTION);
655                options.add(output.toString());
656            }
657            if (!addMods.isEmpty()) {
658                options.add(ADD_MODULES_OPTION);
659                options.add(addMods.stream().collect(Collectors.joining(",")));
660            }
661            if (!limitMods.isEmpty()) {
662                options.add(LIMIT_MODULES_OPTION);
663                options.add(limitMods.stream().collect(Collectors.joining(",")));
664            }
665            if (repeatedLimitMods != null) {
666                options.add(LIMIT_MODULES_OPTION);
667                options.add(repeatedLimitMods);
668            }
669            if (!jars.isEmpty() || !jmods.isEmpty()) {
670                options.add(MODULE_PATH_OPTION);
671                options.add(modulePath());
672            }
673            if (modulePath != null) {
674                options.add(MODULE_PATH_OPTION);
675                options.add(modulePath);
676            }
677            if (repeatedModulePath != null) {
678                options.add(MODULE_PATH_OPTION);
679                options.add(repeatedModulePath);
680            }
681            if (!pluginModulePath.isEmpty()) {
682                options.add(PLUGIN_MODULE_PATH);
683                options.add(toPath(pluginModulePath));
684            }
685            options.addAll(this.options);
686            return options.toArray(new String[options.size()]);
687        }
688
689        private String[] optionsPostProcessJLink() {
690            List<String> options = new ArrayList<>();
691            if (existing != null) {
692                options.add(POST_PROCESS_OPTION);
693                options.add(existing.toString());
694            }
695            options.addAll(this.options);
696            return options.toArray(new String[options.size()]);
697        }
698
699        public Result call() {
700            String[] args = optionsJLink();
701            System.err.println("jlink options: " + optionsPrettyPrint(args));
702            StringWriter writer = new StringWriter();
703            PrintWriter pw = new PrintWriter(writer);
704            int exitCode = JLINK_TOOL.run(pw, pw, args);
705            return new Result(exitCode, writer.toString(), output);
706        }
707
708        public Result callPostProcess() {
709            String[] args = optionsPostProcessJLink();
710            System.err.println("jlink options: " + optionsPrettyPrint(args));
711            StringWriter writer = new StringWriter();
712            PrintWriter pw = new PrintWriter(writer);
713            int exitCode = JLINK_TOOL.run(pw, pw, args);
714            return new Result(exitCode, writer.toString(), output);
715        }
716    }
717
718    public static class InMemorySourceFile {
719        public final String packageName;
720        public final String className;
721        public final String source;
722
723        public InMemorySourceFile(String packageName, String simpleName, String source) {
724            this.packageName = packageName;
725            this.className = simpleName;
726            this.source = source;
727        }
728    }
729
730    public static class InMemoryFile {
731        private final String path;
732        private final byte[] bytes;
733
734        public String getPath() {
735            return path;
736        }
737
738        public InputStream getBytes() {
739            return new ByteArrayInputStream(bytes);
740        }
741
742        public InMemoryFile(String path, byte[] bytes) {
743            this.path = path;
744            this.bytes = bytes;
745        }
746
747        public InMemoryFile(String path, InputStream is) throws IOException {
748            this(path, readAllBytes(is));
749        }
750    }
751
752    public static byte[] readAllBytes(InputStream is) throws IOException {
753        ByteArrayOutputStream baos = new ByteArrayOutputStream();
754        byte[] buf = new byte[1024];
755        while (true) {
756            int n = is.read(buf);
757            if (n < 0) {
758                break;
759            }
760            baos.write(buf, 0, n);
761        }
762        return baos.toByteArray();
763    }
764}
765