Skip to content

113 refactoring to reduce coupling #128

Merged
merged 32 commits into from
May 25, 2026
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
adaec06
Fix: Validator for stock symbol rename
tommyah May 25, 2026
19611bd
Feat: Basic improvements, testing.
tommyah May 25, 2026
3488978
Merge remote-tracking branch 'origin' into 113-refactoring-to-reduce-…
tommyah May 25, 2026
3ee9296
Feat: Refactored Exchange, testing
tommyah May 25, 2026
dccb4f8
Feat: Refactored transactionarchive + testing
tommyah May 25, 2026
e75440c
Feat: EventManager refactor + test
tommyah May 25, 2026
7b3de60
Feat: Changed how portfolio manages shares
tommyah May 25, 2026
f7aafc6
Feat: Updated various classes.
tommyah May 25, 2026
de95bc3
Fix: Updated version number for some classes
tommyah May 25, 2026
290fdab
Feat: Validation for savegame
tommyah May 25, 2026
706db1d
Feat: Updated validation for share
tommyah May 25, 2026
1c015f8
Feat: Updated validation for Stock
tommyah May 25, 2026
ada261d
Feat: Updated validation for Transaction
tommyah May 25, 2026
5e9017c
Feat: Updated Validator Enum
tommyah May 25, 2026
e2a84b6
Feat: removed OnUpdate for CreateGameView
tommyah May 25, 2026
a0fa328
Update ViewElement.java
tommyah May 25, 2026
44ff6e3
Update ViewManager.java
tommyah May 25, 2026
d34b9a2
Update PurchaseTest.java
tommyah May 25, 2026
76f784d
Update SaleTest.java
tommyah May 25, 2026
e673691
Create SaveGameTest.java
tommyah May 25, 2026
e6a3914
Update ShareTest.java
tommyah May 25, 2026
98bdd9f
Update StockTest.java
tommyah May 25, 2026
c174d6e
Update ViewControllerTest.java
tommyah May 25, 2026
757b17b
Create ViewElementTest.java
tommyah May 25, 2026
006979a
Update ViewManagerTest.java
tommyah May 25, 2026
5fe81c2
Feat: Renamed File Parser to File Manager, and File Converter to File…
tommyah May 25, 2026
9dca303
Feat: Renamed FileParser and FileManager to StockFileParser and Stock…
tommyah May 25, 2026
329c2f1
Feat: Updated File Manager to have a fallback resource
tommyah May 25, 2026
0bc91d8
Fix: Fixed bug where test would create stock file
tommyah May 25, 2026
93969e0
Feat: Refactor FileManager and FileParser, unit testing
tommyah May 25, 2026
9310cae
Feat: Removed seetter method for viewname (unused)
tommyah May 25, 2026
d6d29ed
Fix: Renamed method in Portfolio
tommyah May 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.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;
Expand Down Expand Up @@ -86,9 +86,9 @@ public void start(final Stage stage) throws Exception {
ViewManager viewManager = new ViewManager(stage, eventManager);

List<Stock> stocksInFile;
FileParser parser1 = new FileParser("/sp500.csv");
StockFileManager parser1 = new StockFileManager("src/main/resources/sp500.csv");

FileConverter converter1 = new FileConverter();
StockFileParser converter1 = new StockFileParser();
stocksInFile = converter1.getStocksFromStrings(parser1.readFile());

Exchange exchange = new Exchange("Exchange", stocksInFile);
Expand Down
190 changes: 108 additions & 82 deletions src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/engine/Exchange.java
Original file line number Diff line number Diff line change
@@ -1,38 +1,35 @@
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.service.TransactionFactory;
import edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionType;
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.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;

/**
* Represents a stock exchange where stocks can be traded.
*
* <p>Holds a map of stocks where stock symbol is key and stock is value.</p>
* <p>Holds a map of {@link Stock} objects where stock symbol is key and
* stock is value.</p>
*
* <p>Delegates buying and selling to player elements using calculators</p>
*
* <p>Advances week.</p>
*
* @see Player
* @see TransactionCalculator
* @see edu.ntnu.idi.idatt2003.g40.mappe.service.TransactionCalculator
*
* @version 1.1.0
* */
public final class Exchange {

Expand All @@ -44,7 +41,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.
Expand All @@ -59,13 +56,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<Stock> stocks) throws IllegalArgumentException {
if (!Validator.NOT_EMPTY.isValid(name) || stocks == null || stocks.isEmpty()) {
public Exchange(final String name, final List<Stock> stocks)
throws IllegalArgumentException {
if (!Validator.NOT_EMPTY.isValid(name)
|| stocks == null
|| stocks.isEmpty()) {
throw new IllegalArgumentException("Invalid exchange parameters!");
}
this.name = name;
Expand Down Expand Up @@ -95,20 +95,20 @@ 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();
}

/**
* Method for checking whether exchange has a stock.
*
* @param symbol the stock symbol.
*
* @return true or false.
* @return true or false.
* */
public boolean hasStock(final String symbol) {
return stockMap.containsKey(symbol);
Expand All @@ -117,16 +117,18 @@ 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.
* @throws IllegalArgumentException if symbol is invalid or not in exchange.
* */
public Stock getStock(final String symbol) throws IllegalArgumentException {
if (!Validator.VALID_STOCK_NAME.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_NAME.getErrorMessage());
Validator.VALID_STOCK_SYMBOL.getErrorMessage());
}
return stockMap.get(symbol);
}
Expand All @@ -136,7 +138,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<Stock> findStocks(final String searchTerm) {
List<Stock> result = new ArrayList<>();
Expand All @@ -154,35 +156,38 @@ public List<Stock> 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)
|| 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);
Purchase purchase = new Purchase(share, week.get(), calculator);
Transaction purchase = TransactionFactory.createTransaction(
TransactionType.PURCHASE, share, getWeek()
);
player.handleTransaction(purchase);
return purchase;
}

/**
* 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.
* */
Expand All @@ -191,75 +196,84 @@ public Transaction sell(final Share share, final Player player)
if (share == null || player == null) {
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()
);
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.
*
* <p>{@link edu.ntnu.idi.idatt2003.g40.mappe.model.Portfolio}</p>
*
* @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,
* or if player does not own any shares
* of the given stock.
* */
public List<Transaction> sell(BigDecimal amount,
public Transaction sell(BigDecimal amount,
final String stockSymbol,
final Player player)
throws IllegalArgumentException {
if (amount == null || player == null || !Validator.NOT_EMPTY.isValid(stockSymbol)) {
throw new IllegalArgumentException("Invalid sell!");
} else {
if (amount == null
|| player == null
|| !Validator.VALID_STOCK_SYMBOL.isValid(stockSymbol)) {
throw new IllegalArgumentException("Invalid sell parameters!");
}

List<Share> sharesOfStock = player.getPortfolio().getShares().stream()
.filter(s -> s.getStock().getSymbol().equals(stockSymbol))
.toList();
List<Share> matchingShares = player.getPortfolio().getShares(stockSymbol);

BigDecimal totalOwned = player.getPortfolio().getTotalSharesBySymbol(stockSymbol);
if (matchingShares.isEmpty()) {
throw new IllegalArgumentException("Player does not own any shares of this stock!");
}

if (amount.compareTo(totalOwned) > 0) {
amount = totalOwned;
}
ArrayList<Transaction> transactions = new ArrayList<>();
BigDecimal remainingToSell = amount;
Share ownedPosition = matchingShares.getFirst();
BigDecimal totalOwned = ownedPosition.getQuantity();

for (Share share : sharesOfStock) {
if (remainingToSell.compareTo(BigDecimal.ZERO) <= 0) {
break;
}
if (amount.compareTo(totalOwned) > 0) {
amount = totalOwned;
}

BigDecimal shareQty = share.getQuantity();
Stock stock = ownedPosition.getStock();
Share shareToSell = new Share(stock, amount, stock.getSalesPrice());

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));
}
}
return transactions;
}
Transaction sale = TransactionFactory.createTransaction(
TransactionType.SALE, shareToSell, getWeek()
);
player.handleTransaction(sale);

return sale;
}

/**
* Method for advancing time, increasing the amount of weeks.
*
* <p>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%.</p>
*
* @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();
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);
Expand All @@ -269,11 +283,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<Stock> getGainers(final int limit) {
public List<Stock> 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 ->
Expand All @@ -294,11 +314,17 @@ public List<Stock> 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<Stock> getLosers(final int limit) {
public List<Stock> 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 ->
Expand Down
Loading