Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions src/main/java/chapter14/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package chapter14;

import chapter14.args.Args;

import java.text.ParseException;

class Application {

public static void main(String[] args) {
try {
Args arg = new Args("l,p#,d*", args);
boolean logging = arg.getBoolean('l');
int port = arg.getInt('p');
String directory = arg.getString('d');
executeApplication(logging, port, directory);
} catch (ParseException e) {
System.out.printf("Parse error: %s\n", e.getMessage());
}
}

// stub
private static void executeApplication(boolean logging, int port, String directory) {
System.out.printf("logging: %b, port: %d, directory: %s\n", logging, port, directory);
}
}
263 changes: 263 additions & 0 deletions src/main/java/chapter14/args/Args.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
package chapter14.args;

import java.text.ParseException;
import java.util.*;

public class Args {
private String schema;
private String[] args;
private boolean valid = true;
private Set<Character> unexpectedArguments = new TreeSet<>();
private Map<Character, Boolean> booleanArgs = new HashMap<>();
private Map<Character, String> stringArgs = new HashMap<>();
private Map<Character, Integer> intArgs = new HashMap<>();
private Set<Character> argsFound = new HashSet<>();
private int currentArgument;
private char errorArgumentId = '\0';
private String errorParameter = "TILT";
private ErrorCode errorCode = ErrorCode.OK;

enum ErrorCode {
OK, MISSING_STRING, MISSING_INTEGER, INVALID_INTEGER, UNEXPECTED_ARGUMENT
}

public Args(String schema, String[] args) throws ParseException {
this.schema = schema;
this.args = args;
valid = parse();
}

private boolean parse() throws ParseException {
if (schema.length() == 0 && args.length == 0)
return true;
parseSchema();
try {
parseArguments();
} catch (ArgsException e) {
}
return valid;
}

private boolean parseSchema() throws ParseException {
for (String element : schema.split(",")) {
if (element.length() > 0) {
String trimmedElement = element.trim();
parseSchemaElement(trimmedElement);
}
}
return true;
}

private void parseSchemaElement(String element) throws ParseException {
char elementId = element.charAt(0);
String elementTail = element.substring(1);
validateSchemaElementId(elementId);
if (isBooleanSchemaElement(elementTail))
parseBooleanSchemaElement(elementId);
else if (isStringSchemaElement(elementTail))
parseStringSchemaElement(elementId);
else if (isIntegerSchemaElement(elementTail))
parseIntegerSchemaElement(elementId);
else {
throw new ParseException(
String.format("Argument: %c has invalid format: %s.",
elementId, elementTail), 0);
}
}

private void validateSchemaElementId(char elementId) throws ParseException {
if (!Character.isLetter(elementId)) {
throw new ParseException(
"Bad character: " + elementId + " in Args format: " + schema, 0);
}
}

private void parseBooleanSchemaElement(char elementId) {
booleanArgs.put(elementId, false);
}

private void parseIntegerSchemaElement(char elementId) {
intArgs.put(elementId, 0);
}

private void parseStringSchemaElement(char elementId) {
stringArgs.put(elementId, "");
}

private boolean isStringSchemaElement(String elementTail) {
return elementTail.equals("*");
}

private boolean isBooleanSchemaElement(String elementTail) {
return elementTail.length() == 0;
}

private boolean isIntegerSchemaElement(String elementTail) {
return elementTail.equals("#");
}

private boolean parseArguments() throws ArgsException {
for (currentArgument = 0; currentArgument < args.length; currentArgument++) {
String arg = args[currentArgument];
parseArgument(arg);
}
return true;
}

private void parseArgument(String arg) throws ArgsException {
if (arg.startsWith("-"))
parseElements(arg);
}

private void parseElements(String arg) throws ArgsException {
for (int i = 1; i < arg.length(); i++) {
parseElement(arg.charAt(i));
}
}

private void parseElement(char argChar) throws ArgsException {
if (setArgument(argChar))
argsFound.add(argChar);
else {
unexpectedArguments.add(argChar);
errorCode = ErrorCode.UNEXPECTED_ARGUMENT;
valid = false;
}
}

private boolean setArgument(char argChar) throws ArgsException {
if (isBooleanArg(argChar))
setBooleanArg(argChar, true);
else if (isStringArg(argChar))
setStringArg(argChar);
else if (isIntArg(argChar))
setIntArg(argChar);
else
return false;

return true;
}

private boolean isIntArg(char argChar) {
return intArgs.containsKey(argChar);
}

private void setIntArg(char argChar) throws ArgsException {
currentArgument++;
String parameter = null;
try {
parameter = args[currentArgument];
intArgs.put(argChar, new Integer(parameter));
} catch (ArrayIndexOutOfBoundsException e) {
valid = false;
errorArgumentId = argChar;
errorCode = ErrorCode.MISSING_INTEGER;
throw new ArgsException();
} catch (NumberFormatException e) {
valid = false;
errorArgumentId = argChar;
errorParameter = parameter;
errorCode = ErrorCode.INVALID_INTEGER;
throw new ArgsException();
}
}

private void setStringArg(char argChar) {
currentArgument++;
try {
stringArgs.put(argChar, args[currentArgument]);
} catch (ArrayIndexOutOfBoundsException e) {
valid = false;
errorArgumentId = argChar;
errorCode = ErrorCode.MISSING_STRING;
}
}

private boolean isStringArg(char argChar) {
return stringArgs.containsKey(argChar);
}

private void setBooleanArg(char argChar, boolean value) {
booleanArgs.put(argChar, value);
}

private boolean isBooleanArg(char argChar) {
return booleanArgs.containsKey(argChar);
}

public int cardinality() {
return argsFound.size();
}

public String usage() {
if (schema.length() > 0)
return "-[" + schema + "]";
else
return "";
}

public String errorMessage() throws Exception {
switch (errorCode) {
case OK:
throw new Exception("TILT: Should not get here.");
case UNEXPECTED_ARGUMENT:
return unexpectedArgumentMessage();
case MISSING_STRING:
return String.format("Could not find string parameter for -%c.",
errorArgumentId);
case INVALID_INTEGER:
return String.format("Argument -%c expects an integer but was '%s'.",
errorArgumentId, errorParameter);
case MISSING_INTEGER:
return String.format("Could not find integer parameter for -%c.",
errorArgumentId);
}
return "";

}

private String unexpectedArgumentMessage() {
StringBuffer message = new StringBuffer("Argument(s) -");
for (char c : unexpectedArguments) {
message.append(c);
}
message.append(" unexpected.");

return message.toString();
}

private boolean falseIfNull(Boolean b) {
return b == null ? false : b;
}

private int zeroIfNull(Integer i) {
return i == null ? 0 : i;
}

private String blankIfNull(String s) {
return s == null ? "" : s;
}

public String getString(char arg) {
return blankIfNull(stringArgs.get(arg));
}

public int getInt(char arg) {
return zeroIfNull(intArgs.get(arg));
}

public boolean getBoolean(char arg) {
return falseIfNull(booleanArgs.get(arg));
}

public boolean has(char arg) {
return argsFound.contains(arg);
}

public boolean isValid() {
return valid;
}

private class ArgsException extends Exception {
}
}
73 changes: 73 additions & 0 deletions src/test/java/chapter14/ApplicationTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package chapter14;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class ApplicationTest extends MainMethodTest {

@Test
void booleanArguments() {
//given
String[] args = new String[]{"-l"};

//when
runMain(args);

//then
assertThat(output()).contains("true");
}

@Test
void stringArguments() {
//given
String[] args = new String[]{"-d", "root"};

//when
runMain(args);

//then
assertThat(output()).contains("root");
}

@Test
void integerArguments() {
//given
String[] args = new String[]{"-p", "42"};

//when
runMain(args);

//then
assertThat(output()).contains("42");
}

@Test
void allArgumentsPresent() {
//given
String[] args = new String[]{"-l", "-p", "8080", "-d", "user"};

//when
runMain(args);

//then
assertThat(output()).isEqualTo("logging: true, port: 8080, directory: user");
}

@Test
void noArguments() {
//given
String[] args = new String[0];

//when
runMain(args);

//then
assertThat(output()).isEqualTo("logging: false, port: 0, directory:");
}

@Override
protected void runMain(String... args) {
Application.main(args);
}
}
Loading