diff --git a/src/Lecture4_interfaces_abstract_classes/BaseTransaction.java b/src/Lecture4_interfaces_abstract_classes/BaseTransaction.java index ed81eb8..74bb627 100644 --- a/src/Lecture4_interfaces_abstract_classes/BaseTransaction.java +++ b/src/Lecture4_interfaces_abstract_classes/BaseTransaction.java @@ -1,51 +1,80 @@ package Lecture4_interfaces_abstract_classes; import org.jetbrains.annotations.NotNull; - import java.util.Calendar; -public abstract class BaseTransaction implements TransactionInterface { +/** + * BaseTransaction represents a concrete transaction implementing TransactionInterface. + * It provides base fields, getters, and a default implementation of common transaction methods. + */ +public class BaseTransaction implements TransactionInterface { private final int 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 + * Constructs a BaseTransaction with a specified amount and date. + * + * @param amount the transaction amount + * @param date the transaction date (must not be null) */ 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; + // Fixed: Parentheses added around Math.random() * 10000 to cast the result instead of casting Math.random() to 0 + int uniq = (int) (Math.random() * 10000); + this.transactionID = date.getTimeInMillis() + "_" + uniq; } /** - * getAmount() - * @return integer + * Returns the transaction amount. + * + * @return the transaction amount */ + @Override public double getAmount() { - return amount; // Because we are dealing with Value types we need not worry about what we return + return amount; } /** - * getDate() - * @return Calendar Object + * Returns a copy of the transaction date. + * + * @return the transaction date */ + @Override 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(); + } + + /** + * Returns the unique identifier for the transaction. + * + * @return the transaction ID + */ + @Override + public String getTransactionID() { + return transactionID; + } + + /** + * Prints the details of this transaction to the standard output. + */ + public void printTransactionDetails() { + System.out.println("Transaction ID: " + getTransactionID()); + System.out.println("Date: " + getDate().getTime()); + System.out.println("Amount: $" + getAmount()); } - // Method to get a unique identifier for the transaction - public String getTransactionID(){ - return transactionID; + /** + * Applies the transaction to the specified bank account. + * The base implementation is a generic operation that does not modify the account balance. + * + * @param ba the BankAccount to apply the transaction to + * @throws InsufficientFundsException if the transaction cannot be applied due to insufficient funds + */ + public void apply(BankAccount ba) throws InsufficientFundsException { + System.out.println("Warning: Applying a generic BaseTransaction of amount " + + getAmount() + " on the account. Balance remains unchanged."); } - // 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/DepositTransaction.java b/src/Lecture4_interfaces_abstract_classes/DepositTransaction.java new file mode 100644 index 0000000..c26a2ff --- /dev/null +++ b/src/Lecture4_interfaces_abstract_classes/DepositTransaction.java @@ -0,0 +1,61 @@ +package Lecture4_interfaces_abstract_classes; + +import org.jetbrains.annotations.NotNull; +import java.util.Calendar; + +/** + * DepositTransaction represents a transaction that deposits funds into a BankAccount. + * All deposits are irreversible. + */ +public class DepositTransaction extends BaseTransaction { + + /** + * Constructs a DepositTransaction with a specified amount and date. + * + * @param amount the deposit amount + * @param date the transaction date (must not be null) + */ + public DepositTransaction(int amount, @NotNull Calendar date) { + super(amount, date); + } + + /** + * Validates that the deposit amount is non-negative. + * + * @param amt the amount to validate + * @return true if the amount is non-negative, false otherwise + */ + private boolean checkDepositAmount(int amt) { + return amt >= 0; + } + + /** + * Prints a transaction receipt for this deposit transaction. + */ + @Override + public void printTransactionDetails() { + System.out.println("--- Deposit Transaction Receipt ---"); + System.out.println("Transaction ID: " + getTransactionID()); + System.out.println("Date: " + getDate().getTime()); + System.out.println("Amount: $" + getAmount()); + System.out.println("Status: Completed (Irreversible)"); + System.out.println("-----------------------------------"); + } + + /** + * Applies this deposit transaction to the specified bank account by increasing its balance. + * + * @param ba the BankAccount to apply the deposit to + */ + @Override + public void apply(BankAccount ba) { + if (!checkDepositAmount((int) getAmount())) { + System.out.println("Error: Cannot deposit a negative amount."); + return; + } + double curr_balance = ba.getBalance(); + double new_balance = curr_balance + getAmount(); + ba.setBalance(new_balance); + System.out.println("Deposit applied successfully. New balance: $" + new_balance); + } +} diff --git a/src/Lecture4_interfaces_abstract_classes/DepositTrasaction.java b/src/Lecture4_interfaces_abstract_classes/DepositTrasaction.java deleted file mode 100644 index 81afab5..0000000 --- a/src/Lecture4_interfaces_abstract_classes/DepositTrasaction.java +++ /dev/null @@ -1,30 +0,0 @@ -package Lecture4_interfaces_abstract_classes; - -import org.jetbrains.annotations.NotNull; - -import java.util.Calendar; - -public class DepositTrasaction extends BaseTransaction { - public DepositTrasaction(int amount, @NotNull Calendar date){ - super(amount, date); - } - private boolean checkDepositAmount(int amt){ - if (amt < 0){ - return false; - } - else{ - return true; - } - } - - // Method to print a transaction receipt or details - public void printTransactionDetails(){ - System.out.println("Deposit Trasaction: "+this.toString()); - } - - public void apply(BankAccount ba){ - double curr_balance = ba.getBalance(); - double new_balance = curr_balance + getAmount(); - ba.setBalance(new_balance); - } -} diff --git a/src/Lecture4_interfaces_abstract_classes/InsufficientFundsException.java b/src/Lecture4_interfaces_abstract_classes/InsufficientFundsException.java new file mode 100644 index 0000000..ba39642 --- /dev/null +++ b/src/Lecture4_interfaces_abstract_classes/InsufficientFundsException.java @@ -0,0 +1,52 @@ +package Lecture4_interfaces_abstract_classes; + +/** + * Custom checked exception thrown when a bank account does not have + * sufficient funds to cover a withdrawal transaction. + */ +public class InsufficientFundsException extends Exception { + private final double amount; + private final double balance; + + /** + * Constructs a new InsufficientFundsException with a custom message. + * + * @param message the detail message + */ + public InsufficientFundsException(String message) { + super(message); + this.amount = 0.0; + this.balance = 0.0; + } + + /** + * Constructs a new InsufficientFundsException with detailed transaction information. + * + * @param message the detail message + * @param amount the requested withdrawal amount + * @param balance the current available balance + */ + public InsufficientFundsException(String message, double amount, double balance) { + super(message + " (Requested: " + amount + ", Available: " + balance + ")"); + this.amount = amount; + this.balance = balance; + } + + /** + * Gets the requested amount that caused the exception. + * + * @return the requested amount + */ + public double getAmount() { + return amount; + } + + /** + * Gets the available balance at the time of the exception. + * + * @return the available balance + */ + public double getBalance() { + return balance; + } +} diff --git a/src/Lecture4_interfaces_abstract_classes/WithdrawalTransaction.java b/src/Lecture4_interfaces_abstract_classes/WithdrawalTransaction.java index face5b6..f98c3c4 100644 --- a/src/Lecture4_interfaces_abstract_classes/WithdrawalTransaction.java +++ b/src/Lecture4_interfaces_abstract_classes/WithdrawalTransaction.java @@ -1,45 +1,168 @@ package Lecture4_interfaces_abstract_classes; import org.jetbrains.annotations.NotNull; - import java.util.Calendar; +/** + * WithdrawalTransaction represents a transaction that withdraws funds from a BankAccount. + * Unlike deposits, withdrawals are reversible. + */ public class WithdrawalTransaction extends BaseTransaction { + private BankAccount associatedAccount; + private double amountWithdrawn = 0.0; + private double amountNotWithdrawn = 0.0; + private boolean isReversed = false; + + /** + * Constructs a WithdrawalTransaction with a specified amount and date. + * + * @param amount the withdrawal amount + * @param date the transaction date (must not be null) + */ public WithdrawalTransaction(int amount, @NotNull Calendar date) { super(amount, date); } - private boolean checkDepositAmount(int amt) { - if (amt < 0) { - return false; - } else { - return true; - } + /** + * Checks if the transaction amount is positive. + * + * @param amt the amount to check + * @return true if the amount is positive, false otherwise + */ + private boolean checkWithdrawalAmount(double amt) { + return amt > 0; } - // Method to reverse the transaction + /** + * Reverses this withdrawal transaction, restoring the withdrawn amount + * back to the associated bank account. + * + * @return true if the reversal was successful, false otherwise + */ public boolean reverse() { + if (associatedAccount == null) { + System.out.println("Error: Transaction has not been successfully applied. Cannot reverse."); + return false; + } + if (isReversed) { + System.out.println("Error: Transaction has already been reversed."); + return false; + } + double currentBalance = associatedAccount.getBalance(); + associatedAccount.setBalance(currentBalance + amountWithdrawn); + isReversed = true; + System.out.println("Withdrawal transaction successfully reversed. Restored $" + amountWithdrawn); return true; - } // return true if reversal was successful + } - // Method to print a transaction receipt or details + /** + * Prints the receipt details for this withdrawal transaction. + */ + @Override public void printTransactionDetails() { - System.out.println("Deposit Trasaction: " + this.toString()); + System.out.println("--- Withdrawal Transaction Receipt ---"); + System.out.println("Transaction ID: " + getTransactionID()); + System.out.println("Date: " + getDate().getTime()); + System.out.println("Requested Amount: $" + getAmount()); + System.out.println("Amount Withdrawn: $" + amountWithdrawn); + System.out.println("Amount Not Withdrawn:$" + amountNotWithdrawn); + System.out.println("Reversal Status: " + (isReversed ? "Reversed" : "Not Reversed")); + System.out.println("--------------------------------------"); } - /* - Oportunity for assignment: implementing different form of withdrawal + /** + * Applies this withdrawal transaction to the specified bank account. + * Throws an InsufficientFundsException if the account balance does not cover the withdrawal. + * + * @param ba the BankAccount to apply the withdrawal to + * @throws InsufficientFundsException if the account balance is less than the transaction amount */ - public void apply(BankAccount ba) { + @Override + public void apply(BankAccount ba) throws InsufficientFundsException { + if (!checkWithdrawalAmount(getAmount())) { + System.out.println("Error: Withdrawal amount must be positive."); + return; + } double curr_balance = ba.getBalance(); - if (curr_balance > getAmount()) { - double new_balance = curr_balance - getAmount(); - ba.setBalance(new_balance); + if (curr_balance < getAmount()) { + throw new InsufficientFundsException("Insufficient funds to complete full withdrawal.", getAmount(), curr_balance); } + double new_balance = curr_balance - getAmount(); + ba.setBalance(new_balance); + this.associatedAccount = ba; + this.amountWithdrawn = getAmount(); + this.amountNotWithdrawn = 0.0; + System.out.println("Withdrawal applied successfully. New balance: $" + new_balance); } - /* - Assignment 1 Q3: Write the Reverse method - a method unique to the WithdrawalTransaction Class + /** + * Overloaded apply method that can allow partial withdrawals when funds are insufficient. + * If allowPartial is true, and the balance is positive but less than the withdrawal amount, + * it withdraws all remaining funds, setting the account balance to 0, and records the unpaid amount. + * Uses a try-catch-finally block to handle the exception thrown by full withdrawal attempts. + * + * @param ba the BankAccount to apply the withdrawal to + * @param allowPartial true if partial withdrawal is permitted when full funds are unavailable */ -} + public void apply(BankAccount ba, boolean allowPartial) { + this.associatedAccount = ba; + try { + // Attempt to perform full withdrawal + apply(ba); + } catch (InsufficientFundsException e) { + double balance = ba.getBalance(); + if (allowPartial && balance > 0) { + // 0 < balance < withdrawal amount + this.amountWithdrawn = balance; + this.amountNotWithdrawn = getAmount() - balance; + ba.setBalance(0.0); + System.out.println("Insufficient funds. Overloaded method applied partial withdrawal."); + System.out.println("Withdrew remaining balance: $" + balance); + System.out.println("Amount not withdrawn (deficit): $" + amountNotWithdrawn); + } else { + // balance is <= 0, or partial withdrawals not allowed + this.amountWithdrawn = 0.0; + this.amountNotWithdrawn = getAmount(); + System.out.println("Withdrawal failed: " + e.getMessage()); + } + } finally { + System.out.println("Finally block: Withdrawal execution attempt complete. Current balance: $" + ba.getBalance()); + } + } + + /** + * Gets the amount that was successfully withdrawn. + * + * @return the amount withdrawn + */ + public double getAmountWithdrawn() { + return amountWithdrawn; + } + + /** + * Gets the amount that was not withdrawn due to insufficient funds. + * + * @return the amount not withdrawn + */ + public double getAmountNotWithdrawn() { + return amountNotWithdrawn; + } + + /** + * Checks if this transaction has been reversed. + * + * @return true if reversed, false otherwise + */ + public boolean isReversed() { + return isReversed; + } + /** + * Gets the bank account associated with this transaction. + * + * @return the associated BankAccount + */ + public BankAccount getAssociatedAccount() { + return associatedAccount; + } +} diff --git a/src/Main.java b/src/Main.java index 584a048..800568d 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,4 +1,5 @@ import Lecture1_adt.*; // Import all classes from Lecture1_adt package to be used in this client code +import Lecture4_interfaces_abstract_classes.*; import java.util.Calendar; import java.util.GregorianCalendar; @@ -144,13 +145,115 @@ public static void testTransaction4() { } - 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 + public static void testLecture4Transactions() { + System.out.println("\n=========================================="); + System.out.println(" Testing Lecture 4 Transaction System "); + System.out.println("==========================================\n"); + + // 1. Create a BankAccount + System.out.println("[Step 1] Initializing Bank Account with $500.00"); + BankAccount account = new BankAccount(500.00); + System.out.println("Initial Balance: $" + account.getBalance()); + + Calendar now = new GregorianCalendar(); + + // 2. BaseTransaction behavior + System.out.println("\n[Step 2] Testing BaseTransaction"); + BaseTransaction baseTx = new BaseTransaction(150, now); + baseTx.printTransactionDetails(); + try { + baseTx.apply(account); // Will print warning and not change balance + } catch (InsufficientFundsException e) { + System.out.println("Caught exception: " + e.getMessage()); + } + System.out.println("Balance after BaseTransaction: $" + account.getBalance()); + + // 3. DepositTransaction behavior + System.out.println("\n[Step 3] Testing DepositTransaction"); + DepositTransaction depositTx = new DepositTransaction(250, now); + depositTx.printTransactionDetails(); + depositTx.apply(account); // Balance should become 750 + System.out.println("Balance after DepositTransaction: $" + account.getBalance()); + + // 4. WithdrawalTransaction (Successful Case) + System.out.println("\n[Step 4] Testing WithdrawalTransaction (Successful Full Withdrawal)"); + WithdrawalTransaction withdrawalTx1 = new WithdrawalTransaction(300, now); + withdrawalTx1.printTransactionDetails(); + try { + withdrawalTx1.apply(account); // Balance should become 450 + } catch (InsufficientFundsException e) { + System.out.println("Caught exception: " + e.getMessage()); + } + System.out.println("Balance after WithdrawalTransaction: $" + account.getBalance()); + + // 5. WithdrawalTransaction (Insufficient Funds Case - Standard apply) + System.out.println("\n[Step 5] Testing WithdrawalTransaction (Insufficient Funds - Exception Thrown)"); + WithdrawalTransaction withdrawalTx2 = new WithdrawalTransaction(1000, now); + withdrawalTx2.printTransactionDetails(); + try { + System.out.println("Attempting to withdraw $1000 from balance of $" + account.getBalance()); + withdrawalTx2.apply(account); // Should throw InsufficientFundsException + } catch (InsufficientFundsException e) { + System.out.println("Caught Expected Exception: " + e.getMessage()); + System.out.println("Deficit details - Requested: " + e.getAmount() + ", Available: " + e.getBalance()); + } + System.out.println("Balance after failed withdrawal: $" + account.getBalance()); + + // 6. WithdrawalTransaction (Insufficient Funds Case - Overloaded apply with partial allowance) + System.out.println("\n[Step 6] Testing Overloaded apply(ba, true) for Partial Withdrawal"); + // Remaining balance is 450. Attempting to withdraw 600 with partial allowed. + WithdrawalTransaction withdrawalTx3 = new WithdrawalTransaction(600, now); + System.out.println("Attempting to withdraw $600 with partial allowed. Balance: $" + account.getBalance()); + withdrawalTx3.apply(account, true); // Should withdraw 450, set balance to 0, record 150 not withdrawn + withdrawalTx3.printTransactionDetails(); + System.out.println("Balance after partial withdrawal: $" + account.getBalance()); + + // 7. Reversal behavior + System.out.println("\n[Step 7] Testing Withdrawal Reversal"); + System.out.println("Reversing partial withdrawal..."); + boolean reverseResult = withdrawalTx3.reverse(); // Should restore 450, balance becomes 450 + System.out.println("Reversal success: " + reverseResult); + System.out.println("Balance after reversal: $" + account.getBalance()); + withdrawalTx3.printTransactionDetails(); + + System.out.println("\nReversing full withdrawal from Step 4..."); + boolean reverseResultFull = withdrawalTx1.reverse(); // Should restore 300, balance becomes 750 + System.out.println("Reversal success: " + reverseResultFull); + System.out.println("Balance after reversing full withdrawal: $" + account.getBalance()); + + // 8. Polymorphism and Type Casting (Late Binding vs Early Binding) + System.out.println("\n[Step 8] Testing Polymorphism & Upcasting (Late vs Early Binding)"); + // Upcasting subtype objects to BaseTransaction reference + BaseTransaction polyDeposit = (BaseTransaction) new DepositTransaction(100, now); + BaseTransaction polyWithdrawal = (BaseTransaction) new WithdrawalTransaction(200, now); + + System.out.println("\nRunning polyDeposit.apply() [Declared type: BaseTransaction, Actual type: DepositTransaction]"); + try { + // Late binding: JVM resolves the actual class at runtime and executes DepositTransaction.apply() + polyDeposit.apply(account); // Balance should become 750 + 100 = 850 + } catch (InsufficientFundsException e) { + System.out.println("Error: " + e.getMessage()); + } + System.out.println("Balance after polymorphic deposit: $" + account.getBalance()); + + System.out.println("\nRunning polyWithdrawal.apply() [Declared type: BaseTransaction, Actual type: WithdrawalTransaction]"); + try { + // Late binding: JVM resolves the actual class at runtime and executes WithdrawalTransaction.apply() + polyWithdrawal.apply(account); // Balance should become 850 - 200 = 650 + } catch (InsufficientFundsException e) { + System.out.println("Error: " + e.getMessage()); + } + System.out.println("Balance after polymorphic withdrawal: $" + account.getBalance()); - // testTransaction1() - // testTransaction2() - // testTransaction3() - // testTransaction4() + System.out.println("\nDemonstrating Early (Static) Binding vs Late (Dynamic) Binding:"); + System.out.println("- Early Binding occurs at compile-time. If we called a private or static method, or constructor, the compiler would bind it based on the reference type."); + System.out.println("- Late Binding occurs at runtime. The JVM binds the overridden 'apply' and 'printTransactionDetails' methods based on the actual object type on the heap."); + System.out.print("Polymorphic Deposit details: "); + polyDeposit.printTransactionDetails(); // Executes DepositTransaction.printTransactionDetails + } + + public static void main(String[] args) { + // Run Lecture 4 tests + testLecture4Transactions(); } } \ No newline at end of file