BankExampleWithoutDatabase.java

/*
 * BankExampleWithoutDatabase.java
 *
 * Copyright (C) 2004-08 Side of Software (SOS)
 * All rights reserved.
 *
 *    http://www.sideofsoftware.com
 */

package examples;

import java.util.*;

/**
 * A sample program that does not use Side of Software's
 * Persistence Library. Compare this implementation
 * with BankExampleWithDatabase.java, which
 * is the same application, except that it uses a database.<p>
 * This class is part of Side of Software's Persistence Library
 * tutorial.<p>
 *
 * @author  Side of Software
 */
public class BankExampleWithoutDatabase
{
  /** Number of accounts in this sample application. */
  private static final int NUM_ACCOUNTS = 10;
  
  /** Number of reports for each report thread to generate. */
  private static final int NUM_REPORTS = 10;
  
  /** Number of deposits for each deposit thread to make. */
  private static final int NUM_DEPOSITS = 10;
  
  /** Number of transfers for each transfer thread to make. */
  private static final int NUM_TRANSFERS = 10;
  
  /** Number of threads to make deposits. */
  private static final int NUM_DEPOSIT_THREADS = 5;
  
  /** Number of threads to make transfers. */
  private static final int NUM_TRANSFER_THREADS = 5;
  
  /** Number of threads to generate summary reports. */
  private static final int NUM_REPORT_THREADS = 10;

  /** Initial balance of new accounts. */
  private static final int INITIAL_BALANCE = 100;
  
  /** Amount of each deposit. */
  private static final int AMOUNT_TO_DEPOSIT = 10;
  
  /** Amount of each transfer. */
  private static final int AMOUNT_TO_TRANSFER = 20;
  
  /** A mapping from account number to account. */
  private Map accounts;
  
  /** 
   * Creates an instance of <code>BankExampleWithoutDatabase</code>.
   */
  public BankExampleWithoutDatabase()
  {
    accounts = new HashMap();
    
    // create the accounts
    for( int i = 0; i < NUM_ACCOUNTS; i++ )
      openAccount( i, INITIAL_BALANCE );
  }
  
  /**
   * Returns a new thread that repeatedly deposits to all of the accounts.<p>
   *
   * @param depositAmount amount of each deposit
   * @return the new thread that makes deposits
   */  
  private Thread createDepositThread( final double depositAmount )
  {
    Thread thread = new Thread( new Runnable() {
      public void run()
      {
        for( int acctNumber = 0; acctNumber < NUM_ACCOUNTS; acctNumber++ )
        {
          Account acct = (Account)lookupAccount( acctNumber );

          for( int i = 0; i < NUM_DEPOSITS; i++ )
          {
            deposit( acct, depositAmount );   // make the deposit
          }
        }
      }
    } );
    return thread;
  }
  
  /**
   * Returns a new thread that repeatedly sums the balances of all accounts.<p>
   *
   * @return the new thread to sum the balances
   */  
  private Thread createReportThread()
  {
    Thread thread = new Thread( new Runnable() {
      public void run()
      {
        for( int i = 0; i < NUM_REPORTS; i++ )
        {
          generateReport();   // generate the report
        }
      }
    } );
    return thread;
  }
  
  /**
   * Returns a new thread that repeatedly transfers from one account to the next.<p>
   *
   * @param transferAmount amount of each deposit
   * @return the new thread that makes deposits
   */  
  private Thread createTransferThread( final double transferAmount )
  {
    Thread thread = new Thread( new Runnable() {
      public void run()
      {
        for( int acctNumber = 0; acctNumber < NUM_ACCOUNTS; acctNumber++ )
        {
          Account fromAcct = lookupAccount( acctNumber );
          Account toAcct = lookupAccount( ( acctNumber + 1 ) % NUM_ACCOUNTS );

          for( int i = 0; i < NUM_TRANSFERS; i++ )
          {
            transfer( fromAcct, toAcct, transferAmount );   // make the transfer
          }
        }
      }
    } );
    return thread;
  }
  
  /**
   * Deposits the specified amount to the specified account.<p>
   *
   * @param acct account to add or subtract money
   * @param amount amount to deposit (may be negative)
   */  
  private synchronized void deposit( Account acct, double amount )
  {
    double balance = acct.getBalance();   // get the current balance
    balance += amount;                    // adjust the balance
    acct.setBalance( balance );           // set the new balance
  }

  /**
   * Iterates through all accounts, summing the balances.<p>
   */
  private synchronized void generateReport()
  {
    int numAccounts = accounts.size();                // how many accounts?
    double total = 0;
    
    // for each account
    Collection values = accounts.values();
    Iterator acctIterator = values.iterator();
    while( acctIterator.hasNext() )
    {
      Account account = (Account)acctIterator.next();
      double balance = account.getBalance();          // get the balance
      total += balance;                               // keep a running total
    }
    
    // print the report
    System.err.println( total + " for " + numAccounts + " accounts " );
  }
  
  /**
   * Finds the account with the specified account number. Returns <code>null</code>
   * if no account is found.<p>
   *
   * @param accountNumber the number of the account to find
   * @return the account with the specified account number or <code>null</code>
   *    if none found
   */  
  private synchronized Account lookupAccount( int accountNumber )
  {
    Object key = new Integer( accountNumber );
    return (Account)accounts.get( key );        // search the accounts
  }
  
  /**
   * Serves as the entry point for the sample program.<p>
   *
   * @param args ignored
   */
  public static void main(String[] args) throws InterruptedException
  {
    BankExampleWithoutDatabase example = new BankExampleWithoutDatabase();
    example.run();
  }
  
  /**
   * Adds a new account with the specified number and initial balance.<p>
   *
   * @param accountNumber account number of account to open
   * @param initialBalance initial balance of the account to open
   * @return the new account
   */  
  private synchronized Account openAccount( int accountNumber, double initialBalance )
  {
    // create the account
    Account account = new DefaultAccount();
    
    // store this account in the map indexed by account number
    Object key = new Integer( accountNumber );
    accounts.put( key, account );
    
    // set the initial balance of this account
    account.setBalance( initialBalance );
    
    return account;
  }

  /**
   * Runs the sample program.<p>
   */
  public void run() throws InterruptedException
  {
    // create threads that will test the banking system
    int numThreads = NUM_DEPOSIT_THREADS + NUM_TRANSFER_THREADS + NUM_REPORT_THREADS;
    Thread[] threads = new Thread[numThreads];
    
    int index = 0;
    
    // create threads that repeatedly deposit money to all accounts
    for( int i = 0; i < NUM_DEPOSIT_THREADS; i++ )
      threads[index++] = createDepositThread( AMOUNT_TO_DEPOSIT );
    
    // create threads that repeatedly transfer money among accounts
    for( int i = 0; i < NUM_TRANSFER_THREADS; i++ )
      threads[index++] = createTransferThread( AMOUNT_TO_TRANSFER );
    
    // create threads that repeatedly report account totals
    for( int i = 0; i < NUM_REPORT_THREADS; i++ )
      threads[index++] = createReportThread();

    // start the threads
    for( int i = 0; i < threads.length; i++ )
      threads[i].start();
    
    // wait until the threads stop
    for( int i = 0; i < threads.length; i++ )
      threads[i].join();
    
    // verify that the account balances are what we expected
    double expectedBalance = INITIAL_BALANCE + NUM_DEPOSIT_THREADS * NUM_DEPOSITS * AMOUNT_TO_DEPOSIT;
    for( int i = 0; i < NUM_ACCOUNTS; i++ )
    {
      Account acct = lookupAccount( i );
      double balance = acct.getBalance();
      assert balance == expectedBalance : "Incorrect balance for account " + i + ": " + balance;
    }
  }
  
  /**
   * Transfers the specified amount from one account to another.<p>
   *
   * @param fromAcct account to transfer from
   * @param toAcct account to transfer to
   * @param amount the amount to transfer
   */  
  private synchronized void transfer( Account fromAcct, Account toAcct, double amount )
  {
    deposit( fromAcct, -amount );   // take money from one account
    deposit( toAcct, amount );      // add the money to the other account
  }
}