0
\$\begingroup\$

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.

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Some my remarks:

1) Working with done:

if (command.equals(QUIT_COMMAND)) {
    done = true;
}

can be simplified to

done = command.equals(QUIT_COMMAND)

2) Avoid usage of finalize, better to have a special close method and call it directly at the app end:

void closeScanner() {
    scanner.close();
}

3) Naming of collections in plural form:

private final List<CommandListener> commandListeners = new ArrayList<>();
private final List<String> bufferedLines = new ArrayList<>();

4) The validation of commandSeparator can be done in one line if you use Apache commons lang:

Validate.notEmpty(commandSeparator, "The commandSeparator couldn't be null or empty.");

5) By default all interface methods are already public:

void onCommand(String command);

6) Better to pass the given value to the parent constructor and remove the setter:

public BufferedCommandProvider(String commandSeparator) {
    super(commandSeparator);
}

7) Finally you may inline temporary local variables:

 broadcastCommand(bufferedCommand.trim());
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.