8219695: Use a copy of javac's implementation of @argfile in jpackager
Reviewed-by: kbr
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java Mon Feb 25 08:21:37 2019 -0500
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java Tue Feb 26 12:03:51 2019 -0500
@@ -494,18 +494,9 @@
private void initArgumentList(String[] args) throws PackagerException {
argList = new ArrayList<String>(args.length);
for (String arg : args) {
- if (arg.startsWith("@")) {
- if (arg.length() > 1) {
- String filename = arg.substring(1);
- argList.addAll(extractArgList(filename));
- } else {
- throw new PackagerException("ERR_InvalidOption", arg);
- }
- } else {
- argList.add(arg);
- }
+ argList.add(arg);
}
- Log.debug ("\nJPackage argument list: \n" + argList + "\n");
+ Log.debug ("\njpackage argument list: \n" + argList + "\n");
pos = 0;
deployParams = new DeployParams();
@@ -516,47 +507,6 @@
secondaryLaunchers = new ArrayList<>();
}
- private List<String> extractArgList(String filename) {
- List<String> args = new ArrayList<String>();
- try {
- File f = new File(filename);
- if (f.exists()) {
- List<String> lines = Files.readAllLines(f.toPath());
- for (String line : lines) {
- String [] qsplit;
- String quote = "\"";
- if (line.contains("\"")) {
- qsplit = line.split("\"");
- } else {
- qsplit = line.split("\'");
- quote = "\'";
- }
- for (int i=0; i<qsplit.length; i++) {
- // every other qsplit of line is a quoted string
- if ((i & 1) == 0) {
- // non-quoted string - split by whitespace
- String [] newargs = qsplit[i].split("\\s");
- for (String newarg : newargs) {
- args.add(newarg);
- }
- } else {
- // quoted string - don't split by whitespace
- args.add(qsplit[i]);
- }
- }
- }
- } else {
- Log.info(MessageFormat.format(I18N.getString(
- "warning.missing.arg.file"), f));
- }
- } catch (IOException ioe) {
- Log.verbose(ioe.getMessage());
- Log.verbose(ioe);
- }
- return args;
- }
-
-
public boolean processArguments() throws Exception {
try {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/main/CommandLine.java Tue Feb 26 12:03:51 2019 -0500
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 1999, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jpackage.main;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Various utility methods for processing Java tool command line arguments.
+ *
+ * <p><b>This is NOT part of any supported API.
+ * If you write code that depends on this, you do so at your own risk.
+ * This code and its internal interfaces are subject to change or
+ * deletion without notice.</b>
+ */
+public class CommandLine {
+ /**
+ * Process Win32-style command files for the specified command line
+ * arguments and return the resulting arguments. A command file argument
+ * is of the form '@file' where 'file' is the name of the file whose
+ * contents are to be parsed for additional arguments. The contents of
+ * the command file are parsed using StreamTokenizer and the original
+ * '@file' argument replaced with the resulting tokens. Recursive command
+ * files are not supported. The '@' character itself can be quoted with
+ * the sequence '@@'.
+ * @param args the arguments that may contain @files
+ * @return the arguments, with @files expanded
+ * @throws IOException if there is a problem reading any of the @files
+ */
+ public static String[] parse(String[] args) throws IOException {
+ List<String> newArgs = new ArrayList<>();
+ appendParsedCommandArgs(newArgs, Arrays.asList(args));
+ return newArgs.toArray(new String[newArgs.size()]);
+ }
+
+ private static void appendParsedCommandArgs(List<String> newArgs, List<String> args) throws IOException {
+ for (String arg : args) {
+ if (arg.length() > 1 && arg.charAt(0) == '@') {
+ arg = arg.substring(1);
+ if (arg.charAt(0) == '@') {
+ newArgs.add(arg);
+ } else {
+ loadCmdFile(arg, newArgs);
+ }
+ } else {
+ newArgs.add(arg);
+ }
+ }
+ }
+
+ /**
+ * Process the given environment variable and appends any Win32-style
+ * command files for the specified command line arguments and return
+ * the resulting arguments. A command file argument
+ * is of the form '@file' where 'file' is the name of the file whose
+ * contents are to be parsed for additional arguments. The contents of
+ * the command file are parsed using StreamTokenizer and the original
+ * '@file' argument replaced with the resulting tokens. Recursive command
+ * files are not supported. The '@' character itself can be quoted with
+ * the sequence '@@'.
+ * @param envVariable the env variable to process
+ * @param args the arguments that may contain @files
+ * @return the arguments, with environment variable's content and expansion of @files
+ * @throws IOException if there is a problem reading any of the @files
+ * @throws com.sun.tools.javac.main.CommandLine.UnmatchedQuote
+ */
+ public static List<String> parse(String envVariable, List<String> args)
+ throws IOException, UnmatchedQuote {
+
+ List<String> inArgs = new ArrayList<>();
+ appendParsedEnvVariables(inArgs, envVariable);
+ inArgs.addAll(args);
+ List<String> newArgs = new ArrayList<>();
+ appendParsedCommandArgs(newArgs, inArgs);
+ return newArgs;
+ }
+
+ /**
+ * Process the given environment variable and appends any Win32-style
+ * command files for the specified command line arguments and return
+ * the resulting arguments. A command file argument
+ * is of the form '@file' where 'file' is the name of the file whose
+ * contents are to be parsed for additional arguments. The contents of
+ * the command file are parsed using StreamTokenizer and the original
+ * '@file' argument replaced with the resulting tokens. Recursive command
+ * files are not supported. The '@' character itself can be quoted with
+ * the sequence '@@'.
+ * @param envVariable the env variable to process
+ * @param args the arguments that may contain @files
+ * @return the arguments, with environment variable's content and expansion of @files
+ * @throws IOException if there is a problem reading any of the @files
+ * @throws com.sun.tools.javac.main.CommandLine.UnmatchedQuote
+ */
+ public static String[] parse(String envVariable, String[] args) throws IOException, UnmatchedQuote {
+ List<String> out = parse(envVariable, Arrays.asList(args));
+ return out.toArray(new String[out.size()]);
+ }
+
+ private static void loadCmdFile(String name, List<String> args) throws IOException {
+ try (Reader r = Files.newBufferedReader(Paths.get(name), Charset.defaultCharset())) {
+ Tokenizer t = new Tokenizer(r);
+ String s;
+ while ((s = t.nextToken()) != null) {
+ args.add(s);
+ }
+ }
+ }
+
+ public static class Tokenizer {
+ private final Reader in;
+ private int ch;
+
+ public Tokenizer(Reader in) throws IOException {
+ this.in = in;
+ ch = in.read();
+ }
+
+ public String nextToken() throws IOException {
+ skipWhite();
+ if (ch == -1) {
+ return null;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ char quoteChar = 0;
+
+ while (ch != -1) {
+ switch (ch) {
+ case ' ':
+ case '\t':
+ case '\f':
+ if (quoteChar == 0) {
+ return sb.toString();
+ }
+ sb.append((char) ch);
+ break;
+
+ case '\n':
+ case '\r':
+ return sb.toString();
+
+ case '\'':
+ case '"':
+ if (quoteChar == 0) {
+ quoteChar = (char) ch;
+ } else if (quoteChar == ch) {
+ quoteChar = 0;
+ } else {
+ sb.append((char) ch);
+ }
+ break;
+
+ case '\\':
+ if (quoteChar != 0) {
+ ch = in.read();
+ switch (ch) {
+ case '\n':
+ case '\r':
+ while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\f') {
+ ch = in.read();
+ }
+ continue;
+
+ case 'n':
+ ch = '\n';
+ break;
+ case 'r':
+ ch = '\r';
+ break;
+ case 't':
+ ch = '\t';
+ break;
+ case 'f':
+ ch = '\f';
+ break;
+ }
+ }
+ sb.append((char) ch);
+ break;
+
+ default:
+ sb.append((char) ch);
+ }
+
+ ch = in.read();
+ }
+
+ return sb.toString();
+ }
+
+ void skipWhite() throws IOException {
+ while (ch != -1) {
+ switch (ch) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ case '\f':
+ break;
+
+ case '#':
+ ch = in.read();
+ while (ch != '\n' && ch != '\r' && ch != -1) {
+ ch = in.read();
+ }
+ break;
+
+ default:
+ return;
+ }
+
+ ch = in.read();
+ }
+ }
+ }
+
+ @SuppressWarnings("fallthrough")
+ private static void appendParsedEnvVariables(List<String> newArgs, String envVariable)
+ throws UnmatchedQuote {
+
+ if (envVariable == null) {
+ return;
+ }
+ String in = System.getenv(envVariable);
+ if (in == null || in.trim().isEmpty()) {
+ return;
+ }
+
+ final char NUL = (char)0;
+ final int len = in.length();
+
+ int pos = 0;
+ StringBuilder sb = new StringBuilder();
+ char quote = NUL;
+ char ch;
+
+ loop:
+ while (pos < len) {
+ ch = in.charAt(pos);
+ switch (ch) {
+ case '\"': case '\'':
+ if (quote == NUL) {
+ quote = ch;
+ } else if (quote == ch) {
+ quote = NUL;
+ } else {
+ sb.append(ch);
+ }
+ pos++;
+ break;
+ case '\f': case '\n': case '\r': case '\t': case ' ':
+ if (quote == NUL) {
+ newArgs.add(sb.toString());
+ sb.setLength(0);
+ while (ch == '\f' || ch == '\n' || ch == '\r' || ch == '\t' || ch == ' ') {
+ pos++;
+ if (pos >= len) {
+ break loop;
+ }
+ ch = in.charAt(pos);
+ }
+ break;
+ }
+ // fall through
+ default:
+ sb.append(ch);
+ pos++;
+ }
+ }
+ if (sb.length() != 0) {
+ newArgs.add(sb.toString());
+ }
+ if (quote != NUL) {
+ throw new UnmatchedQuote(envVariable);
+ }
+ }
+
+ public static class UnmatchedQuote extends Exception {
+ private static final long serialVersionUID = 0;
+
+ public final String variableName;
+
+ UnmatchedQuote(String variable) {
+ this.variableName = variable;
+ }
+ }
+}
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/main/Main.java Mon Feb 25 08:21:37 2019 -0500
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/main/Main.java Tue Feb 26 12:03:51 2019 -0500
@@ -75,18 +75,19 @@
}
private static int run(String... args) throws Exception {
- if (args.length == 0) {
+ String[] newArgs = CommandLine.parse(args);
+ if (newArgs.length == 0) {
CLIHelp.showHelp(true);
- } else if (hasHelp(args)){
- if (hasVersion(args)) {
+ } else if (hasHelp(newArgs)){
+ if (hasVersion(newArgs)) {
Log.info(version + "\n");
}
CLIHelp.showHelp(false);
- } else if (hasVersion(args)) {
+ } else if (hasVersion(newArgs)) {
Log.info(version);
} else {
try {
- Arguments arguments = new Arguments(args);
+ Arguments arguments = new Arguments(newArgs);
if (!arguments.processArguments()) {
// processArguments() should log error message if failed.
return -1;