From 517518d9049188b956bd6cbc37d039e3c8b3d2f5 Mon Sep 17 00:00:00 2001 From: Roar Date: Wed, 27 May 2026 04:40:34 +0200 Subject: [PATCH] Updated all main java files Fixed google checkstyle, missing javadoc, and minor warnings. --- .../src/main/java/no/ntnu/gruppe53/App.java | 32 +- .../gruppe53/controller/GameController.java | 615 +++++++------- .../controller/GameSetupController.java | 165 ++-- .../controller/StartViewController.java | 74 +- .../gruppe53/controller/ViewController.java | 116 +-- .../java/no/ntnu/gruppe53/model/Exchange.java | 460 +++++----- .../java/no/ntnu/gruppe53/model/Player.java | 327 ++++---- .../no/ntnu/gruppe53/model/Portfolio.java | 253 +++--- .../java/no/ntnu/gruppe53/model/Purchase.java | 87 +- .../gruppe53/model/PurchaseCalculator.java | 207 +++-- .../ntnu/gruppe53/model/PurchaseFactory.java | 24 +- .../java/no/ntnu/gruppe53/model/Sale.java | 94 ++- .../ntnu/gruppe53/model/SaleCalculator.java | 237 +++--- .../no/ntnu/gruppe53/model/SaleFactory.java | 22 +- .../java/no/ntnu/gruppe53/model/Share.java | 109 +-- .../java/no/ntnu/gruppe53/model/Stock.java | 273 +++--- .../no/ntnu/gruppe53/model/Transaction.java | 153 ++-- .../gruppe53/model/TransactionArchive.java | 240 +++--- .../gruppe53/model/TransactionCalculator.java | 32 +- .../gruppe53/model/TransactionFactory.java | 43 +- .../no/ntnu/gruppe53/service/FileHandler.java | 214 ++--- .../gruppe53/service/FormatBigDecimal.java | 21 +- .../gruppe53/service/LanguageManager.java | 148 ++-- .../gruppe53/service/LanguageRegistry.java | 76 +- .../service/StockLineChartService.java | 101 +-- .../no/ntnu/gruppe53/view/CSVChooserView.java | 49 -- .../no/ntnu/gruppe53/view/CsvChooserView.java | 51 ++ .../java/no/ntnu/gruppe53/view/EndView.java | 384 ++++----- .../java/no/ntnu/gruppe53/view/FooterBar.java | 366 ++++---- .../no/ntnu/gruppe53/view/MarketView.java | 787 +++++++++--------- .../no/ntnu/gruppe53/view/NavigationBar.java | 416 +++++---- .../gruppe53/view/PlayerNameChooserView.java | 141 ++-- .../view/PlayerStartingMoneyChooserView.java | 156 ++-- .../no/ntnu/gruppe53/view/PortfolioView.java | 672 +++++++-------- .../java/no/ntnu/gruppe53/view/StartView.java | 117 ++- .../gruppe53/view/StockLineChartView.java | 196 ++--- .../gruppe53/view/TransactionArchiveView.java | 356 ++++---- 37 files changed, 4016 insertions(+), 3798 deletions(-) delete mode 100644 millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java create mode 100644 millions/src/main/java/no/ntnu/gruppe53/view/CsvChooserView.java diff --git a/millions/src/main/java/no/ntnu/gruppe53/App.java b/millions/src/main/java/no/ntnu/gruppe53/App.java index b3c3b06..3170ab6 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/App.java +++ b/millions/src/main/java/no/ntnu/gruppe53/App.java @@ -8,28 +8,34 @@ /** * Represents the main app of the application. + * *

Sets up necessary components and shows the first view * of the application: {@link StartView}.

*/ public class App extends Application { - @Override - public void start(Stage stage) { - ViewController vm = new ViewController(stage); + @Override + public void start(Stage stage) { + ViewController vm = new ViewController(stage); - StartView startView = new StartView(vm); + StartView startView = new StartView(vm); - new StartViewController(startView, vm); + new StartViewController(startView, vm); - vm.addView("start", startView); + vm.addView("start", startView); - vm.switchView("start"); + vm.switchView("start"); - stage.setTitle("Millions - the Stock Game"); - stage.show(); - } + stage.setTitle("Millions - the Stock Game"); + stage.show(); + } - public static void main(String[] args) { - launch(); - } + /** + * Launches the program. + * + * @param args arguments + */ + static void main(String[] args) { + launch(); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java index d8ea407..0514822 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameController.java @@ -3,336 +3,375 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.List; - import javafx.application.Platform; import javafx.scene.control.Alert; import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; import javafx.stage.Stage; import javafx.util.Subscription; -import no.ntnu.gruppe53.model.*; +import no.ntnu.gruppe53.model.Exchange; +import no.ntnu.gruppe53.model.Player; +import no.ntnu.gruppe53.model.PurchaseCalculator; +import no.ntnu.gruppe53.model.SaleCalculator; +import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.model.Stock; import no.ntnu.gruppe53.service.LanguageManager; import no.ntnu.gruppe53.service.StockLineChartService; -import no.ntnu.gruppe53.view.*; +import no.ntnu.gruppe53.view.EndView; +import no.ntnu.gruppe53.view.FooterBar; +import no.ntnu.gruppe53.view.MarketView; +import no.ntnu.gruppe53.view.NavigationBar; +import no.ntnu.gruppe53.view.PortfolioView; +import no.ntnu.gruppe53.view.TransactionArchiveView; /** * Represents the main controller for the views of the stock game. + * *

Uses a {@link LanguageManager} for methods to change text dynamically.

*/ public class GameController { - private Player player; - private Exchange exchange; - private List stocks; - private final LanguageManager lm = LanguageManager.getInstance(); - - private final ViewController vm; - - private MarketView marketView; - private PortfolioView portfolioView; - private TransactionArchiveView transactionArchiveView; - private EndView endView; - private PurchaseCalculator purchaseCalculator; - private SaleCalculator saleCalculator; - - private Subscription selectedStockPriceSubscription; - private Subscription selectedSharePriceSubscription; - - /** - * Initializes the {@link ViewController} to be used for switching views. - * @param vm {@code ViewController} to be used when switching views - */ - public GameController(ViewController vm) { - this.vm = vm; + private Player player; + private Exchange exchange; + private final LanguageManager lm = LanguageManager.getInstance(); + + private final ViewController vm; + + private MarketView marketView; + private PortfolioView portfolioView; + private TransactionArchiveView transactionArchiveView; + private EndView endView; + + private Subscription selectedStockPriceSubscription; + private Subscription selectedSharePriceSubscription; + + /** + * Initializes the {@link ViewController} to be used for switching views. + * + * @param vm {@code ViewController} to be used when switching views + */ + public GameController(ViewController vm) { + this.vm = vm; + } + + /** + * Starts a new game by initializing all main views and navigation. + * + *

Creates the player and exchange to be used by the game from + * data from the {@link GameSetupController}.

+ */ + public void startNewGame() { + Stage primaryStage = vm.getStage(); + + GameSetupController setupController = new GameSetupController(primaryStage); + GameSetupController.SetupResult setupResult = setupController.setupNewGame(); + + if (setupResult == null) { + return; } - /** - * Starts a new game by initializing all main views and navigation. - *

Creates the player and exchange to be used by the game from - * data from the {@link GameSetupController}.

- */ - public void startNewGame() { - Stage primaryStage = vm.getStage(); - - GameSetupController setupController = new GameSetupController(primaryStage); - GameSetupController.SetupResult setupResult = setupController.setupNewGame(); - if (setupResult == null) return; + player = setupResult.getPlayer(); + List stocks = setupResult.getStocks(); - player = setupResult.getPlayer(); - stocks = setupResult.getStocks(); + exchange = new Exchange("Millions Exchange", stocks); - exchange = new Exchange("Millions Exchange", stocks); + NavigationBar sharedNav = new NavigationBar(); + FooterBar sharedFooter = new FooterBar(); + vm.setGlobalPanels(sharedNav, sharedFooter); - NavigationBar sharedNav = new NavigationBar(); - FooterBar sharedFooter = new FooterBar(); - vm.setGlobalPanels(sharedNav, sharedFooter); + marketView = new MarketView(); + // marketView.setPlayer(player); + marketView.setExchange(exchange); - marketView = new MarketView(); - //marketView.setPlayer(player); - marketView.setExchange(exchange); + portfolioView = new PortfolioView(); + portfolioView.setPlayer(player); - portfolioView = new PortfolioView(); - portfolioView.setPlayer(player); + transactionArchiveView = new TransactionArchiveView(); + transactionArchiveView.setPlayer(player); - transactionArchiveView = new TransactionArchiveView(); - transactionArchiveView.setPlayer(player); + endView = new EndView(); - endView = new EndView(); + sharedNav.bindPlayer(player); + sharedNav.bindExchange(exchange); + sharedFooter.setExchange(exchange); - sharedNav.bindPlayer(player); - sharedNav.bindExchange(exchange); - sharedFooter.setExchange(exchange); + vm.addView("portfolio", portfolioView); + vm.addView("market", marketView); + vm.addView("history", transactionArchiveView); + vm.addView("endGame", endView); - vm.addView("portfolio", portfolioView); - vm.addView("market", marketView); - vm.addView("history", transactionArchiveView); - vm.addView("endGame", endView); + initialize(sharedNav, sharedFooter); - initialize(sharedNav, sharedFooter); + vm.switchView("market"); + } - vm.switchView("market"); + /** + * Switches the view to the {@link MarketView}. + * + *

MarketView lets the player perform {@link no.ntnu.gruppe53.model.Purchase} transactions.

+ */ + public void showMarketView() { + if (marketView != null) { + vm.switchView("market"); } - - /** - * Switches the view to the {@link MarketView}. - *

MarketView lets the player perform {@link Purchase} transactions.

- */ - public void showMarketView() { - if (marketView != null) { - vm.switchView("market"); - } + } + + /** + * Switches the view to the {@link PortfolioView}. + * + *

Contains the data from a player's {@link no.ntnu.gruppe53.model.Portfolio}.

+ * + *

PortfolioView lets theEndView player perform + * {@link no.ntnu.gruppe53.model.Sale} transactions.

+ */ + public void showPortfolio() { + if (portfolioView != null) { + vm.switchView("portfolio"); } - - /** - * Switches the view to the {@link PortfolioView}. - *

Contains the data from a player's {@link Portfolio}.

- *

PortfolioView lets theEndView player perform {@link Sale} transactions.

- */ - public void showPortfolio() { - if (portfolioView != null) vm.switchView("portfolio"); + } + + /** + * Switches the view to the {@link TransactionArchiveView}. + * + *

TransactionArchiveView contains data from + * {@link no.ntnu.gruppe53.model.TransactionArchive}

+ */ + public void showTransactionHistory() { + if (transactionArchiveView != null) { + vm.switchView("history"); } - - /** - * Switches the view to the {@link TransactionArchiveView}. - *

TransactionArchiveView contains data from {@link TransactionArchive}

- */ - public void showTransactionHistory() { - if (transactionArchiveView != null) vm.switchView("history"); - } - - /** - * Initializes the {@link NavigationBar} and {@link FooterBar} for the controller. - *

Binds functionality to exposed buttons and lists in views.

- *

Sets subscriptions for observable values for views.

- *

Sets the method for setting the selected language locale for the footer.

- * @param nav the NavigationBar view - * @param footer the FooterBar view - */ - public void initialize(NavigationBar nav, FooterBar footer) { - nav.onMarketButtonClick(() -> vm.switchView("market")); - nav.onPortfolioButtonClick(() -> vm.switchView("portfolio")); - nav.onHistoryButtonClick(() -> vm.switchView("history")); - nav.onEndGameButtonClick(() -> endGame()); - - footer.onLanguageChange(selected -> { - lm.setLocale(selected.locale()); + } + + /** + * Initializes the {@link NavigationBar} and {@link FooterBar} for the controller. + * + *

Binds functionality to exposed buttons and lists in views.

+ * + *

Sets subscriptions for observable values for views.

+ * + *

Sets the method for setting the selected language locale for the footer.

+ * + * @param nav the NavigationBar view + * @param footer the FooterBar view + */ + public void initialize(NavigationBar nav, FooterBar footer) { + nav.onMarketButtonClick(() -> vm.switchView("market")); + nav.onPortfolioButtonClick(() -> vm.switchView("portfolio")); + nav.onHistoryButtonClick(() -> vm.switchView("history")); + nav.onEndGameButtonClick(this::endGame); + + footer.onLanguageChange(selected -> lm.setLocale(selected.locale())); + + footer.onAdvanceButtonClick(() -> { + if (exchange != null) { + exchange.advance(); + } + }); + + marketView.onStockSelection(stock -> { + if (selectedStockPriceSubscription != null) { + selectedStockPriceSubscription.unsubscribe(); + selectedStockPriceSubscription = null; + } + + if (stock != null) { + marketView.setSelectedStockLabel(stock.getSymbol() + " - " + stock.getCompany()); + marketView.setupPriceSubscription(stock, currentStock -> { + var series = StockLineChartService.buildSeries(currentStock, exchange); + marketView.updateChart(series, currentStock.getCompany()); }); - footer.onAdvanceButtonClick(() -> { - if (exchange != null) { - exchange.advance(); - } - }); - - marketView.onStockSelection(stock -> { - if (selectedStockPriceSubscription != null) { - selectedStockPriceSubscription.unsubscribe(); - selectedStockPriceSubscription = null; - } - - if (stock != null) { - marketView.setSelectedStockLabel(stock.getSymbol() + " - " + stock.getCompany()); - marketView.setupPriceSubscription(stock, currentStock -> { - var series = StockLineChartService.buildSeries(currentStock, exchange); - marketView.updateChart(series, currentStock.getCompany()); - }); - marketView.setPurchaseCalculator(calculatePurchase(marketView.getSelectedStock(), - marketView.getQuantityPurchase())); - - selectedStockPriceSubscription = stock.salesPriceProperty().subscribe(newPrice -> { - if (marketView.getSelectedStock() != null) { - marketView.setPurchaseCalculator(calculatePurchase( - marketView.getSelectedStock(), - marketView.getQuantityPurchase() - )); - } - }); + marketView.setPurchaseCalculator(calculatePurchase( + marketView.getSelectedStock(), marketView.getQuantityPurchase())); - } else { - marketView.setSelectedStockLabel(""); - marketView.clearChart(); - } + selectedStockPriceSubscription = stock.salesPriceProperty().subscribe(newPrice -> { + if (marketView.getSelectedStock() != null) { + marketView.setPurchaseCalculator(calculatePurchase( + marketView.getSelectedStock(), + marketView.getQuantityPurchase() + )); + } }); - marketView.onPurchaseButton(() -> { - Stock stock = marketView.getSelectedStock(); - if (stock != null) purchaseStock(stock.getSymbol(), marketView.getQuantityPurchase()); - }); - marketView.onQuantitySelect(() -> { - if (marketView.getSelectedStock() != null) { - marketView.setPurchaseCalculator(calculatePurchase(marketView.getSelectedStock(), - marketView.getQuantityPurchase())); - } - }); - - portfolioView.onShareSelection(share -> { - if (selectedSharePriceSubscription != null) { - selectedSharePriceSubscription.unsubscribe(); - selectedSharePriceSubscription = null; - } - if (share != null) { - portfolioView.setSelectedShareLabel(share.getStock().getSymbol() + " - " + share.getStock().getCompany()); - portfolioView.setSaleCalculator(calculateSale(portfolioView.getSelectedShare())); - - selectedSharePriceSubscription = share.getStock().salesPriceProperty().subscribe(newPrice -> { - if (portfolioView.getSelectedShare() != null) { - portfolioView.setSaleCalculator(calculateSale(portfolioView.getSelectedShare())); - } + } else { + marketView.setSelectedStockLabel(""); + marketView.clearChart(); + } + }); + + marketView.onPurchaseButton(() -> { + Stock stock = marketView.getSelectedStock(); + if (stock != null) { + purchaseStock(stock.getSymbol(), marketView.getQuantityPurchase()); + } + }); + + marketView.onQuantitySelect(() -> { + if (marketView.getSelectedStock() != null) { + marketView.setPurchaseCalculator(calculatePurchase( + marketView.getSelectedStock(), marketView.getQuantityPurchase())); + } + }); + + portfolioView.onShareSelection(share -> { + if (selectedSharePriceSubscription != null) { + selectedSharePriceSubscription.unsubscribe(); + selectedSharePriceSubscription = null; + } + + if (share != null) { + portfolioView.setSelectedShareLabel( + share.getStock().getSymbol() + " - " + share.getStock().getCompany()); + portfolioView.setSaleCalculator(calculateSale(portfolioView.getSelectedShare())); + + selectedSharePriceSubscription = + share.getStock().salesPriceProperty().subscribe(newPrice -> { + + if (portfolioView.getSelectedShare() != null) { + portfolioView.setSaleCalculator( + calculateSale(portfolioView.getSelectedShare())); + } }); - } else { - portfolioView.setSelectedShareLabel(""); - } - }); - - portfolioView.onSellButton(this::handleSell); - - endView.onQuitButton(Platform::exit); - - endView.onNewGameButton(() -> { - startNewGame(); - }); + } else { + portfolioView.setSelectedShareLabel(""); + } + }); + + portfolioView.onSellButton(this::handleSell); + + endView.onQuitButton(Platform::exit); + + endView.onNewGameButton(this::startNewGame); + } + + /** + * Lets a player purchase {@link Share}s in a stock. + * + *

Utilizes the buy method from {@link Exchange}.

+ * + * @param stockSymbol the symbol/ticker of the stock to purchase shares for + * @param quantity the quantity of shares to be purchased + */ + private void purchaseStock(String stockSymbol, int quantity) { + if (exchange == null || player == null || stockSymbol == null || quantity <= 0) { + return; } - /** - * Lets a player purchase {@link Share}s in a stock. - *

Utilizes the buy method from {@link Exchange}.

- * @param stockSymbol the symbol/ticker of the stock to purchase shares for - * @param quantity the quantity of shares to be purchased - */ - private void purchaseStock(String stockSymbol, int quantity) { - if (exchange == null || player == null || stockSymbol == null || quantity <= 0) return; - try { - exchange.buy(stockSymbol, BigDecimal.valueOf(quantity), player); - } catch (IllegalStateException | IllegalArgumentException ex) { - showErrorAlert("Purchase Failed", ex.getMessage()); - } + try { + exchange.buy(stockSymbol, BigDecimal.valueOf(quantity), player); + } catch (IllegalStateException | IllegalArgumentException ex) { + showErrorAlert("Purchase Failed", ex.getMessage()); } - - /** - * Handles sale of the selected share. - */ - private void handleSell() { - Share selectedShare = portfolioView.getSelectedShare(); - if (selectedShare != null) sellShare(selectedShare); + } + + /** + * Handles sale of the selected share. + */ + private void handleSell() { + Share selectedShare = portfolioView.getSelectedShare(); + if (selectedShare != null) { + sellShare(selectedShare); } - - /** - * Lets a player sell a share from their portfolio. - * @param selectedShare the currently selected share - */ - private void sellShare(Share selectedShare) { - if (exchange == null || player == null || selectedShare == null) return; - try { - exchange.sell(selectedShare, player); - } catch (IllegalStateException | IllegalArgumentException ex) { - showErrorAlert("Sale Failed", ex.getMessage()); - } + } + + /** + * Lets a player sell a share from their portfolio. + * + * @param selectedShare the currently selected share + */ + private void sellShare(Share selectedShare) { + if (exchange == null || player == null || selectedShare == null) { + return; } - - - /** - * Creates a {@link PurchaseCalculator} that can be used for the - * setPurchaseCalculator() function in the {@link MarketView}. - * @param stock the currently selected stock - * @param quantity the currently selected quantity - * @return {@link PurchaseCalculator} - */ - private PurchaseCalculator calculatePurchase(Stock stock, int quantity) { - stock = marketView.getSelectedStock(); - quantity = marketView.getQuantityPurchase(); - - Share share = new Share(stock, BigDecimal.valueOf(quantity), stock.getSalesPrice()); - - - - return purchaseCalculator = new PurchaseCalculator(share); - } - - /** - * Creates a {@link SaleCalculator} that can be used for the - * setSaleCalculator() function in the {@link PortfolioView}. - * @param share the share to use the calculator on - * @return {@link SaleCalculator} - */ - private SaleCalculator calculateSale(Share share) { - - share = portfolioView.getSelectedShare(); - - return saleCalculator = new SaleCalculator(share); - - } - - - /** - * Creates an alert where the Player can confirm if they want to end the game. - * If yes, then sells entire portfolio and displays the EndView. - */ - private void endGame() { - Alert alert = new Alert(Alert.AlertType.CONFIRMATION); - alert.titleProperty().bind(lm.bindString("endGameAlertTitle")); - alert.headerTextProperty().bind(lm.bindString("endGameAlertHeaderText")); - alert.contentTextProperty().bind(lm.bindString("endGameAlertContentText")); - - ButtonType yesButton = new ButtonType(lm.getString("yes"), ButtonBar.ButtonData.YES); - ButtonType noButton = new ButtonType(lm.getString("no"), ButtonBar.ButtonData.NO); - - - alert.getButtonTypes().setAll(yesButton, noButton); - - alert.showAndWait().ifPresent(button -> { - if (button == yesButton) { - sellPortfolio(); - vm.setGlobalPanels(null, null); - vm.switchView("endGame"); - - endView.getStatusLabel().setText(player.getStatus(exchange)); - endView.getMoneyLabel().setText(player.getMoney().setScale(2, RoundingMode.HALF_EVEN).toString()); - - - - } - }); - - } - - private void sellPortfolio() { - List sharesToSell = List.copyOf(player.getPortfolio().getShares()); - - for (Share share : sharesToSell) { - exchange.sell(share, player); - } + try { + exchange.sell(selectedShare, player); + } catch (IllegalStateException | IllegalArgumentException ex) { + showErrorAlert("Sale Failed", ex.getMessage()); } - - /** - * Creates a default error alert to be used for notifying the player of errors. - * @param title title of the error alert - * @param content the message content of the error alert - */ - private void showErrorAlert(String title, String content) { - Alert alert = new Alert(Alert.AlertType.ERROR); - alert.setTitle(title); - alert.setHeaderText(null); - alert.setContentText(content); - alert.showAndWait(); + } + + /** + * Creates a {@link PurchaseCalculator} that can be used for the + * setPurchaseCalculator() function in the {@link MarketView}. + * + * @param stock the currently selected stock + * @param quantity the currently selected quantity + * @return {@link PurchaseCalculator} + */ + private PurchaseCalculator calculatePurchase(Stock stock, int quantity) { + stock = marketView.getSelectedStock(); + quantity = marketView.getQuantityPurchase(); + + Share share = new Share(stock, BigDecimal.valueOf(quantity), stock.getSalesPrice()); + PurchaseCalculator purchaseCalculator; + return purchaseCalculator = new PurchaseCalculator(share); + } + + /** + * Creates a {@link SaleCalculator} that can be used for the + * setSaleCalculator() function in the {@link PortfolioView}. + * + * @param share the share to use the calculator on + * @return {@link SaleCalculator} + */ + private SaleCalculator calculateSale(Share share) { + + share = portfolioView.getSelectedShare(); + + SaleCalculator saleCalculator; + return saleCalculator = new SaleCalculator(share); + } + + /** + * Creates an alert where the Player can confirm if they want to end the game. + * If yes, then sells entire portfolio and displays the EndView. + */ + private void endGame() { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.titleProperty().bind(lm.bindString("endGameAlertTitle")); + alert.headerTextProperty().bind(lm.bindString("endGameAlertHeaderText")); + alert.contentTextProperty().bind(lm.bindString("endGameAlertContentText")); + + ButtonType yesButton = new ButtonType(lm.getString("yes"), ButtonBar.ButtonData.YES); + ButtonType noButton = new ButtonType(lm.getString("no"), ButtonBar.ButtonData.NO); + + alert.getButtonTypes().setAll(yesButton, noButton); + + alert.showAndWait().ifPresent(button -> { + if (button == yesButton) { + sellPortfolio(); + vm.setGlobalPanels(null, null); + vm.switchView("endGame"); + + endView.getStatusLabel().setText(player.getStatus(exchange)); + endView.getMoneyLabel().setText( + player.getMoney().setScale(2, RoundingMode.HALF_EVEN).toString()); + } + }); + } + + /** + * Sells a player's entire portfolio. + */ + private void sellPortfolio() { + List sharesToSell = List.copyOf(player.getPortfolio().getShares()); + + for (Share share : sharesToSell) { + exchange.sell(share, player); } + } + + /** + * Creates a default error alert to be used for notifying the player of errors. + * + * @param title title of the error alert + * @param content the message content of the error alert + */ + private void showErrorAlert(String title, String content) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle(title); + alert.setHeaderText(null); + alert.setContentText(content); + alert.showAndWait(); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/GameSetupController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/GameSetupController.java index 85432b9..feced72 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/GameSetupController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/GameSetupController.java @@ -1,119 +1,122 @@ package no.ntnu.gruppe53.controller; +import java.io.File; +import java.math.BigDecimal; +import java.util.List; +import javafx.stage.Stage; +import no.ntnu.gruppe53.model.Exchange; import no.ntnu.gruppe53.model.Player; import no.ntnu.gruppe53.model.Stock; -import no.ntnu.gruppe53.model.Exchange; import no.ntnu.gruppe53.service.FileHandler; -import no.ntnu.gruppe53.view.CSVChooserView; +import no.ntnu.gruppe53.view.CsvChooserView; import no.ntnu.gruppe53.view.PlayerNameChooserView; import no.ntnu.gruppe53.view.PlayerStartingMoneyChooserView; -import javafx.stage.Stage; - -import java.io.File; -import java.math.BigDecimal; -import java.util.List; /** * Controller for setting up relevant game data to start a new game. + * *

Lets the {@link Player} set their name, starting money * and provide a .csv file of {@link Stock} data to populate * the {@link Exchange}.

*/ public class GameSetupController { + /** + * Nested class to pass the player and stocks values over to the + * {@link GameController}. + */ + public static class SetupResult { + private final Player player; + private final List stocks; + /** - * Nested class to pass the player and stocks values over to the - * {@link GameController}. + * Initializes the player and stock variables. + * + * @param player the player that will play the stock game + * @param stocks the list of stocks to populate the exchange */ - public static class SetupResult { - private final Player player; - private final List stocks; - - /** - * Initializes the player and stock variables. - * @param player the player that will play the stock game - * @param stocks the list of stocks to populate the exchange - */ - public SetupResult(Player player, List stocks) { - this.player = player; - this.stocks = stocks; - } - - /** - * Returns the player object containing the set name and starting money. - * - * @return the player object for the player playing the game - */ - public Player getPlayer() { - return player; - } - - /** - * A list of stocks that will be used for buying and selling on the exchange. - * - * @return a list of stock objects - */ - public List getStocks() { - return stocks; - } + public SetupResult(Player player, List stocks) { + this.player = player; + this.stocks = stocks; } - private final Stage stage; - /** - * Initializes the initial stage for the view setting up the relevant game data. + * Returns the player object containing the set name and starting money. * - * @param stage the initial stage to be used + * @return the player object for the player playing the game */ - public GameSetupController(Stage stage) { - this.stage = stage; + public Player getPlayer() { + return player; } /** - * Sets up the initial data for the {@code Player} and {@code Exchange}. - *

Uses default values if the player does not enter their desired values: - *

    - *
  • Player name: Player
  • - *
  • Player starting money: 5000
  • - *
  • CSV file: resources/sp500.csv
  • - *

+ * A list of stocks that will be used for buying and selling on the exchange. * - * @return a SetupResult object containing data for the player and the exchange + * @return a list of stock objects */ - public SetupResult setupNewGame() { - PlayerNameChooserView playerNameDialog = new PlayerNameChooserView(); - // Player name - String playerName = - playerNameDialog.getDialog() - .orElse("Player"); - - if (playerName.isBlank()) { - playerName = "Player"; - } + public List getStocks() { + return stocks; + } + } + + private final Stage stage; + + /** + * Initializes the initial stage for the view setting up the relevant game data. + * + * @param stage the initial stage to be used + */ + public GameSetupController(Stage stage) { + this.stage = stage; + } + + /** + * Sets up the initial data for the {@code Player} and {@code Exchange}. + * + *

Uses default values if the player does not enter their desired values: + *

    + *
  • Player name: Player
  • + *
  • Player starting money: 5000
  • + *
  • CSV file: resources/sp500.csv
  • + *

+ * + * @return a SetupResult object containing data for the player and the exchange + */ + public SetupResult setupNewGame() { + PlayerNameChooserView playerNameDialog = new PlayerNameChooserView(); + // Player name + String playerName = + playerNameDialog.getDialog().orElse("Player"); + + if (playerName.isBlank()) { + playerName = "Player"; + } - // Starting money - PlayerStartingMoneyChooserView playerStartingMoneyDialog = new PlayerStartingMoneyChooserView(); + // Starting money + PlayerStartingMoneyChooserView playerStartingMoneyDialog = new PlayerStartingMoneyChooserView(); - BigDecimal startingMoney = + BigDecimal startingMoney = playerStartingMoneyDialog.getDialog() .orElse(new BigDecimal("5000")); - // .csv File - CSVChooserView csvChooser = new CSVChooserView(); - File csvFile = csvChooser.getDialog(stage); + // .csv File + CsvChooserView csvChooser = new CsvChooserView(); + File csvFile = csvChooser.getDialog(stage); - List stocks; - if (csvFile == null) { - stocks = FileHandler.readStocksFromFile("src/main/resources", "sp500.csv"); - } else { - stocks = FileHandler.readStocksFromFile(csvFile.getParent(), csvFile.getName()); - if (stocks.isEmpty()) { - stocks = FileHandler.readStocksFromFile("src/main/resources", "sp500.csv"); - } - } + List stocks; - Player player = new Player(playerName, startingMoney); + if (csvFile == null) { + stocks = FileHandler.readStocksFromFile("src/main/resources", "sp500.csv"); + } else { + stocks = FileHandler.readStocksFromFile(csvFile.getParent(), csvFile.getName()); - return new SetupResult(player, stocks); + if (stocks.isEmpty()) { + stocks = FileHandler.readStocksFromFile("src/main/resources", "sp500.csv"); + } } + + Player player = new Player(playerName, startingMoney); + + return new SetupResult(player, stocks); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java index 82d8fde..5b743a9 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/StartViewController.java @@ -1,56 +1,58 @@ package no.ntnu.gruppe53.controller; +import java.util.List; +import javafx.application.Platform; import no.ntnu.gruppe53.service.LanguageManager; import no.ntnu.gruppe53.service.LanguageOption; import no.ntnu.gruppe53.service.LanguageRegistry; import no.ntnu.gruppe53.view.StartView; -import javafx.application.Platform; - -import java.util.List; /** * The controller for {@link StartView}, * the first view for the player upon launching the app. + * *

Contains three choices: *

    *
  • New Game
  • *
  • Language Select
  • *
  • Quit
  • *

+ * *

Uses a {@link LanguageManager} to change the UI language.

*/ public class StartViewController { - /** - * Handles the events when "New Game" and "Quit" is pressed. - * - * @param view the StartView to be displayed - * @param vm the {@link ViewController} to handle switching of views - */ - public StartViewController(StartView view, ViewController vm) { - view.setOnNewGame(() -> { - GameController gameController = new GameController(vm); - gameController.startNewGame(); - }); - - view.setOnLanguage(() -> { - LanguageManager lm = LanguageManager.getInstance(); - List availableLanguages = LanguageRegistry.getLanguages(); - - int currentIndex = 0; - for (int i = 0; i < availableLanguages.size(); i++) { - if (availableLanguages.get(i).locale().equals(lm.getLocale())) { - currentIndex = i; - break; - } - } - - int nextIndex = (currentIndex + 1) % availableLanguages.size(); - LanguageOption nextLanguage = availableLanguages.get(nextIndex); - - lm.setLocale(nextLanguage.locale()); - }); - - - view.setOnQuit(Platform::exit); - } + /** + * Handles the events when "New Game" and "Quit" is pressed. + * + * @param view the StartView to be displayed + * @param vm the {@link ViewController} to handle switching of views + */ + public StartViewController(StartView view, ViewController vm) { + view.setOnNewGame(() -> { + GameController gameController = new GameController(vm); + gameController.startNewGame(); + }); + + view.setOnLanguage(() -> { + LanguageManager lm = LanguageManager.getInstance(); + List availableLanguages = LanguageRegistry.getLanguages(); + + int currentIndex = 0; + + for (int i = 0; i < availableLanguages.size(); i++) { + if (availableLanguages.get(i).locale().equals(lm.getLocale())) { + currentIndex = i; + break; + } + } + + int nextIndex = (currentIndex + 1) % availableLanguages.size(); + LanguageOption nextLanguage = availableLanguages.get(nextIndex); + + lm.setLocale(nextLanguage.locale()); + }); + + + view.setOnQuit(Platform::exit); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java index 1bf8d20..f24cfeb 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java +++ b/millions/src/main/java/no/ntnu/gruppe53/controller/ViewController.java @@ -1,5 +1,7 @@ package no.ntnu.gruppe53.controller; +import java.util.HashMap; +import java.util.Map; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.layout.BorderPane; @@ -7,74 +9,74 @@ import no.ntnu.gruppe53.view.FooterBar; import no.ntnu.gruppe53.view.NavigationBar; -import java.util.HashMap; -import java.util.Map; - /** * Controller for setting and switching between views. + * *

Uses a {@link HashMap} as an archive for registered views that * are possible to switch between.

*/ public class ViewController { - private final Stage stage; - private final Scene scene; - private final Map views = new HashMap<>(); - private final BorderPane mainLayout; + private final Stage stage; + private final Map views = new HashMap<>(); + private final BorderPane mainLayout; - /** - * Initializes a ViewController using the {@link Stage} as the first default view. - *

Uses a {@link BorderPane} layout for views.

- */ - public ViewController(Stage stage) { - this.stage = stage; - this.mainLayout = new BorderPane(); - this.scene = new Scene(mainLayout, 1200, 900); - this.stage.setScene(scene); - } + /** + * Initializes a ViewController using the {@link Stage} as the first default view. + * + *

Uses a {@link BorderPane} layout for views.

+ */ + public ViewController(Stage stage) { + this.stage = stage; + this.mainLayout = new BorderPane(); + Scene scene = new Scene(mainLayout, 1200, 900); + this.stage.setScene(scene); + } - /** - * Returns the stage of the ViewController. - * - * @return the stage of the ViewController. - */ - public Stage getStage() { - return this.stage; - } + /** + * Returns the stage of the ViewController. + * + * @return the stage of the ViewController. + */ + public Stage getStage() { + return this.stage; + } - /** - * Sets the top and bottom part of the BorderPane. - *

Uses {@link NavigationBar} as the top bar, - * and {@link FooterBar} as the bottom bar.

- */ - public void setGlobalPanels(NavigationBar navigationBar, FooterBar footerBar) { - this.mainLayout.setTop(navigationBar); - this.mainLayout.setBottom(footerBar); - } + /** + * Sets the top and bottom part of the BorderPane. + * + *

Uses {@link NavigationBar} as the top bar, + * and {@link FooterBar} as the bottom bar.

+ */ + public void setGlobalPanels(NavigationBar navigationBar, FooterBar footerBar) { + this.mainLayout.setTop(navigationBar); + this.mainLayout.setBottom(footerBar); + } - /** - * Adds a view to the stored archive of possible views. - * - * @param name the name/key to refer to the view when switching view - * @param view the view to be added - */ - public void addView(String name, Parent view) { - views.put(name, view); - } + /** + * Adds a view to the stored archive of possible views. + * + * @param name the name/key to refer to the view when switching view + * @param view the view to be added + */ + public void addView(String name, Parent view) { + views.put(name, view); + } - /** - * Switches the to the given view based on the string key - * stored in the view archive. - *

Only switches the center pane.

- * - * @param name the name/key of the view to switch to - */ - public void switchView(String name) { - Parent view = views.get(name); + /** + * Switches the to the given view based on the string key + * stored in the view archive. + * + *

Only switches the center pane.

+ * + * @param name the name/key of the view to switch to + */ + public void switchView(String name) { + Parent view = views.get(name); - if (view == null) { - throw new IllegalArgumentException("No views found matching: " + name); - } - - mainLayout.setCenter(view); + if (view == null) { + throw new IllegalArgumentException("No views found matching: " + name); } + + mainLayout.setCenter(view); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java b/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java index f20919c..86fef14 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Exchange.java @@ -1,252 +1,284 @@ package no.ntnu.gruppe53.model; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.stream.Collectors; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.*; -import java.util.stream.Collectors; - /** - * Represents an exchange/market containing {@link Stock}s and allows a {@link Player} to buy {@link Share}s - * of said stocks. - *

Contains method to advance the week of the market, finding stocks, gainers, and losers on the exchange.

+ * Represents an exchange/market containing {@link Stock}s and allows + * a {@link Player} to buy {@link Share}s of said stocks. + * + *

Contains method to advance the week of the market, finding stocks, + * gainers, and losers on the exchange.

*/ public class Exchange { - private final String name; - private int week; - private final Map stockMap; - private final Random random; - private final ObservableList stocks = FXCollections.observableArrayList(); - private final ObjectProperty currentWeekProperty = new SimpleObjectProperty<>(); - private final TransactionFactory purchaseFactory; - private final TransactionFactory saleFactory; - - /** - * Sets the name of the exchange and the list of stocks to populate it. - *

Validates that the values are proper before creating the exchange.

- *

Initializes factories for creating a {@link Transaction}.

- * - * @param name the name of the exchange - * @param stocks the list of stocks to be tradeable on the exchange - * @throws IllegalArgumentException if exchange is null or blank, if list of stocks is null, or if - * the list of stocks contains a null or duplicate symbol stock - */ - public Exchange(String name, List stocks) { - if (name == null || name.isBlank()) { - throw new IllegalArgumentException("Exchange name cannot be null or blank"); - } - - if (stocks == null) { - throw new IllegalArgumentException("Stock list cannot be null"); - } - - this.name = name; - this.week = 1; - this.currentWeekProperty.set(week); - this.stockMap = new HashMap<>(); - this.random = new Random(); - - for (Stock stock : stocks) { - if (stock == null) { - throw new IllegalArgumentException("Stock cannot be null"); - } - - if (stockMap.containsKey(stock.getSymbol())) { - throw new IllegalArgumentException("Duplicate stock symbol: " + stock.getSymbol()); - } - - stockMap.put(stock.getSymbol(), stock); - this.stocks.add(stock); - } - - this.purchaseFactory = new PurchaseFactory(); - this.saleFactory = new SaleFactory(); + private final String name; + private int week; + private final Map stockMap; + private final Random random; + private final ObservableList stocks = FXCollections.observableArrayList(); + private final ObjectProperty currentWeekProperty = new SimpleObjectProperty<>(); + private final TransactionFactory purchaseFactory; + private final TransactionFactory saleFactory; + + /** + * Sets the name of the exchange and the list of stocks to populate it. + * + *

Validates that the values are proper before creating the exchange.

+ * + *

Initializes factories for creating a {@link Transaction}.

+ * + * @param name the name of the exchange + * @param stocks the list of stocks to be tradeable on the exchange + * @throws IllegalArgumentException if exchange is null or blank, if list of stocks is null, or if + * the list of stocks contains a null or duplicate symbol stock + */ + public Exchange(String name, List stocks) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("Exchange name cannot be null or blank"); } - /** - * Returns the name of the exchange - * @return the name of the exchange - */ - public String getName() { - return name; + if (stocks == null) { + throw new IllegalArgumentException("Stock list cannot be null"); } - /** - * Returns the current week of the exchange - * - * @return the current week of the exchange - */ - public int getWeek() { - return week; - } + this.name = name; + this.week = 1; + this.currentWeekProperty.set(week); + this.stockMap = new HashMap<>(); + this.random = new Random(); - /** - * Checks whether a stock with the given symbol/ticker exists in the exchange. - *

Returns {@code true} if it does,v{@code false} otherwise.

- * - * @param symbol the symbol/ticker of the stock - * @return {@code true} if the exchange contains the stock, otherwise {@code false} - */ - public boolean hasStock(String symbol) { - return stockMap.containsKey(symbol); - } + for (Stock stock : stocks) { + if (stock == null) { + throw new IllegalArgumentException("Stock cannot be null"); + } - /** - * Returns the stock object matching the given symbol/ticker. - * - * @param symbol the stock symbol/ticker - * @return the stock object matching the symbol/ticker - * @throws IllegalArgumentException if search symbol is null or symbol is not in stockMap - */ - public Stock getStock(String symbol) { - if (symbol == null || !stockMap.containsKey(symbol)) { - throw new IllegalArgumentException("Stock not found: " + symbol); - } - return stockMap.get(symbol); + if (stockMap.containsKey(stock.getSymbol())) { + throw new IllegalArgumentException("Duplicate stock symbol: " + stock.getSymbol()); + } + + stockMap.put(stock.getSymbol(), stock); + this.stocks.add(stock); } - /** - * Returns an {@link ObservableList} of the list of stocks for JavaFX to observe. - * - * @return an observable list of the stocks in the exchange - */ - public ObservableList getObservableStocks() { - return stocks; + this.purchaseFactory = new PurchaseFactory(); + this.saleFactory = new SaleFactory(); + } + + /** + * Returns the name of the exchange. + * + * @return the name of the exchange + */ + public String getName() { + return name; + } + + /** + * Returns the current week of the exchange. + * + * @return the current week of the exchange + */ + public int getWeek() { + return week; + } + + /** + * Checks whether a stock with the given symbol/ticker exists in the exchange. + * + *

Returns {@code true} if it does,v{@code false} otherwise.

+ * + * @param symbol the symbol/ticker of the stock + * @return {@code true} if the exchange contains the stock, otherwise {@code false} + */ + public boolean hasStock(String symbol) { + return stockMap.containsKey(symbol); + } + + /** + * Returns the stock object matching the given symbol/ticker. + * + * @param symbol the stock symbol/ticker + * @return the stock object matching the symbol/ticker + * @throws IllegalArgumentException if search symbol is null or symbol is not in stockMap + */ + public Stock getStock(String symbol) { + if (symbol == null || !stockMap.containsKey(symbol)) { + throw new IllegalArgumentException("Stock not found: " + symbol); + } + return stockMap.get(symbol); + } + + /** + * Returns an {@link ObservableList} of the list of stocks for JavaFX to observe. + * + * @return an observable list of the stocks in the exchange + */ + public ObservableList getObservableStocks() { + return stocks; + } + + /** + * Returns a list of stocks matching the search term in either the symbol/ticker or company name. + * + * @param searchTerm the string to search for + * @return a list of stocks matching the search term + */ + public List findStocks(String searchTerm) { + if (searchTerm == null || searchTerm.isBlank()) { + return Collections.emptyList(); } - /** - * Returns a list of stocks matching the search term in either the symbol/ticker or company name. - * - * @param searchTerm the string to search for - * @return a list of stocks matching the search term - */ - public List findStocks(String searchTerm) { - if (searchTerm == null || searchTerm.isBlank()) return Collections.emptyList(); - - String search = searchTerm.toLowerCase(); - return stockMap.values().stream() - .filter(stock -> stock.getSymbol().toLowerCase().contains(search) || + String search = searchTerm.toLowerCase(); + return stockMap.values().stream() + .filter(stock -> stock.getSymbol().toLowerCase().contains(search) + || stock.getCompany().toLowerCase().contains(search)) .collect(Collectors.toList()); + } + + /** + * Represents a player buying a share on the exchange. + * + *

Creates a {@link Purchase} transaction for the + * purchase.

+ * + * @param symbol the symbol/ticker of the stock + * @param quantity the quantity of shares to buy for said stock + * @param player the player making the purchase + * @return a purchase transaction containing all details of the purchase + * @throws IllegalArgumentException if player or quantity is null + */ + public Transaction buy(String symbol, BigDecimal quantity, Player player) { + if (player == null) { + throw new IllegalArgumentException("Player cannot be null"); } - /** - * Represents a player buying a share on the exchange. - *

Creates a {@link Purchase} transaction for the - * purchase.

- * - * @param symbol the symbol/ticker of the stock - * @param quantity the quantity of shares to buy for said stock - * @param player the player making the purchase - * @return a purchase transaction containing all details of the purchase - * @throws IllegalArgumentException if player or quantity is null - */ - public Transaction buy(String symbol, BigDecimal quantity, Player player) { - if (player == null) throw new IllegalArgumentException("Player cannot be null"); - if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) - throw new IllegalArgumentException("Quantity must be positive"); - - Stock stock = getStock(symbol); - Share share = new Share(stock, quantity, stock.getSalesPrice()); - Transaction transaction = purchaseFactory.create(share, week); - transaction.commit(player); - - return transaction; + if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) { + throw new IllegalArgumentException("Quantity must be positive"); } - /** - * Represents a player selling a share on the exchange. - *

Creates a {@link Sale} transaction for the sale.

- * - * @param share the share to be sold - * @param player the player selling the share - * @return a sale transaction containing all details of the sale - * @throws IllegalArgumentException if player or share is null - */ - public Transaction sell(Share share, Player player) { - if (player == null) throw new IllegalArgumentException("Player cannot be null"); - if (share == null) throw new IllegalArgumentException("Share cannot be null"); - - Transaction transaction = saleFactory.create(share, week); - transaction.commit(player); - return transaction; + Stock stock = getStock(symbol); + Share share = new Share(stock, quantity, stock.getSalesPrice()); + Transaction transaction = purchaseFactory.create(share, week); + transaction.commit(player); + + return transaction; + } + + /** + * Represents a player selling a share on the exchange. + * + *

Creates a {@link Sale} transaction for the sale.

+ * + * @param share the share to be sold + * @param player the player selling the share + * @return a sale transaction containing all details of the sale + * @throws IllegalArgumentException if player or share is null + */ + public Transaction sell(Share share, Player player) { + if (player == null) { + throw new IllegalArgumentException("Player cannot be null"); } - /** - * Advances the timeline of the exchange by 1 week. - *

Uses a randomized value for the increase and decrease of a stocks sales price, simulating market - * fluctuations on a weekly basis.

- */ - public void advance() { - this.week++; - this.currentWeekProperty.set(week); - - double negativePriceChange = -0.05; - double positivePriceChange = 0.1; - - for (Stock stock : stockMap.values()) { - BigDecimal currentPrice = stock.getSalesPrice(); - double randomPriceMultiplier = negativePriceChange + (positivePriceChange * random.nextDouble()); - BigDecimal bdRandomPriceMultiplier = BigDecimal.valueOf(1 + randomPriceMultiplier); + if (share == null) { + throw new IllegalArgumentException("Share cannot be null"); + } - BigDecimal changedPrice = currentPrice.multiply(bdRandomPriceMultiplier) + Transaction transaction = saleFactory.create(share, week); + transaction.commit(player); + return transaction; + } + + /** + * Advances the timeline of the exchange by 1 week. + * + *

Uses a randomized value for the increase and decrease + * of a stocks sales price, simulating market fluctuations on a weekly basis.

+ */ + public void advance() { + this.week++; + this.currentWeekProperty.set(week); + + double negativePriceChange = -0.05; + double positivePriceChange = 0.1; + + for (Stock stock : stockMap.values()) { + BigDecimal currentPrice = stock.getSalesPrice(); + double randomPriceMultiplier = negativePriceChange + + (positivePriceChange * random.nextDouble()); + BigDecimal bdRandomPriceMultiplier = BigDecimal.valueOf(1 + randomPriceMultiplier); + + BigDecimal changedPrice = currentPrice.multiply(bdRandomPriceMultiplier) .setScale(2, RoundingMode.HALF_UP); - if (changedPrice.compareTo(BigDecimal.ZERO) <= 0) { - changedPrice = new BigDecimal(1); - } + if (changedPrice.compareTo(BigDecimal.ZERO) <= 0) { + changedPrice = new BigDecimal(1); + } - stock.addNewSalesPrice(changedPrice); - } + stock.addNewSalesPrice(changedPrice); + } + } + + /** + * Sorts the stocks in descending order and returns a list of stocks. + * + * @param entries amount of entries in the list to display + * @param comparator the comparator used to compare the stocks + * @return a list of stocks + * @throws IllegalArgumentException if the amount of entries is negative + */ + private List getTopStocks(int entries, Comparator comparator) { + if (entries < 0) { + throw new IllegalArgumentException("Amount of entries in list cannot be negative."); } - /** - * Sorts the stocks in descending order and returns a list of stocks. - * - * @param entries amount of entries in the list to display - * @param comparator the comparator used to compare the stocks - * @return a list of stocks - * @throws IllegalArgumentException if the amount of entries is negative - */ - private List getTopStocks(int entries, Comparator comparator) { - if (entries < 0) throw new IllegalArgumentException("Amount of entries in list cannot be negative."); - return stockMap.values().stream() + return stockMap.values().stream() .sorted(comparator) .limit(entries) .collect(Collectors.toList()); - } - - /** - * Lists the given amount of stocks sorted by highest increase in sales price since last week. - * @param entries amount of stocks to be shown in list - * @return a descending list of stocks with the highest price increase since last week - * @throws IllegalArgumentException if number of entries in list is negative - */ - public List getGainers(int entries) { - return getTopStocks(entries, Comparator.comparing(Stock::getLatestPriceChange).reversed()); - } - - /** - * Lists the given amount of stocks sorted by the stocks with the lowest price increase (or - * highest price decrease) - * @param entries amount of stocks to be shown in the list - * @return a descending list of stocks with the highest price decrease or - * lowest price increase - * @throws IllegalArgumentException if number of entries in list is negative - */ - public List getLosers(int entries) { - return getTopStocks(entries, Comparator.comparing(Stock::getLatestPriceChange)); - } - - /** - * Returns an observable of the current week - * @return an observable of the current week - */ - public ObjectProperty getCurrentWeekProperty() { - return currentWeekProperty; - } + } + + /** + * Lists the given amount of stocks sorted by highest increase in sales price since last week. + * + * @param entries amount of stocks to be shown in list + * @return a descending list of stocks with the highest price increase since last week + * @throws IllegalArgumentException if number of entries in list is negative + */ + public List getGainers(int entries) { + return getTopStocks(entries, Comparator.comparing(Stock::getLatestPriceChange).reversed()); + } + + /** + * Lists the given amount of stocks sorted by the stocks with the lowest price increase (or + * highest price decrease). + * + * @param entries amount of stocks to be shown in the list + * @return a descending list of stocks with the highest price decrease or + * lowest price increase + * @throws IllegalArgumentException if number of entries in list is negative + */ + public List getLosers(int entries) { + return getTopStocks(entries, Comparator.comparing(Stock::getLatestPriceChange)); + } + + /** + * Returns an observable of the current week. + * + * @return an observable of the current week + */ + public ObjectProperty getCurrentWeekProperty() { + return currentWeekProperty; + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Player.java b/millions/src/main/java/no/ntnu/gruppe53/model/Player.java index 21a9e9e..6489d53 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Player.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Player.java @@ -7,181 +7,198 @@ /** * Represents a participant in the trading game. - *

- * A {@code Player} has a name, a monetary balance, a {@link Portfolio} + * + *

A {@code Player} has a name, a monetary balance, a {@link Portfolio} * of owned {@link Share}s, and a {@link TransactionArchive} containing - * a history of the player's transactions. - *

+ * a history of the player's transactions.

* - *

- * The player starts with an initial amount of money and may gain or - * lose funds through transactions. - *

+ *

The player starts with an initial amount of money and may gain or + * lose funds through transactions.

*/ public class Player { - private final String name; - private final BigDecimal startingMoney; - private BigDecimal money; - private final Portfolio portfolio; - private final TransactionArchive transactionArchive; - - private final ObjectProperty moneyProperty = new SimpleObjectProperty<>(); - private final ObjectProperty netWorthProperty = new SimpleObjectProperty<>(); - - /** - * Creates a player with a given starting balance. - *

Subscribes to the netWorth property to notify of changes in the player's net worth.

- * - * @param name the player's name; must not be {@code null} or blank - * @param startingMoney the initial monetary balance of the - * player; must not be {@code null} or negative - * @throws IllegalArgumentException if {@code name} is {@code null} or - * blank, or if {@code startingMoney} - * is {@code null}, negative, or 0 - */ - public Player(String name, BigDecimal startingMoney) { - if (name == null || name.isBlank()) { - throw new IllegalArgumentException("Player name must not be null or empty."); - } - - if (startingMoney == null || startingMoney.signum() <= 0 ) { - throw new IllegalArgumentException("Starting money must not be null, negative, nor 0."); - } - - this.name = name; - this.startingMoney = startingMoney; - this.money = startingMoney; - this.moneyProperty.set(startingMoney); - this.portfolio = new Portfolio(); - this.transactionArchive = new TransactionArchive(); - - portfolio.netWorthProperty().subscribe(price -> updateNetWorth()); - updateNetWorth(); + private final String name; + private final BigDecimal startingMoney; + private BigDecimal money; + private final Portfolio portfolio; + private final TransactionArchive transactionArchive; + + private final ObjectProperty moneyProperty = new SimpleObjectProperty<>(); + private final ObjectProperty netWorthProperty = new SimpleObjectProperty<>(); + + /** + * Creates a player with a given starting balance. + * + *

Subscribes to the netWorth property to notify of changes in the player's net worth.

+ * + * @param name the player's name; must not be {@code null} or blank + * @param startingMoney the initial monetary balance of the + * player; must not be {@code null} or negative + * @throws IllegalArgumentException if {@code name} is {@code null} or + * blank, or if {@code startingMoney} + * is {@code null}, negative, or 0 + */ + public Player(String name, BigDecimal startingMoney) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("Player name must not be null or empty."); } - /** - * Returns an {@link ObjectProperty} of the player's net worth. - * - * @return an observable of the player's net worth - */ - public ObjectProperty getNetWorthProperty() { - return netWorthProperty; + if (startingMoney == null || startingMoney.signum() <= 0) { + throw new IllegalArgumentException("Starting money must not be null, negative, nor 0."); } - /** - * Mirrors the change in the player's net worth to the observable property. - */ - private void updateNetWorth() { - netWorthProperty.set(getNetWorth()); + this.name = name; + this.startingMoney = startingMoney; + this.money = startingMoney; + this.moneyProperty.set(startingMoney); + this.portfolio = new Portfolio(); + this.transactionArchive = new TransactionArchive(); + + portfolio.netWorthProperty().subscribe(price -> updateNetWorth()); + updateNetWorth(); + } + + /** + * Returns an {@link ObjectProperty} of the player's net worth. + * + * @return an observable of the player's net worth + */ + public ObjectProperty getNetWorthProperty() { + return netWorthProperty; + } + + /** + * Mirrors the change in the player's net worth to the observable property. + */ + private void updateNetWorth() { + netWorthProperty.set(getNetWorth()); + } + + /** + * Returns the full name of the player. + * + * @return the player's name + */ + public String getName() { + return this.name; + } + + /** + * Returns the player's current monetary balance. + * + * @return the player's current balance. + */ + public BigDecimal getMoney() { + return this.money; + } + + /** + * Increases the players monetary balance by the given amount. + * + *

Money added must be a {@code positive} number.

+ * + * @param amount the amount to add; must be positive and not {@code null} + * @throws IllegalArgumentException if {@code amount} is {@code null} + * or not positive + */ + public void addMoney(BigDecimal amount) { + if (amount == null) { + throw new IllegalArgumentException("Amount must not be null."); } - - /** - * Returns the full name of the player. - * - * @return the player's name - */ - public String getName() { - return this.name; + if (amount.signum() <= 0) { + throw new IllegalArgumentException("Amount must be positive."); } - /** - * Returns the player's current monetary balance. - * - * @return the player's current balance. - */ - public BigDecimal getMoney() { - return this.money; + this.money = this.money.add(amount); + this.moneyProperty.set(this.money); + updateNetWorth(); + } + + /** + * Decreases the player's monetary balance by the given amount. + * + *

The player's monetary balance must not decrease below {@code 0} + * (overdrawn not possible).

+ * + * @param amount the amount to subtract; must not be negative or {@code null} + * @throws IllegalArgumentException if the amount is not positive, + * {@code null}, or insufficient funds are available + */ + public void withdrawMoney(BigDecimal amount) { + if (amount == null) { + throw new IllegalArgumentException("Amount must not be null."); } - /** - * Increases the players monetary balance by the given amount. - *

- * Money added must be a {@code positive} number. - *

- * - * @param amount the amount to add; must be positive and not {@code null} - * @throws IllegalArgumentException if {@code amount] is {@code null} - * or not positive - */ - public void addMoney(BigDecimal amount) { - if (amount == null) throw new IllegalArgumentException("Amount must not be null."); - if (amount.signum() <= 0) throw new IllegalArgumentException("Amount must be positive."); - this.money = this.money.add(amount); - this.moneyProperty.set(this.money); - updateNetWorth(); + if (amount.signum() <= 0) { + throw new IllegalArgumentException("Amount must be positive."); } - /** - * Decreases the player's monetary balance by the given amount. - *

- * The player's monetary balance must not decrease below {@code 0} - * (overdrawn not possible). - *

- * - * @param amount the amount to subtract; must not be negative or {@code null} - * @throws IllegalArgumentException if the amount is not positive, {@code null} - * , or insufficient funds are available - */ - public void withdrawMoney(BigDecimal amount) { - if (amount == null) throw new IllegalArgumentException("Amount must not be null."); - if (amount.signum() <= 0) throw new IllegalArgumentException("Amount must be positive."); - if (this.money.subtract(amount).signum() < 0) throw new IllegalArgumentException("Insufficient funds."); - this.money = this.money.subtract(amount); - this.moneyProperty.set(this.money); - updateNetWorth(); + if (this.money.subtract(amount).signum() < 0) { + throw new IllegalArgumentException("Insufficient funds."); } - - /** - * Returns an observable of the player's current monetary funds - * @return an observable of the player's current money - */ - public ObjectProperty getMoneyProperty() { - return moneyProperty; + this.money = this.money.subtract(amount); + this.moneyProperty.set(this.money); + updateNetWorth(); + } + + /** + * Returns an observable of the player's current monetary funds. + * + * @return an observable of the player's current money + */ + public ObjectProperty getMoneyProperty() { + return moneyProperty; + } + + /** + * Return's the player's portfolio of shares. + * + * @return the player's portfolio + */ + public Portfolio getPortfolio() { + return this.portfolio; + } + + /** + * Returns the archive of the player's transaction history. + * + * @return the player's transaction archive + */ + public TransactionArchive getTransactionArchive() { + return this.transactionArchive; + } + + /** + * Returns the player's current net worth. + * + *

The net worth is the sum of the player's current money and + * all the value of their shares in their portfolio.

+ * + * @return the player's current net worth + */ + public BigDecimal getNetWorth() { + return money.add(portfolio.getNetWorth()); + } + + /** + * Returns the current status/title of the player, representing their achievements. + * + * @param exchange the exchange the player is trading at + * @return a string representing the players current status + */ + public String getStatus(Exchange exchange) { + String status = "Novice"; + BigDecimal ratio = getNetWorth().divide(this.startingMoney, RoundingMode.HALF_EVEN); + + if (ratio.compareTo(new BigDecimal("1.20")) >= 0 && exchange.getWeek() >= 10) { + status = "Investor"; } - - /** - * Return's the player's portfolio of shares. - * - * @return the player's portfolio - */ - public Portfolio getPortfolio() { - return this.portfolio; + if (ratio.compareTo(new BigDecimal("2")) >= 0 && exchange.getWeek() >= 20) { + status = "Speculator"; } - /** - * Returns the archive of the player's transaction history. - * - * @return the player's transaction archive - */ - public TransactionArchive getTransactionArchive() { - return this.transactionArchive; - } - - /** - * Returns the player's current net worth. - *

The net worth is the sum of the player's current money and all the value of their shares in their - * portfolio.

- * - * @return the player's current net worth - */ - public BigDecimal getNetWorth() { - return money.add(portfolio.getNetWorth()); - } - - /** - * Returns the current status/title of the player, representing their achievements. - * - * @param exchange the exchange the player is trading at - * @return a string representing the players current status - */ - public String getStatus(Exchange exchange) { - String status = "Novice"; - BigDecimal ratio = getNetWorth().divide(this.startingMoney, RoundingMode.HALF_EVEN); - if (ratio.compareTo(new BigDecimal("1.20")) >= 0 && exchange.getWeek() >= 10) status = "Investor"; - if (ratio.compareTo(new BigDecimal("2")) >= 0 && exchange.getWeek() >= 20) status = "Speculator"; - return status; - } + return status; + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java b/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java index 6a3c5f9..31fda6d 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Portfolio.java @@ -11,155 +11,152 @@ /** * Represents the portfolio of the Player, containing information * about which {@link Share}s are owned. - *

- * A {@code Portfolio} manages a mutable collection of shares, allowing - * shares to be added, removed, and queried. - *

* - *

- * Shares are associated with a unique Transaction, however, multiple - * shares from the same stock can exist in the portfolio. - *

+ *

A {@code Portfolio} manages a mutable collection of shares, allowing + * shares to be added, removed, and queried.

+ * + *

Shares are associated with a unique Transaction, however, multiple + * shares from the same stock can exist in the portfolio.

*/ public class Portfolio { - private final ObservableList shares; - private final ObjectProperty netWorthProperty = + private final ObservableList shares; + private final ObjectProperty netWorthProperty = new SimpleObjectProperty<>(BigDecimal.ZERO); - /** - * Creates an empty portfolio for the player. - */ - public Portfolio() { - this.shares = FXCollections.observableArrayList(); + /** + * Creates an empty portfolio for the player. + */ + public Portfolio() { + this.shares = FXCollections.observableArrayList(); + } + + /** + * Adds a share to the player's portfolio. + * + *

The {@link Stock} must not be {@code null} and the share quantity + * and purchase price must be above zero.

+ * + * @param share the share owned by the player; must not be {@code null} + * @return {@code true} if the share was added successfully, + * {@code false} otherwise + * @throws IllegalArgumentException if {@code share} is {@code null} + */ + public boolean addShare(Share share) { + if (share == null) { + throw new IllegalArgumentException("Share cannot be null"); } - /** - * Adds a share to the player's portfolio. - *

- * The {@link Stock} must not be {@code null} and the share quantity - * and purchase price must be above zero. - *

- * - * @param share the share owned by the player; must not be {@code null} - * @return {@code true} if the share was added successfully, - * {@code false} otherwise - * @throws IllegalArgumentException if {@code share} is {@code null} - */ - public boolean addShare(Share share) { - if (share == null) { - throw new IllegalArgumentException("Share cannot be null"); - } - - this.shares.add(share); - - share.getStock().salesPriceProperty() + this.shares.add(share); + + share.getStock().salesPriceProperty() .subscribe(price -> updateNetWorth()); - updateNetWorth(); - return true; + updateNetWorth(); + + return true; + } + + /** + * Removes a share from the player's portfolio. + * + *

The list must already exist in the portfolio to be successfully + * removed.

+ * + * @param share the share being removed; must not be {@code null} + * @return {@code true} if the share removal was successful, + * {@code false} otherwise. + * @throws IllegalArgumentException if {@code share} is {@code null} + */ + public boolean removeShare(Share share) { + if (share == null) { + throw new IllegalArgumentException("Share cannot be null"); } - /** - * Removes a share from the player's portfolio. - *

- * The list must already exist in the portfolio to be successfully - * removed. - *

- * - * @param share the share being removed; must not be {@code null} - * @return {@code true} if the share removal was successful, - * {@code false} otherwise. - * @throws IllegalArgumentException if {@code share} is {@code null} - */ - public boolean removeShare(Share share) { - if (share == null) { - throw new IllegalArgumentException("Share cannot be null"); - } - - boolean removed = this.shares.removeIf(s -> s.equals(share)); - - if (removed) { - updateNetWorth(); - } - - return removed; - } + boolean removed = this.shares.removeIf(s -> s.equals(share)); - /** - * Returns a list of the shares currently in the player's portfolio. - * - * @return a list of the shares in this portfolio - */ - public ObservableList getShares() { - return this.shares; + if (removed) { + updateNetWorth(); } - /** - * Returns a list of the shares that belongs to the stock with the - * given ticker symbol. - * - * @param symbol the ticker symbol of the stock to search for; must not be - * {@code null} or blank - * @return a list of the player's shares in the stock with the given - * stock symbol; an empty list if none are found - * @throws IllegalArgumentException if {@code symbol} is {@code null} or blank - */ - public List getShares(String symbol) { - if (symbol == null || symbol.isBlank()) { - throw new IllegalArgumentException("Symbol cannot be null or blank"); - } - - var tempSymbols = new ArrayList(); - for (Share s : this.shares) { - if (s.getStock().getSymbol().equals(symbol)) { - tempSymbols.add(s); - } - } - return tempSymbols; + return removed; + } + + /** + * Returns a list of the shares currently in the player's portfolio. + * + * @return a list of the shares in this portfolio + */ + public ObservableList getShares() { + return this.shares; + } + + /** + * Returns a list of the shares that belongs to the stock with the + * given ticker symbol. + * + * @param symbol the ticker symbol of the stock to search for; must not be + * {@code null} or blank + * @return a list of the player's shares in the stock with the given + * stock symbol; an empty list if none are found + * @throws IllegalArgumentException if {@code symbol} is {@code null} or blank + */ + public List getShares(String symbol) { + if (symbol == null || symbol.isBlank()) { + throw new IllegalArgumentException("Symbol cannot be null or blank"); } - /** - * Determines whether the player's portfolio contains the specified - * share. - * - * @param share the share to search for in this portfolio - * @return {@code true} if the player's portfolio contains the share - * {@code false} otherwise - */ - public boolean contains(Share share) { - return shares.stream().anyMatch(s -> s.equals(share)); + var tempSymbols = new ArrayList(); + for (Share s : this.shares) { + + if (s.getStock().getSymbol().equals(symbol)) { + tempSymbols.add(s); + } } - /** - * Calculates the net total value of the shares in the portfolio. - *

- * Portfolio value is based on current market price, not transaction rules. - *

- * - * @return the total market value of the portfolio - */ - public BigDecimal getNetWorth() { - return this.shares.stream() + return tempSymbols; + } + + /** + * Determines whether the player's portfolio contains the specified + * share. + * + * @param share the share to search for in this portfolio + * @return {@code true} if the player's portfolio contains the share + * {@code false} otherwise + */ + public boolean contains(Share share) { + return shares.stream().anyMatch(s -> s.equals(share)); + } + + /** + * Calculates the net total value of the shares in the portfolio. + * + *

Portfolio value is based on current market price, not transaction rules.

+ * + * @return the total market value of the portfolio + */ + public BigDecimal getNetWorth() { + return this.shares.stream() .map(share -> share.getStock().getSalesPrice() .multiply(share.getQuantity()) ) .reduce(BigDecimal.ZERO, BigDecimal::add); - } - - /** - * Returns an {@link ObjectProperty} of the net worth of the portfolio - * - * @return an observable of the portfolio's net worth - */ - public ObjectProperty netWorthProperty() { - return netWorthProperty; - } - - /** - * Mirrors the portfolio's net worth to the observable. - */ - private void updateNetWorth() { - netWorthProperty.set(getNetWorth()); - } + } + + /** + * Returns an {@link ObjectProperty} of the net worth of the portfolio. + * + * @return an observable of the portfolio's net worth + */ + public ObjectProperty netWorthProperty() { + return netWorthProperty; + } + + /** + * Mirrors the portfolio's net worth to the observable. + */ + private void updateNetWorth() { + netWorthProperty.set(getNetWorth()); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Purchase.java b/millions/src/main/java/no/ntnu/gruppe53/model/Purchase.java index dbc9a5d..ddb3ada 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Purchase.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Purchase.java @@ -4,9 +4,9 @@ /** * Represents a purchase {@link Transaction} where a {@link Player} purchases - * a {@link Share} - *

- * A purchase can only be commited if: + * a {@link Share}. + * + *

A purchase can only be commited if: *

    *
  • The player has sufficient funds.
  • *
  • The transaction has not already been committed.
  • @@ -14,49 +14,50 @@ *

    */ -public class Purchase extends Transaction{ - - /** - * Constructs a purchase transaction for the given share and week. - * - * @param share the share to be purchased; must not be {@code null} - * @param week the week when the purchase transaction occurs; must be 1 or greater - */ - - public Purchase(Share share, int week) { - super(share, week, new PurchaseCalculator(share)); +public class Purchase extends Transaction { + + /** + * Constructs a purchase transaction for the given share and week. + * + * @param share the share to be purchased; must not be {@code null} + * @param week the week when the purchase transaction occurs; must be 1 or greater + */ + + public Purchase(Share share, int week) { + super(share, week, new PurchaseCalculator(share)); + } + + /** + * Commits the purchase to the given player, making the + * transaction unique. + * + *

    If the transaction is not already committed, and the player + * has sufficient funds, the transaction will: + *

      + *
    • Withdraw money from the player
    • + *
    • Add the share to the player's {@link Portfolio}
    • + *
    • Archive the transaction in the player's + * {@link TransactionArchive}
    • + *
    + *

    + * + * @param player the player performing the transaction; must not be {@code null} + * @throws IllegalArgumentException if {@code player} is {@code null} + */ + + @Override + public void commit(Player player) { + if (player == null) { + throw new IllegalArgumentException("Player cannot be null."); } - /** - * Commits the purchase to the given player, making the - * transaction unique. - *

    - * If the transaction is not already committed, and the player - * has sufficient funds, the transaction will: - *

      - *
    • Withdraw money from the player
    • - *
    • Add the share to the player's {@link Portfolio}
    • - *
    • Archive the transaction in the player's - * {@link TransactionArchive}
    • - *
    - *

    - * @param player the player performing the transaction; must not be {@code null} - * @throws IllegalArgumentException if {@code player} is {@code null} - */ - - @Override - public void commit(Player player) { - if (player == null) { - throw new IllegalArgumentException("Player cannot be null."); - } - - if (!this.isCommitted() && ((player.getMoney().subtract(this.getCalculator().calculateTotal())) + if (!this.isCommitted() && ((player.getMoney().subtract(this.getCalculator().calculateTotal())) .compareTo(BigDecimal.ZERO) >= 0)) { - player.withdrawMoney(this.getCalculator().calculateTotal()); - player.getPortfolio().addShare(this.getShare()); - player.getTransactionArchive().add(this); - super.commit(player); - } + player.withdrawMoney(this.getCalculator().calculateTotal()); + player.getPortfolio().addShare(this.getShare()); + player.getTransactionArchive().add(this); + super.commit(player); } + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java index a98e809..08865b9 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseCalculator.java @@ -1,115 +1,110 @@ package no.ntnu.gruppe53.model; +import java.math.BigDecimal; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; -import java.math.BigDecimal; - /** - * Calculates the costs associated with a {@link Purchase} of a given amount of {@link Share}s on the - * {@link Exchange}. + * Calculates the costs associated with a {@link Purchase} of a given + * amount of {@link Share}s on the {@link Exchange}. */ -public class PurchaseCalculator implements TransactionCalculator{ - private final BigDecimal purchasePrice; - private final BigDecimal quantity; - - //Objectproperties - private final ObjectProperty grossProperty = new SimpleObjectProperty<>(); - private final ObjectProperty commissionProperty = new SimpleObjectProperty<>(); - private final ObjectProperty totalProperty = new SimpleObjectProperty<>(); - - - /** - * Sets the purchase price and quantity based on data from the given share - *

    Also sets up the ObjectProperties of the different calculations such - * that Subscriptions can listen to the calculations

    - * @param share the share to calculate purchase costs for - */ - public PurchaseCalculator(Share share) { - - this.purchasePrice = share.getPurchasePrice(); - this.quantity = share.getQuantity(); - - this.grossProperty.set(calculateGross()); - this.commissionProperty.set(calculateCommission()); - this.totalProperty.set(calculateTotal()); - - } - - /** - * Calculates the gross purchase price of the given share. - * - * @return the gross associated cost of purchasing the share - */ - @Override - public BigDecimal calculateGross(){ - - return this.quantity.multiply(this.purchasePrice); - } - - /** - * Calculates the commission of the exchange for purchasing a share. - *

    Commission is set to a fixed 0.5% of gross purchase price.

    - * - * @return the commission taken by the exchange - */ - @Override +public class PurchaseCalculator implements TransactionCalculator { + private final BigDecimal purchasePrice; + private final BigDecimal quantity; + + // Objectproperties + private final ObjectProperty grossProperty = new SimpleObjectProperty<>(); + private final ObjectProperty commissionProperty = new SimpleObjectProperty<>(); + private final ObjectProperty totalProperty = new SimpleObjectProperty<>(); + + /** + * Sets the purchase price and quantity based on data from the given share. + * + *

    Also sets up the ObjectProperties of the different calculations such + * that Subscriptions can listen to the calculations

    + * + * @param share the share to calculate purchase costs for + */ + public PurchaseCalculator(Share share) { + this.purchasePrice = share.getPurchasePrice(); + this.quantity = share.getQuantity(); + + this.grossProperty.set(calculateGross()); + this.commissionProperty.set(calculateCommission()); + this.totalProperty.set(calculateTotal()); + + } + + /** + * Calculates the gross purchase price of the given share. + * + * @return the gross associated cost of purchasing the share + */ + @Override + public BigDecimal calculateGross() { + return this.quantity.multiply(this.purchasePrice); + } + + /** + * Calculates the commission of the exchange for purchasing a share. + * + *

    Commission is set to a fixed 0.5% of gross purchase price.

    + * + * @return the commission taken by the exchange + */ + @Override public BigDecimal calculateCommission() { - BigDecimal commission = new BigDecimal("0.005"); - - - - return calculateGross().multiply(commission); - } - - /** - * Calculates the tax for a purchase. - *

    For a purchase, there is no tax.

    - * - * @return the tax for a purchase, in this case zero - */ - @Override - public BigDecimal calculateTax() { - return new BigDecimal(0); - } - - /** - * Calculates the total costs associated with the purchase of a share - * - * @return the net total cost of purchasing a share - */ - @Override - public BigDecimal calculateTotal() { - - - return calculateGross().add(calculateCommission()).add(calculateTax()); - } - - /** - * Returns an observable of the calculated gross - * @return an observable of the calculated gross - */ - public ObjectProperty getGrossProperty() { - return grossProperty; - } - - /** - * Returns an observable of the calculated commission - * @return an observable of the calculated commission - */ - public ObjectProperty getCommissionProperty() { - return commissionProperty; - } - - /** - * Returns an observable of the calculated total - * @return an observable of the calculated total - */ - public ObjectProperty getTotalProperty() { - return totalProperty; - } - - - - + BigDecimal commission = new BigDecimal("0.005"); + + return calculateGross().multiply(commission); + } + + /** + * Calculates the tax for a purchase. + * + *

    For a purchase, there is no tax.

    + * + * @return the tax for a purchase, in this case zero + */ + @Override + public BigDecimal calculateTax() { + return new BigDecimal(0); + } + + /** + * Calculates the total costs associated with the purchase of a share. + * + * @return the net total cost of purchasing a share + */ + @Override + public BigDecimal calculateTotal() { + return calculateGross().add(calculateCommission()).add(calculateTax()); + } + + /** + * Returns an observable of the calculated gross. + * + * @return an observable of the calculated gross + */ + public ObjectProperty getGrossProperty() { + return grossProperty; + } + + /** + * Returns an observable of the calculated commission. + * + * @return an observable of the calculated commission + */ + public ObjectProperty getCommissionProperty() { + return commissionProperty; + } + + /** + * Returns an observable of the calculated total. + * + * @return an observable of the calculated total + */ + public ObjectProperty getTotalProperty() { + return totalProperty; + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseFactory.java b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseFactory.java index ffce90a..a05169f 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseFactory.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/PurchaseFactory.java @@ -3,17 +3,17 @@ /** * Creates a {@link Purchase} {@link Transaction} of a {@link Share}. */ -public class PurchaseFactory extends TransactionFactory{ +public class PurchaseFactory extends TransactionFactory { - /** - * Returns the purchase transaction for the given share and week. - * - * @param share the share to be purchased - * @param week the week of purchase - * @return the purchase transaction - */ - @Override - protected Transaction createTransaction(Share share, int week) { - return new Purchase(share, week); - } + /** + * Returns the purchase transaction for the given share and week. + * + * @param share the share to be purchased + * @param week the week of purchase + * @return the purchase transaction + */ + @Override + protected Transaction createTransaction(Share share, int week) { + return new Purchase(share, week); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java b/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java index 732ec1f..ff418ee 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Sale.java @@ -2,9 +2,9 @@ /** * Represents a sale {@link Transaction} where a {@link Player} sells - * a {@link Share} - *

    - * A sale can only be commited if: + * a {@link Share}. + * + *

    A sale can only be commited if: *

      *
    • The share is in the players {@link Portfolio}
    • *
    • The transaction has not already committed
    • @@ -12,52 +12,54 @@ *

      */ -public class Sale extends Transaction{ +public class Sale extends Transaction { - /** - * Constructs a sale transaction for the given share and week. - * - * @param share the share to be sold; must not be {@code null} - * @param week the week when the sale transaction occurs; must be greater than 0 - */ + /** + * Constructs a sale transaction for the given share and week. + * + * @param share the share to be sold; must not be {@code null} + * @param week the week when the sale transaction occurs; must be greater than 0 + */ - public Sale(Share share, int week) { - super(share, week, new SaleCalculator(share)); + public Sale(Share share, int week) { + super(share, week, new SaleCalculator(share)); + } + + /** + * Commits the sale to the given player, making the + * transaction unique. + * + *

      If the player ows the share, the transaction will: + *

        + *
      • Add money to the player
      • + *
      • Remove the share from the player's portfolio
      • + *
      • Archive the transaction in the player's {@link TransactionArchive}
      • + *
      + *

      + * + * @param player the player performing the transaction; must not be {@code null} + * @throws IllegalArgumentException if {@code player} is {@code null} + * @throws IllegalStateException if trying to sell a share not in the player's portfolio or + * if transactions has already been committed. + */ + + @Override + public void commit(Player player) { + if (player == null) { + throw new IllegalArgumentException("Player cannot be null"); } - /** - * Commits the sale to the given player, making the - * transaction unique. - *

      - * If the player ows the share, the transaction will: - *

        - *
      • Add money to the player
      • - *
      • Remove the share from the player's portfolio
      • - *
      • Archive the transaction in the player's {@link TransactionArchive}
      • - *
      - *

      - * @param player the player performing the transaction; must not be {@code null} - * @throws IllegalArgumentException if {@code player} is {@code null} - * @throws IllegalStateException if trying to sell a share not in the player's portfolio or if transactions - * has already been committed. - */ - - @Override - public void commit(Player player) { - if (player == null) { - throw new IllegalArgumentException("Player cannot be null"); - } - - if (this.isCommitted()) { - throw new IllegalStateException("Transaction has already been committed."); - } - if (!player.getPortfolio().contains(this.getShare())) { - throw new IllegalStateException("You do not own this share in your portfolio."); - } - player.addMoney(this.getCalculator().calculateTotal()); - player.getPortfolio().removeShare(this.getShare()); - player.getTransactionArchive().add(this); - super.commit(player); + if (this.isCommitted()) { + throw new IllegalStateException("Transaction has already been committed."); + } + if (!player.getPortfolio().contains(this.getShare())) { + throw new IllegalStateException("You do not own this share in your portfolio."); } -} + + player.addMoney(this.getCalculator().calculateTotal()); + player.getPortfolio().removeShare(this.getShare()); + player.getTransactionArchive().add(this); + super.commit(player); + } +} \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java index c6afe07..a108d86 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/SaleCalculator.java @@ -1,146 +1,153 @@ package no.ntnu.gruppe53.model; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; - import java.math.BigDecimal; import java.math.RoundingMode; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; /** * Calculates the costs associated with the {@link Sale} of a {@link Share} on the {@link Exchange}, * and the net total gain of selling said share. */ public class SaleCalculator implements TransactionCalculator { - - private final BigDecimal purchasePrice; - private final BigDecimal salesPrice; - private final BigDecimal quantity; - - //Objectproperties - private final ObjectProperty grossProperty = new SimpleObjectProperty<>(); - private final ObjectProperty commissionProperty = new SimpleObjectProperty<>(); - private final ObjectProperty taxProperty = new SimpleObjectProperty<>(); - private final ObjectProperty totalProperty = new SimpleObjectProperty<>(); - private final ObjectProperty profitProperty = new SimpleObjectProperty<>(); - - /** - * Sets the quantity, purchase and sales price based on the given share. - * - * @param share the share to calculate the costs and net total of a sale - */ - public SaleCalculator(Share share) { - this.purchasePrice = share.getPurchasePrice(); - this.quantity = share.getQuantity(); - this.salesPrice = share.getStock().getSalesPrice(); - - this.grossProperty.set(calculateGross()); - this.commissionProperty.set(calculateCommission()); - this.taxProperty.set(calculateTax()); - this.totalProperty.set(calculateTotal()); - this.profitProperty.set(calculateTotal().subtract(purchasePrice.multiply(quantity))); - } - - /** - * Calculates the gross sales price of the given share. - * - * @return the gross associated gain from selling the share - */ - @Override - public BigDecimal calculateGross() { - return quantity.multiply(salesPrice) + private final BigDecimal purchasePrice; + private final BigDecimal salesPrice; + private final BigDecimal quantity; + + // Objectproperties + private final ObjectProperty grossProperty = new SimpleObjectProperty<>(); + private final ObjectProperty commissionProperty = new SimpleObjectProperty<>(); + private final ObjectProperty taxProperty = new SimpleObjectProperty<>(); + private final ObjectProperty totalProperty = new SimpleObjectProperty<>(); + private final ObjectProperty profitProperty = new SimpleObjectProperty<>(); + + /** + * Sets the quantity, purchase and sales price based on the given share. + * + * @param share the share to calculate the costs and net total of a sale + */ + public SaleCalculator(Share share) { + this.purchasePrice = share.getPurchasePrice(); + this.quantity = share.getQuantity(); + this.salesPrice = share.getStock().getSalesPrice(); + + this.grossProperty.set(calculateGross()); + this.commissionProperty.set(calculateCommission()); + this.taxProperty.set(calculateTax()); + this.totalProperty.set(calculateTotal()); + this.profitProperty.set(calculateTotal().subtract(purchasePrice.multiply(quantity))); + } + + /** + * Calculates the gross sales price of the given share. + * + * @return the gross associated gain from selling the share + */ + @Override + public BigDecimal calculateGross() { + return quantity.multiply(salesPrice) .setScale(2, RoundingMode.HALF_UP); - } - - /** - * Calculates the exchange's commission for selling the share. - *

      The commission is set to a fixed 1% of the gross total sale value.

      - * - * @return the commission taken by the exchange - */ - @Override + } + + /** + * Calculates the exchange's commission for selling the share. + * + *

      The commission is set to a fixed 1% of the gross total sale value.

      + * + * @return the commission taken by the exchange + */ + @Override public BigDecimal calculateCommission() { - BigDecimal commissionRate = new BigDecimal("0.01"); + BigDecimal commissionRate = new BigDecimal("0.01"); - return calculateGross() + return calculateGross() .multiply(commissionRate) .setScale(2, RoundingMode.HALF_UP); - } + } - /** - * Calculates the taxable profit from the sale. - *

      Only calculates tax for profitable sales.

      > - * - * @return the tax amount - */ - @Override - public BigDecimal calculateTax() { + /** + * Calculates the taxable profit from the sale. + * + *

      Only calculates tax for profitable sales.

      > + * + * @return the tax amount + */ + @Override + public BigDecimal calculateTax() { - BigDecimal taxRate = new BigDecimal("0.30"); + BigDecimal taxRate = new BigDecimal("0.30"); - BigDecimal originalCost = purchasePrice.multiply(quantity); + BigDecimal originalCost = purchasePrice.multiply(quantity); - BigDecimal profit = calculateGross() + BigDecimal profit = calculateGross() .subtract(calculateCommission()) .subtract(originalCost); - if (profit.compareTo(BigDecimal.ZERO) <= 0) { - return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); - } + if (profit.compareTo(BigDecimal.ZERO) <= 0) { + return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP); + } - return profit.multiply(taxRate) + return profit.multiply(taxRate) .setScale(2, RoundingMode.HALF_UP); - } + } - /** - * Returns the net total gain for selling the share. - * - * @return the net total winnings of selling the share - */ - @Override - public BigDecimal calculateTotal() { + /** + * Returns the net total gain for selling the share. + * + * @return the net total winnings of selling the share + */ + @Override + public BigDecimal calculateTotal() { - return calculateGross() + return calculateGross() .subtract(calculateCommission()) .subtract(calculateTax()) .setScale(2, RoundingMode.HALF_UP); - } - - /** - * Returns an observable of the calculated gross - * @return an observable of the calculated gross - */ - public ObjectProperty getGrossProperty() { - return grossProperty; - } - - /** - * Returns an observable of the calculated commission - * @return an observable of the calculated commission - */ - public ObjectProperty getCommissionProperty() { - return commissionProperty; - } - - /** - * Returns an observable of the calculated tax - * @return an observable of the calculated tax - */ - public ObjectProperty getTaxProperty() { - return taxProperty; - } - - /** - * Returns an observable of the calculated total - * @return an observable of the calculated total - */ - public ObjectProperty getTotalProperty() { - return totalProperty; - } - - /** - * Returns an observable of the calculated profit - * @return an observable of the calculated profit - */ - public ObjectProperty getProfitProperty() {return profitProperty;} + } + + /** + * Returns an observable of the calculated gross. + * + * @return an observable of the calculated gross + */ + public ObjectProperty getGrossProperty() { + return grossProperty; + } + + /** + * Returns an observable of the calculated commission. + * + * @return an observable of the calculated commission + */ + public ObjectProperty getCommissionProperty() { + return commissionProperty; + } + + /** + * Returns an observable of the calculated tax. + * + * @return an observable of the calculated tax + */ + public ObjectProperty getTaxProperty() { + return taxProperty; + } + + /** + * Returns an observable of the calculated total. + * + * @return an observable of the calculated total + */ + public ObjectProperty getTotalProperty() { + return totalProperty; + } + + /** + * Returns an observable of the calculated profit. + * + * @return an observable of the calculated profit + */ + public ObjectProperty getProfitProperty() { + return profitProperty; + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/SaleFactory.java b/millions/src/main/java/no/ntnu/gruppe53/model/SaleFactory.java index 2340e5c..9128a74 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/SaleFactory.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/SaleFactory.java @@ -5,15 +5,15 @@ */ public class SaleFactory extends TransactionFactory { - /** - * Returns the sale transaction of the given share and week. - * - * @param share the share to be sold - * @param week the week of the sale - * @return the sale transaction - */ - @Override - protected Transaction createTransaction(Share share, int week) { - return new Sale(share, week); - } + /** + * Returns the sale transaction of the given share and week. + * + * @param share the share to be sold + * @param week the week of the sale + * @return the sale transaction + */ + @Override + protected Transaction createTransaction(Share share, int week) { + return new Sale(share, week); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Share.java b/millions/src/main/java/no/ntnu/gruppe53/model/Share.java index f2ec731..229c473 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Share.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Share.java @@ -4,72 +4,73 @@ /** * Represents a holding of a specific {@link Stock}. - *

      - * A {@code Share} contains the quantity of shares owned and + * + *

      A {@code Share} contains the quantity of shares owned and * the price per share at the time of purchase. Future price * fluctuations are not included in share. *

      */ public class Share { - private final Stock stock; - private final BigDecimal quantity; - private final BigDecimal purchasePrice; + private final Stock stock; + private final BigDecimal quantity; + private final BigDecimal purchasePrice; - /** - * Creates a share for a given stock. - * - * @param stock the stock associated with the share; must not be {@code null} - * @param quantity the number of shares purchased; must not be {@code null}, negative, or zero - * @param purchasePrice price per share at the time of purchase; must not be {@code null}, negative or zero - * @throws IllegalArgumentException if {@code stock} is {@code null}, or {@code quantity} is {@code null}, - * negative, or zero, or {@code purchasePrice} is {@code null}, negative, - * or zero - */ - - public Share(Stock stock, BigDecimal quantity, BigDecimal purchasePrice) { - if (stock == null) { - throw new IllegalArgumentException("Stock cannot be null."); - } - - if (quantity == null || quantity.signum() <= 0) { - throw new IllegalArgumentException("Quantity cannot be null, negative, or zero."); - } - - if (purchasePrice == null || purchasePrice.signum() <= 0) { - throw new IllegalArgumentException("PurchasePrice cannot be null, negative, or zero."); - } + /** + * Creates a share for a given stock. + * + * @param stock the stock associated with the share; must not be {@code null} + * @param quantity the number of shares purchased; must not be {@code null}, negative, or zero + * @param purchasePrice price per share at the time of purchase; + * must not be {@code null}, negative or zero + * @throws IllegalArgumentException if {@code stock} is {@code null}, + * or {@code quantity} is {@code null}, + * negative, or zero, or {@code purchasePrice} + * is {@code null}, negative, or zero + */ + public Share(Stock stock, BigDecimal quantity, BigDecimal purchasePrice) { + if (stock == null) { + throw new IllegalArgumentException("Stock cannot be null."); + } - this.stock = stock; - this.quantity = quantity; - this.purchasePrice = purchasePrice; + if (quantity == null || quantity.signum() <= 0) { + throw new IllegalArgumentException("Quantity cannot be null, negative, or zero."); } - /** - * Returns the stock the share is associated with. - * - * @return the stock - */ - public Stock getStock() { - return stock; + if (purchasePrice == null || purchasePrice.signum() <= 0) { + throw new IllegalArgumentException("PurchasePrice cannot be null, negative, or zero."); } - /** - * Returns the quantity of shares owned in the stock. - * - * @return the quantity of shares - */ + this.stock = stock; + this.quantity = quantity; + this.purchasePrice = purchasePrice; + } - public BigDecimal getQuantity() { - return quantity; - } + /** + * Returns the stock the share is associated with. + * + * @return the stock + */ + public Stock getStock() { + return stock; + } - /** - * Returns the purchase price per share for the stock at time of purchase. - * - * @return the purchase price of the shares. - */ - public BigDecimal getPurchasePrice() { - return purchasePrice; - } + /** + * Returns the quantity of shares owned in the stock. + * + * @return the quantity of shares + */ + + public BigDecimal getQuantity() { + return quantity; + } + + /** + * Returns the purchase price per share for the stock at time of purchase. + * + * @return the purchase price of the shares. + */ + public BigDecimal getPurchasePrice() { + return purchasePrice; + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java b/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java index 6fe5424..04c53c8 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Stock.java @@ -1,162 +1,163 @@ package no.ntnu.gruppe53.model; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleObjectProperty; - import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleObjectProperty; /** * Represents a stock with its ticker symbol, company name, * and a list of sales prices. - *

      - * The list is a historical representation of price changes + * + *

      The list is a historical representation of price changes * over time for the given stock. *

      - *

      - * The most recently added price represents the current market price. - *

      * - *

      - * A {@code Stock} instance always contains at least one price, - * instantiated at construction. + *

      The most recently added price represents the current market price. *

      + * + *

      A {@code Stock} instance always contains at least one price, + * instantiated at construction.

      */ public class Stock { - private final String symbol; - private final String company; - private final List prices; - - private final ObjectProperty salesPrice; - - /** - * Creates a stock with an already defined sales price. - * - * @param symbol the stock's unique ticker symbol (e.g. "AAPL"); must not be {@code null} or blank - * @param company the full name of the company; must not be {@code null} or blank - * @param salesPrice the current price per share; must not be {@code null}, negative, or zero - * @throws IllegalArgumentException if {@code symbol} is {@code null} or blank, {@code company} is - * {@code null} or blank, or {@code salesPrice} is {@code null}, - * negative, or zero - */ - public Stock(String symbol, String company, BigDecimal salesPrice) { - if (symbol == null || symbol.isBlank()) { - throw new IllegalArgumentException("Symbol cannot be null or empty."); - } - if (company == null || company.isBlank()) { - throw new IllegalArgumentException("Company cannot be null or empty."); - } - if (salesPrice == null || salesPrice.signum() <= 0) { - throw new IllegalArgumentException("SalesPrice cannot be null, negative, or zero."); - } - - this.symbol = symbol; - this.company = company; - this.prices = new ArrayList<>(); - this.prices.add(salesPrice); - - this.salesPrice = new SimpleObjectProperty<>(salesPrice); - } - - /** - * Returns the stock's unique ticker symbol. - * - * @return the stock's ticker symbol - */ - public String getSymbol() { - return symbol; - } - - /** - * Returns the full name of the company associated with the stock. - * - * @return the full name of the stock's company - */ - public String getCompany() { - return company; - } - - /** - * Returns the current sale price per share of the stock. - *

      - * The current price is defined as the most recent price in the price history. - *

      - * @return the current sale price of the stock - */ - public BigDecimal getSalesPrice() { - return salesPrice.get(); - } - - /** - * Adds a new sales price to the stock's price history. - *

      - * The added price becomes the new current market price. - *

      - * - *

      - * The new price must be positive. - *

      - * @param price the new market price per share of the stock; must not be {@code null}, negative or zero - * @throws IllegalArgumentException if {@code price} is {@code null}, negative, or zero - */ - public void addNewSalesPrice(BigDecimal price) { - if (price == null || price.signum() <= 0) { - throw new IllegalArgumentException("Price cannot be null, negative, or zero."); - } - this.prices.add(price); - this.salesPrice.set(price); - } - - - /** - * Returns an {@link ObjectProperty} of the sales price. - * - * @return an observable of the sales price - */ - public ObjectProperty salesPriceProperty() { - return salesPrice; + private final String symbol; + private final String company; + private final List prices; + + private final ObjectProperty salesPrice; + + /** + * Creates a stock with an already defined sales price. + * + * @param symbol the stock's unique ticker symbol (e.g. "AAPL"); must not be {@code null} or blank + * @param company the full name of the company; must not be {@code null} or blank + * @param salesPrice the current price per share; must not be {@code null}, negative, or zero + * @throws IllegalArgumentException if {@code symbol} is {@code null} or blank, {@code company} is + * {@code null} or blank, or {@code salesPrice} is {@code null}, + * negative, or zero + */ + public Stock( + String symbol, String company, BigDecimal salesPrice) { + + if (symbol == null || symbol.isBlank()) { + throw new IllegalArgumentException("Symbol cannot be null or empty."); } - /** - * Returns a list of all the sales prices for this stock since (and including) week 1. - * - * @return a list of all the stock's sales prices - */ - public List getHistoricalPrices() { - return prices; + if (company == null || company.isBlank()) { + throw new IllegalArgumentException("Company cannot be null or empty."); } - /** - * Returns the maximum historical sales price since (and including) week 1 for the stock. - * - * @return the stock's highest sales price - */ - public BigDecimal getHighestPrice() { - return Collections.max(prices); + if (salesPrice == null || salesPrice.signum() <= 0) { + throw new IllegalArgumentException("SalesPrice cannot be null, negative, or zero."); } - /** - * Returns the minimum historical sales price since (and including) week 1 for the stock. - * - * @return the stock's lowest sales price - */ - public BigDecimal getLowestPrice() { - return Collections.min(prices); + this.symbol = symbol; + this.company = company; + this.prices = new ArrayList<>(); + this.prices.add(salesPrice); + + this.salesPrice = new SimpleObjectProperty<>(salesPrice); + } + + /** + * Returns the stock's unique ticker symbol. + * + * @return the stock's ticker symbol + */ + public String getSymbol() { + return symbol; + } + + /** + * Returns the full name of the company associated with the stock. + * + * @return the full name of the stock's company + */ + public String getCompany() { + return company; + } + + /** + * Returns the current sale price per share of the stock. + * + *

      The current price is defined as the most recent price + * in the price history.

      + * + * @return the current sale price of the stock + */ + public BigDecimal getSalesPrice() { + return salesPrice.get(); + } + + /** + * Adds a new sales price to the stock's price history. + * + *

      The added price becomes the new current market price.

      + * + *

      The new price must be positive.

      + * + * @param price the new market price per share of the stock; + * must not be {@code null}, negative or zero + * @throws IllegalArgumentException if {@code price} is {@code null}, negative, or zero + */ + public void addNewSalesPrice(BigDecimal price) { + if (price == null || price.signum() <= 0) { + throw new IllegalArgumentException("Price cannot be null, negative, or zero."); } - /** - * Returns the sales price increase/decrease from previous week to this week. Returns 0 if - * the price list only has 1 value. - * - * @return the net difference in sales price between the last and second last week - */ - public BigDecimal getLatestPriceChange() { - if (prices.size() >= 2) { - return prices.getLast().subtract(prices.get(prices.size() - 2)); - } else { - return BigDecimal.ZERO; - } + this.prices.add(price); + this.salesPrice.set(price); + } + + /** + * Returns an {@link ObjectProperty} of the sales price. + * + * @return an observable of the sales price + */ + public ObjectProperty salesPriceProperty() { + return salesPrice; + } + + /** + * Returns a list of all the sales prices for this stock since (and including) week 1. + * + * @return a list of all the stock's sales prices + */ + public List getHistoricalPrices() { + return prices; + } + + /** + * Returns the maximum historical sales price since (and including) week 1 for the stock. + * + * @return the stock's highest sales price + */ + public BigDecimal getHighestPrice() { + return Collections.max(prices); + } + + /** + * Returns the minimum historical sales price since (and including) week 1 for the stock. + * + * @return the stock's lowest sales price + */ + public BigDecimal getLowestPrice() { + return Collections.min(prices); + } + + /** + * Returns the sales price increase/decrease from previous week to this week. Returns 0 if + * the price list only has 1 value. + * + * @return the net difference in sales price between the last and second last week + */ + public BigDecimal getLatestPriceChange() { + if (prices.size() >= 2) { + return prices.getLast().subtract(prices.get(prices.size() - 2)); + } else { + return BigDecimal.ZERO; } + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/Transaction.java b/millions/src/main/java/no/ntnu/gruppe53/model/Transaction.java index bb661c0..ee2f11c 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/Transaction.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/Transaction.java @@ -2,98 +2,93 @@ /** * Represents a financial transaction of a {@link Share}. - *

      - * A transaction occurs in a specific week and uses a + * + *

      A transaction occurs in a specific week and uses a * {@link TransactionCalculator} to determine the monetary change. * Transactions are unique and is committed to a {@link Player}'s - * {@link Portfolio}. - *

      + * {@link Portfolio}.

      * - *

      - * Subclasses define the nature of the transaction (e.g. purchase - * or sale) by implementing the {@link #commit(Player)} - *

      + *

      Subclasses define the nature of the transaction (e.g. purchase + * or sale) by implementing the {@link #commit(Player)}

      */ +public abstract class Transaction { + private final Share share; + private final int week; + private final TransactionCalculator calculator; + protected boolean committed; -abstract public class Transaction { - private final Share share; - private final int week; - private final TransactionCalculator calculator; - protected boolean committed; - - /** - * Constructor for a transaction. - * - * @param share the share for the transaction; must not be {@code null} - * @param week the week the transaction takes place; must be greater than 0 - * @param calculator the transaction calculator used to - * compute the {@link Transaction} - * @throws IllegalArgumentException if {@code share} is {@code null} or {@code week} - * is less than 1 - */ - - protected Transaction(Share share, int week, TransactionCalculator calculator) { - if (week <= 0) { - throw new IllegalArgumentException("Week cannot be negative or zero"); - } + /** + * Constructor for a transaction. + * + * @param share the share for the transaction; must not be {@code null} + * @param week the week the transaction takes place; must be greater than 0 + * @param calculator the transaction calculator used to + * compute the {@link Transaction} + * @throws IllegalArgumentException if {@code share} is {@code null} or {@code week} + * is less than 1 + */ - this.share = share; - this.week = week; - this.calculator = calculator; - this.committed = false; + protected Transaction(Share share, int week, TransactionCalculator calculator) { + if (week <= 0) { + throw new IllegalArgumentException("Week cannot be negative or zero"); } - /** - * Returns the share for the current transaction. - * - * @return the share belonging to the transaction - */ - public Share getShare() { - return this.share; - } + this.share = share; + this.week = week; + this.calculator = calculator; + this.committed = false; + } - /** - * Returns the week the transaction took place. - * - * @return the week for the transaction - */ - public int getWeek() { - return this.week; - } + /** + * Returns the share for the current transaction. + * + * @return the share belonging to the transaction + */ + public Share getShare() { + return this.share; + } - /** - * Returns the transaction calculator used to calculate - * the transaction. - * - * @return the transaction calculator used for the transaction - */ + /** + * Returns the week the transaction took place. + * + * @return the week for the transaction + */ + public int getWeek() { + return this.week; + } - public TransactionCalculator getCalculator() { - return this.calculator; - } + /** + * Returns the transaction calculator used to calculate + * the transaction. + * + * @return the transaction calculator used for the transaction + */ - /** - * Returns whether the transaction has been committed to a player. - * - * @return {@code true} if committed, {@code false} otherwise - */ + public TransactionCalculator getCalculator() { + return this.calculator; + } - public boolean isCommitted() { - return this.committed; - } + /** + * Returns whether the transaction has been committed to a player. + * + * @return {@code true} if committed, {@code false} otherwise + */ - /** - * Commits the transaction to a player, making it unique. - * - *

      - * Subclasses should define the business logic required before - * committing. Then {@code super.commit(player)} should be called - * once the transaction has been successfully applied. - *

      - * @param player the player performing the transaction; must not be {@code null} - */ + public boolean isCommitted() { + return this.committed; + } - public void commit(Player player) { - this.committed = true; - } + /** + * Commits the transaction to a player, making it unique. + * + *

      Subclasses should define the business logic required before + * committing. Then {@code super.commit(player)} should be called + * once the transaction has been successfully applied.

      + * + * @param player the player performing the transaction; must not be {@code null} + */ + + public void commit(Player player) { + this.committed = true; + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java index 70bba67..88e5f7b 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionArchive.java @@ -1,143 +1,145 @@ package no.ntnu.gruppe53.model; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; /** * Maintains a history of all completed {@link Transaction}s. - *

      - * A {@code TransactionArchive} stores transactions in the order + * + *

      A {@code TransactionArchive} stores transactions in the order * they are added and provides methods to retrieve transactions - * filtered by type and week. - *

      + * filtered by type and week.

      */ public class TransactionArchive { - private final ObservableList transactions; - - /** - * Creates an empty transaction archive. - */ - - public TransactionArchive() { - this.transactions = FXCollections.observableArrayList(); + private final ObservableList transactions; + + /** + * Creates an empty transaction archive. + */ + + public TransactionArchive() { + this.transactions = FXCollections.observableArrayList(); + } + + /** + * Adds a transaction to the archive. + * + * @param transaction the transaction being added; must not be {@code null} + * @return {@code true} if the transaction was successfully added + * {@code false} otherwise + * @throws IllegalArgumentException if {@code transaction} is {@code null} + */ + + public boolean add(Transaction transaction) { + if (transaction == null) { + throw new IllegalArgumentException("Transaction cannot be null."); } - /** - * Adds a transaction to the archive. - * - * @param transaction the transaction being added; must not be {@code null} - * @return {@code true} if the transaction was successfully added - * {@code false} otherwise - * @throws IllegalArgumentException if {@code transaction} is {@code null} - */ - - public boolean add(Transaction transaction) { - if (transaction == null) { - throw new IllegalArgumentException("Transaction cannot be null."); - } - - this.transactions.add(transaction); - - return this.getTransactions().get(transactions.size()-1).equals(transaction); + this.transactions.add(transaction); + + return this.getTransactions().get(transactions.size() - 1).equals(transaction); + } + + /** + * Checks whether there are no recorded transactions in the archive. + * + * @return {@code true} if the archive is empty. + * {@code false} otherwise + */ + + public boolean isEmpty() { + return this.transactions.isEmpty(); + } + + /** + * Returns all the transactions contained in the archive. + * + * @return the list of transactions + */ + + public ObservableList getObservableTransactions() { + return this.transactions; + } + + /** + * Returns the transactions in the transaction archive. + * + * @return the transactions in the transaction archive + */ + public List getTransactions() { + return this.transactions; + } + + /** + * Returns all {@link Purchase} transactions for the given week. + * + * @param week the week number for transactions; must be greater than 0 + * @return a list of purchase transactions for the given week; + * empty if none exist + * @throws IllegalArgumentException if {@code week} is not greater than 0 + */ + + public List getPurchases(int week) { + if (week <= 0) { + throw new IllegalArgumentException("Week must be greater than 0."); } + var purchaseList = new ArrayList(); - /** - * Checks whether there are no recorded transactions in the archive. - * - * @return {@code true} if the archive is empty. - * {@code false} otherwise - */ - - public boolean isEmpty() { - return this.transactions.isEmpty(); + for (Transaction t : this.transactions) { + if (t instanceof Purchase && t.getWeek() == week) { + purchaseList.add((Purchase) t); + } } - - /** - * Returns all the transactions contained in the archive. - * - * @return the list of transactions - */ - - public ObservableList getObservableTransactions() { - return this.transactions; + return purchaseList; + } + + /** + * Returns all {@link Sale} transactions for the given week. + * + * @param week the week number for transactions; must be greater than 0 + * @return a list of sale transactions for the given week; + * empty if none exist + * @throws IllegalArgumentException if {@code week} is not greater than 0 + */ + + public List getSales(int week) { + if (week <= 0) { + throw new IllegalArgumentException("Week must be greater than 0."); } - public List getTransactions() { - return this.transactions; - } + var saleList = new ArrayList(); - /** - * Returns all {@link Purchase} transactions for the given week. - * - * @param week the week number for transactions; must be greater than 0 - * @return a list of purchase transactions for the given week; - * empty if none exist - * @throws IllegalArgumentException if {@code week} is not greater than 0 - */ - - public List getPurchases(int week) { - if (week <= 0) { - throw new IllegalArgumentException("Week must be greater than 0."); - } - var purchaseList = new ArrayList(); - - for (Transaction t : this.transactions) { - if (t instanceof Purchase && t.getWeek() == week) { - purchaseList.add((Purchase) t); - } - } - return purchaseList; + for (Transaction t : this.transactions) { + if (t instanceof Sale && t.getWeek() == week) { + saleList.add((Sale) t); + } } - - /** - * Returns all {@link Sale} transactions for the given week. - * - * @param week the week number for transactions; must be greater than 0 - * @return a list of sale transactions for the given week; - * empty if none exist - * @throws IllegalArgumentException if {@code week} is not greater than 0 - */ - - public List getSales(int week) { - if (week <= 0) { - throw new IllegalArgumentException("Week must be greater than 0."); - } - - var saleList = new ArrayList(); - - for (Transaction t : this.transactions) { - if (t instanceof Sale && t.getWeek() == week) { - saleList.add((Sale) t); - } - } - return saleList; + return saleList; + } + + /** + * Counts the number of distinct week numbers represented in the + * transaction archive. + * + *

      A week is considered distinct if at least one transaction has + * occurred within that week. Multiple transactions in the span of the + * same week will only be counted as one distinct week.

      + * + * @return the number of unique week values in the archive; + * {@code 0} if the archive contains no transactions + */ + + public int countDistinctWeeks() { + var distinctWeeks = new HashSet(); + + for (Transaction t : this.transactions) { + distinctWeeks.add(t.getWeek()); } - /** - * Counts the number of distinct week numbers represented in the - * transaction archive. - *

      - * A week is considered distinct if at least one transaction has - * occurred within that week. Multiple transactions in the span of the - * same week will only be counted as one distinct week. - *

      - * - * @return the number of unique week values in the archive; - * {@code 0} if the archive contains no transactions - */ - - public int countDistinctWeeks() { - var distinctWeeks = new HashSet(); - - for (Transaction t : this.transactions) { - distinctWeeks.add(t.getWeek()); - } - - return distinctWeeks.size(); - } + return distinctWeeks.size(); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java index 511cdfc..c9f2097 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionCalculator.java @@ -4,12 +4,36 @@ /** * Represents the necessary calculations to perform a transaction. + * *

      {@link Purchase} and {@link Sale} are examples of implementations of a transaction.

      */ public interface TransactionCalculator { - BigDecimal calculateGross(); - BigDecimal calculateCommission(); - BigDecimal calculateTax(); - BigDecimal calculateTotal(); + /** + * Calculates the gross value of a transaction. + * + * @return the gross value of a transaction + */ + BigDecimal calculateGross(); + + /** + * Calculates the commission for the exchange of a transaction. + * + * @return the commission of a transaction + */ + BigDecimal calculateCommission(); + + /** + * Calculates the tax of a transaction. + * + * @return the tax of a transaction + */ + BigDecimal calculateTax(); + + /** + * Returns the total value of a transaction. + * + * @return the total value of a transaction + */ + BigDecimal calculateTotal(); } diff --git a/millions/src/main/java/no/ntnu/gruppe53/model/TransactionFactory.java b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionFactory.java index 9ddb766..cb27b9f 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/model/TransactionFactory.java +++ b/millions/src/main/java/no/ntnu/gruppe53/model/TransactionFactory.java @@ -2,31 +2,38 @@ /** * Factory for creating a {@link Transaction} for a {@link Share} in an {@link Exchange}. + * *

      Subclasses implement the specific type of transaction to be used.

      */ public abstract class TransactionFactory { - /** - * Creates a transaction. - * - * @param share the share involved - * @param week the transaction week - * @return a transaction - */ - public Transaction create(Share share, int week) { - - if (share == null) { - throw new IllegalArgumentException("Share cannot be null."); - } - - if (week <= 0) { - throw new IllegalArgumentException("Week must be positive whole number."); - } + /** + * Creates a transaction. + * + * @param share the share involved + * @param week the transaction week + * @return a transaction + */ + public Transaction create(Share share, int week) { + if (share == null) { + throw new IllegalArgumentException("Share cannot be null."); + } - return createTransaction(share, week); + if (week <= 0) { + throw new IllegalArgumentException("Week must be positive whole number."); } - protected abstract Transaction createTransaction( + return createTransaction(share, week); + } + + /** + * Creates a transaction from a share and week. + * + * @param share the share for the transaction + * @param week the week of the transaction + * @return a transaction for the share and week + */ + protected abstract Transaction createTransaction( Share share, int week ); diff --git a/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java b/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java index 8ed2b0d..a587c09 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java +++ b/millions/src/main/java/no/ntnu/gruppe53/service/FileHandler.java @@ -1,8 +1,5 @@ package no.ntnu.gruppe53.service; -import no.ntnu.gruppe53.model.Stock; - - import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; @@ -13,131 +10,146 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import no.ntnu.gruppe53.model.Stock; /** * Handles reading and writing of .csv files for stocks. */ public class FileHandler { + /** + * Writes a list of {@link Stock}s to a CSV file in UTF-8 encoding. + * + * @param stockList the list of stocks to be written + * @param path directory path for the file + * @param filename name of the file (without or with .csv extension) + * @throws IllegalArgumentException if stockList, path, + * or filename is null, or if stockList is empty + */ + public static void writeStocksToFile(List stockList, String path, String filename) { + if (stockList == null) { + throw new IllegalArgumentException("StockList cannot be null"); + } + if (path == null || path.isBlank()) { + throw new IllegalArgumentException("Path cannot be null or blank"); + } - /** - * Writes a list of {@link Stock}s to a CSV file in UTF-8 encoding. - * - * @param stockList the list of stocks to be written - * @param path directory path for the file - * @param filename name of the file (without or with .csv extension) - * @throws IllegalArgumentException if stockList, path, or filename is null, or if stockList is empty - */ - public static void writeStocksToFile(List stockList, String path, String filename) { - if (stockList == null) throw new IllegalArgumentException("StockList cannot be null"); - if (path == null || path.isBlank()) throw new IllegalArgumentException("Path cannot be null or blank"); - if (filename == null || filename.isBlank()) throw new IllegalArgumentException( - "Filename cannot be null or blank"); - - - path = path.trim(); - filename = filename.trim(); - String filenameWithExtension = filename.endsWith(".csv") ? filename : filename + ".csv"; - - Path filePath = Paths.get(path, filenameWithExtension); - - Path dirPath = filePath.getParent(); - if (!Files.exists(dirPath)) { - try { - Files.createDirectories(dirPath); - } catch (IOException e) { - System.out.println("ERROR: Could not create directories."); - e.printStackTrace(); - return; - } - } + if (filename == null || filename.isBlank()) { + throw new IllegalArgumentException( + "Filename cannot be null or blank"); + } + + path = path.trim(); + filename = filename.trim(); + String filenameWithExtension = filename.endsWith(".csv") ? filename : filename + ".csv"; + + Path filePath = Paths.get(path, filenameWithExtension); + + Path dirPath = filePath.getParent(); + + if (!Files.exists(dirPath)) { + try { + Files.createDirectories(dirPath); + } catch (IOException e) { + System.out.println("ERROR: Could not create directories."); + e.printStackTrace(); + return; + } + } + + try (BufferedWriter writer = Files.newBufferedWriter(filePath, StandardCharsets.UTF_8)) { + writer.write("#Ticker,Name,Price"); + writer.newLine(); - try (BufferedWriter writer = Files.newBufferedWriter(filePath, StandardCharsets.UTF_8)) { - writer.write("#Ticker,Name,Price"); - writer.newLine(); + for (Stock stock : stockList) { + if (stock == null) { + continue; + } - for (Stock stock : stockList) { - if (stock == null) continue; - String line = String.join(",", + String line = String.join(",", stock.getSymbol(), stock.getCompany(), stock.getSalesPrice().toString()); - writer.write(line); - writer.newLine(); - } + writer.write(line); + writer.newLine(); + } - } catch (IOException e) { - System.out.println("ERROR: Something went wrong while writing the CSV file."); - e.printStackTrace(); - } + } catch (IOException e) { + System.out.println("ERROR: Something went wrong while writing the CSV file."); + e.printStackTrace(); + } + } + + /** + * Reads a CSV file and converts it to a list of {@link Stock} objects. + * + * @param path directory path of the CSV file + * @param filename CSV file name (with or without .csv extension) + * @return list of Stock objects + * @throws IllegalArgumentException if path or filename is null or blank + */ + public static List readStocksFromFile(String path, String filename) { + if (path == null || path.isBlank()) { + throw new IllegalArgumentException("Path cannot be null or blank."); } - /** - * Reads a CSV file and converts it to a list of {@link Stock} objects. - * - * @param path directory path of the CSV file - * @param filename CSV file name (with or without .csv extension) - * @return list of Stock objects - * @throws IllegalArgumentException if path or filename is null or blank - */ - public static List readStocksFromFile(String path, String filename) { - List stocks = new ArrayList<>(); - if (path == null || path.isBlank()) { - throw new IllegalArgumentException("Path cannot be null or blank."); - } - if (filename == null || filename.isBlank()) { - throw new IllegalArgumentException("Filename cannot be null or blank."); - } - - path = path.trim(); - filename = filename.trim(); - String filenameWithExtension = filename.endsWith(".csv") ? filename : filename + ".csv"; + if (filename == null || filename.isBlank()) { + throw new IllegalArgumentException("Filename cannot be null or blank."); + } - Path filePath = Paths.get(path, filenameWithExtension); + path = path.trim(); + filename = filename.trim(); + String filenameWithExtension = filename.endsWith(".csv") ? filename : filename + ".csv"; - if (!Files.exists(filePath)) { - System.out.println("ERROR: File not found: " + filePath.toAbsolutePath()); - return stocks; - } + Path filePath = Paths.get(path, filenameWithExtension); - try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) { - String line; + List stocks = new ArrayList<>(); + if (!Files.exists(filePath)) { + System.out.println("ERROR: File not found: " + filePath.toAbsolutePath()); + return stocks; + } - while ((line = reader.readLine()) != null) { - line = line.trim(); + try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) { + String line; - if (line.isBlank() || line.startsWith("#")) continue; + while ((line = reader.readLine()) != null) { + line = line.trim(); - String[] values = line.split(",", -1); - if (values.length != 3) { - System.out.println("ERROR: Bad line in CSV (expected 3 values): " + line); - continue; - } + if (line.isBlank() || line.startsWith("#")) { + continue; + } - String symbol = values[0].trim(); - String name = values[1].trim(); - BigDecimal price; + String[] values = line.split(",", -1); - try { - price = new BigDecimal(values[2].trim()); - } catch (NumberFormatException e) { - System.out.println("ERROR: Price is not a valid number: " + values[2]); - continue; - } + if (values.length != 3) { + System.out.println("ERROR: Bad line in CSV (expected 3 values): " + line); + continue; + } - if (symbol.isBlank() || name.isBlank() || price.compareTo(BigDecimal.ZERO) <= 0) { - System.out.println("ERROR: Invalid stock data, skipping: " + line); - continue; - } + String symbol = values[0].trim(); + String name = values[1].trim(); + BigDecimal price; - stocks.add(new Stock(symbol, name, price)); - } + try { + price = new BigDecimal(values[2].trim()); + } catch (NumberFormatException e) { + System.out.println("ERROR: Price is not a valid number: " + values[2]); + continue; + } - } catch (IOException e) { - System.out.println("ERROR: Something went wrong while reading the CSV file."); - e.printStackTrace(); + if (symbol.isBlank() || name.isBlank() || price.compareTo(BigDecimal.ZERO) <= 0) { + System.out.println("ERROR: Invalid stock data, skipping: " + line); + continue; } - return stocks; + stocks.add(new Stock(symbol, name, price)); + } + + } catch (IOException e) { + System.out.println("ERROR: Something went wrong while reading the CSV file."); + e.printStackTrace(); } + + return stocks; + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/service/FormatBigDecimal.java b/millions/src/main/java/no/ntnu/gruppe53/service/FormatBigDecimal.java index 5db1ea5..422b321 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/service/FormatBigDecimal.java +++ b/millions/src/main/java/no/ntnu/gruppe53/service/FormatBigDecimal.java @@ -7,14 +7,15 @@ * Service/helper class that formats a {@link BigDecimal} to a string with 2 decimal accuracy. */ public class FormatBigDecimal { - /** - * Static method used to format a Big Decimal to a string representation with 2 decimals accuracy. - *

      Returns "0.00" if the BigDecimal is null.

      - * - * @param number the BigDecimal to be formated - * @return the string representation of the BigDecimal with 2 decimals accuracy. - */ - public static String formatNumber(BigDecimal number) { - return number == null ? "0.00" : number.setScale(2, RoundingMode.HALF_UP).toPlainString(); - } + /** + * Static method used to format a Big Decimal to a string representation with 2 decimals accuracy. + * + *

      Returns "0.00" if the BigDecimal is null.

      + * + * @param number the BigDecimal to be formated + * @return the string representation of the BigDecimal with 2 decimals accuracy. + */ + public static String formatNumber(BigDecimal number) { + return number == null ? "0.00" : number.setScale(2, RoundingMode.HALF_UP).toPlainString(); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/service/LanguageManager.java b/millions/src/main/java/no/ntnu/gruppe53/service/LanguageManager.java index 776f15a..6d6356d 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/service/LanguageManager.java +++ b/millions/src/main/java/no/ntnu/gruppe53/service/LanguageManager.java @@ -1,97 +1,101 @@ package no.ntnu.gruppe53.service; +import java.util.Locale; +import java.util.ResourceBundle; import javafx.beans.binding.Bindings; import javafx.beans.binding.StringBinding; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; -import java.util.Locale; -import java.util.ResourceBundle; - /** * Manages the language used in the user-interface. + * *

      The possible languages to be used are set by {@link LanguageRegistry}.

      */ public class LanguageManager { - /** - * Sets a default language and creates an observable {@link ResourceBundle}. - */ - private final ObjectProperty bundleProperty = - new SimpleObjectProperty<>(); + /** + * Sets a default language and creates an observable {@link ResourceBundle}. + */ + private final ObjectProperty bundleProperty = + new SimpleObjectProperty<>(); - private LanguageManager() { - setLocale(LanguageRegistry.getDefaultLocale()); - } + /** + * Sets the language to the default locale at first instantiation. + */ + private LanguageManager() { + setLocale(LanguageRegistry.getDefaultLocale()); + } - /** - * Static Inner Class to lazily create the instance - */ - private static class LanguageManagerInner { - private static final LanguageManager INSTANCE = - new LanguageManager(); - } + /** + * Static Inner Class to lazily create the instance. + */ + private static class LanguageManagerInner { + private static final LanguageManager INSTANCE = + new LanguageManager(); + } - /** - * Returns the instance of the language manager - * - * @return the instance of the language manager - */ - public static LanguageManager getInstance() { - return LanguageManagerInner.INSTANCE; - } + /** + * Returns the instance of the language manager. + * + * @return the instance of the language manager + */ + public static LanguageManager getInstance() { + return LanguageManagerInner.INSTANCE; + } - /** - * Returns the observable resource bundle. - * - * @return an observable resource bundle - */ - public ObjectProperty getBundleProperty() { - return bundleProperty; - } + /** + * Returns the observable resource bundle. + * + * @return an observable resource bundle + */ + public ObjectProperty getBundleProperty() { + return bundleProperty; + } - /** - * Sets the locale of the instance to a given locale which determines the properties file to load - * values from. - * - * @param locale the locale to change to - */ - public void setLocale(Locale locale) { - ResourceBundle bundle = - ResourceBundle.getBundle("i18n.lang", locale); + /** + * Sets the locale of the instance to a given locale which determines the properties file to load + * values from. + * + * @param locale the locale to change to + */ + public void setLocale(Locale locale) { + ResourceBundle bundle = + ResourceBundle.getBundle("i18n.lang", locale); - bundleProperty.set(bundle); - } + bundleProperty.set(bundle); + } - /** - * Returns the string value of the given key in a .properties file. - * - * @param key the key to look for in the .properties file - * @return the value of the key - */ - public String getString(String key) { - return bundleProperty.get().getString(key); - } + /** + * Returns the string value of the given key in a .properties file. + * + * @param key the key to look for in the .properties file + * @return the value of the key + */ + public String getString(String key) { + return bundleProperty.get().getString(key); + } - /** - * Returns the current locale. - * - * @return the current locale - */ - public Locale getLocale() { - return bundleProperty.get().getLocale(); - } + /** + * Returns the current locale. + * + * @return the current locale + */ + public Locale getLocale() { + return bundleProperty.get().getLocale(); + } - /** - * Creates a {@link StringBinding} to a given string and associates it with a given key in a .properties file - * - * @param key the key to associate the string binding with - * @return a string binding observable - */ - public StringBinding bindString(String key) { - return Bindings.createStringBinding( + /** + * Creates a {@link StringBinding} to a given string and associates it + * with a given key in a .properties file. + * + * @param key the key to associate the string binding with + * @return a string binding observable + */ + public StringBinding bindString(String key) { + return Bindings.createStringBinding( () -> getString(key), getBundleProperty() - ); - } + ); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/service/LanguageRegistry.java b/millions/src/main/java/no/ntnu/gruppe53/service/LanguageRegistry.java index 1122d2a..490ab9f 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/service/LanguageRegistry.java +++ b/millions/src/main/java/no/ntnu/gruppe53/service/LanguageRegistry.java @@ -8,47 +8,49 @@ */ public class LanguageRegistry { - /** - * Sets all possible languages that can be used. - */ - private static final List languages = List.of( + /** + * Sets all possible languages that can be used. + */ + private static final List languages = List.of( new LanguageOption("English", new Locale("en")), new LanguageOption("Norsk", new Locale("no")) - ); + ); - /** - * Sets the default language to be used. - *

      Searches through the registry to find english and sets it as default.

      - */ - private static final LanguageOption defaultLang = languages.stream() - .filter(lang -> lang.locale().getLanguage().equals("en")) - .findFirst() - .orElse(languages.getFirst()); + /** + * Sets the default language to be used. + * + *

      Searches through the registry to find english and sets it as default.

      + */ + private static final LanguageOption defaultLang = + languages.stream() + .filter(lang -> lang.locale().getLanguage().equals("en")) + .findFirst() + .orElse(languages.getFirst()); - /** - * Returns all registered languages - * - * @return a list of registered languages - */ - public static List getLanguages() { - return languages; - } + /** + * Returns all registered languages. + * + * @return a list of registered languages + */ + public static List getLanguages() { + return languages; + } - /** - * Returns the default language. - * - * @return the default language - */ - public static LanguageOption getDefaultLanguage() { - return defaultLang; - } + /** + * Returns the default language. + * + * @return the default language + */ + public static LanguageOption getDefaultLanguage() { + return defaultLang; + } - /** - * Returns the default locale of the default language. - * - * @return the default locale - */ - public static Locale getDefaultLocale() { - return defaultLang.locale(); - } + /** + * Returns the default locale of the default language. + * + * @return the default locale + */ + public static Locale getDefaultLocale() { + return defaultLang.locale(); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/service/StockLineChartService.java b/millions/src/main/java/no/ntnu/gruppe53/service/StockLineChartService.java index 28076e3..5e785cd 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/service/StockLineChartService.java +++ b/millions/src/main/java/no/ntnu/gruppe53/service/StockLineChartService.java @@ -1,72 +1,79 @@ package no.ntnu.gruppe53.service; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; import javafx.scene.chart.XYChart; import no.ntnu.gruppe53.model.Exchange; import no.ntnu.gruppe53.model.Stock; import no.ntnu.gruppe53.view.StockLineChartView; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.List; - /** * Builds the {@link XYChart.Series} values for the {@link StockLineChartView}. */ public class StockLineChartService { - /** - * Builds the {@code XYChart.Series} for the {@code LineChart} of a single {@link Stock}. - *

      Returns the {@code XYChart.Series}.

      - * - * @param stock the stock whose price values will be shown in the LineChart - * @param exchange the exchange containing the stock whose week number will be shown in the LineChart - * @return the {@code XYChart.Series} with the y-values from the price history of the stock, - * and the x-values from the week number of the exchange - * @throws IllegalArgumentException if there are more x-values than y-values or vice versa - */ - - public static XYChart.Series buildSeries(Stock stock, Exchange exchange) { - XYChart.Series series = new XYChart.Series<>(); - series.setName(stock.getSymbol()); + /** + * Builds the {@code XYChart.Series} for the {@code LineChart} of a single {@link Stock}. + * + *

      Returns the {@code XYChart.Series}.

      + * + * @param stock the stock whose price values will be shown in the LineChart + * @param exchange the exchange containing the stock whose + * week number will be shown in the LineChart + * @return the {@code XYChart.Series} with + * the y-values from the price history of the stock, + * and the x-values from the week number of the exchange + * @throws IllegalArgumentException if there are more x-values than y-values or vice versa + */ - // Check for 1-1 ratio of price and week list size for plotting - if (stock.getHistoricalPrices().size() != exchange.getWeek()) { - throw new IllegalArgumentException("Invalid Data: mismatch between prices and weeks. " + - "Should have 1-1 ratio."); - } + public static XYChart.Series buildSeries(Stock stock, Exchange exchange) { + XYChart.Series series = new XYChart.Series<>(); + series.setName(stock.getSymbol()); - int week = 1; + // Check for 1-1 ratio of price and week list size for plotting + if (stock.getHistoricalPrices().size() != exchange.getWeek()) { + throw new IllegalArgumentException("Invalid Data: mismatch between prices and weeks. " + + "Should have 1-1 ratio."); + } - for (BigDecimal price : stock.getHistoricalPrices()) { - series.getData().add( - new XYChart.Data<>(week, price.doubleValue())); - week++; - } + int week = 1; - return series; + for (BigDecimal price : stock.getHistoricalPrices()) { + series.getData().add( + new XYChart.Data<>(week, price.doubleValue())); + week++; } - /** - * Builds the {@code XYChart.Series} for the {@code LineChart} of a list of {@code Stock}s. - *

      Returns a list of the {@code XYChart.Series} for each stock.

      - * - * @param stocks the list of stocks whose price values will be shown in the LineChart - * @param exchange the exchange containing the stock whose week number will be shown in the LineChart - * @return the list of {@code XYChart.Series} with the y-values from the price history of the stock, - * and the x-values from the week number of the exchange - */ + return series; + } - public static List> buildSeries(List stocks, Exchange exchange) { - List> seriesList = new ArrayList<>(); + /** + * Builds the {@code XYChart.Series} for the {@code LineChart} of a list of {@code Stock}s. + * + *

      Returns a list of the {@code XYChart.Series} for each stock.

      + * + * @param stocks the list of stocks whose price values will be shown + * in the LineChart + * @param exchange the exchange containing the stock whose week number + * will be shown in the LineChart + * @return the list of {@code XYChart.Series} with + * the y-values from the price history of the stock, + * and the x-values from the week number of the exchange + */ - for (Stock stock : stocks) { - XYChart.Series series = - buildSeries(stock, exchange); + public static List> buildSeries( + List stocks, Exchange exchange) { + List> seriesList = new ArrayList<>(); - seriesList.add(series); - } + for (Stock stock : stocks) { + XYChart.Series series = + buildSeries(stock, exchange); - return seriesList; + seriesList.add(series); } + + return seriesList; + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java b/millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java deleted file mode 100644 index a60d295..0000000 --- a/millions/src/main/java/no/ntnu/gruppe53/view/CSVChooserView.java +++ /dev/null @@ -1,49 +0,0 @@ -package no.ntnu.gruppe53.view; - -import javafx.scene.control.Alert; -import javafx.stage.FileChooser; -import javafx.stage.Stage; -import no.ntnu.gruppe53.service.LanguageManager; - -import java.io.File; - -/** - * Represents an {@link Alert} and {@link FileChooser} for informing user of .csv requirement and picking said file. - *

      Uses a {@link LanguageManager} to insert set text to target language.

      - */ -public class CSVChooserView { - private final FileChooser fileChooser = new FileChooser(); - Alert newGameAlert = new Alert(Alert.AlertType.INFORMATION); - private final LanguageManager lm = LanguageManager.getInstance(); - - /** - * Returns the dialog for the alert and file chooser and exposes it to controllers. - * - * @param stage the stage for the dialog to be shown - * @return the dialog for the alert and file chooser - */ - public File getDialog(Stage stage) { - // Notifies the user about .csv selection - newGameAlert.setTitle(null); - newGameAlert.headerTextProperty().bind(lm.bindString("csvHeader")); - newGameAlert.contentTextProperty().bind(lm.bindString("csvContent")); - - // Sets the dimensions of the alert to show all text, and as a band-aid, be resizable - newGameAlert.setResizable(true); - - newGameAlert.getDialogPane().setPrefWidth(450); - newGameAlert.getDialogPane().setPrefHeight(350); - - newGameAlert.showAndWait(); - - // File chooser to choose .csv file - fileChooser.setTitle(lm.getString("csvFileChooserTitle")); - - // Limits to .csv file only - fileChooser.getExtensionFilters().clear(); - fileChooser.getExtensionFilters().add( - new FileChooser.ExtensionFilter(lm.getString("csvFileFilterLabel"), "*.csv") - ); - - return fileChooser.showOpenDialog(stage); } -} \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/CsvChooserView.java b/millions/src/main/java/no/ntnu/gruppe53/view/CsvChooserView.java new file mode 100644 index 0000000..4264887 --- /dev/null +++ b/millions/src/main/java/no/ntnu/gruppe53/view/CsvChooserView.java @@ -0,0 +1,51 @@ +package no.ntnu.gruppe53.view; + +import java.io.File; +import javafx.scene.control.Alert; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import no.ntnu.gruppe53.service.LanguageManager; + +/** + * Represents an {@link Alert} and {@link FileChooser} for + * informing user of .csv requirement and picking said file. + * + *

      Uses a {@link LanguageManager} to insert set text to target language.

      + */ +public class CsvChooserView { + private final FileChooser fileChooser = new FileChooser(); + Alert newGameAlert = new Alert(Alert.AlertType.INFORMATION); + private final LanguageManager lm = LanguageManager.getInstance(); + + /** + * Returns the dialog for the alert and file chooser and exposes it to controllers. + * + * @param stage the stage for the dialog to be shown + * @return the dialog for the alert and file chooser + */ + public File getDialog(Stage stage) { + // Notifies the user about .csv selection + newGameAlert.setTitle(null); + newGameAlert.headerTextProperty().bind(lm.bindString("csvHeader")); + newGameAlert.contentTextProperty().bind(lm.bindString("csvContent")); + + // Sets the dimensions of the alert to show all text, and as a band-aid, be resizable + newGameAlert.setResizable(true); + + newGameAlert.getDialogPane().setPrefWidth(450); + newGameAlert.getDialogPane().setPrefHeight(350); + + newGameAlert.showAndWait(); + + // File chooser to choose .csv file + fileChooser.setTitle(lm.getString("csvFileChooserTitle")); + + // Limits to .csv file only + fileChooser.getExtensionFilters().clear(); + fileChooser.getExtensionFilters().add( + new FileChooser.ExtensionFilter(lm.getString("csvFileFilterLabel"), "*.csv") + ); + + return fileChooser.showOpenDialog(stage); + } +} \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/EndView.java b/millions/src/main/java/no/ntnu/gruppe53/view/EndView.java index 9ac97de..611e997 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/EndView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/EndView.java @@ -4,200 +4,210 @@ import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.control.Label; -import javafx.scene.layout.*; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; import javafx.scene.text.TextAlignment; import no.ntnu.gruppe53.service.LanguageManager; - /** * Represents the End Game screen, showing the Level the Player reached, * money earned in total after selling portfolio and gives the option * to quit the program or start a new game. */ public class EndView extends BorderPane { - - - private Button newGameButton; - private Button quitButton; - - private Label statusLabel; - private Label moneyLabel; - - - private final LanguageManager lm = LanguageManager.getInstance(); - - public EndView() { - this.setCenter(centerPane()); - } - - private BorderPane centerPane() { - BorderPane pane = new BorderPane(); - pane.setPadding(new Insets(15, 12, 15, 12)); - //pane.setSpacing(10); - //Blue Background color - pane.setStyle("-fx-background-color: #00bcf0;"); - - VBox centerPane = new VBox(); - - HBox congratsBox = new HBox(); - Label congratsLabel = new Label(); - congratsLabel.textProperty().bind(lm.bindString("congratsLabel")); - congratsLabel.setStyle("-fx-font-size: 18"); - congratsLabel.setTextAlignment(TextAlignment.CENTER); - - congratsBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - congratsBox.setPadding(new Insets(8, 14, 8, 14)); - congratsBox.setAlignment(Pos.CENTER); - congratsBox.setMinHeight(65); - - congratsBox.getChildren().add(congratsLabel); - - HBox statusLabelBox = new HBox(); - Label statusLabelText = new Label(); - statusLabelText.textProperty().bind(lm.bindString("statusLabelText")); - statusLabelText.setStyle("-fx-font-size: 18"); - statusLabel = new Label(); - statusLabel.setStyle("-fx-font-size: 18"); - - statusLabelBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - statusLabelBox.setPadding(new Insets(8, 14, 8, 14)); - statusLabelBox.setAlignment(Pos.CENTER); - statusLabelBox.setMinHeight(65); - - statusLabelBox.getChildren().addAll(statusLabelText, statusLabel); - - HBox moneyLabelBox = new HBox(); - Label moneyLabelText = new Label(); - moneyLabelText.textProperty().bind(lm.bindString("navMoneyLabelText")); - moneyLabelText.setStyle("-fx-font-size: 18"); - moneyLabel = new Label(); - moneyLabel.setStyle("-fx-font-size: 18"); - - moneyLabelBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - moneyLabelBox.setPadding(new Insets(8, 14, 8, 14)); - moneyLabelBox.setAlignment(Pos.CENTER); - moneyLabelBox.setMinHeight(65); - - moneyLabelBox.getChildren().addAll(moneyLabelText, moneyLabel); - - centerPane.setPadding(new Insets(15, 12, 15, 12)); - centerPane.setAlignment(Pos.CENTER); - centerPane.setSpacing(10); - centerPane.setMaxWidth(700); - - - centerPane.getChildren().addAll(congratsBox, statusLabelBox, moneyLabelBox); - - HBox quitButtonBox = new HBox(); - quitButton = new Button(); - quitButton.textProperty().bind(lm.bindString("quitGame")); - quitButton.setPrefSize(200, 50); - quitButton.setStyle("-fx-text-fill: #303539;" + - "-fx-font-size: 18px;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" + - "-fx-background-radius: 8;" - ); - - - quitButtonBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - quitButtonBox.setPadding(new Insets(8, 14, 8, 14)); - quitButtonBox.setAlignment(Pos.CENTER); - - - quitButtonBox.getChildren().addAll(quitButton); - - HBox newGameButtonBox = new HBox(); - newGameButton = new Button(); - newGameButton.textProperty().bind(lm.bindString("newGame")); - newGameButton.setPrefSize(200, 50); - newGameButton.setStyle("-fx-text-fill: #303539;" + - "-fx-font-size: 18px;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" + - "-fx-background-radius: 8;" - ); - newGameButtonBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - newGameButtonBox.setPadding(new Insets(8, 14, 8, 14)); - newGameButtonBox.setAlignment(Pos.CENTER); - newGameButtonBox.getChildren().add(newGameButton); - - HBox buttons = new HBox(quitButtonBox, newGameButtonBox); - - - - Region spacerBottomLeft = new Region(); - Region spacerBottomRight = new Region(); - - HBox.setHgrow(spacerBottomLeft, Priority.ALWAYS); - HBox.setHgrow(spacerBottomRight, Priority.ALWAYS); - - HBox bottomPane = new HBox(spacerBottomLeft, buttons, spacerBottomRight); - - pane.setCenter(centerPane); - pane.setBottom(bottomPane); - - return pane; - } - - /** - * Sets the action to be run when clicking the quit button - * - * @param action the action to run when clicking the quit button - */ - public void onQuitButton(Runnable action) { - quitButton.setOnAction(e -> action.run()); - } - - /** - * Sets the action to be run when clicking the New Game button - * - * @param action the action to run when clicking the New Game button - */ - public void onNewGameButton(Runnable action) { - newGameButton.setOnAction(e -> action.run()); - } - - /** - * Returns the statusLabel. - * - * @return the statusLabel - */ - public Label getStatusLabel() { - return statusLabel; - } - - /** - * Returns the moneyLabel. - * - * @return the moneyLabel - */ - public Label getMoneyLabel() { - return moneyLabel; - } + private Button newGameButton; + private Button quitButton; + + private Label statusLabel; + private Label moneyLabel; + + private final LanguageManager lm = LanguageManager.getInstance(); + + /** + * Sets the centerPane to the constructed centerPane. + */ + public EndView() { + this.setCenter(centerPane()); + } + + /** + * Constructs the centerPane using a {@link BorderPane}. + * + * @return a constructed borderpane + */ + private BorderPane centerPane() { + BorderPane pane = new BorderPane(); + pane.setPadding(new Insets(15, 12, 15, 12)); + // pane.setSpacing(10); + // Blue Background color + pane.setStyle("-fx-background-color: #00bcf0;"); + + Label congratsLabel = new Label(); + congratsLabel.textProperty().bind(lm.bindString("congratsLabel")); + congratsLabel.setStyle("-fx-font-size: 18"); + congratsLabel.setTextAlignment(TextAlignment.CENTER); + + HBox congratsBox = new HBox(); + congratsBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + congratsBox.setPadding(new Insets(8, 14, 8, 14)); + congratsBox.setAlignment(Pos.CENTER); + congratsBox.setMinHeight(65); + + congratsBox.getChildren().add(congratsLabel); + + Label statusLabelText = new Label(); + statusLabelText.textProperty().bind(lm.bindString("statusLabelText")); + statusLabelText.setStyle("-fx-font-size: 18"); + statusLabel = new Label(); + statusLabel.setStyle("-fx-font-size: 18"); + + HBox statusLabelBox = new HBox(); + statusLabelBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + statusLabelBox.setPadding(new Insets(8, 14, 8, 14)); + statusLabelBox.setAlignment(Pos.CENTER); + statusLabelBox.setMinHeight(65); + + statusLabelBox.getChildren().addAll(statusLabelText, statusLabel); + + Label moneyLabelText = new Label(); + moneyLabelText.textProperty().bind(lm.bindString("navMoneyLabelText")); + moneyLabelText.setStyle("-fx-font-size: 18"); + moneyLabel = new Label(); + moneyLabel.setStyle("-fx-font-size: 18"); + + HBox moneyLabelBox = new HBox(); + moneyLabelBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + moneyLabelBox.setPadding(new Insets(8, 14, 8, 14)); + moneyLabelBox.setAlignment(Pos.CENTER); + moneyLabelBox.setMinHeight(65); + + moneyLabelBox.getChildren().addAll(moneyLabelText, moneyLabel); + + VBox centerPane = new VBox(); + centerPane.setPadding(new Insets(15, 12, 15, 12)); + centerPane.setAlignment(Pos.CENTER); + centerPane.setSpacing(10); + centerPane.setMaxWidth(700); + + + centerPane.getChildren().addAll(congratsBox, statusLabelBox, moneyLabelBox); + + quitButton = new Button(); + quitButton.textProperty().bind(lm.bindString("quitGame")); + quitButton.setPrefSize(200, 50); + quitButton.setStyle("-fx-text-fill: #303539;" + + "-fx-font-size: 18px;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + + "-fx-background-radius: 8;" + ); + + HBox quitButtonBox = new HBox(); + quitButtonBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + quitButtonBox.setPadding(new Insets(8, 14, 8, 14)); + quitButtonBox.setAlignment(Pos.CENTER); + + + quitButtonBox.getChildren().addAll(quitButton); + + newGameButton = new Button(); + newGameButton.textProperty().bind(lm.bindString("newGame")); + newGameButton.setPrefSize(200, 50); + newGameButton.setStyle("-fx-text-fill: #303539;" + + "-fx-font-size: 18px;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + + "-fx-background-radius: 8;" + ); + + HBox newGameButtonBox = new HBox(); + newGameButtonBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + newGameButtonBox.setPadding(new Insets(8, 14, 8, 14)); + newGameButtonBox.setAlignment(Pos.CENTER); + newGameButtonBox.getChildren().add(newGameButton); + + HBox buttons = new HBox(quitButtonBox, newGameButtonBox); + + Region spacerBottomLeft = new Region(); + Region spacerBottomRight = new Region(); + + HBox.setHgrow(spacerBottomLeft, Priority.ALWAYS); + HBox.setHgrow(spacerBottomRight, Priority.ALWAYS); + + HBox bottomPane = new HBox(spacerBottomLeft, buttons, spacerBottomRight); + + pane.setCenter(centerPane); + pane.setBottom(bottomPane); + + return pane; + } + + /** + * Sets the action to be run when clicking the quit button. + * + * @param action the action to run when clicking the quit button + */ + public void onQuitButton(Runnable action) { + quitButton.setOnAction(e -> action.run()); + } + + /** + * Sets the action to be run when clicking the New Game button. + * + * @param action the action to run when clicking the New Game button + */ + public void onNewGameButton(Runnable action) { + newGameButton.setOnAction(e -> action.run()); + } + + /** + * Returns the statusLabel. + * + * @return the statusLabel + */ + public Label getStatusLabel() { + return statusLabel; + } + + /** + * Returns the moneyLabel. + * + * @return the moneyLabel + */ + public Label getMoneyLabel() { + return moneyLabel; + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java index 8785d28..dd9857b 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/FooterBar.java @@ -2,7 +2,6 @@ import java.util.Locale; import java.util.function.Consumer; - import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Button; @@ -14,13 +13,12 @@ import javafx.scene.layout.Priority; import javafx.scene.layout.Region; import javafx.util.Subscription; - import no.ntnu.gruppe53.controller.GameController; import no.ntnu.gruppe53.model.Exchange; +import no.ntnu.gruppe53.model.Stock; import no.ntnu.gruppe53.service.LanguageManager; import no.ntnu.gruppe53.service.LanguageOption; import no.ntnu.gruppe53.service.LanguageRegistry; -import no.ntnu.gruppe53.model.Stock; /* Yellow Color, Hex: #ffe556 RGB(255, 229, 86) @@ -28,208 +26,208 @@ Black Color, Hex: #303539 RGB(48, 53, 57) */ - /** * A {@link HBox} view representing the footer/bottom to be used in a {@link BorderPane} layout. + * *

      Uses a {@link LanguageManager} to change text to target language dynamically.

      */ public class FooterBar extends HBox { - - private final Button advanceWeekButton; - private Label currentWeekLabel; - private Label biggestLoserLabel; - private Label biggestLoserPriceLabel; - private Label biggestWinnerLabel; - private Label biggestWinnerPriceLabel; - private Subscription currentWeekSubscription; - private Subscription biggestLoserSubscription; - private Subscription biggestWinnerSubscription; - - private Exchange exchange; - private final LanguageManager lm = LanguageManager.getInstance(); - - private final ComboBox languageBox; - - /** - * Constructs the layout of the view and its components. - *

      Constructs a {@link ComboBox} to let the user switch language on the fly.

      - *

      Sets the text of the UI elements dynamically.

      - */ - public FooterBar() { - this.setSpacing(10); - this.setPadding(new Insets(15, 12, 15, 12)); - this.setStyle("-fx-background-color: #00bcf0;" + - "-fx-border-color: #303539 transparent transparent transparent;" + - "-fx-border-width: 5 0 0 0;"); - - languageBox = new ComboBox<>(); - languageBox.getItems().addAll(LanguageRegistry.getLanguages()); - - Locale activeLocale = lm.getLocale(); - LanguageOption activeOption = LanguageRegistry.getLanguages().stream() + private final Button advanceWeekButton; + private final Label currentWeekLabel; + private final Label biggestLoserLabel; + private final Label biggestLoserPriceLabel; + private final Label biggestWinnerLabel; + private final Label biggestWinnerPriceLabel; + private Subscription biggestLoserSubscription; + private Subscription biggestWinnerSubscription; + + private final LanguageManager lm = LanguageManager.getInstance(); + + private final ComboBox languageBox; + + /** + * Constructs the layout of the view and its components. + * + *

      Constructs a {@link ComboBox} to let the user switch language on the fly.

      + * + *

      Sets the text of the UI elements dynamically.

      + */ + public FooterBar() { + this.setSpacing(10); + this.setPadding(new Insets(15, 12, 15, 12)); + this.setStyle("-fx-background-color: #00bcf0;" + + "-fx-border-color: #303539 transparent transparent transparent;" + + "-fx-border-width: 5 0 0 0;"); + + languageBox = new ComboBox<>(); + languageBox.getItems().addAll(LanguageRegistry.getLanguages()); + + Locale activeLocale = lm.getLocale(); + LanguageOption activeOption = LanguageRegistry.getLanguages().stream() .filter(lang -> lang.locale().equals(activeLocale)) .findFirst() .orElse(LanguageRegistry.getDefaultLanguage()); - languageBox.setValue(activeOption); + languageBox.setValue(activeOption); - // Listens for changes in language from other sources (e.g. startView) - lm.getBundleProperty().subscribe(bundle -> { - java.util.Locale currentLocale = lm.getLocale(); + // Listens for changes in language from other sources (e.g. startView) + lm.getBundleProperty().subscribe(bundle -> { + java.util.Locale currentLocale = lm.getLocale(); - LanguageOption matchingOption = LanguageRegistry.getLanguages().stream() + LanguageOption matchingOption = LanguageRegistry.getLanguages().stream() .filter(lang -> lang.locale().equals(currentLocale)) .findFirst() .orElse(null); - if (matchingOption != null && !matchingOption.equals(languageBox.getValue())) { - languageBox.setValue(matchingOption); + if (matchingOption != null && !matchingOption.equals(languageBox.getValue())) { + languageBox.setValue(matchingOption); + } + }); + + languageBox.setCellFactory(list -> new ListCell<>() { + private Subscription cellSub; + @Override + protected void updateItem(LanguageOption item, boolean empty) { + super.updateItem(item, empty); + if (cellSub != null) { + cellSub.unsubscribe(); } - }); - - languageBox.setCellFactory(list -> new ListCell<>() { - private Subscription cellSub; - @Override - protected void updateItem(LanguageOption item, boolean empty) { - super.updateItem(item, empty); - if (cellSub != null) cellSub.unsubscribe(); - - if (empty || item == null) { - setText(null); - } else { - cellSub = lm.getBundleProperty().subscribe(bundle -> setText(item.name())); - } - } - }); - - languageBox.setButtonCell(languageBox.getCellFactory().call(null)); - - HBox loserBox = new HBox(); - - Label biggestLoserLabelText = new Label(); - biggestLoserLabelText.textProperty().bind(lm.bindString("biggestLoserLabelText")); - biggestLoserLabel = new Label(); - biggestLoserPriceLabel = new Label(); - biggestLoserPriceLabel.setStyle("-fx-text-fill: red;"); - - loserBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - - loserBox.setPadding(new Insets(8, 14, 8, 14)); - loserBox.setAlignment(Pos.CENTER); - - loserBox.getChildren().addAll(biggestLoserLabelText, biggestLoserLabel, biggestLoserPriceLabel); - - HBox winnerBox = new HBox(); - - Label biggestWinnerLabelText = new Label(); - biggestWinnerLabelText.textProperty().bind(lm.bindString("biggestWinnerLabelText")); - biggestWinnerLabel = new Label(); - biggestWinnerPriceLabel = new Label(); - biggestWinnerPriceLabel.setStyle("-fx-text-fill: green;"); - winnerBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - - winnerBox.setPadding(new Insets(8, 14, 8, 14)); - winnerBox.setAlignment(Pos.CENTER); - - winnerBox.getChildren().addAll(biggestWinnerLabelText, biggestWinnerLabel, biggestWinnerPriceLabel); - - - Region spacer = new Region(); - HBox.setHgrow(spacer, Priority.ALWAYS); - - HBox currentWeekBox = new HBox(); - currentWeekBox.setSpacing(10); - - currentWeekBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - currentWeekBox.setPadding(new Insets(8, 14, 8, 14)); - currentWeekBox.setAlignment(Pos.CENTER); - - Label currentWeekLabelText = new Label(); - currentWeekLabelText.textProperty().bind(lm.bindString("currentWeekLabelText")); - currentWeekLabel = new Label(); - currentWeekBox.getChildren().addAll(currentWeekLabelText, currentWeekLabel); - - advanceWeekButton = new Button(); - advanceWeekButton.textProperty().bind(lm.bindString("advanceWeekButton")); - advanceWeekButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - - this.getChildren().addAll(languageBox, loserBox, winnerBox, spacer, currentWeekBox, advanceWeekButton); - } - - /** - * Sets the exchange and subscribes to week updates. - * - * @param exchange the {@link Exchange} used in the game - */ - public void setExchange(Exchange exchange) { - this.exchange = exchange; - currentWeekSubscription = exchange.getCurrentWeekProperty().subscribe(newVal -> { - if (exchange != null) { - currentWeekLabel.setText(newVal.toString()); + if (empty || item == null) { + setText(null); } else { - currentWeekLabel.setText("None"); + cellSub = lm.getBundleProperty().subscribe(bundle -> setText(item.name())); } - }); - - if (biggestLoserSubscription != null) { - biggestLoserSubscription.unsubscribe(); - } - - biggestLoserSubscription = exchange.getCurrentWeekProperty().subscribe(newWeek -> { - Stock loser = exchange.getLosers(1).getFirst(); - biggestLoserLabel.setText(loser.getSymbol() + " - " + loser.getCompany()); - biggestLoserPriceLabel.setText(" $" + loser.getLatestPriceChange()); - }); - - if (biggestWinnerSubscription != null) { - biggestWinnerSubscription.unsubscribe(); - } - - biggestWinnerSubscription = exchange.getCurrentWeekProperty().subscribe(newWeek -> { - Stock winner = exchange.getGainers(1).getFirst(); - biggestWinnerLabel.setText(winner.getSymbol() + " - " + winner.getCompany()); - biggestWinnerPriceLabel.setText(" $" + winner.getLatestPriceChange()); - }); + } + }); + + languageBox.setButtonCell(languageBox.getCellFactory().call(null)); + + Label biggestLoserLabelText = new Label(); + biggestLoserLabelText.textProperty().bind(lm.bindString("biggestLoserLabelText")); + biggestLoserLabel = new Label(); + biggestLoserPriceLabel = new Label(); + biggestLoserPriceLabel.setStyle("-fx-text-fill: red;"); + + HBox loserBox = new HBox(); + loserBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + loserBox.setPadding(new Insets(8, 14, 8, 14)); + loserBox.setAlignment(Pos.CENTER); + + loserBox.getChildren().addAll(biggestLoserLabelText, biggestLoserLabel, biggestLoserPriceLabel); + + Label biggestWinnerLabelText = new Label(); + biggestWinnerLabelText.textProperty().bind(lm.bindString("biggestWinnerLabelText")); + biggestWinnerLabel = new Label(); + biggestWinnerPriceLabel = new Label(); + biggestWinnerPriceLabel.setStyle("-fx-text-fill: green;"); + + HBox winnerBox = new HBox(); + winnerBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + winnerBox.setPadding(new Insets(8, 14, 8, 14)); + winnerBox.setAlignment(Pos.CENTER); + + winnerBox.getChildren().addAll( + biggestWinnerLabelText, biggestWinnerLabel, biggestWinnerPriceLabel); + + + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + + HBox currentWeekBox = new HBox(); + currentWeekBox.setSpacing(10); + + currentWeekBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + currentWeekBox.setPadding(new Insets(8, 14, 8, 14)); + currentWeekBox.setAlignment(Pos.CENTER); + + Label currentWeekLabelText = new Label(); + currentWeekLabelText.textProperty().bind(lm.bindString("currentWeekLabelText")); + currentWeekLabel = new Label(); + currentWeekBox.getChildren().addAll(currentWeekLabelText, currentWeekLabel); + + advanceWeekButton = new Button(); + advanceWeekButton.textProperty().bind(lm.bindString("advanceWeekButton")); + advanceWeekButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + + this.getChildren().addAll( + languageBox, loserBox, winnerBox, spacer, currentWeekBox, advanceWeekButton); + } + + /** + * Sets the exchange and subscribes to week updates. + * + * @param exchange the {@link Exchange} used in the game + */ + public void setExchange(Exchange exchange) { + Subscription currentWeekSubscription = exchange.getCurrentWeekProperty().subscribe(newVal -> { + if (exchange != null) { + currentWeekLabel.setText(newVal.toString()); + } else { + currentWeekLabel.setText("None"); + } + }); + + if (biggestLoserSubscription != null) { + biggestLoserSubscription.unsubscribe(); } - /** - * Exposes the advanceWeekButton to {@link GameController} so it can - * advance the week of the {@link Exchange}. - * - * @param action the action to be performed when clicking the button - */ - public void onAdvanceButtonClick(Runnable action) { - advanceWeekButton.setOnAction(e -> action.run()); - } + biggestLoserSubscription = exchange.getCurrentWeekProperty().subscribe(newWeek -> { + Stock loser = exchange.getLosers(1).getFirst(); + biggestLoserLabel.setText(loser.getSymbol() + " - " + loser.getCompany()); + biggestLoserPriceLabel.setText(" $" + loser.getLatestPriceChange()); + }); - - /** - * Exoposes the LanguageOption ComboButton to the controller. - * - * @param action the action to be performed when selecting the language - */ - public void onLanguageChange(Consumer action) { - languageBox.setOnAction(e -> { - LanguageOption selected = languageBox.getValue(); - if (selected != null) { - action.accept(selected); - } - }); + if (biggestWinnerSubscription != null) { + biggestWinnerSubscription.unsubscribe(); } + + biggestWinnerSubscription = exchange.getCurrentWeekProperty().subscribe(newWeek -> { + Stock winner = exchange.getGainers(1).getFirst(); + biggestWinnerLabel.setText(winner.getSymbol() + " - " + winner.getCompany()); + biggestWinnerPriceLabel.setText(" $" + winner.getLatestPriceChange()); + }); + } + + /** + * Exposes the advanceWeekButton to {@link GameController} so it can + * advance the week of the {@link Exchange}. + * + * @param action the action to be performed when clicking the button + */ + public void onAdvanceButtonClick(Runnable action) { + advanceWeekButton.setOnAction(e -> action.run()); + } + + /** + * Exposes the LanguageOption ComboButton to the controller. + * + * @param action the action to be performed when selecting the language + */ + public void onLanguageChange(Consumer action) { + languageBox.setOnAction(e -> { + LanguageOption selected = languageBox.getValue(); + if (selected != null) { + action.accept(selected); + } + }); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java index 9dc83fe..ce37a5d 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/MarketView.java @@ -2,17 +2,29 @@ import java.math.RoundingMode; import java.util.function.Consumer; - import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.SortedList; import javafx.geometry.Insets; import javafx.geometry.Pos; -import javafx.scene.control.*; -import javafx.scene.chart.XYChart; -import javafx.scene.layout.*; import javafx.scene.chart.LineChart; +import javafx.scene.chart.XYChart; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.Spinner; +import javafx.scene.control.TextField; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; import javafx.util.Subscription; -import no.ntnu.gruppe53.model.*; +import no.ntnu.gruppe53.model.Exchange; +import no.ntnu.gruppe53.model.Player; +import no.ntnu.gruppe53.model.PurchaseCalculator; +import no.ntnu.gruppe53.model.Share; +import no.ntnu.gruppe53.model.Stock; import no.ntnu.gruppe53.service.FormatBigDecimal; import no.ntnu.gruppe53.service.LanguageManager; @@ -22,413 +34,420 @@ Black Color, Hex: #303539 RGB(48, 53, 57) */ - /** * Represents a view of the market of an {@link Exchange}, allowing the {@link Player} to see * price history statistics in the form of a {@link LineChart} through the * {@link StockLineChartView} class, as well as buying {@link Stock}s. + * *

      Extends borderpane to allow easy organizing of the internal layout.

      + * *

      Uses a {@link LanguageManager} to set text to the targer language dynamically.

      */ public class MarketView extends BorderPane { - private final LanguageManager lm = LanguageManager.getInstance(); - - private Button purchaseButton; - private Label selectedStock; - - //Dynamic Labels/Text - public TextField gross; - public TextField commission; - public TextField total; - private TextField searchField; - - //StockListView - private ListView stocksListView; - private FilteredList filteredStocksList; - - //Input - private Spinner quantitySpinner; - private Exchange exchange; - private PurchaseCalculator purchaseCalculator; - - private final StockLineChartView stockLineChartView; - private Subscription priceSubscription; - private Subscription grossSubscription; - private Subscription commissionSubscription; - private Subscription totalSubscription; - - /** - * Initializes the StockLineChartView graph and runs the view - * construction method and places it in the center of the - * BorderPane. - */ - public MarketView() { - stockLineChartView = new StockLineChartView(); - this.setCenter(centerPane()); - } + private final LanguageManager lm = LanguageManager.getInstance(); + + private Button purchaseButton; + private Label selectedStock; + + // Dynamic Labels/Text + public TextField gross; + public TextField commission; + public TextField total; + private TextField searchField; + + // StockListView + private ListView stocksListView; + private FilteredList filteredStocksList; + + // Input + private Spinner quantitySpinner; + + private final StockLineChartView stockLineChartView; + private Subscription priceSubscription; + + /** + * Initializes the StockLineChartView graph and runs the view + * construction method and places it in the center of the + * BorderPane. + */ + public MarketView() { + stockLineChartView = new StockLineChartView(); + this.setCenter(centerPane()); + } + + /** + * Constructs the view and its components. + * + *

      Subscribes to a stock's sales price to update the value immediately.

      + * + *

      Subscribes to the current language to change text immediately.

      + * + *

      Uses a cell factory to format the string found in the stocksListView.

      + * + * @return the constructed MarketView + */ + private VBox centerPane() { + VBox vbox = new VBox(); + + vbox.setPadding(new Insets(15, 12, 15, 12)); + vbox.setSpacing(10); + + // Blue Background color + vbox.setStyle("-fx-background-color: #00bcf0;"); + + Label titleLabel = new Label(); + titleLabel.textProperty().bind(lm.bindString("marketTitle")); + titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; "); + + VBox titleBox = new VBox(titleLabel); + titleBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + titleBox.setAlignment(Pos.CENTER); + titleBox.setPadding(new Insets(4)); + titleBox.setMaxWidth(200); + + // Stock Graph + stockLineChartView.getChart().setMinHeight(0); + stockLineChartView.getChart().setMaxHeight(Double.MAX_VALUE); + stockLineChartView.getChart().setMinWidth(0); + stockLineChartView.getChart().setMaxWidth(Double.MAX_VALUE); + stockLineChartView.getChart().getXAxis().labelProperty().bind(lm.bindString("xAxisLabel")); + stockLineChartView.getChart().getYAxis().labelProperty().bind(lm.bindString("yAxisLabel")); + + // List of stocks + stocksListView = new ListView<>(); + stocksListView.setMinHeight(0); + stocksListView.setMaxHeight(Double.MAX_VALUE); + stocksListView.setMinWidth(0); + stocksListView.setMaxWidth(Double.MAX_VALUE); + stocksListView.setStyle("-fx-background-color: transparent;"); + + stocksListView.setCellFactory(list -> new ListCell<>() { + private Subscription cellSubscription; + private Subscription langSubscription; + + @Override + protected void updateItem(Stock stock, boolean empty) { + super.updateItem(stock, empty); + + if (cellSubscription != null) { + cellSubscription.unsubscribe(); + cellSubscription = null; + } + + if (langSubscription != null) { + langSubscription.unsubscribe(); + langSubscription = null; + } - /** - * Constructs the view and its components. - *

      Subscribes to a stock's sales price to update the value immediately.

      - *

      Subscribes to the current language to change text immediately.

      - *

      Uses a cell factory to format the string found in the stocksListView.

      - * - * @return the constructed MarketView - */ - private VBox centerPane() { - VBox vBox = new VBox(); - - vBox.setPadding(new Insets(15, 12, 15, 12)); - vBox.setSpacing(10); - //Blue Background color - vBox.setStyle("-fx-background-color: #00bcf0;"); - - Label titleLabel = new Label(); - titleLabel.textProperty().bind(lm.bindString("marketTitle")); - titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; "); - - VBox titleBox = new VBox(titleLabel); - titleBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - titleBox.setAlignment(Pos.CENTER); - titleBox.setPadding(new Insets(4)); - titleBox.setMaxWidth(200); - - // Stock Graph - stockLineChartView.getChart().setMinHeight(0); - stockLineChartView.getChart().setMaxHeight(Double.MAX_VALUE); - stockLineChartView.getChart().setMinWidth(0); - stockLineChartView.getChart().setMaxWidth(Double.MAX_VALUE); - stockLineChartView.getChart().getXAxis().labelProperty().bind(lm.bindString("xAxisLabel")); - stockLineChartView.getChart().getYAxis().labelProperty().bind(lm.bindString("yAxisLabel")); - - // List of stocks - stocksListView = new ListView<>(); - stocksListView.setMinHeight(0); - stocksListView.setMaxHeight(Double.MAX_VALUE); - stocksListView.setMinWidth(0); - stocksListView.setMaxWidth(Double.MAX_VALUE); - stocksListView.setStyle("-fx-background-color: transparent;"); - - stocksListView.setCellFactory(list -> new ListCell<>() { - private Subscription cellSubscription; - private Subscription langSubscription; - - @Override - protected void updateItem(Stock stock, boolean empty) { - super.updateItem(stock, empty); - - if (cellSubscription != null) { - cellSubscription.unsubscribe(); - cellSubscription = null; - } - if (langSubscription != null) { - langSubscription.unsubscribe(); - langSubscription = null; - } - - if (empty || stock == null) { - setText(null); - } else { - Runnable updateTextAction = () -> { - String template = lm.getString("stockCellFormat"); - setText(String.format(template, + if (empty || stock == null) { + setText(null); + } else { + Runnable updateTextAction = () -> { + String template = lm.getString("stockCellFormat"); + setText(String.format(template, stock.getSymbol(), stock.getCompany(), FormatBigDecimal.formatNumber(stock.salesPriceProperty().get()), FormatBigDecimal.formatNumber(stock.getLatestPriceChange()), FormatBigDecimal.formatNumber(stock.getHighestPrice()), FormatBigDecimal.formatNumber(stock.getLowestPrice()) - )); - }; - - cellSubscription = stock.salesPriceProperty().subscribe(newPrice -> updateTextAction.run()); - langSubscription = lm.getBundleProperty().subscribe(bundle -> updateTextAction.run()); - - updateTextAction.run(); - } - } - }); - - HBox searchBox = new HBox(); - searchField = new TextField(); - searchField.promptTextProperty().bind(lm.bindString("search")); - searchBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - - searchBox.setPadding(new Insets(8, 14, 8, 14)); - searchBox.setAlignment(Pos.CENTER_LEFT); - searchBox.setMaxWidth(Region.USE_PREF_SIZE); - searchBox.getChildren().addAll(searchField); - - - // Container for list of stocks - HBox stockListBox = new HBox(stocksListView); - stockListBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - - // Container for graph of stocks - HBox stockChartBox = new HBox(stockLineChartView.getChart()); - stockChartBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - - HBox stockBox = new HBox(stockListBox, stockChartBox); - stockBox.setSpacing(10); - - stockListBox.setPadding(new Insets(4)); - stockListBox.setMinHeight(0); - stockListBox.setMaxHeight(Double.MAX_VALUE); - stockListBox.setMinWidth(0); - stockListBox.setMaxWidth(Double.MAX_VALUE); - - VBox.setVgrow(stocksListView, Priority.ALWAYS); - HBox.setHgrow(stocksListView, Priority.ALWAYS); - VBox.setVgrow(stockLineChartView.getChart(), Priority.ALWAYS); - HBox.setHgrow(stockLineChartView.getChart(), Priority.ALWAYS); - VBox.setVgrow(stockListBox, Priority.ALWAYS); - HBox.setHgrow(stockListBox, Priority.ALWAYS); - VBox.setVgrow(stockChartBox, Priority.ALWAYS); - HBox.setHgrow(stockChartBox, Priority.ALWAYS); - VBox.setVgrow(stockBox, Priority.ALWAYS); - HBox.setHgrow(stockBox, Priority.ALWAYS); - - HBox selectedStockBox = new HBox(); - - Label selectedStockLabel = new Label(); - selectedStockLabel.textProperty().bind(lm.bindString("selectedStock")); - selectedStock = new Label(""); - - selectedStockBox.getChildren().addAll(selectedStockLabel, selectedStock); - - HBox quantityBox = new HBox(); + )); + }; - Label quantityLabel = new Label(); - quantityLabel.textProperty().bind(lm.bindString("purchaseQuantity")); - quantitySpinner = new Spinner<>(1, 1_000_000, 1); - quantitySpinner.setEditable(true); + cellSubscription = stock.salesPriceProperty() + .subscribe(newPrice -> updateTextAction.run()); + langSubscription = lm.getBundleProperty() + .subscribe(bundle -> updateTextAction.run()); - quantityBox.getChildren().addAll(quantityLabel, quantitySpinner); - - HBox calculationBox = new HBox(); - - Label grossLabel = new Label(); - grossLabel.textProperty().bind(lm.bindString("purchaseGross")); - gross = new TextField(); - gross.setEditable(false); - gross.setMaxWidth(200); - Label commissionLabel = new Label(); - commissionLabel.textProperty().bind(lm.bindString("purchaseCommission")); - commission = new TextField(); - commission.setEditable(false); - commission.setMaxWidth(200); - - calculationBox.getChildren().addAll(grossLabel, gross, commissionLabel, commission); - - HBox totalBox = new HBox(); - - Label totalLabel = new Label(); - totalLabel.textProperty().bind(lm.bindString("purchaseTotal")); - total = new TextField(""); - total.setEditable(false); - total.setMaxWidth(200); - - totalBox.getChildren().addAll(totalLabel, total); - - purchaseButton = new Button(); - purchaseButton.textProperty().bind(lm.bindString("purchaseButton")); - - VBox purchaseBox = new VBox(5); - - purchaseBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - - purchaseBox.setPadding(new Insets(8, 14, 8, 14)); - purchaseBox.setAlignment(Pos.CENTER_LEFT); - - purchaseBox.getChildren().addAll(selectedStockBox, quantityBox, calculationBox, totalBox, purchaseButton); - - - - vBox.getChildren().addAll(titleBox, searchBox, + updateTextAction.run(); + } + } + }); + + HBox searchBox = new HBox(); + searchField = new TextField(); + searchField.promptTextProperty().bind(lm.bindString("search")); + searchBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + searchBox.setPadding(new Insets(8, 14, 8, 14)); + searchBox.setAlignment(Pos.CENTER_LEFT); + searchBox.setMaxWidth(Region.USE_PREF_SIZE); + searchBox.getChildren().addAll(searchField); + + + // Container for list of stocks + HBox stockListBox = new HBox(stocksListView); + stockListBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + // Container for graph of stocks + HBox stockChartBox = new HBox(stockLineChartView.getChart()); + stockChartBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + HBox stockBox = new HBox(stockListBox, stockChartBox); + stockBox.setSpacing(10); + + stockListBox.setPadding(new Insets(4)); + stockListBox.setMinHeight(0); + stockListBox.setMaxHeight(Double.MAX_VALUE); + stockListBox.setMinWidth(0); + stockListBox.setMaxWidth(Double.MAX_VALUE); + + VBox.setVgrow(stocksListView, Priority.ALWAYS); + HBox.setHgrow(stocksListView, Priority.ALWAYS); + VBox.setVgrow(stockLineChartView.getChart(), Priority.ALWAYS); + HBox.setHgrow(stockLineChartView.getChart(), Priority.ALWAYS); + VBox.setVgrow(stockListBox, Priority.ALWAYS); + HBox.setHgrow(stockListBox, Priority.ALWAYS); + VBox.setVgrow(stockChartBox, Priority.ALWAYS); + HBox.setHgrow(stockChartBox, Priority.ALWAYS); + VBox.setVgrow(stockBox, Priority.ALWAYS); + HBox.setHgrow(stockBox, Priority.ALWAYS); + + HBox selectedStockBox = new HBox(); + + Label selectedStockLabel = new Label(); + selectedStockLabel.textProperty().bind(lm.bindString("selectedStock")); + selectedStock = new Label(""); + + selectedStockBox.getChildren().addAll(selectedStockLabel, selectedStock); + + Label quantityLabel = new Label(); + quantityLabel.textProperty().bind(lm.bindString("purchaseQuantity")); + quantitySpinner = new Spinner<>(1, 1_000_000, 1); + quantitySpinner.setEditable(true); + + HBox quantityBox = new HBox(); + quantityBox.getChildren().addAll(quantityLabel, quantitySpinner); + + Label grossLabel = new Label(); + grossLabel.textProperty().bind(lm.bindString("purchaseGross")); + gross = new TextField(); + gross.setEditable(false); + gross.setMaxWidth(200); + Label commissionLabel = new Label(); + commissionLabel.textProperty().bind(lm.bindString("purchaseCommission")); + commission = new TextField(); + commission.setEditable(false); + commission.setMaxWidth(200); + + HBox calculationBox = new HBox(); + calculationBox.getChildren().addAll(grossLabel, gross, commissionLabel, commission); + + Label totalLabel = new Label(); + totalLabel.textProperty().bind(lm.bindString("purchaseTotal")); + total = new TextField(""); + total.setEditable(false); + total.setMaxWidth(200); + + HBox totalBox = new HBox(); + totalBox.getChildren().addAll(totalLabel, total); + + purchaseButton = new Button(); + purchaseButton.textProperty().bind(lm.bindString("purchaseButton")); + + VBox purchaseBox = new VBox(5); + + purchaseBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + purchaseBox.setPadding(new Insets(8, 14, 8, 14)); + purchaseBox.setAlignment(Pos.CENTER_LEFT); + + purchaseBox.getChildren().addAll( + selectedStockBox, quantityBox, calculationBox, totalBox, purchaseButton); + + + + vbox.getChildren().addAll(titleBox, searchBox, stockBox, purchaseBox); - return vBox; - } - - /** - * Sets and updates subscription to the selected stock's sales price. - * @param stock the selected stock - * @param priceChangeAction the action to run on price change (updates the sales price) - */ - public void setupPriceSubscription(Stock stock, Consumer priceChangeAction) { - if (priceSubscription != null) { - priceSubscription.unsubscribe(); - priceSubscription = null; - } - - if (stock != null) { - priceSubscription = stock.salesPriceProperty().subscribe(newPrice -> { - priceChangeAction.accept(stock); - }); - } - } - - /** - * Updates chart data in the StockLineChartView. - * @param series the {@link javafx.scene.chart.XYChart.Series} data for the chart - * @param companyName the name of the company of the selected stock - */ - public void updateChart(XYChart.Series series, String companyName) { - stockLineChartView.setSeries(series, companyName); - } - - /** - * Unsubscribes from the previous stocks sales price and clears the LineChart. - */ - public void clearChart() { - if (priceSubscription != null) { - priceSubscription.unsubscribe(); - priceSubscription = null; - } - stockLineChartView.getChart().getData().clear(); + return vbox; + } + + /** + * Sets and updates subscription to the selected stock's sales price. + * + * @param stock the selected stock + * @param priceChangeAction the action to run on price change (updates the sales price) + */ + public void setupPriceSubscription(Stock stock, Consumer priceChangeAction) { + if (priceSubscription != null) { + priceSubscription.unsubscribe(); + priceSubscription = null; } - /** - * Sets the action to be run on selecting a stock in stockListView. - * @param action the action to run when selecting a stock - */ - public void onStockSelection(Consumer action) { - stocksListView.getSelectionModel().selectedItemProperty().subscribe(action); + if (stock != null) { + priceSubscription = stock.salesPriceProperty().subscribe(newPrice -> { + priceChangeAction.accept(stock); + }); } - - /** - * Sets the action to be run when clicking the purchase button - * - * @param action the action to run when clicking the purchase button - */ - public void onPurchaseButton(Runnable action) { - purchaseButton.setOnAction(e -> action.run()); + } + + /** + * Updates chart data in the StockLineChartView. + * + * @param series the {@link javafx.scene.chart.XYChart.Series} data for the chart + * @param companyName the name of the company of the selected stock + */ + public void updateChart(XYChart.Series series, String companyName) { + stockLineChartView.setSeries(series, companyName); + } + + /** + * Unsubscribes from the previous stocks sales price and clears the LineChart. + */ + public void clearChart() { + if (priceSubscription != null) { + priceSubscription.unsubscribe(); + priceSubscription = null; } - - public void onQuantitySelect(Runnable action) { - quantitySpinner.valueProperty().addListener((obs, oldValue, newValue) -> { - action.run(); - }); - } - - - - /** - * Sets the exchange to use for the view. - *

      Uses first a {@link FilteredList} to account for what the user has searched for.

      - *

      Then utilises a {@link SortedList} to sort the stocks in the exchange alphabetically - * on their symbol/ticker.

      - * - * @param exchange the exchange to be used for the view - */ - public void setExchange(Exchange exchange) { - this.exchange = exchange; - - - if (exchange != null) { - filteredStocksList = new FilteredList<>(exchange.getObservableStocks()); - - SortedList sortedStocks = new SortedList<>(filteredStocksList); - sortedStocks.setComparator((s1, s2) -> s1.getSymbol().compareToIgnoreCase(s2.getSymbol())); - - stocksListView.setItems(sortedStocks); - - searchField.textProperty().addListener((observable, oldText, newText) -> { - String search = newText == null ? "" : newText.toLowerCase(); - - filteredStocksList.setPredicate(stock -> search.isBlank() + stockLineChartView.getChart().getData().clear(); + } + + /** + * Sets the action to be run on selecting a stock in stockListView. + * + * @param action the action to run when selecting a stock + */ + public void onStockSelection(Consumer action) { + stocksListView.getSelectionModel().selectedItemProperty().subscribe(action); + } + + /** + * Sets the action to be run when clicking the purchase button. + * + * @param action the action to run when clicking the purchase button + */ + public void onPurchaseButton(Runnable action) { + purchaseButton.setOnAction(e -> action.run()); + } + + /** + * Sets the action to be run when changing the value of the quantitySpinner. + * + * @param action the action to run when using the quantitySpinner + */ + public void onQuantitySelect(Runnable action) { + quantitySpinner.valueProperty().addListener((obs, oldValue, newValue) -> { + action.run(); + }); + } + + /** + * Sets the exchange to use for the view. + * + *

      Uses first a {@link FilteredList} to account for what the user has searched for.

      + * + *

      Then utilises a {@link SortedList} to sort the stocks in the exchange alphabetically + * on their symbol/ticker.

      + * + * @param exchange the exchange to be used for the view + */ + public void setExchange(Exchange exchange) { + if (exchange != null) { + filteredStocksList = new FilteredList<>(exchange.getObservableStocks()); + + SortedList sortedStocks = new SortedList<>(filteredStocksList); + sortedStocks.setComparator((s1, s2) -> s1.getSymbol().compareToIgnoreCase(s2.getSymbol())); + + stocksListView.setItems(sortedStocks); + + searchField.textProperty().addListener((observable, oldText, newText) -> { + String search = newText == null ? "" : newText.toLowerCase(); + + filteredStocksList.setPredicate(stock -> search.isBlank() || stock.getSymbol().toLowerCase().contains(search) || stock.getCompany().toLowerCase().contains(search) - ); - }); - } - + ); + }); } - - /** - * Sets the PurchaseCalculator to use for the view. - *

      Uses a {@link Subscription} to observe the calculations made by the current - * Stock and Quantity selected.

      - * - * @param purchaseCalculator the purchaseCalculator to be used for the view - */ - public void setPurchaseCalculator(PurchaseCalculator purchaseCalculator) { - this.purchaseCalculator = purchaseCalculator; - grossSubscription = purchaseCalculator.getGrossProperty().subscribe(newVal -> { - if (newVal != null) { - gross.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { - gross.setText(""); - } - }); - commissionSubscription = purchaseCalculator.getCommissionProperty().subscribe(newVal -> { - if (newVal != null) { + } + + /** + * Sets the PurchaseCalculator to use for the view. + * + *

      Uses a {@link Subscription} to observe the calculations made by the current + * Stock and Quantity selected.

      + * + * @param purchaseCalculator the purchaseCalculator to be used for the view + */ + public void setPurchaseCalculator(PurchaseCalculator purchaseCalculator) { + Subscription grossSubscription = purchaseCalculator.getGrossProperty().subscribe(newVal -> { + if (newVal != null) { + gross.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + gross.setText(""); + } + }); + + Subscription commissionSubscription = + purchaseCalculator.getCommissionProperty().subscribe(newVal -> { + if (newVal != null) { commission.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { + } else { commission.setText(""); - } - }); - totalSubscription = purchaseCalculator.getTotalProperty().subscribe(newVal -> { - if (newVal != null) { - total.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { - total.setText(""); - } - }); - } - - /** - * Returns the selected stock in the stocksListView. - * - * @return the selected stock in the stocksListView - */ - public Stock getSelectedStock() { - return stocksListView.getSelectionModel().getSelectedItem(); - } - - /** - * Returns the numeric value of the quantitySpinner for purchasing {@link Share}s in a stock. - * - * @return an integer of the amount of shares to buy - */ - public int getQuantityPurchase() { - return quantitySpinner.getValue(); - } - - /** - * Sets the company name of the selected stock in the stocksListView. - * - * @param selectedStockString the company name of the selected stock - */ - public void setSelectedStockLabel(String selectedStockString) { - selectedStock.setText(selectedStockString); - } - + } + }); + Subscription totalSubscription = purchaseCalculator.getTotalProperty().subscribe(newVal -> { + if (newVal != null) { + total.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + total.setText(""); + } + }); + } + + /** + * Returns the selected stock in the stocksListView. + * + * @return the selected stock in the stocksListView + */ + public Stock getSelectedStock() { + return stocksListView.getSelectionModel().getSelectedItem(); + } + + /** + * Returns the numeric value of the quantitySpinner for purchasing {@link Share}s in a stock. + * + * @return an integer of the amount of shares to buy + */ + public int getQuantityPurchase() { + return quantitySpinner.getValue(); + } + + /** + * Sets the company name of the selected stock in the stocksListView. + * + * @param selectedStockString the company name of the selected stock + */ + public void setSelectedStockLabel(String selectedStockString) { + selectedStock.setText(selectedStockString); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java index 2360452..838e521 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/NavigationBar.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53.view; +import java.math.RoundingMode; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Button; @@ -12,235 +13,228 @@ import no.ntnu.gruppe53.model.Player; import no.ntnu.gruppe53.service.LanguageManager; -import java.math.RoundingMode; - - /* Yellow Color, Hex: #ffe556 RGB(255, 229, 86) Blue Color, Hex: #00bcf0 RGB(0, 188, 240) Black Color, Hex: #303539 RGB(48, 53, 57) */ - /** * Represents the top bar of the views. + * *

      Provides the user with the ability to switch between views on the fly by * clicking on the buttons representing the desired view.

      + * *

      The {@link no.ntnu.gruppe53.controller.GameController} handles * the actions for the buttons.

      + * *

      Uses a {@link LanguageManager} to set text of UI elements dynamically.

      */ public class NavigationBar extends HBox { - private final LanguageManager lm = LanguageManager.getInstance(); - - private final Button marketButton; - private final Button portfolioButton; - private final Button historyButton; - private final Button endGameButton; - private Subscription netWorthSubscription; - private Subscription moneySubscription; - private Subscription currentWeekSubscription; - - - - private Player player; - private Exchange exchange; - - //Navigation Buttons - - - //Dynamic Labels - private Label playerLabel; - private Label statusLabel; - private Label netWorthLabel; - private Label moneyLabel; - - /** - * Constructs the UI elements of the navigationBar. - *

      Binds the language manager to the textProperty of the UI elements to dynamically change the text.

      - */ - public NavigationBar() { - - this.setPadding(new Insets(15, 12, 15, 12)); - this.setSpacing(10); - //Blue Background color - this.setStyle("-fx-background-color: #00bcf0;" + - "-fx-border-color: transparent transparent #303539 transparent;" + - "-fx-border-width: 0 0 5 0;"); - - - marketButton = new Button(); - marketButton.textProperty().bind(lm.bindString("marketButton")); - marketButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - portfolioButton = new Button(); - portfolioButton.textProperty().bind(lm.bindString("portfolioButton")); - portfolioButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - historyButton = new Button(); - historyButton.textProperty().bind(lm.bindString("historyButton")); - historyButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - - Region spacer = new Region(); - HBox.setHgrow(spacer, Priority.ALWAYS); - - - - HBox playerBox = new HBox(); - - Label playerLabelText = new Label(); - playerLabelText.textProperty().bind(lm.bindString("navPlayerLabelText")); - playerLabel = new Label("Player"); - - playerBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - - playerBox.setPadding(new Insets(8, 14, 8, 14)); - playerBox.setAlignment(Pos.CENTER); - - playerBox.getChildren().addAll(playerLabelText, playerLabel); - - HBox statusBox = new HBox(); - - Label statusLabelText = new Label(); - statusLabelText.textProperty().bind(lm.bindString("statusLabelText")); - statusLabel = new Label(); - - statusBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - statusBox.setPadding(new Insets(8, 14, 8, 14)); - statusBox.setAlignment(Pos.CENTER); - - statusBox.getChildren().addAll(statusLabelText, statusLabel); - - HBox moneyBox = new HBox(); - - Label moneyLabelText = new Label(); - moneyLabelText.textProperty().bind(lm.bindString("navMoneyLabelText")); - moneyLabel = new Label(); - - moneyBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - moneyBox.setPadding(new Insets(8, 14, 8, 14)); - moneyBox.setAlignment(Pos.CENTER); - moneyBox.getChildren().addAll(moneyLabelText, moneyLabel); - - HBox networthBox = new HBox(); - - Label networthLabelText = new Label(); - networthLabelText.textProperty().bind(lm.bindString("navNetWorthLabelText")); - netWorthLabel = new Label(); - - networthBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - networthBox.setPadding(new Insets(8, 14, 8, 14)); - networthBox.setAlignment(Pos.CENTER); - networthBox.getChildren().addAll(networthLabelText, netWorthLabel); - - endGameButton = new Button(); - endGameButton.textProperty().bind(lm.bindString("endGameButton")); - endGameButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - - this.getChildren().addAll(marketButton, portfolioButton, historyButton, spacer, playerBox, statusBox, moneyBox, networthBox, endGameButton); - } - /** - * Exposes the marketButton to the GameController. - * - * @param action the action to be run when clicking the button - */ - public void onMarketButtonClick(Runnable action) { - marketButton.setOnAction(e -> action.run()); + private final Button marketButton; + private final Button portfolioButton; + private final Button historyButton; + private final Button endGameButton; + private Subscription netWorthSubscription; + + private Player player; + + // Dynamic Labels + private final Label playerLabel; + private final Label statusLabel; + private final Label netWorthLabel; + private final Label moneyLabel; + + /** + * Constructs the UI elements of the navigationBar. + * + *

      Binds the language manager to the textProperty of the + * UI elements to dynamically change the text.

      + */ + public NavigationBar() { + this.setPadding(new Insets(15, 12, 15, 12)); + this.setSpacing(10); + // Blue Background color + this.setStyle("-fx-background-color: #00bcf0;" + + "-fx-border-color: transparent transparent #303539 transparent;" + + "-fx-border-width: 0 0 5 0;"); + + marketButton = new Button(); + LanguageManager lm = LanguageManager.getInstance(); + marketButton.textProperty().bind(lm.bindString("marketButton")); + marketButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + portfolioButton = new Button(); + portfolioButton.textProperty().bind(lm.bindString("portfolioButton")); + portfolioButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + historyButton = new Button(); + historyButton.textProperty().bind(lm.bindString("historyButton")); + historyButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + + HBox playerBox = new HBox(); + + Label playerLabelText = new Label(); + playerLabelText.textProperty().bind(lm.bindString("navPlayerLabelText")); + playerLabel = new Label("Player"); + + playerBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + playerBox.setPadding(new Insets(8, 14, 8, 14)); + playerBox.setAlignment(Pos.CENTER); + + playerBox.getChildren().addAll(playerLabelText, playerLabel); + + HBox statusBox = new HBox(); + + Label statusLabelText = new Label(); + statusLabelText.textProperty().bind(lm.bindString("statusLabelText")); + statusLabel = new Label(); + + statusBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + statusBox.setPadding(new Insets(8, 14, 8, 14)); + statusBox.setAlignment(Pos.CENTER); + + statusBox.getChildren().addAll(statusLabelText, statusLabel); + + HBox moneyBox = new HBox(); + + Label moneyLabelText = new Label(); + moneyLabelText.textProperty().bind(lm.bindString("navMoneyLabelText")); + moneyLabel = new Label(); + + moneyBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + moneyBox.setPadding(new Insets(8, 14, 8, 14)); + moneyBox.setAlignment(Pos.CENTER); + moneyBox.getChildren().addAll(moneyLabelText, moneyLabel); + + HBox networthBox = new HBox(); + + Label networthLabelText = new Label(); + networthLabelText.textProperty().bind(lm.bindString("navNetWorthLabelText")); + netWorthLabel = new Label(); + + networthBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + networthBox.setPadding(new Insets(8, 14, 8, 14)); + networthBox.setAlignment(Pos.CENTER); + networthBox.getChildren().addAll(networthLabelText, netWorthLabel); + + endGameButton = new Button(); + endGameButton.textProperty().bind(lm.bindString("endGameButton")); + endGameButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + + this.getChildren().addAll( + marketButton, portfolioButton, historyButton, + spacer, playerBox, statusBox, moneyBox, networthBox, endGameButton); + } + + /** + * Exposes the marketButton to the GameController. + * + * @param action the action to be run when clicking the button + */ + public void onMarketButtonClick(Runnable action) { + marketButton.setOnAction(e -> action.run()); + } + + /** + * Exposes the portfolioButton to the GameController. + * + * @param action the action to be run when clicking the button + */ + public void onPortfolioButtonClick(Runnable action) { + portfolioButton.setOnAction(e -> action.run()); + } + + /** + * Exposes the historyButton to the GameController. + * + * @param action the action to be run when clicking the button + */ + public void onHistoryButtonClick(Runnable action) { + historyButton.setOnAction(e -> action.run()); + } + + /** + * Exposes the endGameButton to the GameController. + * + * @param action the action to be run when clicking the button + */ + public void onEndGameButtonClick(Runnable action) { + endGameButton.setOnAction(e -> action.run()); + } + + /** + * Binds a subscription/observer to a {@link Player}. + * + *

      It specifically listens to the players money, net worth and name.

      + * + * @param player the player object to be observed + */ + public void bindPlayer(Player player) { + this.player = player; + + if (netWorthSubscription != null) { + netWorthSubscription.unsubscribe(); } - /** - * Exposes the portfolioButton to the GameController. - * - * @param action the action to be run when clicking the button - */ - public void onPortfolioButtonClick(Runnable action) { - portfolioButton.setOnAction(e -> action.run()); - } + if (player != null) { + playerLabel.setText(player.getName()); + netWorthSubscription = player.getNetWorthProperty().subscribe(newVal -> { + + if (newVal != null) { + netWorthLabel.setText("$" + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + netWorthLabel.setText("$0.00"); + } + }); + + Subscription moneySubscription = player.getMoneyProperty().subscribe(newVal -> { + if (newVal != null) { + moneyLabel.setText("$" + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + moneyLabel.setText("$0.00"); + } + }); - /** - * Exposes the historyButton to the GameController. - * - * @param action the action to be run when clicking the button - */ - public void onHistoryButtonClick(Runnable action) { - historyButton.setOnAction(e -> action.run()); } - - /** - * Exposes the endGameButton to the GameController. - * - * @param action the action to be run when clicking the button - */ - public void onEndGameButtonClick(Runnable action) { - endGameButton.setOnAction(e -> action.run()); - } - - - /** - * Binds a subscription/observer to a {@link Player}. - *

      It specifically listens to the players money, net worth and name.

      - * - * @param player the player object to be observed - */ - public void bindPlayer(Player player) { - this.player = player; - - if (netWorthSubscription != null) { - netWorthSubscription.unsubscribe(); - } - - if (player != null) { - playerLabel.setText(player.getName()); - netWorthSubscription = player.getNetWorthProperty().subscribe(newVal -> { - if (newVal != null) { - netWorthLabel.setText("$" + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { - netWorthLabel.setText("$0.00"); - } - }); - moneySubscription = player.getMoneyProperty().subscribe(newVal -> { - if (newVal != null) { - moneyLabel.setText("$" + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { - moneyLabel.setText("$0.00"); - } - }); - - } - } - - /** - * Binds a subscription/observer to a {@link Exchange}. - *

      It specifically listens to week changes so that it can update the player status.

      - * - * @param exchange the exchange object to be observed - */ - public void bindExchange(Exchange exchange) { - this.exchange = exchange; - - currentWeekSubscription = exchange.getCurrentWeekProperty().subscribe(newVal -> { - statusLabel.setText(player.getStatus(exchange)); - }); - } - - + } + + /** + * Binds a subscription/observer to a {@link Exchange}. + * + *

      It specifically listens to week changes so that it can update the player status.

      + * + * @param exchange the exchange object to be observed + */ + public void bindExchange(Exchange exchange) { + Subscription currentWeekSubscription = exchange.getCurrentWeekProperty().subscribe(newVal -> { + statusLabel.setText(player.getStatus(exchange)); + }); + } } diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java index 0711ae2..046fcb1 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerNameChooserView.java @@ -1,15 +1,19 @@ package no.ntnu.gruppe53.view; -import no.ntnu.gruppe53.model.Player; - +import java.util.Optional; import javafx.geometry.Insets; -import javafx.scene.control.*; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.control.TextFormatter; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; +import no.ntnu.gruppe53.model.Player; import no.ntnu.gruppe53.service.LanguageManager; -import java.util.Optional; - /* Yellow Color, Hex: #ffe556 RGB(255, 229, 86) Blue Color, Hex: #00bcf0 RGB(0, 188, 240) @@ -17,93 +21,94 @@ */ /** - * Represents a prompt for the {@link Player}'s name + * Represents a prompt for the {@link Player}'s name. */ public class PlayerNameChooserView { - private final LanguageManager lm = LanguageManager.getInstance(); - Dialog dialog; - - /** - * Returns the dialog where the player can input a name of maximum 50 characters. - *

      A {@link TextFormatter} is used to set the maximum amount - * of characters for the player name.

      - *

      Uses a {@link LanguageManager} to determine text language.

      - * - * @return the dialog for player name input - */ - public Optional getDialog() { - dialog = new Dialog<>(); - - // Sets dialog title and header text - dialog.setTitle(null); - dialog.setHeaderText(null); - - // Sets the dimensions for the dialog pane - dialog.setResizable(true); - - dialog.getDialogPane().setPrefWidth(320); - dialog.getDialogPane().setPrefHeight(50); - - ButtonType confirmButton = + private final LanguageManager lm = LanguageManager.getInstance(); + Dialog dialog; + + /** + * Returns the dialog where the player can input a name of maximum 50 characters. + * + *

      A {@link TextFormatter} is used to set the maximum amount + * of characters for the player name.

      + * + *

      Uses a {@link LanguageManager} to determine text language.

      + * + * @return the dialog for player name input + */ + public Optional getDialog() { + dialog = new Dialog<>(); + + // Sets dialog title and header text + dialog.setTitle(null); + dialog.setHeaderText(null); + + // Sets the dimensions for the dialog pane + dialog.setResizable(true); + + dialog.getDialogPane().setPrefWidth(320); + dialog.getDialogPane().setPrefHeight(50); + + ButtonType confirmButton = new ButtonType("Confirm", ButtonBar.ButtonData.OK_DONE); - dialog.getDialogPane().getButtonTypes().add(confirmButton); - Button actualConfirmButton = (Button) dialog.getDialogPane().lookupButton(confirmButton); - - actualConfirmButton.textProperty().bind(lm.bindString("playerNameConfirmButton")); + dialog.getDialogPane().getButtonTypes().add(confirmButton); + Button actualConfirmButton = (Button) dialog.getDialogPane().lookupButton(confirmButton); - TextField nameField = new TextField(); + actualConfirmButton.textProperty().bind(lm.bindString("playerNameConfirmButton")); - nameField.setEditable(true); + TextField nameField = new TextField(); - int maxLength = 50; + nameField.setEditable(true); - // Prevents player name over 50 characters + int maxLength = 50; - TextFormatter formatter = new TextFormatter<>(change -> { + // Prevents player name over 50 characters - String newText = change.getControlNewText(); + TextFormatter formatter = new TextFormatter<>(change -> { - if (newText.length() <= maxLength) { - return change; - } + String newText = change.getControlNewText(); - return null; + if (newText.length() <= maxLength) { + return change; + } - }); + return null; - nameField.setTextFormatter(formatter); + }); - // Adds a counter for max characters - Label counterLabel = new Label("0 / 50"); + nameField.setTextFormatter(formatter); - nameField.textProperty().addListener((obs, oldVal, newVal) -> { - counterLabel.setText(newVal.length() + " / 50"); - }); + // Adds a counter for max characters + Label counterLabel = new Label("0 / 50"); - Label infoLabel = new Label(); - infoLabel.textProperty().bind(lm.bindString("playerNameInfo")); + nameField.textProperty().addListener((obs, oldVal, newVal) -> { + counterLabel.setText(newVal.length() + " / 50"); + }); - VBox layout = new VBox(15); + Label infoLabel = new Label(); + infoLabel.textProperty().bind(lm.bindString("playerNameInfo")); - HBox hBox = new HBox(15); + VBox layout = new VBox(15); - layout.setPadding(new Insets(10)); + HBox hbox = new HBox(15); - hBox.getChildren().addAll(nameField, counterLabel); + layout.setPadding(new Insets(10)); - layout.getChildren().addAll(infoLabel, hBox); + hbox.getChildren().addAll(nameField, counterLabel); - dialog.getDialogPane().setContent(layout); + layout.getChildren().addAll(infoLabel, hbox); - dialog.setResultConverter(button -> { - if (button == confirmButton) { + dialog.getDialogPane().setContent(layout); - return nameField.getText(); - } + dialog.setResultConverter(button -> { + if (button == confirmButton) { + return nameField.getText(); + } - return null; - }); + return null; + }); - return dialog.showAndWait(); - } + return dialog.showAndWait(); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PlayerStartingMoneyChooserView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerStartingMoneyChooserView.java index 7dd6d65..7962367 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/PlayerStartingMoneyChooserView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PlayerStartingMoneyChooserView.java @@ -1,110 +1,116 @@ package no.ntnu.gruppe53.view; -import no.ntnu.gruppe53.model.Player; - +import java.math.BigDecimal; +import java.util.Optional; import javafx.geometry.Insets; -import javafx.scene.control.*; +import javafx.scene.control.Button; +import javafx.scene.control.ButtonBar; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Dialog; +import javafx.scene.control.Label; +import javafx.scene.control.Spinner; +import javafx.scene.control.SpinnerValueFactory; +import javafx.scene.control.TextFormatter; import javafx.scene.layout.VBox; +import no.ntnu.gruppe53.model.Player; import no.ntnu.gruppe53.service.LanguageManager; -import java.math.BigDecimal; -import java.util.Optional; - /** * Represents the prompt for the {@link Player}'s starting money. + * *

      Uses a {@link LanguageManager} to set text to target language.

      */ public class PlayerStartingMoneyChooserView { - Dialog dialog = new Dialog<>(); - private final LanguageManager lm = LanguageManager.getInstance(); - - /** - * Returns the dialog for the player's starting money. - *

      Uses a {@link TextFormatter} and listener for value integrity.

      - *

      Uses a {@link SpinnerValueFactory} to set min, max, default, - * and step size values for the spinner.

      - * @return the dialog for player's starting money - */ - public Optional getDialog() { - - // Sets dialog title and header text - dialog.setTitle(null); - dialog.setHeaderText(null); - - // Sets the dimensions for the dialog pane - dialog.setResizable(true); - - dialog.getDialogPane().setPrefWidth(320); - dialog.getDialogPane().setPrefHeight(50); - - ButtonType confirmButton = - new ButtonType("Confirm", ButtonBar.ButtonData.OK_DONE); - dialog.getDialogPane().getButtonTypes().add(confirmButton); - - Button actualConfirmButton = (Button) dialog.getDialogPane().lookupButton(confirmButton); - - actualConfirmButton.textProperty().bind(lm.bindString("playerStartingMoneyConfirmButton")); - - Spinner spinner = new Spinner<>(); - - // Sets allowed values and step size for the spinner - SpinnerValueFactory.DoubleSpinnerValueFactory valueFactory = - new SpinnerValueFactory.DoubleSpinnerValueFactory( + Dialog dialog = new Dialog<>(); + private final LanguageManager lm = LanguageManager.getInstance(); + + /** + * Returns the dialog for the player's starting money. + * + *

      Uses a {@link TextFormatter} and listener for value integrity.

      + * + *

      Uses a {@link SpinnerValueFactory} to set min, max, default, + * and step size values for the spinner.

      + * + * @return the dialog for player's starting money + */ + public Optional getDialog() { + // Sets dialog title and header text + dialog.setTitle(null); + dialog.setHeaderText(null); + + // Sets the dimensions for the dialog pane + dialog.setResizable(true); + + dialog.getDialogPane().setPrefWidth(320); + dialog.getDialogPane().setPrefHeight(50); + + ButtonType confirmButton = + new ButtonType("Confirm", ButtonBar.ButtonData.OK_DONE); + dialog.getDialogPane().getButtonTypes().add(confirmButton); + + Button actualConfirmButton = (Button) dialog.getDialogPane().lookupButton(confirmButton); + + actualConfirmButton.textProperty().bind(lm.bindString("playerStartingMoneyConfirmButton")); + + Spinner spinner = new Spinner<>(); + + // Sets allowed values and step size for the spinner + SpinnerValueFactory.DoubleSpinnerValueFactory valueFactory = + new SpinnerValueFactory.DoubleSpinnerValueFactory( 1000.0, // min 1000000.0, // max 5000.0, // initial value 100.0 // step-size - ); - - spinner.setValueFactory(valueFactory); - - spinner.setEditable(true); + ); - // Formats the text to only allow numbers, and doubles preceded by a number - TextFormatter formatter = new TextFormatter<>(change -> { + spinner.setValueFactory(valueFactory); - String newText = change.getControlNewText(); + spinner.setEditable(true); - if (newText.matches("\\d+(\\.\\d*)?")) { + // Formats the text to only allow numbers, and doubles preceded by a number + TextFormatter formatter = new TextFormatter<>(change -> { - return change; - } + String newText = change.getControlNewText(); - return null; - }); + if (newText.matches("\\d+(\\.\\d*)?")) { + return change; + } - spinner.focusedProperty().addListener((obs, oldVal, newVal) -> { + return null; + }); - if (!newVal) { - spinner.increment(0); - } - }); + spinner.focusedProperty().addListener((obs, oldVal, newVal) -> { - spinner.getEditor().setTextFormatter(formatter); + if (!newVal) { + spinner.increment(0); + } + }); - spinner.setPrefWidth(200); + spinner.getEditor().setTextFormatter(formatter); - Label infoLabel = new Label(); - infoLabel.textProperty().bind(lm.bindString("playerStartingMoneyInfoLabel")); + spinner.setPrefWidth(200); - VBox layout = new VBox(15); + Label infoLabel = new Label(); + infoLabel.textProperty().bind(lm.bindString("playerStartingMoneyInfoLabel")); - layout.setPadding(new Insets(10)); + VBox layout = new VBox(15); - layout.getChildren().addAll(infoLabel, spinner); + layout.setPadding(new Insets(10)); - dialog.getDialogPane().setContent(layout); + layout.getChildren().addAll(infoLabel, spinner); - dialog.setResultConverter(button -> { + dialog.getDialogPane().setContent(layout); - if (button == confirmButton) { + dialog.setResultConverter(button -> { - return BigDecimal.valueOf(spinner.getValue()); - } + if (button == confirmButton) { + return BigDecimal.valueOf(spinner.getValue()); + } - return null; - }); + return null; + }); - return dialog.showAndWait(); - } + return dialog.showAndWait(); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java index 4c55fe5..416d5c0 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/PortfolioView.java @@ -3,17 +3,19 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.util.function.Consumer; - -import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; -import javafx.geometry.Pos; -import javafx.scene.control.*; -import javafx.scene.layout.*; +import javafx.scene.control.TextField; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; import javafx.scene.text.Text; import javafx.scene.text.TextFlow; import javafx.util.Subscription; @@ -29,354 +31,351 @@ Black Color, Hex: #303539 RGB(48, 53, 57) */ - /** * Represent a view of the {@link Player}s {@link no.ntnu.gruppe53.model.Portfolio}. + * *

      Lets the player view their current {@link Share} assets, see their * current sale value, whether the value has gone up or down since they purchased, * and the ability to sell the share for money.

      + * *

      Extends the {@link BorderPane}

      for easy layout setup. + * *

      Events are handled by the {@link no.ntnu.gruppe53.controller.GameController}.

      + * *

      Uses a {@link LanguageManager} to dynamically set text based on target language.

      */ public class PortfolioView extends BorderPane { - private final LanguageManager lm = LanguageManager.getInstance(); - - private Button sellButton; - - //Static Labels - private Label titleLabel; - - //Dynamic Labels - private Label selectedShare; - private TextField gross; - private TextField commission; - private TextField tax; - private TextField total; - private TextField profit; - private TextField searchField; - - //PortfolioListView - private ListView portfolioListView; - private FilteredList filteredPortfolioList; - - private Player player; - private SaleCalculator saleCalculator; - - private Subscription grossSubscription; - private Subscription commissionSubscription; - private Subscription taxSubscription; - private Subscription totalSubscription; - private Subscription profitSubscription; - - /** - * Builds the view components and places them in the center pane. - */ - public PortfolioView() { - this.setCenter(centerPane()); - } - - /** - * Constructs the view of the portfolio. - *

      Subscribes to shares for sales price updates.

      - *

      Uses a cell factory to format the displayed string representing each share. - * The sell factory has the text dynamically set by the language manager.

      - * - * @return a VBox of the portfolio view - */ - private VBox centerPane() { - VBox vBox = new VBox(); - vBox.setPadding(new Insets(15, 12, 15, 12)); - vBox.setSpacing(10); - //Blue Background color - vBox.setStyle("-fx-background-color: #00bcf0; "); - - titleLabel = new Label(); - titleLabel.textProperty().bind(lm.bindString("portfolioTitle")); - titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; -fx-text-fill: #303539;"); - - VBox titleBox = new VBox(titleLabel); - titleBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - titleBox.setAlignment(Pos.CENTER); - titleBox.setPadding(new Insets(4)); - titleBox.setMaxWidth(200); - - portfolioListView = new ListView<>(); - portfolioListView.setMinHeight(0); - portfolioListView.setMaxHeight(Double.MAX_VALUE); - - portfolioListView.setCellFactory(list -> new ListCell<>() { - private Subscription cellSubscription; - private Subscription langSubscription; - - @Override - protected void updateItem(Share item, boolean empty) { - super.updateItem(item, empty); - - if (cellSubscription != null) { - cellSubscription.unsubscribe(); - cellSubscription = null; - } - if (langSubscription != null) { - langSubscription.unsubscribe(); - langSubscription = null; - } - - if (empty || item == null || item.getStock() == null) { - setText(null); - setGraphic(null); - } else { - setText(null); - - - Runnable updateGraphicAction = () -> { - BigDecimal currentPrice = item.getStock().salesPriceProperty().get(); - setGraphic(createColoredShareText(item, currentPrice)); - }; - - cellSubscription = item.getStock().salesPriceProperty().subscribe(currentPrice -> { - updateGraphicAction.run(); - }); - - langSubscription = lm.getBundleProperty().subscribe(bundle -> { - updateGraphicAction.run(); - }); - - updateGraphicAction.run(); - } + private final LanguageManager lm = LanguageManager.getInstance(); + + private Button sellButton; + + // Dynamic Labels + private Label selectedShare; + private TextField gross; + private TextField commission; + private TextField tax; + private TextField total; + private TextField profit; + private TextField searchField; + + // PortfolioListView + private ListView portfolioListView; + private FilteredList filteredPortfolioList; + + /** + * Builds the view components and places them in the center pane. + */ + public PortfolioView() { + this.setCenter(centerPane()); + } + + /** + * Constructs the view of the portfolio. + * + *

      Subscribes to shares for sales price updates.

      + * + *

      Uses a cell factory to format the displayed string representing each share. + * The sell factory has the text dynamically set by the language manager.

      + * + * @return a VBox of the portfolio view + */ + private VBox centerPane() { + VBox vbox = new VBox(); + vbox.setPadding(new Insets(15, 12, 15, 12)); + vbox.setSpacing(10); + // Blue Background color + vbox.setStyle("-fx-background-color: #00bcf0; "); + + Label titleLabel = new Label(); + titleLabel.textProperty().bind(lm.bindString("portfolioTitle")); + titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; -fx-text-fill: #303539;"); + + VBox titleBox = new VBox(titleLabel); + titleBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + titleBox.setAlignment(Pos.CENTER); + titleBox.setPadding(new Insets(4)); + titleBox.setMaxWidth(200); + + portfolioListView = new ListView<>(); + portfolioListView.setMinHeight(0); + portfolioListView.setMaxHeight(Double.MAX_VALUE); + + portfolioListView.setCellFactory(list -> new ListCell<>() { + private Subscription cellSubscription; + private Subscription langSubscription; + + @Override + protected void updateItem(Share item, boolean empty) { + super.updateItem(item, empty); + + if (cellSubscription != null) { + cellSubscription.unsubscribe(); + cellSubscription = null; } - }); - - HBox searchBox = new HBox(); - searchField = new TextField(); - searchField.promptTextProperty().bind(lm.bindString("search")); - searchBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - - searchBox.setPadding(new Insets(8, 14, 8, 14)); - searchBox.setAlignment(Pos.CENTER_LEFT); - searchBox.setMaxWidth(Region.USE_PREF_SIZE); - searchBox.getChildren().addAll(searchField); - - VBox portfolioListBox = new VBox(portfolioListView); - portfolioListBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - portfolioListBox.setPadding(new Insets(4)); - portfolioListBox.setMinHeight(0); - portfolioListBox.setMaxHeight(Double.MAX_VALUE); - - // Ensures the portfoliolist grows to fit the available screenspace - VBox.setVgrow(portfolioListView, Priority.ALWAYS); - VBox.setVgrow(portfolioListBox, Priority.ALWAYS); - - - HBox selectedShareBox = new HBox(); - - Label selectedShareLabel = new Label(); - selectedShareLabel.textProperty().bind(lm.bindString("selectedShare")); - selectedShare = new Label(); - - selectedShareBox.getChildren().addAll(selectedShareLabel, selectedShare); - - HBox calculationBox = new HBox(); - Label grossLabel = new Label(); - grossLabel.textProperty().bind(lm.bindString("portfolioGross")); - gross = new TextField(); - gross.setEditable(false); - gross.setMaxWidth(200); - Label commissionLabel = new Label(); - commissionLabel.textProperty().bind(lm.bindString("portfolioCommission")); - commission = new TextField(); - commission.setEditable(false); - commission.setMaxWidth(200); - Label taxLabel = new Label(); - taxLabel.textProperty().bind(lm.bindString("portfolioTax")); - tax = new TextField(); - tax.setEditable(false); - tax.setMaxWidth(200); - - calculationBox.getChildren().addAll(grossLabel, gross, commissionLabel, commission, taxLabel, tax); - - - - HBox totalBox = new HBox(); - - Label totalLabel = new Label(); - totalLabel.textProperty().bind(lm.bindString("portfolioTotal")); - total = new TextField(); - total.setEditable(false); - total.setMaxWidth(200); - - Label profitLabel = new Label(); - profitLabel.textProperty().bind(lm.bindString("portfolioProfit")); - profit = new TextField(); - profit.setEditable(false); - profit.setMaxWidth(200); - - totalBox.getChildren().addAll(totalLabel, total, profitLabel, profit); - - sellButton = new Button(); - sellButton.textProperty().bind(lm.bindString("sellButton")); + if (langSubscription != null) { + langSubscription.unsubscribe(); + langSubscription = null; + } - VBox saleBox = new VBox(5); + if (empty || item == null || item.getStock() == null) { + setText(null); + setGraphic(null); + } else { + setText(null); - saleBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - saleBox.setPadding(new Insets(8, 14, 8, 14)); - saleBox.setAlignment(Pos.CENTER_LEFT); + Runnable updateGraphicAction = () -> { + BigDecimal currentPrice = item.getStock().salesPriceProperty().get(); + setGraphic(createColoredShareText(item, currentPrice)); + }; - saleBox.getChildren().addAll(selectedShareBox, calculationBox, totalBox, sellButton); + cellSubscription = item.getStock().salesPriceProperty().subscribe(currentPrice -> { + updateGraphicAction.run(); + }); + langSubscription = lm.getBundleProperty().subscribe(bundle -> { + updateGraphicAction.run(); + }); - vBox.getChildren().addAll(titleBox, searchBox, + updateGraphicAction.run(); + } + } + }); + + HBox searchBox = new HBox(); + searchField = new TextField(); + searchField.promptTextProperty().bind(lm.bindString("search")); + searchBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + searchBox.setPadding(new Insets(8, 14, 8, 14)); + searchBox.setAlignment(Pos.CENTER_LEFT); + searchBox.setMaxWidth(Region.USE_PREF_SIZE); + searchBox.getChildren().addAll(searchField); + + VBox portfolioListBox = new VBox(portfolioListView); + portfolioListBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + portfolioListBox.setPadding(new Insets(4)); + portfolioListBox.setMinHeight(0); + portfolioListBox.setMaxHeight(Double.MAX_VALUE); + + // Ensures the portfoliolist grows to fit the available screenspace + VBox.setVgrow(portfolioListView, Priority.ALWAYS); + VBox.setVgrow(portfolioListBox, Priority.ALWAYS); + + HBox selectedShareBox = new HBox(); + + Label selectedShareLabel = new Label(); + selectedShareLabel.textProperty().bind(lm.bindString("selectedShare")); + selectedShare = new Label(); + + selectedShareBox.getChildren().addAll(selectedShareLabel, selectedShare); + + Label grossLabel = new Label(); + grossLabel.textProperty().bind(lm.bindString("portfolioGross")); + gross = new TextField(); + gross.setEditable(false); + gross.setMaxWidth(200); + Label commissionLabel = new Label(); + commissionLabel.textProperty().bind(lm.bindString("portfolioCommission")); + commission = new TextField(); + commission.setEditable(false); + commission.setMaxWidth(200); + Label taxLabel = new Label(); + taxLabel.textProperty().bind(lm.bindString("portfolioTax")); + tax = new TextField(); + tax.setEditable(false); + tax.setMaxWidth(200); + + HBox calculationBox = new HBox(); + calculationBox.getChildren().addAll( + grossLabel, gross, commissionLabel, commission, taxLabel, tax); + + Label totalLabel = new Label(); + totalLabel.textProperty().bind(lm.bindString("portfolioTotal")); + total = new TextField(); + total.setEditable(false); + total.setMaxWidth(200); + + Label profitLabel = new Label(); + profitLabel.textProperty().bind(lm.bindString("portfolioProfit")); + profit = new TextField(); + profit.setEditable(false); + profit.setMaxWidth(200); + + HBox totalBox = new HBox(); + totalBox.getChildren().addAll(totalLabel, total, profitLabel, profit); + + sellButton = new Button(); + sellButton.textProperty().bind(lm.bindString("sellButton")); + + VBox saleBox = new VBox(5); + + saleBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + saleBox.setPadding(new Insets(8, 14, 8, 14)); + saleBox.setAlignment(Pos.CENTER_LEFT); + + saleBox.getChildren().addAll(selectedShareBox, calculationBox, totalBox, sellButton); + + + vbox.getChildren().addAll(titleBox, searchBox, portfolioListBox, saleBox); - return vBox; - } - - /** - * Exposes the sell button to the GameController. - * @param action the action to be run when pressing the sell button - */ - public void onSellButton(Runnable action) { - sellButton.setOnAction(e -> action.run()); - } - - /** - * Sets the action to be run on selecting a share in portfolioListView. - * @param action the action to run when selecting a share - */ - public void onShareSelection(Consumer action) { - portfolioListView.getSelectionModel().selectedItemProperty().subscribe(action); - } - - /** - * Gets the selected share in the portfolioListView. - * - * @return the currently selected share - */ - public Share getSelectedShare() { - return portfolioListView.getSelectionModel().getSelectedItem(); - } - - /** - * Sets the player to be observed. - *

      The values observed are the players portfolio and name.

      - *

      Uses a {@link FilteredList} to get and filter the shares and - * binds it to the portfolioListView.

      - * - * @param player the player to be observed - */ - public void setPlayer(Player player) { - this.player = player; - - if (player != null && player.getPortfolio() != null) { - - filteredPortfolioList = new FilteredList<>(player.getPortfolio().getShares()); - - portfolioListView.setItems(filteredPortfolioList); - - searchField.textProperty().addListener((observable, oldText, newText) -> { - String search = newText == null ? "" : newText.toLowerCase(); - - filteredPortfolioList.setPredicate(share -> search.isBlank() + return vbox; + } + + /** + * Exposes the sell button to the GameController. + * + * @param action the action to be run when pressing the sell button + */ + public void onSellButton(Runnable action) { + sellButton.setOnAction(e -> action.run()); + } + + /** + * Sets the action to be run on selecting a share in portfolioListView. + * + * @param action the action to run when selecting a share + */ + public void onShareSelection(Consumer action) { + portfolioListView.getSelectionModel().selectedItemProperty().subscribe(action); + } + + /** + * Gets the selected share in the portfolioListView. + * + * @return the currently selected share + */ + public Share getSelectedShare() { + return portfolioListView.getSelectionModel().getSelectedItem(); + } + + /** + * Sets the player to be observed. + * + *

      The values observed are the players portfolio and name.

      + * + *

      Uses a {@link FilteredList} to get and filter the shares and + * binds it to the portfolioListView.

      + * + * @param player the player to be observed + */ + public void setPlayer(Player player) { + if (player != null && player.getPortfolio() != null) { + filteredPortfolioList = new FilteredList<>(player.getPortfolio().getShares()); + + portfolioListView.setItems(filteredPortfolioList); + + searchField.textProperty().addListener((observable, oldText, newText) -> { + String search = newText == null ? "" : newText.toLowerCase(); + + filteredPortfolioList.setPredicate(share -> search.isBlank() || share.getStock().getSymbol().toLowerCase().contains(search) || share.getStock().getCompany().toLowerCase().contains(search) - ); - }); - - } else { + ); + }); - portfolioListView.setItems(null); - } - } + } else { - /** - * Sets the share name of the selected share in the portfolioListView. - * - * @param selectedShareString the share name of the selected share - */ - public void setSelectedShareLabel(String selectedShareString) { - selectedShare.setText(selectedShareString); + portfolioListView.setItems(null); } - - /** - * Sets the SaleCalculator to use for the view. - *

      Uses a {@link Subscription} to observe the calculations made by the current - * Share selected.

      - * - * @param saleCalculator the SaleCalculator to be used for the view - */ - public void setSaleCalculator(SaleCalculator saleCalculator) { - this.saleCalculator = saleCalculator; - grossSubscription = saleCalculator.getGrossProperty().subscribe(newVal -> { - if (newVal != null) { + } + + /** + * Sets the share name of the selected share in the portfolioListView. + * + * @param selectedShareString the share name of the selected share + */ + public void setSelectedShareLabel(String selectedShareString) { + selectedShare.setText(selectedShareString); + } + + /** + * Sets the SaleCalculator to use for the view. + * + *

      Uses a {@link Subscription} to observe the calculations made by the current + * Share selected.

      + * + * @param saleCalculator the SaleCalculator to be used for the view + */ + public void setSaleCalculator(SaleCalculator saleCalculator) { + Subscription grossSubscription = + saleCalculator.getGrossProperty().subscribe(newVal -> { + if (newVal != null) { gross.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { + } else { gross.setText(""); - } - }); - commissionSubscription = saleCalculator.getCommissionProperty().subscribe(newVal -> { - if (newVal != null) { + } + }); + + Subscription commissionSubscription = + saleCalculator.getCommissionProperty().subscribe(newVal -> { + if (newVal != null) { commission.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { + } else { commission.setText(""); - } - }); - taxSubscription = saleCalculator.getTaxProperty().subscribe(newVal -> { - if (newVal != null) { - tax.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN).abs()); - } else { - tax.setText(""); - } - }); - totalSubscription = saleCalculator.getTotalProperty().subscribe(newVal -> { - if (newVal != null) { - total.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { - total.setText(""); - } - }); - profitSubscription = saleCalculator.getProfitProperty().subscribe(newVal -> { - if (newVal != null) { - profit.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); - } else { - profit.setText(""); - } - }); - } - + } + }); - /** - * Formats the text representation of the share in the portfolioListView - * and applies a color to it based on its current sales price compared - * to the buy price of the share. - * - * @param share the share to be formatted - * @param currentPrice the cusort the stocks in the exchange alphabetically - * on their symbol/tickerrrent price of the share - * @return a {@link TextFlow} representation of the share - */ - private TextFlow createColoredShareText(Share share, BigDecimal currentPrice) { - String text = share.getStock().getSymbol() + Subscription taxSubscription = saleCalculator.getTaxProperty().subscribe(newVal -> { + if (newVal != null) { + tax.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN).abs()); + } else { + tax.setText(""); + } + }); + + Subscription totalSubscription = saleCalculator.getTotalProperty().subscribe(newVal -> { + if (newVal != null) { + total.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + total.setText(""); + } + }); + + Subscription profitSubscription = saleCalculator.getProfitProperty().subscribe(newVal -> { + if (newVal != null) { + profit.setText("$ " + newVal.setScale(2, RoundingMode.HALF_EVEN)); + } else { + profit.setText(""); + } + }); + } + + /** + * Formats the text representation of the share in the portfolioListView + * and applies a color to it based on its current sales price compared + * to the buy price of the share. + * + * @param share the share to be formatted + * @param currentPrice current price of the stock the share belongs to + * @return a {@link TextFlow} representation of the share + */ + private TextFlow createColoredShareText(Share share, BigDecimal currentPrice) { + String text = share.getStock().getSymbol() + " - " + share.getStock().getCompany() + " | " @@ -390,17 +389,22 @@ private TextFlow createColoredShareText(Share share, BigDecimal currentPrice) { + lm.getString("portfolioCurrentPrice") + "$"; - Text t1 = new Text(text); - Text tValue = new Text(FormatBigDecimal.formatNumber(currentPrice)); + Text t1 = new Text(text); + Text tvalue = new Text(FormatBigDecimal.formatNumber(currentPrice)); - BigDecimal buyPrice = share.getPurchasePrice(); - if (buyPrice != null && currentPrice != null) { - int compare = currentPrice.compareTo(buyPrice); - if (compare > 0) tValue.setStyle("-fx-fill: #2ecc71; -fx-font-weight: bold;"); - else if (compare < 0) tValue.setStyle("-fx-fill: #e74c3c; -fx-font-weight: bold;"); - else tValue.setStyle("-fx-fill: #000000;"); - } + BigDecimal buyPrice = share.getPurchasePrice(); + if (buyPrice != null && currentPrice != null) { + int compare = currentPrice.compareTo(buyPrice); - return new TextFlow(t1, tValue); + if (compare > 0) { + tvalue.setStyle("-fx-fill: #2ecc71; -fx-font-weight: bold;"); + } else if (compare < 0) { + tvalue.setStyle("-fx-fill: #e74c3c; -fx-font-weight: bold;"); + } else { + tvalue.setStyle("-fx-fill: #000000;"); + } } + + return new TextFlow(t1, tvalue); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/StartView.java b/millions/src/main/java/no/ntnu/gruppe53/view/StartView.java index a49d52b..264fa74 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/StartView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/StartView.java @@ -5,7 +5,6 @@ import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.VBox; - import javafx.util.Subscription; import no.ntnu.gruppe53.controller.StartViewController; import no.ntnu.gruppe53.controller.ViewController; @@ -13,77 +12,77 @@ /** * Represents the first page seen by the user of the application. + * *

      Uses a {@link LanguageManager} for dynamically setting button and label text. * Subscribes to changes in language set by the manager.

      */ public class StartView extends VBox { - private final Button newGameButton; - private final Button languageButton; - private final Button quitButton; - private final Image logo; - private Subscription languageSubscription; - - /** - * Constructs the view and sets parameters to be exposed for the {@link StartViewController}. - * - * @param vm the {@link ViewController} for the view - */ - public StartView(ViewController vm) { - LanguageManager lm = LanguageManager.getInstance(); + private final Button newGameButton; + private final Button languageButton; + private final Button quitButton; - this.setAlignment(Pos.CENTER); - this.setSpacing(20); + /** + * Constructs the view and sets parameters to be exposed for the {@link StartViewController}. + * + * @param vm the {@link ViewController} for the view + */ + public StartView(ViewController vm) { + this.setAlignment(Pos.CENTER); + this.setSpacing(20); - this.setStyle("-fx-background-color: #00bcf0;"); + this.setStyle("-fx-background-color: #00bcf0;"); - logo = new Image(getClass().getClassLoader().getResourceAsStream("Millions-logo.png")); - ImageView logoView = new ImageView(logo); - logoView.setFitWidth(300); - logoView.setPreserveRatio(true); + Image logo = new Image(getClass().getClassLoader().getResourceAsStream("Millions-logo.png")); + ImageView logoView = new ImageView(logo); + logoView.setFitWidth(300); + logoView.setPreserveRatio(true); - newGameButton = new Button(); - newGameButton.textProperty().bind(lm.bindString("newGame")); + LanguageManager lm = LanguageManager.getInstance(); + newGameButton = new Button(); + newGameButton.textProperty().bind(lm.bindString("newGame")); - languageButton = new Button(); - languageButton.setPrefWidth(200); - languageButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - languageSubscription = lm.getBundleProperty().subscribe(bundle -> { - languageButton.setText(lm.getString("languageButtonLabel")); - }); + languageButton = new Button(); + languageButton.setPrefWidth(200); + languageButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + Subscription languageSubscription = lm.getBundleProperty().subscribe(bundle -> { + languageButton.setText(lm.getString("languageButtonLabel")); + }); - quitButton = new Button(); - quitButton.textProperty().bind(lm.bindString("quitGame")); + quitButton = new Button(); + quitButton.textProperty().bind(lm.bindString("quitGame")); - newGameButton.setPrefWidth(200); - newGameButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - quitButton.setPrefWidth(200); - quitButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + newGameButton.setPrefWidth(200); + newGameButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); + quitButton.setPrefWidth(200); + quitButton.setStyle("-fx-background-color: #ffe556; -fx-text-fill: #303539;"); - this.getChildren().addAll(logoView, newGameButton, languageButton, quitButton); - } + this.getChildren().addAll(logoView, newGameButton, languageButton, quitButton); + } - /** - * Sets the method to be run when the new game button is pressed - * - * @param action the method to execute on button press - */ - public void setOnNewGame(Runnable action) { - newGameButton.setOnAction(e -> action.run()); - } + /** + * Sets the method to be run when the new game button is pressed. + * + * @param action the method to execute on button press + */ + public void setOnNewGame(Runnable action) { + newGameButton.setOnAction(e -> action.run()); + } - /** - * Sets the method to be run when the languageButton is pressed - * - * @param action the method to execute on button press - */ - public void setOnLanguage(Runnable action) {languageButton.setOnAction(e -> action.run());} + /** + * Sets the method to be run when the languageButton is pressed. + * + * @param action the method to execute on button press + */ + public void setOnLanguage(Runnable action) { + languageButton.setOnAction(e -> action.run()); + } - /** - * Sets the method to be run when the quit game button is pressed - * - * @param action the method to execute on button press - */ - public void setOnQuit(Runnable action) { - quitButton.setOnAction(e -> action.run()); - } + /** + * Sets the method to be run when the quit game button is pressed. + * + * @param action the method to execute on button press + */ + public void setOnQuit(Runnable action) { + quitButton.setOnAction(e -> action.run()); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/StockLineChartView.java b/millions/src/main/java/no/ntnu/gruppe53/view/StockLineChartView.java index adb5dc5..048d5b9 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/StockLineChartView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/StockLineChartView.java @@ -1,126 +1,128 @@ package no.ntnu.gruppe53.view; + +import java.util.List; import javafx.geometry.Side; import javafx.scene.chart.LineChart; import javafx.scene.chart.NumberAxis; import javafx.scene.chart.XYChart; import no.ntnu.gruppe53.model.Stock; -import java.util.List; - /** * Represents a {@link LineChart} view of a {@link Stock}. + * *

      Y-axis: Price as a {@code double} value. Represented by a {@link NumberAxis}

      + * *

      X-axis: Week as an {@code int} value. Represented by a {@code NumberAxis}

      */ public class StockLineChartView { - private final NumberAxis xAxis = new NumberAxis(); - private final NumberAxis yAxis = new NumberAxis(); - - private final LineChart chart; - - /** - * Sets the settings for the {@code LineChart}. - *

      Sets autoscale, stepsize and manual values for the range x- and y-axis, as well - * as the labels.

      - *

      Initializes the LineChart.

      - */ - - public StockLineChartView() { - xAxis.setAutoRanging(false); - yAxis.setAutoRanging(true); - yAxis.setForceZeroInRange(false); - - xAxis.setLowerBound(1); - - xAxis.setLabel("Week"); - yAxis.setLabel("Price"); - - chart = new LineChart<>(xAxis, yAxis); - } - - /** - * Returns the constructed LineChart. - * - * @return the current LineChart - */ - public LineChart getChart() { - return chart; - } - - /** - * Calculates the max x-axis value for a {@link XYChart.Series}. - * - * @param series the XYChart series to determine max x-axis value from - * @return the maximum x-axis value in the XYChart.Series - */ - - private double calculateXAxisMax(XYChart.Series series) { - return series.getData().stream() + private final NumberAxis xaxis = new NumberAxis(); + + private final LineChart chart; + + /** + * Sets the settings for the {@code LineChart}. + * + *

      Sets autoscale, stepsize and manual values for the range x- and y-axis, as well + * as the labels.

      + * + *

      Initializes the LineChart.

      + */ + public StockLineChartView() { + xaxis.setAutoRanging(false); + NumberAxis yaxis = new NumberAxis(); + yaxis.setAutoRanging(true); + yaxis.setForceZeroInRange(false); + + xaxis.setLowerBound(1); + + xaxis.setLabel("Week"); + yaxis.setLabel("Price"); + + chart = new LineChart<>(xaxis, yaxis); + } + + /** + * Returns the constructed LineChart. + * + * @return the current LineChart + */ + public LineChart getChart() { + return chart; + } + + /** + * Calculates the max x-axis value for a {@link XYChart.Series}. + * + * @param series the XYChart series to determine max x-axis value from + * @return the maximum x-axis value in the XYChart.Series + */ + private double calculatexAxisMax(XYChart.Series series) { + return series.getData().stream() .mapToDouble(data -> data.getXValue() .doubleValue()) - .max(). - orElse(0); + .max() + .orElse(0); + } + + /** + * Calculates the max x-axis value for a {@code List}. + * + * @param seriesList the list of XYChart.Series to determine max x-axis value from + * @return the maximum x-axis value in the List of XYChart.Series + */ + private double calculatexAxisMax(List> seriesList) { + double xmax = 0; + + for (XYChart.Series s : seriesList) { + double tempMax = calculatexAxisMax(s); + + if (tempMax > xmax) { + xmax = tempMax; + } } - /** - * Calculates the max x-axis value for a {@code List}. - * - * @param seriesList the list of XYChart.Series to determine max x-axis value from - * @return the maximum x-axis value in the List of XYChart.Series - */ + return xmax; + } - private double calculateXAxisMax(List> seriesList) { - double xMax = 0; + /** + * Sets the {@code XYChart.Series} containing the x- and y-values for the {@code LineChart}. + * + * @param series the XYChart.Series containing the x- and y-values for the LineChart + * @param title the name of the company the stock represents + */ - for (XYChart.Series s : seriesList) { - double tempMax = calculateXAxisMax(s); - if (tempMax > xMax) { - xMax = tempMax; - } - } + public void setSeries(XYChart.Series series, String title) { + chart.getData().clear(); - return xMax; - } - - /** - * Sets the {@code XYChart.Series} containing the x- and y-values for the {@code LineChart}. - * - * @param series the XYChart.Series containing the x- and y-values for the LineChart - * @param title the name of the company the stock represents - */ + chart.setTitle(title); + chart.setLegendSide(Side.RIGHT); - public void setSeries(XYChart.Series series, String title) { - chart.getData().clear(); + double xmax = calculatexAxisMax(series); + xaxis.setUpperBound(xmax); - chart.setTitle(title); - chart.setLegendSide(Side.RIGHT); + chart.getData().add(series); + } - double xMax = calculateXAxisMax(series); - xAxis.setUpperBound(xMax); + /** + * Sets the {@code XYChart.Series} containing the x- and y-values for the {@code LineChart}. + * + *

      Here the series values are given as a list of {@code XYChart.Series} objects.

      + * + * @param seriesList the list of XYChart.Series containing the x- and y-values for each stock + */ + public void setSeries(List> seriesList) { + chart.getData().clear(); - chart.getData().add(series); - } + chart.setTitle("Price History"); + chart.setLegendSide(Side.RIGHT); - /** - * Sets the {@code XYChart.Series} containing the x- and y-values for the {@code LineChart}. - *

      Here the series values are given as a list of {@code XYChart.Series} objects.

      - * - * @param seriesList the list of XYChart.Series containing the x- and y-values for each stock - */ + double xax = calculatexAxisMax(seriesList); - public void setSeries(List> seriesList) { - chart.getData().clear(); - - chart.setTitle("Price History"); - chart.setLegendSide(Side.RIGHT); - - double xMax = calculateXAxisMax(seriesList); - - if (xMax != 0) { - xAxis.setUpperBound(xMax); - } - - chart.getData().addAll(seriesList); + if (xax != 0) { + xaxis.setUpperBound(xax); } + + chart.getData().addAll(seriesList); + } } \ No newline at end of file diff --git a/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java b/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java index 4c949a5..75ea202 100644 --- a/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java +++ b/millions/src/main/java/no/ntnu/gruppe53/view/TransactionArchiveView.java @@ -1,5 +1,6 @@ package no.ntnu.gruppe53.view; +import java.math.BigDecimal; import javafx.collections.transformation.FilteredList; import javafx.geometry.Insets; import javafx.geometry.Pos; @@ -7,13 +8,18 @@ import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.control.TextField; -import javafx.scene.layout.*; - -import java.math.BigDecimal; -import java.math.RoundingMode; - +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; import javafx.util.Subscription; -import no.ntnu.gruppe53.model.*; +import no.ntnu.gruppe53.model.Player; +import no.ntnu.gruppe53.model.Purchase; +import no.ntnu.gruppe53.model.Sale; +import no.ntnu.gruppe53.model.SaleCalculator; +import no.ntnu.gruppe53.model.Transaction; +import no.ntnu.gruppe53.model.TransactionArchive; import no.ntnu.gruppe53.service.FormatBigDecimal; import no.ntnu.gruppe53.service.LanguageManager; @@ -26,105 +32,112 @@ /** * Represents a view of all the {@link Purchase} and {@link Sale} * {@link Transaction}s a {@link Player} has committed in a game. + * *

      Extends the {@link BorderPane} for easy layout setup.

      */ public class TransactionArchiveView extends BorderPane { - private final LanguageManager lm = LanguageManager.getInstance(); + private final LanguageManager lm = LanguageManager.getInstance(); + + private ListView historyListView; + private FilteredList filteredTransactionList; + + private TextField searchField; + + /** + * Builds the view components and places them in the center pane. + */ + public TransactionArchiveView() { + this.setCenter(centerPane()); + } + + /** + * Constructs the view of the transaction archive. + * + *

      Builds the necessary components and places them in a {@link VBox}.

      + * + *

      Uses a cell factory to format the string shown

      + * + *

      Assigns red text to purchases, and green text to sales.

      + * + *

      Uses the formatBigDecimal method to convert the BigDecimal to a + * string with 2 decimals.

      + * + *

      Uses a {@link LanguageManager} to dynamically set text based on target language.

      + * + * @return a VBox of the constructed view of the transaction archive + */ + private VBox centerPane() { + VBox vbox = new VBox(10); + vbox.setPadding(new Insets(15, 12, 15, 12)); + vbox.setStyle("-fx-background-color: #00bcf0; "); + + Label titleLabel = new Label(); + titleLabel.textProperty().bind(lm.bindString("historyTitle")); + titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; -fx-text-fill: #303539;"); + + VBox titleBox = new VBox(titleLabel); + titleBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + titleBox.setAlignment(Pos.CENTER); + titleBox.setPadding(new Insets(4)); + titleBox.setMaxWidth(300); + + historyListView = new ListView<>(); + historyListView.setMinHeight(0); + historyListView.setMaxHeight(Double.MAX_VALUE); + + historyListView.setCellFactory(list -> new ListCell<>() { + private Subscription langSubscription; + + @Override + protected void updateItem(Transaction item, boolean empty) { + super.updateItem(item, empty); + + if (langSubscription != null) { + langSubscription.unsubscribe(); + langSubscription = null; + } - private ListView historyListView; - private FilteredList filteredTransactionList; + if (empty || item == null) { + setText(null); + setStyle(""); + } else { + Runnable updateTextAction = () -> { + String typeStr; + BigDecimal price = BigDecimal.ZERO; + BigDecimal profit = BigDecimal.ZERO; + + // Changed to allow for more transaction types in the future + if (item instanceof Purchase) { + typeStr = lm.getString("historyTypePurchase"); + price = item.getShare().getPurchasePrice(); + } else if (item instanceof Sale sale) { + typeStr = lm.getString("historyTypeSale"); + price = item.getShare().getStock().getSalesPrice(); + + if (sale.getCalculator() instanceof SaleCalculator saleCalc) { + profit = saleCalc.getProfitProperty().getValue(); + } + } else { + typeStr = "ERROR"; + } - private Label titleLabel; + BigDecimal quantity = item.getShare().getQuantity(); + BigDecimal gross = item.getCalculator().calculateGross(); + BigDecimal commission = item.getCalculator().calculateCommission(); + BigDecimal tax = item.getCalculator().calculateTax(); + BigDecimal total = item.getCalculator().calculateTotal(); - private TextField searchField; + String symbol = item.getShare().getStock().getSymbol(); - /** - * Builds the view components and places them in the center pane. - */ - public TransactionArchiveView() { - this.setCenter(centerPane()); - } + String template = lm.getString("historyCellFormat"); - /** - * Constructs the view of the transaction archive. - *

      Builds the necessary components and places them in a {@link VBox}.

      - *

      Uses a cell factory to format the string shown

      - *

      Assigns red text to purchases, and green text to sales.

      - *

      Uses the formatBigDecimal method to convert the BigDecimal to a - * string with 2 decimals.

      - *

      Uses a {@link LanguageManager} to dynamically set text based on target language.

      - * - * @return a VBox of the constructed view of the transaction archive - */ - private VBox centerPane() { - VBox vBox = new VBox(10); - vBox.setPadding(new Insets(15, 12, 15, 12)); - vBox.setStyle("-fx-background-color: #00bcf0; "); - - titleLabel = new Label(); - titleLabel.textProperty().bind(lm.bindString("historyTitle")); - titleLabel.setStyle("-fx-font-size: 20px; -fx-font-weight: bold; -fx-text-fill: #303539;"); - - VBox titleBox = new VBox(titleLabel); - titleBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); - titleBox.setAlignment(Pos.CENTER); - titleBox.setPadding(new Insets(4)); - titleBox.setMaxWidth(300); - - historyListView = new ListView<>(); - historyListView.setMinHeight(0); - historyListView.setMaxHeight(Double.MAX_VALUE); - - historyListView.setCellFactory(list -> new ListCell<>() { - private Subscription langSubscription; - - @Override - protected void updateItem(Transaction item, boolean empty) { - super.updateItem(item, empty); - - if (langSubscription != null) { - langSubscription.unsubscribe(); - langSubscription = null; - } - - if (empty || item == null) { - setText(null); - setStyle(""); - } else { - Runnable updateTextAction = () -> { - String typeStr; - BigDecimal price = BigDecimal.ZERO; - BigDecimal profit = BigDecimal.ZERO; - // Changed to allow for more transaction types in the future - if (item instanceof Purchase) { - typeStr = lm.getString("historyTypePurchase"); - price = item.getShare().getPurchasePrice(); - } else if (item instanceof Sale sale) { - typeStr = lm.getString("historyTypeSale"); - price = item.getShare().getStock().getSalesPrice(); - if (sale.getCalculator() instanceof SaleCalculator saleCalc) { - profit = saleCalc.getProfitProperty().getValue(); - } - } else { - typeStr = "ERROR"; - } - - BigDecimal quantity = item.getShare().getQuantity(); - BigDecimal gross = item.getCalculator().calculateGross(); - BigDecimal commission = item.getCalculator().calculateCommission(); - BigDecimal tax = item.getCalculator().calculateTax(); - BigDecimal total = item.getCalculator().calculateTotal(); - - String symbol = item.getShare().getStock().getSymbol(); - - String template = lm.getString("historyCellFormat"); - - setText(String.format(template, + setText(String.format(template, item.getWeek(), typeStr, symbol, @@ -135,81 +148,88 @@ protected void updateItem(Transaction item, boolean empty) { FormatBigDecimal.formatNumber(tax), FormatBigDecimal.formatNumber(total), FormatBigDecimal.formatNumber(profit) - )); - }; - - langSubscription = lm.getBundleProperty().subscribe(bundle -> updateTextAction.run()); + )); + }; - updateTextAction.run(); + langSubscription = lm.getBundleProperty() + .subscribe(bundle -> updateTextAction.run()); - if (item instanceof Purchase) { - setStyle("-fx-text-fill: #c0392b; -fx-font-weight: bold;"); - } else if (item instanceof Sale) { - setStyle("-fx-text-fill: #27ae60; -fx-font-weight: bold;"); - } - } - } - }); - - HBox searchBox = new HBox(); - searchField = new TextField(); - searchField.promptTextProperty().bind(lm.bindString("search")); - searchBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" - ); + updateTextAction.run(); - searchBox.setPadding(new Insets(8, 14, 8, 14)); - searchBox.setAlignment(Pos.CENTER_LEFT); - searchBox.setMaxWidth(Region.USE_PREF_SIZE); - searchBox.getChildren().addAll(searchField); - - VBox historyListBox = new VBox(historyListView); - historyListBox.setStyle("-fx-background-color: #ffe556;" + - "-fx-text-fill: #303539;" + - "-fx-background-radius: 8;" + - "-fx-border-color: #303539;" + - "-fx-border-radius: 8;" + if (item instanceof Purchase) { + setStyle("-fx-text-fill: #c0392b; -fx-font-weight: bold;"); + } else if (item instanceof Sale) { + setStyle("-fx-text-fill: #27ae60; -fx-font-weight: bold;"); + } + } + } + }); + + HBox searchBox = new HBox(); + searchField = new TextField(); + searchField.promptTextProperty().bind(lm.bindString("search")); + searchBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + searchBox.setPadding(new Insets(8, 14, 8, 14)); + searchBox.setAlignment(Pos.CENTER_LEFT); + searchBox.setMaxWidth(Region.USE_PREF_SIZE); + searchBox.getChildren().addAll(searchField); + + VBox historyListBox = new VBox(historyListView); + historyListBox.setStyle("-fx-background-color: #ffe556;" + + "-fx-text-fill: #303539;" + + "-fx-background-radius: 8;" + + "-fx-border-color: #303539;" + + "-fx-border-radius: 8;" + ); + + historyListBox.setPadding(new Insets(4)); + historyListBox.setMinHeight(0); + historyListBox.setMaxHeight(Double.MAX_VALUE); + + VBox.setVgrow(historyListView, Priority.ALWAYS); + VBox.setVgrow(historyListBox, Priority.ALWAYS); + + vbox.getChildren().addAll(titleBox, searchBox, historyListBox); + return vbox; + } + + /** + * Sets the player to be observed. + * + *

      And puts the {@link TransactionArchive} into a {@link FilteredList} for searching.

      + * + * @param player the player to be observed + */ + public void setPlayer(Player player) { + if (player != null && player.getTransactionArchive().getObservableTransactions() != null) { + + filteredTransactionList = new FilteredList<>( + player.getTransactionArchive().getObservableTransactions()); + + historyListView.setItems(filteredTransactionList); + + searchField.textProperty().addListener(( + observable, oldText, newText) -> { + String search = newText == null ? "" : newText.toLowerCase(); + + filteredTransactionList.setPredicate(transaction -> search.isBlank() + || + transaction.getShare().getStock() + .getSymbol().toLowerCase().contains(search) + || + transaction.getShare().getStock() + .getCompany().toLowerCase().contains(search) ); + }); - historyListBox.setPadding(new Insets(4)); - historyListBox.setMinHeight(0); - historyListBox.setMaxHeight(Double.MAX_VALUE); - - VBox.setVgrow(historyListView, Priority.ALWAYS); - VBox.setVgrow(historyListBox, Priority.ALWAYS); - - vBox.getChildren().addAll(titleBox, searchBox, historyListBox); - return vBox; - } - - /** - * Sets the player to be observed. - *

      And puts the {@link TransactionArchive} into a {@link FilteredList} for searching.

      - * - * @param player the player to be observed - */ - public void setPlayer(Player player) { - if (player != null && player.getTransactionArchive().getObservableTransactions() != null) { - - - filteredTransactionList = new FilteredList<>(player.getTransactionArchive().getObservableTransactions()); - - historyListView.setItems(filteredTransactionList); - - searchField.textProperty().addListener((observable, oldText, newText) -> { - String search = newText == null ? "" : newText.toLowerCase(); - - filteredTransactionList.setPredicate( transaction -> search.isBlank() - || transaction.getShare().getStock().getSymbol().toLowerCase().contains(search) - || transaction.getShare().getStock().getCompany().toLowerCase().contains(search) - ); - }); - - } else { - historyListView.setItems(null); - } + } else { + historyListView.setItems(null); } + } }