summaryrefslogtreecommitdiff
path: root/base/src/main/java/bjc/utils/cli
diff options
context:
space:
mode:
Diffstat (limited to 'base/src/main/java/bjc/utils/cli')
-rw-r--r--base/src/main/java/bjc/utils/cli/StreamTerminal.java185
-rw-r--r--base/src/main/java/bjc/utils/cli/Terminal.java38
-rw-r--r--base/src/main/java/bjc/utils/cli/TerminalCodes.java8
-rw-r--r--base/src/main/java/bjc/utils/cli/objects/DelimSplitterCLI.java26
4 files changed, 199 insertions, 58 deletions
diff --git a/base/src/main/java/bjc/utils/cli/StreamTerminal.java b/base/src/main/java/bjc/utils/cli/StreamTerminal.java
index a45a22e..185891f 100644
--- a/base/src/main/java/bjc/utils/cli/StreamTerminal.java
+++ b/base/src/main/java/bjc/utils/cli/StreamTerminal.java
@@ -6,7 +6,16 @@ import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
+import java.util.function.Consumer;
+import bjc.data.Either;
+
+/**
+ * Implementation of {@link Terminal} using {@link Reader} and {@link Writer}
+ *
+ * @author bjcul
+ *
+ */
public class StreamTerminal implements Terminal, Runnable {
private SortedSet<Long> pendingRequests;
private ConcurrentMap<Long, String> pendingReplies;
@@ -21,9 +30,32 @@ public class StreamTerminal implements Terminal, Runnable {
private Scanner inputScanner;
private Writer output;
+ private String prefix;
+ private Consumer<String> mode;
+
private long currentRequest = -1;
+ /**
+ * Create a new stream terminal.
+ *
+ * @param input The input source
+ * @param output The output source
+ */
public StreamTerminal(Reader input, Writer output) {
+ this(input, output, null, null);
+ }
+
+ /**
+ * Create a new stream terminal which backs an application
+ *
+ * @param input The input source
+ * @param output The output source
+ * @param prefix The string any input must start with to be directed to the
+ * terminal
+ * @param mode The place where all input not directed to the terminal is
+ * written.
+ */
+ public StreamTerminal(Reader input, Writer output, String prefix, Consumer<String> mode) {
this.inputScanner = new Scanner(input);
this.output = output;
@@ -33,6 +65,9 @@ public class StreamTerminal implements Terminal, Runnable {
this.replyLock = new ReentrantLock();
this.replyCondition = replyLock.newCondition();
+
+ this.prefix = prefix;
+ this.mode = mode;
}
@Override
@@ -40,6 +75,9 @@ public class StreamTerminal implements Terminal, Runnable {
running = true;
try {
output.write(INFO_STARTCOMPROC.toString() + "\n");
+ while (!pendingOutput.isEmpty())
+ output.write(pendingOutput.remove());
+ output.flush();
} catch (IOException e) {
// TODO Consider if there is some better way to handle these
throw new RuntimeException(e);
@@ -49,57 +87,71 @@ public class StreamTerminal implements Terminal, Runnable {
try {
while (!pendingOutput.isEmpty())
output.write(pendingOutput.remove());
+ output.flush();
String ln = inputScanner.nextLine();
- String com = "";
- int spcIdx = ln.indexOf(' ');
- if (spcIdx == -1) {
- com = ln;
- } else {
- com = ln.substring(0, spcIdx);
- ln = ln.substring(spcIdx + 1);
- }
- comswt: switch (com) {
- case "r": {
- // General command format is 'r <request no.>,<reply>
- String subRep = ln.substring(2);
- // Process a reply
- int comIndex = subRep.indexOf(',');
- long repNo = 0;
- if (comIndex == -1) {
- // Reply to the oldest message by default
- repNo = pendingRequests.first();
+ if (prefix != null && ln.startsWith(prefix)) {
+ ln = ln.substring(prefix.length());
+ String com = "";
+ int spcIdx = ln.indexOf(' ');
+ if (spcIdx == -1) {
+ com = ln;
} else {
- String repStr = subRep.substring(0, comIndex);
- try {
- repNo = Long.parseLong(repStr);
- } catch (NumberFormatException nfex) {
- output.write(ERROR_INVREPNO.toString() + "\n");
- continue overall;
- }
- // Skip over the comma
- subRep = subRep.substring(comIndex + 1);
+ com = ln.substring(0, spcIdx);
+ ln = ln.substring(spcIdx + 1);
}
- if (!pendingRequests.contains(repNo)) {
- output.write(ERROR_UNKREPNO.toString() + "\n");
- continue overall;
- }
+ comswt: switch (com) {
+ case "r": {
+ // General command format is 'r <request no.>,<reply>
+ String subRep = ln.substring(2);
+ // Process a reply
+ int comIndex = subRep.indexOf(',');
+ long repNo = 0;
+ if (comIndex == -1) {
+ // Reply to the oldest message by default
+ repNo = pendingRequests.first();
+ } else {
+ String repStr = subRep.substring(0, comIndex);
+ try {
+ repNo = Long.parseLong(repStr);
+ } catch (NumberFormatException nfex) {
+ output.write(ERROR_INVREPNO.toString() + "\n");
+ output.flush();
+ continue overall;
+ }
+ // Skip over the comma
+ subRep = subRep.substring(comIndex + 1);
+ }
- pendingRequests.remove(repNo);
- pendingReplies.put(repNo, subRep);
+ if (!pendingRequests.contains(repNo)) {
+ output.write(ERROR_UNKREPNO.toString() + "\n");
+ output.flush();
+ continue overall;
+ }
- replyLock.lock();
- replyCondition.signalAll();
- replyLock.unlock();
- break comswt;
- }
- case "q":
- running = false;
- break comswt;
- default:
- output.write(ERROR_UNRECCOM.toString() + "\n");
+ pendingRequests.remove(repNo);
+ pendingReplies.put(repNo, subRep);
+
+ replyLock.lock();
+ replyCondition.signalAll();
+ replyLock.unlock();
+ break comswt;
+ }
+ case "q":
+ running = false;
+ break comswt;
+ default:
+ output.write(ERROR_UNRECCOM.toString() + "\n");
+ output.flush();
+ }
+ } else {
+ mode.accept(ln);
}
+
+ while (!pendingOutput.isEmpty())
+ output.write(pendingOutput.remove());
+ output.flush();
} catch (IOException ioex) {
throw new RuntimeException(ioex);
}
@@ -107,7 +159,10 @@ public class StreamTerminal implements Terminal, Runnable {
running = false;
try {
+ while (!pendingOutput.isEmpty())
+ output.write(pendingOutput.remove());
output.write(INFO_ENDCOMPROC.toString() + "\n");
+ output.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -139,6 +194,25 @@ public class StreamTerminal implements Terminal, Runnable {
}
@Override
+ public Optional<String> awaitReply(long id, TimeUnit unit, long delay) throws InterruptedException {
+ if (pendingReplies.containsKey(id))
+ return Optional.of(pendingReplies.get(id));
+ while (true) {
+ replyLock.lock();
+ boolean stat = replyCondition.await(delay, unit);
+ replyLock.unlock();
+
+ // If we timed out, say so
+ if (stat == false)
+ return Optional.empty();
+ // Explanation: Since the reply map is add-only, the lock isn't actually
+ // protecting anything. We just want to wait until a response is received.
+ if (pendingReplies.containsKey(id))
+ return Optional.of(pendingReplies.get(id));
+ }
+ }
+
+ @Override
public Optional<String> checkReply(long id) {
return Optional.ofNullable(pendingReplies.get(id));
}
@@ -147,6 +221,29 @@ public class StreamTerminal implements Terminal, Runnable {
public String submitRequestSync(String req) throws InterruptedException {
return awaitReply(submitRequest(req));
}
+
+ @Override
+ public Either<String, Long> submitRequestSync(String req, TimeUnit unit, long delay) throws InterruptedException {
+ long id = submitRequest(req);
+ Optional<String> rep = awaitReply(id, unit, delay);
+ return rep.isEmpty() ? Either.right(id) : Either.left(rep.get());
+ }
- // TODO add variants of the two blocking methods above with timeout support
+ /**
+ * Add a string to be printed next time output is printed.
+ *
+ * @param out The output
+ */
+ public void addOutput(String out) {
+ pendingOutput.add(out);
+ }
+
+ /**
+ * Set the mode for handling non-terminal input
+ *
+ * @param mode The new mode for handling non-terminal input
+ */
+ public void setMode(Consumer<String> mode) {
+ this.mode = mode;
+ }
} \ No newline at end of file
diff --git a/base/src/main/java/bjc/utils/cli/Terminal.java b/base/src/main/java/bjc/utils/cli/Terminal.java
index a10d82e..e8b3029 100644
--- a/base/src/main/java/bjc/utils/cli/Terminal.java
+++ b/base/src/main/java/bjc/utils/cli/Terminal.java
@@ -1,6 +1,9 @@
package bjc.utils.cli;
import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+import bjc.data.Either;
/**
* A terminal with support for asking multiple questions, and retrieving the
@@ -17,6 +20,7 @@ public interface Terminal {
* Submit a request to the terminal
*
* @param req The body of the request
+ *
* @return The ID of the request
*/
long submitRequest(String req);
@@ -26,15 +30,32 @@ public interface Terminal {
* response is available.
*
* @param id The ID of the request
+ *
* @return The response to that request
+ *
* @throws InterruptedException If we are interrupted waiting for the reply
*/
String awaitReply(long id) throws InterruptedException;
+
+ /**
+ * Await a reply for a given request. Will block the current thread until a
+ * response is available or the specified timeout occurs.
+ *
+ * @param id The ID of the request
+ * @param unit The unit of time to wait
+ * @param delay The amount of units to wait before timing out
+ *
+ * @return The response to that request, or an empty optional if it timed out
+ *
+ * @throws InterruptedException If we are interrupted waiting for the reply
+ */
+ Optional<String> awaitReply(long id, TimeUnit unit, long delay) throws InterruptedException;
/**
* Check if a reply for a request is available, without blocking.
*
* @param id The ID of the request
+ *
* @return The reply to the request if one is available, and empty otherwise
*/
Optional<String> checkReply(long id);
@@ -47,5 +68,20 @@ public interface Terminal {
* @return The reply to the request
* @throws InterruptedException If we are interrupted waiting for the reply
*/
- String submitRequestSync(String req) throws InterruptedException;;
+ String submitRequestSync(String req) throws InterruptedException;
+
+ /**
+ * Submit a request and await the reply to it. Will block the current thread
+ * until a response is received or the timeout occurs.
+ *
+ * @param req The request to submit
+ * @param unit The unit of time to wait
+ * @param delay The amount of units to wait before timing out
+ *
+ * @return The reply to the request, or the ID of the request if the timeout occurred
+ *
+ * @throws InterruptedException If we are interrupted waiting for the reply
+ */
+ Either<String, Long> submitRequestSync(String req, TimeUnit unit, long delay) throws InterruptedException;
+
} \ No newline at end of file
diff --git a/base/src/main/java/bjc/utils/cli/TerminalCodes.java b/base/src/main/java/bjc/utils/cli/TerminalCodes.java
index c1d7dfc..7e2206e 100644
--- a/base/src/main/java/bjc/utils/cli/TerminalCodes.java
+++ b/base/src/main/java/bjc/utils/cli/TerminalCodes.java
@@ -14,14 +14,22 @@ public enum TerminalCodes {
// The general idea of the format would be a tab-separated value file, with the
// first value being a command, and then the rest being the body of that command.
// Would also have the line-continuation feature
+ /** Alert for starting processing. */
INFO_STARTCOMPROC("IOLPI00001", "STARTING PROCESSING"),
+ /** Alert for ending processing. */
INFO_ENDCOMPROC("IOLPI00002", "ENDING PROCESSING"),
+ /** Error for an unknown command */
ERROR_UNRECCOM("IOINE00001", "UNRECOGNIZED COMMAND"),
+ /** Error for an invalid number format when identifying a reply. */
ERROR_INVREPNO("IOINE00002", "INVALID REPLY NUMBER FORMAT"),
+ /** Error for specifying an unrecognized reply. */
ERROR_UNKREPNO("IOINE00002", "UNKNOWN REPLY NUMBER"),
;
+
+ /** The code for this error. */
public final String code;
+ /** The summary message for this error. */
public final String message;
private TerminalCodes(String code, String message) {
diff --git a/base/src/main/java/bjc/utils/cli/objects/DelimSplitterCLI.java b/base/src/main/java/bjc/utils/cli/objects/DelimSplitterCLI.java
index 53d6d1e..ba478f7 100644
--- a/base/src/main/java/bjc/utils/cli/objects/DelimSplitterCLI.java
+++ b/base/src/main/java/bjc/utils/cli/objects/DelimSplitterCLI.java
@@ -60,24 +60,22 @@ public class DelimSplitterCLI {
* Run the tester interface.
*/
private void runLoop() {
- final Scanner scn = new Scanner(System.in);
+ try (Scanner scn = new Scanner(System.in)) {
+ System.out.print("Enter a command (blank line to quit): ");
+ String inp = scn.nextLine().trim();
+ System.out.println();
- System.out.print("Enter a command (blank line to quit): ");
- String inp = scn.nextLine().trim();
- System.out.println();
+ while (!inp.equals("")) {
+ handleCommand(inp, scn, true);
- while (!inp.equals("")) {
- handleCommand(inp, scn, true);
+ System.out.println();
- System.out.println();
+ System.out.print("Enter a command (blank line to quit): ");
+ inp = scn.nextLine();
- System.out.print("Enter a command (blank line to quit): ");
- inp = scn.nextLine();
-
- System.out.println();
+ System.out.println();
+ }
}
-
- scn.close();
}
/*
@@ -221,6 +219,8 @@ public class DelimSplitterCLI {
}
try (FileInputStream fis = new FileInputStream(pth)) {
+ @SuppressWarnings("resource")
+ // Handled by above
final Scanner scn = new Scanner(fis);
while (scn.hasNextLine()) {