1   // joi/7/bank/Bank.java                         
2   //                                                            
3   //                                                            
4   // Copyright 2003 Bill Campbell and Ethan Bolker                         
5                                                               
6   import java.util.*;
7   
8   /**
9    * A Bank object simulates the behavior of a simple bank/ATM.
10   * It contains a Terminal object and a collection of 
11   * BankAccount objects.
12   *
13   * The visit method opens this Bank for business,
14   * prompting the customer for input.
15   *
16   * To create a Bank and open it for business issue the command 
17   * <code>java Bank</code>.
18   *
19   * @see BankAccount
20   * @version 7
21   */
22  
23  public class Bank
24  {
25      private String bankName;           // the name of this Bank 
26      private Terminal atm;              // for talking with the customer
27      private int balance = 0;           // total cash on hand
28      private int transactionCount = 0;  // number of Bank transactions
29      private Month month;               // the current month.
30      private Map accountList;           // mapping names to accounts.
31  
32      private int checkFee = 2;            // cost for each check
33      private int transactionFee = 1;      // fee for each transaction
34      private int monthlyCharge = 5;       // monthly charge
35      private double interestRate = 0.05;  // annual rate paid on savings
36      private int maxFreeTransactions = 3; // for savings accounts
37  
38      // what the banker can ask of the bank
39  
40      private static final String BANKER_COMMANDS =
41      "Banker commands: " +
42          "exit, open, customer, nextmonth, report, help.";
43   
44      // what the customer can ask of the bank
45  
46      private static final String CUSTOMER_TRANSACTIONS =
47      "    Customer transactions: " +
48      "deposit, withdraw, transfer, balance, cash check, quit, help.";
49  
50      /**
51       * Construct a Bank with the given name and Terminal.
52       * 
53       * @param bankName the name for this Bank.
54       * @param atm  this Bank's Terminal.
55       */
56  
57      public Bank( String bankName, Terminal atm )
58      {
59          this.atm      = atm;
60          this.bankName = bankName;
61          accountList   = new TreeMap();
62          month         = new Month();
63      }
64  
65      /**
66       * Simulates interaction with a Bank.
67       * Presents the user with an interactive loop, prompting for
68       * banker transactions and in the case of the banker 
69       * transaction "customer", an account id and further 
70       * customer transactions.
71       */    
72  
73      public void visit()
74      {
75          instructUser();
76          
77          String command;
78          while (!(command =
79                   atm.readWord("banker command: ")).equals("exit")) {
80      
81              if (command.startsWith("h")) {
82                  help( BANKER_COMMANDS );
83              }
84              else if (command.startsWith("o")) {
85                  openNewAccount();
86              }
87              else if (command.startsWith("n")) {
88                  newMonth();
89              }
90              else if (command.startsWith("r")) {
91                  report();
92              }
93              else if (command.startsWith( "c" ) ) {
94                  BankAccount acct = whichAccount();
95                  if ( acct != null ) {
96                          processTransactionsForAccount( acct );
97                  }
98              }
99              else {
100                 // Unrecognized Request
101                 atm.println( "unknown command: " + command );
102             }
103         }
104         report();
105         atm.println( "Goodbye from " + bankName );
106     }
107 
108 
109     // Open a new bank account, 
110     // prompting the user for information.
111 
112     private void openNewAccount()  
113     {
114         String accountName = atm.readWord("Account name: ");
115         char accountType = 
116             atm.readChar( "Type of account (r/c/f/s): " );
117         try {
118             int startup = readPosAmt( "Initial deposit: " );
119             BankAccount newAccount;
120             switch( accountType ) {
121             case 'c': 
122                 newAccount = new CheckingAccount(startup, this);
123                 break;
124             case 'f': 
125                 newAccount = new FeeAccount(startup, this);
126                 break;
127             case 's':
128                 newAccount = new SavingsAccount(startup, this);
129                 break;
130             case 'r': 
131                 newAccount = new RegularAccount( startup, this );
132                 break;
133             default:  
134                 atm.println("invalid account type: " + accountType);
135                 return;
136             }
137             accountList.put( accountName, newAccount );
138             atm.println( "opened new account " + accountName
139                          + " with $" + startup );
140         } // end of try block
141         catch (NegativeAmountException e) {
142             atm.errPrintln(
143               "can't start with a negative balance");
144         }
145         catch (InsufficientFundsException e) {
146             atm.errPrintln("Initial deposit less than fee");
147         }
148     }
149 
150     // Prompt the customer for transaction to process.
151     // Then send an appropriate message to the account.
152 
153     private void processTransactionsForAccount( BankAccount acct ) 
154     {
155         help( CUSTOMER_TRANSACTIONS );
156 
157         String transaction;
158         while (!(transaction =
159                  atm.readWord("    transaction: ")).equals("quit")) {
160 
161             try {
162                 if ( transaction.startsWith( "h" ) ) {
163                     help( CUSTOMER_TRANSACTIONS );
164                 }
165                 else if ( transaction.startsWith( "d" ) ) {
166                     int amount = readPosAmt( "    amount: " );
167                     atm.println("    deposited " 
168                                 + acct.deposit( amount ));
169                 }
170                 else if ( transaction.startsWith( "w" ) ) {
171                     int amount = readPosAmt( "    amount: " );
172                     atm.println("    withdrew " 
173                                 + acct.withdraw( amount ));
174                 }
175                 else if ( transaction.startsWith( "c" ) ) {
176                     int amount = readPosAmt( "    amount of check: " );
177                     try { // to cast acct to CheckingAccount ...
178                         atm.println("    cashed check for " +
179                            ((CheckingAccount) acct).honorCheck( amount ));
180                     }
181                     catch (ClassCastException e) {
182                         // if not a checking account, report error
183                         atm.errPrintln(
184                             "    Sorry, not a checking account." );
185                     }
186                 }
187                 else if (transaction.startsWith("t")) {
188                     atm.print( "    to ");
189                     BankAccount toacct = whichAccount();
190                     if (toacct != null) {
191                     int amount = readPosAmt("    amount to transfer: ");
192                     atm.println("    transfered " 
193                         + toacct.deposit(acct.withdraw(amount)));
194                     }
195                 }
196                 else if (transaction.startsWith("b")) {
197                     atm.println("    current balance " 
198                                 + acct.requestBalance());
199                 }
200                 else {
201                     atm.println("    sorry, unknown transaction" );
202                 }
203             }
204             catch (InsufficientFundsException e) {
205                 atm.errPrintln( "    Insufficient funds " +
206                                 e.getMessage() );
207             }
208             catch (NegativeAmountException e) {
209               atm.errPrintln("    Sorry, negative amounts disallowed." );
210             }
211             atm.println();
212         }
213     }
214 
215     // Prompt for an account name (or number), look it up
216     // in the account list. If it's there, return it;
217     // otherwise report an error and return null.
218 
219     private BankAccount whichAccount() 
220     {
221         String accountName = atm.readWord( "account name: " );
222         BankAccount account = (BankAccount) accountList.get(accountName);
223         if (account == null) {
224             atm.println( "not a valid account" );
225         }
226         return account;
227     }
228 
229     // Action to take when a new month starts.
230     // Update the month field by sending a next message.
231     // Loop on all accounts, sending each a newMonth message.
232 
233     private void newMonth()
234     {
235         month.next();
236         Iterator i = accountList.keySet().iterator();
237         while ( i.hasNext() ) {
238             String name = (String) i.next();
239             BankAccount acct = (BankAccount)accountList.get(name);
240             try {
241                 acct.newMonth();
242             }
243             catch (InsufficientFundsException exception) {
244                 atm.errPrintln( 
245                    "Insufficient funds in account \"" +
246                    name + "\" for monthly fee" );
247             }
248         }
249     }
250 
251     // Report bank activity. For each BankAccount, 
252     // print the customer id (name or number), balance, and
253     // the number of transactions. Then print Bank totals.
254 
255     private void report() 
256     {
257         atm.println( bankName + " report for " + month );
258         atm.println( "\nSummaries of individual accounts:" );
259         atm.println( "account  balance   transaction count" );
260         for (Iterator i = accountList.keySet().iterator(); 
261              i.hasNext(); ) {
262             String accountName = (String) i.next();
263             BankAccount acct = (BankAccount) accountList.get(accountName);
264             atm.println(accountName + "\t$" + acct.getBalance() + "\t\t" 
265                         + acct.getTransactionCount());
266         }
267         atm.println( "\nBank totals");
268         atm.println( "open accounts: " + getNumberOfAccounts() );
269         atm.println( "cash on hand: $" + getBalance());
270         atm.println( "transactions:  " + getTransactionCount());
271         atm.println();
272     }
273 
274 
275     // Welcome the user to the bank and instruct her on 
276     //  her options.
277     
278     private void instructUser() 
279     { 
280         atm.println( "Welcome to " + bankName );
281         atm.println( month.toString() );
282         atm.println( "Open some accounts and work with them." );
283         help( BANKER_COMMANDS );
284     }
285 
286     // Display a help string.
287 
288     private void help( String helpString ) 
289     {
290         atm.println( helpString );
291         atm.println();
292     }
293 
294     // Read amount prompted for from the atm.
295     // Throw a NegativeAmountException if amount < 0
296 
297     private int readPosAmt( String prompt )
298          throws NegativeAmountException
299     {
300         int amount = atm.readInt( prompt );
301         if (amount < 0) {
302             throw new NegativeAmountException();
303         }
304         return amount;
305     }
306 
307     /**
308      * Increment bank balance by given amount.
309      *
310      * @param amount the amount increment.
311      */
312 
313     public void incrementBalance(int amount)
314     {
315         balance += amount;
316     }
317     
318     /**
319      * Increment by one the count of transactions, 
320      * for this bank.
321      */
322 
323     public void countTransaction()
324     {
325         transactionCount++;
326     }
327 
328     /**
329      * Get the number of transactions performed by this bank.
330      *
331      * @return number of transactions performed.
332      */
333 
334     public int getTransactionCount( )
335     {
336         return transactionCount ;
337     }
338 
339     /**
340      * The charge this bank levies for cashing a check.
341      *
342      * @return check fee
343      */
344 
345     public int getCheckFee( )
346     {
347         return checkFee ;
348     }
349 
350     /**
351      * The charge this bank levies for a transaction.
352      *
353      * @return the transaction fee
354      */
355 
356     public int getTransactionFee( )
357     {
358         return transactionFee ;
359     }
360 
361     /**
362      * The charge this bank levies each month.
363      *
364      * @return the monthly charge
365      */
366 
367     public int getMonthlyCharge( )
368     {
369         return monthlyCharge;
370     }
371 
372     /**
373      * The current interest rate on savings.
374      *
375      * @return the interest rate
376      */
377 
378     public double getInterestRate( )
379     {
380         return interestRate;
381     }
382 
383     /**
384      * The number of free transactions per month.
385      *
386      * @return the number of transactions
387      */
388 
389     public int getMaxFreeTransactions()
390     {
391         return maxFreeTransactions;
392     }
393 
394     /**
395      * Get the current bank balance.
396      *
397      * @return current bank balance.
398      */
399 
400     public int getBalance()
401     {
402         return balance;
403     }
404 
405     /**
406      * Get the current number of open accounts.
407      *
408      * @return number of open accounts.
409      */
410 
411     public int getNumberOfAccounts()
412     {
413         return accountList.size(); 
414     }
415 
416     /**
417      * Run the simulation by creating and then visiting a new Bank.
418      * <p>
419      * A -e argument causes the input to be echoed. 
420      * This can be useful for executing the program against
421      * a test script, e.g.,
422      * <pre>
423      *   java Bank -e < Bank.in
424      * </pre>
425      *
426      * @param args the command line arguments:
427      *         <pre>
428      *         -e echo input.
429      *         bankName any other command line argument.
430      *         </pre>
431      */
432 
433     public static void main( String[] args )     
434     {
435         // parse the command line arguments for the echo
436         // flag and the name of the bank
437 
438         boolean echo    = false;         // default does not echo
439         String bankName = "River Bank";  // default bank name
440 
441         for (int i = 0; i < args.length; i++ ) {
442             if (args[i].equals("-e")) {
443                 echo = true;
444             }
445             else {
446                 bankName = args[i];
447             }
448         }
449         Bank aBank = new Bank( bankName, new Terminal(echo) );
450         aBank.visit();
451     }
452 }
453