diff --git a/src/Bank account b/src/Bank account new file mode 100644 index 0000000..c1b9692 --- /dev/null +++ b/src/Bank account @@ -0,0 +1,36 @@ +/** + * Represents a simple bank account with a balance. + */ +public class BankAccount { + + private double balance; + private String accountID; + + public BankAccount(String accountID, double initialBalance) { + this.accountID = accountID; + this.balance = initialBalance; + } + + public double getBalance() { + return balance; + } + + public String getAccountID() { + return accountID; + } + + + public void credit(double amount) { + this.balance += amount; + } + + + public void debit(double amount) { + this.balance -= amount; + } + + @Override + public String toString() { + return String.format("BankAccount[ID=%s, Balance=%.2f]", accountID, balance); + } +} diff --git a/src/Base Transaction b/src/Base Transaction new file mode 100644 index 0000000..7e37022 --- /dev/null +++ b/src/Base Transaction @@ -0,0 +1,86 @@ +import java.util.Calendar; +import java.util.UUID; + +/** + * BaseTransaction is a concrete class implementing TransactionInterface. + */ +public class BaseTransaction implements TransactionInterface { + + // ----- Fields ----- + private final double amount; + private final Calendar date; + private final String transactionID; + + // ----- Constructor ----- + + + public BaseTransaction(double amount, Calendar date) { + this.amount = amount; + this.date = date; + this.transactionID = UUID.randomUUID().toString(); + } + + // ----- Getters (Q1 required methods) ----- + + /** + * Returns the transaction amount. + * Early binding: resolved at compile time for BaseTransaction references. + */ + @Override + public double getAmount() { + return amount; + } + + /** + * Returns the transaction date. + */ + @Override + public Calendar getDate() { + return date; + } + + /** + * Returns the unique transaction ID. + */ + @Override + public String getTransactionID() { + return transactionID; + } + + // ----- Common transaction methods ----- + + /** + * Prints the details of this transaction to standard output. + */ + @Override + public void printTransactionDetails() { + System.out.println("=== Transaction Details ==="); + System.out.println("Transaction ID : " + transactionID); + System.out.printf ("Amount : %.2f%n", amount); + System.out.printf ("Date : %04d-%02d-%02d%n", + date.get(Calendar.YEAR), + date.get(Calendar.MONTH) + 1, // Calendar months are 0-indexed + date.get(Calendar.DAY_OF_MONTH)); + System.out.println("Type : Base Transaction"); + System.out.println("==========================="); + } + + /** + * Base implementation of apply(). + * + * This implementation DIFFERS SUBSTANTIALLY from DepositTransaction and + * WithdrawalTransaction. Here we only log the transaction without modifying + * the bank account — simulating a "pending" or "record-only" transaction. + * + * Demonstrates EARLY BINDING: when called on a BaseTransaction reference, + * this method is resolved at compile time. + * + * @param ba the BankAccount on which to (not yet) apply the transaction + */ + @Override + public void apply(BankAccount ba) throws InsufficientFundsException { + System.out.println("[BaseTransaction] Recording transaction " + transactionID + + " for account " + ba.getAccountID() + + " — no balance change (base behaviour)."); + } +} diff --git a/src/Deposit Transaction b/src/Deposit Transaction new file mode 100644 index 0000000..10ff099 --- /dev/null +++ b/src/Deposit Transaction @@ -0,0 +1,70 @@ +import java.util.Calendar; + +/** + * DepositTransaction represents an irreversible credit to a BankAccount. + * + * Design assumption (Q2): deposits are IRREVERSIBLE — no reverse() method. + * + * Demonstrates LATE BINDING / POLYMORPHISM: + * When apply() is called on a TransactionInterface or BaseTransaction reference + * that actually holds a DepositTransaction object, the JVM resolves the call + * to THIS override at runtime — not the base-class version. + */ +public class DepositTransaction extends BaseTransaction { + + // ----- Constructor ----- + + /** + * Creates a DepositTransaction. + * + * @param amount the amount to deposit (should be > 0) + * @param date the date of the deposit + */ + public DepositTransaction(double amount, Calendar date) { + super(amount, date); + } + + // ----- Overridden methods ----- + + /** + * Prints deposit-specific details, then delegates to the superclass + * for common fields. + */ + @Override + public void printTransactionDetails() { + System.out.println("=== Deposit Transaction Details ==="); + System.out.println("Transaction ID : " + getTransactionID()); + System.out.printf ("Deposit Amount : %.2f%n", getAmount()); + System.out.printf ("Date : %04d-%02d-%02d%n", + getDate().get(Calendar.YEAR), + getDate().get(Calendar.MONTH) + 1, + getDate().get(Calendar.DAY_OF_MONTH)); + System.out.println("Reversible : No"); + System.out.println("==================================="); + } + + /** + * Applies the deposit by crediting the given BankAccount. + * + * LATE BINDING: this override is selected at runtime when the actual + * object is a DepositTransaction, regardless of the reference type used. + * + * Differs from BaseTransaction.apply() (which makes no balance change) + * and WithdrawalTransaction.apply() (which debits and may throw exceptions). + * + * @param ba the BankAccount to credit + */ + @Override + public void apply(BankAccount ba) throws InsufficientFundsException { + System.out.printf("[DepositTransaction] Crediting %.2f to account %s.%n", + getAmount(), ba.getAccountID()); + + double balanceBefore = ba.getBalance(); + ba.credit(getAmount()); + + System.out.printf(" Balance before : %.2f%n", balanceBefore); + System.out.printf(" Balance after : %.2f%n", ba.getBalance()); + + printTransactionDetails(); + } +} diff --git a/src/InsufficientFundsException b/src/InsufficientFundsException new file mode 100644 index 0000000..d41dc54 --- /dev/null +++ b/src/InsufficientFundsException @@ -0,0 +1,32 @@ +/** + * Custom exception thrown when a withdrawal exceeds the available balance. + * Extends Exception to make it a checked exception, requiring explicit handling. + */ +public class InsufficientFundsException extends Exception { + + private double amountRequested; + private double amountAvailable; + + + public InsufficientFundsException(double amountRequested, double amountAvailable) { + super(String.format( + "Insufficient funds: attempted to withdraw %.2f but only %.2f is available.", + amountRequested, amountAvailable + )); + this.amountRequested = amountRequested; + this.amountAvailable = amountAvailable; + } + + public double getAmountRequested() { + return amountRequested; + } + + public double getAmountAvailable() { + return amountAvailable; + } + + + public double getShortfall() { + return amountRequested - amountAvailable; + } +} diff --git a/src/Lecture4_interfaces_abstract_classes/BankAccount.java b/src/Lecture4_interfaces_abstract_classes/BankAccount.java index 28d0d07..f865220 100644 --- a/src/Lecture4_interfaces_abstract_classes/BankAccount.java +++ b/src/Lecture4_interfaces_abstract_classes/BankAccount.java @@ -1,16 +1,40 @@ -package Lecture4_interfaces_abstract_classes; - +/** + * Represents a simple bank account with a balance. + */ public class BankAccount { + private double balance; - public BankAccount(double balance) { - this.balance = balance; + private String accountID; + + public BankAccount(String accountID, double initialBalance) { + this.accountID = accountID; + this.balance = initialBalance; } public double getBalance() { return balance; } - public void setBalance(double balance) { - this.balance = balance; + public String getAccountID() { + return accountID; + } + + /** + * Credits (adds) an amount to the account balance. + */ + public void credit(double amount) { + this.balance += amount; + } + + /** + * Debits (subtracts) an amount from the account balance. + */ + public void debit(double amount) { + this.balance -= amount; + } + + @Override + public String toString() { + return String.format("BankAccount[ID=%s, Balance=%.2f]", accountID, balance); } } diff --git a/src/Main.java b/src/Main.java index 584a048..c03cbaf 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,156 +1,218 @@ -import Lecture1_adt.*; // Import all classes from Lecture1_adt package to be used in this client code - import java.util.Calendar; -import java.util.GregorianCalendar; -import java.util.ArrayList; -import java.util.List; - -//TIP To Run code, press or -// click the icon in the gutter. -/* -* Client Code for accessing the Lecture1_adt.TransactionInterface.java module - */ -public class Main { - - public static void testTransaction1() { - Calendar d1 = new GregorianCalendar(); // d1 is an Object [Objects are Reference types] - Lecture1_adt.Transaction1 t1 = new Lecture1_adt.Transaction1(1000, d1); // amount and d1 are arguments - System.out.println(t1.toString()); - System.out.println("Lecture1_adt.TransactionInterface Amount: \t " + t1.amount); - System.out.println("Lecture1_adt.TransactionInterface Date: \t " + t1.date); +public class Main { - // Please note that the Client Codes can access the data in the class directly through the dot operator - // This kind of exposure is a threat to both the Representation Independence and Preservation of Invariants + // ------------------------------------------------------------------ // + // Helper: separator line // + // ------------------------------------------------------------------ // + private static void section(String title) { + System.out.println("\n" + "=".repeat(55)); + System.out.println(" " + title); + System.out.println("=".repeat(55)); } + + // Test 1 — BaseTransaction (base / early-binding apply) // + + static void testBaseTransaction() { + section("TEST 1 — BaseTransaction.apply() [early binding]"); + + BankAccount account = new BankAccount("ACC-001", 1000.00); + Calendar today = Calendar.getInstance(); - /** @return a transaction of same amount as t, one month later - * This is a PRODUCER of the class Lecture1_adt.Transaction2 - * This code will help demostrate the Design exposures still present in transaction2 class - * */ + // BaseTransaction is a concrete class — instantiate directly + BaseTransaction base = new BaseTransaction(250.00, today); + base.printTransactionDetails(); - public static Transaction2 makeNextPayment(Transaction2 t) { - Calendar d = t.getDate(); - d.add(Calendar.MONTH, 1); - return new Transaction2(t.getAmount(), d); + System.out.println("\nAccount before apply: " + account); + try { + base.apply(account); // calls BaseTransaction.apply() — no balance change + } catch (InsufficientFundsException e) { + System.out.println("Error: " + e.getMessage()); + } + System.out.println("Account after apply : " + account); } - /* - Testing Transaction2 class - */ - public static void testTransaction2() { + // ------------------------------------------------------------------ // + // Test 2 — DepositTransaction // + // ------------------------------------------------------------------ // + static void testDepositTransaction() { + section("TEST 2 — DepositTransaction.apply()"); - Calendar d1 = new GregorianCalendar(); + BankAccount account = new BankAccount("ACC-002", 500.00); + Calendar today = Calendar.getInstance(); - Lecture1_adt.Transaction2 t = new Lecture1_adt.Transaction2(1000, d1); + DepositTransaction deposit = new DepositTransaction(300.00, today); - Lecture1_adt.Transaction2 modified_t = makeNextPayment(t); + System.out.println("Account before deposit: " + account); + try { + deposit.apply(account); + } catch (InsufficientFundsException e) { + System.out.println("Error: " + e.getMessage()); + } + System.out.println("Account after deposit : " + account); + } - System.out.println("\n\nState of the Object T1 After Client Code Tried to Change the Amount"); - System.out.println("Lecture1_adt.TransactionInterface Amount: \t "+modified_t.getAmount()); - System.out.println("Lecture1_adt.TransactionInterface Date: \t "+modified_t.getDate().getTime()); + // ------------------------------------------------------------------ // + // Test 3 — WithdrawalTransaction: sufficient funds // + // ------------------------------------------------------------------ // + static void testWithdrawalSufficientFunds() { + section("TEST 3 — WithdrawalTransaction.apply() [sufficient funds]"); - System.out.println("\n\nHow does T2 Look Like?????"); - System.out.println("Lecture1_adt.TransactionInterface Amount: \t "+modified_t.getAmount()); - System.out.println("Lecture1_adt.TransactionInterface Date: \t "+modified_t.getDate().getTime()); + BankAccount account = new BankAccount("ACC-003", 800.00); + Calendar today = Calendar.getInstance(); - /* Please note that Although we have solved the problem of Transaction1 - * And client code can no longer use the dot (.) operator to directly access the data - * There is still some exposure especially if we pass an object of a previous Transaction2 to create a new Transaction2 object - */ + WithdrawalTransaction withdrawal = new WithdrawalTransaction(200.00, today); + System.out.println("Account before withdrawal: " + account); + try { + withdrawal.apply(account); + } catch (InsufficientFundsException e) { + System.out.println("UNEXPECTED ERROR: " + e.getMessage()); + } + System.out.println("Account after withdrawal : " + account); } + // ------------------------------------------------------------------ // + // Test 4 — WithdrawalTransaction: insufficient funds (throws) // + // ------------------------------------------------------------------ // + static void testWithdrawalInsufficientFunds() { + section("TEST 4 — WithdrawalTransaction.apply() [insufficient funds]"); - /** @return a list of 12 monthly payments of identical amounts - * This code will help demostrate the Design exposures still present in transaction3 class - * */ - public static List makeYearOfPayments (int amount) throws NullPointerException { + BankAccount account = new BankAccount("ACC-004", 50.00); + Calendar today = Calendar.getInstance(); - List listOfTransaction3s = new ArrayList(); - Calendar date = new GregorianCalendar(2024, Calendar.JANUARY, 3); + WithdrawalTransaction withdrawal = new WithdrawalTransaction(200.00, today); + System.out.println("Account before withdrawal: " + account); + try { + withdrawal.apply(account); + } catch (InsufficientFundsException e) { + System.out.println("Caught InsufficientFundsException: " + e.getMessage()); + System.out.printf(" Shortfall: %.2f%n", e.getShortfall()); + } + System.out.println("Account after (unchanged): " + account); + } - for (int i = 0; i < 12; i++) { - listOfTransaction3s.add(new Transaction3(amount, date)); - date.add(Calendar.MONTH, 1); + // ------------------------------------------------------------------ // + // Test 5 — Type casting: subtype → base type (late-binding demo) // + // ------------------------------------------------------------------ // + static void testPolymorphismTypeCast() { + section("TEST 5 — Polymorphism / late binding via type cast"); + + BankAccount account = new BankAccount("ACC-005", 1000.00); + Calendar today = Calendar.getInstance(); + + // Create subtype objects + DepositTransaction depositSub = new DepositTransaction(400.00, today); + WithdrawalTransaction withdrawSub = new WithdrawalTransaction(150.00, today); + + // Cast subtypes to base type — the reference is BaseTransaction, + // but the actual object is still Deposit / Withdrawal. + BaseTransaction baseRef1 = (BaseTransaction) depositSub; + BaseTransaction baseRef2 = (BaseTransaction) withdrawSub; + + System.out.println("Calling apply() via BaseTransaction reference on DepositTransaction:"); + System.out.println("Account before: " + account); + // LATE BINDING: JVM resolves to DepositTransaction.apply() at runtime + try { + baseRef1.apply(account); + } catch (InsufficientFundsException e) { + System.out.println("Error: " + e.getMessage()); + } + System.out.println("Account after deposit (via base ref): " + account); + + System.out.println("\nCalling apply() via BaseTransaction reference on WithdrawalTransaction:"); + try { + // LATE BINDING: JVM resolves to WithdrawalTransaction.apply() at runtime + baseRef2.apply(account); + } catch (InsufficientFundsException e) { + System.out.println("Caught: " + e.getMessage()); } - return listOfTransaction3s; + System.out.println("Account after withdrawal (via base ref): " + account); } - /* - Testing Transaction3 class - */ - public static void testTransaction3() { + // ------------------------------------------------------------------ // + // Test 6 — Partial withdrawal (overloaded apply + try-catch-finally) // + // ------------------------------------------------------------------ // + static void testPartialWithdrawal() { + section("TEST 6 — Partial withdrawal [overloaded apply(ba, true)]"); - List allPaymentsIn2024 = makeYearOfPayments(1000); + BankAccount account = new BankAccount("ACC-006", 80.00); + Calendar today = Calendar.getInstance(); - for (Transaction3 t3 : allPaymentsIn2024) { + // Try to withdraw 200 when only 80 is available + WithdrawalTransaction withdrawal = new WithdrawalTransaction(200.00, today); - // Display all the 12 Transactions - for (Transaction3 transact : allPaymentsIn2024) { - System.out.println("\n\n ::::::::::::::::::::::::::::::::::::::::::::\n"); - System.out.println("Lecture1_adt.TransactionInterface Amount: \t "+transact.getAmount()); - System.out.println("Lecture1_adt.TransactionInterface Date: \t "+transact.getDate().getTime()); - } + System.out.println("Account before: " + account); + try { + withdrawal.apply(account, true); // allowPartial = true + } catch (InsufficientFundsException e) { + System.out.println("Caught: " + e.getMessage()); } - - /* Please Check all the 12 transactions displayed and hwo their dates look like - * Note that Although Transaction3 class resolves to an extent the exposure in Transaction2 class - * There is still some exposure especially if we pass an object of a previous Transaction3 to create a - * new Transaction3 object - */ + System.out.printf("Amount not withdrawn: %.2f%n", withdrawal.getAmountNotWithdrawn()); + System.out.println("Account after: " + account); } + // ------------------------------------------------------------------ // + // Test 7 — reverse() a withdrawal // + // ------------------------------------------------------------------ // + static void testReverseWithdrawal() { + section("TEST 7 — Reversing a WithdrawalTransaction"); - /** @return a list of 12 monthly payments of identical amounts - * This code Show that by judicious copying and defensive programming we eliminate the exposure in Transaction3 - * As defined in the constructor of Transaction4 class - * */ + BankAccount account = new BankAccount("ACC-007", 500.00); + Calendar today = Calendar.getInstance(); - public static List makeYearOfPaymentsFinal (int amount) throws NullPointerException { + WithdrawalTransaction withdrawal = new WithdrawalTransaction(150.00, today); - List listOfTransaction4s = new ArrayList(); - Calendar date = new GregorianCalendar(2024, Calendar.JANUARY, 3); + System.out.println("Account before withdrawal : " + account); + try { + withdrawal.apply(account); + } catch (InsufficientFundsException e) { + System.out.println("Error: " + e.getMessage()); + } + System.out.println("Account after withdrawal : " + account); + // Now reverse it + boolean reversed = withdrawal.reverse(); + System.out.println("Reversal successful: " + reversed); + System.out.println("Account after reversal : " + account); - for (int i = 0; i < 12; i++) { - listOfTransaction4s.add(new Transaction4(amount, date)); - date.add(Calendar.MONTH, 1); - } - return listOfTransaction4s; + // Attempt a second reversal (should fail) + System.out.println("\nAttempting second reversal..."); + withdrawal.reverse(); } - /* - Testing Transaction3 class - */ - public static void testTransaction4() { + // ------------------------------------------------------------------ // + // Test 8 — Zero-balance account (partial withdrawal edge case) // + // ------------------------------------------------------------------ // + static void testZeroBalance() { + section("TEST 8 — Zero balance account [should throw]"); - /* - * Call the function to make all the Twelve transaction in a year of our business - */ + BankAccount account = new BankAccount("ACC-008", 0.00); + Calendar today = Calendar.getInstance(); - List transactionsIn2024 = makeYearOfPaymentsFinal(1200); + WithdrawalTransaction withdrawal = new WithdrawalTransaction(100.00, today); - // Display all the 12 Transactions - for (Transaction4 transact : transactionsIn2024) { - System.out.println("\n\n ::::::::::::::::::::::::::::::::::::::::::::\n"); - System.out.println("Lecture1_adt.TransactionInterface Amount: \t "+transact.getAmount()); - System.out.println("Lecture1_adt.TransactionInterface Date: \t "+transact.getDate().getTime()); + try { + withdrawal.apply(account, true); // allowPartial, but balance is 0 + } catch (InsufficientFundsException e) { + System.out.println("Correctly caught: " + e.getMessage()); } - - // Please Take a look at all the 12 transaction now and compare with the outputs of the Transaction3 class } - public static void main(String[] args) { - // This is the client code - // Uncomment the following lines to test the class which you would like to test - - // testTransaction1() - // testTransaction2() - // testTransaction3() - // testTransaction4() + testBaseTransaction(); + testDepositTransaction(); + testWithdrawalSufficientFunds(); + testWithdrawalInsufficientFunds(); + testPolymorphismTypeCast(); + testPartialWithdrawal(); + testReverseWithdrawal(); + testZeroBalance(); + + System.out.println("\n" + "=".repeat(55)); + System.out.println(" All tests complete."); + System.out.println("=".repeat(55)); } -} \ No newline at end of file +} diff --git a/src/Transaction Interface b/src/Transaction Interface new file mode 100644 index 0000000..290530b --- /dev/null +++ b/src/Transaction Interface @@ -0,0 +1,16 @@ +import java.util.Calendar; +public interface TransactionInterface { + + + double getAmount(); + + Calendar getDate(); + + String getTransactionID(); + + + void printTransactionDetails(); + + + void apply(BankAccount ba) throws InsufficientFundsException; +}