Functional Scope (In-Scope)
- State-Driven Terminal Control: Support distinct step flows (Idle, CardInserted, PINVerified, Dispensing) using structured State Pattern classes.
- Greedy Cash Dispensation: Distribute banknotes dynamically matching high-to-low greedy note alignments using a Chain of Responsibility pattern.
- Atomic Account Debiting: Prevent partial payout errors by locking cash and debiting accounts atomically.
- Fail-Safe Banknote Rollbacks: On failure to dispense requested note patterns, roll back balance subtractions instantly.
Explicit Boundaries (Out-of-Scope)
- No Physical Mechanical Hardware Control: Rollout ignores thermal slip printer APIs, magnetic strip reads, or physical deposit vaults.
- No Central Network Routing Broker: Assumes direct secure loop connections to bank accounts.
Practical reference designs showing ATM state control and transaction safety in Java and Python:
// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
interface ATMState {
void insertCard(ATMMachine atm, String accountNumber);
void enterPin(ATMMachine atm, String pin);
void withdraw(ATMMachine atm, int amount);
void cancel(ATMMachine atm);
}
class IdleState implements ATMState {
@Override
public void insertCard(ATMMachine atm, String accountNumber) {
Account acc = atm.getAccount(accountNumber);
if (acc != null) {
atm.setActiveAccount(acc);
atm.setState(new CardInsertedState());
System.out.println("[ATM State] Card inserted successfully for: " + accountNumber);
} else {
System.out.println("[ATM State] Invalid Card: Account not found.");
}
}
@Override public void enterPin(ATMMachine atm, String pin) { System.out.println("[ATM ERROR] Please insert card first."); }
@Override public void withdraw(ATMMachine atm, int amount) { System.out.println("[ATM ERROR] Please insert card first."); }
@Override public void cancel(ATMMachine atm) { System.out.println("[ATM ERROR] No active session to cancel."); }
}
class CardInsertedState implements ATMState {
@Override public void insertCard(ATMMachine atm, String accountNumber) { System.out.println("[ATM ERROR] Card already inside."); }
@Override
public void enterPin(ATMMachine atm, String pin) {
if (atm.getActiveAccount().verifyPin(pin)) {
atm.setState(new PinVerifiedState());
System.out.println("[ATM State] PIN Verified. Welcome back!");
} else {
System.out.println("[ATM State] Incorrect PIN. Session terminated.");
atm.cancel();
}
}
@Override public void withdraw(ATMMachine atm, int amount) { System.out.println("[ATM ERROR] Please enter PIN first."); }
@Override
public void cancel(ATMMachine atm) {
System.out.println("[ATM State] Session cancelled. Card returned.");
atm.setActiveAccount(null);
atm.setState(new IdleState());
}
}
class PinVerifiedState implements ATMState {
@Override public void insertCard(ATMMachine atm, String accountNumber) { System.out.println("[ATM ERROR] Card already inside."); }
@Override public void enterPin(ATMMachine atm, String pin) { System.out.println("[ATM ERROR] PIN already verified."); }
@Override
public void withdraw(ATMMachine atm, int amount) {
atm.setState(new DispensingState());
System.out.println("[ATM State] Processing withdrawal of $" + amount + "...");
atm.dispenseCash(amount);
}
@Override
public void cancel(ATMMachine atm) {
System.out.println("[ATM State] Ejecting card. Session completed.");
atm.setActiveAccount(null);
atm.setState(new IdleState());
}
}
class DispensingState implements ATMState {
@Override public void insertCard(ATMMachine atm, String accNum) { System.out.println("[ATM ERROR] Please wait, currently dispensing."); }
@Override public void enterPin(ATMMachine atm, String pin) { System.out.println("[ATM ERROR] Please wait, currently dispensing."); }
@Override public void withdraw(ATMMachine atm, int amount) { System.out.println("[ATM ERROR] Please wait, currently dispensing."); }
@Override public void cancel(ATMMachine atm) { System.out.println("[ATM ERROR] Cannot cancel while dispensing cash."); }
}
interface CashDispenseChain {
void setNext(CashDispenseChain next);
boolean dispense(int amount, Map<Integer, Integer> dispensedNotes, Map<Integer, Integer> inventory);
}
class NoteDispenser implements CashDispenseChain {
private final int denomination;
private CashDispenseChain next;
public NoteDispenser(int denomination) {
this.denomination = denomination;
}
@Override
public void setNext(CashDispenseChain next) {
this.next = next;
}
@Override
public boolean dispense(int amount, Map<Integer, Integer> dispensedNotes, Map<Integer, Integer> inventory) {
if (amount <= 0) return true;
int available = inventory.getOrDefault(denomination, 0);
int needed = amount / denomination;
int actual = Math.min(needed, available);
if (actual > 0) {
dispensedNotes.put(denomination, actual);
amount -= actual * denomination;
}
if (amount > 0) {
if (next != null) {
return next.dispense(amount, dispensedNotes, inventory);
}
return false;
}
return true;
}
}
class CashDispenser {
private final Map<Integer, Integer> noteInventory = new ConcurrentHashMap<>();
private final CashDispenseChain chain;
private final ReentrantLock lock = new ReentrantLock();
public CashDispenser() {
noteInventory.put(100, 20); // $2000
noteInventory.put(50, 40); // $2000
noteInventory.put(20, 100); // $2000
CashDispenseChain c100 = new NoteDispenser(100);
CashDispenseChain c50 = new NoteDispenser(50);
CashDispenseChain c20 = new NoteDispenser(20);
c100.setNext(c50);
c50.setNext(c20);
this.chain = c100;
}
public boolean dispense(int amount) {
lock.lock();
try {
Map<Integer, Integer> dispensed = new HashMap<>();
if (chain.dispense(amount, dispensed, noteInventory)) {
for (Map.Entry<Integer, Integer> entry : dispensed.entrySet()) {
noteInventory.put(entry.getKey(), noteInventory.get(entry.getKey()) - entry.getValue());
}
System.out.println("[Dispenser] Dispensed bills: " + dispensed);
return true;
}
System.out.println("[Dispenser] Cannot dispense requested amount with current bill combinations!");
return false;
} finally {
lock.unlock();
}
}
}
class Account {
private final String accountNumber;
private final String pin;
private double balance;
private final ReentrantLock lock = new ReentrantLock();
public Account(String accountNumber, String pin, double initialBalance) {
this.accountNumber = accountNumber;
this.pin = pin;
this.balance = initialBalance;
}
public String getAccountNumber() { return accountNumber; }
public boolean verifyPin(String enteredPin) { return pin.equals(enteredPin); }
public double getBalance() {
lock.lock();
try { return balance; } finally { lock.unlock(); }
}
public boolean debit(double amount) {
lock.lock();
try {
if (balance >= amount) {
balance -= amount;
return true;
}
return false;
} finally {
lock.unlock();
}
}
public void credit(double amount) {
lock.lock();
try {
balance += amount;
} finally {
lock.unlock();
}
}
}
class ATMMachine {
private ATMState state = new IdleState();
private Account activeAccount = null;
private final CashDispenser dispenser = new CashDispenser();
private final Map<String, Account> accounts = new ConcurrentHashMap<>();
private final ReentrantLock atmLock = new ReentrantLock();
public void registerAccount(Account acc) {
accounts.put(acc.getAccountNumber(), acc);
}
public Account getAccount(String accountNumber) {
return accounts.get(accountNumber);
}
public void setState(ATMState state) {
this.state = state;
}
public void setActiveAccount(Account acc) {
this.activeAccount = acc;
}
public Account getActiveAccount() {
return activeAccount;
}
public void insertCard(String accountNumber) {
atmLock.lock();
try { state.insertCard(this, accountNumber); } finally { atmLock.unlock(); }
}
public void enterPin(String pin) {
atmLock.lock();
try { state.enterPin(this, pin); } finally { atmLock.unlock(); }
}
public void withdraw(int amount) {
atmLock.lock();
try { state.withdraw(this, amount); } finally { atmLock.unlock(); }
}
public void cancel() {
atmLock.lock();
try { state.cancel(this); } finally { atmLock.unlock(); }
}
public void dispenseCash(int amount) {
if (activeAccount == null) return;
if (activeAccount.debit(amount)) {
if (dispenser.dispense(amount)) {
System.out.println("[ATM Success] Completed withdraw of $" + amount + ". Ejecting card.");
state = new IdleState();
activeAccount = null;
} else {
System.out.println("[ATM Error] Dispensation error. Restoring account balance.");
activeAccount.credit(amount);
state = new PinVerifiedState();
}
} else {
System.out.println("[ATM Error] Insufficient account balance.");
state = new PinVerifiedState();
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== ATM CONCURRENT SIMULATION DRIVER ===");
ATMMachine atm = new ATMMachine();
Account acc = new Account("1234-5678", "4321", 500.00);
atm.registerAccount(acc);
System.out.println("Initial Balance: $" + acc.getBalance());
atm.insertCard("1234-5678");
atm.enterPin("4321");
atm.withdraw(280); // Should dispense 2x$100, 1x$50, 1x$20 = $270 + $10 fails if denomination is 20/50/100 only! Wait, 280: 2x100 + 1x50 + 1x20 = 270 (10 left - fail)
// Let's retry with a multiple of 20/50/100
atm.withdraw(290); // 2x100, 1x50, 2x20 = 290! Dispenses successfully.
System.out.println("Final Account Balance: $" + acc.getBalance());
}
}