diff --git a/data/duke.txt b/data/duke.txt new file mode 100644 index 0000000000..be072a3ff5 --- /dev/null +++ b/data/duke.txt @@ -0,0 +1,2 @@ +T | 1 | borrow book +E | 0 | project meeting|Mon 2-4pm diff --git a/docs/README.md b/docs/README.md index fd44069597..d5e94b427a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,20 +1,173 @@ -# User Guide +# User Guide for Application SHEENA ## Features -### Feature 1 -Description of feature. +### 1. Add a todo task -## Usage +Users are able to add one specific task every time they type "todo" command +A capital letter "T" will shown when you successfully added the task. +A tick or cross will shown besides the capital letter "E" to indicate whether the task is done by the user. -### `Keyword` - Describe action +"todo" - to indicate the system that this is a todo task +"description" - describe the task -Describe action and its outcome. -Example of usage: +Format: todo description -`keyword (optional arguments)` +Eg: todo Love Sheena. -Expected outcome: +IMPORTANT: User must provide a description. -`outcome` +Outcome: +[T].[tick / cross] todo Love Sheena + + +### 2. Add an Event task + +Users are able to add one specific event task every time they type "event" command +We will use /at to differentiate the time +A capital letter "E" will shown when you successfully added the event. +A tick or cross will shown besides the capital letter "E" to indicate whether the event is done by the user. + + +"event" - to indicate the system that this is a todo task +"/at" - to indicate the system that the time is behind the /at +"description" - describe the event +"time" - describe the details for time (at: Mon 2-4pm) + +Format: event (description) /at (time) + +Eg: event Love Coco /at Mon 2-4pm + +IMPORTANT: User must provide a description and time. + +Outcome: +[E][tick / cross] event Love Coco (at: Mon 2-4pm) + + + +### 3. Add a Deadline task + +Users are able to add one specific deadline task every time they type "event" command +We will use /by to differentiate the day +A capital letter "D" will shown when you successfully added the deadline. +A tick or cross will shown besides the capital letter "E" to indicate whether the task is done by the user. + + +"event" - to indicate the system that this is a todo task +"/by" - to indicate the system that the day is behind the /by +"description" - describe the event +"day" - describe the details for day (Eg: Sunday) + +Format: event (description) /by (day) + +Eg: deadline I love you Sheena /by Sunday + +IMPORTANT: User must provide a description and day. + +Outcome: +[D][tick / cross] event I love you Sheena (by: Sunday) + + +### 4. List Down the task List + +Users are able to retrieve the task that they had saved. + +"list" - to list down the task. + +Format: list + +Eg: list + +Outcome: + +Sheena: Yay! Now you have these ~ +---------------------------- +Sheena: Well, you have these items in your list: + + 1. [T][✓]buy food + 2. [T][✓]miss Sheena + 3. [T][✘]love Sheena forever + 4. [T][✘]I love you Coco +---------------------------- +Sheena: Yay! 4 tasks listed! + + +### 5. Delete A Task. + +Users are able to delete one specific event task every time they type "delete" command + + +"delete" - to indicate the system that this is a delete function +"taskNumber" - indicate the index number for the specific task (eg: 5) + +Format: delete taskNumber + +Eg: delete 5 + +IMPORTANT: User must provide a description and time. + +Outcome: + +Sheena: YAY! You deleted task: [T][✘]buy books ! + +Now you have (taskNumber-1) tasks in your list ~ + + +### 6. Find a task + +Users are able to add one specific event task every time they type "find" command + + +"find" - to indicate the system that this is a todo task +"description" - describe the thing you want to find + +Format: find (description) + +Eg: find Sheena + +IMPORTANT: User must provide a description and time. + +Outcome: +Sheena: Yay! Here are the matching tasks in your list: + +---------------------------- +Sheena: Well, you have these items in your list: + + 1. [T][✓]miss Sheena + 2. [T][✘]love Sheena forever +---------------------------- +Sheena: Yay! 2 tasks listed! + + +### 7. Clear the list + +Users are able to clear the task list + + +"clear" - to indicate the system that user is clearing the task + +Format: clear + +Eg: clear + +Outcome: +Sheena: So sad but yeah the list has been cleared.. + + +### 8. Exit the program + +Users are able to exit program. The task will be saved in a text file (eg: duke.txt) + + +"bye" - to indicate the system that user is going to exit the program + +Format: bye + +Eg: bye + +Outcome: + +Sheena: Let me save down everything ^^ + +Sheena: I will miss you! Hope to see you again soon! diff --git a/duke.txt b/duke.txt new file mode 100644 index 0000000000..e3a6446603 --- /dev/null +++ b/duke.txt @@ -0,0 +1,4 @@ +T | 1 | buy food +T | 1 | miss Sheena +T | 0 | love Sheena forever +T | 0 | I love you Coco diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 5d313334cc..65c858e702 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,10 +1,78 @@ +import duke.command.Command; +import duke.command.CommandOption; +import duke.command.CommandBye; +import duke.taskList.TaskList; +import duke.parser.Parser; +import duke.storage.Storage; +import duke.storage.Storage.StorageFilePathException; +import duke.Ui.TextUi; + +/** + * The main program should keep it short + * Exits when command "bye" stated + * + */ public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); + + private TextUi ui; + public Storage storage; + private TaskList list; + + public static void main(String... launchArgs) { + new Duke().runTheProgram(launchArgs); } + + public void runTheProgram(String[] launchArgs) { + start(launchArgs); + runCommand(); + exit(); + } + + private void start(String[] launchArgs) { + try { + this.ui = new TextUi(); + this.storage = initializeStorage(launchArgs); + this.list = storage.load(); + ui.welcomeMessage(storage.getPath()); + ui.storedList(); + } catch (Storage.StorageOperationException | StorageFilePathException e) { + ui.failed(); + throw new RuntimeException(e); + } + } + + private void exit(){ + ui.goodbye(); + System.exit(0); + } + + /** run the program until the user type "bye" */ + private void runCommand(){ + Command command; + do { + String userCommand = ui.getUserCommand(); + command = new Parser().parseCommand(userCommand); + CommandOption result = executeCommand(command); + ui.printResult(result); + } while (!CommandBye.isExit(command)); + } + + private CommandOption executeCommand(Command command) { + try { + command.setData(list); + storage.save(list); + return command.execute(); + } catch (Exception e) { + ui.PresentMessages(e.getMessage()); + throw new RuntimeException(e); + } + + } + + private Storage initializeStorage(String[] launchArgs) throws StorageFilePathException { + boolean isStorageFileSpecified = launchArgs.length > 0; + return !isStorageFileSpecified ? new Storage() : new Storage(launchArgs[0]); + } + + } diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..9f37e4e0aa --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: Duke + diff --git a/src/main/java/duke/Ui/TextUi.java b/src/main/java/duke/Ui/TextUi.java new file mode 100644 index 0000000000..ff6e24a2d5 --- /dev/null +++ b/src/main/java/duke/Ui/TextUi.java @@ -0,0 +1,71 @@ +package duke.Ui; +import duke.command.CommandOption; +import duke.taskList.TaskList; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.Scanner; + +public class TextUi { + + private final Scanner in; + private final PrintStream out; + + public TextUi() { + this(System.in, System.out); + } + + public TextUi(InputStream in, PrintStream out) { + this.in = new Scanner(in); + this.out = out; + } + + public String getUserCommand() { + out.print("Sheena: Please enter your command ~ \n"); + String Input = in.nextLine(); + PresentMessages("Sheena: Yay! This is your input (" + Input + ") !"); + return Input; + } + + public void welcomeMessage(String storageFile) { + String Info = String.format(USING_STORAGE, storageFile); + PresentMessages(WELCOME, Info); + } + + public void goodbye() { + PresentMessages(GOODBYE); + } + + public void failed() { + PresentMessages(FAILED); + } + + /** + * Shows message(s) to the user + */ + public void PresentMessages(String... message) { + for (String a : message) { + out.println(a); + } + } + + public void printResult(CommandOption result) { + PresentMessages(result.feedback); + } + + + public void storedList() { + if (TaskList.size() <= 0) { + System.out.println("\nSheena: Hmm nothing here.\nSheena: It's okay! Let me create a new one!"); + } else { + System.out.println("---------------------------------------------------------"); + System.out.println("Sheena: Yay! I successfully retreat your previous list!"); + TaskList.storedTaskList(); + System.out.println("---------------------------------------------------------"); + } + } + + public static final String GOODBYE = "Sheena: I will miss you! Hope to see you again soon!"; + public static final String FAILED = "Sheena: Erm can't run now. Gonna Exit first."; + public static final String WELCOME = "Hello! I'm Sheena ^^ \nWhat can I do for you ?"; + public static final String USING_STORAGE = "Sheena: using storage file ( %1$s )"; +} diff --git a/src/main/java/duke/command/Command.java b/src/main/java/duke/command/Command.java new file mode 100644 index 0000000000..2bdbf32bbe --- /dev/null +++ b/src/main/java/duke/command/Command.java @@ -0,0 +1,42 @@ +package duke.command; + +import duke.taskList.TaskList; +import duke.task.Task; +import java.util.ArrayList; + +public class Command { + private String nameOfCommand; + private String Argument; + protected TaskList taskList; + + + public static final String MESSAGE_TASKS_LISTED_OVERVIEW = "Sheena: Yay! %1$d tasks listed!"; + + public static String getMessageForTaskListShownSummary(ArrayList tasksDisplayed) { + return String.format(MESSAGE_TASKS_LISTED_OVERVIEW, tasksDisplayed.size()); + } + + public CommandOption execute() { + throw new UnsupportedOperationException("This method must be implemented by child classes"); + } + + public void setData(TaskList taskList) { + this.taskList = taskList; + } + + public String getCommandName() { + return nameOfCommand; + } + + public void setCommandName(String commandName) { + nameOfCommand = commandName; + } + + public String getArgs() { + return Argument; + } + + public void setArgs(String args) { + Argument = args; + } +} \ No newline at end of file diff --git a/src/main/java/duke/command/CommandBye.java b/src/main/java/duke/command/CommandBye.java new file mode 100644 index 0000000000..fce5bdc075 --- /dev/null +++ b/src/main/java/duke/command/CommandBye.java @@ -0,0 +1,15 @@ +package duke.command; + +public class CommandBye extends Command { + + public static final String EXIT = "Sheena: Let me save down everything ^^\n "; + + @Override + public CommandOption execute() { + return new CommandOption(EXIT); + } + + public static boolean isExit(Command command) { + return command instanceof CommandBye; // instanceof returns false if it is null + } +} diff --git a/src/main/java/duke/command/CommandClear.java b/src/main/java/duke/command/CommandClear.java new file mode 100644 index 0000000000..63acb9c1bb --- /dev/null +++ b/src/main/java/duke/command/CommandClear.java @@ -0,0 +1,17 @@ +package duke.command; + +import duke.taskList.TaskList; + +public class CommandClear extends Command { + + public static final String SUCCESS = "Sheena: So sad but yeah the list has been cleared.."; + + public CommandClear() { + } + + @Override + public CommandOption execute() { + TaskList.clear(); + return new CommandOption(SUCCESS); + } +} diff --git a/src/main/java/duke/command/CommandDeadline.java b/src/main/java/duke/command/CommandDeadline.java new file mode 100644 index 0000000000..7aed8ff46f --- /dev/null +++ b/src/main/java/duke/command/CommandDeadline.java @@ -0,0 +1,34 @@ +package duke.command; + +import duke.taskList.TaskList; +import duke.task.Deadlines; + +public class CommandDeadline extends Command { + + public static final String SUCCESS = "Sheena: YAY! This task is added: \n" + +"\n%s.\n Sheena: Now you have %d tasks in your list ~ "; + + public static final String DUPLICATE = "Sheena: Erm. This task is already in the list..."; + + private Deadlines Add; + + public CommandDeadline(Deadlines Add){ + this.Add = Add; + } + public CommandDeadline(String desc,String dueDate) throws StringIndexOutOfBoundsException { + if (desc.isEmpty() || dueDate.isEmpty()) { + throw new StringIndexOutOfBoundsException(); + } + this.Add = new Deadlines(desc,dueDate); + } + + @Override + public CommandOption execute() { + try { + TaskList.add(Add); + return new CommandOption(String.format(SUCCESS, Add.toString(),TaskList.size())); + } catch (TaskList.DuplicateTaskException dpe) { + return new CommandOption(DUPLICATE); + } + } +} diff --git a/src/main/java/duke/command/CommandDelete.java b/src/main/java/duke/command/CommandDelete.java new file mode 100644 index 0000000000..64437f0da8 --- /dev/null +++ b/src/main/java/duke/command/CommandDelete.java @@ -0,0 +1,30 @@ +package duke.command; +import duke.taskList.TaskList; +import duke.task.Task; + +public class CommandDelete extends Command { + + int deleteIndex; + + public CommandDelete(int targetIndex) { + this.deleteIndex = targetIndex; + } + + public static final String INVALID_TASK = "Sheena: I can't delete, you have not added task %s yet.."; + + public static final String DELETE = "Sheena: YAY! You deleted task: %1$s !\n"; + + @Override + + public CommandOption execute() { + try { + Task t = TaskList.retrieve(deleteIndex-1); + TaskList.remove(deleteIndex-1); + return new CommandOption(String.format(DELETE,t.toString()) + + "\nNow you have " + TaskList.size() + " tasks in your list ~"); + + } catch (IndexOutOfBoundsException | TaskList.TaskNotFoundException ie) { + return new CommandOption(String.format(INVALID_TASK,deleteIndex)); + } + } +} diff --git a/src/main/java/duke/command/CommandDone.java b/src/main/java/duke/command/CommandDone.java new file mode 100644 index 0000000000..94e141bd11 --- /dev/null +++ b/src/main/java/duke/command/CommandDone.java @@ -0,0 +1,30 @@ +package duke.command; +import duke.taskList.TaskList; +import duke.task.Task; + +public class CommandDone extends Command { + + int numberToMark; + + public static final String MARK_TASK_DONE = "Sheena: YAY! I have marked it as done: %1$s"; + public static final String INVALID_TASK = "Sheena: Erm. Maybe you provided a wrong number ?"; + public static final String TASK_NOT_EXIST = "Sheena: Erm, I can't delete because you have not add task %s yet.."; + + public CommandDone(int numberToMark) { + this.numberToMark = numberToMark; + } + + + @Override + public CommandOption execute() { + try { + Task t = TaskList.markAsDone(numberToMark-1); + return new CommandOption(String.format(MARK_TASK_DONE,t.toString())); + } catch (TaskList.TaskNotFoundException tnf) { + return new CommandOption(TASK_NOT_EXIST); + } catch (IndexOutOfBoundsException ie) { + //ie: if the user type 0 / number that is not within the list + return new CommandOption(INVALID_TASK); + } + } +} diff --git a/src/main/java/duke/command/CommandEvent.java b/src/main/java/duke/command/CommandEvent.java new file mode 100644 index 0000000000..c5a818915b --- /dev/null +++ b/src/main/java/duke/command/CommandEvent.java @@ -0,0 +1,36 @@ +package duke.command; + +import duke.taskList.TaskList; +import duke.task.Events; + +public class CommandEvent extends Command { + + public static final String SUCCESS = "Sheena: YAY! added this task: \n" + +"\t%s.\n Sheena: Now you have %d tasks in your list ~"; + + public static final String DUPLICATE = "Sheena: Erm. This task is already in the list..."; + + private Events Add; + + public CommandEvent(Events Add) { + this.Add = Add; + } + + public CommandEvent(String desc, String date) throws StringIndexOutOfBoundsException { + if(!(!desc.isEmpty()) || !(!date.isEmpty())){ + throw new StringIndexOutOfBoundsException(); + } + this.Add = new Events(desc, date); + } + + @Override + public CommandOption execute() { + try { + TaskList.add(Add); + return new CommandOption(String.format(SUCCESS, Add, TaskList.size())); + } catch (TaskList.DuplicateTaskException dpe) { + return new CommandOption(DUPLICATE); + } + + } +} diff --git a/src/main/java/duke/command/CommandFind.java b/src/main/java/duke/command/CommandFind.java new file mode 100644 index 0000000000..c73c8ba308 --- /dev/null +++ b/src/main/java/duke/command/CommandFind.java @@ -0,0 +1,31 @@ +package duke.command; + +import duke.taskList.TaskList; +import duke.task.Task; +import duke.Ui.TextUi; +import java.util.ArrayList; + +import static duke.taskList.TaskList.filterTheList; +public class CommandFind extends Command{ + + public static final String EMPTY_LIST_MESSAGE = "Sheena: Erm, List is currently empty."; + String Keyword; + public CommandFind (String filtered_word){ + + this.Keyword = filtered_word; + } + + @Override + public CommandOption execute() { + try { + ArrayList duplicateList = TaskList.copy(); + ArrayList listToFilter = filterTheList(duplicateList, Keyword); + System.out.println("Sheena: Yay! Here are the matching tasks in your list:\n"); + TaskList.printList(listToFilter); + return new CommandOption(String.format(getMessageForTaskListShownSummary(listToFilter),listToFilter)); + } catch (NullPointerException e) { + return new CommandOption(EMPTY_LIST_MESSAGE); + } + + } +} diff --git a/src/main/java/duke/command/CommandList.java b/src/main/java/duke/command/CommandList.java new file mode 100644 index 0000000000..5608d63f20 --- /dev/null +++ b/src/main/java/duke/command/CommandList.java @@ -0,0 +1,23 @@ +package duke.command; +import duke.taskList.TaskList; +import duke.task.Task; +import java.util.ArrayList; + +public class CommandList extends Command{ + + public static final String EMPTY_LIST = "Sheena: Erm. There is nothing in the list..."; + + @Override + public CommandOption execute() { + try { + + ArrayList duplicateTaskList = TaskList.copy(); + System.out.println("Sheena: Yay! Now you have these ~"); + TaskList.printList(duplicateTaskList); + return new CommandOption(String.format(getMessageForTaskListShownSummary(duplicateTaskList),duplicateTaskList)); + } catch (NullPointerException e) { + return new CommandOption(EMPTY_LIST); + } + + } +} diff --git a/src/main/java/duke/command/CommandOption.java b/src/main/java/duke/command/CommandOption.java new file mode 100644 index 0000000000..31f44d2339 --- /dev/null +++ b/src/main/java/duke/command/CommandOption.java @@ -0,0 +1,11 @@ +package duke.command; + +public class CommandOption { + + public final String feedback; + + public CommandOption(String feedback) { + this.feedback = feedback; + + } +} diff --git a/src/main/java/duke/command/CommandToDo.java b/src/main/java/duke/command/CommandToDo.java new file mode 100644 index 0000000000..18c624d920 --- /dev/null +++ b/src/main/java/duke/command/CommandToDo.java @@ -0,0 +1,36 @@ +package duke.command; +import duke.taskList.TaskList; +import duke.task.ToDos; + +public class CommandToDo extends Command { + + private ToDos Add; + + public CommandToDo(ToDos Add) { + this.Add = Add; + } + + public CommandToDo(String desc) throws NullPointerException { + if (!(!desc.isEmpty())) { + throw new NullPointerException(); + } + this.Add = new ToDos(desc); + } + + public static final String SUCCESS = "Sheena: Yay! I've added this task: \n" + + "\n %s.\nNow you have %d tasks in your list ~ "; + public static final String DUPLICATE_TASK = "Sheena: This task already exists in the list ~"; + public static final String ERROR = "Sheena: Sorry, but the %s of a todo command cannot be empty ~"; + + @Override + public CommandOption execute() { + try { + taskList.add(Add); + return new CommandOption(String.format(SUCCESS, Add.toString(), taskList.size())); + } catch (NullPointerException NPE) { + return new CommandOption(String.format(ERROR, "description")); + } catch (TaskList.DuplicateTaskException dpe) { + return new CommandOption(DUPLICATE_TASK); + } + } +} diff --git a/src/main/java/duke/command/FalseCommand.java b/src/main/java/duke/command/FalseCommand.java new file mode 100644 index 0000000000..ef07480996 --- /dev/null +++ b/src/main/java/duke/command/FalseCommand.java @@ -0,0 +1,15 @@ +package duke.command; + +public class FalseCommand extends Command { + + public final String feedback; + + public FalseCommand(String feedbackToUser) { + this.feedback = feedbackToUser; + } + + @Override + public CommandOption execute() { + return new CommandOption(feedback); + } +} diff --git a/src/main/java/duke/exception/DuplicateInformationException.java b/src/main/java/duke/exception/DuplicateInformationException.java new file mode 100644 index 0000000000..7a0407a520 --- /dev/null +++ b/src/main/java/duke/exception/DuplicateInformationException.java @@ -0,0 +1,8 @@ +package duke.exception; + +public abstract class DuplicateInformationException extends IncorrectValueException { + + public DuplicateInformationException(String message) { + super(message); + } +} diff --git a/src/main/java/duke/exception/IncorrectValueException.java b/src/main/java/duke/exception/IncorrectValueException.java new file mode 100644 index 0000000000..cac30126cf --- /dev/null +++ b/src/main/java/duke/exception/IncorrectValueException.java @@ -0,0 +1,8 @@ +package duke.exception; + +public class IncorrectValueException extends Exception { + + public IncorrectValueException(String message) { + super(message); + } +} diff --git a/src/main/java/duke/exception/TaskNotFoundException.java b/src/main/java/duke/exception/TaskNotFoundException.java new file mode 100644 index 0000000000..43adc5cd04 --- /dev/null +++ b/src/main/java/duke/exception/TaskNotFoundException.java @@ -0,0 +1,4 @@ +package duke.exception; + +public class TaskNotFoundException { +} diff --git a/src/main/java/duke/parser/Parser.java b/src/main/java/duke/parser/Parser.java new file mode 100644 index 0000000000..f77b4fc69c --- /dev/null +++ b/src/main/java/duke/parser/Parser.java @@ -0,0 +1,105 @@ +package duke.parser; +import duke.command.Command; +import duke.command.CommandToDo; +import duke.command.CommandEvent; +import duke.command.CommandDeadline; +import duke.command.CommandFind; +import duke.command.CommandDone; +import duke.command.CommandDelete; +import duke.command.CommandList; +import duke.command.CommandClear; +import duke.command.CommandBye; +import duke.command.FalseCommand; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Parser { + + public static final Pattern COMMAND_FORMAT = Pattern.compile("(?^[\\S]+)(?[\\d\\s\\S]*$)"); + public static final String INVALID_TASK = "Sheena: Erm. This number is invalid..Maybe try another number?"; + public static final String ERROR = "Sheena: Erm, The %s of a %s command cannot be empty."; + public static final String TRY_AGAIN = "Sheena: Try again maybe? This is not part of the command."; + + public Command parseCommand(String userInput) { + final Matcher matcher = COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + System.out.println("Sheena: Erm, This is a wrong command. Wanna try again? "); + } + + final String commandWord = matcher.group("commandWord").trim(); + final String arguments = matcher.group("arguments").trim(); + + if (commandWord.equals("bye")) { + + return new CommandBye(); + + } else if (commandWord.equals("list")) { + + return new CommandList(); + + } else if (commandWord.equals("done")) { + + try { + final int targetIndex = parseArgsAsDisplayedIndex(arguments); + return new CommandDone(targetIndex); + } catch (NumberFormatException nfe) { + return new FalseCommand(INVALID_TASK); + } + + } else if (commandWord.equals("todo")) { + try { + return new CommandToDo(arguments); + } catch (NullPointerException npe) { + return new FalseCommand(String.format(ERROR, "description", "todo")); + } + + } else if (commandWord.equals("event")) { + + try { + final int indexOfAtPrefix = arguments.indexOf("/at"); + + String description = arguments.substring(0, indexOfAtPrefix); + String timeOfEvent = arguments.substring(indexOfAtPrefix + 3).trim(); + return new CommandEvent(description, timeOfEvent); + } catch (StringIndexOutOfBoundsException iob) { + return new FalseCommand(String.format(ERROR, "description and/or time", "event")); + } + + } else if (commandWord.equals("deadline")) { + + try { + final int indexOfByPrefix = arguments.trim().indexOf("/by"); + String description = arguments.trim().substring(0, indexOfByPrefix); + String dueDate = arguments.substring(indexOfByPrefix + 3).trim(); + return new CommandDeadline(description, dueDate); + } catch (StringIndexOutOfBoundsException iob) { + return new FalseCommand(String.format(ERROR, "description and/or due date", "deadline")); + } + + } else if (commandWord.equals("delete")) { + + try { + final int targetIndex = parseArgsAsDisplayedIndex(arguments); + return new CommandDelete(targetIndex); + } catch (NumberFormatException nfe) { + return new FalseCommand(INVALID_TASK); + } + + } else if (commandWord.equals("clear")){ + + return new CommandClear(); + + } else if (commandWord.equals("find")){ + + return new CommandFind(arguments); + } else { + return new FalseCommand(TRY_AGAIN); + } + + } + + private int parseArgsAsDisplayedIndex(String args) throws NumberFormatException { + return Integer.parseInt(args.trim()); + } +} + diff --git a/src/main/java/duke/storage/ReadTaskList.java b/src/main/java/duke/storage/ReadTaskList.java new file mode 100644 index 0000000000..603744e0c6 --- /dev/null +++ b/src/main/java/duke/storage/ReadTaskList.java @@ -0,0 +1,44 @@ +package duke.storage; +import duke.taskList.TaskList; +import duke.task.Deadlines; +import duke.task.Events; +import duke.task.Task; +import duke.task.ToDos; +import java.util.*; + +public class ReadTaskList { + + public static TaskList accessTaskList(List saveTaskList) throws TaskList.DuplicateTaskException { + final List readTasks = new ArrayList<>(); + for (String saveTask : saveTaskList) { + readTasks.add(accessTask(saveTask)); + } + return new TaskList(readTasks); + } + + private static Task accessTask(String savedTask) { + String[] parse = savedTask.split("\\s*\\|\\s*"); + if (parse.length == 3) { + ToDos t = new ToDos(parse[2]); + if (parse[1].equals("1")) { + t.markAsDone(); + } + return t; + } else if (parse.length == 4) { + if (parse[0].equals("D")) { + Deadlines d = new Deadlines(parse[2], parse[3]); + if (parse[1].equals("1")) { + d.markAsDone(); + } + return d; + } else { + Events e = new Events(parse[2], parse[3]); + if (parse[1].equals("1")) { + e.markAsDone(); + } + return e; + } + } + return null; + } +} diff --git a/src/main/java/duke/storage/SaveTaskList.java b/src/main/java/duke/storage/SaveTaskList.java new file mode 100644 index 0000000000..1a93f35903 --- /dev/null +++ b/src/main/java/duke/storage/SaveTaskList.java @@ -0,0 +1,44 @@ +package duke.storage; +import duke.taskList.TaskList; +import duke.task.Deadlines; +import duke.task.Events; +import duke.task.Task; +import duke.task.ToDos; +import java.util.*; +import java.util.ArrayList; + +public class SaveTaskList { + + public static List saveTaskList(TaskList toSave) { + final List saveTask = new ArrayList<>(); + toSave.getAllTasks().forEach(task -> saveTask.add(storeTask(task))); + return saveTask; + } + + private static String storeTask(Task t) { + String fileDoneStatus; + if (t.getStatusIcon().equals("\u2718")) { + fileDoneStatus = "0"; + } else { + fileDoneStatus = "1"; + } + final StringBuilder storedTask = new StringBuilder(); + if (t instanceof ToDos){ + storedTask.append(t.getTaskType()); + storedTask.append(" | ").append(fileDoneStatus); + storedTask.append(" | ").append(t.description); + } else if (t instanceof Deadlines) { + storedTask.append(t.getTaskType()); + storedTask.append(" | ").append(fileDoneStatus); + storedTask.append(" | ").append(t.description).append(" | "); + storedTask.append(((Deadlines) t).getDueDate()); + } else if (t instanceof Events) { + storedTask.append(t.getTaskType()); + storedTask.append(" | ").append(fileDoneStatus); + storedTask.append(" | ").append(t.description).append(" | "); + storedTask.append(((Events) t).getTimeOfEvent()); + } + + return storedTask.toString(); + } +} diff --git a/src/main/java/duke/storage/Storage.java b/src/main/java/duke/storage/Storage.java new file mode 100644 index 0000000000..c0ace8acbd --- /dev/null +++ b/src/main/java/duke/storage/Storage.java @@ -0,0 +1,85 @@ +package duke.storage; +import duke.taskList.TaskList; +import duke.exception.IncorrectValueException; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +/** + * Represents the file used to store address book data. + */ + +public class Storage { + /** Default file path used if the user doesn't provide the file name. */ + public static final String DEFAULT_FILE = "duke.txt"; + public final Path path; + + + public Storage() throws StorageFilePathException { + this(DEFAULT_FILE); + } + + public Storage(String filePath) throws StorageFilePathException { + path = Paths.get(filePath); + if (!isValidPath(path)) { + throw new StorageFilePathException("Storage file should end with '.txt'"); + } + } + + private static boolean isValidPath(Path filePath) { + return filePath.toString().endsWith(".txt"); + } + + /** + * Saves the {@code addressBook} data to the storage file. + * + * @throws StorageOperationException if there were errors converting and/or storing data to file. + */ + public void save(TaskList tasklist) throws StorageOperationException { + try { + List writeTaskList = SaveTaskList.saveTaskList(tasklist); + Files.write(path, writeTaskList); + } catch (IOException ioe) { + throw new StorageOperationException("Error writing to file: " + path); + } + } + + public TaskList load() throws StorageOperationException { + + if (!Files.isRegularFile(path) || !Files.exists(path) ) { + return new TaskList(); + } + + try { + return ReadTaskList.accessTaskList(Files.readAllLines(path)); + } catch (FileNotFoundException fnfe) { + throw new AssertionError("Sheena: The non-existent file scenario is already handled."); + // other errors + } catch (IOException ioe) { + throw new StorageOperationException("Sheena: Error writing to file " + path); + } catch (IncorrectValueException ive) { + throw new StorageOperationException("Sheena: Hey! File contains illegal data!"); + } + } + + public String getPath() { + return path.toString(); + } + + + public static class StorageFilePathException extends IncorrectValueException { + public StorageFilePathException(String message) { + super(message); + } + } + + public static class StorageOperationException extends Exception { + public StorageOperationException(String message) { + super(message); + } + } + +} \ No newline at end of file diff --git a/src/main/java/duke/task/Deadlines.java b/src/main/java/duke/task/Deadlines.java new file mode 100644 index 0000000000..00e159269e --- /dev/null +++ b/src/main/java/duke/task/Deadlines.java @@ -0,0 +1,31 @@ +package duke.task; + +public class Deadlines extends Task { + protected String dueDate; + + public Deadlines(String description, String dueDate) { + super(description); + super.setTaskType("D"); + this.dueDate = dueDate; + + } + + public String getDueDate() { + return dueDate; + } + + public void setDueDate(String dueDate) { + this.dueDate = dueDate; + } + + @Override + public String toString() { + return "[" + super.getTaskType() + "]" + super.toString() + "(by: " + getDueDate() + ")"; + } + + @Override + public boolean isSameTask(Task toCheck) { + return (toCheck == this) || ( !(toCheck == null) && toCheck.getDescription().equals(this.getDescription()) + && ((Deadlines) toCheck).getDueDate().equals(this.getDueDate())); + } +} \ No newline at end of file diff --git a/src/main/java/duke/task/Events.java b/src/main/java/duke/task/Events.java new file mode 100644 index 0000000000..95fa86c772 --- /dev/null +++ b/src/main/java/duke/task/Events.java @@ -0,0 +1,32 @@ +package duke.task; + +import duke.task.Task; + +public class Events extends Task { + protected String timeOfEvent; + + public Events(String description, String timeOfEvent) { + super(description); + super.setTaskType("E"); + this.timeOfEvent = timeOfEvent; + } + + public String getTimeOfEvent() { + return timeOfEvent; + } + + public void setTimeOfEvent(String timeOfEvent) { + this.timeOfEvent = timeOfEvent; + } + + @Override + public String toString() { + return "[" + super.getTaskType() + "]" + super.toString() + "(at: " + getTimeOfEvent() + ")"; + } + + @Override + public boolean isSameTask(Task toCheck) { + return (toCheck == this) || ( !(toCheck == null) && toCheck.getDescription().equals(this.getDescription()) + && ((Events) toCheck).getTimeOfEvent().equals(this.getTimeOfEvent())); + } +} \ No newline at end of file diff --git a/src/main/java/duke/task/Task.java b/src/main/java/duke/task/Task.java new file mode 100644 index 0000000000..5047e04555 --- /dev/null +++ b/src/main/java/duke/task/Task.java @@ -0,0 +1,51 @@ +package duke.task; + +public class Task { + public String description; + public boolean isDone; + public String taskType; + + public Task(String description) { + this.description = description; + this.isDone = false; + this.taskType = null; + } + + public String getDescription() { + return description; + } + + public boolean isDone() { + return isDone; + } + + public void setDescription(String description) { + this.description = description; + } + + public void markAsDone() { + isDone = true; + } + + public String getTaskType() { + return taskType; + } + + public void setTaskType(String taskType) { + this.taskType = taskType; + } + + public String getStatusIcon() { + return (isDone ? "\u2713" : "\u2718"); //return tick or X symbols + } + + + @Override + public String toString() { + return "[" + getStatusIcon() + "]" + description; + } + + public boolean isSameTask(Task toCheck){ + return (toCheck == this) || ( !(toCheck == null) && toCheck.getDescription().equals(this.getDescription())); + } +} \ No newline at end of file diff --git a/src/main/java/duke/task/ToDos.java b/src/main/java/duke/task/ToDos.java new file mode 100644 index 0000000000..4777596f33 --- /dev/null +++ b/src/main/java/duke/task/ToDos.java @@ -0,0 +1,21 @@ +package duke.task; + +import duke.task.Task; + +public class ToDos extends Task { + + public ToDos(String description) { + super(description); + super.setTaskType("T"); + } + + @Override + public String toString() { + return "[" + super.getTaskType() + "]" + super.toString(); + } + + @Override + public boolean isSameTask(Task toCheck) { + return super.isSameTask(toCheck); + } +} \ No newline at end of file diff --git a/src/main/java/duke/taskList/TaskList.java b/src/main/java/duke/taskList/TaskList.java new file mode 100644 index 0000000000..86b63c6d2c --- /dev/null +++ b/src/main/java/duke/taskList/TaskList.java @@ -0,0 +1,189 @@ +package duke.taskList; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import duke.task.Deadlines; +import duke.task.Events; +import duke.task.Task; +import duke.task.ToDos; +import duke.exception.DuplicateInformationException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + + +/** + * Represents the entire task list. Contains the data of the task list. + */ +public class TaskList { + + public static ArrayList taskList = new ArrayList<>(); + + public TaskList() { + } + + /** + * Constructs a list from the items in the given collection. + * + * @param Tasks a collection of tasks + * @throws DuplicateTaskException if the {@code persons} contains duplicate tasks + */ + public TaskList(List Tasks) throws DuplicateTaskException { + if (!uniqueElements(Tasks)) { + throw new DuplicateTaskException(); + } + taskList.addAll(Tasks); + } + + + public static boolean contains(Task toCheck) { + for (Task a : taskList) { + if (a instanceof ToDos && toCheck instanceof ToDos) { + return (((ToDos)a).isSameTask(toCheck)); + } else if (a instanceof Events && toCheck instanceof Events) { + return (((Events)a).isSameTask(toCheck)); + } else if (a instanceof Deadlines && toCheck instanceof Deadlines) { + return (((Deadlines)a).isSameTask(toCheck)); + } + } + return false; + } + + /** + * Adds one task to the list. + * + * @throws DuplicateTaskException if the task to add is a duplicate of an existing task in the list. + */ + public static void add(Task toAdd) throws DuplicateTaskException { + if (contains(toAdd)) { + throw new DuplicateTaskException(); + } + taskList.add(toAdd); + } + + public static Task retrieve(int targetIndex) { + return taskList.get(targetIndex); + } + + /** + * Removes task from the list. + * + * @throws TaskNotFoundException if no such task could be found in the list. + */ + public static void remove(int toRemove) throws TaskNotFoundException { + Task t = taskList.get(toRemove); + final boolean taskFoundAndDeleted = taskList.remove(t); + if (!taskFoundAndDeleted) { + throw new TaskNotFoundException(); + } + } + + + public static int size() { + return taskList.size(); + } + + /** + * Clears task list. + */ + public static void clear() { + taskList.clear(); + } + + /** + * @return the duplicate task list + */ + public static ArrayList copy() { + + return (new ArrayList<>(taskList)); + } + + /** + * Shows to user all elements in task list. + */ + public static void printList(ArrayList taskList) throws NullPointerException { + int taskCounter = 1; + if (taskList.size() == 0) { + throw new NullPointerException(); + } else { + System.out.println("----------------------------"); + System.out.println("Sheena: Well, you have these items in your list: \n"); + for (Task t : taskList) { + System.out.println(" " + taskCounter + ". " + t.toString()); + taskCounter++; + } + System.out.println("----------------------------"); + } + } + + public static ArrayList filterTheList(ArrayList taskList, String keyword) { + + return (ArrayList) taskList.stream() + .filter(task -> task.description.contains(keyword)) + .collect(Collectors.toList()); + } + + /** + * Marks a task as [done]. + */ + public static Task markAsDone(int targetIndex) throws TaskNotFoundException { + Task t = TaskList.retrieve(targetIndex); + t.markAsDone(); + final boolean taskFound_Marked = t.isDone; + if (!taskFound_Marked) { + throw new TaskNotFoundException(); + } + return t; + } + + /** + * Returns a new task list of all tasks in list at the time of the call. + */ + public ArrayList getAllTasks() { + return taskList; + } + + + /** + * Signals that an operation would have violated the 'no duplicates' property of the list. + */ + public static class DuplicateTaskException extends DuplicateInformationException { + protected DuplicateTaskException() { + super("Sheena: Erm. There will be duplicate tasks tho.."); + } + } + + + public static class TaskNotFoundException extends Exception { + protected TaskNotFoundException() { + super("Sheena: Erm, I can't delete because you haven't added that task.."); + } + } + + + public static boolean uniqueElements(Collection items) { + final Set testSet = new HashSet<>(); + for (Object item : items) { + final boolean itemExists = !testSet.add(item); // see Set documentation + if (itemExists) { + return false; + } + } + return true; + } + + public static void storedTaskList() throws NullPointerException { + int counter = 1; + if (taskList.size() == 0) { + throw new NullPointerException(); + } else { + for (Task t : taskList) { + System.out.println(" " + counter + ". " + t.toString()); + counter++; + } + } + } + + +}