From e2c9b9fb7b2ef0f2a8065b4030c5b85e5c715652 Mon Sep 17 00:00:00 2001 From: Nikollai Date: Mon, 18 May 2026 16:13:39 +0200 Subject: [PATCH 1/6] Added more robust verification of csv file formats, and expanded on error messages for more clarity on the errors occuring --- src/main/java/millions/App.java | 2 +- .../fileIO/CSV/CSVStockFileParser.java | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/millions/App.java b/src/main/java/millions/App.java index 9b459b9..3d419c3 100644 --- a/src/main/java/millions/App.java +++ b/src/main/java/millions/App.java @@ -39,7 +39,7 @@ public void start(Stage stage) { Alert alert = new Alert(Alert.AlertType.ERROR); alert.setTitle("Error"); alert.setHeaderText("Error with selected file"); - alert.setContentText("Please control the format of the selected file"); + alert.setContentText(e.getMessage() + "\nPlease control the format of the selected file"); alert.showAndWait(); diff --git a/src/main/java/millions/controller/fileIO/CSV/CSVStockFileParser.java b/src/main/java/millions/controller/fileIO/CSV/CSVStockFileParser.java index 2eab5ab..eb8339f 100644 --- a/src/main/java/millions/controller/fileIO/CSV/CSVStockFileParser.java +++ b/src/main/java/millions/controller/fileIO/CSV/CSVStockFileParser.java @@ -26,14 +26,20 @@ public List parse(List lines) { .filter(l -> !((l.startsWith("#") || l.isBlank()))) .forEach( l -> { - String[] split = l.split(","); - String symbol = split[0]; - String company = split[1]; - BigDecimal price = new BigDecimal(split[2]); - stocks.add(new Stock(symbol, company, price)); + try { + String[] split = l.split(","); + String symbol = split[0]; + String company = split[1]; + BigDecimal price = new BigDecimal(split[2]); + stocks.add(new Stock(symbol, company, price)); + } catch (NumberFormatException e) { + throw new InvalidFormatException("Error with number conversion on line: " + l + "\n" + "Last field must be a number"); + } catch (IllegalArgumentException e) { + throw new InvalidFormatException("Illegal argument on line: " + l + "\n" + e.getMessage()); + } }); } else { - throw new InvalidFormatException("Incorrect format for CSV File"); + throw new InvalidFormatException("Incorrect format for CSV File: incorrect amount of data fields detected on one or more lines"); } return stocks; } From 25088a1853555143fb5910511375c292c005f5ef Mon Sep 17 00:00:00 2001 From: Nikollai Date: Mon, 18 May 2026 16:48:53 +0200 Subject: [PATCH 2/6] Cleaned up CSVStockFileParserTest --- .../java/millions/CSVStockFileParserTest.java | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/test/java/millions/CSVStockFileParserTest.java b/src/test/java/millions/CSVStockFileParserTest.java index df753aa..aa5fc8c 100644 --- a/src/test/java/millions/CSVStockFileParserTest.java +++ b/src/test/java/millions/CSVStockFileParserTest.java @@ -1,33 +1,66 @@ package millions; import millions.controller.fileIO.CSV.CSVStockFileParser; +import millions.controller.fileIO.InvalidFormatException; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; + +import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; public class CSVStockFileParserTest { static String exampleString; + final CSVStockFileParser parser = new CSVStockFileParser(); @BeforeAll - public static void setUpTestFile() throws Exception { + public static void setUpTestString() { exampleString = "# Top 500 US Stocks by Market Cap\n"; exampleString += "# Ticker,Name,Price\n"; exampleString += "\n"; exampleString += "NVDA,Nvidia,191.27\n"; exampleString += "AAPL,Apple Inc.,276.43\n"; exampleString += "MSFT,Microsoft,404.68\n"; + } @Test public void parseStockFileTest(){ List testList = List.of(exampleString.split("\n")); - - CSVStockFileParser parser = new CSVStockFileParser(); assertEquals(3, parser.parse(testList).size()); } + + @Test + public void InvalidFormatExceptionTest() { + exampleString += "Line with incorrect amount of data"; + List testList = List.of(exampleString.split("\n")); + Exception e = assertThrows(InvalidFormatException.class, () -> {parser.parse(testList);}); + String expectedMessage = "Incorrect format for CSV File: incorrect amount of data fields detected on one or more lines"; + assertEquals(expectedMessage, e.getMessage()); + } + + @Test + public void NumberConversionExceptionTest() { + exampleString += "Company, Company Inc., NotANumber"; + List testList = List.of(exampleString.split("\n")); + Exception e = assertThrows(InvalidFormatException.class, () -> {parser.parse(testList);}); + String expectedMessage = "Error with number conversion on line: Company, Company Inc., NotANumber\n" + + "Last field must be a number"; + assertEquals(expectedMessage, e.getMessage()); + } + + @Test + public void EmptyFieldExceptionTest() { + exampleString += ",,1"; + List testList = List.of(exampleString.split("\n")); + Exception e = assertThrows(InvalidFormatException.class, () -> {parser.parse(testList);}); + String expectedMessage = "Illegal argument on line: ,,1\n" + + "Symbol cannot be null or blank"; + assertEquals(expectedMessage, e.getMessage()); + } } From 81a8164874bffe2e5f101dbb4289766fd720b8ab Mon Sep 17 00:00:00 2001 From: Nikollai Date: Mon, 18 May 2026 17:01:38 +0200 Subject: [PATCH 3/6] Added additional tests to csv parsing --- .../java/millions/CSVStockFileParserTest.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/test/java/millions/CSVStockFileParserTest.java b/src/test/java/millions/CSVStockFileParserTest.java index aa5fc8c..8266d35 100644 --- a/src/test/java/millions/CSVStockFileParserTest.java +++ b/src/test/java/millions/CSVStockFileParserTest.java @@ -55,12 +55,21 @@ public void NumberConversionExceptionTest() { } @Test - public void EmptyFieldExceptionTest() { - exampleString += ",,1"; + public void EmptySymbolFieldExceptionTest() { + exampleString += ",test,1"; List testList = List.of(exampleString.split("\n")); Exception e = assertThrows(InvalidFormatException.class, () -> {parser.parse(testList);}); - String expectedMessage = "Illegal argument on line: ,,1\n" + + String expectedMessage = "Illegal argument on line: ,test,1\n" + "Symbol cannot be null or blank"; assertEquals(expectedMessage, e.getMessage()); } + @Test + public void EmptyCompanyNameFieldExceptionTest() { + exampleString += "test,,1"; + List testList = List.of(exampleString.split("\n")); + Exception e = assertThrows(InvalidFormatException.class, () -> {parser.parse(testList);}); + String expectedMessage = "Illegal argument on line: test,,1\n" + + "Company cannot be null or blank"; + assertEquals(expectedMessage, e.getMessage()); + } } From 91ce42c60fbc5751adba5eb4e91bfc0a4ee29c04 Mon Sep 17 00:00:00 2001 From: Nikollai Date: Mon, 18 May 2026 17:29:08 +0200 Subject: [PATCH 4/6] Added JavaDoc to fileIO files --- .../controller/fileIO/CSV/CSVFileHandler.java | 9 +++++++ .../fileIO/CSV/CSVStockFileParser.java | 20 +++++++++++++- .../fileIO/CSV/CSVStockFileWriter.java | 26 ++++++++++++++----- .../fileIO/InvalidFormatException.java | 3 +++ .../controller/fileIO/StockFileReader.java | 5 ++++ 5 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/main/java/millions/controller/fileIO/CSV/CSVFileHandler.java b/src/main/java/millions/controller/fileIO/CSV/CSVFileHandler.java index 5a7d9b8..bcd783d 100644 --- a/src/main/java/millions/controller/fileIO/CSV/CSVFileHandler.java +++ b/src/main/java/millions/controller/fileIO/CSV/CSVFileHandler.java @@ -7,6 +7,9 @@ import java.nio.file.Path; import java.util.List; +/** + *

Bundles StockFileReader and CSVStockFileParser together to reduce boilerplate code when reading stocks from a csv file

+ */ public class CSVFileHandler { StockFileReader reader; CSVStockFileParser parser; @@ -16,6 +19,12 @@ public CSVFileHandler() { this.parser = new CSVStockFileParser(); } + /** + * Reads and parses stocks from a csv file + * @param filePath Path to stock file + * @return list of stock object created from parsed file + * @throws InvalidFormatException Throws an InvalidFormatException received from parser + */ public List getStocksFromFile(Path filePath) { try { StockFileReader reader = new StockFileReader(); diff --git a/src/main/java/millions/controller/fileIO/CSV/CSVStockFileParser.java b/src/main/java/millions/controller/fileIO/CSV/CSVStockFileParser.java index eb8339f..9208bbc 100644 --- a/src/main/java/millions/controller/fileIO/CSV/CSVStockFileParser.java +++ b/src/main/java/millions/controller/fileIO/CSV/CSVStockFileParser.java @@ -12,13 +12,31 @@ public class CSVStockFileParser { public CSVStockFileParser() {} - // returns true if all entries have exactly 3 data points + /** + * Verifies the amount of data fields present in supplied CSV file. + * + * @param lines Lines from csv file + * @return Boolean: True if file satisfies expected format (3 fields) + */ public boolean verifyCSV(List lines) { return lines.stream() .filter(l -> !(l.startsWith("#") || l.isBlank())) .noneMatch(l -> l.split(",").length != 3); } + /** + * Parses the supplied lines if they satisfy the correct format expectations + * + * @param lines

lines to be parsed.
+ * Each line must contain three data fields: String,String,BigDecimal
+ * (Fields cannot be blank)
+ * blank lines or lines beginning with '#' are ignored + *

+ * @return List of stock objects created from the supplied lines + * @throws InvalidFormatException If one or more lines contain too many or too few data fields + * @throws InvalidFormatException If the BigDecimal field on one or more lines are not compatible + * @throws InvalidFormatException Upon recieving an IllegalArgumentException (Either Symbol or Company name is blank) + */ public List parse(List lines) { List stocks = new ArrayList<>(); if (verifyCSV(lines)) { diff --git a/src/main/java/millions/controller/fileIO/CSV/CSVStockFileWriter.java b/src/main/java/millions/controller/fileIO/CSV/CSVStockFileWriter.java index 0233a7c..3e7a791 100644 --- a/src/main/java/millions/controller/fileIO/CSV/CSVStockFileWriter.java +++ b/src/main/java/millions/controller/fileIO/CSV/CSVStockFileWriter.java @@ -1,25 +1,32 @@ package millions.controller.fileIO.CSV; -import millions.controller.fileIO.StockFileWriter; -import millions.model.Stock; - -import java.io.BufferedWriter; import java.io.*; +import java.io.BufferedWriter; import java.nio.file.Path; import java.util.List; +import millions.controller.fileIO.StockFileWriter; +import millions.model.Stock; -//TODO: Validation of data before writing /** - * Writes stock data to a CSV file. + * Implements StockFileWriter.
+ * Converts a list of stock objects into a writeable string with a CSV format. + * */ public class CSVStockFileWriter implements StockFileWriter { private final List stocks; private String finalString; + /** + * Constructor for CSVStockFileWriter + * @param stocks list of stocks to be formatted and written + */ public CSVStockFileWriter(List stocks) { this.stocks = stocks; } + /** + * Formats given string to CSV format to prepare for writing to file + */ @Override public void formatString() { StringBuilder builder = new StringBuilder(); @@ -34,11 +41,18 @@ public void formatString() { this.finalString = builder.toString(); } + /** + * Writes the saved string to a file + * @param path Path to desired file + * @return Boolean for success + */ + // TODO: Disable writing before formatting @Override public boolean write(Path path){ try (FileWriter fw = new FileWriter(path.toString()); BufferedWriter writer = new BufferedWriter(fw);) { this.formatString(); writer.write(finalString); + // TODO: exception handling } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/millions/controller/fileIO/InvalidFormatException.java b/src/main/java/millions/controller/fileIO/InvalidFormatException.java index 6883256..a1e2136 100644 --- a/src/main/java/millions/controller/fileIO/InvalidFormatException.java +++ b/src/main/java/millions/controller/fileIO/InvalidFormatException.java @@ -1,5 +1,8 @@ package millions.controller.fileIO; +/** + * Exception to be thrown when verifying the format of files + */ public class InvalidFormatException extends RuntimeException { public InvalidFormatException(String message) { super(message); diff --git a/src/main/java/millions/controller/fileIO/StockFileReader.java b/src/main/java/millions/controller/fileIO/StockFileReader.java index 33956b1..f873896 100644 --- a/src/main/java/millions/controller/fileIO/StockFileReader.java +++ b/src/main/java/millions/controller/fileIO/StockFileReader.java @@ -17,6 +17,11 @@ public class StockFileReader { public StockFileReader() {} + /** + * Reads the file found at the specified path + * @param path Path to the desired file + * @return List of each line in the file as a string + */ public List readFile(Path path) { File file = new File(path.toString()); List lines = new ArrayList<>(); From c71717546a68fc7a08b41ed8cdbfd71641a16833 Mon Sep 17 00:00:00 2001 From: Nikollai Date: Mon, 18 May 2026 18:01:43 +0200 Subject: [PATCH 5/6] Cleaned up CSVStockFileWriter and added a logger for exceptions Deleted CSVStockFileWriterTest --- src/main/java/millions/App.java | 9 +++- .../fileIO/CSV/CSVStockFileWriter.java | 25 +++++------ .../controller/fileIO/StockFileWriter.java | 7 ++- .../java/millions/CSVStockFileWriterTest.java | 43 ------------------- 4 files changed, 22 insertions(+), 62 deletions(-) delete mode 100644 src/test/java/millions/CSVStockFileWriterTest.java diff --git a/src/main/java/millions/App.java b/src/main/java/millions/App.java index 3d419c3..89fa403 100644 --- a/src/main/java/millions/App.java +++ b/src/main/java/millions/App.java @@ -1,18 +1,22 @@ package millions; import java.math.BigDecimal; +import java.util.logging.Level; +import java.util.logging.Logger; + import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.control.Alert; import javafx.stage.Stage; import millions.controller.GameController; +import millions.controller.fileIO.CSV.CSVStockFileWriter; import millions.controller.fileIO.InvalidFormatException; import millions.view.GameView; import millions.view.StartView; /** Main JavaFX application entry point for the Millions stock trading game. */ public class App extends Application { - + private static final Logger logger = Logger.getLogger(App.class.getName()); @Override public void start(Stage stage) { GameController controller = new GameController(); @@ -36,6 +40,7 @@ public void start(Stage stage) { Scene gameScene = new Scene(gameView, 1920, 1080); stage.setScene(gameScene); } catch (InvalidFormatException e) { + logger.log(Level.FINE, "Invalid format", e); Alert alert = new Alert(Alert.AlertType.ERROR); alert.setTitle("Error"); alert.setHeaderText("Error with selected file"); @@ -44,7 +49,7 @@ public void start(Stage stage) { alert.showAndWait(); } catch (RuntimeException ex) { - System.err.println(ex); + logger.log(Level.SEVERE, "Runtime error", ex); System.exit(0); } }); diff --git a/src/main/java/millions/controller/fileIO/CSV/CSVStockFileWriter.java b/src/main/java/millions/controller/fileIO/CSV/CSVStockFileWriter.java index 3e7a791..fdce35d 100644 --- a/src/main/java/millions/controller/fileIO/CSV/CSVStockFileWriter.java +++ b/src/main/java/millions/controller/fileIO/CSV/CSVStockFileWriter.java @@ -4,6 +4,8 @@ import java.io.BufferedWriter; import java.nio.file.Path; import java.util.List; +import java.util.logging.Logger; + import millions.controller.fileIO.StockFileWriter; import millions.model.Stock; @@ -13,22 +15,16 @@ * */ public class CSVStockFileWriter implements StockFileWriter { - private final List stocks; + private static final Logger logger = Logger.getLogger(CSVStockFileWriter.class.getName()); private String finalString; - /** - * Constructor for CSVStockFileWriter - * @param stocks list of stocks to be formatted and written - */ - public CSVStockFileWriter(List stocks) { - this.stocks = stocks; - } + public CSVStockFileWriter() {} /** * Formats given string to CSV format to prepare for writing to file */ @Override - public void formatString() { + public String formatString(List stocks) { StringBuilder builder = new StringBuilder(); stocks.forEach(stock -> { builder.append(stock.getSymbol()); @@ -38,23 +34,22 @@ public void formatString() { builder.append(stock.getSalesPrice().toString()); builder.append("\n"); }); - this.finalString = builder.toString(); + return builder.toString(); } /** * Writes the saved string to a file + * @param stocks List of stock objects to write * @param path Path to desired file * @return Boolean for success */ - // TODO: Disable writing before formatting @Override - public boolean write(Path path){ + public boolean write(List stocks, Path path){ try (FileWriter fw = new FileWriter(path.toString()); BufferedWriter writer = new BufferedWriter(fw);) { - this.formatString(); + this.formatString(stocks); writer.write(finalString); - // TODO: exception handling } catch (IOException e) { - e.printStackTrace(); + logger.severe(e.getMessage()); } return false; } diff --git a/src/main/java/millions/controller/fileIO/StockFileWriter.java b/src/main/java/millions/controller/fileIO/StockFileWriter.java index cfd1baf..a85f363 100644 --- a/src/main/java/millions/controller/fileIO/StockFileWriter.java +++ b/src/main/java/millions/controller/fileIO/StockFileWriter.java @@ -1,11 +1,14 @@ package millions.controller.fileIO; +import millions.model.Stock; + import java.nio.file.Path; +import java.util.List; /** * Interface for writing stock data to a file. */ public interface StockFileWriter { - public void formatString(); - public boolean write(Path path); + String formatString(List stocks); + boolean write(List stocks, Path path); } diff --git a/src/test/java/millions/CSVStockFileWriterTest.java b/src/test/java/millions/CSVStockFileWriterTest.java deleted file mode 100644 index c6fe15a..0000000 --- a/src/test/java/millions/CSVStockFileWriterTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package millions; - -import millions.controller.fileIO.CSV.CSVStockFileWriter; -import millions.controller.fileIO.StockFileReader; -import millions.model.Stock; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import java.math.BigDecimal; -import java.nio.file.Path; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class CSVStockFileWriterTest { - @TempDir - static Path tempDir; - - List stocks; - - @BeforeEach - void setup() { - Stock s1 = new Stock("PEAR", "Pear Inc.", BigDecimal.valueOf(300)); - Stock s2 = new Stock("DOGL", "DOOGLE Inc.", BigDecimal.valueOf(200.00)); - Stock s3 = new Stock("MSFT", "EpsteinSoft Inc.", BigDecimal.valueOf(0.02)); - - this.stocks = List.of(s1, s2, s3); - } - - @Test - public void testWrite() { - for(Stock stock : this.stocks) { - System.out.println(stock.toString()); - } - CSVStockFileWriter csvStockFileWriter = new CSVStockFileWriter(stocks); - csvStockFileWriter.write(tempDir.resolve("stocks.csv")); - - StockFileReader stockFileReader = new StockFileReader(); - assertEquals(3, stockFileReader.readFile(tempDir.resolve("stocks.csv")).size()); - } -} From 6e01d27cccf15c4266f25315b3c6a26270932a01 Mon Sep 17 00:00:00 2001 From: Nikollai Date: Mon, 18 May 2026 18:23:13 +0200 Subject: [PATCH 6/6] Added logging for exceptions in App.java --- src/main/java/millions/App.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/millions/App.java b/src/main/java/millions/App.java index 89fa403..cb1b7d2 100644 --- a/src/main/java/millions/App.java +++ b/src/main/java/millions/App.java @@ -40,7 +40,7 @@ public void start(Stage stage) { Scene gameScene = new Scene(gameView, 1920, 1080); stage.setScene(gameScene); } catch (InvalidFormatException e) { - logger.log(Level.FINE, "Invalid format", e); + logger.log(Level.WARNING, "InvalidFormatException: " + e.getMessage()); Alert alert = new Alert(Alert.AlertType.ERROR); alert.setTitle("Error"); alert.setHeaderText("Error with selected file"); @@ -49,7 +49,7 @@ public void start(Stage stage) { alert.showAndWait(); } catch (RuntimeException ex) { - logger.log(Level.SEVERE, "Runtime error", ex); + logger.log(Level.SEVERE, ex.getMessage()); System.exit(0); } });