diff --git a/src/main/java/mi/hdm/filesystem/CSVParser.java b/src/main/java/mi/hdm/filesystem/CSVParser.java index a5362801f9e6f1cc5eee0cb5b09fc0a7ff47a784..59fa405cd904c649ca39b6174822fe224faa6148 100644 --- a/src/main/java/mi/hdm/filesystem/CSVParser.java +++ b/src/main/java/mi/hdm/filesystem/CSVParser.java @@ -19,35 +19,39 @@ public class CSVParser { private static final Logger log = LogManager.getLogger(CSVParser.class); /** - * @param filepath Path where the CSV-file is located + * @param filepath Path where the CSV file is located * @param split The character that the columns are split by - * @param extract Names of the columns to extract + * @param extract Names of the columns to extract with extract[0] being the name of the ingredient * @return list of all ingredients inside the csv */ - public List<Ingredient> getIngredientsFromCSV(String filepath, char split, String... extract) { - log.info("Reading ingredients from CSV: {}", filepath); - try { - BufferedReader reader = new BufferedReader(new FileReader(filepath)); + public List<Ingredient> getIngredientsFromCSV(String filepath, char split, String... extract) throws IOException { + log.info("Trying to read ingredients from CSV: {}", filepath); + + //try-block with automatic resource management + try (BufferedReader reader = new BufferedReader(new FileReader(filepath))) { final String header = reader.readLine(); //read first line of CSV - int[] indexes = getColumnIndexes(header, split, extract); + final int[] indexes = getColumnIndexes(header, split, extract); //Loop through lines - List<Ingredient> ingredients = reader.lines() + final List<Ingredient> ingredients = reader.lines() .map(line -> getIngredientFromLine(line, split, indexes)) .toList(); - reader.close(); - log.info("Found and parsed {} ingredients.", ingredients.size()); + if (ingredients.size() > 0) { + log.info("Found and parsed {} ingredients.", ingredients.size()); + } else { + log.warn("No ingredients found in CSV, returning empty list."); + } return ingredients; + } catch (FileNotFoundException fileNotFoundException) { - Path filename = Path.of(filepath).getFileName(); + final Path filename = Path.of(filepath).getFileName(); log.error("File '{}' not found at specified filepath: {}.", filename, filepath); fileNotFoundException.printStackTrace(); + throw fileNotFoundException; } catch (IOException io) { log.error("An error occurred while reading CSV: {}", filepath); - io.printStackTrace(); + throw io; } - log.warn("No ingredients found in CSV, returning empty list."); - return List.of(); } private Ingredient getIngredientFromLine(String line, char split, int[] idx) throws NumberFormatException { @@ -56,21 +60,20 @@ public class CSVParser { final Measurement measurement = Measurement.GRAM; final List<String> splitLine = splitLine(line, split); - String name = splitLine.get(idx[0]); - List<Double> nutrition = new ArrayList<>(); + final String recipeName = splitLine.get(idx[0]); + final List<Double> nutrition = new ArrayList<>(); for (int i = 1; i < idx.length; i++) { String element = splitLine.get(idx[i]).split(" ")[0]; nutrition.add(parseNumberFromString(element)); } - NutritionTable nutritionTable = new NutritionTable(nutrition); - - return new Ingredient(measurement, name, nutritionTable); + final NutritionTable nutritionTable = new NutritionTable(nutrition); + return new Ingredient(measurement, recipeName, nutritionTable); } private int[] getColumnIndexes(String header, char splitChar, String... extract) throws InvalidPropertiesFormatException { - final String[] split = header.split(String.valueOf(splitChar)); //read first line of CSV + final String[] split = header.split(String.valueOf(splitChar)); //split header of CSV //find indexes of columns that need to be extracted int[] colIndexes = new int[extract.length]; for (int i = 0; i < extract.length; i++) { @@ -117,13 +120,18 @@ public class CSVParser { return Double.parseDouble(numValue.toString()); } + /** + * Split a line of CSV in its individual tokens based on a character while accounting for strings inside of columns + * + * @return list of tokens inside the provided line + */ private List<String> splitLine(String line, char splitChar) { - List<String> output = new ArrayList<>(); + final List<String> output = new ArrayList<>(); StringBuilder builder = new StringBuilder(); boolean inQuotes = false; for (int i = 0; i < line.length(); i++) { - char c = line.charAt(i); + final char c = line.charAt(i); if (c == '"') { inQuotes = !inQuotes; } else if (c == splitChar && !inQuotes) { diff --git a/src/main/java/mi/hdm/recipes/RecipeSearch.java b/src/main/java/mi/hdm/recipes/RecipeSearch.java index f23abdac42727b319de00a26d37b0db78789df83..70b689bf53c23cd38c4589df1444ff186b996e23 100644 --- a/src/main/java/mi/hdm/recipes/RecipeSearch.java +++ b/src/main/java/mi/hdm/recipes/RecipeSearch.java @@ -1,10 +1,14 @@ package mi.hdm.recipes; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + import java.util.ArrayList; import java.util.List; public class RecipeSearch { private final List<Recipe> recipesToSearch; + private static final Logger log = LogManager.getLogger(RecipeSearch.class); public RecipeSearch (List<Recipe> recipesToSearch) { this.recipesToSearch = recipesToSearch; @@ -17,8 +21,11 @@ public class RecipeSearch { * @param query a String with keywords to search for * @return a list of Recipes which contain one or several of the keywords in its name or its ingredients */ - public List<Recipe> searchByQuery(String query) { //TODO: filter out chars like ! ? . : etc - if (query == null || query.isBlank()) return recipesToSearch; + public List<Recipe> searchByQuery(String query) { + if (query == null) return recipesToSearch; + query = query.replaceAll("[.,!+?;-]", " ").trim(); + if (query.isBlank()) return recipesToSearch; + log.debug("Searching for {}", query); List<Recipe> result = new ArrayList<>(); String[] split = query.split(" "); //split String into separate words and put them into a String Array diff --git a/src/test/java/mi/hdm/filesystem/CSVParserTest.java b/src/test/java/mi/hdm/filesystem/CSVParserTest.java index 63d2e109f949f35b52c3305f12362f6c67ae7344..f872a7b40eb3a3b7dfb224fea06044ba19e01262 100644 --- a/src/test/java/mi/hdm/filesystem/CSVParserTest.java +++ b/src/test/java/mi/hdm/filesystem/CSVParserTest.java @@ -1,5 +1,6 @@ package mi.hdm.filesystem; +import jdk.jfr.Description; import mi.hdm.recipes.Ingredient; import mi.hdm.recipes.Measurement; import mi.hdm.recipes.NutritionTable; @@ -22,6 +23,7 @@ class CSVParserTest { @BeforeAll public static void setupAll() throws NoSuchMethodException { + //make private methods accessible for testing splitLine = underTest.getClass().getDeclaredMethod("splitLine", String.class, char.class); splitLine.setAccessible(true); getColIdxs = underTest.getClass().getDeclaredMethod("getColumnIndexes", String.class, char.class, String[].class); @@ -29,6 +31,7 @@ class CSVParserTest { } @Test + @Description("Test if splitting a line in the CSV file works properly") public void testSplitLine() throws Exception { String line = "1,\"Nuts, pecans\",100 g,691,72g"; List<String> expected = List.of("1", "Nuts, pecans", "100 g", "691", "72g"); @@ -39,6 +42,7 @@ class CSVParserTest { } @Test + @Description("Tests, if the columns to be extracted are found in the correct place and assigned with the correct index") public void testGetColumnIndexes() throws Exception { String header = ",name,calories,total_fat,saturated_fat,cholesterol,sodium"; String[] extract = new String[]{"name", "sodium"}; @@ -49,6 +53,7 @@ class CSVParserTest { } @Test + @Description("Test, if getting a list of ingredients from CSV works") public void testGetIngredientsFromCSV() throws Exception { String relative = "/data/testdata.csv"; URL resourceUrl = underTest.getClass().getResource(relative); diff --git a/src/test/java/mi/hdm/recipes/RecipeSearchTest.java b/src/test/java/mi/hdm/recipes/RecipeSearchTest.java index 605241a127e4aa5b78ab7c324b592d0e024c9d60..f29d421ab1091160c37bbf790b1d94e3c5b7dc81 100644 --- a/src/test/java/mi/hdm/recipes/RecipeSearchTest.java +++ b/src/test/java/mi/hdm/recipes/RecipeSearchTest.java @@ -2,6 +2,9 @@ package mi.hdm.recipes; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; import java.util.List; @@ -23,18 +26,18 @@ class RecipeSearchTest { underTest = new RecipeSearch(recipes); } - @Test - public void testSearchByEmptyQuery() { - assertEquals(recipes, underTest.searchByQuery(null)); - assertEquals(recipes, underTest.searchByQuery("")); - assertEquals(recipes, underTest.searchByQuery(" \n")); + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {" \n", ".", "!", "..."}) + public void testSearchByEmptyQuery(String input) { + assertEquals(recipes, underTest.searchByQuery(input)); } - @Test - public void testSearchByNameQuery() { + @ParameterizedTest + @ValueSource(strings = {"valid", "valid!", "...valid"}) + public void testSearchByNameQuery(String query) { //given List<Recipe> expected = List.of(r1); - String query = "valid"; //when List<Recipe> result = underTest.searchByQuery(query); @@ -56,10 +59,10 @@ class RecipeSearchTest { assertEquals(expected, result); } - @Test - public void testSearchByEmptyCategories() { - assertEquals(recipes, underTest.searchByCategory(List.of())); - assertEquals(recipes, underTest.searchByCategory(null)); + @ParameterizedTest + @NullAndEmptySource + public void testSearchByEmptyCategories(List<Category> input) { + assertEquals(recipes, underTest.searchByCategory(input)); } @Test