This question elaborates on A command line reader buffering the commands in Java.
I have essentially hacked the entire code into classes and interfaces.
CommandLineReader.java
import java.util.Objects;
import java.util.Scanner;
import net.coderodde.command.line.CommandListener;
import net.coderodde.command.line.AbstractCommandProvider;
import net.coderodde.command.line.support.BufferedCommandProvider;
/**
* This class implements a command reader via standard input.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Jul 26, 2017)
*/
public final class CommandLineReader {
/**
* The quit command.
*/
private static final String QUIT_COMMAND = "quit";
/**
* The command line prompt.
*/
private static final String PROMPT = "> ";
/**
* The command provider.
*/
private final AbstractCommandProvider commandProvider;
/**
* Indicates whether the user requested the exit from the loop.
*/
private boolean done = false;
/**
* The scanner for reading the lines.
*/
private final Scanner scanner = new Scanner(System.in);
/**
* Constructs a command line reader with given command provider.
*
* @param commandProvider the command provider.
*/
public CommandLineReader(AbstractCommandProvider commandProvider) {
this.commandProvider = Objects.requireNonNull(
commandProvider,
"The input command provider is null.");
}
/**
* Runs the program.
*/
public void run() {
CommandListener commandListener1 = (String command) -> {
System.out.println(
command + commandProvider.getCommandSeparator());
if (command.equals(QUIT_COMMAND)) {
done = true;
}
};
CommandListener commandListener2 = (String command) -> {
System.err.println(
command + commandProvider.getCommandSeparator());
};
commandProvider.addCommandListener(commandListener2);
commandProvider.addCommandListener(commandListener1);
while (!done) {
System.out.print(PROMPT);
String line = scanner.nextLine();
commandProvider.processLine(line);
}
System.out.println("Bye!");
}
@Override
protected void finalize() {
scanner.close();
}
public static void main(String[] args) {
CommandLineReader commandLineReader =
new CommandLineReader(new BufferedCommandProvider());
commandLineReader.run();
}
}
AbstractCommandProvider.java
package net.coderodde.command.line;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* This abstract class defines some commonalities and API for command providers.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Jul 26, 2017)
*/
public abstract class AbstractCommandProvider {
/**
* The string used for separating adjacent commands.
*/
protected String commandSeparator;
/**
* List of command listeners.
*/
private final List<CommandListener> commandListenerList = new ArrayList<>();
/**
* Processes a line of text.
*
* @param line the line of text.
*/
public abstract void processLine(String line);
/**
* Returns the current command separator string.
*
* @return the command separator string.
*/
public String getCommandSeparator() {
return commandSeparator;
}
/**
* Sets the command separator string.
*
* @param commandSeparator the command separator.
*/
public void setCommandSeparator(String commandSeparator) {
this.commandSeparator = checkCommandSeparator(commandSeparator);
}
/**
* Adds a command listener to the current listener list.
*
* @param commandListener the command listener to add.
*/
public void addCommandListener(CommandListener commandListener) {
if (commandListener != null) {
commandListenerList.add(commandListener);
}
}
/**
* Removes a command listener from the current listener list.
*
* @param commandListener the command listener to remove.
*/
public void removeCommandListener(CommandListener commandListener) {
commandListenerList.remove(commandListener);
}
/**
* Broadcasts the command to all command listeners of this provider.
*
* @param command the command to broadcast.
*/
protected void broadcastCommand(String command) {
for (CommandListener commandListener : commandListenerList) {
commandListener.onCommand(command);
}
}
/**
* Checks that the command separator is not {@code null} and is not empty.
*
* @param commandSeparator the command separator to check.
*
* @return the input commandSeparator.
*
* @throws NullPointerException if the command separator is {@code null}.
* @throws IllegalArgumentException if the command separator is empty.
*/
private String checkCommandSeparator(String commandSeparator) {
Objects.requireNonNull(commandSeparator,
"The input command separator is null.");
if (commandSeparator.isEmpty()) {
throw new IllegalArgumentException(
"The command separator is empty.");
}
return commandSeparator;
}
}
CommandListener.java
package net.coderodde.command.line;
/**
* This interface defines the API for command line listeners.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Jul 26, 2017)
*/
public interface CommandListener {
/**
* Called on any new command.
*
* @param command the command text.
*/
public void onCommand(String command);
}
BufferedCommandProvider.java
package net.coderodde.command.line.support;
import java.util.ArrayList;
import java.util.List;
import net.coderodde.command.line.AbstractCommandProvider;
/**
* This class implements a command provider that caches the command lines until
* they form at least one full command. The commands are assumed to be separated
* via semicolons. The user may set other command separator.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Jul 26, 2017)
*/
public final class BufferedCommandProvider extends AbstractCommandProvider {
/**
* The default command separator.
*/
private static final String DEFAULT_COMMAND_SEPARATOR = ";";
/**
* Buffers the lines that do not yet form a single command.
*/
private final List<String> bufferedLineList = new ArrayList<>();
/**
* An auxiliary string buffer for manipulating the lines in the buffer list.
*/
private final StringBuffer stringBuffer = new StringBuffer();
/**
* Constructs a new buffered command provider using the given command
* separator.
*
* @param commandSeparator the string used for separating the commands.
*/
public BufferedCommandProvider(String commandSeparator) {
setCommandSeparator(commandSeparator);
}
/**
* Constructs a new buffered command provider using the default command
* separator.
*/
public BufferedCommandProvider() {
this(DEFAULT_COMMAND_SEPARATOR);
}
/**
* Processes the line of text.
*
* @param line the line to handle.
*/
@Override
public void processLine(String line) {
bufferedLineList.add(line);
clearStringBuffer();
for (String bufferedLine : bufferedLineList) {
stringBuffer.append(bufferedLine.trim()).append(' ');
}
String bufferContent = stringBuffer.toString().trim();
String[] bufferCommands = bufferContent.split(commandSeparator);
boolean lastCommandTerminated =
bufferContent.endsWith(commandSeparator);
if (!lastCommandTerminated && bufferCommands.length < 2) {
// We don't yet have any full command, so it remains stored in the
// line list.
return;
}
if (lastCommandTerminated) {
handleTerminated(bufferCommands);
} else {
handleNonTerminated(bufferCommands);
}
}
private void clearStringBuffer() {
stringBuffer.delete(0, stringBuffer.length());
}
private void handleTerminated(String[] bufferCommands) {
for (String bufferedCommand : bufferCommands) {
String command = bufferedCommand.trim();
broadcastCommand(command);
}
bufferedLineList.clear();
}
private void handleNonTerminated(String[] bufferCommands) {
for (int i = 0; i < bufferCommands.length - 1; ++i) {
String command = bufferCommands[i].trim();
broadcastCommand(command);
}
bufferedLineList.clear();
bufferedLineList.add(bufferCommands[bufferCommands.length - 1]);
}
}
Demo
Possible session might go like this:
> this is a > multiline command; another this is a multiline command; this is a multiline command; > come on!; another come on!; another come on!; > quit; quit; quit; Bye!
Critique request
Please tell me anything that comes to mind.