Skip to content

Commit

Permalink
Update StorageService thrown exceptions
Browse files Browse the repository at this point in the history
StorageService now throws DukeStorageException instead of DukeException
for more specificity and different erorr handling.
  • Loading branch information
euchangxian committed Aug 31, 2023
1 parent 6f4deae commit 3bb7235
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 64 deletions.
100 changes: 66 additions & 34 deletions src/main/java/CliParserService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import exception.DukeStorageException;
import exception.TaskParseException;

import java.util.*;
import java.util.function.Function;

public class CliParserService {
private final Duke dukeBot;
Expand All @@ -28,10 +28,10 @@ public void parse() {
outputService.printTasks(dukeBot.getTaskList());
break;
case "mark":
handleTaskAction(input, dukeBot::markTask, "Nice! I've marked this task as done:");
handleMarkTask(input);
break;
case "unmark":
handleTaskAction(input, dukeBot::unmarkTask, "OK, I've unmarked this task:");
handleUnmarkTask(input);
break;
case "delete":
handleDelete(input);
Expand Down Expand Up @@ -66,30 +66,57 @@ private void parseTaskCommand(String line) {
outputService.echo(displayText);
} catch (TaskParseException e) {
outputService.echo(e.getMessage());
} catch (DukeStorageException e) {
outputService.echo("Failed to write task to storage! :<");
}
}

private void handleTaskAction(String[] input, Function<Integer, Optional<Task>> action, String successMessage) {
private void handleMarkTask(String[] input) {
if (!isValidTaskNumberArgument(input)) {
return;
}

int taskNumber = Integer.parseInt(input[1]);
Optional<Task> optionalTask = action.apply(taskNumber - 1);

optionalTask.ifPresentOrElse(task -> {
List<String> displayText = new ArrayList<>();
displayText.add(successMessage);
displayText.add(outputService.indentLeft(task.toString()));
outputService.echo(displayText);
}, () -> {
if (dukeBot.getNumberOfTasks() == 0) {
outputService.echo("There are no tasks left!");
}
outputService.echo(String.format("Invalid Task index: %s provided.%n" +
"Specify a number between %s - %s", taskNumber, 1, dukeBot.getNumberOfTasks() + 1));
try {
Optional<Task> optionalTask = dukeBot.markTask(taskNumber - 1);
optionalTask.ifPresentOrElse(task -> {
List<String> displayText = new ArrayList<>();
displayText.add("Nice! I've marked this task as done:");
displayText.add(outputService.indentLeft(task.toString()));
outputService.echo(displayText);
}, () -> {
if (dukeBot.getNumberOfTasks() == 0) {
outputService.echo("There are no tasks left!");
}
outputService.echo(String.format("Invalid Task index: %s provided.%n" +
"Specify a number between %s - %s", taskNumber, 1, dukeBot.getNumberOfTasks() + 1));
});
} catch (DukeStorageException e) {
outputService.echo("Failed to save marked task to storage :>");
}
}

});
private void handleUnmarkTask(String[] input) {
if (!isValidTaskNumberArgument(input)) {
return;
}
int taskNumber = Integer.parseInt(input[1]);
try {
Optional<Task> optionalTask = dukeBot.unmarkTask(taskNumber - 1);
optionalTask.ifPresentOrElse(task -> {
List<String> displayText = new ArrayList<>();
displayText.add("OK, I've unmarked this task:");
displayText.add(outputService.indentLeft(task.toString()));
outputService.echo(displayText);
}, () -> {
if (dukeBot.getNumberOfTasks() == 0) {
outputService.echo("There are no tasks left!");
}
outputService.echo(String.format("Invalid Task index: %s provided.%n" +
"Specify a number between %s - %s", taskNumber, 1, dukeBot.getNumberOfTasks() + 1));

});
} catch (DukeStorageException e) {
outputService.echo("Failed to save unmarked task to storage! :<");
}
}

private void handleDelete(String[] input) {
Expand All @@ -98,20 +125,25 @@ private void handleDelete(String[] input) {
}

int taskNumber = Integer.parseInt(input[1]);
Optional<Task> optionalTask = dukeBot.deleteTask(taskNumber - 1);

optionalTask.ifPresentOrElse(task -> {
List<String> displayText = new ArrayList<>();
displayText.add("Noted. I have removed this task:");
displayText.add(outputService.indentLeft(task.toString()));
displayText.add(String.format("Now you have %s %s in the list.",
dukeBot.getNumberOfTasks(),
dukeBot.getNumberOfTasks() == 1 ? "task" : "tasks"));
outputService.echo(displayText);
}, () ->
outputService.echo(String.format("Invalid Task index: %s provided.%n" +
"Specify a number between %s - %s", taskNumber, 1, dukeBot.getNumberOfTasks() + 1))
);
try {
Optional<Task> optionalTask = dukeBot.deleteTask(taskNumber - 1);

optionalTask.ifPresentOrElse(task -> {
List<String> displayText = new ArrayList<>();
displayText.add("Noted. I have removed this task:");
displayText.add(outputService.indentLeft(task.toString()));
displayText.add(String.format("Now you have %s %s in the list.",
dukeBot.getNumberOfTasks(),
dukeBot.getNumberOfTasks() == 1 ? "task" : "tasks"
));
outputService.echo(displayText);
}, () ->
outputService.echo(String.format("Invalid Task index: %s provided.%n" +
"Specify a number between %s - %s", taskNumber, 1, dukeBot.getNumberOfTasks() + 1))
);
} catch (DukeStorageException e) {
outputService.echo("Failed to delete task from local storage :<");
}
}

// Utility method to check if a string is numeric
Expand Down
32 changes: 26 additions & 6 deletions src/main/java/Duke.java
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import exception.DukeException;
import exception.DukeStorageException;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
Expand All @@ -6,15 +9,28 @@
public class Duke {
private final String botName;
private final List<Task> taskList;
private final StorageService storageService;

public Duke(String botName) {
public Duke(String botName, StorageService storageService) {
this.botName = botName;
this.taskList = new ArrayList<>();
this.storageService = storageService;
}

public static void main(String[] args) {
Duke changooseBot = new Duke("Changoose");
OutputService outputService = new OutputService();
StorageService storageService = null;
try {
storageService = new StorageService();
} catch (DukeStorageException e) {
outputService.echo("Warning: Error initializing storage. " +
"Any changes made during this session will not be saved!");
}
assert storageService != null;
if (storageService.wasFileCorrupted()) {
outputService.echo("Warning: The existing tasks file was corrupted and has been reset.");
}
Duke changooseBot = new Duke("Changoose", storageService);
TaskFactory taskFactory = new TaskFactory();
CliParserService cliParserService = new CliParserService(changooseBot, outputService, taskFactory);
String startMessage = String.format("Hello! I'm %s%nWhat can I do for you?", changooseBot.getBotName());
Expand All @@ -29,33 +45,37 @@ public String getBotName() {
return botName;
}

public boolean addTask(Task task) {
public boolean addTask(Task task) throws DukeStorageException {
storageService.saveTask(task);
return this.taskList.add(task);
}

public Optional<Task> deleteTask(int index) {
public Optional<Task> deleteTask(int index) throws DukeStorageException {
if (index < 0 || index >= taskList.size()) {
return Optional.empty();
}
storageService.deleteTask(index);
Task removedTask = taskList.remove(index);
return Optional.of(removedTask);
}

public Optional<Task> markTask(int index) {
public Optional<Task> markTask(int index) throws DukeStorageException {
if (index < 0 || index >= taskList.size()) {
return Optional.empty();
}
Task task = taskList.get(index);
task.markAsDone();
storageService.saveTasks(taskList);
return Optional.of(task);
}

public Optional<Task> unmarkTask(int index) {
public Optional<Task> unmarkTask(int index) throws DukeStorageException {
if (index < 0 || index >= taskList.size()) {
return Optional.empty();
}
Task task = taskList.get(index);
task.markAsNotDone();
storageService.saveTasks(taskList);
return Optional.of(task);
}

Expand Down
60 changes: 36 additions & 24 deletions src/main/java/StorageService.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import exception.DukeException;
import exception.DukeStorageException;
import exception.FileCorruptedException;

import java.io.*;
Expand All @@ -14,7 +15,7 @@ public class StorageService {
private List<Task> tasksCache;
private boolean wasFileCorrupted;

public StorageService() throws DukeException {
public StorageService() throws DukeStorageException {
this.directory = new File(PATH_NAME);
this.tasksStorageFile = new File(FILE_NAME);
this.wasFileCorrupted = false;
Expand All @@ -28,76 +29,87 @@ public boolean wasFileCorrupted() {
}

public List<Task> loadTasks() throws FileCorruptedException {
File file = new File(FILE_NAME);
if (!file.exists() || file.length() == 0) {
return new ArrayList<>();
}
try {
FileInputStream fis = new FileInputStream(FILE_NAME);
ObjectInputStream ois = new ObjectInputStream(fis);
if (ois.available() == 0) {
return new ArrayList<>();
}

// It is safe to cast the object returned by readObject() to List<Task>,
// because the only way to write to the file is through saveTask() or saveTasks(),
// which can only take in a Task or a List<Task> respectively,
// and then writes a List<Task> to the file.
@SuppressWarnings("unchecked")
List<Task> storedTaskList = (List<Task>) ois.readObject();
List<Task> storedTaskList = getTasks(file);
return storedTaskList;

} catch (IOException | ClassNotFoundException e) {
throw new FileCorruptedException(String.format("Failed to load tasks!%nError: %s", e.getMessage()));
}
}

public void saveTask(Task task) throws DukeException {
private static List<Task> getTasks(File file) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
// It is safe to cast the object returned by readObject() to List<Task>,
// because the only way to write to the file is through saveTask() or saveTasks(),
// which can only take in a Task or a List<Task> respectively,
// and then writes a List<Task> to the file.
@SuppressWarnings("unchecked")
List<Task> storedTaskList = (List<Task>) ois.readObject();
return storedTaskList;
}


public void saveTask(Task task) throws DukeStorageException {
tasksCache.add(task);
writeTasksToFile();
}

public void saveTasks(List<Task> taskList) throws DukeException {
public void saveTasks(List<Task> taskList) throws DukeStorageException {
tasksCache = taskList;
writeTasksToFile();
}

public void deleteTask(Task task) throws DukeException {
public void deleteTask(int index) throws DukeStorageException {
tasksCache.remove(index);
writeTasksToFile();
}
public void deleteTask(Task task) throws DukeStorageException {
tasksCache.remove(task);
writeTasksToFile();
}

private void writeTasksToFile() throws DukeException {
private void writeTasksToFile() throws DukeStorageException {
try {
FileOutputStream fos = new FileOutputStream(FILE_NAME);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(tasksCache);
} catch (IOException e) {
throw new DukeException(String.format("Failed to save tasks to file!%nError: %s", e.getMessage()));
throw new DukeStorageException(String.format("Failed to write tasks to file!%nError: %s", e.getMessage()));
}
}

private void initDirectory() throws DukeException {
private void initDirectory() throws DukeStorageException {
try {
Files.createDirectories(directory.toPath());
} catch (IOException e) {
throw new DukeException(String.format("Failed to create/load directory at %s", PATH_NAME));
throw new DukeStorageException(String.format("Failed to create/load directory at %s", PATH_NAME));
}
}

private void initFile() throws DukeException {
private void initFile() throws DukeStorageException {
if (!tasksStorageFile.exists()) {
try {
Files.createFile(tasksStorageFile.toPath());
} catch (IOException e) {
throw new DukeException(String.format("Failed to create/load %s file", FILE_NAME));
throw new DukeStorageException(String.format("Failed to create/load %s file", FILE_NAME));
}
}
}

private void initCache() throws DukeException {
private void initCache() throws DukeStorageException {
try {
this.tasksCache = loadTasks();
} catch (FileCorruptedException e) {
try {
Files.newBufferedWriter(tasksStorageFile.toPath()).close(); // this empties the file
} catch (IOException ex) {
throw new DukeException("Failed to overwrite corrupted file.");
throw new DukeStorageException("Failed to overwrite corrupted file.");
}
wasFileCorrupted = true;
tasksCache = new ArrayList<>(); // initialize with an empty list after clearing corrupted file
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/exception/DukeStorageException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package exception;

public class DukeStorageException extends DukeException {
public DukeStorageException(String message) {
super(message);
}
}

0 comments on commit 3bb7235

Please sign in to comment.