From 029a0db1a07ed382fd2c699d3b46ded335db3ad6 Mon Sep 17 00:00:00 2001 From: Dennis Date: Wed, 10 Jun 2026 15:21:23 +0300 Subject: [PATCH] Assignment Submission. --- .../BaseTransaction.java | 74 +++++--- .../DepositTrasaction.java | 39 +++- .../InsufficientFundsException.java | 39 ++++ .../WithdrawalTransaction.java | 158 ++++++++++++++-- src/Main.java | 178 ++++++++++++++++++ 5 files changed, 450 insertions(+), 38 deletions(-) create mode 100644 src/Lecture4_interfaces_abstract_classes/InsufficientFundsException.java diff --git a/src/Lecture4_interfaces_abstract_classes/BaseTransaction.java b/src/Lecture4_interfaces_abstract_classes/BaseTransaction.java index ed81eb8..b8fbd1d 100644 --- a/src/Lecture4_interfaces_abstract_classes/BaseTransaction.java +++ b/src/Lecture4_interfaces_abstract_classes/BaseTransaction.java @@ -4,48 +4,78 @@ import java.util.Calendar; -public abstract class BaseTransaction implements TransactionInterface { - private final int amount; +/** + * BaseTransaction - A concrete class that implements the TransactionInterface. + * This class provides base implementations for all transaction-related methods. + * Subclasses (DepositTransaction, WithdrawalTransaction) override the apply() method + * to provide specific transaction behavior (polymorphism / late binding). + */ +public class BaseTransaction implements TransactionInterface { + private final double amount; private final Calendar date; private final String transactionID; /** - * Lecture1_adt.TransactionInterface Constructor - * @param amount in an integer - * @param date: Not null, and must be a Calendar object - * @return void - * Instialises the field, attributes of a transaction - * Creates a object of this + * BaseTransaction Constructor + * @param amount the transaction amount as an integer + * @param date Not null, must be a Calendar object + * Initialises the fields/attributes of a transaction */ public BaseTransaction(int amount, @NotNull Calendar date) { this.amount = amount; this.date = (Calendar) date.clone(); - int uniq = (int) Math.random()*10000; - transactionID = date.toString()+uniq; + int uniq = (int) (Math.random() * 10000); + transactionID = date.getTimeInMillis() + "-" + uniq; } /** - * getAmount() - * @return integer + * getAmount() - Method to get the transaction amount + * @return the transaction amount as a double */ public double getAmount() { - return amount; // Because we are dealing with Value types we need not worry about what we return + return amount; // Value type, safe to return directly } /** - * getDate() - * @return Calendar Object + * getDate() - Method to get the transaction date + * @return a defensive copy of the Calendar date object */ public Calendar getDate() { -// return date; // Because we are dealing with Reference types we need to judiciously copy what our getters return - return (Calendar) date.clone(); // Defensive copying or Judicious Copying + return (Calendar) date.clone(); // Defensive copying to preserve invariants } - // Method to get a unique identifier for the transaction + /** + * getTransactionID() - Method to get a unique identifier for the transaction + * @return the unique transaction ID string + */ public String getTransactionID(){ - return transactionID; + return transactionID; + } + + /** + * printTransactionDetails() - Prints the details of this transaction. + * Displays the transaction ID, amount, and date. + */ + public void printTransactionDetails(){ + System.out.println("-------- Transaction Details --------"); + System.out.println("Transaction ID: \t" + getTransactionID()); + System.out.println("Transaction Amount: \t" + getAmount()); + System.out.println("Transaction Date: \t" + getDate().getTime()); + System.out.println("-------------------------------------"); + } + + /** + * apply() - Applies this transaction on a BankAccount object. + * The BaseTransaction implementation simply prints the transaction details + * without modifying the bank account balance. This differs substantially from + * the DepositTransaction (which adds to balance) and WithdrawalTransaction + * (which subtracts from balance) implementations. + * @param ba the BankAccount to apply the transaction to + */ + public void apply(BankAccount ba) throws InsufficientFundsException { + System.out.println("Base Transaction Applied."); + printTransactionDetails(); + System.out.println("Account Balance: \t" + ba.getBalance()); + // BaseTransaction does NOT modify the balance — subclasses override this } - // Method to print a transaction receipt or details - public abstract void printTransactionDetails(); - public abstract void apply(BankAccount ba); } diff --git a/src/Lecture4_interfaces_abstract_classes/DepositTrasaction.java b/src/Lecture4_interfaces_abstract_classes/DepositTrasaction.java index 81afab5..e86da48 100644 --- a/src/Lecture4_interfaces_abstract_classes/DepositTrasaction.java +++ b/src/Lecture4_interfaces_abstract_classes/DepositTrasaction.java @@ -4,10 +4,27 @@ import java.util.Calendar; +/** + * DepositTransaction - A subclass of BaseTransaction that handles deposit operations. + * Deposits are irreversible (no reverse() method provided). + * Overrides the apply() method to add the transaction amount to the bank account balance. + */ public class DepositTrasaction extends BaseTransaction { + + /** + * DepositTransaction Constructor + * @param amount the deposit amount as an integer + * @param date Not null, the date of the transaction + */ public DepositTrasaction(int amount, @NotNull Calendar date){ super(amount, date); } + + /** + * Validates the deposit amount. + * @param amt the amount to check + * @return true if the amount is non-negative, false otherwise + */ private boolean checkDepositAmount(int amt){ if (amt < 0){ return false; @@ -17,14 +34,32 @@ private boolean checkDepositAmount(int amt){ } } - // Method to print a transaction receipt or details + /** + * printTransactionDetails() - Overrides BaseTransaction to print deposit-specific details. + * Displays the type of transaction (Deposit) along with the transaction information. + */ + @Override public void printTransactionDetails(){ - System.out.println("Deposit Trasaction: "+this.toString()); + System.out.println("-------- Deposit Transaction Details --------"); + System.out.println("Transaction ID: \t" + getTransactionID()); + System.out.println("Deposit Amount: \t" + getAmount()); + System.out.println("Transaction Date: \t" + getDate().getTime()); + System.out.println("----------------------------------------------"); } + /** + * apply() - Overrides BaseTransaction's apply() method. + * Adds the deposit amount to the bank account's current balance. + * This demonstrates method overriding and polymorphism (late binding): + * even if a DepositTransaction is referenced by a BaseTransaction variable, + * this overridden method will be called at runtime. + * @param ba the BankAccount to apply the deposit to + */ + @Override public void apply(BankAccount ba){ double curr_balance = ba.getBalance(); double new_balance = curr_balance + getAmount(); ba.setBalance(new_balance); + System.out.println("Deposit Applied Successfully. New Balance: " + ba.getBalance()); } } diff --git a/src/Lecture4_interfaces_abstract_classes/InsufficientFundsException.java b/src/Lecture4_interfaces_abstract_classes/InsufficientFundsException.java new file mode 100644 index 0000000..b3730fd --- /dev/null +++ b/src/Lecture4_interfaces_abstract_classes/InsufficientFundsException.java @@ -0,0 +1,39 @@ +package Lecture4_interfaces_abstract_classes; + +/** + * InsufficientFundsException - Custom exception class for handling insufficient funds + * during withdrawal transactions. + * Extends Exception to make it a checked exception (actual Java Exception class via inheritance). + * This demonstrates using inheritance to create a custom Java Exception class. + */ +public class InsufficientFundsException extends Exception { + + private final double shortfall; // The amount that could not be withdrawn + + /** + * Constructor with a custom error message. + * @param message the error message describing the exception + */ + public InsufficientFundsException(String message) { + super(message); + this.shortfall = 0; + } + + /** + * Constructor with a custom error message and the shortfall amount. + * @param message the error message describing the exception + * @param shortfall the amount that could not be covered by the account balance + */ + public InsufficientFundsException(String message, double shortfall) { + super(message); + this.shortfall = shortfall; + } + + /** + * Gets the shortfall amount (the amount not withdrawn due to insufficient funds). + * @return the shortfall amount as a double + */ + public double getShortfall() { + return shortfall; + } +} diff --git a/src/Lecture4_interfaces_abstract_classes/WithdrawalTransaction.java b/src/Lecture4_interfaces_abstract_classes/WithdrawalTransaction.java index face5b6..4bc5f91 100644 --- a/src/Lecture4_interfaces_abstract_classes/WithdrawalTransaction.java +++ b/src/Lecture4_interfaces_abstract_classes/WithdrawalTransaction.java @@ -4,12 +4,36 @@ import java.util.Calendar; +/** + * WithdrawalTransaction - A subclass of BaseTransaction that handles withdrawal operations. + * Withdrawals CAN be reversed (unlike deposits which are irreversible). + * Overrides the apply() method to subtract the transaction amount from the bank account balance. + * Implements an overloaded apply() method with exception handling for insufficient funds. + */ public class WithdrawalTransaction extends BaseTransaction { + + private double amountNotWithdrawn; // Tracks the amount that could not be withdrawn + private BankAccount targetAccount; // Reference to the bank account the transaction was applied to + private boolean applied; // Flag to track if the transaction has been applied + + /** + * WithdrawalTransaction Constructor + * @param amount the withdrawal amount as an integer + * @param date Not null, the date of the transaction + */ public WithdrawalTransaction(int amount, @NotNull Calendar date) { super(amount, date); + this.amountNotWithdrawn = 0; + this.targetAccount = null; + this.applied = false; } - private boolean checkDepositAmount(int amt) { + /** + * Validates the withdrawal amount. + * @param amt the amount to check + * @return true if the amount is non-negative, false otherwise + */ + private boolean checkWithdrawalAmount(int amt) { if (amt < 0) { return false; } else { @@ -17,29 +41,135 @@ private boolean checkDepositAmount(int amt) { } } - // Method to reverse the transaction + /** + * getAmountNotWithdrawn() - Returns the amount that was not withdrawn + * in cases where the balance was less than the requested withdrawal. + * @return the shortfall amount + */ + public double getAmountNotWithdrawn() { + return amountNotWithdrawn; + } + + /** + * reverse() - Reverses the withdrawal transaction. + * This method restores the balance in the BankAccount to its original amount + * before the transaction was applied. + * Withdrawals can be reversed (design assumption), while deposits cannot. + * @return true if the reversal was successful, false if the transaction was never applied + */ public boolean reverse() { + if (!applied || targetAccount == null) { + System.out.println("Reversal Failed: Transaction was never applied to an account."); + return false; + } + // Restore the withdrawn amount back to the account + double amountActuallyWithdrawn = getAmount() - amountNotWithdrawn; + double currentBalance = targetAccount.getBalance(); + targetAccount.setBalance(currentBalance + amountActuallyWithdrawn); + System.out.println("Withdrawal Reversed Successfully. Balance Restored to: " + targetAccount.getBalance()); + applied = false; // Mark as no longer applied return true; - } // return true if reversal was successful + } - // Method to print a transaction receipt or details + /** + * printTransactionDetails() - Overrides BaseTransaction to print withdrawal-specific details. + * Displays the type of transaction (Withdrawal) along with the transaction information. + */ + @Override public void printTransactionDetails() { - System.out.println("Deposit Trasaction: " + this.toString()); + System.out.println("-------- Withdrawal Transaction Details --------"); + System.out.println("Transaction ID: \t" + getTransactionID()); + System.out.println("Withdrawal Amount: \t" + getAmount()); + System.out.println("Transaction Date: \t" + getDate().getTime()); + if (amountNotWithdrawn > 0) { + System.out.println("Amount Not Withdrawn: \t" + amountNotWithdrawn); + } + System.out.println("-------------------------------------------------"); } - /* - Oportunity for assignment: implementing different form of withdrawal + /** + * apply() - Overrides BaseTransaction's apply() method. + * Subtracts the withdrawal amount from the bank account's current balance. + * Uses the 'throws' keyword to declare that InsufficientFundsException may be thrown + * if the balance is less than the withdrawal amount. + * This demonstrates method overriding and polymorphism (late binding). + * @param ba the BankAccount to apply the withdrawal to + * @throws InsufficientFundsException if the account balance is less than the withdrawal amount */ - public void apply(BankAccount ba) { + @Override + public void apply(BankAccount ba) throws InsufficientFundsException { double curr_balance = ba.getBalance(); - if (curr_balance > getAmount()) { - double new_balance = curr_balance - getAmount(); - ba.setBalance(new_balance); + + // Check if the balance covers the withdrawal amount + if (curr_balance < getAmount()) { + throw new InsufficientFundsException( + "Insufficient funds! Balance: " + curr_balance + ", Withdrawal Amount: " + getAmount(), + getAmount() - curr_balance + ); } + + double new_balance = curr_balance - getAmount(); + ba.setBalance(new_balance); + this.targetAccount = ba; + this.applied = true; + System.out.println("Withdrawal Applied Successfully. New Balance: " + ba.getBalance()); } - /* - Assignment 1 Q3: Write the Reverse method - a method unique to the WithdrawalTransaction Class + /** + * apply() - Overloaded apply method that handles insufficient funds gracefully. + * This method not only checks if the balance covers the withdrawal amount, but also + * checks if the balance is greater than 0. In the case where 0 < balance < withdrawal amount, + * it withdraws all the available balance and keeps a record of the amount not withdrawn. + * + * Implements exception handling using try{...} catch{...} finally{...} block. + * + * @param ba the BankAccount to apply the withdrawal to + * @param checkAvailable a flag to enable available-balance withdrawal mode */ -} + public void apply(BankAccount ba, boolean checkAvailable) { + double curr_balance = ba.getBalance(); + + try { + // First check: is the balance sufficient for a full withdrawal? + if (curr_balance < getAmount()) { + throw new InsufficientFundsException( + "Insufficient funds for full withdrawal. Balance: " + curr_balance + + ", Requested: " + getAmount(), + getAmount() - curr_balance + ); + } + + // Full withdrawal is possible + double new_balance = curr_balance - getAmount(); + ba.setBalance(new_balance); + this.targetAccount = ba; + this.applied = true; + this.amountNotWithdrawn = 0; + System.out.println("Full Withdrawal Applied. New Balance: " + ba.getBalance()); + + } catch (InsufficientFundsException e) { + // Handle the case where 0 < balance < withdrawal amount + System.out.println("Exception Caught: " + e.getMessage()); + + if (checkAvailable && curr_balance > 0) { + // Withdraw all available balance and record the shortfall + this.amountNotWithdrawn = getAmount() - curr_balance; + ba.setBalance(0); + this.targetAccount = ba; + this.applied = true; + System.out.println("Partial Withdrawal Applied: Withdrew " + curr_balance + + " from available balance."); + System.out.println("Amount Not Withdrawn: " + amountNotWithdrawn); + } else { + // Balance is 0 or negative, cannot withdraw anything + this.amountNotWithdrawn = getAmount(); + System.out.println("Withdrawal Failed: No available funds in the account."); + } + } finally { + // This block always executes regardless of whether an exception occurred + System.out.println("Transaction Complete. Final Account Balance: " + ba.getBalance()); + printTransactionDetails(); + } + } +} diff --git a/src/Main.java b/src/Main.java index 584a048..3696e38 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,5 +1,7 @@ import Lecture1_adt.*; // Import all classes from Lecture1_adt package to be used in this client code +import Lecture4_interfaces_abstract_classes.*; // Import all classes for Assignment 1 + import java.util.Calendar; import java.util.GregorianCalendar; import java.util.ArrayList; @@ -144,6 +146,172 @@ public static void testTransaction4() { } + // ======================================================================================== + // ASSIGNMENT 1 - CLIENT TEST CODE (Question 4) + // Testing the functionality of DepositTransaction and WithdrawalTransaction + // ======================================================================================== + + /** + * Test the DepositTransaction class. + * Creates a BankAccount, applies a deposit, and prints details. + */ + public static void testDepositTransaction() { + System.out.println("\n============================================"); + System.out.println(" TEST: DepositTransaction "); + System.out.println("============================================\n"); + + // Create a BankAccount with an initial balance of 1000 + BankAccount account = new BankAccount(1000); + System.out.println("Initial Account Balance: " + account.getBalance()); + + // Create a DepositTransaction of 500 + Calendar date = new GregorianCalendar(2024, Calendar.OCTOBER, 15); + DepositTrasaction deposit = new DepositTrasaction(500, date); + + // Print deposit transaction details + deposit.printTransactionDetails(); + + // Apply the deposit to the bank account + deposit.apply(account); + System.out.println("Balance After Deposit: " + account.getBalance()); + + System.out.println("\n--- Testing Polymorphism (Type Casting to BaseTransaction) ---"); + // Type cast the DepositTransaction to BaseTransaction (early binding vs late binding) + BaseTransaction baseRef = deposit; // Upcasting: subtype -> base type + System.out.println("Calling apply() through BaseTransaction reference:"); + // Late binding: even though baseRef is of type BaseTransaction, + // the overridden apply() in DepositTrasaction will be called at runtime + try { + baseRef.apply(account); + } catch (InsufficientFundsException e) { + System.out.println("Exception: " + e.getMessage()); + } + System.out.println("Balance After Second Deposit (via BaseTransaction ref): " + account.getBalance()); + + // Print details using the base reference (late binding for printTransactionDetails too) + baseRef.printTransactionDetails(); + } + + /** + * Test the WithdrawalTransaction class including: + * - Successful withdrawal + * - Withdrawal reversal + * - Withdrawal with insufficient funds (throws exception) + * - Overloaded apply with partial withdrawal (try-catch-finally) + */ + public static void testWithdrawalTransaction() { + System.out.println("\n============================================"); + System.out.println(" TEST: WithdrawalTransaction "); + System.out.println("============================================\n"); + + // Create a BankAccount with an initial balance of 1000 + BankAccount account = new BankAccount(1000); + System.out.println("Initial Account Balance: " + account.getBalance()); + + // --- Test 1: Successful Withdrawal --- + System.out.println("\n--- Test 1: Successful Withdrawal ---"); + Calendar date1 = new GregorianCalendar(2024, Calendar.OCTOBER, 20); + WithdrawalTransaction withdrawal1 = new WithdrawalTransaction(300, date1); + withdrawal1.printTransactionDetails(); + + try { + withdrawal1.apply(account); + } catch (InsufficientFundsException e) { + System.out.println("Exception: " + e.getMessage()); + } + System.out.println("Balance After Withdrawal: " + account.getBalance()); + + // --- Test 2: Reversal of Withdrawal --- + System.out.println("\n--- Test 2: Reversing the Withdrawal ---"); + boolean reversed = withdrawal1.reverse(); + System.out.println("Reversal Successful: " + reversed); + System.out.println("Balance After Reversal: " + account.getBalance()); + + // --- Test 3: Withdrawal with Insufficient Funds (using throws) --- + System.out.println("\n--- Test 3: Withdrawal with Insufficient Funds (throws) ---"); + Calendar date2 = new GregorianCalendar(2024, Calendar.NOVEMBER, 5); + WithdrawalTransaction withdrawal2 = new WithdrawalTransaction(5000, date2); + + try { + withdrawal2.apply(account); // This should throw InsufficientFundsException + } catch (InsufficientFundsException e) { + System.out.println("Exception Caught: " + e.getMessage()); + System.out.println("Shortfall Amount: " + e.getShortfall()); + } + System.out.println("Balance After Failed Withdrawal: " + account.getBalance()); + + // --- Test 4: Overloaded apply() - Partial Withdrawal with try-catch-finally --- + System.out.println("\n--- Test 4: Partial Withdrawal (Overloaded apply with try-catch-finally) ---"); + BankAccount account2 = new BankAccount(200); + System.out.println("Account2 Initial Balance: " + account2.getBalance()); + + Calendar date3 = new GregorianCalendar(2024, Calendar.NOVEMBER, 10); + WithdrawalTransaction withdrawal3 = new WithdrawalTransaction(500, date3); + + // Use the overloaded apply() method which handles partial withdrawals + withdrawal3.apply(account2, true); + System.out.println("Account2 Balance After Partial Withdrawal: " + account2.getBalance()); + System.out.println("Amount Not Withdrawn: " + withdrawal3.getAmountNotWithdrawn()); + + // --- Test 5: Overloaded apply() on Zero Balance Account --- + System.out.println("\n--- Test 5: Withdrawal on Zero Balance Account ---"); + BankAccount account3 = new BankAccount(0); + System.out.println("Account3 Initial Balance: " + account3.getBalance()); + + Calendar date4 = new GregorianCalendar(2024, Calendar.DECEMBER, 1); + WithdrawalTransaction withdrawal4 = new WithdrawalTransaction(100, date4); + withdrawal4.apply(account3, true); + System.out.println("Account3 Balance: " + account3.getBalance()); + + // --- Test 6: Polymorphism - Type casting WithdrawalTransaction to BaseTransaction --- + System.out.println("\n--- Test 6: Polymorphism (Type Casting to BaseTransaction) ---"); + BankAccount account4 = new BankAccount(2000); + System.out.println("Account4 Initial Balance: " + account4.getBalance()); + + Calendar date5 = new GregorianCalendar(2024, Calendar.DECEMBER, 15); + WithdrawalTransaction withdrawal5 = new WithdrawalTransaction(400, date5); + + // Upcast to BaseTransaction + BaseTransaction baseRef = withdrawal5; + System.out.println("Calling apply() through BaseTransaction reference:"); + // Late binding: the overridden apply() in WithdrawalTransaction will be called + try { + baseRef.apply(account4); + } catch (Exception e) { + System.out.println("Exception: " + e.getMessage()); + } + System.out.println("Account4 Balance After Withdrawal (via BaseTransaction ref): " + account4.getBalance()); + + // Print details using the base reference + baseRef.printTransactionDetails(); + } + + /** + * Test BaseTransaction directly to show the difference in apply() behavior + * compared to the subclasses. + */ + public static void testBaseTransaction() { + System.out.println("\n============================================"); + System.out.println(" TEST: BaseTransaction (Superclass) "); + System.out.println("============================================\n"); + + BankAccount account = new BankAccount(1500); + System.out.println("Initial Account Balance: " + account.getBalance()); + + Calendar date = new GregorianCalendar(2024, Calendar.OCTOBER, 10); + BaseTransaction baseTxn = new BaseTransaction(750, date); + + // Apply the base transaction - should NOT modify the balance + try { + baseTxn.apply(account); + } catch (InsufficientFundsException e) { + System.out.println("Exception: " + e.getMessage()); + } + System.out.println("Balance After BaseTransaction apply(): " + account.getBalance()); + System.out.println("(Note: BaseTransaction.apply() does NOT modify the balance)\n"); + } + + 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 @@ -152,5 +320,15 @@ public static void main(String[] args) { // testTransaction2() // testTransaction3() // testTransaction4() + + // ===== Assignment 1 Test Code ===== + // Test the BaseTransaction superclass behavior + testBaseTransaction(); + + // Test the DepositTransaction subclass (Q1 & Q4) + testDepositTransaction(); + + // Test the WithdrawalTransaction subclass (Q1, Q2, Q3 & Q4) + testWithdrawalTransaction(); } } \ No newline at end of file