Skip to content

Commit

Permalink
Improve JTSOp command - error handling, testing (#459)
Browse files Browse the repository at this point in the history
* Improve JTSOpCmd to make it testable
* Improve error handling
* Update Travis CI with dist: trusty as per JDK8 failing advice

Signed-off-by: Martin Davis <[email protected]>
  • Loading branch information
dr-jts authored Aug 15, 2019
1 parent a535d02 commit 5ab5f90
Show file tree
Hide file tree
Showing 5 changed files with 304 additions and 48 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ cache:
directories:
- $HOME/.m2
install: mvn install -B -V
dist: trusty

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.locationtech.jtstest.cmd;

public class CommandError extends RuntimeException {
public CommandError(String msg) {
super(msg);
}
public CommandError(String msg, String val) {
super(msg + ": " + val);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.locationtech.jtstest.cmd;

public class CommandOutput {

private StringBuilder output = new StringBuilder();
private boolean isCapture = false;

public CommandOutput() {

}

public CommandOutput(boolean isCapture) {
this.isCapture = true;
}

public void println() {
if (isCapture ) {
output.append("\n");
}
else {
System.out.println();
}
}

public void println(Object o) {
if (isCapture ) {
output.append(o);
output.append("\n");
}
else {
System.out.println(o);
}
}

public void print(String s) {
if (isCapture ) {
output.append(s);
}
else {
System.out.print(s);
}
}

public String getOutput() {
return output.toString();
}

}
154 changes: 106 additions & 48 deletions modules/app/src/main/java/org/locationtech/jtstest/cmd/JTSOpCmd.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
*/
package org.locationtech.jtstest.cmd;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;

import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.GeometryFactory;
Expand All @@ -36,7 +36,7 @@
import org.locationtech.jtstest.util.io.MultiFormatReader;

/**
* A simple CLI to run TestBuilder operations.
* A CLI to run JTS TestBuilder operations.
* Allows easier execution of JTS functions on test data for debugging purposes.
* <p>
* Examples:
Expand Down Expand Up @@ -69,6 +69,13 @@ public class JTSOpCmd {
// TODO: add option -ab to read both geoms from a file
// TODO: allow -a stdin to indicate reading from stdin.

public static final String ERR_FILE_NOT_FOUND = "File not found";
public static final String ERR_FUNCTION_NOT_FOUND = "Function not found";
public static final String ERR_REQUIRED_A = "Geometry A may be required";
public static final String ERR_REQUIRED_B = "Geometry B is required";
public static final String ERR_WRONG_ARG_COUNT = "Arguments and parameters do not match";
public static final String ERR_FUNCTION_ERR = "Error executing function";

static final String[] helpDoc = new String[] {
"",
"Usage: jtsop - CLI for JTS operations",
Expand Down Expand Up @@ -101,41 +108,38 @@ public static void main(String[] args)
{
JTSOpCmd cmd = new JTSOpCmd();
try {
CmdArgs cmdArgs = parseArgs(args);

if (args.length == 0 || isHelp) {
printHelp(isHelp);
System.exit(0);
}

CmdArgs cmdArgs = cmd.parseArgs(args);
cmd.execute(cmdArgs);
} catch (Exception e) {
}
catch (CommandError e) {
// for command errors, just print the message
System.err.println(e.getMessage() );
}
catch (Exception e) {
// unexpected errors get a stack track to help debugging
e.printStackTrace();
}
}

private static void printHelp(boolean showFunctions) {
private void printHelp(boolean showFunctions) {
for (String s : helpDoc) {
System.out.println(s);
out.println(s);
}
if (showFunctions) {
System.out.println();
System.out.println("Operations:");
out.println();
out.println("Operations:");
printFunctions();
}
}

private static void printFunctions() {
private void printFunctions() {
//TODO: include any loaded functions
DoubleKeyMap funcMap = funcRegistry.getCategorizedGeometryFunctions();
Collection categories = funcMap.keySet();
for (Iterator i = categories.iterator(); i.hasNext();) {
String category = (String) i.next();

Collection funcs = funcMap.values(category);
for (Iterator j = funcs.iterator(); j.hasNext();) {
GeometryFunction fun = (GeometryFunction) j.next();
System.out.println(category + "." + functionDesc(fun));
Collection<String> categories = funcMap.keySet();
for (String category : categories) {
Collection<GeometryFunction> funcs = funcMap.values(category);
for (GeometryFunction fun : funcs) {
out.println(category + "." + functionDesc(fun));
}
}
}
Expand All @@ -147,29 +151,41 @@ private static String functionDesc(GeometryFunction fun) {
//return geomFun.getName();
}

private static GeometryFactory geomFactory = new GeometryFactory();
private GeometryFactory geomFactory = new GeometryFactory();

private static GeometryFunctionRegistry funcRegistry = GeometryFunctionRegistry.createTestBuilderRegistry();
private static CommandLine commandLine = createCmdLine();
private GeometryFunctionRegistry funcRegistry = GeometryFunctionRegistry.createTestBuilderRegistry();
private CommandLine commandLine = createCmdLine();

private boolean isHelp = false;
private boolean isHelpWithFunctions = false;
private boolean isVerbose = false;

private static boolean isHelp = false;
private static boolean isVerbose = false;
private CommandOutput out = new CommandOutput();

private static class CmdArgs {
static class CmdArgs {
String operation;
String geomA;
public String geomB;
public String arg1;

String format = null;
}


public JTSOpCmd() {

}

private void execute(CmdArgs cmdArgs) throws IOException, Exception {
public void captureOutput() {
out = new CommandOutput(true);
}

public String getOutput() {
return out.getOutput();
}
void execute(CmdArgs cmdArgs) throws IOException, Exception {
if (isHelp || isHelpWithFunctions) {
printHelp(isHelpWithFunctions);
return;
}

Geometry geomA = null;
Geometry geomB = null;
Expand Down Expand Up @@ -203,31 +219,43 @@ private void execute(CmdArgs cmdArgs) throws IOException, Exception {
private Object executeFunction(CmdArgs cmdArgs, Geometry geomA, Geometry geomB) {
GeometryFunction func = getFunction(cmdArgs.operation);
if (func == null) {
System.err.println("Function not found: " + cmdArgs.operation);
return null;
throw new CommandError(ERR_FUNCTION_NOT_FOUND, cmdArgs.operation);
}
checkFunctionArgs(func, geomB, cmdArgs.arg1);
Object funArgs[] = createFunctionArgs(func, geomB, cmdArgs.arg1);

if (isVerbose) {
System.out.println(writeOpSummary(cmdArgs));
out.println(writeOpSummary(cmdArgs));
}

Stopwatch timer = new Stopwatch();
Object result = null;
try {
result = func.invoke(geomA, funArgs);
} finally {
}
catch (NullPointerException ex) {
if (geomA == null)
throw new CommandError(ERR_REQUIRED_A, cmdArgs.operation);
// if A is present then must be something else
throw new CommandError(ERR_FUNCTION_ERR, ex.getMessage());
}
finally {
timer.stop();
}
if (isVerbose) {
System.out.println("Time: " + timer.getTimeString());
out.println("Time: " + timer.getTimeString());
}
return result;
}

private Geometry readGeometry(String arg) throws Exception, IOException {
if (isFilename(arg)) {
return IOUtil.readFile(arg ,geomFactory );
try {
return IOUtil.readFile(arg ,geomFactory );
}
catch (FileNotFoundException ex) {
throw new CommandError(ERR_FILE_NOT_FOUND, arg);
}
}
MultiFormatReader rdr = new MultiFormatReader(geomFactory);
return rdr.read(arg);
Expand Down Expand Up @@ -269,7 +297,7 @@ private void printResult(Object result, String outputFormat) {
printGeometry((Geometry) result, outputFormat);
return;
}
System.out.println(result);
out.println(result);
}

private void printGeometry(Geometry geom, String outputFormat) {
Expand All @@ -292,7 +320,7 @@ else if (outputFormat.equalsIgnoreCase(FORMAT_SVG)) {
}

if (txt == null) return;
System.out.println(txt);
out.println(txt);
}

private String writeGeoJSON(Geometry geom) {
Expand All @@ -305,7 +333,7 @@ private void printGeometrySummary(String label, Geometry geom, String arg) {
if (! isVerbose) return;
String filename = "";
if (arg != null & isFilename(arg)) filename = " -- " + arg;
System.out.println( writeGeometrySummary(label, geom) + filename);
out.println( writeGeometrySummary(label, geom) + filename);
}

private String writeGeometrySummary(String label,
Expand All @@ -318,6 +346,30 @@ private String writeGeometrySummary(String label,
buf.append(" " + GeometryUtil.metricsSummary(g));
return buf.toString();
}

private void checkFunctionArgs(GeometryFunction func, Geometry geomB, String arg1) {
Class[] paramTypes = func.getParameterTypes();
int nParam = paramTypes.length;

if (func.isBinary() && geomB == null)
throw new CommandError(ERR_REQUIRED_B);
/**
// MD not sure whether to check this?
if (! func.isBinary() && geomB != null)
throw new CommandError(ERR_REQUIRED_B);
*/

/*
* check count of supplied args.
* Assumes B has been checked.
*/
int argCount = 0;
if (func.isBinary() && geomB != null) argCount++;
if (arg1 != null) argCount++;
if (nParam != argCount) {
throw new CommandError(ERR_WRONG_ARG_COUNT, func.getName());
}
}

private Object[] createFunctionArgs(GeometryFunction func, Geometry geomB, String arg1) {
Class[] paramTypes = func.getParameterTypes();
Expand All @@ -328,6 +380,7 @@ private Object[] createFunctionArgs(GeometryFunction func, Geometry geomB, Strin
paramVal[0] = geomB;
iparam++;
}
// just handling one scalar arg for now
if (iparam < paramVal.length) {
paramVal[iparam] = SwingUtil.coerce(arg1, paramTypes[iparam]);
}
Expand All @@ -345,20 +398,25 @@ private GeometryFunction getFunction(String operation) {
return funcRegistry.find(category, name);
}

private static CmdArgs parseArgs(String[] args) throws ParseException, ClassNotFoundException {
commandLine.parse(args);

CmdArgs parseArgs(String[] args) throws ParseException, ClassNotFoundException {
CmdArgs cmdArgs = new CmdArgs();

if (args.length == 0) {
isHelp = true;
return cmdArgs;
}

commandLine.parse(args);

if (commandLine.hasOption(CommandOptions.GEOMFUNC)) {
Option opt = commandLine.getOption(CommandOptions.GEOMFUNC);
for (int i = 0; i < opt.getNumArgs(); i++) {
String geomFuncClassname = opt.getArg(i);
try {
funcRegistry.add(geomFuncClassname);
System.out.println("Added Geometry Functions from: " + geomFuncClassname);
out.println("Added Geometry Functions from: " + geomFuncClassname);
} catch (ClassNotFoundException ex) {
System.out.println("Unable to load function class: " + geomFuncClassname);
out.println("Unable to load function class: " + geomFuncClassname);
}
}
}
Expand All @@ -370,7 +428,7 @@ private static CmdArgs parseArgs(String[] args) throws ParseException, ClassNotF
cmdArgs.format = commandLine.getOptionArg(CommandOptions.FORMAT, 0);
isVerbose = commandLine.hasOption(CommandOptions.VERBOSE)
|| commandLine.hasOption(CommandOptions.V);
isHelp = commandLine.hasOption(CommandOptions.HELP);
isHelpWithFunctions = commandLine.hasOption(CommandOptions.HELP);

String[] freeArgs = commandLine.getOptionArgs(OptionSpec.OPTION_FREE_ARGS);
if (freeArgs != null) {
Expand All @@ -385,7 +443,7 @@ private static CmdArgs parseArgs(String[] args) throws ParseException, ClassNotF
return cmdArgs;
}

private static CommandLine createCmdLine() {
private CommandLine createCmdLine() {
commandLine = new CommandLine('-');
commandLine.addOptionSpec(new OptionSpec(CommandOptions.GEOMFUNC, OptionSpec.NARGS_ONE_OR_MORE))
.addOptionSpec(new OptionSpec(CommandOptions.VERBOSE, 0))
Expand Down
Loading

0 comments on commit 5ab5f90

Please sign in to comment.