Skip to content

Commit

Permalink
Feat: Updated File Manager to have a fallback resource
Browse files Browse the repository at this point in the history
Also updated tests to be independent on external files.
  • Loading branch information
tommyah committed May 25, 2026
1 parent 9dca303 commit 329c2f1
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 71 deletions.
2 changes: 1 addition & 1 deletion src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public void start(final Stage stage) throws Exception {
ViewManager viewManager = new ViewManager(stage, eventManager);

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

StockFileParser converter1 = new StockFileParser();
stocksInFile = converter1.getStocksFromStrings(parser1.readFile());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package edu.ntnu.idi.idatt2003.g40.mappe.service;

import java.io.*;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
Expand Down Expand Up @@ -72,7 +76,7 @@ private enum ParserRuleSet {
try {
new BigDecimal(s);
return true;
} catch (NumberFormatException e) {
} catch (NumberFormatException _) {
return false;
}
}),
Expand Down Expand Up @@ -109,6 +113,9 @@ public StockFileManager(final String pathName) {
/**
* Reads the file and returns a list element of all valid stocks as strings.
*
* <p>If file is not found,
* falls back to default file in resources folder.</p>
*
* <p>Uses {@link BufferedReader} for opening a file stream. </p>
*
* @return {@link List<String>} object of all valid stock strings in file.
Expand All @@ -117,38 +124,66 @@ public StockFileManager(final String pathName) {
*
* @see Path
* */

public List<String> readFile() throws IOException {
try (InputStream inputStream = getClass().getResourceAsStream(pathName);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
Path path = Paths.get(pathName);

if (!Files.exists(path)) {
extractResourceFallback(path);
}

List<String> allLines = bufferedReader.readAllLines();
List<String> readableLines =
allLines.stream()
.filter(ParserRuleSet.VALID_FORMAT.rule).toList();
try (BufferedReader bufferedReader =
Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
List<String> allLines = bufferedReader.lines().toList();
List<String> readableLines = allLines.stream()
.filter(ParserRuleSet.VALID_FORMAT.rule)
.toList();

// Valid lines (following the correct regular expressions)
return readableLines.stream().filter(s -> {
String[] parts = s.trim().split(",");

if (parts.length != 3) {
return false;
}

boolean validCode = ParserRuleSet
.VALID_CODE.rule.test(parts[0].trim());

boolean validName = ParserRuleSet
.VALID_NAME.rule.test(parts[1].trim());

boolean validPrice = ParserRuleSet
.VALID_PRICE.rule.test(parts[2].trim());
boolean validCode =
ParserRuleSet.VALID_CODE.rule.test(parts[0].trim());
boolean validName =
ParserRuleSet.VALID_NAME.rule.test(parts[1].trim());
boolean validPrice =
ParserRuleSet.VALID_PRICE.rule.test(parts[2].trim());

return validCode && validName && validPrice;
}).toList();

} catch (IOException e) {
throw new IOException("File parser could not parse file!");
throw new IOException("File parser could not parse file!", e);
}
}

/**
* Extracts the fallback template file from the application resources to
* the local file system.
*
* @param targetPath path to send the fallback file.
*
* @throws IOException if resource or input stream is not found or null.
*/
private void extractResourceFallback(final Path targetPath)
throws IOException {
String resourceName = "/sp500.csv";

try (InputStream inputStream = getClass().getResourceAsStream(resourceName)) {
if (inputStream == null) {
throw new FileNotFoundException(
"Resource file not found in JAR: " + resourceName
);
}

if (targetPath.getParent() != null) {
Files.createDirectories(targetPath.getParent());
}

Files.copy(inputStream, targetPath);
}
}

Expand Down
11 changes: 0 additions & 11 deletions src/main/resources/testStockData.txt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,60 +1,66 @@
package edu.ntnu.idi.idatt2003.g40.mappe.service;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

class StockFileManagerTest {

private final String testStockDataPath = "/testStockData.txt";

private final String absoluteTestStockDataPath = "src/main/resources/testStockData.txt";
StockFileManager stockFileManager;

private final String validStockFromFile = "NVID, Nvidida Corporation, 241.591";

private final String invalidStockFromFile = "COOLI, This is a cool name, 252.2";
/**
* Path used to test file system.
* */
@TempDir
private Path tempDir;

private final String commentFromFile = "#Above me are some valid formats.";

private List<String> allLines = new ArrayList<>();
@Test
void readFileParsesValidStocksAndFiltersOutCommentsAndInvalidData()
throws IOException {
Path tempFile = tempDir.resolve("testStockData.txt");
List<String> mockFileData = List.of(
"# This is a comment header line and should be skipped",
"NVID, Nvidida Corporation, 241.591",
"AAPL, Apple Inc, 175.50",
"",
"INVALID_ROW_MISSING_COLUMNS",
"COOLI, This is a cool name but missing price token"
);
Files.write(tempFile, mockFileData);

private List<String> validStocks = new ArrayList<>();
StockFileManager stockFileManager =
new StockFileManager(tempFile.toString());
List<String> parsingResults = stockFileManager.readFile();

@BeforeEach
void setUp() throws Exception {
stockFileManager = new StockFileManager(testStockDataPath);
Path path = Paths.get(absoluteTestStockDataPath);
allLines = Files.readAllLines(path);
try {
validStocks = stockFileManager.readFile();
} catch (Exception _) {
throw new Exception("Test failed");
}
assertEquals(2, parsingResults.size());
assertTrue(parsingResults.contains("NVID, Nvidida Corporation, 241.591"));
assertTrue(parsingResults.contains("AAPL, Apple Inc, 175.50"));
assertFalse(parsingResults.stream().anyMatch(line -> line.startsWith("#")));
}

@Test
void parser_gets_valid_stock_from_file() {
assertTrue(allLines.contains(validStockFromFile));
assertTrue(validStocks.contains(validStockFromFile));
}
void readFileHandlesEmptyFileGracefully() throws IOException {
Path emptyFile = tempDir.resolve("emptyStockData.txt");
Files.createFile(emptyFile);

@Test
void parser_skips_comments_from_file() {
assertTrue(allLines.contains(commentFromFile));
assertFalse(validStocks.contains(commentFromFile));
StockFileManager stockFileManager = new StockFileManager(emptyFile.toString());
List<String> results = stockFileManager.readFile();

assertTrue(results.isEmpty());
}

@Test
void parser_skips_invalid_stock_from_file() {
assertTrue(allLines.contains(invalidStockFromFile));
assertFalse(validStocks.contains(invalidStockFromFile));
void constructorOrReadFileDoesNotThrowExceptionOnMissingFile() {
StockFileManager stockFileManager =
new StockFileManager("non_existent_directory/missing_file.txt");

assertDoesNotThrow(stockFileManager::readFile);
}
}
}

0 comments on commit 329c2f1

Please sign in to comment.