From 88a85f2e04449387a82b86f4d3651ecc7503726f Mon Sep 17 00:00:00 2001 From: Steven Liu Date: Mon, 22 Dec 2025 02:58:50 +0200 Subject: [PATCH] refactor: revised class layout for JPA entities --- .../main/java/commons/FormalIngredient.java | 53 +++++++++++ commons/src/main/java/commons/Ingredient.java | 25 ++++-- commons/src/main/java/commons/Recipe.java | 25 +++--- .../main/java/commons/RecipeIngredient.java | 89 ++++++++----------- commons/src/main/java/commons/Unit.java | 55 ++++++------ .../main/java/commons/VagueIngredient.java | 34 +++++++ 6 files changed, 184 insertions(+), 97 deletions(-) create mode 100644 commons/src/main/java/commons/FormalIngredient.java create mode 100644 commons/src/main/java/commons/VagueIngredient.java diff --git a/commons/src/main/java/commons/FormalIngredient.java b/commons/src/main/java/commons/FormalIngredient.java new file mode 100644 index 0000000..231abd3 --- /dev/null +++ b/commons/src/main/java/commons/FormalIngredient.java @@ -0,0 +1,53 @@ +package commons; + +import jakarta.persistence.Entity; + +import java.util.Optional; + +/** + * A formal ingredient inheriting from base {@link RecipeIngredient RecipeIngredient}, + * holds an amount and a unit suffix in {@link String String} form. + * @see RecipeIngredient + */ +@Entity +public class FormalIngredient extends RecipeIngredient { + private double amount; + private String unitSuffix; + + public double getAmount() { + return amount; + } + + public void setAmount(double amount) { + this.amount = amount; + } + + public String getUnitSuffix() { + return unitSuffix; + } + + public void setUnitSuffix(String unitSuffix) { + this.unitSuffix = unitSuffix; + } + + public FormalIngredient(Ingredient ingredient, double amount, String unitSuffix) { + super(ingredient); + this.amount = amount; + this.unitSuffix = unitSuffix; + } + + public FormalIngredient() { + // ORM + } + + public double amountInBaseUnit() { + Optional unit = Unit.fromString(unitSuffix); + if (unit.isEmpty() || !unit.get().isFormal() || unit.get().conversionFactor <= 0) { + return 0.0; + } + return amount * unit.get().conversionFactor; + } + public String toString() { + return amount + unitSuffix + " of " + ingredient.name; + } +} diff --git a/commons/src/main/java/commons/Ingredient.java b/commons/src/main/java/commons/Ingredient.java index 2ecc532..f84ab9b 100644 --- a/commons/src/main/java/commons/Ingredient.java +++ b/commons/src/main/java/commons/Ingredient.java @@ -20,7 +20,8 @@ public class Ingredient { @GeneratedValue(strategy = GenerationType.AUTO) public long id; - @Column(name = "name", nullable = false, unique = true) + // FIXME Dec 22 2025::temporarily made this not a unique constraint because of weird JPA behaviour + @Column(name = "name", nullable = false, unique = false) public String name; @@ -35,10 +36,24 @@ public class Ingredient { public Ingredient() {} - public Ingredient(String name, - double proteinPer100g, - double fatPer100g, - double carbsPer100g) { + public Ingredient( + Long id, + String name, + double proteinPer100g, + double fatPer100g, + double carbsPer100g) { + this.id = id; + this.name = name; + this.proteinPer100g = proteinPer100g; + this.fatPer100g = fatPer100g; + this.carbsPer100g = carbsPer100g; + } + public Ingredient( + String name, + double proteinPer100g, + double fatPer100g, + double carbsPer100g) { + this.name = name; this.proteinPer100g = proteinPer100g; this.fatPer100g = fatPer100g; diff --git a/commons/src/main/java/commons/Recipe.java b/commons/src/main/java/commons/Recipe.java index ffe87cb..1ae1f83 100644 --- a/commons/src/main/java/commons/Recipe.java +++ b/commons/src/main/java/commons/Recipe.java @@ -15,16 +15,17 @@ */ package commons; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import jakarta.persistence.Entity; import jakarta.persistence.CollectionTable; import jakarta.persistence.Column; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.OrderColumn; -import jakarta.persistence.GeneratedValue; import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OrderColumn; +import jakarta.persistence.Table; import java.util.ArrayList; @@ -59,11 +60,11 @@ public class Recipe { // | 1 (Steak) | 40g pepper | // | 1 (Steak) | Meat | // |----------------------------------| - @ElementCollection + @OneToMany @CollectionTable(name = "recipe_ingredients", joinColumns = @JoinColumn(name = "recipe_id")) @Column(name = "ingredient") // TODO: Replace String with Embeddable Ingredient Class - private List ingredients = new ArrayList<>(); + private List ingredients = new ArrayList<>(); // Creates another table named recipe_preparation which stores: // recipe_preparation(recipe_id -> recipes(id), preparation_step, step_order). @@ -94,7 +95,7 @@ public class Recipe { } // TODO: Replace String with Embeddable Ingredient Class for ingredients - public Recipe(Long id, String name, List ingredients, List preparationSteps) { + public Recipe(Long id, String name, List ingredients, List preparationSteps) { // Not used by JPA/Spring this.id = id; this.name = name; @@ -119,14 +120,14 @@ public class Recipe { } // TODO: Replace String with Embeddable Ingredient Class - public List getIngredients() { + public List getIngredients() { // Disallow modifying the returned list. // You can still copy it with List.copyOf(...) return Collections.unmodifiableList(ingredients); } // TODO: Replace String with Embeddable Ingredient Class - public void setIngredients(List ingredients) { + public void setIngredients(List ingredients) { this.ingredients = ingredients; } diff --git a/commons/src/main/java/commons/RecipeIngredient.java b/commons/src/main/java/commons/RecipeIngredient.java index 8b18d41..8569334 100644 --- a/commons/src/main/java/commons/RecipeIngredient.java +++ b/commons/src/main/java/commons/RecipeIngredient.java @@ -1,77 +1,60 @@ package commons; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +/** + * The base RecipeIngredient class, holding a reference to the + * {@link Ingredient Ingredient} it has an (in)formal amount + * linked to. + * It uses {@link JsonSubTypes JsonSubTypes} such that the + * Jackson framework can conveniently decode JSON data sent + * by client, which could be either a formal or vague + * ingredient. + * @see Ingredient + */ @Entity -public class RecipeIngredient { - +@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, // identifies subtype by logical name + include = JsonTypeInfo.As.PROPERTY, + property = "type" // JSON property carrying the subtype id +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = FormalIngredient.class, name = "formal"), + @JsonSubTypes.Type(value = VagueIngredient.class, name = "vague") +}) +public abstract class RecipeIngredient { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) + @GeneratedValue(strategy = GenerationType.AUTO) public Long id; - // which recipe is used - @ManyToOne(optional = false) - @JoinColumn(name = "recipe_id") - public Recipe recipe; - - //which ingredient is used + /** + * Many-to-one: Many {@link RecipeIngredient RecipeIngredient} + * can use the same {@link Ingredient Ingredient} with varying + * amounts. This allows better ingredient collecting for + * nutrition view. + */ @ManyToOne(optional = false) @JoinColumn(name = "ingredient_id") public Ingredient ingredient; - public double amount; - - // store the unit name in the database - public String unitName; - @SuppressWarnings("unused") protected RecipeIngredient() { - // for sebastian + // for ORM } - public RecipeIngredient(Recipe recipe, //which recipe - Ingredient ingredient, // which ingredient - double amount, // the amount - String unit) { //gram liter etc - //store it im tha field - this.recipe = recipe; + public RecipeIngredient( + Ingredient ingredient) { + //store it in the field this.ingredient = ingredient; - this.amount = amount; - this.unitName = unit; - } - - // Convert unitName to Unit object so java can read it - public Unit getUnit() { - return switch (unitName) { - case "GRAM" -> Unit.GRAM; - case "KILOGRAM" -> Unit.KILOGRAM; - case "MILLILITER" -> Unit.MILLILITER; - case "LITER" -> Unit.LITER; - case "TABLESPOON" -> Unit.TABLESPOON; - case "TEASPOON" -> Unit.TEASPOON; - case "CUP" -> Unit.CUP; - case "PIECE" -> Unit.PIECE; - - case "PINCH" -> Unit.PINCH; - case "HANDFUL" -> Unit.HANDFUL; - case "TO_TASTE" -> Unit.TO_TASTE; - - default -> null; - }; - } - - - - public double amountInBaseUnit() { - Unit unit = getUnit(); - if (unit == null || !unit.isFormal() || unit.conversionFactor <= 0) { - return 0.0; - } - return amount * unit.conversionFactor; } } diff --git a/commons/src/main/java/commons/Unit.java b/commons/src/main/java/commons/Unit.java index 16627f4..89146f6 100644 --- a/commons/src/main/java/commons/Unit.java +++ b/commons/src/main/java/commons/Unit.java @@ -1,33 +1,30 @@ package commons; -//what is a record class and why is it recommended +import java.util.Optional; + +/** + * A unit enum that holds some values. + * A {@link Unit Unit} enum holds primarily formal mass-units + * Except for an informal unit that always has a factor of zero + * is made for user input purposes. + * @see VagueIngredient + * @see FormalIngredient + */ public enum Unit { - //formal units - //weight units - GRAM("GRAM", true, 1.0), - KILOGRAM("KILOGRAM", true, 1000.0 ), + // Mass units with their absolute scale + GRAMME("g", true, 1.0), + KILOGRAMME("kg", true, 1_000.0 ), + TONNE("t", true, 1_000_000.0), - //volume units - MILLILITER("MILLILITER",true, 1.0), - LITER("LITER", true, 1000.0), - TABLESPOON("TABLESPOON",true, 15.0), - TEASPOON("TEASPOON", true, 5), - CUP ("CUP", true, 240.0), + // A special informal unit + INFORMAL("", false, 0.0); - //piece should be a formal unit to converse for portions like 3eggs can become 1,5 eggs this way - PIECE("PIECE", true, 1.0), - - //informal units - PINCH("PINCH", false, 0.0), - HANDFUL("HANDFUL", false, 0.0), - TO_TASTE("TO_TASTE", false, 0.0); - - public final String name; + public final String suffix; public final boolean formal; public final double conversionFactor; - Unit(String name, boolean formal, double conversionFactor) { - this.name = name; + Unit(String suffix, boolean formal, double conversionFactor) { + this.suffix = suffix; this.formal = formal; this.conversionFactor = conversionFactor; } @@ -36,12 +33,16 @@ public enum Unit { return formal; } - public boolean isInformal() { - return !formal; - } //for oskar - @Override public String toString() { - return name; + return suffix; + } + public static Optional fromString(String suffix) { + for (Unit unit : Unit.values()) { + if (unit.suffix != null && unit.suffix.equals(suffix)) { + return Optional.of(unit); + } + } + return Optional.empty(); } } diff --git a/commons/src/main/java/commons/VagueIngredient.java b/commons/src/main/java/commons/VagueIngredient.java new file mode 100644 index 0000000..9eecc12 --- /dev/null +++ b/commons/src/main/java/commons/VagueIngredient.java @@ -0,0 +1,34 @@ +package commons; + +import jakarta.persistence.Entity; + +/** + * A vague ingredient inheriting from {@link RecipeIngredient RecipeIngredient}. + * It has a {@link String String} description that describes an informal quantity. + * The vague ingredient renders as its descriptor and its ingredient name + * conjugated by a space via {@link VagueIngredient#toString() toString()} method. + * @see RecipeIngredient + */ +@Entity +public class VagueIngredient extends RecipeIngredient { + private String description; + public VagueIngredient() {} + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public VagueIngredient(Ingredient ingredient, String description) { + super(ingredient); + this.description = description; + } + + @Override + public String toString() { + return description + " " + ingredient.name; + } +}