Skip to content
Snippets Groups Projects
FileManager.java 16.3 KiB
Newer Older
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import mi.hdm.mealPlan.MealPlan;
import mi.hdm.recipes.*;
import mi.hdm.shoppingList.ShoppingList;
Karsch Lukas's avatar
Karsch Lukas committed
import mi.hdm.typeAdapters.CategoryManagerTypeAdapter;
import mi.hdm.typeAdapters.MealPlanTypeAdapter;
import mi.hdm.typeAdapters.RecipeTypeAdapter;
Karsch Lukas's avatar
Karsch Lukas committed
import mi.hdm.typeAdapters.ShoppingListTypeAdapter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Containing methods to serialize and deserialize parts of the app content for saving to / loading from the filesystem
 */
    private final static String PATH_TO_DEFAULT_INGREDIENTS = "/data/simple_nutrition.csv";
    private final static String PATH_TO_DEMO_RECIPES = "/recipes/";
Blersch Lara's avatar
Blersch Lara committed
    private final static String PATH_TO_DEMO_CATEGORIES = "/data/categories.json";
    private final static String FOLDER_NAME = "TastyPages";
    private final static Path PATH_TO_USER_DATA = Path.of(System.getProperty("user.home"), FOLDER_NAME);

    private final static Logger log = LogManager.getLogger(FileManager.class);

    public static void serializeToFile(TastyPages app) {
        log.info("Writing user data to '{}'", PATH_TO_USER_DATA);
        //Serialize ingredient manager
        serializeIngredientManager(app.getIngredientManager(), PATH_TO_USER_DATA.resolve("ingredients.csv"));
        serializeRecipeManager(app.getRecipeManager(), PATH_TO_USER_DATA.resolve("recipes"));
        serializeCategoryManager(app.getCategoryManager(), PATH_TO_USER_DATA.resolve("categories.json"));
        serializeMealPlan(app.getMealPlan(), PATH_TO_USER_DATA.resolve("mealplan.json"));
        serializeShoppingList(app.getShoppingList(), app.getRecipeManager(), PATH_TO_USER_DATA.resolve("shoppingList.json"));
Karsch Lukas's avatar
Karsch Lukas committed
    @SuppressWarnings("ResultOfMethodCallIgnored")
    public static TastyPages deserializeFromFilesystem() throws IOException {
        log.info("Trying to read {}", PATH_TO_USER_DATA);
        if (PATH_TO_USER_DATA.toFile().mkdirs()) { //If .mkdirs() returns true, that means the folder has not existed before -> in this case, only load default ingredients!
            log.info("TastyPages folder has been created at {}", PATH_TO_USER_DATA);
            IngredientManager deserializedIngredientManager = deserializeIngredientManager(getAbsolutePathFromResourceFolder(FileManager.PATH_TO_DEFAULT_INGREDIENTS));
Blersch Lara's avatar
Blersch Lara committed
            RecipeManager recipeManager = deserializeRecipeManager(Path.of(getAbsolutePathFromResourceFolder(FileManager.PATH_TO_DEMO_RECIPES)));
            String fileContent = Files.readString(Path.of(FileManager.getAbsolutePathFromResourceFolder(FileManager.PATH_TO_DEMO_CATEGORIES)));
            CategoryManager categoryManager = deserializeCategoryManager(fileContent);
            return new TastyPages(deserializedIngredientManager, recipeManager, categoryManager);
            log.info("Found TastyPages folder at {}, loading user data from there.", PATH_TO_USER_DATA);
            IngredientManager ingredientManager;
            RecipeManager recipeManager;
            CategoryManager categoryManager;
            MealPlan mealPlan;
            ShoppingList shoppingList;

                Path toIngredients = PATH_TO_USER_DATA.resolve("ingredients.csv");
                if (Files.exists(toIngredients)) {
                    log.info("Loading ingredients from '{}'", toIngredients);
                    ingredientManager = deserializeIngredientManager(toIngredients.toString());
                } else {
                    log.info("Could not find /ingredients.csv, falling back to default ingredients.");
                    ingredientManager = deserializeIngredientManager(getAbsolutePathFromResourceFolder(FileManager.PATH_TO_DEFAULT_INGREDIENTS));
                Path recipeFolder = PATH_TO_USER_DATA.resolve("recipes");
                if (Files.exists(recipeFolder) && Files.isDirectory(recipeFolder)) {
                    log.info("Loading recipes from '{}'", recipeFolder);
                    recipeManager = deserializeRecipeManager(recipeFolder);
                } else {
                    log.info("Could not find recipe folder, creating empty instance of RecipeManger");
                    recipeFolder.toFile().mkdir();
                    recipeManager = deserializeRecipeManager(Path.of(getAbsolutePathFromResourceFolder(FileManager.PATH_TO_DEMO_RECIPES)));
                Path toCategoryJson = PATH_TO_USER_DATA.resolve("categories.json");
                if (Files.exists(toCategoryJson) && Files.isRegularFile(toCategoryJson)) {
                    log.info("Loading categories from '{}'", toCategoryJson);
                    String fileContent = Files.readString(toCategoryJson);
                    categoryManager = deserializeCategoryManager(fileContent);
                } else {
                    log.info("Could not find /categories.json, creating empty instance of CategoryManager");
Blersch Lara's avatar
Blersch Lara committed
                    String fileContent = Files.readString(Path.of(FileManager.getAbsolutePathFromResourceFolder(FileManager.PATH_TO_DEMO_CATEGORIES)));
                    categoryManager = deserializeCategoryManager(fileContent);
                    /*toCategoryJson.toFile().createNewFile();
                    categoryManager = new CategoryManager();*/
                Path toMealPlanJson = PATH_TO_USER_DATA.resolve("mealplan.json");
                if (Files.exists(toMealPlanJson) && Files.isRegularFile(toMealPlanJson)) {
                    log.info("Loading meal plan from '{}'", toMealPlanJson);
                    String fileContent = Files.readString(toMealPlanJson);
                    mealPlan = deserializeMealPlan(fileContent, recipeManager);
                } else {
                    log.info("Could not find /mealplan.json, creating empty instance of MealPlan");
                    toMealPlanJson.toFile().createNewFile();
                    mealPlan = new MealPlan(recipeManager);
                }
                Path toShoppingListJson = PATH_TO_USER_DATA.resolve("shoppingList.json");
                if (Files.exists(toShoppingListJson) && Files.isRegularFile(toShoppingListJson)) {
                    log.info("Loading shopping list from '{}'", toShoppingListJson);
                    String fileContent = Files.readString(toShoppingListJson);
                    shoppingList = deserializeShoppingList(fileContent, recipeManager);
                } else {
                    log.info("Could not find /shoppingList.json, creating empty shopping list");
                    toShoppingListJson.toFile().createNewFile();
                    shoppingList = new ShoppingList(recipeManager);
Karsch Lukas's avatar
Karsch Lukas committed
            return new TastyPages(recipeManager, ingredientManager, categoryManager, mealPlan, shoppingList);
    private static IngredientManager deserializeIngredientManager(String path) throws IOException {
        CSVParser parser = new CSVParser();
        List<Ingredient> ingredients = parser.getIngredientsFromCSV(
                path,
                ',',
                "name", "calories", "carbohydrate", "fat", "protein", "fiber", "sodium"
        );
        return new IngredientManager(ingredients);
    }

    private static void serializeIngredientManager(IngredientManager ingredientManager, Path path) {
        log.info("Writing ingredients to '{}'", path.getFileName());
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(path.toFile()))) {
            writer.write("name,calories,carbohydrate,fat,protein,fiber,sodium\n");
            final List<Ingredient> ingredients = ingredientManager.getAllIngredients().values().stream().toList();
            for (int i = 0; i < ingredients.size(); i++) {
                if (i % 200 == 0)
                    writer.flush(); //flush the writer regularly
                writer.write(ingredientToCSV(ingredients.get(i)));
            }
        } catch (IOException e) {
            log.error("Exception when writing ingredients to file!");
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    private static RecipeManager deserializeRecipeManager(Path path) throws IOException {
        try (Stream<Path> stream = Files.list(path)) {
Karsch Lukas's avatar
Karsch Lukas committed
        List<Recipe> recipes = recipePaths
                .stream()
                .map(p -> {
                    try {
                        return Files.readString(p);
                    } catch (IOException e) {
                        e.printStackTrace();
                        log.error("Could not read file contents for {}. Check file permissions and content", p);
Karsch Lukas's avatar
Karsch Lukas committed
                        return null;
Karsch Lukas's avatar
Karsch Lukas committed
                .filter(Objects::nonNull)
    private static void serializeRecipeManager(RecipeManager recipeManager, Path path) {
        String[] recipeFolderContent = path.toFile().list();
        if (recipeFolderContent == null) { //wrong path provided
            log.fatal("Path to recipe folder is NOT a folder! '{}' Unable to save recipes.", path);
            return;
        }
        List<String> toDelete = Arrays.stream(recipeFolderContent).collect(Collectors.toCollection(ArrayList::new));
        log.info("Writing all recipes to /recipes");
        recipeManager.getRecipes()
                .forEach(recipe -> {
                            try {
                                String recipeName = recipe.getName() + ".json";
                                String recipePath = path + "\\" + recipeName;
                                Files.writeString(Path.of(recipePath), recipeToJSON(recipe));
                                log.debug("Wrote recipe '{}' to '{}'", recipe.getName(), recipePath);
                                toDelete.remove(recipeName);
                                log.error("Error writing recipe '{}' to file. Recipe will be lost.", recipe.getName());
                                e.printStackTrace();
                            }
                        }
                );
        long deleted = toDelete.stream()
                .filter(filename -> {
                    try {
                        return Files.deleteIfExists(Path.of(path + "\\" + filename));
                    } catch (IOException e) {
                        log.warn("Could not delete '{}': {}", filename, e.getMessage());
                        return false;
                    }
                })
                .count();
        log.info("Deleted {} recipes from the filesystem", deleted);
    private static CategoryManager deserializeCategoryManager(String json) {
        if (json.isBlank()) {
            log.info("JSON file is blank, creating empty instance of Category Manager");
            return new CategoryManager();
        }
Karsch Lukas's avatar
Karsch Lukas committed
        Gson gson = new GsonBuilder().registerTypeAdapter(CategoryManager.class, new CategoryManagerTypeAdapter()).create();
        return gson.fromJson(json, CategoryManager.class);
    private static void serializeCategoryManager(CategoryManager categoryManager, Path path) {
        log.info("Writing categories to categories.json");
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(CategoryManager.class, new CategoryManagerTypeAdapter())
                .create();
        String json = gson.toJson(categoryManager);
        try {
            Files.writeString(path, json);
        } catch (IOException io) {
            log.error("Error writing categories to file.");
            io.printStackTrace();
            throw new RuntimeException();
        }
    }

    private static MealPlan deserializeMealPlan(String json, RecipeManager recipeManager) {
        if (json.isBlank()) {
            log.info("JSON file is blank, creating empty instance of Meal Plan");
            return new MealPlan(recipeManager);
        }
Karsch Lukas's avatar
Karsch Lukas committed
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(MealPlan.class, new MealPlanTypeAdapter(recipeManager))
                .create();
        return gson.fromJson(json, MealPlan.class);
    private static void serializeMealPlan(MealPlan mealPlan, Path path) {
        Gson gson = new GsonBuilder().
                registerTypeAdapter(MealPlan.class, new MealPlanTypeAdapter(null))
                .create();
        String json = gson.toJson(mealPlan);
        try {
            Files.writeString(path, json);
        } catch (IOException io) {
            log.error("Error writing meal plan to file.");
            io.printStackTrace();
            throw new RuntimeException();
        }
    }

    private static ShoppingList deserializeShoppingList(String json, RecipeManager recipeManager) {
        if (json.isBlank()) {
            log.info("JSON file is blank, creating empty instance of Shopping List");
            return new ShoppingList(recipeManager);
Karsch Lukas's avatar
Karsch Lukas committed
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(ShoppingList.class, new ShoppingListTypeAdapter(recipeManager))
Karsch Lukas's avatar
Karsch Lukas committed
                .create();
        return gson.fromJson(json, ShoppingList.class);
    private static void serializeShoppingList(ShoppingList shoppingList, RecipeManager recipeManager, Path path) {
                .registerTypeAdapter(ShoppingList.class, new ShoppingListTypeAdapter(recipeManager))
                .create();
        String json = gson.toJson(shoppingList);
        try {
            Files.writeString(path, json);
        } catch (IOException io) {
            log.error("Error writing shopping list to file.");
            io.printStackTrace();
            throw new RuntimeException();
        }
    }

    private static String recipeToJSON(Recipe recipe) {
                .registerTypeAdapter(Recipe.class, new RecipeTypeAdapter())
     * Used to convert an ingredient into a CSV String seperated by comma. Floating point numbers
     * will use dots as decimal points
     *
     * @return a comma seperated String containing the name of the ingredient as well as the contents of its nutrition table
     */
    private static String ingredientToCSV(Ingredient ingredient) {
        //"name", "calories", "carbohydrate", "fat", "protein", "fiber", "sodium"
        Map<Nutrition, BigDecimal> map = ingredient.getNutritionTable().getTable();
        return String.format(
                Locale.ENGLISH,
                "\"%s\", %f, %f, %f, %f, %f, %f%n",
                ingredient.getName(),
                map.get(Nutrition.CALORIES).doubleValue(),
                map.get(Nutrition.CARBS).doubleValue(),
                map.get(Nutrition.FAT).doubleValue(),
                map.get(Nutrition.PROTEINS).doubleValue(),
                map.get(Nutrition.FIBERS).doubleValue(),
                map.get(Nutrition.SALT).doubleValue()
        );
    }

    private static Recipe JSONtoRecipe(String json) {
        Gson gson = new GsonBuilder().registerTypeAdapter(Recipe.class, new RecipeTypeAdapter()).create();
Karsch Lukas's avatar
Karsch Lukas committed
        return gson.fromJson(json, Recipe.class);
    public static String getAbsolutePathFromResourceFolder(String p) {
        try {
            URL resourceUrl = FileManager.class.getResource(p);
            assert resourceUrl != null;
            Path path = Paths.get(resourceUrl.toURI());
            return path.toFile().getAbsolutePath();
        } catch (URISyntaxException e) {
            log.error("Absolute path for \"{}\" not found", p);
            return null;
        }