diff --git a/src/main/java/app/MainNoteApplication.java b/src/main/java/app/MainNoteApplication.java
deleted file mode 100644
index c37860156..000000000
--- a/src/main/java/app/MainNoteApplication.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package app;
-
-import data_access.DBNoteDataAccessObject;
-import use_case.note.NoteDataAccessInterface;
-
-/**
- * An application where we can view and add to a note stored by a user.
- *
- * This is a minimal example of using the password-protected user API from lab 5,
- * but demonstrating the endpoint allowing you to store an arbitrary JSON object.
- * This functionality could be used in any project where your team wants to persist
- * data which is then accessible across devices.
- * The code is intentionally somewhat incomplete to leave work to be done if your
- * team were to choose to work on a project which would require similar functionality.
- * For example, we have intentionally not created a full "Note" entity here, but
- * rather just represented a note as a string.
- *
- * The ViewManager code has also been removed, since this minimal program only requires a single
- * view. Your team may wish to bring back the ViewManager or make your own implementation of supporting
- * switching between views depending on your project.
- */
-public class MainNoteApplication {
-
- /**
- * The main entry point of the application.
- *
- * The program will show you the note currently saved in the system.
- * You are able to edit it and then save it to the system. You can refresh
- * to update the note to reflect what was saved most recently. This
- * uses the API from lab, so there is one database storing the note,
- * which means that if anyone updates the note, that is what you will
- * see when you refresh.
- *
- * You can generalize the code to allow you to
- * specify which "user" to save the note for, which will allow your team
- * to store information specific to your team which is password-protected.
- * The username and password used in this application are currently for
- * user jonathan_calver2, but you can change that. As you did in lab 3,
- * you will likely want to store password information locally rather than
- * in your repo. Or you can require the user to enter their credentials
- * in your application; it just depends on what your program's main
- * functionality.
- *
- * @param args commandline arguments are ignored
- */
- public static void main(String[] args) {
-
- // create the data access and inject it into our builder!
- final NoteDataAccessInterface noteDataAccess = new DBNoteDataAccessObject();
-
- final NoteAppBuilder builder = new NoteAppBuilder();
- builder.addNoteDAO(noteDataAccess)
- .addNoteView()
- .addNoteUseCase().build().setVisible(true);
- }
-}
diff --git a/src/main/java/app/MainRecipeApplication.java b/src/main/java/app/MainRecipeApplication.java
new file mode 100644
index 000000000..eb462f79a
--- /dev/null
+++ b/src/main/java/app/MainRecipeApplication.java
@@ -0,0 +1,42 @@
+package app;
+
+import data_access.RecipeSearchDataAccessObject;
+import data_access.SearchWithRestrictionDataAccessObject;
+
+/**
+ * An application where users can search for recipes based on ingredients.
+ *
+ * This application allows users to input ingredients and search for matching recipes
+ * using the Edamam Recipe Search API. Users can view recipe details including
+ * ingredients and instructions.
+ *
+ */
+public class MainRecipeApplication {
+ /**
+ * Main method for the Recipe Application.
+ * @param args The command line arguments
+ */
+ public static void main(String[] args) {
+ // Create the data access object for recipe search
+ final RecipeSearchDataAccessObject recipeSearchDataAccessObject = new RecipeSearchDataAccessObject();
+ final SearchWithRestrictionDataAccessObject searchWithRestrictionDataAccessObject = new SearchWithRestrictionDataAccessObject();
+
+ // Create and configure the application using the builder
+ final RecipeAppBuilder builder = new RecipeAppBuilder();
+
+ // Build the application with correct order
+ builder.addRecipeSearchAPI(recipeSearchDataAccessObject, searchWithRestrictionDataAccessObject)
+ .addMealPlanningView()
+ .addMealPlanningUseCase()
+ .addNutritionAnalysisView()
+ .addRecipeSearchView()
+ .addRecipeSearchUseCase()
+ .addRestrictionSearchUseCase()
+ .addNutritionAnalysisUseCase()
+ .addServingAdjustUseCase()
+ .addFrontpageView();
+
+ // Build and show the frame
+ builder.build();
+ }
+}
diff --git a/src/main/java/app/NoteAppBuilder.java b/src/main/java/app/NoteAppBuilder.java
deleted file mode 100644
index a68cb9ad6..000000000
--- a/src/main/java/app/NoteAppBuilder.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package app;
-
-import javax.swing.JFrame;
-import javax.swing.WindowConstants;
-
-import interface_adapter.note.NoteController;
-import interface_adapter.note.NotePresenter;
-import interface_adapter.note.NoteViewModel;
-import use_case.note.NoteDataAccessInterface;
-import use_case.note.NoteInteractor;
-import use_case.note.NoteOutputBoundary;
-import view.NoteView;
-
-/**
- * Builder for the Note Application.
- */
-public class NoteAppBuilder {
- public static final int HEIGHT = 300;
- public static final int WIDTH = 400;
- private NoteDataAccessInterface noteDAO;
- private NoteViewModel noteViewModel = new NoteViewModel();
- private NoteView noteView;
- private NoteInteractor noteInteractor;
-
- /**
- * Sets the NoteDAO to be used in this application.
- * @param noteDataAccess the DAO to use
- * @return this builder
- */
- public NoteAppBuilder addNoteDAO(NoteDataAccessInterface noteDataAccess) {
- noteDAO = noteDataAccess;
- return this;
- }
-
- /**
- * Creates the objects for the Note Use Case and connects the NoteView to its
- * controller.
- * This method must be called after addNoteView!
- * @return this builder
- * @throws RuntimeException if this method is called before addNoteView
- */
- public NoteAppBuilder addNoteUseCase() {
- final NoteOutputBoundary noteOutputBoundary = new NotePresenter(noteViewModel);
- noteInteractor = new NoteInteractor(
- noteDAO, noteOutputBoundary);
-
- final NoteController controller = new NoteController(noteInteractor);
- if (noteView == null) {
- throw new RuntimeException("addNoteView must be called before addNoteUseCase");
- }
- noteView.setNoteController(controller);
- return this;
- }
-
- /**
- * Creates the NoteView and underlying NoteViewModel.
- * @return this builder
- */
- public NoteAppBuilder addNoteView() {
- noteViewModel = new NoteViewModel();
- noteView = new NoteView(noteViewModel);
- return this;
- }
-
- /**
- * Builds the application.
- * @return the JFrame for the application
- */
- public JFrame build() {
- final JFrame frame = new JFrame();
- frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
- frame.setTitle("Note Application");
- frame.setSize(WIDTH, HEIGHT);
-
- frame.add(noteView);
-
- // refresh so that the note will be visible when we start the program
- noteInteractor.executeRefresh();
-
- return frame;
-
- }
-}
diff --git a/src/main/java/app/RecipeAppBuilder.java b/src/main/java/app/RecipeAppBuilder.java
new file mode 100644
index 000000000..9af1effd0
--- /dev/null
+++ b/src/main/java/app/RecipeAppBuilder.java
@@ -0,0 +1,270 @@
+package app;
+
+import javax.swing.*;
+import data_access.*;
+
+import interface_adapter.nutrition_analysis.NutritionAnalysisController;
+import interface_adapter.nutrition_analysis.NutritionAnalysisPresenter;
+import interface_adapter.nutrition_analysis.NutritionAnalysisViewModel;
+import interface_adapter.recipe_search.*;
+import interface_adapter.meal_planning.*;
+import interface_adapter.search_with_restriction.RestrictionController;
+import interface_adapter.search_with_restriction.RestrictionPresenter;
+import interface_adapter.search_with_restriction.RestrictionViewModel;
+import interface_adapter.serving_adjust.ServingAdjustController;
+import interface_adapter.serving_adjust.ServingAdjustPresenter;
+import interface_adapter.serving_adjust.ServingAdjustViewModel;
+import use_case.nutrition_analysis.NutritionAnalysis;
+import use_case.nutrition_analysis.NutritionAnalysisInteractor;
+import use_case.recipe_search.*;
+import use_case.meal_planning.*;
+import use_case.search_with_restriction.RecipeSearchWithRestrictionInteractor;
+import use_case.search_with_restriction.SearchWithRestrictionInputBoundary;
+import use_case.serving_adjust.ServingAdjustInputBoundary;
+import use_case.serving_adjust.ServingAdjustInteractor;
+import use_case.serving_adjust.ServingAdjustOutputBoundary;
+import view.*;
+
+/**
+ * Builder class for the RecipeApp.
+ */
+public class RecipeAppBuilder {
+ public static final int HEIGHT = 600;
+ public static final int WIDTH = 800;
+
+ private RecipeSearchDataAccessObject recipeSearchDataAccessObject;
+ private SearchWithRestrictionDataAccessObject searchWithRestrictionDataAccessObject;
+ private SavedRecipesDataAccessInterface savedRecipesDataAccessInterface;
+ private MealPlanningDataAccessInterface mealPlanningDataAccessInterface;
+ private NutritionAnalysisDataAccessObject nutritionAnalysisDataAccess;
+
+ private RecipeSearchViewModel recipeSearchViewModel;
+ private RestrictionViewModel restrictionViewModel;
+ private MealPlanningViewModel mealPlanningViewModel;
+ private NutritionAnalysisViewModel nutritionAnalysisViewModel;
+
+ private FrontPageView frontPageView;
+ private RecipeSearchView recipeSearchView;
+ private MealPlanningView mealPlanningView;
+ private NutritionAnalysisView nutritionAnalysisView;
+
+ private RecipeSearchInputBoundary recipeSearchInputBoundaryUseCase;
+ private SearchWithRestrictionInputBoundary searchWithRestrictionInputBoundaryUseCase;
+ private MealPlanningInputBoundary mealPlanningInputBoundaryUseCase;
+ private NutritionAnalysis nutritionAnalysisUseCase;
+
+ /**
+ * Add the recipe search API.
+ * @param recipeSearchDataAccessObject The recipe search API
+ * @param searchWithRestrictionDataAccessObject The restriction search API
+ * @return The builder instance
+ */
+ public RecipeAppBuilder addRecipeSearchAPI(RecipeSearchDataAccessObject recipeSearchDataAccessObject, SearchWithRestrictionDataAccessObject searchWithRestrictionDataAccessObject) {
+ this.recipeSearchDataAccessObject = recipeSearchDataAccessObject;
+ this.searchWithRestrictionDataAccessObject = searchWithRestrictionDataAccessObject;
+ this.savedRecipesDataAccessInterface = new SavedRecipesDataAccessObject();
+ this.mealPlanningDataAccessInterface = new MealPlanningDataAccessObject(this.savedRecipesDataAccessInterface);
+ this.nutritionAnalysisDataAccess = new NutritionAnalysisDataAccessObject();
+ return this;
+ }
+
+ /**
+ * Add the meal planning view.
+ * @return The builder instance
+ */
+ public RecipeAppBuilder addMealPlanningView() {
+ mealPlanningViewModel = new MealPlanningViewModel();
+ mealPlanningView = new MealPlanningView(mealPlanningViewModel);
+ return this;
+ }
+
+ /**
+ * Add the meal planning view.
+ * @return The builder instance
+ */
+ public RecipeAppBuilder addNutritionAnalysisView() {
+ nutritionAnalysisViewModel = new NutritionAnalysisViewModel();
+ nutritionAnalysisView = new NutritionAnalysisView(nutritionAnalysisViewModel);
+ return this;
+ }
+
+ /**
+ * Add the recipe search view.
+ * @return The builder instance
+ */
+ public RecipeAppBuilder addRecipeSearchView() {
+ // Make sure mealPlanningView is created before this
+ if (mealPlanningView == null) {
+ throw new RuntimeException("addMealPlanningView must be called before addRecipeSearchView");
+ }
+ if (nutritionAnalysisView == null) {
+ throw new RuntimeException("addNutritionAnalysisView must be called before addRecipeSearchView");
+ }
+
+ recipeSearchViewModel = new RecipeSearchViewModel();
+ restrictionViewModel = new RestrictionViewModel();
+
+ RecipeSearchPresenter presenter = new RecipeSearchPresenter(recipeSearchViewModel);
+ recipeSearchInputBoundaryUseCase = new RecipeSearchInteractor(recipeSearchDataAccessObject, savedRecipesDataAccessInterface, presenter);
+ RecipeSearchController controller = new RecipeSearchController(recipeSearchInputBoundaryUseCase);
+
+ RestrictionPresenter restrictionPresenter = new RestrictionPresenter(restrictionViewModel);
+ searchWithRestrictionInputBoundaryUseCase = new RecipeSearchWithRestrictionInteractor(searchWithRestrictionDataAccessObject, restrictionPresenter);
+ RestrictionController restrictionController = new RestrictionController(searchWithRestrictionInputBoundaryUseCase);
+
+ ServingAdjustViewModel servingAdjustViewModel = new ServingAdjustViewModel();
+ ServingAdjustPresenter servingAdjustPresenter = new ServingAdjustPresenter(servingAdjustViewModel);
+ ServingAdjustInputBoundary servingAdjustUseCase = new ServingAdjustInteractor(servingAdjustPresenter);
+ ServingAdjustController servingAdjustController = new ServingAdjustController(servingAdjustUseCase);
+
+ recipeSearchView = new RecipeSearchView(recipeSearchViewModel, restrictionViewModel, controller,
+ restrictionController, servingAdjustController);
+ recipeSearchView.setMealPlanningView(mealPlanningView);
+ recipeSearchView.setNutritionAnalysisView(nutritionAnalysisView);
+
+ return this;
+ }
+
+ /**
+ * Add the front page view.
+ */
+ public void addFrontpageView() {
+ frontPageView = new FrontPageView(recipeSearchView, mealPlanningView);
+ }
+
+ /**
+ * Add the meal planning use case.
+ * @return The builder instance
+ */
+ public RecipeAppBuilder addMealPlanningUseCase() {
+ if (mealPlanningView == null) {
+ throw new RuntimeException("addMealPlanningView must be called before addMealPlanningUseCase");
+ }
+
+ MealPlanningPresenter presenter = new MealPlanningPresenter(mealPlanningViewModel);
+ mealPlanningInputBoundaryUseCase = new MealPlanningInteractor(
+ mealPlanningDataAccessInterface,
+ savedRecipesDataAccessInterface,
+ presenter
+ );
+ MealPlanningController controller = new MealPlanningController(mealPlanningInputBoundaryUseCase);
+ mealPlanningView.setController(controller);
+
+ return this;
+ }
+
+ /**
+ * Add the Nutrition Analysis Use Case.
+ * @return The builder instance
+ */
+ public RecipeAppBuilder addNutritionAnalysisUseCase() {
+ if (nutritionAnalysisView == null) {
+ throw new RuntimeException("addNutritionAnalysisView must be called before addNutritionAnalysisUseCase");
+ }
+
+ NutritionAnalysisPresenter presenter = new NutritionAnalysisPresenter(nutritionAnalysisViewModel);
+ nutritionAnalysisUseCase = new NutritionAnalysisInteractor(
+ nutritionAnalysisDataAccess,
+ presenter
+ );
+ NutritionAnalysisController controller = new NutritionAnalysisController(nutritionAnalysisUseCase);
+ recipeSearchView.setNutritionAnalysisController(controller);
+
+ return this;
+ }
+
+ /**
+ * Add the recipe search use case.
+ * @return The builder instance
+ */
+ public RecipeAppBuilder addRecipeSearchUseCase() {
+ if (recipeSearchView == null) {
+ throw new RuntimeException("addRecipeSearchView must be called before addRecipeSearchUseCase");
+ }
+
+ RecipeSearchPresenter presenter = new RecipeSearchPresenter(recipeSearchViewModel);
+ recipeSearchInputBoundaryUseCase = new RecipeSearchInteractor(
+ recipeSearchDataAccessObject,
+ savedRecipesDataAccessInterface,
+ presenter
+ );
+ RecipeSearchController controller = new RecipeSearchController(recipeSearchInputBoundaryUseCase);
+ recipeSearchView.setRecipeSearchController(controller);
+
+ return this;
+ }
+
+ /**
+ * Add the recipe search with restriction use case.
+ * @return The builder instance
+ */
+ public RecipeAppBuilder addRestrictionSearchUseCase() {
+ if (recipeSearchView == null) {
+ throw new RuntimeException("addRecipeSearchView must be called before addRecipeSearchUseCase");
+ }
+
+ RestrictionPresenter restrictionPresenter = new RestrictionPresenter(restrictionViewModel);
+ searchWithRestrictionInputBoundaryUseCase = new RecipeSearchWithRestrictionInteractor(
+ searchWithRestrictionDataAccessObject,
+ restrictionPresenter
+ );
+ RestrictionController controller = new RestrictionController(searchWithRestrictionInputBoundaryUseCase);
+ recipeSearchView.setRestrictionController(controller);
+
+ return this;
+ }
+
+ /**
+ * Add the serving adjust use case.
+ * @return The builder instance
+ */
+ public RecipeAppBuilder addServingAdjustUseCase() {
+ ServingAdjustViewModel servingAdjustViewModel = new ServingAdjustViewModel();
+
+ ServingAdjustOutputBoundary presenter = new ServingAdjustPresenter(servingAdjustViewModel);
+ ServingAdjustInputBoundary servingAdjustUseCase = new ServingAdjustInteractor(presenter);
+
+ ServingAdjustController controller = new ServingAdjustController(servingAdjustUseCase);
+
+ if (recipeSearchView == null) {
+ throw new RuntimeException("addRecipeSearchView must be called before addServingAdjustUseCase");
+ }
+
+ recipeSearchView.setServingAdjustController(controller);
+ recipeSearchView.setServingAdjustViewModel(servingAdjustViewModel);
+
+ return this;
+ }
+
+ /**
+ * Get the saved recipes data access.
+ * @return The saved recipes data access
+ */
+ public SavedRecipesDataAccessInterface getSavedRecipesDataAccess() {
+ return savedRecipesDataAccessInterface;
+ }
+
+ /**
+ * Get the Nutrition Analysis data access.
+ * @return The Nutrition Analysis data access
+ */
+ public NutritionAnalysisDataAccessObject getNutritionAnalysisDataAccess() {
+ return nutritionAnalysisDataAccess;
+ }
+
+ /**
+ * Get the meal planning data access.
+ * @return The meal planning data access
+ */
+ public MealPlanningDataAccessInterface getMealPlanningDataAccess() {
+ return mealPlanningDataAccessInterface;
+ }
+
+ /**
+ * Build the main frame.
+ * @return The main frame
+ */
+ public JFrame build() {
+ return frontPageView;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/data_access/DBNoteDataAccessObject.java b/src/main/java/data_access/DBNoteDataAccessObject.java
deleted file mode 100644
index dadb0cab0..000000000
--- a/src/main/java/data_access/DBNoteDataAccessObject.java
+++ /dev/null
@@ -1,107 +0,0 @@
-package data_access;
-
-import java.io.IOException;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import entity.User;
-import okhttp3.MediaType;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-import okhttp3.Response;
-import use_case.note.DataAccessException;
-import use_case.note.NoteDataAccessInterface;
-
-/**
- * The DAO for accessing notes stored in the database.
- * This class demonstrates how your group can use the password-protected user
- * endpoints of the API used in lab 5 to store persistent data in your program.
- *
- * You can also refer to the lab 5 code for signing up a new user and other use cases.
- *
- * See
- *
- * the documentation
- * of the API for more details.
- */
-public class DBNoteDataAccessObject implements NoteDataAccessInterface {
- private static final int SUCCESS_CODE = 200;
- private static final int CREDENTIAL_ERROR = 401;
- private static final String CONTENT_TYPE_LABEL = "Content-Type";
- private static final String CONTENT_TYPE_JSON = "application/json";
- private static final String STATUS_CODE_LABEL = "status_code";
- private static final String USERNAME = "username";
- private static final String PASSWORD = "password";
- private static final String MESSAGE = "message";
-
- @Override
- public String saveNote(User user, String note) throws DataAccessException {
- final OkHttpClient client = new OkHttpClient().newBuilder()
- .build();
-
- // POST METHOD
- final MediaType mediaType = MediaType.parse(CONTENT_TYPE_JSON);
- final JSONObject requestBody = new JSONObject();
- requestBody.put(USERNAME, user.getName());
- requestBody.put(PASSWORD, user.getPassword());
- final JSONObject extra = new JSONObject();
- extra.put("note", note);
- requestBody.put("info", extra);
- final RequestBody body = RequestBody.create(requestBody.toString(), mediaType);
- final Request request = new Request.Builder()
- .url("http://vm003.teach.cs.toronto.edu:20112/modifyUserInfo")
- .method("PUT", body)
- .addHeader(CONTENT_TYPE_LABEL, CONTENT_TYPE_JSON)
- .build();
- try {
- final Response response = client.newCall(request).execute();
-
- final JSONObject responseBody = new JSONObject(response.body().string());
-
- if (responseBody.getInt(STATUS_CODE_LABEL) == SUCCESS_CODE) {
- return loadNote(user);
- }
- else if (responseBody.getInt(STATUS_CODE_LABEL) == CREDENTIAL_ERROR) {
- throw new DataAccessException("message could not be found or password was incorrect");
- }
- else {
- throw new DataAccessException("database error: " + responseBody.getString(MESSAGE));
- }
- }
- catch (IOException | JSONException ex) {
- throw new DataAccessException(ex.getMessage());
- }
- }
-
- @Override
- public String loadNote(User user) throws DataAccessException {
- // Make an API call to get the user object.
- final String username = user.getName();
- final OkHttpClient client = new OkHttpClient().newBuilder().build();
- final Request request = new Request.Builder()
- .url(String.format("http://vm003.teach.cs.toronto.edu:20112/user?username=%s", username))
- .addHeader("Content-Type", CONTENT_TYPE_JSON)
- .build();
- try {
- final Response response = client.newCall(request).execute();
-
- final JSONObject responseBody = new JSONObject(response.body().string());
-
- if (responseBody.getInt(STATUS_CODE_LABEL) == SUCCESS_CODE) {
- final JSONObject userJSONObject = responseBody.getJSONObject("user");
- final JSONObject data = userJSONObject.getJSONObject("info");
- return data.getString("note");
- }
- else {
- throw new DataAccessException(responseBody.getString(MESSAGE));
- }
- }
- catch (IOException | JSONException ex) {
- throw new RuntimeException(ex);
- }
- }
-}
diff --git a/src/main/java/data_access/JSONArrayTypeAdapter.java b/src/main/java/data_access/JSONArrayTypeAdapter.java
new file mode 100644
index 000000000..5300f27e0
--- /dev/null
+++ b/src/main/java/data_access/JSONArrayTypeAdapter.java
@@ -0,0 +1,52 @@
+package data_access;
+
+import java.lang.reflect.Type;
+
+import org.json.JSONArray;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+/**
+ * Custom Gson type adapter for serializing and deserializing objects.
+ */
+public class JSONArrayTypeAdapter implements JsonDeserializer, JsonSerializer {
+
+ /**
+ * Serializes an org.json.JSONArray into a Gson JsonArray.
+ *
+ * @param src the source {@link JSONArray} to serialize
+ * @param typeOfSrc the specific genericized type of the source object
+ * @param context the context of the serialization process
+ * @return a {@link JsonElement} representing the serialized {@link JSONArray}
+ */
+ @Override
+ public JsonElement serialize(JSONArray src, Type typeOfSrc, JsonSerializationContext context) {
+ final JsonArray jsonArray = new JsonArray();
+ for (int i = 0; i < src.length(); i++) {
+ jsonArray.add(src.get(i).toString());
+ }
+ return jsonArray;
+ }
+
+ /**
+ * Deserializes a Gson JsonElement into an org.json.JSONArray.
+ *
+ * @param json the JSON data being deserialized
+ * @param typeOfT the specific generalized type of the desired object
+ * @param context the context of the deserialization process
+ * @return a {@link JSONArray} representing the deserialized data
+ */
+ @Override
+ public JSONArray deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
+ final JSONArray jsonArray = new JSONArray();
+ for (JsonElement element : json.getAsJsonArray()) {
+ jsonArray.put(element.getAsString());
+ }
+ return jsonArray;
+ }
+}
diff --git a/src/main/java/data_access/LocalDateAdapter.java b/src/main/java/data_access/LocalDateAdapter.java
new file mode 100644
index 000000000..fdf22f3ce
--- /dev/null
+++ b/src/main/java/data_access/LocalDateAdapter.java
@@ -0,0 +1,43 @@
+package data_access;
+
+import java.lang.reflect.Type;
+import java.time.LocalDate;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+/**
+ * A custom Gson adapter for serializing and deserializing objects.
+ */
+public class LocalDateAdapter implements JsonDeserializer, JsonSerializer {
+
+ /**
+ * Serializes LocalDate object into a JSON string.
+ *
+ * @param date the {@link LocalDate} to serialize
+ * @param typeOfSrc the type of the source object
+ * @param context the context of the serialization process
+ * @return a {@link JsonElement} representing the serialized {@link LocalDate}
+ */
+ @Override
+ public JsonElement serialize(LocalDate date, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(date.toString());
+ }
+
+ /**
+ * Deserializes a JSON string into a LocalDate object.
+ *
+ * @param json the JSON element to deserialize
+ * @param typeOfT the type of the target object
+ * @param context the context of the deserialization process
+ * @return a {@link LocalDate} parsed from the JSON string
+ */
+ @Override
+ public LocalDate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
+ return LocalDate.parse(json.getAsString());
+ }
+}
diff --git a/src/main/java/data_access/MealPlanningDataAccessObject.java b/src/main/java/data_access/MealPlanningDataAccessObject.java
new file mode 100644
index 000000000..50c90ea10
--- /dev/null
+++ b/src/main/java/data_access/MealPlanningDataAccessObject.java
@@ -0,0 +1,134 @@
+package data_access;
+
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.lang.reflect.Type;
+import java.time.LocalDate;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.json.JSONArray;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import entity.MealPlanEntry;
+import entity.Recipe;
+import use_case.meal_planning.MealPlanningDataAccessInterface;
+
+/**
+ * Implementation of MealPlanningDataAccessInterface that manages meal plan entries using JSON file storage.
+ */
+public class MealPlanningDataAccessObject implements MealPlanningDataAccessInterface {
+
+ private final Map mealPlanEntries = new HashMap<>();
+ private final String filePath = "meal_plan.json";
+ private final SavedRecipesDataAccessInterface savedRecipesDataAccessInterface;
+ private int nextEntryId = 1;
+
+ /**
+ * Constructs a new MealPlanningDataAccessObject.
+ *
+ * @param savedRecipesDataAccessInterface the data access object for saved recipes
+ */
+ public MealPlanningDataAccessObject(SavedRecipesDataAccessInterface savedRecipesDataAccessInterface) {
+ this.savedRecipesDataAccessInterface = savedRecipesDataAccessInterface;
+ loadFromJsonFile();
+ }
+
+ @Override
+ public MealPlanEntry getMealPlanEntry(int userId, int entryId) {
+ final MealPlanEntry entry = mealPlanEntries.get(entryId);
+ final MealPlanEntry result;
+ if (entry != null && entry.getUserId() == userId) {
+ result = entry;
+ }
+ else {
+ result = null;
+ }
+ return result;
+ }
+
+ @Override
+ public void updateMealStatus(int userId, int entryId, String status) {
+ final MealPlanEntry entry = mealPlanEntries.get(entryId);
+ if (entry != null && entry.getUserId() == userId) {
+ entry.setStatus(status);
+ saveToJsonFile();
+ }
+ else {
+ throw new IllegalArgumentException("Entry not found or unauthorized access");
+ }
+ }
+
+ @Override
+ public void addMealPlanEntry(int userId, int recipeId, LocalDate date, String mealType) {
+ final Recipe recipe = savedRecipesDataAccessInterface.getSavedRecipe(userId, recipeId);
+ if (recipe == null) {
+ throw new IllegalArgumentException("Recipe not found in saved recipes");
+ }
+
+ final MealPlanEntry entry = new MealPlanEntry(nextEntryId++, recipe, date, userId, mealType);
+ mealPlanEntries.put(entry.getEntryId(), entry);
+ saveToJsonFile();
+ }
+
+ @Override
+ public void removeMealPlanEntry(int userId, int mealPlanEntryId) {
+ final MealPlanEntry entry = mealPlanEntries.get(mealPlanEntryId);
+ if (entry != null && entry.getUserId() == userId) {
+ mealPlanEntries.remove(mealPlanEntryId);
+ saveToJsonFile();
+ }
+ }
+
+ @Override
+ public List getWeeklyPlan(int userId, LocalDate weekStart) {
+ final int daysInWeek = 7;
+ return mealPlanEntries.values().stream()
+ .filter(entry -> entry.getUserId() == userId)
+ .filter(entry -> {
+ final LocalDate date = entry.getDate();
+ return !date.isBefore(weekStart) && date.isBefore(weekStart.plusDays(daysInWeek));
+ })
+ .toList();
+ }
+
+ private void saveToJsonFile() {
+ try (Writer writer = new FileWriter(filePath)) {
+ final Gson gson = new GsonBuilder()
+ .setPrettyPrinting()
+ .registerTypeAdapter(LocalDate.class, new LocalDateAdapter())
+ .registerTypeAdapter(JSONArray.class, new JSONArrayTypeAdapter())
+ .create();
+ gson.toJson(mealPlanEntries, writer);
+ }
+ catch (IOException exception) {
+ System.err.println("Error saving to JSON file: " + exception.getMessage());
+ }
+ }
+
+ private void loadFromJsonFile() {
+ try (Reader reader = new FileReader(filePath)) {
+ final Gson gson = new GsonBuilder()
+ .registerTypeAdapter(LocalDate.class, new LocalDateAdapter())
+ .registerTypeAdapter(JSONArray.class, new JSONArrayTypeAdapter())
+ .create();
+
+ final Type type = new TypeToken