1 /* 2 * Copyright (c) 2018, 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 24 import java.io.File; 25 import java.io.PrintWriter; 26 import java.io.StringWriter; 27 28 import java.nio.file.Files; 29 import java.nio.file.Path; 30 import java.util.List; 31 32 import java.util.spi.ToolProvider; 33 34 public class JPackagerHelper { 35 36 private static final boolean VERBOSE = false; 37 private static final String OS = 38 System.getProperty("os.name").toLowerCase(); 39 private static final String JAVA_HOME = System.getProperty("java.home"); 40 private static final String TEST_SRC = System.getProperty("test.src"); 41 private static final Path BIN_DIR = Path.of(JAVA_HOME, "bin"); 42 private static final Path JPACKAGER; 43 private static final Path JAVAC; 44 private static final Path JAR; 45 46 static { 47 if (OS.startsWith("win")) { 48 JPACKAGER = BIN_DIR.resolve("jpackager.exe"); 49 JAVAC = BIN_DIR.resolve("javac.exe"); 50 JAR = BIN_DIR.resolve("jar.exe"); 51 } else { 52 JPACKAGER = BIN_DIR.resolve("jpackager"); 53 JAVAC = BIN_DIR.resolve("javac"); 54 JAR = BIN_DIR.resolve("jar"); 55 } 56 } 57 58 static final ToolProvider JPACKAGER_TOOL = 59 ToolProvider.findFirst("jpackager").orElseThrow( 60 () -> new RuntimeException("jpackager tool not found")); 61 62 public static int execute(File out, String... command) throws Exception { 63 if (VERBOSE) { 64 System.out.print("Execute command: "); 65 for (String c : command) { 66 System.out.print(c); 67 System.out.print(" "); 68 } 69 System.out.println(); 70 } 71 72 ProcessBuilder builder = new ProcessBuilder(command); 73 if (out != null) { 74 builder.redirectErrorStream(true); 75 builder.redirectOutput(out); 76 } 77 78 Process process = builder.start(); 79 return process.waitFor(); 80 } 81 82 private static String[] getCommand(String... args) { 83 String[] command; 84 if (args == null) { 85 command = new String[1]; 86 } else { 87 command = new String[args.length + 1]; 88 } 89 90 int index = 0; 91 command[index] = JPACKAGER.toString(); 92 93 if (args != null) { 94 for (String arg : args) { 95 index++; 96 command[index] = arg; 97 } 98 } 99 100 return command; 101 } 102 103 public static String executeCLI(boolean retValZero, String... args) 104 throws Exception { 105 int retVal; 106 File outfile = new File("output.log"); 107 try { 108 String[] command = getCommand(args); 109 retVal = execute(outfile, command); 110 } catch (Exception ex) { 111 if (outfile.exists()) { 112 System.err.println(Files.readString(outfile.toPath())); 113 } 114 throw ex; 115 } 116 117 String output = Files.readString(outfile.toPath()); 118 if (retValZero) { 119 if (retVal != 0) { 120 System.err.println(output); 121 throw new AssertionError("jpackager exited with error: " 122 + retVal); 123 } 124 } else { 125 if (retVal == 0) { 126 System.err.println(output); 127 throw new AssertionError("jpackager exited without error: " 128 + retVal); 129 } 130 } 131 132 if (VERBOSE) { 133 System.out.println("output ="); 134 System.out.println(output); 135 } 136 137 return output; 138 } 139 140 public static String executeToolProvider( 141 boolean retValZero, String... args) throws Exception { 142 StringWriter writer = new StringWriter(); 143 PrintWriter pw = new PrintWriter(writer); 144 int retVal = JPACKAGER_TOOL.run(pw, pw, args); 145 String output = writer.toString(); 146 147 if (retValZero) { 148 if (retVal != 0) { 149 System.err.println(output); 150 throw new AssertionError("jpackager exited with error: " 151 + retVal); 152 } 153 } else { 154 if (retVal == 0) { 155 System.err.println(output); 156 throw new AssertionError("jpackager exited without error"); 157 } 158 } 159 160 if (VERBOSE) { 161 System.out.println("output ="); 162 System.out.println(output); 163 } 164 165 return output; 166 } 167 168 public static boolean isWindows() { 169 return (OS.contains("win")); 170 } 171 172 public static boolean isOSX() { 173 return (OS.contains("mac")); 174 } 175 176 public static boolean isLinux() { 177 return ((OS.contains("nix") || OS.contains("nux"))); 178 } 179 180 public static void createHelloJar() throws Exception { 181 int retVal; 182 183 File input = new File("input"); 184 if (!input.exists()) { 185 input.mkdir(); 186 } 187 188 Files.copy(Path.of(TEST_SRC + File.separator + "Hello.java"), 189 Path.of("Hello.java")); 190 191 File javacLog = new File("javac.log"); 192 try { 193 retVal = execute(javacLog, JAVAC.toString(), "Hello.java"); 194 } catch (Exception ex) { 195 if (javacLog.exists()) { 196 System.err.println(Files.readString(javacLog.toPath())); 197 } 198 throw ex; 199 } 200 201 if (retVal != 0) { 202 if (javacLog.exists()) { 203 System.err.println(Files.readString(javacLog.toPath())); 204 } 205 throw new AssertionError("javac exited with error: " + retVal); 206 } 207 208 File jarLog = new File("jar.log"); 209 try { 210 retVal = execute(null, JAR.toString(), "cvf", 211 "input" + File.separator + "hello.jar", "Hello.class"); 212 } catch (Exception ex) { 213 if (jarLog.exists()) { 214 System.err.println(Files.readString(jarLog.toPath())); 215 } 216 throw ex; 217 } 218 219 if (retVal != 0) { 220 if (jarLog.exists()) { 221 System.err.println(Files.readString(jarLog.toPath())); 222 } 223 throw new AssertionError("jar exited with error: " + retVal); 224 } 225 } 226 227 public static String listToArgumentsMap(List<String> arguments, 228 boolean toolProvider) { 229 if (arguments.isEmpty()) { 230 return ""; 231 } 232 233 String argsStr = ""; 234 for (int i = 0; i < arguments.size(); i++) { 235 String arg = arguments.get(i); 236 argsStr += quote(arg, toolProvider); 237 if ((i + 1) != arguments.size()) { 238 argsStr += " "; 239 } 240 } 241 242 if (!toolProvider && isWindows()) { 243 if (argsStr.contains(" ")) { 244 if (argsStr.contains("\"")) { 245 argsStr = escapeQuote(argsStr, toolProvider); 246 } 247 argsStr = "\"" + argsStr + "\""; 248 } 249 } 250 return argsStr; 251 } 252 253 private static String quote(String in, boolean toolProvider) { 254 if (in == null) { 255 return null; 256 } 257 258 if (in.isEmpty()) { 259 return ""; 260 } 261 262 if (!in.contains("=")) { 263 // Not a property 264 if (in.contains(" ")) { 265 in = escapeQuote(in, toolProvider); 266 return "\"" + in + "\""; 267 } 268 return in; 269 } 270 271 if (!in.contains(" ")) { 272 return in; // No need to quote 273 } 274 275 int paramIndex = in.indexOf("="); 276 if (paramIndex <= 0) { 277 return in; // Something wrong, just skip quoting 278 } 279 280 String param = in.substring(0, paramIndex); 281 String value = in.substring(paramIndex + 1); 282 283 if (value.length() == 0) { 284 return in; // No need to quote 285 } 286 287 value = escapeQuote(value, toolProvider); 288 289 return param + "=" + "\"" + value + "\""; 290 } 291 292 private static String escapeQuote(String in, boolean toolProvider) { 293 if (in == null) { 294 return null; 295 } 296 297 if (in.isEmpty()) { 298 return ""; 299 } 300 301 if (in.contains("\"")) { 302 // Use code points to preserve non-ASCII chars 303 StringBuilder sb = new StringBuilder(); 304 int codeLen = in.codePointCount(0, in.length()); 305 for (int i = 0; i < codeLen; i++) { 306 int code = in.codePointAt(i); 307 // Note: No need to escape '\' on Linux or OS X 308 // jpackager expects us to pass arguments and properties with 309 // quotes and spaces as a map 310 // with quotes being escaped with additional \ for 311 // internal quotes. 312 // So if we want two properties below: 313 // -Djnlp.Prop1=Some "Value" 1 314 // -Djnlp.Prop2=Some Value 2 315 // jpackager will need: 316 // "-Djnlp.Prop1=\"Some \\"Value\\" 1\" -Djnlp.Prop2=\"Some Value 2\"" 317 // but since we using ProcessBuilder to run jpackager we will need to escape 318 // our escape symbols as well, so we will need to pass string below to ProcessBuilder: 319 // "-Djnlp.Prop1=\\\"Some \\\\\\\"Value\\\\\\\" 1\\\" -Djnlp.Prop2=\\\"Some Value 2\\\"" 320 switch (code) { 321 case '"': 322 // " -> \" -> \\\" 323 if (i == 0 || in.codePointAt(i - 1) != '\\') { 324 if (!toolProvider && isWindows()) { 325 sb.appendCodePoint('\\'); 326 sb.appendCodePoint('\\'); 327 } 328 sb.appendCodePoint('\\'); 329 sb.appendCodePoint(code); 330 } 331 break; 332 case '\\': 333 // We need to escape already escaped symbols as well 334 if ((i + 1) < codeLen) { 335 int nextCode = in.codePointAt(i + 1); 336 if (nextCode == '"') { 337 // \" -> \\\" 338 sb.appendCodePoint('\\'); 339 sb.appendCodePoint('\\'); 340 sb.appendCodePoint('\\'); 341 sb.appendCodePoint(nextCode); 342 } else { 343 sb.appendCodePoint('\\'); 344 sb.appendCodePoint(code); 345 } 346 } else { 347 if (isWindows()) { 348 sb.appendCodePoint('\\'); 349 } 350 sb.appendCodePoint(code); 351 } 352 break; 353 default: 354 sb.appendCodePoint(code); 355 break; 356 } 357 } 358 return sb.toString(); 359 } 360 361 return in; 362 } 363 }