The Atm.cpp File
// Atm.cpp: The source file of the main classes composing the ATM
// side of the application. It consists of all method and global
// data definitions required by these classes.
#include <stdio.h>
#include <iostream.h>
#include <string.h>
#include <math.h>
#include ''network.hpp''
#include ''atm.hpp''
#include ''trans.hpp''
// Definition of the two card slots in the ATM system, the
// card reader's slot, where a user inserts his or her card,
// and the ATM' s slot into which eaten cards are taken.
// These are simulated in this system by directories in the
// file system. The two path names are given to the ATM application
// as its first two command-line arguments.
char CardSlots[large_string];
char ATMSlots[large_string];
// The definition of the two format strings for simulating
// the ejecting and eating of bank cards. Can be changed to
// the equivalent Unix commands for portability.
const char* file_delete_format = ''del %s\\%s'';
const char* file_move_format = ''copy %s\\%s %s\\%s.%d'';
// The checking of an account name determines that it consists of
// numeric digits. (Note: The actual account on the Bank side of the
// application sees seven digits plus a terminating S or C for
// savings and checking, respectively).
int
bad_account(char* account)
{
while (*account != '\0') {
if (*account< '0' | | *account > '9') {
return(1);
}
account++;
}
return(0);
}
// For now PIN numbers and account numbers use the same algorithm.
// They may drift in the future.
int
bad_pin(char* pin)
{
return(bad_account(pin));
}
// Each PhysicalCardReader has a name, which it uses as the name of
// the BankCardfile when it is inserted into CardSlot directory.
// This naming would not be necessary in a real system, since the
// hardware would take care of this naming problem. It appears in
// this application only for simplifying the simulation.
PhysicalCardReader::PhysicalCardReader(char* n)
{
strcpy(name, n);
}
// The readinfo method tries to open a file in the CardSlots
// directory with the name of the card reader. This name
// would not be needed in a real system. The encoded data
// would be read off the card reader's hardware. The method
// returns a one if the card cannot be read, zero if it
// was read successfully. The buf argument is filled in
// with the first line of the file on success. It is assumed
// to contain a seven-digit numeric string (account number)
// followed by a four-digit numeric string (pin number).
int
PhysicalCardReader::readinfo(char* buf)
{
FILE* fd;
sprintf(buf, ''%s/%s'', CardSlots, name);
if ((fd = fopen(buf, ''r'')) == NULL) {
return(1);
}
fgets(buf, large_string, fd);
fclose(fd);
return(0);
}
// The simulation for eject cards is to remove the file from the card
// slot directory. In a real ATM system, this method would be a call
// to a hardware driver.
void PhysicalCardReader::eject_card()
{
char buf[large_string];
sprintf(buf, file_delete_format, CardSlots, name);
system(buf);
}
// The simulation for eating cards is to move the BankCard file from
// the CardSlot directory to the ATM slot directory. In a real ATM
// system, this method would be a call to a hardware driver.
void
PhysicalCardReader::eat_card()
{
char buf[large_string];
static int count=1;
sprintf(buf, file_move_format, CardSlots, name, ATMSlots,
name, count++);
system(buf);
}
// The constructor for CardReader calls the constructor of its
//PhysicalCardReader.
CardReader::CardReader(char* name) : cardReader(name)
{
validcard = 0;
account[0] = pin[0] = '\0';
}
// The read_card method checks to see if there is a card in
// the slot. If there isn't, it returns 1. If there is and
// it isn't readable, then the card is rejected and a 1 is
// returned. If the data on the card is readable, then the account
// and PIN number are read from the card (account is assumed to be
// a seven-character numeric string, the PIN a four-digit numeric
// string). If the data cannot be parsed (the card is not a valid
// bank card), then a 1 is returned and the card is ejected.
int
CardReader::read_card()
{
char buf[large_string];
validcard = 0;
switch (cardReader.readinfo(buf)) {
case -1: // If the card couldn't be read, then eject it.
cardReader.eject_card();
return(1);
case 1: // If there is no card, then report it to ATM.
return(1);
case 0: // We have the information, parse it.
// If the account number is bad, return 1 and
eject.
sscanf(buf, ''%7s'', account);
account[7] = '\0';
if (bad_account(account)) {
cardReader.eject_card();
return(1);
}
//If the PIN number is bad, return 1 and
eject.
sscanf(&buf[7], ''%4s'', pin);
pin[4] = '\0';
if (bad_pin(pin)) {
cardReader.eject_card();
return(1);
}
}
validcard= 1;
return(0);
}
// The accessor methods are required for verifying a user-
// supplied PIN number and building transactions. These are
// valid since there is a design situation facing policy between
// two separate key abstractions, i.e., the SuperKeypad and
// the CardReader. Both return 0 on success, 1 on failure.
int
CardReader::get_account(char* acc)
{
if (validcard) {
strcpy(acc, account);
}
return(!validcard);
}
int
CardReader::get_pin(char* p)
{
if (validcard) {
strcpy(p, pin);
}
return(!validcard);
}
// The following two methods simply delegate to their wrapped
// PhysicalCardReader class and execute its methods.
void
CardReader::eject_card()
{
cardReader.eject_card();
}
void
CardReader::eat_card()
{
cardReader.eat_card();
}
Keypad::Keypad()
{
enabled = 0;
}
void
Keypad::enable()
{
fflush(stdin);
enabled =1;
}
void
Keypad::disable()
{
enabled = 0;
}
// The getkey method reads a single character from the Keypad
// (in the simulation, the hardware is assumed to be the standard
// input). We assume the newline character to be the Enter key,
// implying that all input has been received. The method returns
// the character read on success, NULL terminator if the keypad
// is not enabled.
char
Keypad::getkey()
{
return(enabled?getchar() : '\0');
}
void
DisplayScreen::display_msg(const char* msg)
{
cout << ''@ATM Display@ '' << msg;
}
SuperKeypad::SuperKeypad()
{
keypad = new Keypad;
display = new DisplayScreen;
}
SuperKeypad::~SuperKeypad()
{
delete keypad;
delete display;
}
// This method delegates to its contained display screen. Such
// noncommunicating behavior is an argument for splitting the
// SuperKeypad class. However, the verify_pin()and
// get_transaction()methodsprovide more than enough cohesion
// of data to justify the SuperKeypad's existence.
void
SuperKeypad::display_msg(const char* msg)
{
display->display_msg(msg);
}
// The verify_pin method enables the keypad, prompts the user
// for a PIN number, and checks it against the user-supplied
// PIN number. The method returns zero on success, nonzero
// on failure.
int
SuperKeypad::verify_pin(const char* pin_to_verify)
{
char pin[small_string];
int i = 0;
keypad->enable();
display->display_msg(''Enter Pin Number: '');
while ((pin[i++] = keypad->getkey()) != EnterKey)
;
pin[i]= '\0';
keypad->disable();
return(strncmp(pin,pin_to_verify,4));
}
// Note the case analysis on the type of transaction. This case
// analysis is necessary since our object-oriented design has
// bumped up against an action-oriented (text-menu driven) user
// interface as per our discussion in Chapter 9. At least this case
// analysis is restricted to one point in the design (one method)
// and hidden in the SuperKeypad class. Any classes higher in the
// system are oblivious to the case analysis.
Transaction*
SuperKeypad::get_transaction(char* account, char* pin)
{
int i = 0;
char amount_str[small_string], trans_type;
char target_account[small_string];
double amount;
keypad->enable();
do{
display->display_msg(''Select a Transaction\n'');
display->display_msg(''\tW)ithdrawal\n'');
display->display_msg(''\tD)eposit\n'');
display->display_msg(''\tB)alance\n'');
display->display_msg(''\tT)ransfer\n'');
display->display_msg(''\tQ)uit\n'');
trans_type = keypad->getkey();
while (keypad->getkey() != EnterKey)
;
} while (trans_type != 'W' && trans_type != 'D' &&
trans_type != 'B' && trans_type != 'T' && trans_type
!= 'Q');
if (trans_type == 'Q') {
return(NULL);
}
display->display_msg(''Enter Account Type (S/C): '');
account[7] = keypad->getkey();
account[8] = '\0';
while (keypad->getkey() != EnterKey)
;
if (trans_type != 'B') {
display->display_msg(''Enter Amount: '');
while ((amount_str[i++] = keypad->getkey()) !=
EnterKey)
;
amount_str[i-1] = '\0';
amount = atof(amount_str);
}
if (trans_type == 'T') {
display->display_msg(''Enter Target Account Number: '');
i=0;
while ((target_account[i++] = keypad->getkey()) !=
EnterKey)
;
target_account[i-1] = '\0';
display->display_msg(''Enter Target Account Type (S/
C) : '');
target_account[7] = keypad->getkey();
target_account[8] = '\0';
while (keypad->getkey() != EnterKey)
;
}
switch (trans_type) {
case 'W' :
return(new Withdraw(account, pin, amount));
case 'D':
return(new Deposit(account, pin, amount));
case 'B':
return(new Balance(account, pin));
case 'T':
return(new Transfer(account, pin,
target_account, amount));
default:
cerr << ''Unknown type in get_transaction
switch statement\n'';
return(NULL);
}
}
CashDispenser::CashDispenser(int initial_cash)
{
cash_on_hand = initial_cash;
}
int
CashDispenser::enough_cash(int amount)
{
return(amount <= cash_on_hand);
}
// We can give out only multiples of $10. The reader may want to
// elaborate on this class by giving it fixed numbers of $20 bills,
// $10 's, $5's, etc. Some ATMs allow for the dispensing of stamps,
// theater tickets, etc., as well. Many warn the user that they are
// out of $10 bills and will dispense only multiples of $20. All of
// these items can be added to this class without impact on the rest
// of the system.
int
CashDispenser::dispense(int amount)
{
amount -= amount % 10;
if (enough_cash(amount)) {
cout << ''@CashDispenser@ Giving the user '' << amount
<< '' cash\n'';
return(0);
}
return(1);
}
int
DepositSlot::retrieve_envelope()
{
cout << ''@DepositSlot@ Getting an envelope from the
user\n'';
return(0);
}
// The receipt printer simulates the printing of receipts by
// creating a Receipts file in the current working directory.
// Again, the reader can elaborate on this class, adding a number
// of error checks, paper availability, etc. Like the cash
// dispenser, this is left as an exercise // to the reader since it
// adds no pedagogical benefit to this example.
void
ReceiptPrinter::print(TransactionList* translist)
{
FILE* fd;
cout << ''@ReceiptPrinter@ Your receipt is as follows: \n'';
if ((fd = fopen(''receipt'', ''w'')) == NULL) {
fd = stdout;
}
translist->print(fd);
if (fd != stdout) {
fclose(fd);
}
}
// The BankProxy is an extremely important class. It is the
// representative of the Bank class within the ATM application. It
// is merely a wrapper for the Network class, which is a wrapper
// itself for which transport mechanism a distributed process is
// going to use for communication. In this example, I chose to
// simulate the network, but readers are free to use any network
// or byte-transfer mechanism they wish. The application is
// completely independent of this mechanism. (Note: Changes in the
// byte-transfer mechanism affect only the Network class's
// implementation.)
BankProxy::BankProxy(Network* n)
{
network = n;
}
// When a BankProxy needs to process a transaction, it asks its
// Network object to send it. Assuming the send works correctly,
// the method then asks the Network for a response, which takes the
// form of a status integer (0,1, indicating success or failure on
// part of the real Bank class living in the bank's application
// space). If other Transaction application-specific data is
// required, then it is sent to the appropriate transaction's
// update message. This is to allow the Bank to update the state of
// a transaction in the ATM's application space from changes
// generated from the Bank's application space. Currently, only
// the Balance derived transaction uses this method to update its
// balance from the account in the Bank's application space.
int
BankProxy::process(Transaction* t)
{
int status, count;
char other_info[small_string];
if (network-> send(t)) {
return(1);
}
count = network->receive(status, other_info);
if (count) {
t->update(other_info, count);
}
return(status);
}
// Anew ATM object is given its Bank Proxy, a name to be handed down
// to its PhysicalCardReader (only needed for simulation), and its
// initial cash.
ATM::ATM(BankProxy* b, char* name, int cash)
{
bank = new BankProxy(*b);
cardReader = new CardReader(name);
superKeypad = new SuperKeypad;
cashDispenser = new CashDispenser(cash);
depositSlot = new DepositSlot;
receiptPrinter = new ReceiptPrinter;
translist = new TransactionList(max_transaction_atm);
}
ATM::~ATM()
{
delete bank;
delete cardReader;
delete superKeypad;
delete cashDispenser;
delete depositSlot;
delete receiptPrinter;
delete translist;
}
// The activate method for the ATM class is the main driver for the
// ATM objects. This method puts up the welcome message and waits
// for a card to become available (in simulation, a card becomes
// available when a user copies a file with the PhysicalCard-
// Reader's name into the CardSlots directory). When a card is
// available, the ATM retrieves the account and PIN number from the
// CardReader. It then asks its SuperKeypad to verify the PIN.
// The SuperKeypad verifies the PIN by getting a PIN number from the
// user and ensuring that it equals the one from the card. The
// actual check will be done by the Bank, which ensures that the PIN
// is equal to the one stored in the Account object.
// Once the PIN is verified, this method asks the SuperKeypad to
// collect and build a transaction. It preprocesses the
// transaction (handling things like getting envelopes for
// deposits, checking the cash dispenser to ensure enough cash is
// available for a withdrawal, etc.). If preprocessing was
// successful, it then asks its BankProxy to process the
// transaction. This method packages up the transaction, ships it
// over the network to the Bank's application, and collects a
// response from the Bank's application via the network. Notice
// the transparent nature of the interprocess communication. At
// design time we were able to completely ignore this distributed
// processing. Assuming the processing went well, we then execute
// a postprocess, which performs tasks like giving the user his or
// her money, etc. This method repeats the processing of the
// transaction until the user selects Quit, which requires the
// SuperKeypad::get_transaction method to return NULL. At this
// time the receipt printer generates a receipt and ejects the
// card.
void
ATM::activate()
{
char account[small_string], pin[small_string];
int count = 1, verified;
Transaction* trans;
while (1) {
superKeypad->display_msg(''Welcome to the Bank of
Heuristics!!!\n'');
superKeypad->display_msg(''Please Insert Your Card In
the Card Reader\n'');
// Get a card.
while (cardReader->read_card()!=0) {
;
}
cardReader->get_account(account);
cardReader->get_pin(pin);
// Try three times to verify the PIN number.
do {
verified = superKeypad->verify_pin(pin);
} while (verified != 0 && count++ < 3);
// If it couldn't be verified, then eat the card.
if (verified != 0) {
superKeypad->display_msg(''Sorry, three
strikes and you're out!!! \n'');
cardReader->eat_card();
}
else {
// Otherwise, keep getting Transactions until the user asks to
// quit.
// while ((trans = superKeypad->get_transaction(account, pin))
// != NULL) {
// Preprocess the transaction, if necessary. The default is to do
// nothing.
// if (trans->preprocess(this) == 0) {
// If preprocessing was successful, then process the Transaction.
// If the Bank says the Transaction is valid, then add it to the
// current list (for the receipt) and carry out any postprocessing.
// if (bank->process(trans) == 0)
// { translist->add_trans(trans);
// trans->postprocess(this);
}
// If problems occur, display an appropriate message and continue.
else {
superKeypad->display_msg(''The Bank Refuses Your
Transaction!\n'');
superKeypad->display_msg(''Contact your Bank
Representative.\n'');
delete trans;
}
}
else {
superKeypad->display_msg(''This ATM is unable to
comply with your'');
superKeypad->display_msg('' request at this
time.\n'');
delete trans;
}
}
//When we're done, print the receipt, clean up the Transaction
// list, and eject the card. We're now ready to loop for another
// user.
receiptPrinter->print(translist);
translist->cleanup();
cardReader->eject_card();
}
}
}
// These are methods used by derived types of Transaction,
// specifically, in their pre-/post-process methods.
int
ATM::retrieve_envelope()
{
return(depositSlot->retrieve_envelope());
}
int
ATM::enough_cash(double amount)
{
return(cashDispenser->enough_cash((int) amount));
}
int
ATM::dispense_cash(double amount)
{
return(cashDispenser->dispense((int) amount));
}
|