diff --git a/data/duke.txt b/data/duke.txt new file mode 100644 index 000000000..47879be7a --- /dev/null +++ b/data/duke.txt @@ -0,0 +1 @@ +D | 0 | deadline ba | by 6 diff --git a/src/main/java/Action.java b/src/main/java/Action.java new file mode 100644 index 000000000..b7d70db06 --- /dev/null +++ b/src/main/java/Action.java @@ -0,0 +1,4 @@ +public enum Action{ + BYE, LIST, CREATE_TODO, CREATE_DEADLINE, CREATE_EVENT, + MARK_AS_DONE, DELETE, CLEAR, FIND, UNKNOWN_ACTION +} \ No newline at end of file diff --git a/src/main/java/Command.java b/src/main/java/Command.java new file mode 100644 index 000000000..20ec771dc --- /dev/null +++ b/src/main/java/Command.java @@ -0,0 +1,109 @@ +import duke.exception.DukeException; +import duke.exception.IrregularInputException; +import duke.exception.RepeatedCompletionException; +import duke.task.Task; +import duke.task.TaskBank; + +import java.util.ArrayList; + +public class Command { + public static void perform(String sentence, Action action, Ui ui, Storage storage, TaskBank tb) throws DukeException { + switch (action) { + case UNKNOWN_ACTION: + System.out.printf("☹ OOPS!!! I'm sorry, but I don't know what that means :-( %nPlease try again!%n"); + break; + case CLEAR: + clear(tb, storage); + ui.showClearMessage(); + storage.exportTasks(tb); + break; + case DELETE: + Task deletedTask = deleteTask(sentence, tb); + ui.showDeleteMessage(deletedTask, tb.getTaskSize()); + storage.exportTasks(tb); + break; + case BYE: + bye(); + ui.showByeMessage(); + break; + case LIST: + ui.showAllTaskMessage(); + listAllTask(tb); + break; + case CREATE_TODO: + case CREATE_DEADLINE: + case CREATE_EVENT: + Task createdTask = createTask(action, tb, sentence); + ui.showTaskAddedMessage(createdTask, tb.getTaskSize()); + storage.exportTasks(tb); + break; + case MARK_AS_DONE: + Task completedTask = completeTask(tb, sentence); + ui.showCompleteMessage(completedTask); + storage.exportTasks(tb); + break; + case FIND: + TaskBank matchedTaskBank = findTask(sentence, tb); + ui.showFindMessage(); + listAllTask(matchedTaskBank); + break; + default: + System.out.println("ERROR IN COMMMAND"); + } + + } + + private static void clear(TaskBank tb, Storage storage) { + tb.clear(); + } + + private static Task deleteTask(String deleteStament, TaskBank tb) throws IrregularInputException { + int targetIndex = Parser.parseIndex(deleteStament); + Task deletedTask = tb.removeTask(targetIndex); + return deletedTask; + } + + private static void bye() { + Duke.terminateDuke(); + } + + private static void listAllTask(TaskBank tb) { + tb.printList(); + } + + private static Task createTask(Action action, TaskBank tb, String sentence) { + Task newTask; + String description = Parser.parseDescription(sentence); + switch (action) { + case CREATE_TODO: + newTask = tb.addTodo(description); + break; + case CREATE_DEADLINE: + newTask = tb.addDeadline(description); + break; + case CREATE_EVENT: + newTask = tb.addEvent(description); + break; + default: + newTask = null; + } + return newTask; + } + + private static TaskBank findTask(String input, TaskBank givenTaskBank) throws IrregularInputException { + String keywordInput = Parser.parseKeyWord(input); + ArrayList matchingTasks = TaskBank.findMatchingTask(givenTaskBank, keywordInput); + if (matchingTasks.isEmpty()) { + return null; + } else { + return new TaskBank(matchingTasks); + } + } + + private static Task completeTask(TaskBank tb, String sentence) throws RepeatedCompletionException, NumberFormatException, IrregularInputException { + int targetIndex = Parser.parseIndex(sentence); + Task completedTask = tb.searchTask(targetIndex); + completedTask.markAsDone(); + return completedTask; + } +} diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 5d313334c..bc08d48ff 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,10 +1,55 @@ +import duke.exception.DukeException; +import duke.task.TaskBank; + +import java.util.Scanner; + public class Duke { + private Storage storage; + private TaskBank taskBank; + private Ui ui; + private static boolean isTerminating; + + /** + * Instantiates Storage, TaskBank and Ui of the programme + * @param filePath the filePath which duke.txt is stored + */ + public Duke(String filePath) { + ui = new Ui(); + taskBank = new TaskBank(); + storage = new Storage(filePath, taskBank, ui); + } + + /** + * Runs Duke program + */ + public void run() { + try (Scanner sc = new Scanner(System.in)) { + String fullCommand; + while (!isTerminating) { + fullCommand = ui.readInput(sc); + ui.printDashLine(); + Action action = Parser.parseCommand(fullCommand); + Command.perform(fullCommand, action, ui, storage, taskBank); + ui.printDashLine(); + } + } catch (DukeException e) { + ui.showErrorMessage(e); + } + } + public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); + isTerminating = false; + new Duke("./data/duke.txt").run(); + } + + /** + * Changes the variable isTerminating to true + * Used to end the while loop that continuously read user input + */ + public static void terminateDuke() { + isTerminating = true; } } + + + diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 000000000..9f37e4e0a --- /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/Parser.java b/src/main/java/Parser.java new file mode 100644 index 000000000..e958344d8 --- /dev/null +++ b/src/main/java/Parser.java @@ -0,0 +1,86 @@ +import duke.exception.DukeException; +import duke.exception.EmptyInputException; +import duke.exception.IrregularInputException; +import duke.task.Task; + +public class Parser { + /** Parses user input to corresponding command enum based on user input. + * + * @param sentence Raw String taken from user input + * @return an action to be taken by Command + * @throws DukeException including illegal inputs and empty inputs + */ + public static Action parseCommand(String sentence) throws DukeException { + if (sentence.isEmpty()) { + throw new EmptyInputException("Empty input! Try again (o|o)\n"); + } else if (sentence.equals("bye")) { + return Action.BYE; + } else if (sentence.equals("list")) { + return Action.LIST; + } else if (sentence.startsWith("done")) { + return Action.MARK_AS_DONE; + } else if (sentence.startsWith("todo")) { + return Action.CREATE_TODO; + } else if (sentence.startsWith("deadline")) { + return Action.CREATE_DEADLINE; + } else if (sentence.startsWith("event")) { + return Action.CREATE_EVENT; + } else if (sentence.startsWith("delete")) { + return Action.DELETE; + } else if (sentence.startsWith("clear")) { + return Action.CLEAR; + } else if (sentence.startsWith("find")) { + return Action.FIND; + } else { + return Action.UNKNOWN_ACTION; + } + } + + /** Parse the index of the tasks the user wants to done / delete + * + * @param sentence the raw String from user input of "done" and "delete" + * @return targetIndex, + * @throws IrregularInputException if an int cannot be parsed + */ + public static int parseIndex(String sentence) throws IrregularInputException { + String[] words = sentence.split(" "); + try { + int targetIndex = Integer.parseInt(words[1]) - 1; + return targetIndex; + } catch (NumberFormatException nfe) { + throw new IrregularInputException("Not a number. Try again!"); + } + } + + /** Parse the keyword of the looks for + * + * @param sentence, raw user input + * @return the keyword that the user looks for + * @throws IrregularInputException if bad keyword or no keyword is keyyed in + */ + public static String parseKeyWord(String sentence) throws IrregularInputException { + try { + int spaceIndex = sentence.indexOf(' '); + if (spaceIndex == -1) { + throw new IndexOutOfBoundsException(); + } + String keyWord = sentence.substring(spaceIndex + 1); + return keyWord; + } catch (NumberFormatException nfe) { + throw new IrregularInputException("Bad keyword!"); + } catch (IndexOutOfBoundsException ie) { + throw new IrregularInputException("You have not keyed in any keyword"); + } + } + + /** Parse the description of task from user input + * + * @param sentence raw user input + * @return the description of the task + */ + public static String parseDescription(String sentence) { + int spaceIndex = sentence.indexOf(' '); + String description = sentence.substring(spaceIndex + 1); + return description; + } +} diff --git a/src/main/java/Storage.java b/src/main/java/Storage.java new file mode 100644 index 000000000..55ecd305e --- /dev/null +++ b/src/main/java/Storage.java @@ -0,0 +1,113 @@ +import duke.exception.RepeatedCompletionException; +import duke.task.Task; +import duke.task.TaskBank; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Scanner; + +public class Storage { + private String filePath; + private File file; + + Storage(String filePath, TaskBank tb, Ui ui) { + this.filePath = filePath; + file = new File(filePath); + if (file.exists()) { + loadTasks(tb); + ui.showLoadMessage(); + } else { + File directoryPath = new File(filePath); + directoryPath.mkdir(); + try { + file.createNewFile(); + } catch(IOException e){ + System.out.println(e.getMessage()); + } + ui.showGreeting(); + } + } + + /** Exports the content in the TaskBank to duke.txt + * + * @param tb - the TaskBank which tasks are exported from + */ + public void exportTasks(TaskBank tb) { + StringBuffer taskTextString = new StringBuffer(); + for (Task task : tb.getTasks()) { + taskTextString.append(task.getTaskType()); + taskTextString.append(" | "); + taskTextString.append(task.getDone() ? "1" : "0"); + taskTextString.append(" | "); + taskTextString.append(task.describeInFile()); + taskTextString.append("\r\n"); + } + try { + FileWriter fw = new FileWriter(filePath); + fw.write(taskTextString.toString()); + fw.close(); + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } + + /** Loads taks from file to target TaskBank + * + * @param tb TaskBank which tasks are loaded to + */ + public void loadTasks(TaskBank tb) { + File f = new File(this.filePath); + try (Scanner sc = new Scanner(f)) { + while (sc.hasNext()) { + loadTaskLine(sc.nextLine(), tb); + } + } catch (FileNotFoundException e) { + System.out.println("duke.txt is not found"); + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } + + private void loadTaskLine(String taskLine, TaskBank tb) throws IOException { + String taskTypeString = taskLine.substring(0, 1); + int firstDivisor = taskLine.indexOf("|", 1); + int secondDivisor = taskLine.indexOf("|", firstDivisor + 1); + int thirdDivisor = taskLine.indexOf("|", secondDivisor + 1); + if (thirdDivisor == -1) { // todo type + tb.addTodo(taskLine.substring(secondDivisor + 1).trim()); + } else { + if (taskTypeString.equals("D")) { + String deadLineInput = taskLine.substring(secondDivisor + 1).trim(); + String taskCompletionStatus = taskLine.substring(firstDivisor + 2, secondDivisor).trim(); + Task newTask = tb.addDeadline(deadLineInput.replace("| ", "/")); + if (taskCompletionStatus.equals("1")) { + try { + newTask.markAsDone(); + } catch (RepeatedCompletionException e) { + // This is left blank intentinally + // as from tasks are generated from + // local files, and will not be completed repeatedly + } + } + + } else if (taskTypeString.equals("E")) { + String eventLineInput = taskLine.substring(secondDivisor + 1).trim(); + String taskCompletionStatus = taskLine.substring(firstDivisor + 2, secondDivisor).trim(); + Task newTask = tb.addEvent(eventLineInput.replace("| ", "/")); + if (taskCompletionStatus.equals("1")) { + try { + newTask.markAsDone(); + } catch (RepeatedCompletionException e) { + // This is left blank intentinally + // as from tasks are generated from + // local files, and will not be completed repeatedly + } + } + } else { + throw new IOException("Wrong type from data loader"); + } + } + } +} diff --git a/src/main/java/Ui.java b/src/main/java/Ui.java new file mode 100644 index 000000000..d12b3fa17 --- /dev/null +++ b/src/main/java/Ui.java @@ -0,0 +1,83 @@ +import duke.task.Task; +import duke.task.TaskBank; + +import java.util.ArrayList; +import java.util.Scanner; + +public class Ui { + public void printDashLine() { + System.out.printf("____________________________________________________________%n"); + } + + public void showGreeting() { + printDashLine(); + System.out.printf("Hello! I'm Duke, your task manager.%n" + + "Its the first time I see you%n" + + "Key in your tasks below!%n"); + printDashLine(); + } + + + public void showErrorMessage(Exception e) { + System.out.println(e.getMessage()); + } + + public void showClearMessage() { + System.out.println("I have cleared all the tasks!"); + } + + public void showDeleteMessage(Task deletedTask, int taskBankSize) { + System.out.printf("Noted. I've removed this task: %n"); + System.out.println(" " + deletedTask); + System.out.printf("Now you have " + taskBankSize + + " tasks in the list.%n"); + } + + public void showByeMessage() { + System.out.printf("Bye. Hope to see you again soon!%n"); + } + + public void showAllTaskMessage() { + System.out.printf("Here are the tasks in your list:%n"); + } + + public void showTaskAddedMessage(Task addedTask, int taskBankSize) { + System.out.printf("Got it. I've added this task: %n " + + addedTask + + "%nNow you have " + taskBankSize + + " tasks in the list.%n"); + } + + public void showCompleteMessage(Task completedTask) { + System.out.printf("Nice! I've marked this task as done: %n"); + System.out.println(" " + completedTask); + } + + public String readInput(Scanner sc) { + return sc.nextLine(); + } + + public static ArrayList findMatchingTask(TaskBank givenBank, String keyword) { + ArrayList matchingTasks = new ArrayList<>(); + for (Task task : givenBank.getTasks()) { + String lowercaseTaskDescription = task.getDescription().toLowerCase(); + if (lowercaseTaskDescription.contains(keyword)) { + matchingTasks.add(task); + } + } + return matchingTasks; + } + + + public void showLoadMessage() { + printDashLine(); + System.out.printf("Hello! I'm Duke, your task manager.%n" + + "I have loaded the tasks you have keyed in the last time%n" + + "Continue to key in your tasks below!%n"); + printDashLine(); + } + + public void showFindMessage() { + System.out.printf(" Here are the matching tasks in your list:%n"); + } +} diff --git a/src/main/java/duke/exception/DukeException.java b/src/main/java/duke/exception/DukeException.java new file mode 100644 index 000000000..3e7ff435e --- /dev/null +++ b/src/main/java/duke/exception/DukeException.java @@ -0,0 +1,11 @@ +package duke.exception; + +public class DukeException extends Exception { + public DukeException() { + super(); + } + + public DukeException(String message) { + super(message); + } +} diff --git a/src/main/java/duke/exception/EmptyInputException.java b/src/main/java/duke/exception/EmptyInputException.java new file mode 100644 index 000000000..afdabb965 --- /dev/null +++ b/src/main/java/duke/exception/EmptyInputException.java @@ -0,0 +1,13 @@ +package duke.exception; + +public class EmptyInputException extends DukeException { + + public EmptyInputException() { + super(); + } + + public EmptyInputException(String mesagge) { + super(mesagge); + } + +} diff --git a/src/main/java/duke/exception/IrregularInputException.java b/src/main/java/duke/exception/IrregularInputException.java new file mode 100644 index 000000000..16a4c9740 --- /dev/null +++ b/src/main/java/duke/exception/IrregularInputException.java @@ -0,0 +1,11 @@ +package duke.exception; + +public class IrregularInputException extends DukeException { + public IrregularInputException() { + super(); + } + + public IrregularInputException(String message) { + super(message); + } +} diff --git a/src/main/java/duke/exception/RepeatedCompletionException.java b/src/main/java/duke/exception/RepeatedCompletionException.java new file mode 100644 index 000000000..4e7c662bb --- /dev/null +++ b/src/main/java/duke/exception/RepeatedCompletionException.java @@ -0,0 +1,12 @@ +package duke.exception; + +public class RepeatedCompletionException extends DukeException{ + + public RepeatedCompletionException(){ + super(); + } + + public RepeatedCompletionException(String message){ + super(message); + } +} diff --git a/src/main/java/duke/task/Deadline.java b/src/main/java/duke/task/Deadline.java new file mode 100644 index 000000000..795474769 --- /dev/null +++ b/src/main/java/duke/task/Deadline.java @@ -0,0 +1,32 @@ +package duke.task; + +public class Deadline extends Task { + private String deadlineDateString; + + public Deadline(String description) { + super(); + int indexDivider = description.indexOf("/"); + String deadlineName = description.substring(0, indexDivider).trim(); + String deadlineDateString = description.substring(indexDivider + 1).trim(); + this.description = deadlineName; + this.deadlineDateString = deadlineDateString; + this.taskType = TaskType.DEADLINE; + } + + public String getDeadlineDateString() { + return deadlineDateString; + } + + @Override + public String toString() { + return super.toString() + " (" + getDeadlineDateString() + ")"; + } + + + /** Returns a formatted String that to be stored in file, duke.txt + * @return the formatted String + */ + public String describeInFile() { + return getDescription() + " | " + getDeadlineDateString(); + } +} diff --git a/src/main/java/duke/task/Event.java b/src/main/java/duke/task/Event.java new file mode 100644 index 000000000..46d0a5416 --- /dev/null +++ b/src/main/java/duke/task/Event.java @@ -0,0 +1,31 @@ +package duke.task; + +public class Event extends Task { + private String eventDateString; + + public Event(String description) { + super(); + int indexDivider = description.indexOf("/"); + String eventName = description.substring(0, indexDivider).trim(); + String eventDateString = description.substring(indexDivider + 1).trim(); + this.description = eventName; + this.eventDateString = eventDateString; + this.taskType = TaskType.EVENT; + } + + public String getEventDateString(){ + return eventDateString; + } + + @Override + public String toString() { + return super.toString()+ " (" + getEventDateString() + ")"; + } + + /** Returns a formatted String that to be stored in file, duke.txt + * @return the formatted String + */ + public String describeInFile() { + return getDescription() + " | " + getEventDateString(); + } +} diff --git a/src/main/java/duke/task/Task.java b/src/main/java/duke/task/Task.java new file mode 100644 index 000000000..6fce1b3d0 --- /dev/null +++ b/src/main/java/duke/task/Task.java @@ -0,0 +1,63 @@ +package duke.task; + +import duke.exception.RepeatedCompletionException; + +enum TaskType { + TO_DO, DEADLINE, EVENT; +} + + +public abstract class Task { + protected String description; + protected boolean isDone; + protected TaskType taskType; + + public Task() { + this.isDone = false; + } + + public abstract String describeInFile(); + + public Task(String description) { + this(); + this.description = description; + } + + public String getStatusIcon() { + return (isDone ? "X" : " "); // mark done task with X + } + + public String getDescription() { + return this.description; + } + + public boolean getDone() { + return this.isDone; + } + + public void markAsDone() throws RepeatedCompletionException { + if (this.getDone()) { + throw new RepeatedCompletionException("This task has already been completed!\n"); + } + this.isDone = true; + } + + public String getTaskType() { + switch (this.taskType) { + case TO_DO: + return "T"; + case EVENT: + return "E"; + case DEADLINE: + return "D"; + default: + return "N"; // stands for unknown tasks + } + } + + @Override + public String toString() { + return "[" + getTaskType() + "][" + getStatusIcon() + "] " + getDescription(); + } + +} diff --git a/src/main/java/duke/task/TaskBank.java b/src/main/java/duke/task/TaskBank.java new file mode 100644 index 000000000..d151137a3 --- /dev/null +++ b/src/main/java/duke/task/TaskBank.java @@ -0,0 +1,118 @@ +package duke.task; + +import java.util.ArrayList; + +public class TaskBank { + private ArrayList tasks; + + public TaskBank() { + tasks = new ArrayList<>(); + } + + public TaskBank(ArrayList givenTasks){ + tasks = givenTasks; + } + + public ArrayList getTasks() { + return tasks; + } + + + /** Adds a todo to taskbank + * + * Creates a new todo task from the raw String input + * @param todoInput + * @return the newly created todo Task + */ + public Task addTodo(String todoInput) { + Task newTask = new ToDo(todoInput); + tasks.add(newTask); + return newTask; + } + + /** Adds an event to taskbank + * + * Creates a new event task from the raw String input + * @param eventInput + * @return the newly created event Task + */ + public Task addEvent(String eventInput) { + Task newTask = new Event(eventInput); + tasks.add(newTask); + return newTask; + } + + /** Adds a deadline to taskbank + * + * Creates a new event task from the raw String input + * @param deadlilneInput + * @return the newly created deadline Task + */ + public Task addDeadline(String deadlilneInput) { + Task newTask = new Deadline(deadlilneInput); + tasks.add(newTask); + return newTask; + } + + public int getTaskSize() { + return tasks.size(); + } + + /** Prints all tasks in TaskBank + * + */ + public void printList() { + int i = 0; + for (Task task : tasks) { + System.out.printf("%d.%s%n", i + 1, task); + i++; + } + } + + /** Returns the target task from the TaskBank by specific index + * + * @param taskIndex the index of target task + * @return the target index + * @throws IndexOutOfBoundsException if index provided is out of bound + */ + public Task searchTask(int taskIndex) throws IndexOutOfBoundsException { + if (taskIndex < 0 || taskIndex > this.getTaskSize()) { + throw new IndexOutOfBoundsException("Ouch! Index is out of range. Try again!\n"); + } + return tasks.get(taskIndex); + } + + /** Removes the target task from the TaskBank + * + * @param taskIndex the index of target task + * @return the removed Task + * @throws IndexOutOfBoundsException if index provided is out of bound + */ + public Task removeTask(int taskIndex) throws IndexOutOfBoundsException { + if (taskIndex < 0 || taskIndex > this.getTaskSize()) { + throw new IndexOutOfBoundsException("Ouch! Index is out of range. Try again!\n"); + } + Task deletedTask = tasks.get(taskIndex); + tasks.remove(taskIndex); + return deletedTask; + } + + public static ArrayList findMatchingTask(TaskBank givenBank, String keyword) { + ArrayList matchingTasks = new ArrayList<>(); + for (Task task : givenBank.getTasks()) { + String lowercaseTaskDescription = task.getDescription().toLowerCase(); + if (lowercaseTaskDescription.contains(keyword)) { + matchingTasks.add(task); + } + } + return matchingTasks; + } + + /** Clears the content of the the Task Bank + */ + public void clear() { + tasks = new ArrayList<>(); + } + + +} diff --git a/src/main/java/duke/task/ToDo.java b/src/main/java/duke/task/ToDo.java new file mode 100644 index 000000000..3d0fc5f3b --- /dev/null +++ b/src/main/java/duke/task/ToDo.java @@ -0,0 +1,16 @@ +package duke.task; + +public class ToDo extends Task { + + public ToDo(String description) { + super(description); + this.taskType = TaskType.TO_DO; + } + + /** Returns a formatted String that to be stored in file, duke.txt + * @return the formatted String + */ + public String describeInFile() { + return getDescription(); + } +}