24 |
24 |
25 import java.io.File; |
25 import java.io.File; |
26 import java.io.IOException; |
26 import java.io.IOException; |
27 import java.nio.file.Files; |
27 import java.nio.file.Files; |
28 import java.nio.file.Path; |
28 import java.nio.file.Path; |
29 import java.util.Collections; |
29 import java.util.ArrayList; |
30 import java.util.Enumeration; |
|
31 import java.util.List; |
30 import java.util.List; |
32 import java.util.concurrent.atomic.AtomicInteger; |
31 import java.util.concurrent.atomic.AtomicBoolean; |
33 import java.util.function.Consumer; |
32 import java.util.regex.Matcher; |
|
33 import java.util.regex.Pattern; |
|
34 import java.util.stream.Collectors; |
|
35 import jdk.jpackage.test.Functional.ThrowingFunction; |
|
36 import jdk.jpackage.test.Functional.ThrowingSupplier; |
34 |
37 |
35 public class HelloApp { |
38 public class HelloApp { |
36 |
39 |
37 private static final String MAIN_CLASS = "Hello"; |
40 private HelloApp() { |
38 private static final String JAR_FILENAME = "hello.jar"; |
41 setClassName(CLASS_NAME).setJarFileName("hello.jar"); |
39 private static final Consumer<JPackageCommand> CREATE_JAR_ACTION = (cmd) -> { |
42 } |
40 new JarBuilder() |
43 |
41 .setOutputJar(cmd.inputDir().resolve(JAR_FILENAME).toFile()) |
44 /** |
42 .setMainClass(MAIN_CLASS) |
45 * Set fully qualified class name. E.g: foo.bar.Buzz. |
43 .addSourceFile(Test.TEST_SRC_ROOT.resolve( |
46 */ |
44 Path.of("apps", "image", MAIN_CLASS + ".java"))) |
47 private HelloApp setClassName(String v) { |
45 .create(); |
48 qualifiedClassName = v; |
46 }; |
49 return this; |
47 |
50 } |
48 static void addTo(JPackageCommand cmd) { |
51 |
49 cmd.addAction(CREATE_JAR_ACTION); |
52 private HelloApp setModuleName(String v) { |
50 cmd.addArguments("--main-jar", JAR_FILENAME); |
53 moduleName = v; |
51 cmd.addArguments("--main-class", MAIN_CLASS); |
54 return this; |
52 if (PackageType.WINDOWS.contains(cmd.packageType())) { |
55 } |
|
56 |
|
57 private HelloApp setJarFileName(String v) { |
|
58 jarFileName = v; |
|
59 return this; |
|
60 } |
|
61 |
|
62 private HelloApp setModuleVersion(String v) { |
|
63 moduleVersion = v; |
|
64 return this; |
|
65 } |
|
66 |
|
67 private JarBuilder prepareSources(Path srcDir) throws IOException { |
|
68 final String className = qualifiedClassName.substring( |
|
69 qualifiedClassName.lastIndexOf('.') + 1); |
|
70 final String packageName = packageName(); |
|
71 |
|
72 final Path srcFile = srcDir.resolve(Path.of(String.join( |
|
73 File.separator, qualifiedClassName.split("\\.")) + ".java")); |
|
74 Files.createDirectories(srcFile.getParent()); |
|
75 |
|
76 JarBuilder jarBuilder = createJarBuilder().addSourceFile(srcFile); |
|
77 if (moduleName != null) { |
|
78 Path moduleInfoFile = srcDir.resolve("module-info.java"); |
|
79 TKit.createTextFile(moduleInfoFile, List.of( |
|
80 String.format("module %s {", moduleName), |
|
81 String.format(" exports %s;", packageName), |
|
82 "}" |
|
83 )); |
|
84 jarBuilder.addSourceFile(moduleInfoFile); |
|
85 if (moduleVersion != null) { |
|
86 jarBuilder.setModuleVersion(moduleVersion); |
|
87 } |
|
88 } |
|
89 |
|
90 // Add package directive and replace class name in java source file. |
|
91 // Works with simple test Hello.java. |
|
92 // Don't expect too much from these regexps! |
|
93 Pattern classDeclaration = Pattern.compile("(^.*\\bclass\\s+)Hello(.*$)"); |
|
94 Pattern importDirective = Pattern.compile( |
|
95 "(?<=import (?:static )?+)[^;]+"); |
|
96 AtomicBoolean classDeclared = new AtomicBoolean(); |
|
97 AtomicBoolean packageInserted = new AtomicBoolean(packageName == null); |
|
98 |
|
99 var packageInserter = Functional.identityFunction((line) -> { |
|
100 packageInserted.setPlain(true); |
|
101 return String.format("package %s;%s%s", packageName, |
|
102 System.lineSeparator(), line); |
|
103 }); |
|
104 |
|
105 Files.write(srcFile, Files.readAllLines(HELLO_JAVA).stream().map(line -> { |
|
106 if (classDeclared.getPlain()) { |
|
107 return line; |
|
108 } |
|
109 |
|
110 Matcher m; |
|
111 if (!packageInserted.getPlain() && importDirective.matcher(line).find()) { |
|
112 line = packageInserter.apply(line); |
|
113 } else if ((m = classDeclaration.matcher(line)).find()) { |
|
114 classDeclared.setPlain(true); |
|
115 line = m.group(1) + className + m.group(2); |
|
116 if (!packageInserted.getPlain()) { |
|
117 line = packageInserter.apply(line); |
|
118 } |
|
119 } |
|
120 return line; |
|
121 }).collect(Collectors.toList())); |
|
122 |
|
123 return jarBuilder; |
|
124 } |
|
125 |
|
126 private JarBuilder createJarBuilder() { |
|
127 return new JarBuilder().setMainClass(qualifiedClassName); |
|
128 } |
|
129 |
|
130 private void addTo(JPackageCommand cmd) { |
|
131 if (moduleName != null && packageName() == null) { |
|
132 throw new IllegalArgumentException(String.format( |
|
133 "Module [%s] with default package", moduleName)); |
|
134 } |
|
135 |
|
136 if (moduleName == null && CLASS_NAME.equals(qualifiedClassName)) { |
|
137 // Use Hello.java as is. |
|
138 cmd.addAction((self) -> { |
|
139 File jarFile = self.inputDir().resolve(jarFileName).toFile(); |
|
140 createJarBuilder().setOutputJar(jarFile).addSourceFile( |
|
141 HELLO_JAVA).create(); |
|
142 }); |
|
143 } else { |
|
144 cmd.addAction((self) -> { |
|
145 final Path jarFile; |
|
146 if (moduleName == null) { |
|
147 jarFile = self.inputDir().resolve(jarFileName); |
|
148 } else { |
|
149 // `--module-path` option should be set by the moment |
|
150 // when this action is being executed. |
|
151 jarFile = Path.of(self.getArgumentValue("--module-path", |
|
152 () -> self.inputDir().toString()), jarFileName); |
|
153 Files.createDirectories(jarFile.getParent()); |
|
154 } |
|
155 |
|
156 TKit.withTempDirectory("src", |
|
157 workDir -> prepareSources(workDir).setOutputJar( |
|
158 jarFile.toFile()).create()); |
|
159 }); |
|
160 } |
|
161 |
|
162 if (moduleName == null) { |
|
163 cmd.addArguments("--main-jar", jarFileName); |
|
164 cmd.addArguments("--main-class", qualifiedClassName); |
|
165 } else { |
|
166 cmd.addArguments("--module-path", TKit.workDir().resolve( |
|
167 "input-modules")); |
|
168 cmd.addArguments("--module", String.join("/", moduleName, |
|
169 qualifiedClassName)); |
|
170 // For modular app assume nothing will go in input directory and thus |
|
171 // nobody will create input directory, so remove corresponding option |
|
172 // from jpackage command line. |
|
173 cmd.removeArgumentWithValue("--input"); |
|
174 } |
|
175 if (TKit.isWindows()) { |
53 cmd.addArguments("--win-console"); |
176 cmd.addArguments("--win-console"); |
54 } |
177 } |
55 } |
178 } |
56 |
179 |
57 static void verifyOutputFile(Path outputFile, String... args) { |
180 private String packageName() { |
|
181 int lastDotIdx = qualifiedClassName.lastIndexOf('.'); |
|
182 if (lastDotIdx == -1) { |
|
183 return null; |
|
184 } |
|
185 return qualifiedClassName.substring(0, lastDotIdx); |
|
186 } |
|
187 |
|
188 /** |
|
189 * Configures Java application to be used with the given jpackage command. |
|
190 * Syntax of encoded Java application description is |
|
191 * [jar_file:][module_name/]qualified_class_name[@module_version]. |
|
192 * |
|
193 * E.g.: duke.jar:com.other/com.other.foo.bar.Buz@3.7 encodes modular |
|
194 * application. Module name is `com.other`. Main class is |
|
195 * `com.other.foo.bar.Buz`. Module version is `3.7`. Application will be |
|
196 * compiled and packed in `duke.jar` jar file. |
|
197 * |
|
198 * @param cmd jpackage command to configure |
|
199 * @param javaAppDesc encoded Java application description |
|
200 */ |
|
201 static void addTo(JPackageCommand cmd, String javaAppDesc) { |
|
202 HelloApp helloApp = new HelloApp(); |
|
203 if (javaAppDesc != null) { |
|
204 String moduleNameAndOther = Functional.identity(() -> { |
|
205 String[] components = javaAppDesc.split(":", 2); |
|
206 if (components.length == 2) { |
|
207 helloApp.setJarFileName(components[0]); |
|
208 } |
|
209 return components[components.length - 1]; |
|
210 }).get(); |
|
211 |
|
212 String classNameAndOther = Functional.identity(() -> { |
|
213 String[] components = moduleNameAndOther.split("/", 2); |
|
214 if (components.length == 2) { |
|
215 helloApp.setModuleName(components[0]); |
|
216 } |
|
217 return components[components.length - 1]; |
|
218 }).get(); |
|
219 |
|
220 Functional.identity(() -> { |
|
221 String[] components = classNameAndOther.split("@", 2); |
|
222 helloApp.setClassName(components[0]); |
|
223 if (components.length == 2) { |
|
224 helloApp.setModuleVersion(components[1]); |
|
225 } |
|
226 }).run(); |
|
227 } |
|
228 helloApp.addTo(cmd); |
|
229 } |
|
230 |
|
231 static void verifyOutputFile(Path outputFile, List<String> args) { |
58 if (!outputFile.isAbsolute()) { |
232 if (!outputFile.isAbsolute()) { |
59 verifyOutputFile(outputFile.toAbsolutePath().normalize(), args); |
233 verifyOutputFile(outputFile.toAbsolutePath().normalize(), args); |
60 return; |
234 return; |
61 } |
235 } |
62 |
236 |
63 Test.assertFileExists(outputFile, true); |
237 TKit.assertFileExists(outputFile); |
64 |
238 |
65 List<String> output = null; |
239 List<String> contents = ThrowingSupplier.toSupplier( |
66 try { |
240 () -> Files.readAllLines(outputFile)).get(); |
67 output = Files.readAllLines(outputFile); |
241 |
68 } catch (IOException ex) { |
242 List<String> expected = new ArrayList<>(List.of( |
69 throw new RuntimeException(ex); |
243 "jpackage test application", |
70 } |
244 String.format("args.length: %d", args.size()) |
71 |
245 )); |
72 final int expectedNumberOfLines = 2 + args.length; |
246 expected.addAll(args); |
73 Test.assertEquals(expectedNumberOfLines, output.size(), String.format( |
247 |
74 "Check file [%s] contains %d text lines", outputFile, |
248 TKit.assertStringListEquals(expected, contents, String.format( |
75 expectedNumberOfLines)); |
249 "Check contents of [%s] file", outputFile)); |
76 |
|
77 Test.assertEquals("jpackage test application", output.get(0), |
|
78 String.format( |
|
79 "Check contents of the first text line in [%s] file", |
|
80 outputFile)); |
|
81 |
|
82 Test.assertEquals(String.format("args.length: %d", args.length), |
|
83 output.get(1), String.format( |
|
84 "Check contents of the second text line in [%s] file", |
|
85 outputFile)); |
|
86 |
|
87 Enumeration<String> argsEnum = Collections.enumeration(List.of(args)); |
|
88 AtomicInteger counter = new AtomicInteger(2); |
|
89 output.stream().skip(2).sequential().forEach(line -> Test.assertEquals( |
|
90 argsEnum.nextElement(), line, String.format( |
|
91 "Check contents of %d text line in [%s] file", |
|
92 counter.incrementAndGet(), outputFile))); |
|
93 } |
250 } |
94 |
251 |
95 public static void executeLauncherAndVerifyOutput(JPackageCommand cmd) { |
252 public static void executeLauncherAndVerifyOutput(JPackageCommand cmd) { |
96 final Path launcherPath; |
253 final Path launcherPath; |
97 if (cmd.packageType() == PackageType.IMAGE) { |
254 if (cmd.packageType() == PackageType.IMAGE) { |