diff --git a/pom.xml b/pom.xml index 0e8382b993894da6738068e5f5705536e79edac9..19e61cf85b4f2e726ff0cbd2f10bd19de0af5257 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,12 @@ <version>2.17.1</version> </dependency> + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + <version>2.10.1</version> + </dependency> + </dependencies> <build> diff --git a/src/main/java/mi/hdm/GuiController.java b/src/main/java/mi/hdm/GuiController.java new file mode 100644 index 0000000000000000000000000000000000000000..28651a997ff43d1132c343afc757d54517c9ca02 --- /dev/null +++ b/src/main/java/mi/hdm/GuiController.java @@ -0,0 +1,54 @@ +package mi.hdm; + +import javafx.application.Application; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; +import mi.hdm.controllers.View; +import mi.hdm.filesystem.FileManager; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class GuiController extends Application { + private final static Logger log = LogManager.getLogger(GuiController.class); + + private static Stage stage; + private static TastyPages model; + + public static void main(String[] args) { + log.info("Starting TastyPages"); + try { + model = FileManager.deserializeFromFilesystem(); + } catch (Exception e) { + log.fatal("Internal exception, check filepaths and URIs"); + e.printStackTrace(); + System.exit(1); + } + launch(args); + log.info("Shutting down application"); + log.info("Saving all data"); + FileManager.serializeToFile(model); + } + + @Override + public void start(Stage stage) throws Exception { + GuiController.stage = stage; + final View startupView = View.MAIN; + + Parent parent = startupView.getScene(); + final Scene scene = new Scene(parent, 1400, 800); + + stage.setTitle(startupView.getWindowTitle()); + stage.setMaximized(true); //open scene in full screen + stage.setScene(scene); + stage.show(); + } + + public static TastyPages getModel() { + return model; + } + + public static Stage getApplicationStage() { + return stage; + } +} diff --git a/src/main/java/mi/hdm/TastyPages.java b/src/main/java/mi/hdm/TastyPages.java index 8e29204005ebcbf71a04cb53ee83d79cacc2bd2a..ea65bde8ab36b87b3674fb189c86ce13ea28ef7e 100644 --- a/src/main/java/mi/hdm/TastyPages.java +++ b/src/main/java/mi/hdm/TastyPages.java @@ -1,57 +1,76 @@ package mi.hdm; -import javafx.application.Application; -import javafx.scene.Parent; -import javafx.scene.Scene; -import javafx.stage.Stage; -import mi.hdm.controllers.View; import mi.hdm.mealPlan.MealPlan; -import mi.hdm.recipes.*; +import mi.hdm.recipes.CategoryManager; +import mi.hdm.recipes.IngredientManager; +import mi.hdm.recipes.RecipeManager; import mi.hdm.shoppingList.ShoppingList; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class TastyPages extends Application { - //TODO: instantiate all dependencies HERE! The getScene() method from View.java should go here as well for dependency injection - public final static RecipeManager recipeManager = new RecipeManager(); - public final static ShoppingList shoppingList = new ShoppingList(); - public final static CategoryManager categoryManager = new CategoryManager(List.of(new Category("Mittagessen", 0xFF4040), new Category("mjam", 0x00FF00))); - public final static MealPlan mealPlan = new MealPlan(new HashMap<>()); +/** + * This class contains all the services in the application: recipe manager, ingredient manager, shopping list, + * category manager and meal plan. + */ +public class TastyPages { + private final RecipeManager recipeManager; + private final IngredientManager ingredientManager; + private final ShoppingList shoppingList; + private final CategoryManager categoryManager; + private final MealPlan mealPlan; private final static Logger log = LogManager.getLogger(TastyPages.class); - private static Stage stage; - public static final View startUpView = View.MAIN; + /** + * Creates a new instance of the application holding all user data. The parameters are expected to be parsed with the FileManager + */ + public TastyPages(RecipeManager recipeManager, IngredientManager ingredientManager, CategoryManager categoryManager, MealPlan mealPlan, ShoppingList shoppingList) { + this.recipeManager = recipeManager; + this.ingredientManager = ingredientManager; + this.categoryManager = categoryManager; + this.mealPlan = mealPlan; + this.shoppingList = shoppingList; + log.info("Loaded TastyPages with all dependencies."); + } - public static void main(String[] args) { - log.info("Starting TastyPages"); + public TastyPages(IngredientManager ingredientManager) { + this.ingredientManager = ingredientManager; + this.recipeManager = new RecipeManager(); + this.categoryManager = new CategoryManager(); + this.mealPlan = new MealPlan(recipeManager); + this.shoppingList = new ShoppingList(recipeManager); + log.info("Loaded TastyPages with ingredient manager"); + } - recipeManager.addRecipe(new Recipe("Mein erstes Rezept", Map.of(new Ingredient(Measurement.GRAM, "Mehl", new NutritionTable(100, 20, 8, 14, 2.5, 3)), 100), "Description", List.of("joa"), List.of(categoryManager.getAllCategories().get(1)), 40)); - recipeManager.addRecipe(new Recipe("Mein letztes Rezept", Map.of(new Ingredient(Measurement.GRAM, "Zucker", new NutritionTable(100, 500, 8, 14, 2.5, 3)), 100), "Description", List.of("joa"), List.of(categoryManager.getAllCategories().get(0)), 40)); + /** + * Creates a new, completely empty instance of the application. This constructor is not expected to be used. + */ + public TastyPages() { + this.recipeManager = new RecipeManager(); + this.ingredientManager = new IngredientManager(); + this.categoryManager = new CategoryManager(); + this.mealPlan = new MealPlan(recipeManager); + this.shoppingList = new ShoppingList(recipeManager); + log.info("Created empty instance of TastyPages"); + } - launch(args); + public RecipeManager getRecipeManager() { + return recipeManager; } - public static Stage getApplicationStage() { - return stage; + public IngredientManager getIngredientManager() { + return ingredientManager; } - @Override - public void start(Stage stage) throws IOException { - TastyPages.stage = stage; - final View startupView = View.MAIN; + public ShoppingList getShoppingList() { + return shoppingList; + } - Parent parent = startupView.getScene(); - final Scene scene = new Scene(parent, 1400, 800); + public CategoryManager getCategoryManager() { + return categoryManager; + } - stage.setTitle(startupView.getWindowTitle()); - stage.setMaximized(true); //open scene in full screen - stage.setScene(scene); - stage.show(); + public MealPlan getMealPlan() { + return mealPlan; } } diff --git a/src/main/java/mi/hdm/components/RecipeVbox.java b/src/main/java/mi/hdm/components/RecipeVbox.java index c393f6b9dc5b804b92b7370cf6d64e613f21be68..7577123fa6652913561aeb510befab6402383b20 100644 --- a/src/main/java/mi/hdm/components/RecipeVbox.java +++ b/src/main/java/mi/hdm/components/RecipeVbox.java @@ -5,6 +5,7 @@ import javafx.scene.control.Label; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import mi.hdm.recipes.Category; +import mi.hdm.recipes.CategoryManager; import mi.hdm.recipes.Recipe; import java.util.ArrayList; @@ -16,7 +17,7 @@ import java.util.List; public class RecipeVbox extends VBox { private final Recipe recipe; - public RecipeVbox(Recipe recipe) { + public RecipeVbox(Recipe recipe, CategoryManager categoryManager) { super(); this.recipe = recipe; @@ -30,20 +31,20 @@ public class RecipeVbox extends VBox { List<Label> categoryLabels = new ArrayList<>(); //add a maximum of 3 categories to the recipe tile - for (int i = 0; i < Math.min(recipe.getCategories().size(), 3); i++) { - Category c = recipe.getCategories().get(i); - Label l = new Label(c.getName()); + List<Category> categories = categoryManager.getCategoriesFromKeys(recipe.getCategoryCodes()); + for (int i = 0; i < Math.min(recipe.getCategoryCodes().size(), 3); i++) { + Label l = new Label(categories.get(i).getName()); //TODO: make border radius work on the styling - l.setStyle("-fx-background-color: " + c.getColourCode() + ";" + + l.setStyle("-fx-background-color: " + categories.get(i).getColourCode() + ";" + "-fx-padding: 3px;" + "-fx-border-radius: 5px;" ); categoryLabels.add(l); } - HBox categories = new HBox(); - categories.getChildren().addAll(categoryLabels); + HBox categoryHbox = new HBox(); + categoryHbox.getChildren().addAll(categoryLabels); - this.getChildren().addAll(recipeName, recipeDescription, categories); + this.getChildren().addAll(recipeName, recipeDescription, categoryHbox); this.setPrefWidth(180); this.setPrefHeight(250); diff --git a/src/main/java/mi/hdm/controllers/BaseController.java b/src/main/java/mi/hdm/controllers/BaseController.java index e0a2bc92126eaaf3c0bb87dc432303b54b5be9b2..2d6abfa2cb9a55870b8252e97d96fc8b2543917c 100644 --- a/src/main/java/mi/hdm/controllers/BaseController.java +++ b/src/main/java/mi/hdm/controllers/BaseController.java @@ -4,7 +4,7 @@ import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.layout.AnchorPane; import javafx.stage.Stage; -import mi.hdm.TastyPages; +import mi.hdm.GuiController; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -12,13 +12,13 @@ import java.io.IOException; public abstract class BaseController { private final static Logger log = LogManager.getLogger(BaseController.class); - private final static Stage stage = TastyPages.getApplicationStage(); protected Scene currentScene; private static View currentView = TastyPages.startUpView; protected void changeScene(final View newScene) { if (newScene == currentView) { return; } + final Stage stage = GuiController.getApplicationStage(); try { boolean wasMaximized = stage.isMaximized(); double sizeX = stage.getWidth(); diff --git a/src/main/java/mi/hdm/controllers/HeaderController.java b/src/main/java/mi/hdm/controllers/HeaderController.java index cb6c76bb896df29af8a22213cf702138f08e268e..fa60084bea511a0d349c6b4bec33dbde1f6e3bc0 100644 --- a/src/main/java/mi/hdm/controllers/HeaderController.java +++ b/src/main/java/mi/hdm/controllers/HeaderController.java @@ -2,14 +2,14 @@ package mi.hdm.controllers; import javafx.fxml.FXML; import javafx.scene.control.TextField; +import mi.hdm.recipes.IngredientManager; import mi.hdm.recipes.Recipe; import mi.hdm.recipes.RecipeManager; import mi.hdm.recipes.RecipeSearch; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.List; - +import java.util.Collection; public class HeaderController extends BaseController { private final static Logger log = LogManager.getLogger(HeaderController.class); @@ -18,10 +18,13 @@ public class HeaderController extends BaseController { private TextField searchBox; private final RecipeManager recipeManager; - private List<Recipe> lastSearchResults; + private final IngredientManager ingredientManager; + + private Collection<Recipe> lastSearchResults; - public HeaderController(RecipeManager recipeManager) { + public HeaderController(RecipeManager recipeManager, IngredientManager ingredientManager) { this.recipeManager = recipeManager; + this.ingredientManager = ingredientManager; lastSearchResults = recipeManager.getRecipes(); } @@ -44,8 +47,8 @@ public class HeaderController extends BaseController { public void searchByQuery() { String query = searchBox.getText(); log.debug("User submitted search box"); - //RecipeSearch recipeSearch = new RecipeSearch(recipeManager.getRecipes()); //not needed when methods are static - lastSearchResults = RecipeSearch.searchByQuery(recipeManager.getRecipes(), query); + RecipeSearch recipeSearch = new RecipeSearch(recipeManager, ingredientManager); + lastSearchResults = recipeSearch.searchByQuery(recipeManager.getRecipes(), query); //TODO: pass last search results System.out.println("Results: "); lastSearchResults.forEach(result -> System.out.println(" " + result.getName())); } diff --git a/src/main/java/mi/hdm/controllers/MainPageController.java b/src/main/java/mi/hdm/controllers/MainPageController.java index 4cdba9b21b14f44ff0ccd0b6be8e9d0dc17da0ee..55581fb42c7b9003db87eaea8c8ffbd32abe7db4 100644 --- a/src/main/java/mi/hdm/controllers/MainPageController.java +++ b/src/main/java/mi/hdm/controllers/MainPageController.java @@ -18,6 +18,7 @@ public class MainPageController extends BaseController { private final static Logger log = LogManager.getLogger(MainPageController.class); private final RecipeManager recipeManager; + private final IngredientManager ingredientManager; private final CategoryManager categoryManager; private final List<CategoryCheckBox> categoryCheckboxesInGui = new ArrayList<>(); @@ -30,8 +31,9 @@ public class MainPageController extends BaseController { @FXML private HBox categories; - public MainPageController(RecipeManager recipeManager, CategoryManager categoryManager) { + public MainPageController(RecipeManager recipeManager, IngredientManager ingredientManager, CategoryManager categoryManager) { this.recipeManager = recipeManager; + this.ingredientManager = ingredientManager; this.categoryManager = categoryManager; } @@ -53,7 +55,7 @@ public class MainPageController extends BaseController { currentRecipesInGUI.clear(); //remove all recipes... for (Recipe recipe : recipes) { - RecipeVbox recipeVbox = new RecipeVbox(recipe); + RecipeVbox recipeVbox = new RecipeVbox(recipe, categoryManager); recipeVbox.setOnMouseClicked(mouseEvent -> System.out.format("User selected '%s'.%n", recipe.getName())); //TODO: this needs to display the recipe view currentRecipesInGUI.add(recipeVbox); } @@ -80,7 +82,7 @@ public class MainPageController extends BaseController { .filter(CheckBox::isSelected) .map(CategoryCheckBox::getAssociatedCategory) .toList(); - //RecipeSearch recipeSearch = new RecipeSearch(recipeManager.getRecipes()); //not needed when methods are static - return RecipeSearch.searchByCategory(recipeManager.getRecipes(), currentlySelectedCategories); + RecipeSearch recipeSearch = new RecipeSearch(recipeManager, ingredientManager); + return recipeSearch.searchByCategory(recipeManager.getRecipes(), currentlySelectedCategories); } } diff --git a/src/main/java/mi/hdm/controllers/View.java b/src/main/java/mi/hdm/controllers/View.java index 471e0364954c27de795a66c372dd3aff7594d2da..22fecfa10f14383daf16fcaf895bb16ee3df46cd 100644 --- a/src/main/java/mi/hdm/controllers/View.java +++ b/src/main/java/mi/hdm/controllers/View.java @@ -2,11 +2,8 @@ package mi.hdm.controllers; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; +import mi.hdm.GuiController; import mi.hdm.TastyPages; -import mi.hdm.mealPlan.MealPlan; -import mi.hdm.recipes.CategoryManager; -import mi.hdm.recipes.RecipeManager; -import mi.hdm.shoppingList.ShoppingList; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -20,10 +17,7 @@ public enum View { private static final Logger log = LogManager.getLogger(View.class); - private final RecipeManager recipeManager = TastyPages.recipeManager; - private final ShoppingList shoppingList = TastyPages.shoppingList; - private final MealPlan mealPlan = TastyPages.mealPlan; - private final CategoryManager categoryManager = TastyPages.categoryManager; + private final static TastyPages model = GuiController.getModel(); private final String path; private final String windowTitle; @@ -41,19 +35,19 @@ public enum View { //set the correct controller factory for views (necessary to enable dependency injection) switch (this) { case MAIN -> loader.setControllerFactory((callback) -> - new MainPageController(recipeManager, categoryManager) + new MainPageController(model.getRecipeManager(), model.getIngredientManager(), model.getCategoryManager()) ); case RECIPE_VIEW -> loader.setControllerFactory((callback) -> - new RecipeViewController(recipeManager) + new RecipeViewController(model.getRecipeManager()) ); case SHOPPINGLIST_VIEW -> loader.setControllerFactory((callback) -> - new ShoppingListViewController(recipeManager, shoppingList) + new ShoppingListViewController(model.getRecipeManager(), model.getShoppingList()) ); case MEALPLAN_VIEW -> loader.setControllerFactory((callback) -> - new MealPlanController(mealPlan) + new MealPlanController(model.getMealPlan()) ); } log.debug("Set controller factory for {}", windowTitle); @@ -64,4 +58,8 @@ public enum View { public String getWindowTitle() { return windowTitle; } + + public String getPath() { + return path; + } } diff --git a/src/main/java/mi/hdm/controllers/ViewComponent.java b/src/main/java/mi/hdm/controllers/ViewComponent.java index 940c70e7606d4391585fb6e17df2e5fad41c518b..c5391cf45cdd1a2f01a43f4af973152d9b19abeb 100644 --- a/src/main/java/mi/hdm/controllers/ViewComponent.java +++ b/src/main/java/mi/hdm/controllers/ViewComponent.java @@ -2,8 +2,8 @@ package mi.hdm.controllers; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; +import mi.hdm.GuiController; import mi.hdm.TastyPages; -import mi.hdm.recipes.RecipeManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -16,8 +16,7 @@ public enum ViewComponent { private final String path; - //dependencies - private final RecipeManager recipeManager = TastyPages.recipeManager; + private static final TastyPages app = GuiController.getModel(); ViewComponent(String path) { this.path = path; @@ -30,7 +29,7 @@ public enum ViewComponent { switch (this) { case HEADER -> loader.setControllerFactory((callback) -> - new HeaderController(recipeManager) + new HeaderController(app.getRecipeManager(), app.getIngredientManager()) ); } diff --git a/src/main/java/mi/hdm/filesystem/CSVParser.java b/src/main/java/mi/hdm/filesystem/CSVParser.java index c3c9c65a8bf469f3c064c777d531091839da159a..8888ec8c6c5067a1e648e4c097e5ccc9fd8e3eea 100644 --- a/src/main/java/mi/hdm/filesystem/CSVParser.java +++ b/src/main/java/mi/hdm/filesystem/CSVParser.java @@ -28,6 +28,11 @@ public class CSVParser { public List<Ingredient> getIngredientsFromCSV(String filepath, char split, String... extract) throws IOException { log.info("Trying to read ingredients from CSV: {}", filepath); + if (extract.length == 0) { + log.fatal("Arguments for parser are incorrect."); + throw new InvalidPropertiesFormatException("Arguments to extract are length 0"); + } + //try-block with automatic resource management try (BufferedReader reader = new BufferedReader(new FileReader(filepath))) { final String header = reader.readLine(); //read first line of CSV @@ -46,11 +51,11 @@ public class CSVParser { } catch (FileNotFoundException fileNotFoundException) { final Path filename = Path.of(filepath).getFileName(); - log.error("File '{}' not found at specified filepath: {}.", filename, filepath); + 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); + log.error("An error occurred while reading CSV: '{}'", filepath); throw io; } } @@ -65,7 +70,6 @@ public class CSVParser { final List<BigDecimal> nutrition = new ArrayList<>(); for (int i = 1; i < idx.length; i++) { - //String element = splitLine.get(idx[i]).split(" ")[0]; String element = splitLine.get(idx[i]); double quantity = parseNumberFromString(element); if (getMeasurementFromString(element).equals("mg")) { @@ -126,8 +130,6 @@ public class CSVParser { } else if (!hasDot && numValue.length() > 0 && c == '.') { hasDot = true; numValue.append(c); - } else { - break; } } return Double.parseDouble(numValue.toString()); diff --git a/src/main/java/mi/hdm/filesystem/FileManager.java b/src/main/java/mi/hdm/filesystem/FileManager.java index 414b37fb5304873780cfb98988386c8c13e08be1..1ffffe9ad3508907f3b37aa49d3d27df2831694e 100644 --- a/src/main/java/mi/hdm/filesystem/FileManager.java +++ b/src/main/java/mi/hdm/filesystem/FileManager.java @@ -1,24 +1,312 @@ package mi.hdm.filesystem; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import mi.hdm.TastyPages; +import mi.hdm.mealPlan.MealPlan; +import mi.hdm.recipes.*; +import mi.hdm.shoppingList.ShoppingList; +import mi.hdm.typeAdapters.CategoryManagerTypeAdapter; +import mi.hdm.typeAdapters.MealPlanTypeAdapter; +import mi.hdm.typeAdapters.RecipeTypeAdapter; +import mi.hdm.typeAdapters.ShoppingListTypeAdapter; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.math.BigDecimal; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +/** + * Containing methods to serialize and deserialize parts of the app content for saving to / loading from the filesystem + */ public class FileManager { - private static final FileManager fileManager = new FileManager(); + private final static String PATH_TO_DEFAULT_INGREDIENTS = "/data/nutrition.csv"; + private final static String PATH_TO_USER_DATA = System.getProperty("user.home") + "\\AppData\\Roaming\\TastyPages"; + + private final static Logger log = LogManager.getLogger(FileManager.class); + + public static void serializeToFile(TastyPages app) { + log.info("Writing user data to '{}'", PATH_TO_USER_DATA); + //Serialize ingredient manager + serializeIngredientManager(app.getIngredientManager(), Path.of(PATH_TO_USER_DATA, "ingredients.csv")); + serializeRecipeManager(app.getRecipeManager(), Path.of(PATH_TO_USER_DATA, "recipes")); + serializeCategoryManager(app.getCategoryManager(), Path.of(PATH_TO_USER_DATA, "categories.json")); + serializeMealPlan(app.getMealPlan(), Path.of(PATH_TO_USER_DATA, "mealplan.json")); + serializeShoppingList(app.getShoppingList(), app.getRecipeManager(), Path.of(PATH_TO_USER_DATA, "shoppingList.json")); + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + public static TastyPages deserializeFromFilesystem() throws IOException, URISyntaxException { + log.info("Trying to read {}", PATH_TO_USER_DATA); + Path userPath = Path.of(PATH_TO_USER_DATA); + if (userPath.toFile().mkdirs()) { //If .mkdirs() returns true, that means the folder has not existed before -> in this case, only load default ingredients! + log.info("TastyPages folder has been created at {}", userPath); + IngredientManager deserializedIngredientManager = deserializeIngredientManager(getAbsolutePathFromResourceFolder()); + return new TastyPages(deserializedIngredientManager); + } else { //otherwise, read user data + log.info("Found TastyPages folder at {}, loading user data from there.", userPath); + IngredientManager ingredientManager; + RecipeManager recipeManager; + CategoryManager categoryManager; + MealPlan mealPlan; + ShoppingList shoppingList; + + //Deserialize ingredient manager + { + Path toIngredients = Path.of(userPath + "/ingredients.csv"); + if (Files.exists(toIngredients)) { + log.info("Loading ingredients from '{}'", toIngredients); + ingredientManager = deserializeIngredientManager(toIngredients.toString()); + } else { + log.info("Could not find /ingredients.csv, falling back to default ingredients."); + ingredientManager = deserializeIngredientManager(getAbsolutePathFromResourceFolder()); + } + } + //Deserialize recipe manager + { + Path recipeFolder = Path.of(userPath + "/recipes"); + if (Files.exists(recipeFolder) && Files.isDirectory(recipeFolder)) { + log.info("Loading recipes from '{}'", recipeFolder); + recipeManager = deserializeRecipeManager(recipeFolder); + } else { + log.info("Could not find recipe folder, creating empty instance of RecipeManger"); + recipeFolder.toFile().mkdir(); + recipeManager = new RecipeManager(); + } + } + //Deserialize category manager + { + Path toCategoryJson = Path.of(userPath + "/categories.json"); + if (Files.exists(toCategoryJson) && Files.isRegularFile(toCategoryJson)) { + log.info("Loading categories from '{}'", toCategoryJson); + String fileContent = Files.readString(toCategoryJson); + categoryManager = deserializeCategoryManager(fileContent); + } else { + log.info("Could not find /categories.json, creating empty instance of CategoryManager"); + toCategoryJson.toFile().createNewFile(); + categoryManager = new CategoryManager(); + } + } + //Deserialize meal plan + { + Path toMealPlanJson = Path.of(userPath + "/mealplan.json"); + if (Files.exists(toMealPlanJson) && Files.isRegularFile(toMealPlanJson)) { + log.info("Loading meal plan from '{}'", toMealPlanJson); + String fileContent = Files.readString(toMealPlanJson); + mealPlan = deserializeMealPlan(fileContent, recipeManager); + } else { + log.info("Could not find /mealplan.json, creating empty instance of MealPlan"); + toMealPlanJson.toFile().createNewFile(); + mealPlan = new MealPlan(recipeManager); + } + } + //Deserialize shopping list + { + Path toShoppingListJson = Path.of(userPath + "/shoppingList.json"); + if (Files.exists(toShoppingListJson) && Files.isRegularFile(toShoppingListJson)) { + log.info("Loading shopping list from '{}'", toShoppingListJson); + String fileContent = Files.readString(toShoppingListJson); + shoppingList = deserializeShoppingList(fileContent, recipeManager); + } else { + log.info("Could not find /shoppingList.json, creating empty shopping list"); + toShoppingListJson.toFile().createNewFile(); + shoppingList = new ShoppingList(recipeManager); + } + } + return new TastyPages(recipeManager, ingredientManager, categoryManager, mealPlan, shoppingList); + } + } + + private static IngredientManager deserializeIngredientManager(String path) throws IOException { + CSVParser parser = new CSVParser(); + List<Ingredient> ingredients = parser.getIngredientsFromCSV( + path, + ',', + "name", "calories", "carbohydrate", "fat", "protein", "fiber", "sodium" + ); + return new IngredientManager(ingredients); + } + + private static void serializeIngredientManager(IngredientManager ingredientManager, Path path) { + log.info("Writing ingredients to '{}'", path.getFileName()); + try (BufferedWriter writer = new BufferedWriter(new FileWriter(path.toFile()))) { + writer.write("name,calories,carbohydrate,fat,protein,fiber,sodium\n"); + final List<Ingredient> ingredients = ingredientManager.getAllIngredients().values().stream().toList(); + for (int i = 0; i < ingredients.size(); i++) { + if (i % 200 == 0) + writer.flush(); //flush the writer regularly + writer.write(ingredientToCSV(ingredients.get(i))); + } + } catch (IOException e) { + log.error("Exception when writing ingredients to file!"); + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + private static RecipeManager deserializeRecipeManager(Path path) throws IOException { + List<Path> recipePaths; + try (Stream<Path> stream = Files.list(path)) { + recipePaths = stream.toList(); + } + + List<Recipe> recipes = recipePaths + .stream() + .map(p -> { + try { + return Files.readString(p); + } catch (IOException e) { + e.printStackTrace(); + log.error("Could not read file contents for {}. Check file permissions and content", p); + return null; + } + }) + .filter(Objects::nonNull) + .map(FileManager::JSONtoRecipe) + .toList(); + return new RecipeManager(recipes); + } + + private static void serializeRecipeManager(RecipeManager recipeManager, Path path) { + log.info("Writing all recipes to /recipes"); + recipeManager.getRecipes() + .forEach(recipe -> { + try { + String recipePath = path + "\\" + recipe.getName() + ".json"; + Files.writeString(Path.of(recipePath), recipeToJSON(recipe)); + log.debug("Wrote recipe '{}' to '{}'", recipe.getName(), recipePath); + } catch (IOException e) { //TODO: is it smart to just swallow this exception? + log.error("Error writing recipe '{}' to file. Recipe will be lost.", recipe.getName()); + e.printStackTrace(); + } + } + ); + } + + private static CategoryManager deserializeCategoryManager(String json) { + if (json.isBlank()) { + log.info("JSON file is blank, creating empty instance of Category Manager"); + return new CategoryManager(); + } + Gson gson = new GsonBuilder().registerTypeAdapter(CategoryManager.class, new CategoryManagerTypeAdapter()).create(); + return gson.fromJson(json, CategoryManager.class); + } + + private static void serializeCategoryManager(CategoryManager categoryManager, Path path) { + log.info("Writing categories to categories.json"); + Gson gson = new GsonBuilder() + .registerTypeAdapter(CategoryManager.class, new CategoryManagerTypeAdapter()) + .create(); + String json = gson.toJson(categoryManager); + try { + Files.writeString(path, json); + } catch (IOException io) { + log.error("Error writing categories to file."); + io.printStackTrace(); + throw new RuntimeException(); + } + } + + private static MealPlan deserializeMealPlan(String json, RecipeManager recipeManager) { + if (json.isBlank()) { + log.info("JSON file is blank, creating empty instance of Meal Plan"); + return new MealPlan(recipeManager); + } + Gson gson = new GsonBuilder() + .registerTypeAdapter(MealPlan.class, new MealPlanTypeAdapter(recipeManager)) + .create(); + return gson.fromJson(json, MealPlan.class); + } + + private static void serializeMealPlan(MealPlan mealPlan, Path path) { + Gson gson = new GsonBuilder(). + registerTypeAdapter(MealPlan.class, new MealPlanTypeAdapter(null)) + .create(); + String json = gson.toJson(mealPlan); + try { + Files.writeString(path, json); + } catch (IOException io) { + log.error("Error writing meal plan to file."); + io.printStackTrace(); + throw new RuntimeException(); + } + } + + private static ShoppingList deserializeShoppingList(String json, RecipeManager recipeManager) { + if (json.isBlank()) { + log.info("JSON file is blank, creating empty instance of Shopping List"); + return new ShoppingList(recipeManager); + } + Gson gson = new GsonBuilder() + .registerTypeAdapter(ShoppingList.class, new ShoppingListTypeAdapter(recipeManager)) + .create(); + return gson.fromJson(json, ShoppingList.class); + } - public static FileManager getInstance() { - return fileManager; + private static void serializeShoppingList(ShoppingList shoppingList, RecipeManager recipeManager, Path path) { + Gson gson = new GsonBuilder() + .registerTypeAdapter(ShoppingList.class, new ShoppingListTypeAdapter(recipeManager)) + .create(); + String json = gson.toJson(shoppingList); + try { + Files.writeString(path, json); + } catch (IOException io) { + log.error("Error writing shopping list to file."); + io.printStackTrace(); + throw new RuntimeException(); + } } - public void serializeToFile(TastyPages app) { + private static String recipeToJSON(Recipe recipe) { + Gson gson = new GsonBuilder() + .registerTypeAdapter(Recipe.class, new RecipeTypeAdapter()) + .create(); + return gson.toJson(recipe); } - public TastyPages deserializeFromFile(String filepath) { - return null; + /** + * Used to convert an ingredient into a CSV String seperated by comma. Floating point numbers + * will use dots as decimal points + * + * @return a comma seperated String containing the name of the ingredient as well as the contents of its nutrition table + */ + private static String ingredientToCSV(Ingredient ingredient) { + //"name", "calories", "carbohydrate", "fat", "protein", "fiber", "sodium" + Map<Nutrition, BigDecimal> map = ingredient.getNutritionTable().getTable(); + return String.format( + Locale.ENGLISH, + "\"%s\", %f, %f, %f, %f, %f, %f%n", + ingredient.getName(), + map.get(Nutrition.CALORIES).doubleValue(), + map.get(Nutrition.CARBS).doubleValue(), + map.get(Nutrition.FAT).doubleValue(), + map.get(Nutrition.PROTEINS).doubleValue(), + map.get(Nutrition.FIBERS).doubleValue(), + map.get(Nutrition.SALT).doubleValue() + ); } - private void recipeToJSON() { + private static Recipe JSONtoRecipe(String json) { + Gson gson = new GsonBuilder().registerTypeAdapter(Recipe.class, new RecipeTypeAdapter()).create(); + return gson.fromJson(json, Recipe.class); } - private void ingredientToCSV() { + private static String getAbsolutePathFromResourceFolder() throws URISyntaxException { + URL resourceUrl = FileManager.class.getResource(FileManager.PATH_TO_DEFAULT_INGREDIENTS); + assert resourceUrl != null; + Path path = Paths.get(resourceUrl.toURI()); + return path.toFile().getAbsolutePath(); } } diff --git a/src/main/java/mi/hdm/mealPlan/MealPlan.java b/src/main/java/mi/hdm/mealPlan/MealPlan.java index ef6f68712568be1b04d1ad27315960966d55707e..a3270489dfec5387cb24df178f1e1915ac1ce4d4 100644 --- a/src/main/java/mi/hdm/mealPlan/MealPlan.java +++ b/src/main/java/mi/hdm/mealPlan/MealPlan.java @@ -4,6 +4,7 @@ import mi.hdm.exceptions.InvalidMealPlanException; import mi.hdm.recipes.NutritionCalculator; import mi.hdm.recipes.NutritionTable; import mi.hdm.recipes.Recipe; +import mi.hdm.recipes.RecipeManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -14,16 +15,29 @@ import java.util.*; * Allow the user to create a meal plan for the week. The user can check out his nutrition scores for the week. */ public class MealPlan { + private final Map<LocalDate, String> plan; + + private final RecipeManager recipeManager; + private static final Logger log = LogManager.getLogger(MealPlan.class); - private final Map<LocalDate, Recipe> plan; - public MealPlan(Map<LocalDate, Recipe> plan) { + public MealPlan(Map<LocalDate, String> plan, RecipeManager recipeManager) { + //TODO: check dates of the map, dates that have already passed or that are too far in the future should be removed before adding them + this.recipeManager = recipeManager; + assert plan != null; if (!plan.isEmpty()) { - if (plan.keySet().stream().anyMatch(Objects::isNull)) + if (plan.keySet().stream().anyMatch(Objects::isNull)) { throw new InvalidMealPlanException("The specified map is invalid (null key)"); + } } + this.plan = new HashMap<>(plan); + log.info("Created MealPlan with {}/7 days being filled.", this.plan.size()); + } - this.plan = plan; + public MealPlan(RecipeManager recipeManager) { + this.recipeManager = recipeManager; + plan = new HashMap<>(); + log.info("Created empty instance of MealPlan"); } public void addRecipeToMealPlan(Recipe recipe, LocalDate date) { @@ -33,7 +47,19 @@ public class MealPlan { if (!isDateValid(date)) { throw new InvalidMealPlanException("You can only add recipes between today and in 7 days from now!"); } - plan.put(date, recipe); + log.info("Added recipe {} to meal plan.", recipe.getName()); + plan.put(date, recipe.getUniqueCode()); + } + + public void addRecipeToMealPlan(String recipeCode, LocalDate date) { + if (recipeCode == null || recipeManager.getRecipeByCode(recipeCode) == null) { + throw new NullPointerException("Your specified recipe code was null or invalid"); + } + if (!isDateValid(date)) { + throw new InvalidMealPlanException("You can only add recipes between today and in 7 days from now!"); + } + log.info("Added recipe {} to meal plan.", recipeManager.getRecipeByCode(recipeCode).getName()); + plan.put(date, recipeCode); } public void clear(LocalDate date) { @@ -54,11 +80,19 @@ public class MealPlan { plan.clear(); } - public Optional<Recipe> getRecipeForDay(LocalDate date) { + public Optional<String> getRecipeCodeForDay(LocalDate date) { return Optional.ofNullable(plan.get(date)); } public Map<LocalDate, Recipe> getAllRecipesFromPlan() { + Map<LocalDate, Recipe> result = new HashMap<>(); + for (LocalDate d : plan.keySet()) { + result.put(d, recipeManager.getRecipeByCode(plan.get(d))); + } + return result; + } + + public Map<LocalDate, String> getAllRecipeCodesFromPlan() { return plan; } @@ -80,4 +114,13 @@ public class MealPlan { LocalDate invalid = LocalDate.now().plusDays(7); return !(date.isBefore(LocalDate.now()) || invalid.isBefore(date)); } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (o instanceof MealPlan m) { + return m.getAllRecipeCodesFromPlan().equals(this.plan); + } + return false; + } } diff --git a/src/main/java/mi/hdm/recipes/Category.java b/src/main/java/mi/hdm/recipes/Category.java index e654671eec55e2c1ca6c1aa5046ff0d0e12ebfb9..f5a7fd3bb5d4dfbdb3ae36086850dceca52aed7a 100644 --- a/src/main/java/mi/hdm/recipes/Category.java +++ b/src/main/java/mi/hdm/recipes/Category.java @@ -2,23 +2,30 @@ package mi.hdm.recipes; import mi.hdm.exceptions.InvalidCategoryException; -public class Category { +import java.util.Objects; +public class Category { + private final int code; private final String name; private final String colourCode; //0x für Hexzahlen - - public Category (String name, int colourCode) { - if(name == null || name.equals("")) { + public Category(String name, int colourCode) { + if (name == null || name.equals("")) { throw new InvalidCategoryException("Can not add category with name " + name); } - if(colourCode < 0 || colourCode > 0xFFFFFF) { + if (colourCode < 0 || colourCode > 0xFFFFFF) { throw new InvalidCategoryException("Can not add category with color code " + colourCode); } this.name = name; this.colourCode = CategoryManager.numberToColorCodeString(colourCode); + this.code = Objects.hash(name, colourCode); } + public Category(String name, String colourCode, int code) { + this.name = name; + this.colourCode = colourCode; + this.code = code; + } public String getColourCode() { return colourCode; @@ -41,4 +48,7 @@ public class Category { return String.format("Category %s with color code %s", name, colourCode); } + public int getCode() { + return code; + } } diff --git a/src/main/java/mi/hdm/recipes/CategoryManager.java b/src/main/java/mi/hdm/recipes/CategoryManager.java index 63fa28b604591ecd9241d0577ce6376ede848a46..addeb2266f4623e5d777ef967d07432b8dd09c2e 100644 --- a/src/main/java/mi/hdm/recipes/CategoryManager.java +++ b/src/main/java/mi/hdm/recipes/CategoryManager.java @@ -4,21 +4,30 @@ import mi.hdm.exceptions.InvalidCategoryException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; public class CategoryManager { private static final Logger log = LogManager.getLogger(CategoryManager.class); - private final List<Category> allCategories; + private final Map<Integer, Category> allCategories; public CategoryManager() { - allCategories = new ArrayList<>(); + allCategories = new HashMap<>(); + log.info("Created empty instance of CategoryManager"); + } + + @Deprecated + public CategoryManager(Map<Integer, Category> categories) { + allCategories = new HashMap<>(categories); + log.info("Created instance of CategoryManager with {} categories.", categories.size()); } public CategoryManager(List<Category> categories) { - allCategories = categories; + allCategories = new HashMap<>(); + categories.forEach(c -> allCategories.put(c.getCode(), c)); } /** @@ -30,20 +39,24 @@ public class CategoryManager { */ public void addCategory(String name, int colourCode) { Category c = new Category(name, colourCode); - if (getCategoryByName(name).isPresent() || getCategoryByCode(colourCode).isPresent()) { - log.error("Category {} not added because it already exists", c.getName()); + addCategory(c); + } + + public void addCategory(Category c) { + if (allCategories.get(c.getCode()) != null || getCategoryByName(c.getName()).isPresent() || getCategoryByColourCode(c.getColourCode()).isPresent()) { + log.error("Category '{}' not added because it already exists", c.getName()); throw new InvalidCategoryException("Category already exists."); } else { - allCategories.add(c); - log.info("Category {} added successfully.", c.getName()); + allCategories.put(c.getCode(), c); + log.info("Category '{}' added successfully.", c.getName()); } } public void deleteCategory(Category c) { - log.info("Category {} deleted successfully.", c.getName()); - if (!allCategories.remove(c)) { + if (!allCategories.values().remove(c)) { throw new InvalidCategoryException("Category is not listed."); } + log.info("Category '{}' deleted successfully.", c.getName()); } public void deleteCategory(String name) { @@ -52,29 +65,54 @@ public class CategoryManager { } public List<Category> getAllCategories() { - return allCategories; + return allCategories.values().stream().toList(); } public void clearCategories() { allCategories.clear(); - log.info("List allCategories cleared successfully."); + log.info("List of categories cleared successfully."); } private Optional<Category> getCategoryByName(String name) { - return allCategories.stream() + return allCategories.values().stream() .filter(c -> c.getName().equals(name)) .findFirst(); } - private Optional<Category> getCategoryByCode(int colourCode) { - return allCategories.stream() - .filter(c -> c.getColourCode().equals(numberToColorCodeString(colourCode))) + private Optional<Category> getCategoryByColourCode(String colourCode) { + return allCategories.values().stream() + .filter(c -> c.getColourCode().equals(colourCode)) .findFirst(); } + public Optional<Category> get(Integer key) { + return Optional.ofNullable(allCategories.get(key)); + } + + public List<Category> getCategoriesFromKeys(List<Integer> keys) { + return keys + .stream() + .map(key -> { + Category c = allCategories.get(key); + if (c == null) + throw new InvalidCategoryException("Invalid category: no category exists with key " + key); + return c; + }) + .toList(); + } + public static String numberToColorCodeString(int colourCode) { String intToHex = Integer.toHexString(colourCode); intToHex = intToHex.indent(6 - intToHex.length()).replace(" ", "0"); - return String.format("#%s", intToHex); + return ("#" + intToHex).strip(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof CategoryManager c) { + return c.getAllCategories().equals(this.getAllCategories()); + } + return false; } } diff --git a/src/main/java/mi/hdm/recipes/Ingredient.java b/src/main/java/mi/hdm/recipes/Ingredient.java index d07cc2eacca41f3487aac7a98534acf6a6e36553..3b1bc194fc62712602aae1b22498091590948d65 100644 --- a/src/main/java/mi/hdm/recipes/Ingredient.java +++ b/src/main/java/mi/hdm/recipes/Ingredient.java @@ -2,7 +2,10 @@ package mi.hdm.recipes; import mi.hdm.exceptions.InvalidIngredientException; +import java.util.Objects; + public class Ingredient implements RecipeComponent { + private final String code; private final Measurement unit; private final String name; private final NutritionTable nutritionTable; @@ -18,12 +21,15 @@ public class Ingredient implements RecipeComponent { this.unit = unit; this.name = name; this.nutritionTable = nutritionTable; + this.code = calculateUniqueCode(); } + @Override public String getName() { return name; } + @Override public NutritionTable getNutritionTable() { return nutritionTable; } @@ -33,6 +39,16 @@ public class Ingredient implements RecipeComponent { return unit; } + @Override + public String getUniqueCode() { + return code; + } + + private String calculateUniqueCode() { + int hash = Objects.hash(name, nutritionTable.getTable()); + return "i" + hash; + } + @Override public boolean equals(Object o) { if (o instanceof Ingredient in) { diff --git a/src/main/java/mi/hdm/recipes/IngredientManager.java b/src/main/java/mi/hdm/recipes/IngredientManager.java index 909e84a234fec836f9a36c35ad7043502b88f6ea..7d5fe5d8696999026ed853004d049e14de3c47a1 100644 --- a/src/main/java/mi/hdm/recipes/IngredientManager.java +++ b/src/main/java/mi/hdm/recipes/IngredientManager.java @@ -4,22 +4,32 @@ import mi.hdm.exceptions.InvalidIngredientException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; + public class IngredientManager { private static final Logger log = LogManager.getLogger(IngredientManager.class); - private final List<Ingredient> allIngredients; + private final Map<String, Ingredient> allIngredients; //Map key of ingredient to ingredient object public IngredientManager() { - allIngredients = new ArrayList<>(); + allIngredients = new HashMap<>(); } public IngredientManager(List<Ingredient> ingredients) { - allIngredients = ingredients; + allIngredients = new HashMap<>(); + ingredients.forEach(i -> allIngredients.put(i.getUniqueCode(), i)); + log.info("Created ingredient manager with {} ingredients.", allIngredients.keySet().size()); + } + + public IngredientManager(Map<String, Ingredient> ingredients) { + allIngredients = new HashMap<>(ingredients); + log.info("Created ingredient manager with {} ingredients.", allIngredients.keySet().size()); } + /** * Adds an ingredient if there is no equal ingredient (no ingredient with the same name). * @@ -28,11 +38,11 @@ public class IngredientManager { */ public void addIngredient(Ingredient in) { if (getIngredientByName(in.getName()).isPresent()) { - log.error("Ingredient {} not added because it already exists.", in.getName()); + log.error("Ingredient '{}' not added because it already exists.", in.getName()); throw new InvalidIngredientException("Ingredient already exists."); } else { - log.info("Ingredient {} added successfully.", in.getName()); - allIngredients.add(in); + log.info("Ingredient '{}' added successfully.", in.getName()); + allIngredients.put(in.getUniqueCode(), in); } } @@ -41,28 +51,35 @@ public class IngredientManager { addIngredient(in); } - public Ingredient deleteIngredient(int i) { - log.info("Ingredient {} deleted successfully.", allIngredients.get(i).getName()); - return allIngredients.remove(i); - } - public void deleteIngredient(String name) { Ingredient ingredient = getIngredientByName(name).orElseThrow(() -> new InvalidIngredientException("No ingredient with name " + name)); - log.info("Ingredient {} deleted successfully.", name); - allIngredients.remove(ingredient); + log.info("Ingredient '{}' deleted successfully.", name); + allIngredients.values().remove(ingredient); } public void clearIngredients() { - log.info("List allIngredients cleared successfully."); + log.info("List of ingredients cleared successfully."); allIngredients.clear(); } - public List<Ingredient> getAllIngredients() { + public Optional<Ingredient> getIngredient(String code) { + return Optional.ofNullable(allIngredients.get(code)); + } + + public Map<String, Ingredient> getAllIngredients() { return allIngredients; } + public Map<Ingredient, Integer> getIngredientsFromKeys(Map<String, Integer> m) { + Map<Ingredient, Integer> result = new HashMap<>(); + for (String key : m.keySet()) { + result.put(allIngredients.get(key), m.get(key)); + } + return result; + } + private Optional<Ingredient> getIngredientByName(String name) { - for (final Ingredient i : allIngredients) { + for (final Ingredient i : allIngredients.values()) { if (name.equals(i.getName())) { return Optional.of(i); } diff --git a/src/main/java/mi/hdm/recipes/NutritionCalculator.java b/src/main/java/mi/hdm/recipes/NutritionCalculator.java index 7e7a396dbb1ff319d36b00d7b90aba8ff306bb6f..8c2de0a5e6a51ef362e54fe537c76580fe5607c3 100644 --- a/src/main/java/mi/hdm/recipes/NutritionCalculator.java +++ b/src/main/java/mi/hdm/recipes/NutritionCalculator.java @@ -9,7 +9,6 @@ import java.time.LocalDate; import java.util.Map; public class NutritionCalculator { - private static final Logger log = LogManager.getLogger(NutritionCalculator.class); /** @@ -17,7 +16,7 @@ public class NutritionCalculator { * * @return the nutrition table for this recipe based on its ingredients. All nutrition values are added up. */ - public static NutritionTable calculateNutritionTable(Map<RecipeComponent, Integer> ingredients) { + public static NutritionTable calculateNutritionTable(Map<RecipeComponent, Integer> ingredients) { //TODO: this needs to accept a map of keys & ing. manager + recipe manager if (ingredients == null || ingredients.isEmpty()) { log.info("Map ingredients is null or empty. Therefore new nutritionTable() was generated."); return NutritionTable.empty(); @@ -31,7 +30,6 @@ public class NutritionCalculator { totalSalt = BigDecimal.ZERO; for (RecipeComponent entry : ingredients.keySet()) { - final Map<Nutrition, BigDecimal> nutritionTable = entry.getNutritionTable().getTable(); int divisor; @@ -53,7 +51,6 @@ public class NutritionCalculator { public static NutritionTable calculateNutritionTable(MealPlan mealPlan) { - Map<LocalDate, Recipe> plan = mealPlan.getAllRecipesFromPlan(); double totalCals = 0, @@ -63,7 +60,6 @@ public class NutritionCalculator { totalFibers = 0, totalSalt = 0; - //we calculate with double -> still rounding errors! that's why testCalculateNutritionTableFromIngredients failes for (Recipe r : plan.values()) { final Map<Nutrition, BigDecimal> nutritionTable = r.getNutritionTable().getTable(); totalCals += nutritionTable.get(Nutrition.CALORIES).doubleValue(); diff --git a/src/main/java/mi/hdm/recipes/NutritionTable.java b/src/main/java/mi/hdm/recipes/NutritionTable.java index 226f32ee7622d0abe2d7ae7cfce5dc0b19cbed4b..0ef2320c694e17d164aad31fbf4eb7a055cf39fa 100644 --- a/src/main/java/mi/hdm/recipes/NutritionTable.java +++ b/src/main/java/mi/hdm/recipes/NutritionTable.java @@ -82,9 +82,15 @@ public class NutritionTable { @Override public String toString() { - return "NutritionTable{" + - "table=" + table + - '}'; + String nutritionTable = "Nutrition Table"; + + final StringBuilder stringBuilder = new StringBuilder(nutritionTable + "\n"); + stringBuilder.append("-".repeat(23)).append("\n"); + table.keySet() + .forEach(key -> stringBuilder.append(String.format("| %10s : %06.2f |%n", key, table.get(key).doubleValue()))); + stringBuilder.append("-".repeat(23)); + + return stringBuilder.toString(); } public static NutritionTable empty() { diff --git a/src/main/java/mi/hdm/recipes/Recipe.java b/src/main/java/mi/hdm/recipes/Recipe.java index bfd7207a6fa1e20cd16c5e07cb35a0e71084c4b7..d5c9e824a60a601191ef6bb8b1d5c3827ec71827 100644 --- a/src/main/java/mi/hdm/recipes/Recipe.java +++ b/src/main/java/mi/hdm/recipes/Recipe.java @@ -5,19 +5,18 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; +import java.util.stream.Collectors; public class Recipe implements RecipeComponent { private static final Logger log = LogManager.getLogger(Recipe.class); - private Map<RecipeComponent, Integer> ingredients; + private final String code; private String name; + private Map<String, Integer> ingredients; private String description; private List<String> preparation; - private List<Category> categories; + private List<Integer> categories; private int preparationTimeMins; private NutritionTable nutritionTable; private final LocalDateTime creationTime; @@ -66,15 +65,39 @@ public class Recipe implements RecipeComponent { int preparationTimeMins, NutritionTable nutritionTable) { - setIngredients(ingredients); + setIngredientFromRecipeComponents(ingredients); setDescription(description); setPreparation(preparation); setNutritionTable(nutritionTable); setName(name); setPreparationTimeMins(preparationTimeMins); - setCategories(categories); + setCategoriesFromObjects(categories); this.creationTime = LocalDateTime.now(); +<<<<<<< src/main/java/mi/hdm/recipes/Recipe.java + this.code = calculateUniqueCode(); + } + + public Recipe( + String name, + Map<String, Integer> ingredients, + String description, + List<String> preparation, + List<Integer> categories, + Integer preparationTimeMins, + NutritionTable nutritionTable, + LocalDateTime creationTime, + String code) { + + this.ingredients = ingredients; + setDescription(description); + setPreparation(preparation); + setNutritionTable(nutritionTable); + setName(name); + setPreparationTimeMins(preparationTimeMins); + setCategories(categories); + this.creationTime = creationTime; + this.code = code; filepathForImage = "C:\\Users\\Lara Blersch\\Documents\\Studium\\SE2\\tasty-pages\\src\\main\\resources\\images\\dish-fork-and-knife.png"; //path to default image which can be changed by the user later } @@ -111,21 +134,21 @@ public class Recipe implements RecipeComponent { log.info("Preparation set successfully."); } - public List<Category> getCategories() { + public List<Integer> getCategoryCodes() { return categories; } - public void setCategories(List<Category> categories) { + public void setCategories(List<Integer> categories) { if (categories == null) { categories = new ArrayList<>(); } - this.categories = categories; + this.categories = new ArrayList<>(categories); log.info("Categories set successfully."); } public void addCategory(Category category) { - categories.add(category); + categories.add(category.getCode()); log.info("Category {} added successfully.", category.getName()); } @@ -133,9 +156,9 @@ public class Recipe implements RecipeComponent { return preparationTimeMins; } - public void setPreparationTimeMins(int preparationTimeMins) { - if (preparationTimeMins < 0) { - throw new InvalidRecipeException("PreparationTime must be a positive value"); + public void setPreparationTimeMins(Integer preparationTimeMins) { + if (preparationTimeMins == null || preparationTimeMins < 0) { + throw new InvalidRecipeException("Preparation time must be a positive integer"); } this.preparationTimeMins = preparationTimeMins; } @@ -161,20 +184,49 @@ public class Recipe implements RecipeComponent { return Measurement.PIECE; } - public Map<RecipeComponent, Integer> getIngredients() { + public Map<String, Integer> getIngredients() { return ingredients; } - public void setIngredients(Map<RecipeComponent, Integer> ingredients) { + public void setIngredientFromRecipeComponents(Map<RecipeComponent, Integer> ingredients) { + if (ingredients == null || ingredients.size() == 0) { + throw new InvalidRecipeException("Ingredient list can not be null or empty"); + } + this.ingredients = recipeObjectMapToKeyMap(ingredients); + } + + public void setIngredientFromKeys(Map<String, Integer> ingredients) { if (ingredients == null || ingredients.size() == 0) { throw new InvalidRecipeException("Ingredient list can not be null or empty"); } this.ingredients = ingredients; } +<<<<<<< src/main/java/mi/hdm/recipes/Recipe.java + + private void setCategoriesFromObjects(List<Category> categories) { + if (categories != null) { + this.categories = categories.stream() + .filter(Objects::nonNull) + .map(Category::getCode) + .collect(Collectors.toCollection(ArrayList::new)); + } else { + this.categories = new ArrayList<>(); + } + } + + + private Map<String, Integer> recipeObjectMapToKeyMap(Map<RecipeComponent, Integer> map) { + Map<String, Integer> result = new HashMap<>(); + for (RecipeComponent c : map.keySet()) { + result.put(c.getUniqueCode(), map.get(c)); + } + return result; + public void setImage(String path) { filepathForImage = path; } +======= @Override public boolean equals(Object o) { @@ -189,4 +241,14 @@ public class Recipe implements RecipeComponent { String desc = description == null ? "No description" : description; return String.format("Recipe: %s%n--------%n%s%n%nCreated: %s%n", name, desc, creationTime); } + + @Override + public String getUniqueCode() { + return code; + } + + private String calculateUniqueCode() { + int hash = Objects.hash(name, creationTime); + return "r" + hash; + } } diff --git a/src/main/java/mi/hdm/recipes/RecipeComponent.java b/src/main/java/mi/hdm/recipes/RecipeComponent.java index 0e147423db109a518bf5630729190a3bb086a2d5..bd85f8849061fc1063a039632066cc60267607c1 100644 --- a/src/main/java/mi/hdm/recipes/RecipeComponent.java +++ b/src/main/java/mi/hdm/recipes/RecipeComponent.java @@ -7,4 +7,11 @@ public interface RecipeComponent { NutritionTable getNutritionTable(); Measurement getMeasurement(); + + /** + * The code is generated using by hashing fields of the object. It will be generated on object creation and never change. + * + * @return Recipes will return "r"+hash, ingredients will return "i"+hash + */ + String getUniqueCode(); } diff --git a/src/main/java/mi/hdm/recipes/RecipeManager.java b/src/main/java/mi/hdm/recipes/RecipeManager.java index 1ec73070920b48a5928c26eb215f679d636609e5..2dd8cb8978b611e2a49e57c399f68055c4d23862 100644 --- a/src/main/java/mi/hdm/recipes/RecipeManager.java +++ b/src/main/java/mi/hdm/recipes/RecipeManager.java @@ -4,28 +4,26 @@ import mi.hdm.exceptions.InvalidRecipeException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; public class RecipeManager { //TODO observable array list? private static final Logger log = LogManager.getLogger(RecipeManager.class); - private final List<Recipe> recipes; - private final CategoryManager categories; - private final IngredientManager ingredients; + private final Map<String, Recipe> allRecipes; - public RecipeManager(List<Recipe> recipes, CategoryManager categories, IngredientManager ingredients) { - this.recipes = recipes; - this.categories = categories; - this.ingredients = ingredients; + public RecipeManager(List<Recipe> recipes) { + this.allRecipes = new HashMap<>(); + recipes.forEach(i -> allRecipes.put(i.getName(), i)); + log.info("Loaded recipe manager with {} recipes", allRecipes.keySet().size()); } public RecipeManager() { - recipes = new ArrayList<>(); - categories = new CategoryManager(); - ingredients = new IngredientManager(); + allRecipes = new HashMap<>(); + log.info("Created empty instance of RecipeManager"); } public void addRecipe(Recipe recipe) { @@ -33,32 +31,31 @@ public class RecipeManager { log.error("Recipe '{}' not added because another recipe with the same name already exists", recipe.getName()); throw new InvalidRecipeException("Recipe with this name already exists."); } - recipes.add(recipe); - log.info("Recipe {} added successfully.", recipe.getName()); + allRecipes.put(recipe.getUniqueCode(), recipe); + log.info("Recipe '{}' added successfully.", recipe.getName()); } - public Recipe deleteRecipe(int i) { - log.info("Recipe {} deleted successfully.", recipes.get(i).getName()); - return recipes.remove(i); + public Recipe getRecipeByCode(String code) { + return allRecipes.get(code); } public void deleteRecipe(String name) { Recipe r = getRecipeByName(name).orElseThrow(() -> new InvalidRecipeException("A recipe with this name does not exist")); - log.info("Recipe {} deleted successfully.", name); - recipes.remove(r); + log.info("Recipe '{}' deleted successfully.", name); + allRecipes.values().remove(r); } // Exception vs return false: What's the best approach? // -> false for normal situations, exceptions for real faults which should not occur public void deleteRecipe(Recipe r) { - if (!recipes.remove(r)) { + if (!allRecipes.values().remove(r)) { throw new InvalidRecipeException("Recipe is not listed."); } log.info("Recipe deleted successfully."); } private Optional<Recipe> getRecipeByName(String name) { - for (final Recipe r : recipes) { + for (final Recipe r : allRecipes.values()) { if (name.equals(r.getName())) { return Optional.of(r); } @@ -66,7 +63,11 @@ public class RecipeManager { return Optional.empty(); } + public void clear() { + allRecipes.clear(); + } + public List<Recipe> getRecipes() { - return recipes; + return allRecipes.values().stream().toList(); } } diff --git a/src/main/java/mi/hdm/recipes/RecipeSearch.java b/src/main/java/mi/hdm/recipes/RecipeSearch.java index 7a770088e676c7828cd42f3066f6338797dd7197..2e9cb343a7afd4d2119de6b3f35ad065b22dc9ff 100644 --- a/src/main/java/mi/hdm/recipes/RecipeSearch.java +++ b/src/main/java/mi/hdm/recipes/RecipeSearch.java @@ -7,10 +7,17 @@ import java.util.ArrayList; import java.util.List; public class RecipeSearch { - //private final List<Recipe> recipesToSearch; //not needed when methods are static +<<<<<<< src/main/java/mi/hdm/recipes/RecipeSearch.java + //TODO: lets make these methods static, no need to instantiate a new object every time private static final Logger log = LogManager.getLogger(RecipeSearch.class); - //public RecipeSearch (List<Recipe> recipesToSearch) { this.recipesToSearch = recipesToSearch; } //not needed when methods are static + private final RecipeManager recipeManager; + private final IngredientManager ingredientManager; + + public RecipeSearch(RecipeManager recipeManager, IngredientManager ingredientManager) { + this.recipeManager = recipeManager; + this.ingredientManager = ingredientManager; + } /** * A method to search a list of recipes for keywords. @@ -19,7 +26,7 @@ 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 static List<Recipe> searchByQuery(List<Recipe> recipesToSearch, String query) { + public List<Recipe> searchByQuery(List<Recipe> recipesToSearch, String query) { if (query == null) return recipesToSearch; query = query.replaceAll("[.,!+?;-]", " ").trim(); if (query.isBlank()) return recipesToSearch; @@ -33,9 +40,13 @@ public class RecipeSearch { if (r.getName().toLowerCase().contains(l)) { //check if recipe name contains words from query (convert name of recipe to lowercase too) result.add(r); } - for (final RecipeComponent c : r.getIngredients().keySet()) { - if (c.getName().toLowerCase().contains(l)) { //check if ingredients contain words from query - result.add(r); + for (final String key : r.getIngredients().keySet()) { + if (key.charAt(0) == 'i') { + Ingredient i = ingredientManager.getIngredient(key).orElseThrow(() -> new RuntimeException("This ingredient does not exist in ingredient manager")); + if (i.getName().toLowerCase().contains(l)) result.add(r); + } else { + Recipe recipe = recipeManager.getRecipeByCode(key); + if (recipe.getName().toLowerCase().contains(l)) result.add(r); } } } @@ -49,14 +60,14 @@ public class RecipeSearch { * @param categoriesToSearch a list of categories to which the recipes shall belong * @return a list of recipes which belong to all categories of categoriesToSearch */ - public static List<Recipe> searchByCategory(List<Recipe> recipesToSearch, List<Category> categoriesToSearch) { + public List<Recipe> searchByCategory(List<Recipe> recipesToSearch, List<Category> categoriesToSearch) { if (categoriesToSearch == null || categoriesToSearch.isEmpty()) return recipesToSearch; List<Recipe> result = new ArrayList<>(recipesToSearch); //create List containing all the recipes to search for (final Recipe r : recipesToSearch) { for (final Category c : categoriesToSearch) { - if (!r.getCategories().contains(c)) { //if a recipe does not contain one of the categories it will be removed from the result set + if (!r.getCategoryCodes().contains(c.getCode())) { //if a recipe does not contain one of the categories it will be removed from the result set result.remove(r); } } diff --git a/src/main/java/mi/hdm/shoppingList/ShoppingList.java b/src/main/java/mi/hdm/shoppingList/ShoppingList.java index 340f9e3898edd728b6f26801d6b95e3ca4ff3acd..d9cf385deb1cc621766ad916fcdc0a5caaed2898 100644 --- a/src/main/java/mi/hdm/shoppingList/ShoppingList.java +++ b/src/main/java/mi/hdm/shoppingList/ShoppingList.java @@ -3,12 +3,12 @@ package mi.hdm.shoppingList; import mi.hdm.exceptions.InvalidIngredientException; import mi.hdm.recipes.Ingredient; import mi.hdm.recipes.Recipe; +import mi.hdm.recipes.RecipeManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.HashMap; import java.util.Map; -import java.util.Optional; /** * Shopping list that the user can add ingredients to. The elements on the list can be marked as done or undone. @@ -16,14 +16,20 @@ import java.util.Optional; public class ShoppingList { private static final Logger log = LogManager.getLogger(ShoppingList.class); - private final Map<Ingredient, Boolean> list; + private final RecipeManager recipeManager; - public ShoppingList() { + private final Map<String, Boolean> list; + + public ShoppingList(RecipeManager recipeManager) { + this.recipeManager = recipeManager; list = new HashMap<>(); + log.info("Created empty instance of ShoppingList"); } - public ShoppingList(Map<Ingredient, Boolean> shoppingListMap) { - list = shoppingListMap; + public ShoppingList(Map<String, Boolean> shoppingListMap, RecipeManager recipeManager) { + list = new HashMap<>(shoppingListMap); + this.recipeManager = recipeManager; + log.info("Created ShoppingList with {} entries.", list.size()); } public void clear() { @@ -35,18 +41,26 @@ public class ShoppingList { if (ingredient == null) { throw new InvalidIngredientException("Can not add ingredient that is null"); } - list.put(ingredient, false); - log.info("Added {} to shopping list.", ingredient.getName()); + list.put(ingredient.getUniqueCode(), false); + log.info("Added '{}' to shopping list.", ingredient.getName()); + } + + public void addToShoppingList(String key) { + if (key == null || key.charAt(0) != 'i') { + throw new InvalidIngredientException("Can not add ingredient for this key: " + key); + } + list.put(key, false); + log.info("Added ingredient with code {} to shopping list.", key); } public void addAllToShoppingList(Recipe recipe) { recipe.getIngredients().keySet() .forEach(element -> { - if (element instanceof Ingredient i) - addToShoppingList(i); - //adds recipes recursively (see below); comment next 2 lines out to avoid adding recipes recursively - else - addAllToShoppingList((Recipe) element); + if (element.startsWith("i")) + addToShoppingList(element); + //adds recipes recursively (see below); comment next 2 lines out to avoid adding recipes recursively + else + addAllToShoppingList(recipeManager.getRecipeByCode(element)); } ); } @@ -54,17 +68,23 @@ public class ShoppingList { public void flipStatus(Ingredient in) { if (in == null) throw new NullPointerException("Ingredient can not be null"); - if (!list.containsKey(in)) + if (!list.containsKey(in.getUniqueCode())) throw new InvalidIngredientException("No ingredient with name " + in.getName()); - boolean newStatus = !list.get(in); - list.put(in, newStatus); + boolean newStatus = !list.get(in.getUniqueCode()); + list.put(in.getUniqueCode(), newStatus); log.info("Ingredient {} marked as {}.", in.getName(), newStatus ? "done" : "not done"); } - public void flipStatus(String name) { - Ingredient in = getIngredientByName(name).orElseThrow(() -> new InvalidIngredientException("No ingredient with name " + name)); - flipStatus(in); + public void flipStatus(String key) { + if (key == null) + throw new NullPointerException("Ingredient can not be null"); + if (!list.containsKey(key)) + throw new InvalidIngredientException("No ingredient with key " + key); + + boolean newStatus = !list.get(key); + list.put(key, newStatus); + log.info("Ingredient with key {} marked as {}.", key, newStatus ? "done" : "not done"); } /** @@ -72,26 +92,24 @@ public class ShoppingList { */ public void removeAllBought() { int removed = 0; - for (Ingredient i : list.keySet()) { - if (list.get(i)) list.remove(i); + for (String s : list.keySet()) { + if (list.get(s)) list.remove(s); removed++; - log.debug("Removed ingredient '{}' from shopping list.", i.getName()); + log.debug("Removed ingredient '{}' from shopping list.", s); } log.info("Removed {} ingredients from shopping list.", removed); } - public Map<Ingredient, Boolean> getList() { + public Map<String, Boolean> getList() { return list; } - private Optional<Ingredient> getIngredientByName(String name) { - if (name == null) throw new NullPointerException("Name can not be null"); - - for (final Ingredient i : list.keySet()) { - if (name.equals(i.getName())) { - return Optional.of(i); - } + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj instanceof ShoppingList s) { + return s.getList().equals(this.list); } - return Optional.empty(); + return false; } } \ No newline at end of file diff --git a/src/main/java/mi/hdm/typeAdapters/CategoryManagerTypeAdapter.java b/src/main/java/mi/hdm/typeAdapters/CategoryManagerTypeAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..d0a2bb8ba748e48e3bb51d0d8e44ef2359b8cf5b --- /dev/null +++ b/src/main/java/mi/hdm/typeAdapters/CategoryManagerTypeAdapter.java @@ -0,0 +1,48 @@ +package mi.hdm.typeAdapters; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import mi.hdm.recipes.Category; +import mi.hdm.recipes.CategoryManager; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class CategoryManagerTypeAdapter extends TypeAdapter<CategoryManager> { + @Override + public void write(JsonWriter writer, CategoryManager categoryManager) throws IOException { + writer.beginObject(); + writer.name("categories").jsonValue(new GsonBuilder() + .registerTypeAdapter(Category.class, new CategoryTypeAdapter()) + .create() + .toJson(categoryManager.getAllCategories())); + writer.endObject(); + } + + @Override + public CategoryManager read(JsonReader reader) throws IOException { + List<Category> categories = new ArrayList<>(); + reader.beginObject(); + while (reader.hasNext()) { + String propertyName = reader.nextName(); + if (propertyName.equals("categories")) { + CategoryTypeAdapter typeAdapter = new CategoryTypeAdapter(); + reader.beginArray(); + Gson gson = new GsonBuilder() + .registerTypeAdapter(Category.class, typeAdapter) + .create(); + while (reader.hasNext()) { + Category next = gson.fromJson(reader, Category.class); + categories.add(next); + } + reader.endArray(); + } + } + reader.endObject(); + return new CategoryManager(categories); + } +} diff --git a/src/main/java/mi/hdm/typeAdapters/CategoryTypeAdapter.java b/src/main/java/mi/hdm/typeAdapters/CategoryTypeAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..d53d539cfcfebb646c000aef1c1856d5d71ecd92 --- /dev/null +++ b/src/main/java/mi/hdm/typeAdapters/CategoryTypeAdapter.java @@ -0,0 +1,50 @@ +package mi.hdm.typeAdapters; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import mi.hdm.recipes.Category; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; + +public class CategoryTypeAdapter extends TypeAdapter<Category> { + private final static Logger log = LogManager.getLogger(CategoryTypeAdapter.class); + + @Override + public void write(JsonWriter jsonWriter, Category category) throws IOException { + jsonWriter.beginObject(); + jsonWriter.name("name").value(category.getName()); + jsonWriter.name("colourCode").value(category.getColourCode()); + jsonWriter.name("code").value(category.getCode()); + jsonWriter.endObject(); + } + + @Override + public Category read(JsonReader jsonReader) throws IOException { + String name = null; + String colourCode = null; + Integer code = null; + + jsonReader.beginObject(); + while (jsonReader.hasNext()) { + String propertyName = jsonReader.nextName(); + switch (propertyName) { + case "name" -> name = jsonReader.nextString(); + case "colourCode" -> colourCode = jsonReader.nextString(); + case "code" -> code = jsonReader.nextInt(); + default -> jsonReader.skipValue(); + } + } + jsonReader.endObject(); + + if (name == null || colourCode == null || code == null) { + String msg = "Null value: invalid or missing value in JSON object for a category"; + log.error(msg); + throw new IOException(msg); + } + + return new Category(name, colourCode, code); + } +} diff --git a/src/main/java/mi/hdm/typeAdapters/MealPlanTypeAdapter.java b/src/main/java/mi/hdm/typeAdapters/MealPlanTypeAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..e8771f2e90d6915dafb2f253121a0243083ac382 --- /dev/null +++ b/src/main/java/mi/hdm/typeAdapters/MealPlanTypeAdapter.java @@ -0,0 +1,50 @@ +package mi.hdm.typeAdapters; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import mi.hdm.mealPlan.MealPlan; +import mi.hdm.recipes.RecipeManager; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.HashMap; +import java.util.Map; + +public class MealPlanTypeAdapter extends TypeAdapter<MealPlan> { + private final RecipeManager recipeManager; + + public MealPlanTypeAdapter(RecipeManager recipeManager) { + this.recipeManager = recipeManager; + } + + @Override + public void write(JsonWriter writer, MealPlan mealPlan) throws IOException { + writer.beginObject(); + Map<LocalDate, String> map = mealPlan.getAllRecipeCodesFromPlan(); + writer.name("mealPlan").jsonValue(new Gson().toJson(map)); + writer.endObject(); + } + + @Override + public MealPlan read(JsonReader reader) throws IOException { + final Map<LocalDate, String> map = new HashMap<>(); + reader.beginObject(); + String name = reader.nextName(); + if (name.equals("mealPlan")) { + reader.beginObject(); + while (reader.hasNext()) { + String date = reader.nextName(); + String code = reader.nextString(); + map.put(LocalDate.parse(date), code); + } + reader.endObject(); + } else { + return null; + } + reader.endObject(); + + return new MealPlan(map, recipeManager); + } +} diff --git a/src/main/java/mi/hdm/typeAdapters/NutritionTableTypeAdapter.java b/src/main/java/mi/hdm/typeAdapters/NutritionTableTypeAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..beb5743378556849e38463b5f56d71bcf8e2d18e --- /dev/null +++ b/src/main/java/mi/hdm/typeAdapters/NutritionTableTypeAdapter.java @@ -0,0 +1,52 @@ +package mi.hdm.typeAdapters; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import mi.hdm.recipes.NutritionTable; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; + +public class NutritionTableTypeAdapter extends TypeAdapter<NutritionTable> { + private final static Logger log = LogManager.getLogger(NutritionTableTypeAdapter.class); + + @Override + public void write(JsonWriter jsonWriter, NutritionTable nutritionTable) throws IOException { + jsonWriter.beginObject(); + jsonWriter.name("values").value(new Gson().toJson(nutritionTable.getTable())); + jsonWriter.endObject(); + } + + @Override + public NutritionTable read(JsonReader jsonReader) throws IOException { + Double calories = null, carbs = null, fat = null, proteins = null, fibers = null, salt = null; + jsonReader.beginObject(); + if (jsonReader.hasNext()) { + String propertyName = jsonReader.nextName(); + if (propertyName.equals("values")) { + String valuesJson = jsonReader.nextString(); + JsonObject valuesObject = JsonParser.parseString(valuesJson).getAsJsonObject(); + calories = valuesObject.get("CALORIES").getAsDouble(); + carbs = valuesObject.get("CARBS").getAsDouble(); + fat = valuesObject.get("FAT").getAsDouble(); + proteins = valuesObject.get("PROTEINS").getAsDouble(); + fibers = valuesObject.get("FIBERS").getAsDouble(); + salt = valuesObject.get("SALT").getAsDouble(); + } else { + jsonReader.skipValue(); + } + } + jsonReader.endObject(); + try { + return new NutritionTable(calories, carbs, fat, proteins, fibers, salt); + } catch (NullPointerException e) { + log.error("IO Exception: invalid or missing values in JSON object for ingredient."); + throw new IOException("null pointer exception - invalid or missing values in JSON"); + } + } +} diff --git a/src/main/java/mi/hdm/typeAdapters/RecipeTypeAdapter.java b/src/main/java/mi/hdm/typeAdapters/RecipeTypeAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..8e4432d2a2974b8723467ba7af0fa9babe00906d --- /dev/null +++ b/src/main/java/mi/hdm/typeAdapters/RecipeTypeAdapter.java @@ -0,0 +1,99 @@ +package mi.hdm.typeAdapters; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import mi.hdm.recipes.NutritionTable; +import mi.hdm.recipes.Recipe; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +public class RecipeTypeAdapter extends TypeAdapter<Recipe> { + private final static Logger log = LogManager.getLogger(RecipeTypeAdapter.class); + + @Override + public void write(JsonWriter writer, Recipe recipe) throws IOException { + writer.beginObject(); + writer.name("name").value(recipe.getName()); + writer.name("nutritionTable").jsonValue(new GsonBuilder().registerTypeAdapter(NutritionTable.class, new NutritionTableTypeAdapter()).create().toJson(recipe.getNutritionTable())); + writer.name("code").value(recipe.getUniqueCode()); + + writer.name("ingredients").jsonValue(new Gson().toJson(recipe.getIngredients())); + + writer.name("description").value(recipe.getDescription()); + writer.name("preparation").jsonValue(new Gson().toJson(recipe.getPreparation())); + + writer.name("categories").jsonValue(new Gson().toJson(recipe.getCategoryCodes())); + + writer.name("preparationTimeMins").value(recipe.getPreparationTimeMins()); + writer.name("creationTime").value(recipe.getCreationTime().toString()); + writer.endObject(); + log.debug("Wrote recipe '{}' to JSON.", recipe.getName()); + } + + @Override + public Recipe read(JsonReader reader) throws IOException { + String name = null; + Map<String, Integer> ingredients = null; + String description = null; + List<String> preparation = null; + List<Integer> categories = null; + Integer preparationTimeMins = null; + NutritionTable nutritionTable = null; + LocalDateTime creationTime = null; + String code = null; + + reader.beginObject(); + while (reader.hasNext()) { + String propertyName = reader.nextName(); + switch (propertyName) { + case "name": + name = reader.nextString(); + break; + case "ingredients": + ingredients = new Gson().fromJson(reader, new TypeToken<Map<String, Integer>>() { + }.getType()); + break; + case "description": + description = reader.nextString(); + break; + case "preparation": + preparation = new Gson().fromJson(reader, new TypeToken<List<String>>() { + }.getType()); + break; + case "categories": + categories = new Gson().fromJson(reader, new TypeToken<List<Integer>>() { + }.getType()); + break; + case "preparationTimeMins": + preparationTimeMins = reader.nextInt(); + break; + case "nutritionTable": + nutritionTable = new GsonBuilder().registerTypeAdapter(NutritionTable.class, new NutritionTableTypeAdapter()).create().fromJson(reader, NutritionTable.class); + break; + case "creationTime": + creationTime = LocalDateTime.parse(reader.nextString()); + break; + case "code": + code = reader.nextString(); + break; + default: + reader.skipValue(); + break; + } + } + reader.endObject(); + + log.debug("Read recipe '{}' from JSON.", name); + + return new Recipe(name, ingredients, description, preparation, categories, preparationTimeMins, nutritionTable, creationTime, code); + } +} diff --git a/src/main/java/mi/hdm/typeAdapters/ShoppingListTypeAdapter.java b/src/main/java/mi/hdm/typeAdapters/ShoppingListTypeAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..b640d5d65851f15f3fe3ad19b8e118192b7626ab --- /dev/null +++ b/src/main/java/mi/hdm/typeAdapters/ShoppingListTypeAdapter.java @@ -0,0 +1,42 @@ +package mi.hdm.typeAdapters; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import mi.hdm.recipes.RecipeManager; +import mi.hdm.shoppingList.ShoppingList; + +import java.io.IOException; +import java.util.Map; + +public class ShoppingListTypeAdapter extends TypeAdapter<ShoppingList> { + private final RecipeManager recipeManager; + + public ShoppingListTypeAdapter(RecipeManager recipeManager) { + this.recipeManager = recipeManager; + } + + @Override + public void write(JsonWriter writer, ShoppingList shoppingList) throws IOException { + final Map<String, Boolean> map = shoppingList.getList(); + writer.beginObject(); + writer.name("shoppingList").jsonValue(new Gson().toJson(map)); + writer.endObject(); + } + + @Override + public ShoppingList read(JsonReader reader) throws IOException { + Map<String, Boolean> map = null; + reader.beginObject(); + String name = reader.nextName(); + if (name.equals("shoppingList")) { + map = new Gson().fromJson(reader, new TypeToken<Map<String, Boolean>>() { + }.getType()); + } + reader.endObject(); + if (map == null) return null; + return new ShoppingList(map, recipeManager); + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 4dd3f6d7da4b6cc0be7d1b77968ebf3e2ba84db2..ca7b90450d52322be1667a7987fad50ad0796758 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -2,8 +2,9 @@ module gui { requires javafx.controls; requires javafx.fxml; requires org.apache.logging.log4j; + requires com.google.gson; - opens mi.hdm to javafx.fxml; + opens mi.hdm to javafx.fxml, com.google.gson; opens mi.hdm.controllers to javafx.fxml; exports mi.hdm; diff --git a/src/test/java/mi/hdm/filesystem/JsonTest.java b/src/test/java/mi/hdm/filesystem/JsonTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3ffbdc41274626bc2f66c7fce412eacead6c73c6 --- /dev/null +++ b/src/test/java/mi/hdm/filesystem/JsonTest.java @@ -0,0 +1,118 @@ +package mi.hdm.filesystem; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import mi.hdm.mealPlan.MealPlan; +import mi.hdm.recipes.*; +import mi.hdm.shoppingList.ShoppingList; +import mi.hdm.typeAdapters.*; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class JsonTest { + private final static IngredientManager ingredientManager = new IngredientManager(); + private final static CategoryManager categoryManager = new CategoryManager(); + private final static RecipeManager recipeManager = new RecipeManager(); + + @BeforeAll + public static void setupAll() { + ingredientManager.addIngredient(ValidObjectsPool.getValidIngredientTwo()); + ingredientManager.addIngredient(ValidObjectsPool.getValidIngredientOne()); + + categoryManager.addCategory("Kategorie 1", 0xFF0000); + categoryManager.addCategory("Kategorie 2", 0xFF00F8); + } + + @Test + public void testCategoryToJSON() { + Category expected = ValidObjectsPool.getValidCategoryOne(); + Gson gson = new GsonBuilder() + .registerTypeAdapter(Category.class, new CategoryTypeAdapter()) + .create(); + + String jsonResult = gson.toJson(expected); + + Category result = gson.fromJson(jsonResult, Category.class); + assertEquals(expected, result); + } + + @Test + public void testCategoryManagerToJson() { + Gson gson = new GsonBuilder() + .registerTypeAdapter(CategoryManager.class, new CategoryManagerTypeAdapter()) + .create(); + String jsonResult = gson.toJson(categoryManager); + System.out.println(jsonResult); + CategoryManager result = gson.fromJson(jsonResult, CategoryManager.class); + assertEquals(categoryManager, result); + } + + @Test + public void testNutritionTableToJSON() { + NutritionTable expected = ValidObjectsPool.getValidNutritionTableOne(); + Gson gson = new GsonBuilder() + .registerTypeAdapter(NutritionTable.class, new NutritionTableTypeAdapter()) + .create(); + + String json = gson.toJson(expected, NutritionTable.class); + + NutritionTable result = gson.fromJson(json, NutritionTable.class); + assertEquals(expected, result); + } + + @Test + public void testRecipeToJSON() { + Recipe expected = ValidObjectsPool.getValidRecipeOne(); + Gson gson = new GsonBuilder() + .registerTypeAdapter(Recipe.class, new RecipeTypeAdapter()) + .create(); + String json = gson.toJson(expected); + System.out.println(json); + Recipe result = gson.fromJson(json, Recipe.class); + assertEquals(expected, result); + } + + @Test + public void testMealPlanToJson() { + MealPlan expected = ValidObjectsPool.getValidMealPlanOne(); + Gson gson = new GsonBuilder() + .registerTypeAdapter(MealPlan.class, new MealPlanTypeAdapter(new RecipeManager())) + .create(); + String json = gson.toJson(expected); + System.out.println(json); + MealPlan result = gson.fromJson(json, MealPlan.class); + + assertEquals(expected, result); + } + + @Test + public void testShoppingListToJson() { + ShoppingList expected = ValidObjectsPool.getShoppingList(); + Gson gson = new GsonBuilder() + .registerTypeAdapter(ShoppingList.class, new ShoppingListTypeAdapter(recipeManager)) + .create(); + String json = gson.toJson(expected); + System.out.println(json); + ShoppingList result = gson.fromJson(json, ShoppingList.class); + + assertEquals(expected, result); + } + + @Test + public void getCorrectObjectsFromRecipe() { + Ingredient ingredient = ValidObjectsPool.getValidIngredientOne(); + Category category = ValidObjectsPool.getValidCategoryOne(); + Recipe recipe = new Recipe("Expect", Map.of(ingredient, 100), "Description", List.of("Prepare"), List.of(category), 15); + + IngredientManager ingredientManager = new IngredientManager(List.of(ingredient)); + CategoryManager categoryManager = new CategoryManager(List.of(category)); + + assertEquals(category, categoryManager.get(category.getCode()).orElseThrow()); + assertEquals(ingredient, ingredientManager.getIngredient(ingredient.getUniqueCode()).orElseThrow()); + } +} diff --git a/src/test/java/mi/hdm/mealPlan/MealPlanTest.java b/src/test/java/mi/hdm/mealPlan/MealPlanTest.java index 8ed43c3f1101d1c5901a4d72cb3e474bee7c68fa..52cf84a0913a09023f6a89f17bc69bb2877ae418 100644 --- a/src/test/java/mi/hdm/mealPlan/MealPlanTest.java +++ b/src/test/java/mi/hdm/mealPlan/MealPlanTest.java @@ -2,7 +2,9 @@ package mi.hdm.mealPlan; import mi.hdm.exceptions.InvalidMealPlanException; import mi.hdm.recipes.Recipe; +import mi.hdm.recipes.RecipeManager; import mi.hdm.recipes.ValidObjectsPool; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -15,12 +17,20 @@ import static org.junit.jupiter.api.Assertions.assertThrows; class MealPlanTest { private MealPlan underTest; + private final static RecipeManager recipeManager = ValidObjectsPool.getRecipeManager(); private final static Recipe recipeOne = ValidObjectsPool.getValidRecipeOne(); private final static Recipe recipeTwo = ValidObjectsPool.getValidRecipeTwo(); + @BeforeAll + public static void setupAll() { + recipeManager.clear(); + recipeManager.addRecipe(recipeOne); + recipeManager.addRecipe(recipeTwo); + } + @BeforeEach public void setup() { - underTest = new MealPlan(new HashMap<>()); + underTest = new MealPlan(new HashMap<>(), recipeManager); } @Test @@ -28,7 +38,7 @@ class MealPlanTest { //given LocalDate date = LocalDate.now(); - assertThrows(NullPointerException.class, () -> underTest = new MealPlan(Map.of(null, recipeOne, date, recipeTwo))); + assertThrows(NullPointerException.class, () -> underTest = new MealPlan(Map.of(null, recipeOne.getUniqueCode(), date, recipeTwo.getUniqueCode()), recipeManager)); } @Test @@ -64,16 +74,16 @@ class MealPlanTest { public void shouldRemoveOldRecipes() { //given LocalDate date = LocalDate.of(2022, 1, 1); - Map<LocalDate, Recipe> expected = Map.of(); - HashMap<LocalDate, Recipe> entries = new HashMap<>(); - entries.put(date, recipeOne); - underTest = new MealPlan(entries); + Map<LocalDate, String> expected = Map.of(); + HashMap<LocalDate, String> entries = new HashMap<>(); + entries.put(date, recipeOne.getUniqueCode()); + underTest = new MealPlan(entries, recipeManager); //when underTest.removePastRecipes(); //expect - assertEquals(expected, underTest.getAllRecipesFromPlan()); + assertEquals(expected, underTest.getAllRecipeCodesFromPlan()); } @Test @@ -81,10 +91,10 @@ class MealPlanTest { //given LocalDate old = LocalDate.of(2022, 1, 1); LocalDate now = LocalDate.now(); - HashMap<LocalDate, Recipe> entries = new HashMap<>(); - entries.put(old, recipeOne); - entries.put(now, recipeTwo); - underTest = new MealPlan(entries); + HashMap<LocalDate, String> entries = new HashMap<>(); + entries.put(old, recipeOne.getUniqueCode()); + entries.put(now, recipeTwo.getUniqueCode()); + underTest = new MealPlan(entries, recipeManager); Map<LocalDate, Recipe> expected = Map.of(now, recipeTwo); diff --git a/src/test/java/mi/hdm/recipes/NutritionCalculatorTest.java b/src/test/java/mi/hdm/recipes/NutritionCalculatorTest.java index 2be5d61a80479759f2d398864c05d759940e6835..80f02a3594ecd65287d1759f61b72f35060f4be8 100644 --- a/src/test/java/mi/hdm/recipes/NutritionCalculatorTest.java +++ b/src/test/java/mi/hdm/recipes/NutritionCalculatorTest.java @@ -1,23 +1,25 @@ package mi.hdm.recipes; -import mi.hdm.mealPlan.MealPlan; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.time.LocalDate; import java.util.HashMap; import java.util.List; import java.util.Map; public class NutritionCalculatorTest { - List<RecipeComponent> ingredients = ValidObjectsPool.getValidIngredientList(); - Map<RecipeComponent, Integer> recipeMap; + private List<RecipeComponent> ingredients = ValidObjectsPool.getValidIngredientList(); + private Map<RecipeComponent, Integer> recipeMap; + private final RecipeManager recipeManager = new RecipeManager(); + + private Recipe recipe = ValidObjectsPool.getValidRecipeOne(); @BeforeEach public void setUp() { recipeMap = new HashMap<>(); ingredients.forEach(ingredient -> recipeMap.put(ingredient, 100)); + recipeManager.addRecipe(recipe); } @Test diff --git a/src/test/java/mi/hdm/recipes/RecipeSearchTest.java b/src/test/java/mi/hdm/recipes/RecipeSearchTest.java index 1c5e296ad4eeec9d2243cb411fac62ce248f0a00..9fa5d9a82cf81ad0208c1ee8860f383ac6d52db1 100644 --- a/src/test/java/mi/hdm/recipes/RecipeSearchTest.java +++ b/src/test/java/mi/hdm/recipes/RecipeSearchTest.java @@ -1,5 +1,6 @@ package mi.hdm.recipes; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -17,20 +18,31 @@ class RecipeSearchTest { private final static Category c1 = ValidObjectsPool.getValidCategoryOne(); private final static Category c2 = ValidObjectsPool.getValidCategoryTwo(); + private static final RecipeManager recipeManager = new RecipeManager(); + private static final IngredientManager ingredientManager = new IngredientManager(); + private List<Recipe> recipes; //private RecipeSearch underTest; //not needed when methods are static + @BeforeAll + public static void setupAll() { + ingredientManager.addIngredient(ValidObjectsPool.getValidIngredientOne()); + ingredientManager.addIngredient(ValidObjectsPool.getValidIngredientTwo()); + recipeManager.addRecipe(r1); + recipeManager.addRecipe(r2); + } + @BeforeEach public void setup() { recipes = List.of(r1, r2); - //underTest = new RecipeSearch(recipes); //not needed when methods are static + underTest = new RecipeSearch(recipeManager, ingredientManager); } @ParameterizedTest @NullAndEmptySource @ValueSource(strings = {" \n", ".", "!", "..."}) public void testSearchByEmptyQuery(String input) { - assertEquals(recipes, RecipeSearch.searchByQuery(recipes, input)); + assertEquals(recipes, underTest.searchByQuery(recipes, input)); } @ParameterizedTest @@ -40,7 +52,7 @@ class RecipeSearchTest { List<Recipe> expected = List.of(r1); //when - List<Recipe> result = RecipeSearch.searchByQuery(recipes, query); + List<Recipe> result = underTest.searchByQuery(recipes, query); //expected assertEquals(expected, result); @@ -53,7 +65,7 @@ class RecipeSearchTest { String query = "Apple "; //when - List<Recipe> result = RecipeSearch.searchByQuery(recipes, query); + List<Recipe> result = underTest.searchByQuery(recipes, query); //expected assertEquals(expected, result); @@ -62,13 +74,13 @@ class RecipeSearchTest { @ParameterizedTest @NullAndEmptySource public void testSearchByEmptyCategories(List<Category> input) { - assertEquals(recipes, RecipeSearch.searchByCategory(recipes, input)); + assertEquals(recipes, underTest.searchByCategory(recipes, input)); } @Test public void testSearchByCategories() { - assertEquals(List.of(r1, r2), RecipeSearch.searchByCategory(recipes, List.of(c1))); - assertEquals(List.of(r2), RecipeSearch.searchByCategory(recipes, List.of(c1, c2))); - assertEquals(List.of(r2), RecipeSearch.searchByCategory(recipes, List.of(c2))); + assertEquals(List.of(r1, r2), underTest.searchByCategory(recipes, List.of(c1))); + assertEquals(List.of(r2), underTest.searchByCategory(recipes, List.of(c1, c2))); + assertEquals(List.of(r2), underTest.searchByCategory(recipes, List.of(c2))); } } diff --git a/src/test/java/mi/hdm/recipes/RecipeTest.java b/src/test/java/mi/hdm/recipes/RecipeTest.java index 8fdc169495b8c1c104d0812da9ff6084b6f7e773..f623ab74679da08dd81af86e64e35d240e816c5c 100644 --- a/src/test/java/mi/hdm/recipes/RecipeTest.java +++ b/src/test/java/mi/hdm/recipes/RecipeTest.java @@ -87,8 +87,8 @@ class RecipeTest { //then assertEquals( - List.of(category), - underTest.getCategories() + List.of(category.getCode()), + underTest.getCategoryCodes() ); } @@ -105,8 +105,8 @@ class RecipeTest { //then assertEquals( - List.of(category), - underTest.getCategories() + List.of(category.getCode()), + underTest.getCategoryCodes() ); } } diff --git a/src/test/java/mi/hdm/recipes/ValidObjectsPool.java b/src/test/java/mi/hdm/recipes/ValidObjectsPool.java index e9edac91ec77e3f9b25c5e0584d445007d9148f8..252c59799e61e7402907015d316310a66d6b93f0 100644 --- a/src/test/java/mi/hdm/recipes/ValidObjectsPool.java +++ b/src/test/java/mi/hdm/recipes/ValidObjectsPool.java @@ -1,13 +1,14 @@ package mi.hdm.recipes; import mi.hdm.mealPlan.MealPlan; +import mi.hdm.shoppingList.ShoppingList; import java.time.LocalDate; -import java.util.HashMap; import java.util.List; import java.util.Map; public class ValidObjectsPool { + private final static RecipeManager recipeManager = new RecipeManager(); private final static Category categoryOne = new Category("Frühstück", 0xFF0000); private final static Category categoryTwo = new Category("Abendessen", 0x00FF00); private final static NutritionTable nutritionTableOne = new NutritionTable(100.0, 17.2, 9.4, 4.3, 2.4, 0.4); @@ -16,8 +17,9 @@ public class ValidObjectsPool { private final static Ingredient ingredientTwo = new Ingredient(Measurement.GRAM, "Apple", nutritionTableTwo); private final static Recipe recipeOne = new Recipe("Valid recipe 1", Map.of(ingredientOne, 100, ingredientTwo, 200), "Description for this recipe", List.of("step one", "step two"), List.of(categoryOne), 15); private final static Recipe recipeTwo = new Recipe("Apfelkuchen", Map.of(ingredientOne, 250), "Mein liebster APfelkuchen", List.of("mjam mjam", "ich liebe kochen"), List.of(categoryTwo, categoryOne), 25); - private final static Map<LocalDate, Recipe> recipeMap1 = Map.of(LocalDate.now(), recipeOne, LocalDate.now().plusDays(1), recipeTwo); - private final static MealPlan planOne = new MealPlan(recipeMap1); + private final static Map<LocalDate, String> recipeMap1 = Map.of(LocalDate.now(), recipeOne.getUniqueCode(), LocalDate.now().plusDays(1), recipeTwo.getUniqueCode()); + private final static MealPlan planOne = new MealPlan(recipeMap1, recipeManager); + private final static ShoppingList shoppingList = new ShoppingList(Map.of("r-192991", true, "r-89134", false), recipeManager); public static NutritionTable getValidNutritionTableOne() { return nutritionTableOne; @@ -59,5 +61,15 @@ public class ValidObjectsPool { return recipeTwo; } - public static MealPlan getValidMealPlanOne() { return planOne; } + public static MealPlan getValidMealPlanOne() { + return planOne; + } + + public static ShoppingList getShoppingList() { + return shoppingList; + } + + public static RecipeManager getRecipeManager() { + return recipeManager; + } } diff --git a/src/test/java/mi/hdm/shoppingList/ShoppingListTest.java b/src/test/java/mi/hdm/shoppingList/ShoppingListTest.java index ec06b37f4fa1afd4932b91b7f78c94fd218c02af..808a42ba2c8a6cfb9a51fd1c6c545c31e476b0db 100644 --- a/src/test/java/mi/hdm/shoppingList/ShoppingListTest.java +++ b/src/test/java/mi/hdm/shoppingList/ShoppingListTest.java @@ -13,7 +13,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; class ShoppingListTest { - private final ShoppingList underTest = new ShoppingList(); + private final ShoppingList underTest = new ShoppingList(ValidObjectsPool.getRecipeManager()); private final static Ingredient ing1 = ValidObjectsPool.getValidIngredientOne(); private final static Ingredient ing2 = ValidObjectsPool.getValidIngredientTwo(); @@ -21,13 +21,14 @@ class ShoppingListTest { @BeforeEach public void setup() { + ValidObjectsPool.getRecipeManager().clear(); underTest.clear(); } @Test public void shouldAddIngredient() { //given - Map<Ingredient, Boolean> expected = Map.of(ing1, false); + Map<String, Boolean> expected = Map.of(ing1.getUniqueCode(), false); //when underTest.addToShoppingList(ing1); @@ -39,7 +40,7 @@ class ShoppingListTest { @Test public void shouldAddAllIngredients() { //given - Map<Ingredient, Boolean> expected = Map.of(ing1, false, ing2, false); + Map<String, Boolean> expected = Map.of(ing1.getUniqueCode(), false, ing2.getUniqueCode(), false); //when underTest.addAllToShoppingList(r1); @@ -48,10 +49,16 @@ class ShoppingListTest { assertEquals(expected, underTest.getList()); } + @Test + public void shouldAllAllIngredientsRecursively() { + //TODO: write test for adding ingredients to shopping list recursively + throw new RuntimeException("not implemented"); + } + @Test public void testSetStatusByObject() { //given - Map<Ingredient, Boolean> expected = Map.of(ing1, true); + Map<String, Boolean> expected = Map.of(ing1.getUniqueCode(), true); underTest.addToShoppingList(ing1); //when @@ -64,24 +71,24 @@ class ShoppingListTest { } @Test - public void testSetStatusByName() { + public void testSetStatusByKey() { //given - Map<Ingredient, Boolean> expected = Map.of(ing1, true); + Map<String, Boolean> expected = Map.of(ing1.getUniqueCode(), true); underTest.addToShoppingList(ing1); //when - underTest.flipStatus(ing1.getName()); + underTest.flipStatus(ing1.getUniqueCode()); //expect assertEquals(expected, underTest.getList()); - assertThrows(InvalidIngredientException.class, () -> underTest.flipStatus(ing2.getName())); - assertThrows(NullPointerException.class, () -> underTest.flipStatus((String) null)); + assertThrows(InvalidIngredientException.class, () -> underTest.flipStatus(ing2)); + assertThrows(NullPointerException.class, () -> underTest.flipStatus((Ingredient) null)); } @Test public void shouldRemoveAllBoughtItems() { //given - Map<Ingredient, Boolean> expected = Map.of(); + Map<String, Boolean> expected = Map.of(); underTest.addToShoppingList(ing1); underTest.flipStatus(ing1);