diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/MarketController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/MarketController.java index 41294bd..53d3408 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/MarketController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/MarketController.java @@ -1,9 +1,12 @@ package edu.ntnu.idi.idatt2003.gruppe42.Controller; +import edu.ntnu.idi.idatt2003.gruppe42.Model.Exceptions.StockFileParseException; import edu.ntnu.idi.idatt2003.gruppe42.Model.Exchange; import edu.ntnu.idi.idatt2003.gruppe42.Model.Stock; import edu.ntnu.idi.idatt2003.gruppe42.Model.StockFileHandler; import edu.ntnu.idi.idatt2003.gruppe42.View.Apps.StockApp; +import java.io.IOException; +import java.net.URISyntaxException; import java.nio.file.Path; import java.util.List; import java.util.Objects; @@ -29,9 +32,12 @@ public MarketController() { startMarket(); System.out.println("Market loaded"); - - } catch (Exception exception) { + } catch (IOException exception) { System.out.println("File not found"); + } catch (URISyntaxException exception) { + System.out.println("Invalid file path"); + } catch (StockFileParseException exception) { + System.out.println("Invalid stock file format"); } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/DesktopViewController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/DesktopViewController.java index 0e7091c..64037c0 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/DesktopViewController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/DesktopViewController.java @@ -1,7 +1,5 @@ package edu.ntnu.idi.idatt2003.gruppe42.Controller.ViewControllers; -import edu.ntnu.idi.idatt2003.gruppe42.Audio.Audio; -import edu.ntnu.idi.idatt2003.gruppe42.Audio.AudioManager; import edu.ntnu.idi.idatt2003.gruppe42.Controller.AppControllers.AppStoreController; import edu.ntnu.idi.idatt2003.gruppe42.Controller.AppControllers.BankAppController; import edu.ntnu.idi.idatt2003.gruppe42.Controller.AppControllers.StockAppController; @@ -10,7 +8,7 @@ import edu.ntnu.idi.idatt2003.gruppe42.Controller.PopupsController; import edu.ntnu.idi.idatt2003.gruppe42.Controller.TimeAndWeatherController; import edu.ntnu.idi.idatt2003.gruppe42.Model.App; -import edu.ntnu.idi.idatt2003.gruppe42.Model.Day; +import edu.ntnu.idi.idatt2003.gruppe42.Model.Exceptions.StockFileParseException; import edu.ntnu.idi.idatt2003.gruppe42.Model.Player; import edu.ntnu.idi.idatt2003.gruppe42.Model.Transaction.Purchase; import edu.ntnu.idi.idatt2003.gruppe42.Model.Transaction.Sale; @@ -70,6 +68,7 @@ public DesktopViewController(final Player player, final GameController gameContr this.marketController = new MarketController(); + AppStoreApp appStoreApp = new AppStoreApp(); gameController.addAppController(new AppStoreController(appStoreApp)); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Exceptions/StockFileParseException.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Exceptions/StockFileParseException.java new file mode 100644 index 0000000..8fd4e91 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Exceptions/StockFileParseException.java @@ -0,0 +1,15 @@ +package edu.ntnu.idi.idatt2003.gruppe42.Model.Exceptions; + +public class StockFileParseException extends Exception { + private final int lineNumber; + private final String lineContent; + + public StockFileParseException(int lineNumber, String lineContent, String reason) { + super(String.format("Parse error on line %d: %s%n Content : \"%s\"", lineNumber, reason, lineContent)); + this.lineNumber = lineNumber; + this.lineContent = lineContent; + } + + public int getLineNumber() { return lineNumber; } + public String getLineContent() { return lineContent; } +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/StockFileHandler.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/StockFileHandler.java index 19dd290..8f08e75 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/StockFileHandler.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/StockFileHandler.java @@ -1,20 +1,27 @@ package edu.ntnu.idi.idatt2003.gruppe42.Model; +import edu.ntnu.idi.idatt2003.gruppe42.Model.Exceptions.StockFileParseException; import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.FileReader; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStreamReader; import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.regex.Pattern; /** * Utility class for handling file data. * Includes methods for reading and writing from file. */ public final class StockFileHandler { + private static final Pattern SYMBOL_PATTERN = Pattern.compile("^[A-Z]{3}$"); + private static final Pattern PRICE_PATTERN = Pattern.compile("^\\d+\\.\\d{2}$"); + private static final int EXPECTED_FIELDS = 3; + /** * Private constructor to prevent instantiation. */ @@ -28,62 +35,85 @@ private StockFileHandler() { * @return list of stock objects * @throws IOException if file not found or error occurs */ - public static List readFromFile(final Path path) throws IOException { + + public static List readFromFile(final Path path) + throws IOException, StockFileParseException { + + if (!Files.exists(path)) { + throw new IOException("File not found: " + path); + } + if (!Files.isReadable(path)) { + throw new IOException("File is not readable: " + path); + } List stocks = new ArrayList<>(); + int lineNumber = 0; + + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(new FileInputStream(path.toFile()), StandardCharsets.UTF_8))) { - try (BufferedReader bufferedReader = new BufferedReader( - new FileReader(path.toFile()))) { String line; - while ((line = bufferedReader.readLine()) != null) { - line = line.trim(); + while ((line = reader.readLine()) != null) { + lineNumber++; + String trimmed = line.trim(); - if (line.isEmpty() || line.startsWith("#")) { + // Skip blank lines and comments + if (trimmed.isEmpty() || trimmed.startsWith("#")) { continue; } - String[] parts = line.split(","); + String[] parts = trimmed.split(","); + if (parts.length != EXPECTED_FIELDS) { + throw new StockFileParseException(lineNumber, trimmed, + String.format("Expected %d comma-separated fields but found %d. " + + "Hint: decimal prices must use '.' not ','", + EXPECTED_FIELDS, parts.length)); + } + + String symbol = parts[0].trim(); + if (symbol.isEmpty()) { + throw new StockFileParseException(lineNumber, trimmed, + "Ticker symbol is empty"); + } + if (!SYMBOL_PATTERN.matcher(symbol).matches()) { + throw new StockFileParseException(lineNumber, trimmed, + String.format("Ticker symbol \"%s\" is invalid — must be exactly 3 uppercase letters (A-Z)", symbol)); + } + + String company = parts[1].trim(); + if (company.isEmpty()) { + throw new StockFileParseException(lineNumber, trimmed, + "Company name is empty"); + } + + String rawPrice = parts[2].trim(); + if (!PRICE_PATTERN.matcher(rawPrice).matches()) { + throw new StockFileParseException(lineNumber, trimmed, + String.format("Price \"%s\" is invalid — must be a positive number with exactly 2 decimal places using '.' (e.g. 191.27)", rawPrice)); + } - final int symbolIndex = 0; - final int companyIndex = 1; - final int priceIndex = 2; + BigDecimal price; + try { + price = new BigDecimal(rawPrice); + } catch (NumberFormatException e) { + throw new StockFileParseException(lineNumber, trimmed, + String.format("Price \"%s\" could not be parsed as a number", rawPrice)); + } - String symbol = parts[symbolIndex]; - String company = parts[companyIndex]; - BigDecimal salePrice = new BigDecimal(parts[priceIndex].trim()); + if (price.compareTo(BigDecimal.ZERO) <= 0) { + throw new StockFileParseException(lineNumber, trimmed, + String.format("Price \"%s\" must be greater than zero", rawPrice)); + } - stocks.add(new Stock(symbol, company, salePrice)); + stocks.add(new Stock(symbol, company, price)); } } - return stocks; - } - /** - * Method for writing stock information to file. - * - * @param path the path of the file to write to - * @param stocks list of stock objects to be transcribed - * @throws IOException if error occurs - */ - public static void writeToFile( - final Path path, - final List stocks - ) throws IOException { - - try (BufferedWriter writer = Files.newBufferedWriter(path)) { - writer.write("# S&P 500 Companies by Market Cap"); - writer.write("\n# Ticker,Name,Price"); - writer.newLine(); - - for (Stock stock : stocks) { - String line = String.format("%s,%s,%s", - stock.getSymbol(), - stock.getCompany(), - stock.getSalesPrice()); - - writer.write(line); - writer.newLine(); - } + if (stocks.isEmpty()) { + throw new StockFileParseException(lineNumber, "", + "File contained no valid stock entries"); } + + return stocks; } } diff --git a/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/Model/StockFileHandlerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/Model/StockFileHandlerTest.java index cd6d20b..26d3711 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/Model/StockFileHandlerTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/Model/StockFileHandlerTest.java @@ -16,25 +16,25 @@ public class StockFileHandlerTest { void readFromFileTest() throws IOException { Path testFile = Files.createTempFile("stocks", "csv"); Files.writeString(testFile, "AAPL,Apple Inc.,276.43\nMSFT,Microsoft,404.68"); - List stocks = StockFileHandler.readFromFile(testFile); - assertEquals(2, stocks.size()); - assertEquals("AAPL", stocks.get(0).getSymbol()); + try { + List stocks = StockFileHandler.readFromFile(testFile); + assertEquals(2, stocks.size()); + assertEquals("AAPL", stocks.get(0).getSymbol()); + } catch (Exception e) { + throw new RuntimeException(e); + } } @Test void readFromFileWithCommentsAndEmptyLines() throws IOException { Path testFile = Files.createTempFile("stocks", "csv"); Files.writeString(testFile, "#Comment\n\nAAPL,Apple Inc.,276.43"); - List stocks = StockFileHandler.readFromFile(testFile); - assertEquals(1, stocks.size()); - } - @Test - void writeToFileTest() throws IOException { - Path testFile = Files.createTempFile("stocks", "csv"); - List stocks = List.of(new Stock("AAPL", "Apple Inc.", new BigDecimal("276.43"))); - StockFileHandler.writeToFile(testFile, stocks); - List lines = Files.readAllLines(testFile); - assertTrue(lines.get(2).startsWith("AAPL,Apple Inc.,276.43")); + try { + List stocks = StockFileHandler.readFromFile(testFile); + assertEquals(1, stocks.size()); + } catch (Exception e) { + throw new RuntimeException(e); + } } }