From 6e2f8c3880c5172ac0ffea55a1d6e58882a19919 Mon Sep 17 00:00:00 2001 From: KerenHappuch21 Date: Wed, 10 Jun 2026 22:46:52 +0300 Subject: [PATCH] Assignment Submission-Keren-Happuch Kyende --- .../BankAccount.java | 23 +- .../BaseTransaction.java | 95 ++++--- .../DepositTrasaction.java | 58 ++-- .../InsufficientFundsException.java | 29 ++ .../TransactionInterface.java | 14 +- .../WithdrawalTransaction.java | 166 +++++++++-- src/Main.java | 261 ++++++++++++------ 7 files changed, 479 insertions(+), 167 deletions(-) create mode 100644 src/Lecture4_interfaces_abstract_classes/InsufficientFundsException.java diff --git a/src/Lecture4_interfaces_abstract_classes/BankAccount.java b/src/Lecture4_interfaces_abstract_classes/BankAccount.java index 28d0d07..6009c01 100644 --- a/src/Lecture4_interfaces_abstract_classes/BankAccount.java +++ b/src/Lecture4_interfaces_abstract_classes/BankAccount.java @@ -1,7 +1,13 @@ package Lecture4_interfaces_abstract_classes; +/** + * Represents a simple bank account with a balance. + * Starter code preserved; credit() and debit() helpers added + * so transaction classes don't need to call setBalance() directly. + */ public class BankAccount { private double balance; + public BankAccount(double balance) { this.balance = balance; } @@ -13,4 +19,19 @@ public double getBalance() { public void setBalance(double balance) { this.balance = balance; } -} + + /** Adds amount to the balance (used by DepositTransaction). */ + public void credit(double amount) { + this.balance += amount; + } + + /** Subtracts amount from the balance (used by WithdrawalTransaction). */ + public void debit(double amount) { + this.balance -= amount; + } + + @Override + public String toString() { + return String.format("BankAccount[Balance=%.2f]", balance); + } +} \ No newline at end of file diff --git a/src/Lecture4_interfaces_abstract_classes/BaseTransaction.java b/src/Lecture4_interfaces_abstract_classes/BaseTransaction.java index ed81eb8..3c134bb 100644 --- a/src/Lecture4_interfaces_abstract_classes/BaseTransaction.java +++ b/src/Lecture4_interfaces_abstract_classes/BaseTransaction.java @@ -1,51 +1,84 @@ package Lecture4_interfaces_abstract_classes; import org.jetbrains.annotations.NotNull; - import java.util.Calendar; -public abstract class BaseTransaction implements TransactionInterface { +/** + * Q1 - Concrete class that implements TransactionInterface. + * + * The starter code made this abstract; the assignment asks us to make it + * a CONCRETE class that fully implements the interface, while ensuring + * its apply() behaves differently from the subclasses. + * + * Fields match the starter code exactly (int amount, Calendar date, String transactionID). + */ +public class BaseTransaction implements TransactionInterface { + + // --- Fields (preserved from starter code) --- 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 + * Constructor - mirrors the starter code signature. + * + * @param amount integer transaction amount + * @param date must not be null; defensively copied to preserve invariants */ - 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; + public BaseTransaction(int amount, @NotNull Calendar date) { + this.amount = amount; + this.date = (Calendar) date.clone(); // defensive copy (from lecture) + int uniq = (int)(Math.random() * 10000); + this.transactionID = date.toString() + uniq; } - /** - * getAmount() - * @return integer - */ + // --------------------------------------------------------------- + // Q1 – Getter implementations (from TransactionInterface) + // --------------------------------------------------------------- + + @Override public double getAmount() { - return amount; // Because we are dealing with Value types we need not worry about what we return + return amount; // int → double widening; value type, safe to return directly } - /** - * getDate() - * @return Calendar Object - */ + @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(); // defensive / judicious copy (from lecture) } - // Method to get a unique identifier for the transaction - public String getTransactionID(){ - return transactionID; + @Override + public String getTransactionID() { + return transactionID; + } + + // --------------------------------------------------------------- + // Q1 – printTransactionDetails() + // --------------------------------------------------------------- + + @Override + public void printTransactionDetails() { + System.out.println("=== Base Transaction Details ==="); + System.out.println(" Transaction ID : " + transactionID); + System.out.printf (" Amount : %.2f%n", (double) amount); + System.out.printf (" Date : %tF%n", date); + System.out.println(" Type : BaseTransaction"); + } + + // --------------------------------------------------------------- + // Q1 – apply() + // + // BaseTransaction.apply() is intentionally DIFFERENT from the + // subclass overrides: it is a neutral / informational application + // that logs the call but does NOT modify the account balance. + // This makes the base behaviour clearly distinct from Deposit + // (which credits) and Withdrawal (which debits). + // --------------------------------------------------------------- + + @Override + public void apply(BankAccount ba) { + System.out.printf( + "[BaseTransaction] apply() called — Amount: %.2f | Balance unchanged at: %.2f%n", + (double) amount, ba.getBalance() + ); } - // Method to print a transaction receipt or details - public abstract void printTransactionDetails(); - public abstract void apply(BankAccount ba); -} +} \ No newline at end of file diff --git a/src/Lecture4_interfaces_abstract_classes/DepositTrasaction.java b/src/Lecture4_interfaces_abstract_classes/DepositTrasaction.java index 81afab5..8483338 100644 --- a/src/Lecture4_interfaces_abstract_classes/DepositTrasaction.java +++ b/src/Lecture4_interfaces_abstract_classes/DepositTrasaction.java @@ -1,30 +1,54 @@ package Lecture4_interfaces_abstract_classes; import org.jetbrains.annotations.NotNull; - import java.util.Calendar; +/** + * Q1 & Q2 - Concrete class for deposit transactions. + * + * Deposits are IRREVERSIBLE by design (assignment spec, Q2). + * Overrides apply() to credit the BankAccount. + * + * Note: class name intentionally matches starter code spelling ("Trasaction"). + */ public class DepositTrasaction extends BaseTransaction { - public DepositTrasaction(int amount, @NotNull Calendar date){ + + // --- Constructor --- + public DepositTrasaction(int amount, @NotNull Calendar date) { super(amount, date); } - private boolean checkDepositAmount(int amt){ - if (amt < 0){ - return false; - } - else{ - return true; - } + + // Helper preserved from starter code + private boolean checkDepositAmount(int amt) { + return amt >= 0; } - // Method to print a transaction receipt or details - public void printTransactionDetails(){ - System.out.println("Deposit Trasaction: "+this.toString()); + // --------------------------------------------------------------- + // Q1 – Override apply() + // Credits the account; clearly different from BaseTransaction.apply() + // which makes no balance change, and from WithdrawalTransaction + // which debits. + // --------------------------------------------------------------- + + @Override + public void apply(BankAccount ba) { + ba.credit(getAmount()); + System.out.printf( + "[DepositTransaction] Credited %.2f → New balance: %.2f%n", + getAmount(), ba.getBalance() + ); } - public void apply(BankAccount ba){ - double curr_balance = ba.getBalance(); - double new_balance = curr_balance + getAmount(); - ba.setBalance(new_balance); + // --------------------------------------------------------------- + // Q1 – Override printTransactionDetails() + // --------------------------------------------------------------- + + @Override + public void printTransactionDetails() { + System.out.println("=== Deposit Transaction Details ==="); + System.out.println(" Transaction ID : " + getTransactionID()); + System.out.printf (" Amount : +%.2f%n", getAmount()); + System.out.printf (" Date : %tF%n", getDate()); + System.out.println(" Type : Deposit (irreversible)"); } -} +} \ No newline at end of file diff --git a/src/Lecture4_interfaces_abstract_classes/InsufficientFundsException.java b/src/Lecture4_interfaces_abstract_classes/InsufficientFundsException.java new file mode 100644 index 0000000..61a5e8e --- /dev/null +++ b/src/Lecture4_interfaces_abstract_classes/InsufficientFundsException.java @@ -0,0 +1,29 @@ +package Lecture4_interfaces_abstract_classes; + +/** + * Q3 - Custom checked exception for insufficient funds. + * + * Extends Exception (not RuntimeException) so it is a proper checked exception + * and callers are forced to handle or declare it — demonstrating Java's + * exception hierarchy through inheritance. + */ +public class InsufficientFundsException extends Exception { + + private final double amountRequested; + private final double amountAvailable; + + public InsufficientFundsException(double amountRequested, double amountAvailable) { + super(String.format( + "Insufficient funds: requested %.2f but only %.2f is available.", + amountRequested, amountAvailable + )); + this.amountRequested = amountRequested; + this.amountAvailable = amountAvailable; + } + + public double getAmountRequested() { return amountRequested; } + public double getAmountAvailable() { return amountAvailable; } + + /** Convenience: how much short the account is. */ + public double getShortfall() { return amountRequested - amountAvailable; } +} \ No newline at end of file diff --git a/src/Lecture4_interfaces_abstract_classes/TransactionInterface.java b/src/Lecture4_interfaces_abstract_classes/TransactionInterface.java index 5902713..b758909 100644 --- a/src/Lecture4_interfaces_abstract_classes/TransactionInterface.java +++ b/src/Lecture4_interfaces_abstract_classes/TransactionInterface.java @@ -1,9 +1,12 @@ package Lecture4_interfaces_abstract_classes; + import java.util.Calendar; /** - * Interface for Transactions - * Any class that defines a transaction is expected to implement this Interface + * Interface for Transactions. + * Any class that defines a transaction is expected to implement this interface. + * + * Q1: Extended with printTransactionDetails() and apply() as required by the assignment. */ public interface TransactionInterface { @@ -16,6 +19,9 @@ public interface TransactionInterface { // Method to get a unique identifier for the transaction String getTransactionID(); -} - + // Method to print transaction details (Q1) + void printTransactionDetails(); + // Method to apply the transaction to a BankAccount (Q1) + void apply(BankAccount ba); +} \ No newline at end of file diff --git a/src/Lecture4_interfaces_abstract_classes/WithdrawalTransaction.java b/src/Lecture4_interfaces_abstract_classes/WithdrawalTransaction.java index face5b6..df03959 100644 --- a/src/Lecture4_interfaces_abstract_classes/WithdrawalTransaction.java +++ b/src/Lecture4_interfaces_abstract_classes/WithdrawalTransaction.java @@ -1,45 +1,163 @@ package Lecture4_interfaces_abstract_classes; import org.jetbrains.annotations.NotNull; - import java.util.Calendar; +/** + * Q1, Q2 & Q3 - Concrete class for withdrawal transactions. + * + * Withdrawals are REVERSIBLE by design (assignment spec, Q2). + * + * Q1 – Overrides apply() to debit the BankAccount. + * Q2 – Implements reverse() to restore the account to its pre-withdrawal balance. + * Q3 – Overloaded apply(BankAccount, boolean) uses the 'throws' keyword and a + * try { } catch { } finally { } block; handles the partial-withdrawal case. + */ public class WithdrawalTransaction extends BaseTransaction { + + // Tracks the account this transaction was applied to (needed for reverse()) + private BankAccount appliedAccount = null; + + // Records any amount that could NOT be withdrawn in a partial withdrawal + private double amountNotWithdrawn = 0.0; + + // Guards against reversing a transaction that was never successfully applied + private boolean isApplied = false; + + // --- Constructor (matches starter code signature) --- public WithdrawalTransaction(int amount, @NotNull Calendar date) { super(amount, date); } + // Helper preserved from starter code private boolean checkDepositAmount(int amt) { - if (amt < 0) { - return false; - } else { - return true; + return amt >= 0; + } + + // --------------------------------------------------------------- + // Q1 – Override apply() + // + // Simple debit; delegates to the overloaded version. + // Any InsufficientFundsException is caught here and reported so + // that this override still satisfies the void / no-throws signature + // required by the interface. + // --------------------------------------------------------------- + + @Override + public void apply(BankAccount ba) { + try { + apply(ba, false); + } catch (InsufficientFundsException e) { + System.err.println("[WithdrawalTransaction] apply() blocked: " + e.getMessage()); } } - // Method to reverse the transaction - public boolean reverse() { - return true; - } // return true if reversal was successful + // --------------------------------------------------------------- + // Q3 – Overloaded apply() with full exception handling + // + // @param ba the account to debit + // @param allowPartial if true: when 0 < balance < amount, withdraw + // all available balance and record the shortfall. + // if false: throw InsufficientFundsException. + // + // Uses: throws keyword (method signature) + // try / catch / finally block (inside the method body) + // --------------------------------------------------------------- - // Method to print a transaction receipt or details - public void printTransactionDetails() { - System.out.println("Deposit Trasaction: " + this.toString()); + public void apply(BankAccount ba, boolean allowPartial) throws InsufficientFundsException { + + try { + double balance = ba.getBalance(); + + // Case A: account is empty or overdrawn + if (balance <= 0) { + throw new InsufficientFundsException(getAmount(), balance); + } + + // Case B: partial withdrawal allowed and 0 < balance < amount + if (allowPartial && balance < getAmount()) { + amountNotWithdrawn = getAmount() - balance; + ba.debit(balance); // withdraw everything available + appliedAccount = ba; + isApplied = true; + + System.out.printf( + "[WithdrawalTransaction] Partial withdrawal: debited %.2f. " + + "Amount not withdrawn: %.2f. New balance: %.2f%n", + balance, amountNotWithdrawn, ba.getBalance() + ); + + } else if (balance < getAmount()) { + // Case C: insufficient funds, partial NOT allowed → throw + throw new InsufficientFundsException(getAmount(), balance); + + } else { + // Case D: sufficient funds → normal full debit + ba.debit(getAmount()); + appliedAccount = ba; + isApplied = true; + + System.out.printf( + "[WithdrawalTransaction] Debited %.2f. New balance: %.2f%n", + getAmount(), ba.getBalance() + ); + } + + } catch (InsufficientFundsException e) { + // Log and re-throw so the caller can decide how to handle it + System.err.println("[WithdrawalTransaction] Caught: " + e.getMessage()); + throw e; + + } finally { + // Always runs — suitable for audit/logging regardless of outcome + System.out.println("[WithdrawalTransaction] apply() finished for account balance: " + + ba.getBalance()); + } } - /* - Oportunity for assignment: implementing different form of withdrawal - */ - public void apply(BankAccount ba) { - double curr_balance = ba.getBalance(); - if (curr_balance > getAmount()) { - double new_balance = curr_balance - getAmount(); - ba.setBalance(new_balance); + // --------------------------------------------------------------- + // Q2 – reverse() + // + // Reverses a previously applied withdrawal by crediting the exact + // amount that was debited back to the original BankAccount. + // Returns true on success, false if the transaction was never applied. + // --------------------------------------------------------------- + + public boolean reverse() { + if (!isApplied || appliedAccount == null) { + System.out.println("[WithdrawalTransaction] reverse() failed: transaction not yet applied."); + return false; } + + // Amount actually debited = requested amount minus whatever was not withdrawn + double actuallyDebited = getAmount() - amountNotWithdrawn; + appliedAccount.credit(actuallyDebited); + + System.out.printf( + "[WithdrawalTransaction] Reversed: credited %.2f back. Restored balance: %.2f%n", + actuallyDebited, appliedAccount.getBalance() + ); + + // Reset state so the transaction cannot be reversed twice + isApplied = false; + appliedAccount = null; + + return true; } - /* - Assignment 1 Q3: Write the Reverse method - a method unique to the WithdrawalTransaction Class - */ -} + // --------------------------------------------------------------- + // Q1 – Override printTransactionDetails() + // --------------------------------------------------------------- + + @Override + public void printTransactionDetails() { + System.out.println("=== Withdrawal Transaction Details ==="); + System.out.println(" Transaction ID : " + getTransactionID()); + System.out.printf (" Amount : -%.2f%n", getAmount()); + System.out.printf (" Date : %tF%n", getDate()); + System.out.printf (" Amount Not Withdrawn : %.2f%n", amountNotWithdrawn); + System.out.println(" Type : Withdrawal (reversible)"); + } + public double getAmountNotWithdrawn() { return amountNotWithdrawn; } +} \ No newline at end of file diff --git a/src/Main.java b/src/Main.java index 584a048..7ee0ddc 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,77 +1,56 @@ -import Lecture1_adt.*; // Import all classes from Lecture1_adt package to be used in this client code +import Lecture1_adt.*; +import Lecture4_interfaces_abstract_classes.*; 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 +/** + * Client Code - Main class. + * + * Existing lecture test functions are preserved unchanged. + * Q4 adds testDepositTransaction(), testWithdrawalTransaction(), + * and testPolymorphism() below. */ public class Main { + // ================================================================ + // Existing lecture test functions (unchanged) + // ================================================================ + 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 + Calendar d1 = new GregorianCalendar(); + Lecture1_adt.Transaction1 t1 = new Lecture1_adt.Transaction1(1000, d1); 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); - - // 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 + System.out.println("Lecture1_adt.TransactionInterface Date: \t " + t1.date); } - - /** @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 - * */ - public static Transaction2 makeNextPayment(Transaction2 t) { - Calendar d = t.getDate(); + Calendar d = t.getDate(); d.add(Calendar.MONTH, 1); return new Transaction2(t.getAmount(), d); } - /* - Testing Transaction2 class - */ public static void testTransaction2() { - Calendar d1 = new GregorianCalendar(); - - Lecture1_adt.Transaction2 t = new Lecture1_adt.Transaction2(1000, d1); - + Lecture1_adt.Transaction2 t = new Lecture1_adt.Transaction2(1000, d1); Lecture1_adt.Transaction2 modified_t = makeNextPayment(t); 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()); + System.out.println("Lecture1_adt.TransactionInterface Amount: \t " + modified_t.getAmount()); + System.out.println("Lecture1_adt.TransactionInterface Date: \t " + modified_t.getDate().getTime()); 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()); - - /* 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 - */ - + System.out.println("Lecture1_adt.TransactionInterface Amount: \t " + modified_t.getAmount()); + System.out.println("Lecture1_adt.TransactionInterface Date: \t " + modified_t.getDate().getTime()); } - - /** @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 { - - List listOfTransaction3s = new ArrayList(); + public static List makeYearOfPayments(int amount) throws NullPointerException { + List listOfTransaction3s = new ArrayList<>(); Calendar date = new GregorianCalendar(2024, Calendar.JANUARY, 3); - - for (int i = 0; i < 12; i++) { listOfTransaction3s.add(new Transaction3(amount, date)); date.add(Calendar.MONTH, 1); @@ -79,42 +58,20 @@ public static List makeYearOfPayments (int amount) throws NullPoin return listOfTransaction3s; } - /* - Testing Transaction3 class - */ public static void testTransaction3() { - List allPaymentsIn2024 = makeYearOfPayments(1000); - for (Transaction3 t3 : allPaymentsIn2024) { - - // 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("Lecture1_adt.TransactionInterface Amount: \t " + transact.getAmount()); + System.out.println("Lecture1_adt.TransactionInterface Date: \t " + transact.getDate().getTime()); } } - - /* 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 - */ } - - /** @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 - * */ - - public static List makeYearOfPaymentsFinal (int amount) throws NullPointerException { - - List listOfTransaction4s = new ArrayList(); + public static List makeYearOfPaymentsFinal(int amount) throws NullPointerException { + List listOfTransaction4s = new ArrayList<>(); Calendar date = new GregorianCalendar(2024, Calendar.JANUARY, 3); - - for (int i = 0; i < 12; i++) { listOfTransaction4s.add(new Transaction4(amount, date)); date.add(Calendar.MONTH, 1); @@ -122,35 +79,159 @@ public static List makeYearOfPaymentsFinal (int amount) throws Nul return listOfTransaction4s; } - /* - Testing Transaction3 class - */ public static void testTransaction4() { - - /* - * Call the function to make all the Twelve transaction in a year of our business - */ - List transactionsIn2024 = makeYearOfPaymentsFinal(1200); - - // 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()); + System.out.println("Lecture1_adt.TransactionInterface Amount: \t " + transact.getAmount()); + System.out.println("Lecture1_adt.TransactionInterface Date: \t " + transact.getDate().getTime()); } + } - // Please Take a look at all the 12 transaction now and compare with the outputs of the Transaction3 class + + // ================================================================ + // Q4 – New client code: DepositTransaction tests + // ================================================================ + + public static void testDepositTransaction() { + System.out.println("\n============================================================"); + System.out.println(" TEST: DepositTransaction"); + System.out.println("============================================================"); + + Calendar date = new GregorianCalendar(); + BankAccount account = new BankAccount(500.00); + System.out.println("Initial balance: " + account.getBalance()); + + // --- Subtype object, subtype reference --- + DepositTrasaction deposit = new DepositTrasaction(200, date); + deposit.printTransactionDetails(); + deposit.apply(account); + System.out.println("Balance after deposit: " + account.getBalance()); + + // --- Type-cast subtype to supertype, then call apply() --- + // The reference is BaseTransaction (supertype), object is DepositTrasaction. + // Java resolves apply() at RUNTIME (late binding / dynamic dispatch), + // so DepositTrasaction.apply() is called — not BaseTransaction.apply(). + System.out.println("\n-- Type-casting DepositTrasaction → BaseTransaction --"); + BaseTransaction castedDeposit = (BaseTransaction) new DepositTrasaction(100, date); + castedDeposit.printTransactionDetails(); + castedDeposit.apply(account); // late binding → DepositTrasaction.apply() + System.out.println("Balance after casted deposit: " + account.getBalance()); } - 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 + // ================================================================ + // Q4 – New client code: WithdrawalTransaction tests + // ================================================================ + + public static void testWithdrawalTransaction() { + System.out.println("\n============================================================"); + System.out.println(" TEST: WithdrawalTransaction"); + System.out.println("============================================================"); + + Calendar date = new GregorianCalendar(); + BankAccount account = new BankAccount(1000.00); + System.out.println("Initial balance: " + account.getBalance()); + + // --- Normal withdrawal (sufficient funds) --- + WithdrawalTransaction w1 = new WithdrawalTransaction(300, date); + w1.printTransactionDetails(); + w1.apply(account); + System.out.println("Balance after withdrawal: " + account.getBalance()); + + // --- Reverse the withdrawal (Q2) --- + System.out.println("\n-- Reversing withdrawal --"); + boolean reversed = w1.reverse(); + System.out.println("Reversal successful: " + reversed); + System.out.println("Balance after reversal: " + account.getBalance()); + + // --- Insufficient funds, allowPartial = false (Q3, throws) --- + System.out.println("\n-- Withdrawal exceeding balance (allowPartial=false) --"); + WithdrawalTransaction w2 = new WithdrawalTransaction(9999, date); + try { + w2.apply(account, false); + } catch (InsufficientFundsException e) { + System.out.println("Caught InsufficientFundsException: " + e.getMessage()); + System.out.printf ("Shortfall: %.2f%n", e.getShortfall()); + } + System.out.println("Balance unchanged: " + account.getBalance()); + + // --- Partial withdrawal: 0 < balance < amount (Q3) --- + System.out.println("\n-- Partial withdrawal (allowPartial=true, balance < amount) --"); + BankAccount smallAccount = new BankAccount(80.00); + WithdrawalTransaction w3 = new WithdrawalTransaction(200, date); + try { + w3.apply(smallAccount, true); + } catch (InsufficientFundsException e) { + System.err.println("Unexpected exception: " + e.getMessage()); + } + w3.printTransactionDetails(); + System.out.println("Balance after partial withdrawal: " + smallAccount.getBalance()); + } + + + // ================================================================ + // Q4 – New client code: Polymorphism (early vs. late binding) + // ================================================================ + + public static void testPolymorphism() { + System.out.println("\n============================================================"); + System.out.println(" TEST: Polymorphism — Early vs. Late Binding"); + System.out.println("============================================================"); + + Calendar date = new GregorianCalendar(); + BankAccount account = new BankAccount(500.00); - // testTransaction1() - // testTransaction2() - // testTransaction3() - // testTransaction4() + /* + * EARLY BINDING (compile-time / static dispatch): + * The reference type AND the object type are both BaseTransaction. + * The compiler resolves apply() to BaseTransaction.apply() at + * compile time because there is no subtype to dispatch to. + * Result: no balance change (BaseTransaction.apply() is informational). + */ + System.out.println("-- Early Binding: BaseTransaction reference, BaseTransaction object --"); + BaseTransaction earlyBound = new BaseTransaction(50, date); + earlyBound.apply(account); + System.out.println("Balance (unchanged): " + account.getBalance()); + + /* + * LATE BINDING (runtime / dynamic dispatch): + * The reference type is BaseTransaction (supertype), but the actual + * object at runtime is DepositTrasaction (subtype). + * The JVM looks up the method in the actual object's class at runtime + * and dispatches to DepositTrasaction.apply() → balance increases. + */ + System.out.println("\n-- Late Binding: BaseTransaction reference, DepositTrasaction object --"); + BaseTransaction lateDeposit = new DepositTrasaction(150, date); + lateDeposit.apply(account); // JVM dispatches to DepositTrasaction.apply() + System.out.println("Balance after late-bound deposit: " + account.getBalance()); + + /* + * LATE BINDING with WithdrawalTransaction: + * Same principle — the supertype reference holds a WithdrawalTransaction. + * apply() resolves to WithdrawalTransaction.apply() at runtime. + */ + System.out.println("\n-- Late Binding: BaseTransaction reference, WithdrawalTransaction object --"); + BaseTransaction lateWithdraw = new WithdrawalTransaction(100, date); + lateWithdraw.apply(account); // JVM dispatches to WithdrawalTransaction.apply() + System.out.println("Balance after late-bound withdrawal: " + account.getBalance()); + } + + + // ================================================================ + // main() + // ================================================================ + + public static void main(String[] args) { + // Existing lecture tests (uncomment as needed) + // testTransaction1(); + // testTransaction2(); + // testTransaction3(); + // testTransaction4(); + + // Q4 – Assignment tests + testDepositTransaction(); + testWithdrawalTransaction(); + testPolymorphism(); } } \ No newline at end of file