From adaec06083ea7ed687f2860354fedadb94ec00b6 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 10:22:07 +0200
Subject: [PATCH 01/31] Fix: Validator for stock symbol rename
---
.../edu/ntnu/idi/idatt2003/g40/mappe/utils/Validator.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/Validator.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/Validator.java
index 6d12cbc..b31108e 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/Validator.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/Validator.java
@@ -55,8 +55,8 @@ public enum Validator {
/**
* Rule that checks if a string is considered a valid stock name.
* */
- VALID_STOCK_NAME(NOT_EMPTY.validationRule.and(s ->
- s.length() == 4), "Invalid stock name!"),
+ VALID_STOCK_SYMBOL(NOT_EMPTY.validationRule.and(s ->
+ s.length() == 4), "Invalid stock symbol!"),
/**
* Rule that checks if a string represents a positive integer.
From 19611bd6935ca2c58e0f442f909ebcdb7e7bab02 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 11:38:02 +0200
Subject: [PATCH 02/31] Feat: Basic improvements, testing.
---
.../idatt2003/g40/mappe/engine/Exchange.java | 119 ++++++++-----
.../idatt2003/g40/mappe/model/Portfolio.java | 4 +-
.../idi/idatt2003/g40/mappe/model/Stock.java | 4 +-
.../view/widgets/market/MarketController.java | 2 +-
.../g40/mappe/engine/ExchangeTest.java | 165 ++++++++++++++----
5 files changed, 214 insertions(+), 80 deletions(-)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java
index b73f634..a3a9af3 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java
@@ -1,31 +1,25 @@
package edu.ntnu.idi.idatt2003.g40.mappe.engine;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Player;
-import edu.ntnu.idi.idatt2003.g40.mappe.model.Purchase;
-import edu.ntnu.idi.idatt2003.g40.mappe.model.Sale;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Share;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Transaction;
import edu.ntnu.idi.idatt2003.g40.mappe.service.*;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventData;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventPublisher;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventType;
import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator;
-import javafx.beans.property.IntegerProperty;
-import javafx.beans.property.SimpleIntegerProperty;
-
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.SimpleIntegerProperty;
/**
* Represents a stock exchange where stocks can be traded.
*
- * Holds a map of stocks where stock symbol is key and stock is value.
+ * Holds a map of {@link Stock} objects where stock symbol is key and
+ * stock is value.
*
* Delegates buying and selling to player elements using calculators
*
@@ -33,6 +27,8 @@
*
* @see Player
* @see TransactionCalculator
+ *
+ * @version 1.0.0
* */
public final class Exchange {
@@ -59,13 +55,16 @@ public final class Exchange {
/**
* Constructor.
*
- * @param name name of exchange.
- * @param stocks list of {@link Stock} objects.
+ * @param name name of exchange.
+ * @param stocks list of {@link Stock} objects.
*
* @throws IllegalArgumentException if name or stocks are empty/null.
* */
- public Exchange(final String name, final List stocks) throws IllegalArgumentException {
- if (!Validator.NOT_EMPTY.isValid(name) || stocks == null || stocks.isEmpty()) {
+ public Exchange(final String name, final List stocks)
+ throws IllegalArgumentException {
+ if (!Validator.NOT_EMPTY.isValid(name)
+ || stocks == null
+ || stocks.isEmpty()) {
throw new IllegalArgumentException("Invalid exchange parameters!");
}
this.name = name;
@@ -108,7 +107,7 @@ public IntegerProperty weekProperty() {
*
* @param symbol the stock symbol.
*
- * @return true or false.
+ * @return true or false.
* */
public boolean hasStock(final String symbol) {
return stockMap.containsKey(symbol);
@@ -117,16 +116,16 @@ public boolean hasStock(final String symbol) {
/**
* Getter method for stock element.
*
- * @param symbol the symbol of the stock to get.
+ * @param symbol the symbol of the stock to get.
*
- * @return {@link Stock} element gotten.
+ * @return {@link Stock} element gotten.
*
* @throws IllegalArgumentException if symbol is invalid.
* */
public Stock getStock(final String symbol) throws IllegalArgumentException {
- if (!Validator.VALID_STOCK_NAME.isValid(symbol)) {
+ if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol)) {
throw new IllegalArgumentException(
- Validator.VALID_STOCK_NAME.getErrorMessage());
+ Validator.VALID_STOCK_SYMBOL.getErrorMessage());
}
return stockMap.get(symbol);
}
@@ -136,7 +135,7 @@ public Stock getStock(final String symbol) throws IllegalArgumentException {
*
* @param searchTerm the term to search for.
*
- * @return a list of {@link Stock} objects.
+ * @return a list of {@link Stock} objects.
* */
public List findStocks(final String searchTerm) {
List result = new ArrayList<>();
@@ -154,24 +153,26 @@ public List findStocks(final String searchTerm) {
/**
* Method called when a player buys a stock.
*
- * @param symbol the stock this player buys.
- * @param quantity the amount of stock to buy.
- * @param player the player buying stock.
+ * @param symbol the stock this player buys.
+ * @param quantity the amount of stock to buy.
+ * @param player the player buying stock.
*
- * @return Transaction representing the transaction.
+ * @return Transaction representing the transaction.
*
* @throws IllegalArgumentException if symbol or player is invalid.
* */
public Transaction buy(final String symbol,
final BigDecimal quantity,
final Player player) throws IllegalArgumentException {
- if (!Validator.VALID_STOCK_NAME.isValid(symbol) || player == null) {
+ if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol) || player == null) {
throw new IllegalArgumentException("Invalid purchase!");
}
Stock stock = stockMap.get(symbol);
Share share = new Share(stock, quantity, stock.getSalesPrice());
TransactionCalculator calculator = new PurchaseCalculator(share);
- Purchase purchase = new Purchase(share, week.get(), calculator);
+ Transaction purchase = TransactionFactory.createTransaction(
+ TransactionType.PURCHASE, share, getWeek(), calculator
+ );
player.handleTransaction(purchase);
return purchase;
}
@@ -179,10 +180,10 @@ public Transaction buy(final String symbol,
/**
* Method called when a player sells share.
*
- * @param share the share to sell.
- * @param player the player buying stock.
+ * @param share the share to sell.
+ * @param player the player buying stock.
*
- * @return Transaction representing the transaction.
+ * @return Transaction representing the transaction.
*
* @throws IllegalArgumentException if share or player is null.
* */
@@ -192,27 +193,37 @@ public Transaction sell(final Share share, final Player player)
throw new IllegalArgumentException("Invalid sell!");
}
TransactionCalculator calculator = new SaleCalculator(share);
- Sale sale = new Sale(share, week.get(), calculator);
+ Transaction sale = TransactionFactory.createTransaction(
+ TransactionType.SALE, share, getWeek(), calculator
+ );
player.handleTransaction(sale);
return sale;
}
/**
- * Method called when a player sells share.
+ * Method called when a player sells share,
+ * defined by an amount instead of specific {@link Share} object.
+ *
+ * Might split shares into multiples to ensure proper selling.
+ * Uses the {@link edu.ntnu.idi.idatt2003.g40.mappe.model.Portfolio}
+ * to split.
*
- * @param amount the amount of "shares" to sell.
+ * @param amount the amount of "shares" to sell.
* @param stockSymbol the stock to sell shares in.
- * @param player the player buying stock.
+ * @param player the player buying stock.
*
- * @return Transaction representing the transaction.
+ * @return List of transactions commited to sell the shares.
*
- * @throws IllegalArgumentException if any parameter is null, or if player does not have enough shares.
+ * @throws IllegalArgumentException if any parameter is null,
+ * or if player does not have enough shares.
* */
public List sell(BigDecimal amount,
final String stockSymbol,
final Player player)
throws IllegalArgumentException {
- if (amount == null || player == null || !Validator.NOT_EMPTY.isValid(stockSymbol)) {
+ if (amount == null
+ || player == null
+ || !Validator.VALID_STOCK_SYMBOL.isValid(stockSymbol)) {
throw new IllegalArgumentException("Invalid sell!");
} else {
@@ -220,7 +231,8 @@ public List sell(BigDecimal amount,
.filter(s -> s.getStock().getSymbol().equals(stockSymbol))
.toList();
- BigDecimal totalOwned = player.getPortfolio().getTotalSharesBySymbol(stockSymbol);
+ BigDecimal totalOwned = player.getPortfolio()
+ .getTotalSharesBySymbol(stockSymbol);
if (amount.compareTo(totalOwned) > 0) {
amount = totalOwned;
@@ -239,7 +251,9 @@ public List sell(BigDecimal amount,
remainingToSell = remainingToSell.subtract(shareQty);
transactions.add(sell(share, player));
} else {
- Share newShare = player.getPortfolio().splitShare(share, remainingToSell);
+ Share newShare = player.getPortfolio().splitShare(
+ share, remainingToSell
+ );
remainingToSell = BigDecimal.ZERO;
transactions.add(sell(newShare, player));
}
@@ -250,12 +264,19 @@ public List sell(BigDecimal amount,
/**
* Method for advancing time, increasing the amount of weeks.
+ *
+ * Applies a random price change from -5% to 5% to every stock,
+ * plus a flat percent determined by their fortune, that can range from
+ * -10% to +10%.
+ *
+ * @see edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.GameEngineView
* */
public void advance() {
for (Stock stock : stockMap.values()) {
BigDecimal currentPrice = stock.getSalesPrice();
- double change = ((random.nextDouble() * 0.10) - 0.05) + stock.getFortune();
+ double change = (
+ (random.nextDouble() * 0.10) - 0.05) + stock.getFortune();
stock.setFortune(0);
BigDecimal factor = BigDecimal.valueOf(1 + change);
@@ -269,11 +290,17 @@ public void advance() {
* Method for getting the stocks with the most
* amount of increase since last week.
*
- * @param limit the maximum amount of stocks returned
+ * @param limit the maximum amount of stocks returned
*
* @return list of {@link Stock} objects.
+ *
+ * @throws IllegalArgumentException if limit is invalid (negative or zero).
* */
- public List getGainers(final int limit) {
+ public List getGainers(final int limit)
+ throws IllegalArgumentException {
+ if (limit < 1) {
+ throw new IllegalArgumentException("Invalid limit for getting gainers!");
+ }
return stockMap.entrySet().stream()
// We only want the stocks with a positive price change.
.filter(e ->
@@ -294,11 +321,17 @@ public List getGainers(final int limit) {
* Method for getting the stocks with the highest
* loss of price since last week.
*
- * @param limit the maximum amount of stocks returned
+ * @param limit the maximum amount of stocks returned
*
* @return list of {@link Stock} objects.
+ *
+ * @throws IllegalArgumentException if limit is invalid (negative or zero).
* */
- public List getLosers(final int limit) {
+ public List getLosers(final int limit)
+ throws IllegalArgumentException {
+ if (limit < 1) {
+ throw new IllegalArgumentException("Invalid limit for getting losers!");
+ }
return stockMap.entrySet().stream()
// Only get entries with negative price change.
.filter(e ->
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java
index 483adb8..0c7ea5e 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java
@@ -82,9 +82,9 @@ public List getShares() {
* @throws IllegalArgumentException if symbol is invalid.
*/
public List getShares(final String symbol) throws IllegalArgumentException {
- if (!Validator.VALID_STOCK_NAME.isValid(symbol)) {
+ if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol)) {
throw new IllegalArgumentException(
- Validator.VALID_STOCK_NAME.getErrorMessage());
+ Validator.VALID_STOCK_SYMBOL.getErrorMessage());
}
return shares.stream()
.filter(s -> symbol.equalsIgnoreCase(s.getStock().getSymbol()))
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Stock.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Stock.java
index 1ca9664..0aff105 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Stock.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Stock.java
@@ -44,9 +44,9 @@ public final class Stock {
public Stock(final String symbol,
final String company,
final BigDecimal salesPrice) throws IllegalArgumentException {
- if (!Validator.VALID_STOCK_NAME.isValid(symbol)) {
+ if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol)) {
throw new IllegalArgumentException(
- Validator.VALID_STOCK_NAME.getErrorMessage());
+ Validator.VALID_STOCK_SYMBOL.getErrorMessage());
} else {
this.symbol = symbol;
this.company = company;
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketController.java
index e185fb3..86838a9 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketController.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/market/MarketController.java
@@ -84,7 +84,7 @@ protected void initInteractions() {
* @return the total quantity, or 0 if the symbol is invalid.
* */
private int ownedQuantity(final String symbol) {
- if (!Validator.VALID_STOCK_NAME.isValid(symbol)) {
+ if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol)) {
return 0;
}
return player.getPortfolio().getShares(symbol).stream()
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/ExchangeTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/ExchangeTest.java
index 8f4f1a5..e4dd5ee 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/ExchangeTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/ExchangeTest.java
@@ -4,98 +4,183 @@
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import edu.ntnu.idi.idatt2003.g40.mappe.model.Player;
+import edu.ntnu.idi.idatt2003.g40.mappe.model.Purchase;
+import edu.ntnu.idi.idatt2003.g40.mappe.model.Sale;
+import edu.ntnu.idi.idatt2003.g40.mappe.model.Share;
+import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock;
+import edu.ntnu.idi.idatt2003.g40.mappe.model.Transaction;
import java.math.BigDecimal;
import java.util.List;
-import edu.ntnu.idi.idatt2003.g40.mappe.model.*;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.PurchaseCalculator;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.SaleCalculator;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionCalculator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
final class ExchangeTest {
+ /**
+ * Test sample stock.
+ * */
private Stock appleStock;
+ /**
+ * Test sample stock.
+ * */
+ private Stock teslaStock;
+
+ /**
+ * Test sample stock.
+ * */
+ private Stock pearStock;
+
+ /**
+ * Test exchange to use.
+ * */
+ private Exchange testExchange;
+
+ /**
+ * Test player for testing transactions.
+ * */
+ private Player testPlayer;
+
@BeforeEach
void setUp() {
appleStock = new Stock("AAPL", "Apple", new BigDecimal("100"));
+ teslaStock = new Stock("TSLA", "Tesla", new BigDecimal("200"));
+ pearStock = new Stock("Pear", "Pear Inc.", new BigDecimal("97"));
+ testExchange = new Exchange("NASDAQ", List.of(appleStock, teslaStock));
+ testPlayer = new Player("Alice", new BigDecimal("1000"));
}
@Test
void constructorSetsNameWeekAndStocksCorrectly() {
- Stock tesla = new Stock("TSLA", "Tesla", new BigDecimal("200"));
-
- Exchange exchange = new Exchange("NASDAQ", List.of(appleStock, tesla));
-
- assertEquals("NASDAQ", exchange.getName());
- assertEquals(1, exchange.getWeek());
- assertTrue(exchange.hasStock("AAPL"));
- assertTrue(exchange.hasStock("TSLA"));
+ assertEquals("NASDAQ", testExchange.getName());
+ assertEquals(1, testExchange.getWeek());
+ assertTrue(testExchange.hasStock("AAPL"));
+ assertTrue(testExchange.hasStock("TSLA"));
}
@Test
void getStockReturnsCorrectStock() {
- Exchange exchange = new Exchange("NASDAQ", List.of(appleStock));
-
- Stock result = exchange.getStock("AAPL");
+ Stock result = testExchange.getStock("AAPL");
assertSame(appleStock, result);
}
@Test
void findStocksReturnsMatchingStocksBySymbolOrCompany() {
- Stock tesla = new Stock("TSLA", "Tesla", new BigDecimal("200"));
- Exchange exchange = new Exchange("NASDAQ", List.of(appleStock, tesla));
-
- List resultBySymbol = exchange.findStocks("AAP");
- List resultByCompany = exchange.findStocks("tes");
+ List resultBySymbol = testExchange.findStocks("AAP");
+ List resultByCompany = testExchange.findStocks("tes");
assertEquals(1, resultBySymbol.size());
assertTrue(resultBySymbol.contains(appleStock));
assertEquals(1, resultByCompany.size());
- assertTrue(resultByCompany.contains(tesla));
+ assertTrue(resultByCompany.contains(teslaStock));
}
@Test
void buyReturnsPurchaseAndWithdrawsMoneyFromPlayer() {
- Exchange exchange = new Exchange("NASDAQ", List.of(appleStock));
- Player player = new Player("Alice", new BigDecimal("1000"));
+ Transaction transaction =
+ testExchange.buy("AAPL", new BigDecimal("2"), testPlayer);
+
+ TransactionCalculator calculator =
+ new PurchaseCalculator(transaction.getShare());
- Transaction transaction = exchange.buy("AAPL", new BigDecimal("2"), player);
+ BigDecimal expectedPlayerMoney =
+ testPlayer.getStartingMoney().subtract(calculator.calculateTotal());
assertInstanceOf(Purchase.class, transaction);
assertEquals(1, transaction.getWeek());
assertEquals(new BigDecimal("2"), transaction.getShare().getQuantity());
- assertEquals(new BigDecimal("100"), transaction.getShare()
+ assertEquals(appleStock.getSalesPrice(), transaction.getShare()
.getPurchasePrice());
- assertEquals(new BigDecimal("799.000"), player.getMoney());
+ assertEquals(expectedPlayerMoney, testPlayer.getMoney());
}
@Test
void sellReturnsSaleAndAddsMoneyToPlayer() {
- Stock apple = new Stock("AAPL", "Apple", new BigDecimal("150"));
- Exchange exchange = new Exchange("NASDAQ", List.of(apple));
- Player player = new Player("Alice", new BigDecimal("1000"));
- Share share = new Share(apple, new BigDecimal("2"), new BigDecimal("100"));
+ Share share =
+ new Share(appleStock, new BigDecimal("2"), appleStock.getSalesPrice());
- Transaction transaction = exchange.sell(share, player);
+ Transaction transaction = testExchange.sell(share, testPlayer);
+
+ TransactionCalculator calculator = new SaleCalculator(share);
+ BigDecimal expectedPlayerMoney =
+ testPlayer.getStartingMoney().add(calculator.calculateTotal());
assertInstanceOf(Sale.class, transaction);
assertEquals(1, transaction.getWeek());
assertSame(share, transaction.getShare());
- assertEquals(new BigDecimal("1267.9000"), player.getMoney());
+ assertEquals(expectedPlayerMoney, testPlayer.getMoney());
+ }
+
+ @Test
+ void sellingAmountOfSharesRequiringSplitFunctions() {
+ Transaction purchase = testExchange.buy(
+ appleStock.getSymbol(), new BigDecimal("3"), testPlayer
+ );
+
+ List sales = testExchange.sell(
+ new BigDecimal("1.5"),
+ appleStock.getSymbol(),
+ testPlayer
+ );
+
+ SaleCalculator saleCalculator = new SaleCalculator(sales.getFirst().getShare());
+ PurchaseCalculator purchaseCalculator = new PurchaseCalculator(purchase.getShare());
+ BigDecimal expectedPlayerMoney = testPlayer.getStartingMoney()
+ .subtract(purchaseCalculator.calculateTotal())
+ .add(saleCalculator.calculateTotal());
+
+ assertEquals(1, sales.size());
+
+ assertEquals(expectedPlayerMoney, testPlayer.getMoney());
+
+ BigDecimal expectedShareAmountOwned = purchase.getShare().getQuantity()
+ .subtract(sales.getFirst().getShare().getQuantity());
+
+ assertEquals(expectedShareAmountOwned,
+ testPlayer
+ .getPortfolio()
+ .getShares(appleStock.getSymbol())
+ .getFirst()
+ .getQuantity()
+ );
+ }
+
+ @Test
+ void sellingAmountOfSharesRequiringSplitFunctions2() {
+ // Arrange: Clear setup with explicit numbers
+ Transaction purchase1 = testExchange.buy(appleStock.getSymbol(), new BigDecimal("3.12"), testPlayer);
+ Transaction purchase2 = testExchange.buy(appleStock.getSymbol(), new BigDecimal("4.56"), testPlayer);
+
+ // Act: Sell 6 shares (requires taking 3.12 from purchase1 and 2.88 from purchase2)
+ List sales = testExchange.sell(new BigDecimal("6.00"), appleStock.getSymbol(), testPlayer);
+
+ // Assert 1: Verify the split logic actually happened
+ assertEquals(2, sales.size(), "Should split the sale across two purchase records");
+ assertEquals(new BigDecimal("3.12"), sales.get(0).getShare().getQuantity());
+ assertEquals(new BigDecimal("2.88"), sales.get(1).getShare().getQuantity());
+
+ // Assert 2: Verify player money matches a known, pre-calculated constant
+ BigDecimal expectedFinalMoney = new BigDecimal("822.16000"); // Replace with the actual hardcoded math result
+ assertEquals(expectedFinalMoney, testPlayer.getMoney());
}
@Test
void advanceIncreasesWeekAndUpdatesStockPrice() {
- Exchange exchange = new Exchange("NASDAQ", List.of(appleStock));
BigDecimal oldPrice = appleStock.getSalesPrice();
- exchange.advance();
+ testExchange.advance();
- assertEquals(2, exchange.getWeek());
+ assertEquals(2, testExchange.getWeek());
assertNotEquals(oldPrice, appleStock.getSalesPrice());
}
@@ -146,4 +231,20 @@ void getLosersActuallyReturnsProperLosers() {
boolean actualLosersNotContainingLoserOutsideOfLimit = !actualLosers.contains(pearStock);
assertTrue(actualLosersNotContainingLoserOutsideOfLimit);
}
+
+ @Test
+ void invalidLimitForGettingGainersThrowError() {
+ Exchange exchange = new Exchange("Exchange", List.of(appleStock));
+
+ assertThrows(IllegalArgumentException.class, () -> exchange.getGainers(0));
+ assertThrows(IllegalArgumentException.class, () -> exchange.getGainers(-1));
+ }
+
+ @Test
+ void invalidLimitForGettingLosersThrowError() {
+ Exchange exchange = new Exchange("Exchange", List.of(appleStock));
+
+ assertThrows(IllegalArgumentException.class, () -> exchange.getLosers(0));
+ assertThrows(IllegalArgumentException.class, () -> exchange.getLosers(-1));
+ }
}
From 3ee92965fe364d8792330024dd458f328e95a8fe Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 15:13:21 +0200
Subject: [PATCH 03/31] Feat: Refactored Exchange, testing
Refactored Exchange, fixed semantic bugs, centralized logic, and made integerproperty read only for safety and encapsulation
---
.../idatt2003/g40/mappe/engine/Exchange.java | 102 +++++----
.../g40/mappe/engine/ExchangeTest.java | 215 +++++++++++++-----
2 files changed, 221 insertions(+), 96 deletions(-)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java
index a3a9af3..bc1ec00 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java
@@ -4,7 +4,11 @@
import edu.ntnu.idi.idatt2003.g40.mappe.model.Share;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Transaction;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.*;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.PurchaseCalculator;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.SaleCalculator;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionCalculator;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionFactory;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionType;
import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator;
import java.math.BigDecimal;
import java.util.ArrayList;
@@ -13,7 +17,7 @@
import java.util.Map;
import java.util.Random;
import javafx.beans.property.IntegerProperty;
-import javafx.beans.property.SimpleIntegerProperty;
+import javafx.beans.property.ReadOnlyIntegerWrapper;
/**
* Represents a stock exchange where stocks can be traded.
@@ -40,7 +44,7 @@ public final class Exchange {
/**
* Current week (set to 1 in constructor).
* */
- private final IntegerProperty week = new SimpleIntegerProperty(1);
+ private final ReadOnlyIntegerWrapper week = new ReadOnlyIntegerWrapper(1);
/**
* Map of {@link Stock} objects. Key is stock symbol. Value is stock.
@@ -120,10 +124,12 @@ public boolean hasStock(final String symbol) {
*
* @return {@link Stock} element gotten.
*
- * @throws IllegalArgumentException if symbol is invalid.
+ * @throws IllegalArgumentException if symbol is invalid or not in exchange.
* */
- public Stock getStock(final String symbol) throws IllegalArgumentException {
- if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol)) {
+ public Stock getStock(final String symbol)
+ throws IllegalArgumentException {
+ if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol)
+ || !stockMap.containsKey(symbol)) {
throw new IllegalArgumentException(
Validator.VALID_STOCK_SYMBOL.getErrorMessage());
}
@@ -164,10 +170,12 @@ public List findStocks(final String searchTerm) {
public Transaction buy(final String symbol,
final BigDecimal quantity,
final Player player) throws IllegalArgumentException {
- if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol) || player == null) {
+ if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol)
+ || quantity == null
+ || player == null) {
throw new IllegalArgumentException("Invalid purchase!");
}
- Stock stock = stockMap.get(symbol);
+ Stock stock = getStock(symbol);
Share share = new Share(stock, quantity, stock.getSalesPrice());
TransactionCalculator calculator = new PurchaseCalculator(share);
Transaction purchase = TransactionFactory.createTransaction(
@@ -212,10 +220,12 @@ TransactionType.SALE, share, getWeek(), calculator
* @param stockSymbol the stock to sell shares in.
* @param player the player buying stock.
*
- * @return List of transactions commited to sell the shares.
+ * @return List of transactions commited to sell the shares.
*
* @throws IllegalArgumentException if any parameter is null,
- * or if player does not have enough shares.
+ * or if player does not have enough shares,
+ * or if player does not own any shares
+ * of the given stock.
* */
public List sell(BigDecimal amount,
final String stockSymbol,
@@ -225,41 +235,50 @@ public List sell(BigDecimal amount,
|| player == null
|| !Validator.VALID_STOCK_SYMBOL.isValid(stockSymbol)) {
throw new IllegalArgumentException("Invalid sell!");
- } else {
+ }
- List sharesOfStock = player.getPortfolio().getShares().stream()
- .filter(s -> s.getStock().getSymbol().equals(stockSymbol))
- .toList();
+ // Get all shares the player owns of the stock given in the parameter.
+ List sharesOfStock = player.getPortfolio().getShares().stream()
+ .filter(s -> s.getStock().getSymbol().equals(stockSymbol))
+ .toList();
- BigDecimal totalOwned = player.getPortfolio()
- .getTotalSharesBySymbol(stockSymbol);
+ // Throws error if player does not own any shares of the given stock.
+ if (sharesOfStock.isEmpty()) {
+ throw new IllegalArgumentException("Player does not own"
+ + " any shares of this stock!");
+ }
- if (amount.compareTo(totalOwned) > 0) {
- amount = totalOwned;
- }
- ArrayList transactions = new ArrayList<>();
- BigDecimal remainingToSell = amount;
+ // Gets the total quantity amount of shares owned of given stock.
+ BigDecimal totalOwned = player.getPortfolio()
+ .getTotalSharesBySymbol(stockSymbol);
+
+ // If amount wanted to sell is greater than total owned,
+ // sells entire collection.
+ if (amount.compareTo(totalOwned) > 0) {
+ amount = totalOwned;
+ }
+ ArrayList transactions = new ArrayList<>();
+ BigDecimal remainingToSell = amount;
- for (Share share : sharesOfStock) {
- if (remainingToSell.compareTo(BigDecimal.ZERO) <= 0) {
- break;
- }
+ // For every share owned, sells if the quantity is less than
+ // or equal to the remaining amount of shares to sell.
+ // Splits share if the remaining amount to sell is less than share quantity.
+ for (Share share : sharesOfStock) {
- BigDecimal shareQty = share.getQuantity();
+ BigDecimal shareQty = share.getQuantity();
- if (shareQty.compareTo(remainingToSell) <= 0) {
- remainingToSell = remainingToSell.subtract(shareQty);
- transactions.add(sell(share, player));
- } else {
- Share newShare = player.getPortfolio().splitShare(
- share, remainingToSell
- );
- remainingToSell = BigDecimal.ZERO;
- transactions.add(sell(newShare, player));
- }
+ if (shareQty.compareTo(remainingToSell) <= 0) {
+ remainingToSell = remainingToSell.subtract(shareQty);
+ transactions.add(sell(share, player));
+ } else {
+ Share newShare = player.getPortfolio().splitShare(
+ share, remainingToSell
+ );
+ transactions.add(sell(newShare, player));
+ break;
}
- return transactions;
}
+ return transactions;
}
/**
@@ -275,12 +294,13 @@ public void advance() {
for (Stock stock : stockMap.values()) {
BigDecimal currentPrice = stock.getSalesPrice();
- double change = (
- (random.nextDouble() * 0.10) - 0.05) + stock.getFortune();
+ BigDecimal change = BigDecimal.valueOf(random.nextDouble() * 0.10 - 0.05)
+ .add(BigDecimal.valueOf(stock.getFortune()));
stock.setFortune(0);
- BigDecimal factor = BigDecimal.valueOf(1 + change);
+ BigDecimal factor = BigDecimal.ONE.add(change);
- BigDecimal newPrice = currentPrice.multiply(factor);
+ BigDecimal newPrice = currentPrice.multiply(factor)
+ .setScale(2, java.math.RoundingMode.HALF_UP);
stock.addNewSalesPrice(newPrice);
}
week.set(week.get() + 1);
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/ExchangeTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/ExchangeTest.java
index e4dd5ee..147810b 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/ExchangeTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/ExchangeTest.java
@@ -1,6 +1,8 @@
package edu.ntnu.idi.idatt2003.g40.mappe.engine;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
@@ -13,12 +15,12 @@
import edu.ntnu.idi.idatt2003.g40.mappe.model.Share;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Transaction;
-import java.math.BigDecimal;
-import java.util.List;
-
import edu.ntnu.idi.idatt2003.g40.mappe.service.PurchaseCalculator;
import edu.ntnu.idi.idatt2003.g40.mappe.service.SaleCalculator;
import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionCalculator;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -54,7 +56,8 @@ void setUp() {
appleStock = new Stock("AAPL", "Apple", new BigDecimal("100"));
teslaStock = new Stock("TSLA", "Tesla", new BigDecimal("200"));
pearStock = new Stock("Pear", "Pear Inc.", new BigDecimal("97"));
- testExchange = new Exchange("NASDAQ", List.of(appleStock, teslaStock));
+ testExchange = new Exchange("NASDAQ",
+ List.of(appleStock, teslaStock, pearStock));
testPlayer = new Player("Alice", new BigDecimal("1000"));
}
@@ -62,10 +65,27 @@ void setUp() {
void constructorSetsNameWeekAndStocksCorrectly() {
assertEquals("NASDAQ", testExchange.getName());
assertEquals(1, testExchange.getWeek());
+ assertEquals(1, testExchange.weekProperty().intValue());
assertTrue(testExchange.hasStock("AAPL"));
assertTrue(testExchange.hasStock("TSLA"));
}
+ @Test
+ void constructorThrowsErrorOnInvalidParameters() {
+ assertDoesNotThrow(() ->
+ new Exchange("Valid name",
+ List.of(appleStock, teslaStock, pearStock)));
+
+ assertThrows(IllegalArgumentException.class, () ->
+ new Exchange(null, List.of(appleStock, teslaStock, pearStock)));
+
+ assertThrows(IllegalArgumentException.class, () ->
+ new Exchange("Valid name", null));
+
+ assertThrows(IllegalArgumentException.class, () ->
+ new Exchange("Valid name", new ArrayList<>()));
+ }
+
@Test
void getStockReturnsCorrectStock() {
Stock result = testExchange.getStock("AAPL");
@@ -73,6 +93,17 @@ void getStockReturnsCorrectStock() {
assertSame(appleStock, result);
}
+ @Test
+ void getStockThrowsExceptionOnInvalidStock() {
+ assertDoesNotThrow(
+ () -> testExchange.getStock("AAPL")
+ );
+ assertThrows(IllegalArgumentException.class,
+ () -> testExchange.getStock("INVALIDSYMBOL"));
+ assertThrows(IllegalArgumentException.class,
+ () -> testExchange.getStock("NVID"));
+ }
+
@Test
void findStocksReturnsMatchingStocksBySymbolOrCompany() {
List resultBySymbol = testExchange.findStocks("AAP");
@@ -104,10 +135,30 @@ void buyReturnsPurchaseAndWithdrawsMoneyFromPlayer() {
assertEquals(expectedPlayerMoney, testPlayer.getMoney());
}
+ @Test
+ void buyThrowsExceptionOnIllegalArguments() {
+ assertDoesNotThrow(() ->
+ testExchange.buy("AAPL", new BigDecimal("2"), testPlayer));
+
+ assertThrows(IllegalArgumentException.class, () ->
+ testExchange.buy("NVID", new BigDecimal("2"), testPlayer));
+
+ assertThrows(IllegalArgumentException.class, () ->
+ testExchange.buy(null, new BigDecimal("2"), testPlayer));
+
+ assertThrows(IllegalArgumentException.class, () ->
+ testExchange.buy("AAPL", null, testPlayer));
+
+ assertThrows(IllegalArgumentException.class, () ->
+ testExchange.buy("AAPL", new BigDecimal("2"), null));
+
+ }
+
@Test
void sellReturnsSaleAndAddsMoneyToPlayer() {
Share share =
- new Share(appleStock, new BigDecimal("2"), appleStock.getSalesPrice());
+ new Share(appleStock, new BigDecimal("2"),
+ appleStock.getSalesPrice());
Transaction transaction = testExchange.sell(share, testPlayer);
@@ -122,31 +173,33 @@ void sellReturnsSaleAndAddsMoneyToPlayer() {
}
@Test
- void sellingAmountOfSharesRequiringSplitFunctions() {
- Transaction purchase = testExchange.buy(
- appleStock.getSymbol(), new BigDecimal("3"), testPlayer
- );
+ void sellShareObjectThrowsExceptionOnIllegalArguments() {
+ Transaction transaction = testExchange.buy("AAPL", new BigDecimal("2"), testPlayer);
+ Share shareObject = transaction.getShare();
+ assertDoesNotThrow(() ->
+ testExchange.sell(shareObject, testPlayer));
- List sales = testExchange.sell(
- new BigDecimal("1.5"),
- appleStock.getSymbol(),
- testPlayer
- );
+ assertThrows(IllegalArgumentException.class, () ->
+ testExchange.sell(null, testPlayer));
- SaleCalculator saleCalculator = new SaleCalculator(sales.getFirst().getShare());
- PurchaseCalculator purchaseCalculator = new PurchaseCalculator(purchase.getShare());
- BigDecimal expectedPlayerMoney = testPlayer.getStartingMoney()
- .subtract(purchaseCalculator.calculateTotal())
- .add(saleCalculator.calculateTotal());
+ assertThrows(IllegalArgumentException.class, () ->
+ testExchange.sell(shareObject, null));
+ }
+
+ @Test
+ void sellingPartialSharesUpdatesPlayerMoneyAndPortfolioCorrectly() {
+ testExchange.buy(appleStock.getSymbol(), new BigDecimal("3"), testPlayer);
+
+ List sales =
+ testExchange.sell(new BigDecimal("1.5"), appleStock.getSymbol(), testPlayer);
assertEquals(1, sales.size());
+ BigDecimal expectedPlayerMoney = new BigDecimal("847.000");
assertEquals(expectedPlayerMoney, testPlayer.getMoney());
- BigDecimal expectedShareAmountOwned = purchase.getShare().getQuantity()
- .subtract(sales.getFirst().getShare().getQuantity());
-
- assertEquals(expectedShareAmountOwned,
+ BigDecimal expectedRemainingShares = new BigDecimal("1.5");
+ assertEquals(expectedRemainingShares,
testPlayer
.getPortfolio()
.getShares(appleStock.getSymbol())
@@ -156,22 +209,64 @@ void sellingAmountOfSharesRequiringSplitFunctions() {
}
@Test
- void sellingAmountOfSharesRequiringSplitFunctions2() {
- // Arrange: Clear setup with explicit numbers
- Transaction purchase1 = testExchange.buy(appleStock.getSymbol(), new BigDecimal("3.12"), testPlayer);
- Transaction purchase2 = testExchange.buy(appleStock.getSymbol(), new BigDecimal("4.56"), testPlayer);
+ void sellingPartialAmountOfSharesRequiringSplitSplitsSharesCorrectly() {
+ testExchange.buy(appleStock.getSymbol(),
+ new BigDecimal("3.12"), testPlayer);
+ testExchange.buy(appleStock.getSymbol(),
+ new BigDecimal("4.56"), testPlayer);
- // Act: Sell 6 shares (requires taking 3.12 from purchase1 and 2.88 from purchase2)
- List sales = testExchange.sell(new BigDecimal("6.00"), appleStock.getSymbol(), testPlayer);
+ List sales =
+ testExchange.sell(new BigDecimal("6.00"),
+ appleStock.getSymbol(), testPlayer);
- // Assert 1: Verify the split logic actually happened
- assertEquals(2, sales.size(), "Should split the sale across two purchase records");
+ assertEquals(2, sales.size());
assertEquals(new BigDecimal("3.12"), sales.get(0).getShare().getQuantity());
assertEquals(new BigDecimal("2.88"), sales.get(1).getShare().getQuantity());
- // Assert 2: Verify player money matches a known, pre-calculated constant
- BigDecimal expectedFinalMoney = new BigDecimal("822.16000"); // Replace with the actual hardcoded math result
- assertEquals(expectedFinalMoney, testPlayer.getMoney());
+ BigDecimal expectedPlayerMoney = new BigDecimal("822.16000");
+ assertEquals(expectedPlayerMoney, testPlayer.getMoney());
+ }
+
+ @Test
+ void attemptingToSellMoreSharesThanOwnedSellsAllSharesOwned() {
+ testExchange.buy(appleStock.getSymbol(),
+ new BigDecimal("3.12"), testPlayer);
+ testExchange.buy(appleStock.getSymbol(),
+ new BigDecimal("4.56"), testPlayer);
+
+ List sales =
+ testExchange.sell(new BigDecimal("100.00"),
+ appleStock.getSymbol(), testPlayer);
+ assertEquals(2, sales.size());
+
+ assertEquals(new BigDecimal("3.12"), sales.get(0).getShare().getQuantity());
+ assertEquals(new BigDecimal("4.56"), sales.get(1).getShare().getQuantity());
+
+ BigDecimal expectedPlayerMoney = new BigDecimal("988.48000");
+ assertEquals(expectedPlayerMoney, testPlayer.getMoney());
+ }
+
+ @Test
+ void attemptingToSellPartialAmountOfSharesWithStockNotOwnedByPlayerThrowsError() {
+ BigDecimal testAmount = new BigDecimal("6.00");
+ assertThrows(IllegalArgumentException.class,
+ () -> testExchange.sell(testAmount,
+ appleStock.getSymbol(), testPlayer));
+ }
+
+ @Test
+ void attemptingToSellPartialAmountOfSharesWithInvalidNumberAndPlayerThrowsError() {
+ BigDecimal testAmount = new BigDecimal("6.00");
+ testExchange.buy(appleStock.getSymbol(), testAmount, testPlayer);
+
+ assertDoesNotThrow(() -> testExchange.sell(testAmount,
+ appleStock.getSymbol(), testPlayer));
+ assertThrows(IllegalArgumentException.class,
+ () -> testExchange.sell(null, appleStock.getSymbol(), testPlayer));
+ assertThrows(IllegalArgumentException.class,
+ () -> testExchange.sell(testAmount, null, testPlayer));
+ assertThrows(IllegalArgumentException.class,
+ () -> testExchange.sell(testAmount, appleStock.getSymbol(), null));
}
@Test
@@ -186,16 +281,11 @@ void advanceIncreasesWeekAndUpdatesStockPrice() {
@Test
void getGainersActuallyReturnsProperGainers() {
- Stock teslaStock = new Stock("TSLA", "Tesla", new BigDecimal("200.00"));
- Stock pearStock = new Stock("PEAR", "Pear inc.", new BigDecimal("97.00"));
-
appleStock.addNewSalesPrice(new BigDecimal("150.00"));
teslaStock.addNewSalesPrice(new BigDecimal("230.00"));
pearStock.addNewSalesPrice(new BigDecimal("112.00"));
- Exchange exchange = new Exchange("Exchange", List.of(appleStock, teslaStock, pearStock));
-
- List actualGainers = exchange.getGainers(2);
+ List actualGainers = testExchange.getGainers(2);
boolean actualGainersContainLimitedGainers = actualGainers.contains(teslaStock)
&& actualGainers.contains(appleStock);
@@ -209,17 +299,23 @@ void getGainersActuallyReturnsProperGainers() {
}
@Test
- void getLosersActuallyReturnsProperLosers() {
- Stock teslaStock = new Stock("TSLA", "Tesla", new BigDecimal("200.00"));
- Stock pearStock = new Stock("PEAR", "Pear inc.", new BigDecimal("97.00"));
+ void getGainersDoesNotReturnLoser() {
+ appleStock.addNewSalesPrice(new BigDecimal("150.00"));
+ teslaStock.addNewSalesPrice(new BigDecimal("230.00"));
+ pearStock.addNewSalesPrice(new BigDecimal("20"));
+ List actualGainers = testExchange.getGainers(3);
+
+ assertFalse(actualGainers.contains(pearStock));
+ }
+
+ @Test
+ void getLosersActuallyReturnsProperLosers() {
appleStock.addNewSalesPrice(new BigDecimal("50.00"));
teslaStock.addNewSalesPrice(new BigDecimal("170.00"));
- pearStock.addNewSalesPrice(new BigDecimal("82.00"));
-
- Exchange exchange = new Exchange("Exchange", List.of(appleStock, teslaStock, pearStock));
+ pearStock.addNewSalesPrice(new BigDecimal("87"));
- List actualLosers = exchange.getLosers(2);
+ List actualLosers = testExchange.getLosers(2);
boolean actualLosersContainsValidLosers = actualLosers.contains(teslaStock)
&& actualLosers.contains(appleStock);
@@ -233,18 +329,27 @@ void getLosersActuallyReturnsProperLosers() {
}
@Test
- void invalidLimitForGettingGainersThrowError() {
- Exchange exchange = new Exchange("Exchange", List.of(appleStock));
+ void getLosersDoesNotReturnWinners() {
+
+ appleStock.addNewSalesPrice(new BigDecimal("50.00"));
+ teslaStock.addNewSalesPrice(new BigDecimal("170.00"));
+ pearStock.addNewSalesPrice(new BigDecimal("200"));
- assertThrows(IllegalArgumentException.class, () -> exchange.getGainers(0));
- assertThrows(IllegalArgumentException.class, () -> exchange.getGainers(-1));
+ List actualLosers = testExchange.getLosers(3);
+
+ boolean actualLosersNotContainsWinner = !actualLosers.contains(pearStock);
+ assertTrue(actualLosersNotContainsWinner);
}
@Test
- void invalidLimitForGettingLosersThrowError() {
- Exchange exchange = new Exchange("Exchange", List.of(appleStock));
+ void invalidLimitForGettingGainersThrowError() {
+ assertThrows(IllegalArgumentException.class, () -> testExchange.getGainers(0));
+ assertThrows(IllegalArgumentException.class, () -> testExchange.getGainers(-1));
+ }
- assertThrows(IllegalArgumentException.class, () -> exchange.getLosers(0));
- assertThrows(IllegalArgumentException.class, () -> exchange.getLosers(-1));
+ @Test
+ void invalidLimitForGettingLosersThrowError() {
+ assertThrows(IllegalArgumentException.class, () -> testExchange.getLosers(0));
+ assertThrows(IllegalArgumentException.class, () -> testExchange.getLosers(-1));
}
}
From dccb4f8cdac9d5869168b5a3eda290ae37817b03 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 15:14:15 +0200
Subject: [PATCH 04/31] Feat: Refactored transactionarchive + testing
Replaced for loops with streams, centralized concrete getters via get transactions, and added parameter guarding. Also updated test class.
---
.../g40/mappe/engine/TransactionArchive.java | 78 ++++---
.../mappe/engine/TransactionArchiveTest.java | 202 +++++++++++-------
2 files changed, 162 insertions(+), 118 deletions(-)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchive.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchive.java
index dadc496..b7b9cf5 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchive.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchive.java
@@ -3,11 +3,15 @@
import edu.ntnu.idi.idatt2003.g40.mappe.model.Purchase;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Sale;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Transaction;
+import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
- * Stores completed transactions.
+ * Stores completed transactions in an {@link ArrayList} object.
+ *
+ * @version 1.1.0
*/
public final class TransactionArchive {
@@ -20,6 +24,7 @@ public final class TransactionArchive {
* Creates an empty transaction archive.
*/
public TransactionArchive() {
+ // Empty constructor.
}
/**
@@ -28,8 +33,13 @@ public TransactionArchive() {
* @param transaction the transaction to add
*
* @return true if the transaction was added
+ *
+ * @throws IllegalArgumentException if transaction is null
*/
public boolean add(final Transaction transaction) {
+ if (transaction == null) {
+ throw new IllegalArgumentException("Transaction cannot be null!");
+ }
return transactions.add(transaction);
}
@@ -48,24 +58,27 @@ public boolean isEmpty() {
* @param week the week number
*
* @return list of transactions from the given week
+ *
+ * @throws IllegalArgumentException if week is less than 1.
*/
public List getTransactions(final int week) {
- List result = new ArrayList<>();
- for (Transaction transaction : transactions) {
- if (transaction.getWeek() == week) {
- result.add(transaction);
- }
+ if (!Validator.VALID_WEEK.isValid(Integer.toString(week))) {
+ throw new IllegalArgumentException(
+ Validator.VALID_WEEK.getErrorMessage()
+ );
}
- return result;
+ return transactions.stream()
+ .filter(transaction -> transaction.getWeek() == week)
+ .toList();
}
/**
- * Returns all transactions.
+ * Returns an un-mutable reference to the transactions list.
*
- * @return list of transactions from the given week
+ * @return unmodifiable version of list.
*/
public List getTransactions() {
- return transactions;
+ return Collections.unmodifiableList(transactions);
}
/**
@@ -74,16 +87,15 @@ public List getTransactions() {
* @param week the week number
*
* @return list of purchases from the given week
+ *
+ * @throws IllegalArgumentException if week is less than 1.
*/
- public List getPurchases(final int week) {
- List result = new ArrayList<>();
- for (Transaction transaction : transactions) {
- if (transaction instanceof Purchase purchase
- && transaction.getWeek() == week) {
- result.add(purchase);
- }
- }
- return result;
+ public List getPurchases(final int week)
+ throws IllegalArgumentException {
+ return getTransactions(week).stream()
+ .filter(Purchase.class::isInstance)
+ .map(Purchase.class::cast)
+ .toList();
}
/**
@@ -92,15 +104,15 @@ public List getPurchases(final int week) {
* @param week the week number
*
* @return list of sales from the given week
+ *
+ * @throws IllegalArgumentException if week is less than 1.
*/
- public List getSales(final int week) {
- List result = new ArrayList<>();
- for (Transaction transaction : transactions) {
- if (transaction instanceof Sale sale && transaction.getWeek() == week) {
- result.add(sale);
- }
- }
- return result;
+ public List getSales(final int week)
+ throws IllegalArgumentException {
+ return getTransactions(week).stream()
+ .filter(Sale.class::isInstance)
+ .map(Sale.class::cast)
+ .toList();
}
/**
@@ -109,13 +121,9 @@ public List getSales(final int week) {
* @return number of distinct weeks
*/
public int countDistinctWeeks() {
- List weeks = new ArrayList<>();
- for (Transaction transaction : transactions) {
- int week = transaction.getWeek();
- if (!weeks.contains(week)) {
- weeks.add(week);
- }
- }
- return weeks.size();
+ return (int) transactions.stream()
+ .map(Transaction::getWeek)
+ .distinct()
+ .count();
}
}
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchiveTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchiveTest.java
index 091daeb..78ffaf7 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchiveTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchiveTest.java
@@ -1,141 +1,177 @@
package edu.ntnu.idi.idatt2003.g40.mappe.engine;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import edu.ntnu.idi.idatt2003.g40.mappe.model.Purchase;
+import edu.ntnu.idi.idatt2003.g40.mappe.model.Sale;
+import edu.ntnu.idi.idatt2003.g40.mappe.model.Share;
+import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock;
+import edu.ntnu.idi.idatt2003.g40.mappe.model.Transaction;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.PurchaseCalculator;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.SaleCalculator;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionFactory;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionType;
import java.math.BigDecimal;
import java.util.List;
-
-import edu.ntnu.idi.idatt2003.g40.mappe.model.*;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionCalculator;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
final class TransactionArchiveTest {
/**
- * Transaction calculator to be used for testing.
+ * Test transactionArchive.
+ * */
+ private TransactionArchive transactionArchive;
+
+ /**
+ * Sample share used to emulate transactions.
+ * */
+ private Share sampleShare;
+
+ /**
+ * Sample sale calculator to calculate sales.
+ * */
+ private SaleCalculator saleCalculator;
+
+ /**
+ * Sample purchase calculater used to calculate purchases.
* */
- private final TransactionCalculator calculator = new TransactionCalculator() {
- @Override
- public BigDecimal calculateGross() {
- return BigDecimal.ZERO;
- }
-
- @Override
- public BigDecimal calculateCommission() {
- return BigDecimal.ZERO;
- }
-
- @Override
- public BigDecimal calculateTax() {
- return BigDecimal.ZERO;
- }
-
- @Override
- public BigDecimal calculateTotal() {
- return BigDecimal.ZERO;
- }
- };
+ private PurchaseCalculator purchaseCalculator;
+
+ @BeforeEach
+ void setUp() {
+ transactionArchive = new TransactionArchive();
+ Stock sampleStock = new Stock("AAPL", "Apple", new BigDecimal("100.00"));
+ sampleShare = new Share(sampleStock,
+ new BigDecimal("1.00"), sampleStock.getSalesPrice());
+ saleCalculator = new SaleCalculator(sampleShare);
+ purchaseCalculator = new PurchaseCalculator(sampleShare);
+ }
@Test
void newArchiveIsEmpty() {
- TransactionArchive archive = new TransactionArchive();
-
- assertTrue(archive.isEmpty());
+ assertTrue(transactionArchive.isEmpty());
}
@Test
- void addMakesArchiveNonEmpty() {
- TransactionArchive archive = new TransactionArchive();
- Transaction transaction = createPurchase("AAPL", "Apple", 1);
+ void addValidTransactionMakesArchiveNonEmpty() {
+ Transaction purchase = createPurchase(1);
- boolean result = archive.add(transaction);
+ boolean result = transactionArchive.add(purchase);
assertTrue(result);
- assertFalse(archive.isEmpty());
+ assertFalse(transactionArchive.isEmpty());
}
@Test
- void getTransactionsReturnsOnlyTransactionsFromGivenWeek() {
- TransactionArchive archive = new TransactionArchive();
+ void addNullTransactionThrowsException() {
+ Transaction purchase = createPurchase(1);
+ assertDoesNotThrow(() -> transactionArchive.add(purchase));
+ assertThrows(IllegalArgumentException.class,
+ () -> transactionArchive.add(null));
+ }
+
+ @Test
+ void genericGetTransactionsGetsAllTransactions() {
+ Transaction week1Purchase1 = createPurchase(1);
+ Transaction week1Purchase2 = createPurchase(1);
+ Transaction week2Purchase = createPurchase(2);
+ Transaction week4Sale = createPurchase(4);
+
+ transactionArchive.add(week1Purchase1);
+ transactionArchive.add(week1Purchase2);
+ transactionArchive.add(week2Purchase);
+ transactionArchive.add(week4Sale);
+
+ List result = transactionArchive.getTransactions();
+
+ assertEquals(4, result.size());
+ assertTrue(result.contains(week1Purchase1));
+ assertTrue(result.contains(week1Purchase2));
+ assertTrue(result.contains(week2Purchase));
+ assertTrue(result.contains(week4Sale));
+ }
- Transaction transaction1 = createPurchase("AAPL", "Apple", 1);
- Transaction transaction2 = createSale("TSLA", "Tesla", 2);
- Transaction transaction3 = createPurchase("NVDA", "Nvidia", 1);
+ @Test
+ void getTransactionsWithWeekSpecifierReturnsOnlyTransactionsFromGivenWeek() {
+ Transaction week1Purchase1 = createPurchase(1);
+ Transaction week1Purchase2 = createPurchase(1);
+ Transaction week2Purchase = createPurchase(2);
- archive.add(transaction1);
- archive.add(transaction2);
- archive.add(transaction3);
+ transactionArchive.add(week1Purchase1);
+ transactionArchive.add(week1Purchase2);
+ transactionArchive.add(week2Purchase);
- List result = archive.getTransactions(1);
+ List result = transactionArchive.getTransactions(1);
assertEquals(2, result.size());
- assertTrue(result.contains(transaction1));
- assertTrue(result.contains(transaction3));
+ assertTrue(result.contains(week1Purchase1));
+ assertTrue(result.contains(week1Purchase2));
+ assertFalse(result.contains(week2Purchase));
}
@Test
- void getPurchasesReturnsOnlyPurchasesFromGivenWeek() {
- TransactionArchive archive = new TransactionArchive();
+ void getTransactionsWithWeekSpecifierInvalidWeekThrowsException() {
+ assertDoesNotThrow(() -> transactionArchive.getTransactions(1));
+ assertThrows(IllegalArgumentException.class,
+ () -> transactionArchive.getTransactions(0));
+ }
- Purchase purchase1 = createPurchase("AAPL", "Apple", 1);
- Purchase purchase2 = createPurchase("NVDA", "Nvidia", 2);
- Sale sale = createSale("TSLA", "Tesla", 1);
+ @Test
+ void getPurchasesReturnsOnlyPurchasesFromGivenWeek() {
+ Transaction week1Purchase = createPurchase(1);
+ Transaction week2Purchase = createPurchase(2);
+ Transaction week1Sale = createSale(1);
- archive.add(purchase1);
- archive.add(purchase2);
- archive.add(sale);
+ transactionArchive.add(week1Purchase);
+ transactionArchive.add(week2Purchase);
+ transactionArchive.add(week1Sale);
- List result = archive.getPurchases(1);
+ List result = transactionArchive.getPurchases(1);
assertEquals(1, result.size());
- assertTrue(result.contains(purchase1));
+ assertTrue(result.contains(week1Purchase));
}
@Test
void getSalesReturnsOnlySalesFromGivenWeek() {
- TransactionArchive archive = new TransactionArchive();
+ Transaction week1Sale = createSale(1);
+ Transaction week3Sale = createSale(3);
+ Transaction week1Purchase = createPurchase(1);
- Sale sale1 = createSale("TSLA", "Tesla", 1);
- Sale sale2 = createSale("NVDA", "Nvidia", 2);
- Purchase purchase = createPurchase("AAPL", "Apple", 1);
+ transactionArchive.add(week1Sale);
+ transactionArchive.add(week3Sale);
+ transactionArchive.add(week1Purchase);
- archive.add(sale1);
- archive.add(sale2);
- archive.add(purchase);
-
- List result = archive.getSales(1);
+ List result = transactionArchive.getSales(1);
assertEquals(1, result.size());
- assertTrue(result.contains(sale1));
+ assertTrue(result.contains(week1Sale));
}
@Test
void countDistinctWeeksCountsUniqueWeeksOnly() {
- TransactionArchive archive = new TransactionArchive();
-
- archive.add(createPurchase("AAPL", "Apple", 1));
- archive.add(createSale("TSLA", "Tesla", 1));
- archive.add(createPurchase("NVDA", "Nvidia", 2));
- archive.add(createSale("META", "Meta", 3));
+ transactionArchive.add(createPurchase(1));
+ transactionArchive.add(createPurchase(1));
+ transactionArchive.add(createPurchase(2));
+ transactionArchive.add(createSale(3));
- assertEquals(3, archive.countDistinctWeeks());
+ assertEquals(3, transactionArchive.countDistinctWeeks());
}
- private Purchase createPurchase(final String symbol,
- final String company,
- final int week) {
- Stock stock = new Stock(symbol, company, new BigDecimal("100"));
- Share share = new Share(stock, BigDecimal.ONE, new BigDecimal("100"));
- return new Purchase(share, week, calculator);
+ private Transaction createPurchase(int week) {
+ return TransactionFactory.createTransaction(
+ TransactionType.PURCHASE, sampleShare, week, purchaseCalculator
+ );
}
- private Sale createSale(final String symbol,
- final String company,
- final int week) {
- Stock stock = new Stock(symbol, company, new BigDecimal("100"));
- Share share = new Share(stock, BigDecimal.ONE, new BigDecimal("100"));
- return new Sale(share, week, calculator);
+ private Transaction createSale(int week) {
+ return TransactionFactory.createTransaction(
+ TransactionType.SALE, sampleShare, week, saleCalculator
+ );
}
}
From e75440ca13a272decdb05e44258d5d1a0f8e150b Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 15:15:09 +0200
Subject: [PATCH 05/31] Feat: EventManager refactor + test
Added validation for adding subscribers, and refactored test class for eventmanager. Removed unused getname method for eventchannel, and added validator ruleset for valid week.
---
.../g40/mappe/service/event/EventChannel.java | 7 -
.../g40/mappe/service/event/EventManager.java | 19 +-
.../g40/mappe/service/event/EventType.java | 8 -
.../idatt2003/g40/mappe/utils/Validator.java | 6 +
.../mappe/service/event/EventManagerTest.java | 185 +++++++++++++-----
5 files changed, 152 insertions(+), 73 deletions(-)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventChannel.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventChannel.java
index e67e360..215547e 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventChannel.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventChannel.java
@@ -8,11 +8,4 @@
* Decreases coupling and enables testing of event types.
* */
public interface EventChannel {
-
- /**
- * Getter method for enum name.
- *
- * @return String name of enum.
- * */
- String getName();
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManager.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManager.java
index a4a2bad..79e4f7b 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManager.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManager.java
@@ -32,9 +32,20 @@ public final class EventManager {
* @param subscriber the {@link EventSubscriber} object to add.
* @param eventChannel the {@link EventChannel} type this subscriber should subscribe to.
*
- *
+ * @throws IllegalArgumentException if subscriber is already subscribed to event channel,
+ * or if parameters are null.
*/
- public void addSubscriber(final EventSubscriber subscriber, final EventChannel eventChannel) {
+ public void addSubscriber(final EventSubscriber subscriber, final EventChannel eventChannel)
+ throws IllegalArgumentException {
+ if (subscriber == null || eventChannel == null) {
+ throw new IllegalArgumentException("Parameters cannot be null!");
+ }
+
+ List subscribers = subscriberMap.get(eventChannel);
+ if (subscribers != null && subscribers.contains(subscriber)) {
+ throw new IllegalArgumentException("Subscriber already subscribed to event channel!");
+ }
+
subscriberMap.computeIfAbsent(eventChannel, k -> new ArrayList<>()).add(subscriber);
}
@@ -49,9 +60,9 @@ public void addSubscriber(final EventSubscriber subscriber, final EventChannel e
*/
public void invokeEvent(final EventData data)
throws IllegalArgumentException {
- if (data == null || !subscriberMap.containsKey(data.channel())) {
+ if (data == null || data.data() == null) {
throw new IllegalArgumentException(
- "No subscriber listening to this event!"
+ "Data cannot be null!"
);
} else {
for (EventSubscriber e : subscriberMap.get(data.channel())) {
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventType.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventType.java
index ace4933..215ff37 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventType.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventType.java
@@ -51,12 +51,4 @@ public enum EventType implements EventChannel {
*
*/
SELECT_STOCK_FOR_MINIGAME;
-
- /**
- * {@inheritDoc}
- * */
- @Override
- public String getName() {
- return this.name();
- }
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/Validator.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/Validator.java
index b31108e..a9969db 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/Validator.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/Validator.java
@@ -63,6 +63,12 @@ public enum Validator {
* */
VALID_POSITIVE_INT(VALID_INT.validationRule.and(s ->
Integer.parseInt(s) >= 0), "Number is not positive!"),
+
+ /**
+ * Rule that checks if a string represents a valid week. (Greater than 1).
+ * */
+ VALID_WEEK(VALID_POSITIVE_INT.validationRule.and(s ->
+ Integer.parseInt(s) > 0), "Invalid week"),
/**
* Rule that checks if string is not empty,
* and if it can be parsed into a {@link LocalDate} object.
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManagerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManagerTest.java
index 716e96e..4852d1c 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManagerTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManagerTest.java
@@ -1,99 +1,176 @@
package edu.ntnu.idi.idatt2003.g40.mappe.service.event;
-import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewData;
-import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewEnum;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-
class EventManagerTest {
private enum TestEventTypes implements EventChannel {
+ /**
+ * Test event type 1.
+ * */
TEST_TYPE_1,
+
+ /**
+ * Test event type 2.
+ * */
TEST_TYPE_2,
+
+ /**
+ * Test event type 3 (Used by both subscribers).
+ * */
TEST_TYPE_3;
- @Override
- public String getName() {
- return this.name();
- }
}
- private GenericEventPublisher testEventPublisher;
- private GenericEventPublisher testEventPublisher2;
- private GenericEventPublisher testEventPublisher3;
-
- private GenericEventSubscriber testEventSubscriber;
- private GenericEventSubscriber testEventSubscriber2;
-
private EventManager testEventManager;
+ private SampleEventSubscriber sampleEventSubscriber1;
+ private SampleEventSubscriber sampleEventSubscriber2;
@BeforeEach
void setUp() {
testEventManager = new EventManager();
- testEventSubscriber = new GenericEventSubscriber();
- testEventSubscriber2 = new GenericEventSubscriber();
+ sampleEventSubscriber1 = new SampleEventSubscriber();
+ sampleEventSubscriber2 = new SampleEventSubscriber();
+
+ testEventManager.addSubscriber(sampleEventSubscriber1, TestEventTypes.TEST_TYPE_1);
+ testEventManager.addSubscriber(sampleEventSubscriber1, TestEventTypes.TEST_TYPE_3);
+
+ testEventManager.addSubscriber(sampleEventSubscriber2, TestEventTypes.TEST_TYPE_2);
+ testEventManager.addSubscriber(sampleEventSubscriber2, TestEventTypes.TEST_TYPE_3);
+ }
- testEventPublisher = new GenericEventPublisher(testEventManager, TestEventTypes.TEST_TYPE_1);
- testEventPublisher2 = new GenericEventPublisher(testEventManager, TestEventTypes.TEST_TYPE_2);
- testEventPublisher3 = new GenericEventPublisher(testEventManager, TestEventTypes.TEST_TYPE_3);
+ @Test
+ void addingSubscriberNullParametersThrowsException() {
+ assertThrows(IllegalArgumentException.class,
+ () -> testEventManager.addSubscriber(
+ null,
+ TestEventTypes.TEST_TYPE_2));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> testEventManager.addSubscriber(
+ sampleEventSubscriber1,
+ null));
+
+ // Will only throw exception if sample event subscriber 1 is duplicate.
+ assertDoesNotThrow(
+ () -> testEventManager.addSubscriber(
+ sampleEventSubscriber1,
+ TestEventTypes.TEST_TYPE_2));
- testEventManager.addSubscriber(testEventSubscriber, TestEventTypes.TEST_TYPE_1);
- testEventManager.addSubscriber(testEventSubscriber2, TestEventTypes.TEST_TYPE_2);
+ }
+
+ @Test
+ void addingDuplicateSubscriberForSameChannelThrowsException() {
+ assertDoesNotThrow(
+ () -> testEventManager.addSubscriber(
+ sampleEventSubscriber1,
+ TestEventTypes.TEST_TYPE_2));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> testEventManager.addSubscriber(
+ sampleEventSubscriber1,
+ TestEventTypes.TEST_TYPE_1));
}
@Test
void firedEventCaughtByCorrectSubscriber() {
- assertFalse(testEventSubscriber.invokedEvent);
- testEventPublisher.fireEvent();
- assertTrue(testEventSubscriber.invokedEvent);
+ String dataToSend = "Data for type 1";
+
+ assertFalse(sampleEventSubscriber1.getInvoked());
+ assertNull(sampleEventSubscriber1.getLastReceivedData());
+
+ EventData eventData = new EventData<>(
+ TestEventTypes.TEST_TYPE_1,
+ dataToSend
+ );
+ testEventManager.invokeEvent(eventData);
+
+ assertTrue(sampleEventSubscriber1.getInvoked());
+ assertEquals(dataToSend, sampleEventSubscriber1.getLastReceivedData());
}
@Test
void firedEventNotCaughtByIncorrectSubscriber() {
- assertFalse(testEventSubscriber.invokedEvent);
- testEventPublisher2.fireEvent();
- assertFalse(testEventSubscriber.invokedEvent);
+ String dataToSend = "Data for type 1";
+
+ assertFalse(sampleEventSubscriber1.getInvoked());
+ assertNull(sampleEventSubscriber1.getLastReceivedData());
+ assertFalse(sampleEventSubscriber2.getInvoked());
+ assertNull(sampleEventSubscriber2.getLastReceivedData());
+
+ EventData eventData = new EventData<>(
+ TestEventTypes.TEST_TYPE_2,
+ dataToSend
+ );
+ testEventManager.invokeEvent(eventData);
+
+ assertFalse(sampleEventSubscriber1.getInvoked());
+ assertNull(sampleEventSubscriber1.getLastReceivedData());
+
+ assertTrue(sampleEventSubscriber2.getInvoked());
+ assertEquals(dataToSend, sampleEventSubscriber2.getLastReceivedData());
}
@Test
- void firedEventThrowsErrorWhenNoSubscribers() {
- assertFalse(testEventSubscriber.invokedEvent);
- assertThrows(IllegalArgumentException.class, () -> {
- testEventPublisher3.fireEvent();
- });
- assertFalse(testEventSubscriber.invokedEvent);
- }
+ void firedEventsWithMultipleSubscribersCaughtByAllSubscribers() {
+ String dataToSend = "Data for type 1";
- private class GenericEventPublisher implements EventPublisher {
+ assertFalse(sampleEventSubscriber1.getInvoked());
+ assertNull(sampleEventSubscriber1.getLastReceivedData());
+ assertFalse(sampleEventSubscriber2.getInvoked());
+ assertNull(sampleEventSubscriber2.getLastReceivedData());
- private final ViewData viewData;
- private final EventData eventData;
- private final EventManager eventManager;
+ EventData eventData = new EventData<>(
+ TestEventTypes.TEST_TYPE_3,
+ dataToSend
+ );
+ testEventManager.invokeEvent(eventData);
- public GenericEventPublisher(final EventManager eventManager, final TestEventTypes eventType) {
- viewData = new ViewData(ViewEnum.IN_GAME);
- eventData = new EventData(eventType, viewData);
- this.eventManager = eventManager;
- }
+ assertTrue(sampleEventSubscriber1.getInvoked());
+ assertEquals(dataToSend, sampleEventSubscriber1.getLastReceivedData());
- public void fireEvent() {
- invoke(eventData, eventManager);
- }
+ assertTrue(sampleEventSubscriber2.getInvoked());
+ assertEquals(dataToSend, sampleEventSubscriber2.getLastReceivedData());
+ }
- @Override
- public void invoke(EventData data, EventManager eventManager) {
- eventManager.invokeEvent(data);
- }
+ @Test
+ void firedEventThrowsErrorWhenDataIsNull() {
+
+ EventData invalidEventData = new EventData<>(
+ TestEventTypes.TEST_TYPE_1,
+ null
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> testEventManager.invokeEvent(null));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> testEventManager.invokeEvent(invalidEventData));
}
- private class GenericEventSubscriber implements EventSubscriber {
- public boolean invokedEvent = false;
+ private static class SampleEventSubscriber implements EventSubscriber {
+ private boolean invokedEvent = false;
+ private Object lastReceivedData = null;
@Override
- public void handleEvent(EventData data) {
+ public void handleEvent(final EventData data) {
invokedEvent = true;
+ lastReceivedData = data.data();
+ }
+
+ private boolean getInvoked() {
+ return invokedEvent;
+ }
+
+ private Object getLastReceivedData() {
+ return lastReceivedData;
}
}
}
\ No newline at end of file
From 7b3de6068f350b4f5dfd3481dd32f066f6796371 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 17:59:20 +0200
Subject: [PATCH 06/31] Feat: Changed how portfolio manages shares
Previously, the portfolio held a list over multiple share objects. Now, it holds a map keyed with symbol and value is a share determined by the quantity of all shares of that symbol. This enables a more intuitive way of storing shares.
---
.../idatt2003/g40/mappe/engine/Exchange.java | 75 +++------
.../idatt2003/g40/mappe/model/Portfolio.java | 133 +++++++++-------
.../dashboard/DashBoardController.java | 8 +-
.../g40/mappe/engine/ExchangeTest.java | 49 ++----
.../mappe/engine/TransactionArchiveTest.java | 22 +--
.../g40/mappe/model/PortfolioTest.java | 150 ++++++++++++------
.../mappe/service/TransactionFactoryTest.java | 61 +++----
7 files changed, 244 insertions(+), 254 deletions(-)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java
index bc1ec00..c467434 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java
@@ -4,9 +4,6 @@
import edu.ntnu.idi.idatt2003.g40.mappe.model.Share;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Transaction;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.PurchaseCalculator;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.SaleCalculator;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionCalculator;
import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionFactory;
import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionType;
import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator;
@@ -16,7 +13,7 @@
import java.util.List;
import java.util.Map;
import java.util.Random;
-import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
/**
@@ -30,7 +27,7 @@
* Advances week.
*
* @see Player
- * @see TransactionCalculator
+ * @see edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionCalculator
*
* @version 1.0.0
* */
@@ -98,12 +95,12 @@ public int getWeek() {
}
/**
- * Getter method for the {@link IntegerProperty} object of week.
+ * Getter method for the {@link ReadOnlyIntegerProperty} object of week.
*
* @return week.
* */
- public IntegerProperty weekProperty() {
- return week;
+ public ReadOnlyIntegerProperty weekProperty() {
+ return week.getReadOnlyProperty();
}
/**
@@ -177,9 +174,8 @@ public Transaction buy(final String symbol,
}
Stock stock = getStock(symbol);
Share share = new Share(stock, quantity, stock.getSalesPrice());
- TransactionCalculator calculator = new PurchaseCalculator(share);
Transaction purchase = TransactionFactory.createTransaction(
- TransactionType.PURCHASE, share, getWeek(), calculator
+ TransactionType.PURCHASE, share, getWeek()
);
player.handleTransaction(purchase);
return purchase;
@@ -200,9 +196,8 @@ public Transaction sell(final Share share, final Player player)
if (share == null || player == null) {
throw new IllegalArgumentException("Invalid sell!");
}
- TransactionCalculator calculator = new SaleCalculator(share);
Transaction sale = TransactionFactory.createTransaction(
- TransactionType.SALE, share, getWeek(), calculator
+ TransactionType.SALE, share, getWeek()
);
player.handleTransaction(sale);
return sale;
@@ -212,9 +207,7 @@ TransactionType.SALE, share, getWeek(), calculator
* Method called when a player sells share,
* defined by an amount instead of specific {@link Share} object.
*
- * Might split shares into multiples to ensure proper selling.
- * Uses the {@link edu.ntnu.idi.idatt2003.g40.mappe.model.Portfolio}
- * to split.
+ * {@link edu.ntnu.idi.idatt2003.g40.mappe.model.Portfolio}
*
* @param amount the amount of "shares" to sell.
* @param stockSymbol the stock to sell shares in.
@@ -227,58 +220,38 @@ TransactionType.SALE, share, getWeek(), calculator
* or if player does not own any shares
* of the given stock.
* */
- public List sell(BigDecimal amount,
+ public Transaction sell(BigDecimal amount,
final String stockSymbol,
final Player player)
throws IllegalArgumentException {
if (amount == null
- || player == null
- || !Validator.VALID_STOCK_SYMBOL.isValid(stockSymbol)) {
- throw new IllegalArgumentException("Invalid sell!");
+ || player == null
+ || !Validator.VALID_STOCK_SYMBOL.isValid(stockSymbol)) {
+ throw new IllegalArgumentException("Invalid sell parameters!");
}
- // Get all shares the player owns of the stock given in the parameter.
- List sharesOfStock = player.getPortfolio().getShares().stream()
- .filter(s -> s.getStock().getSymbol().equals(stockSymbol))
- .toList();
+ List matchingShares = player.getPortfolio().getShares(stockSymbol);
- // Throws error if player does not own any shares of the given stock.
- if (sharesOfStock.isEmpty()) {
- throw new IllegalArgumentException("Player does not own"
- + " any shares of this stock!");
+ if (matchingShares.isEmpty()) {
+ throw new IllegalArgumentException("Player does not own any shares of this stock!");
}
- // Gets the total quantity amount of shares owned of given stock.
- BigDecimal totalOwned = player.getPortfolio()
- .getTotalSharesBySymbol(stockSymbol);
+ Share ownedPosition = matchingShares.getFirst();
+ BigDecimal totalOwned = ownedPosition.getQuantity();
- // If amount wanted to sell is greater than total owned,
- // sells entire collection.
if (amount.compareTo(totalOwned) > 0) {
amount = totalOwned;
}
- ArrayList transactions = new ArrayList<>();
- BigDecimal remainingToSell = amount;
- // For every share owned, sells if the quantity is less than
- // or equal to the remaining amount of shares to sell.
- // Splits share if the remaining amount to sell is less than share quantity.
- for (Share share : sharesOfStock) {
+ Stock stock = ownedPosition.getStock();
+ Share shareToSell = new Share(stock, amount, stock.getSalesPrice());
- BigDecimal shareQty = share.getQuantity();
+ Transaction sale = TransactionFactory.createTransaction(
+ TransactionType.SALE, shareToSell, getWeek()
+ );
+ player.handleTransaction(sale);
- if (shareQty.compareTo(remainingToSell) <= 0) {
- remainingToSell = remainingToSell.subtract(shareQty);
- transactions.add(sell(share, player));
- } else {
- Share newShare = player.getPortfolio().splitShare(
- share, remainingToSell
- );
- transactions.add(sell(newShare, player));
- break;
- }
- }
- return transactions;
+ return sale;
}
/**
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java
index 0c7ea5e..333d2e2 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java
@@ -1,13 +1,10 @@
package edu.ntnu.idi.idatt2003.g40.mappe.model;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.PurchaseCalculator;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.SaleCalculator;
import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator;
-
import java.math.BigDecimal;
-import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
-import java.util.Objects;
+import java.util.Map;
/**
* Represents a player's portfolio of shares.
@@ -18,9 +15,9 @@
public final class Portfolio {
/**
- * List of shares.
+ * Map used to handle internal shares.
* */
- private final List shares = new ArrayList<>();
+ private final Map shares = new HashMap<>();
/**
* Creates an empty portfolio.
@@ -30,24 +27,41 @@ public Portfolio() {
}
/**
- * Adds a share to the portfolio.
+ * Adds a share to the portfolio. If share already exists, merges shares.
*
* @param share the share to add
*
- * @return {@code true} if the share was added, {@code false} otherwise
- *
* @throws IllegalArgumentException if share is null.
*/
- public boolean addShare(final Share share) throws IllegalArgumentException {
+ public void addShare(final Share share) throws IllegalArgumentException {
if (share == null) {
throw new IllegalArgumentException("Invalid share!");
}
- return shares.add(share);
+ String symbol = share.getStock().getSymbol().toUpperCase();
+
+ if (shares.containsKey(symbol)) {
+ Share existingShare = shares.get(symbol);
+ BigDecimal totalQuantity =
+ existingShare.getQuantity().add(share.getQuantity());
+
+ shares.put(symbol,
+ new Share(
+ share.getStock(),
+ totalQuantity, existingShare.getPurchasePrice()
+ )
+ );
+ } else {
+ shares.put(symbol, share);
+ }
}
/**
* Removes a share from the portfolio.
*
+ * Uses the quantity value to deduct share amount from the map.
+ * If quantity to remove is equal to amount held, removes share entirely.
+ * If not, splits the share.
+ *
* @param share the share to remove
*
* @return {@code true} if the share was removed,
@@ -56,11 +70,37 @@ public boolean addShare(final Share share) throws IllegalArgumentException {
* @throws IllegalArgumentException if share is null.
*
*/
- public boolean removeShare(final Share share) throws IllegalArgumentException {
+ public boolean removeShare(final Share share)
+ throws IllegalArgumentException {
if (share == null) {
throw new IllegalArgumentException("Invalid share!");
}
- return shares.remove(share);
+ String symbol = share.getStock().getSymbol().toUpperCase();
+ if (!shares.containsKey(symbol)) {
+ return false;
+ }
+
+ Share ownedShare = shares.get(symbol);
+ int comparison = ownedShare.getQuantity().compareTo(share.getQuantity());
+
+ if (comparison < 0) {
+ throw new IllegalArgumentException(
+ "Cannot remove more shares than are currently owned!");
+ } else if (comparison == 0) {
+
+ shares.remove(symbol);
+ } else {
+ BigDecimal remainingQuantity =
+ ownedShare.getQuantity().subtract(share.getQuantity());
+ shares.put(symbol,
+ new Share(
+ share.getStock(),
+ remainingQuantity,
+ ownedShare.getPurchasePrice()
+ )
+ );
+ }
+ return true;
}
/**
@@ -69,7 +109,7 @@ public boolean removeShare(final Share share) throws IllegalArgumentException {
* @return a list of shares
*/
public List getShares() {
- return List.copyOf(shares);
+ return List.copyOf(shares.values());
}
/**
@@ -81,14 +121,14 @@ public List getShares() {
*
* @throws IllegalArgumentException if symbol is invalid.
*/
- public List getShares(final String symbol) throws IllegalArgumentException {
+ public List getShares(final String symbol)
+ throws IllegalArgumentException {
if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol)) {
throw new IllegalArgumentException(
Validator.VALID_STOCK_SYMBOL.getErrorMessage());
}
- return shares.stream()
- .filter(s -> symbol.equalsIgnoreCase(s.getStock().getSymbol()))
- .toList();
+ Share share = shares.get(symbol.toUpperCase());
+ return share != null ? List.of(share) : List.of();
}
/**
@@ -101,11 +141,15 @@ public List getShares(final String symbol) throws IllegalArgumentExceptio
*
* @throws IllegalArgumentException if share is null.
*/
- public boolean contains(final Share share) throws IllegalArgumentException {
+ public boolean contains(final Share share)
+ throws IllegalArgumentException {
if (share == null) {
throw new IllegalArgumentException("Invalid share!");
}
- return shares.contains(share);
+ String symbol = share.getStock().getSymbol().toUpperCase();
+ Share owned = shares.get(symbol);
+ return owned != null
+ && owned.getQuantity().compareTo(share.getQuantity()) >= 0;
}
/**
@@ -115,11 +159,11 @@ public boolean contains(final Share share) throws IllegalArgumentException {
* @return the net worth.
* */
public BigDecimal getNetWorth() {
- BigDecimal netWorth = new BigDecimal("0");
-
- for (Share s : shares) {
- SaleCalculator calculator = new SaleCalculator(s);
- netWorth = netWorth.add(calculator.calculateTotal());
+ BigDecimal netWorth = BigDecimal.ZERO;
+ for (Share s : shares.values()) {
+ netWorth = netWorth.add(
+ s.getQuantity().multiply(s.getStock().getSalesPrice())
+ );
}
return netWorth;
}
@@ -127,37 +171,16 @@ public BigDecimal getNetWorth() {
/**
* Helper method to get total amount of shares owned in a specific stock.
*
- * @param symbol the symbol of the stock to check for shares.
- * */
- public BigDecimal getTotalSharesBySymbol(final String symbol) {
- return shares.stream()
- .filter(s -> s.getStock().getSymbol().equals(symbol))
- .map(Share::getQuantity)
- .reduce(BigDecimal.ZERO, BigDecimal::add);
- }
-
- /**
- * "Splits" a share in two pieces based on an amount.
- *
- * @param share the share to split.
- * @param splitAmount the amount to split by.
+ * @param symbol the symbol of the stock to check for shares.
*
- * @return the split share from the original to the split amount.
- *
- * @throws IllegalArgumentException if share or split amount is invalid.
+ * @return BigDecimal representing total quantity of all
+ * shares of this symbol.
* */
- public Share splitShare(final Share share, final BigDecimal splitAmount)
- throws IllegalArgumentException {
- if (!contains(share) || splitAmount.compareTo(share.getQuantity()) > 0) {
- throw new IllegalArgumentException("Cannot split share!");
+ public BigDecimal getTotalSharesBySymbol(final String symbol) {
+ if (symbol == null) {
+ return BigDecimal.ZERO;
}
- BigDecimal remainingAmount = share.getQuantity().subtract(splitAmount);
-
- Share newShare1 = new Share(share.getStock(), splitAmount, share.getPurchasePrice());
- Share newShare2 = new Share(share.getStock(), remainingAmount, share.getPurchasePrice());
- removeShare(share);
- addShare(newShare1);
- addShare(newShare2);
- return newShare1;
+ Share share = shares.get(symbol.toUpperCase());
+ return share != null ? share.getQuantity() : BigDecimal.ZERO;
}
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java
index 9d31d0d..fa1333c 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java
@@ -149,15 +149,13 @@ protected void initInteractions() {
getViewElement().setOnAction(DashBoardActions.SELL_SHARES, () -> {
if (Validator.NOT_EMPTY.isValid(getViewElement().getQuantityInputField().getText())
&& Float.parseFloat(getViewElement().getQuantityInputField().getText()) > 0) {
- List transactions = exchange.sell(
+ Transaction sale = exchange.sell(
new BigDecimal(getViewElement().getQuantityInputField().getText()),
getViewElement().getCurrentStock().getSymbol(),
player);
- for (Transaction t : transactions) {
- if(t.isCommited()) {
- getViewElement().addOwnedShares(-t.getShare().getQuantity().floatValue());
- }
+ if(sale.isCommited()) {
+ getViewElement().addOwnedShares(-sale.getShare().getQuantity().floatValue());
}
}
});
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/ExchangeTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/ExchangeTest.java
index 147810b..66ce0c5 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/ExchangeTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/ExchangeTest.java
@@ -187,44 +187,18 @@ void sellShareObjectThrowsExceptionOnIllegalArguments() {
}
@Test
- void sellingPartialSharesUpdatesPlayerMoneyAndPortfolioCorrectly() {
+ void sellingSharesBasedOnAmountFunctionsAsIntended() {
testExchange.buy(appleStock.getSymbol(), new BigDecimal("3"), testPlayer);
- List sales =
- testExchange.sell(new BigDecimal("1.5"), appleStock.getSymbol(), testPlayer);
-
- assertEquals(1, sales.size());
+ Transaction sale = testExchange.sell(new BigDecimal("1.5"), appleStock.getSymbol(), testPlayer);
+ assertInstanceOf(Sale.class, sale);
BigDecimal expectedPlayerMoney = new BigDecimal("847.000");
assertEquals(expectedPlayerMoney, testPlayer.getMoney());
+ BigDecimal actualQuantity = testPlayer.getPortfolio().getShares(appleStock.getSymbol()).getFirst().getQuantity();
BigDecimal expectedRemainingShares = new BigDecimal("1.5");
- assertEquals(expectedRemainingShares,
- testPlayer
- .getPortfolio()
- .getShares(appleStock.getSymbol())
- .getFirst()
- .getQuantity()
- );
- }
-
- @Test
- void sellingPartialAmountOfSharesRequiringSplitSplitsSharesCorrectly() {
- testExchange.buy(appleStock.getSymbol(),
- new BigDecimal("3.12"), testPlayer);
- testExchange.buy(appleStock.getSymbol(),
- new BigDecimal("4.56"), testPlayer);
-
- List sales =
- testExchange.sell(new BigDecimal("6.00"),
- appleStock.getSymbol(), testPlayer);
-
- assertEquals(2, sales.size());
- assertEquals(new BigDecimal("3.12"), sales.get(0).getShare().getQuantity());
- assertEquals(new BigDecimal("2.88"), sales.get(1).getShare().getQuantity());
-
- BigDecimal expectedPlayerMoney = new BigDecimal("822.16000");
- assertEquals(expectedPlayerMoney, testPlayer.getMoney());
+ assertEquals(0, expectedRemainingShares.compareTo(actualQuantity));
}
@Test
@@ -234,16 +208,13 @@ void attemptingToSellMoreSharesThanOwnedSellsAllSharesOwned() {
testExchange.buy(appleStock.getSymbol(),
new BigDecimal("4.56"), testPlayer);
- List sales =
- testExchange.sell(new BigDecimal("100.00"),
- appleStock.getSymbol(), testPlayer);
- assertEquals(2, sales.size());
-
- assertEquals(new BigDecimal("3.12"), sales.get(0).getShare().getQuantity());
- assertEquals(new BigDecimal("4.56"), sales.get(1).getShare().getQuantity());
+ testExchange.sell(new BigDecimal("100.00"),
+ appleStock.getSymbol(), testPlayer);
BigDecimal expectedPlayerMoney = new BigDecimal("988.48000");
- assertEquals(expectedPlayerMoney, testPlayer.getMoney());
+ assertEquals(0, expectedPlayerMoney.compareTo(testPlayer.getMoney()));
+
+ assertTrue(testPlayer.getPortfolio().getShares(appleStock.getSymbol()).isEmpty());
}
@Test
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchiveTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchiveTest.java
index 78ffaf7..fce1960 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchiveTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/TransactionArchiveTest.java
@@ -11,8 +11,6 @@
import edu.ntnu.idi.idatt2003.g40.mappe.model.Share;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Transaction;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.PurchaseCalculator;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.SaleCalculator;
import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionFactory;
import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionType;
import java.math.BigDecimal;
@@ -32,24 +30,12 @@ final class TransactionArchiveTest {
* */
private Share sampleShare;
- /**
- * Sample sale calculator to calculate sales.
- * */
- private SaleCalculator saleCalculator;
-
- /**
- * Sample purchase calculater used to calculate purchases.
- * */
- private PurchaseCalculator purchaseCalculator;
-
@BeforeEach
void setUp() {
transactionArchive = new TransactionArchive();
Stock sampleStock = new Stock("AAPL", "Apple", new BigDecimal("100.00"));
sampleShare = new Share(sampleStock,
new BigDecimal("1.00"), sampleStock.getSalesPrice());
- saleCalculator = new SaleCalculator(sampleShare);
- purchaseCalculator = new PurchaseCalculator(sampleShare);
}
@Test
@@ -163,15 +149,15 @@ void countDistinctWeeksCountsUniqueWeeksOnly() {
assertEquals(3, transactionArchive.countDistinctWeeks());
}
- private Transaction createPurchase(int week) {
+ private Transaction createPurchase(final int week) {
return TransactionFactory.createTransaction(
- TransactionType.PURCHASE, sampleShare, week, purchaseCalculator
+ TransactionType.PURCHASE, sampleShare, week
);
}
- private Transaction createSale(int week) {
+ private Transaction createSale(final int week) {
return TransactionFactory.createTransaction(
- TransactionType.SALE, sampleShare, week, saleCalculator
+ TransactionType.SALE, sampleShare, week
);
}
}
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PortfolioTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PortfolioTest.java
index 470f26a..781e06f 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PortfolioTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PortfolioTest.java
@@ -1,104 +1,152 @@
package edu.ntnu.idi.idatt2003.g40.mappe.model;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.math.BigDecimal;
import java.util.List;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.SaleCalculator;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
final class PortfolioTest {
+ /**
+ * Test portfolio used.
+ * */
+ private Portfolio testPortfolio;
+
+ /**
+ * Test stock used.
+ * */
+ private Stock testStock;
+
+ /**
+ * Test share used.
+ * */
+ private Share testShare;
+
+ @BeforeEach
+ void setUp() {
+ testPortfolio = new Portfolio();
+ testStock = new Stock("AAPL", "Apple", new BigDecimal("150"));
+ testShare = new Share(testStock, new BigDecimal("2"), new BigDecimal("100"));
+ }
+
@Test
void addShareAddsShareToPortfolio() {
- Portfolio portfolio = new Portfolio();
- Stock stock = new Stock("AAPL", "Apple", new BigDecimal("150"));
- Share share = new Share(stock, new BigDecimal("2"), new BigDecimal("100"));
+ assertDoesNotThrow(
+ () -> testPortfolio.addShare(testShare));
- boolean result = portfolio.addShare(share);
+ assertTrue(testPortfolio.contains(testShare));
+ }
- assertTrue(result);
- assertTrue(portfolio.contains(share));
+ @Test
+ void addingNullShareThrowsException() {
+ assertThrows(IllegalArgumentException.class,
+ () -> testPortfolio.addShare(null));
+
+ assertFalse(testPortfolio.contains(testShare));
}
@Test
void removeShareRemovesShareFromPortfolio() {
- Portfolio portfolio = new Portfolio();
- Stock stock = new Stock("TSLA", "Tesla", new BigDecimal("200"));
- Share share = new Share(stock, new BigDecimal("1"), new BigDecimal("200"));
+ assertFalse(testPortfolio.contains(testShare));
+
+ testPortfolio.addShare(testShare);
- portfolio.addShare(share);
- boolean result = portfolio.removeShare(share);
+ assertTrue(testPortfolio.contains(testShare));
+
+ boolean result = testPortfolio.removeShare(testShare);
assertTrue(result);
- assertFalse(portfolio.contains(share));
+ assertFalse(testPortfolio.contains(testShare));
}
@Test
- void getSharesReturnsAllShares() {
- Portfolio portfolio = new Portfolio();
+ void removingNullShareThrowsException() {
+ assertThrows(IllegalArgumentException.class,
+ () -> testPortfolio.removeShare(null));
+ }
- Stock stock = new Stock("AAPL", "Apple", new BigDecimal("150"));
- Share share = new Share(stock, new BigDecimal("3"), new BigDecimal("150"));
+ @Test
+ void removingMoreSharesThanOwnedThrowsException() {
+ testPortfolio.addShare(testShare);
+ Share testShare2 = new Share(testStock, new BigDecimal("4"), testStock.getSalesPrice());
+ assertThrows(IllegalArgumentException.class,
+ () -> testPortfolio.removeShare(testShare2)
+ );
+ }
- portfolio.addShare(share);
+ @Test
+ void getSharesReturnsAllShares() {
+ testPortfolio.addShare(testShare);
- List shares = portfolio.getShares();
+ List shares = testPortfolio.getShares();
assertEquals(1, shares.size());
- assertTrue(shares.contains(share));
+ assertTrue(shares.contains(testShare));
}
@Test
void getSharesWithSymbolReturnsMatchingShares() {
- Portfolio portfolio = new Portfolio();
+ Stock testStock2 = new Stock("TSLA", "Tesla", new BigDecimal("200"));
- Stock apple = new Stock("AAPL", "Apple", new BigDecimal("150"));
- Stock tesla = new Stock("TSLA", "Tesla", new BigDecimal("200"));
-
- Share appleShare = new Share(apple,
- new BigDecimal("1"),
- new BigDecimal("150"));
- Share teslaShare = new Share(tesla,
+ Share testShare2 = new Share(testStock2,
new BigDecimal("1"),
new BigDecimal("200"));
- portfolio.addShare(appleShare);
- portfolio.addShare(teslaShare);
+ testPortfolio.addShare(testShare);
+ testPortfolio.addShare(testShare2);
- List result = portfolio.getShares("AAPL");
+ List result = testPortfolio.getShares("AAPL");
assertEquals(1, result.size());
- assertTrue(result.contains(appleShare));
+ assertTrue(result.contains(testShare));
}
@Test
- void containsReturnsFalseWhenShareNotPresent() {
- Portfolio portfolio = new Portfolio();
-
- Stock stock = new Stock("NVDA", "Nvidia", new BigDecimal("800"));
- Share share = new Share(stock, new BigDecimal("1"), new BigDecimal("800"));
-
- assertFalse(portfolio.contains(share));
+ void getSharesWithSymbolThrowsExceptionOnIllegalArgument() {
+ assertThrows(IllegalArgumentException.class,
+ () -> testPortfolio.getShares(null)
+ );
}
@Test
- void getNetWorthReturnsNetWorth() {
- Portfolio portfolio = new Portfolio();
-
- Stock stock = new Stock("NVDA", "Nvidia", new BigDecimal("800"));
- Share share = new Share(stock, new BigDecimal("1"), new BigDecimal("800"));
- portfolio.addShare(share);
-
- SaleCalculator saleCalculator = new SaleCalculator(share);
-
- BigDecimal calculatedNetWorth = saleCalculator.calculateTotal();
+ void containsReturnsFalseWhenShareNotPresent() {
+ assertFalse(testPortfolio.contains(testShare));
+ }
- BigDecimal actualNetWorth = portfolio.getNetWorth();
+ @Test
+ void containsThrowsExceptionOnIllegalArgument() {
+ assertThrows(IllegalArgumentException.class,
+ () -> testPortfolio.contains(null)
+ );
+ }
- assertEquals(calculatedNetWorth, actualNetWorth);
+ @Test
+ void getTotalSharesBySymbolReturnsCorrectValues() {
+ assertEquals(0,
+ BigDecimal.ZERO.compareTo(
+ testPortfolio.getTotalSharesBySymbol("AAPL")
+ )
+ );
+
+ assertEquals(0,
+ BigDecimal.ZERO.compareTo(
+ testPortfolio.getTotalSharesBySymbol(null)
+ )
+ );
+
+ testPortfolio.addShare(testShare);
+
+ assertEquals(0,
+ new BigDecimal("2.0").compareTo(
+ testPortfolio.getTotalSharesBySymbol("AAPL")
+ )
+ );
}
}
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactoryTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactoryTest.java
index 6b25c35..1f49c32 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactoryTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactoryTest.java
@@ -1,7 +1,9 @@
package edu.ntnu.idi.idatt2003.g40.mappe.service;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Purchase;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Sale;
@@ -15,56 +17,45 @@
class TransactionFactoryTest {
private Stock testStock;
private Share testShare;
- private Sale testSale;
- private Purchase testPurchase;
- private SaleCalculator testSaleCalculator;
- private PurchaseCalculator testPurchaseCalculator;
@BeforeEach
void setUp() {
testStock = new Stock("AAPL", "APPLE INC.", new BigDecimal("100.00"));
testShare = new Share(testStock, new BigDecimal("10.0"), testStock.getSalesPrice());
- testSaleCalculator = new SaleCalculator(testShare);
- testSale = new Sale(testShare, 1, testSaleCalculator);
- testPurchaseCalculator = new PurchaseCalculator(testShare);
- testPurchase = new Purchase(testShare, 1, testPurchaseCalculator);
}
@Test
void factoryReturnsCorrectSale() {
- Transaction sale2 = TransactionFactory.createTransaction(TransactionType.SALE, testShare, 1, testSaleCalculator);
- assertTrue(equalTransactions(testSale, sale2));
+ int targetWeek = 1;
+ Transaction transaction = TransactionFactory.createTransaction(TransactionType.SALE, testShare, targetWeek);
+ assertNotNull(transaction);
+ assertInstanceOf(Sale.class, transaction);
+ assertEquals(targetWeek, transaction.getWeek());
+ assertEquals(testShare, transaction.getShare());
+ assertNotNull(transaction.getCalculator());
}
@Test
void factoryReturnsCorrectPurchase() {
- Transaction purchase2 = TransactionFactory.createTransaction(TransactionType.PURCHASE, testShare, 1, testPurchaseCalculator);
- assertTrue(equalTransactions(testPurchase, purchase2));
+ int targetWeek = 1;
+ Transaction transaction = TransactionFactory.createTransaction(TransactionType.PURCHASE, testShare, targetWeek);
+ assertNotNull(transaction);
+ assertInstanceOf(Purchase.class, transaction);
+ assertEquals(targetWeek, transaction.getWeek());
+ assertEquals(testShare, transaction.getShare());
+ assertNotNull(transaction.getCalculator());
}
@Test
void factoryThrowsErrors() {
- assertThrows(IllegalArgumentException.class, () -> {
- TransactionFactory.createTransaction(TransactionType.PURCHASE, null, 1, testPurchaseCalculator);
- });
-
- assertThrows(IllegalArgumentException.class, () -> {
- TransactionFactory.createTransaction(TransactionType.PURCHASE, testShare, -1, testPurchaseCalculator);
- });
-
- assertThrows(IllegalArgumentException.class, () -> {
- TransactionFactory.createTransaction(TransactionType.PURCHASE, testShare, 1, null);
- });
-
- assertThrows(IllegalArgumentException.class, () -> {
- TransactionFactory.createTransaction(null, testShare, 1, testPurchaseCalculator);
- });
- }
-
- private boolean equalTransactions(final Transaction transaction1, final Transaction transaction2) {
- return (transaction1.getWeek() == transaction2.getWeek()
- && transaction1.getShare() == transaction2.getShare()
- && transaction1.getCalculator() == transaction2.getCalculator()
- && transaction1.isCommited() == transaction2.isCommited());
+ assertThrows(IllegalArgumentException.class,
+ () -> TransactionFactory.createTransaction(TransactionType.PURCHASE, null, 1)
+ );
+ assertThrows(IllegalArgumentException.class,
+ () -> TransactionFactory.createTransaction(TransactionType.PURCHASE, testShare, -1)
+ );
+ assertThrows(IllegalArgumentException.class,
+ () -> TransactionFactory.createTransaction(null, testShare, 1)
+ );
}
}
\ No newline at end of file
From f7aafc67613e0820a43939955b6d07a30a46885b Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 17:59:32 +0200
Subject: [PATCH 07/31] Feat: Updated various classes.
---
.../exceptions/NotEnoughMoneyException.java | 18 ++
.../idi/idatt2003/g40/mappe/model/Player.java | 128 +++++++++---
.../idatt2003/g40/mappe/model/SaveGame.java | 2 +-
.../g40/mappe/service/TransactionFactory.java | 12 +-
.../g40/mappe/model/PlayerStatusTest.java | 8 +-
.../idatt2003/g40/mappe/model/PlayerTest.java | 186 +++++++++++++++---
6 files changed, 283 insertions(+), 71 deletions(-)
create mode 100644 src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/exceptions/NotEnoughMoneyException.java
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/exceptions/NotEnoughMoneyException.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/exceptions/NotEnoughMoneyException.java
new file mode 100644
index 0000000..8d19749
--- /dev/null
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/exceptions/NotEnoughMoneyException.java
@@ -0,0 +1,18 @@
+package edu.ntnu.idi.idatt2003.g40.mappe.exceptions;
+
+/**
+ * Exception primarily thrown when the active
+ * {@link edu.ntnu.idi.idatt2003.g40.mappe.model.Player} object
+ * does not have enough money for a transaction to complete.
+ * */
+public class NotEnoughMoneyException extends RuntimeException {
+
+ /**
+ * Constructor.
+ *
+ * @param message the exception message.
+ * */
+ public NotEnoughMoneyException(final String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java
index 9cac71e..61809ce 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java
@@ -2,10 +2,11 @@
import edu.ntnu.idi.idatt2003.g40.mappe.controller.PlayerStatusController;
import edu.ntnu.idi.idatt2003.g40.mappe.engine.TransactionArchive;
+import edu.ntnu.idi.idatt2003.g40.mappe.exceptions.NotEnoughMoneyException;
import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator;
import java.math.BigDecimal;
-import javafx.beans.property.FloatProperty;
-import javafx.beans.property.SimpleFloatProperty;
+import javafx.beans.property.ReadOnlyFloatProperty;
+import javafx.beans.property.ReadOnlyFloatWrapper;
/**
* Represents a player in the system.
@@ -37,14 +38,18 @@ public final class Player {
private BigDecimal money;
/**
- * Current net-worth of player as a listenable {@link FloatProperty} object.
+ * Current net-worth of player as a listenable,
+ * read-only, {@link ReadOnlyFloatWrapper} object.
* */
- private final FloatProperty networthAsFloatProp = new SimpleFloatProperty(0);
+ private final ReadOnlyFloatWrapper networthAsFloatProp =
+ new ReadOnlyFloatWrapper(0f);
/**
- * Current money of player as a listenable {@link FloatProperty} object.
+ * Current money of player as a read-only
+ * {@link ReadOnlyFloatWrapper} object.
* */
- private final FloatProperty moneyAsFloatProp = new SimpleFloatProperty(0);
+ private final ReadOnlyFloatWrapper moneyAsFloatProp
+ = new ReadOnlyFloatWrapper(0f);
/**
* The players' portfolio, holding their shares.
@@ -63,19 +68,28 @@ public final class Player {
* @param name the name of the player
* @param startingMoney the starting amount of money
*
- * @throws IllegalArgumentException if name is null.
+ * @throws IllegalArgumentException if name is empty,
+ * or starting money is null,
+ * zero or negative.
*/
- public Player(final String name, final BigDecimal startingMoney) throws IllegalArgumentException {
+ public Player(final String name,
+ final BigDecimal startingMoney)
+ throws IllegalArgumentException {
if (!Validator.NOT_EMPTY.isValid(name)) {
- throw new IllegalArgumentException("Invalid name!");
+ throw new IllegalArgumentException("Player name cannot be empty!");
+ }
+ if (startingMoney == null
+ || startingMoney.compareTo(BigDecimal.ZERO) <= 0) {
+ throw new IllegalArgumentException(
+ "Starting money cannot be null, zero, or negative!"
+ );
}
this.name = name;
this.startingMoney = startingMoney;
this.money = this.startingMoney;
- this.networthAsFloatProp.setValue(this.startingMoney);
- this.moneyAsFloatProp.setValue(this.startingMoney);
this.portfolio = new Portfolio();
this.transactionArchive = new TransactionArchive();
+ updateObservableProperties();
}
/**
@@ -109,18 +123,42 @@ public BigDecimal getMoney() {
* Adds money to the players balance.
*
* @param amount the amount to add
+ *
+ * @throws IllegalArgumentException if money to add is negative or zero.
*/
- public void addMoney(final BigDecimal amount) {
+ public void addMoney(final BigDecimal amount)
+ throws IllegalArgumentException {
+ if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
+ throw new IllegalArgumentException(
+ "Can only add positive values to player!"
+ );
+ }
money = money.add(amount);
+ updateObservableProperties();
}
/**
* Withdraws money from the players balance.
*
* @param amount the amount to withdraw
+ *
+ * @throws IllegalArgumentException if money to withdraw is negative or zero,
+ * or if amount is more than current money.
*/
- public void withdrawMoney(final BigDecimal amount) {
+ public void withdrawMoney(final BigDecimal amount)
+ throws IllegalArgumentException {
+ if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
+ throw new IllegalArgumentException(
+ "Amount to withdraw must be positive!"
+ );
+ }
+ if (money.compareTo(amount) < 0) {
+ throw new IllegalArgumentException(
+ "Cannot withdraw more money than available balance!"
+ );
+ }
money = money.subtract(amount);
+ updateObservableProperties();
}
/**
@@ -152,21 +190,23 @@ public BigDecimal getNetWorth() {
}
/**
- * Get net-worth as a {@link FloatProperty} object, allowing listening for changes.
+ * Get net-worth as a {@link ReadOnlyFloatProperty} object,
+ * allowing listening for changes.
*
- * @return FloatProperty.
+ * @return networth as an immutable value.
* */
- public FloatProperty getNetWorthAsFloatProperty() {
- return networthAsFloatProp;
+ public ReadOnlyFloatProperty getNetWorthAsFloatProperty() {
+ return networthAsFloatProp.getReadOnlyProperty();
}
/**
- * Get money as a {@link FloatProperty} object, allowing listening for changes.
+ * Get money as a {@link ReadOnlyFloatProperty} object,
+ * allowing listening for changes.
*
- * @return FloatProperty.
+ * @return money as an immutable value.
* */
- public FloatProperty getMoneyAsFloatProperty() {
- return moneyAsFloatProp;
+ public ReadOnlyFloatProperty getMoneyAsFloatProperty() {
+ return moneyAsFloatProp.getReadOnlyProperty();
}
/**
@@ -184,22 +224,46 @@ public PlayerStatus getStatus() {
* Method for handling a transaction for the player.
*
* @param transaction the transaction to handle.
+ *
+ * @throws IllegalArgumentException if transaction is null.
+ * @throws NotEnoughMoneyException if player does not have enough
+ * money for the transaction.
* */
- public void handleTransaction(final Transaction transaction) {
+ public void handleTransaction(final Transaction transaction)
+ throws IllegalArgumentException, NotEnoughMoneyException {
+ if (transaction == null) {
+ throw new IllegalArgumentException("Cannot handle null transaction!");
+ }
+
if (transaction instanceof Purchase purchase) {
- if (money.floatValue() > transaction.getCalculator().calculateTotal().floatValue()) {
+ BigDecimal totalCost = purchase.getCalculator().calculateTotal();
+ if (this.money.compareTo(totalCost) < 0) {
+ throw new NotEnoughMoneyException("Not enough money for transaction!");
+ }
+ }
+
+ switch (transaction) {
+ case Purchase purchase -> {
withdrawMoney(purchase.getCalculator().calculateTotal());
portfolio.addShare(purchase.getShare());
- transactionArchive.add(transaction);
- transaction.commit(this);
}
- } else if (transaction instanceof Sale sale) {
- addMoney(sale.getCalculator().calculateTotal());
- portfolio.removeShare(sale.getShare());
- transactionArchive.add(transaction);
- transaction.commit(this);
+ case Sale sale -> {
+ addMoney(sale.getCalculator().calculateTotal());
+ portfolio.removeShare(sale.getShare());
+ }
+ default -> throw new IllegalStateException("Unexpected value: " + transaction);
}
- networthAsFloatProp.setValue(getNetWorth().floatValue());
- moneyAsFloatProp.setValue(money);
+ transactionArchive.add(transaction);
+ transaction.commit(this);
+
+ updateObservableProperties();
+ }
+
+ /**
+ * Helper method to synchronize the listener values.
+ */
+ private void updateObservableProperties() {
+ this.moneyAsFloatProp.setValue(this.money.floatValue());
+ this.networthAsFloatProp.setValue(this.getNetWorth().floatValue());
}
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGame.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGame.java
index ff00e77..d790f97 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGame.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGame.java
@@ -14,7 +14,7 @@
* expected to be loaded with the default bundled stock data file.
*
*/
-public class SaveGame {
+public final class SaveGame {
/** Display name of the save. */
private final String name;
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactory.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactory.java
index 733b9c5..490a93c 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactory.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactory.java
@@ -30,19 +30,17 @@ private TransactionFactory() {
public static Transaction createTransaction(final TransactionType
transactionType,
final Share share,
- final int week,
- final TransactionCalculator
- calculator)
+ final int week)
throws IllegalArgumentException {
if (transactionType == null
|| share == null
- || !Validator.VALID_POSITIVE_INT.isValid(Integer.toString(week))
- || calculator == null) {
+ || !Validator.VALID_WEEK.isValid(Integer.toString(week))
+ ) {
throw new IllegalArgumentException("Null or empty parameters for factory!");
} else {
return switch (transactionType) {
- case SALE -> new Sale(share, week, calculator);
- case PURCHASE -> new Purchase(share, week, calculator);
+ case SALE -> new Sale(share, week, new SaleCalculator(share));
+ case PURCHASE -> new Purchase(share, week, new PurchaseCalculator(share));
default -> throw new
IllegalArgumentException("Invalid transaction type!");
};
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PlayerStatusTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PlayerStatusTest.java
index 1765d46..d5952dc 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PlayerStatusTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PlayerStatusTest.java
@@ -31,7 +31,7 @@ void getStatusAfterDoubleIncreaseReturnsBetterStatus() {
@Test
void gettingStatusWhenNegativeDifferenceReturnsWorstStatus() {
- testPlayer.addMoney(new BigDecimal(-1000));
+ testPlayer.withdrawMoney(new BigDecimal(1000));
assertEquals(PlayerStatus.NOOB, testPlayer.getStatus());
}
@@ -40,10 +40,10 @@ void multipleChangesInValueCauseCorrectStatus() {
testPlayer.addMoney(new BigDecimal(2000));
assertEquals(PlayerStatus.PRO, testPlayer.getStatus());
- testPlayer.addMoney(new BigDecimal(-1000));
+ testPlayer.withdrawMoney(new BigDecimal(1000));
assertEquals(PlayerStatus.TRYHARD, testPlayer.getStatus());
- testPlayer.addMoney(new BigDecimal(-500));
+ testPlayer.withdrawMoney(new BigDecimal(500));
assertEquals(PlayerStatus.GOOD, testPlayer.getStatus());
}
-}
\ No newline at end of file
+}
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PlayerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PlayerTest.java
index 70dd13b..9ac2d64 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PlayerTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PlayerTest.java
@@ -1,65 +1,197 @@
package edu.ntnu.idi.idatt2003.g40.mappe.model;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import edu.ntnu.idi.idatt2003.g40.mappe.exceptions.NotEnoughMoneyException;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionFactory;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionType;
import java.math.BigDecimal;
-
-import edu.ntnu.idi.idatt2003.g40.mappe.service.SaleCalculator;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
final class PlayerTest {
+ /**
+ * Test player to use.
+ * */
+ private Player testPlayer;
+
+ @BeforeEach
+ void setUp() {
+ testPlayer = new Player("Alice", new BigDecimal("1000"));
+ }
+
@Test
void constructorSetsNameMoneyPortfolioAndArchive() {
- Player player = new Player("Alice", new BigDecimal("1000"));
+ assertEquals("Alice", testPlayer.getName());
+ assertEquals(new BigDecimal("1000"), testPlayer.getMoney());
+ assertNotNull(testPlayer.getPortfolio());
+ assertNotNull(testPlayer.getTransactionArchive());
+ assertEquals(1000f,
+ testPlayer.getMoneyAsFloatProperty().floatValue());
+ assertEquals(1000f,
+ testPlayer.getNetWorthAsFloatProperty().floatValue());
+ }
- assertEquals("Alice", player.getName());
- assertEquals(new BigDecimal("1000"), player.getMoney());
- assertNotNull(player.getPortfolio());
- assertNotNull(player.getTransactionArchive());
+ @Test
+ void constructorThrowsExceptionOnIllegalArguments() {
+ assertDoesNotThrow(
+ () -> new Player("Bob", new BigDecimal("2000"))
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new Player("", new BigDecimal("2000"))
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new Player(null, new BigDecimal("2000"))
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new Player("Bob", null)
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new Player("Bob", new BigDecimal("0"))
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new Player("Bob", new BigDecimal("-100"))
+ );
}
@Test
void addMoneyIncreasesBalance() {
- Player player = new Player("Bob", new BigDecimal("500"));
-
- player.addMoney(new BigDecimal("200"));
+ testPlayer.addMoney(new BigDecimal("200"));
+ assertEquals(new BigDecimal("1200"), testPlayer.getMoney());
+ }
- assertEquals(new BigDecimal("700"), player.getMoney());
+ @Test
+ void addMoneyThrowsExceptionWhenIllegalArguments() {
+ assertDoesNotThrow(
+ () -> testPlayer.addMoney(new BigDecimal("200"))
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> testPlayer.addMoney(null)
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> testPlayer.addMoney(new BigDecimal("-10"))
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> testPlayer.addMoney(new BigDecimal("0"))
+ );
}
@Test
void withdrawMoneyDecreasesBalance() {
- Player player = new Player("Charlie", new BigDecimal("500"));
-
- player.withdrawMoney(new BigDecimal("150"));
+ testPlayer.withdrawMoney(new BigDecimal("150"));
+ assertEquals(new BigDecimal("850"), testPlayer.getMoney());
+ }
- assertEquals(new BigDecimal("350"), player.getMoney());
+ @Test
+ void withdrawMoneyThrowsExceptionOnIllegalArguments() {
+ assertDoesNotThrow(
+ () -> testPlayer.withdrawMoney(new BigDecimal("200"))
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> testPlayer.withdrawMoney(null)
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> testPlayer.withdrawMoney(new BigDecimal("-10"))
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> testPlayer.withdrawMoney(new BigDecimal("0"))
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> testPlayer.withdrawMoney(new BigDecimal("99999"))
+ );
}
@Test
void addAndWithdrawMoneyUpdateBalanceCorrectly() {
- Player player = new Player("Dana", new BigDecimal("1000"));
+ testPlayer.addMoney(new BigDecimal("250"));
+ testPlayer.withdrawMoney(new BigDecimal("300"));
- player.addMoney(new BigDecimal("250"));
- player.withdrawMoney(new BigDecimal("300"));
-
- assertEquals(new BigDecimal("950"), player.getMoney());
+ assertEquals(new BigDecimal("950"), testPlayer.getMoney());
}
@Test
- void getNetWorthCalculatesCorrectly() {
+ void getNetWorthCalculatesCorrectlyForSales() {
Stock stock = new Stock("AAPL", "Apple inc.,", new BigDecimal("100.00"));
- Player player = new Player("Bob", new BigDecimal("900"));
Share share = new Share(stock, new BigDecimal("1"), new BigDecimal("100.00"));
- player.getPortfolio().addShare(share);
- SaleCalculator saleCalculator = new SaleCalculator(share);
+ BigDecimal expectedNetWorth = testPlayer.getNetWorth();
+ Transaction transaction = TransactionFactory.createTransaction(TransactionType.SALE, share, 1);
+
+ testPlayer.handleTransaction(transaction);
+
+ BigDecimal actualNetWorth = testPlayer.getNetWorth();
+ expectedNetWorth = expectedNetWorth.add(transaction.getCalculator().calculateTotal());
+
+ assertEquals(expectedNetWorth, actualNetWorth);
+ assertEquals(actualNetWorth.floatValue(),
+ testPlayer.getNetWorthAsFloatProperty().floatValue());
+ assertEquals(testPlayer.getMoney().floatValue(),
+ testPlayer.getMoneyAsFloatProperty().floatValue());
+ }
+
+ @Test
+ void getNetWorthCalculatesCorrectlyForPurchases() {
+ Stock stock = new Stock("AAPL", "Apple inc.,", new BigDecimal("100.00"));
+ Share share = new Share(stock, new BigDecimal("1"), stock.getSalesPrice());
+
+ BigDecimal expectedNetWorth = testPlayer.getNetWorth();
+ Transaction transaction = TransactionFactory.createTransaction(TransactionType.PURCHASE, share, 1);
+
+ testPlayer.handleTransaction(transaction);
+
+ BigDecimal actualNetWorth = testPlayer.getNetWorth();
+ expectedNetWorth = expectedNetWorth.subtract(
+ transaction.getCalculator().calculateCommission()
+ );
+
+ assertEquals(expectedNetWorth, actualNetWorth);
+ assertEquals(actualNetWorth.floatValue(),
+ testPlayer.getNetWorthAsFloatProperty().floatValue());
+ assertEquals(testPlayer.getMoney().floatValue(),
+ testPlayer.getMoneyAsFloatProperty().floatValue());
+ }
+
+ @Test
+ void handleTransactionThrowsExceptionsOnIllegalArgumentsAndNotEnoughMoney() {
+ Stock stock = new Stock("AAPL", "Apple inc.,", new BigDecimal("100.00"));
+ Share share = new Share(stock, new BigDecimal("1"), stock.getSalesPrice());
+
+ Share expensiveShare = new Share(stock, new BigDecimal("999.99"), new BigDecimal("999.99"));
+
+ Transaction validTransaction = TransactionFactory.createTransaction(
+ TransactionType.PURCHASE, share, 1
+ );
+
+ Transaction tooExpensiveTransaction = TransactionFactory.createTransaction(
+ TransactionType.PURCHASE, expensiveShare, 1
+ );
+
+ assertDoesNotThrow(
+ () -> testPlayer.handleTransaction(validTransaction)
+ );
- BigDecimal calculatedNetWorth = player.getMoney().add(saleCalculator.calculateTotal());
- BigDecimal actualNetWorth = player.getNetWorth();
+ assertThrows(IllegalArgumentException.class,
+ () -> testPlayer.handleTransaction(null)
+ );
- assertEquals(calculatedNetWorth, actualNetWorth);
+ assertThrows(NotEnoughMoneyException.class,
+ () -> testPlayer.handleTransaction(tooExpensiveTransaction)
+ );
}
}
From de95bc3364eb737fa05f89138209c257c36763fc Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:48:30 +0200
Subject: [PATCH 08/31] Fix: Updated version number for some classes
---
.../java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java | 2 +-
.../java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java | 2 ++
.../java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java | 2 ++
3 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java
index c467434..9cf5230 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java
@@ -29,7 +29,7 @@
* @see Player
* @see edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionCalculator
*
- * @version 1.0.0
+ * @version 1.1.0
* */
public final class Exchange {
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java
index 61809ce..0b66b11 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Player.java
@@ -19,6 +19,8 @@
* Has a set amount of money to use on said exchange.
* Has a {@link TransactionArchive}
*
+ *
+ * @version 1.1.0
* */
public final class Player {
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java
index 333d2e2..40a3afd 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java
@@ -11,6 +11,8 @@
*
* The portfolio stores shares and provides operations for adding, removing,
* retrieving and checking ownership of shares.
+ *
+ * @version 1.1.0
*/
public final class Portfolio {
From 290fdab0a34cde5d661d82a99e20e1a12be59094 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:48:40 +0200
Subject: [PATCH 09/31] Feat: Validation for savegame
---
.../edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGame.java | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGame.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGame.java
index d790f97..e6c60ec 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGame.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGame.java
@@ -1,5 +1,7 @@
package edu.ntnu.idi.idatt2003.g40.mappe.model;
+import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator;
+
/**
* Represents one save game entry.
*
@@ -44,6 +46,11 @@ public SaveGame(final String name,
final double balance,
final double startingCapital,
final String stockDataPath) {
+ if (!Validator.NOT_EMPTY.isValid(name)
+ || balance <= 0
+ || startingCapital <= 0) {
+ throw new IllegalArgumentException("Invalid Save configuration!");
+ }
this.name = name;
this.balance = balance;
this.startingCapital = startingCapital;
From 706db1dc9f90b95ec00079f2847bfbb8b6e42667 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:48:50 +0200
Subject: [PATCH 10/31] Feat: Updated validation for share
---
.../idi/idatt2003/g40/mappe/model/Share.java | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Share.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Share.java
index 3328a74..30aba43 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Share.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Share.java
@@ -32,13 +32,25 @@ public final class Share {
* @param quantity the quantity purchased
* @param purchasePrice the price per unit at purchase time
*
- * @throws IllegalArgumentException if stock is null.
+ * @throws IllegalArgumentException if parameters are null or invalid.
*/
public Share(final Stock stock,
final BigDecimal quantity,
final BigDecimal purchasePrice) throws IllegalArgumentException {
- if (stock == null) {
- throw new IllegalArgumentException("Invalid stock!");
+ if (stock == null
+ || quantity == null
+ || purchasePrice == null) {
+ throw new IllegalArgumentException("Invalid share configuration!");
+ }
+ if (quantity.compareTo(BigDecimal.ZERO) <= 0) {
+ throw new IllegalArgumentException(
+ "Quantity cannot be negative or zero!"
+ );
+ }
+ if (purchasePrice.compareTo(BigDecimal.ZERO) <= 0) {
+ throw new IllegalArgumentException(
+ "Purchase price cannot be negative or zero!"
+ );
}
this.stock = stock;
this.quantity = quantity;
From 1c015f89ca73082851390e8db914f64cfabbeb16 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:49:18 +0200
Subject: [PATCH 11/31] Feat: Updated validation for Stock
---
.../idi/idatt2003/g40/mappe/model/Stock.java | 25 ++++++++++++-------
1 file changed, 16 insertions(+), 9 deletions(-)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Stock.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Stock.java
index 0aff105..d7ff05a 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Stock.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Stock.java
@@ -40,6 +40,8 @@ public final class Stock {
* @param symbol the unique stock symbol
* @param company the name of the company
* @param salesPrice the initial sales price of the stock
+ *
+ * @throws IllegalArgumentException if parameters are null or invalid.
*/
public Stock(final String symbol,
final String company,
@@ -47,12 +49,17 @@ public Stock(final String symbol,
if (!Validator.VALID_STOCK_SYMBOL.isValid(symbol)) {
throw new IllegalArgumentException(
Validator.VALID_STOCK_SYMBOL.getErrorMessage());
- } else {
- this.symbol = symbol;
- this.company = company;
- this.fortune = 0;
- prices.add(salesPrice);
}
+ if (!Validator.NOT_EMPTY.isValid(company)) {
+ throw new IllegalArgumentException(Validator.NOT_EMPTY.getErrorMessage());
+ }
+ if (salesPrice == null || salesPrice.compareTo(BigDecimal.ZERO) <= 0) {
+ throw new IllegalArgumentException("Sales price of cannot be negative or zero!");
+ }
+ this.symbol = symbol;
+ this.company = company;
+ this.fortune = 0;
+ prices.add(salesPrice);
}
/**
@@ -109,11 +116,11 @@ public BigDecimal getSalesPrice() {
*/
public void addNewSalesPrice(final BigDecimal price)
throws IllegalArgumentException {
- if (price != null && price.intValue() != 0) {
- prices.add(price);
- } else {
- throw new IllegalArgumentException("Invalid price to add to stock: " + getSymbol());
+ if (price == null || price.compareTo(BigDecimal.ZERO) <= 0) {
+ throw new IllegalArgumentException("Invalid price to add to stock: "
+ + getSymbol());
}
+ prices.add(price);
}
/**
From ada261d0a0b130eb7b47bf5377641308f7486864 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:49:31 +0200
Subject: [PATCH 12/31] Feat: Updated validation for Transaction
---
.../g40/mappe/model/Transaction.java | 23 +++++++++++++------
1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Transaction.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Transaction.java
index fe7e6c5..62993f1 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Transaction.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Transaction.java
@@ -1,6 +1,7 @@
package edu.ntnu.idi.idatt2003.g40.mappe.model;
import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionCalculator;
+import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator;
/**
* Transaction abstract class.
@@ -40,14 +41,22 @@ public abstract class Transaction {
protected Transaction(final Share share, final int week,
final TransactionCalculator calculator)
- throws IllegalArgumentException{
- if (share == null || calculator == null) {
- throw new IllegalArgumentException("Invalid stock or calculator!");
- } else {
- this.share = share;
- this.week = week;
- this.calculator = calculator;
+ throws IllegalArgumentException {
+ if (share == null
+ || calculator == null) {
+ throw new IllegalArgumentException(
+ "Invalid configuration for transaction!"
+ );
}
+ if (!Validator.VALID_WEEK.isValid(Integer.toString(week))) {
+ throw new IllegalArgumentException(
+ Validator.VALID_WEEK.getErrorMessage()
+ );
+ }
+ this.share = share;
+ this.week = week;
+ this.calculator = calculator;
+
}
/**
From 5e9017ce68898d7883702cd9699788bcadc7b65a Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:49:42 +0200
Subject: [PATCH 13/31] Feat: Updated Validator Enum
---
.../idatt2003/g40/mappe/utils/Validator.java | 24 ++-----------------
1 file changed, 2 insertions(+), 22 deletions(-)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/Validator.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/Validator.java
index a9969db..6e1830b 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/Validator.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/utils/Validator.java
@@ -1,7 +1,5 @@
package edu.ntnu.idi.idatt2003.g40.mappe.utils;
-import java.time.LocalDate;
-import java.time.format.DateTimeParseException;
import java.util.function.Predicate;
/**
@@ -58,29 +56,11 @@ public enum Validator {
VALID_STOCK_SYMBOL(NOT_EMPTY.validationRule.and(s ->
s.length() == 4), "Invalid stock symbol!"),
- /**
- * Rule that checks if a string represents a positive integer.
- * */
- VALID_POSITIVE_INT(VALID_INT.validationRule.and(s ->
- Integer.parseInt(s) >= 0), "Number is not positive!"),
-
/**
* Rule that checks if a string represents a valid week. (Greater than 1).
* */
- VALID_WEEK(VALID_POSITIVE_INT.validationRule.and(s ->
- Integer.parseInt(s) > 0), "Invalid week"),
- /**
- * Rule that checks if string is not empty,
- * and if it can be parsed into a {@link LocalDate} object.
- */
- VALID_DATE(NOT_EMPTY.validationRule.and(s -> {
- try {
- LocalDate.parse(s);
- return true;
- } catch (DateTimeParseException e) {
- return false;
- }
- }), "Invalid Date!");
+ VALID_WEEK(VALID_INT.validationRule.and(s ->
+ Integer.parseInt(s) > 0), "Invalid week");
/** The predicate field set when creating constants. */
private final Predicate validationRule;
From e2a84b6e0fc1cffcfef5e0123ed7962e9e976481 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:50:20 +0200
Subject: [PATCH 14/31] Feat: removed OnUpdate for CreateGameView
---
.../idatt2003/g40/mappe/view/creategame/CreateGameView.java | 6 ------
1 file changed, 6 deletions(-)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameView.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameView.java
index a64bec2..48550ac 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameView.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/creategame/CreateGameView.java
@@ -302,12 +302,6 @@ protected void initStyling() {
createGameButton.setDisable(true);
}
- /** {@inheritDoc} */
- @Override
- public void onUpdate() {
- resetFields();
- }
-
/**
* Refreshes the highlight on the two stock-source buttons so the
* currently-active choice stands out from the inactive one.
From a0fa3283953c23b739e9bfa9df991311b64e9c43 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:50:25 +0200
Subject: [PATCH 15/31] Update ViewElement.java
---
.../idatt2003/g40/mappe/view/ViewElement.java | 41 ++++++++-----------
1 file changed, 17 insertions(+), 24 deletions(-)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElement.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElement.java
index 11e91cd..d0c2ef0 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElement.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElement.java
@@ -1,6 +1,5 @@
package edu.ntnu.idi.idatt2003.g40.mappe.view;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventData;
import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator;
import java.util.EnumMap;
import java.util.Map;
@@ -47,14 +46,18 @@ public abstract class ViewElement> {
* @param rootPane an instance of type T (defined in the class).
* @param viewName The name of the view as an {@link ViewEnum}.
*
+ * @throws IllegalArgumentException if parameters are invalid.
+ *
*/
- protected ViewElement(final T rootPane, final ViewEnum viewName, final Class actionEnum) {
+ protected ViewElement(final T rootPane,
+ final ViewEnum viewName,
+ final Class actionEnum)
+ throws IllegalArgumentException {
this(rootPane, actionEnum);
- if (Validator.NOT_EMPTY.isValid(viewName.name())) {
- this.viewName = viewName;
- } else {
+ if (!Validator.NOT_EMPTY.isValid(viewName.name())) {
throw new IllegalArgumentException(Validator.NOT_EMPTY.getErrorMessage());
}
+ this.viewName = viewName;
}
/**
@@ -62,16 +65,17 @@ protected ViewElement(final T rootPane, final ViewEnum viewName, final Class
*
* @param rootPane the root of this view.
*
+ * @throws IllegalArgumentException if parameters are null.
*/
- protected ViewElement(final T rootPane, final Class actionEnum) {
- if (rootPane != null) {
- setRootPane(rootPane);
- this.buttonMap = new EnumMap<>(actionEnum);
- initLayout();
- initStyling();
- } else {
+ protected ViewElement(final T rootPane, final Class actionEnum)
+ throws IllegalArgumentException {
+ if (rootPane == null || actionEnum == null) {
throw new IllegalArgumentException("Invalid ViewElement!");
}
+ setRootPane(rootPane);
+ this.buttonMap = new EnumMap<>(actionEnum);
+ initLayout();
+ initStyling();
}
/**
@@ -155,21 +159,10 @@ public void setOnAction(final A action, final Runnable logic)
}
}
- /**
- * Method that defines how view elements set data.
- *
- * @param The type of data to set.
- * @param data the data to set.
- *
- */
- public void setData(final T2 data) {
- setViewName(data.getSceneName());
- }
-
/**
* Method called when updating a view.
* */
public void onUpdate() {
-
+ // Empty by default.
}
}
From 44ff6e3b51e7b321ea6c1cd819247d1cb8a72853 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:50:28 +0200
Subject: [PATCH 16/31] Update ViewManager.java
---
.../idi/idatt2003/g40/mappe/view/ViewManager.java | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManager.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManager.java
index b3ce064..f6de33d 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManager.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManager.java
@@ -6,6 +6,8 @@
import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventType;
import java.util.EnumMap;
import java.util.Map;
+
+import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator;
import javafx.scene.Scene;
import javafx.stage.Stage;
@@ -108,6 +110,7 @@ public void setScene(final ViewElement, ?> viewElement)
);
}
currentView = viewElement;
+ viewElement.onUpdate();
}
}
@@ -128,12 +131,10 @@ public void setScene(final ViewElement, ?> viewElement)
public void setScene(final ViewData data) throws IllegalArgumentException {
if (data == null) {
throw new IllegalArgumentException("Data is null!");
- } else {
- ViewElement, ?> viewElement = viewMap.get(data.getSceneName());
- viewElement.setData(data);
- setScene(viewElement);
- currentView = viewElement;
}
+ ViewElement, ?> viewElement = viewMap.get(data.getSceneName());
+ setScene(viewElement);
+ currentView = viewElement;
}
/**
From d34b9a23077505407e07b279c31c294ffc5ae17f Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:50:31 +0200
Subject: [PATCH 17/31] Update PurchaseTest.java
---
.../g40/mappe/model/PurchaseTest.java | 69 +++++++++++++------
1 file changed, 49 insertions(+), 20 deletions(-)
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PurchaseTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PurchaseTest.java
index 42caf80..b2c03c6 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PurchaseTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PurchaseTest.java
@@ -1,11 +1,14 @@
package edu.ntnu.idi.idatt2003.g40.mappe.model;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import java.math.BigDecimal;
-
import edu.ntnu.idi.idatt2003.g40.mappe.service.PurchaseCalculator;
+import java.math.BigDecimal;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
@@ -14,44 +17,70 @@
final class PurchaseTest {
/**
- * Valid test stock.
+ * Test purchase used in various tests.
* */
- private final Stock testStock =
- new Stock("AAPL", "Apple Inc.", new BigDecimal("100.00"));
+ private Purchase testPurchase;
/**
* Valid test share.
* */
- private final Share testShare =
- new Share(testStock, new BigDecimal("10"), new BigDecimal("10"));
+ private Share testShare;
/**
* Valid test purchase calculator.
* */
- private final PurchaseCalculator testPurchaseCalculator =
- new PurchaseCalculator(testShare);
+ private PurchaseCalculator testPurchaseCalculator;
/**
* Valid test player.
* */
- private final Player testPlayer =
- new Player("TestName", new BigDecimal("1000.00"));
+ private Player testPlayer;
+
+ @BeforeEach
+ void setUp() {
+ Stock testStock = new Stock(
+ "AAPL",
+ "Apple Inc.",
+ new BigDecimal("100.00")
+ );
+ testShare = new Share(
+ testStock,
+ new BigDecimal("10"),
+ new BigDecimal("10")
+ );
+ testPurchaseCalculator = new PurchaseCalculator(testShare);
+ testPlayer = new Player("TestName", new BigDecimal("1000.00"));
+ testPurchase = new Purchase(testShare, 1, testPurchaseCalculator);
+ }
@Test
void constructorSetsValues() {
- Purchase purchase = new Purchase(testShare, 1, testPurchaseCalculator);
-
- assertEquals(testShare, purchase.getShare());
- assertEquals(1, purchase.getWeek());
- assertEquals(testPurchaseCalculator, purchase.getCalculator());
+ assertEquals(testShare, testPurchase.getShare());
+ assertEquals(1, testPurchase.getWeek());
+ assertEquals(testPurchaseCalculator, testPurchase.getCalculator());
}
@Test
- void commitMethodSetsCommitToTrue() {
- Purchase purchase = new Purchase(testShare, 1, testPurchaseCalculator);
+ void constructorThrowsExceptionOnIllegalArguments() {
+ assertDoesNotThrow(
+ () -> new Purchase(testShare, 1, testPurchaseCalculator)
+ );
- purchase.commit(testPlayer);
+ assertThrows(IllegalArgumentException.class,
+ () -> new Purchase(null, 1, testPurchaseCalculator)
+ );
+ assertThrows(IllegalArgumentException.class,
+ () -> new Purchase(testShare, 0, testPurchaseCalculator)
+ );
+ assertThrows(IllegalArgumentException.class,
+ () -> new Purchase(testShare, 1, null)
+ );
+ }
- assertTrue(purchase.isCommited());
+ @Test
+ void commitMethodSetsCommitToTrue() {
+ assertFalse(testPurchase.isCommited());
+ testPurchase.commit(testPlayer);
+ assertTrue(testPurchase.isCommited());
}
}
From 76f784d4c65a77027289097dab9dd3b862be5069 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:50:35 +0200
Subject: [PATCH 18/31] Update SaleTest.java
---
.../idatt2003/g40/mappe/model/SaleTest.java | 69 +++++++++++++------
1 file changed, 49 insertions(+), 20 deletions(-)
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaleTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaleTest.java
index 22e4e4e..feda7ac 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaleTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaleTest.java
@@ -1,11 +1,14 @@
package edu.ntnu.idi.idatt2003.g40.mappe.model;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.SaleCalculator;
import java.math.BigDecimal;
-
-import edu.ntnu.idi.idatt2003.g40.mappe.service.PurchaseCalculator;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
@@ -14,44 +17,70 @@
final class SaleTest {
/**
- * Valid test stock.
+ * Test sale used in various tests.
* */
- private final Stock testStock =
- new Stock("AAPL", "Apple Inc.", new BigDecimal("100.00"));
+ private Sale testSale;
/**
* Valid test share.
* */
- private final Share testShare =
- new Share(testStock, new BigDecimal("10"), new BigDecimal("10"));
+ private Share testShare;
/**
* Valid test purchase calculator.
* */
- private final PurchaseCalculator testSaleCalculator =
- new PurchaseCalculator(testShare);
+ private SaleCalculator testSaleCalculator;
/**
* Valid test player.
* */
- private final Player testPlayer =
- new Player("TestName", new BigDecimal("1000.00"));
+ private Player testPlayer;
+
+ @BeforeEach
+ void setUp() {
+ Stock testStock = new Stock(
+ "AAPL",
+ "Apple Inc.",
+ new BigDecimal("100.00")
+ );
+ testShare = new Share(
+ testStock,
+ new BigDecimal("10"),
+ new BigDecimal("10")
+ );
+ testSaleCalculator = new SaleCalculator(testShare);
+ testPlayer = new Player("TestName", new BigDecimal("1000.00"));
+ testSale = new Sale(testShare, 1, testSaleCalculator);
+ }
@Test
void constructorSetsValues() {
- Sale sale = new Sale(testShare, 1, testSaleCalculator);
-
- assertEquals(testShare, sale.getShare());
- assertEquals(1, sale.getWeek());
- assertEquals(testSaleCalculator, sale.getCalculator());
+ assertEquals(testShare, testSale.getShare());
+ assertEquals(1, testSale.getWeek());
+ assertEquals(testSaleCalculator, testSale.getCalculator());
}
@Test
- void commitMethodSetsCommitToTrue() {
- Sale sale = new Sale(testShare, 1, testSaleCalculator);
+ void constructorThrowsExceptionOnIllegalArguments() {
+ assertDoesNotThrow(
+ () -> new Purchase(testShare, 1, testSaleCalculator)
+ );
- sale.commit(testPlayer);
+ assertThrows(IllegalArgumentException.class,
+ () -> new Purchase(null, 1, testSaleCalculator)
+ );
+ assertThrows(IllegalArgumentException.class,
+ () -> new Purchase(testShare, 0, testSaleCalculator)
+ );
+ assertThrows(IllegalArgumentException.class,
+ () -> new Purchase(testShare, 1, null)
+ );
+ }
- assertTrue(sale.isCommited());
+ @Test
+ void commitMethodSetsCommitToTrue() {
+ assertFalse(testSale.isCommited());
+ testSale.commit(testPlayer);
+ assertTrue(testSale.isCommited());
}
}
From e6736910fb35b7b2cfc42c6cac41a6e7ec7ca218 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:50:37 +0200
Subject: [PATCH 19/31] Create SaveGameTest.java
---
.../g40/mappe/model/SaveGameTest.java | 53 +++++++++++++++++++
1 file changed, 53 insertions(+)
create mode 100644 src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGameTest.java
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGameTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGameTest.java
new file mode 100644
index 0000000..907431e
--- /dev/null
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/SaveGameTest.java
@@ -0,0 +1,53 @@
+package edu.ntnu.idi.idatt2003.g40.mappe.model;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Test class for {@link SaveGame}.
+ * */
+class SaveGameTest {
+
+ /**
+ * {@link SaveGame} object to use for testing.
+ * */
+ private SaveGame testSaveGame;
+
+ @BeforeEach
+ void setUp() {
+ testSaveGame = new SaveGame("Save 1", 10, 100, "Stock path");
+ }
+
+ @Test
+ void constructorSetsValuesAsExpected() {
+ Assertions.assertEquals("Save 1", testSaveGame.getName());
+ Assertions.assertEquals(10, testSaveGame.getBalance());
+ Assertions.assertEquals(100, testSaveGame.getStartingCapital());
+ Assertions.assertEquals("Stock path", testSaveGame.getStockDataPath());
+ }
+
+ @Test
+ void constructorThrowsExceptionOnIllegalArguments() {
+ assertDoesNotThrow(
+ () -> new SaveGame("Save 2", 10, 100, "Stock path 2")
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new SaveGame("", 10, 100, "Stock path 2")
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new SaveGame("Save 2", 0, 100, "Stock path 2")
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new SaveGame("Save 2", 10, -10, "Stock path 2")
+ );
+ }
+}
\ No newline at end of file
From e6a3914431972e3dce24f0de212b1559229c8de2 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:50:40 +0200
Subject: [PATCH 20/31] Update ShareTest.java
---
.../idatt2003/g40/mappe/model/ShareTest.java | 106 ++++++++++++++----
1 file changed, 84 insertions(+), 22 deletions(-)
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/ShareTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/ShareTest.java
index bb78ea1..f73a2e4 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/ShareTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/ShareTest.java
@@ -1,52 +1,114 @@
package edu.ntnu.idi.idatt2003.g40.mappe.model;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import java.math.BigDecimal;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
final class ShareTest {
- @Test
- void constructorStoresAllValuesCorrectly() {
- Stock stock = new Stock("AAPL", "Apple Inc.", new BigDecimal("150.00"));
+ /**
+ * Share to use for testing.
+ */
+ private Share testShare;
+
+ /**
+ * Stock to use for testing.
+ * */
+ private Stock testStock;
+
+ @BeforeEach
+ void setUp() {
+ testStock = new Stock(
+ "AAPL",
+ "Apple Inc.",
+ new BigDecimal("150.00")
+ );
BigDecimal quantity = new BigDecimal("10");
BigDecimal purchasePrice = new BigDecimal("145.50");
+ testShare = new Share(testStock, quantity, purchasePrice);
+ }
+
+ @Test
+ void constructorStoresAllValuesCorrectly() {
+ assertSame(testStock, testShare.getStock());
+
+ assertEquals(0,
+ new BigDecimal("10")
+ .compareTo(testShare.getQuantity())
+ );
+
+ assertEquals(0,
+ new BigDecimal("145.50")
+ .compareTo(testShare.getPurchasePrice())
+ );
+ }
+
+ @Test
+ void constructorThrowsExceptionOnIllegalArguments() {
+ assertDoesNotThrow(
+ () -> new Share(testStock, new BigDecimal("1"),
+ new BigDecimal("100"))
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new Share(null, new BigDecimal("1"), new BigDecimal("100"))
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new Share(testStock, null, new BigDecimal("100"))
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new Share(testStock, new BigDecimal("1"), null)
+ );
- Share share = new Share(stock, quantity, purchasePrice);
+ assertThrows(IllegalArgumentException.class,
+ () -> new Share(testStock, new BigDecimal("0"),
+ new BigDecimal("100"))
+ );
- assertSame(stock, share.getStock());
- assertEquals(quantity, share.getQuantity());
- assertEquals(purchasePrice, share.getPurchasePrice());
+ assertThrows(IllegalArgumentException.class,
+ () -> new Share(testStock, new BigDecimal("1"), new BigDecimal("0"))
+ );
}
@Test
void shareSupportsDecimalValues() {
- Stock stock = new Stock("TSLA", "Tesla Inc.", new BigDecimal("200.00"));
- Share share = new Share(
- stock,
+ Share testShare2 = new Share(
+ testStock,
new BigDecimal("2.5"),
new BigDecimal("198.75")
);
- assertEquals(new BigDecimal("2.5"), share.getQuantity());
- assertEquals(new BigDecimal("198.75"), share.getPurchasePrice());
+ assertEquals(0,
+ new BigDecimal("2.5")
+ .compareTo(testShare2.getQuantity())
+ );
+
+ assertEquals(0,
+ new BigDecimal("198.75")
+ .compareTo(testShare2.getPurchasePrice())
+ );
}
@Test
void getStockReturnsCorrectStockObject() {
- Stock stock = new Stock("NVDA",
- "NVIDIA Corporation",
- new BigDecimal("875.40"));
- Share share = new Share(stock,
- new BigDecimal("4"),
- new BigDecimal("850.00"));
-
- assertSame(stock, share.getStock());
- assertEquals("NVDA", share.getStock().getSymbol());
- assertEquals("NVIDIA Corporation", share.getStock().getCompany());
+ assertSame(testStock, testShare.getStock());
+ assertEquals(
+ "AAPL",
+ testShare.getStock().getSymbol()
+ );
+
+ assertEquals(
+ "Apple Inc.",
+ testShare.getStock().getCompany()
+ );
}
}
From 98bdd9f63151085c600f7cdafd3eb6980aeed77c Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:50:42 +0200
Subject: [PATCH 21/31] Update StockTest.java
---
.../idatt2003/g40/mappe/model/StockTest.java | 44 ++++++++++++++++---
1 file changed, 39 insertions(+), 5 deletions(-)
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/StockTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/StockTest.java
index c5f907d..8c7f48d 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/StockTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/StockTest.java
@@ -1,15 +1,20 @@
package edu.ntnu.idi.idatt2003.g40.mappe.model;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
import java.math.BigDecimal;
import java.util.List;
-
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
-
final class StockTest {
+ /**
+ * Stock to use for testing.
+ * */
private Stock testStock;
@BeforeEach
@@ -23,6 +28,28 @@ void constructorSetsSymbolAndCompany() {
assertEquals("Apple Inc.", testStock.getCompany());
}
+ @Test
+ void constructorThrowsExceptionOnIllegalArguments() {
+ assertDoesNotThrow(
+ () -> new Stock("AAPL", "APPLE INC.", new BigDecimal("100"))
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new Stock("", "APPLE INC.", new BigDecimal("100"))
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new Stock("AAPL", "", new BigDecimal("100"))
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new Stock("AAPL", "APPLE INC.", new BigDecimal("0"))
+ );
+ assertThrows(IllegalArgumentException.class,
+ () -> new Stock("AAPL", "APPLE INC.", null)
+ );
+ }
+
@Test
void addNewSalesPriceThenGetSalesPriceReturnsLastAddedPrice() {
testStock.addNewSalesPrice(new BigDecimal("123.45"));
@@ -39,11 +66,18 @@ void addNewSalesPriceTwiceGetSalesPriceReturnsMostRecent() {
}
@Test
- void addNewSalesPriceDoesNotAllowNullCurrentImplementation() {
-
+ void addNewSalesPriceThrowsExceptionOnIllegalArguments() {
assertThrows(IllegalArgumentException.class, () -> {
testStock.addNewSalesPrice(null);
});
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ testStock.addNewSalesPrice(new BigDecimal("0"));
+ });
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ testStock.addNewSalesPrice(new BigDecimal("-10"));
+ });
}
@Test
From c174d6e52b3616c6b5c2e3a8eff820e5f1dc879c Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:50:44 +0200
Subject: [PATCH 22/31] Update ViewControllerTest.java
---
.../g40/mappe/view/ViewControllerTest.java | 45 ++++++++++++++-----
1 file changed, 33 insertions(+), 12 deletions(-)
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewControllerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewControllerTest.java
index e105cdf..948d9ca 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewControllerTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewControllerTest.java
@@ -11,30 +11,40 @@
import org.testfx.framework.junit5.ApplicationTest;
class ViewControllerTest extends ApplicationTest {
- private EventManager testEventManager;
- private GenericViewController testViewController;
+
+ /**
+ * View element instance used for testing.
+ * */
private GenericViewElement testViewElement;
@Override
- public void start(Stage stage) {
- testEventManager = new EventManager();
- testViewElement = new ViewControllerTest.GenericViewElement(new Pane());
- testViewController = new GenericViewController(testViewElement, testEventManager);
+ public void start(final Stage stage) {
+ EventManager testEventManager = new EventManager();
+ testViewElement = new GenericViewElement(new Pane());
+ new GenericViewController(testViewElement, testEventManager);
}
@Test
void controllerElementSetsButtonBehavior() {
- assertFalse(testViewElement.buttonPressed);
+ assertFalse(testViewElement.getButtonPressed());
testViewElement.getInteractableButton().fire();
- assertTrue(testViewElement.buttonPressed);
+ assertTrue(testViewElement.getButtonPressed());
}
private enum GenericViewActions {
+ /**
+ * Action used for testing purposes.
+ * */
TEST_ACTION;
}
- private class GenericViewElement extends ViewElement {
- public Boolean buttonPressed = false;
+ /**
+ * Test class meant for simulating a view element instance.
+ *
+ * @see ViewElement
+ * */
+ private static class GenericViewElement extends ViewElement {
+ private boolean buttonPressed = false;
private Button interactableButton;
protected GenericViewElement(final Pane rootPane) {
@@ -51,11 +61,22 @@ public Button getInteractableButton() {
return interactableButton;
}
+ public boolean getButtonPressed() {
+ return buttonPressed;
+ }
+
@Override
- protected void initStyling() { }
+ protected void initStyling() {
+ // Empty
+ }
}
- private class GenericViewController extends ViewController {
+ /**
+ * View controller class used for testing.
+ *
+ * @see ViewController
+ * */
+ private static class GenericViewController extends ViewController {
protected GenericViewController(final ViewControllerTest.GenericViewElement viewElement,
final EventManager eventManager)
From 757b17b184917ed26df5e8795a7acff23e68f402 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:50:46 +0200
Subject: [PATCH 23/31] Create ViewElementTest.java
---
.../g40/mappe/view/ViewElementTest.java | 107 ++++++++++++++++++
1 file changed, 107 insertions(+)
create mode 100644 src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElementTest.java
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElementTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElementTest.java
new file mode 100644
index 0000000..ad4b19e
--- /dev/null
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElementTest.java
@@ -0,0 +1,107 @@
+package edu.ntnu.idi.idatt2003.g40.mappe.view;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager;
+import javafx.scene.control.Button;
+import javafx.scene.layout.Pane;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
+import org.junit.jupiter.api.Test;
+import org.testfx.framework.junit5.ApplicationTest;
+
+class ViewElementTest extends ApplicationTest {
+
+ /**
+ * View element instance used for testing.
+ * */
+ private ViewElementTest.GenericViewElement testViewElement;
+
+ /**
+ * Root of generic view instance.
+ * */
+ private Pane rootPane;
+
+ @Override
+ public void start(final Stage stage) {
+ rootPane = new Pane();
+ testViewElement = new ViewElementTest.GenericViewElement(rootPane);
+ }
+
+ @Test
+ void constructorSetsValuesAsExpected() {
+ assertEquals(rootPane, testViewElement.getRootPane());
+ }
+
+ @Test
+ void constructorThrowsExceptionWhenIllegalArguments() {
+ assertDoesNotThrow(
+ () -> new ViewElementTest.GenericViewElement(new VBox())
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new ViewElementTest.GenericViewElement(null)
+ );
+ }
+
+ @Test
+ void setOnActionThrowsExceptionOnIllegalArguments() {
+ assertThrows(IllegalArgumentException.class,
+ () -> testViewElement.setOnAction(
+ GenericViewActions.UNUSED_TEST_ACTION,
+ () -> testViewElement.setButtonPressed()
+ )
+ );
+ }
+
+ private enum GenericViewActions {
+ /**
+ * Action used for testing purposes.
+ * */
+ TEST_ACTION,
+
+ /**
+ * Unused test action to check exception throwing.
+ * */
+ UNUSED_TEST_ACTION
+ }
+
+ /**
+ * Test class meant for simulating a view element instance.
+ *
+ * @see ViewElement
+ * */
+ private static class GenericViewElement extends ViewElement {
+ private boolean buttonPressed = false;
+ private Button interactableButton;
+
+ protected GenericViewElement(final Pane rootPane) {
+ super(rootPane, ViewElementTest.GenericViewActions.class);
+ }
+
+ @Override
+ protected void initLayout() {
+ interactableButton = new Button("Click me!");
+ registerButton(ViewElementTest.GenericViewActions.TEST_ACTION, interactableButton);
+ }
+
+ public Button getInteractableButton() {
+ return interactableButton;
+ }
+
+ public boolean getButtonPressed() {
+ return buttonPressed;
+ }
+
+ public void setButtonPressed() {
+ buttonPressed = true;
+ }
+
+ @Override
+ protected void initStyling() {
+ // Empty
+ }
+ }
+}
\ No newline at end of file
From 006979aa457e1d14862882b4e390539b3bba4566 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:50:48 +0200
Subject: [PATCH 24/31] Update ViewManagerTest.java
---
.../g40/mappe/view/ViewManagerTest.java | 69 ++++++++++++-------
1 file changed, 44 insertions(+), 25 deletions(-)
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManagerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManagerTest.java
index f4d04e9..0ecc2a9 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManagerTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManagerTest.java
@@ -2,7 +2,6 @@
import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager;
import javafx.scene.Scene;
-import javafx.scene.control.Button;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import org.junit.jupiter.api.Assertions;
@@ -17,16 +16,22 @@
*/
class ViewManagerTest extends ApplicationTest {
+ /**
+ * View manager object used for testing.
+ * */
private ViewManager testViewManager;
- private EventManager testEventManager;
- private ViewManagerTest.GenericViewElement genericViewElement;
+
+ /**
+ * Generic view element instance used for testing.
+ * */
+ private ViewManagerTest.GenericViewElement testViewElement;
@Override
public void start(final Stage stage) {
stage.setScene(new Scene(new Pane()));
- testEventManager = new EventManager();
+ EventManager testEventManager = new EventManager();
testViewManager = new ViewManager(stage, testEventManager);
- genericViewElement = new GenericViewElement(new Pane(), ViewEnum.IN_GAME);
+ testViewElement = new GenericViewElement(new Pane(), ViewEnum.IN_GAME);
}
@Test
@@ -36,42 +41,59 @@ void testViewManagerHoldsNoViewAtStart() {
@Test
void addingMultipleOfSameViewThrowsException() {
- testViewManager.addView(genericViewElement);
+ testViewManager.addView(testViewElement);
Assertions.assertThrows(IllegalArgumentException.class, () -> {
- testViewManager.addView(genericViewElement);
+ testViewManager.addView(testViewElement);
});
}
@Test
void testViewManagerSettingCorrectView() {
- testViewManager.addView(genericViewElement);
- testViewManager.setScene(genericViewElement);
- Assertions.assertEquals(genericViewElement, testViewManager.getCurrentView());
+ testViewManager.addView(testViewElement);
+ testViewManager.setScene(testViewElement);
+ Assertions.assertEquals(testViewElement, testViewManager.getCurrentView());
}
@Test
void testViewManagerThrowingErrorWhenSettingNonExistentView() {
Assertions.assertThrows(IllegalArgumentException.class, () -> {
- testViewManager.setScene(genericViewElement);
+ testViewManager.setScene(testViewElement);
});
}
@Test
void throwsErrorWhenAddingWidgetWithNoViewName() {
- genericViewElement = new GenericViewElement(new Pane());
+ testViewElement = new GenericViewElement(new Pane());
Assertions.assertThrows(IllegalArgumentException.class, () -> {
- testViewManager.addView(genericViewElement);
+ testViewManager.addView(testViewElement);
});
}
- private enum GenericActions {
- ACTION_1;
+ @Test
+ void settingViewThroughViewDataWorksAsExpected() {
+ GenericViewElement testViewElement2 =
+ new GenericViewElement(new Pane(), ViewEnum.MAIN_MENU);
+
+ testViewManager.addView(testViewElement2);
+ ViewData viewData = new ViewData(ViewEnum.MAIN_MENU);
+
+ Assertions.assertNotEquals(testViewElement2,
+ testViewManager.getCurrentView());
+
+ testViewManager.setScene(viewData);
+ Assertions.assertEquals(testViewElement2, testViewManager.getCurrentView());
}
+ private enum GenericActions {
+ // Empty.
+ }
- private class GenericViewElement extends ViewElement {
- public Boolean buttonPressed = false;
- private Button interactableButton;
+ /**
+ * Generic view element class used to test view elements within
+ * the view manager.
+ * */
+ private static class GenericViewElement
+ extends ViewElement {
protected GenericViewElement(final Pane rootPane, final ViewEnum viewName) {
super(rootPane, viewName, GenericActions.class);
@@ -83,15 +105,12 @@ protected GenericViewElement(final Pane rootPane) {
@Override
protected void initLayout() {
- interactableButton = new Button("Click me!");
- registerButton(GenericActions.ACTION_1, interactableButton);
- }
-
- public Button getInteractableButton() {
- return interactableButton;
+ // Empty for view manager testing.
}
@Override
- protected void initStyling() { }
+ protected void initStyling() {
+ // Empty for view manager testing.
+ }
}
}
From 5fe81c2be588ceed10f099358299c8f7292ab90d Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:53:06 +0200
Subject: [PATCH 25/31] Feat: Renamed File Parser to File Manager, and File
Converter to File Parser
---
.../ntnu/idi/idatt2003/g40/mappe/Main.java | 6 +-
.../g40/mappe/service/FileConverter.java | 93 --------
.../g40/mappe/service/FileManager.java | 187 ++++++++++++++++
.../g40/mappe/service/FileParser.java | 204 +++++-------------
.../g40/mappe/service/package-info.java | 2 +-
.../g40/mappe/service/FileConverterTest.java | 72 -------
.../g40/mappe/service/FileManagerTest.java | 60 ++++++
.../g40/mappe/service/FileParserTest.java | 82 ++++---
8 files changed, 353 insertions(+), 353 deletions(-)
delete mode 100644 src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileConverter.java
create mode 100644 src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileManager.java
delete mode 100644 src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileConverterTest.java
create mode 100644 src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileManagerTest.java
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
index 3caea4b..dd5a61d 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
@@ -3,8 +3,8 @@
import edu.ntnu.idi.idatt2003.g40.mappe.engine.Exchange;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Player;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.FileConverter;
import edu.ntnu.idi.idatt2003.g40.mappe.service.FileParser;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.FileManager;
import edu.ntnu.idi.idatt2003.g40.mappe.service.SaveGameService;
import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager;
import edu.ntnu.idi.idatt2003.g40.mappe.utils.ConfigValues;
@@ -86,9 +86,9 @@ public void start(final Stage stage) throws Exception {
ViewManager viewManager = new ViewManager(stage, eventManager);
List stocksInFile;
- FileParser parser1 = new FileParser("/sp500.csv");
+ FileManager parser1 = new FileManager("/sp500.csv");
- FileConverter converter1 = new FileConverter();
+ FileParser converter1 = new FileParser();
stocksInFile = converter1.getStocksFromStrings(parser1.readFile());
Exchange exchange = new Exchange("Exchange", stocksInFile);
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileConverter.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileConverter.java
deleted file mode 100644
index 397328d..0000000
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileConverter.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package edu.ntnu.idi.idatt2003.g40.mappe.service;
-
-import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock;
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Converts stock objects to/from string format for file handling.
- *
- * Responsibilities:
- *
- * - Turn a valid list of stock string elements to a
- * list of {@link Stock} objects.
- *
- * - Turn a list of stock objects to a
- * list of string elements.
- *
- *
- * Used with {@link FileParser}
- *
- * @see FileParser
- * @author tohja
- * @version 1.0.0
- * */
-public class FileConverter {
-
- /**
- * Turns a list of valid string representations
- * of stock objects to a list of stock objects.
- *
- * @param validStocks list of string elements properly
- * representing stock objects.
- *
- * @return {@link List}
- *
- * @throws IllegalArgumentException if stock object(s) cannot be converted,
- * or if list is null/empty.
- *
- * */
- public List getStocksFromStrings(final List validStocks)
- throws IllegalArgumentException {
- if (validStocks == null || validStocks.isEmpty()) {
- throw new IllegalArgumentException("Empty or null stock list!");
- } else {
- List stocksFromFile = new ArrayList<>();
- List stockSymbols = new ArrayList<>();
-
- validStocks.forEach(s -> {
- String[] lineElements = s.split(",");
- String stockSymbol = lineElements[0].trim();
- String stockName = lineElements[1].trim();
- BigDecimal stockPrice = new BigDecimal(lineElements[2].trim());
-
- try {
- Stock stockObject = new Stock(stockSymbol, stockName, stockPrice);
- if (!stockSymbols.contains(stockSymbol)) {
- stockSymbols.add(stockSymbol);
- stocksFromFile.add(stockObject);
- }
- } catch (IllegalArgumentException e) {
- System.err.println("(" + s + ") is not a valid stock! Skipping...");
- }
-
- });
- return stocksFromFile;
- }
- }
-
- /**
- * Converts a list of stocks to string representations of that stock.
- *
- * format: SYMBOL, NAME, PRICE
- *
- * @param stocks a list of {@link Stock} objects.
- *
- * @return a list of string representation of the stock objects.
- *
- * @throws IllegalArgumentException if stocks is empty or null.
- * */
- public List stocksToStrings(final List stocks) {
- if (stocks == null || stocks.isEmpty()) {
- throw new IllegalArgumentException("Empty or null stock list!");
- } else {
- ArrayList stringList = new ArrayList<>();
- stocks.forEach(s ->
- stringList.add(s.getSymbol().trim() + "," + s.getCompany().trim()
- + "," + s.getSalesPrice().toString())
- );
- return stringList;
- }
- }
-}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileManager.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileManager.java
new file mode 100644
index 0000000..cd674cd
--- /dev/null
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileManager.java
@@ -0,0 +1,187 @@
+package edu.ntnu.idi.idatt2003.g40.mappe.service;
+
+import java.io.*;
+import java.math.BigDecimal;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * Class used to parse (filter) valid stocks from a .txt file.
+ *
+ * Responsibilities:
+ *
+ * - Read file and return a filtered list of string elements,
+ * where each element represents a valid stock line.
+ *
+ * - Write a list of string representations of stock objects
+ * to file, each stock separated by a line.
+ *
+ *
+ * Used with {@link FileParser}
+ *
+ * @see FileParser
+ * @author tohja
+ * @version 1.0.0
+ * */
+public class FileManager {
+
+ /** The path name this parser is using.*/
+ private final String pathName;
+
+ /**
+ * Rule set for validating lines and strings.
+ *
+ * Uses predicates.
+ * */
+ private enum ParserRuleSet {
+
+ /**
+ * Rule for whether string is empty or not.
+ * */
+ NOT_EMPTY(s -> !s.trim().isEmpty()),
+
+ /**
+ * Rule for if string is comment or not.
+ * */
+ NOT_COMMENT(s -> !s.startsWith("#")),
+
+ /**
+ * Rule for if line is in valid format.
+ * */
+ VALID_FORMAT(NOT_EMPTY.rule.and(NOT_COMMENT.rule)),
+
+ /**
+ * Rule for if string is considered a valid company symbol.
+ * */
+ VALID_CODE(s -> s.matches("[A-Z]{4}")),
+
+ /**
+ * Rule for if string is a valid company name.
+ * */
+ VALID_NAME(s -> s.matches(".*")),
+
+ /**
+ * Rule for if string can be parsed to a {@link BigDecimal} object.
+ * */
+ CAN_PARSE_TO_BIG_DECIMAL(s -> {
+ try {
+ new BigDecimal(s);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }),
+
+ /**
+ * Rule for if string is in a valid price format.
+ * */
+ VALID_PRICE_FORMAT(s -> s.matches("[^a-zA-Z]+")),
+
+ /**
+ * Rule for if string is a valid price.
+ * */
+ VALID_PRICE(VALID_PRICE_FORMAT.rule.and(CAN_PARSE_TO_BIG_DECIMAL.rule));
+
+ /**
+ * The constants' rules as predicates with input of type string.
+ * */
+ private final Predicate rule;
+
+ ParserRuleSet(final Predicate rule) {
+ this.rule = rule;
+ }
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param pathName the file path name to read.
+ * */
+ public FileManager(final String pathName) {
+ this.pathName = pathName;
+ }
+
+ /**
+ * Reads the file and returns a list element of all valid stocks as strings.
+ *
+ * Uses {@link BufferedReader} for opening a file stream.
+ *
+ * @return {@link List} object of all valid stock strings in file.
+ *
+ * @throws IOException if path cannot be read.
+ *
+ * @see Path
+ * */
+
+ public List readFile() throws IOException {
+ try (InputStream inputStream = getClass().getResourceAsStream(pathName);
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
+
+ List allLines = bufferedReader.readAllLines();
+ List readableLines =
+ allLines.stream()
+ .filter(ParserRuleSet.VALID_FORMAT.rule).toList();
+
+ // Valid lines (following the correct regular expressions)
+ return readableLines.stream().filter(s -> {
+ String[] parts = s.trim().split(",");
+
+ if (parts.length != 3) {
+ return false;
+ }
+
+ boolean validCode = ParserRuleSet
+ .VALID_CODE.rule.test(parts[0].trim());
+
+ boolean validName = ParserRuleSet
+ .VALID_NAME.rule.test(parts[1].trim());
+
+ boolean validPrice = ParserRuleSet
+ .VALID_PRICE.rule.test(parts[2].trim());
+
+ return validCode && validName && validPrice;
+ }).toList();
+
+ } catch (IOException e) {
+ throw new IOException("File parser could not parse file!");
+ }
+ }
+
+ /**
+ * Writes a given lists of stocks to the file.
+ *
+ * Uses {@link BufferedWriter}.
+ *
+ * @param stringList list of strings representing stocks in the format
+ * SYMBOL, NAME, PRICE
+ *
+ * @throws IOException if writing process throws error.
+ * */
+ public void writeStocksToFile(final List stringList)
+ throws IllegalArgumentException, IOException {
+ if (stringList == null || stringList.isEmpty()) {
+ throw new IllegalArgumentException("Empty or null list of stocks!");
+ } else {
+ Path path = Paths.get(pathName);
+
+ try (BufferedWriter writer = Files.newBufferedWriter(path,
+ StandardOpenOption.CREATE,
+ StandardOpenOption.APPEND)) {
+
+ writer.newLine();
+
+ for (String line : stringList) {
+ writer.write(line);
+ writer.newLine();
+ }
+ } catch (IOException e) {
+ throw new IOException("Error during buffered write", e);
+ }
+ }
+ }
+}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParser.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParser.java
index 481d423..c621a47 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParser.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParser.java
@@ -1,187 +1,93 @@
package edu.ntnu.idi.idatt2003.g40.mappe.service;
-import java.io.*;
+import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock;
import java.math.BigDecimal;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
import java.util.List;
-import java.util.function.Predicate;
/**
- * Class used to parse (filter) valid stocks from a .txt file.
+ * Converts stock objects to/from string format for file handling.
*
* Responsibilities:
*
- * - Read file and return a filtered list of string elements,
- * where each element represents a valid stock line.
+ * - Turn a valid list of stock string elements to a
+ * list of {@link Stock} objects.
*
- * - Write a list of string representations of stock objects
- * to file, each stock separated by a line.
+ * - Turn a list of stock objects to a
+ * list of string elements.
*
*
- * Used with {@link FileConverter}
+ * Used with {@link FileManager}
*
- * @see FileConverter
+ * @see FileManager
* @author tohja
* @version 1.0.0
* */
public class FileParser {
- /** The path name this parser is using.*/
- private final String pathName;
-
- /**
- * Rule set for validating lines and strings.
- *
- * Uses predicates.
- * */
- private enum ParserRuleSet {
-
- /**
- * Rule for whether string is empty or not.
- * */
- NOT_EMPTY(s -> !s.trim().isEmpty()),
-
- /**
- * Rule for if string is comment or not.
- * */
- NOT_COMMENT(s -> !s.startsWith("#")),
-
- /**
- * Rule for if line is in valid format.
- * */
- VALID_FORMAT(NOT_EMPTY.rule.and(NOT_COMMENT.rule)),
-
- /**
- * Rule for if string is considered a valid company symbol.
- * */
- VALID_CODE(s -> s.matches("[A-Z]{4}")),
-
- /**
- * Rule for if string is a valid company name.
- * */
- VALID_NAME(s -> s.matches(".*")),
-
- /**
- * Rule for if string can be parsed to a {@link BigDecimal} object.
- * */
- CAN_PARSE_TO_BIG_DECIMAL(s -> {
- try {
- new BigDecimal(s);
- return true;
- } catch (NumberFormatException e) {
- return false;
- }
- }),
-
- /**
- * Rule for if string is in a valid price format.
- * */
- VALID_PRICE_FORMAT(s -> s.matches("[^a-zA-Z]+")),
-
- /**
- * Rule for if string is a valid price.
- * */
- VALID_PRICE(VALID_PRICE_FORMAT.rule.and(CAN_PARSE_TO_BIG_DECIMAL.rule));
-
- /**
- * The constants' rules as predicates with input of type string.
- * */
- private final Predicate rule;
-
- ParserRuleSet(final Predicate rule) {
- this.rule = rule;
- }
- }
-
- /**
- * Constructor.
- *
- * @param pathName the file path name to read.
- * */
- public FileParser(final String pathName) {
- this.pathName = pathName;
- }
-
/**
- * Reads the file and returns a list element of all valid stocks as strings.
+ * Turns a list of valid string representations
+ * of stock objects to a list of stock objects.
*
- * Uses {@link BufferedReader} for opening a file stream.
+ * @param validStocks list of string elements properly
+ * representing stock objects.
*
- * @return {@link List} object of all valid stock strings in file.
+ * @return {@link List}
*
- * @throws IOException if path cannot be read.
+ * @throws IllegalArgumentException if stock object(s) cannot be converted,
+ * or if list is null/empty.
*
- * @see Path
* */
-
- public List readFile() throws IOException {
- try (InputStream inputStream = getClass().getResourceAsStream(pathName);
- BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
-
- List allLines = bufferedReader.readAllLines();
- List readableLines =
- allLines.stream()
- .filter(ParserRuleSet.VALID_FORMAT.rule).toList();
-
- // Valid lines (following the correct regular expressions)
- return readableLines.stream().filter(s -> {
- String[] parts = s.trim().split(",");
-
- if (parts.length != 3) {
- return false;
+ public List getStocksFromStrings(final List validStocks)
+ throws IllegalArgumentException {
+ if (validStocks == null || validStocks.isEmpty()) {
+ throw new IllegalArgumentException("Empty or null stock list!");
+ } else {
+ List stocksFromFile = new ArrayList<>();
+ List stockSymbols = new ArrayList<>();
+
+ validStocks.forEach(s -> {
+ String[] lineElements = s.split(",");
+ String stockSymbol = lineElements[0].trim();
+ String stockName = lineElements[1].trim();
+ BigDecimal stockPrice = new BigDecimal(lineElements[2].trim());
+
+ try {
+ Stock stockObject = new Stock(stockSymbol, stockName, stockPrice);
+ if (!stockSymbols.contains(stockSymbol)) {
+ stockSymbols.add(stockSymbol);
+ stocksFromFile.add(stockObject);
+ }
+ } catch (IllegalArgumentException e) {
+ System.err.println("(" + s + ") is not a valid stock! Skipping...");
}
- boolean validCode = ParserRuleSet
- .VALID_CODE.rule.test(parts[0].trim());
-
- boolean validName = ParserRuleSet
- .VALID_NAME.rule.test(parts[1].trim());
-
- boolean validPrice = ParserRuleSet
- .VALID_PRICE.rule.test(parts[2].trim());
-
- return validCode && validName && validPrice;
- }).toList();
-
- } catch (IOException e) {
- throw new IOException("File parser could not parse file!");
+ });
+ return stocksFromFile;
}
}
/**
- * Writes a given lists of stocks to the file.
+ * Converts a list of stocks to string representations of that stock.
+ *
+ * format: SYMBOL, NAME, PRICE
*
- * Uses {@link BufferedWriter}.
+ * @param stocks a list of {@link Stock} objects.
*
- * @param stringList list of strings representing stocks in the format
- * SYMBOL, NAME, PRICE
+ * @return a list of string representation of the stock objects.
*
- * @throws IOException if writing process throws error.
+ * @throws IllegalArgumentException if stocks is empty or null.
* */
- public void writeStocksToFile(final List stringList)
- throws IllegalArgumentException, IOException {
- if (stringList == null || stringList.isEmpty()) {
- throw new IllegalArgumentException("Empty or null list of stocks!");
+ public List stocksToStrings(final List stocks) {
+ if (stocks == null || stocks.isEmpty()) {
+ throw new IllegalArgumentException("Empty or null stock list!");
} else {
- Path path = Paths.get(pathName);
-
- try (BufferedWriter writer = Files.newBufferedWriter(path,
- StandardOpenOption.CREATE,
- StandardOpenOption.APPEND)) {
-
- writer.newLine();
-
- for (String line : stringList) {
- writer.write(line);
- writer.newLine();
- }
- } catch (IOException e) {
- throw new IOException("Error during buffered write", e);
- }
+ ArrayList stringList = new ArrayList<>();
+ stocks.forEach(s ->
+ stringList.add(s.getSymbol().trim() + "," + s.getCompany().trim()
+ + "," + s.getSalesPrice().toString())
+ );
+ return stringList;
}
}
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/package-info.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/package-info.java
index 633c60a..9a013e4 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/package-info.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/package-info.java
@@ -1,5 +1,5 @@
/**
* Contains classes providing modular functionality to the application,
- * such as the {@link edu.ntnu.idi.idatt2003.g40.mappe.service.FileConverter}.
+ * such as the {@link edu.ntnu.idi.idatt2003.g40.mappe.service.FileParser}.
* */
package edu.ntnu.idi.idatt2003.g40.mappe.service;
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileConverterTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileConverterTest.java
deleted file mode 100644
index 5cb5444..0000000
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileConverterTest.java
+++ /dev/null
@@ -1,72 +0,0 @@
-package edu.ntnu.idi.idatt2003.g40.mappe.service;
-
-import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.util.ArrayList;
-import java.util.List;
-
-class FileConverterTest {
-
- private FileConverter converter;
-
- private String validStockAsString1;
- private String validStockAsString2;
- private String validStockAsString3;
- private ArrayList allStocks;
- private String invalidStockAsString1;
-
- @BeforeEach
- void setUp() {
- validStockAsString1 = "AAPL, Apple inc., 251.42";
- validStockAsString2 = "NVID, Nvidia corp., 100.25";
- validStockAsString3 = "SAMS, Samsung corporation, 103.21";
-
- invalidStockAsString1 = "INVALID, This stock has an invalid code!, 100.21";
-
- allStocks = new ArrayList<>();
-
- allStocks.add(validStockAsString1);
- allStocks.add(validStockAsString2);
- allStocks.add(validStockAsString3);
- allStocks.add(invalidStockAsString1);
-
- converter = new FileConverter();
- }
-
- @Test
- void converter_returns_valid_stock_apple() {
-
- boolean stockIncluded = false;
-
- List stocksFromConverter = converter.getStocksFromStrings(allStocks);
-
- for (Stock s : stocksFromConverter) {
- if (s.getSymbol().equals("AAPL")) {
- stockIncluded = true;
- break;
- }
- }
-
- Assertions.assertTrue(stockIncluded);
- }
-
- @Test
- void converter_ignores_invalid_stock_representation() {
-
- boolean stockIncluded = false;
-
- List stocksFromConverter = converter.getStocksFromStrings(allStocks);
-
- for (Stock s : stocksFromConverter) {
- if (s.getSymbol().equals("INVALID")) {
- stockIncluded = true;
- break;
- }
- }
-
- Assertions.assertFalse(stockIncluded);
- }
-}
\ No newline at end of file
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileManagerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileManagerTest.java
new file mode 100644
index 0000000..49dbd7e
--- /dev/null
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileManagerTest.java
@@ -0,0 +1,60 @@
+package edu.ntnu.idi.idatt2003.g40.mappe.service;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class FileManagerTest {
+
+ private final String testStockDataPath = "/testStockData.txt";
+
+ private final String absoluteTestStockDataPath = "src/main/resources/testStockData.txt";
+ FileManager fileManager;
+
+ private final String validStockFromFile = "NVID, Nvidida Corporation, 241.591";
+
+ private final String invalidStockFromFile = "COOLI, This is a cool name, 252.2";
+
+ private final String commentFromFile = "#Above me are some valid formats.";
+
+ private List allLines = new ArrayList<>();
+
+ private List validStocks = new ArrayList<>();
+
+ @BeforeEach
+ void setUp() throws Exception {
+ fileManager = new FileManager(testStockDataPath);
+ Path path = Paths.get(absoluteTestStockDataPath);
+ allLines = Files.readAllLines(path);
+ try {
+ validStocks = fileManager.readFile();
+ } catch (Exception _) {
+ throw new Exception("Test failed");
+ }
+ }
+
+ @Test
+ void parser_gets_valid_stock_from_file() {
+ assertTrue(allLines.contains(validStockFromFile));
+ assertTrue(validStocks.contains(validStockFromFile));
+ }
+
+ @Test
+ void parser_skips_comments_from_file() {
+ assertTrue(allLines.contains(commentFromFile));
+ assertFalse(validStocks.contains(commentFromFile));
+ }
+
+ @Test
+ void parser_skips_invalid_stock_from_file() {
+ assertTrue(allLines.contains(invalidStockFromFile));
+ assertFalse(validStocks.contains(invalidStockFromFile));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParserTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParserTest.java
index 7d87b1a..fb59fc6 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParserTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParserTest.java
@@ -1,60 +1,72 @@
package edu.ntnu.idi.idatt2003.g40.mappe.service;
+import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
-import static org.junit.jupiter.api.Assertions.*;
-
class FileParserTest {
- private final String testStockDataPath = "/testStockData.txt";
-
- private final String absoluteTestStockDataPath = "src/main/resources/testStockData.txt";
- FileParser fileParser;
+ private FileParser converter;
- private final String validStockFromFile = "NVID, Nvidida Corporation, 241.591";
+ private String validStockAsString1;
+ private String validStockAsString2;
+ private String validStockAsString3;
+ private ArrayList allStocks;
+ private String invalidStockAsString1;
- private final String invalidStockFromFile = "COOLI, This is a cool name, 252.2";
+ @BeforeEach
+ void setUp() {
+ validStockAsString1 = "AAPL, Apple inc., 251.42";
+ validStockAsString2 = "NVID, Nvidia corp., 100.25";
+ validStockAsString3 = "SAMS, Samsung corporation, 103.21";
- private final String commentFromFile = "#Above me are some valid formats.";
+ invalidStockAsString1 = "INVALID, This stock has an invalid code!, 100.21";
- private List allLines = new ArrayList<>();
+ allStocks = new ArrayList<>();
- private List validStocks = new ArrayList<>();
+ allStocks.add(validStockAsString1);
+ allStocks.add(validStockAsString2);
+ allStocks.add(validStockAsString3);
+ allStocks.add(invalidStockAsString1);
- @BeforeEach
- void setUp() throws Exception {
- fileParser = new FileParser(testStockDataPath);
- Path path = Paths.get(absoluteTestStockDataPath);
- allLines = Files.readAllLines(path);
- try {
- validStocks = fileParser.readFile();
- } catch (Exception _) {
- throw new Exception("Test failed");
- }
+ converter = new FileParser();
}
@Test
- void parser_gets_valid_stock_from_file() {
- assertTrue(allLines.contains(validStockFromFile));
- assertTrue(validStocks.contains(validStockFromFile));
- }
+ void converter_returns_valid_stock_apple() {
- @Test
- void parser_skips_comments_from_file() {
- assertTrue(allLines.contains(commentFromFile));
- assertFalse(validStocks.contains(commentFromFile));
+ boolean stockIncluded = false;
+
+ List stocksFromConverter = converter.getStocksFromStrings(allStocks);
+
+ for (Stock s : stocksFromConverter) {
+ if (s.getSymbol().equals("AAPL")) {
+ stockIncluded = true;
+ break;
+ }
+ }
+
+ Assertions.assertTrue(stockIncluded);
}
@Test
- void parser_skips_invalid_stock_from_file() {
- assertTrue(allLines.contains(invalidStockFromFile));
- assertFalse(validStocks.contains(invalidStockFromFile));
+ void converter_ignores_invalid_stock_representation() {
+
+ boolean stockIncluded = false;
+
+ List stocksFromConverter = converter.getStocksFromStrings(allStocks);
+
+ for (Stock s : stocksFromConverter) {
+ if (s.getSymbol().equals("INVALID")) {
+ stockIncluded = true;
+ break;
+ }
+ }
+
+ Assertions.assertFalse(stockIncluded);
}
}
\ No newline at end of file
From 9dca3038d3a8c5b75f7a0fa6fbbc85cfa4d2c7bf Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 19:55:16 +0200
Subject: [PATCH 26/31] Feat: Renamed FileParser and FileManager to
StockFileParser and StockFileManager, respectively.
---
.../java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java | 8 ++++----
.../idatt2003/g40/mappe/service/SaveGameService.java | 1 -
.../{FileManager.java => StockFileManager.java} | 8 ++++----
.../{FileParser.java => StockFileParser.java} | 6 +++---
.../g40/mappe/service/TransactionFactory.java | 1 -
.../idatt2003/g40/mappe/service/package-info.java | 2 +-
...ileManagerTest.java => StockFileManagerTest.java} | 8 ++++----
...{FileParserTest.java => StockFileParserTest.java} | 6 +++---
.../g40/mappe/service/event/EventManagerTest.java | 12 ++++++++++++
9 files changed, 31 insertions(+), 21 deletions(-)
rename src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/{FileManager.java => StockFileManager.java} (97%)
rename src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/{FileParser.java => StockFileParser.java} (96%)
rename src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/{FileManagerTest.java => StockFileManagerTest.java} (89%)
rename src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/{FileParserTest.java => StockFileParserTest.java} (94%)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
index dd5a61d..0ef6962 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
@@ -3,8 +3,8 @@
import edu.ntnu.idi.idatt2003.g40.mappe.engine.Exchange;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Player;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.FileParser;
-import edu.ntnu.idi.idatt2003.g40.mappe.service.FileManager;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.StockFileParser;
+import edu.ntnu.idi.idatt2003.g40.mappe.service.StockFileManager;
import edu.ntnu.idi.idatt2003.g40.mappe.service.SaveGameService;
import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventManager;
import edu.ntnu.idi.idatt2003.g40.mappe.utils.ConfigValues;
@@ -86,9 +86,9 @@ public void start(final Stage stage) throws Exception {
ViewManager viewManager = new ViewManager(stage, eventManager);
List stocksInFile;
- FileManager parser1 = new FileManager("/sp500.csv");
+ StockFileManager parser1 = new StockFileManager("/sp500.csv");
- FileParser converter1 = new FileParser();
+ StockFileParser converter1 = new StockFileParser();
stocksInFile = converter1.getStocksFromStrings(parser1.readFile());
Exchange exchange = new Exchange("Exchange", stocksInFile);
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/SaveGameService.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/SaveGameService.java
index 2c6aaed..0350071 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/SaveGameService.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/SaveGameService.java
@@ -1,7 +1,6 @@
package edu.ntnu.idi.idatt2003.g40.mappe.service;
import edu.ntnu.idi.idatt2003.g40.mappe.model.SaveGame;
-
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileManager.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManager.java
similarity index 97%
rename from src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileManager.java
rename to src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManager.java
index cd674cd..b1a782d 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileManager.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManager.java
@@ -22,13 +22,13 @@
* to file, each stock separated by a line.
*
*
- * Used with {@link FileParser}
+ * Used with {@link StockFileParser}
*
- * @see FileParser
+ * @see StockFileParser
* @author tohja
* @version 1.0.0
* */
-public class FileManager {
+public class StockFileManager {
/** The path name this parser is using.*/
private final String pathName;
@@ -102,7 +102,7 @@ private enum ParserRuleSet {
*
* @param pathName the file path name to read.
* */
- public FileManager(final String pathName) {
+ public StockFileManager(final String pathName) {
this.pathName = pathName;
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParser.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParser.java
similarity index 96%
rename from src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParser.java
rename to src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParser.java
index c621a47..b08558d 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParser.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParser.java
@@ -17,13 +17,13 @@
* list of string elements.
*
*
- * Used with {@link FileManager}
+ * Used with {@link StockFileManager}
*
- * @see FileManager
+ * @see StockFileManager
* @author tohja
* @version 1.0.0
* */
-public class FileParser {
+public class StockFileParser {
/**
* Turns a list of valid string representations
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactory.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactory.java
index 490a93c..62e849c 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactory.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/TransactionFactory.java
@@ -23,7 +23,6 @@ private TransactionFactory() {
* @param transactionType the type of transaction to create.
* @param share the share this transaction is about.
* @param week the week this transaction takes place in.
- * @param calculator the calculator to use when calculating the transaction.
*
* @return an implementation of {@link Transaction}.
* */
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/package-info.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/package-info.java
index 9a013e4..10593f1 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/package-info.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/package-info.java
@@ -1,5 +1,5 @@
/**
* Contains classes providing modular functionality to the application,
- * such as the {@link edu.ntnu.idi.idatt2003.g40.mappe.service.FileParser}.
+ * such as the {@link edu.ntnu.idi.idatt2003.g40.mappe.service.StockFileParser}.
* */
package edu.ntnu.idi.idatt2003.g40.mappe.service;
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileManagerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java
similarity index 89%
rename from src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileManagerTest.java
rename to src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java
index 49dbd7e..9706b5c 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileManagerTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java
@@ -11,12 +11,12 @@
import static org.junit.jupiter.api.Assertions.*;
-class FileManagerTest {
+class StockFileManagerTest {
private final String testStockDataPath = "/testStockData.txt";
private final String absoluteTestStockDataPath = "src/main/resources/testStockData.txt";
- FileManager fileManager;
+ StockFileManager stockFileManager;
private final String validStockFromFile = "NVID, Nvidida Corporation, 241.591";
@@ -30,11 +30,11 @@ class FileManagerTest {
@BeforeEach
void setUp() throws Exception {
- fileManager = new FileManager(testStockDataPath);
+ stockFileManager = new StockFileManager(testStockDataPath);
Path path = Paths.get(absoluteTestStockDataPath);
allLines = Files.readAllLines(path);
try {
- validStocks = fileManager.readFile();
+ validStocks = stockFileManager.readFile();
} catch (Exception _) {
throw new Exception("Test failed");
}
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParserTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParserTest.java
similarity index 94%
rename from src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParserTest.java
rename to src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParserTest.java
index fb59fc6..61ecfb2 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/FileParserTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParserTest.java
@@ -8,9 +8,9 @@
import java.util.ArrayList;
import java.util.List;
-class FileParserTest {
+class StockFileParserTest {
- private FileParser converter;
+ private StockFileParser converter;
private String validStockAsString1;
private String validStockAsString2;
@@ -33,7 +33,7 @@ void setUp() {
allStocks.add(validStockAsString3);
allStocks.add(invalidStockAsString1);
- converter = new FileParser();
+ converter = new StockFileParser();
}
@Test
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManagerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManagerTest.java
index 4852d1c..eee20c2 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManagerTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/event/EventManagerTest.java
@@ -6,6 +6,7 @@
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -28,8 +29,19 @@ private enum TestEventTypes implements EventChannel {
}
+ /**
+ * Event manager used for testing.
+ * */
private EventManager testEventManager;
+
+ /**
+ * Example event subscriber 1.
+ * */
private SampleEventSubscriber sampleEventSubscriber1;
+
+ /**
+ * Example event subscriber 2.
+ * */
private SampleEventSubscriber sampleEventSubscriber2;
@BeforeEach
From 329c2f1b595f83f954f5be64cfc8ebbcec748fb8 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 20:24:58 +0200
Subject: [PATCH 27/31] Feat: Updated File Manager to have a fallback resource
Also updated tests to be independent on external files.
---
.../ntnu/idi/idatt2003/g40/mappe/Main.java | 2 +-
.../g40/mappe/service/StockFileManager.java | 73 ++++++++++++----
src/main/resources/testStockData.txt | 11 ---
.../mappe/service/StockFileManagerTest.java | 86 ++++++++++---------
4 files changed, 101 insertions(+), 71 deletions(-)
delete mode 100644 src/main/resources/testStockData.txt
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
index 0ef6962..10ae9b9 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
@@ -86,7 +86,7 @@ public void start(final Stage stage) throws Exception {
ViewManager viewManager = new ViewManager(stage, eventManager);
List stocksInFile;
- StockFileManager parser1 = new StockFileManager("/sp500.csv");
+ StockFileManager parser1 = new StockFileManager("src/main/resources/sp500.csv");
StockFileParser converter1 = new StockFileParser();
stocksInFile = converter1.getStocksFromStrings(parser1.readFile());
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManager.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManager.java
index b1a782d..ac5cb7c 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManager.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManager.java
@@ -1,6 +1,10 @@
package edu.ntnu.idi.idatt2003.g40.mappe.service;
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -72,7 +76,7 @@ private enum ParserRuleSet {
try {
new BigDecimal(s);
return true;
- } catch (NumberFormatException e) {
+ } catch (NumberFormatException _) {
return false;
}
}),
@@ -109,6 +113,9 @@ public StockFileManager(final String pathName) {
/**
* Reads the file and returns a list element of all valid stocks as strings.
*
+ * If file is not found,
+ * falls back to default file in resources folder.
+ *
* Uses {@link BufferedReader} for opening a file stream.
*
* @return {@link List} object of all valid stock strings in file.
@@ -117,17 +124,20 @@ public StockFileManager(final String pathName) {
*
* @see Path
* */
-
public List readFile() throws IOException {
- try (InputStream inputStream = getClass().getResourceAsStream(pathName);
- BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
+ Path path = Paths.get(pathName);
+
+ if (!Files.exists(path)) {
+ extractResourceFallback(path);
+ }
- List allLines = bufferedReader.readAllLines();
- List readableLines =
- allLines.stream()
- .filter(ParserRuleSet.VALID_FORMAT.rule).toList();
+ try (BufferedReader bufferedReader =
+ Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
+ List allLines = bufferedReader.lines().toList();
+ List readableLines = allLines.stream()
+ .filter(ParserRuleSet.VALID_FORMAT.rule)
+ .toList();
- // Valid lines (following the correct regular expressions)
return readableLines.stream().filter(s -> {
String[] parts = s.trim().split(",");
@@ -135,20 +145,45 @@ public List readFile() throws IOException {
return false;
}
- boolean validCode = ParserRuleSet
- .VALID_CODE.rule.test(parts[0].trim());
-
- boolean validName = ParserRuleSet
- .VALID_NAME.rule.test(parts[1].trim());
-
- boolean validPrice = ParserRuleSet
- .VALID_PRICE.rule.test(parts[2].trim());
+ boolean validCode =
+ ParserRuleSet.VALID_CODE.rule.test(parts[0].trim());
+ boolean validName =
+ ParserRuleSet.VALID_NAME.rule.test(parts[1].trim());
+ boolean validPrice =
+ ParserRuleSet.VALID_PRICE.rule.test(parts[2].trim());
return validCode && validName && validPrice;
}).toList();
} catch (IOException e) {
- throw new IOException("File parser could not parse file!");
+ throw new IOException("File parser could not parse file!", e);
+ }
+ }
+
+ /**
+ * Extracts the fallback template file from the application resources to
+ * the local file system.
+ *
+ * @param targetPath path to send the fallback file.
+ *
+ * @throws IOException if resource or input stream is not found or null.
+ */
+ private void extractResourceFallback(final Path targetPath)
+ throws IOException {
+ String resourceName = "/sp500.csv";
+
+ try (InputStream inputStream = getClass().getResourceAsStream(resourceName)) {
+ if (inputStream == null) {
+ throw new FileNotFoundException(
+ "Resource file not found in JAR: " + resourceName
+ );
+ }
+
+ if (targetPath.getParent() != null) {
+ Files.createDirectories(targetPath.getParent());
+ }
+
+ Files.copy(inputStream, targetPath);
}
}
diff --git a/src/main/resources/testStockData.txt b/src/main/resources/testStockData.txt
deleted file mode 100644
index b349d0e..0000000
--- a/src/main/resources/testStockData.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-#THIS IS A COMMENT.
-
-AAPL, Apple Inc., 276.43
-NVID, Nvidida Corporation, 241.591
-
-#Above me are some valid formats.
-#Below me are some invalid formats
-
-COOLI, This is a cool name, 252.2
-
-COOL, This is a cool name, 252.2a
\ No newline at end of file
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java
index 9706b5c..6630d44 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java
@@ -1,60 +1,66 @@
package edu.ntnu.idi.idatt2003.g40.mappe.service;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.*;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
class StockFileManagerTest {
- private final String testStockDataPath = "/testStockData.txt";
-
- private final String absoluteTestStockDataPath = "src/main/resources/testStockData.txt";
- StockFileManager stockFileManager;
-
- private final String validStockFromFile = "NVID, Nvidida Corporation, 241.591";
-
- private final String invalidStockFromFile = "COOLI, This is a cool name, 252.2";
+ /**
+ * Path used to test file system.
+ * */
+ @TempDir
+ private Path tempDir;
- private final String commentFromFile = "#Above me are some valid formats.";
-
- private List allLines = new ArrayList<>();
+ @Test
+ void readFileParsesValidStocksAndFiltersOutCommentsAndInvalidData()
+ throws IOException {
+ Path tempFile = tempDir.resolve("testStockData.txt");
+ List mockFileData = List.of(
+ "# This is a comment header line and should be skipped",
+ "NVID, Nvidida Corporation, 241.591",
+ "AAPL, Apple Inc, 175.50",
+ "",
+ "INVALID_ROW_MISSING_COLUMNS",
+ "COOLI, This is a cool name but missing price token"
+ );
+ Files.write(tempFile, mockFileData);
- private List validStocks = new ArrayList<>();
+ StockFileManager stockFileManager =
+ new StockFileManager(tempFile.toString());
+ List parsingResults = stockFileManager.readFile();
- @BeforeEach
- void setUp() throws Exception {
- stockFileManager = new StockFileManager(testStockDataPath);
- Path path = Paths.get(absoluteTestStockDataPath);
- allLines = Files.readAllLines(path);
- try {
- validStocks = stockFileManager.readFile();
- } catch (Exception _) {
- throw new Exception("Test failed");
- }
+ assertEquals(2, parsingResults.size());
+ assertTrue(parsingResults.contains("NVID, Nvidida Corporation, 241.591"));
+ assertTrue(parsingResults.contains("AAPL, Apple Inc, 175.50"));
+ assertFalse(parsingResults.stream().anyMatch(line -> line.startsWith("#")));
}
@Test
- void parser_gets_valid_stock_from_file() {
- assertTrue(allLines.contains(validStockFromFile));
- assertTrue(validStocks.contains(validStockFromFile));
- }
+ void readFileHandlesEmptyFileGracefully() throws IOException {
+ Path emptyFile = tempDir.resolve("emptyStockData.txt");
+ Files.createFile(emptyFile);
- @Test
- void parser_skips_comments_from_file() {
- assertTrue(allLines.contains(commentFromFile));
- assertFalse(validStocks.contains(commentFromFile));
+ StockFileManager stockFileManager = new StockFileManager(emptyFile.toString());
+ List results = stockFileManager.readFile();
+
+ assertTrue(results.isEmpty());
}
@Test
- void parser_skips_invalid_stock_from_file() {
- assertTrue(allLines.contains(invalidStockFromFile));
- assertFalse(validStocks.contains(invalidStockFromFile));
+ void constructorOrReadFileDoesNotThrowExceptionOnMissingFile() {
+ StockFileManager stockFileManager =
+ new StockFileManager("non_existent_directory/missing_file.txt");
+
+ assertDoesNotThrow(stockFileManager::readFile);
}
-}
\ No newline at end of file
+}
From 0bc91d8260faa0ea936cac88d8612cca7c820b94 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 20:25:43 +0200
Subject: [PATCH 28/31] Fix: Fixed bug where test would create stock file
---
.../idatt2003/g40/mappe/service/StockFileManagerTest.java | 8 --------
1 file changed, 8 deletions(-)
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java
index 6630d44..81d5f95 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java
@@ -55,12 +55,4 @@ void readFileHandlesEmptyFileGracefully() throws IOException {
assertTrue(results.isEmpty());
}
-
- @Test
- void constructorOrReadFileDoesNotThrowExceptionOnMissingFile() {
- StockFileManager stockFileManager =
- new StockFileManager("non_existent_directory/missing_file.txt");
-
- assertDoesNotThrow(stockFileManager::readFile);
- }
}
From 93969e08841f955e21bbfb095780772172a66aac Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 20:42:16 +0200
Subject: [PATCH 29/31] Feat: Refactor FileManager and FileParser, unit testing
---
.../g40/mappe/service/StockFileParser.java | 60 ++++++++++-------
.../mappe/service/StockFileManagerTest.java | 2 -
.../mappe/service/StockFileParserTest.java | 65 ++++++++++++++-----
3 files changed, 84 insertions(+), 43 deletions(-)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParser.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParser.java
index b08558d..07fd802 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParser.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParser.java
@@ -3,7 +3,9 @@
import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock;
import java.math.BigDecimal;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Converts stock objects to/from string format for file handling.
@@ -42,29 +44,29 @@ public List getStocksFromStrings(final List validStocks)
throws IllegalArgumentException {
if (validStocks == null || validStocks.isEmpty()) {
throw new IllegalArgumentException("Empty or null stock list!");
- } else {
- List stocksFromFile = new ArrayList<>();
- List stockSymbols = new ArrayList<>();
+ }
+ List stocksFromFile = new ArrayList<>();
+ Set stockSymbols = new HashSet<>();
- validStocks.forEach(s -> {
- String[] lineElements = s.split(",");
- String stockSymbol = lineElements[0].trim();
- String stockName = lineElements[1].trim();
- BigDecimal stockPrice = new BigDecimal(lineElements[2].trim());
+ validStocks.forEach(s -> {
+ String[] lineElements = s.split(",");
+ String stockSymbol = lineElements[0].trim();
+ String stockName = lineElements[1].trim();
+ BigDecimal stockPrice = new BigDecimal(lineElements[2].trim());
- try {
- Stock stockObject = new Stock(stockSymbol, stockName, stockPrice);
- if (!stockSymbols.contains(stockSymbol)) {
- stockSymbols.add(stockSymbol);
- stocksFromFile.add(stockObject);
- }
- } catch (IllegalArgumentException e) {
- System.err.println("(" + s + ") is not a valid stock! Skipping...");
+ try {
+ Stock stockObject = new Stock(stockSymbol, stockName, stockPrice);
+ if (stockSymbols.add(stockSymbol)) {
+ stocksFromFile.add(stockObject);
}
-
- });
- return stocksFromFile;
+ } catch (IllegalArgumentException _) {
+ // Ignore invalid strings.
+ }
+ });
+ if (stocksFromFile.isEmpty()) {
+ throw new IllegalArgumentException("No stocks parsed succesfully!");
}
+ return stocksFromFile;
}
/**
@@ -81,13 +83,21 @@ public List getStocksFromStrings(final List validStocks)
public List stocksToStrings(final List stocks) {
if (stocks == null || stocks.isEmpty()) {
throw new IllegalArgumentException("Empty or null stock list!");
- } else {
- ArrayList stringList = new ArrayList<>();
- stocks.forEach(s ->
- stringList.add(s.getSymbol().trim() + "," + s.getCompany().trim()
- + "," + s.getSalesPrice().toString())
+ }
+
+ List stringList = new ArrayList<>();
+ for (Stock s : stocks) {
+ if (s == null) {
+ continue;
+ }
+
+ String csvRow = String.format("%s, %s, %s",
+ s.getSymbol().trim(),
+ s.getCompany().trim(),
+ s.getSalesPrice().toPlainString()
);
- return stringList;
+ stringList.add(csvRow);
}
+ return stringList;
}
}
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java
index 81d5f95..74f0470 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileManagerTest.java
@@ -1,9 +1,7 @@
package edu.ntnu.idi.idatt2003.g40.mappe.service;
-import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParserTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParserTest.java
index 61ecfb2..8fe5158 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParserTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/service/StockFileParserTest.java
@@ -1,30 +1,37 @@
package edu.ntnu.idi.idatt2003.g40.mappe.service;
import edu.ntnu.idi.idatt2003.g40.mappe.model.Stock;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import java.util.ArrayList;
-import java.util.List;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
class StockFileParserTest {
- private StockFileParser converter;
+ /**
+ * Test stock parser to use.
+ * */
+ private StockFileParser testParser;
- private String validStockAsString1;
- private String validStockAsString2;
- private String validStockAsString3;
+ /**
+ * List of strings containing valid and invalid representations
+ * of stocks.
+ * */
private ArrayList allStocks;
- private String invalidStockAsString1;
@BeforeEach
void setUp() {
- validStockAsString1 = "AAPL, Apple inc., 251.42";
- validStockAsString2 = "NVID, Nvidia corp., 100.25";
- validStockAsString3 = "SAMS, Samsung corporation, 103.21";
+ String validStockAsString1 = "AAPL, Apple inc., 251.42";
+ String validStockAsString2 = "NVID, Nvidia corp., 100.25";
+ String validStockAsString3 = "SAMS, Samsung corporation, 103.21";
- invalidStockAsString1 = "INVALID, This stock has an invalid code!, 100.21";
+ String invalidStockAsString1 = "INVALID, This stock has an invalid code!, 100.21";
allStocks = new ArrayList<>();
@@ -33,15 +40,15 @@ void setUp() {
allStocks.add(validStockAsString3);
allStocks.add(invalidStockAsString1);
- converter = new StockFileParser();
+ testParser = new StockFileParser();
}
@Test
- void converter_returns_valid_stock_apple() {
+ void getStocksFromStringsReturnsValidStocks() {
boolean stockIncluded = false;
- List stocksFromConverter = converter.getStocksFromStrings(allStocks);
+ List stocksFromConverter = testParser.getStocksFromStrings(allStocks);
for (Stock s : stocksFromConverter) {
if (s.getSymbol().equals("AAPL")) {
@@ -54,11 +61,11 @@ void converter_returns_valid_stock_apple() {
}
@Test
- void converter_ignores_invalid_stock_representation() {
+ void getStocksFromStringsIgnoresInvalidStocks() {
boolean stockIncluded = false;
- List stocksFromConverter = converter.getStocksFromStrings(allStocks);
+ List stocksFromConverter = testParser.getStocksFromStrings(allStocks);
for (Stock s : stocksFromConverter) {
if (s.getSymbol().equals("INVALID")) {
@@ -69,4 +76,30 @@ void converter_ignores_invalid_stock_representation() {
Assertions.assertFalse(stockIncluded);
}
+
+ @Test
+ void stocksToStringsConvertsValidStocksToCsvFormat() {
+ Stock apple = new Stock("AAPL", "Apple Inc", new BigDecimal("175.50"));
+ Stock tesla = new Stock("TSLA", "Tesla Inc", new BigDecimal("200.00"));
+ List stocks = List.of(apple, tesla);
+
+ List result = testParser.stocksToStrings(stocks);
+
+ assertEquals(2, result.size());
+ assertEquals("AAPL, Apple Inc, 175.50", result.get(0));
+ assertEquals("TSLA, Tesla Inc, 200.00", result.get(1));
+ }
+
+ @Test
+ void stocksToStringsThrowsExceptionOnNullOrEmptyList() {
+ List emptyList = new ArrayList<>();
+
+ assertThrows(IllegalArgumentException.class,
+ () -> testParser.stocksToStrings(null)
+ );
+
+ assertThrows(IllegalArgumentException.class,
+ () -> testParser.stocksToStrings(emptyList)
+ );
+ }
}
\ No newline at end of file
From 9310cae6edd0fc87fe8d701b1a0111539f17c5f8 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 21:19:29 +0200
Subject: [PATCH 30/31] Feat: Removed seetter method for viewname (unused)
---
.../ntnu/idi/idatt2003/g40/mappe/view/ViewElement.java | 10 ----------
.../ntnu/idi/idatt2003/g40/mappe/view/ViewManager.java | 2 --
2 files changed, 12 deletions(-)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElement.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElement.java
index d0c2ef0..77e7d8b 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElement.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewElement.java
@@ -88,16 +88,6 @@ public ViewEnum getViewName() {
return viewName;
}
- /**
- * Setter method for the view name.
- *
- * @param name the new name to set this view element to.
- *
- */
- protected void setViewName(final ViewEnum name) {
- viewName = name;
- }
-
/**
* Getter method for the root pane.
*
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManager.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManager.java
index f6de33d..45da26b 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManager.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/ViewManager.java
@@ -6,8 +6,6 @@
import edu.ntnu.idi.idatt2003.g40.mappe.service.event.EventType;
import java.util.EnumMap;
import java.util.Map;
-
-import edu.ntnu.idi.idatt2003.g40.mappe.utils.Validator;
import javafx.scene.Scene;
import javafx.stage.Stage;
From d6d29ed873112b1ce89de79c6faa698be0dbfacc Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 25 May 2026 21:39:37 +0200
Subject: [PATCH 31/31] Fix: Renamed method in Portfolio
---
.../edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java | 2 +-
.../mappe/view/widgets/dashboard/DashBoardController.java | 2 +-
.../ntnu/idi/idatt2003/g40/mappe/model/PortfolioTest.java | 6 +++---
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java
index 40a3afd..415a065 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/model/Portfolio.java
@@ -178,7 +178,7 @@ public BigDecimal getNetWorth() {
* @return BigDecimal representing total quantity of all
* shares of this symbol.
* */
- public BigDecimal getTotalSharesBySymbol(final String symbol) {
+ public BigDecimal getTotalShareQuantityBySymbol(final String symbol) {
if (symbol == null) {
return BigDecimal.ZERO;
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java
index fa1333c..8bc66c5 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/view/widgets/dashboard/DashBoardController.java
@@ -116,7 +116,7 @@ private void populateStockList(final String filter) {
getViewElement().setOnStockAction(stockBtn, s, (Stock stock) -> {
BigDecimal amountOfSharesOwned = player.getPortfolio()
- .getTotalSharesBySymbol(s.getSymbol());
+ .getTotalShareQuantityBySymbol(s.getSymbol());
handleStockSelection(stock, amountOfSharesOwned.floatValue());
});
}
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PortfolioTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PortfolioTest.java
index 781e06f..fde5639 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PortfolioTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/model/PortfolioTest.java
@@ -131,13 +131,13 @@ void containsThrowsExceptionOnIllegalArgument() {
void getTotalSharesBySymbolReturnsCorrectValues() {
assertEquals(0,
BigDecimal.ZERO.compareTo(
- testPortfolio.getTotalSharesBySymbol("AAPL")
+ testPortfolio.getTotalShareQuantityBySymbol("AAPL")
)
);
assertEquals(0,
BigDecimal.ZERO.compareTo(
- testPortfolio.getTotalSharesBySymbol(null)
+ testPortfolio.getTotalShareQuantityBySymbol(null)
)
);
@@ -145,7 +145,7 @@ void getTotalSharesBySymbolReturnsCorrectValues() {
assertEquals(0,
new BigDecimal("2.0").compareTo(
- testPortfolio.getTotalSharesBySymbol("AAPL")
+ testPortfolio.getTotalShareQuantityBySymbol("AAPL")
)
);
}