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 }