From 20c89380ae47fbde2a968be775dc1d11ba1d6d19 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 11 Mar 2026 14:28:49 +0100 Subject: [PATCH 01/12] Feat: Fileparser, file converter, dummydata Added a file parser class that is responsible for parsing a .txt file that has stocks. Added a file converter class that is responsible for converting a list of strings (from file parser) to a list of stock objects. Added dummydata.txt which includes some stocks --- .../idatt2003/g40/mappe/FileConverter.java | 23 +++++++ .../idi/idatt2003/g40/mappe/FileParser.java | 68 +++++++++++++++++++ src/main/resources/dummydata.txt | 11 +++ 3 files changed, 102 insertions(+) create mode 100644 src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/FileConverter.java create mode 100644 src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParser.java create mode 100644 src/main/resources/dummydata.txt diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/FileConverter.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/FileConverter.java new file mode 100644 index 0000000..936a9d4 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/FileConverter.java @@ -0,0 +1,23 @@ +package edu.ntnu.idi.idatt2003.g40.mappe; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +public class FileConverter { + + public List getStocksFromFile(FileParser fileParser) throws IOException { + List strings = fileParser.readFile(); + List stocksFromFile = new ArrayList<>(); + + strings.forEach(s -> { + String[] lineElements = s.split(","); + String stockSymbol = lineElements[0].trim(); + String stockName = lineElements[1].trim(); + BigDecimal stockPrice = new BigDecimal(lineElements[2].trim()); + stocksFromFile.add(new Stock(stockSymbol, stockName, stockPrice)); + }); + return stocksFromFile; + } +} diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParser.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParser.java new file mode 100644 index 0000000..23c8f8a --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParser.java @@ -0,0 +1,68 @@ +package edu.ntnu.idi.idatt2003.g40.mappe; + +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.function.Predicate; + +public class FileParser { + + private final String pathName; + + private enum ParserRuleSet { + NOT_EMPTY(s -> !s.trim().isEmpty()), + NOT_COMMENT(s -> !s.startsWith("#")), + VALID_FORMAT(NOT_EMPTY.rule.and(NOT_COMMENT.rule)), + VALID_CODE(s -> s.matches("[A-Z]{4}")), + VALID_NAME(s -> s.matches(".*")), + CAN_PARSE_TO_BIG_DECIMAL(s -> { + try { + new BigDecimal(s); + return true; + } catch (NumberFormatException e) { + return false; + } + }), + VALID_PRICE_FORMAT(s -> s.matches("[^a-zA-Z]+")), + VALID_PRICE(VALID_PRICE_FORMAT.rule.and(CAN_PARSE_TO_BIG_DECIMAL.rule)); + + private final Predicate rule; + + ParserRuleSet(final Predicate rule) { + this.rule = rule; + } + } + + public FileParser(final String pathName) { + this.pathName = pathName; + } + + public List readFile() throws IOException { + try { + Path path = Paths.get(pathName); + List allLines = Files.readAllLines(path); + List 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()); + + return validCode && validName && validPrice; + }).toList(); + + } catch (IOException e) { + throw new IOException("File parser could not parse file!"); + } + } +} diff --git a/src/main/resources/dummydata.txt b/src/main/resources/dummydata.txt new file mode 100644 index 0000000..29f6c6b --- /dev/null +++ b/src/main/resources/dummydata.txt @@ -0,0 +1,11 @@ +#THIS IS A COMMENT. Below me is an empty line + +AAPL, Apple Inc., 276.43 +NVID, Nvidida Corporation, 241.591 + +#Above me are some valid formats. +#Below me are some invalid formats + +COOLI, This is a cool name, 252.2 + +COOL, This is a cool name, 252.2a \ No newline at end of file From 113ed67f2d73a2aab724536040e295928e5f3469 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 11 Mar 2026 15:03:55 +0100 Subject: [PATCH 02/12] Feat: Added FileParserTest --- .../idatt2003/g40/mappe/FileParserTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParserTest.java diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParserTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParserTest.java new file mode 100644 index 0000000..ef7e8fb --- /dev/null +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParserTest.java @@ -0,0 +1,18 @@ +package edu.ntnu.idi.idatt2003.g40.mappe; + +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.*; + +class FileParserTest { + + private final String filePath = "src/main/resources/dummydata.txt"; + FileParser fileParser; + + @Test + void test1() { + fileParser = new FileParser(filePath); + } +} \ No newline at end of file From 741b9173e9741313253dea7414f97173de7ce449 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 11 Mar 2026 15:37:02 +0100 Subject: [PATCH 03/12] Fix: Commented out invalid test in StockTest --- src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/StockTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/StockTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/StockTest.java index 097cd27..c7f0a46 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/StockTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/StockTest.java @@ -17,14 +17,14 @@ void constructor_setsSymbolAndCompany() { assertEquals("Apple Inc.", stock.getCompany()); } - @Test + /*@Test void getSalesPrice_throwsWhenNoPricesExist_currentImplementation() { Stock stock = new Stock("AAPL", "Apple Inc.", new BigDecimal("100.00")); // Because constructor does not add the initial salesPrice to prices, // prices is empty and getLast() throws NoSuchElementException. assertThrows(NoSuchElementException.class, stock::getSalesPrice); - } + }*/ @Test void addNewSalesPrice_thenGetSalesPrice_returnsLastAddedPrice() { From a5b9485c82ec05de64695c65eefddb5c55e5cde5 Mon Sep 17 00:00:00 2001 From: = Date: Wed, 11 Mar 2026 15:37:17 +0100 Subject: [PATCH 04/12] Feat: Unit tests for FileParser --- .../idatt2003/g40/mappe/FileParserTest.java | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParserTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParserTest.java index ef7e8fb..fc901fc 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParserTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParserTest.java @@ -1,8 +1,15 @@ package edu.ntnu.idi.idatt2003.g40.mappe; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.File; +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.*; @@ -11,8 +18,43 @@ class FileParserTest { private final String filePath = "src/main/resources/dummydata.txt"; FileParser fileParser; - @Test - void test1() { + private final String validStockFromFile = "NVID, Nvidida Corporation, 241.591"; + + private final String invalidStockFromFile = "COOLI, This is a cool name, 252.2"; + + private final String commentFromFile = "#Above me are some valid formats."; + + private List allLines = new ArrayList<>(); + + private List validStocks = new ArrayList<>(); + + @BeforeEach + void setUp() throws Exception { fileParser = new FileParser(filePath); + Path path = Paths.get(filePath); + allLines = Files.readAllLines(path); + try { + validStocks = fileParser.readFile(); + } catch (Exception _) { + throw new Exception("Test failed"); + } + } + + @Test + void parser_gets_valid_stock_from_file() { + assertTrue(allLines.contains(validStockFromFile)); + assertTrue(validStocks.contains(validStockFromFile)); + } + + @Test + void parser_skips_comments_from_file() { + assertTrue(allLines.contains(commentFromFile)); + assertFalse(validStocks.contains(commentFromFile)); + } + + @Test + void parser_skips_invalid_stock_from_file() { + assertTrue(allLines.contains(invalidStockFromFile)); + assertFalse(validStocks.contains(invalidStockFromFile)); } } \ No newline at end of file From d4193878b26dc78c90244032aa9b979b6ead198b Mon Sep 17 00:00:00 2001 From: = Date: Sun, 15 Mar 2026 18:04:53 +0100 Subject: [PATCH 05/12] Feat: seperated .txt files for testing and dummydata --- src/main/resources/dummydata.txt | 7 +++++-- src/main/resources/testStockData.txt | 11 +++++++++++ .../ntnu/idi/idatt2003/g40/mappe/FileParserTest.java | 6 +++--- 3 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 src/main/resources/testStockData.txt diff --git a/src/main/resources/dummydata.txt b/src/main/resources/dummydata.txt index 29f6c6b..25144a3 100644 --- a/src/main/resources/dummydata.txt +++ b/src/main/resources/dummydata.txt @@ -1,4 +1,4 @@ -#THIS IS A COMMENT. Below me is an empty line +#THIS IS A COMMENT. AAPL, Apple Inc., 276.43 NVID, Nvidida Corporation, 241.591 @@ -8,4 +8,7 @@ NVID, Nvidida Corporation, 241.591 COOLI, This is a cool name, 252.2 -COOL, This is a cool name, 252.2a \ No newline at end of file +COOL, This is a cool name, 252.2a + +AAPL,Apple Inc.,276.43 +NVID,Nvidida Corporation,241.591 \ No newline at end of file diff --git a/src/main/resources/testStockData.txt b/src/main/resources/testStockData.txt new file mode 100644 index 0000000..b349d0e --- /dev/null +++ b/src/main/resources/testStockData.txt @@ -0,0 +1,11 @@ +#THIS IS A COMMENT. + +AAPL, Apple Inc., 276.43 +NVID, Nvidida Corporation, 241.591 + +#Above me are some valid formats. +#Below me are some invalid formats + +COOLI, This is a cool name, 252.2 + +COOL, This is a cool name, 252.2a \ No newline at end of file diff --git a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParserTest.java b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParserTest.java index fc901fc..2935a30 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParserTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParserTest.java @@ -15,7 +15,7 @@ class FileParserTest { - private final String filePath = "src/main/resources/dummydata.txt"; + private final String testStockDataPath = "src/main/resources/testStockData.txt"; FileParser fileParser; private final String validStockFromFile = "NVID, Nvidida Corporation, 241.591"; @@ -30,8 +30,8 @@ class FileParserTest { @BeforeEach void setUp() throws Exception { - fileParser = new FileParser(filePath); - Path path = Paths.get(filePath); + fileParser = new FileParser(testStockDataPath); + Path path = Paths.get(testStockDataPath); allLines = Files.readAllLines(path); try { validStocks = fileParser.readFile(); From 9a015dae599d049b12d2e2fd3ddb2fdf18fd44a1 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 15 Mar 2026 18:19:16 +0100 Subject: [PATCH 06/12] Feat: Updated fileconverter and fileparser. --- .../idatt2003/g40/mappe/FileConverter.java | 60 +++++++- .../idi/idatt2003/g40/mappe/FileParser.java | 130 +++++++++++++++++- 2 files changed, 178 insertions(+), 12 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/FileConverter.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/FileConverter.java index 936a9d4..6075200 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/FileConverter.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/FileConverter.java @@ -1,23 +1,73 @@ package edu.ntnu.idi.idatt2003.g40.mappe; -import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; +/** + * Converts stock objects to/from string format for file handling. + * + *

Responsibilities:

+ *
    + *
  • Turn a valid list of stock string elements to a + * list of {@link Stock} objects.
  • + * + *
  • Turn a list of stock objects to a + * list of string elements.
  • + *
+ * + *

Used with {@link FileParser}

+ * + * @see FileParser + * @author tohja + * @version 1.0.0 + * + * */ public class FileConverter { - public List getStocksFromFile(FileParser fileParser) throws IOException { - List strings = fileParser.readFile(); + /** + * Turns a list of valid string representations + * of stock objects to a list of stock objects. + * + * @param validStocks list of string elements properly + * representing stock objects. + * + * @return {@link List} + * */ + public List getStocksFromStrings(final List validStocks) { List stocksFromFile = new ArrayList<>(); + List stockSymbols = new ArrayList<>(); - strings.forEach(s -> { + validStocks.forEach(s -> { String[] lineElements = s.split(","); String stockSymbol = lineElements[0].trim(); String stockName = lineElements[1].trim(); BigDecimal stockPrice = new BigDecimal(lineElements[2].trim()); - stocksFromFile.add(new Stock(stockSymbol, stockName, stockPrice)); + // TODO: try-catch + Stock stockObject = new Stock(stockSymbol, stockName, stockPrice); + if (!stockSymbols.contains(stockSymbol)) { + stockSymbols.add(stockSymbol); + stocksFromFile.add(stockObject); + } }); return stocksFromFile; } + + /** + * Converts a list of stocks to string representations of that stock. + * + *

format: SYMBOL, NAME, PRICE

+ * + * @param stocks a list of {@link Stock} objects. + * + * @return a list of string representation of the stock objects. + * */ + public List stocksToStrings(final List stocks) { + ArrayList stringList = new ArrayList<>(); + stocks.forEach(s -> + stringList.add(s.getSymbol().trim() + "," + s.getCompany().trim() + + "," + s.getSalesPrice().toString()) + ); + return stringList; + } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParser.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParser.java index 23c8f8a..d0d0279 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParser.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/FileParser.java @@ -1,23 +1,74 @@ package edu.ntnu.idi.idatt2003.g40.mappe; +import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.IOException; import java.math.BigDecimal; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; import java.util.List; import java.util.function.Predicate; +/** + * Class used to parse (filter) valid stocks from a .txt file. + * + *

Responsibilities:

+ *
    + *
  • Read file and return a filtered list of string elements, + * where each element represents a valid stock line.
  • + * + *
  • Write a list of string representations of stock objects + * to file, each stock separated by a line.
  • + *
+ * + *

Used with {@link FileConverter}

+ * + * @see FileConverter + * @author tohja + * @version 1.0.0 + * */ public class FileParser { + /** The path name this parser is using.*/ private final String pathName; + /** + * Rule set for validating lines and strings. + * + *

Uses predicates.

+ * */ private enum ParserRuleSet { + + /** + * Rule for whether string is empty or not. + * */ NOT_EMPTY(s -> !s.trim().isEmpty()), + + /** + * Rule for if string is comment or not. + * */ NOT_COMMENT(s -> !s.startsWith("#")), + + /** + * Rule for if line is in valid format. + * */ VALID_FORMAT(NOT_EMPTY.rule.and(NOT_COMMENT.rule)), + + /** + * Rule for if string is considered a valid company symbol. + * */ VALID_CODE(s -> s.matches("[A-Z]{4}")), + + /** + * Rule for if string is a valid company name. + * */ VALID_NAME(s -> s.matches(".*")), + + /** + * Rule for if string can be parsed to a {@link BigDecimal} object. + * */ CAN_PARSE_TO_BIG_DECIMAL(s -> { try { new BigDecimal(s); @@ -26,9 +77,20 @@ private enum ParserRuleSet { return false; } }), + + /** + * Rule for if string is in a valid price format. + * */ VALID_PRICE_FORMAT(s -> s.matches("[^a-zA-Z]+")), + + /** + * Rule for if string is a valid price. + * */ VALID_PRICE(VALID_PRICE_FORMAT.rule.and(CAN_PARSE_TO_BIG_DECIMAL.rule)); + /** + * The constants' rules as predicates with input of type string. + * */ private final Predicate rule; ParserRuleSet(final Predicate rule) { @@ -36,15 +98,35 @@ private enum ParserRuleSet { } } + /** + * Constructor. + * + * @param pathName the file path name to read. + * */ public FileParser(final String pathName) { this.pathName = pathName; } + /** + * Reads the file and returns a list element of all valid stocks as strings. + * + *

Uses {@link BufferedReader} for opening a file stream.

+ * + * @return {@link List} object of all valid stock strings in file. + * + * @throws IOException if path cannot be read. + * + * @see Path + * */ + public List readFile() throws IOException { - try { - Path path = Paths.get(pathName); - List allLines = Files.readAllLines(path); - List readableLines = allLines.stream().filter(ParserRuleSet.VALID_FORMAT.rule).toList(); + Path path = Paths.get(pathName); + try (BufferedReader bufferedReader = Files.newBufferedReader(path)) { + + List allLines = bufferedReader.readAllLines(); + List readableLines = + allLines.stream() + .filter(ParserRuleSet.VALID_FORMAT.rule).toList(); // Valid lines (following the correct regular expressions) return readableLines.stream().filter(s -> { @@ -54,9 +136,14 @@ public List readFile() throws IOException { 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(); @@ -65,4 +152,33 @@ public List readFile() throws IOException { throw new IOException("File parser could not parse file!"); } } + + /** + * Writes a given lists of stocks to the file. + * + *

Uses {@link BufferedWriter}.

+ * + * @param stringList list of strings representing stocks in the format + * SYMBOL, NAME, PRICE + * + * @throws IOException if writing process throws error. + * */ + public void writeStocksToFile(final List stringList) + throws IOException { + Path path = Paths.get(pathName); + + try (BufferedWriter writer = Files.newBufferedWriter(path, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND)) { + + writer.newLine(); + + for (String line : stringList) { + writer.write(line); + writer.newLine(); + } + } catch (IOException e) { + throw new IOException("Error during buffered write", e); + } + } } From 2e3b28115f930df2c1a9053d3a524c2ce6875c3d Mon Sep 17 00:00:00 2001 From: = Date: Sun, 15 Mar 2026 18:19:38 +0100 Subject: [PATCH 07/12] Feat: Added a basic main menu for testing purposes --- .../ntnu/idi/idatt2003/g40/mappe/Main.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java index b835759..186a1ba 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Main.java @@ -1,17 +1,18 @@ package edu.ntnu.idi.idatt2003.g40.mappe; -//TIP To Run code, press or -// click the icon in the gutter. +import java.io.IOException; +import java.util.List; + public class Main { static void main() { - //TIP Press with your caret at the highlighted text - // to see how IntelliJ IDEA suggests fixing it. - IO.println(String.format("Hello and welcome!")); - - for (int i = 1; i <= 5; i++) { - //TIP Press to start debugging your code. We have set one breakpoint - // for you, but you can always add more by pressing . - IO.println("i = " + i); + FileParser parser1 = new FileParser("src/main/resources/dummydata.txt"); + FileConverter converter1 = new FileConverter(); + try { + List stocksInFile = converter1.getStocksFromStrings(parser1.readFile()); + parser1.writeStocksToFile(converter1.stocksToStrings(stocksInFile)); + stocksInFile.forEach(s -> System.out.println(s.getSymbol())); + } catch (IOException e) { + System.err.println(e.getMessage()); } } } From cfedec2690a7bb875974a1ee05187aca17936d33 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 15 Mar 2026 18:20:00 +0100 Subject: [PATCH 08/12] Feat: fixed exchange bug --- .idea/checkstyle-idea.xml | 1 + src/main/java/edu/ntnu/idi/idatt2003/g40/mappe/Exchange.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml index cec0567..7fbdf32 100644 --- a/.idea/checkstyle-idea.xml +++ b/.idea/checkstyle-idea.xml @@ -8,6 +8,7 @@