Skip to content

Commit

Permalink
[java-source-utils] Add response file support (#744)
Browse files Browse the repository at this point in the history
Add support for ["response files"][0] to `java-source-utils.jar`,
allowing `java-source-utils.jar` options to be present within a plain-
text file, and the file provided as an option:

	$ cat >response-file <<EOF
	--help
	EOF
	$ java -jar java-source-utils.jar @response-file
	# equivalent to: java -jar java-source-utils.jar --help

In this case, the "scare quotes" is because of limitations when
parsing the response file: only one value per line.  Blank lines are
ignored, as are lines starting with `#`.

Thus, a response file with contents:

	--source
	/path/to/source/directory

is fine, while a response file with the contents:

	--source /path/to/source/directory

will not be parsed properly.

[0]: https://docs.microsoft.com/en-us/windows/win32/midl/response-files
  • Loading branch information
jonpryor authored Oct 30, 2020
1 parent c3c3575 commit b588ef5
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

Expand Down Expand Up @@ -40,7 +42,8 @@
public class JavaSourceUtilsOptions implements AutoCloseable {
public static final String HELP_STRING = "[-v] [<-a|--aar> AAR]* [<-j|--jar> JAR]* [<-s|--source> DIRS]*\n" +
"\t[--bootclasspath CLASSPATH]\n" +
"\t[<-P|--output-params> OUT.params.txt] [<-D|--output-javadoc> OUT.xml] FILES";
"\t[<-P|--output-params> OUT.params.txt] [<-D|--output-javadoc> OUT.xml]\n" +
"\t[@RESPONSE-FILE]* FILES";

public static boolean verboseOutput;

Expand Down Expand Up @@ -111,14 +114,15 @@ private final TypeSolver createTypeSolver(ParserConfiguration config) throws IOE
return typeSolver;
}

public static JavaSourceUtilsOptions parse(final String[] args) throws IOException {
final JavaSourceUtilsOptions options = new JavaSourceUtilsOptions();
private final JavaSourceUtilsOptions parse(Iterator<String> args) throws IOException {
if (args == null || !args.hasNext())
return this;

for (int i = 0; i < args.length; ++i) {
final String arg = args[i];
while (args.hasNext()) {
String arg = args.next();
switch (arg) {
case "-bootclasspath": {
final String bootClassPath = getOptionValue(args, ++i, arg);
final String bootClassPath = getNextOptionValue(args, arg);
final ArrayList<File> files = new ArrayList<File>();
for (final String cp : bootClassPath.split(File.pathSeparator)) {
final File file = new File(cp);
Expand All @@ -129,46 +133,46 @@ public static JavaSourceUtilsOptions parse(final String[] args) throws IOExcepti
files.add(file);
}
for (int j = files.size(); j > 0; --j) {
options.jarFiles.add(0, files.get(j-1));
jarFiles.add(0, files.get(j-1));
}
options.haveBootClassPath = true;
haveBootClassPath = true;
break;
}
case "-a":
case "--aar": {
final File file = getOptionFile(args, ++i, arg);
final File file = getNextOptionFile(args, arg);
if (file == null) {
break;
}
options.aarFiles.add(file);
aarFiles.add(file);
break;
}
case "-j":
case "--jar": {
final File file = getOptionFile(args, ++i, arg);
final File file = getNextOptionFile(args, arg);
if (file == null) {
break;
}
options.jarFiles.add(file);
jarFiles.add(file);
break;
}
case "-s":
case "--source": {
final File dir = getOptionFile(args, ++i, arg);
final File dir = getNextOptionFile(args, arg);
if (dir == null) {
break;
}
options.sourceDirectoryFiles.add(dir);
sourceDirectoryFiles.add(dir);
break;
}
case "-D":
case "--output-javadoc": {
options.outputJavadocXml = getOptionValue(args, ++i, arg);
outputJavadocXml = getNextOptionValue(args, arg);
break;
}
case "-P":
case "--output-params": {
options.outputParamsTxt = getOptionValue(args, ++i, arg);
outputParamsTxt = getNextOptionValue(args, arg);
break;
}
case "-v": {
Expand All @@ -180,37 +184,63 @@ public static JavaSourceUtilsOptions parse(final String[] args) throws IOExcepti
return null;
}
default: {
final File file = getOptionFile(args, i, "FILES");
if (file == null)
if (arg.startsWith("@")) {
// response file?
final String responseFileName = arg.substring(1);
final File responseFile = new File(responseFileName);
if (responseFile.exists()) {
final Iterator<String> lines =
Files.readAllLines(responseFile.toPath())
.stream()
.filter(line -> line.length() > 0 && !line.startsWith("#"))
.iterator();

final JavaSourceUtilsOptions r = parse(lines);
if (r == null)
return null;
break;
}
}
final File file = new File(arg);
if (!file.exists()) {
System.err.println(App.APP_NAME + ": warning: invalid file path for option `FILES`: " + arg);
break;
}

if (file.isDirectory()) {
options.sourceDirectoryFiles.add(file);
sourceDirectoryFiles.add(file);
Files.walk(file.toPath())
.filter(f -> Files.isRegularFile(f) && f.getFileName().toString().endsWith(".java"))
.map(Path::toFile)
.forEach(f -> options.inputFiles.add(f));
.forEach(f -> inputFiles.add(f));
break;
}
if (file.getName().endsWith(".java")) {
options.inputFiles.add(file);
inputFiles.add(file);
break;
}
if (!file.getName().endsWith(".jar") && !file.getName().endsWith(".zip")) {
System.err.println(App.APP_NAME + ": warning: ignoring input file `" + file.getAbsolutePath() +"`.");
break;
}
if (options.extractedTempDir == null) {
options.extractedTempDir = Files.createTempDirectory("ji-jst").toFile();
if (extractedTempDir == null) {
extractedTempDir = Files.createTempDirectory("ji-jst").toFile();
}
File toDir = new File(options.extractedTempDir, file.getName());
options.sourceDirectoryFiles.add(toDir);
extractTo(file, toDir, options.inputFiles);
File toDir = new File(extractedTempDir, file.getName());
sourceDirectoryFiles.add(toDir);
extractTo(file, toDir, inputFiles);
break;
}
}
}
return options;
return this;
}

public static JavaSourceUtilsOptions parse(final String[] args) throws IOException {
final JavaSourceUtilsOptions options = new JavaSourceUtilsOptions();
final Iterator<String> a = Arrays.stream(args).iterator();

return options.parse(a);
}

private static void extractTo(final File zipFilePath, final File toDir, final Collection<File> inputFiles) throws IOException {
Expand All @@ -235,18 +265,18 @@ private static void extractTo(final File zipFilePath, final File toDir, final Co
}
}

static String getOptionValue(final String[] args, final int index, final String option) {
if (index >= args.length)
static String getNextOptionValue(final Iterator<String> args, final String option) {
if (!args.hasNext())
throw new IllegalArgumentException(
"Expected required value for option `" + option + "` at index " + index + ".");
return args[index];
"Expected required value for option `" + option + "`.");
return args.next();
}

static File getOptionFile(final String[] args, final int index, final String option) {
if (index >= args.length)
static File getNextOptionFile(final Iterator<String> args, final String option) {
if (!args.hasNext())
throw new IllegalArgumentException(
"Expected required value for option `" + option + "` at index " + index + ".");
final String fileName = args[index];
"Expected required value for option `" + option + "`.");
final String fileName = args.next();
final File file = new File(fileName);
if (!file.exists()) {
System.err.println(App.APP_NAME + ": warning: invalid file path for option `" + option + "`: " + fileName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
*/
package com.microsoft.android;

import java.io.File;
import java.io.IOException;
import java.io.*;

import org.junit.Test;

import jdk.nashorn.internal.AssertsEnabled;

import static org.junit.Assert.*;

public class JavaSourceUtilsOptionsTest {
Expand All @@ -17,4 +22,52 @@ public class JavaSourceUtilsOptionsTest {
options = JavaSourceUtilsOptions.parse(new String[]{"-h"});
assertNull(options);
}

@Test public void testParse_ResponseFiles() throws IOException {
JavaSourceUtilsOptions options;

final File responseFile = File.createTempFile("jsu-test", ".java");
final String responseFilePath = responseFile.getAbsolutePath();

try (PrintWriter contents = new PrintWriter(responseFile)) {
contents.println("--help");
}

options = JavaSourceUtilsOptions.parse(new String[]{"@" + responseFilePath});
responseFile.delete();
assertNull(options);

try (PrintWriter contents = new PrintWriter(responseFile)) {
contents.println("# aar?");
contents.println("-a");
contents.println(responseFilePath);
contents.println("# jar?");
contents.println("-j");
contents.println(responseFilePath);
contents.println("# source?");
contents.println("-s");
contents.println(responseFilePath);
contents.println("-bootclasspath");
contents.println(responseFilePath + File.pathSeparator + responseFilePath);
contents.println("# params output?");
contents.println("-P");
contents.println("params.txt");
contents.println("# xml javadoc output?");
contents.println("-D");
contents.println("javadoc.xml");
contents.println("# comment; FILEs…");
contents.println(responseFilePath);
}

options = JavaSourceUtilsOptions.parse(new String[]{"@" + responseFilePath});
responseFile.delete();

assertEquals(responseFilePath, options.aarFiles.get(0).getAbsolutePath());
assertEquals(responseFilePath, options.jarFiles.get(0).getAbsolutePath());
assertEquals("params.txt", options.outputParamsTxt);
assertEquals("javadoc.xml", options.outputJavadocXml);
assertEquals(1, options.inputFiles.size());
assertEquals(responseFilePath, options.inputFiles.iterator().next().getAbsolutePath());
assertTrue(options.haveBootClassPath);
}
}

0 comments on commit b588ef5

Please sign in to comment.