diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index ca16acb822..613dddac75 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -236,43 +236,6 @@ Command: `left` Response: `CS2030S CS2040S CS2100 CS2101 CS2106 CS2109S CS3230` -## Input Major Feature - -The input major feature is facilitated by `Student`. It tries to store the major specified in userInput txt -file such that it can be used across sessions. It will print different responses based on whether the storing of the -Major was successful. Additionally, it implements the following operation: - -- `Student#setMajor(Major major)` – Saves the selected major in its memory. - -This operation is exposed in the `Student` interface as `Student#updateMajor(String userInput)`. - -### Usage Examples - -Here are a few examples of how the Input Major Feature behaves: - -#### Example 1: -If "CS" is a valid major: `Student#updateMajor("major CS")` calls `Student#setMajor("CS")`, which sets the Major in the -student object as `CS` and returns a string `newMajor` - -Command: `major CS` - -Response: `Major CS selected!` - - -#### Example 2: -If "abc" is an invalid major: `Student#updateMajor("major abc")` calls `Student#setMajor("abc")`, which generates an -IllegalArgumentException, which is caught and returns a string `invalidMajor` - -Command: `major abc` - -Response: `Please select a major from this list: [list of currently available Majors]` - -#### Example 3: -If no major was specified: `Student#updateMajor("major")` returns a string `currentMajor` - -Command: `major` - -Response: `Current major is [current major in student object].` ## Add Module Feature diff --git a/docs/diagrams/updatedAddModule.png b/docs/diagrams/updatedAddModule.png index 9ea3d3c2f7..e992d8371d 100644 Binary files a/docs/diagrams/updatedAddModule.png and b/docs/diagrams/updatedAddModule.png differ diff --git a/src/main/java/seedu/duke/controllers/MainController.java b/src/main/java/seedu/duke/controllers/MainController.java index 2b5b565b96..48b79ad239 100644 --- a/src/main/java/seedu/duke/controllers/MainController.java +++ b/src/main/java/seedu/duke/controllers/MainController.java @@ -1,6 +1,6 @@ package seedu.duke.controllers; -import seedu.duke.models.schema.Storage; +import seedu.duke.storage.StorageManager; import seedu.duke.models.schema.Student; import seedu.duke.models.schema.CommandManager; import seedu.duke.models.schema.UserCommand; @@ -16,7 +16,7 @@ import static seedu.duke.controllers.ModuleServiceController.validateMajorInput; -import static seedu.duke.models.schema.Storage.saveTimetable; +import static seedu.duke.storage.StorageManager.saveTimetable; import static seedu.duke.utils.Utility.detectInternet; import static seedu.duke.utils.Utility.saveStudentData; import static seedu.duke.views.Ui.displayWelcome; @@ -27,14 +27,14 @@ import static seedu.duke.views.Ui.showLoadingAnimation; import static seedu.duke.views.Ui.stopLoadingAnimation; -import static seedu.duke.models.schema.Storage.saveSchedule; +import static seedu.duke.storage.StorageManager.saveSchedule; public class MainController { private final Parser parser; private final Student student; private final CommandManager commandManager; - private Storage storage; + private StorageManager storageManager; private final Ui ui; @@ -62,7 +62,7 @@ public void start() throws IOException { initialiseUser(); displayReady(); handleUserInputTillExitCommand(); - saveStudentData(storage,student); + saveStudentData(storageManager,student); displayGoodbye(); } //@@author SebasFok @@ -75,23 +75,23 @@ public void start() throws IOException { */ public void initialiseUser() throws IOException { - storage = new Storage(); + storageManager = new StorageManager(); try { System.out.println("Attempting to retrieve data from save file... Sorry this takes a while!"); showLoadingAnimation(); // Load name, major and year from studentDetails.txt file - ArrayList studentDetails = storage.loadStudentDetails(); + ArrayList studentDetails = storageManager.loadStudentDetails(); // Set name, major and year from loaded data, throws exception if file is corrupted. setStudentDetails(studentDetails); // Load and set schedule from schedule.txt file - student.setSchedule(storage.loadSchedule()); + student.setSchedule(storageManager.loadSchedule()); // Load timetable from timetable.txt file try { student.updateTimetable(); - storage.addEventsToStudentTimetable(storage.loadTimetable(student), student); + storageManager.addEventsToStudentTimetable(storageManager.loadTimetable(student), student); } catch (TimetableUnavailableException e) { // no modules in current sem, do nothing @@ -135,7 +135,7 @@ public void initialiseUser() throws IOException { } public void resetStorageData() throws IOException { - storage.createUserStorageFile(); + storageManager.createUserStorageFile(); String userInput; @@ -158,7 +158,7 @@ public void resetStorageData() throws IOException { userInput = ui.getUserCommand("Please enter your current academic year: ").trim(); } while (!Parser.isValidAcademicYear(userInput.toUpperCase())); student.setYear(userInput.toUpperCase()); - storage.saveStudentDetails(student); + storageManager.saveStudentDetails(student); //get blank schedule.txt student.setSchedule(new Schedule()); diff --git a/src/main/java/seedu/duke/controllers/ModuleMethodsController.java b/src/main/java/seedu/duke/controllers/ModuleMethodsController.java index 489a1bfef4..2e7c7c1c8a 100644 --- a/src/main/java/seedu/duke/controllers/ModuleMethodsController.java +++ b/src/main/java/seedu/duke/controllers/ModuleMethodsController.java @@ -18,8 +18,8 @@ import static seedu.duke.controllers.ModuleServiceController.isConfirmedToClearSchedule; import static seedu.duke.models.logic.Api.isValidModule; import static seedu.duke.models.logic.Prerequisite.getModulePrereqBasedOnCourse; -import static seedu.duke.models.schema.Storage.saveSchedule; -import static seedu.duke.models.schema.Storage.saveTimetable; +import static seedu.duke.storage.StorageManager.saveSchedule; +import static seedu.duke.storage.StorageManager.saveTimetable; import static seedu.duke.utils.errors.HttpError.displaySocketError; import static seedu.duke.views.Ui.displayMessage; import static seedu.duke.views.CommandLineView.displaySuccessfulAddMessage; diff --git a/src/main/java/seedu/duke/controllers/ModuleServiceController.java b/src/main/java/seedu/duke/controllers/ModuleServiceController.java index 12000a2413..3243731242 100644 --- a/src/main/java/seedu/duke/controllers/ModuleServiceController.java +++ b/src/main/java/seedu/duke/controllers/ModuleServiceController.java @@ -15,8 +15,8 @@ import java.util.ArrayList; import java.util.Scanner; -import static seedu.duke.models.schema.Storage.saveSchedule; -import static seedu.duke.models.schema.Storage.saveTimetable; +import static seedu.duke.storage.StorageManager.saveSchedule; +import static seedu.duke.storage.StorageManager.saveTimetable; import static seedu.duke.utils.TimetableParser.isExitModify; import static seedu.duke.views.MajorRequirementsView.printRequiredModules; import static seedu.duke.views.SemesterPlannerView.displaySchedule; diff --git a/src/main/java/seedu/duke/models/logic/Prerequisite.java b/src/main/java/seedu/duke/models/logic/Prerequisite.java index 3eb722cd62..1a93fa453c 100644 --- a/src/main/java/seedu/duke/models/logic/Prerequisite.java +++ b/src/main/java/seedu/duke/models/logic/Prerequisite.java @@ -12,7 +12,7 @@ import java.util.List; import java.util.Objects; -import static seedu.duke.models.schema.Storage.getRequirements; +import static seedu.duke.storage.StorageManager.getRequirements; public class Prerequisite { diff --git a/src/main/java/seedu/duke/models/schema/Schedule.java b/src/main/java/seedu/duke/models/schema/Schedule.java index 9ac5cd0102..c30f2a48b8 100644 --- a/src/main/java/seedu/duke/models/schema/Schedule.java +++ b/src/main/java/seedu/duke/models/schema/Schedule.java @@ -15,7 +15,7 @@ import static seedu.duke.models.logic.Prerequisite.getModulePrereqBasedOnCourse; import static seedu.duke.models.logic.Api.getModuleFulfilledRequirements; import static seedu.duke.models.logic.Prerequisite.satisfiesAllPrereq; -import static seedu.duke.models.schema.Storage.getRequirements; +import static seedu.duke.storage.StorageManager.getRequirements; import static seedu.duke.views.SemesterPlannerView.printSemesterPlanner; /** diff --git a/src/main/java/seedu/duke/models/schema/Storage.java b/src/main/java/seedu/duke/models/schema/Storage.java deleted file mode 100644 index aab55584d6..0000000000 --- a/src/main/java/seedu/duke/models/schema/Storage.java +++ /dev/null @@ -1,509 +0,0 @@ -package seedu.duke.models.schema; - - -import seedu.duke.utils.Parser; -import seedu.duke.utils.exceptions.CorruptedFileException; -import seedu.duke.utils.exceptions.InvalidTimetableUserCommandException; -import seedu.duke.utils.exceptions.MissingFileException; -import seedu.duke.utils.exceptions.TimetableUnavailableException; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.FileWriter; -import java.io.BufferedWriter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; - - -public class Storage { - - private String userDirectory = System.getProperty("user.dir"); - - /** - * Constructs a new Storage instance with the specified file path. - */ - public Storage() { - - } - - //@@author ryanlohyr - /** - * Retrieves a list of modules requirements for a specified major. - * - * @param major The major for which to retrieve requirements. - * @return An ArrayList of module codes. - * @throws RuntimeException If the specified major requirements file is not found. - */ - public static ArrayList getRequirements(String major) { - String[] courseArray = determineRequirements(major); - return new ArrayList<>(Arrays.asList(courseArray)); - } - - /** - * Determines the course requirements based on the specified major. Function is used if file is not found - * - * @param major A string representing the major (e.g., "CEG" for Computer Engineering, "CS" for Computer Science). - * @return An array of strings containing the course requirements for the specified major. - */ - private static String[] determineRequirements(String major) { - String[] courseArray; - - String[] csCourseArray = { - "CS1101S", "ES2660", "GEC1000", "GEA1000", "GESS1000", - "GEN2000", "IS1108", "CS1231S", "CS2030", "CS2040S", - "CS2100", "CS2101", "CS2103T", "CS2106", "CS2109S", - "CS3230", "MA1521", "MA1522", "ST2334", "CP3880" - }; - String[] cegCourseArray = { - "CG1111A", "MA1511", "MA1512", "CS1010", "GESS1000", - "GEC1000", "GEN2000", "ES2631", "GEA1000", "DTK1234", - "EG1311", "IE2141", "EE2211", "EG2501", "CDE2000", - "PF1101", "CG4002", "MA1508E", "EG2401A", "CP3880", - "CG2111A", "CS1231", "CG2023", "CG2027", "CG2028", - "CG2271", "ST2334", "CS2040C", "CS2113", "EE2026", "EE4204" - }; - - if(major.equals("CEG")){ - courseArray = cegCourseArray; - }else{ - courseArray = csCourseArray; - } - return courseArray; - } - - //@@author SebasFok - /** - * Creates a "schedule.txt" file in the data directory. - * - */ - public void createUserStorageFile() { - String dataDirectory = userDirectory + "/data"; - - createDirectory(dataDirectory); - - createFileInDirectory(dataDirectory, "schedule.txt"); - createFileInDirectory(dataDirectory, "studentDetails.txt"); - createFileInDirectory(dataDirectory, "timetable.txt"); - - } - - /** - * Loads the student's schedule from the "schedule.txt" file, including modules per semester and individual modules. - * Also retains the completion status of each module in the schedule. - * - * @return A Schedule object representing the loaded schedule. - * @throws MissingFileException If the "schedule.txt" file is missing. - * @throws CorruptedFileException If the file is corrupted or has unexpected content. - */ - public Schedule loadSchedule() throws MissingFileException, CorruptedFileException { - - String scheduleFilePath = userDirectory + "/data/schedule.txt"; - - if (!isFileExist(scheduleFilePath)) { - throw new MissingFileException(); - } - - Schedule schedule = new Schedule(); - - try { - // Create a FileReader and BufferedReader to read the file. - FileReader fileReader = new FileReader(scheduleFilePath); - BufferedReader bufferedReader = new BufferedReader(fileReader); - - String line; - - int targetIndex = 0; - int[] modulesPerSemArray = new int[]{0, 0, 0, 0, 0, 0, 0, 0}; - - // Read lines from the file and add them to the ArrayList. - while ((line = bufferedReader.readLine()) != null) { - - String[] splitParts = line.split(" \\| "); - - switch (splitParts[0]) { - - // Happens once on the first line of txt file so that sorting subsequent modules is possible - case "ModulesPerSem": - String[] modulesPerSemStringArray = splitParts[1].split(","); - for (int i = 0; i < modulesPerSemArray.length; i++) { - modulesPerSemArray[i] = Integer.parseInt(modulesPerSemStringArray[i]); - } - break; - case "Module": - String module = splitParts[1]; - int targetSemester = 1; - int indexOfLastModuleOfSem = modulesPerSemArray[targetSemester - 1] - 1; - while (targetIndex > indexOfLastModuleOfSem) { - indexOfLastModuleOfSem += modulesPerSemArray[targetSemester]; - targetSemester += 1; - } - - schedule.addModule(module, targetSemester); - if (splitParts[2].equals("O")) { - schedule.getModule(module).markModuleAsCompleted(); - } - targetIndex += 1; - break; - default: - if (!splitParts[0].trim().isEmpty()) { - throw new CorruptedFileException(); - } - } - } - - // Close the BufferedReader to release resources. - bufferedReader.close(); - } catch (Exception e) { - throw new CorruptedFileException(); - } - - return schedule; - - } - - //@@author SebasFok - /** - * Loads the student's details (name, major, and year) from the "studentDetails.txt" file. - * - * @return An ArrayList containing the loaded student details in the order [Name, Major, Year]. - * @throws MissingFileException If the "studentDetails.txt" file is missing. - * @throws CorruptedFileException If the file is corrupted or has unexpected content. - */ - public ArrayList loadStudentDetails() throws MissingFileException, CorruptedFileException { - - String studentDetailsFilePath = userDirectory + "/data/studentDetails.txt"; - - if (!isFileExist(studentDetailsFilePath)) { - throw new MissingFileException(); - } - - try { - // Create a FileReader and BufferedReader to read the file. - FileReader fileReader = new FileReader(studentDetailsFilePath); - BufferedReader bufferedReader = new BufferedReader(fileReader); - - ArrayList studentDetails = new ArrayList<>(3); - - String line; - int lineNumber = 0; - - // to track which line it is supposed to be on - HashMap variableMap = new HashMap<>(); - // Adding key-value pairs - variableMap.put("Name", 0); - variableMap.put("Major", 1); - variableMap.put("Year", 2); - - // Read lines from the file and add them to the ArrayList. - while ((line = bufferedReader.readLine()) != null) { - - String[] splitParts = line.split(" \\| "); - - String userAttribute = splitParts[0]; - - //validation to see that variables has not been tampered with - if(variableMap.get(userAttribute) != lineNumber){ - throw new CorruptedFileException(); - } - - switch (splitParts[0]) { - - case "Name": - String name = splitParts[1]; - studentDetails.add(0, name); - break; - case "Major": - String major = splitParts[1]; - - // Check if major stored in txt file is valid - Major.valueOf(major.toUpperCase()); - - studentDetails.add(1, major); - break; - case "Year": - String year = splitParts[1]; - - //Check if year stored in txt file is valid - if (!Parser.isValidAcademicYear(year)){ - throw new CorruptedFileException(); - } - - studentDetails.add(2, year); - break; - default: - if (!splitParts[0].trim().isEmpty()) { - throw new CorruptedFileException(); - } - } - lineNumber += 1; - } - // Close the BufferedReader to release resources. - bufferedReader.close(); - - return studentDetails; - } catch (Exception e) { - throw new CorruptedFileException(); - } - } - - //@@author janelleenqi - /** - * Loads timetable user commands from the timetable.txt save file and processes them to update the student's - * timetable. - * - * @param student The student whose timetable is being updated. - * @return An ArrayList of TimetableUserCommand objects representing the loaded commands. - * @throws MissingFileException If the timetable file is missing. - * @throws CorruptedFileException If the timetable file is corrupted or contains invalid commands. - */ - public ArrayList loadTimetable(Student student) - throws MissingFileException, CorruptedFileException { - - String timetableFilePath = userDirectory + "/data/timetable.txt"; - - if (!isFileExist(timetableFilePath)) { - throw new MissingFileException(); - } - - ArrayList timetableUserCommands; - try { - // Create a FileReader and BufferedReader to read the file. - FileReader fileReader = new FileReader(timetableFilePath); - BufferedReader bufferedReader = new BufferedReader(fileReader); - - String line; - if ((line = bufferedReader.readLine()) != null) { - if (!line.equals("TimetableForCurrentSem")) { - throw new CorruptedFileException(); - } - } - timetableUserCommands = new ArrayList<>(); - - // Read lines from the file and add them to the ArrayList. - while ((line = bufferedReader.readLine()) != null) { - try { - timetableUserCommands.add(new TimetableUserCommand(student, - student.getTimetable().getCurrentSemesterModulesWeekly(), line)); - } catch (InvalidTimetableUserCommandException e) { - //corrupted - throw new CorruptedFileException(); - } - - } - - // Close the BufferedReader to release resources. - bufferedReader.close(); - } catch (Exception e) { - throw new CorruptedFileException(); - } - - return timetableUserCommands; - - } - - //@@author janelleenqi - /** - * Adds events to the student's timetable based on the provided timetable user commands. - * - * @param timetableUserCommands An ArrayList of TimetableUserCommand objects representing the commands to process. - * @param student The student whose timetable is being updated. - * @throws CorruptedFileException If the provided timetable user commands are corrupted or contain invalid commands. - */ - public void addEventsToStudentTimetable(ArrayList timetableUserCommands, Student student) - throws CorruptedFileException { - ArrayList currentSemModulesWeekly = student.getTimetable().getCurrentSemesterModulesWeekly(); - for (TimetableUserCommand currentTimetableCommand : timetableUserCommands) { - //not exit, not clear - try { - currentTimetableCommand.processTimetableCommandLesson(currentSemModulesWeekly); - } catch (InvalidTimetableUserCommandException e) { - //corrupted - throw new CorruptedFileException(); - } - } - } - - //@@author - - public void saveStudentDetails (Student student) throws IOException { - - String studentDetailsFilePath = userDirectory + "/data/studentDetails.txt"; - - try (BufferedWriter writer = new BufferedWriter(new FileWriter(studentDetailsFilePath))) { - - String name = student.getName(); - - String major = student.getMajor(); - - String year = student.getYear(); - - // Write the new content to the file - writer.write("Name | " + name); - writer.newLine(); - - writer.write("Major | " + major); - writer.newLine(); - - writer.write(("Year | " + year)); - writer.newLine(); - } - } - - //@@author SebasFok - /** - * Saves the student's details (name, major, and year) to the "studentDetails.txt" file. - * - * @param student The Student object containing the details to be saved. - * @throws IOException If an I/O error occurs while writing to the file. - */ - public static void saveSchedule(Student student) throws IOException { - - String scheduleFilePath = System.getProperty("user.dir") + "/data/schedule.txt"; - try (BufferedWriter writer = new BufferedWriter(new FileWriter(scheduleFilePath))) { - - int[] modulesPerSemArray = student.getSchedule().getModulesPerSem(); - - StringBuilder modulesPerSemNumbers = new StringBuilder(Integer.toString(modulesPerSemArray[0])); - for (int i = 1; i < modulesPerSemArray.length; i++) { - modulesPerSemNumbers.append(",").append(modulesPerSemArray[i]); - } - - // Write the new content to the file - writer.write("ModulesPerSem | " + modulesPerSemNumbers); - writer.newLine(); - - ModuleList modulesPlanned = student.getSchedule().getModulesPlanned(); - int numberOfModules = student.getSchedule().getModulesPlanned().getMainModuleList().size(); - String completionStatus; - for (int i = 0; i < numberOfModules; i++) { - String moduleCode = modulesPlanned.getModuleByIndex(i).getModuleCode(); - if (modulesPlanned.getModuleByIndex(i).getCompletionStatus()) { - completionStatus = "O"; - } else { - completionStatus = "X"; - } - writer.write("Module | " + moduleCode + " | " + completionStatus); - writer.newLine(); // Move to the next line - } - } - } - - public static void saveTimetable(Student student) throws IOException { - - String timetableFilePath = System.getProperty("user.dir") + "/data/timetable.txt"; - try (BufferedWriter writer = new BufferedWriter(new FileWriter(timetableFilePath))) { - - // Write the new content to the file - writer.write("TimetableForCurrentSem"); - writer.newLine(); - - //latest info - student.updateTimetable(); - - ArrayList currentSemesterModules = student.getTimetable().getCurrentSemesterModulesWeekly(); - for (ModuleWeekly module : currentSemesterModules) { - for (Event event : module.getWeeklyTimetable()) { - writer.write(event.toSave()); - writer.newLine(); - } - } - } catch (TimetableUnavailableException e) { - //no events in timetable, do nothing - } - } - - // Below this comment are standard file methods - - //@@author SebasFok - /** - * Takes in the location of the file in question and returns whether the file exist - * - * @param filePath - * @return return true if the file exist,return false otherwise - */ - public static boolean isFileExist(String filePath) { - Path path = Paths.get(filePath); - return Files.exists(path); - } - - //@@author SebasFok - /** - * This method takes in a path and creates a directory at that location. Should the - * directory already exist, no new directory will be created. - * - * @param folderPath the location of where the directory should be created - */ - public static void createDirectory(String folderPath) { - - File folder = new File(folderPath); - if (folder.mkdir()) { - //System.out.println("Folder created successfully."); - } else { - //System.out.println("Folder already exists"); - } - } - - //@@author SebasFok - /** - * This method takes in the path of a directory and creates a file 'fileName' in - * the directory. Should the file already exist, no new file will be created. - * - * @param directoryPath the location of the directory where the file should be created - * @param fileName the name of the file to be created - */ - public static void createFileInDirectory(String directoryPath, String fileName) { - - // Create the full path to the text file - String filePath = directoryPath + "/" + fileName; - - File file = new File(filePath); - - try { - // Create the file - if (file.createNewFile()) { - //System.out.println("Text file created successfully at: " + filePath); - } else { - //System.out.println("File already exists."); - } - } catch (IOException e) { - System.out.println("An IOException occurred: " + e.getMessage()); - } - } - - //@@author SebasFok - /** - * This method takes in the path of a txt file and adds 'textToAdd' to the last line - * of the file - * - * @param filePath location of the file to be edited - * @param textToAdd String to be added to the end of the txt file - */ - public static void addTextToFile(String filePath, String textToAdd) { - try { - // Create a FileWriter object with the specified file path in append mode (true). - FileWriter fileWriter = new FileWriter(filePath, true); - - // Create a BufferedWriter to efficiently write text. - BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); - - // Write the text to the file. - bufferedWriter.write(textToAdd); - - // Write a newline character to separate lines. - bufferedWriter.newLine(); - - // Close the BufferedWriter to release resources. - bufferedWriter.close(); - - //System.out.println("Text added to the file successfully."); - } catch (IOException e) { - System.out.println("An IOException occurred: " + e.getMessage()); - } - } -} diff --git a/src/main/java/seedu/duke/models/schema/Student.java b/src/main/java/seedu/duke/models/schema/Student.java index 87ed97d1b9..2c0270b48d 100644 --- a/src/main/java/seedu/duke/models/schema/Student.java +++ b/src/main/java/seedu/duke/models/schema/Student.java @@ -15,7 +15,7 @@ import java.util.ArrayList; import static seedu.duke.models.logic.Prerequisite.getModulePrereqBasedOnCourse; -import static seedu.duke.models.schema.Storage.getRequirements; +import static seedu.duke.storage.StorageManager.getRequirements; import static seedu.duke.utils.errors.HttpError.displaySocketError; import static seedu.duke.views.CommandLineView.displaySuccessfulCompleteMessage; import static seedu.duke.views.TimetableUserGuideView.addOrRecommendGuide; diff --git a/src/main/java/seedu/duke/storage/FileDecoder.java b/src/main/java/seedu/duke/storage/FileDecoder.java new file mode 100644 index 0000000000..bf73bf3cce --- /dev/null +++ b/src/main/java/seedu/duke/storage/FileDecoder.java @@ -0,0 +1,307 @@ +package seedu.duke.storage; + +import seedu.duke.models.schema.Major; +import seedu.duke.models.schema.Schedule; +import seedu.duke.models.schema.Student; +import seedu.duke.models.schema.TimetableUserCommand; +import seedu.duke.utils.Parser; +import seedu.duke.utils.exceptions.CorruptedFileException; +import seedu.duke.utils.exceptions.InvalidTimetableUserCommandException; +import seedu.duke.utils.exceptions.MissingFileException; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; + +public class FileDecoder { + + /** + * Retrieves a schedule from a specified file path. + * + * @param scheduleFilePath The file path where the schedule is stored. + * @return The retrieved schedule. + * @throws MissingFileException If the file is missing. + * @throws CorruptedFileException If the file is corrupted or the data format is invalid. + */ + public static Schedule retrieveSchedule(String scheduleFilePath) throws MissingFileException, + CorruptedFileException { + + + if (!isFileExist(scheduleFilePath)) { + throw new MissingFileException(); + } + + Schedule schedule = new Schedule(); + + try { + // Create a FileReader and BufferedReader to read the file. + FileReader fileReader = new FileReader(scheduleFilePath); + BufferedReader bufferedReader = new BufferedReader(fileReader); + + String line; + + int targetIndex = 0; + int[] modulesPerSemArray = new int[]{0, 0, 0, 0, 0, 0, 0, 0}; + + // Read lines from the file and add them to the ArrayList. + while ((line = bufferedReader.readLine()) != null) { + + String[] splitParts = line.split(" \\| "); + + switch (splitParts[0]) { + + // Happens once on the first line of txt file so that sorting subsequent modules is possible + case "ModulesPerSem": + String[] modulesPerSemStringArray = splitParts[1].split(","); + for (int i = 0; i < modulesPerSemArray.length; i++) { + modulesPerSemArray[i] = Integer.parseInt(modulesPerSemStringArray[i]); + } + break; + case "Module": + String module = splitParts[1]; + int targetSemester = 1; + int indexOfLastModuleOfSem = modulesPerSemArray[targetSemester - 1] - 1; + while (targetIndex > indexOfLastModuleOfSem) { + indexOfLastModuleOfSem += modulesPerSemArray[targetSemester]; + targetSemester += 1; + } + + schedule.addModule(module, targetSemester); + if (splitParts[2].equals("O")) { + schedule.getModule(module).markModuleAsCompleted(); + } + targetIndex += 1; + break; + default: + if (!splitParts[0].trim().isEmpty()) { + throw new CorruptedFileException(); + } + } + } + + // Close the BufferedReader to release resources. + bufferedReader.close(); + } catch (Exception e) { + throw new CorruptedFileException(); + } + + return schedule; + + } + + /** + * Retrieves student details from a specified file path. + * + * @param studentDetailsFilePath The file path where the student details are stored. + * @return The retrieved student details. + * @throws MissingFileException If the file is missing. + * @throws CorruptedFileException If the file is corrupted or the data format is invalid. + */ + public static ArrayList retrieveStudentDetails(String studentDetailsFilePath) throws MissingFileException, + CorruptedFileException { + + if (!isFileExist(studentDetailsFilePath)) { + throw new MissingFileException(); + } + + try { + // Create a FileReader and BufferedReader to read the file. + FileReader fileReader = new FileReader(studentDetailsFilePath); + BufferedReader bufferedReader = new BufferedReader(fileReader); + + ArrayList studentDetails = new ArrayList<>(3); + + String line; + int lineNumber = 0; + + // to track which line it is supposed to be on + HashMap variableMap = new HashMap<>(); + // Adding key-value pairs + variableMap.put("Name", 0); + variableMap.put("Major", 1); + variableMap.put("Year", 2); + + // Read lines from the file and add them to the ArrayList. + while ((line = bufferedReader.readLine()) != null) { + + String[] splitParts = line.split(" \\| "); + + String userAttribute = splitParts[0]; + + //validation to see that variables has not been tampered with + if(variableMap.get(userAttribute) != lineNumber){ + throw new CorruptedFileException(); + } + + switch (splitParts[0]) { + + case "Name": + String name = splitParts[1]; + studentDetails.add(0, name); + break; + case "Major": + String major = splitParts[1]; + + // Check if major stored in txt file is valid + Major.valueOf(major.toUpperCase()); + + studentDetails.add(1, major); + break; + case "Year": + String year = splitParts[1]; + + //Check if year stored in txt file is valid + if (!Parser.isValidAcademicYear(year)){ + throw new CorruptedFileException(); + } + + studentDetails.add(2, year); + break; + default: + if (!splitParts[0].trim().isEmpty()) { + throw new CorruptedFileException(); + } + } + lineNumber += 1; + } + // Close the BufferedReader to release resources. + bufferedReader.close(); + + return studentDetails; + } catch (Exception e) { + throw new CorruptedFileException(); + } + } + + //@@author janelleenqi + /** + * Retrieves a timetable from a specified file path. + * + * @param student The student for whom the timetable is retrieved. + * @param timetableFilePath The file path where the timetable is stored. + * @return The retrieved timetable as a list of commands. + * @throws MissingFileException If the file is missing. + * @throws CorruptedFileException If the file is corrupted or the data format is invalid. + */ + public static ArrayList retrieveTimetable(Student student, String timetableFilePath) + throws MissingFileException, CorruptedFileException { + + if (!isFileExist(timetableFilePath)) { + throw new MissingFileException(); + } + + ArrayList timetableUserCommands; + try { + // Create a FileReader and BufferedReader to read the file. + FileReader fileReader = new FileReader(timetableFilePath); + BufferedReader bufferedReader = new BufferedReader(fileReader); + + String line; + if ((line = bufferedReader.readLine()) != null) { + if (!line.equals("TimetableForCurrentSem")) { + throw new CorruptedFileException(); + } + } + timetableUserCommands = new ArrayList<>(); + + // Read lines from the file and add them to the ArrayList. + while ((line = bufferedReader.readLine()) != null) { + try { + timetableUserCommands.add(new TimetableUserCommand(student, + student.getTimetable().getCurrentSemesterModulesWeekly(), line)); + } catch (InvalidTimetableUserCommandException e) { + //corrupted + throw new CorruptedFileException(); + } + + } + + // Close the BufferedReader to release resources. + bufferedReader.close(); + } catch (Exception e) { + throw new CorruptedFileException(); + } + + return timetableUserCommands; + + } + + //@@author SebasFok + /** + * Creates "schedule.txt", "studentDetails.txt" and "timetable.txt" files in the data directory to store student + * data + * + * @param storageDirectory location of storage directory to be created + */ + public static void createNewStorageFile(String storageDirectory) { + + createDirectory(storageDirectory); + + createFileInDirectory(storageDirectory, "schedule.txt"); + createFileInDirectory(storageDirectory, "studentDetails.txt"); + createFileInDirectory(storageDirectory, "timetable.txt"); + + } + + //@@author SebasFok + /** + * Takes in the location of the file in question and returns whether the file exist + * + * @param filePath location of the file in question + * @return return true if the file exist,return false otherwise + */ + public static boolean isFileExist(String filePath) { + Path path = Paths.get(filePath); + return Files.exists(path); + } + + //@@author SebasFok + /** + * This method takes in a path and creates a directory at that location. Should the + * directory already exist, no new directory will be created. + * + * @param folderPath the location of where the directory should be created + */ + public static void createDirectory(String folderPath) { + + File folder = new File(folderPath); + if (folder.mkdir()) { + //System.out.println("Folder created successfully."); + } else { + //System.out.println("Folder already exists"); + } + } + + //@@author SebasFok + /** + * This method takes in the path of a directory and creates a file 'fileName' in + * the directory. Should the file already exist, no new file will be created. + * + * @param directoryPath the location of the directory where the file should be created + * @param fileName the name of the file to be created + */ + public static void createFileInDirectory(String directoryPath, String fileName) { + + // Create the full path to the text file + String filePath = directoryPath + "/" + fileName; + + File file = new File(filePath); + + try { + // Create the file + if (file.createNewFile()) { + //System.out.println("Text file created successfully at: " + filePath); + } else { + //System.out.println("File already exists."); + } + } catch (IOException e) { + System.out.println("An IOException occurred: " + e.getMessage()); + } + } +} diff --git a/src/main/java/seedu/duke/storage/ResourceStorage.java b/src/main/java/seedu/duke/storage/ResourceStorage.java new file mode 100644 index 0000000000..372d44fa41 --- /dev/null +++ b/src/main/java/seedu/duke/storage/ResourceStorage.java @@ -0,0 +1,37 @@ +package seedu.duke.storage; + +public class ResourceStorage { + + + /** + * Determines the course requirements based on the specified major. Function is used if file is not found + * + * @param major A string representing the major (e.g., "CEG" for Computer Engineering, "CS" for Computer Science). + * @return An array of strings containing the course requirements for the specified major. + */ + static String[] determineRequirements(String major) { + String[] courseArray; + + String[] csCourseArray = { + "CS1101S", "ES2660", "GEC1000", "GEA1000", "GESS1000", + "GEN2000", "IS1108", "CS1231S", "CS2030", "CS2040S", + "CS2100", "CS2101", "CS2103T", "CS2106", "CS2109S", + "CS3230", "MA1521", "MA1522", "ST2334", "CP3880" + }; + String[] cegCourseArray = { + "CG1111A", "MA1511", "MA1512", "CS1010", "GESS1000", + "GEC1000", "GEN2000", "ES2631", "GEA1000", "DTK1234", + "EG1311", "IE2141", "EE2211", "EG2501", "CDE2000", + "PF1101", "CG4002", "MA1508E", "EG2401A", "CP3880", + "CG2111A", "CS1231", "CG2023", "CG2027", "CG2028", + "CG2271", "ST2334", "CS2040C", "CS2113", "EE2026", "EE4204" + }; + + if(major.equals("CEG")){ + courseArray = cegCourseArray; + }else{ + courseArray = csCourseArray; + } + return courseArray; + } +} diff --git a/src/main/java/seedu/duke/storage/StorageManager.java b/src/main/java/seedu/duke/storage/StorageManager.java new file mode 100644 index 0000000000..d48dbb810a --- /dev/null +++ b/src/main/java/seedu/duke/storage/StorageManager.java @@ -0,0 +1,256 @@ +package seedu.duke.storage; + +import seedu.duke.models.schema.Event; +import seedu.duke.models.schema.ModuleList; +import seedu.duke.models.schema.ModuleWeekly; +import seedu.duke.models.schema.Schedule; +import seedu.duke.models.schema.Student; +import seedu.duke.models.schema.TimetableUserCommand; +import seedu.duke.utils.exceptions.CorruptedFileException; +import seedu.duke.utils.exceptions.InvalidTimetableUserCommandException; +import seedu.duke.utils.exceptions.MissingFileException; +import seedu.duke.utils.exceptions.TimetableUnavailableException; + +import java.io.IOException; +import java.io.FileWriter; +import java.io.BufferedWriter; +import java.util.ArrayList; +import java.util.Arrays; + +import static seedu.duke.storage.FileDecoder.createNewStorageFile; +import static seedu.duke.storage.FileDecoder.retrieveTimetable; +import static seedu.duke.storage.FileDecoder.retrieveStudentDetails; +import static seedu.duke.storage.FileDecoder.retrieveSchedule; +import static seedu.duke.storage.ResourceStorage.determineRequirements; + + +public class StorageManager { + + private final String userDirectory; + + /** + * Constructs a new Storage instance with the specified file path. + */ + public StorageManager() { + this.userDirectory = System.getProperty("user.dir"); + } + + //@@author ryanlohyr + /** + * Retrieves a list of modules requirements for a specified major. + * + * @param major The major for which to retrieve requirements. + * @return An ArrayList of module codes. + * @throws RuntimeException If the specified major requirements file is not found. + */ + public static ArrayList getRequirements(String major) { + String[] courseArray = determineRequirements(major); + return new ArrayList<>(Arrays.asList(courseArray)); + } + + + + //@@author SebasFok + /** + * Creates a data directory containing txt folders to store student details, schedule and timetable. + */ + public void createUserStorageFile() { + String dataDirectory = userDirectory + "/data"; + createNewStorageFile(dataDirectory); + } + + /** + * Loads the student's schedule from the "schedule.txt" file, including modules per semester and individual modules. + * Also retains the completion status of each module in the schedule. + * + * @return A Schedule object representing the loaded schedule. + * @throws MissingFileException If the "schedule.txt" file is missing. + * @throws CorruptedFileException If the file is corrupted or has unexpected content. + */ + public Schedule loadSchedule() throws MissingFileException, CorruptedFileException { + String scheduleFilePath = userDirectory + "/data/schedule.txt"; + return retrieveSchedule(scheduleFilePath); + } + + //@@author SebasFok + /** + * Loads the student's details (name, major, and year) from the "studentDetails.txt" file. + * + * @return An ArrayList containing the loaded student details in the order [Name, Major, Year]. + * @throws MissingFileException If the "studentDetails.txt" file is missing. + * @throws CorruptedFileException If the file is corrupted or has unexpected content. + */ + public ArrayList loadStudentDetails() throws MissingFileException, CorruptedFileException { + + String studentDetailsFilePath = userDirectory + "/data/studentDetails.txt"; + + return retrieveStudentDetails(studentDetailsFilePath); + } + + //@@author janelleenqi + /** + * Loads timetable user commands from the timetable.txt save file and processes them to update the student's + * timetable. + * + * @param student The student whose timetable is being updated. + * @return An ArrayList of TimetableUserCommand objects representing the loaded commands. + * @throws MissingFileException If the timetable file is missing. + * @throws CorruptedFileException If the timetable file is corrupted or contains invalid commands. + */ + public ArrayList loadTimetable(Student student) + throws MissingFileException, CorruptedFileException { + + String timetableFilePath = userDirectory + "/data/timetable.txt"; + + return retrieveTimetable(student,timetableFilePath); + } + + //@@author janelleenqi + /** + * Adds events to the student's timetable based on the provided timetable user commands. + * + * @param timetableUserCommands An ArrayList of TimetableUserCommand objects representing the commands to process. + * @param student The student whose timetable is being updated. + * @throws CorruptedFileException If the provided timetable user commands are corrupted or contain invalid commands. + */ + public void addEventsToStudentTimetable(ArrayList timetableUserCommands, Student student) + throws CorruptedFileException { + ArrayList currentSemModulesWeekly = student.getTimetable().getCurrentSemesterModulesWeekly(); + for (TimetableUserCommand currentTimetableCommand : timetableUserCommands) { + //not exit, not clear + try { + currentTimetableCommand.processTimetableCommandLesson(currentSemModulesWeekly); + } catch (InvalidTimetableUserCommandException e) { + //corrupted + throw new CorruptedFileException(); + } + } + } + + //@@author SebasFok + /** + * Saves the student's details (name, major, and year) to the "studentDetails.txt" file. + * + * @param student The Student object containing the details to be saved. + * @throws IOException If an I/O error occurs while writing to the file. + */ + public void saveStudentDetails (Student student) throws IOException { + + String studentDetailsFilePath = userDirectory + "/data/studentDetails.txt"; + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(studentDetailsFilePath))) { + + String name = student.getName(); + + String major = student.getMajor(); + + String year = student.getYear(); + + // Write the new content to the file + writer.write("Name | " + name); + writer.newLine(); + + writer.write("Major | " + major); + writer.newLine(); + + writer.write(("Year | " + year)); + writer.newLine(); + } + } + + //@@author SebasFok + /** + * Saves the student's schedule to the "schedule.txt" file. + * + * @param student The Student object containing the details to be saved. + * @throws IOException If an I/O error occurs while writing to the file. + */ + public static void saveSchedule(Student student) throws IOException { + + String scheduleFilePath = System.getProperty("user.dir") + "/data/schedule.txt"; + try (BufferedWriter writer = new BufferedWriter(new FileWriter(scheduleFilePath))) { + + int[] modulesPerSemArray = student.getSchedule().getModulesPerSem(); + + StringBuilder modulesPerSemNumbers = new StringBuilder(Integer.toString(modulesPerSemArray[0])); + for (int i = 1; i < modulesPerSemArray.length; i++) { + modulesPerSemNumbers.append(",").append(modulesPerSemArray[i]); + } + + // Write the new content to the file + writer.write("ModulesPerSem | " + modulesPerSemNumbers); + writer.newLine(); + + ModuleList modulesPlanned = student.getSchedule().getModulesPlanned(); + int numberOfModules = student.getSchedule().getModulesPlanned().getMainModuleList().size(); + String completionStatus; + for (int i = 0; i < numberOfModules; i++) { + String moduleCode = modulesPlanned.getModuleByIndex(i).getModuleCode(); + if (modulesPlanned.getModuleByIndex(i).getCompletionStatus()) { + completionStatus = "O"; + } else { + completionStatus = "X"; + } + writer.write("Module | " + moduleCode + " | " + completionStatus); + writer.newLine(); // Move to the next line + } + } + } + + public static void saveTimetable(Student student) throws IOException { + + String timetableFilePath = System.getProperty("user.dir") + "/data/timetable.txt"; + try (BufferedWriter writer = new BufferedWriter(new FileWriter(timetableFilePath))) { + + // Write the new content to the file + writer.write("TimetableForCurrentSem"); + writer.newLine(); + + //latest info + student.updateTimetable(); + + ArrayList currentSemesterModules = student.getTimetable().getCurrentSemesterModulesWeekly(); + for (ModuleWeekly module : currentSemesterModules) { + for (Event event : module.getWeeklyTimetable()) { + writer.write(event.toSave()); + writer.newLine(); + } + } + } catch (TimetableUnavailableException e) { + //no events in timetable, do nothing + } + } + + // Below this comment are standard file methods + + //@@author SebasFok + /** + * This method takes in the path of a txt file and adds 'textToAdd' to the last line + * of the file + * + * @param filePath location of the file to be edited + * @param textToAdd String to be added to the end of the txt file + */ + public static void addTextToFile(String filePath, String textToAdd) { + try { + // Create a FileWriter object with the specified file path in append mode (true). + FileWriter fileWriter = new FileWriter(filePath, true); + + // Create a BufferedWriter to efficiently write text. + BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); + + // Write the text to the file. + bufferedWriter.write(textToAdd); + + // Write a newline character to separate lines. + bufferedWriter.newLine(); + + // Close the BufferedWriter to release resources. + bufferedWriter.close(); + + //System.out.println("Text added to the file successfully."); + } catch (IOException e) { + System.out.println("An IOException occurred: " + e.getMessage()); + } + } +} diff --git a/src/main/java/seedu/duke/utils/Utility.java b/src/main/java/seedu/duke/utils/Utility.java index d060021812..51435e9a5b 100644 --- a/src/main/java/seedu/duke/utils/Utility.java +++ b/src/main/java/seedu/duke/utils/Utility.java @@ -1,6 +1,6 @@ package seedu.duke.utils; -import seedu.duke.models.schema.Storage; +import seedu.duke.storage.StorageManager; import seedu.duke.models.schema.Student; import java.io.IOException; @@ -38,11 +38,11 @@ public static void detectInternet() throws IOException { } } - public static void saveStudentData(Storage storage, Student student) { + public static void saveStudentData(StorageManager storage, Student student) { try { storage.saveStudentDetails(student); - Storage.saveSchedule(student); - Storage.saveTimetable(student); + StorageManager.saveSchedule(student); + StorageManager.saveTimetable(student); System.out.println("Data successfully saved in save files"); } catch (IOException e) { System.out.println("Unable to save data."); diff --git a/src/test/java/seedu/duke/models/logic/DataRepositoryTest.java b/src/test/java/seedu/duke/models/logic/DataRepositoryTest.java index 5dbaee16e8..3f76bd827c 100644 --- a/src/test/java/seedu/duke/models/logic/DataRepositoryTest.java +++ b/src/test/java/seedu/duke/models/logic/DataRepositoryTest.java @@ -1,7 +1,7 @@ package seedu.duke.models.logic; import org.junit.jupiter.api.Test; -import seedu.duke.models.schema.Storage; +import seedu.duke.storage.StorageManager; import java.util.ArrayList; @@ -11,7 +11,7 @@ class DataRepositoryTest { @Test void validRequirementsReturned() { - ArrayList cegRequirementArray = Storage.getRequirements("CEG"); + ArrayList cegRequirementArray = StorageManager.getRequirements("CEG"); int numberOfRequiredCegMods = 31; assertEquals(numberOfRequiredCegMods,cegRequirementArray.size()); }