// joi/9/bank/Bank.java
//
//
// Copyright 2003 Bill Campbell and Ethan Bolker
import java.util.*;
import java.io.*;
/**
* A Bank object simulates the behavior of a simple bank/ATM.
* It contains a Terminal object and a collection of
* BankAccount objects.
*
* The visit method opens this Bank for business,
* prompting the customer for input.
*
* It is persistent: it can save its state to a file and read it
* back at a later time.
*
* To create a Bank and open it for business issue the command
* java Bank
with appropriate arguments.
*
* @see BankAccount
* @version 9
*/
public class Bank
implements Serializable
{
private String bankName; // the name of this Bank
private transient Terminal atm; // for communication with world
private int balance = 0; // total cash on hand
private int transactionCount = 0; // number of Bank transactions
private Month month; // the current month.
private Map accountList; // mapping names to accounts.
private int checkFee = 2; // cost for each check
private int transactionFee = 1; // fee for each transaction
private int monthlyCharge = 5; // monthly charge
private double interestRate = 0.05; // annual rate paid on savings
private int maxFreeTransactions = 3; // for savings accounts
// what the banker can ask of the bank
private static final String BANKER_COMMANDS =
"Banker commands: " +
"exit, open, customer, nextmonth, report, help.";
// what the customer can ask of the bank
private static final String CUSTOMER_TRANSACTIONS =
" Customer transactions: deposit, withdraw, transfer,\n" +
" balance, cash check, quit, help.";
/**
* Construct a Bank with the given name.
*
* @param bankName the name for this Bank.
*/
public Bank( String bankName )
{
this.atm = atm;
this.bankName = bankName;
accountList = new TreeMap();
month = new Month();
}
/**
* Simulates interaction with a Bank.
* Presents the user with an interactive loop, prompting for
* banker transactions and in the case of the banker
* transaction "customer", an account id and further
* customer transactions.
*/
public void visit()
{
instructUser();
String command;
while (!(command =
atm.readWord("banker command: ")).equals("exit")) {
if (command.startsWith("h")) {
help( BANKER_COMMANDS );
}
else if (command.startsWith("o")) {
openNewAccount();
}
else if (command.startsWith("n")) {
newMonth();
}
else if (command.startsWith("r")) {
report();
}
else if (command.startsWith( "c" ) ) {
BankAccount acct = whichAccount();
if ( acct != null ) {
processTransactionsForAccount( acct );
}
}
else {
// Unrecognized Request
atm.println( "unknown command: " + command );
}
}
report();
atm.println( "Goodbye from " + bankName );
}
// Open a new bank account,
// prompting the user for information.
private void openNewAccount()
{
String accountName = atm.readWord( "Account name: " );
char accountType =
atm.readChar( "Type of account (r/c/f/s): " );
try {
int startup = readPosAmt( "Initial deposit: " );
BankAccount newAccount;
switch( accountType ) {
case 'c':
newAccount = new CheckingAccount( startup, this );
break;
case 'f':
newAccount = new FeeAccount( startup, this );
break;
case 's':
newAccount = new SavingsAccount( startup, this );
break;
case 'r':
newAccount = new RegularAccount( startup, this );
break;
default:
atm.println("invalid account type: " + accountType);
return;
}
accountList.put( accountName, newAccount );
atm.println( "opened new account " + accountName
+ " with $" + startup );
}
catch (NegativeAmountException e) {
atm.errPrintln(
"You cannot open an account with a negative balance");
}
catch (InsufficientFundsException e) {
atm.errPrintln( "Initial deposit doesn't cover fee" );
}
}
// Prompt the customer for transaction to process.
// Then send an appropriate message to the account.
private void processTransactionsForAccount( BankAccount acct )
{
help( CUSTOMER_TRANSACTIONS );
String transaction;
while (!(transaction =
atm.readWord(" transaction: ")).equals("quit")) {
try {
if ( transaction.startsWith( "h" ) ) {
help( CUSTOMER_TRANSACTIONS );
}
else if ( transaction.startsWith( "d" ) ) {
int amount = readPosAmt( " amount: " );
atm.println(" deposited "
+ acct.deposit( amount ));
}
else if ( transaction.startsWith( "w" ) ) {
int amount = readPosAmt( " amount: " );
atm.println(" withdrew "
+ acct.withdraw( amount ));
}
else if ( transaction.startsWith( "c" ) ) {
int amount = readPosAmt( " amount of check: " );
try { // to cast acct to CheckingAccount ...
atm.println(" cashed check for " +
((CheckingAccount) acct).honorCheck( amount ));
}
catch (ClassCastException e) {
// if not a checking account, report error
atm.errPrintln(
" Sorry, not a checking account." );
}
}
else if (transaction.startsWith("t")) {
atm.print( " to ");
BankAccount toacct = whichAccount();
if (toacct != null) {
int amount = readPosAmt(" amount to transfer: ");
atm.println(" transfered "
+ toacct.deposit(acct.withdraw(amount)));
}
}
else if (transaction.startsWith("b")) {
atm.println(" current balance "
+ acct.requestBalance());
}
else {
atm.println(" sorry, unknown transaction" );
}
}
catch (InsufficientFundsException e) {
atm.errPrintln( " Insufficient funds " +
e.getMessage() );
}
catch (NegativeAmountException e) {
atm.errPrintln(" Sorry, negative amounts disallowed." );
}
atm.println();
}
}
// Prompt for an account name (or number), look it up
// in the account list. If it's there, return it;
// otherwise report an error and return null.
private BankAccount whichAccount()
{
String accountName = atm.readWord( "account name: " );
BankAccount account = (BankAccount) accountList.get(accountName);
if (account == null) {
atm.println( "not a valid account" );
}
return account;
}
// Action to take when a new month starts.
// Update the month field by sending a next message.
// Loop on all accounts, sending each a newMonth message.
private void newMonth()
{
month.next();
Iterator i = accountList.keySet().iterator();
while ( i.hasNext() ) {
String name = (String) i.next();
BankAccount acct = (BankAccount) accountList.get( name );
try {
acct.newMonth();
}
catch (InsufficientFundsException exception) {
atm.errPrintln( "Insufficient funds in account \"" +
name + "\" for monthly fee" );
}
}
}
// Report bank activity.
// For each BankAccount, print the customer id (name or number),
// account balance and the number of transactions.
// Then print Bank totals.
private void report()
{
atm.println( bankName + " report for " + month );
atm.println( "\nSummaries of individual accounts:" );
atm.println( "account balance transaction count" );
for (Iterator i = accountList.keySet().iterator();
i.hasNext(); ) {
String accountName = (String) i.next();
BankAccount acct = (BankAccount) accountList.get(accountName);
atm.println(accountName + "\t$" + acct.getBalance() + "\t\t"
+ acct.getTransactionCount());
}
atm.println( "\nBank totals");
atm.println( "open accounts: " + getNumberOfAccounts() );
atm.println( "cash on hand: $" + getBalance());
atm.println( "transactions: " + getTransactionCount());
atm.println();
}
// Welcome the user to the bank and instruct her on
// her options.
private void instructUser()
{
atm.println( "Welcome to " + bankName );
atm.println( month.toString() );
atm.println( "Open some accounts and work with them." );
help( BANKER_COMMANDS );
}
// Display a help string.
private void help( String helpString )
{
atm.println( helpString );
atm.println();
}
// Read amount prompted for from the atm.
// Throw a NegativeAmountException if amount < 0
private int readPosAmt( String prompt )
throws NegativeAmountException
{
int amount = atm.readInt( prompt );
if (amount < 0) {
throw new NegativeAmountException();
}
return amount;
}
/**
* Increment bank balance by given amount.
*
* @param amount the amount increment.
*/
public void incrementBalance(int amount)
{
balance += amount;
}
/**
* Increment by one the count of transactions,
* for this bank.
*/
public void countTransaction()
{
transactionCount++;
}
/**
* Get the number of transactions performed by this bank.
*
* @return number of transactions performed.
*/
public int getTransactionCount( )
{
return transactionCount ;
}
/**
* The charge this bank levies for cashing a check.
*
* @return check fee
*/
public int getCheckFee( )
{
return checkFee ;
}
/**
* The charge this bank levies for a transaction.
*
* @return the transaction fee
*/
public int getTransactionFee( )
{
return transactionFee ;
}
/**
* The charge this bank levies each month.
*
* @return the monthly charge
*/
public int getMonthlyCharge( )
{
return monthlyCharge;
}
/**
* The current interest rate on savings.
*
* @return the interest rate
*/
public double getInterestRate( )
{
return interestRate;
}
/**
* The number of free transactions per month.
*
* @return the number of transactions
*/
public int getMaxFreeTransactions()
{
return maxFreeTransactions;
}
/**
* Get the current bank balance.
*
* @return current bank balance.
*/
public int getBalance()
{
return balance;
}
/**
* Get the current number of open accounts.
*
* @return number of open accounts.
*/
public int getNumberOfAccounts()
{
return accountList.size();
}
/**
* Set the atm for this Bank.
*
* @param atm the Bank's atm.
*/
public void setAtm( Terminal atm ) {
this.atm = atm;
}
/**
* Run the simulation by creating and then visiting a new Bank.
*
* A -e argument causes the input to be echoed. * This can be useful for executing the program against * a test script, e.g., *
* java Bank -e < Bank.in ** * The -f argument reads the bank's state from the specified * file, and writes it to that file when the program exits. * * @param args the command line arguments: *
* -e echo input. * -f filename * bankName any other command line argument. **/ public static void main( String[] args ) { boolean echo = false; String bankFileName = null; String bankName = "Persistent Bank"; Bank theBank = null; // parse the command line arguments for (int i = 0; i < args.length; i++ ) { if (args[i].equals("-e")) { // echo input to output echo = true; continue; } if (args[i].equals("-f")) { // read/write Bank from/to file bankFileName = args[++i]; continue; } } // create a new Bank or read one from a file if (bankFileName == null) { theBank = new Bank( bankName ); } else { theBank = readBank( bankName, bankFileName ); } // give the Bank a Terminal, then visit theBank.setAtm(new Terminal(echo)); theBank.visit(); // write theBank's state to a file if required if (bankFileName != null) { writeBank(theBank, bankFileName); } } // Read a Bank from a file (create it if file doesn't exist). // // @param bankName the name of the Bank // @param bankFileName the name of the file containing the Bank // // @return the Bank private static Bank readBank(String bankName, String bankFileName) { File file = new File( bankFileName ); if (!file.exists()) { return new Bank( bankName ); } ObjectInputStream inStream = null; try { inStream = new ObjectInputStream( new FileInputStream( file ) ); Bank bank = (Bank)inStream.readObject(); System.out.println( "Bank state read from file " + bankFileName); return bank; } catch (Exception e ) { System.err.println( "Problem reading " + bankFileName ); System.err.println(e); System.exit(1); } finally { try { inStream.close(); } catch (Exception e) { } } return null; // you can never get here } // Write a Bank to a file. // // @param bank the Bank // @param fileName the name of the file to write the Bank to private static void writeBank( Bank bank, String fileName) { ObjectOutputStream outStream = null; try { outStream = new ObjectOutputStream( new FileOutputStream( fileName ) ); outStream.writeObject( bank ); System.out.println( "Bank state written to file " + fileName); } catch (Exception e ) { System.err.println( "Problem writing " + fileName ); } finally { try { outStream.close(); } catch (Exception e ) { } } } }