The CUI Framework

The CUI framework provides:

1. A simple console user interface.

2. An extensible error handling strategy.

3. A simplified Model-View architecture.

4. The ability to save application data to secondary memory.

5. The ability to load application data from secondary memory.

6. Extensible help and about mechanisms.

CUI is simple to customize. The user only needs to provide a model that implements the Serializable interface, and a subclass of Console that provides an implementation of the abstract execute method.

See the Javadoc pages for more information.

Design

Console.java

package jutil;
import java.io.*;
abstract public class Console {

//Fields:

   protected BufferedReader stdin;
   protected PrintWriter stdout;
   protected PrintWriter stderr;
   protected String prompt = "-> ";
   protected Serializable model;
   private String fname = null;
   protected boolean unsavedChanges = false;

//Constructors:

   public Console(Serializable model) {
      this.model = model;
      stdout = new PrintWriter(
         new BufferedWriter(
            new OutputStreamWriter(System.out)), true);
      stderr = new PrintWriter(
         new BufferedWriter(
            new OutputStreamWriter(System.out)), true);
      stdin = new BufferedReader(
         new InputStreamReader(System.in));
   }

   public Console() { this(null); }

// The control loop:

   public void controlLoop() {

      String cmmd = " ";
      String result = " ";
      stdout.println("type \"help\" for commands");


      while(true) {
         try {
            stdout.print(prompt);
            stdout.flush(); // force the write
            cmmd = stdin.readLine();
            cmmd = cmmd.trim(); // trim white space

            if (cmmd.equalsIgnoreCase("quit")) {
               saveChanges();
               break;
            }

            if (cmmd.equalsIgnoreCase("help")) {
               help();
               continue;
            }

            if (cmmd.equalsIgnoreCase("about")) {
               about();
               continue;
            }

            if (cmmd.equalsIgnoreCase("save")) {
               save(false);
               stdout.println("done");
               continue;
            }

            if (cmmd.equalsIgnoreCase("save as")) {
               save(true);
               stdout.println("done");
               continue;
            }

            if (cmmd.equalsIgnoreCase("load")) {
               load();
               stdout.println("done");
               continue;
            }

            // application-specific command:
            result = execute(cmmd);
            stdout.println(result);

         } catch(AppError exp) {
            handle(exp);
            continue;
         } catch (Exception exp) {
            exp.printStackTrace();
            stderr.println("Serious error, " + exp);
            break;
         }
      } // while
      stdout.println("bye");
   } // controlLoop

// The abstract execute method:

   abstract protected String execute(String cmmd) throws AppError;

// Overidables:


   protected void help() {
      stdout.println("Console Help Menu:");
      stdout.println("   about:   displays application information");
      stdout.println("   help:    displays this message");
      stdout.println("   save:    saves model to a file");
      stdout.println("   save as: saves model to a new file");
      stdout.println("   load:    load model from a file");
      stdout.println("   quit:    terminate application");
   }


   protected void about() {
      stdout.println("Console Framework");
      stdout.println("copyright (c) 2001, all rights reserved\n");
   }


   protected void handle(AppError exp) {
      stderr.println("Application error, " + exp.getMessage());
   }

// Saving model to a file:


   protected void save(boolean saveAs) throws IOException {
      if (model != null && unsavedChanges) {
         if (saveAs) {
            saveChanges();
            return;
         }
         if (fname == null) fname = getFname();
         ObjectOutputStream obstream =
            new ObjectOutputStream(
               new FileOutputStream(fname));
         obstream.writeObject(model);
         obstream.flush();
         obstream.close();
         unsavedChanges = false;
      }
   }

// Load model:

   protected void load() throws IOException, ClassNotFoundException {
      if (model != null) {
         saveChanges();
         String fname = getFname();
         ObjectInputStream ois =
            new ObjectInputStream(new FileInputStream(fname));
         model = (Serializable) ois.readObject();
         ois.close();
         unsavedChanges = false;
      }
   }

// Supporting methods:

// Save changes?

   private void saveChanges() throws IOException {
      if (model != null && unsavedChanges) {
         stdout.print("Save changes?(y/n): ");
         stdout.flush(); // force the write
         String response = stdin.readLine();
         if (response.equals("y"))
            save(false);
         else
            stdout.println("changes discarded");
      }
   }

// Prompt user for a file name:

   private String getFname() throws IOException {
      stdout.print("enter file name: ");
      stdout.flush(); // force the write
      String result = stdin.readLine();
      return result;
   }
} // Console

AppError.java

package jutil;

public class AppError extends Exception {
   public AppError(String g) { super(g); }
   public AppError() { this("unknown"); }
}

Customizing CUI: ATM

ATM is a simplified account manager. It allows users to load a single account, make deposits and withdrawals from this account, save the account.

BankAccount.java

import jutil.*;
import java.io.*;

public class BankAccount implements Serializable {
   private double balance = 0;
   public double getBalance() { return balance; }
   public void withdraw(double amt) throws AppError {
      if (balance < amt) throw new AppError("Insufficient funds");
      balance -= amt;
   }
   public void deposit(double amt) {
      balance += amt;
   }
}

ATM.java

import jutil.*;
import java.util.*;

public class ATM extends Console {
   public ATM(BankAccount acct) { super(acct); }
   public ATM() { this(new BankAccount()); }
   public BankAccount getModel() { return (BankAccount)model; }

   protected String execute(String cmmd) throws AppError {
      StringTokenizer tokens = new StringTokenizer(cmmd);
      if (!tokens.hasMoreTokens())
         throw new AppError("Type help for commands");


      String operator = tokens.nextToken();
      if (operator.equals("balance")) {
         return "$" + getModel().getBalance();
      }
      if (operator.equals("deposit")) {
         double amount = getOperand(tokens);
         getModel().deposit(amount);
         unsavedChanges = true;
         return "done";
      }
      if (operator.equals("withdraw")) {
         double amount = getOperand(tokens);
         getModel().withdraw(amount);
         unsavedChanges = true;
         return "done";
      }
      throw new AppError("Unrecognized command: " + operator);
   }

// Fetching the operand:

   private double getOperand(StringTokenizer tokens)
      throws AppError {
      if (!tokens.hasMoreTokens())
         throw new AppError("Amount required");
      try {
         Double amt = new Double(tokens.nextToken());
         return amt.doubleValue();
      } catch(NumberFormatException e) {
         throw new AppError("Amount must be a number");
      }
   }

// Overriding help and about:

   protected void help() {
      super.help();
      stdout.println("ATM Help Menu:");
      stdout.println("   balance:         displays current balance");
      stdout.println("   withdraw AMT:    withdraws $AMT");
      stdout.println("   deposit AMT:     deposits $AMT");
   }

   protected void about() {
      super.about();
      stdout.println("ATM Demo");
      stdout.println("copyright (c) 2004, all rights reserved\n");
   }

// Starting ATM:

   public static void main(String[] args) {
      ATM console = new ATM();
      console.controlLoop();
   }
} // ATM

Running ATM

Session 1

type "help" for commands
-> help
Console Help Menu:
   about:   displays application information
   help:    displays this message
   save:    saves model to a file
   save as: saves model to a new file
   load:    load model from a file
   quit:    terminate application
ATM Help Menu:
   balance:             displays current balance
   withdraw AMT:        withdraws $AMT
   deposit AMT:         deposits $AMT
-> about
Console Framework
copyright (c) 2001, all rights reserved

ATM Demo
copyright (c) 2004, all rights reserved

-> deposit 50
done
-> balance
$50.0
-> withdraw 15
done
-> balance
$35.0
-> transfer 100
Application error, Unrecognized command: transfer
-> withdraw
Application error, Amount required
-> withdraw ten
Application error, Amount must be a number
-> withdraw 100
Application error, Insufficient funds
-> withdraw 10
done
-> balance
$25.0
-> quit
Save changes?(y/n): y
enter file name: savings
bye

Session 2

type "help" for commands
-> deposit 1500
done
-> balance
$1500.0
-> load
Save changes?(y/n): y
enter file name: checking
enter file name: savings
done
-> balance
$25.0
-> deposit 30
done
-> quit
Save changes?(y/n): n
changes discarded
bye