Skip to main content
1 of 3
Korey Hinton
  • 2.7k
  • 3
  • 21
  • 30

Memento with optional state?

Menu Program

I built a console-based menu program with multiple levels that selects a particular test to run. Each level more precisely describes the operation. At any level you can type back to go back one level (memento).

Level 1: Server Type?
             [1] Server A [2] Server B 
Level 2: Server environment?
          [1] test [2] production
Level 3: Test type?
          [1] load [2] unit
Level 4: Data Collection?
          [1] Legal docs [2] Corporate docs
Level 4.5 (optional): Load Test Type
          [2] Multi TIF [2] Single PDF
Level 5: Command Type?
          [1] Move [2] Copy [3] Remove [4] Custom
Level 6: Enter a keyword
          [setup, cleanup, run]

Design

Design

States

States

PROBLEM:

Right now the STATES enum is the determining factor as to what state is BACK and what state is NEXT yet it knows nothing about what the current memento state is.

Has anyone experienced a similar issue and found an effective way to handle mementos with optional state?

static enum STATES {
    SERVER, ENVIRONMENT, TEST_TYPE, COLLECTION, COMMAND_TYPE, KEYWORD, FINISHED
}

Possible Solution (Not-flexible)

In reference to my code below, every case statement in the Menu class could check the state of currentMemo and then set the STATE (enum) accordingly to pass to the Builder. However, this doesn't seem flexible very flexible to change and I'm struggling to see an effective way refactor the design.

class Menu extends StateConscious {
    private State state;
    private Scanner reader;
    private ServerUtils utility;

    Menu() {
        state = new State();
        reader = new Scanner(System.in);
        utility = new ServerUtils();
    }

    // Recurring menu logic
    public void startPromptingLoop() {

        List<State> states = new ArrayList<>();
        states.add(new State());

        boolean redoInput = false;
        boolean userIsDone = false;

        while (true) {
            // get Memento from last loop
            Memento currentMemento = states.get(states.size() - 1)
                    .saveMemento();
            if (currentMemento == null)
                currentMemento = new Memento.Builder(0).build();

            if (!redoInput)
                System.out.println(currentMemento.prompt);
            redoInput = false;

            // prepare Memento for next loop
            Memento nextMemento = null;
            STATES state = STATES.values()[states.size() - 1];

            // get user input
            String selection = reader.nextLine();

            switch (selection) {

            case "exit":
                reader.close();
                return; // only escape

            case "quit":
                nextMemento = new Memento.Builder(first(), currentMemento,
                        selection).build();
                states.clear();
                break;

            case "back":
                nextMemento = new Memento.Builder(previous(state),
                        currentMemento, selection).build();
                if (states.size() <= 1) {
                    states.remove(0);
                } else {
                    states.remove(states.size() - 1);
                    states.remove(states.size() - 1);
                }
                break;

            case "1":
                nextMemento = new Memento.Builder(next(state), currentMemento,
                        selection).build();
                break;

            case "2":
                nextMemento = new Memento.Builder(next(state), currentMemento,
                        selection).build();
                break;

            case "3":
                nextMemento = new Memento.Builder(next(state), currentMemento,
                        selection).build();
                break;

            case "4":
                nextMemento = new Memento.Builder(next(state), currentMemento,
                        selection).build();
                break;

            default:
                if (state.equals(STATES.CATEGORY)) {
                    String command = selection;
                    System.out.println("Executing " + command + " command on: "
                            + currentMemento.type + " "
                            + currentMemento.environment);
                    utility.executeCommand(currentMemento.nickname, command);
                    userIsDone = true;
                    states.clear();
                    nextMemento = new Memento.Builder(first(), currentMemento,
                            selection).build();
                } else if (state.equals(STATES.KEYWORD)) {
                    nextMemento = new Memento.Builder(next(state),
                            currentMemento, selection).build();
                    states.clear();
                    nextMemento = new Memento.Builder(first(), currentMemento,
                            selection).build();
                } else {

                    redoInput = true;
                    System.out.println("give it another try");
                    continue;
                }
                break;
            }

            if (userIsDone) {

                // start the recurring menu over from the beginning

                for (int i = 0; i < states.size(); i++) {
                    if (i != 0) {
                        states.remove(i); // remove all except first
                    }
                }
                reader = new Scanner(System.in);
                this.state = new State();
                userIsDone = false;
            }

            if (!redoInput) {
                this.state.restoreMemento(nextMemento);
                states.add(this.state);
            }
        }
    }
}
Korey Hinton
  • 2.7k
  • 3
  • 21
  • 30