refactor: revised class layout for JPA entities
This commit is contained in:
parent
9958ae5fbe
commit
88a85f2e04
6 changed files with 184 additions and 97 deletions
53
commons/src/main/java/commons/FormalIngredient.java
Normal file
53
commons/src/main/java/commons/FormalIngredient.java
Normal file
|
|
@ -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 = 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,7 +20,8 @@ public class Ingredient {
|
||||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
public long id;
|
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;
|
public String name;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -35,10 +36,24 @@ public class Ingredient {
|
||||||
|
|
||||||
public Ingredient() {}
|
public Ingredient() {}
|
||||||
|
|
||||||
public Ingredient(String name,
|
public Ingredient(
|
||||||
double proteinPer100g,
|
Long id,
|
||||||
double fatPer100g,
|
String name,
|
||||||
double carbsPer100g) {
|
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.name = name;
|
||||||
this.proteinPer100g = proteinPer100g;
|
this.proteinPer100g = proteinPer100g;
|
||||||
this.fatPer100g = fatPer100g;
|
this.fatPer100g = fatPer100g;
|
||||||
|
|
|
||||||
|
|
@ -15,16 +15,17 @@
|
||||||
*/
|
*/
|
||||||
package commons;
|
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.CollectionTable;
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
import jakarta.persistence.JoinColumn;
|
|
||||||
import jakarta.persistence.OrderColumn;
|
|
||||||
import jakarta.persistence.GeneratedValue;
|
|
||||||
import jakarta.persistence.ElementCollection;
|
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;
|
import java.util.ArrayList;
|
||||||
|
|
@ -59,11 +60,11 @@ public class Recipe {
|
||||||
// | 1 (Steak) | 40g pepper |
|
// | 1 (Steak) | 40g pepper |
|
||||||
// | 1 (Steak) | Meat |
|
// | 1 (Steak) | Meat |
|
||||||
// |----------------------------------|
|
// |----------------------------------|
|
||||||
@ElementCollection
|
@OneToMany
|
||||||
@CollectionTable(name = "recipe_ingredients", joinColumns = @JoinColumn(name = "recipe_id"))
|
@CollectionTable(name = "recipe_ingredients", joinColumns = @JoinColumn(name = "recipe_id"))
|
||||||
@Column(name = "ingredient")
|
@Column(name = "ingredient")
|
||||||
// TODO: Replace String with Embeddable Ingredient Class
|
// TODO: Replace String with Embeddable Ingredient Class
|
||||||
private List<String> ingredients = new ArrayList<>();
|
private List<RecipeIngredient> ingredients = new ArrayList<>();
|
||||||
|
|
||||||
// Creates another table named recipe_preparation which stores:
|
// Creates another table named recipe_preparation which stores:
|
||||||
// recipe_preparation(recipe_id -> recipes(id), preparation_step, step_order).
|
// 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
|
// TODO: Replace String with Embeddable Ingredient Class for ingredients
|
||||||
public Recipe(Long id, String name, List<String> ingredients, List<String> preparationSteps) {
|
public Recipe(Long id, String name, List<RecipeIngredient> ingredients, List<String> preparationSteps) {
|
||||||
// Not used by JPA/Spring
|
// Not used by JPA/Spring
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
@ -119,14 +120,14 @@ public class Recipe {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Replace String with Embeddable Ingredient Class
|
// TODO: Replace String with Embeddable Ingredient Class
|
||||||
public List<String> getIngredients() {
|
public List<RecipeIngredient> getIngredients() {
|
||||||
// Disallow modifying the returned list.
|
// Disallow modifying the returned list.
|
||||||
// You can still copy it with List.copyOf(...)
|
// You can still copy it with List.copyOf(...)
|
||||||
return Collections.unmodifiableList(ingredients);
|
return Collections.unmodifiableList(ingredients);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Replace String with Embeddable Ingredient Class
|
// TODO: Replace String with Embeddable Ingredient Class
|
||||||
public void setIngredients(List<String> ingredients) {
|
public void setIngredients(List<RecipeIngredient> ingredients) {
|
||||||
this.ingredients = ingredients;
|
this.ingredients = ingredients;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,77 +1,60 @@
|
||||||
package commons;
|
package commons;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import jakarta.persistence.GeneratedValue;
|
||||||
import jakarta.persistence.GenerationType;
|
import jakarta.persistence.GenerationType;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Inheritance;
|
||||||
|
import jakarta.persistence.InheritanceType;
|
||||||
import jakarta.persistence.JoinColumn;
|
import jakarta.persistence.JoinColumn;
|
||||||
import jakarta.persistence.ManyToOne;
|
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
|
@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
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
public Long id;
|
public Long id;
|
||||||
|
|
||||||
// which recipe is used
|
/**
|
||||||
@ManyToOne(optional = false)
|
* Many-to-one: Many {@link RecipeIngredient RecipeIngredient}
|
||||||
@JoinColumn(name = "recipe_id")
|
* can use the same {@link Ingredient Ingredient} with varying
|
||||||
public Recipe recipe;
|
* amounts. This allows better ingredient collecting for
|
||||||
|
* nutrition view.
|
||||||
//which ingredient is used
|
*/
|
||||||
@ManyToOne(optional = false)
|
@ManyToOne(optional = false)
|
||||||
@JoinColumn(name = "ingredient_id")
|
@JoinColumn(name = "ingredient_id")
|
||||||
public Ingredient ingredient;
|
public Ingredient ingredient;
|
||||||
|
|
||||||
public double amount;
|
|
||||||
|
|
||||||
// store the unit name in the database
|
|
||||||
public String unitName;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
protected RecipeIngredient() {
|
protected RecipeIngredient() {
|
||||||
// for sebastian
|
// for ORM
|
||||||
}
|
}
|
||||||
|
|
||||||
public RecipeIngredient(Recipe recipe, //which recipe
|
public RecipeIngredient(
|
||||||
Ingredient ingredient, // which ingredient
|
Ingredient ingredient) {
|
||||||
double amount, // the amount
|
//store it in the field
|
||||||
String unit) { //gram liter etc
|
|
||||||
//store it im tha field
|
|
||||||
this.recipe = recipe;
|
|
||||||
this.ingredient = ingredient;
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,30 @@
|
||||||
package commons;
|
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 {
|
public enum Unit {
|
||||||
//formal units
|
// Mass units with their absolute scale
|
||||||
//weight units
|
GRAMME("g", true, 1.0),
|
||||||
GRAM("GRAM", true, 1.0),
|
KILOGRAMME("kg", true, 1_000.0 ),
|
||||||
KILOGRAM("KILOGRAM", true, 1000.0 ),
|
TONNE("t", true, 1_000_000.0),
|
||||||
|
|
||||||
//volume units
|
// A special informal unit
|
||||||
MILLILITER("MILLILITER",true, 1.0),
|
INFORMAL("<NONE>", false, 0.0);
|
||||||
LITER("LITER", true, 1000.0),
|
|
||||||
TABLESPOON("TABLESPOON",true, 15.0),
|
|
||||||
TEASPOON("TEASPOON", true, 5),
|
|
||||||
CUP ("CUP", true, 240.0),
|
|
||||||
|
|
||||||
//piece should be a formal unit to converse for portions like 3eggs can become 1,5 eggs this way
|
public final String suffix;
|
||||||
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 boolean formal;
|
public final boolean formal;
|
||||||
public final double conversionFactor;
|
public final double conversionFactor;
|
||||||
|
|
||||||
Unit(String name, boolean formal, double conversionFactor) {
|
Unit(String suffix, boolean formal, double conversionFactor) {
|
||||||
this.name = name;
|
this.suffix = suffix;
|
||||||
this.formal = formal;
|
this.formal = formal;
|
||||||
this.conversionFactor = conversionFactor;
|
this.conversionFactor = conversionFactor;
|
||||||
}
|
}
|
||||||
|
|
@ -36,12 +33,16 @@ public enum Unit {
|
||||||
return formal;
|
return formal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInformal() {
|
|
||||||
return !formal;
|
|
||||||
} //for oskar
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return name;
|
return suffix;
|
||||||
|
}
|
||||||
|
public static Optional<Unit> fromString(String suffix) {
|
||||||
|
for (Unit unit : Unit.values()) {
|
||||||
|
if (unit.suffix != null && unit.suffix.equals(suffix)) {
|
||||||
|
return Optional.of(unit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
34
commons/src/main/java/commons/VagueIngredient.java
Normal file
34
commons/src/main/java/commons/VagueIngredient.java
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue