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