Skip to content
This repository was archived by the owner on Jul 24, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 164 additions & 66 deletions src/main/java/shef/data/Recipe.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

import java.util.Collection;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.LinkedHashSet;
Expand All @@ -30,22 +32,67 @@ public class Recipe {

private static final Logger LOGGER = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);

private Entity entity;
private String key;
private String name;
private String description;
private double time;
private double servings;
private String imageKey;
private Set<String> tags;
private Set<Ingredient> ingredients;
private Set<String> equipment;
private List<Step> steps;
private Set<String> tags = new LinkedHashSet<>();
private Set<Ingredient> ingredients = new LinkedHashSet<>();
private Set<String> equipment = new LinkedHashSet<>();
private List<String> steps = new LinkedList<>();
private Set<String> searchStrings = new HashSet<>();
private Set<SpinOff> spinOffs;
private long timestamp;

/**
* Creates a Recipe from a Datastore entity.
* Servlets can cleanly create JSON-ready Recipes by passing a recipe entity into this constructor.
*/
public Recipe(Entity recipeEntity) {
this.entity = recipeEntity;
this.name = (String) recipeEntity.getProperty("name");
this.description = (String) recipeEntity.getProperty("description");
this.time = (double) recipeEntity.getProperty("time");
this.servings = (double) recipeEntity.getProperty("servings");
this.imageKey = (String) recipeEntity.getProperty("imageKey");
this.tags = new LinkedHashSet((ArrayList<String>) recipeEntity.getProperty("tags"));
this.ingredients = getIngredientsFromEntity((Collection<EmbeddedEntity>) recipeEntity.getProperty("ingredients"));
this.equipment = new LinkedHashSet((ArrayList<String>) recipeEntity.getProperty("equipment"));
this.steps = new LinkedList((ArrayList<String>) recipeEntity.getProperty("steps"));
this.timestamp = (long) recipeEntity.getProperty("timestamp");
}

/**
* Copy constructor called when creating spin-offs.
* Creates a recipe from a map of parameters.
* This constructor allows servlets to cleanly create recipes by passing in an HTTPRequest's parameter map.
*/
public Recipe(Map<String, String[]> parameterMap) {
// First, store the parameter data in a Recipe object.
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
handleParameter(entry.getKey(), entry.getValue());
}
// Then build a recipe entity that is ready to be saved in Datastore.
this.entity = buildEntity();
}

/** Constructor called when creating a recipe to display on the recipe feed. */
public Recipe(String key, String name, String description, Set<String> tags, Set<Ingredient> ingredients, List<String> steps, long timestamp) {
this.key = key;
this.name = name;
this.tags = tags;
this.ingredients = ingredients;
this.description = description;
this.tags = tags;
this.ingredients = ingredients;
this.steps = steps;
this.timestamp = timestamp;
this.spinOffs = new HashSet<>();
}

/** Copy constructor called to create a spin-offs. */
public Recipe(Recipe recipe) {
this.name = recipe.name;
this.description = recipe.description;
Expand All @@ -57,7 +104,7 @@ public Recipe(Recipe recipe) {
}

/** Default constructor called when creating a new recipe. */
public Recipe(String name, String description, Set<String> tags, Set<Ingredient> ingredients, List<Step> steps, long timestamp) {
public Recipe(String name, String description, Set<String> tags, Set<Ingredient> ingredients, List<String> steps, long timestamp) {
this.name = name;
this.description = description;
this.tags = tags;
Expand All @@ -68,34 +115,74 @@ public Recipe(String name, String description, Set<String> tags, Set<Ingredient>
}

/**
* Creates a Recipe from a Datastore entity.
* This constructor is used to easily convert an Entity into an object, which can be sent as JSON.
* A helper method that sets the Recipe's fields.
* It allows iteration over the parameter map by specifically setting each field with different behavior.
*/
public Recipe(Entity recipeEntity) {
this.name = (String) recipeEntity.getProperty("name");
this.description = (String) recipeEntity.getProperty("description");
this.time = (double) recipeEntity.getProperty("time");
this.servings = (double) recipeEntity.getProperty("servings");
this.imageKey = (String) recipeEntity.getProperty("imageKey");
this.tags = getTagsFromEntity((Collection<EmbeddedEntity>) recipeEntity.getProperty("tags"));
this.ingredients = getIngredientsFromEntity((Collection<EmbeddedEntity>) recipeEntity.getProperty("ingredients"));
this.equipment = getEquipmentFromEntity((Collection<EmbeddedEntity>) recipeEntity.getProperty("equipment"));
this.steps = getStepsFromEntity((Collection<EmbeddedEntity>) recipeEntity.getProperty("steps"));
this.timestamp = (long) recipeEntity.getProperty("timestamp");
private void handleParameter(String name, String[] values) {
// No values associated with this parameter.
if (values == null || values.length < 1 || values[0] == null || values[0].equals("")) {
return;
}

// Handle each parameter with a different case block for each field's specific behavior.
switch(name) {
case "name":
this.name = values[0];
addToSearchStrings(this.name);
break;
case "description":
this.description = values[0];
addToSearchStrings(this.description);
break;
case "imageKey":
this.imageKey = values[0];
break;
case "time":
this.time = Double.parseDouble(values[0]);
break;
case "servings":
this.servings = Double.parseDouble(values[0]);
break;
default:
// Handle, tags, ingredients, equipment, and steps by adding them to the Recipe's lists.
if (name.contains("tag")) {
addTag(values[0]);
addToSearchStrings(values[0]);
} else if (name.contains("ingredient")) {
double amount = Double.parseDouble(values[0]);
addIngredient(new Ingredient(amount, values[1], values[2]));
addToSearchStrings(values[2]);
} else if (name.contains("equipment")) {
addEquipment(values[0]);
} else if (name.contains("step")) {
appendStep(values[0]);
}
}
}

/** Constructor called when creating a recipe to display on the recipe feed. */
public Recipe(String key, String name, String description, Set<String> tags, Set<Ingredient> ingredients, List<Step> steps, long timestamp) {
this.key = key;
this.name = name;
this.tags = tags;
this.ingredients = ingredients;
this.description = description;
this.tags = tags;
this.ingredients = ingredients;
this.steps = steps;
this.timestamp = timestamp;
this.spinOffs = new HashSet<>();
/**
* Builds a recipe entity from a Recipe object.
* Automatically called by the Recipe constructor that builds a Recipe from a parameter map.
*/
public Entity buildEntity() {
Entity recipeEntity = new Entity("Recipe");
recipeEntity.setProperty("name", name);
recipeEntity.setProperty("time", time);
recipeEntity.setProperty("servings", servings);
recipeEntity.setProperty("imageKey", imageKey);
recipeEntity.setProperty("description", description);
recipeEntity.setProperty("tags", tags);
recipeEntity.setProperty("ingredients", embeddedIngredients());
recipeEntity.setProperty("equipment", equipment);
recipeEntity.setProperty("steps", steps);
recipeEntity.setProperty("search-strings", getSearchStrings());
recipeEntity.setProperty("timestamp", System.currentTimeMillis());
return recipeEntity;
}

/** Gets the recipe's corresponding Datastore entity. */
public Entity getEntity() {
return entity;
}

/** Gets the recipe's name. */
Expand Down Expand Up @@ -133,6 +220,14 @@ public Set<SpinOff> getSpinOffs() {
return spinOffs;
}

/**
* Returns a recipe's search strings.
* Return type is an ArrayList because Filters and CompositeFilters require lists.
*/
public ArrayList<String> getSearchStrings() {
return new ArrayList<String>(searchStrings);
}

/** Adds a tag to the recipe. */
public void addTag(String tag) {
tags.add(tag);
Expand All @@ -143,11 +238,28 @@ public void addIngredient(Ingredient ingredient) {
ingredients.add(ingredient);
}

/** Adds equipment to the recipe. */
public void addEquipment(String newEquipment) {
equipment.add(newEquipment);
}


/** Adds a spin-off to the recipe. */
public void addSpinOff(SpinOff spinOff) {
spinOffs.add(spinOff);
}

/**
* Adds strings to a recipe's search strings.
* Each token in the input string is upper-cased and added as a separate search string.
*/
public void addToSearchStrings(String string) {
String[] tokens = string.split(" ");
for (String token : tokens) {
searchStrings.add(token.toUpperCase());
}
}

/** Removes a tag from the recipe. */
public void removeTag(String tag) {
tags.remove(tag);
Expand All @@ -164,7 +276,7 @@ public void removeSpinOff(SpinOff spinOff) {
}

/** Appends a new step to a recipe's list of steps. */
public void appendStep(Step newStep) {
public void appendStep(String newStep) {
steps.add(newStep);
}

Expand All @@ -174,7 +286,7 @@ public void appendStep(Step newStep) {
* @param newStep The new step to insert.
* @throws IndexOutOfBoundsException Thrown if position is out of bounds of the recipe's list of steps.
*/
public void addStep(int position, Step newStep) throws IndexOutOfBoundsException {
public void addStep(int position, String newStep) throws IndexOutOfBoundsException {
if (position == steps.size()) {
appendStep(newStep);
} else if (!isValidStepPosition(position)) {
Expand All @@ -191,7 +303,7 @@ public void addStep(int position, Step newStep) throws IndexOutOfBoundsException
* @param newStep The replacing step.
* @throws IndexOutOfBoundsException Thrown if position is out of bounds of the recipe's list of steps.
*/
public void setStep(int position, Step newStep) throws IndexOutOfBoundsException {
public void setStep(int position, String newStep) throws IndexOutOfBoundsException {
if (!isValidStepPosition(position)) {
handleStepException("Position " + position + " is out of bounds [0, " + (steps.size() - 1) + "]. " +
"Failed to set step \"" + newStep + "\".");
Expand All @@ -215,7 +327,7 @@ public void removeStep(int position) throws IndexOutOfBoundsException {
}

/** Returns the recipe's list of steps. */
public List<Step> getSteps() {
public List<String> getSteps() {
return steps;
}

Expand All @@ -225,8 +337,8 @@ public String toString() {
String str = String.format("\nName: %s", name);
str += String.format("\nDescription: %s", description);
str += "\nSteps:\n";
for (Step step : steps) {
str += String.format("\t%s\n", step.getInstruction());
for (String step : steps) {
str += String.format("\t%s\n", step);
}
return str;
}
Expand All @@ -246,15 +358,6 @@ protected boolean isValidStepPosition(int position) {
return position >= 0 && position < steps.size();
}

/** Returns the tags of an EmbeddedEntity as a Set. */
private Set<String> getTagsFromEntity(Collection<EmbeddedEntity> entityTags) {
Set<String> tagsSet = new HashSet<>();
for (EmbeddedEntity tag : entityTags) {
tagsSet.add((String) tag.getProperty("tag"));
}
return tagsSet;
}

/** Returns the ingredients of an EmbeddedEntity as a Set. */
private Set<Ingredient> getIngredientsFromEntity(Collection<EmbeddedEntity> entityIngredients) {
Set<Ingredient> ingredientsSet = new HashSet<>();
Expand All @@ -264,22 +367,17 @@ private Set<Ingredient> getIngredientsFromEntity(Collection<EmbeddedEntity> enti
return ingredientsSet;
}

/** Returns the equipment of an EmbeddedEntity as a Set. */
private Set<String> getEquipmentFromEntity(Collection<EmbeddedEntity> entityEquipment) {
Set<String> equipmentSet = new HashSet<>();
for (EmbeddedEntity equipment : entityEquipment) {
equipmentSet.add((String) equipment.getProperty("equipment"));
}
return equipmentSet;
}

/** Returns the steps of an EmbeddedEntity as a List. */
private List<Step> getStepsFromEntity(Collection<EmbeddedEntity> entitySteps) {
List<Step> stepsList = new LinkedList<>();
for (EmbeddedEntity step : entitySteps) {
stepsList.add(new Step((String) step.getProperty("step")));
/** Converts a set of Ingredients into a Datastore-ready Collection of EmbeddedEntities. */
private Collection<EmbeddedEntity> embeddedIngredients() {
Collection<EmbeddedEntity> embeddedIngredients = new LinkedHashSet<>();
for (Ingredient ingredient : ingredients) {
EmbeddedEntity ingredientEntity = new EmbeddedEntity();
ingredientEntity.setProperty("amount", ingredient.amount());
ingredientEntity.setProperty("unit", ingredient.unit());
ingredientEntity.setProperty("name", ingredient.name());
embeddedIngredients.add(ingredientEntity);
}
return stepsList;
return embeddedIngredients;
}

private void handleStepException(String exceptionText) throws IndexOutOfBoundsException {
Expand All @@ -291,12 +389,12 @@ private static boolean equals(Recipe a, Recipe b) {
if (a.steps.size() != b.steps.size() || !a.name.equals(b.name) || !a.description.equals(b.description)) {
return false;
}
Iterator<Step> aSteps = a.steps.iterator();
Iterator<Step> bSteps = b.steps.iterator();
Iterator<String> aSteps = a.steps.iterator();
Iterator<String> bSteps = b.steps.iterator();
while (aSteps.hasNext() && bSteps.hasNext()) {
Step aStep = aSteps.next();
Step bStep = bSteps.next();
if (!aStep.getInstruction().equals(bStep.getInstruction())) {
String aStep = aSteps.next();
String bStep = bSteps.next();
if (!aStep.equals(bStep)) {
return false;
}
}
Expand Down
39 changes: 0 additions & 39 deletions src/main/java/shef/data/Step.java

This file was deleted.

1 change: 0 additions & 1 deletion src/main/java/shef/servlets/DisplayRecipesServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import java.util.Set;
import java.util.stream.Collectors;
import shef.data.Recipe;
import shef.data.Step;
import shef.servlets.NewRecipeServlet;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
Expand Down
Loading