diff --git a/data/backup_taskList.txt b/data/backup_taskList.txt new file mode 100644 index 000000000..94d5e044f --- /dev/null +++ b/data/backup_taskList.txt @@ -0,0 +1,3 @@ +1. [T][X] read book +2. [D][X] read book (by: 10月 22 2023 12:00 下午) +3. [E][ ] read book (from: 10月 20 2023 12:00 下午 to: 10月 27 2023 11:59 下午) diff --git a/data/taskList.txt b/data/taskList.txt new file mode 100644 index 000000000..94d5e044f --- /dev/null +++ b/data/taskList.txt @@ -0,0 +1,3 @@ +1. [T][X] read book +2. [D][X] read book (by: 10月 22 2023 12:00 下午) +3. [E][ ] read book (from: 10月 20 2023 12:00 下午 to: 10月 27 2023 11:59 下午) diff --git a/docs/README.md b/docs/README.md index 8077118eb..d24fb1775 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,126 @@ -# User Guide +# User Guide for Oriento + +Oriento is a desktop application designed to help you manage your tasks and guide your life in the right direction. It is optimized for use through Command Line Interface(CLI). If you can type fast, it would run faster than a traditional GUI app. + +## Quick Start + +1. Ensure you have Java `11` or above installed in your Computer. + +2. Download the latest `ip.jar` from [here](https://github.com/J-Y-Yan/ip/releases/download/v2.0.2/ip.jar). + +3. Copy the file to the folder you want to use as the *home folder* for Oriento. + +4. Open a command terminal, `cd` into the folder you put the jar file in, and use the `java -jar ip.jar` command to run the application. + +5. Use `list` command to see if you have any saved task data ## Features -### Feature-ABC +### Notes about the input command format -Description of the feature. +- For each type command, a specific `COMMAND FORMAT` is required. Please go to the following to look for the details of each command. + e.g. "todo description" means to start with keyword todo and follows by the content -### Feature-XYZ -Description of the feature. +- Both `CAPITAL LETTER` and `small letter` are acceptable. But consecutive white space will be eliminated automatically. -## Usage +- Tasks such as dealine and event task would required time limit. Please use only `Date Time`: + 1. Date-Time format: `DD/MM/YYYY HHMM`. e.g. 21/10/2023 1730 + 2. if the `time is not specified`, please use 2359 by default. + + General Time: Apart from the DateTime format, the current version of app will treat `any time input` as valid time. + e.g. Friday, Birthday of Ethan, etc. Please make wise use of the time input. -### `Keyword` - Describe action -Describe the action and its outcome. +## Command Type -Example of usage: +### `Todo` - Adding a todo task -`keyword (optional arguments)` +A todo task is a general task that doesn't need any time constraint. + +Format: `todo Descrption` +Example: todo read book Expected outcome: -Description of the outcome. +``` +Got it. I've added this task: +[T][ ] read book +Now you have 1 tasks in the list. +``` + +### `Deadline` - Adding a deadline task + +A deadline task typically refers to task with a due time. + +Format: `deadline Description /by Date Time` + +Example: deadline read book /by 22/10/2023 1200 + +Expected outcome (In Chinese): ``` -expected output +Got it. I've added this task: +[D][ ] read book (by: 10月 22 2023 12:00 下午) +Now you have 2 tasks in the list. ``` + +### `Event` - Adding a event task + +A event task refers to task over a specific period + +Format: `event Description /from Date Time /to Date Time` + +Example: event read book /from 20/10/2023 1200 /to 27/10/2023 2359 + +Expected outcome (In Chinese): + +``` +Got it. I've added this task: +[E][ ] read book (from: 10月 20 2023 12:00 下午 to: 10月 27 2023 11:59 下午) +Now you have 3 tasks in the list. +``` + +## Control Task List + Refers to the following as some alternative methods to use the app: + + - **List**: View all the existing tasks created + - **Mark**: Mark an existing task as finished + - **Unmark**: Change a finished task to unfinished + - **Delete**: Remove an existing task, regardless of its finish statue + - **Find**: Look for a specific task + - **Bye**: Exit the program + - **Help**: Look for hints of using the app + +### Format of the above funationalities + Please follows either format for the input, where index refers to the task number of a task, which can be found after list out the tasks + +### 1. `Without Index` : `list`, `help`, `bye` for the corresponding function + +### 2. `With index` - Control a target task: + + Format: Action Index + + Example: mark 1 + + Expected outcome: + + ``` + Nice! I've marked this task as done: + [X] read book + ``` + + More examples: unmark 2, delete 10 + +### 3. `Find` - look for a task: + + Format: Find Content + + Example: Find read book + +### Limitations on the functions +Note that each functions may limit its usage. And these could possibly be supported in the future. +1. Find function could only look for a task content. Search by time is `NOT` supported yet +2. Delete, mark, and unmark function can only control one targte task at each time of execution +3. Don't use list with index as it is not yet supported +4. Current only Date Time format is supported. Date format is not allowed. But you can use it as a general time format. However, general time format `Does NOT` support time validating function. diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334c..000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,10 +0,0 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - } -} diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 000000000..2ce16c054 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: Oriento.Oriento + diff --git a/src/main/java/Oriento/Oriento.java b/src/main/java/Oriento/Oriento.java new file mode 100644 index 000000000..5923d9430 --- /dev/null +++ b/src/main/java/Oriento/Oriento.java @@ -0,0 +1,69 @@ +package Oriento; + + +import command.Command; +import commandFormat.CommandFormat; +import commandFormat.CommandType; + +import task.*; +import message.Text; + +import exception.OrientoException; +import java.io.FileNotFoundException; +import java.io.IOException; + +import java.util.Scanner; +import fileIO.FileIO; + +/** + * Starting point of program + */ +public class Oriento { + + public static int taskCount = 0; + public static Task[] list = new Task[100]; + + public static void main(String[] args) throws IOException { + Text.printWelcomeMessage(); + Scanner keyboard = new Scanner(System.in); + FileIO.outputFileInitialization(); + executeCommand(keyboard); + } + + /** + * this method will loop until user type in exit command + * @param keyboard represents user input source + */ + private static void executeCommand(Scanner keyboard) { + boolean isExit = false; + + while (!isExit) { + String cmd = keyboard.nextLine(); + cmd = CommandFormat.formattedCommand(cmd); + String[] commandSplits = cmd.split(" "); + if (CommandFormat.missingOrExtraTaskDescription(commandSplits)){ + continue; + } + + try { + Command command = CommandType.parseCommand(cmd); + command.executeCommand(); + isExit = command.isExit(); + FileIO.backupTaskFile(); + Text.printdottedline(); + } catch (NumberFormatException nfe) { + System.out.println("Hey, please input your command with the correct task number."); + } catch (NullPointerException npe){ + System.out.println("Your target task doesn't exist. Please input a correct task."); + } catch (OrientoException e){ + e.incorrectFormatException(commandSplits[0]); + } catch (FileNotFoundException fnf){ + System.out.println("Sorry, I cannot find the task source. Please check the task file."); + } catch (IOException io){ + System.out.println("OMG! Something went wrong! Please check if the source files are available."); + } + } + } + + +} diff --git a/src/main/java/command/AddCommand.java b/src/main/java/command/AddCommand.java new file mode 100644 index 000000000..34c289388 --- /dev/null +++ b/src/main/java/command/AddCommand.java @@ -0,0 +1,17 @@ +package command; + +/** + * general command features with user raw command and + */ +public abstract class AddCommand extends Command { + protected String command; + + /** + * initialize the inherited isExit field as false + * @param cmd represents raw user Command + */ + public AddCommand(String cmd){ + super(false); + this.command = cmd; + } +} diff --git a/src/main/java/command/ByeCommand.java b/src/main/java/command/ByeCommand.java new file mode 100644 index 000000000..aa76aa0fe --- /dev/null +++ b/src/main/java/command/ByeCommand.java @@ -0,0 +1,25 @@ +package command; + +import message.Text; + +import java.io.IOException; +import fileIO.FileIO; + + +public class ByeCommand extends Command { + + /** + * only bye message can change isExit field to true + */ + public ByeCommand() { + super(true); + } + + /** + * only print bye message when running bye execution + */ + @Override + public void executeCommand() { + Text.printByeMessage(); + } +} diff --git a/src/main/java/command/Command.java b/src/main/java/command/Command.java new file mode 100644 index 000000000..1937c9431 --- /dev/null +++ b/src/main/java/command/Command.java @@ -0,0 +1,29 @@ +package command; + +import java.io.IOException; + +/** + * parent class representing all user instructions + */ +public abstract class Command { + + /** + * use isExit to keep tract if the user want to quit + */ + private boolean isExit; + + public Command(boolean isExit){ + this.isExit = isExit; + } + + public boolean isExit(){ + return isExit; + } + + /** + * + * @throws IOException file input output error in case file cannot be found + */ + public abstract void executeCommand() throws IOException; +} + diff --git a/src/main/java/command/DeadlineCommand.java b/src/main/java/command/DeadlineCommand.java new file mode 100644 index 000000000..c602b702d --- /dev/null +++ b/src/main/java/command/DeadlineCommand.java @@ -0,0 +1,41 @@ +package command; + +import Oriento.Oriento; +import exception.OrientoException; +import exception.InvalidTimeException; +import message.Text; +import task.Deadline; + +import java.io.IOException; + +public class DeadlineCommand extends AddCommand { + + /** + * reusing the constructor of AddCommand + * @param ddlCmd represents raw user command starts from "deadline" + */ + public DeadlineCommand(String ddlCmd){ + super(ddlCmd); + } + + /** + * Exception could occur in the following cases: + * 1. Input deadline command is in wrong format + * 2. Failed to write into output file + * 3. Inappropriate deadline is detected, e.g. deadline is pass already + */ + @Override + public void executeCommand(){ + try { + Oriento.list[Oriento.taskCount] = Deadline.newDdl(this.command); + Text.createTaskSuccessMsg(); + } catch (OrientoException e) { + e.incorrectFormatException("deadline"); + } catch (IOException io){ + System.out.println("OMG! Something went wrong! Please check if the source files are available."); + } catch (InvalidTimeException ite){ + System.out.println("Deadline is over already!"); + } + + } +} diff --git a/src/main/java/command/DeleteCommand.java b/src/main/java/command/DeleteCommand.java new file mode 100644 index 000000000..a192c9070 --- /dev/null +++ b/src/main/java/command/DeleteCommand.java @@ -0,0 +1,50 @@ +package command; + +import Oriento.Oriento; +import message.Text; +import task.Task; + +import java.io.IOException; + +import static commandFormat.CommandFormat.getTaskNo; +import static fileIO.FileIO.overwriteToFile; + + +public class DeleteCommand extends Command{ + + private final String index; + + public DeleteCommand(String index) { + super(false); + this.index = index; + } + + /** + * delete command uses to delete a task at particular index i, i>0 + * @throws IOException if failed to access file + */ + @Override + public void executeCommand() throws IOException { + try { + int deleteIndex = getTaskNo(this.index); + deleteTask(deleteIndex); + } catch (NumberFormatException nfe) { + System.out.println("Hey, please input your command with the correct task number."); + } + } + + /** + * + * the taskCount is updated in the method deleteTaskSuccessMsg + */ + private static void deleteTask(int deleteIndex) throws IOException{ + if (deleteIndex <= 0 || deleteIndex> Oriento.taskCount){ + System.out.println("Oh, No! invalid index! You don't have that task. Please try again."); + return; + } + Text.deleteTaskSuccessMsg(deleteIndex); + Oriento.list = Task.updatedTaskList(deleteIndex - 1); + overwriteToFile("data/taskList.txt", Task.getConcatenateTasks()); + } + +} diff --git a/src/main/java/command/EventCommand.java b/src/main/java/command/EventCommand.java new file mode 100644 index 000000000..d5ee461b0 --- /dev/null +++ b/src/main/java/command/EventCommand.java @@ -0,0 +1,39 @@ +package command; + +import Oriento.Oriento; +import exception.OrientoException; +import exception.InvalidTimeException; +import message.Text; +import task.Event; + +import java.io.IOException; + +public class EventCommand extends AddCommand { + + public EventCommand(String eventTask){ + super(eventTask); + } + + /** + * eventTask represent the whole raw user command starts from "event" + * Exception could occur in the following cases: + * 1. Input event command is with wrong format + * 2. Failed to write into the output file + * 3. Inappropriate period is detected, e.g. event period pass already + */ + @Override + public void executeCommand(){ + try { + Oriento.list[Oriento.taskCount] = Event.newEventTask(this.command); + Text.createTaskSuccessMsg(); + } catch (OrientoException e) { + e.incorrectFormatException("event"); + } catch (IOException io){ + System.out.println("OMG! Something went wrong! Please check if the source files are available."); + } catch (InvalidTimeException d){ + System.out.println(d.getMessage()); + } + } + + +} diff --git a/src/main/java/command/FindCommand.java b/src/main/java/command/FindCommand.java new file mode 100644 index 000000000..8ab626300 --- /dev/null +++ b/src/main/java/command/FindCommand.java @@ -0,0 +1,43 @@ +package command; + +import Oriento.Oriento; + +public class FindCommand extends Command { + private String keyword; + + public FindCommand(String keyword){ + super(false); + this.keyword = keyword; + } + + /** + * the find method implement by comparing all the task descriptions with the keyword + * @return string containing all lines of results found + */ + private String findResult() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < Oriento.taskCount; i++) { + if(Oriento.list[i].getDescription().contains(this.keyword)) { + String taskAppend = (i +1) + ". " + Oriento.list[i].toString(); + sb.append(taskAppend).append("\n"); + } + } + return sb.toString(); + } + + /** + * The find execution works by simple implementation of + * checking if the description of a task contains the target + */ + @Override + public void executeCommand() { + if(! this.findResult().isEmpty()){ + System.out.println("Great! I have find some similar result for you."); + System.out.println(this.findResult()); + } else { + System.out.println("I am sorry but I cannot find any result for you..."); + } + + } + +} diff --git a/src/main/java/command/HelpCommand.java b/src/main/java/command/HelpCommand.java new file mode 100644 index 000000000..1c401e6fa --- /dev/null +++ b/src/main/java/command/HelpCommand.java @@ -0,0 +1,20 @@ +package command; + +import message.Text; + + +public class HelpCommand extends Command { + + public HelpCommand(){ + super(false); + } + + /** + * use to print help message only + */ + @Override + public void executeCommand() { + Text.printHelpMessage(); + } + +} diff --git a/src/main/java/command/ListCommand.java b/src/main/java/command/ListCommand.java new file mode 100644 index 000000000..72a1b0af6 --- /dev/null +++ b/src/main/java/command/ListCommand.java @@ -0,0 +1,25 @@ +package command; + +import message.Text; + +import java.io.IOException; + +public class ListCommand extends Command { + + public ListCommand(){ + super(false); + } + + /** + * list out all the tasks by print out all content in the saved file + * raises exception if failed to access or locate the output file + */ + @Override + public void executeCommand() { + try { + Text.printList(); + } catch (IOException io) { + System.out.println("OMG! Something went wrong! Please check if the source files are available."); + } + } +} diff --git a/src/main/java/command/MarkCommand.java b/src/main/java/command/MarkCommand.java new file mode 100644 index 000000000..bbd77e323 --- /dev/null +++ b/src/main/java/command/MarkCommand.java @@ -0,0 +1,41 @@ +package command; + +import Oriento.Oriento; +import task.Task; + +import java.io.IOException; + +import static commandFormat.CommandFormat.getTaskNo; +import static fileIO.FileIO.overwriteToFile; + +public class MarkCommand extends Command { + + private String index; + + /** + * + * @param index points to the target task + */ + public MarkCommand(String index) { + super(false); + this.index = index; + } + + /** + * overwriteFile update the whole saved file with updated content, i.e. one task has been marked + * raises exception if input incorrect number, such as in invalid range; or Failed to access output file + */ + @Override + public void executeCommand(){ + try { + int taskNo = getTaskNo(this.index); + Oriento.list[taskNo - 1].setDone(taskNo, Oriento.taskCount, Oriento.list); + overwriteToFile("data/taskList.txt", Task.getConcatenateTasks()); + } catch (NumberFormatException nfe) { + System.out.println("Hey, please input your command with the correct task number."); + } catch (IOException e) { + System.out.println("Oh NO! Failed to find save file!"); + } + } + +} diff --git a/src/main/java/command/TodoCommand.java b/src/main/java/command/TodoCommand.java new file mode 100644 index 000000000..ec087c633 --- /dev/null +++ b/src/main/java/command/TodoCommand.java @@ -0,0 +1,34 @@ +package command; + +import Oriento.Oriento; +import exception.OrientoException; +import message.Text; +import task.Todo; + +import java.io.IOException; + +public class TodoCommand extends AddCommand{ + /** + * + * @param todoCmd represents thw whole original user command starts from "todo“ + */ + public TodoCommand(String todoCmd){ + super(todoCmd); + } + + /** + * add a new todo task into the taskList + * raises exception if todo command is in incorrect format or the output file cannot be accessed + */ + @Override + public void executeCommand(){ + try { + Oriento.list[Oriento.taskCount] = Todo.newTodoTask(this.command); + Text.createTaskSuccessMsg(); + } catch (OrientoException e) { + e.incorrectFormatException("todo"); + } catch (IOException io){ + System.out.println("OMG! Something went wrong! Please check if the source files are available."); + } + } +} diff --git a/src/main/java/command/UnmarkCommand.java b/src/main/java/command/UnmarkCommand.java new file mode 100644 index 000000000..31c112c88 --- /dev/null +++ b/src/main/java/command/UnmarkCommand.java @@ -0,0 +1,40 @@ +package command; + +import Oriento.Oriento; +import task.Task; + +import java.io.IOException; + +import static commandFormat.CommandFormat.getTaskNo; +import static fileIO.FileIO.overwriteToFile; + +public class UnmarkCommand extends Command{ + + private String index; + + /** + * similar to mark + */ + public UnmarkCommand(String index) { + super(false); + this.index = index; + } + + /** + * change the task statue to unfinished + * only applicable to finished task + * It gives error message when wrong index is used or the output file cannot be found + */ + @Override + public void executeCommand() { + try { + int taskNo = getTaskNo(this.index); + Oriento.list[taskNo - 1].setNotDone(taskNo, Oriento.taskCount, Oriento.list); + overwriteToFile("data/taskList.txt", Task.getConcatenateTasks()); + } catch (NumberFormatException nfe) { + System.out.println("Hey, please input your command with the correct task number."); + } catch (IOException e) { + System.out.println("Oh NO! Failed to find save file!"); + } + } +} diff --git a/src/main/java/commandFormat/CommandFormat.java b/src/main/java/commandFormat/CommandFormat.java new file mode 100644 index 000000000..66906ffb2 --- /dev/null +++ b/src/main/java/commandFormat/CommandFormat.java @@ -0,0 +1,51 @@ +package commandFormat; + + +public class CommandFormat { + + /**This method is to transform the user command into 'expected' format for parsing purpose + * User Input is transformed to smaller letter, + * removed leading and ending space, and contracted any consecutive space + * @param cmd user command + * @return updated command + */ + public static String formattedCommand(String cmd){ + cmd = cmd.trim().toLowerCase(); + cmd = cmd.replaceAll("\\s+", " "); + + return cmd; + } + + /** + * raises exception when taskNum is not number, or containing non-numerical value + */ + public static int getTaskNo(String taskNum) { + return Integer.parseInt(taskNum); + } + + /** + * use to tackle cases with valid starting input like "todo", "event", "list", + * but lacking in index or having extra index/content + * e.g. find command should have content while list should not + */ + public static boolean missingOrExtraTaskDescription(String[] cmd){ + if (cmd.length == 1){ + if(cmd[0].equals("todo") || cmd[0].equals("event") || cmd[0].equals("deadline") + || cmd[0].equals("mark") || cmd[0].equals("unmark") || cmd[0].equals("delete") + || cmd[0].equals("find")){ + System.out.println("Please describe your target."); + return true; + } + } + else if (cmd.length>=2 && cmd[0].equals("list") ){ + System.out.println("Do you mean to see the list? Please try again using 'list'."); + return true; + }else if (cmd.length>=2 && cmd[0].equals("save") ){ + System.out.println("Do you mean to save the tasks? Please try again using 'save'."); + return true; + } + return false; + } + + +} diff --git a/src/main/java/commandFormat/CommandType.java b/src/main/java/commandFormat/CommandType.java new file mode 100644 index 000000000..b49c30168 --- /dev/null +++ b/src/main/java/commandFormat/CommandType.java @@ -0,0 +1,59 @@ +package commandFormat; + +import command.*; +import exception.OrientoException; + +public class CommandType { + + /** + * The parseCommand will parse the user input and generate a respective command object + * e.g. if the user command aims to add a todo task, it should return a todo command + * @param input represents raw user command + * @return Command object of correct type + * @throws OrientoException if input is in none of the expected cases + */ + public static Command parseCommand(String input) throws OrientoException { + + String[] commandSplits = input.split(" "); + Command command; + + switch (commandSplits[0]) { + case "list": + command = new ListCommand(); + break; + case "todo": + command = new TodoCommand(input); + break; + case "deadline": + command = new DeadlineCommand(input); + break; + case "event": + command = new EventCommand(input); + break; + case "find": + command = new FindCommand(commandSplits[1]); + break; + case "mark": + command = new MarkCommand(commandSplits[1]); + break; + case "unmark": + command = new UnmarkCommand(commandSplits[1]); + break; + case "delete": + command = new DeleteCommand(commandSplits[1]); + break; + case "bye": + command = new ByeCommand(); + break; + case "help": + command = new HelpCommand(); + break; + default: + throw new OrientoException(); + } + + return command; + } + + +} diff --git a/src/main/java/commandFormat/TimeParser.java b/src/main/java/commandFormat/TimeParser.java new file mode 100644 index 000000000..f42fc145b --- /dev/null +++ b/src/main/java/commandFormat/TimeParser.java @@ -0,0 +1,33 @@ +package commandFormat; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +public class TimeParser { + + /** + * This changes a string of time into a dataTIme object + * It only supports string with format of DDMMYYYY HHMM format + * @param input time string + * @return LocalDataTIme object + */ + public static LocalDateTime parseDateTime(String input) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy HHmm"); + return LocalDateTime.parse(input, formatter); + } + + /** + * This method can transform a dataTime object into a string information of dateTime + * @param dateTime object of LocalDateTime + * @return converted string + */ + public static String convertDateTimetoString(LocalDateTime dateTime) { + return dateTime.format(DateTimeFormatter.ofPattern("MMM d yyyy hh:mm a")); + } + + +} + + diff --git a/src/main/java/exception/InvalidTimeException.java b/src/main/java/exception/InvalidTimeException.java new file mode 100644 index 000000000..97c1ef503 --- /dev/null +++ b/src/main/java/exception/InvalidTimeException.java @@ -0,0 +1,9 @@ +package exception; + +public class InvalidTimeException extends Exception { + + public InvalidTimeException(String message) { + super(message); + } + +} diff --git a/src/main/java/exception/OrientoException.java b/src/main/java/exception/OrientoException.java new file mode 100644 index 000000000..e719f46a4 --- /dev/null +++ b/src/main/java/exception/OrientoException.java @@ -0,0 +1,49 @@ +package exception; + +public class OrientoException extends Exception { + + public OrientoException() { + } + + public OrientoException(String message) { + super(message); + } + + private void sendIncorrectMsg(String taskType){ + System.out.println("Sorry, your command for " + taskType + " task is not in the correct format. Please try again."); + } + + /** + * Use to raises exception when incorrect format is found + * tell the users what is correct format for the current input task + * @param taskType represents the task type string such as "todo", "deadline", etc. + */ + public void incorrectFormatException(String taskType) { + switch (taskType){ + case "todo": + sendIncorrectMsg("todo"); + System.out.println("Correct format: todo 'Your Todo Task' "); + System.out.println("Example: todo read book"); + break; + + case "deadline": + sendIncorrectMsg("deadline"); + System.out.println("Correct format: deadline 'Your Deadline Task' /by 'Due Time' "); + System.out.println("Example: deadline return book /by Sunday 6pm"); + break; + + case "event": + sendIncorrectMsg("event"); + System.out.println("Correct format: event 'Your Event Task' /from 'Starting Time' /to 'End Time' "); + System.out.println("Example: event project meeting /from Mon 2pm /to 4pm"); + break; + + default: + System.out.println("Please start with the supported task types: 'todo', 'deadline', 'event'. "); + } + + + + } +} + diff --git a/src/main/java/fileIO/FileIO.java b/src/main/java/fileIO/FileIO.java new file mode 100644 index 000000000..bf50ba93e --- /dev/null +++ b/src/main/java/fileIO/FileIO.java @@ -0,0 +1,205 @@ +package fileIO; + +import Oriento.Oriento; +import exception.OrientoException; +import exception.InvalidTimeException; +import task.Task; +import task.Todo; +import message.Text; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Scanner; + + +public class FileIO { + /** + * This method check if target repository and input,output files is found + * if not, create the file for users + * if error occurs in restoring data, e.g. the saved content contains invalid contents, + * clear All the data and starts with a empty file + * @throws IOException once cannot access file or file not found + * @throws OrientoException from method restoreSavedData + */ + public static void outputFileInitialization() throws IOException { + checkAndCreateDataFolder(); + checkAndCreateFile("data/taskList.txt"); + checkAndCreateFile("data/backup_taskList.txt"); + clearData(); + try{ + restoreSavedData("data/backup_taskList.txt"); + } catch (Exception e){ + System.out.println("Failed to load the saved file. Please check if your backup is in the correct format."); + System.out.println("We will start with a new empty file."); + clearData(); + } + } + + /** + * + * @param path represents the output file path + * @throws IOException only when file is not found and failed to create a new file + */ + private static void checkAndCreateFile(String path) throws IOException { + File f = new File(path); + if(!f.exists()){ + if(!f.createNewFile()){ + throw new IOException(); + } + } + } + + /** + * similar to checkAndCreateFile + */ + private static void checkAndCreateDataFolder() throws IOException { + File folder = new File("data"); + if (!folder.isDirectory()) { + if(!folder.mkdirs()){ + throw new IOException(); + } + } + } + + /** + * save all the current content to another backup file + */ + public static void backupTaskFile() { + try { + String currentContent = Task.getConcatenateTasks(); + overwriteToFile("data/backup_taskList.txt", currentContent); + } catch (IOException e) { + System.out.println("Oh No! I cannot save the file. Please check if the backup file is available."); + } + } + + /** + * clear all the saved data and set the taskList as an empty list + * use when error occurs in restoring data, e.g. containing message in wrong format + * @throws IOException if failed to access the file + */ + private static void clearData() throws IOException { + Path clearFile = Paths.get("data/taskList.txt"); + if (Files.size(clearFile) != 0) { + Files.write(clearFile, new byte[0], StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); + } + Task[] emptylist = new Task[100]; + Oriento.list = emptylist; + Oriento.taskCount = 0; + } + + /** + * + * @param filePath path of target file + * @param taskAppend the content to replace + * @throws IOException if failed to access target file + */ + public static void overwriteToFile(String filePath, String taskAppend) throws IOException { + FileWriter fw = new FileWriter(filePath); + fw.write(taskAppend); + fw.close(); + } + + /** + * + * this method aims to restore all saved data from backup file once start the program + * it implements by forming old user command for creating tasks, + * and generate the task list using the command one by one + */ + private static void restoreOneTask(Scanner keyboard) throws IndexOutOfBoundsException, + InvalidTimeException, OrientoException, IOException { + String line = keyboard.nextLine(); + line = removeNumberAndDot(line).trim(); //each line looks like [T][ ] Description + boolean isDone = line.charAt(4) == 'X'; + String description = line.substring(7); //remove brackets [T][ ] + switch (line.charAt(1)) { + case 'T': + String todoCmd = "todo " + description; + Oriento.list[Oriento.taskCount] = Todo.newTodoTask(todoCmd); + if (isDone){ + Oriento.list[Oriento.taskCount].restoreIsDone(); + } + Oriento.taskCount++; + Text.restoreTaskIntoFile(); + break; + + case 'D': + //3.[D][ ] return book (by: Friday) + String due = ddlExtract(description); + String ddltask = description.substring(0, description.indexOf("(by: ")).trim(); + String ddlCmd = "deadline " + ddltask + " /by " + due; + Oriento.list[Oriento.taskCount] = task.Deadline.newDdl(ddlCmd); + if (isDone){ + Oriento.list[Oriento.taskCount].restoreIsDone(); + } + Oriento.taskCount++; + Text.restoreTaskIntoFile(); + break; + + case 'E': + String eventCmd = getEventCmd(description); + Oriento.list[Oriento.taskCount] = task.Event.newEventTask(eventCmd); + if (isDone){ + Oriento.list[Oriento.taskCount].restoreIsDone(); + } + Oriento.taskCount++; + Text.restoreTaskIntoFile(); + break; + } + } + + /** + * + * helper function to generate the old user input based on saved file + */ + private static String getEventCmd(String description) throws IndexOutOfBoundsException{ + int indexOfFrom = description.indexOf("(from"); + int indexOfTo = description.indexOf("to:"); + int indexOfEnd = description.indexOf(")"); + String start = description.substring(indexOfFrom + 7, indexOfTo).trim(); + String end = description.substring(indexOfTo + 4, indexOfEnd); + String eventTask = description.substring(0, indexOfFrom); + return "event " + eventTask + " /from " + start + " /to " + end; + } + + private static String removeNumberAndDot(String input) { + return input.replaceFirst("^\\d+\\.\\s*", ""); + } + + /** + * helper function to extract deadline task descriptions from user input + * expected input format: 5. [D][ ] asw (by: mon) + */ + private static String ddlExtract(String description) { + int startIndex = description.indexOf("(by: ") + 5; + int endIndex = description.indexOf(")"); + return description.substring(startIndex, endIndex); + } + + /** + * This method will restore the saved data, but scanner the old data, + * and simple generate a new taskList again with the saved data + * @param DATA_PATH path of backUp file + * @throws IOException if failed to access files + * Exception is generated when creating a task using the saved data + */ + public static void restoreSavedData(String DATA_PATH) throws IOException, OrientoException, IndexOutOfBoundsException{ + try { + File file = new File(DATA_PATH); + Scanner scan = new Scanner(file); + while (scan.hasNext()) { + restoreOneTask(scan); + } + } catch (IndexOutOfBoundsException | InvalidTimeException ofb) { + System.out.println("Failed to load the saved file. Please check if your backup is in the correct format."); + System.out.println("We will start with a new empty file."); + clearData(); + } + } +} diff --git a/src/main/java/message/Text.java b/src/main/java/message/Text.java new file mode 100644 index 000000000..87a601814 --- /dev/null +++ b/src/main/java/message/Text.java @@ -0,0 +1,95 @@ +package message; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Scanner; + +import static Oriento.Oriento.list; +import static Oriento.Oriento.taskCount; + +public class Text { + /** + * this class contains all user interacting messages + */ + + public static void printWelcomeMessage() { + System.out.println("Hello! I'm Oriento."); + System.out.println("What can I help you?"); + } + + public static void printHelpMessage() { + System.out.println("Sorry I do not tell you use how to give commands. Please see the following hints."); + System.out.println("Hint1: To create a task, please start with 'todo', 'event', 'deadline'."); + System.out.println("Hint2: To modify task statue, please start with 'mark' or 'unmark'."); + System.out.println("please use 'list' to look for your task list, or 'delete' to remove a task."); + System.out.println("You may use 'find' to search for your task."); + } + + public static void printByeMessage(){ + System.out.println("Bye. Hope to see you again soon!"); + } + + private static void printFileContents(String filePath) throws IOException { + File f = new File(filePath); // create a File for the given file path + Scanner s = new Scanner(f); // create a Scanner using the File as the source + while (s.hasNext()) { + System.out.println(s.nextLine()); + } + } + + /** + * Instead of printing from current list, this method print the content from a output file + * @throws IOException if failed to access file + */ + public static void printList() throws IOException{ + if (taskCount == 0){ + System.out.println("You don't have any tasks now. Do you want to add a new task?"); + return; + } + printFileContents("data/taskList.txt"); + + } + + /** + * write into output file every time new task is created successfully + * @throws IOException if failed to access tasks + */ + public static void createTaskSuccessMsg() throws IOException{ + System.out.println("Got it. I've added this task:"); + System.out.println(list[taskCount]); + taskCount++; + System.out.println("Now you have " + taskCount + " tasks in the list."); + + String taskAppend = taskCount + ". " + list[taskCount - 1].toString() + "\n"; + writeToFile("data/taskList.txt", taskAppend); + } + + public static void restoreTaskIntoFile() throws IOException { + String taskAppend = taskCount + ". " + list[taskCount - 1].toString() + "\n"; + writeToFile("data/taskList.txt", taskAppend); + } + + private static void writeToFile(String filePath, String taskAppend) throws IOException { + FileWriter fw = new FileWriter(filePath, true); + fw.write(taskAppend); + fw.close(); + } + + /** + * tell the user the task is deleted successfully + * decrement the taskCount by 1 + * @param deleteIndex index of the deleted task + */ + public static void deleteTaskSuccessMsg(int deleteIndex) { + System.out.println("Noted. I've removed this task:"); + System.out.println(list[deleteIndex - 1]); + taskCount--; + System.out.println("Now you have " + taskCount + " tasks in the list."); + } + + public static void printdottedline() { + System.out.println("================================================"); + } + +} diff --git a/src/main/java/task/Deadline.java b/src/main/java/task/Deadline.java new file mode 100644 index 000000000..a0b0787ce --- /dev/null +++ b/src/main/java/task/Deadline.java @@ -0,0 +1,69 @@ +package task; + +import commandFormat.TimeParser; +import exception.OrientoException; +import exception.InvalidTimeException; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; + +public class Deadline extends Task { + + protected String due; + + public Deadline(String description, String due) { + super(description); + this.due = due; + } + + /** + * + * @param userCommand Whole userInput start from "deadline" + * @return Deadline object + * @throws OrientoException Raises exception if invalid due time while able to parse the input time + * If failed to parse the time, just assume valid input + */ + public static Deadline newDdl(String userCommand) throws OrientoException, InvalidTimeException { + // command format: deadline return book /by Sunday + if (!(userCommand.contains("/by"))){ + throw new OrientoException("Oh, no! I cannot detect the keyword '/by' "); + } + userCommand = userCommand.substring(9); //remove "deadline" + + if(userCommand.startsWith("/by")){ + throw new OrientoException("Cannot find deadline description. Please try again."); + } + + int indexOfBy = userCommand.indexOf("/by"); + String ddlTask = userCommand.substring(0, indexOfBy).trim(); + String due = userCommand.substring(indexOfBy + 4); + + try{ + LocalDateTime dueTime = TimeParser.parseDateTime(due); + LocalDateTime now = LocalDateTime.now(); + if (dueTime.isBefore(now)) { + throw new InvalidTimeException("Invalid Time! Deadline is over already!"); + } + } catch (DateTimeParseException d){ + /* do nothing */ + } + return new Deadline(ddlTask, due); + } + + + /** + * print example: [D][ ] return book (by: Friday) + * if failed to parse input time, assume user input is valid + * @return string of task message + */ + @Override + public String toString() { + try { + LocalDateTime time = TimeParser.parseDateTime(this.due); + String dueTime = TimeParser.convertDateTimetoString(time); + return "[D]" + super.toString() + " (by: " + dueTime + ")"; + } catch (Exception e) { + return "[D]" + super.toString() + " (by: " + this.due + ")"; + } + } +} diff --git a/src/main/java/task/Event.java b/src/main/java/task/Event.java new file mode 100644 index 000000000..970ee420e --- /dev/null +++ b/src/main/java/task/Event.java @@ -0,0 +1,87 @@ +package task; + +import commandFormat.TimeParser; +import exception.OrientoException; +import exception.InvalidTimeException; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; + +public class Event extends Task { + protected String start, end; + + public Event(String description, String start, String end) { + super(description); + this.start = start; + this.end = end; + } + + /** + * extreact start and end from userCommand first + * then test if the input is valid + * if failed to parse input, assume the user is true + * @InvalidTimeException caught in EventCommand class + */ + public static Event newEventTask(String userCommand) throws OrientoException,InvalidTimeException { + //command format: event project meeting /from Mon 2pm /to 4pm + if (!(userCommand.contains("/from")) || !(userCommand.contains("/to"))){ + throw new OrientoException("Oh, no! There is no '/from' or '/to' "); + } + userCommand = userCommand.substring(6); //remove "event" + + if(userCommand.startsWith("/from")){ + throw new OrientoException("Cannot find event description. Please try again."); + } + + int indexOfFrom = userCommand.indexOf("/from"); + int indexOfTo = userCommand.indexOf("/to"); + String start = userCommand.substring(indexOfFrom + 6, indexOfTo).trim(); + String end = userCommand.substring(indexOfTo + 4).trim(); + String eventTask = userCommand.substring(0, indexOfFrom).trim(); + testTimeInput(start, end); + + return new Event(eventTask, start, end); + } + + /** + * test if the user input period for event is valid + * @param startTime the starting time of event + * @param endTime the end time of event + * @throws InvalidTimeException if the input time is invalid, which could happen in the following cases: + * 1. end time occurs before start time + * 2. end time is over already + */ + private static void testTimeInput(String startTime, String endTime) throws InvalidTimeException { + try{ + LocalDateTime start = TimeParser.parseDateTime(startTime); + LocalDateTime end = TimeParser.parseDateTime(endTime); + LocalDateTime now = LocalDateTime.now(); + if(end.isBefore(now)){ + throw new InvalidTimeException("The event is ended. Please check the end time again."); + } + if(end.isBefore(start)){ + throw new InvalidTimeException("End time is early than start time."); + } + } catch(DateTimeParseException d) { + /* do nothing */ + } + } + + /** + * print example: [E][ ] project meeting (from: Aug 6th 2pm to: 4pm) + * @return string containing event task information + */ + @Override + public String toString() { + try { + LocalDateTime tmp = TimeParser.parseDateTime(this.start); + String startTime = TimeParser.convertDateTimetoString(tmp); + tmp = TimeParser.parseDateTime(this.end); + String endTime = TimeParser.convertDateTimetoString(tmp); + return "[E]" + super.toString() + " (from: " + startTime + " to: " + endTime + ")"; + } catch (Exception e) { + return "[E]" + super.toString() + " (from: " + this.start + " to: " + this.end + ")"; + } + } + +} diff --git a/src/main/java/task/Task.java b/src/main/java/task/Task.java new file mode 100644 index 000000000..53e6b3112 --- /dev/null +++ b/src/main/java/task/Task.java @@ -0,0 +1,91 @@ +package task; + +import Oriento.Oriento; + + +public class Task { + protected String description; + protected boolean isDone; + + public Task(String description) { + this.description = description; + this.isDone = false; + } + + public String getStatusIcon() { + return (isDone ? "X" : " "); // mark done task with X + } + + /** + * + * @param taskNo the index of the target task + * @param taskCount total number of existing task in the list + * @param list the target taskList (there will only be one list in this application). + */ + public void setDone(int taskNo, int taskCount, Task[] list) { + if ( (taskNo > taskCount ) || (taskNo <1) ){ + System.out.println("Oops! You don't have any task in this positions."); + } else if(this.isDone){ + System.out.println("You have already completed the task."); + } else{ + this.isDone = true; + System.out.println(" Nice! I've marked this task as done:\n" + + " [X] " + list[taskNo - 1].description); + } + } + + public void restoreIsDone(){ + this.isDone = true; + } + + /** + * similar to setDone method + */ + public void setNotDone(int taskNo, int taskCount, Task[] list) { + if ( (taskNo > taskCount ) || (taskNo <1) ){ + System.out.println("Oops! You don't have any task in this position."); + } else if(!this.isDone){ + System.out.println("Oh, you haven't finished this yet."); + } else{ + this.isDone = false; + System.out.println("OK, I've marked this task as not done yet:\n" + + " [ ] " + list[taskNo - 1].description); + } + } + + /** + * only use to update list array once an objected is deleted and removed + * @param indexOfDelete target index + * @return a new list object array + */ + public static Task[] updatedTaskList(int indexOfDelete){ + Task[] newList = new Task[100]; + int numOfCopy = Oriento.list.length - indexOfDelete - 1; + System.arraycopy(Oriento.list, 0, newList, 0, indexOfDelete); + System.arraycopy(Oriento.list, indexOfDelete + 1, newList, indexOfDelete, numOfCopy); + return newList; + } + + /** + * This method will only read the global taskList + * @return string contains all task data inside the global taskList + */ + public static String getConcatenateTasks() { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < Oriento.taskCount; i++) { + String taskAppend = (i +1) + ". " + Oriento.list[i].toString(); + stringBuilder.append(taskAppend).append("\n"); + } + return stringBuilder.toString(); + } + + public String getDescription() { + return description; + } + + @Override + public String toString() { + return "[" + this.getStatusIcon() + "] " + this.description; + } + +} diff --git a/src/main/java/task/Todo.java b/src/main/java/task/Todo.java new file mode 100644 index 000000000..c025ba7f4 --- /dev/null +++ b/src/main/java/task/Todo.java @@ -0,0 +1,27 @@ +package task; + +import exception.OrientoException; + +public class Todo extends Task { + + public Todo(String description) { + super(description); + } + + public static Todo newTodoTask(String userCommand) throws OrientoException { + String[] todoSplit = userCommand.split(" ", 2); + if ( todoSplit[1].isEmpty() ){ + throw new OrientoException("You got nothing to create. Please try again."); + } + return new Todo(todoSplit[1]); + } + + /** + * print example: [T][ ] read book + * @return string contains todo task information + */ + @Override + public String toString() { + return "[T]" + super.toString() ; + } +} diff --git a/src/text-ui-test/ACTUAL.TXT b/src/text-ui-test/ACTUAL.TXT new file mode 100644 index 000000000..e69de29bb diff --git a/src/text-ui-test/EXPECTED.txt b/src/text-ui-test/EXPECTED.txt new file mode 100644 index 000000000..2a13a1132 --- /dev/null +++ b/src/text-ui-test/EXPECTED.txt @@ -0,0 +1,22 @@ +Hello! I'm Oriento. +What can I do for you? +Got it. I've added this task: +[T][ ] read book +Now you have 1 tasks in the list. +Got it. I've added this task: +[T][ ] play piano +Now you have 2 tasks in the list. + Nice! I've marked this task as done: + [X] play piano +Got it. I've added this task: +[D][ ] return book (by: Friday) +Now you have 3 tasks in the list. +Got it. I've added this task: +[E][ ] java lesson (from: Friday 4pm to: 6pm) +Now you have 4 tasks in the list. +Oh, you haven't finished this yet. +1.[T][ ] read book +2.[T][X] play piano +3.[D][ ] return book (by: Friday) +4.[E][ ] java lesson (from: Friday 4pm to: 6pm) +Bye. Hope to see you again soon! diff --git a/src/text-ui-test/input.txt b/src/text-ui-test/input.txt new file mode 100644 index 000000000..eb726634e --- /dev/null +++ b/src/text-ui-test/input.txt @@ -0,0 +1,8 @@ +todo read book +todo play piano +mark 2 +deadline return book /by Friday +event java lesson /from Friday 4pm /to 6pm +unmark 4 +list +bye diff --git a/src/text-ui-test/runtest.bat b/src/text-ui-test/runtest.bat new file mode 100644 index 000000000..0b643f82e --- /dev/null +++ b/src/text-ui-test/runtest.bat @@ -0,0 +1,21 @@ +@ECHO OFF + +REM create bin directory if it doesn't exist +if not exist ..\bin mkdir ..\bin + +REM delete output from previous run +del ACTUAL.TXT + +REM compile the code into the bin folder +javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\*.java +IF ERRORLEVEL 1 ( + echo ********** BUILD FAILURE ********** + exit /b 1 +) +REM no error here, errorlevel == 0 + +REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT +java -classpath ..\bin Duke < input.txt > ACTUAL.TXT + +REM compare the output to the expected output +FC ACTUAL.TXT EXPECTED.TXT \ No newline at end of file diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e..2a13a1132 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,22 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - +Hello! I'm Oriento. +What can I do for you? +Got it. I've added this task: +[T][ ] read book +Now you have 1 tasks in the list. +Got it. I've added this task: +[T][ ] play piano +Now you have 2 tasks in the list. + Nice! I've marked this task as done: + [X] play piano +Got it. I've added this task: +[D][ ] return book (by: Friday) +Now you have 3 tasks in the list. +Got it. I've added this task: +[E][ ] java lesson (from: Friday 4pm to: 6pm) +Now you have 4 tasks in the list. +Oh, you haven't finished this yet. +1.[T][ ] read book +2.[T][X] play piano +3.[D][ ] return book (by: Friday) +4.[E][ ] java lesson (from: Friday 4pm to: 6pm) +Bye. Hope to see you again soon! diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb..eb726634e 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,8 @@ +todo read book +todo play piano +mark 2 +deadline return book /by Friday +event java lesson /from Friday 4pm /to 6pm +unmark 4 +list +bye diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 087374464..0b643f82e 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -4,7 +4,7 @@ REM create bin directory if it doesn't exist if not exist ..\bin mkdir ..\bin REM delete output from previous run -if exist ACTUAL.TXT del ACTUAL.TXT +del ACTUAL.TXT REM compile the code into the bin folder javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\*.java @@ -18,4 +18,4 @@ REM run the program, feed commands from input.txt file and redirect the output t java -classpath ..\bin Duke < input.txt > ACTUAL.TXT REM compare the output to the expected output -FC ACTUAL.TXT EXPECTED.TXT +FC ACTUAL.TXT EXPECTED.TXT \ No newline at end of file