From 7ce2816a1ec28c1d6f5952fef51348974ac3cfc5 Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:56:27 +0200 Subject: [PATCH 01/22] Added Checkstyle to Exchange --- .vscode/settings.json | 3 + src/main/java/Model/Exchange.java | 338 ++++++++++++++++-------------- 2 files changed, 181 insertions(+), 160 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..319ff73 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.checkstyle.configuration": "/google_checks.xml" +} \ No newline at end of file diff --git a/src/main/java/Model/Exchange.java b/src/main/java/Model/Exchange.java index 6db979c..753e980 100644 --- a/src/main/java/Model/Exchange.java +++ b/src/main/java/Model/Exchange.java @@ -1,4 +1,5 @@ package Model; + import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; @@ -6,216 +7,233 @@ import java.util.Map; import java.util.Random; +/** + * Exchange class. + */ public class Exchange { - private final String name; - private int week; - private final Map stockMap; - private final Random random; - - // Registered observers - private final List observers = new ArrayList<>(); + private final String name; + private int week; + private final Map stockMap; + private final Random random; - public Exchange(String name, List stocks) { - if (name == null || name.isBlank()) { - throw new IllegalArgumentException("Exchange name cannot be null or blank"); - } - if (stocks == null) { - throw new IllegalArgumentException("Stock list cannot be null"); - } + // Registered observers + private final List observers = new ArrayList<>(); - this.name = name; - this.week = 1; - this.stockMap = new HashMap<>(); - this.random = new Random(); - - for (Stock stock : stocks) { - if (stock == null) { - throw new IllegalArgumentException("Stock list must not contain null entries"); - } - stockMap.put(stock.getSymbol(), stock); - } + public Exchange(String name, List stocks) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("Exchange name cannot be null or blank"); } - - // ---- Observer ---- - - public void addObserver(ExchangeObserver observer) { - if (observer == null) { - throw new IllegalArgumentException("Observer cannot be null"); - } - if (!observers.contains(observer)) { - observers.add(observer); - } + if (stocks == null) { + throw new IllegalArgumentException("Stock list cannot be null"); } - - public void removeObserver(ExchangeObserver observer) { - observers.remove(observer); + + this.name = name; + this.week = 1; + this.stockMap = new HashMap<>(); + this.random = new Random(); + + for (Stock stock : stocks) { + if (stock == null) { + throw new IllegalArgumentException("Stock list must not contain null entries"); + } + stockMap.put(stock.getSymbol(), stock); } + } - private void notifyObservers() { - for (ExchangeObserver observer : observers) { - observer.onExchangeUpdated(this); - } - } + // ---- Observer ---- - public String getName() { - return name; + public void addObserver(ExchangeObserver observer) { + if (observer == null) { + throw new IllegalArgumentException("Observer cannot be null"); } - - public int getWeek() { - return week; + if (!observers.contains(observer)) { + observers.add(observer); } + } - public boolean hasStock(String symbol) { - if (symbol == null || symbol.isBlank()) { - throw new IllegalArgumentException("Symbol cannot be null or blank"); - } - return stockMap.containsKey(symbol); - } + public void removeObserver(ExchangeObserver observer) { + observers.remove(observer); + } - public Stock getStock(String symbol) { - if (symbol == null || symbol.isBlank()) { - throw new IllegalArgumentException("Symbol cannot be null or blank"); - } - return stockMap.get(symbol); + private void notifyObservers() { + for (ExchangeObserver observer : observers) { + observer.onExchangeUpdated(this); } + } - public List findStocks(String searchTerm) { - if (searchTerm == null) { - throw new IllegalArgumentException("Search term cannot be null"); - } - List result = new ArrayList<>(); - String lowerSearch = searchTerm.toLowerCase(); + public String getName() { + return name; + } - for (Stock stock : stockMap.values()) { - if (stock.getSymbol().toLowerCase().contains(lowerSearch) - || stock.getCompany().toLowerCase().contains(lowerSearch)) { - result.add(stock); - } - } + public int getWeek() { + return week; + } - return result; + public boolean hasStock(String symbol) { + if (symbol == null || symbol.isBlank()) { + throw new IllegalArgumentException("Symbol cannot be null or blank"); + } + return stockMap.containsKey(symbol); + } + + public Stock getStock(String symbol) { + if (symbol == null || symbol.isBlank()) { + throw new IllegalArgumentException("Symbol cannot be null or blank"); + } + return stockMap.get(symbol); + } + + public List findStocks(String searchTerm) { + if (searchTerm == null) { + throw new IllegalArgumentException("Search term cannot be null"); + } + List result = new ArrayList<>(); + String lowerSearch = searchTerm.toLowerCase(); + + for (Stock stock : stockMap.values()) { + if (stock.getSymbol().toLowerCase().contains(lowerSearch) + || stock.getCompany().toLowerCase().contains(lowerSearch)) { + result.add(stock); + } } - public Transaction buy(String symbol, BigDecimal quantity, Player player) { - if (symbol == null || symbol.isBlank()) { - throw new IllegalArgumentException("Symbol cannot be null or blank"); - } - if (quantity == null) { - throw new IllegalArgumentException("Quantity cannot be null"); - } - if (quantity.compareTo(BigDecimal.ZERO) <= 0) { - throw new IllegalArgumentException("Quantity must be greater than zero"); - } - if (player == null) { - throw new IllegalArgumentException("Player cannot be null"); - } + return result; + } + + public Transaction buy(String symbol, BigDecimal quantity, Player player) { + if (symbol == null || symbol.isBlank()) { + throw new IllegalArgumentException("Symbol cannot be null or blank"); + } + if (quantity == null) { + throw new IllegalArgumentException("Quantity cannot be null"); + } + if (quantity.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("Quantity must be greater than zero"); + } + if (player == null) { + throw new IllegalArgumentException("Player cannot be null"); + } - Stock stock = getStock(symbol); - if (stock == null) { - throw new IllegalArgumentException("No stock found with symbol: " + symbol); - } + Stock stock = getStock(symbol); + if (stock == null) { + throw new IllegalArgumentException("No stock found with symbol: " + symbol); + } - // lager en ny "andel" basert på nåværende salgspris - Share shareToBuy = new Share(stock, quantity, stock.getSalesPrice()); - Purchase purchase = new Purchase(shareToBuy, this.week); - purchase.commit(player); + // lager en ny "andel" basert på nåværende salgspris + Share shareToBuy = new Share(stock, quantity, stock.getSalesPrice()); + Purchase purchase = new Purchase(shareToBuy, this.week); + purchase.commit(player); - notifyObservers(); + notifyObservers(); - return purchase; - } + return purchase; + } - public Transaction sell(Share share, Player player) { + public Transaction sell(Share share, Player player) { + return sell(share, share.getQuantity(), player); + } - return sell(share, share.getQuantity(), player); + public Transaction sell(Share originalShare, BigDecimal sellQuantity, Player player) { + if (originalShare == null || sellQuantity == null) { + throw new IllegalArgumentException("Share and quantity cannot be null"); + } + if (player == null) { + throw new IllegalArgumentException("Player cannot be null"); } - public Transaction sell(Share originalShare, BigDecimal sellQuantity, Player player) { - if (originalShare == null || sellQuantity == null) { - throw new IllegalArgumentException("Share and quantity cannot be null"); - } - if (player == null) { - throw new IllegalArgumentException("Player cannot be null"); - } - - // Kan ikke selge mer enn man eier - if (sellQuantity.compareTo(originalShare.getQuantity()) > 0) { - throw new IllegalArgumentException("Cannot sell more shares than owned"); - } - + // Kan ikke selge mer enn man eier + if (sellQuantity.compareTo(originalShare.getQuantity()) > 0) { + throw new IllegalArgumentException("Cannot sell more shares than owned"); + } - Share shareToSell; - if (sellQuantity.compareTo(originalShare.getQuantity()) < 0) { + Share shareToSell; - /** - * Delsalg: - * Original mengde - mengde som selges = gjenværende mengde - */ + if (sellQuantity.compareTo(originalShare.getQuantity()) < 0) { - player.getPortfolio().removeShare(originalShare); - BigDecimal remainderShare = originalShare.getQuantity().subtract(sellQuantity); + //Delsalg: + // Original mengde - mengde som selges = gjenværende mengde - player.getPortfolio().addShare(new Share(originalShare.getStock(), remainderShare, originalShare.getPurchasePrice())); + player.getPortfolio().removeShare(originalShare); - /** - * Legger delmengden midlertidig til i portfolio slik at Sale.commit() finner den - */ + BigDecimal remainderShare = originalShare.getQuantity().subtract(sellQuantity); - shareToSell = new Share(originalShare.getStock(), sellQuantity, originalShare.getPurchasePrice()); + player.getPortfolio().addShare(new Share(originalShare + .getStock(), remainderShare, originalShare.getPurchasePrice())); - player.getPortfolio().addShare(shareToSell); + + //Legger delmengden midlertidig til i portfolio slik at Sale.commit() finner den - } + shareToSell = new Share(originalShare + .getStock(), sellQuantity, originalShare.getPurchasePrice()); - else { - // Fullstendig salg: hele andelen selges som normalt - shareToSell = originalShare; - } + player.getPortfolio().addShare(shareToSell); - // Salgstransaksjon - Transaction sale = TransactionFactory.createSale(shareToSell, this.week); + } - sale.commit(player); - notifyObservers(); - return sale; + else { + // Fullstendig salg: hele andelen selges som normalt + shareToSell = originalShare; } - public void advance() { - - week++; + // Salgstransaksjon + Transaction sale = TransactionFactory.createSale(shareToSell, this.week); - for (Stock stock : stockMap.values()) { // henter stock-objektene + sale.commit(player); + notifyObservers(); + return sale; + } - BigDecimal currentPrice = stock.getSalesPrice(); // henter siste pris fra Stock + /** + * Advances to the next week. + */ + public void advance() { + + week++; - double changePercent = (random.nextDouble() - 0.5) * 0.1; + for (Stock stock : stockMap.values()) { // henter stock-objektene - BigDecimal change = currentPrice.multiply(BigDecimal.valueOf(changePercent)); + BigDecimal currentPrice = stock.getSalesPrice(); // henter siste pris fra Stock - BigDecimal newPrice = currentPrice.add(change); + double changePercent = (random.nextDouble() - 0.5) * 0.1; - if (newPrice.compareTo(BigDecimal.ZERO) > 0) { // unngå negativ pris - stock.addNewSalesPrice(newPrice); - } - } + BigDecimal change = currentPrice.multiply(BigDecimal.valueOf(changePercent)); - notifyObservers(); - } + BigDecimal newPrice = currentPrice.add(change); - public List getGainers(int limit) { // viser "vinnerne" - return stockMap.values().stream() - .sorted((s1, s2) -> s2.getLatestPriceChange().compareTo(s1.getLatestPriceChange())) - .limit(limit) - .toList(); + if (newPrice.compareTo(BigDecimal.ZERO) > 0) { // unngå negativ pris + stock.addNewSalesPrice(newPrice); + } } - public List getLosers(int limit) { // viser "taperne" - return stockMap.values().stream() - .sorted((s1, s2) -> s1.getLatestPriceChange().compareTo(s2.getLatestPriceChange())) - .limit(limit) - .toList(); - } + notifyObservers(); + } + + /** + * Shows the weeks Gainers, the stocks with the best price change. + * + * @param limit how many gainers + * @return returns the gainers + */ + public List getGainers(int limit) { // viser "vinnerne" + return stockMap.values().stream() + .sorted((s1, s2) -> s2.getLatestPriceChange().compareTo(s1.getLatestPriceChange())) + .limit(limit) + .toList(); + } + + /** + * Shows the weeks Losers, the stocks with the worst price change. + * + * @param limit how many losers + * @return returns the losers + */ + public List getLosers(int limit) { // viser "taperne" + return stockMap.values().stream() + .sorted((s1, s2) -> s1.getLatestPriceChange().compareTo(s2.getLatestPriceChange())) + .limit(limit) + .toList(); + } } From 50bf4be748ae02a86a50b511d202af749587dd3a Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:56:35 +0200 Subject: [PATCH 02/22] Added Checkstyle to ExchangeObserver --- src/main/java/Model/ExchangeObserver.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/Model/ExchangeObserver.java b/src/main/java/Model/ExchangeObserver.java index ad3f6a5..145f3cc 100644 --- a/src/main/java/Model/ExchangeObserver.java +++ b/src/main/java/Model/ExchangeObserver.java @@ -7,10 +7,10 @@ */ public interface ExchangeObserver { - /** - * Called by the Exchange after its state has changed. - * - * @param exchange the Exchange that triggered the notification - */ - void onExchangeUpdated(Exchange exchange); + /** + * Called by the Exchange after its state has changed. + * + * @param exchange the Exchange that triggered the notification + */ + void onExchangeUpdated(Exchange exchange); } From f43531407a9958bbba06222c9d36606ed087520e Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:56:43 +0200 Subject: [PATCH 03/22] Added Checkstyle to Player --- src/main/java/Model/Player.java | 173 ++++++++++++++++++-------------- 1 file changed, 98 insertions(+), 75 deletions(-) diff --git a/src/main/java/Model/Player.java b/src/main/java/Model/Player.java index ef078a5..3293db1 100644 --- a/src/main/java/Model/Player.java +++ b/src/main/java/Model/Player.java @@ -1,96 +1,119 @@ package Model; + import java.math.BigDecimal; +/** + * The player class is a description of the person player the game, + * their starting point and their progress. + */ public class Player { - private String name; - private BigDecimal startingMoney; - private BigDecimal money; - private Portfolio portfolio; - private TransactionArchive transactionArchive; - - public Player(String name, BigDecimal startingMoney) { - if (name == null || name.isBlank()) { - throw new IllegalArgumentException("Player name cannot be null or blank"); - } - if (startingMoney == null) { - throw new IllegalArgumentException("Starting money cannot be null"); - } - if (startingMoney.compareTo(BigDecimal.ZERO) < 0) { - throw new IllegalArgumentException("Starting money cannot be negative"); - } + private String name; + private BigDecimal startingMoney; + private BigDecimal money; + private Portfolio portfolio; + private TransactionArchive transactionArchive; - this.name = name; - this.startingMoney = startingMoney; - this.money = startingMoney; - this.portfolio = new Portfolio(); - this.transactionArchive = new TransactionArchive(); + /** + * Player method that includes the players name and the money they start with. + * + * @param name the name of the player + * @param startingMoney money the player starts with + */ + public Player(String name, BigDecimal startingMoney) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("Player name cannot be null or blank"); } - - public String getName() { - return this.name; + if (startingMoney == null) { + throw new IllegalArgumentException("Starting money cannot be null"); } - - public BigDecimal getMoney() { - return this.money; + if (startingMoney.compareTo(BigDecimal.ZERO) < 0) { + throw new IllegalArgumentException("Starting money cannot be negative"); } - public void addMoney(BigDecimal amount) { - if (amount == null) { - throw new IllegalArgumentException("Amount cannot be null"); - } - if (amount.compareTo(BigDecimal.ZERO) < 0) { - throw new IllegalArgumentException("Amount to add cannot be negative"); - } - this.money = this.money.add(amount); - } + this.name = name; + this.startingMoney = startingMoney; + this.money = startingMoney; + this.portfolio = new Portfolio(); + this.transactionArchive = new TransactionArchive(); + } - public void withdrawMoney(BigDecimal amount) { - if (amount == null) { - throw new IllegalArgumentException("Amount cannot be null"); - } - if (amount.compareTo(BigDecimal.ZERO) < 0) { - throw new IllegalArgumentException("Amount to withdraw cannot be negative"); - } - this.money = this.money.subtract(amount); - } + public String getName() { + return this.name; + } - public Portfolio getPortfolio() { - return this.portfolio; + public BigDecimal getMoney() { + return this.money; + } + + /** + * Amount of money added. + * + * @param amount amount of money + */ + public void addMoney(BigDecimal amount) { + if (amount == null) { + throw new IllegalArgumentException("Amount cannot be null"); + } + if (amount.compareTo(BigDecimal.ZERO) < 0) { + throw new IllegalArgumentException("Amount to add cannot be negative"); } + this.money = this.money.add(amount); + } - public TransactionArchive getTransactionArchive() { - return this.transactionArchive; + /** + * Amount of money withdrawn. + * + * @param amount amount of money + */ + public void withdrawMoney(BigDecimal amount) { + if (amount == null) { + throw new IllegalArgumentException("Amount cannot be null"); } + if (amount.compareTo(BigDecimal.ZERO) < 0) { + throw new IllegalArgumentException("Amount to withdraw cannot be negative"); + } + this.money = this.money.subtract(amount); + } - /** - * Returns the player's current status based on trading activity and net worth performance. - * - * Status levels: - * - NOVICE: Starting level, no requirements - * - INVESTOR: At least 10 weeks of trading AND net worth increased by at least 20% - * - SPECULATOR: At least 20 weeks of trading AND net worth at least doubled - * - * @return the player's current PlayerStatus - */ - public PlayerStatus getStatus() { - // Calculate total net worth: current cash + portfolio value - BigDecimal totalNetWorth = this.money.add(this.portfolio.getNetWorth()); - - // Get number of weeks with trading activity - int weeksActive = this.transactionArchive.countDistinctWeeks(); + public Portfolio getPortfolio() { + return this.portfolio; + } + + public TransactionArchive getTransactionArchive() { + return this.transactionArchive; + } + + + /** + * Returns the player's current status based on trading activity and net worth performance. + * Status levels: + * - NOVICE: Starting level, no requirements + * - INVESTOR: At least 10 weeks of trading AND net worth increased by at least 20% + * - SPECULATOR: At least 20 weeks of trading AND net worth at least doubled + * + * @return the player's current PlayerStatus + */ + public PlayerStatus getStatus() { + // Calculate total net worth: current cash + portfolio value + BigDecimal totalNetWorth = this.money.add(this.portfolio.getNetWorth()); - // Check for SPECULATOR status: 20+ weeks AND net worth doubled - if (weeksActive >= 20 && totalNetWorth.compareTo(this.startingMoney.multiply(new BigDecimal("2"))) >= 0) { - return PlayerStatus.SPECULATOR; - } + // Get number of weeks with trading activity + int weeksActive = this.transactionArchive.countDistinctWeeks(); - // Check for INVESTOR status: 10+ weeks AND net worth increased by 20% - if (weeksActive >= 10 && totalNetWorth.compareTo(this.startingMoney.multiply(new BigDecimal("1.2"))) >= 0) { - return PlayerStatus.INVESTOR; - } + // Check for SPECULATOR status: 20+ weeks AND net worth doubled + if (weeksActive >= 20 && totalNetWorth.compareTo(this + .startingMoney.multiply(new BigDecimal("2"))) >= 0) { + return PlayerStatus.SPECULATOR; + } - // Default: NOVICE - return PlayerStatus.NOVICE; + // Check for INVESTOR status: 10+ weeks AND net worth increased by 20% + if (weeksActive >= 10 && totalNetWorth.compareTo(this + .startingMoney.multiply(new BigDecimal("1.2"))) >= 0) { + return PlayerStatus.INVESTOR; } + + // Default: NOVICE + return PlayerStatus.NOVICE; + } } From b08e846fdefdb5234b35827b3d149ede8e0dbee8 Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:56:50 +0200 Subject: [PATCH 04/22] Added Checkstyle to PlayerStatus --- src/main/java/Model/PlayerStatus.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/Model/PlayerStatus.java b/src/main/java/Model/PlayerStatus.java index cf14ea8..e72a9ef 100644 --- a/src/main/java/Model/PlayerStatus.java +++ b/src/main/java/Model/PlayerStatus.java @@ -2,7 +2,7 @@ /** * Enum representing the player's status level based on trading activity and performance. - * + * Status levels: * - NOVICE: Starting level, no requirements * - INVESTOR: Traded for at least 10 weeks AND increased net worth by at least 20% @@ -13,13 +13,13 @@ public enum PlayerStatus { INVESTOR("Investor"), SPECULATOR("Speculator"); - private final String displayName; + private final String displayName; - PlayerStatus(String displayName) { - this.displayName = displayName; - } + PlayerStatus(String displayName) { + this.displayName = displayName; + } - public String getDisplayName() { - return displayName; - } + public String getDisplayName() { + return displayName; + } } From 2774464c535986ca66ad726f95311b5d6d581b17 Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:56:59 +0200 Subject: [PATCH 05/22] Added Checkstyle to Portfolio --- src/main/java/Model/Portfolio.java | 101 +++++++++++++++++++---------- 1 file changed, 67 insertions(+), 34 deletions(-) diff --git a/src/main/java/Model/Portfolio.java b/src/main/java/Model/Portfolio.java index ba050e7..902b8b9 100644 --- a/src/main/java/Model/Portfolio.java +++ b/src/main/java/Model/Portfolio.java @@ -1,54 +1,87 @@ package Model; + import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +/** + * Portfolio class of the player. + */ public class Portfolio { - private final List shares; + private final List shares; - public Portfolio() { - this.shares = new ArrayList<>(); - } + public Portfolio() { + this.shares = new ArrayList<>(); + } - public boolean addShare(Share share) { - if (share == null) { - throw new IllegalArgumentException("Share cannot be null"); - } - return shares.add(share); + /** + * Method to add share. + * + * @param share the share being added + * @return returns the added share + */ + public boolean addShare(Share share) { + if (share == null) { + throw new IllegalArgumentException("Share cannot be null"); } + return shares.add(share); + } - public boolean removeShare(Share share) { - if (share == null) { - throw new IllegalArgumentException("Share cannot be null"); - } - return shares.remove(share); + /** + * Method to remove share. + * + * @param share the share being removed + * @return returns the removed share + */ + public boolean removeShare(Share share) { + if (share == null) { + throw new IllegalArgumentException("Share cannot be null"); } + return shares.remove(share); + } - public List getShares() { - return new ArrayList<>(shares); - } + public List getShares() { + return new ArrayList<>(shares); + } - public List getShares(String symbol) { - if (symbol == null || symbol.isBlank()) { - throw new IllegalArgumentException("Symbol cannot be null or blank"); - } - return shares.stream() - .filter(share -> share.getStock().getSymbol().equals(symbol)) - .toList(); + /** + * Method to get all shares. + * + * @param symbol the symbol for each stock + * @return the shares of stocks + */ + public List getShares(String symbol) { + if (symbol == null || symbol.isBlank()) { + throw new IllegalArgumentException("Symbol cannot be null or blank"); } + return shares.stream() + .filter(share -> share.getStock().getSymbol().equals(symbol)) + .toList(); + } - public boolean contains(Share share) { - if (share == null) { - throw new IllegalArgumentException("Share cannot be null"); - } - return shares.contains(share); + /** + * Method to check if contains share. + * + * @param share the share + * @return the share if not null + */ + public boolean contains(Share share) { + if (share == null) { + throw new IllegalArgumentException("Share cannot be null"); } + return shares.contains(share); + } - public BigDecimal getNetWorth() { - return shares.stream() - .map(share -> new SaleCalculator(share).calculateTotal()) - .reduce(BigDecimal.ZERO, BigDecimal::add); - } + /** + * Method to get the new worth. + * + * @return returns the net worth + */ + public BigDecimal getNetWorth() { + return shares.stream() + .map(share -> new SaleCalculator(share).calculateTotal()) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } } From 6803d26550c4efe151dcab665f965ddcf23bd157 Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:57:05 +0200 Subject: [PATCH 06/22] Added Checkstyle to Purchase --- src/main/java/Model/Purchase.java | 48 +++++++++++++++++-------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/main/java/Model/Purchase.java b/src/main/java/Model/Purchase.java index 33c41e5..d367a7b 100644 --- a/src/main/java/Model/Purchase.java +++ b/src/main/java/Model/Purchase.java @@ -1,32 +1,36 @@ package Model; + import java.math.BigDecimal; +/** + * Purchase class whenever the player makes a purchase. + */ public class Purchase extends Transaction { - public Purchase(Share share, int week) { - super(share, week, new PurchaseCalculator(share)); - } + public Purchase(Share share, int week) { + super(share, week, new PurchaseCalculator(share)); + } - @Override - public void commit(Player player) { - if (player == null) { - throw new IllegalArgumentException("Player cannot be null"); - } - if (isCommitted()) { - throw new IllegalStateException("Purchase has already been committed"); - } + @Override + public void commit(Player player) { + if (player == null) { + throw new IllegalArgumentException("Player cannot be null"); + } + if (isCommitted()) { + throw new IllegalStateException("Purchase has already been committed"); + } - BigDecimal price = this.getCalculator().calculateTotal(); + BigDecimal price = this.getCalculator().calculateTotal(); - if (player.getMoney().compareTo(price) < 0) { - throw new IllegalStateException( - "Insufficient funds: required " + price + ", available " + player.getMoney() - ); - } + if (player.getMoney().compareTo(price) < 0) { + throw new IllegalStateException( + "Insufficient funds: required " + price + ", available " + player.getMoney() + ); + } - player.withdrawMoney(price); - player.getPortfolio().addShare(this.getShare()); - player.getTransactionArchive().add(this); + player.withdrawMoney(price); + player.getPortfolio().addShare(this.getShare()); + player.getTransactionArchive().add(this); - this.committed = true; - } + this.committed = true; + } } From 03e503ca3465ee777018dfad905485c277654de8 Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:57:15 +0200 Subject: [PATCH 07/22] Added Checkstyle to PurchaseCalculator --- src/main/java/Model/PurchaseCalculator.java | 48 ++++++++++++--------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/src/main/java/Model/PurchaseCalculator.java b/src/main/java/Model/PurchaseCalculator.java index 6a29413..e90839f 100644 --- a/src/main/java/Model/PurchaseCalculator.java +++ b/src/main/java/Model/PurchaseCalculator.java @@ -1,30 +1,38 @@ package Model; + import java.math.BigDecimal; +/** + * PurchaseCalculator class that does calculations. + */ public class PurchaseCalculator implements TransactionCalculator { - private BigDecimal purchasePrice; - private BigDecimal quantity; + private final BigDecimal purchasePrice; + private final BigDecimal quantity; - public PurchaseCalculator(Share share) { - this.purchasePrice = share.getPurchasePrice(); - this.quantity = share.getQuantity(); - } + public PurchaseCalculator(Share share) { + this.purchasePrice = share.getPurchasePrice(); + this.quantity = share.getQuantity(); + } - public BigDecimal calculateGross() { - return this.purchasePrice.multiply(this.quantity); - } + @Override + public BigDecimal calculateGross() { + return this.purchasePrice.multiply(this.quantity); + } - public BigDecimal calculateCommission() { - BigDecimal rate = new BigDecimal("0.005"); - return calculateGross().multiply(rate); - } + @Override + public BigDecimal calculateCommission() { + BigDecimal rate = new BigDecimal("0.005"); + return calculateGross().multiply(rate); + } - public BigDecimal calculateTax() { - BigDecimal tax = new BigDecimal("0"); - return tax; - } + @Override + public BigDecimal calculateTax() { + BigDecimal tax = new BigDecimal("0"); + return tax; + } - public BigDecimal calculateTotal() { - return calculateGross().add(calculateCommission()).add(calculateTax()); - } + @Override + public BigDecimal calculateTotal() { + return calculateGross().add(calculateCommission()).add(calculateTax()); + } } From e2c15cb76e49dd4f9336ffe5939f3e8a419844bb Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:57:20 +0200 Subject: [PATCH 08/22] Added Checkstyle to Sale --- src/main/java/Model/Sale.java | 48 +++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/main/java/Model/Sale.java b/src/main/java/Model/Sale.java index 7653df4..e130f41 100644 --- a/src/main/java/Model/Sale.java +++ b/src/main/java/Model/Sale.java @@ -1,31 +1,35 @@ package Model; + import java.math.BigDecimal; +/** + * Sale class for whenever the player makes a sale. + */ public class Sale extends Transaction { - public Sale(Share share, int week) { - super(share, week, new SaleCalculator(share)); - } + public Sale(Share share, int week) { + super(share, week, new SaleCalculator(share)); + } - @Override - public void commit(Player player) { - if (player == null) { - throw new IllegalArgumentException("Player cannot be null"); - } - if (isCommitted()) { - throw new IllegalStateException("Sale has already been committed"); - } - if (!player.getPortfolio().contains(this.getShare())) { - throw new IllegalStateException( - "Share not found in player's portfolio: " + this.getShare().getStock().getSymbol() - ); - } + @Override + public void commit(Player player) { + if (player == null) { + throw new IllegalArgumentException("Player cannot be null"); + } + if (isCommitted()) { + throw new IllegalStateException("Sale has already been committed"); + } + if (!player.getPortfolio().contains(this.getShare())) { + throw new IllegalStateException( + "Share not found in player's portfolio: " + this.getShare().getStock().getSymbol() + ); + } - BigDecimal price = getCalculator().calculateTotal(); + BigDecimal price = getCalculator().calculateTotal(); - player.addMoney(price); - player.getPortfolio().removeShare(this.getShare()); - player.getTransactionArchive().add(this); + player.addMoney(price); + player.getPortfolio().removeShare(this.getShare()); + player.getTransactionArchive().add(this); - this.committed = true; - } + this.committed = true; + } } From b8e334c0ac3a1aeef3c3cb0f6bbd6e1ad524d90f Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:57:29 +0200 Subject: [PATCH 09/22] Added Checkstyle to SaleCalculator --- src/main/java/Model/SaleCalculator.java | 73 +++++++++++++++---------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/src/main/java/Model/SaleCalculator.java b/src/main/java/Model/SaleCalculator.java index 27fd394..93b6f93 100644 --- a/src/main/java/Model/SaleCalculator.java +++ b/src/main/java/Model/SaleCalculator.java @@ -1,36 +1,51 @@ package Model; + import java.math.BigDecimal; +/** + * SaleCalculator class that calculates gross, commission, tax and total. + */ public class SaleCalculator implements TransactionCalculator{ - private BigDecimal purchasePrice; - private BigDecimal salesPrice; - private BigDecimal quantity; - - public SaleCalculator(Share share) { - this.purchasePrice = share.getPurchasePrice(); - this.salesPrice = share.getStock().getSalesPrice(); - this.quantity = share.getQuantity(); - } - - public BigDecimal calculateGross() { - return this.salesPrice.multiply(this.quantity); - } - - public BigDecimal calculateCommission() { - BigDecimal rate = new BigDecimal("0.01"); - return calculateGross().multiply(rate); - } - - public BigDecimal calculateTax() { - BigDecimal sellingCost = this.purchasePrice.multiply(this.quantity); - BigDecimal profit = calculateGross().subtract(calculateCommission()).subtract(sellingCost); - BigDecimal rate = new BigDecimal("0.3"); - return profit.multiply(rate); - } - - public BigDecimal calculateTotal() { - return calculateGross().subtract(calculateCommission()).subtract(calculateTax()); - } + private BigDecimal purchasePrice; + private BigDecimal salesPrice; + private BigDecimal quantity; + + /** + * Method with parameters. + * + * @param share share from calculator + */ + public SaleCalculator(Share share) { + this.purchasePrice = share.getPurchasePrice(); + this.salesPrice = share.getStock().getSalesPrice(); + this.quantity = share.getQuantity(); + } + + @Override + public BigDecimal calculateGross() { + return this.salesPrice.multiply(this.quantity); + } + + @Override + public BigDecimal calculateCommission() { + BigDecimal rate = new BigDecimal("0.01"); + return calculateGross().multiply(rate); + } + + /** + * Method that calculates tax. + */ + public BigDecimal calculateTax() { + BigDecimal sellingCost = this.purchasePrice.multiply(this.quantity); + BigDecimal profit = calculateGross().subtract(calculateCommission()).subtract(sellingCost); + BigDecimal rate = new BigDecimal("0.3"); + return profit.multiply(rate); + } + + @Override + public BigDecimal calculateTotal() { + return calculateGross().subtract(calculateCommission()).subtract(calculateTax()); + } From 098388e3159ffc211b31564c165da7ca32edbe82 Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:57:38 +0200 Subject: [PATCH 10/22] Added Checkstyle to Share --- src/main/java/Model/Share.java | 77 +++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/src/main/java/Model/Share.java b/src/main/java/Model/Share.java index 92d6101..d7fee43 100644 --- a/src/main/java/Model/Share.java +++ b/src/main/java/Model/Share.java @@ -1,44 +1,55 @@ package Model; + import java.math.BigDecimal; +/** + * Share class. + */ public class Share { - private final Stock stock; - private final BigDecimal quantity; - private final BigDecimal purchasePrice; - - public Share(Stock stock, BigDecimal quantity, BigDecimal purchasePrice) { - if (stock == null) { - throw new IllegalArgumentException("Stock cannot be null"); - } - if (quantity == null) { - throw new IllegalArgumentException("Quantity cannot be null"); - } - if (quantity.compareTo(BigDecimal.ZERO) <= 0) { - throw new IllegalArgumentException("Quantity must be greater than zero"); - } - if (purchasePrice == null) { - throw new IllegalArgumentException("Purchase price cannot be null"); - } - if (purchasePrice.compareTo(BigDecimal.ZERO) < 0) { - throw new IllegalArgumentException("Purchase price cannot be negative"); - } - - this.stock = stock; - this.quantity = quantity; - this.purchasePrice = purchasePrice; - } + private final Stock stock; + private final BigDecimal quantity; + private final BigDecimal purchasePrice; - public Stock getStock() { - return stock; + /** + * Share method that has stock, quantity and purchaseprice. + * + * @param stock stock name + * @param quantity quantity of stock + * @param purchasePrice price of stock + */ + public Share(Stock stock, BigDecimal quantity, BigDecimal purchasePrice) { + if (stock == null) { + throw new IllegalArgumentException("Stock cannot be null"); + } + if (quantity == null) { + throw new IllegalArgumentException("Quantity cannot be null"); + } + if (quantity.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("Quantity must be greater than zero"); } - - public BigDecimal getQuantity() { - return quantity; + if (purchasePrice == null) { + throw new IllegalArgumentException("Purchase price cannot be null"); } - - public BigDecimal getPurchasePrice() { - return purchasePrice; + if (purchasePrice.compareTo(BigDecimal.ZERO) < 0) { + throw new IllegalArgumentException("Purchase price cannot be negative"); } + + this.stock = stock; + this.quantity = quantity; + this.purchasePrice = purchasePrice; + } + + public Stock getStock() { + return stock; + } + + public BigDecimal getQuantity() { + return quantity; + } + + public BigDecimal getPurchasePrice() { + return purchasePrice; + } } From dd223594b3692c3ffceacb7f0cdd44f6d89ea2b3 Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:57:43 +0200 Subject: [PATCH 11/22] Added Checkstyle to Stock --- src/main/java/Model/Stock.java | 127 ++++++++++++++++++--------------- 1 file changed, 71 insertions(+), 56 deletions(-) diff --git a/src/main/java/Model/Stock.java b/src/main/java/Model/Stock.java index 52c1fd2..ca117b5 100644 --- a/src/main/java/Model/Stock.java +++ b/src/main/java/Model/Stock.java @@ -1,77 +1,92 @@ package Model; + import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +/** + * STock class. + */ public class Stock { - private final String symbol; - private final String company; - private final List prices; - - public Stock(String symbol, String company, BigDecimal salesPrice) { - if (symbol == null || symbol.isBlank()) { - throw new IllegalArgumentException("Symbol cannot be null or blank"); - } - if (company == null || company.isBlank()) { - throw new IllegalArgumentException("Company name cannot be null or blank"); - } - if (salesPrice == null) { - throw new IllegalArgumentException("Initial sales price cannot be null"); - } - if (salesPrice.compareTo(BigDecimal.ZERO) < 0) { - throw new IllegalArgumentException("Initial sales price cannot be negative"); - } + private final String symbol; + private final String company; + private final List prices; - this.symbol = symbol; - this.company = company; - this.prices = new ArrayList<>(); - this.prices.add(salesPrice); + /** + * Stock method that includes symbol of the stock, company and the salesprice. + */ + public Stock(String symbol, String company, BigDecimal salesPrice) { + if (symbol == null || symbol.isBlank()) { + throw new IllegalArgumentException("Symbol cannot be null or blank"); } - - public String getSymbol() { - return symbol; + if (company == null || company.isBlank()) { + throw new IllegalArgumentException("Company name cannot be null or blank"); } - - public String getCompany() { - return company; + if (salesPrice == null) { + throw new IllegalArgumentException("Initial sales price cannot be null"); } - - public BigDecimal getSalesPrice() { - return prices.get(prices.size() - 1); + if (salesPrice.compareTo(BigDecimal.ZERO) < 0) { + throw new IllegalArgumentException("Initial sales price cannot be negative"); } + + this.symbol = symbol; + this.company = company; + this.prices = new ArrayList<>(); + this.prices.add(salesPrice); + } + + public String getSymbol() { + return symbol; + } + + public String getCompany() { + return company; + } + + public BigDecimal getSalesPrice() { + return prices.get(prices.size() - 1); + } - public void addNewSalesPrice(BigDecimal price) { - if (price == null) { - throw new IllegalArgumentException("Price cannot be null"); - } - if (price.compareTo(BigDecimal.ZERO) < 0) { - throw new IllegalArgumentException("Price cannot be negative"); - } - prices.add(price); + /** + * Method that makes new salesprice. + */ + public void addNewSalesPrice(BigDecimal price) { + if (price == null) { + throw new IllegalArgumentException("Price cannot be null"); } - - public List getHistoricalPrices() { - return new ArrayList<>(prices); // returnerer en kopi for å beskytte selve listen + if (price.compareTo(BigDecimal.ZERO) < 0) { + throw new IllegalArgumentException("Price cannot be negative"); } + prices.add(price); + } - public BigDecimal getHighestPrice() { - return prices.stream() - .reduce(prices.get(0), BigDecimal::max); - } + public List getHistoricalPrices() { + return new ArrayList<>(prices); // returnerer en kopi for å beskytte selve listen + } - public BigDecimal getLowestPrice() { - return prices.stream() - .reduce(prices.get(0), BigDecimal::min); - } + public BigDecimal getHighestPrice() { + return prices.stream() + .reduce(prices.get(0), BigDecimal::max); + } - public BigDecimal getLatestPriceChange() { - if (prices.size() < 2) { - return BigDecimal.ZERO; - } + public BigDecimal getLowestPrice() { + return prices.stream() + .reduce(prices.get(0), BigDecimal::min); + } - BigDecimal latest = prices.get(prices.size() - 1); - BigDecimal previous = prices.get(prices.size() - 2); - return latest.subtract(previous); + /** + * Method that gets the latest price change. + * + * @return returns the price change + */ + public BigDecimal getLatestPriceChange() { + if (prices.size() < 2) { + return BigDecimal.ZERO; } + + BigDecimal latest = prices.get(prices.size() - 1); + BigDecimal previous = prices.get(prices.size() - 2); + return latest.subtract(previous); + } } From e40cc48846bcbc628359a139ee74ae9e1c1092bb Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:57:49 +0200 Subject: [PATCH 12/22] Added Checkstyle to Transaction --- src/main/java/Model/Transaction.java | 68 +++++++++++++++------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/src/main/java/Model/Transaction.java b/src/main/java/Model/Transaction.java index de68854..cedd4d2 100644 --- a/src/main/java/Model/Transaction.java +++ b/src/main/java/Model/Transaction.java @@ -1,42 +1,46 @@ package Model; + +/** + * Transaction class. + */ public abstract class Transaction { - private Share share; - private int week; - private TransactionCalculator calculator; - protected boolean committed; - - protected Transaction(Share share, int week, TransactionCalculator calculator) { - if (share == null) { - throw new IllegalArgumentException("Share cannot be null"); - } - if (week < 1) { - throw new IllegalArgumentException("Week must be at least 1"); - } - if (calculator == null) { - throw new IllegalArgumentException("Calculator cannot be null"); - } - - this.share = share; - this.week = week; - this.calculator = calculator; - } + private Share share; + private int week; + private TransactionCalculator calculator; + protected boolean committed; - public Share getShare() { - return this.share; + protected Transaction(Share share, int week, TransactionCalculator calculator) { + if (share == null) { + throw new IllegalArgumentException("Share cannot be null"); } - - public int getWeek() { - return this.week; + if (week < 1) { + throw new IllegalArgumentException("Week must be at least 1"); } - - public TransactionCalculator getCalculator() { - return this.calculator; + if (calculator == null) { + throw new IllegalArgumentException("Calculator cannot be null"); } - public boolean isCommitted() { - return this.committed; - } + this.share = share; + this.week = week; + this.calculator = calculator; + } + + public Share getShare() { + return this.share; + } + + public int getWeek() { + return this.week; + } + + public TransactionCalculator getCalculator() { + return this.calculator; + } + + public boolean isCommitted() { + return this.committed; + } - public abstract void commit(Player player); + public abstract void commit(Player player); } From 2a35c2eb9a194a30cb04b719507e76a421e168e9 Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:57:57 +0200 Subject: [PATCH 13/22] Added Checkstyle to TransactionArchive --- src/main/java/Model/TransactionArchive.java | 82 +++++++++++++-------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/src/main/java/Model/TransactionArchive.java b/src/main/java/Model/TransactionArchive.java index 149e2be..db7a50c 100644 --- a/src/main/java/Model/TransactionArchive.java +++ b/src/main/java/Model/TransactionArchive.java @@ -1,45 +1,67 @@ package Model; + import java.util.ArrayList; -import java.util.stream.Collectors; import java.util.List; +import java.util.stream.Collectors; +/** + * TransactionArchive class. + */ public class TransactionArchive { - private List transactions; + private List transactions; - public TransactionArchive() { - this.transactions = new ArrayList<>(); - } + public TransactionArchive() { + this.transactions = new ArrayList<>(); + } - public boolean add(Transaction transaction) { - if (transaction == null) { - throw new IllegalArgumentException("Should not be null"); // Eller NullPointerExeption? - } + public boolean add(Transaction transaction) { + if (transaction == null) { + throw new IllegalArgumentException("Should not be null"); // Eller NullPointerExeption? + } - return transactions.add(transaction); + return transactions.add(transaction); + } - } + public boolean isEmpty() { + return transactions.isEmpty(); + } - public boolean isEmpty() { - return transactions.isEmpty(); - } + public List getTransactions(int week) { + return transactions.stream() + .filter(transaction -> transaction.getWeek() == week).collect(Collectors.toList()); + } - public List getTransactions(int week) { - return transactions.stream().filter(transaction -> transaction.getWeek() == week).collect(Collectors.toList()); - } - - public List getPurchase(int week) { - return transactions.stream().filter(transaction -> transaction.getWeek() == week).filter(purchase -> purchase instanceof Purchase).map(transaction -> (Purchase) transaction).collect(Collectors.toList()); - } + /** + * List of all purchases in one week. + * + * @param week the week to find purchases for + * @return returns the purchases + */ + public List getPurchase(int week) { + return transactions.stream() + .filter(transaction -> transaction.getWeek() == week) + .filter(purchase -> purchase instanceof Purchase) + .map(transaction -> (Purchase) transaction).collect(Collectors.toList()); + } - public List getSale(int week) { - return transactions.stream().filter(transaction -> transaction.getWeek() == week).filter(sale -> sale instanceof Sale).map(transaction -> (Sale) transaction).collect(Collectors.toList()); - } + /** + * Gets sales for that week. + * + * @param week the week to find sales for + * @return returns the sales + */ + public List getSale(int week) { + return transactions.stream() + .filter(transaction -> transaction.getWeek() == week) + .filter(sale -> sale instanceof Sale) + .map(transaction -> (Sale) transaction).collect(Collectors.toList()); + } - public List getAllTransactions() { - return new ArrayList<>(transactions); - } + public List getAllTransactions() { + return new ArrayList<>(transactions); + } - public int countDistinctWeeks() { - return (int) transactions.stream().map(Transaction::getWeek).distinct().count(); - } + public int countDistinctWeeks() { + return (int) transactions.stream().map(Transaction::getWeek).distinct().count(); + } } From 19b9c893d54466442d7621c5a7fb6587cb17dde3 Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:58:05 +0200 Subject: [PATCH 14/22] Added Checkstyle to TransactionCalculator --- src/main/java/Model/TransactionCalculator.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/Model/TransactionCalculator.java b/src/main/java/Model/TransactionCalculator.java index 560992d..73fe750 100644 --- a/src/main/java/Model/TransactionCalculator.java +++ b/src/main/java/Model/TransactionCalculator.java @@ -1,11 +1,19 @@ package Model; + import java.math.BigDecimal; +/** + * TransactionCalculator class. + */ public interface TransactionCalculator { - // Methods in interface is automatically public abstract. - BigDecimal calculateGross(); - BigDecimal calculateCommission(); - BigDecimal calculateTax(); - BigDecimal calculateTotal(); + // Methods in interface is automatically public abstract. + BigDecimal calculateGross(); + + BigDecimal calculateCommission(); + + BigDecimal calculateTax(); + + BigDecimal calculateTotal(); + } From 37005452f61a93767c54059c42176e73298a5c0d Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:58:14 +0200 Subject: [PATCH 15/22] Added Checkstyle to TransactionFactory --- src/main/java/Model/TransactionFactory.java | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/Model/TransactionFactory.java b/src/main/java/Model/TransactionFactory.java index a3b8c9a..691d41b 100644 --- a/src/main/java/Model/TransactionFactory.java +++ b/src/main/java/Model/TransactionFactory.java @@ -1,22 +1,22 @@ package Model; /** - * Factory for creating transaction objects + * Factory for creating transaction objects. */ public class TransactionFactory { - /** - * Create purchase transaction - */ - public static Transaction createPurchase(Share share, int week) { - return new Purchase(share, week); - } + /** + * Create purchase transaction. + */ + public static Transaction createPurchase(Share share, int week) { + return new Purchase(share, week); + } - /** - * Create sale transaction - */ - public static Transaction createSale(Share share, int week) { - return new Sale(share, week); - } + /** + * Create sale transaction. + */ + public static Transaction createSale(Share share, int week) { + return new Sale(share, week); + } } From 9310799bfb89ac3b0762a4ab1eef263526573e1c Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:58:21 +0200 Subject: [PATCH 16/22] Added Checkstyle to GameSetupScene --- src/main/java/View/GameSetupScene.java | 414 +++++++++++++------------ 1 file changed, 213 insertions(+), 201 deletions(-) diff --git a/src/main/java/View/GameSetupScene.java b/src/main/java/View/GameSetupScene.java index 0ed8ded..243c181 100644 --- a/src/main/java/View/GameSetupScene.java +++ b/src/main/java/View/GameSetupScene.java @@ -1,6 +1,12 @@ package View; -import javafx.geometry.Insets; +import Controller.StockFileHandler; +import Model.Exchange; +import Model.Stock; +import java.io.File; +import java.math.BigDecimal; +import java.util.List; +import java.util.function.Consumer; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.*; @@ -8,220 +14,226 @@ import javafx.stage.FileChooser; import javafx.stage.Stage; -import Controller.StockFileHandler; -import Model.Exchange; -import Model.Stock; -import java.io.File; -import java.math.BigDecimal; -import java.util.List; -import java.util.function.Consumer; +/** + * GameSetupScene class. + */ public class GameSetupScene { - private Scene scene; - private Consumer onGameStart; - private File selectedFile; - private Label fileLabel; - - public GameSetupScene(Consumer onGameStart) { - this.onGameStart = onGameStart; - this.scene = createScene(); + private Scene scene; + private Consumer onGameStart; + private File selectedFile; + private Label fileLabel; + + public GameSetupScene(Consumer onGameStart) { + this.onGameStart = onGameStart; + this.scene = createScene(); + } + + private Scene createScene() { + + // Card (centered cream box) + VBox card = new VBox(20); + card.getStyleClass().add("card"); + card.setMaxWidth(560); + card.setMinWidth(400); + + // Title + Label titleLabel = new Label("Stock Trading Game"); + titleLabel.getStyleClass().add("title-label"); + + // Spacer below title + Region titleSpacer = new Region(); + titleSpacer.setMinHeight(6); + + // Player name row + HBox nameBox = new HBox(16); + nameBox.setAlignment(Pos.CENTER_LEFT); + nameBox.setMaxWidth(Double.MAX_VALUE); + + Label nameLabel = new Label("Player:"); + nameLabel.setMinWidth(130); + nameLabel.getStyleClass().add("form-label"); + + TextField nameField = new TextField(); + nameField.setPromptText("Enter your name"); + HBox.setHgrow(nameField, Priority.ALWAYS); + + nameBox.getChildren().addAll(nameLabel, nameField); + + // Starting capital row + HBox capitalBox = new HBox(16); + capitalBox.setAlignment(Pos.CENTER_LEFT); + capitalBox.setMaxWidth(Double.MAX_VALUE); + + Label capitalLabel = new Label("Start Capital:"); + capitalLabel.setMinWidth(130); + capitalLabel.getStyleClass().add("form-label"); + + TextField capitalField = new TextField(); + capitalField.setPromptText("e.g. 10000"); + HBox.setHgrow(capitalField, Priority.ALWAYS); + + capitalBox.getChildren().addAll(capitalLabel, capitalField); + + // File selection row + HBox fileBox = new HBox(16); + fileBox.setAlignment(Pos.CENTER_LEFT); + fileBox.setMaxWidth(Double.MAX_VALUE); + + Label fileSelectLabel = new Label("Stock Data File:"); + fileSelectLabel.setMinWidth(130); + fileSelectLabel.getStyleClass().add("form-label"); + + fileLabel = new Label("No file selected"); + fileLabel.getStyleClass().add("file-label"); + HBox.setHgrow(fileLabel, Priority.ALWAYS); + + Button browseButton = new Button("Browse"); + browseButton.getStyleClass().add("browse-button"); + browseButton.setOnAction(e -> selectFile()); + + fileBox.getChildren().addAll(fileSelectLabel, fileLabel, browseButton); + + // Spacer before buttons + Region buttonSpacer = new Region(); + buttonSpacer.setMinHeight(10); + + // Button row — buttons fill full card width equally + HBox buttonBox = new HBox(12); + buttonBox.setAlignment(Pos.CENTER_LEFT); + buttonBox.setMaxWidth(Double.MAX_VALUE); + + Button startButton = new Button("Start Game"); + startButton.getStyleClass().add("start-button"); + startButton.setMaxWidth(Double.MAX_VALUE); + startButton.setOnAction(e -> handleStart(nameField, capitalField)); + HBox.setHgrow(startButton, Priority.ALWAYS); + + Button exitButton = new Button("Exit"); + exitButton.getStyleClass().add("exit-button"); + exitButton.setMaxWidth(Double.MAX_VALUE); + exitButton.setOnAction(e -> System.exit(0)); + HBox.setHgrow(exitButton, Priority.ALWAYS); + + buttonBox.getChildren().addAll(startButton, exitButton); + + card.getChildren().addAll( + titleLabel, + titleSpacer, + nameBox, + capitalBox, + fileBox, + buttonSpacer, + buttonBox + ); + + // Center the card without stretching it vertically + VBox root = new VBox(card); + root.setAlignment(Pos.CENTER); + root.setFillWidth(false); + + Scene scene = new Scene(root, 800, 550); + scene.getStylesheets().add( + getClass().getResource("/Style/Global.css").toExternalForm() + ); + + return scene; + } + + private void handleStart(TextField nameField, TextField capitalField) { + String name = nameField.getText().trim(); + String capitalStr = capitalField.getText().trim(); + + if (name.isEmpty()) { + showAlert("Validation Error", "Please enter a player name"); + return; } - private Scene createScene() { - - // Card (centered cream box) - VBox card = new VBox(20); - card.getStyleClass().add("card"); - card.setMaxWidth(560); - card.setMinWidth(400); - - // Title - Label titleLabel = new Label("Stock Trading Game"); - titleLabel.getStyleClass().add("title-label"); - - // Spacer below title - Region titleSpacer = new Region(); - titleSpacer.setMinHeight(6); - - // Player name row - HBox nameBox = new HBox(16); - nameBox.setAlignment(Pos.CENTER_LEFT); - nameBox.setMaxWidth(Double.MAX_VALUE); - - Label nameLabel = new Label("Player:"); - nameLabel.setMinWidth(130); - nameLabel.getStyleClass().add("form-label"); - - TextField nameField = new TextField(); - nameField.setPromptText("Enter your name"); - HBox.setHgrow(nameField, Priority.ALWAYS); - - nameBox.getChildren().addAll(nameLabel, nameField); - - // Starting capital row - HBox capitalBox = new HBox(16); - capitalBox.setAlignment(Pos.CENTER_LEFT); - capitalBox.setMaxWidth(Double.MAX_VALUE); - - Label capitalLabel = new Label("Start Capital:"); - capitalLabel.setMinWidth(130); - capitalLabel.getStyleClass().add("form-label"); - - TextField capitalField = new TextField(); - capitalField.setPromptText("e.g. 10000"); - HBox.setHgrow(capitalField, Priority.ALWAYS); - - capitalBox.getChildren().addAll(capitalLabel, capitalField); - - // File selection row - HBox fileBox = new HBox(16); - fileBox.setAlignment(Pos.CENTER_LEFT); - fileBox.setMaxWidth(Double.MAX_VALUE); - - Label fileSelectLabel = new Label("Stock Data File:"); - fileSelectLabel.setMinWidth(130); - fileSelectLabel.getStyleClass().add("form-label"); - - fileLabel = new Label("No file selected"); - fileLabel.getStyleClass().add("file-label"); - HBox.setHgrow(fileLabel, Priority.ALWAYS); - - Button browseButton = new Button("Browse"); - browseButton.getStyleClass().add("browse-button"); - browseButton.setOnAction(e -> selectFile()); - - fileBox.getChildren().addAll(fileSelectLabel, fileLabel, browseButton); - - // Spacer before buttons - Region buttonSpacer = new Region(); - buttonSpacer.setMinHeight(10); - - // Button row — buttons fill full card width equally - HBox buttonBox = new HBox(12); - buttonBox.setAlignment(Pos.CENTER_LEFT); - buttonBox.setMaxWidth(Double.MAX_VALUE); - - Button startButton = new Button("Start Game"); - startButton.getStyleClass().add("start-button"); - startButton.setMaxWidth(Double.MAX_VALUE); - startButton.setOnAction(e -> handleStart(nameField, capitalField)); - HBox.setHgrow(startButton, Priority.ALWAYS); - - Button exitButton = new Button("Exit"); - exitButton.getStyleClass().add("exit-button"); - exitButton.setMaxWidth(Double.MAX_VALUE); - exitButton.setOnAction(e -> System.exit(0)); - HBox.setHgrow(exitButton, Priority.ALWAYS); - - buttonBox.getChildren().addAll(startButton, exitButton); - - card.getChildren().addAll( - titleLabel, - titleSpacer, - nameBox, - capitalBox, - fileBox, - buttonSpacer, - buttonBox - ); - - // Center the card without stretching it vertically - VBox root = new VBox(card); - root.setAlignment(Pos.CENTER); - root.setFillWidth(false); - - Scene scene = new Scene(root, 800, 550); - scene.getStylesheets().add( - getClass().getResource("/Style/Global.css").toExternalForm() - ); - - return scene; + if (capitalStr.isEmpty()) { + showAlert("Validation Error", "Please enter starting capital"); + return; } - private void handleStart(TextField nameField, TextField capitalField) { - String name = nameField.getText().trim(); - String capitalStr = capitalField.getText().trim(); - - if (name.isEmpty()) { - showAlert("Validation Error", "Please enter a player name"); - return; - } - - if (capitalStr.isEmpty()) { - showAlert("Validation Error", "Please enter starting capital"); - return; - } - - if (selectedFile == null) { - showAlert("Validation Error", "Please select a stock data file"); - return; - } - - try { - BigDecimal capital = new BigDecimal(capitalStr); - - if (capital.compareTo(BigDecimal.ZERO) <= 0) { - showAlert("Validation Error", "Starting capital must be greater than 0"); - return; - } - - StockFileHandler fileHandler = new StockFileHandler(); - List stocks = fileHandler.loadStocksFromFile(selectedFile.getAbsolutePath()); - - if (stocks.isEmpty()) { - showAlert("Error", "No stocks found in the selected file"); - return; - } - - Exchange exchange = new Exchange("Main Exchange", stocks); - onGameStart.accept(new StartGameData(name, capital, exchange)); - - } catch (NumberFormatException ex) { - showAlert("Validation Error", "Starting capital must be a valid number"); - } catch (Exception ex) { - showAlert("Error", "Failed to load file: " + ex.getMessage()); - } + if (selectedFile == null) { + showAlert("Validation Error", "Please select a stock data file"); + return; } - private void selectFile() { - FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle("Select Stock Data File"); - fileChooser.getExtensionFilters().addAll( - new FileChooser.ExtensionFilter("CSV Files", "*.csv"), - new FileChooser.ExtensionFilter("All Files", "*.*") - ); + try { + BigDecimal capital = new BigDecimal(capitalStr); - File file = fileChooser.showOpenDialog(new Stage()); + if (capital.compareTo(BigDecimal.ZERO) <= 0) { + showAlert("Validation Error", "Starting capital must be greater than 0"); + return; + } - if (file != null) { - selectedFile = file; - fileLabel.setText(file.getName()); - } - } + StockFileHandler fileHandler = new StockFileHandler(); + List stocks = fileHandler.loadStocksFromFile(selectedFile.getAbsolutePath()); - private void showAlert(String title, String message) { - Alert alert = new Alert(Alert.AlertType.WARNING); - alert.setTitle(title); - alert.setHeaderText(null); - alert.setContentText(message); - alert.showAndWait(); - } + if (stocks.isEmpty()) { + showAlert("Error", "No stocks found in the selected file"); + return; + } - public Scene getScene() { - return scene; - } + Exchange exchange = new Exchange("Main Exchange", stocks); + onGameStart.accept(new StartGameData(name, capital, exchange)); - public static class StartGameData { - public final String playerName; - public final BigDecimal startingCapital; - public final Exchange exchange; - - public StartGameData(String playerName, BigDecimal startingCapital, Exchange exchange) { - this.playerName = playerName; - this.startingCapital = startingCapital; - this.exchange = exchange; - } + } catch (NumberFormatException ex) { + showAlert("Validation Error", "Starting capital must be a valid number"); + } catch (Exception ex) { + showAlert("Error", "Failed to load file: " + ex.getMessage()); + } + } + + private void selectFile() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Select Stock Data File"); + fileChooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("CSV Files", "*.csv"), + new FileChooser.ExtensionFilter("All Files", "*.*") + ); + + File file = fileChooser.showOpenDialog(new Stage()); + + if (file != null) { + selectedFile = file; + fileLabel.setText(file.getName()); + } + } + + private void showAlert(String title, String message) { + Alert alert = new Alert(Alert.AlertType.WARNING); + alert.setTitle(title); + alert.setHeaderText(null); + alert.setContentText(message); + alert.showAndWait(); + } + + public Scene getScene() { + return scene; + } + + /** + * StartGameData method. + */ + public static class StartGameData { + public final String playerName; + public final BigDecimal startingCapital; + public final Exchange exchange; + + /** + * StartGameData method that includes playername, startingcapital and exchange. + * + * @param playerName the player's name + * @param startingCapital startingcapital of the player + * @param exchange exchange class + */ + public StartGameData(String playerName, BigDecimal startingCapital, Exchange exchange) { + this.playerName = playerName; + this.startingCapital = startingCapital; + this.exchange = exchange; } + } } From 21f91a60d53960eb6204f3a70b77235292282b8d Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:58:28 +0200 Subject: [PATCH 17/22] Added Checkstyle to Launcher --- src/main/java/View/Launcher.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/View/Launcher.java b/src/main/java/View/Launcher.java index 0dd5db2..b56a4bf 100644 --- a/src/main/java/View/Launcher.java +++ b/src/main/java/View/Launcher.java @@ -1,10 +1,10 @@ package View; /** - * Launcher class for the Stock Trading Game + * Launcher class for the Stock Trading Game. */ public class Launcher { - public static void main(String[] args) { - StockTradingGameApp.main(args); - } + public static void main(String[] args) { + StockTradingGameApp.main(args); + } } From bd3591d8f16e654a5773ecd1a66886c270c19386 Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Sun, 24 May 2026 23:58:36 +0200 Subject: [PATCH 18/22] Added Checkstyle to StockTradingGameApp --- src/main/java/View/StockTradingGameApp.java | 75 +++++++++++---------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/src/main/java/View/StockTradingGameApp.java b/src/main/java/View/StockTradingGameApp.java index a2d6543..838df67 100644 --- a/src/main/java/View/StockTradingGameApp.java +++ b/src/main/java/View/StockTradingGameApp.java @@ -1,49 +1,50 @@ package View; -import javafx.application.Application; -import javafx.stage.Stage; import Controller.StockFileHandler; import Model.*; +import javafx.application.Application; +import javafx.stage.Stage; -import java.math.BigDecimal; - +/** + * StockTradingGameApp class. + */ public class StockTradingGameApp extends Application { - private Stage primaryStage; - private Exchange exchange; - private Player player; - private StockFileHandler fileHandler; - - @Override - public void start(Stage primaryStage) { - this.primaryStage = primaryStage; - this.fileHandler = new StockFileHandler(); + private Stage primaryStage; + private Exchange exchange; + private Player player; + private StockFileHandler fileHandler; + + @Override + public void start(Stage primaryStage) { + this.primaryStage = primaryStage; + this.fileHandler = new StockFileHandler(); - primaryStage.setTitle("Stock Trading Game"); - primaryStage.setWidth(1200); - primaryStage.setHeight(800); + primaryStage.setTitle("Stock Trading Game"); + primaryStage.setWidth(1200); + primaryStage.setHeight(800); - showGameSetup(); - primaryStage.show(); - } - - private void showGameSetup() { - GameSetupScene setupScene = new GameSetupScene(this::startGame); - primaryStage.setScene(setupScene.getScene()); - } - - private void startGame(GameSetupScene.StartGameData gameData) { - this.exchange = gameData.exchange; - this.player = new Player(gameData.playerName, gameData.startingCapital); + showGameSetup(); + primaryStage.show(); + } + + private void showGameSetup() { + GameSetupScene setupScene = new GameSetupScene(this::startGame); + primaryStage.setScene(setupScene.getScene()); + } + + private void startGame(GameSetupScene.StartGameData gameData) { + this.exchange = gameData.exchange; + this.player = new Player(gameData.playerName, gameData.startingCapital); - MainGameScene gameScene = new MainGameScene(exchange, player, this::endGame); - primaryStage.setScene(gameScene.getScene()); - } + MainGameScene gameScene = new MainGameScene(exchange, player, this::endGame); + primaryStage.setScene(gameScene.getScene()); + } - private void endGame() { - primaryStage.close(); - } + private void endGame() { + primaryStage.close(); + } - public static void main(String[] args) { - launch(args); - } + public static void main(String[] args) { + launch(args); + } } From 38d0471c6d97f5cb944af5cb326a1410dc330d84 Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Mon, 25 May 2026 13:43:05 +0200 Subject: [PATCH 19/22] Add checkstyle to GameSetupScene --- src/main/java/View/GameSetupScene.java | 39 ++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/main/java/View/GameSetupScene.java b/src/main/java/View/GameSetupScene.java index 243c181..1e2d66d 100644 --- a/src/main/java/View/GameSetupScene.java +++ b/src/main/java/View/GameSetupScene.java @@ -17,7 +17,10 @@ /** - * GameSetupScene class. + * Displays the game setup scene where players configure their game parameters. + * + * Allows players to enter their name, starting capital, and select a stock data file + * before starting the main game. Notifies listeners when the game starts. */ public class GameSetupScene { @@ -26,11 +29,21 @@ public class GameSetupScene { private File selectedFile; private Label fileLabel; + /** + * Constructs a GameSetupScene with a callback for when the game starts. + * + * @param onGameStart callback invoked when the player starts the game. Cannot be null + */ public GameSetupScene(Consumer onGameStart) { this.onGameStart = onGameStart; this.scene = createScene(); } + /** + * Creates the UI layout for the setup scene. + * + * @return the constructed Scene + */ private Scene createScene() { // Card (centered cream box) @@ -142,6 +155,12 @@ private Scene createScene() { return scene; } + /** + * Validates inputs and starts the game when the Start button is clicked. + * + * @param nameField the text field containing the player name + * @param capitalField the text field containing the starting capital + */ private void handleStart(TextField nameField, TextField capitalField) { String name = nameField.getText().trim(); String capitalStr = capitalField.getText().trim(); @@ -187,6 +206,9 @@ private void handleStart(TextField nameField, TextField capitalField) { } } + /** + * Opens a file chooser dialog to select a stock data file. + */ private void selectFile() { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Select Stock Data File"); @@ -203,6 +225,12 @@ private void selectFile() { } } + /** + * Displays an alert dialog with the given title and message. + * + * @param title the dialog title + * @param message the alert message + */ private void showAlert(String title, String message) { Alert alert = new Alert(Alert.AlertType.WARNING); alert.setTitle(title); @@ -211,12 +239,19 @@ private void showAlert(String title, String message) { alert.showAndWait(); } + /** + * Returns the Scene for display in the application window. + * + * @return the setup scene + */ public Scene getScene() { return scene; } /** - * StartGameData method. + * Data class encapsulating game setup parameters. + * + * Contains the exchange configured with stocks, player name, and starting capital. */ public static class StartGameData { public final String playerName; From 60c219148011c0b277a27920554195ed858b59b9 Mon Sep 17 00:00:00 2001 From: Elisabeth Berg Date: Mon, 25 May 2026 14:00:46 +0200 Subject: [PATCH 20/22] Improve Javadocs Make Javadocs more accurate and descriptive --- checkstyle.xml | 101 ++ pom.xml | 21 + .../java/Controller/StockFileHandler.java | 69 +- src/main/java/Model/Exchange.java | 132 +- src/main/java/Model/ExchangeObserver.java | 8 +- src/main/java/Model/Player.java | 43 +- src/main/java/Model/PlayerStatus.java | 23 +- src/main/java/Model/Portfolio.java | 47 +- src/main/java/Model/Purchase.java | 19 +- src/main/java/Model/PurchaseCalculator.java | 34 +- src/main/java/Model/Sale.java | 19 +- src/main/java/Model/SaleCalculator.java | 37 +- src/main/java/Model/Share.java | 28 +- src/main/java/Model/Stock.java | 36 +- src/main/java/Model/Transaction.java | 40 +- src/main/java/Model/TransactionArchive.java | 51 +- .../java/Model/TransactionCalculator.java | 25 +- src/main/java/Model/TransactionFactory.java | 15 +- src/main/java/View/GameSetupScene.java | 10 +- src/main/java/View/Launcher.java | 9 +- src/main/java/View/MainGameScene.java | 1158 +++++++++-------- src/main/java/View/StockTradingGameApp.java | 25 +- target/test-classes/ExchangeTest.class | Bin 10733 -> 10397 bytes target/test-classes/PortfolioTest.class | Bin 5806 -> 5661 bytes target/test-classes/ShareTest.class | Bin 4255 -> 4137 bytes target/test-classes/StockTest.class | Bin 6225 -> 6116 bytes 26 files changed, 1300 insertions(+), 650 deletions(-) create mode 100644 checkstyle.xml diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 0000000..9f86503 --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index a74b899..1b52053 100644 --- a/pom.xml +++ b/pom.xml @@ -70,6 +70,27 @@ maven-javadoc-plugin 3.12.0 + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.3.1 + + checkstyle.xml + UTF-8 + true + false + false + + + + com.puppycrawl.tools + checkstyle + 10.12.5 + + + \ No newline at end of file diff --git a/src/main/java/Controller/StockFileHandler.java b/src/main/java/Controller/StockFileHandler.java index b64f9c0..bf808a7 100644 --- a/src/main/java/Controller/StockFileHandler.java +++ b/src/main/java/Controller/StockFileHandler.java @@ -1,37 +1,58 @@ package Controller; -import java.nio.file.*; + +import Model.Stock; import java.io.IOException; import java.math.BigDecimal; +import java.nio.file.*; import java.util.ArrayList; import java.util.List; -import Model.Stock; +/** + * Handles loading and saving Stock data from/to CSV files. + * The CSV format is: ticker,company_name,price + * Lines starting with '#' are treated as comments and ignored. + */ public class StockFileHandler { - // lesing - public List loadStocksFromFile(String filename) throws IOException { - return Files.readAllLines(Paths.get(filename)).stream() - .map(String::trim) - .filter(line -> !line.isEmpty() && !line.startsWith("#")) - .map(line -> line.split(",")) - .filter(parts -> parts.length == 3) - .map(parts -> new Stock(parts[0], parts[1], new BigDecimal(parts[2]))) - .toList(); - } + /** + * Loads stocks from a CSV file. + * Expected format: ticker,company,price + * Empty lines and lines starting with '#' are ignored. + * + * @param filename the path to the CSV file + * @return a list of Stock objects loaded from the file + * @throws IOException if the file cannot be read + */ + public List loadStocksFromFile(String filename) throws IOException { + return Files.readAllLines(Paths.get(filename)).stream() + .map(String::trim) + .filter(line -> !line.isEmpty() && !line.startsWith("#")) + .map(line -> line.split(",")) + .filter(parts -> parts.length == 3) + .map(parts -> new Stock(parts[0], parts[1], new BigDecimal(parts[2]))) + .toList(); + } - // lagring - public void saveStocksToFile(String filename, List stocks) throws IOException { - List lines = new ArrayList<>(); - lines.add("# Ticker,Name,Price"); + /** + * Saves stocks to a CSV file. + * The file includes a header comment line followed by one stock per line. + * + * @param filename the path to the CSV file to write + * @param stocks the list of stocks to save + * @throws IOException if the file cannot be written + */ + public void saveStocksToFile(String filename, List stocks) throws IOException { + List lines = new ArrayList<>(); + lines.add("# Ticker,Name,Price"); - lines.addAll(stocks.stream() - .map(stock -> String.format("%s,%s,%s", - stock.getSymbol(), - stock.getCompany(), - stock.getSalesPrice().toString())) - .toList()); + lines.addAll(stocks.stream() + .map(stock -> String.format("%s,%s,%s", + stock.getSymbol(), + stock.getCompany(), + stock.getSalesPrice().toString())) + .toList()); - Files.write(Paths.get(filename), lines); - } + Files.write(Paths.get(filename), lines); + } } \ No newline at end of file diff --git a/src/main/java/Model/Exchange.java b/src/main/java/Model/Exchange.java index 753e980..7feb4f2 100644 --- a/src/main/java/Model/Exchange.java +++ b/src/main/java/Model/Exchange.java @@ -8,7 +8,10 @@ import java.util.Random; /** - * Exchange class. + * Represents a stock exchange where players can buy and sell stocks. + * The Exchange maintains a collection of stocks, advances through weeks, + * and executes trades. It implements the Observer pattern to notify listeners + * (typically the UI) whenever its state changes. */ public class Exchange { @@ -20,6 +23,13 @@ public class Exchange { // Registered observers private final List observers = new ArrayList<>(); + /** + * Constructs a new Exchange with the given name and initial stocks. + * + * @param name the exchange name. Cannot be null or blank + * @param stocks the initial list of stocks. Cannot be null or contain null entries + * @throws IllegalArgumentException if any parameter is invalid + */ public Exchange(String name, List stocks) { if (name == null || name.isBlank()) { throw new IllegalArgumentException("Exchange name cannot be null or blank"); @@ -43,6 +53,12 @@ public Exchange(String name, List stocks) { // ---- Observer ---- + /** + * Registers an observer to receive notifications of state changes. + * + * @param observer the observer to register. Cannot be null + * @throws IllegalArgumentException if observer is null + */ public void addObserver(ExchangeObserver observer) { if (observer == null) { throw new IllegalArgumentException("Observer cannot be null"); @@ -52,24 +68,49 @@ public void addObserver(ExchangeObserver observer) { } } + /** + * Unregisters an observer from receiving notifications. + * + * @param observer the observer to remove + */ public void removeObserver(ExchangeObserver observer) { observers.remove(observer); } + /** + * Notifies all registered observers of a state change. + */ private void notifyObservers() { for (ExchangeObserver observer : observers) { observer.onExchangeUpdated(this); } } + /** + * Returns the exchange name. + * + * @return the name + */ public String getName() { return name; } + /** + * Returns the current week number. + * + * @return the week + */ public int getWeek() { return week; } + /** + * Checks if a stock with the given symbol is available on this exchange. + * + * @param symbol the stock symbol. Cannot be null or blank + * @return true if the stock exists; false otherwise + * @throws IllegalArgumentException if symbol is invalid + */ public boolean hasStock(String symbol) { if (symbol == null || symbol.isBlank()) { throw new IllegalArgumentException("Symbol cannot be null or blank"); @@ -77,6 +118,13 @@ public boolean hasStock(String symbol) { return stockMap.containsKey(symbol); } + /** + * Returns the stock with the given symbol. + * + * @param symbol the stock symbol. Cannot be null or blank + * @return the Stock, or null if not found + * @throws IllegalArgumentException if symbol is invalid + */ public Stock getStock(String symbol) { if (symbol == null || symbol.isBlank()) { throw new IllegalArgumentException("Symbol cannot be null or blank"); @@ -84,6 +132,13 @@ public Stock getStock(String symbol) { return stockMap.get(symbol); } + /** + * Searches for stocks matching the search term by symbol or company name. + * + * @param searchTerm the search term. Cannot be null + * @return a list of matching stocks (case-insensitive) + * @throws IllegalArgumentException if searchTerm is null + */ public List findStocks(String searchTerm) { if (searchTerm == null) { throw new IllegalArgumentException("Search term cannot be null"); @@ -101,6 +156,16 @@ public List findStocks(String searchTerm) { return result; } + /** + * Executes a buy transaction for the specified stock and quantity. + * + * @param symbol the stock symbol. Cannot be null or blank + * @param quantity the number of shares to buy. Must be greater than zero + * @param player the player executing the purchase. Cannot be null + * @return the completed Purchase transaction + * @throws IllegalArgumentException if any parameter is invalid or stock not found + * @throws IllegalStateException if player has insufficient funds + */ public Transaction buy(String symbol, BigDecimal quantity, Player player) { if (symbol == null || symbol.isBlank()) { throw new IllegalArgumentException("Symbol cannot be null or blank"); @@ -120,7 +185,7 @@ public Transaction buy(String symbol, BigDecimal quantity, Player player) { throw new IllegalArgumentException("No stock found with symbol: " + symbol); } - // lager en ny "andel" basert på nåværende salgspris + // Creates a new share based on current selling price Share shareToBuy = new Share(stock, quantity, stock.getSalesPrice()); Purchase purchase = new Purchase(shareToBuy, this.week); purchase.commit(player); @@ -130,19 +195,40 @@ public Transaction buy(String symbol, BigDecimal quantity, Player player) { return purchase; } + /** + * Executes a full sale transaction for all shares in the given holding. + * + * @param share the share to sell completely. Cannot be null + * @param player the player executing the sale. Cannot be null + * @return the completed Sale transaction + * @throws IllegalArgumentException if any parameter is invalid + * @throws IllegalStateException if player doesn't own the share + */ public Transaction sell(Share share, Player player) { return sell(share, share.getQuantity(), player); } + /** + * Executes a partial or complete sale transaction. + * If the sell quantity is less than the share quantity, creates a partial sale + * and leaves the remainder in the portfolio. + * + * @param originalShare the share to sell from. Cannot be null + * @param sellQuantity the quantity to sell. Cannot exceed the original quantity + * @param player the player executing the sale. Cannot be null + * @return the completed Sale transaction + * @throws IllegalArgumentException if any parameter is invalid + * @throws IllegalStateException if sell quantity exceeds holding + */ public Transaction sell(Share originalShare, BigDecimal sellQuantity, Player player) { if (originalShare == null || sellQuantity == null) { throw new IllegalArgumentException("Share and quantity cannot be null"); } if (player == null) { - throw new IllegalArgumentException("Player cannot be null"); + throw new IllegalArgumentException("Player cannot be null"); } - // Kan ikke selge mer enn man eier + // Cannot sell more shares than you have if (sellQuantity.compareTo(originalShare.getQuantity()) > 0) { throw new IllegalArgumentException("Cannot sell more shares than owned"); } @@ -153,8 +239,8 @@ public Transaction sell(Share originalShare, BigDecimal sellQuantity, Player pla if (sellQuantity.compareTo(originalShare.getQuantity()) < 0) { - //Delsalg: - // Original mengde - mengde som selges = gjenværende mengde + // Partial sale: + // Original quantity - quantity sold = remaining quantity player.getPortfolio().removeShare(originalShare); @@ -164,7 +250,7 @@ public Transaction sell(Share originalShare, BigDecimal sellQuantity, Player pla .getStock(), remainderShare, originalShare.getPurchasePrice())); - //Legger delmengden midlertidig til i portfolio slik at Sale.commit() finner den + // Temporarily adds the subset to the portfolio so that Sale.commit() can find it shareToSell = new Share(originalShare .getStock(), sellQuantity, originalShare.getPurchasePrice()); @@ -174,11 +260,11 @@ public Transaction sell(Share originalShare, BigDecimal sellQuantity, Player pla } else { - // Fullstendig salg: hele andelen selges som normalt + // Complete sale: the entire share is sold as normal shareToSell = originalShare; } - // Salgstransaksjon + // Salestransaction Transaction sale = TransactionFactory.createSale(shareToSell, this.week); sale.commit(player); @@ -187,15 +273,17 @@ public Transaction sell(Share originalShare, BigDecimal sellQuantity, Player pla } /** - * Advances to the next week. + * Advances the game to the next week and updates all stock prices. + * Each stock's price is randomly adjusted by -10% to +10% per week. + * Notifies all observers of the change. */ public void advance() { week++; - for (Stock stock : stockMap.values()) { // henter stock-objektene + for (Stock stock : stockMap.values()) { // Retrieves the stock-objects - BigDecimal currentPrice = stock.getSalesPrice(); // henter siste pris fra Stock + BigDecimal currentPrice = stock.getSalesPrice(); // Retrieves last price from Stock double changePercent = (random.nextDouble() - 0.5) * 0.1; @@ -203,7 +291,7 @@ public void advance() { BigDecimal newPrice = currentPrice.add(change); - if (newPrice.compareTo(BigDecimal.ZERO) > 0) { // unngå negativ pris + if (newPrice.compareTo(BigDecimal.ZERO) > 0) { // avoid negative price stock.addNewSalesPrice(newPrice); } } @@ -212,12 +300,13 @@ public void advance() { } /** - * Shows the weeks Gainers, the stocks with the best price change. + * Returns the top-performing stocks (gainers) for the current week. + * Gainers are stocks with the highest positive price change. * - * @param limit how many gainers - * @return returns the gainers + * @param limit the maximum number of gainers to return + * @return a list of the top gainers, sorted by price change (highest first) */ - public List getGainers(int limit) { // viser "vinnerne" + public List getGainers(int limit) { // shows the "winners" return stockMap.values().stream() .sorted((s1, s2) -> s2.getLatestPriceChange().compareTo(s1.getLatestPriceChange())) .limit(limit) @@ -225,12 +314,13 @@ public List getGainers(int limit) { // viser "vinnerne" } /** - * Shows the weeks Losers, the stocks with the worst price change. + * Returns the lowest-performing stocks (losers) for the current week. + * Losers are stocks with the highest negative price change. * - * @param limit how many losers - * @return returns the losers + * @param limit the maximum number of losers to return + * @return a list of the top losers, sorted by price change (lowest first) */ - public List getLosers(int limit) { // viser "taperne" + public List getLosers(int limit) { // shows the "losers" return stockMap.values().stream() .sorted((s1, s2) -> s1.getLatestPriceChange().compareTo(s2.getLatestPriceChange())) .limit(limit) diff --git a/src/main/java/Model/ExchangeObserver.java b/src/main/java/Model/ExchangeObserver.java index 145f3cc..7cef281 100644 --- a/src/main/java/Model/ExchangeObserver.java +++ b/src/main/java/Model/ExchangeObserver.java @@ -1,14 +1,14 @@ package Model; /** - * Observer interface for the Exchange subject. - * Implement this interface to receive notifications whenever the exchange - * state changes (week advances, a trade is committed). + * Observer interface for receiving notifications from the Exchange. + * Implementations are notified whenever the Exchange state changes, + * allowing the View and other components to stay synchronized. */ public interface ExchangeObserver { /** - * Called by the Exchange after its state has changed. + * Called by the Exchange when its state changes. * * @param exchange the Exchange that triggered the notification */ diff --git a/src/main/java/Model/Player.java b/src/main/java/Model/Player.java index 3293db1..dd9d81e 100644 --- a/src/main/java/Model/Player.java +++ b/src/main/java/Model/Player.java @@ -3,8 +3,10 @@ import java.math.BigDecimal; /** - * The player class is a description of the person player the game, - * their starting point and their progress. + * Represents a player in the stock trading game. + * A Player manages their personal portfolio of shares, cash balance, and transaction + * history. Players can buy and sell stocks, and their performance is tracked through + * status levels based on trading activity and net worth growth. */ public class Player { private String name; @@ -14,10 +16,11 @@ public class Player { private TransactionArchive transactionArchive; /** - * Player method that includes the players name and the money they start with. + * Constructs a new Player with the given name and starting capital. * - * @param name the name of the player - * @param startingMoney money the player starts with + * @param name the player's name. Cannot be null or blank + * @param startingMoney the initial cash balance. Cannot be null or negative + * @throws IllegalArgumentException if any parameter is invalid */ public Player(String name, BigDecimal startingMoney) { if (name == null || name.isBlank()) { @@ -37,18 +40,29 @@ public Player(String name, BigDecimal startingMoney) { this.transactionArchive = new TransactionArchive(); } + /** + * Returns the player's name. + * + * @return the player's name + */ public String getName() { return this.name; } + /** + * Returns the player's current cash balance. + * + * @return the current amount of money available + */ public BigDecimal getMoney() { return this.money; } /** - * Amount of money added. + * Adds the specified amount to the player's cash balance. * - * @param amount amount of money + * @param amount the amount to add. Cannot be null or negative + * @throws IllegalArgumentException if amount is invalid */ public void addMoney(BigDecimal amount) { if (amount == null) { @@ -61,9 +75,10 @@ public void addMoney(BigDecimal amount) { } /** - * Amount of money withdrawn. + * Removes the specified amount from the player's cash balance. * - * @param amount amount of money + * @param amount the amount to withdraw. Cannot be null or negative + * @throws IllegalArgumentException if amount is invalid */ public void withdrawMoney(BigDecimal amount) { if (amount == null) { @@ -75,10 +90,20 @@ public void withdrawMoney(BigDecimal amount) { this.money = this.money.subtract(amount); } + /** + * Returns the player's portfolio. + * + * @return the portfolio containing held shares + */ public Portfolio getPortfolio() { return this.portfolio; } + /** + * Returns the player's transaction archive. + * + * @return the archive of all transactions + */ public TransactionArchive getTransactionArchive() { return this.transactionArchive; } diff --git a/src/main/java/Model/PlayerStatus.java b/src/main/java/Model/PlayerStatus.java index e72a9ef..312e38f 100644 --- a/src/main/java/Model/PlayerStatus.java +++ b/src/main/java/Model/PlayerStatus.java @@ -1,12 +1,13 @@ package Model; /** - * Enum representing the player's status level based on trading activity and performance. - - * Status levels: - * - NOVICE: Starting level, no requirements - * - INVESTOR: Traded for at least 10 weeks AND increased net worth by at least 20% - * - SPECULATOR: Traded for at least 20 weeks AND doubled the net worth (100% increase) + * Enum representing the player's trading status level. + * Status levels are determined by trading activity (weeks with transactions) + * and net worth performance compared to starting capital. + * Levels: + * - NOVICE: Initial status (default) + * - INVESTOR: 10+ weeks of trading AND net worth increased by 20% + * - SPECULATOR: 20+ weeks of trading AND net worth doubled */ public enum PlayerStatus { NOVICE("Novice"), @@ -15,10 +16,20 @@ public enum PlayerStatus { private final String displayName; + /** + * Constructs a PlayerStatus with the given display name. + * + * @param displayName the name for UI display + */ PlayerStatus(String displayName) { this.displayName = displayName; } + /** + * Returns the display name for this status. + * + * @return the display name + */ public String getDisplayName() { return displayName; } diff --git a/src/main/java/Model/Portfolio.java b/src/main/java/Model/Portfolio.java index 902b8b9..7c9e38d 100644 --- a/src/main/java/Model/Portfolio.java +++ b/src/main/java/Model/Portfolio.java @@ -5,21 +5,28 @@ import java.util.List; /** - * Portfolio class of the player. + * Represents a portfolio of shares held by a player. + * A Portfolio manages a collection of Share objects and provides methods to add, + * remove, and query holdings. It can calculate the total net worth based on current + * market prices. */ public class Portfolio { private final List shares; + /** + * Constructs a new empty Portfolio. + */ public Portfolio() { this.shares = new ArrayList<>(); } /** - * Method to add share. + * Adds a share to the portfolio. * - * @param share the share being added - * @return returns the added share + * @param share the share to add. Cannot be null + * @return true if the share was added successfully + * @throws IllegalArgumentException if share is null */ public boolean addShare(Share share) { if (share == null) { @@ -29,10 +36,11 @@ public boolean addShare(Share share) { } /** - * Method to remove share. + * Removes a share from the portfolio. * - * @param share the share being removed - * @return returns the removed share + * @param share the share to remove. Cannot be null + * @return true if the share was removed; false if it was not in the portfolio + * @throws IllegalArgumentException if share is null */ public boolean removeShare(Share share) { if (share == null) { @@ -41,15 +49,21 @@ public boolean removeShare(Share share) { return shares.remove(share); } + /** + * Returns a defensive copy of all shares in the portfolio. + * + * @return a copy of the shares list + */ public List getShares() { return new ArrayList<>(shares); } /** - * Method to get all shares. + * Returns all shares in the portfolio for a specific stock symbol. * - * @param symbol the symbol for each stock - * @return the shares of stocks + * @param symbol the stock symbol to filter by. Cannot be null or blank + * @return a list of shares matching the symbol + * @throws IllegalArgumentException if symbol is null or blank */ public List getShares(String symbol) { if (symbol == null || symbol.isBlank()) { @@ -61,10 +75,11 @@ public List getShares(String symbol) { } /** - * Method to check if contains share. + * Checks if the portfolio contains a specific share. * - * @param share the share - * @return the share if not null + * @param share the share to check for. Cannot be null + * @return true if the share is in the portfolio; false otherwise + * @throws IllegalArgumentException if share is null */ public boolean contains(Share share) { if (share == null) { @@ -74,9 +89,11 @@ public boolean contains(Share share) { } /** - * Method to get the new worth. + * Calculates the total net worth of the portfolio based on current market prices. + * The net worth is the sum of all shares valued at their current sales prices, + * minus any transaction costs (commissions and taxes). * - * @return returns the net worth + * @return the total net worth of all holdings */ public BigDecimal getNetWorth() { return shares.stream() diff --git a/src/main/java/Model/Purchase.java b/src/main/java/Model/Purchase.java index d367a7b..b81f655 100644 --- a/src/main/java/Model/Purchase.java +++ b/src/main/java/Model/Purchase.java @@ -3,13 +3,30 @@ import java.math.BigDecimal; /** - * Purchase class whenever the player makes a purchase. + * Represents a stock purchase transaction. + * A Purchase transaction represents a player buying shares. When committed, + * it deducts money from the player's account, adds the shares to their portfolio, + * and records the transaction in the archive. */ public class Purchase extends Transaction { + /** + * Constructs a new Purchase transaction. + * + * @param share the share to purchase. Cannot be null + * @param week the week number when the purchase occurs. Must be at least 1 + */ public Purchase(Share share, int week) { super(share, week, new PurchaseCalculator(share)); } + /** + * Commits this purchase transaction, deducting funds and adding shares to the portfolio. + * + * @param player the player executing the purchase. Cannot be null + * @throws IllegalArgumentException if player is null + * @throws IllegalStateException if purchase has already been committed or if + * the player has insufficient funds + */ @Override public void commit(Player player) { if (player == null) { diff --git a/src/main/java/Model/PurchaseCalculator.java b/src/main/java/Model/PurchaseCalculator.java index e90839f..e2a7482 100644 --- a/src/main/java/Model/PurchaseCalculator.java +++ b/src/main/java/Model/PurchaseCalculator.java @@ -3,34 +3,60 @@ import java.math.BigDecimal; /** - * PurchaseCalculator class that does calculations. + * Calculates costs and totals for purchase transactions. + * A PurchaseCalculator computes the gross cost (quantity × price), applies + * a commission fee, and produces the total transaction cost. */ public class PurchaseCalculator implements TransactionCalculator { private final BigDecimal purchasePrice; private final BigDecimal quantity; - + + /** + * Constructs a PurchaseCalculator for the given share. + * + * @param share the share being purchased. Cannot be null + */ public PurchaseCalculator(Share share) { this.purchasePrice = share.getPurchasePrice(); this.quantity = share.getQuantity(); } + /** + * Calculates the gross cost without fees (quantity × purchase price). + * + * @return the gross cost + */ @Override public BigDecimal calculateGross() { return this.purchasePrice.multiply(this.quantity); } + /** + * Calculates the purchase commission (0.5% of gross). + * + * @return the commission fee + */ @Override public BigDecimal calculateCommission() { BigDecimal rate = new BigDecimal("0.005"); return calculateGross().multiply(rate); } + /** + * Calculates the tax on purchase (always 0 for purchases). + * + * @return zero + */ @Override public BigDecimal calculateTax() { - BigDecimal tax = new BigDecimal("0"); - return tax; + return BigDecimal.ZERO; } + /** + * Calculates the total purchase cost (gross + commission + tax). + * + * @return the total transaction cost + */ @Override public BigDecimal calculateTotal() { return calculateGross().add(calculateCommission()).add(calculateTax()); diff --git a/src/main/java/Model/Sale.java b/src/main/java/Model/Sale.java index e130f41..8b2e05e 100644 --- a/src/main/java/Model/Sale.java +++ b/src/main/java/Model/Sale.java @@ -3,13 +3,30 @@ import java.math.BigDecimal; /** - * Sale class for whenever the player makes a sale. + * Represents a stock sale transaction. + * A Sale transaction represents a player selling shares from their portfolio. + * When committed, it adds money to the player's account, removes the shares from + * their portfolio, and records the transaction in the archive. */ public class Sale extends Transaction { + /** + * Constructs a new Sale transaction. + * + * @param share the share to sell. Cannot be null + * @param week the week number when the sale occurs. Must be at least 1 + */ public Sale(Share share, int week) { super(share, week, new SaleCalculator(share)); } + /** + * Commits this sale transaction, crediting funds and removing shares from the portfolio. + * + * @param player the player executing the sale. Cannot be null + * @throws IllegalArgumentException if player is null + * @throws IllegalStateException if sale has already been committed or if the + * player does not own the share being sold + */ @Override public void commit(Player player) { if (player == null) { diff --git a/src/main/java/Model/SaleCalculator.java b/src/main/java/Model/SaleCalculator.java index 93b6f93..271225a 100644 --- a/src/main/java/Model/SaleCalculator.java +++ b/src/main/java/Model/SaleCalculator.java @@ -3,17 +3,19 @@ import java.math.BigDecimal; /** - * SaleCalculator class that calculates gross, commission, tax and total. + * Calculates costs, taxes, and proceeds for sale transactions. + * A SaleCalculator computes the gross revenue (quantity × sales price), applies + * a commission fee, calculates capital gains tax, and produces the net proceeds. */ -public class SaleCalculator implements TransactionCalculator{ - private BigDecimal purchasePrice; - private BigDecimal salesPrice; - private BigDecimal quantity; +public class SaleCalculator implements TransactionCalculator { + private final BigDecimal purchasePrice; + private final BigDecimal salesPrice; + private final BigDecimal quantity; /** - * Method with parameters. + * Constructs a SaleCalculator for the given share. * - * @param share share from calculator + * @param share the share being sold. Cannot be null */ public SaleCalculator(Share share) { this.purchasePrice = share.getPurchasePrice(); @@ -21,11 +23,21 @@ public SaleCalculator(Share share) { this.quantity = share.getQuantity(); } + /** + * Calculates the gross revenue (quantity × sales price). + * + * @return the gross revenue + */ @Override public BigDecimal calculateGross() { return this.salesPrice.multiply(this.quantity); } + /** + * Calculates the sale commission (1% of gross). + * + * @return the commission fee + */ @Override public BigDecimal calculateCommission() { BigDecimal rate = new BigDecimal("0.01"); @@ -33,8 +45,12 @@ public BigDecimal calculateCommission() { } /** - * Method that calculates tax. + * Calculates the capital gains tax (30% of profit). + * Profit is calculated as: revenue - commission - original purchase cost. + * + * @return the capital gains tax */ + @Override public BigDecimal calculateTax() { BigDecimal sellingCost = this.purchasePrice.multiply(this.quantity); BigDecimal profit = calculateGross().subtract(calculateCommission()).subtract(sellingCost); @@ -42,6 +58,11 @@ public BigDecimal calculateTax() { return profit.multiply(rate); } + /** + * Calculates the net proceeds (gross - commission - tax). + * + * @return the total amount credited to the player + */ @Override public BigDecimal calculateTotal() { return calculateGross().subtract(calculateCommission()).subtract(calculateTax()); diff --git a/src/main/java/Model/Share.java b/src/main/java/Model/Share.java index d7fee43..05a4c08 100644 --- a/src/main/java/Model/Share.java +++ b/src/main/java/Model/Share.java @@ -3,7 +3,9 @@ import java.math.BigDecimal; /** - * Share class. + * Represents a share holding of a stock. + * A Share encapsulates a quantity of a stock purchased at a specific price. + * It provides information about the stock and the purchase terms. */ public class Share { @@ -12,11 +14,12 @@ public class Share { private final BigDecimal purchasePrice; /** - * Share method that has stock, quantity and purchaseprice. + * Constructs a new Share with the specified stock, quantity, and purchase price. * - * @param stock stock name - * @param quantity quantity of stock - * @param purchasePrice price of stock + * @param stock the Stock being held. Cannot be null + * @param quantity the number of shares held. Must be greater than zero + * @param purchasePrice the price per share at purchase. Cannot be null or negative + * @throws IllegalArgumentException if any parameter is invalid */ public Share(Stock stock, BigDecimal quantity, BigDecimal purchasePrice) { if (stock == null) { @@ -40,14 +43,29 @@ public Share(Stock stock, BigDecimal quantity, BigDecimal purchasePrice) { this.purchasePrice = purchasePrice; } + /** + * Returns the Stock associated with this share. + * + * @return the stock being held + */ public Stock getStock() { return stock; } + /** + * Returns the quantity of shares held. + * + * @return the number of shares + */ public BigDecimal getQuantity() { return quantity; } + /** + * Returns the price per share at the time of purchase. + * + * @return the purchase price + */ public BigDecimal getPurchasePrice() { return purchasePrice; } diff --git a/src/main/java/Model/Stock.java b/src/main/java/Model/Stock.java index ca117b5..3c6866e 100644 --- a/src/main/java/Model/Stock.java +++ b/src/main/java/Model/Stock.java @@ -5,7 +5,10 @@ import java.util.List; /** - * STock class. + * Represents a stock traded on the exchange. + * A Stock tracks the company's trading symbol, company name, and historical price + * data. Prices are maintained in chronological order, with the latest price being + * the current sales price. */ public class Stock { @@ -14,7 +17,12 @@ public class Stock { private final List prices; /** - * Stock method that includes symbol of the stock, company and the salesprice. + * Constructs a new Stock with the given symbol, company name, and initial sales price. + * + * @param symbol the unique ticker symbol (e.g., "AAPL"). Cannot be null or blank + * @param company the company name. Cannot be null or blank + * @param salesPrice the initial sales price. Cannot be null or negative + * @throws IllegalArgumentException if any parameter is invalid */ public Stock(String symbol, String company, BigDecimal salesPrice) { if (symbol == null || symbol.isBlank()) { @@ -36,20 +44,38 @@ public Stock(String symbol, String company, BigDecimal salesPrice) { this.prices.add(salesPrice); } + /** + * Returns the ticker symbol of this stock. + * + * @return the stock symbol + */ public String getSymbol() { return symbol; } + /** + * Returns the company name associated with this stock. + * + * @return the company name + */ public String getCompany() { return company; } + /** + * Returns the current (most recent) sales price of this stock. + * + * @return the current sales price + */ public BigDecimal getSalesPrice() { return prices.get(prices.size() - 1); } - + /** - * Method that makes new salesprice. + * Records a new sales price for this stock. + * + * @param price the new sales price. Cannot be null or negative + * @throws IllegalArgumentException if price is invalid */ public void addNewSalesPrice(BigDecimal price) { if (price == null) { @@ -62,7 +88,7 @@ public void addNewSalesPrice(BigDecimal price) { } public List getHistoricalPrices() { - return new ArrayList<>(prices); // returnerer en kopi for å beskytte selve listen + return new ArrayList<>(prices); // returns a copy to protect the list itself } public BigDecimal getHighestPrice() { diff --git a/src/main/java/Model/Transaction.java b/src/main/java/Model/Transaction.java index cedd4d2..a8eb4f1 100644 --- a/src/main/java/Model/Transaction.java +++ b/src/main/java/Model/Transaction.java @@ -1,7 +1,10 @@ package Model; /** - * Transaction class. + * Abstract base class for all transactions (purchases and sales). + * A Transaction represents a trading action taken by a player on a specific week. + * Transactions use the Strategy pattern via TransactionCalculator to compute costs + * and can be committed to apply their effects to the player's portfolio. */ public abstract class Transaction { private Share share; @@ -9,6 +12,14 @@ public abstract class Transaction { private TransactionCalculator calculator; protected boolean committed; + /** + * Constructs a Transaction for the specified share in the given week. + * + * @param share the share being traded. Cannot be null + * @param week the week number when the transaction occurs. Must be at least 1 + * @param calculator the calculator for transaction costs. Cannot be null + * @throws IllegalArgumentException if any parameter is invalid + */ protected Transaction(Share share, int week, TransactionCalculator calculator) { if (share == null) { throw new IllegalArgumentException("Share cannot be null"); @@ -25,22 +36,49 @@ protected Transaction(Share share, int week, TransactionCalculator calculator) { this.calculator = calculator; } + /** + * Returns the share being traded in this transaction. + * + * @return the share + */ public Share getShare() { return this.share; } + /** + * Returns the week number when this transaction occurs. + * + * @return the week number + */ public int getWeek() { return this.week; } + /** + * Returns the calculator used to compute transaction costs. + * + * @return the transaction calculator + */ public TransactionCalculator getCalculator() { return this.calculator; } + /** + * Returns whether this transaction has been committed. + * + * @return true if committed; false otherwise + */ public boolean isCommitted() { return this.committed; } + /** + * Commits this transaction, applying its effects to the player's portfolio. + * + * @param player the player executing the transaction. Cannot be null + * @throws IllegalArgumentException if player is null + * @throws IllegalStateException if the transaction has already been committed + */ public abstract void commit(Player player); } diff --git a/src/main/java/Model/TransactionArchive.java b/src/main/java/Model/TransactionArchive.java index db7a50c..4af5bff 100644 --- a/src/main/java/Model/TransactionArchive.java +++ b/src/main/java/Model/TransactionArchive.java @@ -5,37 +5,60 @@ import java.util.stream.Collectors; /** - * TransactionArchive class. + * Maintains a chronological archive of all transactions executed by a player. + * The TransactionArchive allows retrieval of transactions by week and type + * (purchase or sale), and provides statistics about trading activity. */ public class TransactionArchive { - private List transactions; + private final List transactions; + /** + * Constructs a new empty TransactionArchive. + */ public TransactionArchive() { this.transactions = new ArrayList<>(); } + /** + * Adds a transaction to the archive. + * + * @param transaction the transaction to add. Cannot be null + * @return true if the transaction was added successfully + * @throws IllegalArgumentException if transaction is null + */ public boolean add(Transaction transaction) { if (transaction == null) { - throw new IllegalArgumentException("Should not be null"); // Eller NullPointerExeption? + throw new IllegalArgumentException("Should not be null"); } return transactions.add(transaction); } + /** + * Checks if the archive is empty. + * + * @return true if no transactions have been recorded + */ public boolean isEmpty() { return transactions.isEmpty(); } + /** + * Returns all transactions that occurred in a specific week. + * + * @param week the week number + * @return a list of transactions in that week + */ public List getTransactions(int week) { return transactions.stream() .filter(transaction -> transaction.getWeek() == week).collect(Collectors.toList()); } /** - * List of all purchases in one week. + * Returns all purchases that occurred in a specific week. * - * @param week the week to find purchases for - * @return returns the purchases + * @param week the week number + * @return a list of purchases in that week */ public List getPurchase(int week) { return transactions.stream() @@ -45,10 +68,10 @@ public List getPurchase(int week) { } /** - * Gets sales for that week. + * Returns all sales that occurred in a specific week. * - * @param week the week to find sales for - * @return returns the sales + * @param week the week number + * @return a list of sales in that week */ public List getSale(int week) { return transactions.stream() @@ -57,10 +80,20 @@ public List getSale(int week) { .map(transaction -> (Sale) transaction).collect(Collectors.toList()); } + /** + * Returns a defensive copy of all transactions in the archive. + * + * @return a copy of the transaction list + */ public List getAllTransactions() { return new ArrayList<>(transactions); } + /** + * Counts the number of distinct weeks with trading activity. + * + * @return the number of unique weeks represented in the archive + */ public int countDistinctWeeks() { return (int) transactions.stream().map(Transaction::getWeek).distinct().count(); } diff --git a/src/main/java/Model/TransactionCalculator.java b/src/main/java/Model/TransactionCalculator.java index 73fe750..2691e02 100644 --- a/src/main/java/Model/TransactionCalculator.java +++ b/src/main/java/Model/TransactionCalculator.java @@ -3,16 +3,37 @@ import java.math.BigDecimal; /** - * TransactionCalculator class. + * Strategy interface for calculating transaction costs and proceeds. + * Implementations compute fees and totals for different transaction types + * (purchases and sales), following the Strategy design pattern. */ public interface TransactionCalculator { - // Methods in interface is automatically public abstract. + /** + * Calculates the base cost/revenue without fees. + * + * @return the gross amount + */ BigDecimal calculateGross(); + /** + * Calculates the commission fee on the transaction. + * + * @return the commission amount + */ BigDecimal calculateCommission(); + /** + * Calculates any applicable taxes on the transaction. + * + * @return the tax amount + */ BigDecimal calculateTax(); + /** + * Calculates the final total after all fees and taxes. + * + * @return the total transaction amount + */ BigDecimal calculateTotal(); } diff --git a/src/main/java/Model/TransactionFactory.java b/src/main/java/Model/TransactionFactory.java index 691d41b..1f799d1 100644 --- a/src/main/java/Model/TransactionFactory.java +++ b/src/main/java/Model/TransactionFactory.java @@ -2,18 +2,29 @@ /** * Factory for creating transaction objects. + * Uses the Factory pattern to encapsulate transaction creation logic, + * allowing clients to create Purchase and Sale transactions without + * knowing implementation details. */ public class TransactionFactory { /** - * Create purchase transaction. + * Creates a new Purchase transaction. + * + * @param share the share to purchase. Cannot be null + * @param week the week when the purchase occurs. Must be at least 1 + * @return a new Purchase transaction */ public static Transaction createPurchase(Share share, int week) { return new Purchase(share, week); } /** - * Create sale transaction. + * Creates a new Sale transaction. + * + * @param share the share to sell. Cannot be null + * @param week the week when the sale occurs. Must be at least 1 + * @return a new Sale transaction */ public static Transaction createSale(Share share, int week) { return new Sale(share, week); diff --git a/src/main/java/View/GameSetupScene.java b/src/main/java/View/GameSetupScene.java index 1e2d66d..44052ca 100644 --- a/src/main/java/View/GameSetupScene.java +++ b/src/main/java/View/GameSetupScene.java @@ -18,14 +18,13 @@ /** * Displays the game setup scene where players configure their game parameters. - * * Allows players to enter their name, starting capital, and select a stock data file * before starting the main game. Notifies listeners when the game starts. */ public class GameSetupScene { - private Scene scene; - private Consumer onGameStart; + private final Scene scene; + private final Consumer onGameStart; private File selectedFile; private Label fileLabel; @@ -113,7 +112,7 @@ private Scene createScene() { Region buttonSpacer = new Region(); buttonSpacer.setMinHeight(10); - // Button row — buttons fill full card width equally + // Button row HBox buttonBox = new HBox(12); buttonBox.setAlignment(Pos.CENTER_LEFT); buttonBox.setMaxWidth(Double.MAX_VALUE); @@ -142,7 +141,7 @@ private Scene createScene() { buttonBox ); - // Center the card without stretching it vertically + // Centers the root VBox root = new VBox(card); root.setAlignment(Pos.CENTER); root.setFillWidth(false); @@ -250,7 +249,6 @@ public Scene getScene() { /** * Data class encapsulating game setup parameters. - * * Contains the exchange configured with stocks, player name, and starting capital. */ public static class StartGameData { diff --git a/src/main/java/View/Launcher.java b/src/main/java/View/Launcher.java index b56a4bf..fb7bc49 100644 --- a/src/main/java/View/Launcher.java +++ b/src/main/java/View/Launcher.java @@ -1,9 +1,16 @@ package View; /** - * Launcher class for the Stock Trading Game. + * Entry point for launching the Stock Trading Game application. + * This launcher delegates to the main StockTradingGameApp class to start the JavaFX + * application. It provides a conventional main method entry point. */ public class Launcher { + /** + * Main method to launch the application. + * + * @param args command-line arguments (not used) + */ public static void main(String[] args) { StockTradingGameApp.main(args); } diff --git a/src/main/java/View/MainGameScene.java b/src/main/java/View/MainGameScene.java index fc7bd81..6f4cd27 100644 --- a/src/main/java/View/MainGameScene.java +++ b/src/main/java/View/MainGameScene.java @@ -1,585 +1,693 @@ package View; -import javafx.geometry.Insets; +import Model.*; +import java.math.BigDecimal; +import java.util.List; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.geometry.Pos; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.layout.*; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import Model.*; -import java.math.BigDecimal; -import java.util.List; /** - * Main game UI. Implements ExchangeObserver so the view automatically refreshes - * whenever the Exchange notifies it of a state change (week advance or trade). + * The main game user interface scene. + * Displays the game board with stocks, portfolio, trading interface, and transaction + * history. Implements ExchangeObserver to automatically refresh UI when the game state changes. */ public class MainGameScene implements ExchangeObserver { - private Scene scene; - private Exchange exchange; - private Player player; - private Runnable onExit; - private Label statusLabel; - - // References to UI components that need refreshing - private TableView portfolioTable; - private ListView holdingsList; - private TableView historyTable; - private ComboBox weekFilterCombo; - - public MainGameScene(Exchange exchange, Player player, Runnable onExit) { - this.exchange = exchange; - this.player = player; - this.onExit = onExit; - - // Register this view as an observer of the exchange - this.exchange.addObserver(this); - - this.scene = createScene(); - } - - /** - * Called automatically by Exchange whenever its state changes. - * Refreshes all UI elements to reflect the latest data. - */ - @Override - public void onExchangeUpdated(Exchange exchange) { - updateStatus(); - refreshAllUI(); - } - - private Scene createScene() { - VBox root = new VBox(0); - - // TOP STATUS BAR - HBox topBar = new HBox(12); - topBar.getStyleClass().add("top-bar"); - topBar.setAlignment(Pos.CENTER_LEFT); - - statusLabel = new Label(); - statusLabel.getStyleClass().add("status-label"); - updateStatus(); - - Separator sep = new Separator(javafx.geometry.Orientation.VERTICAL); - sep.setPrefHeight(20); - - Button nextWeekBtn = new Button("Next week"); - nextWeekBtn.getStyleClass().addAll("action-button"); - nextWeekBtn.setOnAction(e -> exchange.advance()); // observer handles the UI update - - Button exitBtn = new Button("Exit"); - exitBtn.getStyleClass().add("exit-button"); - exitBtn.setOnAction(e -> { - if (confirm("Exit Game?", "Final Net Worth: $" + formatMoney(getNetWorth()))) { - exchange.removeObserver(this); // clean up before closing - onExit.run(); + private final Scene scene; + private final Exchange exchange; + private final Player player; + private final Runnable onExit; + private Label statusLabel; + + // References to UI components that need refreshing + private TableView portfolioTable; + private ListView holdingsList; + private TableView historyTable; + private ComboBox weekFilterCombo; + + /** + * Constructs the main game scene. + * + * @param exchange the Exchange managing the game state. Cannot be null + * @param player the Player being displayed. Cannot be null + * @param onExit callback to run when exiting the game. Cannot be null + */ + public MainGameScene(Exchange exchange, Player player, Runnable onExit) { + this.exchange = exchange; + this.player = player; + this.onExit = onExit; + + // Register this view as an observer of the exchange + this.exchange.addObserver(this); + + this.scene = createScene(); + } + + /** + * Callback invoked when the Exchange notifies observers of state changes. + * + * @param exchange the Exchange that changed + */ + @Override + public void onExchangeUpdated(Exchange exchange) { + updateStatus(); + refreshAllUI(); + } + + /** + * Creates the main game scene layout. + * + * @return the constructed Scene + */ + private Scene createScene() { + VBox root = new VBox(0); + + // TOP STATUS BAR + HBox topBar = new HBox(12); + topBar.getStyleClass().add("top-bar"); + topBar.setAlignment(Pos.CENTER_LEFT); + + statusLabel = new Label(); + statusLabel.getStyleClass().add("status-label"); + updateStatus(); + + Separator sep = new Separator(javafx.geometry.Orientation.VERTICAL); + sep.setPrefHeight(20); + + Button nextWeekBtn = new Button("Next week"); + nextWeekBtn.getStyleClass().addAll("action-button"); + nextWeekBtn.setOnAction(e -> exchange.advance()); // observer handles the UI update + + Button exitBtn = new Button("Exit"); + exitBtn.getStyleClass().add("exit-button"); + exitBtn.setOnAction(e -> { + if (confirm("Exit Game?", "Final Net Worth: $" + formatMoney(getNetWorth()))) { + exchange.removeObserver(this); // clean up before closing + onExit.run(); + } + }); + + topBar.getChildren().addAll(statusLabel, sep, nextWeekBtn, exitBtn); + + // MAIN CONTENT TABS + TabPane tabs = new TabPane(); + tabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); + tabs.getTabs().addAll( + new Tab("Stocks", createStocksPanel()), + new Tab("Portfolio", createPortfolioPanel()), + new Tab("Trade", createTradePanel()), + new Tab("History", createHistoryPanel()) + ); + + VBox.setVgrow(tabs, Priority.ALWAYS); + root.getChildren().addAll(topBar, tabs); + + Scene scene = new Scene(root, 1000, 700); + scene.getStylesheets().add( + getClass().getResource("/Style/Global.css").toExternalForm() + ); + return scene; + } + + /** + * Creates the stocks panel displaying available stocks. + * + * @return the stocks panel + */ + private VBox createStocksPanel() { + VBox panel = new VBox(10); + panel.getStyleClass().add("content-area"); + + HBox searchBox = new HBox(8); + searchBox.setAlignment(Pos.CENTER_LEFT); + + TextField search = new TextField(); + search.setPromptText("Search symbol or company..."); + HBox.setHgrow(search, Priority.ALWAYS); + + Button searchBtn = new Button("Search"); + searchBtn.getStyleClass().add("action-button"); + + ComboBox filter = new ComboBox<>(); + filter.setItems(FXCollections.observableArrayList("All", "Gainers", "Losers")); + filter.setValue("All"); + + TableView table = new TableView<>(); + addStockColumns(table); + + Runnable loadStocks = () -> { + List stocks; + String filterVal = filter.getValue(); + String searchVal = search.getText().trim(); + + if (null == filterVal) { + stocks = exchange.findStocks(searchVal); + } else { + stocks = switch (filterVal) { + case "Gainers" -> exchange.getGainers(20); + case "Losers" -> exchange.getLosers(20); + default -> exchange.findStocks(searchVal); + }; + } + + ObservableList data = FXCollections.observableArrayList(); + for (Stock s : stocks) { + data.add(new StockRow(s)); + } + table.setItems(data); + }; + + searchBtn.setOnAction(e -> loadStocks.run()); + filter.setOnAction(e -> loadStocks.run()); + search.setOnAction(e -> loadStocks.run()); + + searchBox.getChildren().addAll(search, searchBtn, filter); + loadStocks.run(); + + VBox.setVgrow(table, Priority.ALWAYS); + panel.getChildren().addAll(searchBox, table); + return panel; + } + + /** + * Creates the portfolio panel showing held shares and net worth. + * + * @return the portfolio panel + */ + private VBox createPortfolioPanel() { + VBox panel = new VBox(10); + panel.getStyleClass().add("content-area"); + + Label heading = new Label("Holdings:"); + + portfolioTable = new TableView<>(); + addPortfolioColumns(portfolioTable); + updatePortfolio(portfolioTable); + + Button refresh = new Button("Refresh"); + refresh.getStyleClass().add("action-button"); + refresh.setOnAction(e -> updatePortfolio(portfolioTable)); + + Button sellSelected = new Button("Sell Selected"); + sellSelected.getStyleClass().add("action-button"); + sellSelected.setOnAction(e -> { + PortfolioRow selected = portfolioTable.getSelectionModel().getSelectedItem(); + if (selected == null) { + alert("Error", "Select a holding to sell."); + return; + } + Transaction trans = exchange.sell(selected.s, player); // observer fires refresh + if (trans != null && trans.isCommitted()) { + showConfirmation("Sale successful", trans); + } else { + alert("Failed", "Could not complete the sale."); + } + }); + + HBox buttons = new HBox(10, refresh, sellSelected); + buttons.setAlignment(Pos.CENTER_LEFT); + + VBox.setVgrow(portfolioTable, Priority.ALWAYS); + panel.getChildren().addAll(heading, portfolioTable, buttons); + return panel; + } + + /** + * Creates the trading panel with buy and sell tabs. + * + * @return the trading panel + */ + private VBox createTradePanel() { + VBox panel = new VBox(0); + panel.getStyleClass().add("content-area"); + + TabPane tradeTabs = new TabPane(); + tradeTabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); + tradeTabs.getTabs().addAll( + new Tab("Buy", createBuyTab()), + new Tab("Sell", createSellTab()) + ); + + VBox.setVgrow(tradeTabs, Priority.ALWAYS); + panel.getChildren().add(tradeTabs); + return panel; + } + + /** + * Creates the buy transaction tab. + * + * @return the buy tab content + */ + private VBox createBuyTab() { + VBox box = new VBox(10); + box.getStyleClass().add("content-area"); + + Label heading = new Label("Buy Stocks:"); + + GridPane form = new GridPane(); + form.setHgap(10); + form.setVgap(10); + + TextField stockField = new TextField(); + stockField.setPromptText("Stock symbol (e.g. AAPL)"); + + TextField qtyField = new TextField(); + qtyField.setPromptText("Quantity"); + + form.add(new Label("Symbol:"), 0, 0); + form.add(stockField, 1, 0); + form.add(new Label("Quantity:"), 0, 1); + form.add(qtyField, 1, 1); + + Label infoLabel = new Label(); + + stockField.textProperty() + .addListener((obs, old, val) -> updateBuyInfo(val, qtyField, infoLabel)); + qtyField.textProperty() + .addListener((obs, old, val) -> updateBuyInfo(stockField.getText(), qtyField, infoLabel)); + + Button buyBtn = new Button("Buy"); + buyBtn.getStyleClass().add("action-button"); + buyBtn.setOnAction(e -> { + try { + Stock s = exchange.getStock(stockField.getText().toUpperCase()); + if (s == null) { + alert("Error", "Stock not found"); + return; + } + BigDecimal qty = new BigDecimal(qtyField.getText()); + Transaction trans = exchange.buy(s.getSymbol(), qty, player); // observer fires refresh + if (trans != null && trans.isCommitted()) { + showConfirmation("Purchase successful", trans); + stockField.clear(); + qtyField.clear(); + infoLabel.setText(""); + } else { + alert("Failed", "Insufficient funds or error"); + } + } catch (Exception ex) { + alert("Error", ex.getMessage()); + } + }); + + box.getChildren().addAll(heading, form, infoLabel, buyBtn); + return box; + } + + /** + * Creates the sell transaction tab. + * + * @return the sell tab content + */ + private VBox createSellTab() { + VBox box = new VBox(10); + box.getStyleClass().add("content-area"); + + Label heading = new Label("Your Holdings:"); + + holdingsList = new ListView<>(); + holdingsList.setPrefHeight(400); + + holdingsList.setCellFactory(lv -> new ListCell() { + @Override + protected void updateItem(Share s, boolean empty) { + super.updateItem(s, empty); + if (empty || s == null) { + setText(null); + } else { + setText( + s.getStock().getSymbol() + " - " + + s.getQuantity() + " @ $" + + formatMoney(s.getStock().getSalesPrice()) + ); } - }); - - topBar.getChildren().addAll(statusLabel, sep, nextWeekBtn, exitBtn); - - // MAIN CONTENT TABS - TabPane tabs = new TabPane(); - tabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); - tabs.getTabs().addAll( - new Tab("Stocks", createStocksPanel()), - new Tab("Portfolio", createPortfolioPanel()), - new Tab("Trade", createTradePanel()), - new Tab("History", createHistoryPanel()) - ); - - VBox.setVgrow(tabs, Priority.ALWAYS); - root.getChildren().addAll(topBar, tabs); - - Scene scene = new Scene(root, 1000, 700); - scene.getStylesheets().add( - getClass().getResource("/Style/Global.css").toExternalForm() + } + }); + + updateHoldingsList(holdingsList); + + // Numer of rows + HBox quantityRow = new HBox(8); + quantityRow.setAlignment(Pos.CENTER_LEFT); + TextField quantityField = new TextField(); + quantityField.setPromptText("Antall"); + quantityField.setPrefWidth(120); + quantityRow.getChildren().addAll(new Label("Quantity to sell:"), quantityField); + + // Fill in the maximum number automatically when selecting a holding + holdingsList.getSelectionModel().selectedItemProperty().addListener((obs, old, selected) -> { + if (selected != null) { + quantityField.setText(selected.getQuantity().toPlainString()); + } + }); + + Button sellBtn = new Button("Sell Selected"); + sellBtn.setOnAction(e -> { + Share selected = holdingsList.getSelectionModel().getSelectedItem(); + if (selected == null) { + alert("Error", "Select a holding to sell"); + return; + } + + try { + BigDecimal quantity = new BigDecimal(quantityField.getText().trim()); + + if (quantity.compareTo(BigDecimal.ZERO) <= 0) { + alert("Error", "Quantity must be greater than zero"); + return; + } + + Transaction trans = exchange.sell(selected, quantity, player); // observer fires refresh + + if (trans != null && trans.isCommitted()) { + showConfirmation("Sale successful", trans); + } else { + alert("Failed", "Could not complete sale. Check quantity."); + } + } catch (NumberFormatException ex) { + alert("Error", "Enter a valid number for quantity"); + } + }); + + box.getChildren().addAll(heading, holdingsList, quantityRow, sellBtn); + + return box; + } + + /** + * Creates the transaction history panel. + * + * @return the history panel + */ + private VBox createHistoryPanel() { + VBox panel = new VBox(10); + panel.getStyleClass().add("content-area"); + + weekFilterCombo = new ComboBox<>(); + updateWeekCombo(weekFilterCombo); + + historyTable = new TableView<>(); + addHistoryColumns(historyTable); + updateHistory(historyTable, null); + + weekFilterCombo.setOnAction(e -> updateHistory( + historyTable, + weekFilterCombo.getValue() == null || weekFilterCombo.getValue() == 0 + ? null : weekFilterCombo.getValue() + )); + + HBox filterRow = new HBox(8); + filterRow.setAlignment(Pos.CENTER_LEFT); + filterRow.getChildren().addAll(new Label("Week:"), weekFilterCombo); + + VBox.setVgrow(historyTable, Priority.ALWAYS); + panel.getChildren().addAll(filterRow, historyTable); + return panel; + } + + // ---- Column helpers ---- + + @SuppressWarnings("unchecked") +private void addStockColumns(TableView table) { + table.getColumns().addAll( + createCol("Symbol", 90, "symbol"), + createCol("Company", 180, "company"), + createCol("Price", 90, "price"), + createCol("Change", 90, "change"), + createCol("High", 90, "high"), + createCol("Low", 90, "low") + ); + } + + @SuppressWarnings("unchecked") + private void addPortfolioColumns(TableView table) { + table.getColumns().addAll( + createCol("Symbol", 90, "symbol"), + createCol("Qty", 70, "qty"), + createCol("Buy Price", 100, "buyPrice"), + createCol("Current Price", 120, "currentPrice"), + createCol("Gain/Loss", 100, "gainLoss") + ); + } + + @SuppressWarnings("unchecked") + private void addHistoryColumns(TableView table) { + table.getColumns().addAll( + createCol("Week", 70, "week"), + createCol("Type", 70, "type"), + createCol("Symbol", 90, "symbol"), + createCol("Qty", 70, "qty"), + createCol("Total", 110, "total") + ); + } + + private TableColumn createCol(String header, int width, String property) { + TableColumn col = new TableColumn<>(header); + col.setPrefWidth(width); + col.setCellValueFactory(cellData -> { + try { + return new javafx.beans.property.SimpleStringProperty( + cellData.getValue().getClass() + .getMethod("get" + capitalize(property)) + .invoke(cellData.getValue()).toString() ); - return scene; + } catch (Exception e) { + return new javafx.beans.property.SimpleStringProperty(""); + } + }); + return col; + } + + private String capitalize(String s) { + return s.substring(0, 1).toUpperCase() + s.substring(1); + } + + // ---- Data updaters ---- + + private void updatePortfolio(TableView table) { + ObservableList data = FXCollections.observableArrayList(); + for (Share s : player.getPortfolio().getShares()) { + data.add(new PortfolioRow(s)); } - - private VBox createStocksPanel() { - VBox panel = new VBox(10); - panel.getStyleClass().add("content-area"); - - HBox searchBox = new HBox(8); - searchBox.setAlignment(Pos.CENTER_LEFT); - - TextField search = new TextField(); - search.setPromptText("Search symbol or company..."); - HBox.setHgrow(search, Priority.ALWAYS); - - Button searchBtn = new Button("Search"); - searchBtn.getStyleClass().add("action-button"); - - ComboBox filter = new ComboBox<>(); - filter.setItems(FXCollections.observableArrayList("All", "Gainers", "Losers")); - filter.setValue("All"); - - TableView table = new TableView<>(); - addStockColumns(table); - - Runnable loadStocks = () -> { - List stocks; - String filterVal = filter.getValue(); - String searchVal = search.getText().trim(); - - if ("Gainers".equals(filterVal)) { - stocks = exchange.getGainers(20); - } else if ("Losers".equals(filterVal)) { - stocks = exchange.getLosers(20); - } else { - stocks = exchange.findStocks(searchVal); - } - - ObservableList data = FXCollections.observableArrayList(); - for (Stock s : stocks) { - data.add(new StockRow(s)); - } - table.setItems(data); - }; - - searchBtn.setOnAction(e -> loadStocks.run()); - filter.setOnAction(e -> loadStocks.run()); - search.setOnAction(e -> loadStocks.run()); - - searchBox.getChildren().addAll(search, searchBtn, filter); - loadStocks.run(); - - VBox.setVgrow(table, Priority.ALWAYS); - panel.getChildren().addAll(searchBox, table); - return panel; + table.setItems(data); + } + + private void updateHoldingsList(ListView list) { + ObservableList items = FXCollections.observableArrayList(); + items.addAll(player.getPortfolio().getShares()); + list.setItems(items); + } + + private void updateHistory(TableView table, Integer week) { + ObservableList data = FXCollections.observableArrayList(); + List trans = week == null + ? player.getTransactionArchive().getAllTransactions() + : player.getTransactionArchive().getTransactions(week); + for (Transaction t : trans) { + data.add(new HistoryRow(t)); } - - private VBox createPortfolioPanel() { - VBox panel = new VBox(10); - panel.getStyleClass().add("content-area"); - - Label heading = new Label("Holdings:"); - - portfolioTable = new TableView<>(); - addPortfolioColumns(portfolioTable); - updatePortfolio(portfolioTable); - - Button refresh = new Button("Refresh"); - refresh.getStyleClass().add("action-button"); - refresh.setOnAction(e -> updatePortfolio(portfolioTable)); - - Button sellSelected = new Button("Sell Selected"); - sellSelected.getStyleClass().add("action-button"); - sellSelected.setOnAction(e -> { - PortfolioRow selected = portfolioTable.getSelectionModel().getSelectedItem(); - if (selected == null) { - alert("Error", "Select a holding to sell."); - return; - } - Transaction trans = exchange.sell(selected.s, player); // observer fires refresh - if (trans != null && trans.isCommitted()) { - showConfirmation("Sale successful", trans); - } else { - alert("Failed", "Could not complete the sale."); - } - }); - - HBox buttons = new HBox(10, refresh, sellSelected); - buttons.setAlignment(Pos.CENTER_LEFT); - - VBox.setVgrow(portfolioTable, Priority.ALWAYS); - panel.getChildren().addAll(heading, portfolioTable, buttons); - return panel; + table.setItems(data); + } + + private void updateWeekCombo(ComboBox combo) { + ObservableList weeks = FXCollections.observableArrayList(); + weeks.add(0); + int total = player.getTransactionArchive().countDistinctWeeks(); + for (int i = 1; i <= total; i++) { + weeks.add(i); } - - private VBox createTradePanel() { - VBox panel = new VBox(0); - panel.getStyleClass().add("content-area"); - - TabPane tradeTabs = new TabPane(); - tradeTabs.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); - tradeTabs.getTabs().addAll( - new Tab("Buy", createBuyTab()), - new Tab("Sell", createSellTab()) - ); - - VBox.setVgrow(tradeTabs, Priority.ALWAYS); - panel.getChildren().add(tradeTabs); - return panel; + combo.setItems(weeks); + combo.setValue(0); + } + + private void updateBuyInfo(String symbol, TextField qtyField, Label label) { + try { + if (symbol == null || symbol.isEmpty()) { + return; + } + Stock s = exchange.getStock(symbol.toUpperCase()); + if (s == null) { + return; + } + BigDecimal qty = new BigDecimal(qtyField.getText()); + BigDecimal gross = s.getSalesPrice().multiply(qty); + BigDecimal comm = gross.multiply(new BigDecimal("0.005")); + label.setText("Gross: $" + formatMoney(gross) + + " | Commission: $" + formatMoney(comm) + + " | Total: $" + formatMoney(gross.add(comm))); + } catch (Exception e) { + // ignore partial input } - - private VBox createBuyTab() { - VBox box = new VBox(10); - box.getStyleClass().add("content-area"); - - Label heading = new Label("Buy Stocks:"); - - GridPane form = new GridPane(); - form.setHgap(10); - form.setVgap(10); - - TextField stockField = new TextField(); - stockField.setPromptText("Stock symbol (e.g. AAPL)"); - - TextField qtyField = new TextField(); - qtyField.setPromptText("Quantity"); - - form.add(new Label("Symbol:"), 0, 0); - form.add(stockField, 1, 0); - form.add(new Label("Quantity:"), 0, 1); - form.add(qtyField, 1, 1); - - Label infoLabel = new Label(); - - stockField.textProperty().addListener((obs, old, val) -> updateBuyInfo(val, qtyField, infoLabel)); - qtyField.textProperty().addListener((obs, old, val) -> updateBuyInfo(stockField.getText(), qtyField, infoLabel)); - - Button buyBtn = new Button("Buy"); - buyBtn.getStyleClass().add("action-button"); - buyBtn.setOnAction(e -> { - try { - Stock s = exchange.getStock(stockField.getText().toUpperCase()); - if (s == null) { - alert("Error", "Stock not found"); - return; - } - BigDecimal qty = new BigDecimal(qtyField.getText()); - Transaction trans = exchange.buy(s.getSymbol(), qty, player); // observer fires refresh - if (trans != null && trans.isCommitted()) { - showConfirmation("Purchase successful", trans); - stockField.clear(); - qtyField.clear(); - infoLabel.setText(""); - } else { - alert("Failed", "Insufficient funds or error"); - } - } catch (Exception ex) { - alert("Error", ex.getMessage()); - } - }); - - box.getChildren().addAll(heading, form, infoLabel, buyBtn); - return box; + } + + private void updateStatus() { + PlayerStatus playerStatus = player.getStatus(); + statusLabel.setText( + "Player: " + player.getName() + + " | Status: " + playerStatus.getDisplayName() + + " | Week: " + exchange.getWeek() + + " | Cash: $" + formatMoney(player.getMoney()) + + " | Net Worth: $" + formatMoney(getNetWorth()) + ); + } + + private void refreshAllUI() { + if (portfolioTable != null) { + updatePortfolio(portfolioTable); } - - private VBox createSellTab() { - VBox box = new VBox(10); - box.getStyleClass().add("content-area"); - - Label heading = new Label("Your Holdings:"); - - holdingsList = new ListView<>(); - holdingsList.setPrefHeight(400); - - holdingsList.setCellFactory(lv -> new ListCell() { - @Override - protected void updateItem(Share s, boolean empty) { - super.updateItem(s, empty); - if (empty || s == null) { - setText(null); - } else { - setText( - s.getStock().getSymbol() + " - " + - s.getQuantity() + " @ $" + - formatMoney(s.getStock().getSalesPrice()) - ); - } - } - }); - - updateHoldingsList(holdingsList); - - // Antall Rad - HBox quantityRow = new HBox(8); - quantityRow.setAlignment(Pos.CENTER_LEFT); - TextField quantityField = new TextField(); - quantityField.setPromptText("Antall"); - quantityField.setPrefWidth(120); - quantityRow.getChildren().addAll(new Label("Quantity to sell:"), quantityField); - - // Fyll inn maks anntall automatisk når man velger en holding - holdingsList.getSelectionModel().selectedItemProperty().addListener((obs, old, selected) -> { - if (selected != null) { - quantityField.setText(selected.getQuantity().toPlainString()); - } - }); - - Button sellBtn = new Button("Sell Selected"); - sellBtn.setOnAction(e -> { - Share selected = holdingsList.getSelectionModel().getSelectedItem(); - if (selected == null) { - alert("Error", "Select a holding to sell"); - return; - } - - try { - BigDecimal quantity = new BigDecimal(quantityField.getText().trim()); - - if (quantity.compareTo(BigDecimal.ZERO) <= 0) { - alert("Error", "Quantity must be greater than zero"); - return; - } - - Transaction trans = exchange.sell(selected, quantity, player); // observer fires refresh - - if (trans != null && trans.isCommitted()) { - showConfirmation("Sale successful", trans); - } - - else { - alert("Failed", "Could not complete sale. Check quantity."); - } - } - - catch (NumberFormatException ex) { - alert("Error", "Enter a valid number for quantity"); - } - }); - - box.getChildren().addAll(heading, holdingsList, quantityRow, sellBtn); - - return box; + if (holdingsList != null) { + updateHoldingsList(holdingsList); } - - private VBox createHistoryPanel() { - VBox panel = new VBox(10); - panel.getStyleClass().add("content-area"); - - weekFilterCombo = new ComboBox<>(); + if (historyTable != null) { + updateHistory(historyTable, + weekFilterCombo != null && weekFilterCombo.getValue() != null + ? weekFilterCombo.getValue() : null); + if (weekFilterCombo != null) { updateWeekCombo(weekFilterCombo); - - historyTable = new TableView<>(); - addHistoryColumns(historyTable); - updateHistory(historyTable, null); - - weekFilterCombo.setOnAction(e -> updateHistory( - historyTable, - weekFilterCombo.getValue() == null || weekFilterCombo.getValue() == 0 - ? null : weekFilterCombo.getValue() - )); - - HBox filterRow = new HBox(8); - filterRow.setAlignment(Pos.CENTER_LEFT); - filterRow.getChildren().addAll(new Label("Week:"), weekFilterCombo); - - VBox.setVgrow(historyTable, Priority.ALWAYS); - panel.getChildren().addAll(filterRow, historyTable); - return panel; + } } - - // ---- Column helpers ---- - - private void addStockColumns(TableView table) { - table.getColumns().addAll( - createCol("Symbol", 90, "symbol"), - createCol("Company", 180, "company"), - createCol("Price", 90, "price"), - createCol("Change", 90, "change"), - createCol("High", 90, "high"), - createCol("Low", 90, "low") - ); + } + + private BigDecimal getNetWorth() { + return player.getMoney().add(player.getPortfolio().getNetWorth()); + } + + private void showConfirmation(String title, Transaction t) { + StringBuilder msg = new StringBuilder(); + if (t instanceof Purchase) { + Purchase p = (Purchase) t; + msg.append("Bought ").append(formatMoney(p.getShare().getQuantity())) + .append(" @ $").append(formatMoney(p.getShare().getPurchasePrice())) + .append("\nCost: $").append(formatMoney(p.getCalculator().calculateTotal())); + } else { + Sale s = (Sale) t; + msg.append("Sold ").append(formatMoney(s.getShare().getQuantity())) + .append(" @ $").append(formatMoney(s.getShare().getStock().getSalesPrice())) + .append("\nProceeds: $").append(formatMoney(s.getCalculator().calculateTotal())); } - - private void addPortfolioColumns(TableView table) { - table.getColumns().addAll( - createCol("Symbol", 90, "symbol"), - createCol("Qty", 70, "qty"), - createCol("Buy Price", 100, "buyPrice"), - createCol("Current Price", 120, "currentPrice"), - createCol("Gain/Loss", 100, "gainLoss") - ); + alert(title, msg.toString()); + } + + private void alert(String title, String msg) { + Alert a = new Alert(Alert.AlertType.INFORMATION); + a.setTitle(title); + a.setHeaderText(null); + a.setContentText(msg); + a.showAndWait(); + } + + private boolean confirm(String title, String msg) { + Alert a = new Alert(Alert.AlertType.CONFIRMATION); + a.setTitle(title); + a.setHeaderText(null); + a.setContentText(msg); + return a.showAndWait().get() == ButtonType.OK; + } + + private String formatMoney(BigDecimal n) { + return n.setScale(2, java.math.RoundingMode.HALF_UP).toString(); + } + + public Scene getScene() { + return scene; + } + + // ---- Data row classes ---- + + static class StockRow { + Stock s; + + StockRow(Stock s) { + this.s = s; } - private void addHistoryColumns(TableView table) { - table.getColumns().addAll( - createCol("Week", 70, "week"), - createCol("Type", 70, "type"), - createCol("Symbol", 90, "symbol"), - createCol("Qty", 70, "qty"), - createCol("Total", 110, "total") - ); + public String getSymbol() { + return s.getSymbol(); } - private TableColumn createCol(String header, int width, String property) { - TableColumn col = new TableColumn<>(header); - col.setPrefWidth(width); - col.setCellValueFactory(cellData -> { - try { - return new javafx.beans.property.SimpleStringProperty( - cellData.getValue().getClass() - .getMethod("get" + capitalize(property)) - .invoke(cellData.getValue()).toString() - ); - } catch (Exception e) { - return new javafx.beans.property.SimpleStringProperty(""); - } - }); - return col; + public String getCompany() { + return s.getCompany(); } - private String capitalize(String s) { - return s.substring(0, 1).toUpperCase() + s.substring(1); + public String getPrice() { + return "$" + s.getSalesPrice(); } - // ---- Data updaters ---- - - private void updatePortfolio(TableView table) { - ObservableList data = FXCollections.observableArrayList(); - for (Share s : player.getPortfolio().getShares()) { - data.add(new PortfolioRow(s)); - } - table.setItems(data); + public String getChange() { + return "$" + s.getLatestPriceChange(); } - - private void updateHoldingsList(ListView list) { - ObservableList items = FXCollections.observableArrayList(); - items.addAll(player.getPortfolio().getShares()); - list.setItems(items); + + public String getHigh() { + return "$" + s.getHighestPrice(); } - private void updateHistory(TableView table, Integer week) { - ObservableList data = FXCollections.observableArrayList(); - List trans = week == null - ? player.getTransactionArchive().getAllTransactions() - : player.getTransactionArchive().getTransactions(week); - for (Transaction t : trans) { - data.add(new HistoryRow(t)); - } - table.setItems(data); + public String getLow() { + return "$" + s.getLowestPrice(); } - private void updateWeekCombo(ComboBox combo) { - ObservableList weeks = FXCollections.observableArrayList(); - weeks.add(0); - int total = player.getTransactionArchive().countDistinctWeeks(); - for (int i = 1; i <= total; i++) weeks.add(i); - combo.setItems(weeks); - combo.setValue(0); - } + } - private void updateBuyInfo(String symbol, TextField qtyField, Label label) { - try { - if (symbol == null || symbol.isEmpty()) return; - Stock s = exchange.getStock(symbol.toUpperCase()); - if (s == null) return; - BigDecimal qty = new BigDecimal(qtyField.getText()); - BigDecimal gross = s.getSalesPrice().multiply(qty); - BigDecimal comm = gross.multiply(new BigDecimal("0.005")); - label.setText("Gross: $" + formatMoney(gross) + - " | Commission: $" + formatMoney(comm) + - " | Total: $" + formatMoney(gross.add(comm))); - } catch (Exception e) { - // ignore partial input - } - } + static class PortfolioRow { + Share s; - private void updateStatus() { - PlayerStatus playerStatus = player.getStatus(); - statusLabel.setText( - "Player: " + player.getName() + - " | Status: " + playerStatus.getDisplayName() + - " | Week: " + exchange.getWeek() + - " | Cash: $" + formatMoney(player.getMoney()) + - " | Net Worth: $" + formatMoney(getNetWorth()) - ); + PortfolioRow(Share s) { + this.s = s; + } + + public String getSymbol() { + return s.getStock().getSymbol(); } - private void refreshAllUI() { - if (portfolioTable != null) { - updatePortfolio(portfolioTable); - } - if (holdingsList != null) { - updateHoldingsList(holdingsList); - } - if (historyTable != null) { - updateHistory(historyTable, - weekFilterCombo != null && weekFilterCombo.getValue() != null - ? weekFilterCombo.getValue() : null); - if (weekFilterCombo != null) { - updateWeekCombo(weekFilterCombo); - } - } + public String getQty() { + return s.getQuantity().toString(); } - private BigDecimal getNetWorth() { - return player.getMoney().add(player.getPortfolio().getNetWorth()); + public String getBuyPrice() { + return "$" + s.getPurchasePrice(); } - private void showConfirmation(String title, Transaction t) { - StringBuilder msg = new StringBuilder(); - if (t instanceof Purchase) { - Purchase p = (Purchase) t; - msg.append("Bought ").append(formatMoney(p.getShare().getQuantity())) - .append(" @ $").append(formatMoney(p.getShare().getPurchasePrice())) - .append("\nCost: $").append(formatMoney(p.getCalculator().calculateTotal())); - } else { - Sale s = (Sale) t; - msg.append("Sold ").append(formatMoney(s.getShare().getQuantity())) - .append(" @ $").append(formatMoney(s.getShare().getStock().getSalesPrice())) - .append("\nProceeds: $").append(formatMoney(s.getCalculator().calculateTotal())); - } - alert(title, msg.toString()); + public String getCurrentPrice() { + return "$" + s.getStock().getSalesPrice(); } - private void alert(String title, String msg) { - Alert a = new Alert(Alert.AlertType.INFORMATION); - a.setTitle(title); - a.setHeaderText(null); - a.setContentText(msg); - a.showAndWait(); + public String getGainLoss() { + SaleCalculator calc = new SaleCalculator(s); + BigDecimal val = calc.calculateTotal(); + BigDecimal cost = s.getPurchasePrice().multiply(s.getQuantity()); + return "$" + val.subtract(cost); } + } + + static class HistoryRow { + Transaction t; - private boolean confirm(String title, String msg) { - Alert a = new Alert(Alert.AlertType.CONFIRMATION); - a.setTitle(title); - a.setHeaderText(null); - a.setContentText(msg); - return a.showAndWait().get() == ButtonType.OK; + HistoryRow(Transaction t) { + this.t = t; } - private String formatMoney(BigDecimal n) { - return n.setScale(2, java.math.RoundingMode.HALF_UP).toString(); + public String getWeek() { + return String.valueOf(t.getWeek()); } - public Scene getScene() { - return scene; + public String getType() { + return t instanceof Purchase ? "BUY" : "SELL"; } - // ---- Data row classes ---- - - static class StockRow { - Stock s; - StockRow(Stock s) { this.s = s; } - public String getSymbol() { return s.getSymbol(); } - public String getCompany() { return s.getCompany(); } - public String getPrice() { return "$" + s.getSalesPrice(); } - public String getChange() { return "$" + s.getLatestPriceChange(); } - public String getHigh() { return "$" + s.getHighestPrice(); } - public String getLow() { return "$" + s.getLowestPrice(); } + public String getSymbol() { + return t.getShare().getStock().getSymbol(); } - static class PortfolioRow { - Share s; - PortfolioRow(Share s) { this.s = s; } - public String getSymbol() { return s.getStock().getSymbol(); } - public String getQty() { return s.getQuantity().toString(); } - public String getBuyPrice() { return "$" + s.getPurchasePrice(); } - public String getCurrentPrice() { return "$" + s.getStock().getSalesPrice(); } - public String getGainLoss() { - SaleCalculator calc = new SaleCalculator(s); - BigDecimal val = calc.calculateTotal(); - BigDecimal cost = s.getPurchasePrice().multiply(s.getQuantity()); - return "$" + val.subtract(cost); - } + public String getQty() { + return t.getShare().getQuantity().toString(); } - static class HistoryRow { - Transaction t; - HistoryRow(Transaction t) { this.t = t; } - public String getWeek() { return String.valueOf(t.getWeek()); } - public String getType() { return t instanceof Purchase ? "BUY" : "SELL"; } - public String getSymbol() { return t.getShare().getStock().getSymbol(); } - public String getQty() { return t.getShare().getQuantity().toString(); } - public String getTotal() { return "$" + t.getCalculator().calculateTotal(); } + public String getTotal() { + return "$" + t.getCalculator().calculateTotal(); } + } } diff --git a/src/main/java/View/StockTradingGameApp.java b/src/main/java/View/StockTradingGameApp.java index 838df67..b1ab987 100644 --- a/src/main/java/View/StockTradingGameApp.java +++ b/src/main/java/View/StockTradingGameApp.java @@ -6,7 +6,9 @@ import javafx.stage.Stage; /** - * StockTradingGameApp class. + * Main JavaFX application class for the Stock Trading Game. + * Manages the application lifecycle, window setup, scene transitions, + * and exchanges between the game setup and main game views. */ public class StockTradingGameApp extends Application { private Stage primaryStage; @@ -14,6 +16,11 @@ public class StockTradingGameApp extends Application { private Player player; private StockFileHandler fileHandler; + /** + * Initializes and displays the primary stage. + * + * @param primaryStage the primary window stage provided by JavaFX + */ @Override public void start(Stage primaryStage) { this.primaryStage = primaryStage; @@ -27,11 +34,19 @@ public void start(Stage primaryStage) { primaryStage.show(); } + /** + * Displays the game setup scene where players configure their game. + */ private void showGameSetup() { GameSetupScene setupScene = new GameSetupScene(this::startGame); primaryStage.setScene(setupScene.getScene()); } + /** + * Starts the main game scene after setup is complete. + * + * @param gameData the game configuration data from the setup scene + */ private void startGame(GameSetupScene.StartGameData gameData) { this.exchange = gameData.exchange; this.player = new Player(gameData.playerName, gameData.startingCapital); @@ -40,10 +55,18 @@ private void startGame(GameSetupScene.StartGameData gameData) { primaryStage.setScene(gameScene.getScene()); } + /** + * Ends the game and closes the application. + */ private void endGame() { primaryStage.close(); } + /** + * Main method to launch the JavaFX application. + * + * @param args command-line arguments (not used) + */ public static void main(String[] args) { launch(args); } diff --git a/target/test-classes/ExchangeTest.class b/target/test-classes/ExchangeTest.class index 238ca9dd2248bbe4e28a26a8a964a5ec95435ded..5b02574e2280eca7a62f88929280c7d347ae094e 100644 GIT binary patch literal 10397 zcmb7K3wRXQbv{QD?6M3-taxJs#@OfqNMK%O0V9C`*((Vn0Rcba(e5BE+T9Vmvw-}5 z#IM*+V&~C3oV1D4rcIi*c5U%$Z6|5k#&zqsX__=`lcufXG)>a9&7)~!C;z#3W@mPH zpu+w>?aZBf|MQ>cJ@YG~*Fg5G|c8uz&(IA6nFwIP6@>X9dH)ItC%^|^xC+(D(Jzy3x z^6m~cI3t;&zo_UaNOPBzZ-N{!RWh{U!v3wR%<&tWab)!rcpFPS2Hy!S|ulw zZSO1;%<*KV=)`C(HBP4uv?)RxVQ7Dfd^PAA+6*i+ovzYL2T-EraD=vCS!L)%363|! zRi4{ol%VaReJ9hE=ha?O6S=OII}DmaQzO(3G%Z2LSCn?cOV^osD@r|3dwqxe7o(kY zy~s#1wVhW+xIOy}iqUjH6(z(GJdN7=J|m{Nl>s_F<^}1Js<~S#(q$XMb-Dvv?6#af zGlxjO^gMy7Vp}YAz@UTlHm1n1w`6v6OKpI}=rG+Bp&OZ&hDUm5v1k=kHW+j>-2y9` zYNY#E$;?8@6+tVx9u0``e2n~^EeiA?lQL%Zpk4exgk3%Bp)>BA$k}% zN6Md>mWR@;tPcsP9Pc#fQ3-*DV&*nW1|B!)UG#250(2HWJ;B&@8A&;O!k{N5%%_QM z6d5F7!77%rShlE|?hZRQX6DBwg`)HUCWaU1R8H!%drc=b5~U9zkOs_Lv6LTnZRsoC zof~t;2dzTRjj7Ho%J@@EsFlj0U8QmN3nb<;j$@_wmI~NPB`AI!a@UK=K5oz_=xOLU zR2pZxKEzf!k*dp0qrXK(&}>DBG+lxnXb0sLyW1>}n;6P)&RTd?+W9@#zdC?OYXGHI*suw{e zP#US7$`jWhiZ2xN;Iq3m>%GjIF-2ym<_9wlTLt@ul8G}5_8=8SahfUmCwpc5Q17>I&vhF${-4NmGbGLo)zUNa=5Etk6KZVp;)hpuz^jl zCW2809V-yN)A$mYlxH&aR0VBy6}cF9#>Q;BptW%OFew*e;Y!tF+$7RxGwlhNt_#}5 z`L3V2WQ+9e_ZcBZkR@kd5ytq z=|${Bhr9Rn``m zgtr-dEpL|=KArZgjxN)K@D8fpCnYR4V59T1c4cJuo8?UzM6$1zqyd92+jg)!m}9+` zGh(NUG4AHw5#Cj?F=X<`k#p@yJ{4c zB(#?)O&8&Wc`0?^q_ByW&<)%wFPWAqin;shk|Z)L5&8DdFyFP zS7Wxri5&q==M<1+d@IFg^uE}Szy}ivAsRSYdfitx&y$o$|krw z6u8m^U<=TDs@e8{?R5cA-vsXn1sw-lNv9Pa(+83bZD;5a#!)#PU)WZEh90Y_=H(#{y4^Kk) zM=q?n3E@K!p1Nq^9}N|rM?Dv_x9UhxweMr7eR%Dipx;Qgou*H=P0**1ck$2S<@XQz zu{RfMiH1?6M`#gcPz8@t3ylVtwYol;hu=S>=TwF~AIjrFm&XPzcp@3j=b-x;l+Hdy zk%muEL%mxPf&pryQjeiJA4A0{1o&)l`9x_oy+AL5&r8q%<@6V{CPIt1O9p=~l;17j zCtp`ykk{v>M*Zm-`U^>Ua>5_I8W6lJ12KM2@C9}Ngym+(&&^<9tX68r6oq4Ccbpz(3g_ylM?4H}<34~^GrXuR%L z`tO9&C~v3nH(JlqzY+5E2Z55NJF8D)JN>K@sFT#+PXBWfp#K#P`V2sy3kCh}P|$L_ z7JuU`{Wu)+G#qg*fa$kuKMTdY%Z-KRu(p_WPaJ=v?JWI|FHhH19g#1huD*mI_#Eo} z=V=9fG2oSEH#njw5dT-<6+NPZ;}@a4D!uZiFFe@qehCJAC7`VDmA57V`pa<8F9Y-| zVW24X!GlO`XO8$`ObUyiuL1NMp`dk>07bbE1N|mIza0wNFbPoX)M22n1N6J0pi@IZ zZ$+7vdcRzbN=+P*exZq{>BCa8iKn-=o#h$Tvh?=%eVpxnfMe2+lsw%rE_Z`w4$Wk# zAuuS<3YC1jTXPc^RdbQ$AcPLJQh8nGevJD16V%tAqQ3q-Kq}#qT7|Qi-s*@ekELET z97Tl^bF4l`ugWHeyz=a!Iw^HN<++|=7F6@lhItd6%3Jt%7WlU+z|w|U?Q&QG!z@(W zE%BmI!&KxBUs^3fRWzQ03&&`!pBwAs@&x~?XE6jf;5KE6I(}}JXN)w;@t8%g(|kG3 z$bRdmPw<}!AU0~kK*ZHc4dNCTF)oNreAW3epABHfUCbEc>ZS&Br8D zSmB4EsJN`FL0#t_t7F2o4Gdqa*867xtXP>LF8OM(Ho90d6c+wSFJBG!%K)6a9l}Al zAFP498g)v7beh2N1aGZ^;;8|sX_^_;b0?P6aTo8vFRl{!I*eV>lGfVY zS?@!(`hJnNlmdN1+oaH@P-rnFA(3C9?t=mi0a{9FDW#=AOM$c$=z~HN_|Ls}W@lHk zR{S;J7isRyz5n^o zl$oFJ9Z1X_v|@>rnG8}BfOhxwg{YCjEVNnB`i^K(>_k4BNY6z&r$Q8<^*m#PpgVnL ztX!kslt~0qJ+;!t09|j;Cb~h8F?2RI4-KtJD+m2*O{OfUG3rq(5~MBobZC715EN^J z6%N_*nOQ0PPgiEHx?Q6UpBGY}QM>{+-7IKLaUM?rs2Pu!nL>FW(s?pKeORIBIpl+M3shkhoHrrrrQ6tGdj#EB(O{Aq zi`0g>-Jm<@C4z#MwhDqkTfza31SkSoO8$|gxnyO7h#jiA$4tW_cfkUC`uM*P?W2BX zFd(SAA_IR19W-c&h5?n40-&#Ep#(-)yTb;J&^>|zb5?%LOu=dEIyzUzL5QL>8lW+Q z#_5QlHak1ld#ZpS#?MS5Z)JPUOrp0xm$S0@gq_YoH&YIVP8ZB1hC997wHG{M9KRq$ zd+De_6Evwf9k;9nxlcxz!W7FtjwtFF8R2m5rD=ms@-7RQ@kKKov-sTu20cg*!T$4R zPVT6(AaM4>NrrrxK_<-z3Msym*#c}(({ZwMDoBW)n$d~jNuD=r&>YWeQ1gbNl@yjR z=oEos5C+u3wt01Rgq3Us222^0CfgM}R!Anxc$OIDv_V;dAw#o?bX*F;pxswe={|_? zHYpgi$R@4HC0=gvi?aqT(aT|3&}OkCUZ-opoGxBz(5pB;>RG*|)QpXM^BO?{LHasa zeZoxT3hB8Zy+IHK>F4le54+$^2ECcy0{EH2lAwEiX!>WC(g5w$!^y0f&Y3ZeECg+o z-fGYkw%=!ES2K^fSI?NxlQyDXBO+xF9T7LQbPHd5V^dW;DqYq>M!C^=2Y<|{G zCa|a(QgpU)Uc!{WNgoN&ZyEI499h?ueb-K(hu9p4%*PD+9X4zrksC^7@=F2w-D2V5 zS?u`VGw5-8f&LRkGJlkZ)V1gv zqOURP-w8VGk2)cv_F7aA(K-4C*XzF*wBHSWZtHZCDBrdId9ZUTObzt4Fd1}(qxzo= z`WAhgt4};WK7*)Uw6cOWIRcyrbv~gH3DS22Rqfhck1X=92K^g7hfR5M=;*kh4gNBN z^gVdwM3BCZg>7xT<^R*5|DqpYXd~~H6dR{DDX3zYK*%6kS!9zaHtJX*HDhHbIXVS} zqjt!$R$9gKb^J_ajJ9KUSZD+ShIH48Zu+^0BFb~860wYrW-0Okr@Lm z6;B>Px0EoG+|Z2V#?vs!Mt?8gJQnEeJiw5m%7KaX}HSw9jwR70c9!oEz?K5eF2hSOHyNrP)Rrlcw6oNt$?ZT$= z87Qh5Grgo5$n~XP$LvuvADb7nt@ORDA(K`%rHS@)y^tCV6qa1_x>!4<8379U&Dx*d z!Xaog<74eXD_)4@bF#b+XYG{gc0qqeIuLCGvP*1X)XeA@l|R(JATskRL&b1 zb^_I`oD-KP4phz&p0kcj8E8M8WsIszy|jRWO4*B+5+*4+HCfOOUI!oQLZ^iuwN4lC z-ik*P=w48t=A1gXUC&pwuEO-Xq^yM6MuTY3^I6!)&a&^>Yff3(!f2lF130%Xcqd44 zo(T*fV~fu}4vN&lZ_-dYpIvfh19`x!*&xn1P9Aq=ZBLr1nYh``#y~u+oYIcd zY{;#bxq#U1oSWsvgTzr;xXS~@v@3FB$R?-2$XTc=*u`b*%hl#(J3VtM8WDX0myU8=NPGCTtvptlj~+0@9dh&#k6vns+vUjQ(TpMXh`qp!^T;y9ZQ@pp z=6E!3h+E|76pt1RaWmG{iBw3Wg&hzXL!1`qsXPaTM0yd8YHw8TJ*aBV;tXnMab{~s zc}LHBdwIP`S>~{rjwh{Ld(^fU3K{P)QC|zFMn&$?l7*9KX1vp1{6{77>TA8n2aP;v zA{F2whi|1u(l}ztgRO;3Qoz+$u|F>%QmS4+oDsC;>QhmLl`Uu+Zu_cm;;O+<+?L^~ zitiHTZ(IS&-?+WP?_L~haFdFkKhz`KHBfEWc@mGyX+OeG{!A36ALFMXpKIyA=_eQw z{SUrY$;Ds7A%unIx-L^-I$R%axJ-@H;p@)R+ViyTDQZ#gH;1>%_uHKJJ1)~r)90xp z+THaewOpjmDmu=OJJcgq^-hO(cb}(QpQ61O?VAqomk0+W!rf2NAXBX-rn(&@2)edH z%Rbry?YGh(wb2;9JU}~e&C@Q`*r2FtHSVAY{V)9#sy&a@tfG4J0-pX4Z(snn0P!zJ z-3So)Wn*|Z$nIUHLl@}YqRsfCn$YDN)a|ibqZa6Z@*EP?F6cfDYSbvq`!3M2a?D;F zUiv(khA(D~i#en*n-%1VT0iJDupiNFI85$=qxX8?n|1obZH64-Kh>~xrBqBY$Y3H>QzB3$2HeUZ*o;eZ`jrdMbk z)D{S1+Ys{duD}?i#mBNh*!e7h6t1Ak>AHpAi@CSb;;f)o9zy9i) zn-D$&;jy1d_!?Ju8X20+-lZZz=Ds%~_hC4=Ob@#9Ko&ejowVR#)}?*YfFu$T{jaF49MNBYAR)irPh3>I_o$S;~TWJ7)Y&b!0^C&UxD$%2r*hB3y5~^e*G*Z>`G#Q|Ce!;NO)$5%)SjVqbuf1jZ zl*Egtu&JH(c4+esX!9f#rsjG9--^$QIF6~T4)nBh)pH=UAK19_#kL}2s9o8jSp9# zv3UiJ%|4lai%Y{Tr{Sl&E)o9le}O*jNolIH`Y6ijIVn(WQvY(gwF2lL`GY~ zZF520b}jt$CHiB3%nNAOE_*Qj+qG>j=BsrqbojN!LiNPqr@JrFGevnSud0Z=g1q`k zYNk&i-+!8R(q}zh>CnN!|3qMWiC5H{mBF#YpLgaj1{9 zeY;$-uh0oMa!m;rTO5S`ww#d4aX&zE{UMU;kC0q{;vp4r6YCn^#M4Z+x$bh z2)S)UE&eT1@aisZs^U8pD8uLUTk)GaoqD#lSf+n0Hhl7|F6umB^~zeJ7Yl08PW1r_ zmo*w{Kp!*KFqB*R%J9%V6m?&ttxt~bCCIoG(#U+=V%s-JWU&0WCyA>og&%uh8(i21 z<^f| z0!mtgYh9SiRPDh+O;cuvyCm z`VR*|)k}r&golrxLvY=zF(X>LwTvmQ0{Jlyq`tT)(xht9k4UK!17Z-r-$;!jiFXIZ z5dK$Bhux=p-KR14=|1;q(tSGaKAm)*9(12x=03&Tr&;$Yfu|gpgpio>Vo{u>jsFjI CFx9dE diff --git a/target/test-classes/PortfolioTest.class b/target/test-classes/PortfolioTest.class index 679e0c4d7ea6495c5d426472e08b43044c6df084..45ae58688b9e7efb5e87f84abf6f429fffc62b8b 100644 GIT binary patch literal 5661 zcmb_f`Fk7H6+PokvNN$AkvJ=f2?=;hAo+t|4F}|_hzIS$rEtDuRp}|=IXt3&pmgUm)HJt z`BeZ1@$V3-6s$aE+Xz_qQ?eD(xvPr~B;UmG-(GBV*XND5!30pHNWKN5lqHqe;aIs0x}A zMn)elq^I@VxHfGPA(F6?nmM864Ds$7YwVPf7rRSbL=e)iZXeGoSU+0G*hX5PF!BbE z#WNYp)@;Md@YL3Xm79y6ERYEQW(`}JrrzI zQH#0|u2rzY*(1XaV+$jS$B!k#xQ^1qvsqIQ;d%wDrLbwuPDOi-xx;$WNNZ*o+bP4Y zeccV%iW^1zZ3@=3CA=hz**POK7i*trz$SEv9i0je2JBdR#<5g*B$A-NLq!PT5TYJ^ zk)1GhDX7}j9mXEPYcF~2#6IEmZ3=qY$`Tw5A8d7;-lU=)4GL77iHgt6j5+nO=_*>aAxEAyq=TUSJ8*V3K}$N8P641VKr?h zgv4ucM8$ygm9#RpW{6s;%jx-o$wp94FgY&wAzBRPR>qJIBf(@3jEoF0@54MYU|Dk} z%X_4nT(;fZE#RDTb=CH-C`kx`+MJ4%5Mf2mZ$E^SD&~cBD;)46gr@8E3WQOyH@NS zCDuaj~h4 z(qR6uKC5T)#woqe$}R|A_o+B7=2C6a)U_NHPx1%15=x=;E{rXtr!7;+_khUkyA*5- zXwxP6tt3zc8n%WsJDCdOJ@hyT33x>-JaywPf0hWWY%*`J*wg{e2}D0^pB1R<%0%W zOz7|-6(7b&ST3yaBA$YE#Tu`iOrm_B5as((1vmPob8EnHb+C8Bia3hWGs3Kb&! zkUYyzq0p#Ud`q@ei7i{)Bsq!cU68wEzc}0@{QPrUYsI!+l9_kBnT=H92T<#6yJimagtwbKP3b~|y#E>_TvV`pv zPB@2pEz8c^IW0S++bL@%-+;^bP6$`ZYQxB!vgY+@LQ>{NZB|R#R&GJXckwElBkh?K zyzWn-d(r_vs%2(OJ-;nsS@VT#%-0O=6~SjIJ2=i^=-MZ}%}^UyCO=g1b*N1t{D216_A?bPO50C8+v_S`lD1!Zw%@6ES=xT<+5W8J+tT(Y z+Smw#nT(#3C9ks%{-I#IR|jPQm&+K!8wxhR`Br{-9L;eZ1F2?5SEEW?{=&%2(IB1SWQb6)^H_uweTr` z&d`b2*WYmkYo{(?{dsKQa??~~bENeOwoXO1UBC?&(0(3WwC|kicm-Y3-c7q)?3Nq$ zp2vQVNCY(mT|>q#xSsR+F!_6=`b(q^l##mGC3O#pR&l+B&NlGh{zb%kHeST7-g`ga zD&1`+*cLRS6`LujaM$9%xb9RcaTJ3x-rE8lzd+GN8YZLyZ;uSg07o1J5+eLgX}P@^ z`QwlEP9*^$^nd0pf=mvD74LxW_KRR(7U6OaJ!@}jg z25}^1p1QezhUFl{(E}r}TD=e8QAT+aj~`&xZuYrZRmxaxDP!qCH)mZpU7i~!1JpfH(^-^W z$g%$tGF8ljtea|j6!jqjzJBua8NW%VaeLxfeOsPM#-3Z#foQR#PF6 z+e25&h2TbNr+Cv5v@1%)PwfbS2k7c3Hn4CzaGQ@>F&C@ZUkeq~gvFtDe;~CtSm>fd z=l(B+E`o?`Uc{N}Esp;zjF z(NYKOcROH&89B-xfy8{j0Hi_rjq@;$zJ5-Adw7UFS*pq^bR~?SUNx>t5TttJ2$-A+Vi^HpYz&WilO`s$zVMbDL2K+{s z0`R^-HwRreO`aP<`?}DXCMh(+@WD2fVBa4I+vCDEzKjnD z8%wB92BJ>8sM|fV#cn=FM~nE_a|qQukDBVTBsuEj2x(&*3O8mxoQPF4nVhDRUt;jb(O^u=X>ekmCAERQ}C2>Rt^fIc4#`Y4Z{ z3j}>(8K7SY27R1Ip9}>3>M}sT77Y3nk3O>$w7E*$>zI^pFl9Atm2b)m%N{pP^`b-+ z_OF*B=l1h}531ft520RM2GndN)Wd;LyhXkxtNu!;#{!{v`Fu;LGnG(J1VZu7x&&&k zt5t*0D)I_PTqV}iKCA|}maDNy#j9|E9RPm)Ywf1qJpR(B8ZCO1>TA@y%h0Q#QXfkf5nf_`_Am_?2%SKAAiWqdwk#L`+U!NH*f#@ z>YD%#WtwoYrdNDFKcS=v#3hH`|v|%4nP~Fiv6+loy(h8aQt)oV}>HGl>cYtg8n!P}kGvqV{w$xAC(7cT*b2Cz;+y|X%Dn~CKh z)+?xq#ZJV7*r=d3mdPab05&PuAQ`4KdnwXqEFRMnMoLQtv4#A44(tt~8QX*{+ZAl= zh!?Dxuq`9K810-2p$R*LM2mvM6(q{fI4dO@i6kt(Sw$P(MS@wunr6GENh4_1&VpG> znxzMEi-M}2y+L#ezWd3y9o>S>ZUwy^MTaY^t-R`xxm863dj%Px%ST2`qasTCRJEn`Gt*{p8ahMCSP z2x)RMY2~Q2rejt(Ie=c3`6YBE%(Sf;>1+^plH1VO*bsvnV`2@N=38CID?WI!& z6etIR7!tlYMBfB3tl}=bhv~DZ+mbNVg*!SOFXU_^8HpQNJ6ewsj0P~K;sowiuvW(1 z+a{^3@i4hDgNKEA6DlTgQi19aA9ygQB?(;HF+A*WpH}f+!M)znQ|4)1Y8D4)Roo+- zQtuobA5~d}DY4Zx&Duws2cc!7YBqu9c1=U*nLa){KW#oB;{0Ntme&({R#Mu*E%Js-tr_fO87k z3ROFtOzMkTGG;C2QhM4RIFrybBI&|xUguRjiucnA`WZcuvl(6%P*K;3cE&3w??Nt} z5HOJec~qNAit@M(kEytT$GyCmT(ZoEm=_a%d;61&ee{0-c4H}BBN3&#Fch2*v-$E- zEgm&EAH(0P!{8(T(oW5gstu+ViztC;R9=HixWeM;tT?$?-xND(Ea(88z+U zv={hs-OgF*tSn6hn}qC8zIV8Lv1Br@fDURZZ6=~@iu`#tllN&$iEMAaW6R!ogobS| zQaGGFrZ4E}tZ`cJH#01QnuL5w^F3O9XD804=1h_*KAm45HAgf%!Rl-i;Qekpb&U~S zcX~-rpP=_yB9(=tH-uDd&JnuFrJ1jM*+x7cGB-;0rww~4AESl|TT3jDXc;%rnuRRW zU^JIZN|}7!NDwDEv znEhPEMLGMWnEgt{8*=s=F?(CZ_c*J=J0biIzYpLKD*lK+6?JeUecD{sBXJoDs@4{? zgl$?Zh5D3#_Kv^Dx`!O5$F=l)QqQ)>O>;SyiTXY??w!uJQgm>Vb?X=Z?>8Nl*FWoT z>n?J?#sc6CNwmh{bXvFMw?bz|{Z+xvLPix4DK;;Fzbn{reNpx<#}~MnL8#)Vu$oWa zx;d&6*HQWAO_1-+{LJuT&*#&eMfg_uuI;)6<@-|XV|~fL-R~{^CfJ(gqtp+mFw*@U2mXOuJ7Wy9CS#5 zu8Y`HP%gfFz(!iqOy-+#jGPNZ23#Vaq>P~XoZy5{d88g7IpKj8Dhu&%&kA~aw_V18 z!aT}Zsk$AsvxPqdTCsyp6zZBC7+0N&^>`kiroz197ldBC=qY}USEy>POi2$M2_KaP z_BkGi3-i0>%CWrdgO2SZ1!6tZV!WG-r)$M%1;?)-uHZ7p3w*ayBe0VW+Qoo&pckDO zKsQcex8&XGuyA>AL=2yy8x_j#!b^Nsm00ju&jQnRU3V!X&gsUfayQNtXf-pdKzJ2B zjMqMF=kJOx?Dx5{+oiO56<2=FBQoL=Syvz;mHX7Kl~&$Pb%z+WJA7)^c@e9v7_rZL zYA(2Hb{Et*5uo4WHQjmF3wP|ff_tkN2bnk35H;c$e1r_{#0DH?-ozM%J|C0aE|U#( z$jkTwWAH_u6ZPb*yi%4;U-Gy;wpuI%H&T0mHwQtxBt`tx2B~U@s*YnTh8c^yeAM!> z*ueVwatSrDQcmqF9<_fl(M5$W{$CPZ7!eMyU~aUAPIi)gPi2p{vEKlr%=|GX+TG0j zac2G`XD2a(DJ)=Gn%L#g^D1E6tAK-U1&lEw&(rK`FCxy$D_kkA@2`4hTW&;J1!F-_ zl&NH|lf8(^qNr3~hB%Gv9){swX3Kr-FZcUG(CYGQMo4lgWQVg-9)hoVTttD04!y0E zi^Hq9!8xTeM;Zxc!n~y5H1%yRg<_Lm_0$}8)if5=5ZYIT;(tZJ5}wuu zuM4~Obu0_y6_}TiEwmB`HO22w+K0NfBI?&Y)HxS*XMt?KnoTNN!ReO~sCgAN)kQ&a zykpTjHvjXI^C{fvD*OgbtVLDp(bC@kO;2~!)!h(&xa%q&0rTJk)1?NTqmD;?_ze{e z`qnC-AFK>|fuK*^5cJ!tfId+f^hts~eM8XitOEL>%An5>^tl^?es>kn4_5|#fuJv4 zAGE1T+({WAao;)M2C7~pN%$iyTwj=+ThEI=xOz84B9t|(xYr5B>#hrSFduaRro4rx zD#1PLgUd(U568Q-3%B2e6U`;0tRPxO?I|Btr~)z})S0yu@A11MrBA5W+zpWqoQ zfD(r41$9Xc>uPE~JTkMu;}%0{#xmlI43)79#w^#vxNm(mOk zW7(9YCHbV5(dbAtl`<^V(u@?H*cUU*`S3!PM9_Cyv$z>n(^|NX&lx5kP~(Y60NV)J zBRhyXQW}kp#)7CPnrJ$$^8j`*Y?F>BRVxwh)8^0cxRzA)Aa;>^UEQ4_G@w!VwVPpk zOUw<@xMgao`AF+z2({QNcI;y~<+Ee$nVe-6BO#T8p-Dk0${2Q9lo%P9e)8EevvJEX zhYf2utLqHA)`@_~Ci&jt`Yv)DL>og#+US_^yi$UeWv!k&P(@SMivW zjhqOEac?&IbxKv_K7j40%Mm6KB^^Z~?{GdvIl3C^wj08iX@=u1o*{HU&e-T8BdgJi zaD!ooajV{trkm9_=60v2s7sPj4sn{0O)#S{q+SJJqaxzA#r3qf^ zrq+o}5J@o&I%#|UWS6_7md;y`24T|t=%DV+n~u55?jV*3r=wfYjvG|i{H!Ks{uXDf z9~SIX+kJ*%(I`{X7r2!$W-}o?iKhd2s-X0=)RM8t!!hX#ZB*w}G5VGjJcDPc)sn7> z;csuj*e7$~LuzVP=b5IMVJv3T5l>IsgWF>%I5@GK=HBg_|1n3o@mX(;+Q|JXwIbbN zk*YF0QH@}T>heVe2?WXT0vT-EYYI|i3*c4P_O=2;+TL<)A1Fvm+xxEVQw14m`^2?< zrNEN5FUdx&crKOVrffP+Me{ww0k>!hf>0RR0Dfd>+;}VfWys|Ld&#+C8X{Cr;+~^b zEv@Na1AUj%TbQ1Vo~3Oo2>n8fA}I7-CXE%Sq_vXUfGwz^H-OaBuC25ZyTpVNK!Xwz zXlT2K+NqlDH4ofF!&J@A6+F0tJ-=hW3m&55Ab@&0>=HCM1P2NT4%!6Mq*|QYe-F)5 zZNG^p;kOdLT(nD~j#&F0bh=bqr1SM?q*zKiGMd*vB$&T=I|>Hf7F+M(dG|t%+DXKr{}6HBSHv_`+}aX&l<0oNg%xzf=&=rQ7ggI4DuJUO z;{CSw^{8;XzvxSRl4wPjC^chuF#+7cmCJ6DbM<_JPOS$=DY}n2O(qw5Zsxd^kQ3xoD&*LS$ELX4Kb-W>0Z{i)iD_8H~LwqDxALBE8E>~aRYkY%m P2}Att_zpkdCp7#EbX!Al literal 4255 zcmbVO3s)0I7`=l)LRb`|q9}p_Di1NB(yBpgKtyZ=1Vph~x`ZKF*zCs52DR_^`~7bJ zMSI%f(R2C(ditAs`pst9Ou{DAat@pMc4qFq^UeKc_OE{){|Vqceh#4u+XARoP=i{A z#uarIJr_a% zd&Gz)hVwonHqXhwDPbg}I_yJp0Q(gjKnp|NOiH!*EYH&7YO-Px3=MHY08FJH4M z%e+tdnvwH0qEj-XPXX*^%BegD(V+{2Auc3eV>r_l_lz5T8qHxU@pu^9#G_g~YCy$0 zLq_f!BuC#MXZH;TaZ@V- z`g%#6C@XH?$S^cd=g13bKBr|hni4Y%(^g#{x0lJzFkdz;KB^{CQHBFHm8rAr5i-AR z2SA;aHr3|#FdFoaK=vbj9+^}HW-R_$rUlOc%UG(lYx6GKXoA>z= z!!FS+YSO8-4O=vAyxo8=>JR{>9s8XbWMNxdXjt$H4aNBpBU&VklHCy9 zp&64-8-7_6j^6H7)CsXTI*o=+(J5)b6Oot0B5VUQ@6}Q4QV@(VGf# zPV}~j-cevX(R(6#UqRN1J`~YM3M?o3L`0t|Fes|U=OKK7F9Y~W!Poev=mgV@HFK3m z;?7o)5OrACLm zGf!E0u6)Y5$RjEh3|(}hF2@anTh7tR$#UN_94T0?NJ#PW0{D@kwenE+c&hiMb4 z=qjLNPkawhYZtZY>xZ7VD2mW8qu-jIb+BKkQH6PWif*7Dx9O?m2jB(Vp$NE3y;aWS zF$A5y=AI`A%!lj3^-s_+AKtN!o$J{BJN6a;#g`YblhlMI&}J9(Md}lj3j!Z6c?1je zRV3zhJi)>Fp5L(7!EYmcr*YUJ>WKF~!qEcNb~^k)M6GB>JN7$tO?eoXu9Xs?Vvz_p zh!cVrOCIsEOH2!^L@Xrr{(<9<&|k{mO8jl;#vzHn$KzLg`MJlx>heb_^Lyza5vP4b zbdrc8^jUON5)tu4?DQ3}{J%tqtsoI+d_?q+h+YzL{3%2vJrN5|tJDHfqWcx+*U=HD zpEAS)WP+1qfm0H3hr9ZHWNtqTq&(ttWEoKeN;viyBfuk!-7IW!-p^+VWynF#7m4Dc z7u!nnFrzMJLpav+7|;8IUZjc}kw6=KL08IvPWXe45$M$|L08LwPWppR5a{(SLG?1A zQ~sbg2=vyLpy@K8*Zo275a>c>(8elpRnZ2CV{x47Pi%ghfBjA84<5-6`2+K=MA7DM zn@KR$1ePHJx*1)97_|feooFQo3?ox-0-lqA_xb?S1zb`2ED3fFi@7q&CnW+u<#gY> z1S4*0nm3eF0ak>*B0+B)_;OO{!@^5SKI>jmSViEN1iU#ZA@Tj<0-q+$;;%($&VzcF zh@257U4G}_iKGkVKFzMe13aYP3xxX_y?Yt2(0{)5y4QNkYrX5WKJZ!}Q|nv82+{6; Khad10n*RmEn4S0l diff --git a/target/test-classes/StockTest.class b/target/test-classes/StockTest.class index 0008700068cbbe4a7129761956c9351e88fe7dbe..e22183d1d0208cb1c72e53109d70a846ae53f6ee 100644 GIT binary patch literal 6116 zcmb_f`Fm8=8GdgPGLzvFV3HuQN+SeGf?*N}3QVvjAwqOWkc3cB+ulrWCKo1i$60`2 zUD~?TYHh2vt(LZ0YxmVE2()(Jtljs0-~Nd{eb2df?!7Z}Nbq?&KTOVi=bZO@zx8~1 z>A&Y*1h5nT386+q!+60?9-T1rg%IjAtT!vg4fXw2#wuK^p|+!QQbXMkcO$69QXL_LH7rk98FREaH)ZA~jH$G#A)2t0Mtai7 zS>m@VtSih~c@0Yw9uZ8xMmmhkc)Yi#rze6|tPzI2T0=`m!uQg6A!lV$@y^Kzns9|Uu~x%Q#fj=W zj-)~*ks6?(UB?pC)9D39X17@wpP!qu(;6vinL9@dOe zag2HE`lgba@WyHtvf!d&jmvwF#hRo2Mp}9yKf)ZhjI>ai(6JYz8k*>h%44jsl23{S zD;-M^CcYH=us?*G7zUZO-j0rOOwgRXb&|EoA|4SbF)4z5kfqTvGSWFIE+5iy7&r3- zHHzdP2_};pWep)#F*(oE9ZnK>dRWe@$_sx!TPl9nP2SmlbxmALjCB|A&AiWB0elwI>jbpnPL+Z zV8;ozU06fE*L6#~+b=|6ER&ue%b3y;4Qyv`)X|SSL~vBCujKK{92?2+GH1+8p6Z9} z?0gt^Q}cFyM5?@5$6Ig@Ik6%t`edmc9Ck}JT}XCKdpa6enR-&aS{XJTl{68 zzuX$er+KHl*B4#wFRuyX^GsmY61j}q#az<7-V(!0qc@*y5|r5I25q~LFXW8uUb8T3 zPv;|e9%n*0U6#jI=9qodj3uOjJZQ`qNzo^DynwSDPUd`(hL?lc=iYR{_ZXRJK8o!L z+df*%2H#nC013RjWfv#dY=g%ye$!F8@ZDg-yU0TZ3!B4Oe0e>dTY+$dS^i}mPa;gg zmnd*$U)S*wDf^l)`;Lx}N!ho3*~>aUE@j{MWk1pJ2`T%rFZ-pAPfFP@eA(}Ge1@_R ze(THrtmCs%_9x0%|09`X4}NRJDq&7KDw?5h>n zYxx?l3t%^Ugm);hrx-ioy(^`cufC%hjUPorF0qs)R0Xh8?ob5Iw4U*#aV z8e15V0XOi2O5KyFlXkTWbEK*v>OGBNZ>_S^(cI0n*+ffY0bAQWTSqQZpRlu}PpmJj zZwvMHQQuajz8jSKPS8ivw+`%$iMowvF{ZI!9*`D_T;5oF7O$Wb^ zj_w#>=?|g@LjjlcdLc+E2^?_=^mz)WD+tUuzomo3oIgDTnZU}Jb z^Ee#+KX4Fy91dv(hf#7EBZqzDaFddQt>jRh`febk-C0j(u1u$s`Uj|UlBswwpwCbJ z;zjB^7Vz_l1@+xbeYa5GtxA0-mHPVK=GyoyZg;#_Zi&;f4I)kjP#e9--Kj*qo!KJ_ zs?&kIF|kp)>n_qii#OHaV4b@WOxs{h%@8)lcVd=Z%aV$np4I6p4)cAhtSKRJYU*Ak zsU5DVdi1_?cq>@S4;}Q&#WB>zQse@L>YkzRTpaUVYRqHAoL6E#vN-0WYRo%`dAAbt z-HT&BrpCO7nD;9&-?KR8d)1f^67yjt=KB`Md|ZwB2r(a1Vm_h7^yi2By3XPKYS72o zm`?>j{rTa87XkVqHRzKBeYzTSMMTV|%*Ll!BI0HJupFfN_`-`1r-!CPnma~`4#P3Z z&r@eW%Su$17_m6hR$*;*v0CJ`@XW$s#{*y-hO5BJh(%j8uk|U2-5J2*5M6~;POK&R z%tB!I2EaIdSAlhNvK60YrO8uMFnTBe!g0L{q>_;{zJ5yW_eVXj8ZqZrfo-8gBcle< z-!x7vm4SOg0yvX(gVS7A)QEo%s-WWT3%pW5NiThloD`5x1t3?t{i+s@;%D(ive{4g zbDVL`<3)T$uD*(I;G1&wEqoW>ldG5T1N=~~euSUmXL9v({0hI8tKZ=F_=8;i5r4s7 R@i*d%f3*INf8yV0{U0rAvtj-}Rh%{XbV< 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 s Date: Mon, 25 May 2026 14:16:28 +0200 Subject: [PATCH 21/22] Added Checkstyle and Javadocs to test-classes --- src/test/java/ExchangeTest.java | 604 +++++++++++++--------- src/test/java/PlayerTest.java | 289 ++++++----- src/test/java/PortfolioTest.java | 344 ++++++------ src/test/java/PurchaseTest.java | 215 ++++---- src/test/java/SaleTest.java | 217 ++++---- src/test/java/ShareTest.java | 205 +++++--- src/test/java/StockTest.java | 374 ++++++++------ src/test/java/TransactionArchiveTest.java | 316 ++++++----- target/test-classes/ExchangeTest.class | Bin 10397 -> 10733 bytes target/test-classes/PortfolioTest.class | Bin 5661 -> 5661 bytes target/test-classes/ShareTest.class | Bin 4137 -> 4255 bytes target/test-classes/StockTest.class | Bin 6116 -> 6225 bytes 12 files changed, 1501 insertions(+), 1063 deletions(-) diff --git a/src/test/java/ExchangeTest.java b/src/test/java/ExchangeTest.java index 9261bd6..4aa8f3a 100644 --- a/src/test/java/ExchangeTest.java +++ b/src/test/java/ExchangeTest.java @@ -1,265 +1,365 @@ -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; import Model.Exchange; import Model.Player; +import Model.Purchase; +import Model.Sale; import Model.Share; import Model.Stock; import Model.Transaction; -import Model.Purchase; -import Model.Sale; - -import static org.junit.jupiter.api.Assertions.*; - import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +/** + * Unit tests for the Exchange class. + * Tests core exchange functionality including stock management, buying/selling, + * market data retrieval, and error handling. + */ public class ExchangeTest { - private Exchange exchange; - private Stock apple; - private Stock google; - private Player player; - - @BeforeEach - void setUp() { - apple = new Stock("AAPL", "Apple", new BigDecimal("100")); - google = new Stock("GOOGL", "Google", new BigDecimal("200")); - - List stocks = new ArrayList<>(); - stocks.add(apple); - stocks.add(google); - - exchange = new Exchange("TestExchange", stocks); - player = new Player("Jane", new BigDecimal("500000")); - } - - // ---- Positive tests ---- - - @Test - void testGetName() { - assertEquals("TestExchange", exchange.getName()); - } - - @Test - void testInitialWeekIsOne() { - assertEquals(1, exchange.getWeek()); - } - - @Test - void testAdvanceIncrementsWeek() { - exchange.advance(); - assertEquals(2, exchange.getWeek()); - } - - @Test - void testHasStockReturnsTrue() { - assertTrue(exchange.hasStock("AAPL")); - } - - @Test - void testHasStockReturnsFalse() { - assertFalse(exchange.hasStock("MSFT")); - } - - @Test - void testGetStockReturnsCorrectStock() { - assertEquals(apple, exchange.getStock("AAPL")); - } - - @Test - void testGetStockReturnsNullForUnknown() { - assertNull(exchange.getStock("MSFT")); - } - - @Test - void testFindStocksBySymbol() { - List result = exchange.findStocks("AAPL"); - assertEquals(1, result.size()); - assertEquals("AAPL", result.get(0).getSymbol()); - } - - @Test - void testFindStocksByCompanyName() { - List result = exchange.findStocks("e"); - assertEquals(2, result.size()); - } - - @Test - void testFindStocksNoMatch() { - List result = exchange.findStocks("Samsung"); - assertEquals(0, result.size()); - } - - @Test - void testFindStocksEmptyTermReturnsAll() { - List result = exchange.findStocks(""); - assertEquals(2, result.size()); - } - - @Test - void testBuyReturnsCommittedPurchase() { - Transaction t = exchange.buy("AAPL", new BigDecimal("5"), player); - assertNotNull(t); - assertTrue(t.isCommitted()); - assertInstanceOf(Purchase.class, t); - } - - @Test - void testBuyDeductsMoneyFromPlayer() { - BigDecimal before = player.getMoney(); - exchange.buy("AAPL", new BigDecimal("5"), player); - assertTrue(player.getMoney().compareTo(before) < 0); - } - - @Test - void testBuyAddsShareToPortfolio() { - exchange.buy("AAPL", new BigDecimal("5"), player); - assertFalse(player.getPortfolio().getShares("AAPL").isEmpty()); - } - - @Test - void testSellReturnsCommittedSale() { - exchange.buy("AAPL", new BigDecimal("5"), player); - Share share = player.getPortfolio().getShares("AAPL").get(0); - - Transaction t = exchange.sell(share, player); - assertNotNull(t); - assertTrue(t.isCommitted()); - assertInstanceOf(Sale.class, t); - } - - @Test - void testSellAddsMoney() { - exchange.buy("AAPL", new BigDecimal("5"), player); - Share share = player.getPortfolio().getShares("AAPL").get(0); - BigDecimal before = player.getMoney(); - - exchange.sell(share, player); - assertTrue(player.getMoney().compareTo(before) > 0); - } - - @Test - void testSellRemovesShareFromPortfolio() { - exchange.buy("AAPL", new BigDecimal("5"), player); - Share share = player.getPortfolio().getShares("AAPL").get(0); - - exchange.sell(share, player); - assertTrue(player.getPortfolio().getShares("AAPL").isEmpty()); - } - - @Test - void testGetGainersReturnsRequestedLimit() { - exchange.advance(); // prices must have changed at least once for a meaningful sort - List gainers = exchange.getGainers(1); - assertEquals(1, gainers.size()); - } - - @Test - void testGetLosersReturnsRequestedLimit() { - exchange.advance(); - List losers = exchange.getLosers(1); - assertEquals(1, losers.size()); - } - - // ---- Negative tests ---- - - @Test - void testConstructorNullNameThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Exchange(null, List.of(apple)) - ); - } - - @Test - void testConstructorBlankNameThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Exchange(" ", List.of(apple)) - ); - } - - @Test - void testConstructorNullStockListThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Exchange("X", null) - ); - } - - @Test - void testConstructorNullStockEntryThrows() { - List withNull = new ArrayList<>(); - withNull.add(apple); - withNull.add(null); - assertThrows(IllegalArgumentException.class, () -> - new Exchange("X", withNull) - ); - } - - @Test - void testBuyNullSymbolThrows() { - assertThrows(IllegalArgumentException.class, () -> - exchange.buy(null, new BigDecimal("5"), player) - ); - } - - @Test - void testBuyUnknownSymbolThrows() { - assertThrows(IllegalArgumentException.class, () -> - exchange.buy("MSFT", new BigDecimal("5"), player) - ); - } - - @Test - void testBuyZeroQuantityThrows() { - assertThrows(IllegalArgumentException.class, () -> - exchange.buy("AAPL", BigDecimal.ZERO, player) - ); - } - - @Test - void testBuyNegativeQuantityThrows() { - assertThrows(IllegalArgumentException.class, () -> - exchange.buy("AAPL", new BigDecimal("-1"), player) - ); - } - - @Test - void testBuyNullPlayerThrows() { - assertThrows(IllegalArgumentException.class, () -> - exchange.buy("AAPL", new BigDecimal("5"), null) - ); - } - - @Test - void testBuyInsufficientFundsThrows() { - Player poorPlayer = new Player("Broke", new BigDecimal("1")); - assertThrows(IllegalStateException.class, () -> - exchange.buy("AAPL", new BigDecimal("5"), poorPlayer) - ); - } - - @Test - void testSellNullPlayerThrows() { - exchange.buy("AAPL", new BigDecimal("5"), player); - Share share = player.getPortfolio().getShares("AAPL").get(0); - assertThrows(IllegalArgumentException.class, () -> - exchange.sell(share, null) - ); - } - - @Test - void testSellShareNotInPortfolioThrows() { - Share unowned = new Share(apple, new BigDecimal("1"), new BigDecimal("100")); - assertThrows(IllegalStateException.class, () -> - exchange.sell(unowned, player) - ); - } - - @Test - void testAddNullObserverThrows() { - assertThrows(IllegalArgumentException.class, () -> - exchange.addObserver(null) - ); - } + private Exchange exchange; + private Stock apple; + private Stock google; + private Player player; + + @BeforeEach + void setUp() { + apple = new Stock("AAPL", "Apple", new BigDecimal("100")); + google = new Stock("GOOGL", "Google", new BigDecimal("200")); + + List stocks = new ArrayList<>(); + stocks.add(apple); + stocks.add(google); + + exchange = new Exchange("TestExchange", stocks); + player = new Player("Jane", new BigDecimal("500000")); + } + + // ---- Positive tests ---- + + /** + * Tests that exchange returns its correct name. + */ + @Test + void testGetName() { + assertEquals("TestExchange", exchange.getName()); + } + + /** + * Tests that exchange starts at week 1. + */ + @Test + void testInitialWeekIsOne() { + assertEquals(1, exchange.getWeek()); + } + + /** + * Tests that calling advance increments the week counter. + */ + @Test + void testAdvanceIncrementsWeek() { + exchange.advance(); + assertEquals(2, exchange.getWeek()); + } + + /** + * Tests that hasStock returns true for a known stock symbol. + */ + @Test + void testHasStockReturnsTrue() { + assertTrue(exchange.hasStock("AAPL")); + } + + /** + * Tests that hasStock returns false for an unknown stock symbol. + */ + @Test + void testHasStockReturnsFalse() { + assertFalse(exchange.hasStock("MSFT")); + } + + /** + * Tests that getStock returns the correct Stock object for a given symbol. + */ + @Test + void testGetStockReturnsCorrectStock() { + assertEquals(apple, exchange.getStock("AAPL")); + } + + /** + * Tests that getStock returns null for an unknown symbol. + */ + @Test + void testGetStockReturnsNullForUnknown() { + assertNull(exchange.getStock("MSFT")); + } + + /** + * Tests that findStocks correctly locates stocks by symbol. + */ + @Test + void testFindStocksBySymbol() { + List result = exchange.findStocks("AAPL"); + assertEquals(1, result.size()); + assertEquals("AAPL", result.get(0).getSymbol()); + } + + /** + * Tests that findStocks correctly locates stocks by partial company name match. + */ + @Test + void testFindStocksByCompanyName() { + List result = exchange.findStocks("e"); + assertEquals(2, result.size()); + } + + /** + * Tests that findStocks returns empty list when no stocks match search term. + */ + @Test + void testFindStocksNoMatch() { + List result = exchange.findStocks("Samsung"); + assertEquals(0, result.size()); + } + + /** + * Tests that findStocks with empty search term returns all stocks. + */ + @Test + void testFindStocksEmptyTermReturnsAll() { + List result = exchange.findStocks(""); + assertEquals(2, result.size()); + } + + /** + * Tests that buying a stock returns a committed Purchase transaction. + */ + @Test + void testBuyReturnsCommittedPurchase() { + Transaction t = exchange.buy("AAPL", new BigDecimal("5"), player); + assertNotNull(t); + assertTrue(t.isCommitted()); + assertInstanceOf(Purchase.class, t); + } + + /** + * Tests that buying stock deducts the correct amount from player's balance. + */ + @Test + void testBuyDeductsMoneyFromPlayer() { + BigDecimal before = player.getMoney(); + exchange.buy("AAPL", new BigDecimal("5"), player); + assertTrue(player.getMoney().compareTo(before) < 0); + } + + /** + * Tests that buying stock adds the share to player's portfolio. + */ + @Test + void testBuyAddsShareToPortfolio() { + exchange.buy("AAPL", new BigDecimal("5"), player); + assertFalse(player.getPortfolio().getShares("AAPL").isEmpty()); + } + + /** + * Tests that selling a share returns a committed Sale transaction. + */ + @Test + void testSellReturnsCommittedSale() { + exchange.buy("AAPL", new BigDecimal("5"), player); + Share share = player.getPortfolio().getShares("AAPL").get(0); + + Transaction t = exchange.sell(share, player); + assertNotNull(t); + assertTrue(t.isCommitted()); + assertInstanceOf(Sale.class, t); + } + + /** + * Tests that selling stock adds the correct amount to player's balance. + */ + @Test + void testSellAddsMoney() { + exchange.buy("AAPL", new BigDecimal("5"), player); + Share share = player.getPortfolio().getShares("AAPL").get(0); + BigDecimal before = player.getMoney(); + + exchange.sell(share, player); + assertTrue(player.getMoney().compareTo(before) > 0); + } + + /** + * Tests that selling stock removes the share from player's portfolio. + */ + @Test + void testSellRemovesShareFromPortfolio() { + exchange.buy("AAPL", new BigDecimal("5"), player); + Share share = player.getPortfolio().getShares("AAPL").get(0); + + exchange.sell(share, player); + assertTrue(player.getPortfolio().getShares("AAPL").isEmpty()); + } + + /** + * Tests that getGainers returns the requested number of gaining stocks. + */ + @Test + void testGetGainersReturnsRequestedLimit() { + exchange.advance(); // prices must have changed at least once for a meaningful sort + List gainers = exchange.getGainers(1); + assertEquals(1, gainers.size()); + } + + /** + * Tests that getLosers returns the requested number of losing stocks. + */ + @Test + void testGetLosersReturnsRequestedLimit() { + exchange.advance(); + List losers = exchange.getLosers(1); + assertEquals(1, losers.size()); + } + + // ---- Negative tests ---- + + /** + * Tests that Exchange constructor throws when given a null name. + */ + @Test + void testConstructorNullNameThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Exchange(null, List.of(apple)) + ); + } + + /** + * Tests that Exchange constructor throws when given a blank name. + */ + @Test + void testConstructorBlankNameThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Exchange(" ", List.of(apple)) + ); + } + + /** + * Tests that Exchange constructor throws when given a null stock list. + */ + @Test + void testConstructorNullStockListThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Exchange("X", null) + ); + } + + /** + * Tests that Exchange constructor throws when stock list contains null entry. + */ + @Test + void testConstructorNullStockEntryThrows() { + List withNull = new ArrayList<>(); + withNull.add(apple); + withNull.add(null); + assertThrows(IllegalArgumentException.class, () -> + new Exchange("X", withNull) + ); + } + + /** + * Tests that buy throws when given a null symbol. + */ + @Test + void testBuyNullSymbolThrows() { + assertThrows(IllegalArgumentException.class, () -> + exchange.buy(null, new BigDecimal("5"), player) + ); + } + + /** + * Tests that buy throws when given an unknown stock symbol. + */ + @Test + void testBuyUnknownSymbolThrows() { + assertThrows(IllegalArgumentException.class, () -> + exchange.buy("MSFT", new BigDecimal("5"), player) + ); + } + + /** + * Tests that buy throws when given zero quantity. + */ + @Test + void testBuyZeroQuantityThrows() { + assertThrows(IllegalArgumentException.class, () -> + exchange.buy("AAPL", BigDecimal.ZERO, player) + ); + } + + /** + * Tests that buy throws when given negative quantity. + */ + @Test + void testBuyNegativeQuantityThrows() { + assertThrows(IllegalArgumentException.class, () -> + exchange.buy("AAPL", new BigDecimal("-1"), player) + ); + } + + /** + * Tests that buy throws when given a null player. + */ + @Test + void testBuyNullPlayerThrows() { + assertThrows(IllegalArgumentException.class, () -> + exchange.buy("AAPL", new BigDecimal("5"), null) + ); + } + + /** + * Tests that buy throws when player has insufficient funds. + */ + @Test + void testBuyInsufficientFundsThrows() { + Player poorPlayer = new Player("Broke", new BigDecimal("1")); + assertThrows(IllegalStateException.class, () -> + exchange.buy("AAPL", new BigDecimal("5"), poorPlayer) + ); + } + + /** + * Tests that sell throws when given a null player. + */ + @Test + void testSellNullPlayerThrows() { + exchange.buy("AAPL", new BigDecimal("5"), player); + Share share = player.getPortfolio().getShares("AAPL").get(0); + assertThrows(IllegalArgumentException.class, () -> + exchange.sell(share, null) + ); + } + + /** + * Tests that sell throws when share is not in player's portfolio. + */ + @Test + void testSellShareNotInPortfolioThrows() { + Share unowned = new Share(apple, new BigDecimal("1"), new BigDecimal("100")); + assertThrows(IllegalStateException.class, () -> + exchange.sell(unowned, player) + ); + } + + /** + * Tests that addObserver throws when given a null observer. + */ + @Test + void testAddNullObserverThrows() { + assertThrows(IllegalArgumentException.class, () -> + exchange.addObserver(null) + ); + } } + diff --git a/src/test/java/PlayerTest.java b/src/test/java/PlayerTest.java index ac1f95a..880ac8f 100644 --- a/src/test/java/PlayerTest.java +++ b/src/test/java/PlayerTest.java @@ -2,127 +2,182 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - import Model.Player; import java.math.BigDecimal; +/** + * Unit tests for the Player class. + * Tests player creation, money management, portfolio, and error handling. + */ public class PlayerTest { - private Player player; - - @BeforeEach - void setUp() { - player = new Player("Jane", new BigDecimal("1000")); - } - - // ---- Positive tests ---- - - @Test - void testGetName() { - assertEquals("Jane", player.getName()); - } - - @Test - void testGetMoneyMatchesStartingCapital() { - assertEquals(new BigDecimal("1000"), player.getMoney()); - } - - @Test - void testAddMoney() { - player.addMoney(new BigDecimal("500")); - assertEquals(new BigDecimal("1500"), player.getMoney()); - } - - @Test - void testAddMoneyZero() { - player.addMoney(BigDecimal.ZERO); - assertEquals(new BigDecimal("1000"), player.getMoney()); - } - - @Test - void testWithdrawMoney() { - player.withdrawMoney(new BigDecimal("400")); - assertEquals(new BigDecimal("600"), player.getMoney()); - } - - @Test - void testWithdrawMoneyZero() { - player.withdrawMoney(BigDecimal.ZERO); - assertEquals(new BigDecimal("1000"), player.getMoney()); - } - - @Test - void testPortfolioInitiallyEmpty() { - assertTrue(player.getPortfolio().getShares().isEmpty()); - } - - @Test - void testTransactionArchiveInitiallyEmpty() { - assertTrue(player.getTransactionArchive().isEmpty()); - } - - @Test - void testStartingMoneyZeroAllowed() { - Player broke = new Player("Broke", BigDecimal.ZERO); - assertEquals(BigDecimal.ZERO, broke.getMoney()); - } - - // ---- Negative tests ---- - - @Test - void testNullNameThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Player(null, new BigDecimal("1000")) - ); - } - - @Test - void testBlankNameThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Player(" ", new BigDecimal("1000")) - ); - } - - @Test - void testNullStartingMoneyThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Player("Jane", null) - ); - } - - @Test - void testNegativeStartingMoneyThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Player("Jane", new BigDecimal("-1")) - ); - } - - @Test - void testAddNullAmountThrows() { - assertThrows(IllegalArgumentException.class, () -> - player.addMoney(null) - ); - } - - @Test - void testAddNegativeAmountThrows() { - assertThrows(IllegalArgumentException.class, () -> - player.addMoney(new BigDecimal("-100")) - ); - } - - @Test - void testWithdrawNullAmountThrows() { - assertThrows(IllegalArgumentException.class, () -> - player.withdrawMoney(null) - ); - } - - @Test - void testWithdrawNegativeAmountThrows() { - assertThrows(IllegalArgumentException.class, () -> - player.withdrawMoney(new BigDecimal("-100")) - ); - } + private Player player; + + @BeforeEach + void setUp() { + player = new Player("Jane", new BigDecimal("1000")); + } + + // ---- Positive tests ---- + + /** + * Tests that player name is stored and retrieved correctly. + */ + @Test + void testGetName() { + assertEquals("Jane", player.getName()); + } + + /** + * Tests that player's initial money equals starting capital. + */ + @Test + void testGetMoneyMatchesStartingCapital() { + assertEquals(new BigDecimal("1000"), player.getMoney()); + } + + /** + * Tests that adding money increases player's balance. + */ + @Test + void testAddMoney() { + player.addMoney(new BigDecimal("500")); + assertEquals(new BigDecimal("1500"), player.getMoney()); + } + + /** + * Tests that adding zero money does not change balance. + */ + @Test + void testAddMoneyZero() { + player.addMoney(BigDecimal.ZERO); + assertEquals(new BigDecimal("1000"), player.getMoney()); + } + + /** + * Tests that withdrawing money decreases player's balance. + */ + @Test + void testWithdrawMoney() { + player.withdrawMoney(new BigDecimal("400")); + assertEquals(new BigDecimal("600"), player.getMoney()); + } + + /** + * Tests that withdrawing zero money does not change balance. + */ + @Test + void testWithdrawMoneyZero() { + player.withdrawMoney(BigDecimal.ZERO); + assertEquals(new BigDecimal("1000"), player.getMoney()); + } + + /** + * Tests that player's portfolio is initially empty. + */ + @Test + void testPortfolioInitiallyEmpty() { + assertTrue(player.getPortfolio().getShares().isEmpty()); + } + + /** + * Tests that player's transaction archive is initially empty. + */ + @Test + void testTransactionArchiveInitiallyEmpty() { + assertTrue(player.getTransactionArchive().isEmpty()); + } + + /** + * Tests that player can be created with zero starting money. + */ + @Test + void testStartingMoneyZeroAllowed() { + Player broke = new Player("Broke", BigDecimal.ZERO); + assertEquals(BigDecimal.ZERO, broke.getMoney()); + } + + // ---- Negative tests ---- + + /** + * Tests that Player constructor throws when given a null name. + */ + @Test + void testNullNameThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Player(null, new BigDecimal("1000")) + ); + } + + /** + * Tests that Player constructor throws when given a blank name. + */ + @Test + void testBlankNameThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Player(" ", new BigDecimal("1000")) + ); + } + + /** + * Tests that Player constructor throws when given null starting money. + */ + @Test + void testNullStartingMoneyThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Player("Jane", null) + ); + } + + /** + * Tests that Player constructor throws when given negative starting money. + */ + @Test + void testNegativeStartingMoneyThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Player("Jane", new BigDecimal("-1")) + ); + } + + /** + * Tests that addMoney throws when given null amount. + */ + @Test + void testAddNullAmountThrows() { + assertThrows(IllegalArgumentException.class, () -> + player.addMoney(null) + ); + } + + /** + * Tests that addMoney throws when given negative amount. + */ + @Test + void testAddNegativeAmountThrows() { + assertThrows(IllegalArgumentException.class, () -> + player.addMoney(new BigDecimal("-100")) + ); + } + + /** + * Tests that withdrawMoney throws when given null amount. + */ + @Test + void testWithdrawNullAmountThrows() { + assertThrows(IllegalArgumentException.class, () -> + player.withdrawMoney(null) + ); + } + + /** + * Tests that withdrawMoney throws when given negative amount. + */ + @Test + void testWithdrawNegativeAmountThrows() { + assertThrows(IllegalArgumentException.class, () -> + player.withdrawMoney(new BigDecimal("-100")) + ); + } } + diff --git a/src/test/java/PortfolioTest.java b/src/test/java/PortfolioTest.java index 34e46aa..d55259d 100644 --- a/src/test/java/PortfolioTest.java +++ b/src/test/java/PortfolioTest.java @@ -1,155 +1,209 @@ import static org.junit.jupiter.api.Assertions.*; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import Model.Portfolio; import Model.Share; import Model.Stock; - import java.math.BigDecimal; import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +/** + * Unit tests for the Portfolio class. + * Tests share management, portfolio queries, net worth calculation, and error + * handling. + */ public class PortfolioTest { - private Portfolio portfolio; - private Stock stock; - private Share share; - - @BeforeEach - void setUp() { - portfolio = new Portfolio(); - stock = new Stock("AAPL", "Apple", new BigDecimal("150")); - share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); - } - - // ---- Positive tests ---- - - @Test - void testAddShare() { - boolean result = portfolio.addShare(share); - assertTrue(result); - assertTrue(portfolio.contains(share)); - } - - @Test - void testAddMultipleShares() { - Stock stock2 = new Stock("GOOGL", "Google", new BigDecimal("200")); - Share share2 = new Share(stock2, new BigDecimal("5"), new BigDecimal("190")); - - portfolio.addShare(share); - portfolio.addShare(share2); - - assertEquals(2, portfolio.getShares().size()); - } - - @Test - void testRemoveShare() { - portfolio.addShare(share); - boolean result = portfolio.removeShare(share); - assertTrue(result); - assertFalse(portfolio.contains(share)); - } - - @Test - void testRemoveShareNotInPortfolioReturnsFalse() { - boolean result = portfolio.removeShare(share); - assertFalse(result); - } - - @Test - void testGetSharesReturnsAll() { - portfolio.addShare(share); - List shares = portfolio.getShares(); - assertEquals(1, shares.size()); - assertTrue(shares.contains(share)); - } - - @Test - void testGetSharesIsDefensiveCopy() { - portfolio.addShare(share); - List copy = portfolio.getShares(); - copy.clear(); - assertEquals(1, portfolio.getShares().size()); - } - - @Test - void testGetSharesBySymbol() { - Stock stock2 = new Stock("GOOGL", "Google", new BigDecimal("200")); - Share share2 = new Share(stock2, new BigDecimal("5"), new BigDecimal("190")); - - portfolio.addShare(share); - portfolio.addShare(share2); - - List result = portfolio.getShares("AAPL"); - assertEquals(1, result.size()); - assertTrue(result.contains(share)); - } - - @Test - void testGetSharesBySymbolNoMatch() { - portfolio.addShare(share); - List result = portfolio.getShares("MSFT"); - assertTrue(result.isEmpty()); - } - - @Test - void testContainsReturnsTrueWhenPresent() { - portfolio.addShare(share); - assertTrue(portfolio.contains(share)); - } - - @Test - void testContainsReturnsFalseWhenAbsent() { - assertFalse(portfolio.contains(share)); - } - - @Test - void testGetNetWorthEmptyPortfolio() { - assertEquals(BigDecimal.ZERO, portfolio.getNetWorth()); - } - - @Test - void testGetNetWorthWithShares() { - portfolio.addShare(share); - // net worth should be positive when portfolio has holdings - assertTrue(portfolio.getNetWorth().compareTo(BigDecimal.ZERO) > 0); - } - - // ---- Negative tests ---- - - @Test - void testAddNullShareThrows() { - assertThrows(IllegalArgumentException.class, () -> - portfolio.addShare(null) - ); - } - - @Test - void testRemoveNullShareThrows() { - assertThrows(IllegalArgumentException.class, () -> - portfolio.removeShare(null) - ); - } - - @Test - void testContainsNullShareThrows() { - assertThrows(IllegalArgumentException.class, () -> - portfolio.contains(null) - ); - } - - @Test - void testGetSharesByNullSymbolThrows() { - assertThrows(IllegalArgumentException.class, () -> - portfolio.getShares((String) null) - ); - } - - @Test - void testGetSharesByBlankSymbolThrows() { - assertThrows(IllegalArgumentException.class, () -> - portfolio.getShares(" ") - ); - } + private Portfolio portfolio; + private Stock stock; + private Share share; + + @BeforeEach + void setUp() { + portfolio = new Portfolio(); + stock = new Stock("AAPL", "Apple", new BigDecimal("150")); + share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); + } + + // ---- Positive tests ---- + + /** + * Tests that adding a share to portfolio returns true and succeeds. + */ + @Test + void testAddShare() { + boolean result = portfolio.addShare(share); + assertTrue(result); + assertTrue(portfolio.contains(share)); + } + + /** + * Tests that portfolio can store multiple shares from different stocks. + */ + @Test + void testAddMultipleShares() { + Stock stock2 = new Stock("GOOGL", "Google", new BigDecimal("200")); + Share share2 = new Share(stock2, new BigDecimal("5"), new BigDecimal("190")); + + portfolio.addShare(share); + portfolio.addShare(share2); + + assertEquals(2, portfolio.getShares().size()); + } + + /** + * Tests that removing a share from portfolio returns true and succeeds. + */ + @Test + void testRemoveShare() { + portfolio.addShare(share); + boolean result = portfolio.removeShare(share); + assertTrue(result); + assertFalse(portfolio.contains(share)); + } + + /** + * Tests that removing a share not in portfolio returns false. + */ + @Test + void testRemoveShareNotInPortfolioReturnsFalse() { + boolean result = portfolio.removeShare(share); + assertFalse(result); + } + + /** + * Tests that getShares returns all shares in portfolio. + */ + @Test + void testGetSharesReturnsAll() { + portfolio.addShare(share); + List shares = portfolio.getShares(); + assertEquals(1, shares.size()); + assertTrue(shares.contains(share)); + } + + /** + * Tests that getShares returns a defensive copy that cannot modify internal + * state. + */ + @Test + void testGetSharesIsDefensiveCopy() { + portfolio.addShare(share); + List copy = portfolio.getShares(); + copy.clear(); + assertEquals(1, portfolio.getShares().size()); + } + + /** + * Tests that getShares by symbol returns only shares of that stock. + */ + @Test + void testGetSharesBySymbol() { + Stock stock2 = new Stock("GOOGL", "Google", new BigDecimal("200")); + Share share2 = new Share(stock2, new BigDecimal("5"), new BigDecimal("190")); + + portfolio.addShare(share); + portfolio.addShare(share2); + + List result = portfolio.getShares("AAPL"); + assertEquals(1, result.size()); + assertTrue(result.contains(share)); + } + + /** + * Tests that getShares by symbol returns empty list when no matches. + */ + @Test + void testGetSharesBySymbolNoMatch() { + portfolio.addShare(share); + List result = portfolio.getShares("MSFT"); + assertTrue(result.isEmpty()); + } + + /** + * Tests that contains returns true for shares in portfolio. + */ + @Test + void testContainsReturnsTrueWhenPresent() { + portfolio.addShare(share); + assertTrue(portfolio.contains(share)); + } + + /** + * Tests that contains returns false for shares not in portfolio. + */ + @Test + void testContainsReturnsFalseWhenAbsent() { + assertFalse(portfolio.contains(share)); + } + + /** + * Tests that empty portfolio has zero net worth. + */ + @Test + void testGetNetWorthEmptyPortfolio() { + assertEquals(BigDecimal.ZERO, portfolio.getNetWorth()); + } + + /** + * Tests that portfolio with shares has positive net worth. + */ + @Test + void testGetNetWorthWithShares() { + portfolio.addShare(share); + // net worth should be positive when portfolio has holdings + assertTrue(portfolio.getNetWorth().compareTo(BigDecimal.ZERO) > 0); + } + + // ---- Negative tests ---- + + /** + * Tests that addShare throws when given null share. + */ + @Test + void testAddNullShareThrows() { + assertThrows(IllegalArgumentException.class, () -> + portfolio.addShare(null) + ); + } + + /** + * Tests that removeShare throws when given null share. + */ + @Test + void testRemoveNullShareThrows() { + assertThrows(IllegalArgumentException.class, () -> + portfolio.removeShare(null) + ); + } + + /** + * Tests that contains throws when given null share. + */ + @Test + void testContainsNullShareThrows() { + assertThrows(IllegalArgumentException.class, () -> + portfolio.contains(null) + ); + } + + /** + * Tests that getShares throws when given null symbol. + */ + @Test + void testGetSharesByNullSymbolThrows() { + assertThrows(IllegalArgumentException.class, () -> + portfolio.getShares((String) null) + ); + } + + /** + * Tests that getShares throws when given blank symbol. + */ + @Test + void testGetSharesByBlankSymbolThrows() { + assertThrows(IllegalArgumentException.class, () -> + portfolio.getShares(" ") + ); + } } diff --git a/src/test/java/PurchaseTest.java b/src/test/java/PurchaseTest.java index d41e71a..26d2dd9 100644 --- a/src/test/java/PurchaseTest.java +++ b/src/test/java/PurchaseTest.java @@ -4,100 +4,137 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - import Model.Player; import Model.Purchase; import Model.Share; import Model.Stock; +/** + * Unit tests for the Purchase class. + * Tests purchase transaction handling, commitment, and error handling. + */ public class PurchaseTest { - private Stock stock; - private Share share; - private Purchase purchase; - private Player player; - - @BeforeEach - void setUp() { - stock = new Stock("AAPL", "Apple", new BigDecimal("100")); - share = new Share(stock, new BigDecimal("20"), new BigDecimal("50")); - purchase = new Purchase(share, 1); - player = new Player("Jane", new BigDecimal("500000")); - } - - // ---- Positive tests ---- - - @Test - void testCommitDeductsMoneyFromPlayer() { - BigDecimal before = player.getMoney(); - purchase.commit(player); - assertTrue(player.getMoney().compareTo(before) < 0); - } - - @Test - void testCommitAddsShareToPortfolio() { - purchase.commit(player); - assertTrue(player.getPortfolio().contains(share)); - } - - @Test - void testCommitRecordedInArchive() { - purchase.commit(player); - assertTrue(player.getTransactionArchive().getAllTransactions().contains(purchase)); - } - - @Test - void testCommitSetsCommittedFlag() { - purchase.commit(player); - assertTrue(purchase.isCommitted()); - } - - @Test - void testGetShareReturnsCorrectShare() { - assertEquals(share, purchase.getShare()); - } - - @Test - void testGetWeekReturnsCorrectWeek() { - assertEquals(1, purchase.getWeek()); - } - - // ---- Negative tests ---- - - @Test - void testCommitNullPlayerThrows() { - assertThrows(IllegalArgumentException.class, () -> - purchase.commit(null) - ); - } - - @Test - void testCommitInsufficientFundsThrows() { - Player poorPlayer = new Player("Broke", new BigDecimal("1")); - assertThrows(IllegalStateException.class, () -> - purchase.commit(poorPlayer) - ); - } - - @Test - void testCommitAlreadyCommittedThrows() { - purchase.commit(player); - assertThrows(IllegalStateException.class, () -> - purchase.commit(player) - ); - } - - @Test - void testConstructorWeekZeroThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Purchase(share, 0) - ); - } - - @Test - void testConstructorNegativeWeekThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Purchase(share, -5) - ); - } + private Stock stock; + private Share share; + private Purchase purchase; + private Player player; + + @BeforeEach + void setUp() { + stock = new Stock("AAPL", "Apple", new BigDecimal("100")); + share = new Share(stock, new BigDecimal("20"), new BigDecimal("50")); + purchase = new Purchase(share, 1); + player = new Player("Jane", new BigDecimal("500000")); + } + + // ---- Positive tests ---- + + /** + * Tests that committing a purchase deducts money from player's balance. + */ + @Test + void testCommitDeductsMoneyFromPlayer() { + BigDecimal before = player.getMoney(); + purchase.commit(player); + assertTrue(player.getMoney().compareTo(before) < 0); + } + + /** + * Tests that committing a purchase adds the share to player's portfolio. + */ + @Test + void testCommitAddsShareToPortfolio() { + purchase.commit(player); + assertTrue(player.getPortfolio().contains(share)); + } + + /** + * Tests that committed purchase is recorded in transaction archive. + */ + @Test + void testCommitRecordedInArchive() { + purchase.commit(player); + assertTrue(player.getTransactionArchive().getAllTransactions().contains(purchase)); + } + + /** + * Tests that committing a purchase sets the committed flag. + */ + @Test + void testCommitSetsCommittedFlag() { + purchase.commit(player); + assertTrue(purchase.isCommitted()); + } + + /** + * Tests that getShare returns the correct share object. + */ + @Test + void testGetShareReturnsCorrectShare() { + assertEquals(share, purchase.getShare()); + } + + /** + * Tests that getWeek returns the purchase week. + */ + @Test + void testGetWeekReturnsCorrectWeek() { + assertEquals(1, purchase.getWeek()); + } + + // ---- Negative tests ---- + + /** + * Tests that commit throws when given null player. + */ + @Test + void testCommitNullPlayerThrows() { + assertThrows(IllegalArgumentException.class, () -> + purchase.commit(null) + ); + } + + /** + * Tests that commit throws when player has insufficient funds. + */ + @Test + void testCommitInsufficientFundsThrows() { + Player poorPlayer = new Player("Broke", new BigDecimal("1")); + assertThrows(IllegalStateException.class, () -> + purchase.commit(poorPlayer) + ); + } + + /** + * Tests that commit throws when purchase was already committed. + */ + @Test + void testCommitAlreadyCommittedThrows() { + purchase.commit(player); + assertThrows(IllegalStateException.class, () -> + purchase.commit(player) + ); + } + + /** + * Tests that Purchase constructor throws when given zero week. + */ + @Test + void testConstructorWeekZeroThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Purchase(share, 0) + ); + } + + /** + * Tests that Purchase constructor throws when given negative week. + */ + @Test + void testConstructorNegativeWeekThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Purchase(share, -5) + ); + } } + diff --git a/src/test/java/SaleTest.java b/src/test/java/SaleTest.java index 622f262..39d5516 100644 --- a/src/test/java/SaleTest.java +++ b/src/test/java/SaleTest.java @@ -5,100 +5,137 @@ import Model.Sale; import Model.Share; import Model.Stock; - import static org.junit.jupiter.api.Assertions.*; import java.math.BigDecimal; +/** + * Unit tests for the Sale class. + * Tests sale transaction handling, commitment, and error handling. + */ public class SaleTest { - private Stock stock; - private Share share; - private Sale sale; - private Player player; - - @BeforeEach - void setUp() { - stock = new Stock("AAPL", "Apple", new BigDecimal("100")); - share = new Share(stock, new BigDecimal("20"), new BigDecimal("80")); - sale = new Sale(share, 1); - player = new Player("Jane", new BigDecimal("500")); - player.getPortfolio().addShare(share); - } - - // ---- Positive tests ---- - - @Test - void testCommitAddsMoney() { - BigDecimal before = player.getMoney(); - sale.commit(player); - assertTrue(player.getMoney().compareTo(before) > 0); - } - - @Test - void testCommitRemovesShareFromPortfolio() { - sale.commit(player); - assertFalse(player.getPortfolio().contains(share)); - } - - @Test - void testCommitRecordedInArchive() { - sale.commit(player); - assertTrue(player.getTransactionArchive().getAllTransactions().contains(sale)); - } - - @Test - void testCommitSetsCommittedFlag() { - sale.commit(player); - assertTrue(sale.isCommitted()); - } - - @Test - void testGetShareReturnsCorrectShare() { - assertEquals(share, sale.getShare()); - } - - @Test - void testGetWeekReturnsCorrectWeek() { - assertEquals(1, sale.getWeek()); - } - - // ---- Negative tests ---- - - @Test - void testCommitNullPlayerThrows() { - assertThrows(IllegalArgumentException.class, () -> - sale.commit(null) - ); - } - - @Test - void testCommitAlreadyCommittedThrows() { - sale.commit(player); - assertThrows(IllegalStateException.class, () -> - sale.commit(player) - ); - } - - @Test - void testCommitShareNotInPortfolioThrows() { - Player otherPlayer = new Player("Bob", new BigDecimal("500")); - assertThrows(IllegalStateException.class, () -> - sale.commit(otherPlayer) - ); - } - - @Test - void testConstructorWeekZeroThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Sale(share, 0) - ); - } - - @Test - void testConstructorNegativeWeekThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Sale(share, -3) - ); - } + private Stock stock; + private Share share; + private Sale sale; + private Player player; + + @BeforeEach + void setUp() { + stock = new Stock("AAPL", "Apple", new BigDecimal("100")); + share = new Share(stock, new BigDecimal("20"), new BigDecimal("80")); + sale = new Sale(share, 1); + player = new Player("Jane", new BigDecimal("500")); + player.getPortfolio().addShare(share); + } + + // ---- Positive tests ---- + + /** + * Tests that committing a sale adds money to player's balance. + */ + @Test + void testCommitAddsMoney() { + BigDecimal before = player.getMoney(); + sale.commit(player); + assertTrue(player.getMoney().compareTo(before) > 0); + } + + /** + * Tests that committing a sale removes the share from player's portfolio. + */ + @Test + void testCommitRemovesShareFromPortfolio() { + sale.commit(player); + assertFalse(player.getPortfolio().contains(share)); + } + + /** + * Tests that committed sale is recorded in transaction archive. + */ + @Test + void testCommitRecordedInArchive() { + sale.commit(player); + assertTrue(player.getTransactionArchive().getAllTransactions().contains(sale)); + } + + /** + * Tests that committing a sale sets the committed flag. + */ + @Test + void testCommitSetsCommittedFlag() { + sale.commit(player); + assertTrue(sale.isCommitted()); + } + + /** + * Tests that getShare returns the correct share object. + */ + @Test + void testGetShareReturnsCorrectShare() { + assertEquals(share, sale.getShare()); + } + + /** + * Tests that getWeek returns the sale week. + */ + @Test + void testGetWeekReturnsCorrectWeek() { + assertEquals(1, sale.getWeek()); + } + + // ---- Negative tests ---- + + /** + * Tests that commit throws when given null player. + */ + @Test + void testCommitNullPlayerThrows() { + assertThrows(IllegalArgumentException.class, () -> + sale.commit(null) + ); + } + + /** + * Tests that commit throws when sale was already committed. + */ + @Test + void testCommitAlreadyCommittedThrows() { + sale.commit(player); + assertThrows(IllegalStateException.class, () -> + sale.commit(player) + ); + } + + /** + * Tests that commit throws when share is not in player's portfolio. + */ + @Test + void testCommitShareNotInPortfolioThrows() { + Player otherPlayer = new Player("Bob", new BigDecimal("500")); + assertThrows(IllegalStateException.class, () -> + sale.commit(otherPlayer) + ); + } + + /** + * Tests that Sale constructor throws when given zero week. + */ + @Test + void testConstructorWeekZeroThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Sale(share, 0) + ); + } + + /** + * Tests that Sale constructor throws when given negative week. + */ + @Test + void testConstructorNegativeWeekThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Sale(share, -3) + ); + } } + diff --git a/src/test/java/ShareTest.java b/src/test/java/ShareTest.java index fbc018d..af8d2cc 100644 --- a/src/test/java/ShareTest.java +++ b/src/test/java/ShareTest.java @@ -5,92 +5,129 @@ import Model.Share; import Model.Stock; - import java.math.BigDecimal; +/** + * Unit tests for the Share class. + * Tests share creation, property access, and error handling. + */ public class ShareTest { - private Stock stock; - - @BeforeEach - void setUp() { - stock = new Stock("AAPL", "Apple", new BigDecimal("150")); - } - - // ---- Positive tests ---- - - @Test - void testShareConstructorNotNull() { - Share share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); - assertNotNull(share); - } - - @Test - void testGetStock() { - Share share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); - assertEquals(stock, share.getStock()); - } - - @Test - void testGetQuantity() { - Share share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); - assertEquals(new BigDecimal("10"), share.getQuantity()); - } - - @Test - void testGetPurchasePrice() { - Share share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); - assertEquals(new BigDecimal("140"), share.getPurchasePrice()); - } - - @Test - void testPurchasePriceZeroAllowed() { - // buying at price 0 is an edge case but not invalid - Share share = new Share(stock, new BigDecimal("5"), BigDecimal.ZERO); - assertEquals(BigDecimal.ZERO, share.getPurchasePrice()); - } - - // ---- Negative tests ---- - - @Test - void testNullStockThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Share(null, new BigDecimal("10"), new BigDecimal("100")) - ); - } - - @Test - void testNullQuantityThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Share(stock, null, new BigDecimal("100")) - ); - } - - @Test - void testZeroQuantityThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Share(stock, BigDecimal.ZERO, new BigDecimal("100")) - ); - } - - @Test - void testNegativeQuantityThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Share(stock, new BigDecimal("-5"), new BigDecimal("100")) - ); - } - - @Test - void testNullPurchasePriceThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Share(stock, new BigDecimal("10"), null) - ); - } - - @Test - void testNegativePurchasePriceThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Share(stock, new BigDecimal("10"), new BigDecimal("-50")) - ); - } + private Stock stock; + + @BeforeEach + void setUp() { + stock = new Stock("AAPL", "Apple", new BigDecimal("150")); + } + + // ---- Positive tests ---- + + /** + * Tests that Share constructor creates a non-null object. + */ + @Test + void testShareConstructorNotNull() { + Share share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); + assertNotNull(share); + } + + /** + * Tests that getStock returns the correct stock object. + */ + @Test + void testGetStock() { + Share share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); + assertEquals(stock, share.getStock()); + } + + /** + * Tests that getQuantity returns the correct quantity. + */ + @Test + void testGetQuantity() { + Share share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); + assertEquals(new BigDecimal("10"), share.getQuantity()); + } + + /** + * Tests that getPurchasePrice returns the correct purchase price. + */ + @Test + void testGetPurchasePrice() { + Share share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); + assertEquals(new BigDecimal("140"), share.getPurchasePrice()); + } + + /** + * Tests that share can be created with zero purchase price. + */ + @Test + void testPurchasePriceZeroAllowed() { + // buying at price 0 is an edge case but not invalid + Share share = new Share(stock, new BigDecimal("5"), BigDecimal.ZERO); + assertEquals(BigDecimal.ZERO, share.getPurchasePrice()); + } + + // ---- Negative tests ---- + + /** + * Tests that Share constructor throws when given null stock. + */ + @Test + void testNullStockThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Share(null, new BigDecimal("10"), new BigDecimal("100")) + ); + } + + /** + * Tests that Share constructor throws when given null quantity. + */ + @Test + void testNullQuantityThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Share(stock, null, new BigDecimal("100")) + ); + } + + /** + * Tests that Share constructor throws when given zero quantity. + */ + @Test + void testZeroQuantityThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Share(stock, BigDecimal.ZERO, new BigDecimal("100")) + ); + } + + /** + * Tests that Share constructor throws when given negative quantity. + */ + @Test + void testNegativeQuantityThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Share(stock, new BigDecimal("-5"), new BigDecimal("100")) + ); + } + + /** + * Tests that Share constructor throws when given null purchase price. + */ + @Test + void testNullPurchasePriceThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Share(stock, new BigDecimal("10"), null) + ); + } + + /** + * Tests that Share constructor throws when given negative purchase price. + */ + @Test + void testNegativePurchasePriceThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Share(stock, new BigDecimal("10"), new BigDecimal("-50")) + ); + } } + diff --git a/src/test/java/StockTest.java b/src/test/java/StockTest.java index 6a6a477..5addd52 100644 --- a/src/test/java/StockTest.java +++ b/src/test/java/StockTest.java @@ -4,161 +4,231 @@ import Model.Stock; import static org.junit.jupiter.api.Assertions.*; - import java.math.BigDecimal; import java.util.List; +/** + * Unit tests for the Stock class. + * Tests stock creation, price tracking, and error handling. + */ public class StockTest { - private Stock stock; - - @BeforeEach - void setUp() { - stock = new Stock("AAPL", "Apple", new BigDecimal("1000")); - } - - // ---- Positive tests ---- - - @Test - void testGetSymbol() { - assertEquals("AAPL", stock.getSymbol()); - } - - @Test - void testGetCompany() { - assertEquals("Apple", stock.getCompany()); - } - - @Test - void testGetSalesPrice() { - assertEquals(new BigDecimal("1000"), stock.getSalesPrice()); - } - - @Test - void testGetSalesPriceZero() { - Stock zeroStock = new Stock("ZERO", "ZeroCorp", new BigDecimal("0")); - assertEquals(new BigDecimal("0"), zeroStock.getSalesPrice()); - } - - @Test - void testAddNewSalesPrice() { - stock.addNewSalesPrice(new BigDecimal("1200")); - assertEquals(new BigDecimal("1200"), stock.getSalesPrice()); - } - - @Test - void testAddNewSalesPriceZero() { - stock.addNewSalesPrice(new BigDecimal("0")); - assertEquals(new BigDecimal("0"), stock.getSalesPrice()); - } - - @Test - void testGetHistoricalPricesInitial() { - List history = stock.getHistoricalPrices(); - assertEquals(1, history.size()); - assertEquals(new BigDecimal("1000"), history.get(0)); - } - - @Test - void testGetHistoricalPricesAfterUpdates() { - stock.addNewSalesPrice(new BigDecimal("1100")); - stock.addNewSalesPrice(new BigDecimal("1200")); - assertEquals(3, stock.getHistoricalPrices().size()); - } - - @Test - void testGetHighestPrice() { - stock.addNewSalesPrice(new BigDecimal("1500")); - stock.addNewSalesPrice(new BigDecimal("800")); - assertEquals(new BigDecimal("1500"), stock.getHighestPrice()); - } - - @Test - void testGetLowestPrice() { - stock.addNewSalesPrice(new BigDecimal("1500")); - stock.addNewSalesPrice(new BigDecimal("800")); - assertEquals(new BigDecimal("800"), stock.getLowestPrice()); - } - - @Test - void testGetLatestPriceChangePositive() { - stock.addNewSalesPrice(new BigDecimal("1100")); - assertEquals(new BigDecimal("100"), stock.getLatestPriceChange()); - } - - @Test - void testGetLatestPriceChangeNegative() { - stock.addNewSalesPrice(new BigDecimal("900")); - assertEquals(new BigDecimal("-100"), stock.getLatestPriceChange()); - } - - @Test - void testGetLatestPriceChangeOnlyOnePrice() { - assertEquals(BigDecimal.ZERO, stock.getLatestPriceChange()); - } - - @Test - void testHistoricalPricesIsDefensiveCopy() { - List history = stock.getHistoricalPrices(); - history.add(new BigDecimal("9999")); - assertEquals(1, stock.getHistoricalPrices().size()); - } - - // ---- Negative tests ---- - - @Test - void testNullSymbolThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Stock(null, "Apple", new BigDecimal("100")) - ); - } - - @Test - void testBlankSymbolThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Stock(" ", "Apple", new BigDecimal("100")) - ); - } - - @Test - void testNullCompanyThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Stock("AAPL", null, new BigDecimal("100")) - ); - } - - @Test - void testBlankCompanyThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Stock("AAPL", " ", new BigDecimal("100")) - ); - } - - @Test - void testNullInitialPriceThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Stock("AAPL", "Apple", null) - ); - } - - @Test - void testNegativeInitialPriceThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Stock("AAPL", "Apple", new BigDecimal("-1")) - ); - } - - @Test - void testAddNullPriceThrows() { - assertThrows(IllegalArgumentException.class, () -> - stock.addNewSalesPrice(null) - ); - } - - @Test - void testAddNegativePriceThrows() { - assertThrows(IllegalArgumentException.class, () -> - stock.addNewSalesPrice(new BigDecimal("-10")) - ); - } + private Stock stock; + + @BeforeEach + void setUp() { + stock = new Stock("AAPL", "Apple", new BigDecimal("1000")); + } + + // ---- Positive tests ---- + + /** + * Tests that getSymbol returns the correct stock symbol. + */ + @Test + void testGetSymbol() { + assertEquals("AAPL", stock.getSymbol()); + } + + /** + * Tests that getCompany returns the correct company name. + */ + @Test + void testGetCompany() { + assertEquals("Apple", stock.getCompany()); + } + + /** + * Tests that getSalesPrice returns the current stock price. + */ + @Test + void testGetSalesPrice() { + assertEquals(new BigDecimal("1000"), stock.getSalesPrice()); + } + + /** + * Tests that stock can have zero sales price. + */ + @Test + void testGetSalesPriceZero() { + Stock zeroStock = new Stock("ZERO", "ZeroCorp", new BigDecimal("0")); + assertEquals(new BigDecimal("0"), zeroStock.getSalesPrice()); + } + + /** + * Tests that adding a new sales price updates the current price. + */ + @Test + void testAddNewSalesPrice() { + stock.addNewSalesPrice(new BigDecimal("1200")); + assertEquals(new BigDecimal("1200"), stock.getSalesPrice()); + } + + /** + * Tests that zero price can be added to price history. + */ + @Test + void testAddNewSalesPriceZero() { + stock.addNewSalesPrice(new BigDecimal("0")); + assertEquals(new BigDecimal("0"), stock.getSalesPrice()); + } + + /** + * Tests that historical prices initially contain only the initial price. + */ + @Test + void testGetHistoricalPricesInitial() { + List history = stock.getHistoricalPrices(); + assertEquals(1, history.size()); + assertEquals(new BigDecimal("1000"), history.get(0)); + } + + /** + * Tests that historical prices accumulates all price changes. + */ + @Test + void testGetHistoricalPricesAfterUpdates() { + stock.addNewSalesPrice(new BigDecimal("1100")); + stock.addNewSalesPrice(new BigDecimal("1200")); + assertEquals(3, stock.getHistoricalPrices().size()); + } + + /** + * Tests that getHighestPrice returns the maximum price from history. + */ + @Test + void testGetHighestPrice() { + stock.addNewSalesPrice(new BigDecimal("1500")); + stock.addNewSalesPrice(new BigDecimal("800")); + assertEquals(new BigDecimal("1500"), stock.getHighestPrice()); + } + + /** + * Tests that getLowestPrice returns the minimum price from history. + */ + @Test + void testGetLowestPrice() { + stock.addNewSalesPrice(new BigDecimal("1500")); + stock.addNewSalesPrice(new BigDecimal("800")); + assertEquals(new BigDecimal("800"), stock.getLowestPrice()); + } + + /** + * Tests that getLatestPriceChange returns positive change for price increase. + */ + @Test + void testGetLatestPriceChangePositive() { + stock.addNewSalesPrice(new BigDecimal("1100")); + assertEquals(new BigDecimal("100"), stock.getLatestPriceChange()); + } + + /** + * Tests that getLatestPriceChange returns negative change for price decrease. + */ + @Test + void testGetLatestPriceChangeNegative() { + stock.addNewSalesPrice(new BigDecimal("900")); + assertEquals(new BigDecimal("-100"), stock.getLatestPriceChange()); + } + + /** + * Tests that price change is zero when only one price exists. + */ + @Test + void testGetLatestPriceChangeOnlyOnePrice() { + assertEquals(BigDecimal.ZERO, stock.getLatestPriceChange()); + } + + /** + * Tests that getHistoricalPrices returns a defensive copy. + */ + @Test + void testHistoricalPricesIsDefensiveCopy() { + List history = stock.getHistoricalPrices(); + history.add(new BigDecimal("9999")); + assertEquals(1, stock.getHistoricalPrices().size()); + } + + // ---- Negative tests ---- + + /** + * Tests that Stock constructor throws when given null symbol. + */ + @Test + void testNullSymbolThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Stock(null, "Apple", new BigDecimal("100")) + ); + } + + /** + * Tests that Stock constructor throws when given blank symbol. + */ + @Test + void testBlankSymbolThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Stock(" ", "Apple", new BigDecimal("100")) + ); + } + + /** + * Tests that Stock constructor throws when given null company name. + */ + @Test + void testNullCompanyThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Stock("AAPL", null, new BigDecimal("100")) + ); + } + + /** + * Tests that Stock constructor throws when given blank company name. + */ + @Test + void testBlankCompanyThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Stock("AAPL", " ", new BigDecimal("100")) + ); + } + + /** + * Tests that Stock constructor throws when given null initial price. + */ + @Test + void testNullInitialPriceThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Stock("AAPL", "Apple", null) + ); + } + + /** + * Tests that Stock constructor throws when given negative initial price. + */ + @Test + void testNegativeInitialPriceThrows() { + assertThrows(IllegalArgumentException.class, () -> + new Stock("AAPL", "Apple", new BigDecimal("-1")) + ); + } + + /** + * Tests that addNewSalesPrice throws when given null price. + */ + @Test + void testAddNullPriceThrows() { + assertThrows(IllegalArgumentException.class, () -> + stock.addNewSalesPrice(null) + ); + } + + /** + * Tests that addNewSalesPrice throws when given negative price. + */ + @Test + void testAddNegativePriceThrows() { + assertThrows(IllegalArgumentException.class, () -> + stock.addNewSalesPrice(new BigDecimal("-10")) + ); + } } + diff --git a/src/test/java/TransactionArchiveTest.java b/src/test/java/TransactionArchiveTest.java index 14ff009..c3a1fa1 100644 --- a/src/test/java/TransactionArchiveTest.java +++ b/src/test/java/TransactionArchiveTest.java @@ -1,148 +1,196 @@ import static org.junit.jupiter.api.Assertions.*; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import Model.Player; import Model.Purchase; import Model.Sale; import Model.Share; import Model.Stock; import Model.TransactionArchive; - import java.math.BigDecimal; import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +/** + * Unit tests for the TransactionArchive class. + * Tests transaction recording, retrieval, filtering, and error handling. + */ public class TransactionArchiveTest { - private TransactionArchive archive; - private Stock stock; - private Share share; - private Purchase purchase; - private Sale sale; - - @BeforeEach - void setUp() { - archive = new TransactionArchive(); - stock = new Stock("AAPL", "Apple", new BigDecimal("100")); - share = new Share(stock, new BigDecimal("10"), new BigDecimal("90")); - purchase = new Purchase(share, 1); - sale = new Sale(share, 2); - } - - // ---- Positive tests ---- - - @Test - void testIsEmptyOnCreation() { - assertTrue(archive.isEmpty()); - } - - @Test - void testIsEmptyAfterAdd() { - archive.add(purchase); - assertFalse(archive.isEmpty()); - } - - @Test - void testAddReturnsTrue() { - assertTrue(archive.add(purchase)); - } - - @Test - void testGetAllTransactions() { - archive.add(purchase); - archive.add(sale); - assertEquals(2, archive.getAllTransactions().size()); - } - - @Test - void testGetAllTransactionsIsDefensiveCopy() { - archive.add(purchase); - List copy = archive.getAllTransactions(); - copy.clear(); - assertEquals(1, archive.getAllTransactions().size()); - } - - @Test - void testGetTransactionsByWeek() { - archive.add(purchase); // week 1 - archive.add(sale); // week 2 - - List week1 = archive.getTransactions(1); - assertEquals(1, week1.size()); - assertTrue(week1.contains(purchase)); - } - - @Test - void testGetTransactionsByWeekNoMatch() { - archive.add(purchase); // week 1 - assertTrue(archive.getTransactions(99).isEmpty()); - } - - @Test - void testGetPurchaseByWeek() { - archive.add(purchase); // week 1 - archive.add(sale); // week 2 - - List purchases = archive.getPurchase(1); - assertEquals(1, purchases.size()); - assertTrue(purchases.contains(purchase)); - } - - @Test - void testGetPurchaseByWeekExcludesSales() { - // Add a sale at week 1 — should not appear in getPurchase(1) - Sale saleWeek1 = new Sale(share, 1); - archive.add(saleWeek1); - - assertTrue(archive.getPurchase(1).isEmpty()); - } - - @Test - void testGetSaleByWeek() { - archive.add(purchase); // week 1 - archive.add(sale); // week 2 - - List sales = archive.getSale(2); - assertEquals(1, sales.size()); - assertTrue(sales.contains(sale)); - } - - @Test - void testGetSaleByWeekExcludesPurchases() { - // Add a purchase at week 2 — should not appear in getSale(2) - Purchase purchaseWeek2 = new Purchase(share, 2); - archive.add(purchaseWeek2); - - assertTrue(archive.getSale(2).isEmpty()); - } - - @Test - void testCountDistinctWeeksEmpty() { - assertEquals(0, archive.countDistinctWeeks()); - } - - @Test - void testCountDistinctWeeksMultipleTransactionsSameWeek() { - Purchase purchase2 = new Purchase(share, 1); - archive.add(purchase); - archive.add(purchase2); - assertEquals(1, archive.countDistinctWeeks()); - } - - @Test - void testCountDistinctWeeksAcrossWeeks() { - archive.add(purchase); // week 1 - archive.add(sale); // week 2 - assertEquals(2, archive.countDistinctWeeks()); - } - - // ---- Negative tests ---- - - @Test - void testAddNullThrows() { - assertThrows(IllegalArgumentException.class, () -> - archive.add(null) - ); - } + private TransactionArchive archive; + private Stock stock; + private Share share; + private Purchase purchase; + private Sale sale; + + @BeforeEach + void setUp() { + archive = new TransactionArchive(); + stock = new Stock("AAPL", "Apple", new BigDecimal("100")); + share = new Share(stock, new BigDecimal("10"), new BigDecimal("90")); + purchase = new Purchase(share, 1); + sale = new Sale(share, 2); + } + + // ---- Positive tests ---- + + /** + * Tests that archive is empty immediately after creation. + */ + @Test + void testIsEmptyOnCreation() { + assertTrue(archive.isEmpty()); + } + + /** + * Tests that isEmpty returns false after adding a transaction. + */ + @Test + void testIsEmptyAfterAdd() { + archive.add(purchase); + assertFalse(archive.isEmpty()); + } + + /** + * Tests that add method returns true for successful addition. + */ + @Test + void testAddReturnsTrue() { + assertTrue(archive.add(purchase)); + } + + /** + * Tests that getAllTransactions returns all stored transactions. + */ + @Test + void testGetAllTransactions() { + archive.add(purchase); + archive.add(sale); + assertEquals(2, archive.getAllTransactions().size()); + } + + /** + * Tests that getAllTransactions returns a defensive copy. + */ + @Test + void testGetAllTransactionsIsDefensiveCopy() { + archive.add(purchase); + List copy = archive.getAllTransactions(); + copy.clear(); + assertEquals(1, archive.getAllTransactions().size()); + } + + /** + * Tests that getTransactions filters transactions by week number. + */ + @Test + void testGetTransactionsByWeek() { + archive.add(purchase); // week 1 + archive.add(sale); // week 2 + + List week1 = archive.getTransactions(1); + assertEquals(1, week1.size()); + assertTrue(week1.contains(purchase)); + } + + /** + * Tests that getTransactions returns empty list for non-existent week. + */ + @Test + void testGetTransactionsByWeekNoMatch() { + archive.add(purchase); // week 1 + assertTrue(archive.getTransactions(99).isEmpty()); + } + + /** + * Tests that getPurchase returns only purchase transactions for week. + */ + @Test + void testGetPurchaseByWeek() { + archive.add(purchase); // week 1 + archive.add(sale); // week 2 + + List purchases = archive.getPurchase(1); + assertEquals(1, purchases.size()); + assertTrue(purchases.contains(purchase)); + } + + /** + * Tests that getPurchase does not return sale transactions. + */ + @Test + void testGetPurchaseByWeekExcludesSales() { + // Add a sale at week 1 — should not appear in getPurchase(1) + Sale saleWeek1 = new Sale(share, 1); + archive.add(saleWeek1); + + assertTrue(archive.getPurchase(1).isEmpty()); + } + + /** + * Tests that getSale returns only sale transactions for week. + */ + @Test + void testGetSaleByWeek() { + archive.add(purchase); // week 1 + archive.add(sale); // week 2 + + List sales = archive.getSale(2); + assertEquals(1, sales.size()); + assertTrue(sales.contains(sale)); + } + + /** + * Tests that getSale does not return purchase transactions. + */ + @Test + void testGetSaleByWeekExcludesPurchases() { + // Add a purchase at week 2 — should not appear in getSale(2) + Purchase purchaseWeek2 = new Purchase(share, 2); + archive.add(purchaseWeek2); + + assertTrue(archive.getSale(2).isEmpty()); + } + + /** + * Tests that countDistinctWeeks returns zero for empty archive. + */ + @Test + void testCountDistinctWeeksEmpty() { + assertEquals(0, archive.countDistinctWeeks()); + } + + /** + * Tests that countDistinctWeeks counts only distinct weeks. + */ + @Test + void testCountDistinctWeeksMultipleTransactionsSameWeek() { + Purchase purchase2 = new Purchase(share, 1); + archive.add(purchase); + archive.add(purchase2); + assertEquals(1, archive.countDistinctWeeks()); + } + + /** + * Tests that countDistinctWeeks correctly counts across multiple weeks. + */ + @Test + void testCountDistinctWeeksAcrossWeeks() { + archive.add(purchase); // week 1 + archive.add(sale); // week 2 + assertEquals(2, archive.countDistinctWeeks()); + } + + // ---- Negative tests ---- + + /** + * Tests that add throws when given null transaction. + */ + @Test + void testAddNullThrows() { + assertThrows(IllegalArgumentException.class, () -> + archive.add(null) + ); + } } + diff --git a/target/test-classes/ExchangeTest.class b/target/test-classes/ExchangeTest.class index 5b02574e2280eca7a62f88929280c7d347ae094e..c9e32fd89c87350a4ead3475fed0f9295f726766 100644 GIT binary patch literal 10733 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 literal 10397 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 BN(=x1 diff --git a/target/test-classes/ShareTest.class b/target/test-classes/ShareTest.class index 65bc1e8252dc9cc05db520ac079a267668bb9a4f..959b07a62f55678b3a56ea82ddb5792a1d317529 100644 GIT binary patch literal 4255 zcmbVO3s)0I7`=l)LRb(Zq9}p_mVm{8l~#?~21KQefPyI6)-GX47B<Q7v_rC$)^K2hl=6!y`i>G$AZVcQWjXB#N{%mZ@b^vHrOb z0@x)+v@x9a8L@ee^QMfEkQ%TD?E&mnun!#!4Kr!gu3vMKClaq&39?=q#>M z3=ZM|L(T9|5ZyxlF_OO@y+Yg}hEtJ}$^I(*--=cCAre5pf&m<6IAEBm=yHJ)PS2cX zaWkssv}in^=cc6@**r;A?O@WdCJVY=mOW?F?CkBwpn@ZaG6YlHvNwxid!)Zate_uO za1_rlC=T%Wt%9l(PoJD~7gx%83DbyzXYm~6jF>JJ)U2ghcNq2%nV$(_B6}wkoD@@9 zXv%cKOs3U5pEk85<@yu@8)+beoKf&RULb#6AHO`s(B_X8z$im^(Locs&Qq!$H&cZS z&syWRlRPJK7beG_RWOco3;}+dCkqy57$lMYNna{tlVVBYGxh|)Zl>(Yvk`5&Fc=a-@#EG)v)|DJX`{e?8A;agiyhc;v*{oryu8+GbWM`Bw873cBlj#`4K8woKUiKK7 z-!cnH%P>9b*X2cVO4Tbun$=MOTGTvsD+2Up_Yi+|8Z5wYl;7d9LKzYYrdj?t9(pGtSr=1W%Qleu6vrC@pJ0ha1nT18Q zcZ-3&_EB6^^}vZMDz^uB_;9epUGj}(}8^ofW*Rgk5q9-oKs1-=a6D+OQUo01bu%dQ$L zJesh#iiD_3s_;D>sf`&q`ddD7+)0k^3u<;z=lR}*VXPE#G3nfP?^o$f$;8<^IW78;FP4nUH>)5f5*59$a2q?b1fL2noO9E|oL0_dlL3vZ)<28@q zCVdr&c|DJ@e}3RM?6&c{2;XiTw268WhacjpB2_aT{y-=Fb)g#vY`QiF#-;0|1gKab z!VTi2;KiaxyyOzo!m1GqNr(Ty(+@FJ&fiD;5%kla$f3hO;PETI{M_STarvXw`Mq?I zh+{q?hDgLPeHI;+L_|FiJA6ef{Vx$>D@ep~9}y==#7Pnndjb(DPsB~TRc?V8(fx|k z>*z_)PX*#Ja@$$5z_>)*!#$3#%aD3nqUh)UMMiqBM0&VgIU9JE+=?}Uo0Mi9rRrxFlwhxQB3d%DQfuC}^ z?_Gj1H#O}W%BcXWLMJ8YjRRj!3Vm33NpZ67C52T5o|k|(CnY4lUtHj0q*?sy5SkrO z?-G$S!lcV@imdJK99<}D)ULs8awc$wa6hAWuj38+@3h|bTKB!yyI$)9uk|stz9ozh M?f!T80Y9PrUo#V+wg3PC literal 4137 zcmbtWX;%|h7=A8WLKp-=DX7H_WfKD`ts1lhM9@Y+KoqOeAzaA7BoikSlz})S0yu@A11MrBA5W+zpWqoQ zfD(r41$9Xc>uPE~JTkMu;}%0{#xmlI43)79#w^#vxNm(mOk zW7(9YCHbV5(dbAtl`<^V(u@?H*cUU*`S3!PM9_Cyv$z>n(^|NX&lx5kP~(Y60NV)J zBRhyXQW}kp#)7CPnrJ$$^8j`*Y?F>BRVxwh)8^0cxRzA)Aa;>^UEQ4_G@w!VwVPpk zOUw<@xMgao`AF+z2({QNcI;y~<+Ee$nVe-6BO#T8p-Dk0${2Q9lo%P9e)8EevvJEX zhYf2utLqHA)`@_~Ci&jt`Yv)DL>og#+US_^yi$UeWv!k&P(@SMivW zjhqOEac?&IbxKv_K7j40%Mm6KB^^Z~?{GdvIl3C^wj08iX@=u1o*{HU&e-T8BdgJi zaD!ooajV{trkm9_=60v2s7sPj4sn{0O)#S{q+SJJqaxzA#r3qf^ zrq+o}5J@o&I%#|UWS6_7md;y`24T|t=%DV+n~u55?jV*3r=wfYjvG|i{H!Ks{uXDf z9~SIX+kJ*%(I`{X7r2!$W-}o?iKhd2s-X0=)RM8t!!hX#ZB*w}G5VGjJcDPc)sn7> z;csuj*e7$~LuzVP=b5IMVJv3T5l>IsgWF>%I5@GK=HBg_|1n3o@mX(;+Q|JXwIbbN zk*YF0QH@}T>heVe2?WXT0vT-EYYI|i3*c4P_O=2;+TL<)A1Fvm+xxEVQw14m`^2?< zrNEN5FUdx&crKOVrffP+Me{ww0k>!hf>0RR0Dfd>+;}VfWys|Ld&#+C8X{Cr;+~^b zEv@Na1AUj%TbQ1Vo~3Oo2>n8fA}I7-CXE%Sq_vXUfGwz^H-OaBuC25ZyTpVNK!Xwz zXlT2K+NqlDH4ofF!&J@A6+F0tJ-=hW3m&55Ab@&0>=HCM1P2NT4%!6Mq*|QYe-F)5 zZNG^p;kOdLT(nD~j#&F0bh=bqr1SM?q*zKiGMd*vB$&T=I|>Hf7F+M(dG|t%+DXKr{}6HBSHv_`+}aX&l<0oNg%xzf=&=rQ7ggI4DuJUO z;{CSw^{8;XzvxSRl4wPjC^chuF#+7cmCJ6DbM<_JPOS$=DY}n2O(qw5Zsxd^kQ3xoD&*LS$ELX4Kb-W>0Z{i)iD_8H~LwqDxALBE8E>~aRYkY%m P2}Att_zpkdCp7#EbX!Al diff --git a/target/test-classes/StockTest.class b/target/test-classes/StockTest.class index e22183d1d0208cb1c72e53109d70a846ae53f6ee..086bf5b8c9ecf660b77db09a000cb1607902004b 100644 GIT binary patch literal 6225 zcmbVP`F9i775>K9mMjkjgzexo0S64&P$Lsa0vM9m#sQ~d2{xvXr5(#-d9bVzS^z`a zq$FLFu35S_X}Ts&w}h}ZG+mR@bV-`-J^igc{VRI<-8ZArNE+KseU3-(&Aacr-@WhN z`|e!*&!sB>cHplO)L=mfwL0ohuVL{~>3Rr ztf6s_J#MCA!+AR~6-GouZC~I1co@2dy1q;%WronCp;g76CpqD19B ztRiGJU{7a^^o)>QujB3LrWr!kNSV3)Su0^`SXLxl>69>HJcuKM2lj=rQA7BMnY9P( zY$l9N8nm7;Zjy7lQO+rZTXbx}Rz`<0KEBsH;aAY+a<9a9*!Jq!jvX47lGbjEt|luZ zrMS`VXpvkYZ>3^!uJ<YyU_CfLa9X!R(p`or( z*}PlF5bmS|HA-L&`47zXWEgQVj!_uKUK!maqy5~p#j*E*j$!fUB1Jnn$w*P0#TN&4 zjLKRQ*W&hx;!+sz9@XVT@CFEC0mG2^~pH5{3s-kv_#}ITFHA4J%7|HIz!3Nh8&lO%|rjbbjz;!pulK zMp+0_I;N4<5He4ii9((p?&jG92l7NAosfO8K_zR9rA+3_N@R2#L)HuKk;$xmf`>fp z5AJ~!Bfs}80Q*l*Au4rIXh=m_x%gNhk6L-u$)g@KV#_HFTRP)jp7*{LW1>94nK0f< z_wsPUxL-p}_r@^Z&);^YUhM$OAA=)qrOmyC=`k}qBCHtCahuUSYGf^$yDw|=lNR03 z=p!Rl$IKthXlOf7Naw9-bJWUN+|rj$+j+w+5-VqMQ@=T3XU#z)G1<#}m*Cn}Dp1$W zvPp^^#hYAlw{mD$Qrz#W87)=sq&Tn;p5l*kDI;5C`l_nIvY^+_?&U6OWf>S^p|lLv z))4)~0x`of_9Wg~?%J75(YXK>`X^ zqJ{uvI~A-7C&w#gi>p$`^w_we{1hzH(ZdVTD`>e8p5Zx`DxX(4G_0t=Dkma1Ykd;4 z+ai*FUcoLADZj>QkBY={zsAb5s1&F=L=m{Y0>OXCvK|?>3)zHuhb2|2$*YPR#6?WE ze%sFH@>wIZ$IMUK)t9pC1x4zJ4WmpYzRvtP;VH67njlD~=IxAhO-!dHU8GEI}hy#9Ms@JK4lqb!b5!OjsQM@4{`>4h^sYf z^DczdTHCtI2px_#L>n(-;o<0_^H_2o%`f7b5}==WOYG_pbCjyh)O!v$d*dKG9nH64F>c2y z^!aS<2(k&BW64>l1+*3hdpEFku zk~N3OU1g!}a4^YXf*g|Agh?NV&4C;~_kVH_J`RVG01h@eWXRzdIb`SM@Oh6zb?CbX zq3k~7={#Jf(+T|<>O93zJnhpL9Qt3FL*G3ouf8vO`nJ2pwdr}h z+woqxB~HnHm|w4t_)wbyPwq<|>S;!gBvh9Jxh=k4xhp~X7h%@mP_4V*4_kjuJ<1$; zjQ7Oj=)@CBu~W0UT*YDDX_Yx8Z>n?Z%O0t1uBm#|x`Zh(mF+{Nba4!Q5{-D;XQ&=% z=##;ixnRs^i22;Sm`?>`=7TX`Am+JwF~1UwSqR2_iI^|Xi}`dg=J8<6SBQCaUd*os zW1a}ce4UtY&Wrgq53|%iSnDp~WH9L8SeXCe0}bvUzCH`+>0r=*67=75gD$R--k6bj zhUwDl7F|`xRK~o=N&ZrpIz8FHe4tC*5d9_r?x8 zDyWEb^)A!~b|CWCTGr>ij||!8sZ7#*ZQ~m#5sJu{f{U_-eW(026;ZUH-LIDI=2yoo z93Jq>a1hFsG8BHvxG3GOB`ppIZBCH0Wgz@G3Z(LY+xE(h5JctKG8BGr1yRxF0ePwf z!j8o`$>;F`y?KE3^4ENK7BBMO8C~#3m%Y&yZ}ekt^fPbtiZ{B-(VN7LFrfZ`KjANE F`!8O2Q-uHk literal 6116 zcmb_f`Fm8=8GdgPGLzvFV3HuQN+SeGf?*N}3QVvjAwqOWkc3cB+ulrWCKo1i$60`2 zUD~?TYHh2vt(LZ0YxmVE2()(Jtljs0-~Nd{eb2df?!7Z}Nbq?&KTOVi=bZO@zx8~1 z>A&Y*1h5nT386+q!+60?9-T1rg%IjAtT!vg4fXw2#wuK^p|+!QQbXMkcO$69QXL_LH7rk98FREaH)ZA~jH$G#A)2t0Mtai7 zS>m@VtSih~c@0Yw9uZ8xMmmhkc)Yi#rze6|tPzI2T0=`m!uQg6A!lV$@y^Kzns9|Uu~x%Q#fj=W zj-)~*ks6?(UB?pC)9D39X17@wpP!qu(;6vinL9@dOe zag2HE`lgba@WyHtvf!d&jmvwF#hRo2Mp}9yKf)ZhjI>ai(6JYz8k*>h%44jsl23{S zD;-M^CcYH=us?*G7zUZO-j0rOOwgRXb&|EoA|4SbF)4z5kfqTvGSWFIE+5iy7&r3- zHHzdP2_};pWep)#F*(oE9ZnK>dRWe@$_sx!TPl9nP2SmlbxmALjCB|A&AiWB0elwI>jbpnPL+Z zV8;ozU06fE*L6#~+b=|6ER&ue%b3y;4Qyv`)X|SSL~vBCujKK{92?2+GH1+8p6Z9} z?0gt^Q}cFyM5?@5$6Ig@Ik6%t`edmc9Ck}JT}XCKdpa6enR-&aS{XJTl{68 zzuX$er+KHl*B4#wFRuyX^GsmY61j}q#az<7-V(!0qc@*y5|r5I25q~LFXW8uUb8T3 zPv;|e9%n*0U6#jI=9qodj3uOjJZQ`qNzo^DynwSDPUd`(hL?lc=iYR{_ZXRJK8o!L z+df*%2H#nC013RjWfv#dY=g%ye$!F8@ZDg-yU0TZ3!B4Oe0e>dTY+$dS^i}mPa;gg zmnd*$U)S*wDf^l)`;Lx}N!ho3*~>aUE@j{MWk1pJ2`T%rFZ-pAPfFP@eA(}Ge1@_R ze(THrtmCs%_9x0%|09`X4}NRJDq&7KDw?5h>n zYxx?l3t%^Ugm);hrx-ioy(^`cufC%hjUPorF0qs)R0Xh8?ob5Iw4U*#aV z8e15V0XOi2O5KyFlXkTWbEK*v>OGBNZ>_S^(cI0n*+ffY0bAQWTSqQZpRlu}PpmJj zZwvMHQQuajz8jSKPS8ivw+`%$iMowvF{ZI!9*`D_T;5oF7O$Wb^ zj_w#>=?|g@LjjlcdLc+E2^?_=^mz)WD+tUuzomo3oIgDTnZU}Jb z^Ee#+KX4Fy91dv(hf#7EBZqzDaFddQt>jRh`febk-C0j(u1u$s`Uj|UlBswwpwCbJ z;zjB^7Vz_l1@+xbeYa5GtxA0-mHPVK=GyoyZg;#_Zi&;f4I)kjP#e9--Kj*qo!KJ_ zs?&kIF|kp)>n_qii#OHaV4b@WOxs{h%@8)lcVd=Z%aV$np4I6p4)cAhtSKRJYU*Ak zsU5DVdi1_?cq>@S4;}Q&#WB>zQse@L>YkzRTpaUVYRqHAoL6E#vN-0WYRo%`dAAbt z-HT&BrpCO7nD;9&-?KR8d)1f^67yjt=KB`Md|ZwB2r(a1Vm_h7^yi2By3XPKYS72o zm`?>j{rTa87XkVqHRzKBeYzTSMMTV|%*Ll!BI0HJupFfN_`-`1r-!CPnma~`4#P3Z z&r@eW%Su$17_m6hR$*;*v0CJ`@XW$s#{*y-hO5BJh(%j8uk|U2-5J2*5M6~;POK&R z%tB!I2EaIdSAlhNvK60YrO8uMFnTBe!g0L{q>_;{zJ5yW_eVXj8ZqZrfo-8gBcle< z-!x7vm4SOg0yvX(gVS7A)QEo%s-WWT3%pW5NiThloD`5x1t3?t{i+s@;%D(ive{4g zbDVL`<3)T$uD*(I;G1&wEqoW>ldG5T1N=~~euSUmXL9v({0hI8tKZ=F_=8;i5r4s7 R@i*d%f3*INf8yV0{U0rAvt Date: Mon, 25 May 2026 21:42:19 +0200 Subject: [PATCH 22/22] Improved Checkstyle on test-classes --- src/test/java/ExchangeTest.java | 200 +++++++++++----------- src/test/java/PlayerTest.java | 82 ++++----- src/test/java/PortfolioTest.java | 115 +++++++------ src/test/java/PurchaseTest.java | 71 ++++---- src/test/java/SaleTest.java | 74 ++++---- src/test/java/ShareTest.java | 67 ++++---- src/test/java/StockTest.java | 108 ++++++------ src/test/java/TransactionArchiveTest.java | 102 +++++------ target/test-classes/ExchangeTest.class | Bin 10733 -> 10397 bytes target/test-classes/PortfolioTest.class | Bin 5661 -> 5661 bytes target/test-classes/ShareTest.class | Bin 4255 -> 4137 bytes target/test-classes/StockTest.class | Bin 6225 -> 6116 bytes 12 files changed, 410 insertions(+), 409 deletions(-) diff --git a/src/test/java/ExchangeTest.java b/src/test/java/ExchangeTest.java index 4aa8f3a..cc55f5d 100644 --- a/src/test/java/ExchangeTest.java +++ b/src/test/java/ExchangeTest.java @@ -27,15 +27,15 @@ public class ExchangeTest { @BeforeEach void setUp() { - apple = new Stock("AAPL", "Apple", new BigDecimal("100")); - google = new Stock("GOOGL", "Google", new BigDecimal("200")); + apple = new Stock("AAPL", "Apple", new BigDecimal("100")); + google = new Stock("GOOGL", "Google", new BigDecimal("200")); - List stocks = new ArrayList<>(); - stocks.add(apple); - stocks.add(google); + List stocks = new ArrayList<>(); + stocks.add(apple); + stocks.add(google); - exchange = new Exchange("TestExchange", stocks); - player = new Player("Jane", new BigDecimal("500000")); + exchange = new Exchange("TestExchange", stocks); + player = new Player("Jane", new BigDecimal("500000")); } // ---- Positive tests ---- @@ -45,7 +45,7 @@ void setUp() { */ @Test void testGetName() { - assertEquals("TestExchange", exchange.getName()); + assertEquals("TestExchange", exchange.getName()); } /** @@ -53,7 +53,7 @@ void testGetName() { */ @Test void testInitialWeekIsOne() { - assertEquals(1, exchange.getWeek()); + assertEquals(1, exchange.getWeek()); } /** @@ -61,8 +61,8 @@ void testInitialWeekIsOne() { */ @Test void testAdvanceIncrementsWeek() { - exchange.advance(); - assertEquals(2, exchange.getWeek()); + exchange.advance(); + assertEquals(2, exchange.getWeek()); } /** @@ -70,7 +70,7 @@ void testAdvanceIncrementsWeek() { */ @Test void testHasStockReturnsTrue() { - assertTrue(exchange.hasStock("AAPL")); + assertTrue(exchange.hasStock("AAPL")); } /** @@ -78,7 +78,7 @@ void testHasStockReturnsTrue() { */ @Test void testHasStockReturnsFalse() { - assertFalse(exchange.hasStock("MSFT")); + assertFalse(exchange.hasStock("MSFT")); } /** @@ -86,7 +86,7 @@ void testHasStockReturnsFalse() { */ @Test void testGetStockReturnsCorrectStock() { - assertEquals(apple, exchange.getStock("AAPL")); + assertEquals(apple, exchange.getStock("AAPL")); } /** @@ -94,7 +94,7 @@ void testGetStockReturnsCorrectStock() { */ @Test void testGetStockReturnsNullForUnknown() { - assertNull(exchange.getStock("MSFT")); + assertNull(exchange.getStock("MSFT")); } /** @@ -102,9 +102,9 @@ void testGetStockReturnsNullForUnknown() { */ @Test void testFindStocksBySymbol() { - List result = exchange.findStocks("AAPL"); - assertEquals(1, result.size()); - assertEquals("AAPL", result.get(0).getSymbol()); + List result = exchange.findStocks("AAPL"); + assertEquals(1, result.size()); + assertEquals("AAPL", result.get(0).getSymbol()); } /** @@ -112,8 +112,8 @@ void testFindStocksBySymbol() { */ @Test void testFindStocksByCompanyName() { - List result = exchange.findStocks("e"); - assertEquals(2, result.size()); + List result = exchange.findStocks("e"); + assertEquals(2, result.size()); } /** @@ -121,8 +121,8 @@ void testFindStocksByCompanyName() { */ @Test void testFindStocksNoMatch() { - List result = exchange.findStocks("Samsung"); - assertEquals(0, result.size()); + List result = exchange.findStocks("Samsung"); + assertEquals(0, result.size()); } /** @@ -130,8 +130,8 @@ void testFindStocksNoMatch() { */ @Test void testFindStocksEmptyTermReturnsAll() { - List result = exchange.findStocks(""); - assertEquals(2, result.size()); + List result = exchange.findStocks(""); + assertEquals(2, result.size()); } /** @@ -139,10 +139,10 @@ void testFindStocksEmptyTermReturnsAll() { */ @Test void testBuyReturnsCommittedPurchase() { - Transaction t = exchange.buy("AAPL", new BigDecimal("5"), player); - assertNotNull(t); - assertTrue(t.isCommitted()); - assertInstanceOf(Purchase.class, t); + Transaction t = exchange.buy("AAPL", new BigDecimal("5"), player); + assertNotNull(t); + assertTrue(t.isCommitted()); + assertInstanceOf(Purchase.class, t); } /** @@ -150,9 +150,9 @@ void testBuyReturnsCommittedPurchase() { */ @Test void testBuyDeductsMoneyFromPlayer() { - BigDecimal before = player.getMoney(); - exchange.buy("AAPL", new BigDecimal("5"), player); - assertTrue(player.getMoney().compareTo(before) < 0); + BigDecimal before = player.getMoney(); + exchange.buy("AAPL", new BigDecimal("5"), player); + assertTrue(player.getMoney().compareTo(before) < 0); } /** @@ -160,8 +160,8 @@ void testBuyDeductsMoneyFromPlayer() { */ @Test void testBuyAddsShareToPortfolio() { - exchange.buy("AAPL", new BigDecimal("5"), player); - assertFalse(player.getPortfolio().getShares("AAPL").isEmpty()); + exchange.buy("AAPL", new BigDecimal("5"), player); + assertFalse(player.getPortfolio().getShares("AAPL").isEmpty()); } /** @@ -169,13 +169,13 @@ void testBuyAddsShareToPortfolio() { */ @Test void testSellReturnsCommittedSale() { - exchange.buy("AAPL", new BigDecimal("5"), player); - Share share = player.getPortfolio().getShares("AAPL").get(0); + exchange.buy("AAPL", new BigDecimal("5"), player); + Share share = player.getPortfolio().getShares("AAPL").get(0); - Transaction t = exchange.sell(share, player); - assertNotNull(t); - assertTrue(t.isCommitted()); - assertInstanceOf(Sale.class, t); + Transaction t = exchange.sell(share, player); + assertNotNull(t); + assertTrue(t.isCommitted()); + assertInstanceOf(Sale.class, t); } /** @@ -183,12 +183,12 @@ void testSellReturnsCommittedSale() { */ @Test void testSellAddsMoney() { - exchange.buy("AAPL", new BigDecimal("5"), player); - Share share = player.getPortfolio().getShares("AAPL").get(0); - BigDecimal before = player.getMoney(); + exchange.buy("AAPL", new BigDecimal("5"), player); + Share share = player.getPortfolio().getShares("AAPL").get(0); + BigDecimal before = player.getMoney(); - exchange.sell(share, player); - assertTrue(player.getMoney().compareTo(before) > 0); + exchange.sell(share, player); + assertTrue(player.getMoney().compareTo(before) > 0); } /** @@ -196,11 +196,11 @@ void testSellAddsMoney() { */ @Test void testSellRemovesShareFromPortfolio() { - exchange.buy("AAPL", new BigDecimal("5"), player); - Share share = player.getPortfolio().getShares("AAPL").get(0); + exchange.buy("AAPL", new BigDecimal("5"), player); + Share share = player.getPortfolio().getShares("AAPL").get(0); - exchange.sell(share, player); - assertTrue(player.getPortfolio().getShares("AAPL").isEmpty()); + exchange.sell(share, player); + assertTrue(player.getPortfolio().getShares("AAPL").isEmpty()); } /** @@ -208,9 +208,9 @@ void testSellRemovesShareFromPortfolio() { */ @Test void testGetGainersReturnsRequestedLimit() { - exchange.advance(); // prices must have changed at least once for a meaningful sort - List gainers = exchange.getGainers(1); - assertEquals(1, gainers.size()); + exchange.advance(); // prices must have changed at least once for a meaningful sort + List gainers = exchange.getGainers(1); + assertEquals(1, gainers.size()); } /** @@ -218,9 +218,9 @@ void testGetGainersReturnsRequestedLimit() { */ @Test void testGetLosersReturnsRequestedLimit() { - exchange.advance(); - List losers = exchange.getLosers(1); - assertEquals(1, losers.size()); + exchange.advance(); + List losers = exchange.getLosers(1); + assertEquals(1, losers.size()); } // ---- Negative tests ---- @@ -230,9 +230,9 @@ void testGetLosersReturnsRequestedLimit() { */ @Test void testConstructorNullNameThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Exchange(null, List.of(apple)) - ); + assertThrows(IllegalArgumentException.class, () -> + new Exchange(null, List.of(apple)) + ); } /** @@ -240,9 +240,9 @@ void testConstructorNullNameThrows() { */ @Test void testConstructorBlankNameThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Exchange(" ", List.of(apple)) - ); + assertThrows(IllegalArgumentException.class, () -> + new Exchange(" ", List.of(apple)) + ); } /** @@ -250,9 +250,9 @@ void testConstructorBlankNameThrows() { */ @Test void testConstructorNullStockListThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Exchange("X", null) - ); + assertThrows(IllegalArgumentException.class, () -> + new Exchange("X", null) + ); } /** @@ -260,12 +260,12 @@ void testConstructorNullStockListThrows() { */ @Test void testConstructorNullStockEntryThrows() { - List withNull = new ArrayList<>(); - withNull.add(apple); - withNull.add(null); - assertThrows(IllegalArgumentException.class, () -> - new Exchange("X", withNull) - ); + List withNull = new ArrayList<>(); + withNull.add(apple); + withNull.add(null); + assertThrows(IllegalArgumentException.class, () -> + new Exchange("X", withNull) + ); } /** @@ -273,9 +273,9 @@ void testConstructorNullStockEntryThrows() { */ @Test void testBuyNullSymbolThrows() { - assertThrows(IllegalArgumentException.class, () -> - exchange.buy(null, new BigDecimal("5"), player) - ); + assertThrows(IllegalArgumentException.class, () -> + exchange.buy(null, new BigDecimal("5"), player) + ); } /** @@ -283,9 +283,9 @@ void testBuyNullSymbolThrows() { */ @Test void testBuyUnknownSymbolThrows() { - assertThrows(IllegalArgumentException.class, () -> - exchange.buy("MSFT", new BigDecimal("5"), player) - ); + assertThrows(IllegalArgumentException.class, () -> + exchange.buy("MSFT", new BigDecimal("5"), player) + ); } /** @@ -293,9 +293,9 @@ void testBuyUnknownSymbolThrows() { */ @Test void testBuyZeroQuantityThrows() { - assertThrows(IllegalArgumentException.class, () -> - exchange.buy("AAPL", BigDecimal.ZERO, player) - ); + assertThrows(IllegalArgumentException.class, () -> + exchange.buy("AAPL", BigDecimal.ZERO, player) + ); } /** @@ -303,9 +303,9 @@ void testBuyZeroQuantityThrows() { */ @Test void testBuyNegativeQuantityThrows() { - assertThrows(IllegalArgumentException.class, () -> - exchange.buy("AAPL", new BigDecimal("-1"), player) - ); + assertThrows(IllegalArgumentException.class, () -> + exchange.buy("AAPL", new BigDecimal("-1"), player) + ); } /** @@ -313,9 +313,9 @@ void testBuyNegativeQuantityThrows() { */ @Test void testBuyNullPlayerThrows() { - assertThrows(IllegalArgumentException.class, () -> - exchange.buy("AAPL", new BigDecimal("5"), null) - ); + assertThrows(IllegalArgumentException.class, () -> + exchange.buy("AAPL", new BigDecimal("5"), null) + ); } /** @@ -323,10 +323,10 @@ void testBuyNullPlayerThrows() { */ @Test void testBuyInsufficientFundsThrows() { - Player poorPlayer = new Player("Broke", new BigDecimal("1")); - assertThrows(IllegalStateException.class, () -> - exchange.buy("AAPL", new BigDecimal("5"), poorPlayer) - ); + Player poorPlayer = new Player("Broke", new BigDecimal("1")); + assertThrows(IllegalStateException.class, () -> + exchange.buy("AAPL", new BigDecimal("5"), poorPlayer) + ); } /** @@ -334,11 +334,11 @@ void testBuyInsufficientFundsThrows() { */ @Test void testSellNullPlayerThrows() { - exchange.buy("AAPL", new BigDecimal("5"), player); - Share share = player.getPortfolio().getShares("AAPL").get(0); - assertThrows(IllegalArgumentException.class, () -> - exchange.sell(share, null) - ); + exchange.buy("AAPL", new BigDecimal("5"), player); + Share share = player.getPortfolio().getShares("AAPL").get(0); + assertThrows(IllegalArgumentException.class, () -> + exchange.sell(share, null) + ); } /** @@ -346,10 +346,10 @@ void testSellNullPlayerThrows() { */ @Test void testSellShareNotInPortfolioThrows() { - Share unowned = new Share(apple, new BigDecimal("1"), new BigDecimal("100")); - assertThrows(IllegalStateException.class, () -> - exchange.sell(unowned, player) - ); + Share unowned = new Share(apple, new BigDecimal("1"), new BigDecimal("100")); + assertThrows(IllegalStateException.class, () -> + exchange.sell(unowned, player) + ); } /** @@ -357,9 +357,9 @@ void testSellShareNotInPortfolioThrows() { */ @Test void testAddNullObserverThrows() { - assertThrows(IllegalArgumentException.class, () -> - exchange.addObserver(null) - ); + assertThrows(IllegalArgumentException.class, () -> + exchange.addObserver(null) + ); } } diff --git a/src/test/java/PlayerTest.java b/src/test/java/PlayerTest.java index 880ac8f..cedf182 100644 --- a/src/test/java/PlayerTest.java +++ b/src/test/java/PlayerTest.java @@ -1,10 +1,10 @@ import static org.junit.jupiter.api.Assertions.*; +import Model.Player; +import java.math.BigDecimal; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import Model.Player; -import java.math.BigDecimal; /** * Unit tests for the Player class. @@ -16,7 +16,7 @@ public class PlayerTest { @BeforeEach void setUp() { - player = new Player("Jane", new BigDecimal("1000")); + player = new Player("Jane", new BigDecimal("1000")); } // ---- Positive tests ---- @@ -26,7 +26,7 @@ void setUp() { */ @Test void testGetName() { - assertEquals("Jane", player.getName()); + assertEquals("Jane", player.getName()); } /** @@ -34,7 +34,7 @@ void testGetName() { */ @Test void testGetMoneyMatchesStartingCapital() { - assertEquals(new BigDecimal("1000"), player.getMoney()); + assertEquals(new BigDecimal("1000"), player.getMoney()); } /** @@ -42,8 +42,8 @@ void testGetMoneyMatchesStartingCapital() { */ @Test void testAddMoney() { - player.addMoney(new BigDecimal("500")); - assertEquals(new BigDecimal("1500"), player.getMoney()); + player.addMoney(new BigDecimal("500")); + assertEquals(new BigDecimal("1500"), player.getMoney()); } /** @@ -51,8 +51,8 @@ void testAddMoney() { */ @Test void testAddMoneyZero() { - player.addMoney(BigDecimal.ZERO); - assertEquals(new BigDecimal("1000"), player.getMoney()); + player.addMoney(BigDecimal.ZERO); + assertEquals(new BigDecimal("1000"), player.getMoney()); } /** @@ -60,8 +60,8 @@ void testAddMoneyZero() { */ @Test void testWithdrawMoney() { - player.withdrawMoney(new BigDecimal("400")); - assertEquals(new BigDecimal("600"), player.getMoney()); + player.withdrawMoney(new BigDecimal("400")); + assertEquals(new BigDecimal("600"), player.getMoney()); } /** @@ -69,8 +69,8 @@ void testWithdrawMoney() { */ @Test void testWithdrawMoneyZero() { - player.withdrawMoney(BigDecimal.ZERO); - assertEquals(new BigDecimal("1000"), player.getMoney()); + player.withdrawMoney(BigDecimal.ZERO); + assertEquals(new BigDecimal("1000"), player.getMoney()); } /** @@ -78,7 +78,7 @@ void testWithdrawMoneyZero() { */ @Test void testPortfolioInitiallyEmpty() { - assertTrue(player.getPortfolio().getShares().isEmpty()); + assertTrue(player.getPortfolio().getShares().isEmpty()); } /** @@ -86,7 +86,7 @@ void testPortfolioInitiallyEmpty() { */ @Test void testTransactionArchiveInitiallyEmpty() { - assertTrue(player.getTransactionArchive().isEmpty()); + assertTrue(player.getTransactionArchive().isEmpty()); } /** @@ -94,8 +94,8 @@ void testTransactionArchiveInitiallyEmpty() { */ @Test void testStartingMoneyZeroAllowed() { - Player broke = new Player("Broke", BigDecimal.ZERO); - assertEquals(BigDecimal.ZERO, broke.getMoney()); + Player broke = new Player("Broke", BigDecimal.ZERO); + assertEquals(BigDecimal.ZERO, broke.getMoney()); } // ---- Negative tests ---- @@ -105,9 +105,9 @@ void testStartingMoneyZeroAllowed() { */ @Test void testNullNameThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Player(null, new BigDecimal("1000")) - ); + assertThrows(IllegalArgumentException.class, () -> + new Player(null, new BigDecimal("1000")) + ); } /** @@ -115,9 +115,9 @@ void testNullNameThrows() { */ @Test void testBlankNameThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Player(" ", new BigDecimal("1000")) - ); + assertThrows(IllegalArgumentException.class, () -> + new Player(" ", new BigDecimal("1000")) + ); } /** @@ -125,9 +125,9 @@ void testBlankNameThrows() { */ @Test void testNullStartingMoneyThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Player("Jane", null) - ); + assertThrows(IllegalArgumentException.class, () -> + new Player("Jane", null) + ); } /** @@ -135,9 +135,9 @@ void testNullStartingMoneyThrows() { */ @Test void testNegativeStartingMoneyThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Player("Jane", new BigDecimal("-1")) - ); + assertThrows(IllegalArgumentException.class, () -> + new Player("Jane", new BigDecimal("-1")) + ); } /** @@ -145,9 +145,9 @@ void testNegativeStartingMoneyThrows() { */ @Test void testAddNullAmountThrows() { - assertThrows(IllegalArgumentException.class, () -> - player.addMoney(null) - ); + assertThrows(IllegalArgumentException.class, () -> + player.addMoney(null) + ); } /** @@ -155,9 +155,9 @@ void testAddNullAmountThrows() { */ @Test void testAddNegativeAmountThrows() { - assertThrows(IllegalArgumentException.class, () -> - player.addMoney(new BigDecimal("-100")) - ); + assertThrows(IllegalArgumentException.class, () -> + player.addMoney(new BigDecimal("-100")) + ); } /** @@ -165,9 +165,9 @@ void testAddNegativeAmountThrows() { */ @Test void testWithdrawNullAmountThrows() { - assertThrows(IllegalArgumentException.class, () -> - player.withdrawMoney(null) - ); + assertThrows(IllegalArgumentException.class, () -> + player.withdrawMoney(null) + ); } /** @@ -175,9 +175,9 @@ void testWithdrawNullAmountThrows() { */ @Test void testWithdrawNegativeAmountThrows() { - assertThrows(IllegalArgumentException.class, () -> - player.withdrawMoney(new BigDecimal("-100")) - ); + assertThrows(IllegalArgumentException.class, () -> + player.withdrawMoney(new BigDecimal("-100")) + ); } } diff --git a/src/test/java/PortfolioTest.java b/src/test/java/PortfolioTest.java index d55259d..359f3c5 100644 --- a/src/test/java/PortfolioTest.java +++ b/src/test/java/PortfolioTest.java @@ -1,4 +1,5 @@ import static org.junit.jupiter.api.Assertions.*; + import Model.Portfolio; import Model.Share; import Model.Stock; @@ -20,9 +21,9 @@ public class PortfolioTest { @BeforeEach void setUp() { - portfolio = new Portfolio(); - stock = new Stock("AAPL", "Apple", new BigDecimal("150")); - share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); + portfolio = new Portfolio(); + stock = new Stock("AAPL", "Apple", new BigDecimal("150")); + share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); } // ---- Positive tests ---- @@ -32,9 +33,9 @@ void setUp() { */ @Test void testAddShare() { - boolean result = portfolio.addShare(share); - assertTrue(result); - assertTrue(portfolio.contains(share)); + boolean result = portfolio.addShare(share); + assertTrue(result); + assertTrue(portfolio.contains(share)); } /** @@ -42,13 +43,13 @@ void testAddShare() { */ @Test void testAddMultipleShares() { - Stock stock2 = new Stock("GOOGL", "Google", new BigDecimal("200")); - Share share2 = new Share(stock2, new BigDecimal("5"), new BigDecimal("190")); + Stock stock2 = new Stock("GOOGL", "Google", new BigDecimal("200")); + Share share2 = new Share(stock2, new BigDecimal("5"), new BigDecimal("190")); - portfolio.addShare(share); - portfolio.addShare(share2); + portfolio.addShare(share); + portfolio.addShare(share2); - assertEquals(2, portfolio.getShares().size()); + assertEquals(2, portfolio.getShares().size()); } /** @@ -56,10 +57,10 @@ void testAddMultipleShares() { */ @Test void testRemoveShare() { - portfolio.addShare(share); - boolean result = portfolio.removeShare(share); - assertTrue(result); - assertFalse(portfolio.contains(share)); + portfolio.addShare(share); + boolean result = portfolio.removeShare(share); + assertTrue(result); + assertFalse(portfolio.contains(share)); } /** @@ -67,8 +68,8 @@ void testRemoveShare() { */ @Test void testRemoveShareNotInPortfolioReturnsFalse() { - boolean result = portfolio.removeShare(share); - assertFalse(result); + boolean result = portfolio.removeShare(share); + assertFalse(result); } /** @@ -76,10 +77,10 @@ void testRemoveShareNotInPortfolioReturnsFalse() { */ @Test void testGetSharesReturnsAll() { - portfolio.addShare(share); - List shares = portfolio.getShares(); - assertEquals(1, shares.size()); - assertTrue(shares.contains(share)); + portfolio.addShare(share); + List shares = portfolio.getShares(); + assertEquals(1, shares.size()); + assertTrue(shares.contains(share)); } /** @@ -88,10 +89,10 @@ void testGetSharesReturnsAll() { */ @Test void testGetSharesIsDefensiveCopy() { - portfolio.addShare(share); - List copy = portfolio.getShares(); - copy.clear(); - assertEquals(1, portfolio.getShares().size()); + portfolio.addShare(share); + List copy = portfolio.getShares(); + copy.clear(); + assertEquals(1, portfolio.getShares().size()); } /** @@ -99,15 +100,15 @@ void testGetSharesIsDefensiveCopy() { */ @Test void testGetSharesBySymbol() { - Stock stock2 = new Stock("GOOGL", "Google", new BigDecimal("200")); - Share share2 = new Share(stock2, new BigDecimal("5"), new BigDecimal("190")); + Stock stock2 = new Stock("GOOGL", "Google", new BigDecimal("200")); + Share share2 = new Share(stock2, new BigDecimal("5"), new BigDecimal("190")); - portfolio.addShare(share); - portfolio.addShare(share2); + portfolio.addShare(share); + portfolio.addShare(share2); - List result = portfolio.getShares("AAPL"); - assertEquals(1, result.size()); - assertTrue(result.contains(share)); + List result = portfolio.getShares("AAPL"); + assertEquals(1, result.size()); + assertTrue(result.contains(share)); } /** @@ -115,9 +116,9 @@ void testGetSharesBySymbol() { */ @Test void testGetSharesBySymbolNoMatch() { - portfolio.addShare(share); - List result = portfolio.getShares("MSFT"); - assertTrue(result.isEmpty()); + portfolio.addShare(share); + List result = portfolio.getShares("MSFT"); + assertTrue(result.isEmpty()); } /** @@ -125,8 +126,8 @@ void testGetSharesBySymbolNoMatch() { */ @Test void testContainsReturnsTrueWhenPresent() { - portfolio.addShare(share); - assertTrue(portfolio.contains(share)); + portfolio.addShare(share); + assertTrue(portfolio.contains(share)); } /** @@ -134,7 +135,7 @@ void testContainsReturnsTrueWhenPresent() { */ @Test void testContainsReturnsFalseWhenAbsent() { - assertFalse(portfolio.contains(share)); + assertFalse(portfolio.contains(share)); } /** @@ -142,7 +143,7 @@ void testContainsReturnsFalseWhenAbsent() { */ @Test void testGetNetWorthEmptyPortfolio() { - assertEquals(BigDecimal.ZERO, portfolio.getNetWorth()); + assertEquals(BigDecimal.ZERO, portfolio.getNetWorth()); } /** @@ -150,9 +151,9 @@ void testGetNetWorthEmptyPortfolio() { */ @Test void testGetNetWorthWithShares() { - portfolio.addShare(share); - // net worth should be positive when portfolio has holdings - assertTrue(portfolio.getNetWorth().compareTo(BigDecimal.ZERO) > 0); + portfolio.addShare(share); + // net worth should be positive when portfolio has holdings + assertTrue(portfolio.getNetWorth().compareTo(BigDecimal.ZERO) > 0); } // ---- Negative tests ---- @@ -162,9 +163,9 @@ void testGetNetWorthWithShares() { */ @Test void testAddNullShareThrows() { - assertThrows(IllegalArgumentException.class, () -> - portfolio.addShare(null) - ); + assertThrows(IllegalArgumentException.class, () -> + portfolio.addShare(null) + ); } /** @@ -172,9 +173,9 @@ void testAddNullShareThrows() { */ @Test void testRemoveNullShareThrows() { - assertThrows(IllegalArgumentException.class, () -> - portfolio.removeShare(null) - ); + assertThrows(IllegalArgumentException.class, () -> + portfolio.removeShare(null) + ); } /** @@ -182,9 +183,9 @@ void testRemoveNullShareThrows() { */ @Test void testContainsNullShareThrows() { - assertThrows(IllegalArgumentException.class, () -> - portfolio.contains(null) - ); + assertThrows(IllegalArgumentException.class, () -> + portfolio.contains(null) + ); } /** @@ -192,9 +193,9 @@ void testContainsNullShareThrows() { */ @Test void testGetSharesByNullSymbolThrows() { - assertThrows(IllegalArgumentException.class, () -> - portfolio.getShares((String) null) - ); + assertThrows(IllegalArgumentException.class, () -> + portfolio.getShares((String) null) + ); } /** @@ -202,8 +203,8 @@ void testGetSharesByNullSymbolThrows() { */ @Test void testGetSharesByBlankSymbolThrows() { - assertThrows(IllegalArgumentException.class, () -> - portfolio.getShares(" ") - ); + assertThrows(IllegalArgumentException.class, () -> + portfolio.getShares(" ") + ); } } diff --git a/src/test/java/PurchaseTest.java b/src/test/java/PurchaseTest.java index 26d2dd9..de1c2ca 100644 --- a/src/test/java/PurchaseTest.java +++ b/src/test/java/PurchaseTest.java @@ -1,13 +1,12 @@ import static org.junit.jupiter.api.Assertions.*; -import java.math.BigDecimal; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import Model.Player; import Model.Purchase; import Model.Share; import Model.Stock; +import java.math.BigDecimal; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; /** * Unit tests for the Purchase class. @@ -22,10 +21,10 @@ public class PurchaseTest { @BeforeEach void setUp() { - stock = new Stock("AAPL", "Apple", new BigDecimal("100")); - share = new Share(stock, new BigDecimal("20"), new BigDecimal("50")); - purchase = new Purchase(share, 1); - player = new Player("Jane", new BigDecimal("500000")); + stock = new Stock("AAPL", "Apple", new BigDecimal("100")); + share = new Share(stock, new BigDecimal("20"), new BigDecimal("50")); + purchase = new Purchase(share, 1); + player = new Player("Jane", new BigDecimal("500000")); } // ---- Positive tests ---- @@ -35,9 +34,9 @@ void setUp() { */ @Test void testCommitDeductsMoneyFromPlayer() { - BigDecimal before = player.getMoney(); - purchase.commit(player); - assertTrue(player.getMoney().compareTo(before) < 0); + BigDecimal before = player.getMoney(); + purchase.commit(player); + assertTrue(player.getMoney().compareTo(before) < 0); } /** @@ -45,8 +44,8 @@ void testCommitDeductsMoneyFromPlayer() { */ @Test void testCommitAddsShareToPortfolio() { - purchase.commit(player); - assertTrue(player.getPortfolio().contains(share)); + purchase.commit(player); + assertTrue(player.getPortfolio().contains(share)); } /** @@ -54,8 +53,8 @@ void testCommitAddsShareToPortfolio() { */ @Test void testCommitRecordedInArchive() { - purchase.commit(player); - assertTrue(player.getTransactionArchive().getAllTransactions().contains(purchase)); + purchase.commit(player); + assertTrue(player.getTransactionArchive().getAllTransactions().contains(purchase)); } /** @@ -63,8 +62,8 @@ void testCommitRecordedInArchive() { */ @Test void testCommitSetsCommittedFlag() { - purchase.commit(player); - assertTrue(purchase.isCommitted()); + purchase.commit(player); + assertTrue(purchase.isCommitted()); } /** @@ -72,7 +71,7 @@ void testCommitSetsCommittedFlag() { */ @Test void testGetShareReturnsCorrectShare() { - assertEquals(share, purchase.getShare()); + assertEquals(share, purchase.getShare()); } /** @@ -80,7 +79,7 @@ void testGetShareReturnsCorrectShare() { */ @Test void testGetWeekReturnsCorrectWeek() { - assertEquals(1, purchase.getWeek()); + assertEquals(1, purchase.getWeek()); } // ---- Negative tests ---- @@ -90,9 +89,9 @@ void testGetWeekReturnsCorrectWeek() { */ @Test void testCommitNullPlayerThrows() { - assertThrows(IllegalArgumentException.class, () -> - purchase.commit(null) - ); + assertThrows(IllegalArgumentException.class, () -> + purchase.commit(null) + ); } /** @@ -100,10 +99,10 @@ void testCommitNullPlayerThrows() { */ @Test void testCommitInsufficientFundsThrows() { - Player poorPlayer = new Player("Broke", new BigDecimal("1")); - assertThrows(IllegalStateException.class, () -> - purchase.commit(poorPlayer) - ); + Player poorPlayer = new Player("Broke", new BigDecimal("1")); + assertThrows(IllegalStateException.class, () -> + purchase.commit(poorPlayer) + ); } /** @@ -111,10 +110,10 @@ void testCommitInsufficientFundsThrows() { */ @Test void testCommitAlreadyCommittedThrows() { - purchase.commit(player); - assertThrows(IllegalStateException.class, () -> - purchase.commit(player) - ); + purchase.commit(player); + assertThrows(IllegalStateException.class, () -> + purchase.commit(player) + ); } /** @@ -122,9 +121,9 @@ void testCommitAlreadyCommittedThrows() { */ @Test void testConstructorWeekZeroThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Purchase(share, 0) - ); + assertThrows(IllegalArgumentException.class, () -> + new Purchase(share, 0) + ); } /** @@ -132,9 +131,9 @@ void testConstructorWeekZeroThrows() { */ @Test void testConstructorNegativeWeekThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Purchase(share, -5) - ); + assertThrows(IllegalArgumentException.class, () -> + new Purchase(share, -5) + ); } } diff --git a/src/test/java/SaleTest.java b/src/test/java/SaleTest.java index 39d5516..31efe4d 100644 --- a/src/test/java/SaleTest.java +++ b/src/test/java/SaleTest.java @@ -1,13 +1,13 @@ -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; import Model.Player; import Model.Sale; import Model.Share; import Model.Stock; -import static org.junit.jupiter.api.Assertions.*; - import java.math.BigDecimal; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + /** * Unit tests for the Sale class. @@ -22,11 +22,11 @@ public class SaleTest { @BeforeEach void setUp() { - stock = new Stock("AAPL", "Apple", new BigDecimal("100")); - share = new Share(stock, new BigDecimal("20"), new BigDecimal("80")); - sale = new Sale(share, 1); - player = new Player("Jane", new BigDecimal("500")); - player.getPortfolio().addShare(share); + stock = new Stock("AAPL", "Apple", new BigDecimal("100")); + share = new Share(stock, new BigDecimal("20"), new BigDecimal("80")); + sale = new Sale(share, 1); + player = new Player("Jane", new BigDecimal("500")); + player.getPortfolio().addShare(share); } // ---- Positive tests ---- @@ -36,9 +36,9 @@ void setUp() { */ @Test void testCommitAddsMoney() { - BigDecimal before = player.getMoney(); - sale.commit(player); - assertTrue(player.getMoney().compareTo(before) > 0); + BigDecimal before = player.getMoney(); + sale.commit(player); + assertTrue(player.getMoney().compareTo(before) > 0); } /** @@ -46,8 +46,8 @@ void testCommitAddsMoney() { */ @Test void testCommitRemovesShareFromPortfolio() { - sale.commit(player); - assertFalse(player.getPortfolio().contains(share)); + sale.commit(player); + assertFalse(player.getPortfolio().contains(share)); } /** @@ -55,8 +55,8 @@ void testCommitRemovesShareFromPortfolio() { */ @Test void testCommitRecordedInArchive() { - sale.commit(player); - assertTrue(player.getTransactionArchive().getAllTransactions().contains(sale)); + sale.commit(player); + assertTrue(player.getTransactionArchive().getAllTransactions().contains(sale)); } /** @@ -64,8 +64,8 @@ void testCommitRecordedInArchive() { */ @Test void testCommitSetsCommittedFlag() { - sale.commit(player); - assertTrue(sale.isCommitted()); + sale.commit(player); + assertTrue(sale.isCommitted()); } /** @@ -73,7 +73,7 @@ void testCommitSetsCommittedFlag() { */ @Test void testGetShareReturnsCorrectShare() { - assertEquals(share, sale.getShare()); + assertEquals(share, sale.getShare()); } /** @@ -81,7 +81,7 @@ void testGetShareReturnsCorrectShare() { */ @Test void testGetWeekReturnsCorrectWeek() { - assertEquals(1, sale.getWeek()); + assertEquals(1, sale.getWeek()); } // ---- Negative tests ---- @@ -91,9 +91,9 @@ void testGetWeekReturnsCorrectWeek() { */ @Test void testCommitNullPlayerThrows() { - assertThrows(IllegalArgumentException.class, () -> - sale.commit(null) - ); + assertThrows(IllegalArgumentException.class, () -> + sale.commit(null) + ); } /** @@ -101,10 +101,10 @@ void testCommitNullPlayerThrows() { */ @Test void testCommitAlreadyCommittedThrows() { - sale.commit(player); - assertThrows(IllegalStateException.class, () -> - sale.commit(player) - ); + sale.commit(player); + assertThrows(IllegalStateException.class, () -> + sale.commit(player) + ); } /** @@ -112,10 +112,10 @@ void testCommitAlreadyCommittedThrows() { */ @Test void testCommitShareNotInPortfolioThrows() { - Player otherPlayer = new Player("Bob", new BigDecimal("500")); - assertThrows(IllegalStateException.class, () -> - sale.commit(otherPlayer) - ); + Player otherPlayer = new Player("Bob", new BigDecimal("500")); + assertThrows(IllegalStateException.class, () -> + sale.commit(otherPlayer) + ); } /** @@ -123,9 +123,9 @@ void testCommitShareNotInPortfolioThrows() { */ @Test void testConstructorWeekZeroThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Sale(share, 0) - ); + assertThrows(IllegalArgumentException.class, () -> + new Sale(share, 0) + ); } /** @@ -133,9 +133,9 @@ void testConstructorWeekZeroThrows() { */ @Test void testConstructorNegativeWeekThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Sale(share, -3) - ); + assertThrows(IllegalArgumentException.class, () -> + new Sale(share, -3) + ); } } diff --git a/src/test/java/ShareTest.java b/src/test/java/ShareTest.java index af8d2cc..37b5422 100644 --- a/src/test/java/ShareTest.java +++ b/src/test/java/ShareTest.java @@ -1,11 +1,12 @@ import static org.junit.jupiter.api.Assertions.*; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - import Model.Share; import Model.Stock; import java.math.BigDecimal; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + + /** * Unit tests for the Share class. @@ -17,7 +18,7 @@ public class ShareTest { @BeforeEach void setUp() { - stock = new Stock("AAPL", "Apple", new BigDecimal("150")); + stock = new Stock("AAPL", "Apple", new BigDecimal("150")); } // ---- Positive tests ---- @@ -27,8 +28,8 @@ void setUp() { */ @Test void testShareConstructorNotNull() { - Share share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); - assertNotNull(share); + Share share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); + assertNotNull(share); } /** @@ -36,8 +37,8 @@ void testShareConstructorNotNull() { */ @Test void testGetStock() { - Share share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); - assertEquals(stock, share.getStock()); + Share share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); + assertEquals(stock, share.getStock()); } /** @@ -45,8 +46,8 @@ void testGetStock() { */ @Test void testGetQuantity() { - Share share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); - assertEquals(new BigDecimal("10"), share.getQuantity()); + Share share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); + assertEquals(new BigDecimal("10"), share.getQuantity()); } /** @@ -54,8 +55,8 @@ void testGetQuantity() { */ @Test void testGetPurchasePrice() { - Share share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); - assertEquals(new BigDecimal("140"), share.getPurchasePrice()); + Share share = new Share(stock, new BigDecimal("10"), new BigDecimal("140")); + assertEquals(new BigDecimal("140"), share.getPurchasePrice()); } /** @@ -63,9 +64,9 @@ void testGetPurchasePrice() { */ @Test void testPurchasePriceZeroAllowed() { - // buying at price 0 is an edge case but not invalid - Share share = new Share(stock, new BigDecimal("5"), BigDecimal.ZERO); - assertEquals(BigDecimal.ZERO, share.getPurchasePrice()); + // buying at price 0 is an edge case but not invalid + Share share = new Share(stock, new BigDecimal("5"), BigDecimal.ZERO); + assertEquals(BigDecimal.ZERO, share.getPurchasePrice()); } // ---- Negative tests ---- @@ -75,9 +76,9 @@ void testPurchasePriceZeroAllowed() { */ @Test void testNullStockThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Share(null, new BigDecimal("10"), new BigDecimal("100")) - ); + assertThrows(IllegalArgumentException.class, () -> + new Share(null, new BigDecimal("10"), new BigDecimal("100")) + ); } /** @@ -85,9 +86,9 @@ void testNullStockThrows() { */ @Test void testNullQuantityThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Share(stock, null, new BigDecimal("100")) - ); + assertThrows(IllegalArgumentException.class, () -> + new Share(stock, null, new BigDecimal("100")) + ); } /** @@ -95,9 +96,9 @@ void testNullQuantityThrows() { */ @Test void testZeroQuantityThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Share(stock, BigDecimal.ZERO, new BigDecimal("100")) - ); + assertThrows(IllegalArgumentException.class, () -> + new Share(stock, BigDecimal.ZERO, new BigDecimal("100")) + ); } /** @@ -105,9 +106,9 @@ void testZeroQuantityThrows() { */ @Test void testNegativeQuantityThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Share(stock, new BigDecimal("-5"), new BigDecimal("100")) - ); + assertThrows(IllegalArgumentException.class, () -> + new Share(stock, new BigDecimal("-5"), new BigDecimal("100")) + ); } /** @@ -115,9 +116,9 @@ void testNegativeQuantityThrows() { */ @Test void testNullPurchasePriceThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Share(stock, new BigDecimal("10"), null) - ); + assertThrows(IllegalArgumentException.class, () -> + new Share(stock, new BigDecimal("10"), null) + ); } /** @@ -125,9 +126,9 @@ void testNullPurchasePriceThrows() { */ @Test void testNegativePurchasePriceThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Share(stock, new BigDecimal("10"), new BigDecimal("-50")) - ); + assertThrows(IllegalArgumentException.class, () -> + new Share(stock, new BigDecimal("10"), new BigDecimal("-50")) + ); } } diff --git a/src/test/java/StockTest.java b/src/test/java/StockTest.java index 5addd52..c07c75b 100644 --- a/src/test/java/StockTest.java +++ b/src/test/java/StockTest.java @@ -17,7 +17,7 @@ public class StockTest { @BeforeEach void setUp() { - stock = new Stock("AAPL", "Apple", new BigDecimal("1000")); + stock = new Stock("AAPL", "Apple", new BigDecimal("1000")); } // ---- Positive tests ---- @@ -27,7 +27,7 @@ void setUp() { */ @Test void testGetSymbol() { - assertEquals("AAPL", stock.getSymbol()); + assertEquals("AAPL", stock.getSymbol()); } /** @@ -35,7 +35,7 @@ void testGetSymbol() { */ @Test void testGetCompany() { - assertEquals("Apple", stock.getCompany()); + assertEquals("Apple", stock.getCompany()); } /** @@ -43,7 +43,7 @@ void testGetCompany() { */ @Test void testGetSalesPrice() { - assertEquals(new BigDecimal("1000"), stock.getSalesPrice()); + assertEquals(new BigDecimal("1000"), stock.getSalesPrice()); } /** @@ -51,8 +51,8 @@ void testGetSalesPrice() { */ @Test void testGetSalesPriceZero() { - Stock zeroStock = new Stock("ZERO", "ZeroCorp", new BigDecimal("0")); - assertEquals(new BigDecimal("0"), zeroStock.getSalesPrice()); + Stock zeroStock = new Stock("ZERO", "ZeroCorp", new BigDecimal("0")); + assertEquals(new BigDecimal("0"), zeroStock.getSalesPrice()); } /** @@ -60,8 +60,8 @@ void testGetSalesPriceZero() { */ @Test void testAddNewSalesPrice() { - stock.addNewSalesPrice(new BigDecimal("1200")); - assertEquals(new BigDecimal("1200"), stock.getSalesPrice()); + stock.addNewSalesPrice(new BigDecimal("1200")); + assertEquals(new BigDecimal("1200"), stock.getSalesPrice()); } /** @@ -69,8 +69,8 @@ void testAddNewSalesPrice() { */ @Test void testAddNewSalesPriceZero() { - stock.addNewSalesPrice(new BigDecimal("0")); - assertEquals(new BigDecimal("0"), stock.getSalesPrice()); + stock.addNewSalesPrice(new BigDecimal("0")); + assertEquals(new BigDecimal("0"), stock.getSalesPrice()); } /** @@ -78,9 +78,9 @@ void testAddNewSalesPriceZero() { */ @Test void testGetHistoricalPricesInitial() { - List history = stock.getHistoricalPrices(); - assertEquals(1, history.size()); - assertEquals(new BigDecimal("1000"), history.get(0)); + List history = stock.getHistoricalPrices(); + assertEquals(1, history.size()); + assertEquals(new BigDecimal("1000"), history.get(0)); } /** @@ -88,9 +88,9 @@ void testGetHistoricalPricesInitial() { */ @Test void testGetHistoricalPricesAfterUpdates() { - stock.addNewSalesPrice(new BigDecimal("1100")); - stock.addNewSalesPrice(new BigDecimal("1200")); - assertEquals(3, stock.getHistoricalPrices().size()); + stock.addNewSalesPrice(new BigDecimal("1100")); + stock.addNewSalesPrice(new BigDecimal("1200")); + assertEquals(3, stock.getHistoricalPrices().size()); } /** @@ -98,9 +98,9 @@ void testGetHistoricalPricesAfterUpdates() { */ @Test void testGetHighestPrice() { - stock.addNewSalesPrice(new BigDecimal("1500")); - stock.addNewSalesPrice(new BigDecimal("800")); - assertEquals(new BigDecimal("1500"), stock.getHighestPrice()); + stock.addNewSalesPrice(new BigDecimal("1500")); + stock.addNewSalesPrice(new BigDecimal("800")); + assertEquals(new BigDecimal("1500"), stock.getHighestPrice()); } /** @@ -108,9 +108,9 @@ void testGetHighestPrice() { */ @Test void testGetLowestPrice() { - stock.addNewSalesPrice(new BigDecimal("1500")); - stock.addNewSalesPrice(new BigDecimal("800")); - assertEquals(new BigDecimal("800"), stock.getLowestPrice()); + stock.addNewSalesPrice(new BigDecimal("1500")); + stock.addNewSalesPrice(new BigDecimal("800")); + assertEquals(new BigDecimal("800"), stock.getLowestPrice()); } /** @@ -118,8 +118,8 @@ void testGetLowestPrice() { */ @Test void testGetLatestPriceChangePositive() { - stock.addNewSalesPrice(new BigDecimal("1100")); - assertEquals(new BigDecimal("100"), stock.getLatestPriceChange()); + stock.addNewSalesPrice(new BigDecimal("1100")); + assertEquals(new BigDecimal("100"), stock.getLatestPriceChange()); } /** @@ -127,8 +127,8 @@ void testGetLatestPriceChangePositive() { */ @Test void testGetLatestPriceChangeNegative() { - stock.addNewSalesPrice(new BigDecimal("900")); - assertEquals(new BigDecimal("-100"), stock.getLatestPriceChange()); + stock.addNewSalesPrice(new BigDecimal("900")); + assertEquals(new BigDecimal("-100"), stock.getLatestPriceChange()); } /** @@ -136,7 +136,7 @@ void testGetLatestPriceChangeNegative() { */ @Test void testGetLatestPriceChangeOnlyOnePrice() { - assertEquals(BigDecimal.ZERO, stock.getLatestPriceChange()); + assertEquals(BigDecimal.ZERO, stock.getLatestPriceChange()); } /** @@ -144,9 +144,9 @@ void testGetLatestPriceChangeOnlyOnePrice() { */ @Test void testHistoricalPricesIsDefensiveCopy() { - List history = stock.getHistoricalPrices(); - history.add(new BigDecimal("9999")); - assertEquals(1, stock.getHistoricalPrices().size()); + List history = stock.getHistoricalPrices(); + history.add(new BigDecimal("9999")); + assertEquals(1, stock.getHistoricalPrices().size()); } // ---- Negative tests ---- @@ -156,9 +156,9 @@ void testHistoricalPricesIsDefensiveCopy() { */ @Test void testNullSymbolThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Stock(null, "Apple", new BigDecimal("100")) - ); + assertThrows(IllegalArgumentException.class, () -> + new Stock(null, "Apple", new BigDecimal("100")) + ); } /** @@ -166,9 +166,9 @@ void testNullSymbolThrows() { */ @Test void testBlankSymbolThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Stock(" ", "Apple", new BigDecimal("100")) - ); + assertThrows(IllegalArgumentException.class, () -> + new Stock(" ", "Apple", new BigDecimal("100")) + ); } /** @@ -176,9 +176,9 @@ void testBlankSymbolThrows() { */ @Test void testNullCompanyThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Stock("AAPL", null, new BigDecimal("100")) - ); + assertThrows(IllegalArgumentException.class, () -> + new Stock("AAPL", null, new BigDecimal("100")) + ); } /** @@ -186,9 +186,9 @@ void testNullCompanyThrows() { */ @Test void testBlankCompanyThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Stock("AAPL", " ", new BigDecimal("100")) - ); + assertThrows(IllegalArgumentException.class, () -> + new Stock("AAPL", " ", new BigDecimal("100")) + ); } /** @@ -196,9 +196,9 @@ void testBlankCompanyThrows() { */ @Test void testNullInitialPriceThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Stock("AAPL", "Apple", null) - ); + assertThrows(IllegalArgumentException.class, () -> + new Stock("AAPL", "Apple", null) + ); } /** @@ -206,9 +206,9 @@ void testNullInitialPriceThrows() { */ @Test void testNegativeInitialPriceThrows() { - assertThrows(IllegalArgumentException.class, () -> - new Stock("AAPL", "Apple", new BigDecimal("-1")) - ); + assertThrows(IllegalArgumentException.class, () -> + new Stock("AAPL", "Apple", new BigDecimal("-1")) + ); } /** @@ -216,9 +216,9 @@ void testNegativeInitialPriceThrows() { */ @Test void testAddNullPriceThrows() { - assertThrows(IllegalArgumentException.class, () -> - stock.addNewSalesPrice(null) - ); + assertThrows(IllegalArgumentException.class, () -> + stock.addNewSalesPrice(null) + ); } /** @@ -226,9 +226,9 @@ void testAddNullPriceThrows() { */ @Test void testAddNegativePriceThrows() { - assertThrows(IllegalArgumentException.class, () -> - stock.addNewSalesPrice(new BigDecimal("-10")) - ); + assertThrows(IllegalArgumentException.class, () -> + stock.addNewSalesPrice(new BigDecimal("-10")) + ); } } diff --git a/src/test/java/TransactionArchiveTest.java b/src/test/java/TransactionArchiveTest.java index c3a1fa1..df7ed0c 100644 --- a/src/test/java/TransactionArchiveTest.java +++ b/src/test/java/TransactionArchiveTest.java @@ -25,11 +25,11 @@ public class TransactionArchiveTest { @BeforeEach void setUp() { - archive = new TransactionArchive(); - stock = new Stock("AAPL", "Apple", new BigDecimal("100")); - share = new Share(stock, new BigDecimal("10"), new BigDecimal("90")); - purchase = new Purchase(share, 1); - sale = new Sale(share, 2); + archive = new TransactionArchive(); + stock = new Stock("AAPL", "Apple", new BigDecimal("100")); + share = new Share(stock, new BigDecimal("10"), new BigDecimal("90")); + purchase = new Purchase(share, 1); + sale = new Sale(share, 2); } // ---- Positive tests ---- @@ -39,7 +39,7 @@ void setUp() { */ @Test void testIsEmptyOnCreation() { - assertTrue(archive.isEmpty()); + assertTrue(archive.isEmpty()); } /** @@ -47,8 +47,8 @@ void testIsEmptyOnCreation() { */ @Test void testIsEmptyAfterAdd() { - archive.add(purchase); - assertFalse(archive.isEmpty()); + archive.add(purchase); + assertFalse(archive.isEmpty()); } /** @@ -56,7 +56,7 @@ void testIsEmptyAfterAdd() { */ @Test void testAddReturnsTrue() { - assertTrue(archive.add(purchase)); + assertTrue(archive.add(purchase)); } /** @@ -64,9 +64,9 @@ void testAddReturnsTrue() { */ @Test void testGetAllTransactions() { - archive.add(purchase); - archive.add(sale); - assertEquals(2, archive.getAllTransactions().size()); + archive.add(purchase); + archive.add(sale); + assertEquals(2, archive.getAllTransactions().size()); } /** @@ -74,10 +74,10 @@ void testGetAllTransactions() { */ @Test void testGetAllTransactionsIsDefensiveCopy() { - archive.add(purchase); - List copy = archive.getAllTransactions(); - copy.clear(); - assertEquals(1, archive.getAllTransactions().size()); + archive.add(purchase); + List copy = archive.getAllTransactions(); + copy.clear(); + assertEquals(1, archive.getAllTransactions().size()); } /** @@ -85,12 +85,12 @@ void testGetAllTransactionsIsDefensiveCopy() { */ @Test void testGetTransactionsByWeek() { - archive.add(purchase); // week 1 - archive.add(sale); // week 2 + archive.add(purchase); // week 1 + archive.add(sale); // week 2 - List week1 = archive.getTransactions(1); - assertEquals(1, week1.size()); - assertTrue(week1.contains(purchase)); + List week1 = archive.getTransactions(1); + assertEquals(1, week1.size()); + assertTrue(week1.contains(purchase)); } /** @@ -98,8 +98,8 @@ void testGetTransactionsByWeek() { */ @Test void testGetTransactionsByWeekNoMatch() { - archive.add(purchase); // week 1 - assertTrue(archive.getTransactions(99).isEmpty()); + archive.add(purchase); // week 1 + assertTrue(archive.getTransactions(99).isEmpty()); } /** @@ -107,12 +107,12 @@ void testGetTransactionsByWeekNoMatch() { */ @Test void testGetPurchaseByWeek() { - archive.add(purchase); // week 1 - archive.add(sale); // week 2 + archive.add(purchase); // week 1 + archive.add(sale); // week 2 - List purchases = archive.getPurchase(1); - assertEquals(1, purchases.size()); - assertTrue(purchases.contains(purchase)); + List purchases = archive.getPurchase(1); + assertEquals(1, purchases.size()); + assertTrue(purchases.contains(purchase)); } /** @@ -120,11 +120,11 @@ void testGetPurchaseByWeek() { */ @Test void testGetPurchaseByWeekExcludesSales() { - // Add a sale at week 1 — should not appear in getPurchase(1) - Sale saleWeek1 = new Sale(share, 1); - archive.add(saleWeek1); + // Add a sale at week 1 — should not appear in getPurchase(1) + Sale saleWeek1 = new Sale(share, 1); + archive.add(saleWeek1); - assertTrue(archive.getPurchase(1).isEmpty()); + assertTrue(archive.getPurchase(1).isEmpty()); } /** @@ -132,12 +132,12 @@ void testGetPurchaseByWeekExcludesSales() { */ @Test void testGetSaleByWeek() { - archive.add(purchase); // week 1 - archive.add(sale); // week 2 + archive.add(purchase); // week 1 + archive.add(sale); // week 2 - List sales = archive.getSale(2); - assertEquals(1, sales.size()); - assertTrue(sales.contains(sale)); + List sales = archive.getSale(2); + assertEquals(1, sales.size()); + assertTrue(sales.contains(sale)); } /** @@ -145,11 +145,11 @@ void testGetSaleByWeek() { */ @Test void testGetSaleByWeekExcludesPurchases() { - // Add a purchase at week 2 — should not appear in getSale(2) - Purchase purchaseWeek2 = new Purchase(share, 2); - archive.add(purchaseWeek2); + // Add a purchase at week 2 — should not appear in getSale(2) + Purchase purchaseWeek2 = new Purchase(share, 2); + archive.add(purchaseWeek2); - assertTrue(archive.getSale(2).isEmpty()); + assertTrue(archive.getSale(2).isEmpty()); } /** @@ -157,7 +157,7 @@ void testGetSaleByWeekExcludesPurchases() { */ @Test void testCountDistinctWeeksEmpty() { - assertEquals(0, archive.countDistinctWeeks()); + assertEquals(0, archive.countDistinctWeeks()); } /** @@ -165,10 +165,10 @@ void testCountDistinctWeeksEmpty() { */ @Test void testCountDistinctWeeksMultipleTransactionsSameWeek() { - Purchase purchase2 = new Purchase(share, 1); - archive.add(purchase); - archive.add(purchase2); - assertEquals(1, archive.countDistinctWeeks()); + Purchase purchase2 = new Purchase(share, 1); + archive.add(purchase); + archive.add(purchase2); + assertEquals(1, archive.countDistinctWeeks()); } /** @@ -176,9 +176,9 @@ void testCountDistinctWeeksMultipleTransactionsSameWeek() { */ @Test void testCountDistinctWeeksAcrossWeeks() { - archive.add(purchase); // week 1 - archive.add(sale); // week 2 - assertEquals(2, archive.countDistinctWeeks()); + archive.add(purchase); // week 1 + archive.add(sale); // week 2 + assertEquals(2, archive.countDistinctWeeks()); } // ---- Negative tests ---- @@ -188,8 +188,8 @@ void testCountDistinctWeeksAcrossWeeks() { */ @Test void testAddNullThrows() { - assertThrows(IllegalArgumentException.class, () -> - archive.add(null) + assertThrows(IllegalArgumentException.class, () -> + archive.add(null) ); } } diff --git a/target/test-classes/ExchangeTest.class b/target/test-classes/ExchangeTest.class index c9e32fd89c87350a4ead3475fed0f9295f726766..c301cc2bbe7c95b0d8949da3ce9eb1513c14c45e 100644 GIT binary patch literal 10397 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 diff --git a/target/test-classes/PortfolioTest.class b/target/test-classes/PortfolioTest.class index 6b62be54f6a640c947ba2b422f49ac89d83264c0..69298632699854eac962578599797b25f07ecf7b 100644 GIT binary patch delta 358 zcmW-aJugE67=_Or?$ETi?rnRe>N|>r##hv$v=O4ZXp2EmVrUyuY_t=#NNbS3n`jIY z!c;Lxh^<(JnEiwP365^(dGfyJlznAirD#=jIklc>ZdlQTE_g7FX6dwTYF^mT4IMpb zL$93KuFL_L7{m~U5x@v$Fp4GkC9LI*0BP$&@{7L`gBY z5NF>4`mrb<_JXlWyGEPf@;W~^B-QZNn&MKLOIuvp=28ap*ugS(k;I-vbkX+tcYsD@ z(T<$d^ql30CeM~*$ry3906U6OHDYQB8bmyfwB?)^7x1DiMaPPIO}dqk(=0j1)e7N( z5LKU;UjDV*(N{_Db^0ZJlD;MAiYPbIVVZL3@+v_3PP7L4orK09 zL@XMM5SykGV)q~PpWtYI%lVvq&pG*(ePv&zXjL3?YCYDPVMPnPXvHi%(rsJUys)7M zI(pH8J~^}9n1e7egkin_Mlgp_EMZJSTHf&UHo*KO+AxKF1m#CdTIT64upuNf_N);h zMd3z_eQ^w6Q9kSiBSCkC?kYS;GPx#cN3A`@r8IXo*}BE0ZA6g4GIo%{u7q{b_L$p8 z6AsXctkm^GOODPFTk?`I;_30vB{^?4J&V+hPx|eB}S#tT+ zN5T^!s=iRY{oC?D|D9B8&@bte^esVmShb#Zx#Z^XAKY_ug-P`RkwCe*!pxpF=2Ps2-V7O+L!= zRtOagb+hWc8q?KmIyN{t%TpFZW!^GUa||`f3&s@JV8rtSg)2o%~M)N)x+3H?sau{M$m+2;nyyP zZLLW!NF$c1Wz+Gtu?Xt1N9@?ka3WyG>NB=w1tTFnlIuqZJyFn#2+WfoGF zVGbD9Ktb0TcCHZtkxlZw)$?8CIE;3NvaZfB?iWrSC8t_&NJx$`^t6^_H&|P+Rrub8 zBOx4S*d3gbL_W_=nKlJS(M`3aN{j1BNlfj+qezH45XybECtj&O6cLxR+>*5@{5Y;4 zjvj_^+A&i7wYHV`BvhVM@Gwr%d7HogdO_8xlv?}=bLf2u^9ibTh%XjYDokrhaQ7)V zjeZI?aTo0jjYJ<*Q>1>Y12R-FQ!{Fw51CquTBil)6r2}Gb%a#%CFNS2DX~p((@5yL zvB0OoNHVbQYO2x!1%nu(BA@6V9%N`(OGCZD6J;brI67k*3;7U6sI9!>KCkOMt?CIg zUC8jP)xVhHIg!OE&5uh8#&DS-#20z0U{Q`b$l>7hPZzQ&aW2*`_lo9^i)@T5n84$1 zHf#|LBmQjk>6EJYT>v{#mm^FfN;--}(cxl>Y`Q3Q+g0JqHHKrYz9Dos&N%2IBP-F% zaFt=ZLB;2)QWV6 z#cQkZR2{+*s>>G@%pgpLn`CfouPMlqEreG++uI5ZX?x4FeV`yGZSQ-wPZi{)?Gw-T zl>$rJz9bv9;`wZro3iOR70vey2fU&w2|{UTL->)QdHt>Qlfljd_Kn-yO7! zxBn)dgx^N^a&b@+btDhoLZ?TySvtQ9&Df2-6b#`{gAL=*HB(p~z=P7qV}Zoe4ly~l z94+dQz(5C`Hr#Aqa z4hFqJpf9Zkt&4~&fRdt7qAMufvvRJ*sr$s=4$*zgZ8Evg?V00Mf;~2eBsdjnU75H+ zRzaR3NKs7<#fG+F*Fc-_No}yxPq=got-|efa2t!b)DG+Eobn+zy2!Zz zUMKh0!_WHgn_Yam7gs4ili>BDz) z(>1a{87spgmgsqs^u9p08&pUy%hfA*9dF3hn|KHB%GG=L5Fg3a$M_7N%heb78sFes P!Vo_@zQYgr2~Gb3NajXU literal 4255 zcmbVO3s)0I7`=l)LRb(Zq9}p_mVm{8l~#?~21KQefPyI6)-GX47B<Q7v_rC$)^K2hl=6!y`i>G$AZVcQWjXB#N{%mZ@b^vHrOb z0@x)+v@x9a8L@ee^QMfEkQ%TD?E&mnun!#!4Kr!gu3vMKClaq&39?=q#>M z3=ZM|L(T9|5ZyxlF_OO@y+Yg}hEtJ}$^I(*--=cCAre5pf&m<6IAEBm=yHJ)PS2cX zaWkssv}in^=cc6@**r;A?O@WdCJVY=mOW?F?CkBwpn@ZaG6YlHvNwxid!)Zate_uO za1_rlC=T%Wt%9l(PoJD~7gx%83DbyzXYm~6jF>JJ)U2ghcNq2%nV$(_B6}wkoD@@9 zXv%cKOs3U5pEk85<@yu@8)+beoKf&RULb#6AHO`s(B_X8z$im^(Locs&Qq!$H&cZS z&syWRlRPJK7beG_RWOco3;}+dCkqy57$lMYNna{tlVVBYGxh|)Zl>(Yvk`5&Fc=a-@#EG)v)|DJX`{e?8A;agiyhc;v*{oryu8+GbWM`Bw873cBlj#`4K8woKUiKK7 z-!cnH%P>9b*X2cVO4Tbun$=MOTGTvsD+2Up_Yi+|8Z5wYl;7d9LKzYYrdj?t9(pGtSr=1W%Qleu6vrC@pJ0ha1nT18Q zcZ-3&_EB6^^}vZMDz^uB_;9epUGj}(}8^ofW*Rgk5q9-oKs1-=a6D+OQUo01bu%dQ$L zJesh#iiD_3s_;D>sf`&q`ddD7+)0k^3u<;z=lR}*VXPE#G3nfP?^o$f$;8<^IW78;FP4nUH>)5f5*59$a2q?b1fL2noO9E|oL0_dlL3vZ)<28@q zCVdr&c|DJ@e}3RM?6&c{2;XiTw268WhacjpB2_aT{y-=Fb)g#vY`QiF#-;0|1gKab z!VTi2;KiaxyyOzo!m1GqNr(Ty(+@FJ&fiD;5%kla$f3hO;PETI{M_STarvXw`Mq?I zh+{q?hDgLPeHI;+L_|FiJA6ef{Vx$>D@ep~9}y==#7Pnndjb(DPsB~TRc?V8(fx|k z>*z_)PX*#Ja@$$5z_>)*!#$3#%aD3nqUh)UMMiqBM0&VgIU9JE+=?}Uo0Mi9rRrxFlwhxQB3d%DQfuC}^ z?_Gj1H#O}W%BcXWLMJ8YjRRj!3Vm33NpZ67C52T5o|k|(CnY4lUtHj0q*?sy5SkrO z?-G$S!lcV@imdJK99<}D)ULs8awc$wa6hAWuj38+@3h|bTKB!yyI$)9uk|stz9ozh M?f!T80Y9PrUo#V+wg3PC diff --git a/target/test-classes/StockTest.class b/target/test-classes/StockTest.class index 086bf5b8c9ecf660b77db09a000cb1607902004b..c71a54dd84e2404fb2664bd285e6e436ff4ece4a 100644 GIT binary patch literal 6116 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 F`!8O2Q-uHk