diff --git a/src/main/java/mi/hdm/controllers/MealPlanController.java b/src/main/java/mi/hdm/controllers/MealPlanController.java index eb30903d6fbb52c244dda4143c5afc5dc7850860..e600016e56858d7de77c20d2b1d2b0b010737a6b 100644 --- a/src/main/java/mi/hdm/controllers/MealPlanController.java +++ b/src/main/java/mi/hdm/controllers/MealPlanController.java @@ -11,6 +11,7 @@ import javafx.scene.layout.*; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; import mi.hdm.components.IngredientSearchResultLabel; +import mi.hdm.exceptions.MissingContentException; import mi.hdm.mealPlan.MealPlan; import mi.hdm.recipes.IngredientManager; import mi.hdm.recipes.Recipe; @@ -77,40 +78,53 @@ public class MealPlanController extends BaseController { for (int j = 0; j < 7; j++) { final int J = j; mealPlan.getRecipeCodeForDay(LocalDate.now().plusDays(J)).ifPresentOrElse(code -> { - Recipe r = recipeManager.getRecipe(code).get(); - VBox displayRecipeVBox = new VBox(); - ImageView recipeImage = new ImageView(new Image(r.getImageURL().toString())); - - HBox recipeHBox = new HBox(); - Label name = new Label(r.getName()); - name.setFont(font); - name.setStyle("-fx-padding: 0 50 10 10;"); - name.setCursor(Cursor.HAND); - name.setTooltip(new Tooltip("Click to display this recipe")); - name.setOnMouseClicked(e -> - changeScene(View.RECIPE_VIEW, r) - ); + try { + //if this day has code, fetch the recipe and display it in the grid + Recipe r = recipeManager.getRecipe(code).orElseThrow(() -> new MissingContentException(code, Recipe.class)); + VBox displayRecipeVBox = new VBox(); + ImageView recipeImage = new ImageView(new Image(r.getImageURL().toString())); + + HBox recipeHBox = new HBox(); + Label name = new Label(r.getName()); + name.setFont(font); + name.setStyle("-fx-padding: 0 50 10 10;"); + name.setCursor(Cursor.HAND); + name.setTooltip(new Tooltip("Click to display this recipe")); + name.setOnMouseClicked(e -> + changeScene(View.RECIPE_VIEW, r) + ); - Button removeRecipeButton = new Button(); - removeRecipeButton.setStyle("-fx-background-color: dedede;" + - "-fx-background-radius: 5;"); - ImageView xIconImage = new ImageView(String.valueOf(Recipe.class.getResource("/images/Tasty_Pages_X_Icon.png"))); - xIconImage.setPreserveRatio(true); - xIconImage.setFitHeight(20); - xIconImage.setFitWidth(20); - removeRecipeButton.setGraphic(xIconImage); - removeRecipeButton.setOnAction(e -> { - mealPlan.clear(LocalDate.now().plusDays(J)); + Button removeRecipeButton = new Button(); + removeRecipeButton.setStyle("-fx-background-color: #dedede;" + + "-fx-background-radius: 5;"); + ImageView xIconImage = new ImageView(String.valueOf(Recipe.class.getResource("/images/Tasty_Pages_X_Icon.png"))); + xIconImage.setPreserveRatio(true); + xIconImage.setFitHeight(20); + xIconImage.setFitWidth(20); + removeRecipeButton.setGraphic(xIconImage); + removeRecipeButton.setOnAction(e -> { + mealPlan.clear(LocalDate.now().plusDays(J)); + render(); + }); + + recipeHBox.getChildren().addAll(name, removeRecipeButton); + displayRecipeVBox.getChildren().addAll(recipeImage, recipeHBox); + mealPlanGrid.add(displayRecipeVBox, J, 1); + recipeImage.setPreserveRatio(true); + recipeImage.fitWidthProperty().bind(mealPlanGrid.widthProperty().divide(7)); + recipeImage.fitHeightProperty().bind(mealPlanGrid.heightProperty().subtract(60)); + } catch (MissingContentException e) { + //Display error message to the user, remove missing recipe from meal plan and re-render + log.error(e.getMessage()); + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setHeaderText("Missing recipe"); + alert.setContentText("A recipe with this code wasn't found. It will be removed from the meal plan"); + alert.show(); + mealPlan.remove(e.getCode()); render(); - }); - - recipeHBox.getChildren().addAll(name, removeRecipeButton); - displayRecipeVBox.getChildren().addAll(recipeImage, recipeHBox); - mealPlanGrid.add(displayRecipeVBox, J, 1); - recipeImage.setPreserveRatio(true); - recipeImage.fitWidthProperty().bind(mealPlanGrid.widthProperty().divide(7)); - recipeImage.fitHeightProperty().bind(mealPlanGrid.heightProperty().subtract(60)); + } }, () -> { + //if this day has no code, display "plus" button Button addRecipeButton = new Button(); addRecipeButton.setStyle("-fx-background-color: #dedede;" + "-fx-background-radius: 10;"); @@ -128,11 +142,10 @@ public class MealPlanController extends BaseController { } } - public void searchRecipes(LocalDate date) { - searchVBox = new VBox(); - searchVBox.setMaxSize(800, 800); - searchVBox.setAlignment(Pos.CENTER); + searchVBox = new VBox(); + searchVBox.setMaxSize(800, 800); + searchVBox.setAlignment(Pos.CENTER); TextField searchTextField = new TextField(); searchTextField.setPromptText("Search for recipe"); searchTextField.textProperty().addListener(e -> { @@ -149,24 +162,24 @@ public class MealPlanController extends BaseController { stackPane.getChildren().add(searchVBox); } - private void drawRecipeSearchResults(LocalDate date) { - log.debug("Drawing search results"); - VBox resultContainer = new VBox(); - searchResults - .forEach( - result -> { - IngredientSearchResultLabel resultLabel = new IngredientSearchResultLabel(result); - resultLabel.setOnMouseClicked(e -> { - log.debug("User added recipe '{}' to mealplan for day {}.", result.getName(), date); - mealPlan.addRecipeToMealPlan(result, date); - - stackPane.getChildren().remove(searchVBox); - render(); - }); - resultContainer.getChildren().add(resultLabel); - } - ); - searchScrollPane.setContent(resultContainer); + private void drawRecipeSearchResults(LocalDate date) { + log.debug("Drawing search results"); + VBox resultContainer = new VBox(); + searchResults + .forEach( + result -> { + IngredientSearchResultLabel resultLabel = new IngredientSearchResultLabel(result); + resultLabel.setOnMouseClicked(e -> { + log.debug("User added recipe '{}' to mealplan for day {}.", result.getName(), date); + mealPlan.addRecipeToMealPlan(result, date); + + stackPane.getChildren().remove(searchVBox); + render(); + }); + resultContainer.getChildren().add(resultLabel); + } + ); + searchScrollPane.setContent(resultContainer); } @FXML diff --git a/src/main/java/mi/hdm/controllers/RecipeEditorController.java b/src/main/java/mi/hdm/controllers/RecipeEditorController.java index 4b624e0cca37983ff3a741d0cccc7622da2946cf..468c662c5d21a790d037a5b07353fc33ca35067c 100644 --- a/src/main/java/mi/hdm/controllers/RecipeEditorController.java +++ b/src/main/java/mi/hdm/controllers/RecipeEditorController.java @@ -11,6 +11,7 @@ import mi.hdm.components.IngredientSearchResultLabel; import mi.hdm.components.SelectedIngredientLabel; import mi.hdm.exceptions.InvalidIngredientException; import mi.hdm.exceptions.InvalidRecipeException; +import mi.hdm.exceptions.MissingContentException; import mi.hdm.recipes.*; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -32,7 +33,7 @@ import static mi.hdm.helpers.Validation.isInteger; public class RecipeEditorController extends BaseController { private static final int ELEMENTS_PER_SEARCH_PAGE = 100; - private Recipe recipe; + private final Recipe recipe; private final RecipeManager recipeManager; private final CategoryManager categoryManager; private final IngredientManager ingredientManager; @@ -41,7 +42,7 @@ public class RecipeEditorController extends BaseController { private List<RecipeComponent> searchResults; private final List<Category> selectedCategories; private final List<RecipeComponent> selectedIngredients; - private List<SelectedIngredientLabel> selectedIngredientLabels; + private final List<SelectedIngredientLabel> selectedIngredientLabels; private int currentPage = 0; private int maxPages = 0; @@ -82,8 +83,13 @@ public class RecipeEditorController extends BaseController { searchResults = new ArrayList<>(); selectedIngredients = new ArrayList<>(); - recipe.getIngredients().forEach((code, amount) -> - selectedIngredients.add(componentFromCode(code)) + recipe.getIngredients().forEach((code, amount) -> { + try { + selectedIngredients.add(componentFromCode(code)); + } catch (MissingContentException e) { + throw new RuntimeException(e); + } + } ); recipeSearch = new RecipeSearch(recipeManager, ingredientManager); selectedIngredientsVbox = new VBox(); @@ -101,24 +107,45 @@ public class RecipeEditorController extends BaseController { private void displayRecipe() { nameTextField.setText(recipe.getName()); descriptionTextArea.setText(recipe.getDescription()); - //TODO: this is duplicate code (see around line 190) -> refactor into new method + final List<MissingContentException> missing = new ArrayList<>(); recipe.getIngredients().forEach((code, amount) -> { - HBox ingredientHBox = new HBox(); - Button deleteIngredientButton = new Button("X"); - SelectedIngredientLabel label = new SelectedIngredientLabel(ingredientManager.getIngredient(code).get(), amount); - selectedIngredientLabels.add(label); - deleteIngredientButton.setStyle("-fx-text-fill: #d91c1c;" + - "-fx-font-size: 12;" + - "-fx-background-size: small;"); - deleteIngredientButton.setOnAction(h -> { + //TODO: fetching ingredients needs to happen in constructor, add all to selected ingredients immediately, then + //TODO just stream over the list right here + try { RecipeComponent c = componentFromCode(code); - log.debug("User deleted ingredient '{}' from recipe.", c.getName()); - selectedIngredientLabels.remove(label); - selectedIngredientsVbox.getChildren().remove(ingredientHBox); - }); - ingredientHBox.getChildren().addAll(label, deleteIngredientButton); - selectedIngredientsVbox.getChildren().add(ingredientHBox); + HBox ingredientHBox = new HBox(); + Button deleteIngredientButton = new Button("X"); + SelectedIngredientLabel label = new SelectedIngredientLabel(c, amount); + selectedIngredientLabels.add(label); + deleteIngredientButton.setStyle("-fx-text-fill: #d91c1c;" + + "-fx-font-size: 12;"); + deleteIngredientButton.setOnAction(h -> { + selectedIngredients.remove(c); + log.debug("User deleted ingredient '{}' from recipe.", c.getName()); + selectedIngredientLabels.remove(label); + selectedIngredientsVbox.getChildren().remove(ingredientHBox); + }); + ingredientHBox.getChildren().addAll(label, deleteIngredientButton); + selectedIngredientsVbox.getChildren().add(ingredientHBox); + } catch (MissingContentException e) { + missing.add(e); + log.warn("Missing ingredient in recipe: '{}'", e.getCode()); + } }); + if (missing.size() > 0) { + Alert alert = new Alert(Alert.AlertType.WARNING); + alert.setHeaderText("Missing ingredients"); + alert.setContentText(missing.size() + " ingredients in this recipe do not exist anymore. They will be removed from the recipe."); + alert.show(); + final Map<String, Integer> newIngredients = new HashMap<>(recipe.getIngredients()); + for (MissingContentException e : missing) { + String code = e.getCode(); + newIngredients.remove(code); + log.debug("Removed component with code '{}' from recipe", code); + } + recipe.setIngredientFromKeys(newIngredients); + log.info("Updated ingredients for recipe '{}', removed {} ingredients", recipe.getName(), missing.size()); + } preparationTextArea.setText(String.join("\n", recipe.getPreparation())); prepTimeTextField.setText(String.valueOf(recipe.getPreparationTimeMins())); imagePathTextField.setText(recipe.getImageURL().toString()); @@ -270,16 +297,14 @@ public class RecipeEditorController extends BaseController { changeScene(View.RECIPE_VIEW, recipe); } - private RecipeComponent componentFromCode(String code) { + private RecipeComponent componentFromCode(String code) throws MissingContentException { RecipeComponent c; switch (code.charAt(0)) { case 'i' -> { - c = ingredientManager.getIngredient(code).get(); - selectedIngredients.remove(c); + c = ingredientManager.getIngredient(code).orElseThrow(() -> new MissingContentException(code, Ingredient.class)); } case 'r' -> { - c = recipeManager.getRecipe(code).get(); - selectedIngredients.remove(c); + c = recipeManager.getRecipe(code).orElseThrow(() -> new MissingContentException(code, Recipe.class)); } default -> throw new InvalidIngredientException("Recipe contained an invalid ingredient code: " + code); } diff --git a/src/main/java/mi/hdm/exceptions/MissingContentException.java b/src/main/java/mi/hdm/exceptions/MissingContentException.java new file mode 100644 index 0000000000000000000000000000000000000000..522161a6530edfd679fc6b2d410ebca310436eb3 --- /dev/null +++ b/src/main/java/mi/hdm/exceptions/MissingContentException.java @@ -0,0 +1,25 @@ +package mi.hdm.exceptions; + +import java.lang.reflect.Type; + +/** + * This exception is thrown when no object is found for a code. + */ +public class MissingContentException extends Exception { + private final Type type; + private final String code; + + public MissingContentException(String code, Type type) { + super("Missing content exception: no instance of " + type + " was found with code '" + code + "'"); + this.type = type; + this.code = code; + } + + public Type getType() { + return type; + } + + public String getCode() { + return code; + } +} diff --git a/src/main/java/mi/hdm/mealPlan/MealPlan.java b/src/main/java/mi/hdm/mealPlan/MealPlan.java index 7f8cc38a30f80180b0d0bf7895f25f581b05952a..0a5cb667d8cfe272e20a27f44e9a95422924b955 100644 --- a/src/main/java/mi/hdm/mealPlan/MealPlan.java +++ b/src/main/java/mi/hdm/mealPlan/MealPlan.java @@ -123,6 +123,13 @@ public class MealPlan { return !(date.isBefore(LocalDate.now()) || invalid.isBefore(date)); } + /** + * Removes the recipe with the specified code from the meal plan, if it exists + */ + public void remove(String code) { + plan.values().remove(code); + } + @Override public boolean equals(Object o) { if (o == this) return true;