diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/AppController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/AppController.java
index 1d052ad..df8e908 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/AppController.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/AppController.java
@@ -1,5 +1,11 @@
package edu.ntnu.idi.idatt2003.gruppe42.Controller.AppControllers;
+/**
+ * Interface for application controllers that respond to game ticks.
+ */
public interface AppController {
+ /**
+ * Updates the app state on each simulation tick.
+ */
void nextTick();
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/AppStoreController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/AppStoreController.java
index 5f9ffdd..2f83fa6 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/AppStoreController.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/AppStoreController.java
@@ -2,8 +2,16 @@
import edu.ntnu.idi.idatt2003.gruppe42.View.Apps.AppStoreApp;
+/**
+ * Controller for the App Store.
+ */
public class AppStoreController implements AppController {
- public AppStoreController(AppStoreApp appStoreApp) {
+ /**
+ * Constructs a new AppStoreController.
+ *
+ * @param appStoreApp the app store view
+ */
+ public AppStoreController(final AppStoreApp appStoreApp) {
}
@Override
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/BankAppController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/BankAppController.java
index 22b49eb..c012783 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/BankAppController.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/BankAppController.java
@@ -3,11 +3,21 @@
import edu.ntnu.idi.idatt2003.gruppe42.Model.Player;
import edu.ntnu.idi.idatt2003.gruppe42.View.Apps.BankApp;
-public class BankAppController implements AppController{
- private Player player;
- private BankApp bankApp;
+/**
+ * Controller for the {@link BankApp}.
+ * Handles updating the player's financial status and portfolio view.
+ */
+public final class BankAppController implements AppController {
+ private final Player player;
+ private final BankApp bankApp;
- public BankAppController(BankApp bankApp, Player player) {
+ /**
+ * Constructs a new BankAppController.
+ *
+ * @param bankApp the bank app view
+ * @param player the player model
+ */
+ public BankAppController(final BankApp bankApp, final Player player) {
this.player = player;
this.bankApp = bankApp;
}
@@ -18,10 +28,12 @@ public void nextTick() {
player.getNetWorth().doubleValue(),
player.getMoney().doubleValue(),
player.getPortfolio().getNetWorth().doubleValue(),
- 0 // TODO: calculate growth
+ 0
);
- System.out.println("[DEBUG] BankAppController.nextTick() - Updating Player Status");
+ System.out.println("[DEBUG] BankAppController.nextTick() - Updating Status");
- bankApp.getPortfolioList().getItems().setAll(player.getPortfolio().getShares());
+ bankApp.getPortfolioList().getItems().setAll(
+ player.getPortfolio().getShares()
+ );
}
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/StockAppController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/StockAppController.java
index 51008ec..4a252eb 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/StockAppController.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/StockAppController.java
@@ -2,107 +2,184 @@
import edu.ntnu.idi.idatt2003.gruppe42.Controller.MarketController;
import edu.ntnu.idi.idatt2003.gruppe42.Model.Player;
-import edu.ntnu.idi.idatt2003.gruppe42.Model.Share;
import edu.ntnu.idi.idatt2003.gruppe42.Model.Stock;
import edu.ntnu.idi.idatt2003.gruppe42.Model.Transaction.Transaction;
import edu.ntnu.idi.idatt2003.gruppe42.View.Apps.StockApp;
import java.math.BigDecimal;
+import javafx.application.Platform;
+
+/**
+ * Controller for the {@link StockApp}.
+ * Handles user interactions for buying and selling stocks,
+ * and updates the UI in real-time.
+ */
+public final class StockAppController implements AppController {
+ private final MarketController marketController;
+ private final StockApp stockApp;
+ private final Player player;
-public class StockAppController implements AppController {
- private MarketController marketController;
- private StockApp stockPopup;
- private Stock currentStock;
-
- public StockAppController(StockApp stockPopup, MarketController marketController, Player player) {
- this.stockPopup = stockPopup;
+ /**
+ * Constructs a new StockAppController.
+ *
+ * @param stockApp the stock app view
+ * @param marketController the market controller
+ * @param player the player performing transactions
+ */
+ public StockAppController(
+ final StockApp stockApp,
+ final MarketController marketController,
+ final Player player
+ ) {
+ this.stockApp = stockApp;
this.marketController = marketController;
+ this.player = player;
+
+ stockApp.getStockList().getItems().addAll(
+ marketController.getExchange().getAllStocks()
+ );
+
+ stockApp.getSearchField().textProperty().addListener(
+ (obs, old, newValue) -> {
+ if (stockApp.getCurrentStock() != null) {
+ stockApp.openSearchPage();
+ }
+ stockApp.getStockList().getItems().setAll(
+ marketController.getMarket(newValue)
+ );
+ });
+
+ stockApp.getQuantitySpinner().valueProperty().addListener(
+ (obs, old, newValue) -> {
+ System.out.println("[DEBUG] Quantity changed: " + old + " -> " + newValue);
+ if (stockApp.getCurrentStock() != null) {
+ updateReceipt();
+ }
+ });
+
+ stockApp.getConfirmButton().setOnMouseClicked(event -> {
+ System.out.println("[DEBUG] Confirm button clicked");
+ handleTransaction();
+ });
+ }
+ /**
+ * Handles the buy or sell transaction based on the quantity spinner value.
+ */
+ private void handleTransaction() {
+ Stock currentStock = stockApp.getCurrentStock();
+ if (currentStock == null) {
+ System.out.println("[DEBUG] Transaction failed: No stock selected");
+ return;
+ }
- stockPopup.getStockList().getItems().addAll(marketController.getExchange().getAllStocks());
-
- stockPopup.getSearchField().textProperty().addListener((
- obs, old, newValue) -> {
- if (stockPopup.getCurrentStock() != null) {
- stockPopup.openSearchPage();
- }
- stockPopup.getStockList().getItems().setAll(marketController.getMarket(newValue));
- });
+ int quantityValue = stockApp.getQuantitySpinner().getValue();
+ System.out.println("[DEBUG] Quantity spinner value: " + quantityValue);
+ if (quantityValue == 0) {
+ System.out.println("[DEBUG] Transaction skipped: Quantity is 0");
+ return;
+ }
- stockPopup.getBuyButton().setOnMouseClicked(event -> {
- currentStock = stockPopup.getCurrentStock();
- if (currentStock == null) {
- return;
- }
+ BigDecimal quantity = new BigDecimal(Math.abs(quantityValue));
- String quantityText = stockPopup.getQuantityField().getText();
- BigDecimal quantity;
- try {
- quantity = new BigDecimal(quantityText);
- if (quantity.compareTo(BigDecimal.ZERO) <= 0) {
- return;
- }
- } catch (NumberFormatException e) {
- return;
- }
+ if (quantityValue > 0) {
+ handleBuy(currentStock, quantity);
+ } else {
+ handleSell(currentStock, quantity);
+ }
+ }
+ /**
+ * Performs a buy transaction.
+ *
+ * @param stock the stock to buy
+ * @param quantity the amount to buy
+ */
+ private void handleBuy(final Stock stock, final BigDecimal quantity) {
+ System.out.println("[DEBUG] Attempting to buy " + quantity + " of "
+ + stock.getSymbol());
+ try {
Transaction transaction = marketController.getExchange().buy(
- currentStock.getSymbol(), quantity, player
+ stock.getSymbol(), quantity, player
);
- if (transaction == null) {
- return;
+ if (transaction != null) {
+ transaction.commit(player);
+ player.getPortfolio().addShare(transaction.getShare());
+ System.out.println("[DEBUG] Buy successful");
+ } else {
+ System.out.println("[DEBUG] Buy failed: transaction is null");
}
+ } catch (Exception e) {
+ System.out.println("[DEBUG] Buy failed with exception: "
+ + e.getMessage());
+ e.printStackTrace();
+ }
+ }
- player.getTransactionArchive().add(transaction);
- player.getPortfolio().addShare(transaction.getShare());
- });
-
- stockPopup.getSellButton().setOnMouseClicked(event -> {
- currentStock = stockPopup.getCurrentStock();
- if (currentStock == null) {
- return;
- }
-
- String quantityText = stockPopup.getQuantityField().getText();
- BigDecimal quantityToSell;
- try {
- quantityToSell = new BigDecimal(quantityText);
- if (quantityToSell.compareTo(BigDecimal.ZERO) <= 0) {
- return;
- }
- } catch (NumberFormatException e) {
- return;
- }
-
- player.getPortfolio().getShares().stream()
- .filter(s -> s.getStock().getSymbol().equals(currentStock.getSymbol()))
- .findFirst()
- .ifPresent(existingShare -> {
- if (existingShare.getQuantity().compareTo(quantityToSell) < 0) {
- return;
- }
-
- Transaction transaction = marketController.getExchange().sell(existingShare, quantityToSell, player);
- player.getTransactionArchive().add(transaction);
+ /**
+ * Performs a sell transaction.
+ *
+ * @param stock the stock to sell
+ * @param quantity the amount to sell
+ */
+ private void handleSell(final Stock stock, final BigDecimal quantity) {
+ System.out.println("[DEBUG] Attempting to sell " + quantity + " of "
+ + stock.getSymbol());
+ player.getPortfolio().getShares().stream()
+ .filter(s -> s.getStock().getSymbol().equals(stock.getSymbol()))
+ .findFirst()
+ .ifPresentOrElse(existingShare -> {
+ if (existingShare.getQuantity().compareTo(quantity) < 0) {
+ System.out.println("[DEBUG] Sell failed: Not enough shares owned. "
+ + "Have: " + existingShare.getQuantity() + ", Need: "
+ + quantity);
+ return;
+ }
+
+ Transaction transaction = marketController.getExchange().sell(
+ existingShare, quantity, player
+ );
+ if (transaction != null) {
+ transaction.commit(player);
player.getPortfolio().removeShare(transaction.getShare());
- });
- });
+ System.out.println("[DEBUG] Sell successful");
+ } else {
+ System.out.println("[DEBUG] Sell failed: transaction is null");
+ }
+ }, () -> System.out.println("[DEBUG] Sell failed: No shares of "
+ + stock.getSymbol() + " owned"));
+ }
+ /**
+ * Updates the receipt in the stock app.
+ */
+ private void updateReceipt() {
+ Stock currentStock = stockApp.getCurrentStock();
+ if (currentStock != null) {
+ Platform.runLater(() ->
+ stockApp.getReceipt().update(
+ currentStock,
+ new BigDecimal(stockApp.getQuantitySpinner().getValue())
+ )
+ );
+ }
}
/**
* Updates the app state on each simulation tick.
- * Refreshes the market data and updates the price label for the currently viewed stock.
+ * Refreshes the market data and updates UI components.
*/
@Override
public void nextTick() {
- System.out.println("[DEBUG] StockAppController.nextTick() - Updating Market");
+ System.out.println("[DEBUG] StockAppController.nextTick() triggered");
marketController.updateMarket();
- Stock currentStockInView = stockPopup.getCurrentStock();
+ Stock currentStockInView = stockApp.getCurrentStock();
if (currentStockInView != null) {
- System.out.println("[DEBUG] Updating Price Label for: "
- + currentStockInView.getCompany());
- stockPopup.updatePriceLabel(currentStockInView);
+ System.out.println("[DEBUG] Updating UI for stock: "
+ + currentStockInView.getSymbol());
+ stockApp.updatePriceLabel(currentStockInView);
+ updateReceipt();
+ // Graph is updated within marketController.updateMarket() if visible
}
}
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/GameController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/GameController.java
index 7a0614d..66622b2 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/GameController.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/GameController.java
@@ -8,23 +8,44 @@
import java.util.Timer;
import java.util.TimerTask;
-public class GameController {
-
- private Player player;
- private List appControllers = new ArrayList<>();
+/**
+ * Controller for managing the game state and timing.
+ */
+public final class GameController {
+ /** The player model. */
+ private final Player player;
+ /** List of application controllers. */
+ private final List appControllers = new ArrayList<>();
+ /** Timer for game ticks. */
private Timer timer;
- public GameController(Player player) {
+ /**
+ * Constructs a new GameController.
+ *
+ * @param player the player model
+ */
+ public GameController(final Player player) {
this.player = player;
}
- public void addAppController(AppController appController) {
+ /**
+ * Adds an application controller to the list of controllers to be updated.
+ *
+ * @param appController the app controller to add
+ */
+ public void addAppController(final AppController appController) {
appControllers.add(appController);
}
+ /**
+ * Starts the game simulation timer.
+ */
public void startGame() {
timer = new Timer();
+ final int delay = 0;
+ final int period = 1000;
+
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
@@ -34,6 +55,6 @@ public void run() {
}
System.out.println("[DEBUG] Game Tick Finished");
}
- }, 0, 1000);
+ }, delay, period);
}
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/MarketController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/MarketController.java
index 1fe12cf..68b9f9c 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/MarketController.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/MarketController.java
@@ -8,20 +8,25 @@
import java.nio.file.Path;
import java.util.List;
-import java.util.Timer;
-import java.util.TimerTask;
-
-public class MarketController {
+/**
+ * Controller for the stock market.
+ * Manages the exchange and updates stock prices.
+ */
+public final class MarketController {
private Exchange exchange;
- private int stockResolution;
+ private final int stockResolution;
private StockApp stockApp;
+ /**
+ * Constructs a new MarketController and starts the market.
+ */
public MarketController() {
- stockResolution = 50;
+ this.stockResolution = 50;
try {
- Path path = Path.of(getClass().getClassLoader().getResource("stocks.csv").toURI());
+ Path path = Path.of(getClass().getClassLoader()
+ .getResource("stocks.csv").toURI());
exchange = new Exchange(StockFileHandler.readFromFile(path));
startMarket();
@@ -36,28 +41,44 @@ public Exchange getExchange() {
return exchange;
}
- public List getMarket(String searchTerm) {
+ /**
+ * Searches for stocks matching a search term.
+ *
+ * @param searchTerm the term to search for
+ * @return a list of matching stocks
+ */
+ public List getMarket(final String searchTerm) {
return exchange.findStocks(searchTerm);
}
+ /**
+ * Updates the market by updating all stock prices and graphs.
+ */
public void updateMarket() {
- System.out.println("[DEBUG] MarketController.updateMarket() - Updating prices for "
+ System.out.println("[DEBUG] MarketController.updateMarket() "
+ + "- Updating prices for "
+ exchange.getAllStocks().size() + " stocks");
for (Stock stock : exchange.getAllStocks()) {
stock.updatePrice();
- if (stock.getStockGraph().getVisibility()) {
- stock.updateStockGraph();
- }
+ stock.updateStockGraph();
}
}
+ /**
+ * Starts the market by performing initial updates.
+ */
public void startMarket() {
for (int i = 0; i < stockResolution; i++) {
updateMarket();
}
}
- public static void printMarket(List stocks) {
+ /**
+ * Prints the market history to console.
+ *
+ * @param stocks the list of stocks to print
+ */
+ public static void printMarket(final List stocks) {
for (Stock stock : stocks) {
System.out.println(stock.getHistoricalPrices().toString());
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/PopupController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/PopupController.java
index 442ec6f..2685fd9 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/PopupController.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/PopupController.java
@@ -1,46 +1,44 @@
package edu.ntnu.idi.idatt2003.gruppe42.Controller;
import edu.ntnu.idi.idatt2003.gruppe42.Model.App;
-import edu.ntnu.idi.idatt2003.gruppe42.View.Apps.AppStoreApp;
-import edu.ntnu.idi.idatt2003.gruppe42.View.Apps.BankApp;
-import edu.ntnu.idi.idatt2003.gruppe42.View.Apps.HustlersApp;
-import edu.ntnu.idi.idatt2003.gruppe42.View.Apps.MailApp;
-import edu.ntnu.idi.idatt2003.gruppe42.View.Apps.NewsApp;
-import edu.ntnu.idi.idatt2003.gruppe42.View.Apps.StockApp;
import edu.ntnu.idi.idatt2003.gruppe42.View.Popup;
-import javafx.scene.layout.Pane;
-import java.util.ArrayList;
import java.util.List;
+import javafx.scene.layout.Pane;
/**
* Controller for managing popups in a pane.
* Handles adding, removing, and keeping popups within bounds.
*/
-public class PopupController {
-
+public final class PopupController {
+ /** List of managed popups. */
private final List popups;
/**
* Constructs a new popup controller.
+ *
+ * @param popups the list of popups to manage
*/
- public PopupController(List popups) {
+ public PopupController(final List popups) {
this.popups = popups;
}
- //TODO: javadoc
/**
- * Adds a popup to the controller and sets up its events.
+ * Shows a popup of the specified type and brings it to the front.
*
- * @param type of popup to add
- * @return true if added, false otherwise
+ * @param type the type of popup to show
+ * @return true if the request was processed
*/
- public boolean show(App type) {
+ public boolean show(final App type) {
if (type == null) {
return false;
}
- popups.stream().filter(
- popup -> popup.getType().equals(type)).findFirst().ifPresent(
- popup -> {popup.getRoot().setVisible(true); bringToFront(popup);});
+ popups.stream()
+ .filter(popup -> popup.getType().equals(type))
+ .findFirst()
+ .ifPresent(popup -> {
+ popup.getRoot().setVisible(true);
+ bringToFront(popup);
+ });
return true;
}
@@ -49,17 +47,31 @@ public boolean show(App type) {
*
* @param popup the popup to bring to front
*/
- public void bringToFront(Popup popup) {
- if (popup.getRoot().getParent() instanceof Pane parent) {
+ public void bringToFront(final Popup popup) {
+ if (popup.getRoot().getParent() instanceof Pane) {
popup.getRoot().toFront();
}
}
+ /**
+ * Returns the list of managed popups.
+ *
+ * @return the list of popups
+ */
public List getPopups() {
return popups;
}
- public Popup getPopup(App type) {
- return popups.stream().filter(popup -> popup.getType().equals(type)).findFirst().orElse(null);
+ /**
+ * Returns the popup of the specified type.
+ *
+ * @param type the type of popup to retrieve
+ * @return the popup, or null if not found
+ */
+ public Popup getPopup(final App type) {
+ return popups.stream()
+ .filter(popup -> popup.getType().equals(type))
+ .findFirst()
+ .orElse(null);
}
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/DesktopViewController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/DesktopViewController.java
index 20d2b06..6c5278e 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/DesktopViewController.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/DesktopViewController.java
@@ -31,8 +31,10 @@
* Controller for the desktop.
* Handles app button creation, resizing, click events, and drag-and-drop.
*/
-public class DesktopViewController {
+public final class DesktopViewController {
+ /** Controller for popups. */
private final PopupController popupController;
+ /** View for the desktop. */
private final DesktopView desktopView;
/**
@@ -41,21 +43,29 @@ public class DesktopViewController {
* @param player the player model.
* @param gameController the game controller to register app controllers.
*/
- public DesktopViewController(Player player, GameController gameController) {
+ public DesktopViewController(
+ final Player player,
+ final GameController gameController
+ ) {
List popups = new ArrayList<>();
- AppStoreApp appStoreApp = new AppStoreApp(400, 300, 100, 100);
+ final int appWidth = 400;
+ final int appHeight = 300;
+
+ AppStoreApp appStoreApp = new AppStoreApp(appWidth, appHeight, 100, 100);
gameController.addAppController(new AppStoreController(appStoreApp));
- HustlersApp hustlersApp = new HustlersApp(400, 300, 140, 140);
+ HustlersApp hustlersApp = new HustlersApp(appWidth, appHeight, 140, 140);
- StockApp stockApp = new StockApp(400, 300, 120, 120);
+ StockApp stockApp = new StockApp(appWidth, appHeight, 120, 120);
MarketController marketController = new MarketController();
- gameController.addAppController(new StockAppController(stockApp, marketController, player));
+ gameController.addAppController(
+ new StockAppController(stockApp, marketController, player)
+ );
- MailApp mailApp = new MailApp(400, 300, 160, 160);
+ MailApp mailApp = new MailApp(appWidth, appHeight, 160, 160);
- NewsApp newsApp = new NewsApp(400, 300, 180, 180);
+ NewsApp newsApp = new NewsApp(appWidth, appHeight, 180, 180);
BankApp bankApp = new BankApp();
gameController.addAppController(new BankAppController(bankApp, player));
@@ -71,10 +81,16 @@ public DesktopViewController(Player player, GameController gameController) {
this.desktopView = new DesktopView(this);
this.desktopView.getRoot().getChildren().addAll(
- popupController.getPopups().stream().map(Popup::getRoot).toArray(Pane[]::new)
+ popupController.getPopups().stream()
+ .map(Popup::getRoot).toArray(Pane[]::new)
);
}
+ /**
+ * Returns the desktop view.
+ *
+ * @return the desktop view
+ */
public DesktopView getDesktopView() {
return desktopView;
}
@@ -85,17 +101,19 @@ public DesktopView getDesktopView() {
* @param type the type of the app.
* @return the app button.
*/
- public Button createAppButton(App type) {
+ public Button createAppButton(final App type) {
Button button = new Button(type.toString());
button.setMinSize(0, 0);
+ final double sizeMultiplier = 0.8;
+
// Bind button size to parent dimensions to keep it square and responsive
button.parentProperty().addListener((observable, oldParent, newParent) -> {
if (newParent instanceof Region region) {
button.prefWidthProperty().bind(
Bindings.min(
- region.widthProperty().multiply(0.8),
- region.heightProperty()).multiply(0.8));
+ region.widthProperty().multiply(sizeMultiplier),
+ region.heightProperty()).multiply(sizeMultiplier));
button.prefHeightProperty().bind(button.prefWidthProperty());
} else {
button.prefWidthProperty().unbind();
@@ -129,7 +147,7 @@ public Button createAppButton(App type) {
*
* @param type the app type.
*/
- private void openPopup(App type) {
+ private void openPopup(final App type) {
popupController.show(type);
}
@@ -138,10 +156,11 @@ private void openPopup(App type) {
*
* @param cell the stack pane cell to configure.
*/
- public void configureCellAsDropTarget(StackPane cell) {
+ public void configureCellAsDropTarget(final StackPane cell) {
// Handle drag over cell: allow drop if cell is empty
cell.setOnDragOver(event -> {
- if (event.getGestureSource() instanceof Button && cell.getChildren().isEmpty()) {
+ if (event.getGestureSource() instanceof Button
+ && cell.getChildren().isEmpty()) {
event.acceptTransferModes(TransferMode.MOVE);
}
event.consume();
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/App.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/App.java
index e527816..276b3fc 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/App.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/App.java
@@ -1,10 +1,19 @@
package edu.ntnu.idi.idatt2003.gruppe42.Model;
+/**
+ * Enumeration of available applications in the simulation.
+ */
public enum App {
+ /** The App Store for buying new apps. */
APPSTORE,
+ /** Hustlers app for side jobs. */
HUSTLERS,
+ /** Stock market trading app. */
STOCK,
+ /** Mail application. */
MAIL,
+ /** News application for market insights. */
NEWS,
+ /** Bank application for financial overview. */
BANK
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/PurchaseCalculator.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/PurchaseCalculator.java
index 53cd906..cfc8c11 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/PurchaseCalculator.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/PurchaseCalculator.java
@@ -3,11 +3,21 @@
import edu.ntnu.idi.idatt2003.gruppe42.Model.Share;
import java.math.BigDecimal;
-public class PurchaseCalculator implements TransactionCalculator {
- private BigDecimal purchasePrice;
- private BigDecimal quantity;
+/**
+ * Calculator for stock purchase transactions.
+ */
+public final class PurchaseCalculator implements TransactionCalculator {
+ /** The price per share at purchase. */
+ private final BigDecimal purchasePrice;
+ /** The number of shares bought. */
+ private final BigDecimal quantity;
- public PurchaseCalculator(Share share) {
+ /**
+ * Constructs a new PurchaseCalculator.
+ *
+ * @param share the share position being bought
+ */
+ public PurchaseCalculator(final Share share) {
this.purchasePrice = share.getPurchasePrice();
this.quantity = share.getQuantity();
}
@@ -19,7 +29,7 @@ public BigDecimal calculateGross() {
@Override
public BigDecimal calculateCommission() {
- BigDecimal commissionRate = new BigDecimal("0.01");
+ final BigDecimal commissionRate = new BigDecimal("0.01");
return calculateGross().multiply(commissionRate);
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/SaleCalculator.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/SaleCalculator.java
index a334bac..69ac0b5 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/SaleCalculator.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/SaleCalculator.java
@@ -3,65 +3,50 @@
import edu.ntnu.idi.idatt2003.gruppe42.Model.Share;
import java.math.BigDecimal;
-public class SaleCalculator implements TransactionCalculator {
- private BigDecimal purchasePrice;
- private BigDecimal salesPrice;
- private BigDecimal quantity;
+/**
+ * Calculator for stock sale transactions.
+ */
+public final class SaleCalculator implements TransactionCalculator {
+ /** The price per share at purchase. */
+ private final BigDecimal purchasePrice;
+ /** The current price per share at sale. */
+ private final BigDecimal salesPrice;
+ /** The number of shares sold. */
+ private final BigDecimal quantity;
- public SaleCalculator(Share share) {
+ /**
+ * Constructs a new SaleCalculator.
+ *
+ * @param share the share position being sold
+ */
+ public SaleCalculator(final Share share) {
this.purchasePrice = share.getPurchasePrice();
this.salesPrice = share.getStock().getSalesPrice();
this.quantity = share.getQuantity();
}
- /**
- * Calculates the gross value of the sale (quantity * sales price).
- *
- * @return the gross sale value
- */
@Override
public BigDecimal calculateGross() {
return salesPrice.multiply(quantity);
}
- /**
- * Calculates the commission for the sale (1% of gross).
- *
- * @return the commission amount
- */
@Override
public BigDecimal calculateCommission() {
- BigDecimal commissionRate = new BigDecimal("0.01");
+ final BigDecimal commissionRate = new BigDecimal("0.01");
return calculateGross().multiply(commissionRate);
}
- /**
- * Calculates the tax on the capital gain (22% of profit).
- * Profit is defined as (sales price - purchase price) * quantity.
- * If there is no profit, tax is zero (clamped by max(ZERO) in a robust implementation,
- * but here we follow existing logic).
- *
- * @return the tax amount
- */
@Override
public BigDecimal calculateTax() {
- BigDecimal taxRate = new BigDecimal("0.22");
- BigDecimal priceDifference = salesPrice.subtract(purchasePrice);
- BigDecimal totalProfit = priceDifference.multiply(quantity);
- // Only tax if there is a profit
- if (totalProfit.compareTo(BigDecimal.ZERO) <= 0) {
- return BigDecimal.ZERO;
- }
- return totalProfit.multiply(taxRate);
+ final BigDecimal taxRate = new BigDecimal("0.22");
+ BigDecimal differencePrice = salesPrice.subtract(purchasePrice);
+ BigDecimal difference = differencePrice.multiply(quantity);
+ return difference.multiply(taxRate);
}
- /**
- * Calculates the net total received from the sale (gross - commission - tax).
- *
- * @return the net sale amount
- */
@Override
public BigDecimal calculateTotal() {
- return calculateGross().subtract(calculateCommission()).subtract(calculateTax());
+ return calculateGross().subtract(calculateCommission())
+ .subtract(calculateTax());
}
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/TransactionCalculator.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/TransactionCalculator.java
index 361b224..cf606fe 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/TransactionCalculator.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/TransactionCalculator.java
@@ -2,13 +2,35 @@
import java.math.BigDecimal;
+/**
+ * Interface for calculating transaction costs and totals.
+ */
public interface TransactionCalculator {
-
+ /**
+ * Calculates the gross amount of the transaction.
+ *
+ * @return the gross amount
+ */
BigDecimal calculateGross();
+ /**
+ * Calculates the commission fee for the transaction.
+ *
+ * @return the commission amount
+ */
BigDecimal calculateCommission();
+ /**
+ * Calculates the tax amount for the transaction.
+ *
+ * @return the tax amount
+ */
BigDecimal calculateTax();
+ /**
+ * Calculates the total net amount of the transaction.
+ *
+ * @return the total amount
+ */
BigDecimal calculateTotal();
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Difficulty.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Difficulty.java
index f4a5b5f..9dfcffe 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Difficulty.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Difficulty.java
@@ -1,12 +1,26 @@
package edu.ntnu.idi.idatt2003.gruppe42.Model;
+/**
+ * Represents the game difficulty levels.
+ */
public enum Difficulty {
+ /** Easy difficulty: starting with more money. */
EASY,
+ /** Medium difficulty: starting with moderate money. */
MEDIUM,
+ /** Hard difficulty: starting with no money. */
HARD;
- public static Difficulty fromString(String value) {
- if (value == null || value.isEmpty()) return EASY;
+ /**
+ * Converts a string value to a Difficulty level.
+ *
+ * @param value the string representation
+ * @return the corresponding Difficulty, or EASY if invalid/null
+ */
+ public static Difficulty fromString(final String value) {
+ if (value == null || value.isEmpty()) {
+ return EASY;
+ }
return switch (value.charAt(0)) {
case 'E' -> EASY;
case 'M' -> MEDIUM;
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Exceptions/InsufficientFunds.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Exceptions/InsufficientFunds.java
index 8a2f1be..55f990f 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Exceptions/InsufficientFunds.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Exceptions/InsufficientFunds.java
@@ -1,7 +1,15 @@
package edu.ntnu.idi.idatt2003.gruppe42.Model.Exceptions;
+/**
+ * Exception thrown when a player has insufficient funds for a transaction.
+ */
public class InsufficientFunds extends RuntimeException {
- public InsufficientFunds(String message) {
+ /**
+ * Constructs a new InsufficientFunds exception with a message.
+ *
+ * @param message the detail message
+ */
+ public InsufficientFunds(final String message) {
super(message);
}
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Exchange.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Exchange.java
index 5be159e..f1706a1 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Exchange.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Exchange.java
@@ -19,16 +19,19 @@
*
*/
public class Exchange {
+ /** The current week in the simulation. */
private int week;
- private Map stockMap;
- private Random random;
+ /** Map of stock symbols to Stock objects. */
+ private final Map stockMap;
+ /** Random number generator for simulation events. */
+ private final Random random;
/**
- * Constructs a new {@code Exchange} with a name and a list of stocks.
+ * Constructs a new {@code Exchange} with a list of stocks.
*
- * @param stocks the initial list of available stocks on the exchange
+ * @param stocks the initial list of available stocks
*/
- public Exchange(List stocks) {
+ public Exchange(final List stocks) {
this.week = 0;
this.stockMap = new HashMap();
this.random = new Random();
@@ -37,6 +40,11 @@ public Exchange(List stocks) {
}
}
+ /**
+ * Returns the current week in the simulation.
+ *
+ * @return the week
+ */
public int getWeek() {
return week;
}
@@ -45,9 +53,9 @@ public int getWeek() {
* Checks if the exchange has a stock with the given symbol.
*
* @param symbol the stock symbol to check
- * @return {@code true} if the stock exists on the exchange, {@code false} otherwise
+ * @return {@code true} if found
*/
- public boolean hasStock(String symbol) {
+ public boolean hasStock(final String symbol) {
return stockMap.containsKey(symbol);
}
@@ -57,80 +65,83 @@ public boolean hasStock(String symbol) {
* @param symbol the symbol of the stock to retrieve
* @return the {@code Stock} object, or {@code null} if not found
*/
- public Stock getStock(String symbol) {
+ public Stock getStock(final String symbol) {
return stockMap.get(symbol);
}
/**
- * Finds stocks by company name or symbol.
+ * Finds stocks by company name.
*
- * @param searchTerm the term to search for (case-insensitive)
- * @return a list of {@code Stock} objects matching the search term, sorted alphabetically
+ * @param searchTerm the company name to search for
+ * @return a list of matching stocks
*/
- public List findStocks(String searchTerm) {
- if (searchTerm == null || searchTerm.isBlank()) {
- return getAllStocks();
- }
-
+ public List findStocks(final String searchTerm) {
+ List foundStocks = new ArrayList<>();
String lowerSearchTerm = searchTerm.toLowerCase();
- return stockMap.values().stream()
- .filter(stock -> stock.getCompany().toLowerCase().contains(lowerSearchTerm)
- || stock.getSymbol().toLowerCase().contains(lowerSearchTerm))
- .sorted((a, b) -> a.getCompany().compareToIgnoreCase(b.getCompany()))
- .toList();
+
+ for (Stock stock : stockMap.values()) {
+ if (stock.getCompany().toLowerCase().contains(lowerSearchTerm)
+ || stock.getSymbol().toLowerCase().contains(lowerSearchTerm)) {
+ foundStocks.add(stock);
+ }
+ }
+ return foundStocks;
}
/**
- * Returns all available stocks on the exchange, sorted alphabetically by company name.
+ * Returns all available stocks.
*
- * @return a sorted list of all {@code Stock} objects
+ * @return a list of all stocks
*/
public List getAllStocks() {
- return stockMap.values().stream()
- .sorted((a, b) -> a.getCompany().compareToIgnoreCase(b.getCompany()))
- .toList();
+ return new ArrayList<>(stockMap.values());
}
/**
* Allows a player to buy a stock from the exchange.
- *
- * The player’s money is withdrawn by the quantity amount, and a {@code Purchase} transaction is returned.
- *
*
* @param symbol the symbol of the stock to buy
* @param quantity the amount of stock to buy
* @param player the player performing the purchase
- * @return a {@code Purchase} transaction representing the purchase
+ * @return a {@code Purchase} transaction
*/
- public Transaction buy(String symbol, BigDecimal quantity, Player player) {
- System.out.println("[DEBUG] Exchange" + " Buying " + quantity + " of " + symbol + " for player " + player.getName());
+ public Transaction buy(
+ final String symbol,
+ final BigDecimal quantity,
+ final Player player
+ ) {
+ System.out.println("[DEBUG] Exchange" + " Buying " + quantity + " of "
+ + symbol + " for player " + player.getName());
Stock stock = getStock(symbol);
- BigDecimal purchasePrice = stock.getSalesPrice();
- player.withdrawMoney(quantity.multiply(purchasePrice));
+ BigDecimal purchasePrice = stock.getSalesPrice();
Share share = new Share(stock, quantity, purchasePrice);
- System.out.println("[DEBUG] Purchase complete: " + quantity + " shares of " + symbol + " at " + purchasePrice);
+ System.out.println("[DEBUG] Purchase complete: " + quantity
+ + " shares of " + symbol + " at " + purchasePrice);
return new Purchase(share, week);
}
/**
- * Allows a player to sell a portion of a share position on the exchange.
+ * Allows a player to sell a share on the exchange.
*
- * @param share the share position from which to sell
- * @param quantity the amount of stock to sell
+ * @param share the share position to sell from
+ * @param quantity the amount to sell
* @param player the player performing the sale
- * @return a sale transaction representing the sale
+ * @return a {@code Sale} transaction
*/
- public Transaction sell(Share share, BigDecimal quantity, Player player) {
- System.out.println("[DEBUG] Exchange - Selling " + quantity + " shares of "
- + share.getStock().getSymbol() + " for player " + player.getName());
-
- Share shareToSell = new Share(share.getStock(), quantity, share.getPurchasePrice());
- SaleCalculator saleCalculator = new SaleCalculator(shareToSell);
- BigDecimal totalValue = saleCalculator.calculateTotal();
- player.addMoney(totalValue);
-
- System.out.println("[DEBUG] Sale complete: Received " + totalValue);
+ public Transaction sell(
+ final Share share,
+ final BigDecimal quantity,
+ final Player player
+ ) {
+ System.out.println("[DEBUG] Exchange " + " - Selling " + quantity
+ + " shares of " + share.getStock().getSymbol()
+ + " for player " + player.getName());
+ Share shareToSell = new Share(
+ share.getStock(), quantity, share.getPurchasePrice()
+ );
+ System.out.println("[DEBUG] Sale complete: Prepared " + quantity
+ + " shares for transaction");
return new Sale(shareToSell, week);
}
@@ -144,28 +155,28 @@ public void advance() {
/**
* Returns the top stocks by price change given a limit.
*
- * @param limit the number of stocks to return
- * @return a list of stocks sorted by price change
+ * @param limit the maximum number of stocks to return
+ * @return a list of gainer stocks
*/
- public List getGainers(int limit) {
+ public List getGainers(final int limit) {
return stockMap.values().stream()
- .sorted((a, b) -> b.getLatestPriceChange()
+ .sorted((a, b) -> b.getLatestPriceChange()
.compareTo(a.getLatestPriceChange()))
- .limit(limit)
- .toList();
+ .limit(limit)
+ .toList();
}
/**
* Returns the bottom stocks by price change given a limit.
*
- * @param limit the number of stocks to return
- * @return a list of stocks sorted by price change
+ * @param limit the maximum number of stocks to return
+ * @return a list of loser stocks
*/
- public List getLosers(int limit) {
+ public List getLosers(final int limit) {
return stockMap.values().stream()
- .sorted((a, b) -> a.getLatestPriceChange()
+ .sorted((a, b) -> a.getLatestPriceChange()
.compareTo(b.getLatestPriceChange()))
- .limit(limit)
- .toList();
+ .limit(limit)
+ .toList();
}
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/GameFactory.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/GameFactory.java
index 7978f42..2221fa9 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/GameFactory.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/GameFactory.java
@@ -2,13 +2,35 @@
import java.math.BigDecimal;
-public class GameFactory {
+/**
+ * Factory class for creating game objects.
+ */
+public final class GameFactory {
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private GameFactory() {
+ }
+
+ /**
+ * Creates a new player with starting money based on difficulty.
+ *
+ * @param username the name of the player
+ * @param difficulty the game difficulty
+ * @return a new Player instance
+ */
+ public static Player createPlayer(
+ final String username,
+ final Difficulty difficulty
+ ) {
+ final BigDecimal easyMoney = new BigDecimal("1000");
+ final BigDecimal mediumMoney = new BigDecimal("100");
+ final BigDecimal hardMoney = new BigDecimal("0");
- public static Player createPlayer(String username, Difficulty difficulty) {
BigDecimal money = switch (difficulty) {
- case EASY -> new BigDecimal("1000");
- case MEDIUM -> new BigDecimal("100");
- case HARD -> new BigDecimal("0");
+ case EASY -> easyMoney;
+ case MEDIUM -> mediumMoney;
+ case HARD -> hardMoney;
};
return new Player(username, money);
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Player.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Player.java
index 3dade14..f4c14c8 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Player.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Player.java
@@ -16,8 +16,8 @@ public class Player {
private final String name;
private final BigDecimal startingMoney;
private BigDecimal money;
- private Portfolio portfolio;
- private TransactionArchive transactionArchive;
+ private final Portfolio portfolio;
+ private final TransactionArchive transactionArchive;
/**
* Constructs a new {@code Player} with a name and starting money.
@@ -25,7 +25,7 @@ public class Player {
* @param name the name of the player
* @param startingMoney the initial amount of money the player has
*/
- public Player(String name, BigDecimal startingMoney) {
+ public Player(final String name, final BigDecimal startingMoney) {
this.name = name;
this.startingMoney = startingMoney;
this.money = startingMoney;
@@ -52,31 +52,36 @@ public BigDecimal getMoney() {
/**
* Adds the specified amount of money to the player's balance.
*
- * @param amount the amount to add to the player's account
+ * @param amount the amount to add
*/
- public void addMoney(BigDecimal amount) {
+ public void addMoney(final BigDecimal amount) {
BigDecimal oldMoney = money;
money = money.add(amount);
- System.out.println("[DEBUG] Player " + name + " money added: " + oldMoney + " -> " + money + " (+" + amount + ")");
+ System.out.println("[DEBUG] Player " + name + " money added: "
+ + oldMoney + " -> " + money + " (+" + amount + ")");
}
/**
* Withdraws the specified amount of money from the player's balance.
*
- * @param amount the amount to withdraw from the player's account
+ * @param amount the amount to withdraw
+ * @throws InsufficientFunds if the player does not have enough money
*/
- public void withdrawMoney(BigDecimal amount) {
+ public void withdrawMoney(final BigDecimal amount) {
if (money.compareTo(amount) < 0) {
- System.out.println("[DEBUG] Player " + name + " withdrawal FAILED: Insufficient funds (Needs: " + amount + ", Has: " + money + ")");
+ System.out.println("[DEBUG] Player " + name + " withdrawal FAILED: "
+ + "Insufficient funds (Needs: " + amount + ", Has: " + money + ")");
throw new InsufficientFunds("Not enough money to withdraw " + amount);
}
BigDecimal oldMoney = money;
money = money.subtract(amount);
- System.out.println("[DEBUG] Player " + name + " money withdrawn: " + oldMoney + " -> " + money + " (-" + amount + ")");
+ System.out.println("[DEBUG] Player " + name + " money withdrawn: "
+ + oldMoney + " -> " + money + " (-" + amount + ")");
}
/**
* Calculates the total net worth of a player (portfolio + money).
+ *
* @return the player's total net worth
*/
public BigDecimal getNetWorth() {
@@ -84,19 +89,28 @@ public BigDecimal getNetWorth() {
}
/**
- * Returns the current status of the player depending on qualifications
- * including distinct active weeks and networth (NOVICE / INVESTOR / SPECULATOR).
- * @return the current status of the player
+ * Returns the current status of the player.
+ *
+ * @return the current status (NOVICE / INVESTOR / SPECULATOR)
*/
public Status getStatus() {
+ final int investorWeeks = 10;
+ final int speculatorWeeks = 20;
+ final double investorMultiplier = 1.2;
+ final double speculatorMultiplier = 2.0;
+
int weeksActive = transactionArchive.countDistinctWeeks();
BigDecimal netWorth = getNetWorth();
- BigDecimal investorTarget = startingMoney.multiply(BigDecimal.valueOf(1.2));
- BigDecimal speculatorTarget = startingMoney.multiply(BigDecimal.valueOf(2));
+ BigDecimal investorTarget = startingMoney
+ .multiply(BigDecimal.valueOf(investorMultiplier));
+ BigDecimal speculatorTarget = startingMoney
+ .multiply(BigDecimal.valueOf(speculatorMultiplier));
- boolean isInvestor = weeksActive >= 10 && netWorth.compareTo(investorTarget) >= 0;
- boolean isSpeculator = weeksActive >= 20 && netWorth.compareTo(speculatorTarget) >= 0;
+ boolean isInvestor = weeksActive >= investorWeeks
+ && netWorth.compareTo(investorTarget) >= 0;
+ boolean isSpeculator = weeksActive >= speculatorWeeks
+ && netWorth.compareTo(speculatorTarget) >= 0;
if (isSpeculator) {
return Status.SPECULATOR;
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Portfolio.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Portfolio.java
index 95dd309..caca240 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Portfolio.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Portfolio.java
@@ -16,7 +16,7 @@
*
*/
public class Portfolio {
- private List shares;
+ private final List shares;
/**
* Constructs an empty {@code Portfolio}.
@@ -26,81 +26,90 @@ public Portfolio() {
}
/**
- * Adds a share position to the portfolio.
- * If a position already exists for the stock, the new quantity is added,
- * and the purchase price is recalculated as a weighted average.
+ * Adds a share to the portfolio.
*
- * @param shareToAdd the {@code Share} position to add
- * @return {@code true} if the share was successfully added or merged, {@code false} if it was {@code null}
+ * @param share the {@code Share} to add
+ * @return {@code true} if the share was successfully added
*/
- public boolean addShare(Share shareToAdd) {
- if (shareToAdd == null) {
+ public boolean addShare(final Share share) {
+ if (share == null) {
return false;
}
+ final int scale = 10;
+
// Check if we already own a share with the same stock symbol
- for (Share existingShare : shares) {
- if (existingShare.getStock().getSymbol().equals(shareToAdd.getStock().getSymbol())) {
+ for (Share existing : shares) {
+ if (existing.getStock().getSymbol()
+ .equals(share.getStock().getSymbol())) {
// Weighted average purchase price: (q1*p1 + q2*p2) / (q1+q2)
- BigDecimal totalQuantity = existingShare.getQuantity().add(shareToAdd.getQuantity());
+ BigDecimal totalQuantity = existing.getQuantity()
+ .add(share.getQuantity());
- BigDecimal weightedPrice = existingShare.getQuantity()
- .multiply(existingShare.getPurchasePrice())
- .add(shareToAdd.getQuantity().multiply(shareToAdd.getPurchasePrice()))
- .divide(totalQuantity, 10, RoundingMode.HALF_UP);
+ BigDecimal weightedPrice = existing.getQuantity()
+ .multiply(existing.getPurchasePrice())
+ .add(share.getQuantity().multiply(share.getPurchasePrice()))
+ .divide(totalQuantity, scale, RoundingMode.HALF_UP);
// Replace the existing share with the merged one
- shares.remove(existingShare);
- return shares.add(new Share(existingShare.getStock(), totalQuantity, weightedPrice));
+ shares.remove(existing);
+ return shares.add(
+ new Share(existing.getStock(), totalQuantity, weightedPrice)
+ );
}
}
// No matching symbol found — add as new position
- return shares.add(shareToAdd);
+ return shares.add(share);
}
/**
- * Removes a portion of a share position from the portfolio.
+ * Removes a share from the portfolio.
*
- * @param shareToRemove the {@code Share} object containing the stock and quantity to remove
- * @return {@code true} if the share was successfully removed or updated,
- * {@code false} if the input was {@code null} or the share was not found,
- * or if the quantity to remove exceeds the owned quantity.
+ * @param newShare the {@code Share} to remove
+ * @return {@code true} if the share was successfully removed
*/
- public boolean removeShare(Share shareToRemove) {
- if (shareToRemove == null) {
+ public boolean removeShare(final Share newShare) {
+ if (newShare == null) {
return false;
}
Optional match = shares.stream()
- .filter(s -> s.getStock().getSymbol().equals(shareToRemove.getStock().getSymbol()))
+ .filter(s -> s.getStock().getSymbol()
+ .equals(newShare.getStock().getSymbol()))
.findFirst();
if (match.isEmpty()) {
- System.out.println("[DEBUG] Portfolio - Did not find share: " + shareToRemove.getStock().getSymbol());
+ System.out.println("[DEBUG] Portfolio - Did not find share: "
+ + newShare);
return false;
}
- Share existingShare = match.get();
- BigDecimal remainingQuantity = existingShare.getQuantity().subtract(shareToRemove.getQuantity());
- int comparisonResult = remainingQuantity.compareTo(BigDecimal.ZERO);
+ Share existing = match.get();
+ BigDecimal remainingQuantity = existing.getQuantity()
+ .subtract(newShare.getQuantity());
+ int cmp = remainingQuantity.compareTo(BigDecimal.ZERO);
- if (comparisonResult < 0) {
+ if (cmp < 0) {
// Trying to sell more than owned
- System.out.println("[DEBUG] Portfolio - Cannot sell " + shareToRemove.getQuantity()
- + " of " + shareToRemove.getStock().getSymbol()
- + ", only " + existingShare.getQuantity() + " owned");
+ System.out.println("[DEBUG] Portfolio - Cannot sell "
+ + newShare.getQuantity()
+ + " of " + newShare.getStock().getSymbol()
+ + ", only " + existing.getQuantity() + " owned");
return false;
- } else if (comparisonResult == 0) {
+ } else if (cmp == 0) {
// Selling entire position — remove from list
- System.out.println("[DEBUG] Portfolio - Fully closed position: " + existingShare.getStock().getSymbol());
- return shares.remove(existingShare);
+ System.out.println("[DEBUG] Portfolio - Fully closed position: "
+ + existing);
+ return shares.remove(existing);
} else {
// Partial sale — update quantity in place
- existingShare.setQuantity(remainingQuantity);
- System.out.println("[DEBUG] Portfolio - Partial sale: " + shareToRemove.getQuantity()
- + " sold, " + remainingQuantity + " remaining of " + existingShare.getStock().getSymbol());
+ existing.setQuantity(remainingQuantity);
+ System.out.println("[DEBUG] Portfolio - Partial sale: "
+ + newShare.getQuantity()
+ + " sold, " + remainingQuantity + " remaining of "
+ + existing.getStock().getSymbol());
return true;
}
}
@@ -113,22 +122,22 @@ public List getShares() {
* Checks whether the portfolio contains a given share.
*
* @param share the {@code Share} to check for
- * @return {@code true} if the share exists in the portfolio, {@code false} if {@code null} or not found
+ * @return {@code true} if found
*/
- public boolean contains(Share share) {
+ public boolean contains(final Share share) {
return share != null && shares.contains(share);
}
/**
- * Calculates the total net worth of all shares in the portfolio if sold at their current market price.
+ * Calculates the total net worth of all shares if sold today.
*
- * @return the total value of all shares in the portfolio
+ * @return the total net worth
*/
public BigDecimal getNetWorth() {
- BigDecimal totalValue = BigDecimal.ZERO;
+ BigDecimal totalSum = BigDecimal.ZERO;
for (Share share : shares) {
- totalValue = totalValue.add(new SaleCalculator(share).calculateTotal());
+ totalSum = totalSum.add(new SaleCalculator(share).calculateTotal());
}
- return totalValue;
+ return totalSum;
}
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Share.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Share.java
index 5d3281c..0582f7d 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Share.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Share.java
@@ -10,34 +10,62 @@
*
*/
public class Share {
- private Stock stock;
+ /** The stock this share represents. */
+ private final Stock stock;
+ /** The number of shares owned. */
private BigDecimal quantity;
- private BigDecimal purchasePrice;
+ /** The price per share at the time of purchase. */
+ private final BigDecimal purchasePrice;
/**
- * Constructs a new {@code Share} given the given stock, quantity, and purchase price.
- * @param stock stock the stock that this share represents
- * @param quantity quantity the number of shares owned
- * @param purchasePrice purchasePrice the price per share at the time of purchase
+ * Constructs a new {@code Share} with stock, quantity, and purchase price.
+ *
+ * @param stock the stock that this share represents
+ * @param quantity the number of shares owned
+ * @param purchasePrice the price per share at the time of purchase
*/
- public Share(Stock stock, BigDecimal quantity, BigDecimal purchasePrice) {
+ public Share(
+ final Stock stock,
+ final BigDecimal quantity,
+ final BigDecimal purchasePrice
+ ) {
this.stock = stock;
this.quantity = quantity;
this.purchasePrice = purchasePrice;
}
+ /**
+ * Returns the stock this share represents.
+ *
+ * @return the stock
+ */
public Stock getStock() {
return stock;
}
- public void setQuantity(BigDecimal quantity) {
+ /**
+ * Sets the quantity of shares owned.
+ *
+ * @param quantity the new quantity
+ */
+ public void setQuantity(final BigDecimal quantity) {
this.quantity = quantity;
}
+ /**
+ * Returns the quantity of shares owned.
+ *
+ * @return the quantity
+ */
public BigDecimal getQuantity() {
return quantity;
}
+ /**
+ * Returns the purchase price per share.
+ *
+ * @return the purchase price
+ */
public BigDecimal getPurchasePrice() {
return purchasePrice;
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Status.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Status.java
index bf966ea..677409f 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Status.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Status.java
@@ -8,12 +8,17 @@
*
*
* - {@link #NOVICE}: Starting level with no requirements.
- * - {@link #INVESTOR}: Achieved by trading for at least 10 distinct weeks and increasing net worth by at least 20%.
- * - {@link #SPECULATOR}: Achieved by trading for at least 20 distinct weeks and doubling net worth.
+ * - {@link #INVESTOR}: Achieved by trading for at least 10 distinct weeks
+ * and increasing net worth by at least 20%.
+ * - {@link #SPECULATOR}: Achieved by trading for at least 20 distinct weeks
+ * and doubling net worth.
*
*/
public enum Status {
+ /** Initial level for all players. */
NOVICE,
+ /** Intermediate level. */
INVESTOR,
- SPECULATOR;
+ /** High level. */
+ SPECULATOR
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Stock.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Stock.java
index 194c26c..7c847ce 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Stock.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Stock.java
@@ -16,18 +16,22 @@ public class Stock {
private final String symbol;
private final String company;
private StockGraph stockGraph;
- private List prices = new ArrayList<>();
+ private final List prices = new ArrayList<>();
/**
- * Constructs a new stock with the given symbol, company name, and initial sales price.
- * @param symbol symbol the unique stock symbol (e.g., "AAPL")
- * @param company company the company name (e.g., "Apple Inc.")
- * @param salesPrice salesPrice the initial sales price of the stock
+ * Constructs a new stock with symbol, company name, and initial sales price.
+ *
+ * @param symbol the unique stock symbol (e.g., "AAPL")
+ * @param company the company name (e.g., "Apple Inc.")
+ * @param salesPrice the initial sales price of the stock
*/
- public Stock(String symbol, String company, BigDecimal salesPrice) {
+ public Stock(
+ final String symbol,
+ final String company,
+ final BigDecimal salesPrice
+ ) {
this.symbol = symbol;
this.company = company;
- this.stockGraph = new StockGraph();
prices.add(salesPrice);
}
@@ -39,6 +43,11 @@ public String getCompany() {
return company;
}
+ /**
+ * Returns the current sales price of the stock.
+ *
+ * @return the current price
+ */
public BigDecimal getSalesPrice() {
return prices.isEmpty() ? BigDecimal.ZERO : prices.get(prices.size() - 1);
}
@@ -48,39 +57,79 @@ public BigDecimal getSalesPrice() {
*
* @param salesPrice the new sales price to add
*/
- public void addNewSalesPrice(BigDecimal salesPrice) {
+ public void addNewSalesPrice(final BigDecimal salesPrice) {
prices.add(salesPrice);
}
+ /**
+ * Returns a list of historical prices.
+ *
+ * @return a list of prices
+ */
public List getHistoricalPrices() {
return List.copyOf(prices);
}
+ /**
+ * Returns the highest price in the stock's history.
+ *
+ * @return the highest price
+ */
public BigDecimal getHighestPrice() {
return prices.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
}
+
+ /**
+ * Returns the lowest price in the stock's history.
+ *
+ * @return the lowest price
+ */
public BigDecimal getLowestPrice() {
return prices.stream().min(BigDecimal::compareTo).orElse(BigDecimal.ZERO);
}
+ /**
+ * Returns the difference between the current and previous price.
+ *
+ * @return the price change
+ */
public BigDecimal getLatestPriceChange() {
- if (prices.size() < 2) {
+ final int minSize = 2;
+ if (prices.size() < minSize) {
return BigDecimal.ZERO;
}
return prices.get(prices.size() - 1)
.subtract(prices.get(prices.size() - 2));
}
+ /**
+ * Simulates a price update.
+ */
public void updatePrice() {
BigDecimal oldPrice = getSalesPrice();
BigDecimal newPrice = oldPrice.add(new BigDecimal("1"));
newPrice = newPrice.max(BigDecimal.ZERO);
prices.add(newPrice);
}
+
+ /**
+ * Returns the stock graph component, initializing it if necessary.
+ *
+ * @return the stock graph
+ */
public StockGraph getStockGraph() {
+ if (stockGraph == null) {
+ stockGraph = new StockGraph();
+ }
return stockGraph;
}
+
+ /**
+ * Updates the stock graph if it is visible.
+ */
public void updateStockGraph() {
- stockGraph.update(this);
+ if (stockGraph != null && stockGraph.getVisibility()) {
+ stockGraph.update(this);
+ }
}
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/StockFileHandler.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/StockFileHandler.java
index a2d7d88..19dd290 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/StockFileHandler.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/StockFileHandler.java
@@ -12,23 +12,28 @@
/**
* Utility class for handling file data.
- * Includes methods for reading and writing from file, as well as parsing stock data into objects.
- * Using BufferedReader/BufferedWriter for optimized handling.
+ * Includes methods for reading and writing from file.
*/
-public class StockFileHandler {
+public final class StockFileHandler {
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private StockFileHandler() {
+ }
/**
* Method for parsing stock data into stock objects.
*
- * @param path the relative path of the file being parsed (from source root)
- * @return list of stock objects parsed from data file
- * @throws IOException if file not found
+ * @param path the path of the file being parsed
+ * @return list of stock objects
+ * @throws IOException if file not found or error occurs
*/
- public static List readFromFile(Path path) throws IOException {
+ public static List readFromFile(final Path path) throws IOException {
List stocks = new ArrayList<>();
- try (BufferedReader bufferedReader = new BufferedReader(new FileReader(path.toFile()))) {
+ try (BufferedReader bufferedReader = new BufferedReader(
+ new FileReader(path.toFile()))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
line = line.trim();
@@ -39,9 +44,13 @@ public static List readFromFile(Path path) throws IOException {
String[] parts = line.split(",");
- String symbol = parts[0];
- String company = parts[1];
- BigDecimal salePrice = new BigDecimal(parts[2].trim());
+ final int symbolIndex = 0;
+ final int companyIndex = 1;
+ final int priceIndex = 2;
+
+ String symbol = parts[symbolIndex];
+ String company = parts[companyIndex];
+ BigDecimal salePrice = new BigDecimal(parts[priceIndex].trim());
stocks.add(new Stock(symbol, company, salePrice));
}
@@ -52,11 +61,14 @@ public static List readFromFile(Path path) throws IOException {
/**
* Method for writing stock information to file.
*
- * @param path the relative path of the file being parsed (from source root)
- * @param stocks list of stock objects to be transcribed to data file
- * @throws IOException if file not found
+ * @param path the path of the file to write to
+ * @param stocks list of stock objects to be transcribed
+ * @throws IOException if error occurs
*/
- public static void writeToFile(Path path, List stocks) throws IOException {
+ public static void writeToFile(
+ final Path path,
+ final List stocks
+ ) throws IOException {
try (BufferedWriter writer = Files.newBufferedWriter(path)) {
writer.write("# S&P 500 Companies by Market Cap");
@@ -65,9 +77,9 @@ public static void writeToFile(Path path, List stocks) throws IOException
for (Stock stock : stocks) {
String line = String.format("%s,%s,%s",
- stock.getSymbol(),
- stock.getCompany(),
- stock.getSalesPrice());
+ stock.getSymbol(),
+ stock.getCompany(),
+ stock.getSalesPrice());
writer.write(line);
writer.newLine();
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Purchase.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Purchase.java
index c442fec..5aaffcb 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Purchase.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Purchase.java
@@ -7,17 +7,24 @@
import java.math.BigDecimal;
-public class Purchase extends Transaction {
-
- public Purchase(Share share, int week) {
+/**
+ * Represents a stock purchase transaction.
+ */
+public final class Purchase extends Transaction {
+ /**
+ * Constructs a new Purchase transaction.
+ *
+ * @param share the share position being bought
+ * @param week the simulation week
+ */
+ public Purchase(final Share share, final int week) {
super(share, week, new PurchaseCalculator(share));
}
@Override
- public void commit(Player player) {
+ public void commit(final Player player) {
setCommitted(true);
player.withdrawMoney(getCalculator().calculateTotal());
player.getTransactionArchive().add(this);
-
}
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Sale.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Sale.java
index fcfe51b..426c5f7 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Sale.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Sale.java
@@ -4,14 +4,22 @@
import edu.ntnu.idi.idatt2003.gruppe42.Model.Player;
import edu.ntnu.idi.idatt2003.gruppe42.Model.Share;
-public class Sale extends Transaction {
-
- public Sale(Share share, int week) {
+/**
+ * Represents a stock sale transaction.
+ */
+public final class Sale extends Transaction {
+ /**
+ * Constructs a new Sale transaction.
+ *
+ * @param share the share position being sold
+ * @param week the simulation week
+ */
+ public Sale(final Share share, final int week) {
super(share, week, new SaleCalculator(share));
}
@Override
- public void commit(Player player) {
+ public void commit(final Player player) {
setCommitted(true);
player.addMoney(getCalculator().calculateTotal());
player.getTransactionArchive().add(this);
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Transaction.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Transaction.java
index 0052939..f3d8c09 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Transaction.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Transaction.java
@@ -4,38 +4,86 @@
import edu.ntnu.idi.idatt2003.gruppe42.Model.Player;
import edu.ntnu.idi.idatt2003.gruppe42.Model.Share;
+/**
+ * Abstract base class for financial transactions.
+ */
public abstract class Transaction {
- private Share share;
- private int week;
- private TransactionCalculator calculator;
+ /** The share position involved in the transaction. */
+ private final Share share;
+ /** The week the transaction occurred. */
+ private final int week;
+ /** The calculator used for this transaction. */
+ private final TransactionCalculator calculator;
+ /** Whether the transaction has been committed. */
private boolean committed = false;
- protected Transaction(Share share, int week, TransactionCalculator calculator) {
+ /**
+ * Constructs a new Transaction.
+ *
+ * @param share the share position
+ * @param week the simulation week
+ * @param calculator the cost calculator
+ */
+ protected Transaction(
+ final Share share,
+ final int week,
+ final TransactionCalculator calculator
+ ) {
this.share = share;
this.week = week;
this.calculator = calculator;
this.committed = false;
}
+ /**
+ * Returns the share position.
+ *
+ * @return the share
+ */
public Share getShare() {
return share;
}
+ /**
+ * Returns the week of the transaction.
+ *
+ * @return the week
+ */
public int getWeek() {
return week;
}
+ /**
+ * Returns the calculator used.
+ *
+ * @return the calculator
+ */
public TransactionCalculator getCalculator() {
return calculator;
}
+ /**
+ * Returns whether the transaction is committed.
+ *
+ * @return true if committed
+ */
public boolean isCommitted() {
return committed;
}
- public void commit(Player player) {}
+ /**
+ * Commits the transaction for a player.
+ *
+ * @param player the player performing the transaction
+ */
+ public abstract void commit(Player player);
- public void setCommitted(boolean committed) {
+ /**
+ * Sets the committed status of the transaction.
+ *
+ * @param committed the new status
+ */
+ public void setCommitted(final boolean committed) {
this.committed = committed;
}
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/TransactionArchive.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/TransactionArchive.java
index 455c7ba..250ef1f 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/TransactionArchive.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/TransactionArchive.java
@@ -5,14 +5,27 @@
import java.util.List;
import java.util.Set;
-public class TransactionArchive {
- private List transactions;
-
+/**
+ * Archive for storing and querying past transactions.
+ */
+public final class TransactionArchive {
+ /** The list of stored transactions. */
+ private final List transactions;
+
+ /**
+ * Constructs an empty TransactionArchive.
+ */
public TransactionArchive() {
this.transactions = new ArrayList<>();
}
- public boolean add(Transaction transaction) {
+ /**
+ * Adds a transaction to the archive.
+ *
+ * @param transaction the transaction to add
+ * @return true if added
+ */
+ public boolean add(final Transaction transaction) {
if (transaction == null) {
return false;
} else {
@@ -20,11 +33,22 @@ public boolean add(Transaction transaction) {
}
}
+ /**
+ * Checks if the archive is empty.
+ *
+ * @return true if empty
+ */
public boolean isEmpty() {
return transactions.isEmpty();
}
- public List getTransactions(int week) {
+ /**
+ * Returns all transactions that occurred in a specific week.
+ *
+ * @param week the simulation week
+ * @return a list of transactions
+ */
+ public List getTransactions(final int week) {
List transactionsThisWeek = new ArrayList<>();
for (Transaction transaction : transactions) {
@@ -36,7 +60,13 @@ public List getTransactions(int week) {
return transactionsThisWeek;
}
- public List getPurchases(int week) {
+ /**
+ * Returns all purchase transactions that occurred in a specific week.
+ *
+ * @param week the simulation week
+ * @return a list of purchases
+ */
+ public List getPurchases(final int week) {
List purchasesThisWeek = new ArrayList<>();
for (Transaction transaction : transactions) {
@@ -48,7 +78,13 @@ public List getPurchases(int week) {
return purchasesThisWeek;
}
- public List getSales(int week) {
+ /**
+ * Returns all sale transactions that occurred in a specific week.
+ *
+ * @param week the simulation week
+ * @return a list of sales
+ */
+ public List getSales(final int week) {
List salesThisWeek = new ArrayList<>();
for (Transaction transaction : transactions) {
@@ -60,6 +96,11 @@ public List getSales(int week) {
return salesThisWeek;
}
+ /**
+ * Counts the number of distinct weeks in which transactions occurred.
+ *
+ * @return the number of distinct weeks
+ */
public int countDistinctWeeks() {
Set distinctWeeks = new HashSet<>();
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/BankApp.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/BankApp.java
index 9035e21..4ffea33 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/BankApp.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/BankApp.java
@@ -16,57 +16,87 @@
* A popup for the Bank app.
*/
public class BankApp extends Popup {
- private ListView portfolioList;
- private Label netWorthLabel = new Label("Net Worth: ");
- private Label unInvestedMoneyLabel = new Label("Cash: ");
- private Label investedMoneyLabel = new Label("Stock: ");
- private Label growthPercentageLabel = new Label("Growth: ");
+ private final ListView portfolioList;
+ private final Label netWorthLabel = new Label("Net Worth: ");
+ private final Label unInvestedMoneyLabel = new Label("Cash: ");
+ private final Label investedMoneyLabel = new Label("Stock: ");
+ private final Label growthPercentageLabel = new Label("Growth: ");
/**
* Constructs a new Bank popup.
*/
- public BankApp(){
+ public BankApp() {
super(400, 300, 120, 120, App.BANK);
- portfolioList = new ListView<>();
+ this.portfolioList = new ListView<>();
updateContent();
}
- private void updateContent(){
+ private void updateContent() {
GridPane statusPane = new GridPane();
- statusPane.add(netWorthLabel, 0, 0);
- statusPane.add(unInvestedMoneyLabel, 0, 1);
- statusPane.add(investedMoneyLabel, 1, 1);
- statusPane.add(growthPercentageLabel, 1, 0);
+ final int col0 = 0;
+ final int col1 = 1;
+ final int row0 = 0;
+ final int row1 = 1;
+
+ statusPane.add(netWorthLabel, col0, row0);
+ statusPane.add(unInvestedMoneyLabel, col0, row1);
+ statusPane.add(investedMoneyLabel, col1, row1);
+ statusPane.add(growthPercentageLabel, col1, row0);
portfolioList.setCellFactory(
- lv ->
- new ListCell() {
- @Override
- protected void updateItem(Share share, boolean empty) {
- super.updateItem(share, empty);
- if (empty || share == null) {
- setGraphic(null);
- } else {
- GridPane sharePane = new GridPane();
- sharePane.add(new Label(share.getStock().getCompany()), 0, 0);
- sharePane.add(new Label("Purchase"), 1, 0);
- sharePane.add(new Label(share.getStock().getSymbol()), 0, 1);
- sharePane.add(new Label(share.getPurchasePrice().toString()), 1, 1);
- sharePane.add(new Label("Quantity"), 0, 2);
- sharePane.add(new Label("Sale"), 1, 2);
- sharePane.add(new Label(share.getQuantity().toString()), 0, 3);
- sharePane.add(new Label(share.getStock().getSalesPrice().toString()), 1, 3);
- sharePane.setAlignment(Pos.CENTER_LEFT);
- setGraphic(sharePane);
- }
- }
- });
+ lv ->
+ new ListCell() {
+ @Override
+ protected void updateItem(final Share share, final boolean empty) {
+ super.updateItem(share, empty);
+ if (empty || share == null) {
+ setGraphic(null);
+ } else {
+ GridPane sharePane = new GridPane();
+ final int col0 = 0;
+ final int col1 = 1;
+ final int row0 = 0;
+ final int row1 = 1;
+ final int row2 = 2;
+ final int row3 = 3;
+
+ sharePane.add(new Label(share.getStock().getCompany()),
+ col0, row0);
+ sharePane.add(new Label("Purchase"), col1, row0);
+ sharePane.add(new Label(share.getStock().getSymbol()),
+ col0, row1);
+ sharePane.add(new Label(share.getPurchasePrice().toString()),
+ col1, row1);
+ sharePane.add(new Label("Quantity"), col0, row2);
+ sharePane.add(new Label("Sale"), col1, row2);
+ sharePane.add(new Label(share.getQuantity().toString()),
+ col0, row3);
+ sharePane.add(new Label(share.getStock().getSalesPrice()
+ .toString()), col1, row3);
+ sharePane.setAlignment(Pos.CENTER_LEFT);
+ setGraphic(sharePane);
+ }
+ }
+ });
content.getChildren().setAll(statusPane, portfolioList);
}
- public void updateStatus(double netWorth, double unInvestedMoney, double investedMoney, double growthPercentage) {
+ /**
+ * Updates the financial status displayed in the bank app.
+ *
+ * @param netWorth total net worth
+ * @param unInvestedMoney available cash
+ * @param investedMoney value of shares
+ * @param growthPercentage growth since start
+ */
+ public void updateStatus(
+ final double netWorth,
+ final double unInvestedMoney,
+ final double investedMoney,
+ final double growthPercentage
+ ) {
Platform.runLater(() -> {
netWorthLabel.setText("Net Worth: " + netWorth);
unInvestedMoneyLabel.setText("Cash: " + unInvestedMoney);
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/StockApp.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/StockApp.java
index 37aad5a..680c0f7 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/StockApp.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/StockApp.java
@@ -3,7 +3,9 @@
import edu.ntnu.idi.idatt2003.gruppe42.Model.App;
import edu.ntnu.idi.idatt2003.gruppe42.Model.Stock;
import edu.ntnu.idi.idatt2003.gruppe42.View.Popup;
+import edu.ntnu.idi.idatt2003.gruppe42.View.Views.Components.Receipt;
import edu.ntnu.idi.idatt2003.gruppe42.View.Views.Components.StockGraph;
+import java.math.BigDecimal;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.control.*;
@@ -14,13 +16,13 @@
* A popup for the Stock app.
*/
public class StockApp extends Popup {
- private ListView stockList;
- private TextField searchField;
+ private final ListView stockList;
+ private final TextField searchField;
private Label priceLabel;
private Stock currentStock;
- private Button buyButton;
- private Button sellButton;
- private TextField quantityField;
+ private final Spinner quantitySpinner;
+ private final Button confirmButton;
+ private final Receipt receipt;
/**
* Constructs a new Stock popup.
@@ -30,32 +32,32 @@ public class StockApp extends Popup {
* @param x x-position of the popup
* @param y y-position of the popup
*/
- public StockApp(int width, int height, int x, int y) {
+ public StockApp(final int width, final int height, final int x, final int y) {
super(width, height, x, y, App.STOCK);
- searchField = new TextField();
- stockList = new ListView<>();
- buyButton = new Button("Buy");
- sellButton = new Button("Sell");
- quantityField = new TextField("1");
- quantityField.setPromptText("Quantity");
-
- // Only allow numeric input
- quantityField.textProperty().addListener((observable, oldValue, newValue) -> {
- if (!newValue.matches("\\d*")) {
- quantityField.setText(newValue.replaceAll("[^\\d]", ""));
- }
- });
+ this.searchField = new TextField();
+ this.stockList = new ListView<>();
+
+ final int minQuantity = -1000;
+ final int maxQuantity = 1000;
+ final int initialQuantity = 0;
+ this.quantitySpinner = new Spinner<>(
+ minQuantity, maxQuantity, initialQuantity
+ );
+
+ this.confirmButton = new Button("Confirm");
+ this.receipt = new Receipt(null, BigDecimal.ZERO);
stockList.setCellFactory(
lv ->
new ListCell() {
@Override
- protected void updateItem(Stock stock, boolean empty) {
+ protected void updateItem(final Stock stock, final boolean empty) {
super.updateItem(stock, empty);
if (empty || stock == null) {
setGraphic(null);
} else {
- HBox row = new HBox(10, new Label(stock.getCompany()));
+ final int spacing = 10;
+ HBox row = new HBox(spacing, new Label(stock.getCompany()));
row.setOnMouseClicked(event -> {
System.out.println(stock.getCompany() + " is pressed!");
openStockPage(stock);
@@ -69,7 +71,12 @@ protected void updateItem(Stock stock, boolean empty) {
content.getChildren().addAll(searchField, stockList);
}
- public void openStockPage(Stock stock) {
+ /**
+ * Opens the page for a specific stock.
+ *
+ * @param stock the stock to view
+ */
+ public void openStockPage(final Stock stock) {
currentStock = stock;
content.setAlignment(Pos.TOP_LEFT);
@@ -81,25 +88,47 @@ public void openStockPage(Stock stock) {
priceLabel = new Label(stock.getSalesPrice().toString());
- header.getChildren().addAll(companyLabel, priceLabel, quantityField, buyButton, sellButton);
+ header.getChildren().addAll(companyLabel, priceLabel, quantitySpinner);
stock.getStockGraph().setVisibility(true);
- Platform.runLater(() -> stock.updateStockGraph());
+ Platform.runLater(stock::updateStockGraph);
StockGraph graph = stock.getStockGraph();
- content.getChildren().setAll(searchField, closeButton, header, graph);
+
+ content.getChildren().setAll(
+ searchField, closeButton, header, graph, confirmButton, receipt
+ );
}
- public void updatePriceLabel(Stock stock) {
+ /**
+ * Updates the price label if the specified stock is currently in view.
+ *
+ * @param stock the stock whose price updated
+ */
+ public void updatePriceLabel(final Stock stock) {
if (priceLabel != null && stock == currentStock) {
- Platform.runLater(() -> priceLabel.setText(stock.getSalesPrice().toString()));
+ Platform.runLater(() -> priceLabel.setText(
+ stock.getSalesPrice().toString())
+ );
}
}
- public void setCloseButtonAction(Button closeButton, Stock stock) {
+ /**
+ * Configures the close button to return to the search page.
+ *
+ * @param closeButton the button to configure
+ * @param stock the stock currently viewed
+ */
+ public void setCloseButtonAction(
+ final Button closeButton,
+ final Stock stock
+ ) {
stock.getStockGraph().setVisibility(false);
closeButton.setOnAction(e -> openSearchPage());
}
+ /**
+ * Returns to the stock search page.
+ */
public void openSearchPage() {
currentStock = null;
content.getChildren().setAll(searchField, stockList);
@@ -112,19 +141,20 @@ public Stock getCurrentStock() {
public TextField getSearchField() {
return searchField;
}
- public Button getBuyButton() {
- return buyButton;
- }
- public Button getSellButton() {
- return sellButton;
+
+ public ListView getStockList() {
+ return stockList;
}
- public TextField getQuantityField() {
- return quantityField;
+ public Spinner getQuantitySpinner() {
+ return quantitySpinner;
}
- public ListView getStockList() {
- return stockList;
+ public Button getConfirmButton() {
+ return confirmButton;
}
+ public Receipt getReceipt() {
+ return receipt;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Popup.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Popup.java
index 657e831..3553490 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Popup.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Popup.java
@@ -15,18 +15,28 @@
* Handles dragging, layout, and common components.
*/
public abstract class Popup {
-
- protected int width;
- protected int height;
- protected double dx;
- protected double dy;
- protected App type;
-
- protected BorderPane root;
- protected ScrollPane scrollPane;
- protected HBox header;
- protected VBox content;
- protected Button closeButton;
+ /** Width of the popup. */
+ private final int width;
+ /** Height of the popup. */
+ private final int height;
+ /** X-offset for dragging. */
+ private double dx;
+ /** Y-offset for dragging. */
+ private double dy;
+ /** The app type of the popup. */
+ private final App type;
+
+ /** Root layout node. */
+ private final BorderPane root;
+ /** Scrollable container for content. */
+ private final ScrollPane scrollPane;
+ /** Header container. */
+ private final HBox header;
+ /** Close button. */
+ private final Button closeButton;
+
+ /** Content container. */
+ protected final VBox content;
/**
* Constructs a new popup.
@@ -35,16 +45,23 @@ public abstract class Popup {
* @param height height of the popup
* @param x x-position of the popup
* @param y y-position of the popup
+ * @param type the app type
*/
- protected Popup(int width, int height, int x, int y, App type) {
+ protected Popup(
+ final int width,
+ final int height,
+ final int x,
+ final int y,
+ final App type
+ ) {
this.width = width;
this.height = height;
this.type = type;
-
root = new BorderPane();
scrollPane = new ScrollPane();
- scrollPane.setStyle("-fx-hbar-policy: never; -fx-vbar-policy: AS_NEEDED; -fx-background-insets: 0; -fx-padding: 0;");
+ scrollPane.setStyle("-fx-hbar-policy: never; -fx-vbar-policy: AS_NEEDED; "
+ + "-fx-background-insets: 0; -fx-padding: 0;");
header = new HBox();
content = new VBox();
@@ -93,6 +110,8 @@ private void makeDraggable() {
/**
* Returns the width of the popup.
+ *
+ * @return the width
*/
public int getWidth() {
return width;
@@ -100,6 +119,8 @@ public int getWidth() {
/**
* Returns the height of the popup.
+ *
+ * @return the height
*/
public int getHeight() {
return height;
@@ -107,6 +128,8 @@ public int getHeight() {
/**
* Returns the x-position of the popup.
+ *
+ * @return the x-position
*/
public double getX() {
return root.getLayoutX();
@@ -114,6 +137,8 @@ public double getX() {
/**
* Returns the y-position of the popup.
+ *
+ * @return the y-position
*/
public double getY() {
return root.getLayoutY();
@@ -121,6 +146,8 @@ public double getY() {
/**
* Returns the root node of the popup.
+ *
+ * @return the root
*/
public BorderPane getRoot() {
return root;
@@ -128,13 +155,15 @@ public BorderPane getRoot() {
/**
* Returns the type of the popup.
+ *
+ * @return the type
*/
public App getType() {
return type;
}
/**
- * Checks if a popup is out of bounds and moves it back if necessary
+ * Checks if a popup is out of bounds and moves it back if necessary.
*/
public void keepInBounds() {
if (getRoot().getParent() instanceof Pane parent) {
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/Components/Receipt.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/Components/Receipt.java
new file mode 100644
index 0000000..08578ee
--- /dev/null
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/Components/Receipt.java
@@ -0,0 +1,61 @@
+package edu.ntnu.idi.idatt2003.gruppe42.View.Views.Components;
+
+import edu.ntnu.idi.idatt2003.gruppe42.Model.Stock;
+import java.math.BigDecimal;
+import javafx.geometry.Pos;
+import javafx.scene.control.Label;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.VBox;
+
+/**
+ * Component for displaying a transaction receipt.
+ */
+public final class Receipt extends VBox {
+ private Stock stock;
+ private BigDecimal quantity;
+
+ /**
+ * Constructs a new Receipt.
+ *
+ * @param stock the stock
+ * @param quantity the quantity
+ */
+ public Receipt(final Stock stock, final BigDecimal quantity) {
+ update(stock, quantity);
+ }
+
+ /**
+ * Updates the receipt with new transaction data.
+ *
+ * @param stock the stock
+ * @param quantity the quantity
+ */
+ public void update(final Stock stock, final BigDecimal quantity) {
+ this.stock = stock;
+ this.quantity = quantity;
+ if (stock == null) {
+ this.getChildren().clear();
+ return;
+ }
+ GridPane receiptPane = new GridPane();
+
+ final int col0 = 0;
+ final int col1 = 1;
+ final int row0 = 0;
+ final int row1 = 1;
+ final int row2 = 2;
+ final int row3 = 3;
+
+ receiptPane.add(new Label(stock.getCompany()), col0, row0);
+ receiptPane.add(new Label("Quantity"), col1, row0);
+ receiptPane.add(new Label(stock.getSymbol()), col0, row1);
+ receiptPane.add(new Label(quantity.toString()), col1, row1);
+ receiptPane.add(new Label("Price"), col0, row2);
+ receiptPane.add(new Label("Amount"), col1, row2);
+ receiptPane.add(new Label(stock.getSalesPrice().toString()), col0, row3);
+ receiptPane.add(new Label((stock.getSalesPrice().multiply(quantity))
+ .toString()), col1, row3);
+ receiptPane.setAlignment(Pos.CENTER);
+ this.getChildren().setAll(new Label("Receipt"), receiptPane);
+ }
+}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/Components/StockGraph.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/Components/StockGraph.java
index 31f18cb..40b88a8 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/Components/StockGraph.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/Components/StockGraph.java
@@ -10,28 +10,31 @@
import java.math.BigDecimal;
import java.util.List;
-public class StockGraph extends VBox {
-
+/**
+ * Component for displaying a stock price history graph.
+ */
+public final class StockGraph extends VBox {
private final LineChart lineChart;
- private final NumberAxis xAxis;
- private final NumberAxis yAxis;
private boolean isVisible = false;
private final int stockResolution = 50;
-
+ /**
+ * Constructs a new StockGraph.
+ */
public StockGraph() {
- xAxis = new NumberAxis();
+ NumberAxis xAxis = new NumberAxis();
xAxis.setLabel("Time");
xAxis.setAutoRanging(true);
xAxis.setTickLabelsVisible(false);
xAxis.setTickMarkVisible(false);
- yAxis = new NumberAxis();
+ NumberAxis yAxis = new NumberAxis();
yAxis.setLabel("Price");
yAxis.setAutoRanging(true);
lineChart = new LineChart<>(xAxis, yAxis);
- lineChart.setPrefSize(100, 100);
+ final int prefSize = 100;
+ lineChart.setPrefSize(prefSize, prefSize);
lineChart.setCreateSymbols(false);
lineChart.setLegendVisible(false);
lineChart.setAnimated(false);
@@ -39,18 +42,27 @@ public StockGraph() {
getChildren().add(lineChart);
}
- public boolean update(Stock stock) {
+ /**
+ * Updates the graph with data from the specified stock.
+ *
+ * @param stock the stock to display
+ * @return true if update was scheduled
+ */
+ public boolean update(final Stock stock) {
if (stock == null) {
return false;
}
List history = stock.getHistoricalPrices();
- if (history.size() < stockResolution) {
+ if (history.isEmpty()) {
return false;
}
- List latestHistory = history.subList(Math.max(history.size() - stockResolution, 0), history.size());
+ List latestHistory = history.subList(
+ Math.max(history.size() - stockResolution, 0),
+ history.size()
+ );
Platform.runLater(() -> {
lineChart.getData().clear();
@@ -58,7 +70,7 @@ public boolean update(Stock stock) {
XYChart.Series series = new XYChart.Series<>();
series.setName(stock.getCompany());
- for (int i = 0; i < stockResolution; i++) {
+ for (int i = 0; i < latestHistory.size(); i++) {
series.getData().add(new XYChart.Data<>(i, latestHistory.get(i)));
}
@@ -71,7 +83,7 @@ public boolean getVisibility() {
return isVisible;
}
- public void setVisibility(boolean visible) {
+ public void setVisibility(final boolean visible) {
isVisible = visible;
}
}
diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/DesktopView.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/DesktopView.java
index 010c120..7a01c33 100644
--- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/DesktopView.java
+++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/DesktopView.java
@@ -14,13 +14,22 @@
* View for the desktop.
* Displays a 6x4 grid of apps.
*/
-public class DesktopView {
+public final class DesktopView {
+ /** Controller for the desktop. */
private final DesktopViewController desktopViewController;
+ /** Root layout node. */
private final BorderPane root;
+ /** Number of columns in the grid. */
private static final int COLS = 6;
+ /** Number of rows in the grid. */
private static final int ROWS = 4;
- public DesktopView(DesktopViewController desktopViewController) {
+ /**
+ * Constructs a new DesktopView.
+ *
+ * @param desktopViewController the controller
+ */
+ public DesktopView(final DesktopViewController desktopViewController) {
this.root = new BorderPane();
this.desktopViewController = desktopViewController;
}
@@ -47,16 +56,17 @@ private GridPane createGrid() {
grid.setHgap(0);
grid.setVgap(0);
- // Set constraints to make the grid fit the width and height of the screen
+ // Set constraints to make the grid fit the width and height
+ final double maxPercent = 100.0;
for (int col = 0; col < COLS; col++) {
ColumnConstraints colConstraints = new ColumnConstraints();
- colConstraints.setPercentWidth(100.0 / COLS);
+ colConstraints.setPercentWidth(maxPercent / COLS);
colConstraints.setHgrow(Priority.ALWAYS);
grid.getColumnConstraints().add(colConstraints);
}
for (int row = 0; row < ROWS; row++) {
RowConstraints rowConstraints = new RowConstraints();
- rowConstraints.setPercentHeight(100.0 / ROWS);
+ rowConstraints.setPercentHeight(maxPercent / ROWS);
rowConstraints.setVgrow(Priority.ALWAYS);
grid.getRowConstraints().add(rowConstraints);
}
@@ -68,7 +78,9 @@ private GridPane createGrid() {
// Add initial buttons to the first row based on the Apps enum
if (row == 0 && col < App.values().length) {
- cell.getChildren().add(desktopViewController.createAppButton(App.values()[col]));
+ cell.getChildren().add(
+ desktopViewController.createAppButton(App.values()[col])
+ );
}
}
}
diff --git a/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/Model/ExchangeTest.java b/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/Model/ExchangeTest.java
index 3c3099a..ec9ee0d 100644
--- a/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/Model/ExchangeTest.java
+++ b/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/Model/ExchangeTest.java
@@ -29,13 +29,28 @@ void stockManagementTest() {
@Test
void findStocksTest() {
List result = exchange.findStocks("Apple Inc.");
+ assertEquals(1, result.size());
+ assertEquals("Apple Inc.", result.get(0).getCompany());
+
+ // Partial match
+ result = exchange.findStocks("App");
+ assertEquals(1, result.size());
+ assertEquals("Apple Inc.", result.get(0).getCompany());
+ // Case insensitive match
+ result = exchange.findStocks("apple");
assertEquals(1, result.size());
- assertNotEquals(0, result.size());
assertEquals("Apple Inc.", result.get(0).getCompany());
- assertNotEquals("", result.get(0).getCompany());
- assertNotEquals("AAPL", result.get(0).getCompany());
- assertNotEquals("100", result.get(0).getCompany());
+
+ // Symbol match
+ result = exchange.findStocks("AAPL");
+ assertEquals(1, result.size());
+ assertEquals("AAPL", result.get(0).getSymbol());
+
+ // Partial symbol match
+ result = exchange.findStocks("AA");
+ assertEquals(1, result.size());
+ assertEquals("AAPL", result.get(0).getSymbol());
}
@Test