From 7b53132825ef327a34000097bf56036260662239 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao <78098867+yeozongyao@users.noreply.github.com> Date: Fri, 8 Mar 2024 15:19:25 +0800 Subject: [PATCH 001/311] Update README.md --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..9fdbe47a55 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,4 +1,4 @@ -# Duke +# BookBuddy {Give product intro here} From 72284e458f6bd73cab1ae1e104762737d46bd591 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Fri, 8 Mar 2024 15:52:02 +0800 Subject: [PATCH 002/311] Test edit --- docs/AboutUs.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..d7d3c82523 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,6 @@ # About us -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +Display | Name | Github Profile | Portfolio +--------|:----------:|:--------------:|:---------: +![](https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.thesprucepets.com%2Fabout-tuxedo-cats-554695&psig=AOvVaw0C_GqS3DVZWNcXkFONc6FM&ust=1709970666751000&source=images&cd=vfe&opi=89978449&ved=0CBMQjRxqFwoTCNi3kqOX5IQDFQAAAAAdAAAAABAE) | GARETH Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) + From 98b7fc132bb589dc955284232d45332d260352d3 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Fri, 8 Mar 2024 15:52:25 +0800 Subject: [PATCH 003/311] Update AboutUs --- docs/AboutUs.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..fc4559557e 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,6 @@ # About us -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +Display | Name | Github Profile | Portfolio +--------|:---------:|:---------------------------------------:|:---------: +![](https://via.placeholder.com/100.png?text=Photo) | Joshua Ho | [Github](https://github.com/joshuahoky) | [Portfolio](docs/team/johndoe.md) + From b3a9f306cdced92634800de7acaf268a997cb3d8 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Fri, 8 Mar 2024 15:53:13 +0800 Subject: [PATCH 004/311] hehe --- docs/AboutUs.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..42203bc408 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,6 @@ # About us -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +Display | Name | Github Profile | Portfolio +--------|:------:|:--------------:|:---------: +![](https://www.google.com/imgres?imgurl=https%3A%2F%2Fi.natgeofe.com%2Fn%2F548467d8-c5f1-4551-9f58-6817a8d2c45e%2FNationalGeographic_2572187_square.jpg&tbnid=eAP244UcF5wdYM&vet=12ahUKEwiUzKCel-SEAxVScmwGHcdNBWsQMygAegQIARBy..i&imgrefurl=https%3A%2F%2Fwww.nationalgeographic.com%2Fanimals%2Fmammals%2Ffacts%2Fdomestic-cat&docid=K6Qd9XWnQFQCoM&w=3072&h=3072&q=cat%20image&ved=2ahUKEwiUzKCel-SEAxVScmwGHcdNBWsQMygAegQIARBy) | JoyLiu | [Github](https://github.com/liuzehui03) | [Portfolio](docs/team/joy.md) + From f7f0a66df5be808297b304a03da15c532ea23575 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Fri, 8 Mar 2024 15:53:51 +0800 Subject: [PATCH 005/311] Revert "hehe" This reverts commit b3a9f306cdced92634800de7acaf268a997cb3d8. --- docs/AboutUs.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 42203bc408..0f072953ea 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,6 +1,9 @@ # About us -Display | Name | Github Profile | Portfolio ---------|:------:|:--------------:|:---------: -![](https://www.google.com/imgres?imgurl=https%3A%2F%2Fi.natgeofe.com%2Fn%2F548467d8-c5f1-4551-9f58-6817a8d2c45e%2FNationalGeographic_2572187_square.jpg&tbnid=eAP244UcF5wdYM&vet=12ahUKEwiUzKCel-SEAxVScmwGHcdNBWsQMygAegQIARBy..i&imgrefurl=https%3A%2F%2Fwww.nationalgeographic.com%2Fanimals%2Fmammals%2Ffacts%2Fdomestic-cat&docid=K6Qd9XWnQFQCoM&w=3072&h=3072&q=cat%20image&ved=2ahUKEwiUzKCel-SEAxVScmwGHcdNBWsQMygAegQIARBy) | JoyLiu | [Github](https://github.com/liuzehui03) | [Portfolio](docs/team/joy.md) - +Display | Name | Github Profile | Portfolio +--------|:----:|:--------------:|:---------: +![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) From 6886d51205c72e44bf3f1d2c5980188104ee0adf Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Fri, 8 Mar 2024 15:56:08 +0800 Subject: [PATCH 006/311] about us --- docs/AboutUs.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..4981549a77 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,6 @@ # About us -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +Display | Name | Github Profile | Portfolio +--------|:-------:|:---------------------------------------:|:---------: +![](https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.nationalgeographic.com%2Fanimals%2Fmammals%2Ffacts%2Fdomestic-cat&psig=AOvVaw0h1oKdH4MW00nu2-jCVMT5&ust=1709970657610000&source=images&cd=vfe&opi=89978449&ved=0CBMQjRxqFwoTCOj10J6X5IQDFQAAAAAdAAAAABAI) | Joy Liu | [Github](https://github.com/liuzehui03) | [Portfolio](docs/team/johndoe.md) + From 912bcab3a4042efc0e008893ea5ba4a1adb1a428 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Fri, 8 Mar 2024 16:10:53 +0800 Subject: [PATCH 007/311] Zong Yao --- docs/AboutUs.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..54114ef101 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -1,9 +1,5 @@ # About us -Display | Name | Github Profile | Portfolio ---------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +Display | Name | Github Profile | Portfolio +--------|:--------:|:--------------:|:---------: +![](https://via.placeholder.com/100.png?text=Photo) | Zong Yao | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) From f45fea9cdeb637461dfa1d4f5ecece9fd15832ad Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Fri, 15 Mar 2024 12:22:55 +0800 Subject: [PATCH 008/311] Format --- docs/AboutUs.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 89d7b3929c..be9b3191e8 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -6,11 +6,5 @@ Display | Name | Github Profile | Portfolio ![](https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.nationalgeographic.com%2Fanimals%2Fmammals%2Ffacts%2Fdomestic-cat&psig=AOvVaw0h1oKdH4MW00nu2-jCVMT5&ust=1709970657610000&source=images&cd=vfe&opi=89978449&ved=0CBMQjRxqFwoTCOj10J6X5IQDFQAAAAAdAAAAABAI) | Joy Liu | [Github](https://github.com/liuzehui03) | [Portfolio](docs/team/johndoe.md) ![](https://via.placeholder.com/100.png?text=Photo) | Joshua Ho | [Github](https://github.com/joshuahoky) | [Portfolio](docs/team/johndoe.md) ![](https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.thesprucepets.com%2Fabout-tuxedo-cats-554695&psig=AOvVaw0C_GqS3DVZWNcXkFONc6FM&ust=1709970666751000&source=images&cd=vfe&opi=89978449&ved=0CBMQjRxqFwoTCNi3kqOX5IQDFQAAAAAdAAAAABAE) | GARETH Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) - - - -======= -Display | Name | Github Profile | Portfolio ---------|:--------:|:--------------:|:---------: ![](https://via.placeholder.com/100.png?text=Photo) | Zong Yao | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) ->>>>>>> master + From 3f06ce17dbf7aa78d1801050dee46f5aee4ab4f6 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Fri, 15 Mar 2024 13:29:35 +0800 Subject: [PATCH 009/311] Skeletal code + addBook Method + Junit test for addMethod --- src/main/java/seedu/BookBuddy/BookBuddy.java | 42 +++++++++++++++++++ .../java/seedu/BookBuddy/BookDetails.java | 25 +++++++++++ src/main/java/seedu/duke/Duke.java | 21 ---------- .../java/seedu/BookBuddy/BookBuddyTest.java | 30 +++++++++++++ src/test/java/seedu/duke/DukeTest.java | 12 ------ 5 files changed, 97 insertions(+), 33 deletions(-) create mode 100644 src/main/java/seedu/BookBuddy/BookBuddy.java create mode 100644 src/main/java/seedu/BookBuddy/BookDetails.java delete mode 100644 src/main/java/seedu/duke/Duke.java create mode 100644 src/test/java/seedu/BookBuddy/BookBuddyTest.java delete mode 100644 src/test/java/seedu/duke/DukeTest.java diff --git a/src/main/java/seedu/BookBuddy/BookBuddy.java b/src/main/java/seedu/BookBuddy/BookBuddy.java new file mode 100644 index 0000000000..b9bfd8f3c8 --- /dev/null +++ b/src/main/java/seedu/BookBuddy/BookBuddy.java @@ -0,0 +1,42 @@ +package seedu.BookBuddy; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.Scanner; + +public class BookBuddy { + public static ArrayList bookDetailsList = new ArrayList<>(); + public static void main(String[] args) { + + System.out.println("Hello! We are BookBuddy!"); + Scanner scanner = new Scanner(System.in); + while(true) { + String input = scanner.nextLine(); + String command = input.split(" ", 2)[0]; + if (Objects.equals(command, "addBook")) { + addBook(input); + } else if (Objects.equals(command, "list")) { + printList(); + } + } + } + + public static void addBook(String input) { + String actualDescription = input.split(" ", 2)[1]; + BookDetails newTodo = new BookDetails(actualDescription); + bookDetailsList.add(newTodo); + } + + public static void printList() { + System.out.println("Here are the current books in your list:"); + if (bookDetailsList.isEmpty()) { + System.out.println("Great job! You have no tasks!"); + } else { + for (int i = 0; i < bookDetailsList.size(); i++) { + System.out.println((i + 1) + "." + bookDetailsList.get(i)); + } + } + } + + +} diff --git a/src/main/java/seedu/BookBuddy/BookDetails.java b/src/main/java/seedu/BookBuddy/BookDetails.java new file mode 100644 index 0000000000..85f09d74af --- /dev/null +++ b/src/main/java/seedu/BookBuddy/BookDetails.java @@ -0,0 +1,25 @@ +package seedu.BookBuddy; + +public class BookDetails { + + public String description; + protected boolean isDone; + + /** + * Creates a new Task with the specified description. + * + * @param description The description of the task. + */ + public BookDetails(String description) { + this.description = description;// Description of the task + this.isDone = false;//Completion status of the task(True: Done, False: Undone) + } + + public String getDescription() { + return this.description; + } + + public String toString() { + return this.description; //combine the status and task description for easier listing + } +} diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java deleted file mode 100644 index 5c74e68d59..0000000000 --- a/src/main/java/seedu/duke/Duke.java +++ /dev/null @@ -1,21 +0,0 @@ -package seedu.duke; - -import java.util.Scanner; - -public class Duke { - /** - * Main entry-point for the java.duke.Duke application. - */ - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - System.out.println("What is your name?"); - - Scanner in = new Scanner(System.in); - System.out.println("Hello " + in.nextLine()); - } -} diff --git a/src/test/java/seedu/BookBuddy/BookBuddyTest.java b/src/test/java/seedu/BookBuddy/BookBuddyTest.java new file mode 100644 index 0000000000..5e24958a74 --- /dev/null +++ b/src/test/java/seedu/BookBuddy/BookBuddyTest.java @@ -0,0 +1,30 @@ +package seedu.BookBuddy; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.Test; + +class BookBuddyTest { + @Test + public void sampleTest() { + assertTrue(true); + } + + @Test + void main() { + } + + @Test + void addBook() { + BookBuddy.addBook("addBook Harry Potter"); + assertEquals(1, BookBuddy.bookDetailsList.size()); + assertEquals("Harry Potter", BookBuddy.bookDetailsList.get(0).getDescription()); + } + + @Test + void printList() { + } +} diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/duke/DukeTest.java deleted file mode 100644 index 2dda5fd651..0000000000 --- a/src/test/java/seedu/duke/DukeTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package seedu.duke; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.junit.jupiter.api.Test; - -class DukeTest { - @Test - public void sampleTest() { - assertTrue(true); - } -} From 0d4a5308f885b84b28e9f375981f85ca9580421d Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Fri, 15 Mar 2024 16:24:11 +0800 Subject: [PATCH 010/311] Syntax --- src/main/java/seedu/{BookBuddy => bookbuddy}/BookBuddy.java | 4 ++-- .../java/seedu/{BookBuddy => bookbuddy}/BookDetails.java | 2 +- .../java/seedu/{BookBuddy => bookbuddy}/BookBuddyTest.java | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) rename src/main/java/seedu/{BookBuddy => bookbuddy}/BookBuddy.java (93%) rename src/main/java/seedu/{BookBuddy => bookbuddy}/BookDetails.java (96%) rename src/test/java/seedu/{BookBuddy => bookbuddy}/BookBuddyTest.java (83%) diff --git a/src/main/java/seedu/BookBuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java similarity index 93% rename from src/main/java/seedu/BookBuddy/BookBuddy.java rename to src/main/java/seedu/bookbuddy/BookBuddy.java index b9bfd8f3c8..39840335da 100644 --- a/src/main/java/seedu/BookBuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -1,4 +1,4 @@ -package seedu.BookBuddy; +package seedu.bookbuddy; import java.util.ArrayList; import java.util.Objects; @@ -8,7 +8,7 @@ public class BookBuddy { public static ArrayList bookDetailsList = new ArrayList<>(); public static void main(String[] args) { - System.out.println("Hello! We are BookBuddy!"); + System.out.println("Hello! We are bookbuddy!"); Scanner scanner = new Scanner(System.in); while(true) { String input = scanner.nextLine(); diff --git a/src/main/java/seedu/BookBuddy/BookDetails.java b/src/main/java/seedu/bookbuddy/BookDetails.java similarity index 96% rename from src/main/java/seedu/BookBuddy/BookDetails.java rename to src/main/java/seedu/bookbuddy/BookDetails.java index 85f09d74af..46316c53f1 100644 --- a/src/main/java/seedu/BookBuddy/BookDetails.java +++ b/src/main/java/seedu/bookbuddy/BookDetails.java @@ -1,4 +1,4 @@ -package seedu.BookBuddy; +package seedu.bookbuddy; public class BookDetails { diff --git a/src/test/java/seedu/BookBuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java similarity index 83% rename from src/test/java/seedu/BookBuddy/BookBuddyTest.java rename to src/test/java/seedu/bookbuddy/BookBuddyTest.java index 5e24958a74..15aa3e04d0 100644 --- a/src/test/java/seedu/BookBuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -1,11 +1,11 @@ -package seedu.BookBuddy; +package seedu; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; +import seedu.bookbuddy.BookBuddy; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; -import org.junit.jupiter.api.Test; + class BookBuddyTest { @Test From 63915edc1bed2b1ddcf0a9f2a272a8dc809d0aeb Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Fri, 15 Mar 2024 16:24:34 +0800 Subject: [PATCH 011/311] syntax --- build.gradle | 4 ++-- src/test/java/seedu/bookbuddy/BookBuddyTest.java | 9 +-------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index ea82051fab..5d0a9207c1 100644 --- a/build.gradle +++ b/build.gradle @@ -29,11 +29,11 @@ test { } application { - mainClass.set("seedu.duke.Duke") + mainClass.set("seedu.bookbuddy.BookBuddy") } shadowJar { - archiveBaseName.set("duke") + archiveBaseName.set("bookbuddy") archiveClassifier.set("") } diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index 15aa3e04d0..335e958576 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -1,8 +1,7 @@ -package seedu; +package seedu.bookbuddy; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import seedu.bookbuddy.BookBuddy; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -13,9 +12,6 @@ public void sampleTest() { assertTrue(true); } - @Test - void main() { - } @Test void addBook() { @@ -24,7 +20,4 @@ void addBook() { assertEquals("Harry Potter", BookBuddy.bookDetailsList.get(0).getDescription()); } - @Test - void printList() { - } } From e270bdd3844702711a96bf14a0d23b5684310712 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Fri, 15 Mar 2024 16:28:56 +0800 Subject: [PATCH 012/311] Changed expected txt --- text-ui-test/EXPECTED.TXT | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..f49ba41f61 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +1 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - -What is your name? -Hello James Gosling +Hello! We are bookbuddy! From bd89268ae9bd34c88f767600158cdf1bb58f12cf Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:48:12 +0800 Subject: [PATCH 013/311] Implement BookList class, add functions to add, remove, mark, unmark books, get size of list, print list, return single book --- src/main/java/seedu/bookbuddy/BookList.java | 84 +++++++++++++++++++ .../java/seedu/bookbuddy/BookListTest.java | 23 +++++ 2 files changed, 107 insertions(+) create mode 100644 src/main/java/seedu/bookbuddy/BookList.java create mode 100644 src/test/java/seedu/bookbuddy/BookListTest.java diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java new file mode 100644 index 0000000000..7f4ffd3584 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -0,0 +1,84 @@ +package seedu.bookbuddy; + +import java.util.ArrayList; + +/** + * Manages a list of books, allowing for operations such as adding, deleting, + * and marking book as read or unread. + */ +public class BookList { + private ArrayList books; + + /** + * Constructs a new BookList instance with an empty list. + */ + public BookList() { + this.books = new ArrayList(); // Use ArrayList instead of array + } + + /** + * Returns the current size of the book list. + * @return The number of books in the list. + */ + public int getSize(){ + return books.size(); + } + + /** + * Retrieves a book from the list based on its index. + * @param index The index of the book to retrieve. + * @return The Book at the specified index. + */ + public Book getBook(int index){ + return books.get(index); + } + + /** + * Adds a new Book to the list. + * @param taskDescription The description of the book. + */ + public void addBook(String taskDescription) { + books.add(new Book(taskDescription)); + } + + /** + * Deletes a book from the list by its index. + * @param index The index of the book to delete. + */ + public void deleteBook(int index) { + books.remove(index-1); + } + + /** + * Marks a book as read by its index. + * @param index The index of the book to mark as read. + */ + public void markDoneByIndex(int index) { + books.get(index-1).markBookAsRead(); + } + + /** + * Marks a book as unread by its index. + * @param index The index of the book to mark as unread. + */ + public void markUndoneByIndex(int index) { + books.get(index-1).markBookAsUnread(); + } + + /** + * Prints all books currently in the list. + */ + public void printAllBooks() { + if (!books.isEmpty()) { + System.out.println("All books:"); + for (int i = 0; i < books.size(); i++) { + Book currentBook = books.get(i); + System.out.print((i + 1) + "."); + System.out.println(currentBook); + } + } + else { + System.out.println("The list is empty."); + } + } +} diff --git a/src/test/java/seedu/bookbuddy/BookListTest.java b/src/test/java/seedu/bookbuddy/BookListTest.java new file mode 100644 index 0000000000..846d51767f --- /dev/null +++ b/src/test/java/seedu/bookbuddy/BookListTest.java @@ -0,0 +1,23 @@ +package seedu.bookbuddy; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +class BookListTest { + @Test + public void sampleTest() { + assertTrue(true); + } + + + @Test + void addBook() { + BookBuddy.addBook("addBook Harry Potter"); + assertEquals(1, BookBuddy.bookDetailsList.size()); + assertEquals("Harry Potter", BookBuddy.bookDetailsList.get(0).getDescription()); + } + +} From 00eb7f446b9c803a5a4ffef39e1dceeebdf9aa3d Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:49:15 +0800 Subject: [PATCH 014/311] Change BookDetails class to Book, add methods to return description, read status, change read status and toString method to show read status --- src/main/java/seedu/bookbuddy/Book.java | 55 +++++++++++++++++++ src/main/java/seedu/bookbuddy/BookBuddy.java | 14 +---- .../java/seedu/bookbuddy/BookDetails.java | 25 --------- 3 files changed, 58 insertions(+), 36 deletions(-) create mode 100644 src/main/java/seedu/bookbuddy/Book.java delete mode 100644 src/main/java/seedu/bookbuddy/BookDetails.java diff --git a/src/main/java/seedu/bookbuddy/Book.java b/src/main/java/seedu/bookbuddy/Book.java new file mode 100644 index 0000000000..4a1584c489 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/Book.java @@ -0,0 +1,55 @@ +package seedu.bookbuddy; + +public class Book { + + public String description; + protected boolean isRead; + + + /** + * Creates a new Task with the specified description. + * + * @param description The description of the task. + */ + public Book(String description) { + this.description = description; // Description of the task + this.isRead = false; //Completion status of the task(True: Read, False: Unread) + } + + /** + * Returns the description of the book. + * + * @return The description of the book. + */ + public String getDescription() { + return this.description; + } + + /** + * Checks if the book is read. + * + * @return True if the book is read, false otherwise. + */ + public boolean isRead() { + return this.isRead; + } + + /** + * Marks the book as read. + */ + public void markBookAsRead() { + this.isRead = true; + } + + /** + * Marks the book as unread. + */ + public void markBookAsUnread() { + this.isRead = false; + } + + public String toString() { + String statusMark = this.isRead() ? "X" : " "; // Mark with 'x' if completed + return this.description + "[" + statusMark + "] " ; + } +} diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index 39840335da..d886443278 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -5,7 +5,7 @@ import java.util.Scanner; public class BookBuddy { - public static ArrayList bookDetailsList = new ArrayList<>(); + private static BookList bookList = new BookList(); public static void main(String[] args) { System.out.println("Hello! We are bookbuddy!"); @@ -23,19 +23,11 @@ public static void main(String[] args) { public static void addBook(String input) { String actualDescription = input.split(" ", 2)[1]; - BookDetails newTodo = new BookDetails(actualDescription); - bookDetailsList.add(newTodo); + bookList.addBook(actualDescription); } public static void printList() { - System.out.println("Here are the current books in your list:"); - if (bookDetailsList.isEmpty()) { - System.out.println("Great job! You have no tasks!"); - } else { - for (int i = 0; i < bookDetailsList.size(); i++) { - System.out.println((i + 1) + "." + bookDetailsList.get(i)); - } - } + bookList.printAllBooks(); } diff --git a/src/main/java/seedu/bookbuddy/BookDetails.java b/src/main/java/seedu/bookbuddy/BookDetails.java deleted file mode 100644 index 46316c53f1..0000000000 --- a/src/main/java/seedu/bookbuddy/BookDetails.java +++ /dev/null @@ -1,25 +0,0 @@ -package seedu.bookbuddy; - -public class BookDetails { - - public String description; - protected boolean isDone; - - /** - * Creates a new Task with the specified description. - * - * @param description The description of the task. - */ - public BookDetails(String description) { - this.description = description;// Description of the task - this.isDone = false;//Completion status of the task(True: Done, False: Undone) - } - - public String getDescription() { - return this.description; - } - - public String toString() { - return this.description; //combine the status and task description for easier listing - } -} From 1b2302f733a2d6a66bb54f22210502abaaead66f Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Tue, 19 Mar 2024 16:07:25 +0800 Subject: [PATCH 015/311] Modify JUNIT test for BookList class to call methods correctly --- src/main/java/seedu/bookbuddy/Book.java | 2 +- src/main/java/seedu/bookbuddy/BookBuddy.java | 16 +++------------- src/main/java/seedu/bookbuddy/BookList.java | 3 +-- src/test/java/seedu/bookbuddy/BookBuddyTest.java | 5 +---- src/test/java/seedu/bookbuddy/BookListTest.java | 7 ++++--- 5 files changed, 10 insertions(+), 23 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Book.java b/src/main/java/seedu/bookbuddy/Book.java index 4a1584c489..29cd47b025 100644 --- a/src/main/java/seedu/bookbuddy/Book.java +++ b/src/main/java/seedu/bookbuddy/Book.java @@ -50,6 +50,6 @@ public void markBookAsUnread() { public String toString() { String statusMark = this.isRead() ? "X" : " "; // Mark with 'x' if completed - return this.description + "[" + statusMark + "] " ; + return this.description + " [" + statusMark + "]" ; } } diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index d886443278..fcbd38ee7a 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -1,6 +1,5 @@ package seedu.bookbuddy; -import java.util.ArrayList; import java.util.Objects; import java.util.Scanner; @@ -14,21 +13,12 @@ public static void main(String[] args) { String input = scanner.nextLine(); String command = input.split(" ", 2)[0]; if (Objects.equals(command, "addBook")) { - addBook(input); + String actualDescription = input.split(" ", 2)[1]; + bookList.addBook(actualDescription); } else if (Objects.equals(command, "list")) { - printList(); + bookList.printAllBooks(); } } } - public static void addBook(String input) { - String actualDescription = input.split(" ", 2)[1]; - bookList.addBook(actualDescription); - } - - public static void printList() { - bookList.printAllBooks(); - } - - } diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index 7f4ffd3584..199de1a605 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -76,8 +76,7 @@ public void printAllBooks() { System.out.print((i + 1) + "."); System.out.println(currentBook); } - } - else { + } else { System.out.println("The list is empty."); } } diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index 335e958576..848dac7bea 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -3,7 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; +//import static org.junit.jupiter.api.Assertions.assertEquals; class BookBuddyTest { @@ -15,9 +15,6 @@ public void sampleTest() { @Test void addBook() { - BookBuddy.addBook("addBook Harry Potter"); - assertEquals(1, BookBuddy.bookDetailsList.size()); - assertEquals("Harry Potter", BookBuddy.bookDetailsList.get(0).getDescription()); } } diff --git a/src/test/java/seedu/bookbuddy/BookListTest.java b/src/test/java/seedu/bookbuddy/BookListTest.java index 846d51767f..ba72a86909 100644 --- a/src/test/java/seedu/bookbuddy/BookListTest.java +++ b/src/test/java/seedu/bookbuddy/BookListTest.java @@ -15,9 +15,10 @@ public void sampleTest() { @Test void addBook() { - BookBuddy.addBook("addBook Harry Potter"); - assertEquals(1, BookBuddy.bookDetailsList.size()); - assertEquals("Harry Potter", BookBuddy.bookDetailsList.get(0).getDescription()); + BookList bookList = new BookList(); + bookList.addBook("Harry Potter"); + assertEquals(1, bookList.getSize()); + assertEquals("Harry Potter [ ]", bookList.getBook(0).toString()); } } From eaef53ded98829c392992402f1aaa9252b5539e0 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Tue, 19 Mar 2024 18:37:17 +0800 Subject: [PATCH 016/311] Implement parser support for add, remove, mark, unmark, list and exit commands --- src/main/java/seedu/bookbuddy/Book.java | 26 ++++++----- src/main/java/seedu/bookbuddy/BookBuddy.java | 32 +++++++++----- src/main/java/seedu/bookbuddy/BookList.java | 19 ++++---- src/main/java/seedu/bookbuddy/Parser.java | 44 +++++++++++++++++++ .../java/seedu/bookbuddy/BookListTest.java | 2 +- 5 files changed, 90 insertions(+), 33 deletions(-) create mode 100644 src/main/java/seedu/bookbuddy/Parser.java diff --git a/src/main/java/seedu/bookbuddy/Book.java b/src/main/java/seedu/bookbuddy/Book.java index 29cd47b025..df1f1c46e7 100644 --- a/src/main/java/seedu/bookbuddy/Book.java +++ b/src/main/java/seedu/bookbuddy/Book.java @@ -2,27 +2,27 @@ public class Book { - public String description; + public String title; protected boolean isRead; /** - * Creates a new Task with the specified description. + * Creates a new Book with the specified title. * - * @param description The description of the task. + * @param title The description of the book. */ - public Book(String description) { - this.description = description; // Description of the task - this.isRead = false; //Completion status of the task(True: Read, False: Unread) + public Book(String title) { + this.title = title; // Description of the book + this.isRead = false; //Completion status of the book (True: Read, False: Unread) } /** - * Returns the description of the book. + * Returns the title of the book. * - * @return The description of the book. + * @return The title of the book. */ - public String getDescription() { - return this.description; + public String getTitle() { + return this.title; } /** @@ -39,6 +39,7 @@ public boolean isRead() { */ public void markBookAsRead() { this.isRead = true; + System.out.println("Successfully marked " + this.getTitle() + " as read."); } /** @@ -46,10 +47,11 @@ public void markBookAsRead() { */ public void markBookAsUnread() { this.isRead = false; + System.out.println("Successfully marked " + this.getTitle() + " as unread."); } public String toString() { - String statusMark = this.isRead() ? "X" : " "; // Mark with 'x' if completed - return this.description + " [" + statusMark + "]" ; + String statusMark = this.isRead() ? "R" : "U"; // Mark with 'R' if read and 'U' if unread + return "[" + statusMark + "] " + this.title; } } diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index fcbd38ee7a..9a3c27c196 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -4,21 +4,29 @@ import java.util.Scanner; public class BookBuddy { - private static BookList bookList = new BookList(); + private static BookList books = new BookList(); public static void main(String[] args) { + printWelcomeMessage(); + getUserInput(books); + } + + public static void printWelcomeMessage() { + System.out.println("Hello! We are BookBuddy!"); + System.out.println("How can I help you today?"); + } - System.out.println("Hello! We are bookbuddy!"); - Scanner scanner = new Scanner(System.in); - while(true) { - String input = scanner.nextLine(); - String command = input.split(" ", 2)[0]; - if (Objects.equals(command, "addBook")) { - String actualDescription = input.split(" ", 2)[1]; - bookList.addBook(actualDescription); - } else if (Objects.equals(command, "list")) { - bookList.printAllBooks(); - } + public static void getUserInput(BookList books) { + Scanner input = new Scanner(System.in); + + //noinspection InfiniteLoopStatement + while (true) { + String userInput = input.nextLine(); + Parser.parseCommand(userInput, books); } } + public static void printExitMessage() { + System.out.println("Thank you for using bookbuddy! Hope to see you again!"); + } + } diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index 199de1a605..96091dbe17 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -35,10 +35,11 @@ public Book getBook(int index){ /** * Adds a new Book to the list. - * @param taskDescription The description of the book. + * @param title The title of the book. */ - public void addBook(String taskDescription) { - books.add(new Book(taskDescription)); + public void addBook(String title) { + books.add(new Book(title)); + System.out.println("Successfully added " + title + " to the list."); } /** @@ -46,7 +47,9 @@ public void addBook(String taskDescription) { * @param index The index of the book to delete. */ public void deleteBook(int index) { - books.remove(index-1); + Book book = books.get(index - 1); + books.remove(index - 1); + System.out.println("Successfully removed " + book.getTitle() + " from the list."); } /** @@ -54,7 +57,7 @@ public void deleteBook(int index) { * @param index The index of the book to mark as read. */ public void markDoneByIndex(int index) { - books.get(index-1).markBookAsRead(); + books.get(index - 1).markBookAsRead(); } /** @@ -62,7 +65,7 @@ public void markDoneByIndex(int index) { * @param index The index of the book to mark as unread. */ public void markUndoneByIndex(int index) { - books.get(index-1).markBookAsUnread(); + books.get(index - 1).markBookAsUnread(); } /** @@ -73,8 +76,8 @@ public void printAllBooks() { System.out.println("All books:"); for (int i = 0; i < books.size(); i++) { Book currentBook = books.get(i); - System.out.print((i + 1) + "."); - System.out.println(currentBook); + System.out.print((i + 1) + ". "); + System.out.println(currentBook.toString()); } } else { System.out.println("The list is empty."); diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java new file mode 100644 index 0000000000..907cb86777 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -0,0 +1,44 @@ +package seedu.bookbuddy; + +public class Parser { + public static final String ADD_COMMAND = "add"; + public static final String REMOVE_COMMAND = "remove"; + public static final String LIST_COMMAND = "list"; + public static final String MARK_COMMAND = "mark"; + public static final String UNMARK_COMMAND = "unmark"; + public static final String EXIT_COMMAND = "bye"; + + public static void parseCommand( String input, BookList Books) { + String[] inputArray = input.split(" ", 2); + String command = inputArray[0].toLowerCase(); + int index; + + switch (command) { + case ADD_COMMAND: + Books.addBook(inputArray[1]); + break; + case REMOVE_COMMAND: + index = Integer.parseInt(inputArray[1]); + Books.deleteBook(index); + break; + case LIST_COMMAND: + Books.printAllBooks(); + break; + case MARK_COMMAND: + index = Integer.parseInt(inputArray[1]); + Books.markDoneByIndex(index); + break; + case UNMARK_COMMAND: + index = Integer.parseInt(inputArray[1]); + Books.markUndoneByIndex(index); + break; + case EXIT_COMMAND: + BookBuddy.printExitMessage(); + System.exit(0); + break; + default: + System.out.println("Sorry but that is not a valid command. Please try again"); + } + } + +} diff --git a/src/test/java/seedu/bookbuddy/BookListTest.java b/src/test/java/seedu/bookbuddy/BookListTest.java index ba72a86909..7960e929a1 100644 --- a/src/test/java/seedu/bookbuddy/BookListTest.java +++ b/src/test/java/seedu/bookbuddy/BookListTest.java @@ -18,7 +18,7 @@ void addBook() { BookList bookList = new BookList(); bookList.addBook("Harry Potter"); assertEquals(1, bookList.getSize()); - assertEquals("Harry Potter [ ]", bookList.getBook(0).toString()); + assertEquals("[U] Harry Potter", bookList.getBook(0).toString()); } } From 6fc79ee031259096dbea08738193af92c99c23e8 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Tue, 19 Mar 2024 19:09:30 +0800 Subject: [PATCH 017/311] Implement JUnit test for Parser class --- src/test/java/seedu/bookbuddy/ParserTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/test/java/seedu/bookbuddy/ParserTest.java diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java new file mode 100644 index 0000000000..9c5833dbf8 --- /dev/null +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -0,0 +1,23 @@ +package seedu.bookbuddy; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ParserTest { + @Test + void testParser() { + BookList books = new BookList(); + books.addBook("Don Quixote"); + books.addBook("Gulliver's Travels"); + assertEquals(2, books.getSize()); + books.markDoneByIndex(1); + assertEquals("[R] Don Quixote", books.getBook(0).toString()); + assertEquals("[U] Gulliver's Travels", books.getBook(1).toString()); + books.deleteBook(1); + books.markDoneByIndex(1); + assertTrue(books.getBook(0).isRead); + assertEquals("[R] Gulliver's Travels", books.getBook(0).toString()); + } +} From 22ac458cb2aee882cc59ec624b43d36340ee4888 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Tue, 19 Mar 2024 19:18:02 +0800 Subject: [PATCH 018/311] Add documentation for Parser class --- src/main/java/seedu/bookbuddy/BookBuddy.java | 2 +- src/main/java/seedu/bookbuddy/Parser.java | 22 ++++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index 9a3c27c196..4f44798a87 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -26,7 +26,7 @@ public static void getUserInput(BookList books) { } public static void printExitMessage() { - System.out.println("Thank you for using bookbuddy! Hope to see you again!"); + System.out.println("Thank you for using BookBuddy! Hope to see you again!"); } } diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 907cb86777..afdbb6bbda 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -1,5 +1,9 @@ package seedu.bookbuddy; +/** + * Parses inputs from the user in order to execute the correct commands. + */ + public class Parser { public static final String ADD_COMMAND = "add"; public static final String REMOVE_COMMAND = "remove"; @@ -8,29 +12,35 @@ public class Parser { public static final String UNMARK_COMMAND = "unmark"; public static final String EXIT_COMMAND = "bye"; - public static void parseCommand( String input, BookList Books) { + /** + * Scans the user input for valid commands and handles them accordingly. + * @param input input from the user + * @param books ArrayList of books + */ + + public static void parseCommand( String input, BookList books) { String[] inputArray = input.split(" ", 2); String command = inputArray[0].toLowerCase(); int index; switch (command) { case ADD_COMMAND: - Books.addBook(inputArray[1]); + books.addBook(inputArray[1]); break; case REMOVE_COMMAND: index = Integer.parseInt(inputArray[1]); - Books.deleteBook(index); + books.deleteBook(index); break; case LIST_COMMAND: - Books.printAllBooks(); + books.printAllBooks(); break; case MARK_COMMAND: index = Integer.parseInt(inputArray[1]); - Books.markDoneByIndex(index); + books.markDoneByIndex(index); break; case UNMARK_COMMAND: index = Integer.parseInt(inputArray[1]); - Books.markUndoneByIndex(index); + books.markUndoneByIndex(index); break; case EXIT_COMMAND: BookBuddy.printExitMessage(); From 7f2b6ecb6b7b9323c3c54d29f25a90cda9cd2722 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Tue, 19 Mar 2024 19:21:56 +0800 Subject: [PATCH 019/311] Fix issues with Gradle build --- src/main/java/seedu/bookbuddy/BookBuddy.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index 4f44798a87..c031fce6f4 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -1,6 +1,5 @@ package seedu.bookbuddy; -import java.util.Objects; import java.util.Scanner; public class BookBuddy { From 6add382c70b11db1e58ae6bf52c45fb2d7225891 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Tue, 19 Mar 2024 19:28:55 +0800 Subject: [PATCH 020/311] Fix issues with text-ui-test --- text-ui-test/EXPECTED.TXT | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index f49ba41f61..174077a2e8 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1 +1,3 @@ -Hello! We are bookbuddy! +Hello! We are Book Buddy! +How can I help you today? +Sorry but that is not a valid command. Please try again From 85489483aa8a520289a0994e56d2da66dc0bc051 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Tue, 19 Mar 2024 19:39:11 +0800 Subject: [PATCH 021/311] Fix issues with text-ui-test --- text-ui-test/EXPECTED.TXT | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 174077a2e8..83e2d80f5a 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,3 +1,3 @@ -Hello! We are Book Buddy! +Hello! We are BookBuddy! How can I help you today? Sorry but that is not a valid command. Please try again From c30e0d1e9c97a2dc310f5f0f26ff98e4ce279769 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 00:42:02 +0800 Subject: [PATCH 022/311] BookBuddyTest jUnit Test cases. --- .../java/seedu/bookbuddy/BookBuddyTest.java | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index 848dac7bea..1b96cd7edf 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -1,20 +1,44 @@ package seedu.bookbuddy; - +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; - -//import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; class BookBuddyTest { + private final PrintStream originalOut = System.out; + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + + @BeforeEach + public void setUp() { + System.setOut(new PrintStream(outContent)); + } + + @AfterEach + public void restoreStreams() { + System.setOut(originalOut); + } + @Test public void sampleTest() { assertTrue(true); } + @Test + public void testPrintWelcomeMessage() { + BookBuddy.printWelcomeMessage(); + String expectedOutput = "Hello! We are BookBuddy!\nHow can I help you today?\n"; + assertEquals(expectedOutput, outContent.toString()); + } @Test - void addBook() { + public void testPrintExitMessage() { + BookBuddy.printExitMessage(); + String expectedOutput = "Thank you for using BookBuddy! Hope to see you again!\n"; + assertEquals(expectedOutput, outContent.toString()); } -} +} \ No newline at end of file From 90968ba853f34b0606bafad7364261d26df707dc Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Wed, 20 Mar 2024 00:47:55 +0800 Subject: [PATCH 023/311] Add JUnit tests for all BookList methods --- .../java/seedu/bookbuddy/BookListTest.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/test/java/seedu/bookbuddy/BookListTest.java b/src/test/java/seedu/bookbuddy/BookListTest.java index 7960e929a1..a353b7b352 100644 --- a/src/test/java/seedu/bookbuddy/BookListTest.java +++ b/src/test/java/seedu/bookbuddy/BookListTest.java @@ -21,4 +21,40 @@ void addBook() { assertEquals("[U] Harry Potter", bookList.getBook(0).toString()); } + @Test + void deleteBook() { + BookList bookList = new BookList(); + bookList.addBook("Harry Potter"); + assertEquals(1, bookList.getSize()); + bookList.deleteBook(1); + assertEquals(0, bookList.getSize()); + } + + @Test + void getBook() { + BookList bookList = new BookList(); + bookList.addBook("Harry Potter"); + bookList.addBook("Geronimo"); + bookList.addBook("Cradle"); + assertEquals("[U] Cradle", bookList.getBook(2).toString()); + } + + @Test + void markDoneByIndex() { + BookList bookList = new BookList(); + bookList.addBook("Harry Potter"); + bookList.markDoneByIndex(1); + assertEquals("[R] Harry Potter", bookList.getBook(0).toString()); + } + + @Test + void markUndoneByIndex() { + BookList bookList = new BookList(); + bookList.addBook("Harry Potter"); + bookList.markDoneByIndex(1); + assertEquals("[R] Harry Potter", bookList.getBook(0).toString()); + bookList.markUndoneByIndex(1); + assertEquals("[U] Harry Potter", bookList.getBook(0).toString()); + } + } From f1322bee1edbcb7d193aaf44aa5a251c8e1d1c91 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Wed, 20 Mar 2024 00:48:14 +0800 Subject: [PATCH 024/311] Add exception handling for BookList methods --- src/main/java/seedu/bookbuddy/BookList.java | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index 96091dbe17..4025a724e8 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -29,7 +29,10 @@ public int getSize(){ * @param index The index of the book to retrieve. * @return The Book at the specified index. */ - public Book getBook(int index){ + public Book getBook(int index) throws IndexOutOfBoundsException{ + if (index < 0 || index > books.size()) { + throw new IndexOutOfBoundsException("Book index out of range."); + } return books.get(index); } @@ -46,7 +49,10 @@ public void addBook(String title) { * Deletes a book from the list by its index. * @param index The index of the book to delete. */ - public void deleteBook(int index) { + public void deleteBook(int index) throws IndexOutOfBoundsException{ + if (index < 0 || index > books.size()) { + throw new IndexOutOfBoundsException("Book index out of range."); + } Book book = books.get(index - 1); books.remove(index - 1); System.out.println("Successfully removed " + book.getTitle() + " from the list."); @@ -56,7 +62,10 @@ public void deleteBook(int index) { * Marks a book as read by its index. * @param index The index of the book to mark as read. */ - public void markDoneByIndex(int index) { + public void markDoneByIndex(int index) throws IndexOutOfBoundsException{ + if (index < 0 || index > books.size()) { + throw new IndexOutOfBoundsException("Book index out of range."); + } books.get(index - 1).markBookAsRead(); } @@ -64,7 +73,10 @@ public void markDoneByIndex(int index) { * Marks a book as unread by its index. * @param index The index of the book to mark as unread. */ - public void markUndoneByIndex(int index) { + public void markUndoneByIndex(int index) throws IndexOutOfBoundsException{ + if (index < 0 || index > books.size()) { + throw new IndexOutOfBoundsException("Book index out of range."); + } books.get(index - 1).markBookAsUnread(); } From 5b7287e12b192fc6b2e06dde91913731e3fff307 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 00:50:32 +0800 Subject: [PATCH 025/311] BookListTest jUnit test cases --- .../java/seedu/bookbuddy/BookListTest.java | 60 +++++++++++++++++-- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/BookListTest.java b/src/test/java/seedu/bookbuddy/BookListTest.java index 7960e929a1..5afbb4ac3d 100644 --- a/src/test/java/seedu/bookbuddy/BookListTest.java +++ b/src/test/java/seedu/bookbuddy/BookListTest.java @@ -1,9 +1,11 @@ package seedu.bookbuddy; -import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.*; class BookListTest { @@ -15,10 +17,56 @@ public void sampleTest() { @Test void addBook() { - BookList bookList = new BookList(); - bookList.addBook("Harry Potter"); - assertEquals(1, bookList.getSize()); - assertEquals("[U] Harry Potter", bookList.getBook(0).toString()); + BookList testBookList = new BookList(); + testBookList.addBook("Harry Potter"); + assertEquals(1, testBookList.getSize()); + assertEquals("[U] Harry Potter", testBookList.getBook(0).toString()); + } + + + @Test + void deleteBook() { + BookList testBookList = new BookList(); + testBookList.addBook("Harry Potter"); + testBookList.addBook("The Hobbit"); + assertEquals(2, testBookList.getSize()); + + testBookList.deleteBook(1); // Delete the first book + assertEquals(1, testBookList.getSize()); + assertEquals("The Hobbit", testBookList.getBook(0).getTitle()); } + @Test + void markDoneAndUndoneByIndex() { + BookList testBookList = new BookList(); + testBookList.addBook("Harry Potter"); + + testBookList.markDoneByIndex(1); // Mark the first book as read + assertTrue(testBookList.getBook(0).isRead()); + + testBookList.markUndoneByIndex(1); // Mark the first book as unread + assertFalse(testBookList.getBook(0).isRead()); + } + + + + @Test + void printAllBooks() { + BookList testBookList = new BookList(); + testBookList.addBook("Harry Potter"); + + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outContent)); + + testBookList.printAllBooks(); + + String expectedOutput = "All books:\n1. [U] Harry Potter\n"; + assertEquals(expectedOutput, outContent.toString()); + + System.setOut(System.out); + } + + + + } From fa968fdf658e621ee2c535d5e51de56e68d992a2 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 01:00:25 +0800 Subject: [PATCH 026/311] Parser Test jUnit test cases --- src/test/java/seedu/bookbuddy/ParserTest.java | 52 ++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index 9c5833dbf8..9da91b465e 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -1,9 +1,11 @@ package seedu.bookbuddy; -import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertEquals; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +import static org.junit.jupiter.api.Assertions.*; public class ParserTest { @Test @@ -20,4 +22,50 @@ void testParser() { assertTrue(books.getBook(0).isRead); assertEquals("[R] Gulliver's Travels", books.getBook(0).toString()); } + + @Test + void parseAddCommand() { + BookList testBookList = new BookList(); + Parser.parseCommand("add The Great Gatsby", testBookList); + assertEquals(1, testBookList.getSize()); + assertEquals("The Great Gatsby", testBookList.getBook(0).getTitle()); + } + + @Test + void parseRemoveCommand() { + BookList books = new BookList(); + books.addBook("The Great Gatsby"); + Parser.parseCommand("remove 1", books); + assertEquals(0, books.getSize()); + } + + @Test + void parseMarkCommand() { + BookList books = new BookList(); + books.addBook("The Great Gatsby"); + Parser.parseCommand("mark 1", books); + assertTrue(books.getBook(0).isRead()); + } + + @Test + void parseUnmarkCommand() { + BookList books = new BookList(); + books.addBook("The Great Gatsby"); + Parser.parseCommand("mark 1", books); + Parser.parseCommand("unmark 1", books); + assertFalse(books.getBook(0).isRead()); + } + + @Test + void parseInvalidCommand() { + BookList books = new BookList(); + final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + final PrintStream originalOut = System.out; + System.setOut(new PrintStream(outContent)); + Parser.parseCommand("invalid", books); + assertEquals("Sorry but that is not a valid command. Please try again\n", outContent.toString()); + System.setOut(originalOut); + } + + } From 5789e405ab24cf6a4e3181771155c5f80796edee Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 01:25:04 +0800 Subject: [PATCH 027/311] Checkstyle fixes --- src/test/java/seedu/bookbuddy/BookListTest.java | 4 +++- src/test/java/seedu/bookbuddy/ParserTest.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/BookListTest.java b/src/test/java/seedu/bookbuddy/BookListTest.java index 8473bd95e0..1711a9b713 100644 --- a/src/test/java/seedu/bookbuddy/BookListTest.java +++ b/src/test/java/seedu/bookbuddy/BookListTest.java @@ -5,7 +5,9 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; + class BookListTest { diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index 9da91b465e..cf040aa305 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -5,7 +5,9 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; public class ParserTest { @Test From 05bf1d37f8584595e73595e96bb3fb246218a9de Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 01:28:12 +0800 Subject: [PATCH 028/311] Checkstyle fix - newline at the end of file --- src/test/java/seedu/bookbuddy/BookBuddyTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index 1b96cd7edf..8c635c48eb 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -41,4 +41,4 @@ public void testPrintExitMessage() { assertEquals(expectedOutput, outContent.toString()); } -} \ No newline at end of file +} From d5cde76bf2283e7b22bd67f2ea625174e9e63f07 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 01:38:49 +0800 Subject: [PATCH 029/311] Checkstyle fixes - empty spaces. For Gradle Checkstyle --- src/test/java/seedu/bookbuddy/BookBuddyTest.java | 4 ++-- src/test/java/seedu/bookbuddy/BookListTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index 8c635c48eb..882eb0edce 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -31,14 +31,14 @@ public void sampleTest() { public void testPrintWelcomeMessage() { BookBuddy.printWelcomeMessage(); String expectedOutput = "Hello! We are BookBuddy!\nHow can I help you today?\n"; - assertEquals(expectedOutput, outContent.toString()); + assertEquals(expectedOutput.trim(), outContent.toString().trim()); } @Test public void testPrintExitMessage() { BookBuddy.printExitMessage(); String expectedOutput = "Thank you for using BookBuddy! Hope to see you again!\n"; - assertEquals(expectedOutput, outContent.toString()); + assertEquals(expectedOutput.trim(), outContent.toString().trim()); } } diff --git a/src/test/java/seedu/bookbuddy/BookListTest.java b/src/test/java/seedu/bookbuddy/BookListTest.java index 1711a9b713..324378cd76 100644 --- a/src/test/java/seedu/bookbuddy/BookListTest.java +++ b/src/test/java/seedu/bookbuddy/BookListTest.java @@ -36,7 +36,7 @@ void printAllBooks() { testBookList.printAllBooks(); String expectedOutput = "All books:\n1. [U] Harry Potter\n"; - assertEquals(expectedOutput, outContent.toString()); + assertEquals(expectedOutput.trim(), outContent.toString().trim()); System.setOut(System.out); } From a1b0102f25d0e2692d24fb8f74132204089d25f5 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 01:54:52 +0800 Subject: [PATCH 030/311] checkstyle fixes --- src/test/java/seedu/bookbuddy/BookBuddyTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index 882eb0edce..a2a8ab5bae 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -30,7 +30,7 @@ public void sampleTest() { @Test public void testPrintWelcomeMessage() { BookBuddy.printWelcomeMessage(); - String expectedOutput = "Hello! We are BookBuddy!\nHow can I help you today?\n"; + String expectedOutput = "Hello! We are BookBuddy!" + "\n" + "How can I help you today?\n"; assertEquals(expectedOutput.trim(), outContent.toString().trim()); } From fab0e4ecfdd24436bbd846f0c584a46f42e44a13 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 01:58:24 +0800 Subject: [PATCH 031/311] checkstyle fixes --- src/test/java/seedu/bookbuddy/BookBuddyTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index a2a8ab5bae..f9806f3f54 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -30,7 +30,7 @@ public void sampleTest() { @Test public void testPrintWelcomeMessage() { BookBuddy.printWelcomeMessage(); - String expectedOutput = "Hello! We are BookBuddy!" + "\n" + "How can I help you today?\n"; + String expectedOutput = "Hello! We are BookBuddy!\n" + "How can I help you today?\n"; assertEquals(expectedOutput.trim(), outContent.toString().trim()); } From 56f4794749a406481988a8184ab29ed68371a0a9 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 02:11:35 +0800 Subject: [PATCH 032/311] checkstyle fixes --- .../java/seedu/bookbuddy/BookBuddyTest.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index f9806f3f54..1788115b7c 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import java.io.ByteArrayOutputStream; import java.io.PrintStream; @@ -27,13 +28,27 @@ public void sampleTest() { assertTrue(true); } +// @Test +// public void testPrintWelcomeMessage() { +// BookBuddy.printWelcomeMessage(); +// String expectedOutput = "Hello! We are BookBuddy!\n" + "How can I help you today?\n"; +// assertEquals(expectedOutput.trim(), outContent.toString().trim()); +// } + @Test public void testPrintWelcomeMessage() { BookBuddy.printWelcomeMessage(); - String expectedOutput = "Hello! We are BookBuddy!\n" + "How can I help you today?\n"; - assertEquals(expectedOutput.trim(), outContent.toString().trim()); + String expectedOutput = "Hello! We are BookBuddy!\nHow can I help you today?\n"; + String actualOutput = outContent.toString(); + + // Convert to byte arrays to compare + byte[] expectedBytes = expectedOutput.getBytes(); + byte[] actualBytes = actualOutput.getBytes(); + + assertArrayEquals(expectedBytes, actualBytes); } + @Test public void testPrintExitMessage() { BookBuddy.printExitMessage(); From 0884acd4ec31fc789390043fd708f4727af56907 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 02:16:51 +0800 Subject: [PATCH 033/311] checkstyle fixes --- src/test/java/seedu/bookbuddy/BookBuddyTest.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index 1788115b7c..2ccd424785 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -28,13 +28,6 @@ public void sampleTest() { assertTrue(true); } -// @Test -// public void testPrintWelcomeMessage() { -// BookBuddy.printWelcomeMessage(); -// String expectedOutput = "Hello! We are BookBuddy!\n" + "How can I help you today?\n"; -// assertEquals(expectedOutput.trim(), outContent.toString().trim()); -// } - @Test public void testPrintWelcomeMessage() { BookBuddy.printWelcomeMessage(); From f018db4e99c8eba2457573d894c92e96527fd96b Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 02:23:36 +0800 Subject: [PATCH 034/311] checkstyle fixes --- src/test/java/seedu/bookbuddy/BookBuddyTest.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index 2ccd424785..9b81efd1bd 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -31,17 +31,25 @@ public void sampleTest() { @Test public void testPrintWelcomeMessage() { BookBuddy.printWelcomeMessage(); - String expectedOutput = "Hello! We are BookBuddy!\nHow can I help you today?\n"; + String expectedOutput = "Hello! We are BookBuddy!" + System.lineSeparator() + + "How can I help you today?" + System.lineSeparator(); String actualOutput = outContent.toString(); + // Normalize the line endings in the actual output to match the system's line separator + String normalizedActualOutput = actualOutput.replace(System.lineSeparator(), "\n"); + + // Normalize the expected output line endings to \n for comparison + String normalizedExpectedOutput = expectedOutput.replace("\n", System.lineSeparator()); + // Convert to byte arrays to compare - byte[] expectedBytes = expectedOutput.getBytes(); - byte[] actualBytes = actualOutput.getBytes(); + byte[] expectedBytes = normalizedExpectedOutput.getBytes(); + byte[] actualBytes = normalizedActualOutput.getBytes(); assertArrayEquals(expectedBytes, actualBytes); } + @Test public void testPrintExitMessage() { BookBuddy.printExitMessage(); From e6bc787bda88362cf1a3d801370fe17f437c2e6a Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 02:28:57 +0800 Subject: [PATCH 035/311] checkstyle fixes --- .../java/seedu/bookbuddy/BookBuddyTest.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index 9b81efd1bd..69a614d13e 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -31,25 +31,19 @@ public void sampleTest() { @Test public void testPrintWelcomeMessage() { BookBuddy.printWelcomeMessage(); - String expectedOutput = "Hello! We are BookBuddy!" + System.lineSeparator() + - "How can I help you today?" + System.lineSeparator(); String actualOutput = outContent.toString(); - // Normalize the line endings in the actual output to match the system's line separator - String normalizedActualOutput = actualOutput.replace(System.lineSeparator(), "\n"); + // Normalize line endings to \n in both expected and actual output + String normalizedExpectedOutput = "Hello! We are BookBuddy!\nHow can I help you today?\n".replace("\r\n", "\n"); + String normalizedActualOutput = actualOutput.replace("\r\n", "\n"); - // Normalize the expected output line endings to \n for comparison - String normalizedExpectedOutput = expectedOutput.replace("\n", System.lineSeparator()); - - // Convert to byte arrays to compare - byte[] expectedBytes = normalizedExpectedOutput.getBytes(); - byte[] actualBytes = normalizedActualOutput.getBytes(); - - assertArrayEquals(expectedBytes, actualBytes); + // Assert that the normalized outputs are equal + assertEquals(normalizedExpectedOutput, normalizedActualOutput); } + @Test public void testPrintExitMessage() { BookBuddy.printExitMessage(); From 4927c5402eca03ed051895b59a0c7bbbbcda75eb Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 02:31:04 +0800 Subject: [PATCH 036/311] checkstyle fixes - unused imports --- src/test/java/seedu/bookbuddy/BookBuddyTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index 69a614d13e..659ce88bce 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; import java.io.ByteArrayOutputStream; import java.io.PrintStream; From abfe183e41ae8e0270b27f353f16ec36614ea2b3 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 02:36:51 +0800 Subject: [PATCH 037/311] checkstyle fixes - \r\n on windows system and \n on unix systems --- src/test/java/seedu/bookbuddy/BookListTest.java | 3 ++- src/test/java/seedu/bookbuddy/ParserTest.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/BookListTest.java b/src/test/java/seedu/bookbuddy/BookListTest.java index 324378cd76..5f5be930b6 100644 --- a/src/test/java/seedu/bookbuddy/BookListTest.java +++ b/src/test/java/seedu/bookbuddy/BookListTest.java @@ -36,7 +36,8 @@ void printAllBooks() { testBookList.printAllBooks(); String expectedOutput = "All books:\n1. [U] Harry Potter\n"; - assertEquals(expectedOutput.trim(), outContent.toString().trim()); + String normalizedActualOutput = outContent.toString().replace("\r\n", "\n"); + assertEquals(expectedOutput.trim(), normalizedActualOutput.trim()); System.setOut(System.out); } diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index cf040aa305..0bd87a10d5 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -65,7 +65,8 @@ void parseInvalidCommand() { final PrintStream originalOut = System.out; System.setOut(new PrintStream(outContent)); Parser.parseCommand("invalid", books); - assertEquals("Sorry but that is not a valid command. Please try again\n", outContent.toString()); + String normalizedActualOutput = outContent.toString().replace("\r\n", "\n"); + assertEquals("Sorry but that is not a valid command. Please try again\n", normalizedActualOutput); System.setOut(originalOut); } From fee4c6d9559d849426f903c660cac13c8e5df3db Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 03:52:24 +0800 Subject: [PATCH 038/311] Custom Exception handling for parser class --- .../exceptions/BookNotFoundException.java | 7 ++ .../exceptions/InvalidBookIndexException.java | 8 +++ .../InvalidCommandArgumentException.java | 7 ++ .../UnsupportedCommandException.java | 7 ++ src/main/java/seedu/bookbuddy/Parser.java | 64 +++++++++++-------- 5 files changed, 67 insertions(+), 26 deletions(-) create mode 100644 src/main/java/exceptions/BookNotFoundException.java create mode 100644 src/main/java/exceptions/InvalidBookIndexException.java create mode 100644 src/main/java/exceptions/InvalidCommandArgumentException.java create mode 100644 src/main/java/exceptions/UnsupportedCommandException.java diff --git a/src/main/java/exceptions/BookNotFoundException.java b/src/main/java/exceptions/BookNotFoundException.java new file mode 100644 index 0000000000..4dc275fa2f --- /dev/null +++ b/src/main/java/exceptions/BookNotFoundException.java @@ -0,0 +1,7 @@ +package exceptions; + +public class BookNotFoundException extends RuntimeException { + public BookNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/exceptions/InvalidBookIndexException.java b/src/main/java/exceptions/InvalidBookIndexException.java new file mode 100644 index 0000000000..4d98531bd0 --- /dev/null +++ b/src/main/java/exceptions/InvalidBookIndexException.java @@ -0,0 +1,8 @@ +package exceptions; + +public class InvalidBookIndexException extends RuntimeException { + public InvalidBookIndexException(String message) { + super(message); + } +} + diff --git a/src/main/java/exceptions/InvalidCommandArgumentException.java b/src/main/java/exceptions/InvalidCommandArgumentException.java new file mode 100644 index 0000000000..fe004c5edb --- /dev/null +++ b/src/main/java/exceptions/InvalidCommandArgumentException.java @@ -0,0 +1,7 @@ +package exceptions; + +public class InvalidCommandArgumentException extends IllegalArgumentException { + public InvalidCommandArgumentException(String message) { + super(message); + } +} diff --git a/src/main/java/exceptions/UnsupportedCommandException.java b/src/main/java/exceptions/UnsupportedCommandException.java new file mode 100644 index 0000000000..34ba4c5714 --- /dev/null +++ b/src/main/java/exceptions/UnsupportedCommandException.java @@ -0,0 +1,7 @@ +package exceptions; + +public class UnsupportedCommandException extends RuntimeException { + public UnsupportedCommandException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index afdbb6bbda..655e09e55b 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -1,5 +1,10 @@ package seedu.bookbuddy; +import exceptions.BookNotFoundException; +import exceptions.InvalidBookIndexException; +import exceptions.InvalidCommandArgumentException; +import exceptions.UnsupportedCommandException; + /** * Parses inputs from the user in order to execute the correct commands. */ @@ -22,32 +27,39 @@ public static void parseCommand( String input, BookList books) { String[] inputArray = input.split(" ", 2); String command = inputArray[0].toLowerCase(); int index; - - switch (command) { - case ADD_COMMAND: - books.addBook(inputArray[1]); - break; - case REMOVE_COMMAND: - index = Integer.parseInt(inputArray[1]); - books.deleteBook(index); - break; - case LIST_COMMAND: - books.printAllBooks(); - break; - case MARK_COMMAND: - index = Integer.parseInt(inputArray[1]); - books.markDoneByIndex(index); - break; - case UNMARK_COMMAND: - index = Integer.parseInt(inputArray[1]); - books.markUndoneByIndex(index); - break; - case EXIT_COMMAND: - BookBuddy.printExitMessage(); - System.exit(0); - break; - default: - System.out.println("Sorry but that is not a valid command. Please try again"); + try { + switch (command) { + case ADD_COMMAND: + if (inputArray.length < 2) + throw new InvalidCommandArgumentException("The add command requires a book title."); + books.addBook(inputArray[1]); + break; + case REMOVE_COMMAND: + index = Integer.parseInt(inputArray[1]); + books.deleteBook(index); + break; + case LIST_COMMAND: + books.printAllBooks(); + break; + case MARK_COMMAND: + index = Integer.parseInt(inputArray[1]); + books.markDoneByIndex(index); + break; + case UNMARK_COMMAND: + index = Integer.parseInt(inputArray[1]); + books.markUndoneByIndex(index); + break; + case EXIT_COMMAND: + BookBuddy.printExitMessage(); + System.exit(0); + break; + default: + throw new UnsupportedCommandException("Sorry but that is not a valid command. Please try again"); + } + } catch (NumberFormatException e) { + throw new InvalidBookIndexException("Book index must be an integer."); + } catch (IndexOutOfBoundsException e) { + throw new BookNotFoundException("Book not found at the provided index."); } } From cd116619e6a85d850b75a2b504ab75cb28ab0d53 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 03:57:52 +0800 Subject: [PATCH 039/311] checkstyle fixes --- src/main/java/seedu/bookbuddy/Parser.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 655e09e55b..14728a3b14 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -30,8 +30,9 @@ public static void parseCommand( String input, BookList books) { try { switch (command) { case ADD_COMMAND: - if (inputArray.length < 2) + if (inputArray.length < 2) { throw new InvalidCommandArgumentException("The add command requires a book title."); + } books.addBook(inputArray[1]); break; case REMOVE_COMMAND: From 0c338604f103e280ce9031ad950693b534d6d8ef Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 04:00:14 +0800 Subject: [PATCH 040/311] checkstyle fixes --- src/main/java/seedu/bookbuddy/Parser.java | 54 +++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 14728a3b14..3bbc650d85 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -29,33 +29,33 @@ public static void parseCommand( String input, BookList books) { int index; try { switch (command) { - case ADD_COMMAND: - if (inputArray.length < 2) { - throw new InvalidCommandArgumentException("The add command requires a book title."); - } - books.addBook(inputArray[1]); - break; - case REMOVE_COMMAND: - index = Integer.parseInt(inputArray[1]); - books.deleteBook(index); - break; - case LIST_COMMAND: - books.printAllBooks(); - break; - case MARK_COMMAND: - index = Integer.parseInt(inputArray[1]); - books.markDoneByIndex(index); - break; - case UNMARK_COMMAND: - index = Integer.parseInt(inputArray[1]); - books.markUndoneByIndex(index); - break; - case EXIT_COMMAND: - BookBuddy.printExitMessage(); - System.exit(0); - break; - default: - throw new UnsupportedCommandException("Sorry but that is not a valid command. Please try again"); + case ADD_COMMAND: + if (inputArray.length < 2) { + throw new InvalidCommandArgumentException("The add command requires a book title."); + } + books.addBook(inputArray[1]); + break; + case REMOVE_COMMAND: + index = Integer.parseInt(inputArray[1]); + books.deleteBook(index); + break; + case LIST_COMMAND: + books.printAllBooks(); + break; + case MARK_COMMAND: + index = Integer.parseInt(inputArray[1]); + books.markDoneByIndex(index); + break; + case UNMARK_COMMAND: + index = Integer.parseInt(inputArray[1]); + books.markUndoneByIndex(index); + break; + case EXIT_COMMAND: + BookBuddy.printExitMessage(); + System.exit(0); + break; + default: + throw new UnsupportedCommandException("Sorry but that is not a valid command. Please try again"); } } catch (NumberFormatException e) { throw new InvalidBookIndexException("Book index must be an integer."); From 0306edaef50bac1467f2243abfab3682891f826a Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 04:02:49 +0800 Subject: [PATCH 041/311] checkstyle fixes --- src/test/java/seedu/bookbuddy/ParserTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index 9da91b465e..bd0aa0066f 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -63,7 +63,7 @@ void parseInvalidCommand() { final PrintStream originalOut = System.out; System.setOut(new PrintStream(outContent)); Parser.parseCommand("invalid", books); - assertEquals("Sorry but that is not a valid command. Please try again\n", outContent.toString()); + assertEquals("Sorry but that is not a valid command. Please try again", outContent.toString()); System.setOut(originalOut); } From 815f18cd8229fa6c0bc5652e8e9ca738cd6fce14 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 04:18:32 +0800 Subject: [PATCH 042/311] Junit Test for exception handling --- src/test/java/seedu/bookbuddy/ParserTest.java | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index bd0aa0066f..90a0d674fe 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -1,11 +1,16 @@ package seedu.bookbuddy; +import exceptions.BookNotFoundException; +import exceptions.InvalidBookIndexException; +import exceptions.InvalidCommandArgumentException; +import exceptions.UnsupportedCommandException; import org.junit.jupiter.api.Test; - import java.io.ByteArrayOutputStream; import java.io.PrintStream; - -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; public class ParserTest { @Test @@ -57,15 +62,33 @@ void parseUnmarkCommand() { } @Test - void parseInvalidCommand() { + void parseInvalidAddCommandThrowsException() { BookList books = new BookList(); - final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); - final PrintStream originalOut = System.out; - System.setOut(new PrintStream(outContent)); - Parser.parseCommand("invalid", books); - assertEquals("Sorry but that is not a valid command. Please try again", outContent.toString()); - System.setOut(originalOut); + String input = "add"; // No book title provided + assertThrows(InvalidCommandArgumentException.class, () -> Parser.parseCommand(input, books)); } + @Test + void parseInvalidRemoveCommandThrowsException() { + BookList books = new BookList(); + String input = "remove notAnIndex"; // Invalid index provided + assertThrows(InvalidBookIndexException.class, () -> Parser.parseCommand(input, books)); + } + @Test + void parseRemoveCommandForNonExistentBookThrowsException() { + BookList books = new BookList(); + String input = "remove 1"; // No books in the list, so index 1 is invalid + assertThrows(BookNotFoundException.class, () -> Parser.parseCommand(input, books)); + } + + @Test + void parseUnsupportedCommandThrowsException() { + BookList books = new BookList(); + String input = "Geronimo Stilton"; // Completely unsupported command + assertThrows(UnsupportedCommandException.class, () -> Parser.parseCommand(input, books)); + } } + + + From 2e6ad25e2d28e7c9b65e5fe519e2adf17a21d249 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 04:44:26 +0800 Subject: [PATCH 043/311] checkstyle fixes --- src/test/java/seedu/bookbuddy/ParserTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index 57139669f6..3dd6b277cd 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -64,28 +64,28 @@ void parseUnmarkCommand() { void parseInvalidAddCommandThrowsException() { BookList books = new BookList(); String input = "add"; // No book title provided - assertThrows(InvalidCommandArgumentException.class, () -> Parser.parseCommand(input, books)); + assertThrows(InvalidCommandArgumentException.class, () -> Parser.parseCommand(input, books), "The add command requires a book title."); } @Test void parseInvalidRemoveCommandThrowsException() { BookList books = new BookList(); String input = "remove notAnIndex"; // Invalid index provided - assertThrows(InvalidBookIndexException.class, () -> Parser.parseCommand(input, books)); + assertThrows(InvalidBookIndexException.class, () -> Parser.parseCommand(input, books), "Book index must be an integer."); } @Test void parseRemoveCommandForNonExistentBookThrowsException() { BookList books = new BookList(); String input = "remove 1"; // No books in the list, so index 1 is invalid - assertThrows(BookNotFoundException.class, () -> Parser.parseCommand(input, books)); + assertThrows(BookNotFoundException.class, () -> Parser.parseCommand(input, books), "Book not found at the provided index."); } @Test void parseUnsupportedCommandThrowsException() { BookList books = new BookList(); String input = "Geronimo Stilton"; // Completely unsupported command - assertThrows(UnsupportedCommandException.class, () -> Parser.parseCommand(input, books)); + assertThrows(UnsupportedCommandException.class, () -> Parser.parseCommand(input, books), "Sorry but that is not a valid command. Please try again"); } } From aff00829ff1a1e2940ac2eec47060c22e492a1fa Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 04:47:16 +0800 Subject: [PATCH 044/311] checkstyle fixes --- src/test/java/seedu/bookbuddy/ParserTest.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index 3dd6b277cd..beeae9d6b5 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -64,28 +64,32 @@ void parseUnmarkCommand() { void parseInvalidAddCommandThrowsException() { BookList books = new BookList(); String input = "add"; // No book title provided - assertThrows(InvalidCommandArgumentException.class, () -> Parser.parseCommand(input, books), "The add command requires a book title."); + assertThrows(InvalidCommandArgumentException.class, + () -> Parser.parseCommand(input, books), "The add command requires a book title."); } @Test void parseInvalidRemoveCommandThrowsException() { BookList books = new BookList(); String input = "remove notAnIndex"; // Invalid index provided - assertThrows(InvalidBookIndexException.class, () -> Parser.parseCommand(input, books), "Book index must be an integer."); + assertThrows(InvalidBookIndexException.class, + () -> Parser.parseCommand(input, books), "Book index must be an integer."); } @Test void parseRemoveCommandForNonExistentBookThrowsException() { BookList books = new BookList(); String input = "remove 1"; // No books in the list, so index 1 is invalid - assertThrows(BookNotFoundException.class, () -> Parser.parseCommand(input, books), "Book not found at the provided index."); + assertThrows(BookNotFoundException.class, + () -> Parser.parseCommand(input, books), "Book not found at the provided index."); } @Test void parseUnsupportedCommandThrowsException() { BookList books = new BookList(); String input = "Geronimo Stilton"; // Completely unsupported command - assertThrows(UnsupportedCommandException.class, () -> Parser.parseCommand(input, books), "Sorry but that is not a valid command. Please try again"); + assertThrows(UnsupportedCommandException.class, + () -> Parser.parseCommand(input, books), "Sorry but that is not a valid command. Please try again"); } } From 7e6f4bd21f68a63dfad2c71f3393cf7521d92cf3 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 04:57:38 +0800 Subject: [PATCH 045/311] checkstyle fix --- src/main/java/seedu/bookbuddy/BookBuddy.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index c031fce6f4..db7ef8f3c4 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -1,5 +1,7 @@ package seedu.bookbuddy; +import exceptions.UnsupportedCommandException; + import java.util.Scanner; public class BookBuddy { @@ -19,8 +21,17 @@ public static void getUserInput(BookList books) { //noinspection InfiniteLoopStatement while (true) { - String userInput = input.nextLine(); - Parser.parseCommand(userInput, books); + String userInput = input.nextLine().trim(); + if (userInput.isEmpty()) { + // If the input is empty, do not call parseCommand and just prompt for input again. + continue; + } + + try { + Parser.parseCommand(userInput, books); + } catch (UnsupportedCommandException e) { + System.out.println(e.getMessage()); + } } } From fbeb7eab57fdea1a5e089f4c508dfb13fee492a2 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 12:03:42 +0800 Subject: [PATCH 046/311] Defensive Coding - Adding assertions and logging --- build.gradle | 1 + src/main/java/seedu/bookbuddy/BookBuddy.java | 12 ++++++++++-- src/main/java/seedu/bookbuddy/BookList.java | 1 + src/main/java/seedu/bookbuddy/Parser.java | 6 ++++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 5d0a9207c1..ce9625972b 100644 --- a/build.gradle +++ b/build.gradle @@ -43,4 +43,5 @@ checkstyle { run{ standardInput = System.in + enableAssertions = true } diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index db7ef8f3c4..3280044e11 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -1,14 +1,19 @@ package seedu.bookbuddy; import exceptions.UnsupportedCommandException; - import java.util.Scanner; +import java.util.logging.Logger; +import java.util.logging.Level; + public class BookBuddy { private static BookList books = new BookList(); + static final Logger logger = Logger.getLogger(BookBuddy.class.getName()); public static void main(String[] args) { + logger.log(Level.INFO, "BookBuddy application started."); printWelcomeMessage(); getUserInput(books); + logger.log(Level.INFO, "BookBuddy application is shutting down."); } public static void printWelcomeMessage() { @@ -18,6 +23,7 @@ public static void printWelcomeMessage() { public static void getUserInput(BookList books) { Scanner input = new Scanner(System.in); + logger.log(Level.INFO, "Starting to get user input."); //noinspection InfiniteLoopStatement while (true) { @@ -26,10 +32,12 @@ public static void getUserInput(BookList books) { // If the input is empty, do not call parseCommand and just prompt for input again. continue; } - + assert !userInput.isEmpty() : "User input should not be empty at this point"; + logger.log(Level.FINE, "Processing user input: {0}", userInput); try { Parser.parseCommand(userInput, books); } catch (UnsupportedCommandException e) { + logger.log(Level.WARNING, "Unsupported command: {0}", userInput); System.out.println(e.getMessage()); } } diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index 4025a724e8..39dd907b05 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -56,6 +56,7 @@ public void deleteBook(int index) throws IndexOutOfBoundsException{ Book book = books.get(index - 1); books.remove(index - 1); System.out.println("Successfully removed " + book.getTitle() + " from the list."); + assert books.size() >= 0 : "Book list size should not be negative after deletion"; } /** diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 3bbc650d85..d8b952174e 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -5,6 +5,9 @@ import exceptions.InvalidCommandArgumentException; import exceptions.UnsupportedCommandException; +import java.util.logging.Level; +import static seedu.bookbuddy.BookBuddy.logger; + /** * Parses inputs from the user in order to execute the correct commands. */ @@ -26,11 +29,13 @@ public class Parser { public static void parseCommand( String input, BookList books) { String[] inputArray = input.split(" ", 2); String command = inputArray[0].toLowerCase(); + logger.log(Level.FINE, "Parsing command: {0}", command); int index; try { switch (command) { case ADD_COMMAND: if (inputArray.length < 2) { + logger.log(Level.WARNING, "The add Command requires a book title", inputArray); throw new InvalidCommandArgumentException("The add command requires a book title."); } books.addBook(inputArray[1]); @@ -55,6 +60,7 @@ public static void parseCommand( String input, BookList books) { System.exit(0); break; default: + logger.log(Level.WARNING, "Sorry but that is not a valid command. Please try again", command); throw new UnsupportedCommandException("Sorry but that is not a valid command. Please try again"); } } catch (NumberFormatException e) { From 3d09ba10920cafbcfff6188485e7d4df3a26ab80 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Wed, 20 Mar 2024 12:04:00 +0800 Subject: [PATCH 047/311] Added Ui class --- src/main/java/seedu/bookbuddy/BookBuddy.java | 11 ++--- src/main/java/seedu/bookbuddy/BookList.java | 17 ++++---- src/main/java/seedu/bookbuddy/Parser.java | 2 +- src/main/java/seedu/bookbuddy/Ui.java | 41 +++++++++++++++++++ .../java/seedu/bookbuddy/BookBuddyTest.java | 2 +- 5 files changed, 55 insertions(+), 18 deletions(-) create mode 100644 src/main/java/seedu/bookbuddy/Ui.java diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index db7ef8f3c4..b0d058d6b3 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -4,17 +4,15 @@ import java.util.Scanner; + public class BookBuddy { private static BookList books = new BookList(); public static void main(String[] args) { - printWelcomeMessage(); + Ui.printWelcome(); + //printWelcomeMessage(); getUserInput(books); } - public static void printWelcomeMessage() { - System.out.println("Hello! We are BookBuddy!"); - System.out.println("How can I help you today?"); - } public static void getUserInput(BookList books) { Scanner input = new Scanner(System.in); @@ -35,8 +33,5 @@ public static void getUserInput(BookList books) { } } - public static void printExitMessage() { - System.out.println("Thank you for using BookBuddy! Hope to see you again!"); - } } diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index 4025a724e8..e8ebd42914 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -7,7 +7,7 @@ * and marking book as read or unread. */ public class BookList { - private ArrayList books; + protected static ArrayList books; /** * Constructs a new BookList instance with an empty list. @@ -42,7 +42,7 @@ public Book getBook(int index) throws IndexOutOfBoundsException{ */ public void addBook(String title) { books.add(new Book(title)); - System.out.println("Successfully added " + title + " to the list."); + Ui.addBookMessage(title); } /** @@ -53,9 +53,10 @@ public void deleteBook(int index) throws IndexOutOfBoundsException{ if (index < 0 || index > books.size()) { throw new IndexOutOfBoundsException("Book index out of range."); } - Book book = books.get(index - 1); + //Book book = books.get(index - 1); + //books.remove(index - 1); + Ui.removeBookMessage(index - 1); books.remove(index - 1); - System.out.println("Successfully removed " + book.getTitle() + " from the list."); } /** @@ -83,11 +84,11 @@ public void markUndoneByIndex(int index) throws IndexOutOfBoundsException{ /** * Prints all books currently in the list. */ - public void printAllBooks() { - if (!books.isEmpty()) { + public static void printAllBooks() { + if (!BookList.books.isEmpty()) { System.out.println("All books:"); - for (int i = 0; i < books.size(); i++) { - Book currentBook = books.get(i); + for (int i = 0; i < BookList.books.size(); i++) { + Book currentBook = BookList.books.get(i); System.out.print((i + 1) + ". "); System.out.println(currentBook.toString()); } diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 3bbc650d85..86178cf394 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -51,7 +51,7 @@ public static void parseCommand( String input, BookList books) { books.markUndoneByIndex(index); break; case EXIT_COMMAND: - BookBuddy.printExitMessage(); + Ui.printExitMessage(); System.exit(0); break; default: diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java new file mode 100644 index 0000000000..9de685f566 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -0,0 +1,41 @@ +package seedu.bookbuddy; + +public class Ui { + public static void printWelcome() { + String logo = + " ____ ____ \n" + + "| \\ | \\ \n" + + "| |_) / | |_) / \n" + + "| |_) \\ | |_) \\ \n" + + "|____/ |____/ \n"; + printLine(); + System.out.println("Hello from\n" + logo); + System.out.println("Hello! We are BookBuddy!"); + System.out.println("How can I help you today?"); + printShortLine(); + } + public static void printLine() { + System.out.println("________________________________________"); + } + public static void printShortLine() { + System.out.println("_____________"); + } + public static void printExitMessage() { + System.out.println("Thank you for using BookBuddy! Hope to see you again keke :)"); + } + + /*public static String printInvalidCommand() { + String message = "The add command requires a book title."; + System.out.println(message); + return message; + } */ + public static void addBookMessage(String title) { + System.out.println("okii added [" + title + "] to the list."); + System.out.println("remember to read it soon...."); + } + public static void removeBookMessage(int index) { + System.out.println("alright.. i've removed " + BookList.books.get(index).getTitle() + " from the list."); + } + + +} diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index 659ce88bce..d22685f6ef 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -45,7 +45,7 @@ public void testPrintWelcomeMessage() { @Test public void testPrintExitMessage() { - BookBuddy.printExitMessage(); + Ui.printExitMessage(); String expectedOutput = "Thank you for using BookBuddy! Hope to see you again!\n"; assertEquals(expectedOutput.trim(), outContent.toString().trim()); } From 3a214f8076477d354d4ec40e7ded527d81e161ec Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Wed, 20 Mar 2024 12:09:13 +0800 Subject: [PATCH 048/311] added help message --- src/main/java/seedu/bookbuddy/Parser.java | 4 ++++ src/main/java/seedu/bookbuddy/Ui.java | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 86178cf394..7083eab7f9 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -16,6 +16,7 @@ public class Parser { public static final String MARK_COMMAND = "mark"; public static final String UNMARK_COMMAND = "unmark"; public static final String EXIT_COMMAND = "bye"; + public static final String HELP_COMMAND = "help"; /** * Scans the user input for valid commands and handles them accordingly. @@ -50,6 +51,9 @@ public static void parseCommand( String input, BookList books) { index = Integer.parseInt(inputArray[1]); books.markUndoneByIndex(index); break; + case HELP_COMMAND: + Ui.helpMessage(); + break; case EXIT_COMMAND: Ui.printExitMessage(); System.exit(0); diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 9de685f566..7647ee1b7d 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -36,6 +36,9 @@ public static void addBookMessage(String title) { public static void removeBookMessage(int index) { System.out.println("alright.. i've removed " + BookList.books.get(index).getTitle() + " from the list."); } - - + public static void helpMessage() { + System.out.println("add (Bookname) -> to add new books to the list"); + System.out.println("list -> to show whole list of added books"); + System.out.println("remove (index) -> to remove the book from the corresponding index"); + } } From f5b2f041b2c1207fb4a402915a42cb1f57ebc329 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Wed, 20 Mar 2024 12:25:43 +0800 Subject: [PATCH 049/311] changed according to test expected output --- src/main/java/seedu/bookbuddy/Ui.java | 6 ++++-- src/test/java/seedu/bookbuddy/BookBuddyTest.java | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 7647ee1b7d..b4ca18dd71 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -2,7 +2,7 @@ public class Ui { public static void printWelcome() { - String logo = + /*String logo = " ____ ____ \n" + "| \\ | \\ \n" + "| |_) / | |_) / \n" @@ -12,7 +12,9 @@ public static void printWelcome() { System.out.println("Hello from\n" + logo); System.out.println("Hello! We are BookBuddy!"); System.out.println("How can I help you today?"); - printShortLine(); + printShortLine(); */ + System.out.println("Hello! We are BookBuddy!"); + System.out.println("How can I help you today?"); } public static void printLine() { System.out.println("________________________________________"); diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index d22685f6ef..4bde8edbad 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -29,10 +29,11 @@ public void sampleTest() { @Test public void testPrintWelcomeMessage() { - BookBuddy.printWelcomeMessage(); + Ui.printWelcome(); String actualOutput = outContent.toString(); // Normalize line endings to \n in both expected and actual output + String normalizedExpectedOutput = "Hello! We are BookBuddy!\nHow can I help you today?\n".replace("\r\n", "\n"); String normalizedActualOutput = actualOutput.replace("\r\n", "\n"); @@ -46,7 +47,7 @@ public void testPrintWelcomeMessage() { @Test public void testPrintExitMessage() { Ui.printExitMessage(); - String expectedOutput = "Thank you for using BookBuddy! Hope to see you again!\n"; + String expectedOutput = "Thank you for using BookBuddy! Hope to see you again keke :)\n"; assertEquals(expectedOutput.trim(), outContent.toString().trim()); } From 2a95d1fbb667dc3e5ccf59219857000639460b39 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Wed, 20 Mar 2024 12:36:40 +0800 Subject: [PATCH 050/311] modifided testing --- src/main/java/seedu/bookbuddy/Ui.java | 8 ++++---- src/test/java/seedu/bookbuddy/BookBuddyTest.java | 10 ++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index b4ca18dd71..d04e347dac 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -2,7 +2,7 @@ public class Ui { public static void printWelcome() { - /*String logo = + String logo = " ____ ____ \n" + "| \\ | \\ \n" + "| |_) / | |_) / \n" @@ -12,9 +12,9 @@ public static void printWelcome() { System.out.println("Hello from\n" + logo); System.out.println("Hello! We are BookBuddy!"); System.out.println("How can I help you today?"); - printShortLine(); */ - System.out.println("Hello! We are BookBuddy!"); - System.out.println("How can I help you today?"); + printShortLine(); + //System.out.println("Hello! We are BookBuddy!"); + //System.out.println("How can I help you today?"); } public static void printLine() { System.out.println("________________________________________"); diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index 4bde8edbad..9077b84d95 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -33,8 +33,14 @@ public void testPrintWelcomeMessage() { String actualOutput = outContent.toString(); // Normalize line endings to \n in both expected and actual output - - String normalizedExpectedOutput = "Hello! We are BookBuddy!\nHow can I help you today?\n".replace("\r\n", "\n"); + String normalizedExpectedOutput = "________________________________________\n" + + "Hello from\n" + " ____ ____ \n" + + "| \\ | \\ \n" + + "| |_) / | |_) / \n" + + "| |_) \\ | |_) \\ \n" + + "|____/ |____/ \n\n" + "Hello! We are BookBuddy!\n" + "How can I help you today?\n" + + "_____________\n"; + //String normalizedExpectedOutput = "Hello! We are BookBuddy!\nHow can I help you today?\n".replace("\r\n", "\n"); String normalizedActualOutput = actualOutput.replace("\r\n", "\n"); // Assert that the normalized outputs are equal From 01e22f95ebc4e3a48657fac8cf44a4cc1ab9f3a8 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Wed, 20 Mar 2024 12:55:18 +0800 Subject: [PATCH 051/311] remodify testing to pass checkstyle --- src/main/java/seedu/bookbuddy/Ui.java | 6 +++--- src/test/java/seedu/bookbuddy/BookBuddyTest.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index d04e347dac..414f858c15 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -9,15 +9,15 @@ public static void printWelcome() { + "| |_) \\ | |_) \\ \n" + "|____/ |____/ \n"; printLine(); - System.out.println("Hello from\n" + logo); - System.out.println("Hello! We are BookBuddy!"); + System.out.println("Hello from\n" + logo +"BookBuddy!"); + //System.out.println("Hello! We are BookBuddy!"); System.out.println("How can I help you today?"); printShortLine(); //System.out.println("Hello! We are BookBuddy!"); //System.out.println("How can I help you today?"); } public static void printLine() { - System.out.println("________________________________________"); + System.out.println("___________________________________"); } public static void printShortLine() { System.out.println("_____________"); diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index 9077b84d95..0a6195bc14 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -33,14 +33,14 @@ public void testPrintWelcomeMessage() { String actualOutput = outContent.toString(); // Normalize line endings to \n in both expected and actual output - String normalizedExpectedOutput = "________________________________________\n" + String normalizedExpectedOutput = "___________________________________\n" + "Hello from\n" + " ____ ____ \n" + "| \\ | \\ \n" + "| |_) / | |_) / \n" + "| |_) \\ | |_) \\ \n" - + "|____/ |____/ \n\n" + "Hello! We are BookBuddy!\n" + "How can I help you today?\n" + + "|____/ |____/ \n" + "BookBuddy!\n" + "How can I help you today?\n" + "_____________\n"; - //String normalizedExpectedOutput = "Hello! We are BookBuddy!\nHow can I help you today?\n".replace("\r\n", "\n"); + String normalizedActualOutput = actualOutput.replace("\r\n", "\n"); // Assert that the normalized outputs are equal From 4210b5f1558c7314c2a991398e0013105d590ed4 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Wed, 20 Mar 2024 13:01:50 +0800 Subject: [PATCH 052/311] no message --- src/main/java/seedu/bookbuddy/BookBuddy.java | 3 +++ src/test/java/seedu/bookbuddy/BookBuddyTest.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index b0d058d6b3..a78c662bd3 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -19,6 +19,9 @@ public static void getUserInput(BookList books) { //noinspection InfiniteLoopStatement while (true) { + if (!input.hasNextLine()) { // Check if there is another line of input + break; // Exit the loop if there is no input + } String userInput = input.nextLine().trim(); if (userInput.isEmpty()) { // If the input is empty, do not call parseCommand and just prompt for input again. diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index 0a6195bc14..17c6aa008f 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -40,7 +40,7 @@ public void testPrintWelcomeMessage() { + "| |_) \\ | |_) \\ \n" + "|____/ |____/ \n" + "BookBuddy!\n" + "How can I help you today?\n" + "_____________\n"; - + String normalizedActualOutput = actualOutput.replace("\r\n", "\n"); // Assert that the normalized outputs are equal From 046e1b5f519a8ef3f09953f68b7c5cfc3b476195 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Wed, 20 Mar 2024 13:07:30 +0800 Subject: [PATCH 053/311] no message --- src/main/java/seedu/bookbuddy/BookBuddy.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index a78c662bd3..76d5d6b6a3 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -19,9 +19,9 @@ public static void getUserInput(BookList books) { //noinspection InfiniteLoopStatement while (true) { - if (!input.hasNextLine()) { // Check if there is another line of input + /*if (!input.hasNextLine()) { // Check if there is another line of input break; // Exit the loop if there is no input - } + }*/ String userInput = input.nextLine().trim(); if (userInput.isEmpty()) { // If the input is empty, do not call parseCommand and just prompt for input again. From 21d8fb56b5d5f3bed42d61cc17308d4ddac5332e Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Wed, 20 Mar 2024 13:11:39 +0800 Subject: [PATCH 054/311] change expected test --- src/test/java/seedu/bookbuddy/BookBuddyTest.java | 4 +++- text-ui-test/EXPECTED.TXT | 11 ++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index 17c6aa008f..bde9a8c1b7 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -38,7 +38,9 @@ public void testPrintWelcomeMessage() { + "| \\ | \\ \n" + "| |_) / | |_) / \n" + "| |_) \\ | |_) \\ \n" - + "|____/ |____/ \n" + "BookBuddy!\n" + "How can I help you today?\n" + + "|____/ |____/ \n" + + "BookBuddy!\n" + + "How can I help you today?\n" + "_____________\n"; String normalizedActualOutput = actualOutput.replace("\r\n", "\n"); diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 83e2d80f5a..c6677711de 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,3 +1,12 @@ -Hello! We are BookBuddy! +___________________________________ +Hello from + ____ ____ +| \ | \ +| |_) / | |_) / +| |_) \ | |_) \ +|____/ |____/ +BookBuddy! +How can I help you today? +_____________ How can I help you today? Sorry but that is not a valid command. Please try again From 2ca397a53a1cafba956d4b0c9cb28967b741049b Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Wed, 20 Mar 2024 14:02:19 +0800 Subject: [PATCH 055/311] no message --- text-ui-test/EXPECTED.TXT | 1 - 1 file changed, 1 deletion(-) diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index c6677711de..a473635aac 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -8,5 +8,4 @@ Hello from BookBuddy! How can I help you today? _____________ -How can I help you today? Sorry but that is not a valid command. Please try again From 65178189de8e6d0aec127a80c16e9308cb2865ae Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 14:46:28 +0800 Subject: [PATCH 056/311] checkstyle fixes --- src/main/java/seedu/bookbuddy/BookBuddy.java | 13 +++++++------ src/main/java/seedu/bookbuddy/Parser.java | 8 ++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index 3280044e11..8e8bc6ec78 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -4,16 +4,17 @@ import java.util.Scanner; import java.util.logging.Logger; import java.util.logging.Level; +import static java.util.logging.Logger.getLogger; public class BookBuddy { private static BookList books = new BookList(); - static final Logger logger = Logger.getLogger(BookBuddy.class.getName()); + static final Logger LOGGER = getLogger(BookBuddy.class.getName()); public static void main(String[] args) { - logger.log(Level.INFO, "BookBuddy application started."); + LOGGER.log(Level.INFO, "BookBuddy application started."); printWelcomeMessage(); getUserInput(books); - logger.log(Level.INFO, "BookBuddy application is shutting down."); + LOGGER.log(Level.INFO, "BookBuddy application is shutting down."); } public static void printWelcomeMessage() { @@ -23,7 +24,7 @@ public static void printWelcomeMessage() { public static void getUserInput(BookList books) { Scanner input = new Scanner(System.in); - logger.log(Level.INFO, "Starting to get user input."); + LOGGER.log(Level.INFO, "Starting to get user input."); //noinspection InfiniteLoopStatement while (true) { @@ -33,11 +34,11 @@ public static void getUserInput(BookList books) { continue; } assert !userInput.isEmpty() : "User input should not be empty at this point"; - logger.log(Level.FINE, "Processing user input: {0}", userInput); + LOGGER.log(Level.FINE, "Processing user input: {0}", userInput); try { Parser.parseCommand(userInput, books); } catch (UnsupportedCommandException e) { - logger.log(Level.WARNING, "Unsupported command: {0}", userInput); + LOGGER.log(Level.WARNING, "Unsupported command: {0}", userInput); System.out.println(e.getMessage()); } } diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index d8b952174e..ab0e76f0a9 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -6,7 +6,7 @@ import exceptions.UnsupportedCommandException; import java.util.logging.Level; -import static seedu.bookbuddy.BookBuddy.logger; +import static seedu.bookbuddy.BookBuddy.LOGGER; /** * Parses inputs from the user in order to execute the correct commands. @@ -29,13 +29,13 @@ public class Parser { public static void parseCommand( String input, BookList books) { String[] inputArray = input.split(" ", 2); String command = inputArray[0].toLowerCase(); - logger.log(Level.FINE, "Parsing command: {0}", command); + LOGGER.log(Level.FINE, "Parsing command: {0}", command); int index; try { switch (command) { case ADD_COMMAND: if (inputArray.length < 2) { - logger.log(Level.WARNING, "The add Command requires a book title", inputArray); + LOGGER.log(Level.WARNING, "The add Command requires a book title", inputArray); throw new InvalidCommandArgumentException("The add command requires a book title."); } books.addBook(inputArray[1]); @@ -60,7 +60,7 @@ public static void parseCommand( String input, BookList books) { System.exit(0); break; default: - logger.log(Level.WARNING, "Sorry but that is not a valid command. Please try again", command); + LOGGER.log(Level.WARNING, "Sorry but that is not a valid command. Please try again", command); throw new UnsupportedCommandException("Sorry but that is not a valid command. Please try again"); } } catch (NumberFormatException e) { From 9dbe2dcfdc0b0b9a4077b80e3e6a8f6a7dd78dd7 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Wed, 20 Mar 2024 15:21:11 +0800 Subject: [PATCH 057/311] fix IO test --- src/main/java/seedu/bookbuddy/Ui.java | 5 ++--- src/test/java/seedu/bookbuddy/BookBuddyTest.java | 6 +----- text-ui-test/EXPECTED.TXT | 5 ----- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 414f858c15..ac324d0439 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -9,12 +9,11 @@ public static void printWelcome() { + "| |_) \\ | |_) \\ \n" + "|____/ |____/ \n"; printLine(); - System.out.println("Hello from\n" + logo +"BookBuddy!"); + System.out.println("Hello from"); + System.out.println("BookBuddy!"); //System.out.println("Hello! We are BookBuddy!"); System.out.println("How can I help you today?"); printShortLine(); - //System.out.println("Hello! We are BookBuddy!"); - //System.out.println("How can I help you today?"); } public static void printLine() { System.out.println("___________________________________"); diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index bde9a8c1b7..c6983e19d7 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -34,11 +34,7 @@ public void testPrintWelcomeMessage() { // Normalize line endings to \n in both expected and actual output String normalizedExpectedOutput = "___________________________________\n" - + "Hello from\n" + " ____ ____ \n" - + "| \\ | \\ \n" - + "| |_) / | |_) / \n" - + "| |_) \\ | |_) \\ \n" - + "|____/ |____/ \n" + + "Hello from\n" + "BookBuddy!\n" + "How can I help you today?\n" + "_____________\n"; diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index a473635aac..eefda94c86 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,10 +1,5 @@ ___________________________________ Hello from - ____ ____ -| \ | \ -| |_) / | |_) / -| |_) \ | |_) \ -|____/ |____/ BookBuddy! How can I help you today? _____________ From ec465edd56202b976c12854bc4e8b236a78dab66 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Wed, 20 Mar 2024 15:41:56 +0800 Subject: [PATCH 058/311] modify help command --- src/main/java/seedu/bookbuddy/Ui.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index ac324d0439..085aa6d638 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -38,8 +38,12 @@ public static void removeBookMessage(int index) { System.out.println("alright.. i've removed " + BookList.books.get(index).getTitle() + " from the list."); } public static void helpMessage() { + System.out.println("Here's a list of commands to get you started!!"); System.out.println("add (Bookname) -> to add new books to the list"); System.out.println("list -> to show whole list of added books"); System.out.println("remove (index) -> to remove the book from the corresponding index"); + System.out.println("mark (index) -> to mark book as read [R]"); + System.out.println("unmark (index) -> to unmark book as unread [U]"); + System.out.println("bye -> to exit BookBuddy software"); } } From 8f7a0d54faf3c1cc9321ad05bbd908249d0f610a Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Wed, 20 Mar 2024 16:14:02 +0800 Subject: [PATCH 059/311] fix catch Invalid Command Exception error --- src/main/java/seedu/bookbuddy/BookBuddy.java | 3 +++ src/main/java/seedu/bookbuddy/Parser.java | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index 76d5d6b6a3..39f21654bf 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -1,5 +1,6 @@ package seedu.bookbuddy; +import exceptions.InvalidCommandArgumentException; import exceptions.UnsupportedCommandException; import java.util.Scanner; @@ -32,6 +33,8 @@ public static void getUserInput(BookList books) { Parser.parseCommand(userInput, books); } catch (UnsupportedCommandException e) { System.out.println(e.getMessage()); + } catch (InvalidCommandArgumentException e) { + System.out.println(e.getMessage()); } } } diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 7083eab7f9..d75e3e8aa2 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -59,7 +59,7 @@ public static void parseCommand( String input, BookList books) { System.exit(0); break; default: - throw new UnsupportedCommandException("Sorry but that is not a valid command. Please try again"); + throw new UnsupportedCommandException("Sorry but that is not a valid command use (help) to find out valid commands!"); } } catch (NumberFormatException e) { throw new InvalidBookIndexException("Book index must be an integer."); From 009e65f6a0157408ffd0fa5850064f7c17399af4 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Wed, 20 Mar 2024 16:24:32 +0800 Subject: [PATCH 060/311] fix checkstyle --- src/main/java/seedu/bookbuddy/Parser.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index d75e3e8aa2..10b678a66b 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -59,7 +59,8 @@ public static void parseCommand( String input, BookList books) { System.exit(0); break; default: - throw new UnsupportedCommandException("Sorry but that is not a valid command use (help) to find out valid commands!"); + throw new UnsupportedCommandException("Sorry but that is not a valid command " + + "use (help) to find out valid commands!"); } } catch (NumberFormatException e) { throw new InvalidBookIndexException("Book index must be an integer."); From 7d7b4e3a7b9645706906230177fcc5d3d81d8ad6 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Wed, 20 Mar 2024 17:16:15 +0800 Subject: [PATCH 061/311] Merge branch 'help_feature' of https://github.com/liuzehui03/tp into help_feature # Conflicts: # src/main/java/seedu/bookbuddy/Parser.java --- src/main/java/seedu/bookbuddy/Parser.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 28a428d7b3..84a72b538e 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -66,8 +66,7 @@ public static void parseCommand( String input, BookList books) { default: LOGGER.log(Level.WARNING, "Sorry but that is not a valid command. Please try again", command); - throw new UnsupportedCommandException("Sorry but this is not valid command," + - "use (help) to find out valid commands"); + throw new UnsupportedCommandException("Sorry but that is not a valid command. Please try again"); } } catch (NumberFormatException e) { From b24bfb177b29e5cee05f13360945607b55a8e529 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 18:08:35 +0800 Subject: [PATCH 062/311] Coding standard fixes --- src/main/java/exceptions/BookNotFoundException.java | 2 +- src/main/java/exceptions/InvalidBookIndexException.java | 2 +- src/main/java/seedu/bookbuddy/BookBuddy.java | 3 --- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/exceptions/BookNotFoundException.java b/src/main/java/exceptions/BookNotFoundException.java index 4dc275fa2f..9211efb3e8 100644 --- a/src/main/java/exceptions/BookNotFoundException.java +++ b/src/main/java/exceptions/BookNotFoundException.java @@ -1,6 +1,6 @@ package exceptions; -public class BookNotFoundException extends RuntimeException { +public class BookNotFoundException extends IndexOutOfBoundsException { public BookNotFoundException(String message) { super(message); } diff --git a/src/main/java/exceptions/InvalidBookIndexException.java b/src/main/java/exceptions/InvalidBookIndexException.java index 4d98531bd0..9443591bbe 100644 --- a/src/main/java/exceptions/InvalidBookIndexException.java +++ b/src/main/java/exceptions/InvalidBookIndexException.java @@ -1,6 +1,6 @@ package exceptions; -public class InvalidBookIndexException extends RuntimeException { +public class InvalidBookIndexException extends NumberFormatException { public InvalidBookIndexException(String message) { super(message); } diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index 4208124464..2af1c251d4 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -26,9 +26,6 @@ public static void getUserInput(BookList books) { //noinspection InfiniteLoopStatement while (true) { - /*if (!input.hasNextLine()) { // Check if there is another line of input - break; // Exit the loop if there is no input - }*/ String userInput = input.nextLine().trim(); if (userInput.isEmpty()) { // If the input is empty, do not call parseCommand and just prompt for input again. From b623f53d71b61fc543274b13a64f9162161418c9 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 22:43:49 +0800 Subject: [PATCH 063/311] Add external logging to a logging file and remove logging updates in console --- src/main/java/seedu/bookbuddy/BookBuddy.java | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index 2af1c251d4..a6a39ee285 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -2,15 +2,41 @@ import exceptions.InvalidCommandArgumentException; import exceptions.UnsupportedCommandException; + +import java.io.IOException; import java.util.Scanner; +import java.util.logging.FileHandler; import java.util.logging.Logger; import java.util.logging.Level; +import java.util.logging.SimpleFormatter; +import java.util.logging.Handler; + + import static java.util.logging.Logger.getLogger; public class BookBuddy { static final Logger LOGGER = getLogger(BookBuddy.class.getName()); + + static { + try { + LOGGER.setUseParentHandlers(false); + // Remove all the default handlers + Handler[] handlers = LOGGER.getHandlers(); + for (Handler handler : handlers) { + LOGGER.removeHandler(handler); + } + // Add our file handler + FileHandler fileHandler = new FileHandler("BookBuddy.log", true); // Append to the existing file + fileHandler.setFormatter(new SimpleFormatter()); + LOGGER.addHandler(fileHandler); + LOGGER.setLevel(Level.INFO); + } catch (SecurityException | IOException e) { + LOGGER.log(Level.SEVERE, "FileHandler can not be initialized", e); + } + } + private static BookList books = new BookList(); public static void main(String[] args) { LOGGER.log(Level.INFO, "BookBuddy application started."); From ed7603e4069ac0f38815d9e458be2535cf55705e Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 20 Mar 2024 23:22:42 +0800 Subject: [PATCH 064/311] Assertions in BookList class --- src/main/java/seedu/bookbuddy/BookList.java | 4 ++++ src/main/java/seedu/bookbuddy/Parser.java | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index 465b7a9899..ee603fcd5d 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -33,6 +33,7 @@ public Book getBook(int index) throws IndexOutOfBoundsException{ if (index < 0 || index > books.size()) { throw new IndexOutOfBoundsException("Book index out of range."); } + assert books.get(index) != null : "Retrieved book should not be null"; return books.get(index); } @@ -43,6 +44,7 @@ public Book getBook(int index) throws IndexOutOfBoundsException{ public void addBook(String title) { books.add(new Book(title)); Ui.addBookMessage(title); + assert !books.isEmpty() : "Book list should not be empty after adding a book"; } /** @@ -67,6 +69,7 @@ public void markDoneByIndex(int index) throws IndexOutOfBoundsException{ throw new IndexOutOfBoundsException("Book index out of range."); } books.get(index - 1).markBookAsRead(); + assert books.get(index - 1).isRead() : "Book should be marked as read"; } /** @@ -78,6 +81,7 @@ public void markUndoneByIndex(int index) throws IndexOutOfBoundsException{ throw new IndexOutOfBoundsException("Book index out of range."); } books.get(index - 1).markBookAsUnread(); + assert !books.get(index - 1).isRead() : "Book should not be marked as read"; } /** diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 84a72b538e..b19736da8f 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -64,10 +64,8 @@ public static void parseCommand( String input, BookList books) { System.exit(0); break; default: - LOGGER.log(Level.WARNING, "Sorry but that is not a valid command. Please try again", command); throw new UnsupportedCommandException("Sorry but that is not a valid command. Please try again"); - } } catch (NumberFormatException e) { throw new InvalidBookIndexException("Book index must be an integer."); From 889a5a2c82ec9fd6a9e40fbee864bcd07142e0f2 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 21 Mar 2024 13:53:23 +0800 Subject: [PATCH 065/311] Improved user experience - use help command when command is invalid --- src/main/java/seedu/bookbuddy/Parser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index b19736da8f..f4ddb60a3d 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -65,7 +65,7 @@ public static void parseCommand( String input, BookList books) { break; default: LOGGER.log(Level.WARNING, "Sorry but that is not a valid command. Please try again", command); - throw new UnsupportedCommandException("Sorry but that is not a valid command. Please try again"); + throw new UnsupportedCommandException("Sorry but that is not a valid command. Please try again or type: help"); } } catch (NumberFormatException e) { throw new InvalidBookIndexException("Book index must be an integer."); From 25ad3737b865e7ed5b425fc3e1a32473fb188f3e Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 21 Mar 2024 13:57:54 +0800 Subject: [PATCH 066/311] checkstyle fixes - longer than 120 lines --- src/main/java/seedu/bookbuddy/Parser.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index f4ddb60a3d..5677d35949 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -65,7 +65,8 @@ public static void parseCommand( String input, BookList books) { break; default: LOGGER.log(Level.WARNING, "Sorry but that is not a valid command. Please try again", command); - throw new UnsupportedCommandException("Sorry but that is not a valid command. Please try again or type: help"); + throw new UnsupportedCommandException("Sorry but that is not a valid command. " + + "Please try again or type: help"); } } catch (NumberFormatException e) { throw new InvalidBookIndexException("Book index must be an integer."); From e55e2db90019e99e2a696779ffe926d9419e1d43 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 21 Mar 2024 14:00:39 +0800 Subject: [PATCH 067/311] Expected.txt update --- text-ui-test/EXPECTED.TXT | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index eefda94c86..0a3e28bb3f 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -3,4 +3,4 @@ Hello from BookBuddy! How can I help you today? _____________ -Sorry but that is not a valid command. Please try again +Sorry but that is not a valid command. Please try again or type: help From e89c61ad1f55237d9b90b15f3b4ebfc57d966920 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 21 Mar 2024 14:26:20 +0800 Subject: [PATCH 068/311] BookBuddy Jar Creation Update on build.gradle --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index ce9625972b..2a1b54a276 100644 --- a/build.gradle +++ b/build.gradle @@ -35,6 +35,7 @@ application { shadowJar { archiveBaseName.set("bookbuddy") archiveClassifier.set("") + archiveFileName = 'BookBuddy.jar' } checkstyle { From 92e6b86bbad8174865c4f8b6115ffa0801517328 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Thu, 21 Mar 2024 14:55:18 +0800 Subject: [PATCH 069/311] Add exception handling for marking and unmarking books --- .../java/exceptions/BookBuddyException.java | 11 +++++++ .../exceptions/InvalidBookIndexException.java | 1 - src/main/java/seedu/bookbuddy/BookBuddy.java | 3 +- src/main/java/seedu/bookbuddy/BookList.java | 29 ++++++++++--------- src/main/java/seedu/bookbuddy/Parser.java | 3 +- src/main/java/seedu/bookbuddy/Ui.java | 2 +- src/test/java/seedu/bookbuddy/ParserTest.java | 9 ------ 7 files changed, 29 insertions(+), 29 deletions(-) create mode 100644 src/main/java/exceptions/BookBuddyException.java diff --git a/src/main/java/exceptions/BookBuddyException.java b/src/main/java/exceptions/BookBuddyException.java new file mode 100644 index 0000000000..8c09ab8ed4 --- /dev/null +++ b/src/main/java/exceptions/BookBuddyException.java @@ -0,0 +1,11 @@ +package exceptions; + +/** + * Representation of an exception unique to BookBuddy + */ + +public class BookBuddyException extends Exception { + public BookBuddyException(String message) { + super(message); + } +} diff --git a/src/main/java/exceptions/InvalidBookIndexException.java b/src/main/java/exceptions/InvalidBookIndexException.java index 9443591bbe..5df213195a 100644 --- a/src/main/java/exceptions/InvalidBookIndexException.java +++ b/src/main/java/exceptions/InvalidBookIndexException.java @@ -5,4 +5,3 @@ public InvalidBookIndexException(String message) { super(message); } } - diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index a6a39ee285..0feca35524 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -41,6 +41,7 @@ public class BookBuddy { public static void main(String[] args) { LOGGER.log(Level.INFO, "BookBuddy application started."); Ui.printWelcome(); + assert books != null : "BookList not created"; getUserInput(books); LOGGER.log(Level.INFO, "BookBuddy application is shutting down."); } @@ -69,6 +70,4 @@ public static void getUserInput(BookList books) { } } } - - } diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index ee603fcd5d..992baf44a0 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -51,13 +51,14 @@ public void addBook(String title) { * Deletes a book from the list by its index. * @param index The index of the book to delete. */ - public void deleteBook(int index) throws IndexOutOfBoundsException{ - if (index < 0 || index > books.size()) { - throw new IndexOutOfBoundsException("Book index out of range."); + public void deleteBook(int index) throws IndexOutOfBoundsException { + try { + Ui.removeBookMessage(index - 1); + books.remove(index - 1); + assert books.size() >= 0 : "Book list size should not be negative after deletion"; + } catch (IndexOutOfBoundsException e) { + System.out.println("Invalid book index. Please enter a valid index"); } - Ui.removeBookMessage(index - 1); - books.remove(index - 1); - assert books.size() >= 0 : "Book list size should not be negative after deletion"; } /** @@ -65,11 +66,11 @@ public void deleteBook(int index) throws IndexOutOfBoundsException{ * @param index The index of the book to mark as read. */ public void markDoneByIndex(int index) throws IndexOutOfBoundsException{ - if (index < 0 || index > books.size()) { - throw new IndexOutOfBoundsException("Book index out of range."); + try { + books.get(index - 1).markBookAsRead(); + } catch (IndexOutOfBoundsException e) { + System.out.println("Invalid book index. Please enter a valid index"); } - books.get(index - 1).markBookAsRead(); - assert books.get(index - 1).isRead() : "Book should be marked as read"; } /** @@ -77,11 +78,11 @@ public void markDoneByIndex(int index) throws IndexOutOfBoundsException{ * @param index The index of the book to mark as unread. */ public void markUndoneByIndex(int index) throws IndexOutOfBoundsException{ - if (index < 0 || index > books.size()) { - throw new IndexOutOfBoundsException("Book index out of range."); + try { + books.get(index - 1).markBookAsUnread(); + } catch (IndexOutOfBoundsException e) { + System.out.println("Invalid book index. Please enter a valid index"); } - books.get(index - 1).markBookAsUnread(); - assert !books.get(index - 1).isRead() : "Book should not be marked as read"; } /** diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index b19736da8f..9f228d4849 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -27,7 +27,7 @@ public class Parser { * @param books ArrayList of books */ - public static void parseCommand( String input, BookList books) { + public static void parseCommand(String input, BookList books) { String[] inputArray = input.split(" ", 2); String command = inputArray[0].toLowerCase(); LOGGER.log(Level.FINE, "Parsing command: {0}", command); @@ -73,5 +73,4 @@ public static void parseCommand( String input, BookList books) { throw new BookNotFoundException("Book not found at the provided index."); } } - } diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 085aa6d638..51a60efba5 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -34,7 +34,7 @@ public static void addBookMessage(String title) { System.out.println("okii added [" + title + "] to the list."); System.out.println("remember to read it soon...."); } - public static void removeBookMessage(int index) { + public static void removeBookMessage(int index) throws IndexOutOfBoundsException { System.out.println("alright.. i've removed " + BookList.books.get(index).getTitle() + " from the list."); } public static void helpMessage() { diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index beeae9d6b5..2e238aa198 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -1,6 +1,5 @@ package seedu.bookbuddy; -import exceptions.BookNotFoundException; import exceptions.InvalidBookIndexException; import exceptions.InvalidCommandArgumentException; import exceptions.UnsupportedCommandException; @@ -76,14 +75,6 @@ void parseInvalidRemoveCommandThrowsException() { () -> Parser.parseCommand(input, books), "Book index must be an integer."); } - @Test - void parseRemoveCommandForNonExistentBookThrowsException() { - BookList books = new BookList(); - String input = "remove 1"; // No books in the list, so index 1 is invalid - assertThrows(BookNotFoundException.class, - () -> Parser.parseCommand(input, books), "Book not found at the provided index."); - } - @Test void parseUnsupportedCommandThrowsException() { BookList books = new BookList(); From 00f9dcd3a5cbe13e713402adb378a7c30fc2cb57 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Thu, 21 Mar 2024 15:02:13 +0800 Subject: [PATCH 070/311] Remove unused exception --- src/main/java/seedu/bookbuddy/Ui.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 51a60efba5..085aa6d638 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -34,7 +34,7 @@ public static void addBookMessage(String title) { System.out.println("okii added [" + title + "] to the list."); System.out.println("remember to read it soon...."); } - public static void removeBookMessage(int index) throws IndexOutOfBoundsException { + public static void removeBookMessage(int index) { System.out.println("alright.. i've removed " + BookList.books.get(index).getTitle() + " from the list."); } public static void helpMessage() { From 678f931077e2a15df30dc8b8589dc72ab5c49bcf Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Thu, 21 Mar 2024 15:25:00 +0800 Subject: [PATCH 071/311] no message --- BookBuddy.log | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 BookBuddy.log diff --git a/BookBuddy.log b/BookBuddy.log new file mode 100644 index 0000000000..09e4cc358a --- /dev/null +++ b/BookBuddy.log @@ -0,0 +1,12 @@ +Mar 21, 2024 3:21:59 PM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 21, 2024 3:21:59 PM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 21, 2024 3:22:04 PM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Mar 21, 2024 3:22:04 PM seedu.bookbuddy.BookBuddy getUserInput +WARNING: Unsupported command: drhf +Mar 21, 2024 3:23:35 PM seedu.bookbuddy.Parser parseCommand +WARNING: The add Command requires a book title +Mar 21, 2024 3:23:35 PM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again From 7fee9f93b6d6765edf3ff6761dedeae1f7ba5c04 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Thu, 21 Mar 2024 15:26:03 +0800 Subject: [PATCH 072/311] added assertions to markDone and markundone --- src/main/java/seedu/bookbuddy/BookList.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index 992baf44a0..8aac90a5ae 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -67,6 +67,7 @@ public void deleteBook(int index) throws IndexOutOfBoundsException { */ public void markDoneByIndex(int index) throws IndexOutOfBoundsException{ try { + assert index > 0 && index <= books.size() : "Index out of valid range"; books.get(index - 1).markBookAsRead(); } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index"); @@ -79,6 +80,7 @@ public void markDoneByIndex(int index) throws IndexOutOfBoundsException{ */ public void markUndoneByIndex(int index) throws IndexOutOfBoundsException{ try { + assert index > 0 && index <= books.size() : "Index out of valid range"; books.get(index - 1).markBookAsUnread(); } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index"); From 8edc9696f3918c2af3d11fc83d6c59c8f639811f Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 21 Mar 2024 16:00:00 +0800 Subject: [PATCH 073/311] Bug fixes for unsupported commands - index for mark, unmark and remove are not numbers but are characters --- src/main/java/seedu/bookbuddy/BookList.java | 2 ++ src/main/java/seedu/bookbuddy/Parser.java | 30 ++++++++++++++++----- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index 992baf44a0..2048711e1a 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -1,5 +1,7 @@ package seedu.bookbuddy; +import exceptions.InvalidBookIndexException; + import java.util.ArrayList; /** diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index c41ac20e94..40e59e3a43 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -42,19 +42,37 @@ public static void parseCommand(String input, BookList books) { books.addBook(inputArray[1]); break; case REMOVE_COMMAND: - index = Integer.parseInt(inputArray[1]); - books.deleteBook(index); + try { + index = Integer.parseInt(inputArray[1]); + books.deleteBook(index); + } catch (NumberFormatException e) { + System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. Please enter a valid numeric index."); + } catch (IndexOutOfBoundsException e) { + System.out.println("Invalid book index. Please enter a valid index."); + } break; case LIST_COMMAND: books.printAllBooks(); break; case MARK_COMMAND: - index = Integer.parseInt(inputArray[1]); - books.markDoneByIndex(index); + try { + index = Integer.parseInt(inputArray[1]); + books.markDoneByIndex(index); + } catch (NumberFormatException e) { + System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. Please enter a valid numeric index."); + } catch (IndexOutOfBoundsException e) { + System.out.println("Invalid book index. Please enter a valid index."); + } break; case UNMARK_COMMAND: - index = Integer.parseInt(inputArray[1]); - books.markUndoneByIndex(index); + try { + index = Integer.parseInt(inputArray[1]); + books.markUndoneByIndex(index); + } catch (NumberFormatException e) { + System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. Please enter a valid numeric index."); + } catch (IndexOutOfBoundsException e) { + System.out.println("Invalid book index. Please enter a valid index."); + } break; case HELP_COMMAND: Ui.helpMessage(); From 5f09440856a0db865cc8d7cecf8b854d047c4404 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 21 Mar 2024 16:11:03 +0800 Subject: [PATCH 074/311] Checkstyle fixes --- src/main/java/seedu/bookbuddy/Parser.java | 9 ++++++--- src/test/java/seedu/bookbuddy/ParserTest.java | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 40e59e3a43..00437e68b3 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -46,7 +46,8 @@ public static void parseCommand(String input, BookList books) { index = Integer.parseInt(inputArray[1]); books.deleteBook(index); } catch (NumberFormatException e) { - System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. Please enter a valid numeric index."); + System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + + "Please enter a valid numeric index."); } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index."); } @@ -59,7 +60,8 @@ public static void parseCommand(String input, BookList books) { index = Integer.parseInt(inputArray[1]); books.markDoneByIndex(index); } catch (NumberFormatException e) { - System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. Please enter a valid numeric index."); + System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + + "Please enter a valid numeric index."); } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index."); } @@ -69,7 +71,8 @@ public static void parseCommand(String input, BookList books) { index = Integer.parseInt(inputArray[1]); books.markUndoneByIndex(index); } catch (NumberFormatException e) { - System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. Please enter a valid numeric index."); + System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + + "Please enter a valid numeric index."); } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index."); } diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index 2e238aa198..3cd2851095 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -71,7 +71,7 @@ void parseInvalidAddCommandThrowsException() { void parseInvalidRemoveCommandThrowsException() { BookList books = new BookList(); String input = "remove notAnIndex"; // Invalid index provided - assertThrows(InvalidBookIndexException.class, + assertThrows(NumberFormatException.class, () -> Parser.parseCommand(input, books), "Book index must be an integer."); } From 3aa4aad42a71c7f3f55e4aa3fbdd33da43724780 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 21 Mar 2024 16:32:36 +0800 Subject: [PATCH 075/311] junit update --- src/test/java/seedu/bookbuddy/ParserTest.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index 3cd2851095..30b2e4f0a7 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -4,6 +4,10 @@ import exceptions.InvalidCommandArgumentException; import exceptions.UnsupportedCommandException; import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -68,11 +72,21 @@ void parseInvalidAddCommandThrowsException() { } @Test - void parseInvalidRemoveCommandThrowsException() { + void parseInvalidRemoveCommandPrintsError() { + // Set up BookList books = new BookList(); - String input = "remove notAnIndex"; // Invalid index provided - assertThrows(NumberFormatException.class, - () -> Parser.parseCommand(input, books), "Book index must be an integer."); + String input = "remove notAnIndex"; + ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outContent)); // Redirect standard out to capture console output + // Act + Parser.parseCommand(input, books); + + // Assert + String output = outContent.toString(); + assertTrue(output.contains("not a valid number"), "Error message should contain 'not a valid number'"); + + // Cleanup + System.setOut(System.out); // Reset standard out } @Test From 338b84131492c9458d0693db3920c1025cf5d76e Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 21 Mar 2024 16:34:42 +0800 Subject: [PATCH 076/311] checkstyle fixes --- src/main/java/seedu/bookbuddy/BookList.java | 1 - src/test/java/seedu/bookbuddy/ParserTest.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index 2048711e1a..e961181a31 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -1,6 +1,5 @@ package seedu.bookbuddy; -import exceptions.InvalidBookIndexException; import java.util.ArrayList; diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index 30b2e4f0a7..30f8ea425c 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -1,6 +1,5 @@ package seedu.bookbuddy; -import exceptions.InvalidBookIndexException; import exceptions.InvalidCommandArgumentException; import exceptions.UnsupportedCommandException; import org.junit.jupiter.api.Test; From e36cb061c3f610201355102a185d40f0b0924da7 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 21 Mar 2024 21:29:48 +0800 Subject: [PATCH 077/311] Exception handling --- src/main/java/seedu/bookbuddy/BookList.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index e961181a31..cefa691112 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -1,6 +1,8 @@ package seedu.bookbuddy; +import exceptions.BookNotFoundException; + import java.util.ArrayList; /** @@ -30,9 +32,9 @@ public int getSize(){ * @param index The index of the book to retrieve. * @return The Book at the specified index. */ - public Book getBook(int index) throws IndexOutOfBoundsException{ + public Book getBook(int index) throws BookNotFoundException{ if (index < 0 || index > books.size()) { - throw new IndexOutOfBoundsException("Book index out of range."); + throw new BookNotFoundException("Book index out of range."); } assert books.get(index) != null : "Retrieved book should not be null"; return books.get(index); From 12d45963e7deea1d38d5f7e84de4500e4419ca7c Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 21 Mar 2024 22:05:08 +0800 Subject: [PATCH 078/311] Logging exceptions support --- src/main/java/seedu/bookbuddy/BookBuddy.java | 3 +++ src/main/java/seedu/bookbuddy/BookList.java | 23 +++++++++++++++++--- src/main/java/seedu/bookbuddy/Parser.java | 3 +++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index 0feca35524..e6ac8daa00 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -67,6 +67,9 @@ public static void getUserInput(BookList books) { System.out.println(e.getMessage()); } catch (InvalidCommandArgumentException e) { System.out.println(e.getMessage()); + } catch (Exception e) { // Generic catch block for any other exceptions + LOGGER.log(Level.SEVERE, "An unexpected error occurred: {0}", e.getMessage()); + System.out.println("An unexpected error occurred. Please contact support."); } } } diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index cefa691112..e20e7e7ee6 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -4,6 +4,10 @@ import exceptions.BookNotFoundException; import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static seedu.bookbuddy.BookBuddy.LOGGER; /** * Manages a list of books, allowing for operations such as adding, deleting, @@ -45,9 +49,14 @@ public Book getBook(int index) throws BookNotFoundException{ * @param title The title of the book. */ public void addBook(String title) { - books.add(new Book(title)); - Ui.addBookMessage(title); - assert !books.isEmpty() : "Book list should not be empty after adding a book"; + try { + books.add(new Book(title)); + Ui.addBookMessage(title); + assert !books.isEmpty() : "Book list should not be empty after adding a book"; + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "An unexpected error occurred: {0}", e.getMessage()); + throw e; // Rethrow or handle as needed + } } /** @@ -61,6 +70,9 @@ public void deleteBook(int index) throws IndexOutOfBoundsException { assert books.size() >= 0 : "Book list size should not be negative after deletion"; } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index"); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "An unexpected error occurred: {0}", e.getMessage()); + throw e; // Rethrow or handle as needed } } @@ -73,6 +85,9 @@ public void markDoneByIndex(int index) throws IndexOutOfBoundsException{ books.get(index - 1).markBookAsRead(); } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index"); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "An unexpected error occurred: {0}", e.getMessage()); + throw e; // Rethrow or handle as needed } } @@ -85,6 +100,8 @@ public void markUndoneByIndex(int index) throws IndexOutOfBoundsException{ books.get(index - 1).markBookAsUnread(); } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index"); + } catch (Exception e) { // Generic catch block for any other exceptions + System.out.println("An unexpected error occurred. Please contact support."); } } diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 00437e68b3..1396f7f04c 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -93,6 +93,9 @@ public static void parseCommand(String input, BookList books) { throw new InvalidBookIndexException("Book index must be an integer."); } catch (IndexOutOfBoundsException e) { throw new BookNotFoundException("Book not found at the provided index."); + } catch (Exception e) { // Generic catch block for any other exceptions + LOGGER.log(Level.SEVERE, "An unexpected error occurred: {0}", e.getMessage()); + System.out.println("An unexpected error occurred. Please contact support."); } } } From c096b822cf655f37d882652911191c6c72032b69 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 21 Mar 2024 22:15:51 +0800 Subject: [PATCH 079/311] Assertion support for the main classes --- src/main/java/seedu/bookbuddy/BookList.java | 7 ++++++- src/main/java/seedu/bookbuddy/Parser.java | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index e20e7e7ee6..a971d993a0 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -40,7 +40,8 @@ public Book getBook(int index) throws BookNotFoundException{ if (index < 0 || index > books.size()) { throw new BookNotFoundException("Book index out of range."); } - assert books.get(index) != null : "Retrieved book should not be null"; + assert books.get(index - 1) != null : "Retrieved book should not be null"; + assert books.get(index - 1) instanceof Book : "Object at index should be an instance of Book"; return books.get(index); } @@ -83,6 +84,7 @@ public void deleteBook(int index) throws IndexOutOfBoundsException { public void markDoneByIndex(int index) throws IndexOutOfBoundsException{ try { books.get(index - 1).markBookAsRead(); + assert books.get(index - 1).isRead() : "Book should be marked as read"; } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index"); } catch (Exception e) { @@ -98,6 +100,7 @@ public void markDoneByIndex(int index) throws IndexOutOfBoundsException{ public void markUndoneByIndex(int index) throws IndexOutOfBoundsException{ try { books.get(index - 1).markBookAsUnread(); + assert !books.get(index - 1).isRead() : "Book should be marked as unread"; } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index"); } catch (Exception e) { // Generic catch block for any other exceptions @@ -109,10 +112,12 @@ public void markUndoneByIndex(int index) throws IndexOutOfBoundsException{ * Prints all books currently in the list. */ public static void printAllBooks() { + assert BookList.books != null : "Books list should not be null since it has been initialised."; if (!BookList.books.isEmpty()) { System.out.println("All books:"); for (int i = 0; i < BookList.books.size(); i++) { Book currentBook = BookList.books.get(i); + assert currentBook != null : "Book in list should not be null"; System.out.print((i + 1) + ". "); System.out.println(currentBook.toString()); } diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 1396f7f04c..15c9643b7f 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -35,6 +35,7 @@ public static void parseCommand(String input, BookList books) { try { switch (command) { case ADD_COMMAND: + assert inputArray.length >= 2 : "Command requires additional arguments"; if (inputArray.length < 2) { LOGGER.log(Level.WARNING, "The add Command requires a book title", inputArray); throw new InvalidCommandArgumentException("The add command requires a book title."); @@ -42,6 +43,7 @@ public static void parseCommand(String input, BookList books) { books.addBook(inputArray[1]); break; case REMOVE_COMMAND: + assert inputArray.length >= 2 : "Command requires additional arguments"; try { index = Integer.parseInt(inputArray[1]); books.deleteBook(index); @@ -56,8 +58,10 @@ public static void parseCommand(String input, BookList books) { books.printAllBooks(); break; case MARK_COMMAND: + assert inputArray.length >= 2 : "Command requires additional arguments"; try { index = Integer.parseInt(inputArray[1]); + assert index >= 0 : "Index should be non-negative"; books.markDoneByIndex(index); } catch (NumberFormatException e) { System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + @@ -67,8 +71,10 @@ public static void parseCommand(String input, BookList books) { } break; case UNMARK_COMMAND: + assert inputArray.length >= 2 : "Command requires additional arguments"; try { index = Integer.parseInt(inputArray[1]); + assert index >= 0 : "Index should be non-negative"; books.markUndoneByIndex(index); } catch (NumberFormatException e) { System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + From 31720083adedb4a2ebc4b835009a4c8fb17652a6 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 21 Mar 2024 23:19:14 +0800 Subject: [PATCH 080/311] Bug fixes - getbook logic --- src/main/java/seedu/bookbuddy/BookList.java | 2 +- src/test/java/seedu/bookbuddy/BookListTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index a971d993a0..f4b51a56e4 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -42,7 +42,7 @@ public Book getBook(int index) throws BookNotFoundException{ } assert books.get(index - 1) != null : "Retrieved book should not be null"; assert books.get(index - 1) instanceof Book : "Object at index should be an instance of Book"; - return books.get(index); + return books.get(index - 1); } /** diff --git a/src/test/java/seedu/bookbuddy/BookListTest.java b/src/test/java/seedu/bookbuddy/BookListTest.java index 5f5be930b6..3a36d9aeff 100644 --- a/src/test/java/seedu/bookbuddy/BookListTest.java +++ b/src/test/java/seedu/bookbuddy/BookListTest.java @@ -57,7 +57,7 @@ void getBook() { bookList.addBook("Harry Potter"); bookList.addBook("Geronimo"); bookList.addBook("Cradle"); - assertEquals("[U] Cradle", bookList.getBook(2).toString()); + assertEquals("[U] Cradle", bookList.getBook(3).toString()); } @Test From 37080af157335a771f4f97e129db21285cbe856b Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 21 Mar 2024 23:21:30 +0800 Subject: [PATCH 081/311] Bug fixes - getbook logic --- src/test/java/seedu/bookbuddy/BookListTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/BookListTest.java b/src/test/java/seedu/bookbuddy/BookListTest.java index 3a36d9aeff..6be5b83f2f 100644 --- a/src/test/java/seedu/bookbuddy/BookListTest.java +++ b/src/test/java/seedu/bookbuddy/BookListTest.java @@ -22,7 +22,7 @@ void addBook() { BookList testBookList = new BookList(); testBookList.addBook("Harry Potter"); assertEquals(1, testBookList.getSize()); - assertEquals("[U] Harry Potter", testBookList.getBook(0).toString()); + assertEquals("[U] Harry Potter", testBookList.getBook(1).toString()); } @Test @@ -65,7 +65,7 @@ void markDoneByIndex() { BookList bookList = new BookList(); bookList.addBook("Harry Potter"); bookList.markDoneByIndex(1); - assertEquals("[R] Harry Potter", bookList.getBook(0).toString()); + assertEquals("[R] Harry Potter", bookList.getBook(1).toString()); } @Test @@ -73,9 +73,9 @@ void markUndoneByIndex() { BookList bookList = new BookList(); bookList.addBook("Harry Potter"); bookList.markDoneByIndex(1); - assertEquals("[R] Harry Potter", bookList.getBook(0).toString()); + assertEquals("[R] Harry Potter", bookList.getBook(1).toString()); bookList.markUndoneByIndex(1); - assertEquals("[U] Harry Potter", bookList.getBook(0).toString()); + assertEquals("[U] Harry Potter", bookList.getBook(1).toString()); } } From f600430b50eb0c8e0ff6f4dc18a9ee4c548b6e01 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 21 Mar 2024 23:24:50 +0800 Subject: [PATCH 082/311] Bug fixes - getbook logic --- src/test/java/seedu/bookbuddy/ParserTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index 30f8ea425c..1020a9e4b9 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -21,12 +21,12 @@ void testParser() { books.addBook("Gulliver's Travels"); assertEquals(2, books.getSize()); books.markDoneByIndex(1); - assertEquals("[R] Don Quixote", books.getBook(0).toString()); - assertEquals("[U] Gulliver's Travels", books.getBook(1).toString()); + assertEquals("[R] Don Quixote", books.getBook(1).toString()); + assertEquals("[U] Gulliver's Travels", books.getBook(2).toString()); books.deleteBook(1); books.markDoneByIndex(1); assertTrue(books.getBook(0).isRead); - assertEquals("[R] Gulliver's Travels", books.getBook(0).toString()); + assertEquals("[R] Gulliver's Travels", books.getBook(1).toString()); } @Test @@ -34,7 +34,7 @@ void parseAddCommand() { BookList testBookList = new BookList(); Parser.parseCommand("add The Great Gatsby", testBookList); assertEquals(1, testBookList.getSize()); - assertEquals("The Great Gatsby", testBookList.getBook(0).getTitle()); + assertEquals("The Great Gatsby", testBookList.getBook(1).getTitle()); } @Test @@ -59,7 +59,7 @@ void parseUnmarkCommand() { books.addBook("The Great Gatsby"); Parser.parseCommand("mark 1", books); Parser.parseCommand("unmark 1", books); - assertFalse(books.getBook(0).isRead()); + assertFalse(books.getBook(1).isRead()); } @Test From 8bfda2c6ec2f9e2dfbce60163a7cc249a64f5e98 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 21 Mar 2024 23:27:20 +0800 Subject: [PATCH 083/311] Bug fixes - getbook logic --- src/test/java/seedu/bookbuddy/ParserTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index 1020a9e4b9..68b97cd192 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -25,7 +25,7 @@ void testParser() { assertEquals("[U] Gulliver's Travels", books.getBook(2).toString()); books.deleteBook(1); books.markDoneByIndex(1); - assertTrue(books.getBook(0).isRead); + assertTrue(books.getBook(1).isRead); assertEquals("[R] Gulliver's Travels", books.getBook(1).toString()); } From 4aa1fd738e4136c80031980172eb48afa4a46fc1 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 21 Mar 2024 23:29:12 +0800 Subject: [PATCH 084/311] Bug fixes - getbook logic --- src/test/java/seedu/bookbuddy/ParserTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index 68b97cd192..ac6fed04e3 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -50,7 +50,7 @@ void parseMarkCommand() { BookList books = new BookList(); books.addBook("The Great Gatsby"); Parser.parseCommand("mark 1", books); - assertTrue(books.getBook(0).isRead()); + assertTrue(books.getBook(1).isRead()); } @Test From 66633ad639e7d7548a27c2faf100a24de2b8d100 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 21 Mar 2024 23:38:27 +0800 Subject: [PATCH 085/311] Disable assertions for jUnit tests --- build.gradle | 1 + src/main/java/seedu/bookbuddy/BookList.java | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 2a1b54a276..1a44034215 100644 --- a/build.gradle +++ b/build.gradle @@ -19,6 +19,7 @@ test { testLogging { events "passed", "skipped", "failed" + jvmArgs '-da' showExceptions true exceptionFormat "full" diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index f4b51a56e4..889c33b048 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -5,7 +5,6 @@ import java.util.ArrayList; import java.util.logging.Level; -import java.util.logging.Logger; import static seedu.bookbuddy.BookBuddy.LOGGER; From 744cc106058768386a160c194fdd804f87670088 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Fri, 22 Mar 2024 00:00:49 +0800 Subject: [PATCH 086/311] Bug Fixes - Exception propagation logic --- src/main/java/seedu/bookbuddy/Parser.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 15c9643b7f..c05eaa069e 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -38,6 +38,7 @@ public static void parseCommand(String input, BookList books) { assert inputArray.length >= 2 : "Command requires additional arguments"; if (inputArray.length < 2) { LOGGER.log(Level.WARNING, "The add Command requires a book title", inputArray); + System.out.println("throwing invalidcommand"); throw new InvalidCommandArgumentException("The add command requires a book title."); } books.addBook(inputArray[1]); @@ -99,6 +100,12 @@ public static void parseCommand(String input, BookList books) { throw new InvalidBookIndexException("Book index must be an integer."); } catch (IndexOutOfBoundsException e) { throw new BookNotFoundException("Book not found at the provided index."); + } catch (InvalidCommandArgumentException e) { + LOGGER.log(Level.WARNING, "Invalid command argument: {0}", e.getMessage()); + throw e; + } catch (UnsupportedCommandException e) { + LOGGER.log(Level.WARNING, "Command is invalid", e.getMessage()); + throw e; } catch (Exception e) { // Generic catch block for any other exceptions LOGGER.log(Level.SEVERE, "An unexpected error occurred: {0}", e.getMessage()); System.out.println("An unexpected error occurred. Please contact support."); From 601ae1b228347b7a5e42bfaf569707ad05de1d7a Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Fri, 22 Mar 2024 02:52:07 +0800 Subject: [PATCH 087/311] Add new exceptions and assertions to handle exceptions for marking book status --- BookBuddy.log | 124 ++++++++++++++++++ .../exceptions/BookReadAlreadyException.java | 7 + src/main/java/seedu/bookbuddy/BookList.java | 18 ++- 3 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 src/main/java/exceptions/BookReadAlreadyException.java diff --git a/BookBuddy.log b/BookBuddy.log index 09e4cc358a..a4c172fefb 100644 --- a/BookBuddy.log +++ b/BookBuddy.log @@ -10,3 +10,127 @@ Mar 21, 2024 3:23:35 PM seedu.bookbuddy.Parser parseCommand WARNING: The add Command requires a book title Mar 21, 2024 3:23:35 PM seedu.bookbuddy.Parser parseCommand WARNING: Sorry but that is not a valid command. Please try again +Mar 21, 2024 11:41:25 PM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 21, 2024 11:41:25 PM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 21, 2024 11:41:46 PM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Mar 21, 2024 11:41:46 PM seedu.bookbuddy.BookBuddy getUserInput +WARNING: Unsupported command: 1 +Mar 21, 2024 11:56:27 PM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 21, 2024 11:56:27 PM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 21, 2024 11:56:39 PM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Mar 21, 2024 11:56:39 PM seedu.bookbuddy.BookBuddy getUserInput +WARNING: Unsupported command: book2 +Mar 21, 2024 11:56:56 PM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 21, 2024 11:56:56 PM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 21, 2024 11:59:30 PM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 21, 2024 11:59:30 PM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 21, 2024 11:59:56 PM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 21, 2024 11:59:56 PM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 22, 2024 12:00:26 AM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 22, 2024 12:00:26 AM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 22, 2024 12:01:49 AM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 22, 2024 12:01:49 AM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 22, 2024 12:02:06 AM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 22, 2024 12:02:06 AM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 22, 2024 12:02:32 AM seedu.bookbuddy.Parser parseCommand +WARNING: The add Command requires a book title +Mar 22, 2024 12:02:32 AM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Mar 22, 2024 12:04:40 AM seedu.bookbuddy.Parser parseCommand +WARNING: The add Command requires a book title +Mar 22, 2024 12:04:40 AM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Mar 22, 2024 12:04:44 AM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 22, 2024 12:04:44 AM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 22, 2024 12:04:59 AM seedu.bookbuddy.Parser parseCommand +WARNING: The add Command requires a book title +Mar 22, 2024 12:04:59 AM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Mar 22, 2024 12:10:13 AM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 22, 2024 12:10:13 AM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 22, 2024 2:41:58 AM seedu.bookbuddy.Parser parseCommand +WARNING: The add Command requires a book title +Mar 22, 2024 2:41:58 AM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Mar 22, 2024 2:42:03 AM seedu.bookbuddy.Parser parseCommand +WARNING: The add Command requires a book title +Mar 22, 2024 2:42:03 AM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Mar 22, 2024 2:42:07 AM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 22, 2024 2:42:07 AM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 22, 2024 2:42:42 AM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 22, 2024 2:42:42 AM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 22, 2024 2:43:07 AM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 22, 2024 2:43:07 AM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 22, 2024 2:45:00 AM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 22, 2024 2:45:00 AM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 22, 2024 2:45:18 AM seedu.bookbuddy.Parser parseCommand +WARNING: The add Command requires a book title +Mar 22, 2024 2:45:18 AM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Mar 22, 2024 2:45:23 AM seedu.bookbuddy.Parser parseCommand +WARNING: The add Command requires a book title +Mar 22, 2024 2:45:23 AM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Mar 22, 2024 2:45:29 AM seedu.bookbuddy.Parser parseCommand +WARNING: The add Command requires a book title +Mar 22, 2024 2:45:29 AM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Mar 22, 2024 2:45:46 AM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 22, 2024 2:45:46 AM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 22, 2024 2:46:10 AM seedu.bookbuddy.Parser parseCommand +WARNING: The add Command requires a book title +Mar 22, 2024 2:46:10 AM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Mar 22, 2024 2:46:17 AM seedu.bookbuddy.Parser parseCommand +WARNING: The add Command requires a book title +Mar 22, 2024 2:46:17 AM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Mar 22, 2024 2:46:22 AM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 22, 2024 2:46:23 AM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 22, 2024 2:47:24 AM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 22, 2024 2:47:24 AM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 22, 2024 2:48:06 AM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 22, 2024 2:48:06 AM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 22, 2024 2:50:51 AM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 22, 2024 2:50:51 AM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. diff --git a/src/main/java/exceptions/BookReadAlreadyException.java b/src/main/java/exceptions/BookReadAlreadyException.java new file mode 100644 index 0000000000..78dc65fde0 --- /dev/null +++ b/src/main/java/exceptions/BookReadAlreadyException.java @@ -0,0 +1,7 @@ +package exceptions; + +public class BookReadAlreadyException extends RuntimeException{ + public BookReadAlreadyException(String message) { + super(message); + } +} diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index 7a96c61524..0d8d7b7190 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -2,6 +2,8 @@ import exceptions.BookNotFoundException; +import exceptions.BookReadAlreadyException; +import exceptions.BookUnreadAlreadyException; import java.util.ArrayList; @@ -68,12 +70,18 @@ public void deleteBook(int index) throws IndexOutOfBoundsException { * Marks a book as read by its index. * @param index The index of the book to mark as read. */ - public void markDoneByIndex(int index) throws IndexOutOfBoundsException{ + public void markDoneByIndex(int index) throws IndexOutOfBoundsException, BookReadAlreadyException{ try { assert index > 0 && index <= books.size() : "Index out of valid range"; + if (books.get(index - 1).isRead()) { + throw new BookReadAlreadyException("That book is already marked as read!"); + } + assert !books.get(index - 1).isRead() : "Book is already marked as read"; books.get(index - 1).markBookAsRead(); } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index"); + } catch (BookReadAlreadyException e) { + System.out.println("That book is already marked as read!"); } } @@ -81,12 +89,18 @@ public void markDoneByIndex(int index) throws IndexOutOfBoundsException{ * Marks a book as unread by its index. * @param index The index of the book to mark as unread. */ - public void markUndoneByIndex(int index) throws IndexOutOfBoundsException{ + public void markUndoneByIndex(int index) throws IndexOutOfBoundsException, BookReadAlreadyException{ try { assert index > 0 && index <= books.size() : "Index out of valid range"; + if (!books.get(index - 1).isRead()) { + throw new BookUnreadAlreadyException("That book is already marked as unread!"); + } + assert books.get(index - 1).isRead() : "Book is already marked as unread"; books.get(index - 1).markBookAsUnread(); } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index"); + } catch (BookUnreadAlreadyException e) { + System.out.println("That book is already marked as unread!"); } } From 61a4fe4ce8e49c9240bd2e6a1414ca1a09ee8a82 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Fri, 22 Mar 2024 02:52:45 +0800 Subject: [PATCH 088/311] Add new exceptions BookReadAlreadyException and BookUnreadAlreadyException --- BookBuddy.log.lck | 0 src/main/java/exceptions/BookUnreadAlreadyException.java | 7 +++++++ 2 files changed, 7 insertions(+) create mode 100644 BookBuddy.log.lck create mode 100644 src/main/java/exceptions/BookUnreadAlreadyException.java diff --git a/BookBuddy.log.lck b/BookBuddy.log.lck new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/main/java/exceptions/BookUnreadAlreadyException.java b/src/main/java/exceptions/BookUnreadAlreadyException.java new file mode 100644 index 0000000000..ba13e5b2e0 --- /dev/null +++ b/src/main/java/exceptions/BookUnreadAlreadyException.java @@ -0,0 +1,7 @@ +package exceptions; + +public class BookUnreadAlreadyException extends RuntimeException{ + public BookUnreadAlreadyException(String message) { + super(message); + } +} From 10e3c272198649811fae20d1be5dff8d76b4ea8a Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Fri, 22 Mar 2024 16:28:29 +0800 Subject: [PATCH 089/311] Add gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2873e189e1..22f55fa11c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ src/main/resources/docs/ .DS_Store *.iml bin/ +*.log /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT From 77fb5d97e7bb6d4e2e002e74e5d312f75dd1099c Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Fri, 22 Mar 2024 16:29:38 +0800 Subject: [PATCH 090/311] Add gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 22f55fa11c..b243409105 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ src/main/resources/docs/ *.iml bin/ *.log +*.lck +*.log.1 /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT From 20c64481d8f3909d461a6a7a7d9e711f23eedf88 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Sun, 24 Mar 2024 23:46:22 +0800 Subject: [PATCH 091/311] Add new exceptions --- src/main/java/exceptions/BookReadAlreadyException.java | 7 +++++++ src/main/java/exceptions/BookUnreadAlreadyException.java | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 src/main/java/exceptions/BookReadAlreadyException.java create mode 100644 src/main/java/exceptions/BookUnreadAlreadyException.java diff --git a/src/main/java/exceptions/BookReadAlreadyException.java b/src/main/java/exceptions/BookReadAlreadyException.java new file mode 100644 index 0000000000..78dc65fde0 --- /dev/null +++ b/src/main/java/exceptions/BookReadAlreadyException.java @@ -0,0 +1,7 @@ +package exceptions; + +public class BookReadAlreadyException extends RuntimeException{ + public BookReadAlreadyException(String message) { + super(message); + } +} diff --git a/src/main/java/exceptions/BookUnreadAlreadyException.java b/src/main/java/exceptions/BookUnreadAlreadyException.java new file mode 100644 index 0000000000..ba13e5b2e0 --- /dev/null +++ b/src/main/java/exceptions/BookUnreadAlreadyException.java @@ -0,0 +1,7 @@ +package exceptions; + +public class BookUnreadAlreadyException extends RuntimeException{ + public BookUnreadAlreadyException(String message) { + super(message); + } +} From 2f8a7cde92341342bd029cabf6526e1a9b9e8f7b Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 28 Mar 2024 17:03:20 +0800 Subject: [PATCH 092/311] Categorise the books with personalised labels and with the genre --- src/main/java/seedu/bookbuddy/Book.java | 40 +++++++++++++++++ .../java/seedu/bookbuddy/BookDetails.java | 44 +++++++++++++++++++ src/main/java/seedu/bookbuddy/Parser.java | 38 ++++++++++++++++ src/main/java/seedu/bookbuddy/Ui.java | 8 ++++ 4 files changed, 130 insertions(+) create mode 100644 src/main/java/seedu/bookbuddy/BookDetails.java diff --git a/src/main/java/seedu/bookbuddy/Book.java b/src/main/java/seedu/bookbuddy/Book.java index df1f1c46e7..2dfca10f58 100644 --- a/src/main/java/seedu/bookbuddy/Book.java +++ b/src/main/java/seedu/bookbuddy/Book.java @@ -4,6 +4,8 @@ public class Book { public String title; protected boolean isRead; + protected String label; + protected String genre; /** @@ -14,6 +16,44 @@ public class Book { public Book(String title) { this.title = title; // Description of the book this.isRead = false; //Completion status of the book (True: Read, False: Unread) + this.label = ""; + this.genre = ""; + } + + /** + * Sets the genre for this book. + * + * @param genre The label to set for the book. + */ + public void setGenre(String genre) { + this.label = genre; // Set the label for the book + } + + /** + * Returns the genre of the book. + * + * @return The genre of the book. + */ + public String getGenre() { + return this.genre; + } + + /** + * Sets the label for this book. + * + * @param label The label to set for the book. + */ + public void setLabel(String label) { + this.label = label; // Set the label for the book + } + + /** + * Returns the label of the book. + * + * @return The label of the book. + */ + public String getLabel() { + return this.label; } /** diff --git a/src/main/java/seedu/bookbuddy/BookDetails.java b/src/main/java/seedu/bookbuddy/BookDetails.java new file mode 100644 index 0000000000..f39ebe998e --- /dev/null +++ b/src/main/java/seedu/bookbuddy/BookDetails.java @@ -0,0 +1,44 @@ +package seedu.bookbuddy; + +import static seedu.bookbuddy.BookList.books; + +public class BookDetails { + /** + * Sets the label of the book at the specified index. + * + * @param index The index of the book in the list. + * @param label The label to set for the book. + * @throws IndexOutOfBoundsException if the index is out of range. + */ + public static void setBookLabelByIndex(int index, String label) throws IndexOutOfBoundsException { + // Check for valid index + if (index < 0 || index >= books.size()) { + throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); + } + + // Set the label for the book at the specified index + books.get(index).setLabel(label); + String title = books.get(index).getTitle(); + Ui.labelBookMessage(title, label); + } + + /** + * Sets the genre of the book at the specified index. + * + * @param index The index of the book in the list. + * @param genre The genre to set for the book. + * @throws IndexOutOfBoundsException if the index is out of range. + */ + public static void setBookGenreByIndex(int index, String genre) throws IndexOutOfBoundsException { + // Check for valid index + if (index < 0 || index >= books.size()) { + throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); + } + + // Set the genre for the book at the specified index + books.get(index).setGenre(genre); + String title = books.get(index).getTitle(); + Ui.setGenreBookMessage(title, genre); + } + +} diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index c05eaa069e..9d398e56fc 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -20,6 +20,8 @@ public class Parser { public static final String UNMARK_COMMAND = "unmark"; public static final String EXIT_COMMAND = "bye"; public static final String HELP_COMMAND = "help"; + public static final String LABEL_COMMAND = "label"; + public static final String GENRE_COMMAND = "set-genre"; /** * Scans the user input for valid commands and handles them accordingly. @@ -87,6 +89,42 @@ public static void parseCommand(String input, BookList books) { case HELP_COMMAND: Ui.helpMessage(); break; + case LABEL_COMMAND: + assert inputArray.length >= 2 : "Command requires additional arguments"; + String[] labelMessageParts = inputArray[1].split(" ", 2); // Split the message into index and label message + assert labelMessageParts.length == 2 : "Command requires an index and a label message"; + + try { + index = Integer.parseInt(labelMessageParts[0]); + assert index >= 0 : "Index should be non-negative"; + String label = labelMessageParts[1]; + BookDetails.setBookLabelByIndex(index-1, label); + } catch (NumberFormatException e) { + System.out.println("Invalid input: " + labelMessageParts[0] + " is not a valid number. Please enter a valid numeric index."); + } catch (IndexOutOfBoundsException e) { + System.out.println("Invalid book index. Please enter a valid index."); + } catch (Exception e) { + System.out.println("An error occurred while setting the label: " + e.getMessage()); + } + break; + case GENRE_COMMAND: + assert inputArray.length >= 2 : "Command requires additional arguments"; + String[] genreMessageParts = inputArray[1].split(" ", 2); // Split the message into index and label message + assert genreMessageParts.length == 2 : "Command requires an index and a label message"; + + try { + index = Integer.parseInt(genreMessageParts[0]); + assert index >= 0 : "Index should be non-negative"; + String label = genreMessageParts[1]; + BookDetails.setBookGenreByIndex(index-1, label); + } catch (NumberFormatException e) { + System.out.println("Invalid input: " + genreMessageParts[0] + " is not a valid number. Please enter a valid numeric index."); + } catch (IndexOutOfBoundsException e) { + System.out.println("Invalid book index. Please enter a valid index."); + } catch (Exception e) { + System.out.println("An error occurred while setting the genre: " + e.getMessage()); + } + break; case EXIT_COMMAND: Ui.printExitMessage(); System.exit(0); diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 085aa6d638..d253bf5469 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -34,6 +34,14 @@ public static void addBookMessage(String title) { System.out.println("okii added [" + title + "] to the list."); System.out.println("remember to read it soon...."); } + public static void labelBookMessage(String title, String label) { + System.out.println("okii labeled [" + title + "] as [" + label + "]"); + System.out.println("remember to read it soon...."); + } + public static void setGenreBookMessage(String title, String genre) { + System.out.println("okii categorised [" + title + "] as [" + genre + "]"); + System.out.println("remember to read it soon...."); + } public static void removeBookMessage(int index) { System.out.println("alright.. i've removed " + BookList.books.get(index).getTitle() + " from the list."); } From 00dff763250803c112cd03d8bdc916c1dd6f9f38 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 28 Mar 2024 17:06:55 +0800 Subject: [PATCH 093/311] Checkstyle fixes - lines longer than 120 lines --- src/main/java/seedu/bookbuddy/Parser.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 9d398e56fc..9627aa55c8 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -91,7 +91,8 @@ public static void parseCommand(String input, BookList books) { break; case LABEL_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; - String[] labelMessageParts = inputArray[1].split(" ", 2); // Split the message into index and label message + String[] labelMessageParts = inputArray[1].split(" ", 2); + // Split the message into index and label message assert labelMessageParts.length == 2 : "Command requires an index and a label message"; try { @@ -100,7 +101,8 @@ public static void parseCommand(String input, BookList books) { String label = labelMessageParts[1]; BookDetails.setBookLabelByIndex(index-1, label); } catch (NumberFormatException e) { - System.out.println("Invalid input: " + labelMessageParts[0] + " is not a valid number. Please enter a valid numeric index."); + System.out.println("Invalid input: " + labelMessageParts[0] + + " is not a valid number. Please enter a valid numeric index."); } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index."); } catch (Exception e) { @@ -109,7 +111,8 @@ public static void parseCommand(String input, BookList books) { break; case GENRE_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; - String[] genreMessageParts = inputArray[1].split(" ", 2); // Split the message into index and label message + String[] genreMessageParts = inputArray[1].split(" ", 2); + // Split the message into index and label message assert genreMessageParts.length == 2 : "Command requires an index and a label message"; try { @@ -118,7 +121,8 @@ public static void parseCommand(String input, BookList books) { String label = genreMessageParts[1]; BookDetails.setBookGenreByIndex(index-1, label); } catch (NumberFormatException e) { - System.out.println("Invalid input: " + genreMessageParts[0] + " is not a valid number. Please enter a valid numeric index."); + System.out.println("Invalid input: " + genreMessageParts[0] + + " is not a valid number. Please enter a valid numeric index."); } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index."); } catch (Exception e) { From 42e16b8e72f644adf2fb296b5a174c621660c46f Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 28 Mar 2024 17:41:48 +0800 Subject: [PATCH 094/311] Update developer guide --- docs/DeveloperGuide.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..8a84c5bab4 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -4,9 +4,33 @@ {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +Reference to AB-3 Developer Guide +* [Source URL](https://se-education.org/addressbook-level3/DeveloperGuide.html#documentation-logging-testing-configuration-dev-ops) +* Used as template to structure this DeveloperGuide +* Reference to AB-3 diagrams code + +Reference to AB-3 diagrams code +* [Source URL](https://github.com/se-edu/addressbook-level3/tree/master/docs/diagrams) +* Used as reference to understand PlantUML syntax + + ## Design & implementation {Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +# Categorising the different books by their genres +This functionality will allow the books to be segregated into different groups by their genres for improved tracking. +This operation is a cross class method that: + 1. Accesses the BookDetails class to access methods to categorise the books + 2. The categories are saved directory to the individual books in the Book class + 3. The Parser class will parse the command to obtain the specific index and genre given + +Below is an example usage: +Step 1: When the user inputs the command set-genre 1 Fantasy, the Parser class will split the command into an array +of 2 to access the command set-genre +Step 2: The second part of the array is further split into 2 to access the index "1" and the genre message "Fantasy" +Step 3: The index and genre message is passed into the setBookGenreByIndex method in BookDetails to set the genre of +the book indexed at 1. + ## Product scope From 53e1a8906b2e17e5ff520af07c1172e8ebee5245 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 28 Mar 2024 17:44:29 +0800 Subject: [PATCH 095/311] Update developer guide --- docs/DeveloperGuide.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 8a84c5bab4..f7186e16cd 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -18,18 +18,18 @@ Reference to AB-3 diagrams code {Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} # Categorising the different books by their genres -This functionality will allow the books to be segregated into different groups by their genres for improved tracking. -This operation is a cross class method that: - 1. Accesses the BookDetails class to access methods to categorise the books - 2. The categories are saved directory to the individual books in the Book class - 3. The Parser class will parse the command to obtain the specific index and genre given - -Below is an example usage: -Step 1: When the user inputs the command set-genre 1 Fantasy, the Parser class will split the command into an array -of 2 to access the command set-genre -Step 2: The second part of the array is further split into 2 to access the index "1" and the genre message "Fantasy" +This functionality will allow the books to be segregated into different groups by their genres for improved tracking. +This operation is a cross class method that: + 1. Accesses the BookDetails class to access methods to categorise the books + 2. The categories are saved directory to the individual books in the Book class + 3. The Parser class will parse the command to obtain the specific index and genre given + +Below is an example usage: +Step 1: When the user inputs the command set-genre 1 Fantasy, the Parser class will split the command into an array +of 2 to access the command set-genre +Step 2: The second part of the array is further split into 2 to access the index "1" and the genre message "Fantasy" Step 3: The index and genre message is passed into the setBookGenreByIndex method in BookDetails to set the genre of -the book indexed at 1. +the book indexed at 1. From 0d1053c0f2504966a45fd4ad468c98aed21bbdac Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 28 Mar 2024 17:46:17 +0800 Subject: [PATCH 096/311] Update DG --- docs/DeveloperGuide.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index f7186e16cd..718cac2697 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -20,15 +20,15 @@ Reference to AB-3 diagrams code # Categorising the different books by their genres This functionality will allow the books to be segregated into different groups by their genres for improved tracking. This operation is a cross class method that: - 1. Accesses the BookDetails class to access methods to categorise the books - 2. The categories are saved directory to the individual books in the Book class - 3. The Parser class will parse the command to obtain the specific index and genre given + >1. Accesses the BookDetails class to access methods to categorise the books + >2. The categories are saved directory to the individual books in the Book class + >3. The Parser class will parse the command to obtain the specific index and genre given Below is an example usage: -Step 1: When the user inputs the command set-genre 1 Fantasy, the Parser class will split the command into an array +>Step 1: When the user inputs the command set-genre 1 Fantasy, the Parser class will split the command into an array of 2 to access the command set-genre -Step 2: The second part of the array is further split into 2 to access the index "1" and the genre message "Fantasy" -Step 3: The index and genre message is passed into the setBookGenreByIndex method in BookDetails to set the genre of +>Step 2: The second part of the array is further split into 2 to access the index "1" and the genre message "Fantasy" +>Step 3: The index and genre message is passed into the setBookGenreByIndex method in BookDetails to set the genre of the book indexed at 1. From 47d3c9f2d9f0a98a877dccec8851ca6de62f80f8 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 28 Mar 2024 17:47:10 +0800 Subject: [PATCH 097/311] Update DG --- docs/DeveloperGuide.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 718cac2697..3a32db22b8 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -20,9 +20,9 @@ Reference to AB-3 diagrams code # Categorising the different books by their genres This functionality will allow the books to be segregated into different groups by their genres for improved tracking. This operation is a cross class method that: - >1. Accesses the BookDetails class to access methods to categorise the books - >2. The categories are saved directory to the individual books in the Book class - >3. The Parser class will parse the command to obtain the specific index and genre given +>1. Accesses the BookDetails class to access methods to categorise the books +>2. The categories are saved directory to the individual books in the Book class +>3. The Parser class will parse the command to obtain the specific index and genre given Below is an example usage: >Step 1: When the user inputs the command set-genre 1 Fantasy, the Parser class will split the command into an array From 9d71946143bcfa3bc612a0782a63c750b9fdcb63 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 28 Mar 2024 18:04:58 +0800 Subject: [PATCH 098/311] Update DG --- docs/DeveloperGuide.md | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 3a32db22b8..3720a3b99c 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -17,19 +17,30 @@ Reference to AB-3 diagrams code ## Design & implementation {Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} -# Categorising the different books by their genres -This functionality will allow the books to be segregated into different groups by their genres for improved tracking. -This operation is a cross class method that: ->1. Accesses the BookDetails class to access methods to categorise the books ->2. The categories are saved directory to the individual books in the Book class ->3. The Parser class will parse the command to obtain the specific index and genre given - +### Categorising the different books by their genres +This functionality enables the categorization of books into distinct groups based on their genres, facilitating better +organization and tracking. The implementation of this feature involves interactions across multiple classes within the +system. +#### Overview +The process of categorizing books by genre is a multi-step operation that involves the following classes: +1. 'BookDetails': This class contains methods that handle the categorization of books. +2. 'Book': Individual book objects are updated with their respective genres directly in this class. +3. 'Parser': This class is responsible for parsing the input command to extract the specific index and genre. + +#### Detailed Workflow Below is an example usage: ->Step 1: When the user inputs the command set-genre 1 Fantasy, the Parser class will split the command into an array -of 2 to access the command set-genre ->Step 2: The second part of the array is further split into 2 to access the index "1" and the genre message "Fantasy" ->Step 3: The index and genre message is passed into the setBookGenreByIndex method in BookDetails to set the genre of -the book indexed at 1. +Here’s a step-by-step guide on how the feature works: +Step 1: The user initiates the process by inputting a command like set-genre 1 Fantasy. Here, the Parser class plays a +crucial role as it interprets the command and segregates it into a manageable array. The first part of this array holds +the command set-genre, which indicates the action to be executed. + +Step 2: The second segment of the input string is then further dissected into two components, which are the index (1) +and the genre (Fantasy). This step is essential for identifying the specific book and the genre it needs to be +associated with. + +Step 3: With the index and genre clearly identified, these parameters are passed to the setBookGenreByIndex method +within the BookDetails class. This method is then responsible for assigning the specified genre to the book located at +the given index. From 435c679acca524aaac84fe14652cdd5a2c4bd0d3 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 28 Mar 2024 18:09:35 +0800 Subject: [PATCH 099/311] Update DG --- docs/DeveloperGuide.md | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 3720a3b99c..dd0360d86c 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -23,25 +23,36 @@ organization and tracking. The implementation of this feature involves interacti system. #### Overview The process of categorizing books by genre is a multi-step operation that involves the following classes: -1. 'BookDetails': This class contains methods that handle the categorization of books. -2. 'Book': Individual book objects are updated with their respective genres directly in this class. -3. 'Parser': This class is responsible for parsing the input command to extract the specific index and genre. +1. `BookDetails`: This class contains methods that handle the categorization of books. +2. `Book`: Individual book objects are updated with their respective genres directly in this class. +3. `Parser`: This class is responsible for parsing the input command to extract the specific index and genre. #### Detailed Workflow Below is an example usage: Here’s a step-by-step guide on how the feature works: -Step 1: The user initiates the process by inputting a command like set-genre 1 Fantasy. Here, the Parser class plays a -crucial role as it interprets the command and segregates it into a manageable array. The first part of this array holds -the command set-genre, which indicates the action to be executed. +Step 1: The user initiates the process by inputting a command like `set-genre 1 Fantasy`. Here, the `Parser` class plays +a crucial role as it interprets the command and segregates it into a manageable array. The first part of this array holds +the command `set-genre`, which indicates the action to be executed. -Step 2: The second segment of the input string is then further dissected into two components, which are the index (1) -and the genre (Fantasy). This step is essential for identifying the specific book and the genre it needs to be +Step 2: The second segment of the input string is then further dissected into two components, which are the index (`1`) +and the genre (`Fantasy`). This step is essential for identifying the specific book and the genre it needs to be associated with. -Step 3: With the index and genre clearly identified, these parameters are passed to the setBookGenreByIndex method -within the BookDetails class. This method is then responsible for assigning the specified genre to the book located at +Step 3: With the index and genre clearly identified, these parameters are passed to the `setBookGenreByIndex` method +within the `BookDetails` class. This method is then responsible for assigning the specified genre to the book located at the given index. +#### Implementation and Rationale +The decision to involve multiple classes in this operation is driven by the principles of object-oriented programming, +which emphasize modularity, encapsulation, and separation of concerns. By distributing responsibilities across different +classes, the system remains flexible, with each class focusing on a specific aspect of the functionality. + +* The `BookDetails` class is central to managing book attributes and behaviors, making it the logical location for methods +* that categorize books. +* The `Book` class represents individual books, and it is here that genre information is ultimately stored, aligning with +* the principle that objects should manage their own state. +* The `Parser` class abstracts the complexity of command interpretation, ensuring that user inputs are correctly understood +* and acted upon by the system. ## Product scope From 75a56a6ab372c087632a32dba24b283d632669cf Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 28 Mar 2024 18:13:58 +0800 Subject: [PATCH 100/311] Update DG --- docs/DeveloperGuide.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index dd0360d86c..6f0cf0c585 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -54,6 +54,12 @@ classes, the system remains flexible, with each class focusing on a specific asp * The `Parser` class abstracts the complexity of command interpretation, ensuring that user inputs are correctly understood * and acted upon by the system. +#### Alternatives Considered +An alternative design could have centralized the categorization logic within a single class, such as `BookDetails` or +`Parser`. However, this approach was discarded in favor of the current design to avoid overloading a single class with +multiple responsibilities and to adhere to the Single Responsibility Principle. By distributing the tasks, the system +gains in maintainability and scalability, facilitating future enhancements and modifications. + ## Product scope ### Target user profile From 396e0b49195dce456d80b62a466574e9b8a796ee Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Thu, 28 Mar 2024 21:42:37 +0800 Subject: [PATCH 101/311] Add book display functionality to view details of a book --- src/main/java/seedu/bookbuddy/Book.java | 2 -- src/main/java/seedu/bookbuddy/BookDetails.java | 18 ++++++++++++++++++ src/main/java/seedu/bookbuddy/Parser.java | 17 ++++++++++++++--- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Book.java b/src/main/java/seedu/bookbuddy/Book.java index 2dfca10f58..6d7ed6221f 100644 --- a/src/main/java/seedu/bookbuddy/Book.java +++ b/src/main/java/seedu/bookbuddy/Book.java @@ -1,13 +1,11 @@ package seedu.bookbuddy; public class Book { - public String title; protected boolean isRead; protected String label; protected String genre; - /** * Creates a new Book with the specified title. * diff --git a/src/main/java/seedu/bookbuddy/BookDetails.java b/src/main/java/seedu/bookbuddy/BookDetails.java index f39ebe998e..f2bb5cbb6f 100644 --- a/src/main/java/seedu/bookbuddy/BookDetails.java +++ b/src/main/java/seedu/bookbuddy/BookDetails.java @@ -3,6 +3,7 @@ import static seedu.bookbuddy.BookList.books; public class BookDetails { + /** * Sets the label of the book at the specified index. * @@ -41,4 +42,21 @@ public static void setBookGenreByIndex(int index, String genre) throws IndexOutO Ui.setGenreBookMessage(title, genre); } + /** + * Prints the details of the book at the specified index. + * @param index The index of hte book in the list. + * @throws IndexOutOfBoundsException if the index is out of range. + */ + public static void displayDetails(int index) throws IndexOutOfBoundsException { + if (index < 0 || index >= books.size()) { + throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); + } + + System.out.println("Here are the details of your book:"); + System.out.println("Title: " + books.get(index).getTitle()); + System.out.println("Status: " + (books.get(index).isRead ? "Read" : "Unread")); + System.out.println("Label: " + books.get(index).getLabel()); + System.out.println("Genre: " + books.get(index).getGenre()); + } + } diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 9627aa55c8..ed7156a1d8 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -11,7 +11,6 @@ /** * Parses inputs from the user in order to execute the correct commands. */ - public class Parser { public static final String ADD_COMMAND = "add"; public static final String REMOVE_COMMAND = "remove"; @@ -22,13 +21,13 @@ public class Parser { public static final String HELP_COMMAND = "help"; public static final String LABEL_COMMAND = "label"; public static final String GENRE_COMMAND = "set-genre"; + public static final String DISPLAY_COMMAND = "display"; /** * Scans the user input for valid commands and handles them accordingly. * @param input input from the user * @param books ArrayList of books */ - public static void parseCommand(String input, BookList books) { String[] inputArray = input.split(" ", 2); String command = inputArray[0].toLowerCase(); @@ -40,7 +39,7 @@ public static void parseCommand(String input, BookList books) { assert inputArray.length >= 2 : "Command requires additional arguments"; if (inputArray.length < 2) { LOGGER.log(Level.WARNING, "The add Command requires a book title", inputArray); - System.out.println("throwing invalidcommand"); + System.out.println("Throwing invalid command"); throw new InvalidCommandArgumentException("The add command requires a book title."); } books.addBook(inputArray[1]); @@ -129,6 +128,18 @@ public static void parseCommand(String input, BookList books) { System.out.println("An error occurred while setting the genre: " + e.getMessage()); } break; + case DISPLAY_COMMAND: + assert inputArray.length >= 2 : "Command requires additional arguments"; + try { + index = Integer.parseInt(inputArray[1]); + BookDetails.displayDetails(index - 1); + } catch (NumberFormatException e) { + System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + + "Please enter a valid numeric index."); + } catch (IndexOutOfBoundsException e) { + System.out.println("Invalid book index. Please enter a valid index."); + } + break; case EXIT_COMMAND: Ui.printExitMessage(); System.exit(0); From 7965b708d67030fe6b846b6b5595625fbb107572 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Thu, 28 Mar 2024 21:58:42 +0800 Subject: [PATCH 102/311] Remove redundant code and fix bugs --- src/main/java/seedu/bookbuddy/Book.java | 2 +- src/main/java/seedu/bookbuddy/Parser.java | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Book.java b/src/main/java/seedu/bookbuddy/Book.java index 6d7ed6221f..88bae12927 100644 --- a/src/main/java/seedu/bookbuddy/Book.java +++ b/src/main/java/seedu/bookbuddy/Book.java @@ -24,7 +24,7 @@ public Book(String title) { * @param genre The label to set for the book. */ public void setGenre(String genre) { - this.label = genre; // Set the label for the book + this.genre = genre; // Set the genre for the book } /** diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index ed7156a1d8..dd7ab5f2dd 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -33,10 +33,12 @@ public static void parseCommand(String input, BookList books) { String command = inputArray[0].toLowerCase(); LOGGER.log(Level.FINE, "Parsing command: {0}", command); int index; + try { switch (command) { case ADD_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; + if (inputArray.length < 2) { LOGGER.log(Level.WARNING, "The add Command requires a book title", inputArray); System.out.println("Throwing invalid command"); @@ -46,14 +48,13 @@ public static void parseCommand(String input, BookList books) { break; case REMOVE_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; + try { index = Integer.parseInt(inputArray[1]); books.deleteBook(index); } catch (NumberFormatException e) { System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + "Please enter a valid numeric index."); - } catch (IndexOutOfBoundsException e) { - System.out.println("Invalid book index. Please enter a valid index."); } break; case LIST_COMMAND: @@ -61,6 +62,7 @@ public static void parseCommand(String input, BookList books) { break; case MARK_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; + try { index = Integer.parseInt(inputArray[1]); assert index >= 0 : "Index should be non-negative"; @@ -68,12 +70,11 @@ public static void parseCommand(String input, BookList books) { } catch (NumberFormatException e) { System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + "Please enter a valid numeric index."); - } catch (IndexOutOfBoundsException e) { - System.out.println("Invalid book index. Please enter a valid index."); } break; case UNMARK_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; + try { index = Integer.parseInt(inputArray[1]); assert index >= 0 : "Index should be non-negative"; @@ -81,8 +82,6 @@ public static void parseCommand(String input, BookList books) { } catch (NumberFormatException e) { System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + "Please enter a valid numeric index."); - } catch (IndexOutOfBoundsException e) { - System.out.println("Invalid book index. Please enter a valid index."); } break; case HELP_COMMAND: @@ -98,7 +97,7 @@ public static void parseCommand(String input, BookList books) { index = Integer.parseInt(labelMessageParts[0]); assert index >= 0 : "Index should be non-negative"; String label = labelMessageParts[1]; - BookDetails.setBookLabelByIndex(index-1, label); + BookDetails.setBookLabelByIndex(index - 1, label); } catch (NumberFormatException e) { System.out.println("Invalid input: " + labelMessageParts[0] + " is not a valid number. Please enter a valid numeric index."); @@ -118,7 +117,7 @@ public static void parseCommand(String input, BookList books) { index = Integer.parseInt(genreMessageParts[0]); assert index >= 0 : "Index should be non-negative"; String label = genreMessageParts[1]; - BookDetails.setBookGenreByIndex(index-1, label); + BookDetails.setBookGenreByIndex(index - 1, label); } catch (NumberFormatException e) { System.out.println("Invalid input: " + genreMessageParts[0] + " is not a valid number. Please enter a valid numeric index."); @@ -130,6 +129,7 @@ public static void parseCommand(String input, BookList books) { break; case DISPLAY_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; + try { index = Integer.parseInt(inputArray[1]); BookDetails.displayDetails(index - 1); From 9aac18e4f2d4c9e327dc474c4c66bf11d20901f7 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Thu, 28 Mar 2024 23:30:51 +0800 Subject: [PATCH 103/311] Update DG --- docs/DeveloperGuide.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 6f0cf0c585..a2e9b912a4 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -60,6 +60,26 @@ An alternative design could have centralized the categorization logic within a s multiple responsibilities and to adhere to the Single Responsibility Principle. By distributing the tasks, the system gains in maintainability and scalability, facilitating future enhancements and modifications. +### Parser Class Component +The `Parser` class is responsible for parsing any input from the user and making sense of them to execute the correct commands. + +#### Overview +The `Parser` class contains several predefined string constants representing the valid commands and a public method to parse the +input from the user. + +#### Detailed Workflow +Whenever input from the user is detected by the program, the `Parser` class will split the command into 2 parts, with the first part +containing the command and the second containing details of the command (if present). The command entered is then evaluated using a +switch statement, with the value of it being compared to the values of each case. In the case of a match, the `Parser` class will then +execute the respective action associated with that command by calling other classes from the program such as `BookList` or `BookDetails`. + +#### Implementation and Rationale +The `Parser` class incorporates exception handling to detect invalid or unrecognized commands. This allows the program to continue running +while prompting the user for valid input + +By abstracting out the parsing functionality of BookBuddy into a separate `Parser` class, the complexity of parsing user input is removed +from the main code. It is instead replaced by a simple interface for the user to work with, adhering to the abstraction concept of +object-oriented programming. ## Product scope ### Target user profile From 2b1c27ac6a95eec12e342e970f6427fd50284fa2 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Fri, 29 Mar 2024 23:11:57 +0800 Subject: [PATCH 104/311] Add summary into BookDetails --- BookBuddy.log | 42 +++++++++++++++++++ src/main/java/seedu/bookbuddy/Book.java | 20 +++++++++ src/main/java/seedu/bookbuddy/BookBuddy.java | 1 + .../java/seedu/bookbuddy/BookDetails.java | 18 ++++++++ src/main/java/seedu/bookbuddy/Parser.java | 20 +++++++++ src/main/java/seedu/bookbuddy/Ui.java | 4 ++ 6 files changed, 105 insertions(+) diff --git a/BookBuddy.log b/BookBuddy.log index a4c172fefb..b439aaf9b0 100644 --- a/BookBuddy.log +++ b/BookBuddy.log @@ -134,3 +134,45 @@ Mar 22, 2024 2:50:51 AM seedu.bookbuddy.BookBuddy main INFO: BookBuddy application started. Mar 22, 2024 2:50:51 AM seedu.bookbuddy.BookBuddy getUserInput INFO: Starting to get user input. +Mar 22, 2024 3:14:04 PM seedu.bookbuddy.Parser parseCommand +WARNING: The add Command requires a book title +Mar 22, 2024 3:14:04 PM seedu.bookbuddy.Parser parseCommand +WARNING: Invalid command argument: The add command requires a book title. +Mar 22, 2024 3:14:04 PM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Mar 22, 2024 3:14:04 PM seedu.bookbuddy.Parser parseCommand +WARNING: Command is invalid +Mar 22, 2024 3:16:33 PM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 22, 2024 3:16:33 PM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 22, 2024 3:16:51 PM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 22, 2024 3:16:51 PM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 29, 2024 10:16:12 PM seedu.bookbuddy.Parser parseCommand +WARNING: The add Command requires a book title +Mar 29, 2024 10:16:12 PM seedu.bookbuddy.Parser parseCommand +WARNING: Invalid command argument: The add command requires a book title. +Mar 29, 2024 10:16:12 PM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Mar 29, 2024 10:16:12 PM seedu.bookbuddy.Parser parseCommand +WARNING: Command is invalid +Mar 29, 2024 10:16:27 PM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 29, 2024 10:16:28 PM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 29, 2024 10:22:51 PM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 29, 2024 10:22:51 PM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Mar 29, 2024 10:24:58 PM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Mar 29, 2024 10:24:58 PM seedu.bookbuddy.Parser parseCommand +WARNING: Command is invalid +Mar 29, 2024 10:24:58 PM seedu.bookbuddy.BookBuddy getUserInput +WARNING: Unsupported command: genre 0 testgenre +Mar 29, 2024 11:10:04 PM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 29, 2024 11:10:04 PM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. diff --git a/src/main/java/seedu/bookbuddy/Book.java b/src/main/java/seedu/bookbuddy/Book.java index 88bae12927..6cd0666550 100644 --- a/src/main/java/seedu/bookbuddy/Book.java +++ b/src/main/java/seedu/bookbuddy/Book.java @@ -6,6 +6,8 @@ public class Book { protected String label; protected String genre; + protected String summary; + /** * Creates a new Book with the specified title. * @@ -54,6 +56,24 @@ public String getLabel() { return this.label; } + /** + * Sets the summary for this book. + * + * @param summary The summary to set for the book. + */ + public void setSummary(String summary) { + this.summary = summary; + } + + /** + * Returns the summary of the book. + * + * @return The summary of the book. + */ + public String getSummary() { + return this.summary; + } + /** * Returns the title of the book. * diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index e6ac8daa00..407c8545df 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -39,6 +39,7 @@ public class BookBuddy { private static BookList books = new BookList(); public static void main(String[] args) { + assert false : "dummy assertion set to fail"; LOGGER.log(Level.INFO, "BookBuddy application started."); Ui.printWelcome(); assert books != null : "BookList not created"; diff --git a/src/main/java/seedu/bookbuddy/BookDetails.java b/src/main/java/seedu/bookbuddy/BookDetails.java index f2bb5cbb6f..f642161006 100644 --- a/src/main/java/seedu/bookbuddy/BookDetails.java +++ b/src/main/java/seedu/bookbuddy/BookDetails.java @@ -4,6 +4,24 @@ public class BookDetails { + protected String summary; + + /** + * Sets the summary of the book at the specified index. + * + * @param index The index of the book in the list. + * @param summary The summary to set for the book. + * @throws IndexOutOfBoundsException if the index is out of range. + */ + public static void setBookSummaryByIndex(int index, String summary) throws IndexOutOfBoundsException { + if (index < 0 || index >= books.size()) { + throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); + } + books.get(index).setSummary(summary); + String title = books.get(index).getTitle(); + Ui.summaryBookMessage(title, summary); + } + /** * Sets the label of the book at the specified index. * diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index dd7ab5f2dd..128a96503d 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -21,6 +21,7 @@ public class Parser { public static final String HELP_COMMAND = "help"; public static final String LABEL_COMMAND = "label"; public static final String GENRE_COMMAND = "set-genre"; + public static final String SUMMARY_COMMAND = "give-summary"; public static final String DISPLAY_COMMAND = "display"; /** @@ -107,6 +108,25 @@ public static void parseCommand(String input, BookList books) { System.out.println("An error occurred while setting the label: " + e.getMessage()); } break; + case SUMMARY_COMMAND: + assert inputArray.length >= 2 : "Command requires additional arguments"; + String[] summaryMessageParts = inputArray[1].split(" ", 2); + assert summaryMessageParts.length == 2 : "Command requires an index and a label message"; + + try { + index = Integer.parseInt(summaryMessageParts[0]); + assert index >= 0 : "Index should be non-negative"; + String summary = summaryMessageParts[1]; + BookDetails.setBookSummaryByIndex(index - 1, summary); + } catch (NumberFormatException e) { + System.out.println("Invalid input: " + summaryMessageParts[0] + + " is not a valid number. Please enter a valid numeric index."); + } catch (IndexOutOfBoundsException e) { + System.out.println("Invalid book index. Please enter a valid index."); + } catch (Exception e) { + System.out.println("An error occurred while setting the label: " + e.getMessage()); + } + break; case GENRE_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; String[] genreMessageParts = inputArray[1].split(" ", 2); diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index d253bf5469..5684790ebb 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -38,6 +38,10 @@ public static void labelBookMessage(String title, String label) { System.out.println("okii labeled [" + title + "] as [" + label + "]"); System.out.println("remember to read it soon...."); } + public static void summaryBookMessage(String title, String summary) { + System.out.println("okii you have written: [" + summary + "] for the book: [" + title + "]"); + System.out.println("remember to read it soon...."); + } public static void setGenreBookMessage(String title, String genre) { System.out.println("okii categorised [" + title + "] as [" + genre + "]"); System.out.println("remember to read it soon...."); From 377ce74917369c020b34ff29bee680b31a7cf07b Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Fri, 29 Mar 2024 23:14:24 +0800 Subject: [PATCH 105/311] Fix indentation in Parser --- BookBuddy.log | 8 +++++ src/main/java/seedu/bookbuddy/Parser.java | 36 +++++++++++------------ 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/BookBuddy.log b/BookBuddy.log index b439aaf9b0..d458b9e62f 100644 --- a/BookBuddy.log +++ b/BookBuddy.log @@ -176,3 +176,11 @@ Mar 29, 2024 11:10:04 PM seedu.bookbuddy.BookBuddy main INFO: BookBuddy application started. Mar 29, 2024 11:10:04 PM seedu.bookbuddy.BookBuddy getUserInput INFO: Starting to get user input. +Mar 29, 2024 11:13:30 PM seedu.bookbuddy.Parser parseCommand +WARNING: The add Command requires a book title +Mar 29, 2024 11:13:30 PM seedu.bookbuddy.Parser parseCommand +WARNING: Invalid command argument: The add command requires a book title. +Mar 29, 2024 11:13:30 PM seedu.bookbuddy.Parser parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Mar 29, 2024 11:13:30 PM seedu.bookbuddy.Parser parseCommand +WARNING: Command is invalid diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 128a96503d..4a851fb7bc 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -108,25 +108,25 @@ public static void parseCommand(String input, BookList books) { System.out.println("An error occurred while setting the label: " + e.getMessage()); } break; - case SUMMARY_COMMAND: - assert inputArray.length >= 2 : "Command requires additional arguments"; - String[] summaryMessageParts = inputArray[1].split(" ", 2); - assert summaryMessageParts.length == 2 : "Command requires an index and a label message"; + case SUMMARY_COMMAND: + assert inputArray.length >= 2 : "Command requires additional arguments"; + String[] summaryMessageParts = inputArray[1].split(" ", 2); + assert summaryMessageParts.length == 2 : "Command requires an index and a label message"; - try { - index = Integer.parseInt(summaryMessageParts[0]); - assert index >= 0 : "Index should be non-negative"; - String summary = summaryMessageParts[1]; - BookDetails.setBookSummaryByIndex(index - 1, summary); - } catch (NumberFormatException e) { - System.out.println("Invalid input: " + summaryMessageParts[0] - + " is not a valid number. Please enter a valid numeric index."); - } catch (IndexOutOfBoundsException e) { - System.out.println("Invalid book index. Please enter a valid index."); - } catch (Exception e) { - System.out.println("An error occurred while setting the label: " + e.getMessage()); - } - break; + try { + index = Integer.parseInt(summaryMessageParts[0]); + assert index >= 0 : "Index should be non-negative"; + String summary = summaryMessageParts[1]; + BookDetails.setBookSummaryByIndex(index - 1, summary); + } catch (NumberFormatException e) { + System.out.println("Invalid input: " + summaryMessageParts[0] + + " is not a valid number. Please enter a valid numeric index."); + } catch (IndexOutOfBoundsException e) { + System.out.println("Invalid book index. Please enter a valid index."); + } catch (Exception e) { + System.out.println("An error occurred while setting the label: " + e.getMessage()); + } + break; case GENRE_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; String[] genreMessageParts = inputArray[1].split(" ", 2); From c8cf65cba49dab524130f7fcbec15b6ddd35667e Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Fri, 29 Mar 2024 23:25:25 +0800 Subject: [PATCH 106/311] no message --- BookBuddy.log | 4 ++++ BookBuddy.log.lck | 0 src/main/java/seedu/bookbuddy/BookBuddy.java | 2 ++ 3 files changed, 6 insertions(+) delete mode 100644 BookBuddy.log.lck diff --git a/BookBuddy.log b/BookBuddy.log index a4c172fefb..24af54af7a 100644 --- a/BookBuddy.log +++ b/BookBuddy.log @@ -134,3 +134,7 @@ Mar 22, 2024 2:50:51 AM seedu.bookbuddy.BookBuddy main INFO: BookBuddy application started. Mar 22, 2024 2:50:51 AM seedu.bookbuddy.BookBuddy getUserInput INFO: Starting to get user input. +Mar 22, 2024 3:16:07 PM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Mar 22, 2024 3:16:07 PM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. diff --git a/BookBuddy.log.lck b/BookBuddy.log.lck deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index e6ac8daa00..f7b123323d 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -39,6 +39,8 @@ public class BookBuddy { private static BookList books = new BookList(); public static void main(String[] args) { + assert false : "dummy assertion set to fail"; + //assertions should only be used for development LOGGER.log(Level.INFO, "BookBuddy application started."); Ui.printWelcome(); assert books != null : "BookList not created"; From 0790a0ccdc80c2463aa22f95ee5e84ed166cc723 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Fri, 29 Mar 2024 23:51:19 +0800 Subject: [PATCH 107/311] Updated DG --- docs/DeveloperGuide.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index a2e9b912a4..c82c0140fe 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -60,6 +60,22 @@ An alternative design could have centralized the categorization logic within a s multiple responsibilities and to adhere to the Single Responsibility Principle. By distributing the tasks, the system gains in maintainability and scalability, facilitating future enhancements and modifications. +### BookList Class Component +The `BookList` class is responsible for all actions involving the list of books that the user has. + +#### Overview +The `BookList` class contains one protected static ArrayList named books. This ArrayList will contain Book objects. The methods in +this class all change the ArrayList according to the command given. + +#### Detailed Workflow +Apart from the constructor, the methods of this class like getSize(), addBook() all either return a piece of information about the ArrayList, +the book object that is selected or change an attribute of the ArrayList or selected book object. For the printAllBooks() method, the ArrayList +is iterated through, with the details of each book being printed out according to the toString() format of each book. Other than that, methods like +markDoneByIndex() and markUndoneByIndex() both will change the isRead() attribute of the book of the given index. This class handles errors related to the +ArrayList, throwing exceptions for invalid indexes and invalid actions based on current state (if trying to mark a book that is already read). + +#### Implementation and Rationale + ### Parser Class Component The `Parser` class is responsible for parsing any input from the user and making sense of them to execute the correct commands. @@ -71,7 +87,10 @@ input from the user. Whenever input from the user is detected by the program, the `Parser` class will split the command into 2 parts, with the first part containing the command and the second containing details of the command (if present). The command entered is then evaluated using a switch statement, with the value of it being compared to the values of each case. In the case of a match, the `Parser` class will then -execute the respective action associated with that command by calling other classes from the program such as `BookList` or `BookDetails`. +execute the respective action associated with that command by calling other classes from the program such as `BookList` or `BookDetails`. +This class also handles errors and exceptions associated with the users input. For example, if the user were to give the command `mark` without +specifying an index for which book to mark, or gives a negative number, an appropriate error message will be shown and the command will be rendered +invalid. #### Implementation and Rationale The `Parser` class incorporates exception handling to detect invalid or unrecognized commands. This allows the program to continue running From 54b9d295565b2be6c39788ab37fb4ebe7f3cc627 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Sat, 30 Mar 2024 00:02:51 +0800 Subject: [PATCH 108/311] update DG --- docs/DeveloperGuide.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 4a1aa1b79e..8cfb4ef899 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -103,7 +103,11 @@ object-oriented programming. ## Product scope ### Target user profile -Users that want an all-in-one app to track the books read, progress for each book. Users will be able to sort books according to genre and completion status. +Users that want an all-in-one app to track the books read, progress for each book. +Progress for each book can be recorded according to the number of pages read. +Users will be able to sort books according to genre. +Users can sort books according to Read or Unread. +Users will also be able to search for books via keywords in book titles ### Value proposition From fa0a196665df7b44d337a1cc3f124243349fe10d Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Sat, 30 Mar 2024 02:38:34 +0800 Subject: [PATCH 109/311] Huge bug fix related to static inheritance --- .../java/seedu/bookbuddy/BookDetails.java | 40 +++++----- src/main/java/seedu/bookbuddy/BookList.java | 14 ++-- src/main/java/seedu/bookbuddy/Parser.java | 74 ++++++++++++++----- src/main/java/seedu/bookbuddy/Ui.java | 4 +- src/test/java/seedu/bookbuddy/ParserTest.java | 4 + 5 files changed, 89 insertions(+), 47 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookDetails.java b/src/main/java/seedu/bookbuddy/BookDetails.java index f642161006..6c78d1c387 100644 --- a/src/main/java/seedu/bookbuddy/BookDetails.java +++ b/src/main/java/seedu/bookbuddy/BookDetails.java @@ -1,7 +1,5 @@ package seedu.bookbuddy; -import static seedu.bookbuddy.BookList.books; - public class BookDetails { protected String summary; @@ -13,12 +11,12 @@ public class BookDetails { * @param summary The summary to set for the book. * @throws IndexOutOfBoundsException if the index is out of range. */ - public static void setBookSummaryByIndex(int index, String summary) throws IndexOutOfBoundsException { - if (index < 0 || index >= books.size()) { + public static void setBookSummaryByIndex(int index, String summary, BookList books) throws IndexOutOfBoundsException { + if (index < 0 || index >= books.getSize()) { throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); } - books.get(index).setSummary(summary); - String title = books.get(index).getTitle(); + books.getBook(index).setSummary(summary); + String title = books.getBook(index).getTitle(); Ui.summaryBookMessage(title, summary); } @@ -29,15 +27,15 @@ public static void setBookSummaryByIndex(int index, String summary) throws Index * @param label The label to set for the book. * @throws IndexOutOfBoundsException if the index is out of range. */ - public static void setBookLabelByIndex(int index, String label) throws IndexOutOfBoundsException { + public static void setBookLabelByIndex(int index, String label, BookList books) throws IndexOutOfBoundsException { // Check for valid index - if (index < 0 || index >= books.size()) { - throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); + if (index < 0 || index > books.getSize()) { + throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index. nows"); } // Set the label for the book at the specified index - books.get(index).setLabel(label); - String title = books.get(index).getTitle(); + books.getBook(index).setLabel(label); + String title = books.getBook(index).getTitle(); Ui.labelBookMessage(title, label); } @@ -48,15 +46,15 @@ public static void setBookLabelByIndex(int index, String label) throws IndexOutO * @param genre The genre to set for the book. * @throws IndexOutOfBoundsException if the index is out of range. */ - public static void setBookGenreByIndex(int index, String genre) throws IndexOutOfBoundsException { + public static void setBookGenreByIndex(int index, String genre, BookList books) throws IndexOutOfBoundsException { // Check for valid index - if (index < 0 || index >= books.size()) { + if (index < 0 || index > books.getSize()) { throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); } // Set the genre for the book at the specified index - books.get(index).setGenre(genre); - String title = books.get(index).getTitle(); + books.getBook(index).setGenre(genre); + String title = books.getBook(index).getTitle(); Ui.setGenreBookMessage(title, genre); } @@ -65,16 +63,16 @@ public static void setBookGenreByIndex(int index, String genre) throws IndexOutO * @param index The index of hte book in the list. * @throws IndexOutOfBoundsException if the index is out of range. */ - public static void displayDetails(int index) throws IndexOutOfBoundsException { - if (index < 0 || index >= books.size()) { + public static void displayDetails(int index, BookList books) throws IndexOutOfBoundsException { + if (index < 0 || index > books.getSize()) { throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); } System.out.println("Here are the details of your book:"); - System.out.println("Title: " + books.get(index).getTitle()); - System.out.println("Status: " + (books.get(index).isRead ? "Read" : "Unread")); - System.out.println("Label: " + books.get(index).getLabel()); - System.out.println("Genre: " + books.get(index).getGenre()); + System.out.println("Title: " + books.getBook(index).getTitle()); + System.out.println("Status: " + (books.getBook(index).isRead ? "Read" : "Unread")); + System.out.println("Label: " + books.getBook(index).getLabel()); + System.out.println("Genre: " + books.getBook(index).getGenre()); } } diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index 1bf7a59fc6..033df86219 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -15,7 +15,7 @@ * and marking book as read or unread. */ public class BookList { - protected static ArrayList books; + protected ArrayList books; /** * Constructs a new BookList instance with an empty list. @@ -67,7 +67,7 @@ public void addBook(String title) { */ public void deleteBook(int index) throws IndexOutOfBoundsException { try { - Ui.removeBookMessage(index - 1); + Ui.removeBookMessage(index, this); books.remove(index - 1); assert books.size() >= 0 : "Book list size should not be negative after deletion"; } catch (IndexOutOfBoundsException e) { @@ -126,12 +126,12 @@ public void markUndoneByIndex(int index) throws IndexOutOfBoundsException, BookR /** * Prints all books currently in the list. */ - public static void printAllBooks() { - assert BookList.books != null : "Books list should not be null since it has been initialised."; - if (!BookList.books.isEmpty()) { + public void printAllBooks() { + assert books != null : "Books list should not be null since it has been initialised."; + if (!books.isEmpty()) { System.out.println("All books:"); - for (int i = 0; i < BookList.books.size(); i++) { - Book currentBook = BookList.books.get(i); + for (int i = 0; i < books.size(); i++) { + Book currentBook = books.get(i); assert currentBook != null : "Book in list should not be null"; System.out.print((i + 1) + ". "); System.out.println(currentBook.toString()); diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 4a851fb7bc..da40198a23 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -42,14 +42,16 @@ public static void parseCommand(String input, BookList books) { if (inputArray.length < 2) { LOGGER.log(Level.WARNING, "The add Command requires a book title", inputArray); - System.out.println("Throwing invalid command"); throw new InvalidCommandArgumentException("The add command requires a book title."); } books.addBook(inputArray[1]); break; case REMOVE_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; - + if (inputArray.length < 2) { + LOGGER.log(Level.WARNING, "The remove Command requires a book index", inputArray); + throw new InvalidCommandArgumentException("The remove command requires a book index."); + } try { index = Integer.parseInt(inputArray[1]); books.deleteBook(index); @@ -63,7 +65,10 @@ public static void parseCommand(String input, BookList books) { break; case MARK_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; - + if (inputArray.length < 2) { + LOGGER.log(Level.WARNING, "The mark Command requires a book index", inputArray); + throw new InvalidCommandArgumentException("The mark command requires a book index."); + } try { index = Integer.parseInt(inputArray[1]); assert index >= 0 : "Index should be non-negative"; @@ -74,8 +79,11 @@ public static void parseCommand(String input, BookList books) { } break; case UNMARK_COMMAND: - assert inputArray.length >= 2 : "Command requires additional arguments"; - + assert inputArray.length == 2 : "Command requires additional arguments"; + if (inputArray.length < 2) { + LOGGER.log(Level.WARNING, "The unmark Command requires a book index", inputArray); + throw new InvalidCommandArgumentException("The unmark command requires a book index."); + } try { index = Integer.parseInt(inputArray[1]); assert index >= 0 : "Index should be non-negative"; @@ -89,38 +97,55 @@ public static void parseCommand(String input, BookList books) { Ui.helpMessage(); break; case LABEL_COMMAND: - assert inputArray.length >= 2 : "Command requires additional arguments"; + assert inputArray.length == 2 : "Command requires additional arguments"; + if (inputArray.length < 2) { + LOGGER.log(Level.WARNING, "The Label Command requires a book index and label", inputArray); + throw new InvalidCommandArgumentException("The label command requires a book index and label."); + } String[] labelMessageParts = inputArray[1].split(" ", 2); // Split the message into index and label message assert labelMessageParts.length == 2 : "Command requires an index and a label message"; - + if (labelMessageParts.length < 2) { + throw new InvalidCommandArgumentException("You need to have a label message"); + } try { index = Integer.parseInt(labelMessageParts[0]); assert index >= 0 : "Index should be non-negative"; String label = labelMessageParts[1]; - BookDetails.setBookLabelByIndex(index - 1, label); + System.out.println(index); + BookDetails.setBookLabelByIndex(index, label, books); } catch (NumberFormatException e) { System.out.println("Invalid input: " + labelMessageParts[0] + " is not a valid number. Please enter a valid numeric index."); + } catch (InvalidCommandArgumentException e) { + System.out.println(e.getMessage()); } catch (IndexOutOfBoundsException e) { - System.out.println("Invalid book index. Please enter a valid index."); + System.out.println("Invalid book index. Please enter a valid index"); } catch (Exception e) { System.out.println("An error occurred while setting the label: " + e.getMessage()); } break; case SUMMARY_COMMAND: - assert inputArray.length >= 2 : "Command requires additional arguments"; + assert inputArray.length == 2 : "Command requires additional arguments"; + if (inputArray.length < 2) { + LOGGER.log(Level.WARNING, "The summary Command requires a book index and summary", inputArray); + throw new InvalidCommandArgumentException("The summary command requires a book index and summary."); + } String[] summaryMessageParts = inputArray[1].split(" ", 2); assert summaryMessageParts.length == 2 : "Command requires an index and a label message"; - + if (summaryMessageParts.length < 2) { + throw new InvalidCommandArgumentException("You need to have a summary message"); + } try { index = Integer.parseInt(summaryMessageParts[0]); assert index >= 0 : "Index should be non-negative"; String summary = summaryMessageParts[1]; - BookDetails.setBookSummaryByIndex(index - 1, summary); + BookDetails.setBookSummaryByIndex(index, summary, books); } catch (NumberFormatException e) { System.out.println("Invalid input: " + summaryMessageParts[0] + " is not a valid number. Please enter a valid numeric index."); + } catch (InvalidCommandArgumentException e) { + System.out.println(e.getMessage()); } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index."); } catch (Exception e) { @@ -129,18 +154,28 @@ public static void parseCommand(String input, BookList books) { break; case GENRE_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; + if (inputArray.length < 2) { + LOGGER.log(Level.WARNING, "The genre Command requires a book index and genre", inputArray); + throw new InvalidCommandArgumentException("The genre command requires a book index and genre."); + } String[] genreMessageParts = inputArray[1].split(" ", 2); - // Split the message into index and label message - assert genreMessageParts.length == 2 : "Command requires an index and a label message"; + + if (genreMessageParts.length < 2) { + throw new InvalidCommandArgumentException("You need to have a genre message"); + } + // Split the message into index and genre message + assert genreMessageParts.length == 2 : "Command requires an index and a genre message"; try { index = Integer.parseInt(genreMessageParts[0]); assert index >= 0 : "Index should be non-negative"; String label = genreMessageParts[1]; - BookDetails.setBookGenreByIndex(index - 1, label); + BookDetails.setBookGenreByIndex(index, label, books); } catch (NumberFormatException e) { System.out.println("Invalid input: " + genreMessageParts[0] + " is not a valid number. Please enter a valid numeric index."); + } catch (InvalidCommandArgumentException e) { + System.out.println(e.getMessage()); } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index."); } catch (Exception e) { @@ -149,15 +184,20 @@ public static void parseCommand(String input, BookList books) { break; case DISPLAY_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; - + if (inputArray.length < 2) { + LOGGER.log(Level.WARNING, "The display Command requires a book index", inputArray); + throw new InvalidCommandArgumentException("The display command requires a book index."); + } try { index = Integer.parseInt(inputArray[1]); - BookDetails.displayDetails(index - 1); + BookDetails.displayDetails(index, books); } catch (NumberFormatException e) { System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + "Please enter a valid numeric index."); } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index."); + } catch (InvalidCommandArgumentException e) { + System.out.println(e.getMessage()); } break; case EXIT_COMMAND: diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 5684790ebb..55b035fdab 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -46,8 +46,8 @@ public static void setGenreBookMessage(String title, String genre) { System.out.println("okii categorised [" + title + "] as [" + genre + "]"); System.out.println("remember to read it soon...."); } - public static void removeBookMessage(int index) { - System.out.println("alright.. i've removed " + BookList.books.get(index).getTitle() + " from the list."); + public static void removeBookMessage(int index, BookList books) { + System.out.println("alright.. i've removed " + books.getBook(index).getTitle() + " from the list."); } public static void helpMessage() { System.out.println("Here's a list of commands to get you started!!"); diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index ac6fed04e3..244f5ec4cd 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -48,8 +48,12 @@ void parseRemoveCommand() { @Test void parseMarkCommand() { BookList books = new BookList(); + System.out.println(books.getSize()); books.addBook("The Great Gatsby"); + System.out.println(books); Parser.parseCommand("mark 1", books); + System.out.println(books); + books.markDoneByIndex(1); assertTrue(books.getBook(1).isRead()); } From f68c6b756de41e78974d7b5b74e674004e7657fe Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Sat, 30 Mar 2024 02:43:00 +0800 Subject: [PATCH 110/311] Checkstyle fixes - line longer than 120 --- src/main/java/seedu/bookbuddy/BookDetails.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/BookDetails.java b/src/main/java/seedu/bookbuddy/BookDetails.java index 6c78d1c387..6ef278126f 100644 --- a/src/main/java/seedu/bookbuddy/BookDetails.java +++ b/src/main/java/seedu/bookbuddy/BookDetails.java @@ -11,7 +11,8 @@ public class BookDetails { * @param summary The summary to set for the book. * @throws IndexOutOfBoundsException if the index is out of range. */ - public static void setBookSummaryByIndex(int index, String summary, BookList books) throws IndexOutOfBoundsException { + public static void setBookSummaryByIndex(int index, String summary, BookList books) + throws IndexOutOfBoundsException { if (index < 0 || index >= books.getSize()) { throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); } From 90d1f4ddd30f379b39a4add6f258393970dbbdbe Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Sat, 30 Mar 2024 02:59:09 +0800 Subject: [PATCH 111/311] Added Junit test cases for set label and set genre --- .../java/seedu/bookbuddy/BookDetailsTest.java | 29 +++++++++++++++++++ src/test/java/seedu/bookbuddy/ParserTest.java | 16 ++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/test/java/seedu/bookbuddy/BookDetailsTest.java diff --git a/src/test/java/seedu/bookbuddy/BookDetailsTest.java b/src/test/java/seedu/bookbuddy/BookDetailsTest.java new file mode 100644 index 0000000000..7216c36fde --- /dev/null +++ b/src/test/java/seedu/bookbuddy/BookDetailsTest.java @@ -0,0 +1,29 @@ +package seedu.bookbuddy; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class BookDetailsTest { + @Test + public void testSetBookLabelByIndex() { + BookList books = new BookList(); + books.addBook("The Great Gatsby"); + books.addBook("Geronimo Stilton"); + BookDetails.setBookLabelByIndex(1, "Great Classic", books); + assertEquals("Great Classic" ,books.getBook(1).getLabel()); + BookDetails.setBookLabelByIndex(2, "Great Classic", books); + assertEquals("Great Classic" ,books.getBook(2).getLabel()); + } + + @Test + public void testSetBookGenreByIndex() { + BookList books = new BookList(); + books.addBook("The Great Gatsby"); + books.addBook("Geronimo Stilton"); + BookDetails.setBookGenreByIndex(1, "Classic", books); + assertEquals("Classic" ,books.getBook(1).getGenre()); + BookDetails.setBookGenreByIndex(2, "Fantasy", books); + assertEquals("Fantasy" ,books.getBook(2).getGenre()); + } +} diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index 244f5ec4cd..23d121966a 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -66,6 +66,22 @@ void parseUnmarkCommand() { assertFalse(books.getBook(1).isRead()); } + @Test + void parseLabelCommand() { + BookList books = new BookList(); + books.addBook("The Great Gatsby"); + Parser.parseCommand("label 1 Great Book", books); + assertEquals("Great Book", books.getBook(1).getLabel()); + } + + @Test + void parseGenreCommand() { + BookList books = new BookList(); + books.addBook("The Great Gatsby"); + Parser.parseCommand("set-genre 1 Classic", books); + assertEquals("Classic", books.getBook(1).getGenre()); + } + @Test void parseInvalidAddCommandThrowsException() { BookList books = new BookList(); From efa3f3649a5baa79e963ebcd12a1c2b539b44cad Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Mon, 1 Apr 2024 18:18:12 +0800 Subject: [PATCH 112/311] implement basic findBook function --- src/main/java/seedu/bookbuddy/BookList.java | 14 ++++++++++++++ src/main/java/seedu/bookbuddy/Parser.java | 4 ++++ src/main/java/seedu/bookbuddy/Ui.java | 12 ++++++++++++ 3 files changed, 30 insertions(+) diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index 033df86219..dd03c3ccc0 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -61,6 +61,20 @@ public void addBook(String title) { } } + public void findBook(String title) { + ArrayList bookTitles = new ArrayList<>(); + for (Book book : books) { + if (book.getTitle().contains(title)) { + bookTitles.add(book); + } + } + if (bookTitles.isEmpty()){ + Ui.printNoBookFound(); + } else { + Ui.printBookFound(bookTitles); + } + } + /** * Deletes a book from the list by its index. * @param index The index of the book to delete. diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index da40198a23..de2b79fde9 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -19,6 +19,7 @@ public class Parser { public static final String UNMARK_COMMAND = "unmark"; public static final String EXIT_COMMAND = "bye"; public static final String HELP_COMMAND = "help"; + public static final String FIND_COMMAND = "find"; public static final String LABEL_COMMAND = "label"; public static final String GENRE_COMMAND = "set-genre"; public static final String SUMMARY_COMMAND = "give-summary"; @@ -96,6 +97,9 @@ public static void parseCommand(String input, BookList books) { case HELP_COMMAND: Ui.helpMessage(); break; + case FIND_COMMAND: + books.findBook(inputArray[1]); + break; case LABEL_COMMAND: assert inputArray.length == 2 : "Command requires additional arguments"; if (inputArray.length < 2) { diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 55b035fdab..73f1e15a16 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -1,5 +1,7 @@ package seedu.bookbuddy; +import java.util.ArrayList; + public class Ui { public static void printWelcome() { String logo = @@ -58,4 +60,14 @@ public static void helpMessage() { System.out.println("unmark (index) -> to unmark book as unread [U]"); System.out.println("bye -> to exit BookBuddy software"); } + + public static void printBookFound(ArrayList bookTitles){ + for (int i = 0; i < bookTitles.size(); i++) { + System.out.println(i + 1 + ". " + bookTitles.get(i)); + } + } + public static void printNoBookFound(){ + System.out.println("no such books added..."); + + } } From 60baef5f19d85da0cad965aaad9b360b36b29566 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Tue, 2 Apr 2024 00:46:28 +0800 Subject: [PATCH 113/311] Update User Guide --- docs/UserGuide.md | 157 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 140 insertions(+), 17 deletions(-) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..aa53bece3d 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,33 +1,156 @@ -# User Guide +# BookBuddy User Guide -## Introduction +## Overview -{Give a product intro} +BookBuddy is an application that helps users track and manage the list of books +that they are reading. It is optimised for users that are familiar with the CLI so that +the tracking and management objectives can be achieved more efficiently. -## Quick Start - -{Give steps to get started quickly} +## Getting Started 1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). +2. Download the latest JAR file of BookBuddy [here](https://github.com/AY2324S2-CS2113-F15-4/tp/releases). +3. Move the JAR file into an empty folder. +4. In any command terminal, use the `cd` command to switch to the folder containing the JAR file. +5. Enter `java -jar BookBuddy.jar` in the command terminal to run the application. +6. Refer to the features below for details of the various commands and how to use BookBuddy. + +## Features + +### Adding a book: `help` +View all the commands available in BookBuddy and instructions on their formatting. + +Format: `help` + +Example of usage: + +``` +help +``` + +### Adding a book: `add` +Adds a new book to the book list. + +Format: `add [BOOK_TITLE]` + +Example of usage: + +``` +add Harry Potter +``` + +### Removing a book: `remove` +Removes a specific book from the book list. + +Format: `remove [BOOK_INDEX]` + +Example of usage: + +``` +remove 1 +``` + +### Viewing all books: `list` +Shows all books in the list along with their titles and status. + +Format: `list` + +Example of usage: + +``` +list +``` + +### Marking a book as read: `mark` +Changes the status of a specific book to read. + +Format: `mark [BOOK_INDEX]` + +Example of usage: + +``` +mark 1 +``` + +### Marking a book as unread: `unmark` +Changes the status of a specific book to unread. + +Format: `unmark [BOOK_INDEX]` + +Example of usage: + +``` +unmark 1 +``` + +### Setting the genre of a book: `set-genre` + +Sets the genre of a specific book to the provided input. + +Format: `set-genre [BOOK_INDEX] [BOOK_GENRE]` + +Example of usage: + +``` +set-genre 1 fiction +``` + +### Labelling a book: `label` + +Sets the label of a specific book to the provided input. + +Format: `label [BOOK_INDEX] [LABEL]` + +Example of usage: + +``` +label 1 very cool +``` + +### Adding a book summary: `give-summary` +Provides a summary for the specified book. + +Format: `give-summary [BOOK_INDEX] [BOOK_SUMMARY]` + +Example of usage: + +``` +give-summary 1 A book about a young boy who is invited to study at Hogwarts. +``` + +### Displaying the details of a book: `display` +Gives more detailed information about a specific book like its genre, label and summary. + +Format: `display [BOOK_INDEX]` + +Example of usage: + +``` +display 1 +``` + +### Finding a book: `find` +Returns all books in the book list that contains the keyword. -## Features +Format: `find [KEYWORD]` -{Give detailed description of each feature} +Example of usage: -### Adding a todo: `todo` -Adds a new item to the list of todo items. +``` +find harry +``` -Format: `todo n/TODO_NAME d/DEADLINE` +### Exiting the program: `bye` -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +Exits the application. -Example of usage: +Format: `bye` -`todo n/Write the rest of the User Guide d/next week` +Example of usage: -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +``` +bye +``` ## FAQ From 8c1dc2075abc1b0ec37deab4073ca42383116ab1 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Tue, 2 Apr 2024 01:18:49 +0800 Subject: [PATCH 114/311] Fix bug with give-summary command --- src/main/java/seedu/bookbuddy/BookDetails.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/BookDetails.java b/src/main/java/seedu/bookbuddy/BookDetails.java index 6ef278126f..1e2b3e1911 100644 --- a/src/main/java/seedu/bookbuddy/BookDetails.java +++ b/src/main/java/seedu/bookbuddy/BookDetails.java @@ -13,7 +13,7 @@ public class BookDetails { */ public static void setBookSummaryByIndex(int index, String summary, BookList books) throws IndexOutOfBoundsException { - if (index < 0 || index >= books.getSize()) { + if (index < 0 || index > books.getSize()) { throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); } books.getBook(index).setSummary(summary); From 965baec6c617af988ac307c607e14b8bda79776f Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Tue, 2 Apr 2024 02:26:40 +0800 Subject: [PATCH 115/311] Update set-genre function for a more user-friendly user experience --- .../java/seedu/bookbuddy/BookDetails.java | 7 ++ src/main/java/seedu/bookbuddy/BookList.java | 2 +- src/main/java/seedu/bookbuddy/Parser.java | 74 +++++++++++++------ 3 files changed, 61 insertions(+), 22 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookDetails.java b/src/main/java/seedu/bookbuddy/BookDetails.java index 6ef278126f..e00d99ad50 100644 --- a/src/main/java/seedu/bookbuddy/BookDetails.java +++ b/src/main/java/seedu/bookbuddy/BookDetails.java @@ -1,8 +1,15 @@ package seedu.bookbuddy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + public class BookDetails { protected String summary; + protected static List availableGenres = new ArrayList<>(Arrays.asList("Fiction", "Non-Fiction", + "Mystery", "Science Fiction", "Fantasy")); + /** * Sets the summary of the book at the specified index. diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index dd03c3ccc0..4426b706c2 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -151,7 +151,7 @@ public void printAllBooks() { System.out.println(currentBook.toString()); } } else { - System.out.println("The list is empty."); + System.out.println("The list is empty. Add books by 'add [book]'"); } } } diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index de2b79fde9..e329b0d33d 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -5,7 +5,9 @@ import exceptions.InvalidCommandArgumentException; import exceptions.UnsupportedCommandException; +import java.util.Scanner; import java.util.logging.Level; + import static seedu.bookbuddy.BookBuddy.LOGGER; /** @@ -157,34 +159,64 @@ public static void parseCommand(String input, BookList books) { } break; case GENRE_COMMAND: - assert inputArray.length >= 2 : "Command requires additional arguments"; - if (inputArray.length < 2) { - LOGGER.log(Level.WARNING, "The genre Command requires a book index and genre", inputArray); - throw new InvalidCommandArgumentException("The genre command requires a book index and genre."); - } - String[] genreMessageParts = inputArray[1].split(" ", 2); + try { + if (inputArray.length < 2) { + throw new InvalidCommandArgumentException("Usage: set-genre [index]"); + } - if (genreMessageParts.length < 2) { - throw new InvalidCommandArgumentException("You need to have a genre message"); - } - // Split the message into index and genre message - assert genreMessageParts.length == 2 : "Command requires an index and a genre message"; + index = Integer.parseInt(inputArray[1]); + if (index < 0 || index > books.getSize()) { + throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index. " + + "Type 'list' to view the list of books."); + } + System.out.println("Available genres:"); + for (int i = 0; i < BookDetails.availableGenres.size(); i++) { + System.out.println((i + 1) + ". " + BookDetails.availableGenres.get(i)); + } + System.out.println((BookDetails.availableGenres.size() + 1) + ". Add a new genre"); - try { - index = Integer.parseInt(genreMessageParts[0]); - assert index >= 0 : "Index should be non-negative"; - String label = genreMessageParts[1]; - BookDetails.setBookGenreByIndex(index, label, books); + System.out.println("Enter the number for the desired genre, or add a new one:"); + Scanner scanner = new Scanner(System.in); + + String selectedGenre = null; + while (selectedGenre == null) { + while (!scanner.hasNextInt()) { // Ensure the next input is an integer + String newInput = scanner.nextLine(); + if ("exit".equalsIgnoreCase(newInput)) { + return; // Exit the command if user types 'exit' + } else { + System.out.println("Invalid input. Please enter a valid number or type 'exit'" + + " to cancel."); + } + } + + int genreSelection = scanner.nextInt(); + scanner.nextLine(); // Consume the newline after the number + + if (genreSelection == BookDetails.availableGenres.size() + 1) { + System.out.println("Enter the new genre:"); + selectedGenre = scanner.nextLine(); + BookDetails.availableGenres.add(selectedGenre); // Add the new genre to the list + } else if (genreSelection > 0 && genreSelection <= BookDetails.availableGenres.size()) { + selectedGenre = BookDetails.availableGenres.get(genreSelection - 1); + } else { + System.out.println("Invalid selection. Please enter a valid number " + + "or type 'exit' to cancel."); + // No need for the nextLine or parsing logic here, the while loop will continue + } + } + + BookDetails.setBookGenreByIndex(index, selectedGenre, books); + System.out.println("Genre set to " + selectedGenre + " for book at index " + index); } catch (NumberFormatException e) { - System.out.println("Invalid input: " + genreMessageParts[0] - + " is not a valid number. Please enter a valid numeric index."); - } catch (InvalidCommandArgumentException e) { + System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + + "Please enter a valid numeric index. Type 'list' to view the list of books.") ; + } catch (InvalidCommandArgumentException | IndexOutOfBoundsException e) { System.out.println(e.getMessage()); - } catch (IndexOutOfBoundsException e) { - System.out.println("Invalid book index. Please enter a valid index."); } catch (Exception e) { System.out.println("An error occurred while setting the genre: " + e.getMessage()); } + break; case DISPLAY_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; From 134e577dd87726a5014323075f052a422c1a4cef Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Tue, 2 Apr 2024 02:39:52 +0800 Subject: [PATCH 116/311] Update parseGenreCommand Junit test --- src/test/java/seedu/bookbuddy/ParserTest.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index 23d121966a..5f48fd5259 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -4,7 +4,9 @@ import exceptions.UnsupportedCommandException; import org.junit.jupiter.api.Test; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.InputStream; import java.io.PrintStream; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -13,6 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; + public class ParserTest { @Test void testParser() { @@ -78,8 +81,19 @@ void parseLabelCommand() { void parseGenreCommand() { BookList books = new BookList(); books.addBook("The Great Gatsby"); - Parser.parseCommand("set-genre 1 Classic", books); - assertEquals("Classic", books.getBook(1).getGenre()); + // Simulate user input for genre selection "Classic" + String simulatedUserInput = "6\nClassic\n"; // Assuming '3' is the option to add a new genre + InputStream savedStandardInputStream = System.in; + System.setIn(new ByteArrayInputStream(simulatedUserInput.getBytes())); + Parser.parseCommand("set-genre 1", books); // Changed to fit your updated command-handling logic + assertEquals("Classic", books.getBook(1).getGenre()); // Indexes are typically 0-based in lists + + books.addBook("Geronimo"); + String nextSimulatedUserInput = "3\n"; + System.setIn(new ByteArrayInputStream(nextSimulatedUserInput.getBytes())); + Parser.parseCommand("set-genre 2", books); + assertEquals("Mystery", books.getBook(2).getGenre()); + System.setIn(savedStandardInputStream); } @Test From b14c200988ee9cd3bdb19ca0407ce3c5c945ae0c Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Tue, 2 Apr 2024 02:42:13 +0800 Subject: [PATCH 117/311] Checkstyle fixes - wrong order of variable definition --- src/main/java/seedu/bookbuddy/BookDetails.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookDetails.java b/src/main/java/seedu/bookbuddy/BookDetails.java index e00d99ad50..974b82d7d6 100644 --- a/src/main/java/seedu/bookbuddy/BookDetails.java +++ b/src/main/java/seedu/bookbuddy/BookDetails.java @@ -5,12 +5,10 @@ import java.util.List; public class BookDetails { - - protected String summary; protected static List availableGenres = new ArrayList<>(Arrays.asList("Fiction", "Non-Fiction", "Mystery", "Science Fiction", "Fantasy")); - - + protected String summary; + /** * Sets the summary of the book at the specified index. * From afd2e470715a5197fb21807404c219bdf9dd2ff5 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Tue, 2 Apr 2024 04:36:11 +0800 Subject: [PATCH 118/311] Add rating system for books. Rate books and list books based on good to bad ratings --- src/main/java/seedu/bookbuddy/Book.java | 25 ++++++++- src/main/java/seedu/bookbuddy/BookBuddy.java | 1 - .../java/seedu/bookbuddy/BookDetails.java | 53 +++++++++++++++++-- src/main/java/seedu/bookbuddy/BookList.java | 4 ++ src/main/java/seedu/bookbuddy/Parser.java | 52 +++++++++++++----- src/main/java/seedu/bookbuddy/Ui.java | 4 ++ 6 files changed, 119 insertions(+), 20 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Book.java b/src/main/java/seedu/bookbuddy/Book.java index 6cd0666550..2fe290e1fa 100644 --- a/src/main/java/seedu/bookbuddy/Book.java +++ b/src/main/java/seedu/bookbuddy/Book.java @@ -5,7 +5,7 @@ public class Book { protected boolean isRead; protected String label; protected String genre; - + protected int rating; protected String summary; /** @@ -18,6 +18,29 @@ public Book(String title) { this.isRead = false; //Completion status of the book (True: Read, False: Unread) this.label = ""; this.genre = ""; + this.rating = -1; + } + + /** + * Sets the rating for this book. The rating must be between 1 and 5. + * + * @param rating The rating to set for the book. + * @throws IllegalArgumentException if the rating is not between 1 and 5. + */ + public void setRating(int rating) { + if (rating < 1 || rating > 5) { + throw new IllegalArgumentException("Rating must be between 1 and 5."); + } + this.rating = rating; + } + + /** + * Returns the rating of the book. + * + * @return The rating of the book. + */ + public int getRating() { + return this.rating; } /** diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index 407c8545df..e6ac8daa00 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -39,7 +39,6 @@ public class BookBuddy { private static BookList books = new BookList(); public static void main(String[] args) { - assert false : "dummy assertion set to fail"; LOGGER.log(Level.INFO, "BookBuddy application started."); Ui.printWelcome(); assert books != null : "BookList not created"; diff --git a/src/main/java/seedu/bookbuddy/BookDetails.java b/src/main/java/seedu/bookbuddy/BookDetails.java index 917d9da6cd..174eb6c0ab 100644 --- a/src/main/java/seedu/bookbuddy/BookDetails.java +++ b/src/main/java/seedu/bookbuddy/BookDetails.java @@ -1,14 +1,56 @@ package seedu.bookbuddy; -import java.util.ArrayList; -import java.util.Arrays; +import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; public class BookDetails { - protected static List availableGenres = new ArrayList<>(Arrays.asList("Fiction", "Non-Fiction", - "Mystery", "Science Fiction", "Fantasy")); protected String summary; - + + + /** + * Sets the rating of the book at the specified index. + * + * @param index The index of the book in the list. + * @param rating The rating to set for the book. + * @throws IndexOutOfBoundsException if the index is out of range. + * @throws IllegalArgumentException if the rating is not between 1 and 5. + */ + public static void setBookRatingByIndex(int index, int rating, BookList books) + throws IndexOutOfBoundsException, IllegalArgumentException { + if (index < 0 || index > books.getSize()) { + throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); + } + if (rating < 1 || rating > 5) { + throw new IllegalArgumentException("Rating must be between 1 and 5."); + } + books.getBook(index).setRating(rating); + String title = books.getBook(index).getTitle(); + Ui.setRatingBookMessage(title, rating); + } + + + /** + * Prints all books sorted by rating in descending order. + */ + public static void printBooksByRating(BookList books) { + if (books.books.isEmpty()) { + System.out.println("The list is empty. Add books by 'add [book]'"); + return; + } + + System.out.println("Books sorted by rating:"); + + List sortedBooks = books.books.stream() + .sorted(Comparator.comparingInt(Book::getRating).reversed()) + .collect(Collectors.toList()); + + for (Book book : sortedBooks) { + String rating = book.getRating() >= 0 ? String.valueOf(book.getRating()) : "Not Rated"; + System.out.println(book.getTitle() + " - " + rating); + } + } + /** * Sets the summary of the book at the specified index. * @@ -79,6 +121,7 @@ public static void displayDetails(int index, BookList books) throws IndexOutOfBo System.out.println("Status: " + (books.getBook(index).isRead ? "Read" : "Unread")); System.out.println("Label: " + books.getBook(index).getLabel()); System.out.println("Genre: " + books.getBook(index).getGenre()); + System.out.println("Rating: " + books.getBook(index).getRating()); } } diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index 4426b706c2..04e6e46723 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -6,6 +6,8 @@ import exceptions.BookUnreadAlreadyException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.logging.Level; import static seedu.bookbuddy.BookBuddy.LOGGER; @@ -15,6 +17,8 @@ * and marking book as read or unread. */ public class BookList { + protected static List availableGenres = new ArrayList<>(Arrays.asList("Fiction", "Non-Fiction", + "Mystery", "Science Fiction", "Fantasy")); protected ArrayList books; /** diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index e329b0d33d..efb3312738 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -1,7 +1,6 @@ package seedu.bookbuddy; import exceptions.BookNotFoundException; -import exceptions.InvalidBookIndexException; import exceptions.InvalidCommandArgumentException; import exceptions.UnsupportedCommandException; @@ -26,6 +25,8 @@ public class Parser { public static final String GENRE_COMMAND = "set-genre"; public static final String SUMMARY_COMMAND = "give-summary"; public static final String DISPLAY_COMMAND = "display"; + public static final String RATING_COMMAND = "rate"; + public static final String PRINT_ORDERED_COMMAND = "listrated"; /** * Scans the user input for valid commands and handles them accordingly. @@ -149,7 +150,7 @@ public static void parseCommand(String input, BookList books) { BookDetails.setBookSummaryByIndex(index, summary, books); } catch (NumberFormatException e) { System.out.println("Invalid input: " + summaryMessageParts[0] - + " is not a valid number. Please enter a valid numeric index."); + + " is not a valid number. Please enter a valid numeric index. here"); } catch (InvalidCommandArgumentException e) { System.out.println(e.getMessage()); } catch (IndexOutOfBoundsException e) { @@ -170,10 +171,10 @@ public static void parseCommand(String input, BookList books) { "Type 'list' to view the list of books."); } System.out.println("Available genres:"); - for (int i = 0; i < BookDetails.availableGenres.size(); i++) { - System.out.println((i + 1) + ". " + BookDetails.availableGenres.get(i)); + for (int i = 0; i < BookList.availableGenres.size(); i++) { + System.out.println((i + 1) + ". " + BookList.availableGenres.get(i)); } - System.out.println((BookDetails.availableGenres.size() + 1) + ". Add a new genre"); + System.out.println((BookList.availableGenres.size() + 1) + ". Add a new genre"); System.out.println("Enter the number for the desired genre, or add a new one:"); Scanner scanner = new Scanner(System.in); @@ -193,12 +194,12 @@ public static void parseCommand(String input, BookList books) { int genreSelection = scanner.nextInt(); scanner.nextLine(); // Consume the newline after the number - if (genreSelection == BookDetails.availableGenres.size() + 1) { + if (genreSelection == BookList.availableGenres.size() + 1) { System.out.println("Enter the new genre:"); selectedGenre = scanner.nextLine(); - BookDetails.availableGenres.add(selectedGenre); // Add the new genre to the list - } else if (genreSelection > 0 && genreSelection <= BookDetails.availableGenres.size()) { - selectedGenre = BookDetails.availableGenres.get(genreSelection - 1); + BookList.availableGenres.add(selectedGenre); // Add the new genre to the list + } else if (genreSelection > 0 && genreSelection <= BookList.availableGenres.size()) { + selectedGenre = BookList.availableGenres.get(genreSelection - 1); } else { System.out.println("Invalid selection. Please enter a valid number " + "or type 'exit' to cancel."); @@ -216,7 +217,31 @@ public static void parseCommand(String input, BookList books) { } catch (Exception e) { System.out.println("An error occurred while setting the genre: " + e.getMessage()); } - + break; + case RATING_COMMAND: + assert inputArray.length >= 2 : "Command requires additional arguments"; + if (inputArray.length < 2) { + LOGGER.log(Level.WARNING, "The rating Command requires a book index", inputArray); + throw new InvalidCommandArgumentException("The rating command requires a book index."); + } + try { + String[] ratingParts = inputArray[1].split(" ", 2); + // Split the message into index and label message + assert ratingParts.length == 2 : "Command requires an index and a rating"; + if (ratingParts.length < 2) { + throw new InvalidCommandArgumentException("You need to have a book index and a rating"); + } + index = Integer.parseInt(ratingParts[0]); + int rating = Integer.parseInt(ratingParts[1]); + BookDetails.setBookRatingByIndex(index, rating, books); + } catch (NumberFormatException e) { + System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + + "Please enter a valid numeric index."); + } catch (IndexOutOfBoundsException e) { + System.out.println("Invalid book index. Please enter a valid index."); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + } break; case DISPLAY_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; @@ -236,6 +261,9 @@ public static void parseCommand(String input, BookList books) { System.out.println(e.getMessage()); } break; + case PRINT_ORDERED_COMMAND: + BookDetails.printBooksByRating(books); + break; case EXIT_COMMAND: Ui.printExitMessage(); System.exit(0); @@ -245,9 +273,7 @@ public static void parseCommand(String input, BookList books) { throw new UnsupportedCommandException("Sorry but that is not a valid command. " + "Please try again or type: help"); } - } catch (NumberFormatException e) { - throw new InvalidBookIndexException("Book index must be an integer."); - } catch (IndexOutOfBoundsException e) { + } catch (IndexOutOfBoundsException e) { throw new BookNotFoundException("Book not found at the provided index."); } catch (InvalidCommandArgumentException e) { LOGGER.log(Level.WARNING, "Invalid command argument: {0}", e.getMessage()); diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 73f1e15a16..092cdce164 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -48,6 +48,10 @@ public static void setGenreBookMessage(String title, String genre) { System.out.println("okii categorised [" + title + "] as [" + genre + "]"); System.out.println("remember to read it soon...."); } + public static void setRatingBookMessage(String title, int rating) { + System.out.println("okii set rating for [" + title + "] as [" + rating +"]"); + System.out.println("remember to read it soon...."); + } public static void removeBookMessage(int index, BookList books) { System.out.println("alright.. i've removed " + books.getBook(index).getTitle() + " from the list."); } From c1b0af696872a772fbd0b7040297327749a8faa8 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Tue, 2 Apr 2024 17:16:37 +0800 Subject: [PATCH 119/311] Add view summary command --- .../java/seedu/bookbuddy/BookDetails.java | 19 +++++++++++++-- src/main/java/seedu/bookbuddy/Parser.java | 24 +++++++++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookDetails.java b/src/main/java/seedu/bookbuddy/BookDetails.java index 174eb6c0ab..ff04c55579 100644 --- a/src/main/java/seedu/bookbuddy/BookDetails.java +++ b/src/main/java/seedu/bookbuddy/BookDetails.java @@ -5,8 +5,6 @@ import java.util.stream.Collectors; public class BookDetails { - protected String summary; - /** * Sets the rating of the book at the specified index. @@ -51,6 +49,23 @@ public static void printBooksByRating(BookList books) { } } + /** + * Prints the summary of a book given its index. + */ + public static void printSummaryByIndex(BookList books, int index) throws IndexOutOfBoundsException{ + if (books.books.isEmpty()) { + System.out.println("The list is empty. Add books by 'add [book]'"); + return; + } + + if (index < 0 || index > books.getSize()) { + throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); + } + + System.out.println("This is the summary that you gave: " + books.getBook(index).getSummary()); + + } + /** * Sets the summary of the book at the specified index. * diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index efb3312738..d3c73f3826 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -23,7 +23,8 @@ public class Parser { public static final String FIND_COMMAND = "find"; public static final String LABEL_COMMAND = "label"; public static final String GENRE_COMMAND = "set-genre"; - public static final String SUMMARY_COMMAND = "give-summary"; + public static final String SET_SUMMARY_COMMAND = "give-summary"; + public static final String GET_SUMMARY_COMMAND = "read-summary"; public static final String DISPLAY_COMMAND = "display"; public static final String RATING_COMMAND = "rate"; public static final String PRINT_ORDERED_COMMAND = "listrated"; @@ -132,7 +133,7 @@ public static void parseCommand(String input, BookList books) { System.out.println("An error occurred while setting the label: " + e.getMessage()); } break; - case SUMMARY_COMMAND: + case SET_SUMMARY_COMMAND: assert inputArray.length == 2 : "Command requires additional arguments"; if (inputArray.length < 2) { LOGGER.log(Level.WARNING, "The summary Command requires a book index and summary", inputArray); @@ -159,6 +160,25 @@ public static void parseCommand(String input, BookList books) { System.out.println("An error occurred while setting the label: " + e.getMessage()); } break; + case GET_SUMMARY_COMMAND: + assert inputArray.length == 1 : "Command requires additional arguments"; + if (inputArray.length < 2) { + LOGGER.log(Level.WARNING, "The get-summary Command requires a book index", inputArray); + throw new InvalidCommandArgumentException("The summary command requires a book index."); + } + try { + index = Integer.parseInt(inputArray[1]); + assert index >= 0 : "Index should be non-negative"; + BookDetails.printSummaryByIndex(books, index); + } + catch (InvalidCommandArgumentException e) { + System.out.println(e.getMessage()); + } catch (IndexOutOfBoundsException e) { + System.out.println("Invalid book index. Please enter a valid index."); + } catch (Exception e) { + System.out.println("An error occurred while setting the label: " + e.getMessage()); + } + break; case GENRE_COMMAND: try { if (inputArray.length < 2) { From 9523857f523dc6448cc6aae36d2f455e2d88be50 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Tue, 2 Apr 2024 17:21:33 +0800 Subject: [PATCH 120/311] Add summary to display comand --- src/main/java/seedu/bookbuddy/BookDetails.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/seedu/bookbuddy/BookDetails.java b/src/main/java/seedu/bookbuddy/BookDetails.java index ff04c55579..ed80c8c519 100644 --- a/src/main/java/seedu/bookbuddy/BookDetails.java +++ b/src/main/java/seedu/bookbuddy/BookDetails.java @@ -137,6 +137,7 @@ public static void displayDetails(int index, BookList books) throws IndexOutOfBo System.out.println("Label: " + books.getBook(index).getLabel()); System.out.println("Genre: " + books.getBook(index).getGenre()); System.out.println("Rating: " + books.getBook(index).getRating()); + System.out.println("Summary: " + books.getBook(index).getSummary()); } } From 716e1548f9031341045b439d30ea62f4cf5c7f71 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Tue, 2 Apr 2024 17:31:12 +0800 Subject: [PATCH 121/311] Abstract BookDetails into concise accurate classes. Abstract exception handling in parser class --- src/main/java/seedu/bookbuddy/BookBuddy.java | 2 +- .../java/seedu/bookbuddy/BookDetails.java | 127 ------- src/main/java/seedu/bookbuddy/BookList.java | 4 + src/main/java/seedu/bookbuddy/Parser.java | 311 +++++++----------- .../bookbuddy/bookdetails/BookDisplay.java | 25 ++ .../bookbuddy/bookdetails/BookGenre.java | 25 ++ .../bookbuddy/bookdetails/BookLabel.java | 25 ++ .../bookbuddy/bookdetails/BookRating.java | 53 +++ .../bookbuddy/bookdetails/BookSummary.java | 24 ++ .../java/seedu/bookbuddy/BookDetailsTest.java | 10 +- 10 files changed, 289 insertions(+), 317 deletions(-) delete mode 100644 src/main/java/seedu/bookbuddy/BookDetails.java create mode 100644 src/main/java/seedu/bookbuddy/bookdetails/BookDisplay.java create mode 100644 src/main/java/seedu/bookbuddy/bookdetails/BookGenre.java create mode 100644 src/main/java/seedu/bookbuddy/bookdetails/BookLabel.java create mode 100644 src/main/java/seedu/bookbuddy/bookdetails/BookRating.java create mode 100644 src/main/java/seedu/bookbuddy/bookdetails/BookSummary.java diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index e6ac8daa00..c9043bc508 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -17,7 +17,7 @@ public class BookBuddy { - static final Logger LOGGER = getLogger(BookBuddy.class.getName()); + public static final Logger LOGGER = getLogger(BookBuddy.class.getName()); static { try { diff --git a/src/main/java/seedu/bookbuddy/BookDetails.java b/src/main/java/seedu/bookbuddy/BookDetails.java deleted file mode 100644 index 174eb6c0ab..0000000000 --- a/src/main/java/seedu/bookbuddy/BookDetails.java +++ /dev/null @@ -1,127 +0,0 @@ -package seedu.bookbuddy; - -import java.util.Comparator; -import java.util.List; -import java.util.stream.Collectors; - -public class BookDetails { - protected String summary; - - - /** - * Sets the rating of the book at the specified index. - * - * @param index The index of the book in the list. - * @param rating The rating to set for the book. - * @throws IndexOutOfBoundsException if the index is out of range. - * @throws IllegalArgumentException if the rating is not between 1 and 5. - */ - public static void setBookRatingByIndex(int index, int rating, BookList books) - throws IndexOutOfBoundsException, IllegalArgumentException { - if (index < 0 || index > books.getSize()) { - throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); - } - if (rating < 1 || rating > 5) { - throw new IllegalArgumentException("Rating must be between 1 and 5."); - } - books.getBook(index).setRating(rating); - String title = books.getBook(index).getTitle(); - Ui.setRatingBookMessage(title, rating); - } - - - /** - * Prints all books sorted by rating in descending order. - */ - public static void printBooksByRating(BookList books) { - if (books.books.isEmpty()) { - System.out.println("The list is empty. Add books by 'add [book]'"); - return; - } - - System.out.println("Books sorted by rating:"); - - List sortedBooks = books.books.stream() - .sorted(Comparator.comparingInt(Book::getRating).reversed()) - .collect(Collectors.toList()); - - for (Book book : sortedBooks) { - String rating = book.getRating() >= 0 ? String.valueOf(book.getRating()) : "Not Rated"; - System.out.println(book.getTitle() + " - " + rating); - } - } - - /** - * Sets the summary of the book at the specified index. - * - * @param index The index of the book in the list. - * @param summary The summary to set for the book. - * @throws IndexOutOfBoundsException if the index is out of range. - */ - public static void setBookSummaryByIndex(int index, String summary, BookList books) - throws IndexOutOfBoundsException { - if (index < 0 || index > books.getSize()) { - throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); - } - books.getBook(index).setSummary(summary); - String title = books.getBook(index).getTitle(); - Ui.summaryBookMessage(title, summary); - } - - /** - * Sets the label of the book at the specified index. - * - * @param index The index of the book in the list. - * @param label The label to set for the book. - * @throws IndexOutOfBoundsException if the index is out of range. - */ - public static void setBookLabelByIndex(int index, String label, BookList books) throws IndexOutOfBoundsException { - // Check for valid index - if (index < 0 || index > books.getSize()) { - throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index. nows"); - } - - // Set the label for the book at the specified index - books.getBook(index).setLabel(label); - String title = books.getBook(index).getTitle(); - Ui.labelBookMessage(title, label); - } - - /** - * Sets the genre of the book at the specified index. - * - * @param index The index of the book in the list. - * @param genre The genre to set for the book. - * @throws IndexOutOfBoundsException if the index is out of range. - */ - public static void setBookGenreByIndex(int index, String genre, BookList books) throws IndexOutOfBoundsException { - // Check for valid index - if (index < 0 || index > books.getSize()) { - throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); - } - - // Set the genre for the book at the specified index - books.getBook(index).setGenre(genre); - String title = books.getBook(index).getTitle(); - Ui.setGenreBookMessage(title, genre); - } - - /** - * Prints the details of the book at the specified index. - * @param index The index of hte book in the list. - * @throws IndexOutOfBoundsException if the index is out of range. - */ - public static void displayDetails(int index, BookList books) throws IndexOutOfBoundsException { - if (index < 0 || index > books.getSize()) { - throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); - } - - System.out.println("Here are the details of your book:"); - System.out.println("Title: " + books.getBook(index).getTitle()); - System.out.println("Status: " + (books.getBook(index).isRead ? "Read" : "Unread")); - System.out.println("Label: " + books.getBook(index).getLabel()); - System.out.println("Genre: " + books.getBook(index).getGenre()); - System.out.println("Rating: " + books.getBook(index).getRating()); - } - -} diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index 04e6e46723..79cb81d4e2 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -21,6 +21,10 @@ public class BookList { "Mystery", "Science Fiction", "Fantasy")); protected ArrayList books; + // Public getter method for the books field + public List getBooks() { + return this.books; + } /** * Constructs a new BookList instance with an empty list. */ diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index efb3312738..5e1269af67 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -1,8 +1,12 @@ package seedu.bookbuddy; -import exceptions.BookNotFoundException; import exceptions.InvalidCommandArgumentException; import exceptions.UnsupportedCommandException; +import seedu.bookbuddy.bookdetails.BookDisplay; +import seedu.bookbuddy.bookdetails.BookGenre; +import seedu.bookbuddy.bookdetails.BookLabel; +import seedu.bookbuddy.bookdetails.BookRating; +import seedu.bookbuddy.bookdetails.BookSummary; import java.util.Scanner; import java.util.logging.Level; @@ -26,7 +30,37 @@ public class Parser { public static final String SUMMARY_COMMAND = "give-summary"; public static final String DISPLAY_COMMAND = "display"; public static final String RATING_COMMAND = "rate"; - public static final String PRINT_ORDERED_COMMAND = "listrated"; + public static final String PRINT_ORDERED_COMMAND = "list-rated"; + private static void validateCommandArguments(String[] inputArray, int requiredArgs, String errorMessage) + throws InvalidCommandArgumentException { + if (inputArray.length < requiredArgs) { + LOGGER.log(Level.WARNING, errorMessage, inputArray); + throw new InvalidCommandArgumentException(errorMessage); + } + } + + private static void handleException(Exception e, String command, String[] inputArray) { + if (e instanceof UnsupportedCommandException) { + LOGGER.log(Level.WARNING, "Command is invalid: {0}", e.getMessage()); + throw (UnsupportedCommandException) e; // rethrow if you need to propagate it + } else if (e instanceof NumberFormatException) { + System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + + "Please enter a valid numeric index. Type 'list' to view list of books."); + } else if (e instanceof IndexOutOfBoundsException) { + System.out.println("Invalid book index. Please enter a valid index."); + } else if (e instanceof InvalidCommandArgumentException) { + LOGGER.log(Level.WARNING, "Invalid command argument: {0}", new Object[]{e.getMessage()}); + System.out.println(e.getMessage()); + } else if (e instanceof IllegalArgumentException) { + System.out.println(e.getMessage()); + } else { + LOGGER.log(Level.SEVERE, "An unexpected error occurred while executing {0}: {1}", + new Object[]{command, e.getMessage()}); + System.out.println("An unexpected error occurred while executing " + command + + ". Please contact support."); + } + } + /** * Scans the user input for valid commands and handles them accordingly. @@ -43,59 +77,35 @@ public static void parseCommand(String input, BookList books) { switch (command) { case ADD_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; - - if (inputArray.length < 2) { - LOGGER.log(Level.WARNING, "The add Command requires a book title", inputArray); - throw new InvalidCommandArgumentException("The add command requires a book title."); - } + validateCommandArguments(inputArray, 2, "The add " + + "Command requires a book title"); books.addBook(inputArray[1]); break; case REMOVE_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; - if (inputArray.length < 2) { - LOGGER.log(Level.WARNING, "The remove Command requires a book index", inputArray); - throw new InvalidCommandArgumentException("The remove command requires a book index."); - } - try { - index = Integer.parseInt(inputArray[1]); - books.deleteBook(index); - } catch (NumberFormatException e) { - System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + - "Please enter a valid numeric index."); - } + validateCommandArguments(inputArray, 2, "The remove " + + "Command requires a book index"); + index = Integer.parseInt(inputArray[1]); + books.deleteBook(index); break; case LIST_COMMAND: books.printAllBooks(); break; case MARK_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; - if (inputArray.length < 2) { - LOGGER.log(Level.WARNING, "The mark Command requires a book index", inputArray); - throw new InvalidCommandArgumentException("The mark command requires a book index."); - } - try { - index = Integer.parseInt(inputArray[1]); - assert index >= 0 : "Index should be non-negative"; - books.markDoneByIndex(index); - } catch (NumberFormatException e) { - System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + - "Please enter a valid numeric index."); - } + validateCommandArguments(inputArray, 2, "The mark " + + "Command requires a book index"); + index = Integer.parseInt(inputArray[1]); + assert index >= 0 : "Index should be non-negative"; + books.markDoneByIndex(index); break; case UNMARK_COMMAND: assert inputArray.length == 2 : "Command requires additional arguments"; - if (inputArray.length < 2) { - LOGGER.log(Level.WARNING, "The unmark Command requires a book index", inputArray); - throw new InvalidCommandArgumentException("The unmark command requires a book index."); - } - try { - index = Integer.parseInt(inputArray[1]); - assert index >= 0 : "Index should be non-negative"; - books.markUndoneByIndex(index); - } catch (NumberFormatException e) { - System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + - "Please enter a valid numeric index."); - } + validateCommandArguments(inputArray, 2, "The unmark " + + "Command requires a book index"); + index = Integer.parseInt(inputArray[1]); + assert index >= 0 : "Index should be non-negative"; + books.markUndoneByIndex(index); break; case HELP_COMMAND: Ui.helpMessage(); @@ -105,164 +115,104 @@ public static void parseCommand(String input, BookList books) { break; case LABEL_COMMAND: assert inputArray.length == 2 : "Command requires additional arguments"; - if (inputArray.length < 2) { - LOGGER.log(Level.WARNING, "The Label Command requires a book index and label", inputArray); - throw new InvalidCommandArgumentException("The label command requires a book index and label."); - } + validateCommandArguments(inputArray,2, "The Label " + + "Command requires a book index and label"); String[] labelMessageParts = inputArray[1].split(" ", 2); // Split the message into index and label message assert labelMessageParts.length == 2 : "Command requires an index and a label message"; - if (labelMessageParts.length < 2) { - throw new InvalidCommandArgumentException("You need to have a label message"); - } - try { - index = Integer.parseInt(labelMessageParts[0]); - assert index >= 0 : "Index should be non-negative"; - String label = labelMessageParts[1]; - System.out.println(index); - BookDetails.setBookLabelByIndex(index, label, books); - } catch (NumberFormatException e) { - System.out.println("Invalid input: " + labelMessageParts[0] - + " is not a valid number. Please enter a valid numeric index."); - } catch (InvalidCommandArgumentException e) { - System.out.println(e.getMessage()); - } catch (IndexOutOfBoundsException e) { - System.out.println("Invalid book index. Please enter a valid index"); - } catch (Exception e) { - System.out.println("An error occurred while setting the label: " + e.getMessage()); - } + validateCommandArguments(labelMessageParts, 2, "You " + + "need to have a label message"); + index = Integer.parseInt(labelMessageParts[0]); + assert index >= 0 : "Index should be non-negative"; + String label = labelMessageParts[1]; + System.out.println(index); + BookLabel.setBookLabelByIndex(index, label, books); break; case SUMMARY_COMMAND: assert inputArray.length == 2 : "Command requires additional arguments"; - if (inputArray.length < 2) { - LOGGER.log(Level.WARNING, "The summary Command requires a book index and summary", inputArray); - throw new InvalidCommandArgumentException("The summary command requires a book index and summary."); - } + validateCommandArguments(inputArray,2, "The summary " + + "Command requires a book index and summary"); String[] summaryMessageParts = inputArray[1].split(" ", 2); assert summaryMessageParts.length == 2 : "Command requires an index and a label message"; - if (summaryMessageParts.length < 2) { - throw new InvalidCommandArgumentException("You need to have a summary message"); - } - try { - index = Integer.parseInt(summaryMessageParts[0]); - assert index >= 0 : "Index should be non-negative"; - String summary = summaryMessageParts[1]; - BookDetails.setBookSummaryByIndex(index, summary, books); - } catch (NumberFormatException e) { - System.out.println("Invalid input: " + summaryMessageParts[0] - + " is not a valid number. Please enter a valid numeric index. here"); - } catch (InvalidCommandArgumentException e) { - System.out.println(e.getMessage()); - } catch (IndexOutOfBoundsException e) { - System.out.println("Invalid book index. Please enter a valid index."); - } catch (Exception e) { - System.out.println("An error occurred while setting the label: " + e.getMessage()); - } + validateCommandArguments(summaryMessageParts,2, "You need " + + "to have a summary message"); + index = Integer.parseInt(summaryMessageParts[0]); + assert index >= 0 : "Index should be non-negative"; + String summary = summaryMessageParts[1]; + BookSummary.setBookSummaryByIndex(index, summary, books); break; case GENRE_COMMAND: - try { - if (inputArray.length < 2) { - throw new InvalidCommandArgumentException("Usage: set-genre [index]"); - } - - index = Integer.parseInt(inputArray[1]); - if (index < 0 || index > books.getSize()) { - throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index. " + - "Type 'list' to view the list of books."); - } - System.out.println("Available genres:"); - for (int i = 0; i < BookList.availableGenres.size(); i++) { - System.out.println((i + 1) + ". " + BookList.availableGenres.get(i)); - } - System.out.println((BookList.availableGenres.size() + 1) + ". Add a new genre"); - - System.out.println("Enter the number for the desired genre, or add a new one:"); - Scanner scanner = new Scanner(System.in); - - String selectedGenre = null; - while (selectedGenre == null) { - while (!scanner.hasNextInt()) { // Ensure the next input is an integer - String newInput = scanner.nextLine(); - if ("exit".equalsIgnoreCase(newInput)) { - return; // Exit the command if user types 'exit' - } else { - System.out.println("Invalid input. Please enter a valid number or type 'exit'" + - " to cancel."); - } - } + validateCommandArguments(inputArray, 2, "The set-genre " + + "Command requires a book index."); + index = Integer.parseInt(inputArray[1]); + if (index < 0 || index > books.getSize()) { + throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index. " + + "Type 'list' to view the list of books."); + } + System.out.println("Available genres:"); + for (int i = 0; i < BookList.availableGenres.size(); i++) { + System.out.println((i + 1) + ". " + BookList.availableGenres.get(i)); + } + System.out.println((BookList.availableGenres.size() + 1) + ". Add a new genre"); - int genreSelection = scanner.nextInt(); - scanner.nextLine(); // Consume the newline after the number + System.out.println("Enter the number for the desired genre, or add a new one:"); + Scanner scanner = new Scanner(System.in); - if (genreSelection == BookList.availableGenres.size() + 1) { - System.out.println("Enter the new genre:"); - selectedGenre = scanner.nextLine(); - BookList.availableGenres.add(selectedGenre); // Add the new genre to the list - } else if (genreSelection > 0 && genreSelection <= BookList.availableGenres.size()) { - selectedGenre = BookList.availableGenres.get(genreSelection - 1); + String selectedGenre = null; + while (selectedGenre == null) { + while (!scanner.hasNextInt()) { // Ensure the next input is an integer + String newInput = scanner.nextLine(); + if ("exit".equalsIgnoreCase(newInput)) { + return; // Exit the command if user types 'exit' } else { - System.out.println("Invalid selection. Please enter a valid number " + - "or type 'exit' to cancel."); - // No need for the nextLine or parsing logic here, the while loop will continue + System.out.println("Invalid input. Please enter a valid number or type 'exit'" + + " to cancel."); } } - BookDetails.setBookGenreByIndex(index, selectedGenre, books); - System.out.println("Genre set to " + selectedGenre + " for book at index " + index); - } catch (NumberFormatException e) { - System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + - "Please enter a valid numeric index. Type 'list' to view the list of books.") ; - } catch (InvalidCommandArgumentException | IndexOutOfBoundsException e) { - System.out.println(e.getMessage()); - } catch (Exception e) { - System.out.println("An error occurred while setting the genre: " + e.getMessage()); + int genreSelection = scanner.nextInt(); + scanner.nextLine(); // Consume the newline after the number + + if (genreSelection == BookList.availableGenres.size() + 1) { + System.out.println("Enter the new genre:"); + selectedGenre = scanner.nextLine(); + BookList.availableGenres.add(selectedGenre); // Add the new genre to the list + } else if (genreSelection > 0 && genreSelection <= BookList.availableGenres.size()) { + selectedGenre = BookList.availableGenres.get(genreSelection - 1); + } else { + System.out.println("Invalid selection. Please enter a valid number " + + "or type 'exit' to cancel."); + // No need for the nextLine or parsing logic here, the while loop will continue + } } + + BookGenre.setBookGenreByIndex(index, selectedGenre, books); + System.out.println("Genre set to " + selectedGenre + " for book at index " + index); + break; case RATING_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; - if (inputArray.length < 2) { - LOGGER.log(Level.WARNING, "The rating Command requires a book index", inputArray); - throw new InvalidCommandArgumentException("The rating command requires a book index."); - } - try { - String[] ratingParts = inputArray[1].split(" ", 2); - // Split the message into index and label message - assert ratingParts.length == 2 : "Command requires an index and a rating"; - if (ratingParts.length < 2) { - throw new InvalidCommandArgumentException("You need to have a book index and a rating"); - } - index = Integer.parseInt(ratingParts[0]); - int rating = Integer.parseInt(ratingParts[1]); - BookDetails.setBookRatingByIndex(index, rating, books); - } catch (NumberFormatException e) { - System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + - "Please enter a valid numeric index."); - } catch (IndexOutOfBoundsException e) { - System.out.println("Invalid book index. Please enter a valid index."); - } catch (IllegalArgumentException e) { - System.out.println(e.getMessage()); + validateCommandArguments(inputArray, 2, "The rating " + + "command requires a book index."); + String[] ratingParts = inputArray[1].split(" ", 2); + // Split the message into index and label message + assert ratingParts.length == 2 : "Command requires an index and a rating"; + if (ratingParts.length < 2) { + throw new InvalidCommandArgumentException("You need to have a book index and a rating"); } + index = Integer.parseInt(ratingParts[0]); + int rating = Integer.parseInt(ratingParts[1]); + BookRating.setBookRatingByIndex(index, rating, books); break; case DISPLAY_COMMAND: assert inputArray.length >= 2 : "Command requires additional arguments"; - if (inputArray.length < 2) { - LOGGER.log(Level.WARNING, "The display Command requires a book index", inputArray); - throw new InvalidCommandArgumentException("The display command requires a book index."); - } - try { - index = Integer.parseInt(inputArray[1]); - BookDetails.displayDetails(index, books); - } catch (NumberFormatException e) { - System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + - "Please enter a valid numeric index."); - } catch (IndexOutOfBoundsException e) { - System.out.println("Invalid book index. Please enter a valid index."); - } catch (InvalidCommandArgumentException e) { - System.out.println(e.getMessage()); - } + validateCommandArguments(inputArray,2 , "The display " + + "Command requires a book index"); + index = Integer.parseInt(inputArray[1]); + BookDisplay.displayDetails(index, books); break; case PRINT_ORDERED_COMMAND: - BookDetails.printBooksByRating(books); + BookRating.printBooksByRating(books); break; case EXIT_COMMAND: Ui.printExitMessage(); @@ -273,17 +223,8 @@ public static void parseCommand(String input, BookList books) { throw new UnsupportedCommandException("Sorry but that is not a valid command. " + "Please try again or type: help"); } - } catch (IndexOutOfBoundsException e) { - throw new BookNotFoundException("Book not found at the provided index."); - } catch (InvalidCommandArgumentException e) { - LOGGER.log(Level.WARNING, "Invalid command argument: {0}", e.getMessage()); - throw e; - } catch (UnsupportedCommandException e) { - LOGGER.log(Level.WARNING, "Command is invalid", e.getMessage()); - throw e; - } catch (Exception e) { // Generic catch block for any other exceptions - LOGGER.log(Level.SEVERE, "An unexpected error occurred: {0}", e.getMessage()); - System.out.println("An unexpected error occurred. Please contact support."); + } catch (Exception e) { + handleException(e, command, inputArray); } } } diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetails/BookDisplay.java new file mode 100644 index 0000000000..a3641f6581 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/bookdetails/BookDisplay.java @@ -0,0 +1,25 @@ +package seedu.bookbuddy.bookdetails; + +import seedu.bookbuddy.BookList; + +public class BookDisplay { + //@@author joshuahoky + /** + * Prints the details of the book at the specified index. + * + * @param index The index of hte book in the list. + * @throws IndexOutOfBoundsException if the index is out of range. + */ + public static void displayDetails(int index, BookList books) throws IndexOutOfBoundsException { + if (index < 0 || index > books.getSize()) { + throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); + } + + System.out.println("Here are the details of your book:"); + System.out.println("Title: " + books.getBook(index).getTitle()); + System.out.println("Status: " + (books.getBook(index).isRead() ? "Read" : "Unread")); + System.out.println("Label: " + books.getBook(index).getLabel()); + System.out.println("Genre: " + books.getBook(index).getGenre()); + System.out.println("Rating: " + books.getBook(index).getRating()); + } +} diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookGenre.java b/src/main/java/seedu/bookbuddy/bookdetails/BookGenre.java new file mode 100644 index 0000000000..ee21207b12 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/bookdetails/BookGenre.java @@ -0,0 +1,25 @@ +package seedu.bookbuddy.bookdetails; + +import seedu.bookbuddy.BookList; +import seedu.bookbuddy.Ui; + +public class BookGenre { + /** + * Sets the genre of the book at the specified index. + * + * @param index The index of the book in the list. + * @param genre The genre to set for the book. + * @throws IndexOutOfBoundsException if the index is out of range. + */ + public static void setBookGenreByIndex(int index, String genre, BookList books) throws IndexOutOfBoundsException { + // Check for valid index + if (index < 0 || index > books.getSize()) { + throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); + } + + // Set the genre for the book at the specified index + books.getBook(index).setGenre(genre); + String title = books.getBook(index).getTitle(); + Ui.setGenreBookMessage(title, genre); + } +} diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookLabel.java b/src/main/java/seedu/bookbuddy/bookdetails/BookLabel.java new file mode 100644 index 0000000000..d6dea68eac --- /dev/null +++ b/src/main/java/seedu/bookbuddy/bookdetails/BookLabel.java @@ -0,0 +1,25 @@ +package seedu.bookbuddy.bookdetails; + +import seedu.bookbuddy.BookList; +import seedu.bookbuddy.Ui; + +public class BookLabel { + /** + * Sets the label of the book at the specified index. + * + * @param index The index of the book in the list. + * @param label The label to set for the book. + * @throws IndexOutOfBoundsException if the index is out of range. + */ + public static void setBookLabelByIndex(int index, String label, BookList books) throws IndexOutOfBoundsException { + // Check for valid index + if (index < 0 || index > books.getSize()) { + throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index. nows"); + } + + // Set the label for the book at the specified index + books.getBook(index).setLabel(label); + String title = books.getBook(index).getTitle(); + Ui.labelBookMessage(title, label); + } +} diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookRating.java b/src/main/java/seedu/bookbuddy/bookdetails/BookRating.java new file mode 100644 index 0000000000..db89e75e4c --- /dev/null +++ b/src/main/java/seedu/bookbuddy/bookdetails/BookRating.java @@ -0,0 +1,53 @@ +package seedu.bookbuddy.bookdetails; + +import seedu.bookbuddy.Book; +import seedu.bookbuddy.BookList; +import seedu.bookbuddy.Ui; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public class BookRating { + /** + * Prints all books sorted by rating in descending order. + */ + public static void printBooksByRating(BookList books) { + if (books.getBooks().isEmpty()) { + System.out.println("The list is empty. Add books by 'add [book]'"); + return; + } + + System.out.println("Books sorted by rating:"); + + List sortedBooks = books.getBooks().stream() + .sorted(Comparator.comparingInt(Book::getRating).reversed()) + .collect(Collectors.toList()); + + for (Book book : sortedBooks) { + String rating = book.getRating() >= 0 ? String.valueOf(book.getRating()) : "Not Rated"; + System.out.println(book.getTitle() + " - " + rating); + } + } + + /** + * Sets the rating of the book at the specified index. + * + * @param index The index of the book in the list. + * @param rating The rating to set for the book. + * @throws IndexOutOfBoundsException if the index is out of range. + * @throws IllegalArgumentException if the rating is not between 1 and 5. + */ + public static void setBookRatingByIndex(int index, int rating, BookList books) + throws IndexOutOfBoundsException, IllegalArgumentException { + if (index < 0 || index > books.getSize()) { + throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); + } + if (rating < 1 || rating > 5) { + throw new IllegalArgumentException("Rating must be between 1 and 5."); + } + books.getBook(index).setRating(rating); + String title = books.getBook(index).getTitle(); + Ui.setRatingBookMessage(title, rating); + } +} diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookSummary.java b/src/main/java/seedu/bookbuddy/bookdetails/BookSummary.java new file mode 100644 index 0000000000..929cd1021e --- /dev/null +++ b/src/main/java/seedu/bookbuddy/bookdetails/BookSummary.java @@ -0,0 +1,24 @@ +package seedu.bookbuddy.bookdetails; + +import seedu.bookbuddy.BookList; +import seedu.bookbuddy.Ui; + +public class BookSummary { + //@@author lordgareth10 + /** + * Sets the summary of the book at the specified index. + * + * @param index The index of the book in the list. + * @param summary The summary to set for the book. + * @throws IndexOutOfBoundsException if the index is out of range. + */ + public static void setBookSummaryByIndex(int index, String summary, BookList books) + throws IndexOutOfBoundsException { + if (index < 0 || index > books.getSize()) { + throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); + } + books.getBook(index).setSummary(summary); + String title = books.getBook(index).getTitle(); + Ui.summaryBookMessage(title, summary); + } +} diff --git a/src/test/java/seedu/bookbuddy/BookDetailsTest.java b/src/test/java/seedu/bookbuddy/BookDetailsTest.java index 7216c36fde..dc734ff6c2 100644 --- a/src/test/java/seedu/bookbuddy/BookDetailsTest.java +++ b/src/test/java/seedu/bookbuddy/BookDetailsTest.java @@ -1,6 +1,8 @@ package seedu.bookbuddy; import org.junit.jupiter.api.Test; +import seedu.bookbuddy.bookdetails.BookGenre; +import seedu.bookbuddy.bookdetails.BookLabel; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -10,9 +12,9 @@ public void testSetBookLabelByIndex() { BookList books = new BookList(); books.addBook("The Great Gatsby"); books.addBook("Geronimo Stilton"); - BookDetails.setBookLabelByIndex(1, "Great Classic", books); + BookLabel.setBookLabelByIndex(1, "Great Classic", books); assertEquals("Great Classic" ,books.getBook(1).getLabel()); - BookDetails.setBookLabelByIndex(2, "Great Classic", books); + BookLabel.setBookLabelByIndex(2, "Great Classic", books); assertEquals("Great Classic" ,books.getBook(2).getLabel()); } @@ -21,9 +23,9 @@ public void testSetBookGenreByIndex() { BookList books = new BookList(); books.addBook("The Great Gatsby"); books.addBook("Geronimo Stilton"); - BookDetails.setBookGenreByIndex(1, "Classic", books); + BookGenre.setBookGenreByIndex(1, "Classic", books); assertEquals("Classic" ,books.getBook(1).getGenre()); - BookDetails.setBookGenreByIndex(2, "Fantasy", books); + BookGenre.setBookGenreByIndex(2, "Fantasy", books); assertEquals("Fantasy" ,books.getBook(2).getGenre()); } } From 066851d95b16d714673acdb948e6b4453b5518d6 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Tue, 2 Apr 2024 17:52:46 +0800 Subject: [PATCH 122/311] Checkstyle fixes - order of variable declaration --- src/main/java/seedu/bookbuddy/BookList.java | 8 ++++---- src/main/java/seedu/bookbuddy/Parser.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index 79cb81d4e2..f294dce319 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -21,16 +21,16 @@ public class BookList { "Mystery", "Science Fiction", "Fantasy")); protected ArrayList books; - // Public getter method for the books field - public List getBooks() { - return this.books; - } /** * Constructs a new BookList instance with an empty list. */ public BookList() { this.books = new ArrayList(); // Use ArrayList instead of array } + // Public getter method for the books field + public List getBooks() { + return this.books; + } /** * Returns the current size of the book list. diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index 5e1269af67..0301dfd429 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -42,7 +42,7 @@ private static void validateCommandArguments(String[] inputArray, int requiredAr private static void handleException(Exception e, String command, String[] inputArray) { if (e instanceof UnsupportedCommandException) { LOGGER.log(Level.WARNING, "Command is invalid: {0}", e.getMessage()); - throw (UnsupportedCommandException) e; // rethrow if you need to propagate it + System.out.println(e.getMessage()); } else if (e instanceof NumberFormatException) { System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + "Please enter a valid numeric index. Type 'list' to view list of books."); From 4c5886b9ee9b1c76bd5a4bfd1967f970b022bb11 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Tue, 2 Apr 2024 18:02:19 +0800 Subject: [PATCH 123/311] Update Parsertest --- src/test/java/seedu/bookbuddy/ParserTest.java | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index 5f48fd5259..4d80fb60ca 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -1,7 +1,7 @@ package seedu.bookbuddy; -import exceptions.InvalidCommandArgumentException; -import exceptions.UnsupportedCommandException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.io.ByteArrayInputStream; @@ -9,14 +9,23 @@ import java.io.InputStream; import java.io.PrintStream; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; public class ParserTest { + private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); + private final PrintStream originalOut = System.out; + + @BeforeEach + public void setUpStreams() { + System.setOut(new PrintStream(outContent)); + } + + @AfterEach + public void restoreStreams() { + System.setOut(originalOut); + } @Test void testParser() { BookList books = new BookList(); @@ -100,8 +109,11 @@ void parseGenreCommand() { void parseInvalidAddCommandThrowsException() { BookList books = new BookList(); String input = "add"; // No book title provided - assertThrows(InvalidCommandArgumentException.class, - () -> Parser.parseCommand(input, books), "The add command requires a book title."); + Parser.parseCommand(input, books); // Execute the command that should trigger the error message + + String expectedOutput = "The add Command requires a book title"; + assertTrue(outContent.toString().contains(expectedOutput), + "Expected output message not found in the console output."); } @Test @@ -122,12 +134,23 @@ void parseInvalidRemoveCommandPrintsError() { System.setOut(System.out); // Reset standard out } +// @Test +// void parseUnsupportedCommandThrowsException() { +// BookList books = new BookList(); +// String input = "Geronimo Stilton"; // Completely unsupported command +// assertThrows(UnsupportedCommandException.class, +// () -> Parser.parseCommand(input, books), "Sorry but that is not a valid command. Please try again"); +// } @Test void parseUnsupportedCommandThrowsException() { BookList books = new BookList(); String input = "Geronimo Stilton"; // Completely unsupported command - assertThrows(UnsupportedCommandException.class, - () -> Parser.parseCommand(input, books), "Sorry but that is not a valid command. Please try again"); + Parser.parseCommand(input, books); // Execute the command + + // Check that the specific error message is printed to the console + String expectedMessage = "Sorry but that is not a valid command. Please try again"; + assertTrue(outContent.toString().contains(expectedMessage), + "Expected message not found in the console output."); } } From f99f2afd7731aa31f0acd9fea7b2cd7f1c3ad103 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Tue, 2 Apr 2024 18:04:59 +0800 Subject: [PATCH 124/311] Checkstyle fixes - single imports --- src/test/java/seedu/bookbuddy/ParserTest.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index 4d80fb60ca..f93ed56245 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -9,8 +9,9 @@ import java.io.InputStream; import java.io.PrintStream; -import static org.junit.jupiter.api.Assertions.*; - +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; public class ParserTest { @@ -134,20 +135,11 @@ void parseInvalidRemoveCommandPrintsError() { System.setOut(System.out); // Reset standard out } -// @Test -// void parseUnsupportedCommandThrowsException() { -// BookList books = new BookList(); -// String input = "Geronimo Stilton"; // Completely unsupported command -// assertThrows(UnsupportedCommandException.class, -// () -> Parser.parseCommand(input, books), "Sorry but that is not a valid command. Please try again"); -// } @Test void parseUnsupportedCommandThrowsException() { BookList books = new BookList(); String input = "Geronimo Stilton"; // Completely unsupported command Parser.parseCommand(input, books); // Execute the command - - // Check that the specific error message is printed to the console String expectedMessage = "Sorry but that is not a valid command. Please try again"; assertTrue(outContent.toString().contains(expectedMessage), "Expected message not found in the console output."); From caeb76b04759573b1d98b8ef34aafba72c3e082a Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Tue, 2 Apr 2024 19:37:15 +0800 Subject: [PATCH 125/311] Add FileStorage class to load, write and save data --- .gitignore | 1 + BookBuddy.log | 186 ------------------ src/main/java/seedu/bookbuddy/Book.java | 21 ++ src/main/java/seedu/bookbuddy/BookBuddy.java | 13 +- .../java/seedu/bookbuddy/BookDetails.java | 3 +- src/main/java/seedu/bookbuddy/BookList.java | 12 +- .../java/seedu/bookbuddy/FileStorage.java | 63 ++++++ src/main/java/seedu/bookbuddy/Parser.java | 6 - 8 files changed, 105 insertions(+), 200 deletions(-) delete mode 100644 BookBuddy.log create mode 100644 src/main/java/seedu/bookbuddy/FileStorage.java diff --git a/.gitignore b/.gitignore index b243409105..39b6ad8fb2 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ src/main/resources/docs/ .DS_Store *.iml bin/ +data/books.txt *.log *.lck *.log.1 diff --git a/BookBuddy.log b/BookBuddy.log deleted file mode 100644 index d458b9e62f..0000000000 --- a/BookBuddy.log +++ /dev/null @@ -1,186 +0,0 @@ -Mar 21, 2024 3:21:59 PM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 21, 2024 3:21:59 PM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 21, 2024 3:22:04 PM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 21, 2024 3:22:04 PM seedu.bookbuddy.BookBuddy getUserInput -WARNING: Unsupported command: drhf -Mar 21, 2024 3:23:35 PM seedu.bookbuddy.Parser parseCommand -WARNING: The add Command requires a book title -Mar 21, 2024 3:23:35 PM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 21, 2024 11:41:25 PM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 21, 2024 11:41:25 PM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 21, 2024 11:41:46 PM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 21, 2024 11:41:46 PM seedu.bookbuddy.BookBuddy getUserInput -WARNING: Unsupported command: 1 -Mar 21, 2024 11:56:27 PM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 21, 2024 11:56:27 PM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 21, 2024 11:56:39 PM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 21, 2024 11:56:39 PM seedu.bookbuddy.BookBuddy getUserInput -WARNING: Unsupported command: book2 -Mar 21, 2024 11:56:56 PM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 21, 2024 11:56:56 PM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 21, 2024 11:59:30 PM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 21, 2024 11:59:30 PM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 21, 2024 11:59:56 PM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 21, 2024 11:59:56 PM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 22, 2024 12:00:26 AM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 22, 2024 12:00:26 AM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 22, 2024 12:01:49 AM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 22, 2024 12:01:49 AM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 22, 2024 12:02:06 AM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 22, 2024 12:02:06 AM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 22, 2024 12:02:32 AM seedu.bookbuddy.Parser parseCommand -WARNING: The add Command requires a book title -Mar 22, 2024 12:02:32 AM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 22, 2024 12:04:40 AM seedu.bookbuddy.Parser parseCommand -WARNING: The add Command requires a book title -Mar 22, 2024 12:04:40 AM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 22, 2024 12:04:44 AM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 22, 2024 12:04:44 AM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 22, 2024 12:04:59 AM seedu.bookbuddy.Parser parseCommand -WARNING: The add Command requires a book title -Mar 22, 2024 12:04:59 AM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 22, 2024 12:10:13 AM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 22, 2024 12:10:13 AM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 22, 2024 2:41:58 AM seedu.bookbuddy.Parser parseCommand -WARNING: The add Command requires a book title -Mar 22, 2024 2:41:58 AM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 22, 2024 2:42:03 AM seedu.bookbuddy.Parser parseCommand -WARNING: The add Command requires a book title -Mar 22, 2024 2:42:03 AM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 22, 2024 2:42:07 AM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 22, 2024 2:42:07 AM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 22, 2024 2:42:42 AM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 22, 2024 2:42:42 AM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 22, 2024 2:43:07 AM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 22, 2024 2:43:07 AM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 22, 2024 2:45:00 AM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 22, 2024 2:45:00 AM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 22, 2024 2:45:18 AM seedu.bookbuddy.Parser parseCommand -WARNING: The add Command requires a book title -Mar 22, 2024 2:45:18 AM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 22, 2024 2:45:23 AM seedu.bookbuddy.Parser parseCommand -WARNING: The add Command requires a book title -Mar 22, 2024 2:45:23 AM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 22, 2024 2:45:29 AM seedu.bookbuddy.Parser parseCommand -WARNING: The add Command requires a book title -Mar 22, 2024 2:45:29 AM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 22, 2024 2:45:46 AM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 22, 2024 2:45:46 AM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 22, 2024 2:46:10 AM seedu.bookbuddy.Parser parseCommand -WARNING: The add Command requires a book title -Mar 22, 2024 2:46:10 AM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 22, 2024 2:46:17 AM seedu.bookbuddy.Parser parseCommand -WARNING: The add Command requires a book title -Mar 22, 2024 2:46:17 AM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 22, 2024 2:46:22 AM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 22, 2024 2:46:23 AM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 22, 2024 2:47:24 AM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 22, 2024 2:47:24 AM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 22, 2024 2:48:06 AM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 22, 2024 2:48:06 AM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 22, 2024 2:50:51 AM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 22, 2024 2:50:51 AM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 22, 2024 3:14:04 PM seedu.bookbuddy.Parser parseCommand -WARNING: The add Command requires a book title -Mar 22, 2024 3:14:04 PM seedu.bookbuddy.Parser parseCommand -WARNING: Invalid command argument: The add command requires a book title. -Mar 22, 2024 3:14:04 PM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 22, 2024 3:14:04 PM seedu.bookbuddy.Parser parseCommand -WARNING: Command is invalid -Mar 22, 2024 3:16:33 PM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 22, 2024 3:16:33 PM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 22, 2024 3:16:51 PM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 22, 2024 3:16:51 PM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 29, 2024 10:16:12 PM seedu.bookbuddy.Parser parseCommand -WARNING: The add Command requires a book title -Mar 29, 2024 10:16:12 PM seedu.bookbuddy.Parser parseCommand -WARNING: Invalid command argument: The add command requires a book title. -Mar 29, 2024 10:16:12 PM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 29, 2024 10:16:12 PM seedu.bookbuddy.Parser parseCommand -WARNING: Command is invalid -Mar 29, 2024 10:16:27 PM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 29, 2024 10:16:28 PM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 29, 2024 10:22:51 PM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 29, 2024 10:22:51 PM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 29, 2024 10:24:58 PM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 29, 2024 10:24:58 PM seedu.bookbuddy.Parser parseCommand -WARNING: Command is invalid -Mar 29, 2024 10:24:58 PM seedu.bookbuddy.BookBuddy getUserInput -WARNING: Unsupported command: genre 0 testgenre -Mar 29, 2024 11:10:04 PM seedu.bookbuddy.BookBuddy main -INFO: BookBuddy application started. -Mar 29, 2024 11:10:04 PM seedu.bookbuddy.BookBuddy getUserInput -INFO: Starting to get user input. -Mar 29, 2024 11:13:30 PM seedu.bookbuddy.Parser parseCommand -WARNING: The add Command requires a book title -Mar 29, 2024 11:13:30 PM seedu.bookbuddy.Parser parseCommand -WARNING: Invalid command argument: The add command requires a book title. -Mar 29, 2024 11:13:30 PM seedu.bookbuddy.Parser parseCommand -WARNING: Sorry but that is not a valid command. Please try again -Mar 29, 2024 11:13:30 PM seedu.bookbuddy.Parser parseCommand -WARNING: Command is invalid diff --git a/src/main/java/seedu/bookbuddy/Book.java b/src/main/java/seedu/bookbuddy/Book.java index 2fe290e1fa..2cc9d18fef 100644 --- a/src/main/java/seedu/bookbuddy/Book.java +++ b/src/main/java/seedu/bookbuddy/Book.java @@ -1,5 +1,7 @@ package seedu.bookbuddy; +import java.util.Objects; + public class Book { public String title; protected boolean isRead; @@ -19,6 +21,16 @@ public Book(String title) { this.label = ""; this.genre = ""; this.rating = -1; + this.summary = ""; + } + + public Book(String title, int status, String label, String genre, int rating, String summary) { + this.title = title; + this.isRead = status == 1; + this.label = (Objects.equals(label, "*")) ? "" : label; + this.genre = (Objects.equals(genre, "*")) ? "" : genre; + this.rating = rating; + this.summary = (Objects.equals(summary, "*")) ? "" : summary; } /** @@ -135,4 +147,13 @@ public String toString() { String statusMark = this.isRead() ? "R" : "U"; // Mark with 'R' if read and 'U' if unread return "[" + statusMark + "] " + this.title; } + + public String saveFormat() { + String status = isRead ? "1" : "0"; + String label = (this.label.isEmpty()) ? "*" : this.label; + String genre = (this.genre.isEmpty()) ? "*" : this.genre; + String summary = (this.summary.isEmpty()) ? "*" : this.summary; + return this.title + " | " + status + " | " + label + " | " + genre + " | " + this.rating + + " | " + summary; + } } diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index e6ac8daa00..3c44a9352b 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -11,13 +11,11 @@ import java.util.logging.SimpleFormatter; import java.util.logging.Handler; - import static java.util.logging.Logger.getLogger; - - public class BookBuddy { static final Logger LOGGER = getLogger(BookBuddy.class.getName()); + public static final String EXIT_COMMAND = "bye"; static { try { @@ -38,7 +36,7 @@ public class BookBuddy { } private static BookList books = new BookList(); - public static void main(String[] args) { + public static void main(String[] args) throws IOException { LOGGER.log(Level.INFO, "BookBuddy application started."); Ui.printWelcome(); assert books != null : "BookList not created"; @@ -47,8 +45,9 @@ public static void main(String[] args) { } - public static void getUserInput(BookList books) { + public static void getUserInput(BookList books) throws IOException { Scanner input = new Scanner(System.in); + FileStorage filestorage = new FileStorage(books); LOGGER.log(Level.INFO, "Starting to get user input."); //noinspection InfiniteLoopStatement @@ -57,6 +56,10 @@ public static void getUserInput(BookList books) { if (userInput.isEmpty()) { // If the input is empty, do not call parseCommand and just prompt for input again. continue; + } else if (userInput.equals(EXIT_COMMAND)) { + filestorage.saveData(books); + Ui.printExitMessage(); + System.exit(0); } assert !userInput.isEmpty() : "User input should not be empty at this point"; LOGGER.log(Level.FINE, "Processing user input: {0}", userInput); diff --git a/src/main/java/seedu/bookbuddy/BookDetails.java b/src/main/java/seedu/bookbuddy/BookDetails.java index 174eb6c0ab..7345e7b994 100644 --- a/src/main/java/seedu/bookbuddy/BookDetails.java +++ b/src/main/java/seedu/bookbuddy/BookDetails.java @@ -7,7 +7,6 @@ public class BookDetails { protected String summary; - /** * Sets the rating of the book at the specified index. * @@ -122,6 +121,6 @@ public static void displayDetails(int index, BookList books) throws IndexOutOfBo System.out.println("Label: " + books.getBook(index).getLabel()); System.out.println("Genre: " + books.getBook(index).getGenre()); System.out.println("Rating: " + books.getBook(index).getRating()); + System.out.println("Summary: " + books.getBook(index).getSummary()); } - } diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index 04e6e46723..bf1852e915 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -1,6 +1,5 @@ package seedu.bookbuddy; - import exceptions.BookNotFoundException; import exceptions.BookReadAlreadyException; import exceptions.BookUnreadAlreadyException; @@ -65,6 +64,17 @@ public void addBook(String title) { } } + public void addBookFromFile(String inputArray) { + String[] bookDetails = inputArray.split(" \\| "); + String title = bookDetails[0]; + int status = Integer.parseInt(bookDetails[1]); + String label = bookDetails[2]; + String genre = bookDetails[3]; + int rating = Integer.parseInt(bookDetails[4]); + String summary = bookDetails[5]; + books.add(new Book(title, status, label, genre, rating, summary)); + } + public void findBook(String title) { ArrayList bookTitles = new ArrayList<>(); for (Book book : books) { diff --git a/src/main/java/seedu/bookbuddy/FileStorage.java b/src/main/java/seedu/bookbuddy/FileStorage.java new file mode 100644 index 0000000000..d962ffbd9c --- /dev/null +++ b/src/main/java/seedu/bookbuddy/FileStorage.java @@ -0,0 +1,63 @@ +package seedu.bookbuddy; + +import java.util.Scanner; +import java.util.ArrayList; +import java.io.File; +import java.io.IOException; +import java.io.FileNotFoundException; +import java.io.FileWriter; + +/** + * The FileStorage class handles file operations such as creating directories, + * reading and writing to files and also loading data from files. + */ +public class FileStorage { + private static final String FILE_NAME = "books.txt"; + private static final String FILE_DIRECTORY = "./data"; + private static final String FILE_PATH = FILE_DIRECTORY + '/' + FILE_NAME; + + public FileStorage(BookList books) { + try { + File directory = new File(FILE_DIRECTORY); + if (!directory.exists()) { + directory.mkdir(); + } + File file = new File(FILE_PATH); + if (file.exists()) { + readData(books, file); + } else { + file.createNewFile(); + } + } catch (IOException e) { + System.out.println("Error: " + e.getMessage()); + } + } + + /** + * Scans through each line of the file and converts them to books, + * along with their proper details. + * @param file the name of the file whose content is to be read + * @throws FileNotFoundException when the file does not exist + */ + public void readData(BookList books, File file) throws FileNotFoundException { + Scanner sc = new Scanner(file); + + while (sc.hasNext()) { + String line = sc.nextLine(); + books.addBookFromFile(line); + } + + sc.close(); + } + + public void saveData(BookList books) throws IOException { + File file = new File(FILE_PATH); + FileWriter fw = new FileWriter(file); + for (int i = 1; i <= books.getSize(); i += 1) { + fw.write(books.getBook(i).saveFormat()); + } + + fw.close(); + System.out.println("Writing successful. Data has been saved."); + } +} diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/Parser.java index efb3312738..d1603a050c 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/Parser.java @@ -18,7 +18,6 @@ public class Parser { public static final String LIST_COMMAND = "list"; public static final String MARK_COMMAND = "mark"; public static final String UNMARK_COMMAND = "unmark"; - public static final String EXIT_COMMAND = "bye"; public static final String HELP_COMMAND = "help"; public static final String FIND_COMMAND = "find"; public static final String LABEL_COMMAND = "label"; @@ -119,7 +118,6 @@ public static void parseCommand(String input, BookList books) { index = Integer.parseInt(labelMessageParts[0]); assert index >= 0 : "Index should be non-negative"; String label = labelMessageParts[1]; - System.out.println(index); BookDetails.setBookLabelByIndex(index, label, books); } catch (NumberFormatException e) { System.out.println("Invalid input: " + labelMessageParts[0] @@ -264,10 +262,6 @@ public static void parseCommand(String input, BookList books) { case PRINT_ORDERED_COMMAND: BookDetails.printBooksByRating(books); break; - case EXIT_COMMAND: - Ui.printExitMessage(); - System.exit(0); - break; default: LOGGER.log(Level.WARNING, "Sorry but that is not a valid command. Please try again", command); throw new UnsupportedCommandException("Sorry but that is not a valid command. " + From df3dcbd2b4594acadabab0f3ece867c10e4dd62c Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Tue, 2 Apr 2024 20:23:29 +0800 Subject: [PATCH 126/311] Fix bugs in code --- src/main/java/seedu/bookbuddy/bookdetails/BookDisplay.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetails/BookDisplay.java index a3641f6581..ad326ec7e1 100644 --- a/src/main/java/seedu/bookbuddy/bookdetails/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetails/BookDisplay.java @@ -21,5 +21,6 @@ public static void displayDetails(int index, BookList books) throws IndexOutOfBo System.out.println("Label: " + books.getBook(index).getLabel()); System.out.println("Genre: " + books.getBook(index).getGenre()); System.out.println("Rating: " + books.getBook(index).getRating()); + System.out.println("Summary: " + books.getBook(index).getSummary()); } } From 837ae860fe9138184534f2a33d9238df7ea33a22 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Tue, 2 Apr 2024 20:32:40 +0800 Subject: [PATCH 127/311] Remove redundant class --- .../java/seedu/bookbuddy/BookDetails.java | 126 ------------------ 1 file changed, 126 deletions(-) delete mode 100644 src/main/java/seedu/bookbuddy/BookDetails.java diff --git a/src/main/java/seedu/bookbuddy/BookDetails.java b/src/main/java/seedu/bookbuddy/BookDetails.java deleted file mode 100644 index d0d905fe72..0000000000 --- a/src/main/java/seedu/bookbuddy/BookDetails.java +++ /dev/null @@ -1,126 +0,0 @@ -package seedu.bookbuddy; - -import java.util.Comparator; -import java.util.List; -import java.util.stream.Collectors; - -public class BookDetails { - protected String summary; - - /** - * Sets the rating of the book at the specified index. - * - * @param index The index of the book in the list. - * @param rating The rating to set for the book. - * @throws IndexOutOfBoundsException if the index is out of range. - * @throws IllegalArgumentException if the rating is not between 1 and 5. - */ - public static void setBookRatingByIndex(int index, int rating, BookList books) - throws IndexOutOfBoundsException, IllegalArgumentException { - if (index < 0 || index > books.getSize()) { - throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); - } - if (rating < 1 || rating > 5) { - throw new IllegalArgumentException("Rating must be between 1 and 5."); - } - books.getBook(index).setRating(rating); - String title = books.getBook(index).getTitle(); - Ui.setRatingBookMessage(title, rating); - } - - - /** - * Prints all books sorted by rating in descending order. - */ - public static void printBooksByRating(BookList books) { - if (books.books.isEmpty()) { - System.out.println("The list is empty. Add books by 'add [book]'"); - return; - } - - System.out.println("Books sorted by rating:"); - - List sortedBooks = books.books.stream() - .sorted(Comparator.comparingInt(Book::getRating).reversed()) - .collect(Collectors.toList()); - - for (Book book : sortedBooks) { - String rating = book.getRating() >= 0 ? String.valueOf(book.getRating()) : "Not Rated"; - System.out.println(book.getTitle() + " - " + rating); - } - } - - /** - * Sets the summary of the book at the specified index. - * - * @param index The index of the book in the list. - * @param summary The summary to set for the book. - * @throws IndexOutOfBoundsException if the index is out of range. - */ - public static void setBookSummaryByIndex(int index, String summary, BookList books) - throws IndexOutOfBoundsException { - if (index < 0 || index > books.getSize()) { - throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); - } - books.getBook(index).setSummary(summary); - String title = books.getBook(index).getTitle(); - Ui.summaryBookMessage(title, summary); - } - - /** - * Sets the label of the book at the specified index. - * - * @param index The index of the book in the list. - * @param label The label to set for the book. - * @throws IndexOutOfBoundsException if the index is out of range. - */ - public static void setBookLabelByIndex(int index, String label, BookList books) throws IndexOutOfBoundsException { - // Check for valid index - if (index < 0 || index > books.getSize()) { - throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); - } - - // Set the label for the book at the specified index - books.getBook(index).setLabel(label); - String title = books.getBook(index).getTitle(); - Ui.labelBookMessage(title, label); - } - - /** - * Sets the genre of the book at the specified index. - * - * @param index The index of the book in the list. - * @param genre The genre to set for the book. - * @throws IndexOutOfBoundsException if the index is out of range. - */ - public static void setBookGenreByIndex(int index, String genre, BookList books) throws IndexOutOfBoundsException { - // Check for valid index - if (index < 0 || index > books.getSize()) { - throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); - } - - // Set the genre for the book at the specified index - books.getBook(index).setGenre(genre); - String title = books.getBook(index).getTitle(); - Ui.setGenreBookMessage(title, genre); - } - - /** - * Prints the details of the book at the specified index. - * @param index The index of hte book in the list. - * @throws IndexOutOfBoundsException if the index is out of range. - */ - public static void displayDetails(int index, BookList books) throws IndexOutOfBoundsException { - if (index < 0 || index > books.getSize()) { - throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); - } - - System.out.println("Here are the details of your book:"); - System.out.println("Title: " + books.getBook(index).getTitle()); - System.out.println("Status: " + (books.getBook(index).isRead ? "Read" : "Unread")); - System.out.println("Label: " + books.getBook(index).getLabel()); - System.out.println("Genre: " + books.getBook(index).getGenre()); - System.out.println("Rating: " + books.getBook(index).getRating()); - System.out.println("Summary: " + books.getBook(index).getSummary()); - } -} From 9a3f4c912784be4d5fa6bb7caffe0d7c79b90cb0 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Tue, 2 Apr 2024 20:36:04 +0800 Subject: [PATCH 128/311] Abstraction of parser class --- src/main/java/seedu/bookbuddy/BookBuddy.java | 1 + src/main/java/seedu/bookbuddy/BookList.java | 3 ++ .../seedu/bookbuddy/{ => parser}/Parser.java | 28 +++++++++---------- src/test/java/seedu/bookbuddy/ParserTest.java | 1 + 4 files changed, 18 insertions(+), 15 deletions(-) rename src/main/java/seedu/bookbuddy/{ => parser}/Parser.java (91%) diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index c9043bc508..901a3596ef 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -2,6 +2,7 @@ import exceptions.InvalidCommandArgumentException; import exceptions.UnsupportedCommandException; +import seedu.bookbuddy.parser.Parser; import java.io.IOException; import java.util.Scanner; diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index f294dce319..463a92bd67 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -19,6 +19,9 @@ public class BookList { protected static List availableGenres = new ArrayList<>(Arrays.asList("Fiction", "Non-Fiction", "Mystery", "Science Fiction", "Fantasy")); + public static List getAvailableGenres() { + return availableGenres; + } protected ArrayList books; /** diff --git a/src/main/java/seedu/bookbuddy/Parser.java b/src/main/java/seedu/bookbuddy/parser/Parser.java similarity index 91% rename from src/main/java/seedu/bookbuddy/Parser.java rename to src/main/java/seedu/bookbuddy/parser/Parser.java index 0301dfd429..9b55219e3c 100644 --- a/src/main/java/seedu/bookbuddy/Parser.java +++ b/src/main/java/seedu/bookbuddy/parser/Parser.java @@ -1,12 +1,10 @@ -package seedu.bookbuddy; +package seedu.bookbuddy.parser; import exceptions.InvalidCommandArgumentException; import exceptions.UnsupportedCommandException; -import seedu.bookbuddy.bookdetails.BookDisplay; -import seedu.bookbuddy.bookdetails.BookGenre; -import seedu.bookbuddy.bookdetails.BookLabel; -import seedu.bookbuddy.bookdetails.BookRating; -import seedu.bookbuddy.bookdetails.BookSummary; +import seedu.bookbuddy.BookList; +import seedu.bookbuddy.Ui; +import seedu.bookbuddy.bookdetails.*; import java.util.Scanner; import java.util.logging.Level; @@ -31,7 +29,7 @@ public class Parser { public static final String DISPLAY_COMMAND = "display"; public static final String RATING_COMMAND = "rate"; public static final String PRINT_ORDERED_COMMAND = "list-rated"; - private static void validateCommandArguments(String[] inputArray, int requiredArgs, String errorMessage) + public static void validateCommandArguments(String[] inputArray, int requiredArgs, String errorMessage) throws InvalidCommandArgumentException { if (inputArray.length < requiredArgs) { LOGGER.log(Level.WARNING, errorMessage, inputArray); @@ -39,7 +37,7 @@ private static void validateCommandArguments(String[] inputArray, int requiredAr } } - private static void handleException(Exception e, String command, String[] inputArray) { + public static void handleException(Exception e, String command, String[] inputArray) { if (e instanceof UnsupportedCommandException) { LOGGER.log(Level.WARNING, "Command is invalid: {0}", e.getMessage()); System.out.println(e.getMessage()); @@ -150,10 +148,10 @@ public static void parseCommand(String input, BookList books) { "Type 'list' to view the list of books."); } System.out.println("Available genres:"); - for (int i = 0; i < BookList.availableGenres.size(); i++) { - System.out.println((i + 1) + ". " + BookList.availableGenres.get(i)); + for (int i = 0; i < BookList.getAvailableGenres().size(); i++) { + System.out.println((i + 1) + ". " + BookList.getAvailableGenres().get(i)); } - System.out.println((BookList.availableGenres.size() + 1) + ". Add a new genre"); + System.out.println((BookList.getAvailableGenres().size() + 1) + ". Add a new genre"); System.out.println("Enter the number for the desired genre, or add a new one:"); Scanner scanner = new Scanner(System.in); @@ -173,12 +171,12 @@ public static void parseCommand(String input, BookList books) { int genreSelection = scanner.nextInt(); scanner.nextLine(); // Consume the newline after the number - if (genreSelection == BookList.availableGenres.size() + 1) { + if (genreSelection == BookList.getAvailableGenres().size() + 1) { System.out.println("Enter the new genre:"); selectedGenre = scanner.nextLine(); - BookList.availableGenres.add(selectedGenre); // Add the new genre to the list - } else if (genreSelection > 0 && genreSelection <= BookList.availableGenres.size()) { - selectedGenre = BookList.availableGenres.get(genreSelection - 1); + BookList.getAvailableGenres().add(selectedGenre); // Add the new genre to the list + } else if (genreSelection > 0 && genreSelection <= BookList.getAvailableGenres().size()) { + selectedGenre = BookList.getAvailableGenres().get(genreSelection - 1); } else { System.out.println("Invalid selection. Please enter a valid number " + "or type 'exit' to cancel."); diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserTest.java index f93ed56245..b83ea4fd57 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserTest.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import seedu.bookbuddy.parser.Parser; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; From 8942ef407d9c85ea1b2a492e8ee5e9aa6b09ddee Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Tue, 2 Apr 2024 20:50:19 +0800 Subject: [PATCH 129/311] Improve code based on code review --- src/main/java/seedu/bookbuddy/Book.java | 3 ++- src/main/java/seedu/bookbuddy/BookList.java | 6 ++++++ src/main/java/seedu/bookbuddy/FileStorage.java | 13 ++++++++++--- src/main/java/seedu/bookbuddy/Ui.java | 5 ----- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Book.java b/src/main/java/seedu/bookbuddy/Book.java index 2cc9d18fef..8e9cc1c32e 100644 --- a/src/main/java/seedu/bookbuddy/Book.java +++ b/src/main/java/seedu/bookbuddy/Book.java @@ -3,7 +3,7 @@ import java.util.Objects; public class Book { - public String title; + protected String title; protected boolean isRead; protected String label; protected String genre; @@ -143,6 +143,7 @@ public void markBookAsUnread() { System.out.println("Successfully marked " + this.getTitle() + " as unread."); } + @Override public String toString() { String statusMark = this.isRead() ? "R" : "U"; // Mark with 'R' if read and 'U' if unread return "[" + statusMark + "] " + this.title; diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java index 7e750ec7e4..091f795170 100644 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ b/src/main/java/seedu/bookbuddy/BookList.java @@ -33,6 +33,7 @@ public List getBooks() { /** * Returns the current size of the book list. + * * @return The number of books in the list. */ public int getSize(){ @@ -41,6 +42,7 @@ public int getSize(){ /** * Retrieves a book from the list based on its index. + * * @param index The index of the book to retrieve. * @return The Book at the specified index. */ @@ -55,6 +57,7 @@ public Book getBook(int index) throws BookNotFoundException{ /** * Adds a new Book to the list. + * * @param title The title of the book. */ public void addBook(String title) { @@ -95,6 +98,7 @@ public void findBook(String title) { /** * Deletes a book from the list by its index. + * * @param index The index of the book to delete. */ public void deleteBook(int index) throws IndexOutOfBoundsException { @@ -112,6 +116,7 @@ public void deleteBook(int index) throws IndexOutOfBoundsException { /** * Marks a book as read by its index. + * * @param index The index of the book to mark as read. */ public void markDoneByIndex(int index) throws IndexOutOfBoundsException, BookReadAlreadyException{ @@ -135,6 +140,7 @@ public void markDoneByIndex(int index) throws IndexOutOfBoundsException, BookRea /** * Marks a book as unread by its index. + * * @param index The index of the book to mark as unread. */ public void markUndoneByIndex(int index) throws IndexOutOfBoundsException, BookReadAlreadyException{ diff --git a/src/main/java/seedu/bookbuddy/FileStorage.java b/src/main/java/seedu/bookbuddy/FileStorage.java index f5c8f6cf37..1c0da86f9b 100644 --- a/src/main/java/seedu/bookbuddy/FileStorage.java +++ b/src/main/java/seedu/bookbuddy/FileStorage.java @@ -35,8 +35,9 @@ public FileStorage(BookList books) { /** * Scans through each line of the file and converts them to books, * along with their proper details. - * @param file the name of the file whose content is to be read - * @throws FileNotFoundException when the file does not exist + * + * @param file The name of the file whose content is to be read. + * @throws FileNotFoundException When the file does not exist. */ public void readData(BookList books, File file) throws FileNotFoundException { Scanner sc = new Scanner(file); @@ -49,11 +50,17 @@ public void readData(BookList books, File file) throws FileNotFoundException { sc.close(); } + /** + * Saves the data in the current session to the file. + * + * @param books The list of books. + * @throws IOException when there is an error with the file creation. + */ public void saveData(BookList books) throws IOException { File file = new File(FILE_PATH); FileWriter fw = new FileWriter(file); for (int i = 1; i <= books.getSize(); i += 1) { - fw.write(books.getBook(i).saveFormat()); + fw.write(books.getBook(i).saveFormat() + '\n'); } fw.close(); diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 092cdce164..3100b7ee9d 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -27,11 +27,6 @@ public static void printExitMessage() { System.out.println("Thank you for using BookBuddy! Hope to see you again keke :)"); } - /*public static String printInvalidCommand() { - String message = "The add command requires a book title."; - System.out.println(message); - return message; - } */ public static void addBookMessage(String title) { System.out.println("okii added [" + title + "] to the list."); System.out.println("remember to read it soon...."); From add5af67f7e7f0b4af70bb06f60c412de380bc01 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Tue, 2 Apr 2024 21:05:13 +0800 Subject: [PATCH 130/311] Edit .gitignore --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 39b6ad8fb2..5d3bf2e118 100644 --- a/.gitignore +++ b/.gitignore @@ -13,9 +13,9 @@ src/main/resources/docs/ *.iml bin/ data/books.txt +Bookbuddy.txt *.log -*.lck -*.log.1 +BookBuddy.log.lck /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT From 3509f53ee6f3c4d517babc14e91261e9a532a8b8 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Tue, 2 Apr 2024 22:30:33 +0800 Subject: [PATCH 131/311] Abstraction of code --- src/main/java/seedu/bookbuddy/BookBuddy.java | 5 +- src/main/java/seedu/bookbuddy/BookList.java | 184 --------------- .../java/seedu/bookbuddy/FileStorage.java | 5 +- src/main/java/seedu/bookbuddy/Ui.java | 2 + .../bookbuddy/bookdetails/BookDisplay.java | 40 +++- .../bookbuddy/bookdetails/BookGenre.java | 2 +- .../bookbuddy/bookdetails/BookLabel.java | 2 +- .../seedu/bookbuddy/bookdetails/BookMark.java | 63 +++++ .../bookbuddy/bookdetails/BookRating.java | 2 +- .../bookbuddy/bookdetails/BookSummary.java | 2 +- .../seedu/bookbuddy/booklist/BookList.java | 57 +++++ .../bookbuddy/booklist/BookListModifier.java | 60 +++++ .../java/seedu/bookbuddy/parser/Parser.java | 223 ------------------ .../seedu/bookbuddy/parser/ParserMain.java | 92 ++++++++ .../parser/parsercommands/ParserAdd.java | 19 ++ .../parser/parsercommands/ParserDisplay.java | 21 ++ .../parser/parsercommands/ParserGenre.java | 75 ++++++ .../parser/parsercommands/ParserLabel.java | 28 +++ .../parser/parsercommands/ParserMark.java | 23 ++ .../parser/parsercommands/ParserRating.java | 28 +++ .../parser/parsercommands/ParserRemove.java | 20 ++ .../parser/parsercommands/ParserSummary.java | 27 +++ .../parser/parsercommands/ParserUnmark.java | 21 ++ .../parser/parservalidation/CommandList.java | 17 ++ .../parser/parservalidation/Exceptions.java | 40 ++++ .../java/seedu/bookbuddy/BookDetailsTest.java | 10 +- .../java/seedu/bookbuddy/BookListTest.java | 30 ++- .../{ParserTest.java => ParserMainTest.java} | 53 +++-- 28 files changed, 694 insertions(+), 457 deletions(-) delete mode 100644 src/main/java/seedu/bookbuddy/BookList.java create mode 100644 src/main/java/seedu/bookbuddy/bookdetails/BookMark.java create mode 100644 src/main/java/seedu/bookbuddy/booklist/BookList.java create mode 100644 src/main/java/seedu/bookbuddy/booklist/BookListModifier.java delete mode 100644 src/main/java/seedu/bookbuddy/parser/Parser.java create mode 100644 src/main/java/seedu/bookbuddy/parser/ParserMain.java create mode 100644 src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAdd.java create mode 100644 src/main/java/seedu/bookbuddy/parser/parsercommands/ParserDisplay.java create mode 100644 src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java create mode 100644 src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java create mode 100644 src/main/java/seedu/bookbuddy/parser/parsercommands/ParserMark.java create mode 100644 src/main/java/seedu/bookbuddy/parser/parsercommands/ParserRating.java create mode 100644 src/main/java/seedu/bookbuddy/parser/parsercommands/ParserRemove.java create mode 100644 src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java create mode 100644 src/main/java/seedu/bookbuddy/parser/parsercommands/ParserUnmark.java create mode 100644 src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java create mode 100644 src/main/java/seedu/bookbuddy/parser/parservalidation/Exceptions.java rename src/test/java/seedu/bookbuddy/{ParserTest.java => ParserMainTest.java} (72%) diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index 77ff86a323..62d3c10bec 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -2,7 +2,8 @@ import exceptions.InvalidCommandArgumentException; import exceptions.UnsupportedCommandException; -import seedu.bookbuddy.parser.Parser; +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.parser.ParserMain; import java.io.IOException; import java.util.Scanner; @@ -65,7 +66,7 @@ public static void getUserInput(BookList books) throws IOException { assert !userInput.isEmpty() : "User input should not be empty at this point"; LOGGER.log(Level.FINE, "Processing user input: {0}", userInput); try { - Parser.parseCommand(userInput, books); + ParserMain.parseCommand(userInput, books); } catch (UnsupportedCommandException e) { LOGGER.log(Level.WARNING, "Unsupported command: {0}", userInput); System.out.println(e.getMessage()); diff --git a/src/main/java/seedu/bookbuddy/BookList.java b/src/main/java/seedu/bookbuddy/BookList.java deleted file mode 100644 index 3b3a3631f1..0000000000 --- a/src/main/java/seedu/bookbuddy/BookList.java +++ /dev/null @@ -1,184 +0,0 @@ -package seedu.bookbuddy; - -import exceptions.BookNotFoundException; -import exceptions.BookReadAlreadyException; -import exceptions.BookUnreadAlreadyException; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.logging.Level; - -import static seedu.bookbuddy.BookBuddy.LOGGER; - -/** - * Manages a list of books, allowing for operations such as adding, deleting, - * and marking book as read or unread. - */ -public class BookList { - protected static List availableGenres = new ArrayList<>(Arrays.asList("Fiction", "Non-Fiction", - "Mystery", "Science Fiction", "Fantasy")); - public static List getAvailableGenres() { - return availableGenres; - } - protected ArrayList books; - - /** - * Constructs a new BookList instance with an empty list. - */ - public BookList() { - this.books = new ArrayList(); // Use ArrayList instead of array - } - // Public getter method for the books field - public List getBooks() { - return this.books; - } - - /** - * Returns the current size of the book list. - * - * @return The number of books in the list. - */ - public int getSize(){ - return books.size(); - } - - /** - * Retrieves a book from the list based on its index. - * - * @param index The index of the book to retrieve. - * @return The Book at the specified index. - */ - public Book getBook(int index) throws BookNotFoundException{ - if (index < 0 || index > books.size()) { - throw new BookNotFoundException("Book index out of range."); - } - assert books.get(index - 1) != null : "Retrieved book should not be null"; - assert books.get(index - 1) instanceof Book : "Object at index should be an instance of Book"; - return books.get(index - 1); - } - - /** - * Adds a new Book to the list. - * - * @param title The title of the book. - */ - public void addBook(String title) { - try { - books.add(new Book(title)); - Ui.addBookMessage(title); - assert !books.isEmpty() : "Book list should not be empty after adding a book"; - } catch (Exception e) { - LOGGER.log(Level.SEVERE, "An unexpected error occurred: {0}", e.getMessage()); - throw e; // Rethrow or handle as needed - } - } - - public void addBookFromFile(String inputArray) { - String[] bookDetails = inputArray.split(" \\| "); - String title = bookDetails[0]; - int status = Integer.parseInt(bookDetails[1]); - String label = bookDetails[2]; - String genre = bookDetails[3]; - int rating = Integer.parseInt(bookDetails[4]); - String summary = bookDetails[5]; - books.add(new Book(title, status, label, genre, rating, summary)); - } - - public void findBook(String title) { - ArrayList bookTitles = new ArrayList<>(); - for (Book book : books) { - if (book.getTitle().contains(title)) { - bookTitles.add(book); - } - } - if (bookTitles.isEmpty()){ - Ui.printNoBookFound(); - } else { - Ui.printBookFound(bookTitles); - } - } - - /** - * Deletes a book from the list by its index. - * - * @param index The index of the book to delete. - */ - public void deleteBook(int index) throws IndexOutOfBoundsException { - try { - Ui.removeBookMessage(index, this); - books.remove(index - 1); - assert books.size() >= 0 : "Book list size should not be negative after deletion"; - } catch (IndexOutOfBoundsException e) { - System.out.println("Invalid book index. Please enter a valid index"); - } catch (Exception e) { - LOGGER.log(Level.SEVERE, "An unexpected error occurred: {0}", e.getMessage()); - throw e; // Rethrow or handle as needed - } - } - - /** - * Marks a book as read by its index. - * - * @param index The index of the book to mark as read. - */ - public void markDoneByIndex(int index) throws IndexOutOfBoundsException, BookReadAlreadyException{ - try { - assert index > 0 && index <= books.size() : "Index out of valid range"; - if (books.get(index - 1).isRead()) { - throw new BookReadAlreadyException("That book is already marked as read!"); - } - assert !books.get(index - 1).isRead() : "Book is already marked as read"; - books.get(index - 1).markBookAsRead(); - assert books.get(index - 1).isRead() : "Book should be marked as read"; - } catch (IndexOutOfBoundsException e) { - System.out.println("Invalid book index. Please enter a valid index"); - } catch (BookReadAlreadyException e) { - System.out.println("That book is already marked as read!"); - } catch (Exception e) { - LOGGER.log(Level.SEVERE, "An unexpected error occurred: {0}", e.getMessage()); - throw e; // Rethrow or handle as needed - } - } - - /** - * Marks a book as unread by its index. - * - * @param index The index of the book to mark as unread. - */ - public void markUndoneByIndex(int index) throws IndexOutOfBoundsException, BookReadAlreadyException{ - try { - assert index > 0 && index <= books.size() : "Index out of valid range"; - if (!books.get(index - 1).isRead()) { - throw new BookUnreadAlreadyException("That book is already marked as unread!"); - } - assert books.get(index - 1).isRead() : "Book is already marked as unread"; - books.get(index - 1).markBookAsUnread(); - assert !books.get(index - 1).isRead() : "Book should be marked as unread"; - } catch (IndexOutOfBoundsException e) { - System.out.println("Invalid book index. Please enter a valid index"); - } catch (BookUnreadAlreadyException e) { - System.out.println("That book is already marked as unread!"); - } catch (Exception e) { // Generic catch block for any other exceptions - System.out.println("An unexpected error occurred. Please contact support."); - } - } - - /** - * Prints all books currently in the list. - */ - public void printAllBooks() { - assert books != null : "Books list should not be null since it has been initialised."; - if (!books.isEmpty()) { - System.out.println("All books:"); - for (int i = 0; i < books.size(); i++) { - Book currentBook = books.get(i); - assert currentBook != null : "Book in list should not be null"; - System.out.print((i + 1) + ". "); - System.out.println(currentBook.toString()); - } - } else { - System.out.println("The list is empty. Add books by 'add [book]'"); - } - } -} diff --git a/src/main/java/seedu/bookbuddy/FileStorage.java b/src/main/java/seedu/bookbuddy/FileStorage.java index 1c0da86f9b..d512a36b0b 100644 --- a/src/main/java/seedu/bookbuddy/FileStorage.java +++ b/src/main/java/seedu/bookbuddy/FileStorage.java @@ -1,5 +1,8 @@ package seedu.bookbuddy; +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.booklist.BookListModifier; + import java.util.Scanner; import java.io.File; import java.io.IOException; @@ -44,7 +47,7 @@ public void readData(BookList books, File file) throws FileNotFoundException { while (sc.hasNext()) { String line = sc.nextLine(); - books.addBookFromFile(line); + BookListModifier.addBookFromFile(books, line); } sc.close(); diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 3100b7ee9d..13134dfcb4 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -1,5 +1,7 @@ package seedu.bookbuddy; +import seedu.bookbuddy.booklist.BookList; + import java.util.ArrayList; public class Ui { diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetails/BookDisplay.java index ad326ec7e1..86b165b82f 100644 --- a/src/main/java/seedu/bookbuddy/bookdetails/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetails/BookDisplay.java @@ -1,6 +1,10 @@ package seedu.bookbuddy.bookdetails; -import seedu.bookbuddy.BookList; +import seedu.bookbuddy.Book; +import seedu.bookbuddy.Ui; +import seedu.bookbuddy.booklist.BookList; + +import java.util.ArrayList; public class BookDisplay { //@@author joshuahoky @@ -23,4 +27,38 @@ public static void displayDetails(int index, BookList books) throws IndexOutOfBo System.out.println("Rating: " + books.getBook(index).getRating()); System.out.println("Summary: " + books.getBook(index).getSummary()); } + + /** + * Prints all books currently in the list. + * @param bookList + */ + public static void printAllBooks(BookList bookList) { + assert bookList.getBooks() != null : "Books list should not be null since it has been initialised."; + if (!bookList.getBooks().isEmpty()) { + System.out.println("All books:"); + for (int i = 0; i < bookList.getBooks().size(); i++) { + Book currentBook = bookList.getBooks().get(i); + assert currentBook != null : "Book in list should not be null"; + System.out.print((i + 1) + ". "); + System.out.println(currentBook.toString()); + } + } else { + System.out.println("The list is empty. Add books by 'add [book]'"); + } + } + + //@@author liuzehui03 + public static void findBook(BookList bookList, String title) { + ArrayList bookTitles = new ArrayList<>(); + for (Book book : bookList.getBooks()) { + if (book.getTitle().contains(title)) { + bookTitles.add(book); + } + } + if (bookTitles.isEmpty()){ + Ui.printNoBookFound(); + } else { + Ui.printBookFound(bookTitles); + } + } } diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookGenre.java b/src/main/java/seedu/bookbuddy/bookdetails/BookGenre.java index ee21207b12..cd5c9a14ab 100644 --- a/src/main/java/seedu/bookbuddy/bookdetails/BookGenre.java +++ b/src/main/java/seedu/bookbuddy/bookdetails/BookGenre.java @@ -1,6 +1,6 @@ package seedu.bookbuddy.bookdetails; -import seedu.bookbuddy.BookList; +import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.Ui; public class BookGenre { diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookLabel.java b/src/main/java/seedu/bookbuddy/bookdetails/BookLabel.java index d6dea68eac..fee9262bd6 100644 --- a/src/main/java/seedu/bookbuddy/bookdetails/BookLabel.java +++ b/src/main/java/seedu/bookbuddy/bookdetails/BookLabel.java @@ -1,6 +1,6 @@ package seedu.bookbuddy.bookdetails; -import seedu.bookbuddy.BookList; +import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.Ui; public class BookLabel { diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookMark.java b/src/main/java/seedu/bookbuddy/bookdetails/BookMark.java new file mode 100644 index 0000000000..989e04edbd --- /dev/null +++ b/src/main/java/seedu/bookbuddy/bookdetails/BookMark.java @@ -0,0 +1,63 @@ +package seedu.bookbuddy.bookdetails; + +import exceptions.BookReadAlreadyException; +import exceptions.BookUnreadAlreadyException; +import seedu.bookbuddy.booklist.BookList; + +import java.util.logging.Level; + +import static seedu.bookbuddy.BookBuddy.LOGGER; + +public class BookMark { + + //@@author lordgareth10 + /** + * Marks a book as read by its index. + * + * @param bookList + * @param index The index of the book to mark as read. + */ + public static void markDoneByIndex(BookList bookList, int index) throws IndexOutOfBoundsException, BookReadAlreadyException { + try { + assert index > 0 && index <= bookList.getBooks().size() : "Index out of valid range"; + if (bookList.getBooks().get(index - 1).isRead()) { + throw new BookReadAlreadyException("That book is already marked as read!"); + } + assert !bookList.getBooks().get(index - 1).isRead() : "Book is already marked as read"; + bookList.getBooks().get(index - 1).markBookAsRead(); + assert bookList.getBooks().get(index - 1).isRead() : "Book should be marked as read"; + } catch (IndexOutOfBoundsException e) { + System.out.println("Invalid book index. Please enter a valid index"); + } catch (BookReadAlreadyException e) { + System.out.println("That book is already marked as read!"); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "An unexpected error occurred: {0}", e.getMessage()); + throw e; // Rethrow or handle as needed + } + } + + //@@author lordgareth10 + /** + * Marks a book as unread by its index. + * + * @param bookList + * @param index The index of the book to mark as unread. + */ + public static void markUndoneByIndex(BookList bookList, int index) throws IndexOutOfBoundsException, BookReadAlreadyException{ + try { + assert index > 0 && index <= bookList.getBooks().size() : "Index out of valid range"; + if (!bookList.getBooks().get(index - 1).isRead()) { + throw new BookUnreadAlreadyException("That book is already marked as unread!"); + } + assert bookList.getBooks().get(index - 1).isRead() : "Book is already marked as unread"; + bookList.getBooks().get(index - 1).markBookAsUnread(); + assert !bookList.getBooks().get(index - 1).isRead() : "Book should be marked as unread"; + } catch (IndexOutOfBoundsException e) { + System.out.println("Invalid book index. Please enter a valid index"); + } catch (BookUnreadAlreadyException e) { + System.out.println("That book is already marked as unread!"); + } catch (Exception e) { // Generic catch block for any other exceptions + System.out.println("An unexpected error occurred. Please contact support."); + } + } +} diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookRating.java b/src/main/java/seedu/bookbuddy/bookdetails/BookRating.java index db89e75e4c..b8f748c069 100644 --- a/src/main/java/seedu/bookbuddy/bookdetails/BookRating.java +++ b/src/main/java/seedu/bookbuddy/bookdetails/BookRating.java @@ -1,7 +1,7 @@ package seedu.bookbuddy.bookdetails; import seedu.bookbuddy.Book; -import seedu.bookbuddy.BookList; +import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.Ui; import java.util.Comparator; diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookSummary.java b/src/main/java/seedu/bookbuddy/bookdetails/BookSummary.java index 929cd1021e..ecb7b7c405 100644 --- a/src/main/java/seedu/bookbuddy/bookdetails/BookSummary.java +++ b/src/main/java/seedu/bookbuddy/bookdetails/BookSummary.java @@ -1,6 +1,6 @@ package seedu.bookbuddy.bookdetails; -import seedu.bookbuddy.BookList; +import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.Ui; public class BookSummary { diff --git a/src/main/java/seedu/bookbuddy/booklist/BookList.java b/src/main/java/seedu/bookbuddy/booklist/BookList.java new file mode 100644 index 0000000000..444cf86db8 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/booklist/BookList.java @@ -0,0 +1,57 @@ +package seedu.bookbuddy.booklist; + +import exceptions.BookNotFoundException; +import seedu.bookbuddy.Book; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Manages a list of books, allowing for operations such as adding, deleting, + * and marking book as read or unread. + */ +public class BookList { + protected static List availableGenres = new ArrayList<>(Arrays.asList("Fiction", "Non-Fiction", + "Mystery", "Science Fiction", "Fantasy")); + public static List getAvailableGenres() { + return availableGenres; + } + protected ArrayList books; + + /** + * Constructs a new BookList instance with an empty list. + */ + public BookList() { + this.books = new ArrayList(); // Use ArrayList instead of array + } + // Public getter method for the books field + public List getBooks() { + return this.books; + } + + /** + * Returns the current size of the book list. + * + * @return The number of books in the list. + */ + public int getSize(){ + return books.size(); + } + + /** + * Retrieves a book from the list based on its index. + * + * @param index The index of the book to retrieve. + * @return The Book at the specified index. + */ + public Book getBook(int index) throws BookNotFoundException{ + if (index < 0 || index > books.size()) { + throw new BookNotFoundException("Book index out of range."); + } + assert books.get(index - 1) != null : "Retrieved book should not be null"; + assert books.get(index - 1) instanceof Book : "Object at index should be an instance of Book"; + return books.get(index - 1); + } + +} diff --git a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java new file mode 100644 index 0000000000..992369afaf --- /dev/null +++ b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java @@ -0,0 +1,60 @@ +package seedu.bookbuddy.booklist; + +import seedu.bookbuddy.Book; +import seedu.bookbuddy.Ui; + +import java.util.logging.Level; + +import static seedu.bookbuddy.BookBuddy.LOGGER; + +public class BookListModifier { + + //@@author joshuahoky + public static void addBookFromFile(BookList bookList, String inputArray) { + String[] bookDetails = inputArray.split(" \\| "); + String title = bookDetails[0]; + int status = Integer.parseInt(bookDetails[1]); + String label = bookDetails[2]; + String genre = bookDetails[3]; + int rating = Integer.parseInt(bookDetails[4]); + String summary = bookDetails[5]; + bookList.books.add(new Book(title, status, label, genre, rating, summary)); + } + + //@@author + /** + * Adds a new Book to the list. + * + * @param bookList The bookList arraylist + * @param title The title of the book. + */ + public static void addBook(BookList bookList, String title) { + try { + bookList.books.add(new Book(title)); + Ui.addBookMessage(title); + assert !bookList.books.isEmpty() : "Book list should not be empty after adding a book"; + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "An unexpected error occurred: {0}", e.getMessage()); + throw e; // Rethrow or handle as needed + } + } + + /** + * Deletes a book from the list by its index. + * + * @param bookList + * @param index The index of the book to delete. + */ + public static void deleteBook(BookList bookList, int index) throws IndexOutOfBoundsException { + try { + Ui.removeBookMessage(index, bookList); + bookList.books.remove(index - 1); + assert bookList.books.size() >= 0 : "Book list size should not be negative after deletion"; + } catch (IndexOutOfBoundsException e) { + System.out.println("Invalid book index. Please enter a valid index"); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "An unexpected error occurred: {0}", e.getMessage()); + throw e; // Rethrow or handle as needed + } + } +} diff --git a/src/main/java/seedu/bookbuddy/parser/Parser.java b/src/main/java/seedu/bookbuddy/parser/Parser.java deleted file mode 100644 index 7ba47967d6..0000000000 --- a/src/main/java/seedu/bookbuddy/parser/Parser.java +++ /dev/null @@ -1,223 +0,0 @@ -package seedu.bookbuddy.parser; - -import exceptions.InvalidCommandArgumentException; -import exceptions.UnsupportedCommandException; -import seedu.bookbuddy.BookList; -import seedu.bookbuddy.Ui; -import seedu.bookbuddy.bookdetails.*; - -import java.util.Scanner; -import java.util.logging.Level; - -import static seedu.bookbuddy.BookBuddy.LOGGER; - -/** - * Parses inputs from the user in order to execute the correct commands. - */ -public class Parser { - public static final String ADD_COMMAND = "add"; - public static final String REMOVE_COMMAND = "remove"; - public static final String LIST_COMMAND = "list"; - public static final String MARK_COMMAND = "mark"; - public static final String UNMARK_COMMAND = "unmark"; - public static final String HELP_COMMAND = "help"; - public static final String FIND_COMMAND = "find"; - public static final String LABEL_COMMAND = "label"; - public static final String GENRE_COMMAND = "set-genre"; - public static final String SUMMARY_COMMAND = "give-summary"; - public static final String DISPLAY_COMMAND = "display"; - public static final String RATING_COMMAND = "rate"; - public static final String PRINT_ORDERED_COMMAND = "list-rated"; - public static void validateCommandArguments(String[] inputArray, int requiredArgs, String errorMessage) - throws InvalidCommandArgumentException { - if (inputArray.length < requiredArgs) { - LOGGER.log(Level.WARNING, errorMessage, inputArray); - throw new InvalidCommandArgumentException(errorMessage); - } - } - - public static void handleException(Exception e, String command, String[] inputArray) { - if (e instanceof UnsupportedCommandException) { - LOGGER.log(Level.WARNING, "Command is invalid: {0}", e.getMessage()); - System.out.println(e.getMessage()); - } else if (e instanceof NumberFormatException) { - System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + - "Please enter a valid numeric index. Type 'list' to view list of books."); - } else if (e instanceof IndexOutOfBoundsException) { - System.out.println("Invalid book index. Please enter a valid index."); - } else if (e instanceof InvalidCommandArgumentException) { - LOGGER.log(Level.WARNING, "Invalid command argument: {0}", new Object[]{e.getMessage()}); - System.out.println(e.getMessage()); - } else if (e instanceof IllegalArgumentException) { - System.out.println(e.getMessage()); - } else { - LOGGER.log(Level.SEVERE, "An unexpected error occurred while executing {0}: {1}", - new Object[]{command, e.getMessage()}); - System.out.println("An unexpected error occurred while executing " + command - + ". Please contact support."); - } - } - - - /** - * Scans the user input for valid commands and handles them accordingly. - * @param input input from the user - * @param books ArrayList of books - */ - public static void parseCommand(String input, BookList books) { - String[] inputArray = input.split(" ", 2); - String command = inputArray[0].toLowerCase(); - LOGGER.log(Level.FINE, "Parsing command: {0}", command); - int index; - - try { - switch (command) { - case ADD_COMMAND: - assert inputArray.length >= 2 : "Command requires additional arguments"; - validateCommandArguments(inputArray, 2, "The add " + - "Command requires a book title"); - books.addBook(inputArray[1]); - break; - case REMOVE_COMMAND: - assert inputArray.length >= 2 : "Command requires additional arguments"; - validateCommandArguments(inputArray, 2, "The remove " + - "Command requires a book index"); - index = Integer.parseInt(inputArray[1]); - books.deleteBook(index); - break; - case LIST_COMMAND: - books.printAllBooks(); - break; - case MARK_COMMAND: - assert inputArray.length >= 2 : "Command requires additional arguments"; - validateCommandArguments(inputArray, 2, "The mark " + - "Command requires a book index"); - index = Integer.parseInt(inputArray[1]); - assert index >= 0 : "Index should be non-negative"; - books.markDoneByIndex(index); - break; - case UNMARK_COMMAND: - assert inputArray.length == 2 : "Command requires additional arguments"; - validateCommandArguments(inputArray, 2, "The unmark " + - "Command requires a book index"); - index = Integer.parseInt(inputArray[1]); - assert index >= 0 : "Index should be non-negative"; - books.markUndoneByIndex(index); - break; - case HELP_COMMAND: - Ui.helpMessage(); - break; - case FIND_COMMAND: - books.findBook(inputArray[1]); - break; - case LABEL_COMMAND: - assert inputArray.length == 2 : "Command requires additional arguments"; - validateCommandArguments(inputArray,2, "The Label " + - "Command requires a book index and label"); - String[] labelMessageParts = inputArray[1].split(" ", 2); - // Split the message into index and label message - assert labelMessageParts.length == 2 : "Command requires an index and a label message"; - validateCommandArguments(labelMessageParts, 2, "You " + - "need to have a label message"); - index = Integer.parseInt(labelMessageParts[0]); - assert index >= 0 : "Index should be non-negative"; - String label = labelMessageParts[1]; - System.out.println(index); - BookLabel.setBookLabelByIndex(index, label, books); - break; - case SUMMARY_COMMAND: - assert inputArray.length == 2 : "Command requires additional arguments"; - validateCommandArguments(inputArray,2, "The summary " + - "Command requires a book index and summary"); - String[] summaryMessageParts = inputArray[1].split(" ", 2); - assert summaryMessageParts.length == 2 : "Command requires an index and a label message"; - validateCommandArguments(summaryMessageParts,2, "You need " + - "to have a summary message"); - index = Integer.parseInt(summaryMessageParts[0]); - assert index >= 0 : "Index should be non-negative"; - String summary = summaryMessageParts[1]; - BookSummary.setBookSummaryByIndex(index, summary, books); - break; - case GENRE_COMMAND: - validateCommandArguments(inputArray, 2, "The set-genre " + - "Command requires a book index."); - index = Integer.parseInt(inputArray[1]); - if (index < 0 || index > books.getSize()) { - throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index. " + - "Type 'list' to view the list of books."); - } - System.out.println("Available genres:"); - for (int i = 0; i < BookList.getAvailableGenres().size(); i++) { - System.out.println((i + 1) + ". " + BookList.getAvailableGenres().get(i)); - } - System.out.println((BookList.getAvailableGenres().size() + 1) + ". Add a new genre"); - - System.out.println("Enter the number for the desired genre, or add a new one:"); - Scanner scanner = new Scanner(System.in); - - String selectedGenre = null; - while (selectedGenre == null) { - while (!scanner.hasNextInt()) { // Ensure the next input is an integer - String newInput = scanner.nextLine(); - if ("exit".equalsIgnoreCase(newInput)) { - return; // Exit the command if user types 'exit' - } else { - System.out.println("Invalid input. Please enter a valid number or type 'exit'" + - " to cancel."); - } - } - - int genreSelection = scanner.nextInt(); - scanner.nextLine(); // Consume the newline after the number - - if (genreSelection == BookList.getAvailableGenres().size() + 1) { - System.out.println("Enter the new genre:"); - selectedGenre = scanner.nextLine(); - BookList.getAvailableGenres().add(selectedGenre); // Add the new genre to the list - } else if (genreSelection > 0 && genreSelection <= BookList.getAvailableGenres().size()) { - selectedGenre = BookList.getAvailableGenres().get(genreSelection - 1); - } else { - System.out.println("Invalid selection. Please enter a valid number " + - "or type 'exit' to cancel."); - // No need for the nextLine or parsing logic here, the while loop will continue - } - } - - BookGenre.setBookGenreByIndex(index, selectedGenre, books); - System.out.println("Genre set to " + selectedGenre + " for book at index " + index); - - break; - case RATING_COMMAND: - assert inputArray.length >= 2 : "Command requires additional arguments"; - validateCommandArguments(inputArray, 2, "The rating " + - "command requires a book index."); - String[] ratingParts = inputArray[1].split(" ", 2); - // Split the message into index and label message - assert ratingParts.length == 2 : "Command requires an index and a rating"; - if (ratingParts.length < 2) { - throw new InvalidCommandArgumentException("You need to have a book index and a rating"); - } - index = Integer.parseInt(ratingParts[0]); - int rating = Integer.parseInt(ratingParts[1]); - BookRating.setBookRatingByIndex(index, rating, books); - break; - case DISPLAY_COMMAND: - assert inputArray.length >= 2 : "Command requires additional arguments"; - validateCommandArguments(inputArray,2 , "The display " + - "Command requires a book index"); - index = Integer.parseInt(inputArray[1]); - BookDisplay.displayDetails(index, books); - break; - case PRINT_ORDERED_COMMAND: - BookRating.printBooksByRating(books); - break; - default: - LOGGER.log(Level.WARNING, "Sorry but that is not a valid command. Please try again", command); - throw new UnsupportedCommandException("Sorry but that is not a valid command. " + - "Please try again or type: help"); - } - } catch (Exception e) { - handleException(e, command, inputArray); - } - } -} diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java new file mode 100644 index 0000000000..146b01f849 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -0,0 +1,92 @@ +package seedu.bookbuddy.parser; + +import exceptions.UnsupportedCommandException; +import seedu.bookbuddy.bookdetails.BookDisplay; +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.Ui; +import seedu.bookbuddy.bookdetails.BookRating; +import seedu.bookbuddy.parser.parsercommands.ParserAdd; +import seedu.bookbuddy.parser.parsercommands.ParserDisplay; +import seedu.bookbuddy.parser.parsercommands.ParserGenre; +import seedu.bookbuddy.parser.parsercommands.ParserLabel; +import seedu.bookbuddy.parser.parsercommands.ParserMark; +import seedu.bookbuddy.parser.parsercommands.ParserRating; +import seedu.bookbuddy.parser.parsercommands.ParserRemove; +import seedu.bookbuddy.parser.parsercommands.ParserSummary; +import seedu.bookbuddy.parser.parsercommands.ParserUnmark; +import seedu.bookbuddy.parser.parservalidation.CommandList; +import seedu.bookbuddy.parser.parservalidation.Exceptions; + +import java.util.logging.Level; + +import static seedu.bookbuddy.BookBuddy.LOGGER; + +/** + * Parses inputs from the user in order to execute the correct commands. + */ +public class ParserMain { + + /** + * Scans the user input for valid commands and handles them accordingly. + * @param input input from the user + * @param books ArrayList of books + */ + public static void parseCommand(String input, BookList books) { + String[] inputArray = input.split(" ", 2); + String command = inputArray[0].toLowerCase(); + LOGGER.log(Level.FINE, "Parsing command: {0}", command); + int index; + + try { + switch (command) { + case CommandList.ADD_COMMAND: + ParserAdd.executeParseAdd(books, inputArray); + break; + case CommandList.REMOVE_COMMAND: + ParserRemove.executeParseRemove(books, inputArray); + break; + case CommandList.LIST_COMMAND: + BookDisplay.printAllBooks(books); + break; + case CommandList.MARK_COMMAND: + ParserMark.executeParseMark(books, inputArray); + break; + case CommandList.UNMARK_COMMAND: + ParserUnmark.executeParseUnmark(books, inputArray); + break; + case CommandList.HELP_COMMAND: + Ui.helpMessage(); + break; + case CommandList.FIND_COMMAND: + BookDisplay.findBook(books, inputArray[1]); + break; + case CommandList.LABEL_COMMAND: + ParserLabel.executeParseSetLabel(books, inputArray); + break; + case CommandList.SUMMARY_COMMAND: + ParserSummary.executeParseSummary(books, inputArray); + break; + case CommandList.GENRE_COMMAND: + // Exit the command if user types 'exit' + if (ParserGenre.executeParseSetGenre(books, inputArray)) return; + break; + case CommandList.RATING_COMMAND: + ParserRating.executeParseSetRating(books, inputArray); + break; + case CommandList.DISPLAY_COMMAND: + ParserDisplay.executeParseAdd(books, inputArray); + break; + case CommandList.PRINT_ORDERED_COMMAND: + BookRating.printBooksByRating(books); + break; + default: + LOGGER.log(Level.WARNING, "Sorry but that is not a valid command. Please try again", command); + throw new UnsupportedCommandException("Sorry but that is not a valid command. " + + "Please try again or type: help"); + } + } catch (Exception e) { + Exceptions.handleException(e, command, inputArray); + } + } + +} diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAdd.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAdd.java new file mode 100644 index 0000000000..02d87c9c7c --- /dev/null +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAdd.java @@ -0,0 +1,19 @@ +package seedu.bookbuddy.parser.parsercommands; + +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.booklist.BookListModifier; +import seedu.bookbuddy.parser.parservalidation.Exceptions; + +public class ParserAdd { + //@@author joshuahoky + private static void parseAdd(BookList books, String[] inputArray) { + assert inputArray.length >= 2 : "Command requires additional arguments"; + Exceptions.validateCommandArguments(inputArray, 2, "The add " + + "Command requires a book title"); + BookListModifier.addBook(books, inputArray[1]); + } + //@@author + public static void executeParseAdd (BookList books, String[] inputArray) { + parseAdd(books, inputArray); + } +} diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserDisplay.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserDisplay.java new file mode 100644 index 0000000000..077633c9d3 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserDisplay.java @@ -0,0 +1,21 @@ +package seedu.bookbuddy.parser.parsercommands; + +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.bookdetails.BookDisplay; +import seedu.bookbuddy.parser.parservalidation.Exceptions; + +public class ParserDisplay { + //@@ author joshuahoky + static void parseDisplay(BookList books, String[] inputArray) { + int index; + assert inputArray.length >= 2 : "Command requires additional arguments"; + Exceptions.validateCommandArguments(inputArray,2 , "The display " + + "Command requires a book index"); + index = Integer.parseInt(inputArray[1]); + BookDisplay.displayDetails(index, books); + } + //@@author joshuahoky + public static void executeParseAdd (BookList books, String[] inputArray) { + parseDisplay(books, inputArray); + } +} diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java new file mode 100644 index 0000000000..bc837645a9 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java @@ -0,0 +1,75 @@ +package seedu.bookbuddy.parser.parsercommands; + +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.bookdetails.BookGenre; +import seedu.bookbuddy.parser.parservalidation.Exceptions; + +import java.util.Scanner; + +public class ParserGenre { + static boolean parseSetGenre(BookList books, String[] inputArray) { + int index; + Exceptions.validateCommandArguments(inputArray, 2, "The set-genre " + + "Command requires a book index."); + index = Integer.parseInt(inputArray[1]); + if (index < 0 || index > books.getSize()) { + throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index. " + + "Type 'list' to view the list of books."); + } + genreSelectionPrinter(); + + System.out.println("Enter the number for the desired genre, or add a new one:"); + Scanner scanner = new Scanner(System.in); + + String selectedGenre = null; + selectedGenre = invalidInputLooper(selectedGenre, scanner); + if (selectedGenre == null) return true; + + BookGenre.setBookGenreByIndex(index, selectedGenre, books); + System.out.println("Genre set to " + selectedGenre + " for book at index " + index); + return false; + } + + private static void genreSelectionPrinter() { + System.out.println("Available genres:"); + for (int i = 0; i < BookList.getAvailableGenres().size(); i++) { + System.out.println((i + 1) + ". " + BookList.getAvailableGenres().get(i)); + } + System.out.println((BookList.getAvailableGenres().size() + 1) + ". Add a new genre"); + } + + private static String invalidInputLooper(String selectedGenre, Scanner scanner) { + while (selectedGenre == null) { + while (!scanner.hasNextInt()) { // Ensure the next input is an integer + String newInput = scanner.nextLine(); + if ("exit".equalsIgnoreCase(newInput)) { + return null; + } else { + System.out.println("Invalid input. Please enter a valid number or type 'exit'" + + " to cancel."); + } + } + + int genreSelection = scanner.nextInt(); + scanner.nextLine(); // Consume the newline after the number + + if (genreSelection == BookList.getAvailableGenres().size() + 1) { + System.out.println("Enter the new genre:"); + selectedGenre = scanner.nextLine(); + BookList.getAvailableGenres().add(selectedGenre); // Add the new genre to the list + } else if (genreSelection > 0 && genreSelection <= BookList.getAvailableGenres().size()) { + selectedGenre = BookList.getAvailableGenres().get(genreSelection - 1); + } else { + System.out.println("Invalid selection. Please enter a valid number " + + "or type 'exit' to cancel."); + // No need for the nextLine or parsing logic here, the while loop will continue + } + } + return selectedGenre; + } + + public static boolean executeParseSetGenre (BookList books, String[] inputArray) { + parseSetGenre(books, inputArray); + return false; + } +} diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java new file mode 100644 index 0000000000..3e2383054d --- /dev/null +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java @@ -0,0 +1,28 @@ +package seedu.bookbuddy.parser.parsercommands; + +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.bookdetails.BookLabel; +import seedu.bookbuddy.parser.parservalidation.Exceptions; + +public class ParserLabel { + static void parseSetLabel(BookList books, String[] inputArray) { + int index; + assert inputArray.length == 2 : "Command requires additional arguments"; + Exceptions.validateCommandArguments(inputArray,2, "The Label " + + "Command requires a book index and label"); + String[] labelMessageParts = inputArray[1].split(" ", 2); + // Split the message into index and label message + assert labelMessageParts.length == 2 : "Command requires an index and a label message"; + Exceptions.validateCommandArguments(labelMessageParts, 2, "You " + + "need to have a label message"); + index = Integer.parseInt(labelMessageParts[0]); + assert index >= 0 : "Index should be non-negative"; + String label = labelMessageParts[1]; + System.out.println(index); + BookLabel.setBookLabelByIndex(index, label, books); + } + + public static void executeParseSetLabel (BookList books, String[] inputArray) { + parseSetLabel(books, inputArray); + } +} diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserMark.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserMark.java new file mode 100644 index 0000000000..d1b6c00d03 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserMark.java @@ -0,0 +1,23 @@ +package seedu.bookbuddy.parser.parsercommands; + +import seedu.bookbuddy.bookdetails.BookMark; +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.parser.parservalidation.Exceptions; + +public class ParserMark { + //@@author joshuahoky + static void parseMark(BookList books, String[] inputArray) { + int index; + assert inputArray.length >= 2 : "Command requires additional arguments"; + Exceptions.validateCommandArguments(inputArray, 2, "The mark " + + "Command requires a book index"); + index = Integer.parseInt(inputArray[1]); + assert index >= 0 : "Index should be non-negative"; + BookMark.markDoneByIndex(books, index); + } + + //@@author + public static void executeParseMark (BookList books, String[] inputArray) { + parseMark(books, inputArray); + } +} diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserRating.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserRating.java new file mode 100644 index 0000000000..d9e49e750f --- /dev/null +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserRating.java @@ -0,0 +1,28 @@ +package seedu.bookbuddy.parser.parsercommands; + +import exceptions.InvalidCommandArgumentException; +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.bookdetails.BookRating; +import seedu.bookbuddy.parser.parservalidation.Exceptions; + +public class ParserRating { + static void parseSetRating(BookList books, String[] inputArray) { + int index; + assert inputArray.length >= 2 : "Command requires additional arguments"; + Exceptions.validateCommandArguments(inputArray, 2, "The rating " + + "command requires a book index."); + String[] ratingParts = inputArray[1].split(" ", 2); + // Split the message into index and label message + assert ratingParts.length == 2 : "Command requires an index and a rating"; + if (ratingParts.length < 2) { + throw new InvalidCommandArgumentException("You need to have a book index and a rating"); + } + index = Integer.parseInt(ratingParts[0]); + int rating = Integer.parseInt(ratingParts[1]); + BookRating.setBookRatingByIndex(index, rating, books); + } + + public static void executeParseSetRating (BookList books, String[] inputArray) { + parseSetRating(books, inputArray); + } +} diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserRemove.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserRemove.java new file mode 100644 index 0000000000..e79a659f79 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserRemove.java @@ -0,0 +1,20 @@ +package seedu.bookbuddy.parser.parsercommands; + +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.booklist.BookListModifier; +import seedu.bookbuddy.parser.parservalidation.Exceptions; + +public class ParserRemove { + static void parseRemove(BookList books, String[] inputArray) { + int index; + assert inputArray.length >= 2 : "Command requires additional arguments"; + Exceptions.validateCommandArguments(inputArray, 2, "The remove " + + "Command requires a book index"); + index = Integer.parseInt(inputArray[1]); + BookListModifier.deleteBook(books, index); + } + + public static void executeParseRemove (BookList books, String[] inputArray) { + parseRemove(books, inputArray); + } +} diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java new file mode 100644 index 0000000000..06ae3c31bc --- /dev/null +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java @@ -0,0 +1,27 @@ +package seedu.bookbuddy.parser.parsercommands; + +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.bookdetails.BookSummary; +import seedu.bookbuddy.parser.parservalidation.Exceptions; + +public class ParserSummary { + //@@author lordgareth10 + static void parseSummary(BookList books, String[] inputArray) { + int index; + assert inputArray.length == 2 : "Command requires additional arguments"; + Exceptions.validateCommandArguments(inputArray,2, "The summary " + + "Command requires a book index and summary"); + String[] summaryMessageParts = inputArray[1].split(" ", 2); + assert summaryMessageParts.length == 2 : "Command requires an index and a label message"; + Exceptions.validateCommandArguments(summaryMessageParts,2, "You need " + + "to have a summary message"); + index = Integer.parseInt(summaryMessageParts[0]); + assert index >= 0 : "Index should be non-negative"; + String summary = summaryMessageParts[1]; + BookSummary.setBookSummaryByIndex(index, summary, books); + } + //@@author + public static void executeParseSummary (BookList books, String[] inputArray) { + parseSummary(books, inputArray); + } +} diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserUnmark.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserUnmark.java new file mode 100644 index 0000000000..8712d68d79 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserUnmark.java @@ -0,0 +1,21 @@ +package seedu.bookbuddy.parser.parsercommands; + +import seedu.bookbuddy.bookdetails.BookMark; +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.parser.parservalidation.Exceptions; + +public class ParserUnmark { + static void parseUnmark(BookList books, String[] inputArray) { + int index; + assert inputArray.length == 2 : "Command requires additional arguments"; + Exceptions.validateCommandArguments(inputArray, 2, "The unmark " + + "Command requires a book index"); + index = Integer.parseInt(inputArray[1]); + assert index >= 0 : "Index should be non-negative"; + BookMark.markUndoneByIndex(books, index); + } + + public static void executeParseUnmark (BookList books, String[] inputArray) { + parseUnmark(books, inputArray); + } +} diff --git a/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java b/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java new file mode 100644 index 0000000000..d555b91649 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java @@ -0,0 +1,17 @@ +package seedu.bookbuddy.parser.parservalidation; + +public class CommandList { + public static final String ADD_COMMAND = "add"; + public static final String REMOVE_COMMAND = "remove"; + public static final String LIST_COMMAND = "list"; + public static final String MARK_COMMAND = "mark"; + public static final String UNMARK_COMMAND = "unmark"; + public static final String HELP_COMMAND = "help"; + public static final String FIND_COMMAND = "find"; + public static final String LABEL_COMMAND = "label"; + public static final String GENRE_COMMAND = "set-genre"; + public static final String SUMMARY_COMMAND = "give-summary"; + public static final String DISPLAY_COMMAND = "display"; + public static final String RATING_COMMAND = "rate"; + public static final String PRINT_ORDERED_COMMAND = "list-rated"; +} diff --git a/src/main/java/seedu/bookbuddy/parser/parservalidation/Exceptions.java b/src/main/java/seedu/bookbuddy/parser/parservalidation/Exceptions.java new file mode 100644 index 0000000000..0b5024ec93 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/parser/parservalidation/Exceptions.java @@ -0,0 +1,40 @@ +package seedu.bookbuddy.parser.parservalidation; + +import exceptions.InvalidCommandArgumentException; +import exceptions.UnsupportedCommandException; + +import java.util.logging.Level; + +import static seedu.bookbuddy.BookBuddy.LOGGER; + +public class Exceptions { + public static void validateCommandArguments(String[] inputArray, int requiredArgs, String errorMessage) + throws InvalidCommandArgumentException { + if (inputArray.length < requiredArgs) { + LOGGER.log(Level.WARNING, errorMessage, inputArray); + throw new InvalidCommandArgumentException(errorMessage); + } + } + + public static void handleException(Exception e, String command, String[] inputArray) { + if (e instanceof UnsupportedCommandException) { + LOGGER.log(Level.WARNING, "Command is invalid: {0}", e.getMessage()); + System.out.println(e.getMessage()); + } else if (e instanceof NumberFormatException) { + System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + + "Please enter a valid numeric index. Type 'list' to view list of books."); + } else if (e instanceof IndexOutOfBoundsException) { + System.out.println("Invalid book index. Please enter a valid index."); + } else if (e instanceof InvalidCommandArgumentException) { + LOGGER.log(Level.WARNING, "Invalid command argument: {0}", new Object[]{e.getMessage()}); + System.out.println(e.getMessage()); + } else if (e instanceof IllegalArgumentException) { + System.out.println(e.getMessage()); + } else { + LOGGER.log(Level.SEVERE, "An unexpected error occurred while executing {0}: {1}", + new Object[]{command, e.getMessage()}); + System.out.println("An unexpected error occurred while executing " + command + + ". Please contact support."); + } + } +} diff --git a/src/test/java/seedu/bookbuddy/BookDetailsTest.java b/src/test/java/seedu/bookbuddy/BookDetailsTest.java index dc734ff6c2..f2ba1aa862 100644 --- a/src/test/java/seedu/bookbuddy/BookDetailsTest.java +++ b/src/test/java/seedu/bookbuddy/BookDetailsTest.java @@ -3,6 +3,8 @@ import org.junit.jupiter.api.Test; import seedu.bookbuddy.bookdetails.BookGenre; import seedu.bookbuddy.bookdetails.BookLabel; +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.booklist.BookListModifier; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -10,8 +12,8 @@ public class BookDetailsTest { @Test public void testSetBookLabelByIndex() { BookList books = new BookList(); - books.addBook("The Great Gatsby"); - books.addBook("Geronimo Stilton"); + BookListModifier.addBook(books, "The Great Gatsby"); + BookListModifier.addBook(books, "Geronimo Stilton"); BookLabel.setBookLabelByIndex(1, "Great Classic", books); assertEquals("Great Classic" ,books.getBook(1).getLabel()); BookLabel.setBookLabelByIndex(2, "Great Classic", books); @@ -21,8 +23,8 @@ public void testSetBookLabelByIndex() { @Test public void testSetBookGenreByIndex() { BookList books = new BookList(); - books.addBook("The Great Gatsby"); - books.addBook("Geronimo Stilton"); + BookListModifier.addBook(books, "The Great Gatsby"); + BookListModifier.addBook(books, "Geronimo Stilton"); BookGenre.setBookGenreByIndex(1, "Classic", books); assertEquals("Classic" ,books.getBook(1).getGenre()); BookGenre.setBookGenreByIndex(2, "Fantasy", books); diff --git a/src/test/java/seedu/bookbuddy/BookListTest.java b/src/test/java/seedu/bookbuddy/BookListTest.java index 6be5b83f2f..35d0dfaa73 100644 --- a/src/test/java/seedu/bookbuddy/BookListTest.java +++ b/src/test/java/seedu/bookbuddy/BookListTest.java @@ -1,6 +1,10 @@ package seedu.bookbuddy; import org.junit.jupiter.api.Test; +import seedu.bookbuddy.bookdetails.BookDisplay; +import seedu.bookbuddy.bookdetails.BookMark; +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.booklist.BookListModifier; import java.io.ByteArrayOutputStream; import java.io.PrintStream; @@ -20,7 +24,7 @@ public void sampleTest() { @Test void addBook() { BookList testBookList = new BookList(); - testBookList.addBook("Harry Potter"); + BookListModifier.addBook(testBookList, "Harry Potter"); assertEquals(1, testBookList.getSize()); assertEquals("[U] Harry Potter", testBookList.getBook(1).toString()); } @@ -28,12 +32,12 @@ void addBook() { @Test void printAllBooks() { BookList testBookList = new BookList(); - testBookList.addBook("Harry Potter"); + BookListModifier.addBook(testBookList, "Harry Potter"); ByteArrayOutputStream outContent = new ByteArrayOutputStream(); System.setOut(new PrintStream(outContent)); - testBookList.printAllBooks(); + BookDisplay.printAllBooks(testBookList); String expectedOutput = "All books:\n1. [U] Harry Potter\n"; String normalizedActualOutput = outContent.toString().replace("\r\n", "\n"); @@ -45,36 +49,36 @@ void printAllBooks() { @Test void deleteBook() { BookList bookList = new BookList(); - bookList.addBook("Harry Potter"); + BookListModifier.addBook(bookList, "Harry Potter"); assertEquals(1, bookList.getSize()); - bookList.deleteBook(1); + BookListModifier.deleteBook(bookList, 1); assertEquals(0, bookList.getSize()); } @Test void getBook() { BookList bookList = new BookList(); - bookList.addBook("Harry Potter"); - bookList.addBook("Geronimo"); - bookList.addBook("Cradle"); + BookListModifier.addBook(bookList, "Harry Potter"); + BookListModifier.addBook(bookList, "Geronimo"); + BookListModifier.addBook(bookList, "Cradle"); assertEquals("[U] Cradle", bookList.getBook(3).toString()); } @Test void markDoneByIndex() { BookList bookList = new BookList(); - bookList.addBook("Harry Potter"); - bookList.markDoneByIndex(1); + BookListModifier.addBook(bookList, "Harry Potter"); + BookMark.markDoneByIndex(bookList, 1); assertEquals("[R] Harry Potter", bookList.getBook(1).toString()); } @Test void markUndoneByIndex() { BookList bookList = new BookList(); - bookList.addBook("Harry Potter"); - bookList.markDoneByIndex(1); + BookListModifier.addBook(bookList, "Harry Potter"); + BookMark.markDoneByIndex(bookList, 1); assertEquals("[R] Harry Potter", bookList.getBook(1).toString()); - bookList.markUndoneByIndex(1); + BookMark.markUndoneByIndex(bookList, 1); assertEquals("[U] Harry Potter", bookList.getBook(1).toString()); } diff --git a/src/test/java/seedu/bookbuddy/ParserTest.java b/src/test/java/seedu/bookbuddy/ParserMainTest.java similarity index 72% rename from src/test/java/seedu/bookbuddy/ParserTest.java rename to src/test/java/seedu/bookbuddy/ParserMainTest.java index b83ea4fd57..cf02e2aa81 100644 --- a/src/test/java/seedu/bookbuddy/ParserTest.java +++ b/src/test/java/seedu/bookbuddy/ParserMainTest.java @@ -3,7 +3,10 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import seedu.bookbuddy.parser.Parser; +import seedu.bookbuddy.bookdetails.BookMark; +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.booklist.BookListModifier; +import seedu.bookbuddy.parser.ParserMain; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -15,7 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -public class ParserTest { +public class ParserMainTest { private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); private final PrintStream originalOut = System.out; @@ -31,14 +34,14 @@ public void restoreStreams() { @Test void testParser() { BookList books = new BookList(); - books.addBook("Don Quixote"); - books.addBook("Gulliver's Travels"); + BookListModifier.addBook(books, "Don Quixote"); + BookListModifier.addBook(books, "Gulliver's Travels"); assertEquals(2, books.getSize()); - books.markDoneByIndex(1); + BookMark.markDoneByIndex(books, 1); assertEquals("[R] Don Quixote", books.getBook(1).toString()); assertEquals("[U] Gulliver's Travels", books.getBook(2).toString()); - books.deleteBook(1); - books.markDoneByIndex(1); + BookListModifier.deleteBook(books, 1); + BookMark.markDoneByIndex(books, 1); assertTrue(books.getBook(1).isRead); assertEquals("[R] Gulliver's Travels", books.getBook(1).toString()); } @@ -46,7 +49,7 @@ void testParser() { @Test void parseAddCommand() { BookList testBookList = new BookList(); - Parser.parseCommand("add The Great Gatsby", testBookList); + ParserMain.parseCommand("add The Great Gatsby", testBookList); assertEquals(1, testBookList.getSize()); assertEquals("The Great Gatsby", testBookList.getBook(1).getTitle()); } @@ -54,8 +57,8 @@ void parseAddCommand() { @Test void parseRemoveCommand() { BookList books = new BookList(); - books.addBook("The Great Gatsby"); - Parser.parseCommand("remove 1", books); + BookListModifier.addBook(books, "The Great Gatsby"); + ParserMain.parseCommand("remove 1", books); assertEquals(0, books.getSize()); } @@ -63,46 +66,46 @@ void parseRemoveCommand() { void parseMarkCommand() { BookList books = new BookList(); System.out.println(books.getSize()); - books.addBook("The Great Gatsby"); + BookListModifier.addBook(books, "The Great Gatsby"); System.out.println(books); - Parser.parseCommand("mark 1", books); + ParserMain.parseCommand("mark 1", books); System.out.println(books); - books.markDoneByIndex(1); + BookMark.markDoneByIndex(books, 1); assertTrue(books.getBook(1).isRead()); } @Test void parseUnmarkCommand() { BookList books = new BookList(); - books.addBook("The Great Gatsby"); - Parser.parseCommand("mark 1", books); - Parser.parseCommand("unmark 1", books); + BookListModifier.addBook(books, "The Great Gatsby"); + ParserMain.parseCommand("mark 1", books); + ParserMain.parseCommand("unmark 1", books); assertFalse(books.getBook(1).isRead()); } @Test void parseLabelCommand() { BookList books = new BookList(); - books.addBook("The Great Gatsby"); - Parser.parseCommand("label 1 Great Book", books); + BookListModifier.addBook(books, "The Great Gatsby"); + ParserMain.parseCommand("label 1 Great Book", books); assertEquals("Great Book", books.getBook(1).getLabel()); } @Test void parseGenreCommand() { BookList books = new BookList(); - books.addBook("The Great Gatsby"); + BookListModifier.addBook(books, "The Great Gatsby"); // Simulate user input for genre selection "Classic" String simulatedUserInput = "6\nClassic\n"; // Assuming '3' is the option to add a new genre InputStream savedStandardInputStream = System.in; System.setIn(new ByteArrayInputStream(simulatedUserInput.getBytes())); - Parser.parseCommand("set-genre 1", books); // Changed to fit your updated command-handling logic + ParserMain.parseCommand("set-genre 1", books); // Changed to fit your updated command-handling logic assertEquals("Classic", books.getBook(1).getGenre()); // Indexes are typically 0-based in lists - books.addBook("Geronimo"); + BookListModifier.addBook(books, "Geronimo"); String nextSimulatedUserInput = "3\n"; System.setIn(new ByteArrayInputStream(nextSimulatedUserInput.getBytes())); - Parser.parseCommand("set-genre 2", books); + ParserMain.parseCommand("set-genre 2", books); assertEquals("Mystery", books.getBook(2).getGenre()); System.setIn(savedStandardInputStream); } @@ -111,7 +114,7 @@ void parseGenreCommand() { void parseInvalidAddCommandThrowsException() { BookList books = new BookList(); String input = "add"; // No book title provided - Parser.parseCommand(input, books); // Execute the command that should trigger the error message + ParserMain.parseCommand(input, books); // Execute the command that should trigger the error message String expectedOutput = "The add Command requires a book title"; assertTrue(outContent.toString().contains(expectedOutput), @@ -126,7 +129,7 @@ void parseInvalidRemoveCommandPrintsError() { ByteArrayOutputStream outContent = new ByteArrayOutputStream(); System.setOut(new PrintStream(outContent)); // Redirect standard out to capture console output // Act - Parser.parseCommand(input, books); + ParserMain.parseCommand(input, books); // Assert String output = outContent.toString(); @@ -140,7 +143,7 @@ void parseInvalidRemoveCommandPrintsError() { void parseUnsupportedCommandThrowsException() { BookList books = new BookList(); String input = "Geronimo Stilton"; // Completely unsupported command - Parser.parseCommand(input, books); // Execute the command + ParserMain.parseCommand(input, books); // Execute the command String expectedMessage = "Sorry but that is not a valid command. Please try again"; assertTrue(outContent.toString().contains(expectedMessage), "Expected message not found in the console output."); From 18b0118b879981920c66aee5fea523a509445919 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Tue, 2 Apr 2024 23:04:51 +0800 Subject: [PATCH 132/311] Abstraction of code for code readability in Book, BookDetails, BookList, Parser class --- src/main/java/seedu/bookbuddy/Book.java | 160 ------------------ src/main/java/seedu/bookbuddy/Ui.java | 6 +- .../java/seedu/bookbuddy/book/BookMain.java | 50 ++++++ src/main/java/seedu/bookbuddy/book/Genre.java | 23 +++ src/main/java/seedu/bookbuddy/book/Label.java | 23 +++ .../java/seedu/bookbuddy/book/Rating.java | 27 +++ src/main/java/seedu/bookbuddy/book/Read.java | 18 ++ .../java/seedu/bookbuddy/book/Summary.java | 25 +++ src/main/java/seedu/bookbuddy/book/Title.java | 13 ++ .../BookDisplay.java | 24 +-- .../BookGenre.java | 8 +- .../BookLabel.java | 8 +- .../BookMark.java | 41 ++++- .../BookRating.java | 20 ++- .../BookSummary.java | 8 +- .../seedu/bookbuddy/booklist/BookList.java | 12 +- .../bookbuddy/booklist/BookListModifier.java | 6 +- .../seedu/bookbuddy/parser/ParserMain.java | 4 +- .../parser/parsercommands/ParserDisplay.java | 2 +- .../parser/parsercommands/ParserGenre.java | 2 +- .../parser/parsercommands/ParserLabel.java | 2 +- .../parser/parsercommands/ParserMark.java | 2 +- .../parser/parsercommands/ParserRating.java | 2 +- .../parser/parsercommands/ParserSummary.java | 2 +- .../parser/parsercommands/ParserUnmark.java | 2 +- .../java/seedu/bookbuddy/BookDetailsTest.java | 14 +- .../java/seedu/bookbuddy/BookListTest.java | 4 +- .../java/seedu/bookbuddy/ParserMainTest.java | 17 +- 28 files changed, 290 insertions(+), 235 deletions(-) delete mode 100644 src/main/java/seedu/bookbuddy/Book.java create mode 100644 src/main/java/seedu/bookbuddy/book/BookMain.java create mode 100644 src/main/java/seedu/bookbuddy/book/Genre.java create mode 100644 src/main/java/seedu/bookbuddy/book/Label.java create mode 100644 src/main/java/seedu/bookbuddy/book/Rating.java create mode 100644 src/main/java/seedu/bookbuddy/book/Read.java create mode 100644 src/main/java/seedu/bookbuddy/book/Summary.java create mode 100644 src/main/java/seedu/bookbuddy/book/Title.java rename src/main/java/seedu/bookbuddy/{bookdetails => bookdetailsmodifier}/BookDisplay.java (68%) rename src/main/java/seedu/bookbuddy/{bookdetails => bookdetailsmodifier}/BookGenre.java (77%) rename src/main/java/seedu/bookbuddy/{bookdetails => bookdetailsmodifier}/BookLabel.java (77%) rename src/main/java/seedu/bookbuddy/{bookdetails => bookdetailsmodifier}/BookMark.java (61%) rename src/main/java/seedu/bookbuddy/{bookdetails => bookdetailsmodifier}/BookRating.java (69%) rename src/main/java/seedu/bookbuddy/{bookdetails => bookdetailsmodifier}/BookSummary.java (75%) diff --git a/src/main/java/seedu/bookbuddy/Book.java b/src/main/java/seedu/bookbuddy/Book.java deleted file mode 100644 index 8e9cc1c32e..0000000000 --- a/src/main/java/seedu/bookbuddy/Book.java +++ /dev/null @@ -1,160 +0,0 @@ -package seedu.bookbuddy; - -import java.util.Objects; - -public class Book { - protected String title; - protected boolean isRead; - protected String label; - protected String genre; - protected int rating; - protected String summary; - - /** - * Creates a new Book with the specified title. - * - * @param title The description of the book. - */ - public Book(String title) { - this.title = title; // Description of the book - this.isRead = false; //Completion status of the book (True: Read, False: Unread) - this.label = ""; - this.genre = ""; - this.rating = -1; - this.summary = ""; - } - - public Book(String title, int status, String label, String genre, int rating, String summary) { - this.title = title; - this.isRead = status == 1; - this.label = (Objects.equals(label, "*")) ? "" : label; - this.genre = (Objects.equals(genre, "*")) ? "" : genre; - this.rating = rating; - this.summary = (Objects.equals(summary, "*")) ? "" : summary; - } - - /** - * Sets the rating for this book. The rating must be between 1 and 5. - * - * @param rating The rating to set for the book. - * @throws IllegalArgumentException if the rating is not between 1 and 5. - */ - public void setRating(int rating) { - if (rating < 1 || rating > 5) { - throw new IllegalArgumentException("Rating must be between 1 and 5."); - } - this.rating = rating; - } - - /** - * Returns the rating of the book. - * - * @return The rating of the book. - */ - public int getRating() { - return this.rating; - } - - /** - * Sets the genre for this book. - * - * @param genre The label to set for the book. - */ - public void setGenre(String genre) { - this.genre = genre; // Set the genre for the book - } - - /** - * Returns the genre of the book. - * - * @return The genre of the book. - */ - public String getGenre() { - return this.genre; - } - - /** - * Sets the label for this book. - * - * @param label The label to set for the book. - */ - public void setLabel(String label) { - this.label = label; // Set the label for the book - } - - /** - * Returns the label of the book. - * - * @return The label of the book. - */ - public String getLabel() { - return this.label; - } - - /** - * Sets the summary for this book. - * - * @param summary The summary to set for the book. - */ - public void setSummary(String summary) { - this.summary = summary; - } - - /** - * Returns the summary of the book. - * - * @return The summary of the book. - */ - public String getSummary() { - return this.summary; - } - - /** - * Returns the title of the book. - * - * @return The title of the book. - */ - public String getTitle() { - return this.title; - } - - /** - * Checks if the book is read. - * - * @return True if the book is read, false otherwise. - */ - public boolean isRead() { - return this.isRead; - } - - /** - * Marks the book as read. - */ - public void markBookAsRead() { - this.isRead = true; - System.out.println("Successfully marked " + this.getTitle() + " as read."); - } - - /** - * Marks the book as unread. - */ - public void markBookAsUnread() { - this.isRead = false; - System.out.println("Successfully marked " + this.getTitle() + " as unread."); - } - - @Override - public String toString() { - String statusMark = this.isRead() ? "R" : "U"; // Mark with 'R' if read and 'U' if unread - return "[" + statusMark + "] " + this.title; - } - - public String saveFormat() { - String status = isRead ? "1" : "0"; - String label = (this.label.isEmpty()) ? "*" : this.label; - String genre = (this.genre.isEmpty()) ? "*" : this.genre; - String summary = (this.summary.isEmpty()) ? "*" : this.summary; - return this.title + " | " + status + " | " + label + " | " + genre + " | " + this.rating - + " | " + summary; - } -} diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 13134dfcb4..21338f48f9 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -1,5 +1,7 @@ package seedu.bookbuddy; +import seedu.bookbuddy.book.BookMain; +import seedu.bookbuddy.book.Title; import seedu.bookbuddy.booklist.BookList; import java.util.ArrayList; @@ -50,7 +52,7 @@ public static void setRatingBookMessage(String title, int rating) { System.out.println("remember to read it soon...."); } public static void removeBookMessage(int index, BookList books) { - System.out.println("alright.. i've removed " + books.getBook(index).getTitle() + " from the list."); + System.out.println("alright.. i've removed " + Title.getTitle(books.getBook(index)) + " from the list."); } public static void helpMessage() { System.out.println("Here's a list of commands to get you started!!"); @@ -62,7 +64,7 @@ public static void helpMessage() { System.out.println("bye -> to exit BookBuddy software"); } - public static void printBookFound(ArrayList bookTitles){ + public static void printBookFound(ArrayList bookTitles){ for (int i = 0; i < bookTitles.size(); i++) { System.out.println(i + 1 + ". " + bookTitles.get(i)); } diff --git a/src/main/java/seedu/bookbuddy/book/BookMain.java b/src/main/java/seedu/bookbuddy/book/BookMain.java new file mode 100644 index 0000000000..8243b51e7a --- /dev/null +++ b/src/main/java/seedu/bookbuddy/book/BookMain.java @@ -0,0 +1,50 @@ +package seedu.bookbuddy.book; + +import java.util.Objects; + +public class BookMain { + protected String title; + protected boolean isRead; + protected String label; + protected String genre; + protected int rating; + protected String summary; + + /** + * Creates a new Book with the specified title. + * + * @param title The description of the book. + */ + public BookMain(String title) { + this.title = title; // Description of the book + this.isRead = false; //Completion status of the book (True: Read, False: Unread) + this.label = ""; + this.genre = ""; + this.rating = -1; + this.summary = ""; + } + + public BookMain(String title, int status, String label, String genre, int rating, String summary) { + this.title = title; + this.isRead = status == 1; + this.label = (Objects.equals(label, "*")) ? "" : label; + this.genre = (Objects.equals(genre, "*")) ? "" : genre; + this.rating = rating; + this.summary = (Objects.equals(summary, "*")) ? "" : summary; + } + + @Override + public String toString() { + String statusMark = Read.getRead(this) ? "R" : "U"; // Mark with 'R' if read and 'U' if unread + return "[" + statusMark + "] " + this.title; + } + + public String saveFormat() { + String status = isRead ? "1" : "0"; + String label = (this.label.isEmpty()) ? "*" : this.label; + String genre = (this.genre.isEmpty()) ? "*" : this.genre; + String summary = (this.summary.isEmpty()) ? "*" : this.summary; + return this.title + " | " + status + " | " + label + " | " + genre + " | " + this.rating + + " | " + summary; + } +} diff --git a/src/main/java/seedu/bookbuddy/book/Genre.java b/src/main/java/seedu/bookbuddy/book/Genre.java new file mode 100644 index 0000000000..bcce932c0b --- /dev/null +++ b/src/main/java/seedu/bookbuddy/book/Genre.java @@ -0,0 +1,23 @@ +package seedu.bookbuddy.book; + +public class Genre { + /** + * Sets the genre for this book. + * + * @param book + * @param genre The label to set for the book. + */ + public static void setGenre(BookMain book, String genre) { + book.genre = genre; // Set the genre for the book + } + + /** + * Returns the genre of the book. + * + * @return The genre of the book. + * @param book + */ + public static String getGenre(BookMain book) { + return book.genre; + } +} diff --git a/src/main/java/seedu/bookbuddy/book/Label.java b/src/main/java/seedu/bookbuddy/book/Label.java new file mode 100644 index 0000000000..ad13b76f06 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/book/Label.java @@ -0,0 +1,23 @@ +package seedu.bookbuddy.book; + +public class Label { + /** + * Sets the label for this book. + * + * @param book + * @param label The label to set for the book. + */ + public static void setLabel(BookMain book, String label) { + book.label = label; // Set the label for the book + } + + /** + * Returns the label of the book. + * + * @return The label of the book. + * @param book + */ + public static String getLabel(BookMain book) { + return book.label; + } +} diff --git a/src/main/java/seedu/bookbuddy/book/Rating.java b/src/main/java/seedu/bookbuddy/book/Rating.java new file mode 100644 index 0000000000..a76ac59c7a --- /dev/null +++ b/src/main/java/seedu/bookbuddy/book/Rating.java @@ -0,0 +1,27 @@ +package seedu.bookbuddy.book; + +public class Rating { + /** + * Sets the rating for this book. The rating must be between 1 and 5. + * + * @param book + * @param rating The rating to set for the book. + * @throws IllegalArgumentException if the rating is not between 1 and 5. + */ + public static void setRating(BookMain book, int rating) { + if (rating < 1 || rating > 5) { + throw new IllegalArgumentException("Rating must be between 1 and 5."); + } + book.rating = rating; + } + + /** + * Returns the rating of the book. + * + * @return The rating of the book. + * @param book + */ + public static int getRating(BookMain book) { + return book.rating; + } +} diff --git a/src/main/java/seedu/bookbuddy/book/Read.java b/src/main/java/seedu/bookbuddy/book/Read.java new file mode 100644 index 0000000000..37b6348c89 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/book/Read.java @@ -0,0 +1,18 @@ +package seedu.bookbuddy.book; + +public class Read { + //@@author lordgareth10 + /** + * Checks if the book is read. + * + * @return True if the book is read, false otherwise. + * @param book + */ + public static boolean getRead(BookMain book) { + return book.isRead; + } + + public static void setRead(BookMain book, boolean read) { + book.isRead = read; + } +} diff --git a/src/main/java/seedu/bookbuddy/book/Summary.java b/src/main/java/seedu/bookbuddy/book/Summary.java new file mode 100644 index 0000000000..ee3df3c468 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/book/Summary.java @@ -0,0 +1,25 @@ +package seedu.bookbuddy.book; + +public class Summary { + + //@@author lordgareth10 + /** + * Sets the summary for this book. + * + * @param book + * @param summary The summary to set for the book. + */ + public static void setSummary(BookMain book, String summary) { + book.summary = summary; + } + + /** + * Returns the summary of the book. + * + * @return The summary of the book. + * @param book + */ + public static String getSummary(BookMain book) { + return book.summary; + } +} diff --git a/src/main/java/seedu/bookbuddy/book/Title.java b/src/main/java/seedu/bookbuddy/book/Title.java new file mode 100644 index 0000000000..0623a6f63b --- /dev/null +++ b/src/main/java/seedu/bookbuddy/book/Title.java @@ -0,0 +1,13 @@ +package seedu.bookbuddy.book; + +public class Title { + /** + * Returns the title of the book. + * + * @return The title of the book. + * @param book + */ + public static String getTitle(BookMain book) { + return book.title; + } +} diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java similarity index 68% rename from src/main/java/seedu/bookbuddy/bookdetails/BookDisplay.java rename to src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index 86b165b82f..20f7fa8054 100644 --- a/src/main/java/seedu/bookbuddy/bookdetails/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -1,6 +1,6 @@ -package seedu.bookbuddy.bookdetails; +package seedu.bookbuddy.bookdetailsmodifier; -import seedu.bookbuddy.Book; +import seedu.bookbuddy.book.*; import seedu.bookbuddy.Ui; import seedu.bookbuddy.booklist.BookList; @@ -20,12 +20,12 @@ public static void displayDetails(int index, BookList books) throws IndexOutOfBo } System.out.println("Here are the details of your book:"); - System.out.println("Title: " + books.getBook(index).getTitle()); - System.out.println("Status: " + (books.getBook(index).isRead() ? "Read" : "Unread")); - System.out.println("Label: " + books.getBook(index).getLabel()); - System.out.println("Genre: " + books.getBook(index).getGenre()); - System.out.println("Rating: " + books.getBook(index).getRating()); - System.out.println("Summary: " + books.getBook(index).getSummary()); + System.out.println("Title: " + Title.getTitle(books.getBook(index))); + System.out.println("Status: " + (Read.getRead(books.getBook(index)) ? "Read" : "Unread")); + System.out.println("Label: " + Label.getLabel(books.getBook(index))); + System.out.println("Genre: " + Genre.getGenre(books.getBook(index))); + System.out.println("Rating: " + Rating.getRating(books.getBook(index))); + System.out.println("Summary: " + Summary.getSummary(books.getBook(index))); } /** @@ -37,7 +37,7 @@ public static void printAllBooks(BookList bookList) { if (!bookList.getBooks().isEmpty()) { System.out.println("All books:"); for (int i = 0; i < bookList.getBooks().size(); i++) { - Book currentBook = bookList.getBooks().get(i); + BookMain currentBook = bookList.getBooks().get(i); assert currentBook != null : "Book in list should not be null"; System.out.print((i + 1) + ". "); System.out.println(currentBook.toString()); @@ -49,9 +49,9 @@ public static void printAllBooks(BookList bookList) { //@@author liuzehui03 public static void findBook(BookList bookList, String title) { - ArrayList bookTitles = new ArrayList<>(); - for (Book book : bookList.getBooks()) { - if (book.getTitle().contains(title)) { + ArrayList bookTitles = new ArrayList<>(); + for (BookMain book : bookList.getBooks()) { + if (Title.getTitle(book).contains(title)) { bookTitles.add(book); } } diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookGenre.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookGenre.java similarity index 77% rename from src/main/java/seedu/bookbuddy/bookdetails/BookGenre.java rename to src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookGenre.java index cd5c9a14ab..6a2ddd002f 100644 --- a/src/main/java/seedu/bookbuddy/bookdetails/BookGenre.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookGenre.java @@ -1,5 +1,7 @@ -package seedu.bookbuddy.bookdetails; +package seedu.bookbuddy.bookdetailsmodifier; +import seedu.bookbuddy.book.Genre; +import seedu.bookbuddy.book.Title; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.Ui; @@ -18,8 +20,8 @@ public static void setBookGenreByIndex(int index, String genre, BookList books) } // Set the genre for the book at the specified index - books.getBook(index).setGenre(genre); - String title = books.getBook(index).getTitle(); + Genre.setGenre(books.getBook(index), genre); + String title = Title.getTitle(books.getBook(index)); Ui.setGenreBookMessage(title, genre); } } diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookLabel.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookLabel.java similarity index 77% rename from src/main/java/seedu/bookbuddy/bookdetails/BookLabel.java rename to src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookLabel.java index fee9262bd6..c0217cba36 100644 --- a/src/main/java/seedu/bookbuddy/bookdetails/BookLabel.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookLabel.java @@ -1,5 +1,7 @@ -package seedu.bookbuddy.bookdetails; +package seedu.bookbuddy.bookdetailsmodifier; +import seedu.bookbuddy.book.Label; +import seedu.bookbuddy.book.Title; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.Ui; @@ -18,8 +20,8 @@ public static void setBookLabelByIndex(int index, String label, BookList books) } // Set the label for the book at the specified index - books.getBook(index).setLabel(label); - String title = books.getBook(index).getTitle(); + Label.setLabel(books.getBook(index), label); + String title = Title.getTitle(books.getBook(index)); Ui.labelBookMessage(title, label); } } diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookMark.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java similarity index 61% rename from src/main/java/seedu/bookbuddy/bookdetails/BookMark.java rename to src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java index 989e04edbd..148865258e 100644 --- a/src/main/java/seedu/bookbuddy/bookdetails/BookMark.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java @@ -1,7 +1,10 @@ -package seedu.bookbuddy.bookdetails; +package seedu.bookbuddy.bookdetailsmodifier; import exceptions.BookReadAlreadyException; import exceptions.BookUnreadAlreadyException; +import seedu.bookbuddy.book.BookMain; +import seedu.bookbuddy.book.Read; +import seedu.bookbuddy.book.Title; import seedu.bookbuddy.booklist.BookList; import java.util.logging.Level; @@ -20,12 +23,12 @@ public class BookMark { public static void markDoneByIndex(BookList bookList, int index) throws IndexOutOfBoundsException, BookReadAlreadyException { try { assert index > 0 && index <= bookList.getBooks().size() : "Index out of valid range"; - if (bookList.getBooks().get(index - 1).isRead()) { + if (Read.getRead(bookList.getBooks().get(index - 1))) { throw new BookReadAlreadyException("That book is already marked as read!"); } - assert !bookList.getBooks().get(index - 1).isRead() : "Book is already marked as read"; - bookList.getBooks().get(index - 1).markBookAsRead(); - assert bookList.getBooks().get(index - 1).isRead() : "Book should be marked as read"; + assert !Read.getRead(bookList.getBooks().get(index - 1)) : "Book is already marked as read"; + markBookAsRead(bookList.getBooks().get(index - 1)); + assert Read.getRead(bookList.getBooks().get(index - 1)) : "Book should be marked as read"; } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index"); } catch (BookReadAlreadyException e) { @@ -46,12 +49,12 @@ public static void markDoneByIndex(BookList bookList, int index) throws IndexOut public static void markUndoneByIndex(BookList bookList, int index) throws IndexOutOfBoundsException, BookReadAlreadyException{ try { assert index > 0 && index <= bookList.getBooks().size() : "Index out of valid range"; - if (!bookList.getBooks().get(index - 1).isRead()) { + if (!Read.getRead(bookList.getBooks().get(index - 1))) { throw new BookUnreadAlreadyException("That book is already marked as unread!"); } - assert bookList.getBooks().get(index - 1).isRead() : "Book is already marked as unread"; - bookList.getBooks().get(index - 1).markBookAsUnread(); - assert !bookList.getBooks().get(index - 1).isRead() : "Book should be marked as unread"; + assert Read.getRead(bookList.getBooks().get(index - 1)) : "Book is already marked as unread"; + markBookAsUnread(bookList.getBooks().get(index - 1)); + assert !Read.getRead(bookList.getBooks().get(index - 1)) : "Book should be marked as unread"; } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index"); } catch (BookUnreadAlreadyException e) { @@ -60,4 +63,24 @@ public static void markUndoneByIndex(BookList bookList, int index) throws IndexO System.out.println("An unexpected error occurred. Please contact support."); } } + + //@@author lordgareth10 + /** + * Marks the book as read. + * @param book + */ + public static void markBookAsRead(BookMain book) { + Read.setRead(book, true); + System.out.println("Successfully marked " + Title.getTitle(book) + " as read."); + } + + /** + * Marks the book as unread. + * @param book + */ + public static void markBookAsUnread(BookMain book) { + Read.setRead(book, false); + System.out.println("Successfully marked " + Title.getTitle(book) + " as unread."); + } + //@author } diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookRating.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookRating.java similarity index 69% rename from src/main/java/seedu/bookbuddy/bookdetails/BookRating.java rename to src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookRating.java index b8f748c069..3f6cb798a4 100644 --- a/src/main/java/seedu/bookbuddy/bookdetails/BookRating.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookRating.java @@ -1,6 +1,8 @@ -package seedu.bookbuddy.bookdetails; +package seedu.bookbuddy.bookdetailsmodifier; -import seedu.bookbuddy.Book; +import seedu.bookbuddy.book.BookMain; +import seedu.bookbuddy.book.Rating; +import seedu.bookbuddy.book.Title; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.Ui; @@ -20,13 +22,13 @@ public static void printBooksByRating(BookList books) { System.out.println("Books sorted by rating:"); - List sortedBooks = books.getBooks().stream() - .sorted(Comparator.comparingInt(Book::getRating).reversed()) + List sortedBooks = books.getBooks().stream() + .sorted(Comparator.comparingInt(Rating::getRating).reversed()) .collect(Collectors.toList()); - for (Book book : sortedBooks) { - String rating = book.getRating() >= 0 ? String.valueOf(book.getRating()) : "Not Rated"; - System.out.println(book.getTitle() + " - " + rating); + for (BookMain book : sortedBooks) { + String rating = Rating.getRating(book) >= 0 ? String.valueOf(Rating.getRating(book)) : "Not Rated"; + System.out.println(Title.getTitle(book) + " - " + rating); } } @@ -46,8 +48,8 @@ public static void setBookRatingByIndex(int index, int rating, BookList books) if (rating < 1 || rating > 5) { throw new IllegalArgumentException("Rating must be between 1 and 5."); } - books.getBook(index).setRating(rating); - String title = books.getBook(index).getTitle(); + Rating.setRating(books.getBook(index), rating); + String title = Title.getTitle(books.getBook(index)); Ui.setRatingBookMessage(title, rating); } } diff --git a/src/main/java/seedu/bookbuddy/bookdetails/BookSummary.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookSummary.java similarity index 75% rename from src/main/java/seedu/bookbuddy/bookdetails/BookSummary.java rename to src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookSummary.java index ecb7b7c405..ed140245ec 100644 --- a/src/main/java/seedu/bookbuddy/bookdetails/BookSummary.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookSummary.java @@ -1,5 +1,7 @@ -package seedu.bookbuddy.bookdetails; +package seedu.bookbuddy.bookdetailsmodifier; +import seedu.bookbuddy.book.Summary; +import seedu.bookbuddy.book.Title; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.Ui; @@ -17,8 +19,8 @@ public static void setBookSummaryByIndex(int index, String summary, BookList boo if (index < 0 || index > books.getSize()) { throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); } - books.getBook(index).setSummary(summary); - String title = books.getBook(index).getTitle(); + Summary.setSummary(books.getBook(index), summary); + String title = Title.getTitle(books.getBook(index)); Ui.summaryBookMessage(title, summary); } } diff --git a/src/main/java/seedu/bookbuddy/booklist/BookList.java b/src/main/java/seedu/bookbuddy/booklist/BookList.java index 444cf86db8..e92c514435 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookList.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookList.java @@ -1,7 +1,7 @@ package seedu.bookbuddy.booklist; import exceptions.BookNotFoundException; -import seedu.bookbuddy.Book; +import seedu.bookbuddy.book.BookMain; import java.util.ArrayList; import java.util.Arrays; @@ -17,16 +17,16 @@ public class BookList { public static List getAvailableGenres() { return availableGenres; } - protected ArrayList books; + protected ArrayList books; /** * Constructs a new BookList instance with an empty list. */ public BookList() { - this.books = new ArrayList(); // Use ArrayList instead of array + this.books = new ArrayList(); // Use ArrayList instead of array } // Public getter method for the books field - public List getBooks() { + public List getBooks() { return this.books; } @@ -45,12 +45,12 @@ public int getSize(){ * @param index The index of the book to retrieve. * @return The Book at the specified index. */ - public Book getBook(int index) throws BookNotFoundException{ + public BookMain getBook(int index) throws BookNotFoundException{ if (index < 0 || index > books.size()) { throw new BookNotFoundException("Book index out of range."); } assert books.get(index - 1) != null : "Retrieved book should not be null"; - assert books.get(index - 1) instanceof Book : "Object at index should be an instance of Book"; + assert books.get(index - 1) instanceof BookMain : "Object at index should be an instance of Book"; return books.get(index - 1); } diff --git a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java index 992369afaf..ea0f89f353 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java @@ -1,6 +1,6 @@ package seedu.bookbuddy.booklist; -import seedu.bookbuddy.Book; +import seedu.bookbuddy.book.BookMain; import seedu.bookbuddy.Ui; import java.util.logging.Level; @@ -18,7 +18,7 @@ public static void addBookFromFile(BookList bookList, String inputArray) { String genre = bookDetails[3]; int rating = Integer.parseInt(bookDetails[4]); String summary = bookDetails[5]; - bookList.books.add(new Book(title, status, label, genre, rating, summary)); + bookList.books.add(new BookMain(title, status, label, genre, rating, summary)); } //@@author @@ -30,7 +30,7 @@ public static void addBookFromFile(BookList bookList, String inputArray) { */ public static void addBook(BookList bookList, String title) { try { - bookList.books.add(new Book(title)); + bookList.books.add(new BookMain(title)); Ui.addBookMessage(title); assert !bookList.books.isEmpty() : "Book list should not be empty after adding a book"; } catch (Exception e) { diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index 146b01f849..9453295456 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -1,10 +1,10 @@ package seedu.bookbuddy.parser; import exceptions.UnsupportedCommandException; -import seedu.bookbuddy.bookdetails.BookDisplay; +import seedu.bookbuddy.bookdetailsmodifier.BookDisplay; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.Ui; -import seedu.bookbuddy.bookdetails.BookRating; +import seedu.bookbuddy.bookdetailsmodifier.BookRating; import seedu.bookbuddy.parser.parsercommands.ParserAdd; import seedu.bookbuddy.parser.parsercommands.ParserDisplay; import seedu.bookbuddy.parser.parsercommands.ParserGenre; diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserDisplay.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserDisplay.java index 077633c9d3..595c0f453a 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserDisplay.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserDisplay.java @@ -1,7 +1,7 @@ package seedu.bookbuddy.parser.parsercommands; import seedu.bookbuddy.booklist.BookList; -import seedu.bookbuddy.bookdetails.BookDisplay; +import seedu.bookbuddy.bookdetailsmodifier.BookDisplay; import seedu.bookbuddy.parser.parservalidation.Exceptions; public class ParserDisplay { diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java index bc837645a9..8050b7894b 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java @@ -1,7 +1,7 @@ package seedu.bookbuddy.parser.parsercommands; import seedu.bookbuddy.booklist.BookList; -import seedu.bookbuddy.bookdetails.BookGenre; +import seedu.bookbuddy.bookdetailsmodifier.BookGenre; import seedu.bookbuddy.parser.parservalidation.Exceptions; import java.util.Scanner; diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java index 3e2383054d..9dee212030 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java @@ -1,7 +1,7 @@ package seedu.bookbuddy.parser.parsercommands; import seedu.bookbuddy.booklist.BookList; -import seedu.bookbuddy.bookdetails.BookLabel; +import seedu.bookbuddy.bookdetailsmodifier.BookLabel; import seedu.bookbuddy.parser.parservalidation.Exceptions; public class ParserLabel { diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserMark.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserMark.java index d1b6c00d03..4bf7d63618 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserMark.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserMark.java @@ -1,6 +1,6 @@ package seedu.bookbuddy.parser.parsercommands; -import seedu.bookbuddy.bookdetails.BookMark; +import seedu.bookbuddy.bookdetailsmodifier.BookMark; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.parser.parservalidation.Exceptions; diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserRating.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserRating.java index d9e49e750f..5e33e49977 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserRating.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserRating.java @@ -2,7 +2,7 @@ import exceptions.InvalidCommandArgumentException; import seedu.bookbuddy.booklist.BookList; -import seedu.bookbuddy.bookdetails.BookRating; +import seedu.bookbuddy.bookdetailsmodifier.BookRating; import seedu.bookbuddy.parser.parservalidation.Exceptions; public class ParserRating { diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java index 06ae3c31bc..d0bfe22cc4 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java @@ -1,7 +1,7 @@ package seedu.bookbuddy.parser.parsercommands; import seedu.bookbuddy.booklist.BookList; -import seedu.bookbuddy.bookdetails.BookSummary; +import seedu.bookbuddy.bookdetailsmodifier.BookSummary; import seedu.bookbuddy.parser.parservalidation.Exceptions; public class ParserSummary { diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserUnmark.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserUnmark.java index 8712d68d79..ef7c94b3bd 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserUnmark.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserUnmark.java @@ -1,6 +1,6 @@ package seedu.bookbuddy.parser.parsercommands; -import seedu.bookbuddy.bookdetails.BookMark; +import seedu.bookbuddy.bookdetailsmodifier.BookMark; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.parser.parservalidation.Exceptions; diff --git a/src/test/java/seedu/bookbuddy/BookDetailsTest.java b/src/test/java/seedu/bookbuddy/BookDetailsTest.java index f2ba1aa862..0002d8cbad 100644 --- a/src/test/java/seedu/bookbuddy/BookDetailsTest.java +++ b/src/test/java/seedu/bookbuddy/BookDetailsTest.java @@ -1,8 +1,10 @@ package seedu.bookbuddy; import org.junit.jupiter.api.Test; -import seedu.bookbuddy.bookdetails.BookGenre; -import seedu.bookbuddy.bookdetails.BookLabel; +import seedu.bookbuddy.book.Genre; +import seedu.bookbuddy.book.Label; +import seedu.bookbuddy.bookdetailsmodifier.BookGenre; +import seedu.bookbuddy.bookdetailsmodifier.BookLabel; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.booklist.BookListModifier; @@ -15,9 +17,9 @@ public void testSetBookLabelByIndex() { BookListModifier.addBook(books, "The Great Gatsby"); BookListModifier.addBook(books, "Geronimo Stilton"); BookLabel.setBookLabelByIndex(1, "Great Classic", books); - assertEquals("Great Classic" ,books.getBook(1).getLabel()); + assertEquals("Great Classic" , Label.getLabel(books.getBook(1))); BookLabel.setBookLabelByIndex(2, "Great Classic", books); - assertEquals("Great Classic" ,books.getBook(2).getLabel()); + assertEquals("Great Classic" , Label.getLabel(books.getBook(2))); } @Test @@ -26,8 +28,8 @@ public void testSetBookGenreByIndex() { BookListModifier.addBook(books, "The Great Gatsby"); BookListModifier.addBook(books, "Geronimo Stilton"); BookGenre.setBookGenreByIndex(1, "Classic", books); - assertEquals("Classic" ,books.getBook(1).getGenre()); + assertEquals("Classic" , Genre.getGenre(books.getBook(1))); BookGenre.setBookGenreByIndex(2, "Fantasy", books); - assertEquals("Fantasy" ,books.getBook(2).getGenre()); + assertEquals("Fantasy" , Genre.getGenre(books.getBook(2))); } } diff --git a/src/test/java/seedu/bookbuddy/BookListTest.java b/src/test/java/seedu/bookbuddy/BookListTest.java index 35d0dfaa73..7e63adfb3c 100644 --- a/src/test/java/seedu/bookbuddy/BookListTest.java +++ b/src/test/java/seedu/bookbuddy/BookListTest.java @@ -1,8 +1,8 @@ package seedu.bookbuddy; import org.junit.jupiter.api.Test; -import seedu.bookbuddy.bookdetails.BookDisplay; -import seedu.bookbuddy.bookdetails.BookMark; +import seedu.bookbuddy.bookdetailsmodifier.BookDisplay; +import seedu.bookbuddy.bookdetailsmodifier.BookMark; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.booklist.BookListModifier; diff --git a/src/test/java/seedu/bookbuddy/ParserMainTest.java b/src/test/java/seedu/bookbuddy/ParserMainTest.java index cf02e2aa81..45bd418c92 100644 --- a/src/test/java/seedu/bookbuddy/ParserMainTest.java +++ b/src/test/java/seedu/bookbuddy/ParserMainTest.java @@ -3,7 +3,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import seedu.bookbuddy.bookdetails.BookMark; +import seedu.bookbuddy.book.*; +import seedu.bookbuddy.bookdetailsmodifier.BookMark; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.booklist.BookListModifier; import seedu.bookbuddy.parser.ParserMain; @@ -42,7 +43,7 @@ void testParser() { assertEquals("[U] Gulliver's Travels", books.getBook(2).toString()); BookListModifier.deleteBook(books, 1); BookMark.markDoneByIndex(books, 1); - assertTrue(books.getBook(1).isRead); + assertTrue(Read.getRead(books.getBook(1))); assertEquals("[R] Gulliver's Travels", books.getBook(1).toString()); } @@ -51,7 +52,7 @@ void parseAddCommand() { BookList testBookList = new BookList(); ParserMain.parseCommand("add The Great Gatsby", testBookList); assertEquals(1, testBookList.getSize()); - assertEquals("The Great Gatsby", testBookList.getBook(1).getTitle()); + assertEquals("The Great Gatsby", Title.getTitle(testBookList.getBook(1))); } @Test @@ -71,7 +72,7 @@ void parseMarkCommand() { ParserMain.parseCommand("mark 1", books); System.out.println(books); BookMark.markDoneByIndex(books, 1); - assertTrue(books.getBook(1).isRead()); + assertTrue(Read.getRead(books.getBook(1))); } @Test @@ -80,7 +81,7 @@ void parseUnmarkCommand() { BookListModifier.addBook(books, "The Great Gatsby"); ParserMain.parseCommand("mark 1", books); ParserMain.parseCommand("unmark 1", books); - assertFalse(books.getBook(1).isRead()); + assertFalse(Read.getRead(books.getBook(1))); } @Test @@ -88,7 +89,7 @@ void parseLabelCommand() { BookList books = new BookList(); BookListModifier.addBook(books, "The Great Gatsby"); ParserMain.parseCommand("label 1 Great Book", books); - assertEquals("Great Book", books.getBook(1).getLabel()); + assertEquals("Great Book", Label.getLabel(books.getBook(1))); } @Test @@ -100,13 +101,13 @@ void parseGenreCommand() { InputStream savedStandardInputStream = System.in; System.setIn(new ByteArrayInputStream(simulatedUserInput.getBytes())); ParserMain.parseCommand("set-genre 1", books); // Changed to fit your updated command-handling logic - assertEquals("Classic", books.getBook(1).getGenre()); // Indexes are typically 0-based in lists + assertEquals("Classic", Genre.getGenre(books.getBook(1))); // Indexes are typically 0-based in lists BookListModifier.addBook(books, "Geronimo"); String nextSimulatedUserInput = "3\n"; System.setIn(new ByteArrayInputStream(nextSimulatedUserInput.getBytes())); ParserMain.parseCommand("set-genre 2", books); - assertEquals("Mystery", books.getBook(2).getGenre()); + assertEquals("Mystery", Genre.getGenre(books.getBook(2))); System.setIn(savedStandardInputStream); } From d19a804aee380c92d51450e585bbcdb23673ad26 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Tue, 2 Apr 2024 23:14:00 +0800 Subject: [PATCH 133/311] Checkstyle fixes --- src/main/java/seedu/bookbuddy/book/Genre.java | 3 +-- src/main/java/seedu/bookbuddy/book/Label.java | 3 +-- src/main/java/seedu/bookbuddy/book/Rating.java | 3 +-- src/main/java/seedu/bookbuddy/book/Read.java | 3 +-- src/main/java/seedu/bookbuddy/book/Summary.java | 3 +-- src/main/java/seedu/bookbuddy/book/Title.java | 3 +-- .../seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java | 8 +++++++- .../seedu/bookbuddy/bookdetailsmodifier/BookMark.java | 6 ++++-- src/main/java/seedu/bookbuddy/booklist/BookList.java | 9 +++++---- src/main/java/seedu/bookbuddy/parser/ParserMain.java | 4 +++- .../bookbuddy/parser/parsercommands/ParserGenre.java | 4 +++- src/test/java/seedu/bookbuddy/ParserMainTest.java | 5 ++++- 12 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/book/Genre.java b/src/main/java/seedu/bookbuddy/book/Genre.java index bcce932c0b..82b6e3425c 100644 --- a/src/main/java/seedu/bookbuddy/book/Genre.java +++ b/src/main/java/seedu/bookbuddy/book/Genre.java @@ -13,9 +13,8 @@ public static void setGenre(BookMain book, String genre) { /** * Returns the genre of the book. - * - * @return The genre of the book. * @param book + * @return The genre of the book. */ public static String getGenre(BookMain book) { return book.genre; diff --git a/src/main/java/seedu/bookbuddy/book/Label.java b/src/main/java/seedu/bookbuddy/book/Label.java index ad13b76f06..d81520fb9d 100644 --- a/src/main/java/seedu/bookbuddy/book/Label.java +++ b/src/main/java/seedu/bookbuddy/book/Label.java @@ -13,9 +13,8 @@ public static void setLabel(BookMain book, String label) { /** * Returns the label of the book. - * - * @return The label of the book. * @param book + * @return The label of the book. */ public static String getLabel(BookMain book) { return book.label; diff --git a/src/main/java/seedu/bookbuddy/book/Rating.java b/src/main/java/seedu/bookbuddy/book/Rating.java index a76ac59c7a..3936aa0a49 100644 --- a/src/main/java/seedu/bookbuddy/book/Rating.java +++ b/src/main/java/seedu/bookbuddy/book/Rating.java @@ -17,9 +17,8 @@ public static void setRating(BookMain book, int rating) { /** * Returns the rating of the book. - * - * @return The rating of the book. * @param book + * @return The rating of the book. */ public static int getRating(BookMain book) { return book.rating; diff --git a/src/main/java/seedu/bookbuddy/book/Read.java b/src/main/java/seedu/bookbuddy/book/Read.java index 37b6348c89..9fe261ad10 100644 --- a/src/main/java/seedu/bookbuddy/book/Read.java +++ b/src/main/java/seedu/bookbuddy/book/Read.java @@ -4,9 +4,8 @@ public class Read { //@@author lordgareth10 /** * Checks if the book is read. - * - * @return True if the book is read, false otherwise. * @param book + * @return True if the book is read, false otherwise. */ public static boolean getRead(BookMain book) { return book.isRead; diff --git a/src/main/java/seedu/bookbuddy/book/Summary.java b/src/main/java/seedu/bookbuddy/book/Summary.java index ee3df3c468..64a2ff9ac6 100644 --- a/src/main/java/seedu/bookbuddy/book/Summary.java +++ b/src/main/java/seedu/bookbuddy/book/Summary.java @@ -15,9 +15,8 @@ public static void setSummary(BookMain book, String summary) { /** * Returns the summary of the book. - * - * @return The summary of the book. * @param book + * @return The summary of the book. */ public static String getSummary(BookMain book) { return book.summary; diff --git a/src/main/java/seedu/bookbuddy/book/Title.java b/src/main/java/seedu/bookbuddy/book/Title.java index 0623a6f63b..ebe2543f84 100644 --- a/src/main/java/seedu/bookbuddy/book/Title.java +++ b/src/main/java/seedu/bookbuddy/book/Title.java @@ -3,9 +3,8 @@ public class Title { /** * Returns the title of the book. - * - * @return The title of the book. * @param book + * @return The title of the book. */ public static String getTitle(BookMain book) { return book.title; diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index 20f7fa8054..4be8c9a5dc 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -1,7 +1,13 @@ package seedu.bookbuddy.bookdetailsmodifier; -import seedu.bookbuddy.book.*; import seedu.bookbuddy.Ui; +import seedu.bookbuddy.book.BookMain; +import seedu.bookbuddy.book.Genre; +import seedu.bookbuddy.book.Label; +import seedu.bookbuddy.book.Rating; +import seedu.bookbuddy.book.Read; +import seedu.bookbuddy.book.Summary; +import seedu.bookbuddy.book.Title; import seedu.bookbuddy.booklist.BookList; import java.util.ArrayList; diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java index 148865258e..a743c5cf18 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java @@ -20,7 +20,8 @@ public class BookMark { * @param bookList * @param index The index of the book to mark as read. */ - public static void markDoneByIndex(BookList bookList, int index) throws IndexOutOfBoundsException, BookReadAlreadyException { + public static void markDoneByIndex(BookList bookList, int index) throws IndexOutOfBoundsException, + BookReadAlreadyException { try { assert index > 0 && index <= bookList.getBooks().size() : "Index out of valid range"; if (Read.getRead(bookList.getBooks().get(index - 1))) { @@ -46,7 +47,8 @@ public static void markDoneByIndex(BookList bookList, int index) throws IndexOut * @param bookList * @param index The index of the book to mark as unread. */ - public static void markUndoneByIndex(BookList bookList, int index) throws IndexOutOfBoundsException, BookReadAlreadyException{ + public static void markUndoneByIndex(BookList bookList, int index) throws IndexOutOfBoundsException, + BookReadAlreadyException{ try { assert index > 0 && index <= bookList.getBooks().size() : "Index out of valid range"; if (!Read.getRead(bookList.getBooks().get(index - 1))) { diff --git a/src/main/java/seedu/bookbuddy/booklist/BookList.java b/src/main/java/seedu/bookbuddy/booklist/BookList.java index e92c514435..abab1dde15 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookList.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookList.java @@ -12,19 +12,20 @@ * and marking book as read or unread. */ public class BookList { + protected ArrayList books; + public BookList() { + this.books = new ArrayList(); // Use ArrayList instead of array + } protected static List availableGenres = new ArrayList<>(Arrays.asList("Fiction", "Non-Fiction", "Mystery", "Science Fiction", "Fantasy")); public static List getAvailableGenres() { return availableGenres; } - protected ArrayList books; /** * Constructs a new BookList instance with an empty list. */ - public BookList() { - this.books = new ArrayList(); // Use ArrayList instead of array - } + // Public getter method for the books field public List getBooks() { return this.books; diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index 9453295456..eb9b12bc13 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -68,7 +68,9 @@ public static void parseCommand(String input, BookList books) { break; case CommandList.GENRE_COMMAND: // Exit the command if user types 'exit' - if (ParserGenre.executeParseSetGenre(books, inputArray)) return; + if (ParserGenre.executeParseSetGenre(books, inputArray)) { + return; + } break; case CommandList.RATING_COMMAND: ParserRating.executeParseSetRating(books, inputArray); diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java index 8050b7894b..a6c8aacf07 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java @@ -23,7 +23,9 @@ static boolean parseSetGenre(BookList books, String[] inputArray) { String selectedGenre = null; selectedGenre = invalidInputLooper(selectedGenre, scanner); - if (selectedGenre == null) return true; + if (selectedGenre == null) { + return true; + } BookGenre.setBookGenreByIndex(index, selectedGenre, books); System.out.println("Genre set to " + selectedGenre + " for book at index " + index); diff --git a/src/test/java/seedu/bookbuddy/ParserMainTest.java b/src/test/java/seedu/bookbuddy/ParserMainTest.java index 45bd418c92..2c1d09bac1 100644 --- a/src/test/java/seedu/bookbuddy/ParserMainTest.java +++ b/src/test/java/seedu/bookbuddy/ParserMainTest.java @@ -3,7 +3,10 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import seedu.bookbuddy.book.*; +import seedu.bookbuddy.book.Genre; +import seedu.bookbuddy.book.Label; +import seedu.bookbuddy.book.Read; +import seedu.bookbuddy.book.Title; import seedu.bookbuddy.bookdetailsmodifier.BookMark; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.booklist.BookListModifier; From 7b3b2cd010d251ea20a8ed13a8dc28cc000b97ca Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Tue, 2 Apr 2024 23:16:22 +0800 Subject: [PATCH 134/311] Checkstyle fixes --- src/main/java/seedu/bookbuddy/booklist/BookList.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/booklist/BookList.java b/src/main/java/seedu/bookbuddy/booklist/BookList.java index abab1dde15..d5a76c9a70 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookList.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookList.java @@ -12,15 +12,15 @@ * and marking book as read or unread. */ public class BookList { + public static List getAvailableGenres() { + return availableGenres; + } protected ArrayList books; public BookList() { this.books = new ArrayList(); // Use ArrayList instead of array } protected static List availableGenres = new ArrayList<>(Arrays.asList("Fiction", "Non-Fiction", "Mystery", "Science Fiction", "Fantasy")); - public static List getAvailableGenres() { - return availableGenres; - } /** * Constructs a new BookList instance with an empty list. From 5a648d29fabe810cb009340424b059de026ba30d Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Tue, 2 Apr 2024 23:21:23 +0800 Subject: [PATCH 135/311] Checkstyle fixes --- src/main/java/seedu/bookbuddy/booklist/BookList.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/booklist/BookList.java b/src/main/java/seedu/bookbuddy/booklist/BookList.java index d5a76c9a70..a5eeb6129f 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookList.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookList.java @@ -15,12 +15,14 @@ public class BookList { public static List getAvailableGenres() { return availableGenres; } + protected static List availableGenres = new ArrayList<>(Arrays.asList("Fiction", "Non-Fiction", + "Mystery", "Science Fiction", "Fantasy")); protected ArrayList books; public BookList() { this.books = new ArrayList(); // Use ArrayList instead of array } - protected static List availableGenres = new ArrayList<>(Arrays.asList("Fiction", "Non-Fiction", - "Mystery", "Science Fiction", "Fantasy")); + + /** * Constructs a new BookList instance with an empty list. From f0b226b6a2e792d665af3a6525ebeef647db9658 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Tue, 2 Apr 2024 23:27:09 +0800 Subject: [PATCH 136/311] Checkstyle fixes --- src/main/java/seedu/bookbuddy/booklist/BookList.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/booklist/BookList.java b/src/main/java/seedu/bookbuddy/booklist/BookList.java index a5eeb6129f..6cbf56fc0d 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookList.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookList.java @@ -12,17 +12,15 @@ * and marking book as read or unread. */ public class BookList { - public static List getAvailableGenres() { - return availableGenres; - } protected static List availableGenres = new ArrayList<>(Arrays.asList("Fiction", "Non-Fiction", "Mystery", "Science Fiction", "Fantasy")); protected ArrayList books; public BookList() { this.books = new ArrayList(); // Use ArrayList instead of array } - - + public static List getAvailableGenres() { + return availableGenres; + } /** * Constructs a new BookList instance with an empty list. From 0fd092ed3f539e1a4b5a01612d6859ebfc0745a0 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Wed, 3 Apr 2024 01:57:21 +0800 Subject: [PATCH 137/311] Update User Guide --- docs/UserGuide.md | 98 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 84 insertions(+), 14 deletions(-) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index aa53bece3d..0c932352d9 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,13 +1,31 @@ # BookBuddy User Guide ## Overview - BookBuddy is an application that helps users track and manage the list of books that they are reading. It is optimised for users that are familiar with the CLI so that the tracking and management objectives can be achieved more efficiently. -## Getting Started +## Table of Contents +* [Getting Started](#getting-started) +* [Features](#features) + * [help](#viewing-all-commands-help) + * [add](#adding-a-book-add) + * [remove](#removing-a-book-remove) + * [list](#viewing-all-books-list) + * [mark](#marking-a-book-as-read-mark) + * [unmark](#marking-a-book-as-unread-unmark) + * [set-genre](#setting-the-genre-of-a-book-set-genre) + * [label](#labelling-a-book-label) + * [give-summary](#adding-a-book-summary-give-summary) + * [rate](#rating-a-book-rate) + * [list-rated](#sorting-books-by-rating-list-rated) + * [display](#displaying-the-details-of-a-book-display) + * [find](#finding-a-book-find) + * [bye](#exiting-the-program-bye) +* [FAQ](#faq) +* [Command Summary](#command-summary) +## Getting Started 1. Ensure that you have Java 11 or above installed. 2. Download the latest JAR file of BookBuddy [here](https://github.com/AY2324S2-CS2113-F15-4/tp/releases). 3. Move the JAR file into an empty folder. @@ -17,8 +35,8 @@ the tracking and management objectives can be achieved more efficiently. ## Features -### Adding a book: `help` -View all the commands available in BookBuddy and instructions on their formatting. +### Viewing all commands: `help` +View all the commands available in BookBuddy and instructions on their usage. Format: `help` @@ -87,12 +105,26 @@ unmark 1 Sets the genre of a specific book to the provided input. -Format: `set-genre [BOOK_INDEX] [BOOK_GENRE]` +Format: `set-genre [BOOK_INDEX]` followed by `[NUMBER]` then `[CUSTOM_GENRE]` if 6 is entered +in the previous step -Example of usage: +Example of usage with expected output: ``` -set-genre 1 fiction +set-genre 1 +Available genres: +1. Fiction +2. Non-Fiction +3. Mystery +4. Science Fiction +5. Fantasy +6. Add a new genre +Enter the number for the desired genre, or add a new one: +6 +Enter the new genre: +satire +okii categorised [animal farm] as [satire] +remember to read it soon.... ``` ### Labelling a book: `label` @@ -118,6 +150,28 @@ Example of usage: give-summary 1 A book about a young boy who is invited to study at Hogwarts. ``` +### Rating a book: `rate` +Assigns a rating to a specific book, from a scale of 1-5. + +Format: `rate [BOOK_INDEX] [BOOK_RATING]` + +Example of usage: + +``` +rate 1 3 +``` + +### Sorting books by rating: `list-rated` +Prints a list of books and their ratings in descending order. + +Format: `list-rated` + +Example of usage: + +``` +list-rated +``` + ### Displaying the details of a book: `display` Gives more detailed information about a specific book like its genre, label and summary. @@ -130,7 +184,7 @@ display 1 ``` ### Finding a book: `find` -Returns all books in the book list that contains the keyword. +Returns all books in the book list that contain the keyword in their title. Format: `find [KEYWORD]` @@ -142,7 +196,7 @@ find harry ### Exiting the program: `bye` -Exits the application. +Exits the application and saves all tasks in a file. Format: `bye` @@ -154,12 +208,28 @@ bye ## FAQ -**Q**: How do I transfer my data to another computer? +**Q**: How do I load and save data from a previous session? -**A**: {your answer here} +**A**: All data entered is automatically saved by the program and does not require any +commands from the user. Upon running the file for the first time, the `books.txt` file +will be created in the `data` folder. This folder will be in the same folder as the JAR file. -## Command Summary +**Users MUST exit the program with the `bye` command for the data in the session +to be saved.** -{Give a 'cheat sheet' of commands here} +## Command Summary -* Add todo `todo n/TODO_NAME d/DEADLINE` +* View commands: `help` +* Add book: `add [BOOK_TITLE]` +* Remove book: `remove [BOOK_INDEX]` +* View all books: `list` +* Mark book as read: `mark [BOOK_INDEX]` +* Mark book as unread: `unmark [BOOK_INDEX]` +* Set genre: `set-genre [BOOK_INDEX]`, `[NUMBER]` and `[CUSTOM_GENRE]` if necessary +* Label book: `label [BOOK_INDEX] [LABEL]` +* Add summary: `give-summary [BOOK_INDEX] [BOOK_SUMMARY]` +* Rate a book: `rate [BOOK_INDEX] [BOOK_RATING]` +* Sort books by rating: `list-rated` +* Display details: `display [BOOK_INDEX]` +* Find books: `find [KEYWORD]` +* Exit program: `bye` From 23fdc1877a66605785173f7264b92a8add0b3b66 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Wed, 3 Apr 2024 01:57:35 +0800 Subject: [PATCH 138/311] Update help message in UI --- src/main/java/seedu/bookbuddy/Ui.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 21338f48f9..0e784406f1 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -56,11 +56,18 @@ public static void removeBookMessage(int index, BookList books) { } public static void helpMessage() { System.out.println("Here's a list of commands to get you started!!"); - System.out.println("add (Bookname) -> to add new books to the list"); + System.out.println("add [BOOK_TITLE] -> to add new books to the list"); + System.out.println("remove [BOOK_INDEX] -> to remove a book from the list"); System.out.println("list -> to show whole list of added books"); - System.out.println("remove (index) -> to remove the book from the corresponding index"); - System.out.println("mark (index) -> to mark book as read [R]"); - System.out.println("unmark (index) -> to unmark book as unread [U]"); + System.out.println("mark [BOOK_INDEX] -> to mark book as read [R]"); + System.out.println("unmark [BOOK_INDEX] -> to mark book as unread [U]"); + System.out.println("set-genre [BOOK_INDEX] -> to set a genre for a book"); + System.out.println("label [BOOK_INDEX] [LABEL] -> to set a label for a book"); + System.out.println("give-summary [BOOK_INDEX] [BOOK_SUMMARY] -> to give a book a summary"); + System.out.println("rate [BOOK_INDEX] [BOOK_RATING] -> to rate a book from 1-5"); + System.out.println("list-rated -> to sort books by rating in descending order"); + System.out.println("display [BOOK_INDEX] -> to view more details about a book"); + System.out.println("find [KEYWORD] -> to find books with keyword in their title"); System.out.println("bye -> to exit BookBuddy software"); } From 4d0d418064e2806e638a6648979ff60f99ff9ed7 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Wed, 3 Apr 2024 11:55:03 +0800 Subject: [PATCH 139/311] basic find-genre function implemented --- BookBuddy.log.1 | 8 ++++++++ BookBuddy.log.1.lck | 0 src/main/java/seedu/bookbuddy/Ui.java | 11 ++++++++++- .../bookdetailsmodifier/BookDisplay.java | 15 ++++++++++++++- .../java/seedu/bookbuddy/parser/ParserMain.java | 7 +++++-- .../parser/parservalidation/CommandList.java | 3 ++- 6 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 BookBuddy.log.1 create mode 100644 BookBuddy.log.1.lck diff --git a/BookBuddy.log.1 b/BookBuddy.log.1 new file mode 100644 index 0000000000..e01385e600 --- /dev/null +++ b/BookBuddy.log.1 @@ -0,0 +1,8 @@ +Apr 01, 2024 6:16:13 PM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Apr 01, 2024 6:16:13 PM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. +Apr 01, 2024 6:20:29 PM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Apr 01, 2024 6:20:29 PM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. diff --git a/BookBuddy.log.1.lck b/BookBuddy.log.1.lck new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 0e784406f1..46b9c511c4 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -67,7 +67,7 @@ public static void helpMessage() { System.out.println("rate [BOOK_INDEX] [BOOK_RATING] -> to rate a book from 1-5"); System.out.println("list-rated -> to sort books by rating in descending order"); System.out.println("display [BOOK_INDEX] -> to view more details about a book"); - System.out.println("find [KEYWORD] -> to find books with keyword in their title"); + System.out.println("find-title [KEYWORD] -> to find books with keyword in their title"); System.out.println("bye -> to exit BookBuddy software"); } @@ -80,4 +80,13 @@ public static void printNoBookFound(){ System.out.println("no such books added..."); } + public static void printGenresFound(ArrayList bookGenres){ + for (int i = 0; i < bookGenres.size(); i++) { + System.out.println(i + 1 + ". " + bookGenres.get(i)); + } + } + public static void printNoGenresFound(){ + System.out.println("no such books added..."); + + } } diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index 4be8c9a5dc..e54f93a146 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -54,7 +54,7 @@ public static void printAllBooks(BookList bookList) { } //@@author liuzehui03 - public static void findBook(BookList bookList, String title) { + public static void findBookTitle(BookList bookList, String title) { ArrayList bookTitles = new ArrayList<>(); for (BookMain book : bookList.getBooks()) { if (Title.getTitle(book).contains(title)) { @@ -67,4 +67,17 @@ public static void findBook(BookList bookList, String title) { Ui.printBookFound(bookTitles); } } + public static void findBookGenre(BookList bookList, String genre) { + ArrayList bookGenres = new ArrayList<>(); + for (BookMain book : bookList.getBooks()) { + if (Genre.getGenre(book).contains(genre)) { + bookGenres.add(book); + } + } + if (bookGenres.isEmpty()){ + Ui.printNoGenresFound(); + } else { + Ui.printGenresFound(bookGenres); + } + } } diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index eb9b12bc13..c6d752c17c 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -57,8 +57,11 @@ public static void parseCommand(String input, BookList books) { case CommandList.HELP_COMMAND: Ui.helpMessage(); break; - case CommandList.FIND_COMMAND: - BookDisplay.findBook(books, inputArray[1]); + case CommandList.FIND_TITLE_COMMAND: + BookDisplay.findBookTitle(books, inputArray[1]); + break; + case CommandList.FIND_GENRE_COMMAND: + BookDisplay.findBookGenre(books, inputArray[1]); break; case CommandList.LABEL_COMMAND: ParserLabel.executeParseSetLabel(books, inputArray); diff --git a/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java b/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java index d555b91649..146ad4e5a5 100644 --- a/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java +++ b/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java @@ -7,7 +7,8 @@ public class CommandList { public static final String MARK_COMMAND = "mark"; public static final String UNMARK_COMMAND = "unmark"; public static final String HELP_COMMAND = "help"; - public static final String FIND_COMMAND = "find"; + public static final String FIND_TITLE_COMMAND = "find-title"; + public static final String FIND_GENRE_COMMAND = "find-genre"; public static final String LABEL_COMMAND = "label"; public static final String GENRE_COMMAND = "set-genre"; public static final String SUMMARY_COMMAND = "give-summary"; From 6da134f1f3d3e3ed135779c4255bfc3db0c1a2b3 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Wed, 3 Apr 2024 12:52:51 +0800 Subject: [PATCH 140/311] added ParserFind for all find functions --- BookBuddy.log.1 | 4 +++ BookBuddy.log.1.lck | 0 .../seedu/bookbuddy/parser/ParserMain.java | 16 +++------- .../parser/parsercommands/ParserFind.java | 31 +++++++++++++++++++ .../parser/parsercommands/ParserGenre.java | 4 +-- 5 files changed, 42 insertions(+), 13 deletions(-) delete mode 100644 BookBuddy.log.1.lck create mode 100644 src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java diff --git a/BookBuddy.log.1 b/BookBuddy.log.1 index e01385e600..e523ee283a 100644 --- a/BookBuddy.log.1 +++ b/BookBuddy.log.1 @@ -6,3 +6,7 @@ Apr 01, 2024 6:20:29 PM seedu.bookbuddy.BookBuddy main INFO: BookBuddy application started. Apr 01, 2024 6:20:29 PM seedu.bookbuddy.BookBuddy getUserInput INFO: Starting to get user input. +Apr 03, 2024 12:51:38 PM seedu.bookbuddy.BookBuddy main +INFO: BookBuddy application started. +Apr 03, 2024 12:51:38 PM seedu.bookbuddy.BookBuddy getUserInput +INFO: Starting to get user input. diff --git a/BookBuddy.log.1.lck b/BookBuddy.log.1.lck deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index c6d752c17c..f5530bf564 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -5,15 +5,7 @@ import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.Ui; import seedu.bookbuddy.bookdetailsmodifier.BookRating; -import seedu.bookbuddy.parser.parsercommands.ParserAdd; -import seedu.bookbuddy.parser.parsercommands.ParserDisplay; -import seedu.bookbuddy.parser.parsercommands.ParserGenre; -import seedu.bookbuddy.parser.parsercommands.ParserLabel; -import seedu.bookbuddy.parser.parsercommands.ParserMark; -import seedu.bookbuddy.parser.parsercommands.ParserRating; -import seedu.bookbuddy.parser.parsercommands.ParserRemove; -import seedu.bookbuddy.parser.parsercommands.ParserSummary; -import seedu.bookbuddy.parser.parsercommands.ParserUnmark; +import seedu.bookbuddy.parser.parsercommands.*; import seedu.bookbuddy.parser.parservalidation.CommandList; import seedu.bookbuddy.parser.parservalidation.Exceptions; @@ -58,10 +50,12 @@ public static void parseCommand(String input, BookList books) { Ui.helpMessage(); break; case CommandList.FIND_TITLE_COMMAND: - BookDisplay.findBookTitle(books, inputArray[1]); + ParserFind.parseTitle(books, inputArray[1]); + //BookDisplay.findBookTitle(books, inputArray[1]); break; case CommandList.FIND_GENRE_COMMAND: - BookDisplay.findBookGenre(books, inputArray[1]); + ParserFind.parseFindGenre(books); + //BookDisplay.findBookGenre(books, inputArray[1]); break; case CommandList.LABEL_COMMAND: ParserLabel.executeParseSetLabel(books, inputArray); diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java new file mode 100644 index 0000000000..59bdadd45c --- /dev/null +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java @@ -0,0 +1,31 @@ +package seedu.bookbuddy.parser.parsercommands; + +import seedu.bookbuddy.bookdetailsmodifier.BookDisplay; +import seedu.bookbuddy.booklist.BookList; + +import java.util.Scanner; + +import static seedu.bookbuddy.parser.parsercommands.ParserGenre.genreSelectionPrinter; +import static seedu.bookbuddy.parser.parsercommands.ParserGenre.invalidInputLooper; + +public class ParserFind { + public static void parseTitle(BookList books, String inputArray) { + BookDisplay.findBookTitle(books, inputArray); + } + public static void parseFindGenre(BookList books) { + //BookDisplay.findBookGenre(books, inputArray); + System.out.println("Available genres:"); + for (int i = 0; i < BookList.getAvailableGenres().size(); i++) { + System.out.println((i + 1) + ". " + BookList.getAvailableGenres().get(i)); + } + System.out.println("Enter the number for the desired genre:"); + Scanner scanner = new Scanner(System.in); + String genre = String.valueOf(scanner); + String selectedGenre = null; + selectedGenre = invalidInputLooper(selectedGenre, scanner); + if (selectedGenre == null) { + return; + } + BookDisplay.findBookGenre(books, selectedGenre); + } +} diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java index a6c8aacf07..8ee089563f 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java @@ -32,7 +32,7 @@ static boolean parseSetGenre(BookList books, String[] inputArray) { return false; } - private static void genreSelectionPrinter() { + static void genreSelectionPrinter() { System.out.println("Available genres:"); for (int i = 0; i < BookList.getAvailableGenres().size(); i++) { System.out.println((i + 1) + ". " + BookList.getAvailableGenres().get(i)); @@ -40,7 +40,7 @@ private static void genreSelectionPrinter() { System.out.println((BookList.getAvailableGenres().size() + 1) + ". Add a new genre"); } - private static String invalidInputLooper(String selectedGenre, Scanner scanner) { + static String invalidInputLooper(String selectedGenre, Scanner scanner) { while (selectedGenre == null) { while (!scanner.hasNextInt()) { // Ensure the next input is an integer String newInput = scanner.nextLine(); From cf246e3f8e690e77622b02cd34de05a4a1316b65 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Wed, 3 Apr 2024 13:01:58 +0800 Subject: [PATCH 141/311] resolve check style error --- BookBuddy.log.1 | 8 ++++++++ src/main/java/seedu/bookbuddy/parser/ParserMain.java | 11 ++++++++++- .../bookbuddy/parser/parsercommands/ParserFind.java | 1 - 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/BookBuddy.log.1 b/BookBuddy.log.1 index e523ee283a..83c783b24f 100644 --- a/BookBuddy.log.1 +++ b/BookBuddy.log.1 @@ -10,3 +10,11 @@ Apr 03, 2024 12:51:38 PM seedu.bookbuddy.BookBuddy main INFO: BookBuddy application started. Apr 03, 2024 12:51:38 PM seedu.bookbuddy.BookBuddy getUserInput INFO: Starting to get user input. +Apr 03, 2024 12:57:30 PM seedu.bookbuddy.parser.parservalidation.Exceptions validateCommandArguments +WARNING: The add Command requires a book title +Apr 03, 2024 12:57:30 PM seedu.bookbuddy.parser.parservalidation.Exceptions handleException +WARNING: Invalid command argument: The add Command requires a book title +Apr 03, 2024 12:57:30 PM seedu.bookbuddy.parser.ParserMain parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Apr 03, 2024 12:57:30 PM seedu.bookbuddy.parser.parservalidation.Exceptions handleException +WARNING: Command is invalid: Sorry but that is not a valid command. Please try again or type: help diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index f5530bf564..e8cfee6109 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -5,7 +5,16 @@ import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.Ui; import seedu.bookbuddy.bookdetailsmodifier.BookRating; -import seedu.bookbuddy.parser.parsercommands.*; +import seedu.bookbuddy.parser.parsercommands.ParserFind; +import seedu.bookbuddy.parser.parsercommands.ParserAdd; +import seedu.bookbuddy.parser.parsercommands.ParserRemove; +import seedu.bookbuddy.parser.parsercommands.ParserMark; +import seedu.bookbuddy.parser.parsercommands.ParserGenre; +import seedu.bookbuddy.parser.parsercommands.ParserDisplay; +import seedu.bookbuddy.parser.parsercommands.ParserRating; +import seedu.bookbuddy.parser.parsercommands.ParserUnmark; +import seedu.bookbuddy.parser.parsercommands.ParserLabel; +import seedu.bookbuddy.parser.parsercommands.ParserSummary; import seedu.bookbuddy.parser.parservalidation.CommandList; import seedu.bookbuddy.parser.parservalidation.Exceptions; diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java index 59bdadd45c..bb11aa40e3 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java @@ -5,7 +5,6 @@ import java.util.Scanner; -import static seedu.bookbuddy.parser.parsercommands.ParserGenre.genreSelectionPrinter; import static seedu.bookbuddy.parser.parsercommands.ParserGenre.invalidInputLooper; public class ParserFind { From 7d6314059ed09034195e325e669c378d811db91c Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 3 Apr 2024 13:18:42 +0800 Subject: [PATCH 142/311] Authorship edits --- src/main/java/seedu/bookbuddy/book/BookMain.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/seedu/bookbuddy/book/BookMain.java b/src/main/java/seedu/bookbuddy/book/BookMain.java index 8243b51e7a..73f0bd2d81 100644 --- a/src/main/java/seedu/bookbuddy/book/BookMain.java +++ b/src/main/java/seedu/bookbuddy/book/BookMain.java @@ -33,6 +33,7 @@ public BookMain(String title, int status, String label, String genre, int rating this.summary = (Objects.equals(summary, "*")) ? "" : summary; } + //@@author joshuahoky @Override public String toString() { String statusMark = Read.getRead(this) ? "R" : "U"; // Mark with 'R' if read and 'U' if unread From 79db3785655c033ea7f867809176b01ae2d2046f Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Wed, 3 Apr 2024 17:57:24 +0800 Subject: [PATCH 143/311] Fix spacing issues --- src/main/java/seedu/bookbuddy/BookBuddy.java | 1 - src/main/java/seedu/bookbuddy/Ui.java | 12 +++++++++++- src/main/java/seedu/bookbuddy/parser/ParserMain.java | 1 + .../bookbuddy/parser/parsercommands/ParserFind.java | 1 + src/test/java/seedu/bookbuddy/BookBuddyTest.java | 3 --- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index 62d3c10bec..c7a7351115 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -46,7 +46,6 @@ public static void main(String[] args) throws IOException { LOGGER.log(Level.INFO, "BookBuddy application is shutting down."); } - public static void getUserInput(BookList books) throws IOException { Scanner input = new Scanner(System.in); FileStorage filestorage = new FileStorage(books); diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 46b9c511c4..ab875dcfaf 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -21,12 +21,14 @@ public static void printWelcome() { System.out.println("How can I help you today?"); printShortLine(); } + public static void printLine() { System.out.println("___________________________________"); } public static void printShortLine() { System.out.println("_____________"); } + public static void printExitMessage() { System.out.println("Thank you for using BookBuddy! Hope to see you again keke :)"); } @@ -35,25 +37,31 @@ public static void addBookMessage(String title) { System.out.println("okii added [" + title + "] to the list."); System.out.println("remember to read it soon...."); } + public static void labelBookMessage(String title, String label) { System.out.println("okii labeled [" + title + "] as [" + label + "]"); System.out.println("remember to read it soon...."); } + public static void summaryBookMessage(String title, String summary) { System.out.println("okii you have written: [" + summary + "] for the book: [" + title + "]"); System.out.println("remember to read it soon...."); } + public static void setGenreBookMessage(String title, String genre) { System.out.println("okii categorised [" + title + "] as [" + genre + "]"); System.out.println("remember to read it soon...."); } + public static void setRatingBookMessage(String title, int rating) { System.out.println("okii set rating for [" + title + "] as [" + rating +"]"); System.out.println("remember to read it soon...."); } + public static void removeBookMessage(int index, BookList books) { System.out.println("alright.. i've removed " + Title.getTitle(books.getBook(index)) + " from the list."); } + public static void helpMessage() { System.out.println("Here's a list of commands to get you started!!"); System.out.println("add [BOOK_TITLE] -> to add new books to the list"); @@ -76,17 +84,19 @@ public static void printBookFound(ArrayList bookTitles){ System.out.println(i + 1 + ". " + bookTitles.get(i)); } } + public static void printNoBookFound(){ System.out.println("no such books added..."); } + public static void printGenresFound(ArrayList bookGenres){ for (int i = 0; i < bookGenres.size(); i++) { System.out.println(i + 1 + ". " + bookGenres.get(i)); } } + public static void printNoGenresFound(){ System.out.println("no such books added..."); - } } diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index e8cfee6109..a46f6269fb 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -29,6 +29,7 @@ public class ParserMain { /** * Scans the user input for valid commands and handles them accordingly. + * * @param input input from the user * @param books ArrayList of books */ diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java index bb11aa40e3..009c28b1ab 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java @@ -11,6 +11,7 @@ public class ParserFind { public static void parseTitle(BookList books, String inputArray) { BookDisplay.findBookTitle(books, inputArray); } + public static void parseFindGenre(BookList books) { //BookDisplay.findBookGenre(books, inputArray); System.out.println("Available genres:"); diff --git a/src/test/java/seedu/bookbuddy/BookBuddyTest.java b/src/test/java/seedu/bookbuddy/BookBuddyTest.java index c6983e19d7..013ddf7582 100644 --- a/src/test/java/seedu/bookbuddy/BookBuddyTest.java +++ b/src/test/java/seedu/bookbuddy/BookBuddyTest.java @@ -45,9 +45,6 @@ public void testPrintWelcomeMessage() { assertEquals(normalizedExpectedOutput, normalizedActualOutput); } - - - @Test public void testPrintExitMessage() { Ui.printExitMessage(); From cd37dad9ea776af11ca54fe9a87a70dc0c2ebc38 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Wed, 3 Apr 2024 17:58:12 +0800 Subject: [PATCH 144/311] Edit help message --- src/main/java/seedu/bookbuddy/Ui.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index ab875dcfaf..18508fe85c 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -76,6 +76,7 @@ public static void helpMessage() { System.out.println("list-rated -> to sort books by rating in descending order"); System.out.println("display [BOOK_INDEX] -> to view more details about a book"); System.out.println("find-title [KEYWORD] -> to find books with keyword in their title"); + System.out.println("find-genre -> to see all books with the selected genre"); System.out.println("bye -> to exit BookBuddy software"); } From 2cec9dce202fdc3a2f4154c8b6aa7f06b439fe5e Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Wed, 3 Apr 2024 18:03:18 +0800 Subject: [PATCH 145/311] Fix spacing issues --- .../java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java | 3 +++ .../java/seedu/bookbuddy/bookdetailsmodifier/BookGenre.java | 1 + .../java/seedu/bookbuddy/bookdetailsmodifier/BookLabel.java | 1 + .../java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java | 3 ++- .../java/seedu/bookbuddy/bookdetailsmodifier/BookRating.java | 1 + .../java/seedu/bookbuddy/bookdetailsmodifier/BookSummary.java | 1 + src/main/java/seedu/bookbuddy/booklist/BookList.java | 2 -- 7 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index e54f93a146..569856c03e 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -13,6 +13,7 @@ import java.util.ArrayList; public class BookDisplay { + //@@author joshuahoky /** * Prints the details of the book at the specified index. @@ -36,6 +37,7 @@ public static void displayDetails(int index, BookList books) throws IndexOutOfBo /** * Prints all books currently in the list. + * * @param bookList */ public static void printAllBooks(BookList bookList) { @@ -67,6 +69,7 @@ public static void findBookTitle(BookList bookList, String title) { Ui.printBookFound(bookTitles); } } + public static void findBookGenre(BookList bookList, String genre) { ArrayList bookGenres = new ArrayList<>(); for (BookMain book : bookList.getBooks()) { diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookGenre.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookGenre.java index 6a2ddd002f..9ba5f487f3 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookGenre.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookGenre.java @@ -6,6 +6,7 @@ import seedu.bookbuddy.Ui; public class BookGenre { + /** * Sets the genre of the book at the specified index. * diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookLabel.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookLabel.java index c0217cba36..3a2b57e342 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookLabel.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookLabel.java @@ -6,6 +6,7 @@ import seedu.bookbuddy.Ui; public class BookLabel { + /** * Sets the label of the book at the specified index. * diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java index a743c5cf18..7d5bd25a52 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java @@ -69,6 +69,7 @@ public static void markUndoneByIndex(BookList bookList, int index) throws IndexO //@@author lordgareth10 /** * Marks the book as read. + * * @param book */ public static void markBookAsRead(BookMain book) { @@ -78,11 +79,11 @@ public static void markBookAsRead(BookMain book) { /** * Marks the book as unread. + * * @param book */ public static void markBookAsUnread(BookMain book) { Read.setRead(book, false); System.out.println("Successfully marked " + Title.getTitle(book) + " as unread."); } - //@author } diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookRating.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookRating.java index 3f6cb798a4..ece1e31cd9 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookRating.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookRating.java @@ -11,6 +11,7 @@ import java.util.stream.Collectors; public class BookRating { + /** * Prints all books sorted by rating in descending order. */ diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookSummary.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookSummary.java index ed140245ec..6788b2cdba 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookSummary.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookSummary.java @@ -6,6 +6,7 @@ import seedu.bookbuddy.Ui; public class BookSummary { + //@@author lordgareth10 /** * Sets the summary of the book at the specified index. diff --git a/src/main/java/seedu/bookbuddy/booklist/BookList.java b/src/main/java/seedu/bookbuddy/booklist/BookList.java index 6cbf56fc0d..62844aa9f5 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookList.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookList.java @@ -25,7 +25,6 @@ public static List getAvailableGenres() { /** * Constructs a new BookList instance with an empty list. */ - // Public getter method for the books field public List getBooks() { return this.books; @@ -54,5 +53,4 @@ public BookMain getBook(int index) throws BookNotFoundException{ assert books.get(index - 1) instanceof BookMain : "Object at index should be an instance of Book"; return books.get(index - 1); } - } From 7adfcc24d39e14975c664ad662cdea6a1db5be4a Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Wed, 3 Apr 2024 18:19:34 +0800 Subject: [PATCH 146/311] Remove redundant code --- src/main/java/seedu/bookbuddy/book/Genre.java | 1 + .../java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/book/Genre.java b/src/main/java/seedu/bookbuddy/book/Genre.java index 82b6e3425c..7f39b47c23 100644 --- a/src/main/java/seedu/bookbuddy/book/Genre.java +++ b/src/main/java/seedu/bookbuddy/book/Genre.java @@ -13,6 +13,7 @@ public static void setGenre(BookMain book, String genre) { /** * Returns the genre of the book. + * * @param book * @return The genre of the book. */ diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java index 8ee089563f..3b526417aa 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java @@ -28,7 +28,6 @@ static boolean parseSetGenre(BookList books, String[] inputArray) { } BookGenre.setBookGenreByIndex(index, selectedGenre, books); - System.out.println("Genre set to " + selectedGenre + " for book at index " + index); return false; } From 8882774ebcb2269917a0b40feec52235bd909281 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Wed, 3 Apr 2024 18:25:33 +0800 Subject: [PATCH 147/311] Update User Guide --- docs/UserGuide.md | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 0c932352d9..e3ee5bc564 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -20,7 +20,8 @@ the tracking and management objectives can be achieved more efficiently. * [rate](#rating-a-book-rate) * [list-rated](#sorting-books-by-rating-list-rated) * [display](#displaying-the-details-of-a-book-display) - * [find](#finding-a-book-find) + * [find-title](#finding-a-book-by-title-find-title) + * [find-genre](#finding-a-book-by-genre-find-genre) * [bye](#exiting-the-program-bye) * [FAQ](#faq) * [Command Summary](#command-summary) @@ -183,15 +184,35 @@ Example of usage: display 1 ``` -### Finding a book: `find` +### Finding a book by title: `find-title` Returns all books in the book list that contain the keyword in their title. -Format: `find [KEYWORD]` +Format: `find-title` Example of usage: ``` -find harry +find-title harry +``` + +### Finding a book by genre: `find-genre` +Returns all books in the book list that has the matching genre. + +Format: `find-genre` followed by `[NUMBER]` + +Example of usage: + +``` +find-genre +Available genres: +1. Fiction +2. Non-Fiction +3. Mystery +4. Science Fiction +5. Fantasy +Enter the number for the desired genre: +1 +1. [U] harry potter ``` ### Exiting the program: `bye` @@ -225,11 +246,12 @@ to be saved.** * View all books: `list` * Mark book as read: `mark [BOOK_INDEX]` * Mark book as unread: `unmark [BOOK_INDEX]` -* Set genre: `set-genre [BOOK_INDEX]`, `[NUMBER]` and `[CUSTOM_GENRE]` if necessary +* Set genre: `set-genre [BOOK_INDEX]` followed by `[NUMBER]` then `[CUSTOM_GENRE]` if necessary * Label book: `label [BOOK_INDEX] [LABEL]` * Add summary: `give-summary [BOOK_INDEX] [BOOK_SUMMARY]` * Rate a book: `rate [BOOK_INDEX] [BOOK_RATING]` * Sort books by rating: `list-rated` * Display details: `display [BOOK_INDEX]` -* Find books: `find [KEYWORD]` +* Find books with specific title: `find-title [KEYWORD]` +* Find books with specific genre: `find-genre` followed by `[NUMBER]` * Exit program: `bye` From c35572009d580f4c0de86075e64cc1f66628d4d9 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Wed, 3 Apr 2024 20:46:48 +0800 Subject: [PATCH 148/311] Improve book display function and fix white space issues --- src/main/java/seedu/bookbuddy/BookBuddy.java | 1 - .../java/seedu/bookbuddy/book/BookMain.java | 19 +++++++++++++++++-- src/main/java/seedu/bookbuddy/book/Label.java | 1 + .../java/seedu/bookbuddy/book/Rating.java | 1 + src/main/java/seedu/bookbuddy/book/Read.java | 1 + .../java/seedu/bookbuddy/book/Summary.java | 1 + src/main/java/seedu/bookbuddy/book/Title.java | 1 + .../bookdetailsmodifier/BookDisplay.java | 16 ++++++++++------ .../seedu/bookbuddy/parser/ParserMain.java | 2 -- .../parser/parservalidation/Exceptions.java | 2 ++ 10 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index c7a7351115..ad90c530a2 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -51,7 +51,6 @@ public static void getUserInput(BookList books) throws IOException { FileStorage filestorage = new FileStorage(books); LOGGER.log(Level.INFO, "Starting to get user input."); - //noinspection InfiniteLoopStatement while (true) { String userInput = input.nextLine().trim(); if (userInput.isEmpty()) { diff --git a/src/main/java/seedu/bookbuddy/book/BookMain.java b/src/main/java/seedu/bookbuddy/book/BookMain.java index 73f0bd2d81..9527ca8bff 100644 --- a/src/main/java/seedu/bookbuddy/book/BookMain.java +++ b/src/main/java/seedu/bookbuddy/book/BookMain.java @@ -13,7 +13,7 @@ public class BookMain { /** * Creates a new Book with the specified title. * - * @param title The description of the book. + * @param title The title of the book. */ public BookMain(String title) { this.title = title; // Description of the book @@ -24,6 +24,17 @@ public BookMain(String title) { this.summary = ""; } + //@@author joshuahoky + /** + * Additional BookMain constructor for reading in books from text file. + * + * @param title The title of the book. + * @param status Whether the book is read or unread. + * @param label The label assigned to the book. + * @param genre The genre of the book. + * @param rating The rating assigned to the book. + * @param summary The summary of the book. + */ public BookMain(String title, int status, String label, String genre, int rating, String summary) { this.title = title; this.isRead = status == 1; @@ -33,13 +44,17 @@ public BookMain(String title, int status, String label, String genre, int rating this.summary = (Objects.equals(summary, "*")) ? "" : summary; } - //@@author joshuahoky @Override public String toString() { String statusMark = Read.getRead(this) ? "R" : "U"; // Mark with 'R' if read and 'U' if unread return "[" + statusMark + "] " + this.title; } + /** + * Method to convert details to the correct string format for writing. + * + * @return The string with the details of the book to be written to the text file. + */ public String saveFormat() { String status = isRead ? "1" : "0"; String label = (this.label.isEmpty()) ? "*" : this.label; diff --git a/src/main/java/seedu/bookbuddy/book/Label.java b/src/main/java/seedu/bookbuddy/book/Label.java index d81520fb9d..eee1147957 100644 --- a/src/main/java/seedu/bookbuddy/book/Label.java +++ b/src/main/java/seedu/bookbuddy/book/Label.java @@ -13,6 +13,7 @@ public static void setLabel(BookMain book, String label) { /** * Returns the label of the book. + * * @param book * @return The label of the book. */ diff --git a/src/main/java/seedu/bookbuddy/book/Rating.java b/src/main/java/seedu/bookbuddy/book/Rating.java index 3936aa0a49..9110959abf 100644 --- a/src/main/java/seedu/bookbuddy/book/Rating.java +++ b/src/main/java/seedu/bookbuddy/book/Rating.java @@ -17,6 +17,7 @@ public static void setRating(BookMain book, int rating) { /** * Returns the rating of the book. + * * @param book * @return The rating of the book. */ diff --git a/src/main/java/seedu/bookbuddy/book/Read.java b/src/main/java/seedu/bookbuddy/book/Read.java index 9fe261ad10..eee7ffd0f7 100644 --- a/src/main/java/seedu/bookbuddy/book/Read.java +++ b/src/main/java/seedu/bookbuddy/book/Read.java @@ -4,6 +4,7 @@ public class Read { //@@author lordgareth10 /** * Checks if the book is read. + * * @param book * @return True if the book is read, false otherwise. */ diff --git a/src/main/java/seedu/bookbuddy/book/Summary.java b/src/main/java/seedu/bookbuddy/book/Summary.java index 64a2ff9ac6..4fa1d54c5d 100644 --- a/src/main/java/seedu/bookbuddy/book/Summary.java +++ b/src/main/java/seedu/bookbuddy/book/Summary.java @@ -15,6 +15,7 @@ public static void setSummary(BookMain book, String summary) { /** * Returns the summary of the book. + * * @param book * @return The summary of the book. */ diff --git a/src/main/java/seedu/bookbuddy/book/Title.java b/src/main/java/seedu/bookbuddy/book/Title.java index ebe2543f84..bc57a8c786 100644 --- a/src/main/java/seedu/bookbuddy/book/Title.java +++ b/src/main/java/seedu/bookbuddy/book/Title.java @@ -3,6 +3,7 @@ public class Title { /** * Returns the title of the book. + * * @param book * @return The title of the book. */ diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index 569856c03e..c6dd2a73a5 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -18,7 +18,7 @@ public class BookDisplay { /** * Prints the details of the book at the specified index. * - * @param index The index of hte book in the list. + * @param index The index of the book in the list. * @throws IndexOutOfBoundsException if the index is out of range. */ public static void displayDetails(int index, BookList books) throws IndexOutOfBoundsException { @@ -26,19 +26,23 @@ public static void displayDetails(int index, BookList books) throws IndexOutOfBo throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); } + String label = Label.getLabel(books.getBook(index)); + String genre = Genre.getGenre(books.getBook(index)); + int rating = Rating.getRating(books.getBook(index)); + String summary = Summary.getSummary(books.getBook(index)); System.out.println("Here are the details of your book:"); System.out.println("Title: " + Title.getTitle(books.getBook(index))); System.out.println("Status: " + (Read.getRead(books.getBook(index)) ? "Read" : "Unread")); - System.out.println("Label: " + Label.getLabel(books.getBook(index))); - System.out.println("Genre: " + Genre.getGenre(books.getBook(index))); - System.out.println("Rating: " + Rating.getRating(books.getBook(index))); - System.out.println("Summary: " + Summary.getSummary(books.getBook(index))); + System.out.println("Label: " + (label.isEmpty() ? "No label provided" : label)); + System.out.println("Genre: " + (genre.isEmpty() ? "No genre provided" : genre)); + System.out.println("Rating: " + ((rating == -1) ? "No rating provided" : rating)); + System.out.println("Summary: " + (summary.isEmpty() ? "No summary provided" : summary)); } /** * Prints all books currently in the list. * - * @param bookList + * @param bookList The bookList array with all the books */ public static void printAllBooks(BookList bookList) { assert bookList.getBooks() != null : "Books list should not be null since it has been initialised."; diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index a46f6269fb..3bfdb0a1fb 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -61,11 +61,9 @@ public static void parseCommand(String input, BookList books) { break; case CommandList.FIND_TITLE_COMMAND: ParserFind.parseTitle(books, inputArray[1]); - //BookDisplay.findBookTitle(books, inputArray[1]); break; case CommandList.FIND_GENRE_COMMAND: ParserFind.parseFindGenre(books); - //BookDisplay.findBookGenre(books, inputArray[1]); break; case CommandList.LABEL_COMMAND: ParserLabel.executeParseSetLabel(books, inputArray); diff --git a/src/main/java/seedu/bookbuddy/parser/parservalidation/Exceptions.java b/src/main/java/seedu/bookbuddy/parser/parservalidation/Exceptions.java index 0b5024ec93..6214461b2d 100644 --- a/src/main/java/seedu/bookbuddy/parser/parservalidation/Exceptions.java +++ b/src/main/java/seedu/bookbuddy/parser/parservalidation/Exceptions.java @@ -23,6 +23,8 @@ public static void handleException(Exception e, String command, String[] inputAr } else if (e instanceof NumberFormatException) { System.out.println("Invalid input: " + inputArray[1] + " is not a valid number. " + "Please enter a valid numeric index. Type 'list' to view list of books."); + } else if (e instanceof ArrayIndexOutOfBoundsException) { + System.out.println("Command requires more parameters than provided. Please try again or type: help"); } else if (e instanceof IndexOutOfBoundsException) { System.out.println("Invalid book index. Please enter a valid index."); } else if (e instanceof InvalidCommandArgumentException) { From 9be4f0454dbdaf87c761dc9d1d2017da2dfe0c34 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 3 Apr 2024 22:13:22 +0800 Subject: [PATCH 149/311] Edit set-genre user guide --- docs/UserGuide.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index e3ee5bc564..419120305e 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -113,6 +113,8 @@ Example of usage with expected output: ``` set-genre 1 +``` +```` Available genres: 1. Fiction 2. Non-Fiction @@ -121,12 +123,20 @@ Available genres: 5. Fantasy 6. Add a new genre Enter the number for the desired genre, or add a new one: +```` +```` 6 +```` +```` Enter the new genre: +```` +```` satire +```` +```` okii categorised [animal farm] as [satire] remember to read it soon.... -``` +```` ### Labelling a book: `label` From 5e64a4c2b388c7af8c103d5824f7baacff55cb20 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 3 Apr 2024 22:38:57 +0800 Subject: [PATCH 150/311] Update User Guide with example output --- docs/UserGuide.md | 130 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 122 insertions(+), 8 deletions(-) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 419120305e..02580a659a 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -41,22 +41,43 @@ View all the commands available in BookBuddy and instructions on their usage. Format: `help` -Example of usage: - +Example usage: ``` help ``` +Example output: +```` +Here's a list of commands to get you started!! +add [BOOK_TITLE] -> to add new books to the list +remove [BOOK_INDEX] -> to remove a book from the list +list -> to show whole list of added books +mark [BOOK_INDEX] -> to mark book as read [R] +unmark [BOOK_INDEX] -> to mark book as unread [U] +set-genre [BOOK_INDEX] -> to set a genre for a book +label [BOOK_INDEX] [LABEL] -> to set a label for a book +give-summary [BOOK_INDEX] [BOOK_SUMMARY] -> to give a book a summary +rate [BOOK_INDEX] [BOOK_RATING] -> to rate a book from 1-5 +list-rated -> to sort books by rating in descending order +display [BOOK_INDEX] -> to view more details about a book +find-title [KEYWORD] -> to find books with keyword in their title +find-genre -> to see all books with the selected genre +bye -> to exit BookBuddy software +```` ### Adding a book: `add` Adds a new book to the book list. Format: `add [BOOK_TITLE]` -Example of usage: - +Example usage: ``` add Harry Potter ``` +Example output: +```` +okii added [Harry Potter] to the list. +remember to read it soon.... +```` ### Removing a book: `remove` Removes a specific book from the book list. @@ -69,17 +90,31 @@ Example of usage: remove 1 ``` +Example output + +```` +alright.. i've removed Harry Potter from the list. +```` + ### Viewing all books: `list` Shows all books in the list along with their titles and status. Format: `list` -Example of usage: +Example usage: ``` list ``` +Example output + +```` +All books: +1. [U] Harry Potter +2. [U] Geronimo Stilton +```` + ### Marking a book as read: `mark` Changes the status of a specific book to read. @@ -91,6 +126,12 @@ Example of usage: mark 1 ``` +Example output: + +```` +Successfully marked Harry Potter as read. +```` + ### Marking a book as unread: `unmark` Changes the status of a specific book to unread. @@ -101,6 +142,11 @@ Example of usage: ``` unmark 1 ``` +Example output: + +```` +Successfully marked Harry Potter as unread. +```` ### Setting the genre of a book: `set-genre` @@ -112,9 +158,11 @@ in the previous step Example of usage with expected output: ``` +//input set-genre 1 ``` ```` +//output Available genres: 1. Fiction 2. Non-Fiction @@ -125,15 +173,19 @@ Available genres: Enter the number for the desired genre, or add a new one: ```` ```` +//input 6 ```` ```` +//output Enter the new genre: ```` ```` +//input satire ```` ```` +//output okii categorised [animal farm] as [satire] remember to read it soon.... ```` @@ -150,6 +202,13 @@ Example of usage: label 1 very cool ``` +Example output: + +```` +okii labeled [Harry Potter] as [very cool] +remember to read it soon.... +```` + ### Adding a book summary: `give-summary` Provides a summary for the specified book. @@ -161,6 +220,12 @@ Example of usage: give-summary 1 A book about a young boy who is invited to study at Hogwarts. ``` +Example output: +```` +okii you have written: [A book about a young boy who is invited to study at Hogwarts.] for the book: [Harry Potter] +remember to read it soon.... +```` + ### Rating a book: `rate` Assigns a rating to a specific book, from a scale of 1-5. @@ -172,6 +237,13 @@ Example of usage: rate 1 3 ``` +Example output: + +```` +okii set rating for [Harry Potter] as [3] +remember to read it soon.... +```` + ### Sorting books by rating: `list-rated` Prints a list of books and their ratings in descending order. @@ -183,6 +255,14 @@ Example of usage: list-rated ``` +Example output: +```` +Books sorted by rating: +The Boy in Striped Pyjamas - 5 +Geronimo Stilton - 4 +Harry Potter - 3 +```` + ### Displaying the details of a book: `display` Gives more detailed information about a specific book like its genre, label and summary. @@ -194,6 +274,18 @@ Example of usage: display 1 ``` +Example output: +```` +Here are the details of your book: +Title: Harry Potter +Status: Read +Label: very cool +Genre: No genre provided +Rating: 3 +Summary: A book about a young boy who is invited to study at Hogwarts. + +```` + ### Finding a book by title: `find-title` Returns all books in the book list that contain the keyword in their title. @@ -202,18 +294,28 @@ Format: `find-title` Example of usage: ``` -find-title harry +find-title Harry ``` +Example output: + +```` +1. [R] Harry Potter +```` + ### Finding a book by genre: `find-genre` Returns all books in the book list that has the matching genre. Format: `find-genre` followed by `[NUMBER]` -Example of usage: +Example of usage with expected output: ``` +//input find-genre +``` +```` +//output Available genres: 1. Fiction 2. Non-Fiction @@ -221,9 +323,15 @@ Available genres: 4. Science Fiction 5. Fantasy Enter the number for the desired genre: +```` +```` +//input 1 +```` +```` +//ouput 1. [U] harry potter -``` +```` ### Exiting the program: `bye` @@ -237,6 +345,12 @@ Example of usage: bye ``` +Example output: +```` +Writing successful. Data has been saved. +Thank you for using BookBuddy! Hope to see you again keke :) +```` + ## FAQ **Q**: How do I load and save data from a previous session? From 6bd82caa2507a70107492a6f33bb4b3d00f75769 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 4 Apr 2024 00:34:17 +0800 Subject: [PATCH 151/311] Sequence Diagram for set-genre and label --- docs/DeveloperGuide.md | 131 +++++++++++------- docs/SetGenreSequenceDiagram.png | Bin 0 -> 59258 bytes .../SetGenreSequenceDiagram.puml | 45 ++++++ .../SetLabelSequenceDiagram.puml | 39 ++++++ 4 files changed, 164 insertions(+), 51 deletions(-) create mode 100644 docs/SetGenreSequenceDiagram.png create mode 100644 docs/sequenceDiagram/SetGenreSequenceDiagram.puml create mode 100644 docs/sequenceDiagram/SetLabelSequenceDiagram.puml diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 8cfb4ef899..fdca12afa3 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -17,88 +17,117 @@ Reference to AB-3 diagrams code ## Design & implementation {Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} -### Categorising the different books by their genres + +### FEATURE: Categorising the different books by their genres This functionality enables the categorization of books into distinct groups based on their genres, facilitating better organization and tracking. The implementation of this feature involves interactions across multiple classes within the system. + #### Overview The process of categorizing books by genre is a multi-step operation that involves the following classes: -1. `BookDetails`: This class contains methods that handle the categorization of books. -2. `Book`: Individual book objects are updated with their respective genres directly in this class. -3. `Parser`: This class is responsible for parsing the input command to extract the specific index and genre. +1. `ParserGenre`: This class contains methods that handle the categorization of books. +2. `BookGenre`: Individual book objects are updated with their respective genres directly in this class. +3. `ParserMain`: This class is responsible for parsing the input command to extract the specific index and genre. -#### Detailed Workflow -Below is an example usage: -Here’s a step-by-step guide on how the feature works: -Step 1: The user initiates the process by inputting a command like `set-genre 1 Fantasy`. Here, the `Parser` class plays -a crucial role as it interprets the command and segregates it into a manageable array. The first part of this array holds -the command `set-genre`, which indicates the action to be executed. +#### Architecture-Level Design: -Step 2: The second segment of the input string is then further dissected into two components, which are the index (`1`) -and the genre (`Fantasy`). This step is essential for identifying the specific book and the genre it needs to be -associated with. +- The book management system is structured using a layered architecture model, comprising the UI layer, the command +- parser layer, and the data model layer. +- The `set-genre` feature is primarily situated in the command parser layer but interacts closely with the data model +- layer to update the genre of a book. -Step 3: With the index and genre clearly identified, these parameters are passed to the `setBookGenreByIndex` method -within the `BookDetails` class. This method is then responsible for assigning the specified genre to the book located at -the given index. +#### Component-Level Design: + +- `UI Layer`: Receives input from the user and displays the results of operations. It's where the user's request to set +- a genre starts and ends with feedback. +- `ParserMain` (Command Parser Layer): Acts as the central hub for command processing, determining the type of command +- and delegating to the specific parser. +- `ParserGenre` (Command Parser Layer): Specializes in handling the set-genre command. It prompts the user for inputs +- and validates them. +- `BookList` (Data Model Layer): Maintains the list of books and genres. It's queried to ensure the book index is valid +- and to retrieve or update the list of genres. +- `BookGenre` (Data Model Layer): Provides the functionality to set the genre of a book in the `BookList`. + +#### Implementation Details: + +- Upon invoking the `set-genre` command, `ParserMain` interprets the command and forwards it to `ParserGenre`. +- `ParserGenre` then guides the user through selecting an existing genre or adding a new one, ensuring valid inputs at +- each step. +- The chosen genre is then applied to the specified book through `BookGenre`, which interfaces with `BookList` to make +- the update. +- Throughout this process, `Ui` is called upon to display messages, guiding the user and confirming the successful +- update. + +#### Rationale for Design: + +- The command pattern used allows for easy addition of new commands and features without altering existing code +- structures, adhering to open/closed principles. +- The clear separation of concerns makes the system robust, with each component focusing on a single responsibility, +- enhancing maintainability and scalability. + +#### Alternatives Considered: + +- An embedded approach, where genre setting logic is part of a larger class managing all book attributes, was +- considered. However, this was rejected due to potential scalability issues and difficulty in maintaining code. + +(![SetGenreSequenceDiagram.png](SetGenreSequenceDiagram.png)) -#### Implementation and Rationale -The decision to involve multiple classes in this operation is driven by the principles of object-oriented programming, -which emphasize modularity, encapsulation, and separation of concerns. By distributing responsibilities across different -classes, the system remains flexible, with each class focusing on a specific aspect of the functionality. - -* The `BookDetails` class is central to managing book attributes and behaviors, making it the logical location for methods -* that categorize books. -* The `Book` class represents individual books, and it is here that genre information is ultimately stored, aligning with -* the principle that objects should manage their own state. -* The `Parser` class abstracts the complexity of command interpretation, ensuring that user inputs are correctly understood -* and acted upon by the system. - -#### Alternatives Considered -An alternative design could have centralized the categorization logic within a single class, such as `BookDetails` or -`Parser`. However, this approach was discarded in favor of the current design to avoid overloading a single class with -multiple responsibilities and to adhere to the Single Responsibility Principle. By distributing the tasks, the system -gains in maintainability and scalability, facilitating future enhancements and modifications. ### BookList Class Component The `BookList` class is responsible for all actions involving the list of books that the user has. #### Overview -The `BookList` class contains one protected static ArrayList named books. This ArrayList will contain Book objects. The methods in +The `BookList` class contains one protected static ArrayList named books. This ArrayList will contain Book objects. +The methods in this class all change the ArrayList according to the command given. #### Detailed Workflow -Apart from the constructor, the methods of this class like getSize(), addBook() all either return a piece of information about the ArrayList, -the book object that is selected or change an attribute of the ArrayList or selected book object. For the printAllBooks() method, the ArrayList -is iterated through, with the details of each book being printed out according to the toString() format of each book. Other than that, methods like -markDoneByIndex() and markUndoneByIndex() both will change the isRead() attribute of the book of the given index. This class handles errors related to the -ArrayList, throwing exceptions for invalid indexes and invalid actions based on current state (if trying to mark a book that is already read). +Apart from the constructor, the methods of this class like getSize(), addBook() all either return a piece of +information about the ArrayList, +the book object that is selected or change an attribute of the ArrayList or selected book object. For the +printAllBooks() method, the ArrayList +is iterated through, with the details of each book being printed out according to the toString() format of each book. +Other than that, methods like +markDoneByIndex() and markUndoneByIndex() both will change the isRead() attribute of the book of the given index. This +class handles errors related to the +ArrayList, throwing exceptions for invalid indexes and invalid actions based on current state (if trying to mark a book +that is already read). #### Implementation and Rationale ### Parser Class Component -The `Parser` class is responsible for parsing any input from the user and making sense of them to execute the correct commands. +The `Parser` class is responsible for parsing any input from the user and making sense of them to execute the correct +commands. #### Overview -The `Parser` class contains several predefined string constants representing the valid commands and a public method to parse the +The `Parser` class contains several predefined string constants representing the valid commands and a public method to +parse the input from the user. #### Detailed Workflow -Whenever input from the user is detected by the program, the `Parser` class will split the command into 2 parts, with the first part -containing the command and the second containing details of the command (if present). The command entered is then evaluated using a -switch statement, with the value of it being compared to the values of each case. In the case of a match, the `Parser` class will then -execute the respective action associated with that command by calling other classes from the program such as `BookList` or `BookDetails`. -This class also handles errors and exceptions associated with the users input. For example, if the user were to give the command `mark` without -specifying an index for which book to mark, or gives a negative number, an appropriate error message will be shown and the command will be rendered +Whenever input from the user is detected by the program, the `Parser` class will split the command into 2 parts, with +the first part +containing the command and the second containing details of the command (if present). The command entered is then +evaluated using a +switch statement, with the value of it being compared to the values of each case. In the case of a match, the `Parser` +class will then +execute the respective action associated with that command by calling other classes from the program such as `BookList` +or `BookDetails`. +This class also handles errors and exceptions associated with the users input. For example, if the user were to give +the command `mark` without +specifying an index for which book to mark, or gives a negative number, an appropriate error message will be shown and +the command will be rendered invalid. #### Implementation and Rationale -The `Parser` class incorporates exception handling to detect invalid or unrecognized commands. This allows the program to continue running +The `Parser` class incorporates exception handling to detect invalid or unrecognized commands. This allows the program +to continue running while prompting the user for valid input -By abstracting out the parsing functionality of BookBuddy into a separate `Parser` class, the complexity of parsing user input is removed -from the main code. It is instead replaced by a simple interface for the user to work with, adhering to the abstraction concept of -object-oriented programming. +By abstracting out the parsing functionality of BookBuddy into a separate `Parser` class, the complexity of parsing +user input is removed +from the main code. It is instead replaced by a simple interface for the user to work with, adhering to the abstraction +concept of object-oriented programming. ## Product scope ### Target user profile diff --git a/docs/SetGenreSequenceDiagram.png b/docs/SetGenreSequenceDiagram.png new file mode 100644 index 0000000000000000000000000000000000000000..782192839e615f36d8bb7bc7ab1c80ea67eb573a GIT binary patch literal 59258 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⁡CN>b@IU)sRZ)bNGRO8O`cWiBK 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 ParserMain : set-genre [BOOK_INDEX] +ParserMain -> ParserGenre : executeParseSetGenre(books, inputArray) +ParserGenre -> BookList : validate index +ParserGenre -> ParserGenre : genreSelectionPrinter() +ParserGenre -> User : Display available genres + +alt selecting existing genre + User -> ParserGenre : Select genre number + ParserGenre -> BookList : getAvailableGenres() + ParserGenre -> BookGenre : setBookGenreByIndex(index, selectedGenre, books) + BookGenre -> BookList : getBook(index) + BookGenre -> Ui : setGenreBookMessage(title, genre) + Ui -> User : confirmation message +else adding new genre + User -> ParserGenre : 6 (Add new genre) + ParserGenre -> User : Enter new genre + User -> ParserGenre : Input custom genre + ParserGenre -> BookList : Add new genre to list + ParserGenre -> BookGenre : setBookGenreByIndex(index, newGenre, books) + BookGenre -> BookList : getBook(index) + BookGenre -> Ui : setGenreBookMessage(title, genre) + Ui -> User : confirmation message +end + +@enduml + + + +Flow: +1. The user initiates the set-genre command. +2. ParserMain processes the input and delegates the command to ParserGenre. +3. ParserGenre then interacts with BookList to validate the book index and displays the available genres. +4. The user selects a genre or adds a new one, which ParserGenre processes. +5. If a new genre is added, it is included in the available genres in BookList. +6. Finally, BookGenre sets the genre for the specific book. \ No newline at end of file diff --git a/docs/sequenceDiagram/SetLabelSequenceDiagram.puml b/docs/sequenceDiagram/SetLabelSequenceDiagram.puml new file mode 100644 index 0000000000..12bcc7c013 --- /dev/null +++ b/docs/sequenceDiagram/SetLabelSequenceDiagram.puml @@ -0,0 +1,39 @@ +@startuml +participant User +participant "ParserMain" as ParserMain +participant "ParserLabel" as ParserLabel +participant "BookList" as BookList +participant "BookLabel" as BookLabel +participant "Ui" as Ui + +User -> ParserMain : label [BOOK_INDEX] [LABEL] +activate ParserMain +ParserMain -> ParserLabel : executeParseSetLabel(books, inputArray) +activate ParserLabel + +ParserLabel -> BookList : getBook(index) +activate BookList +BookList --> ParserLabel : book +deactivate BookList + +ParserLabel -> BookLabel : setBookLabelByIndex(index, label, books) +activate BookLabel + +BookLabel -> Ui : labelBookMessage(title, label) +activate Ui +Ui -> User : confirmation message +deactivate Ui + +deactivate BookLabel +deactivate ParserLabel +deactivate ParserMain +@enduml + + + +Flow: +1. The user initiates the label command with a book index and a label. +2. ParserMain receives the command and delegates to ParserLabel. +3. ParserLabel parses the command, validates the input, and then calls BookLabel to set the label for the specified book. +4. BookLabel updates the label in the BookMain instance. +5. Finally, a confirmation message is displayed to the user. \ No newline at end of file From 2fc68399ae0c5c56eb2259230ebe8aaee62ae24d Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Thu, 4 Apr 2024 01:46:14 +0800 Subject: [PATCH 152/311] Update Developer Guide with diagrams --- docs/DeveloperGuide.md | 116 +++++++++++++++++++++--------- docs/UML_Files/AddCommand.puml | 22 ++++++ docs/UML_Files/FileStorage.puml | 21 ++++++ docs/UML_Files/ParserMain.puml | 50 +++++++++++++ docs/UML_diagrams/AddCommand.png | Bin 0 -> 24622 bytes docs/UML_diagrams/FileStorage.png | Bin 0 -> 18809 bytes docs/UML_diagrams/ParserMain.png | Bin 0 -> 26674 bytes 7 files changed, 176 insertions(+), 33 deletions(-) create mode 100644 docs/UML_Files/AddCommand.puml create mode 100644 docs/UML_Files/FileStorage.puml create mode 100644 docs/UML_Files/ParserMain.puml create mode 100644 docs/UML_diagrams/AddCommand.png create mode 100644 docs/UML_diagrams/FileStorage.png create mode 100644 docs/UML_diagrams/ParserMain.png diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 8cfb4ef899..e933182015 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,5 +1,17 @@ # Developer Guide +## Table of Contents +* [Acknowledgements](#acknowledgements) +* [Design & Implementation](#design--implementation) +* * [Categorising the different books by their genres](#categorising-the-different-books-by-their-genres) +* * [BookList Class Component](#booklist-class-component) +* * [ParserMain Class Component](#parsermain-class-component) +* * [FileStorage Class Component](#filestorage-class-component) +* [Product Scope](#product-scope) +* * [Target user profile](#target-user-profile) +* * [Value proposition](#value-proposition) +* [User Stories](#user-stories) + ## Acknowledgements {list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} @@ -13,16 +25,15 @@ Reference to AB-3 diagrams code * [Source URL](https://github.com/se-edu/addressbook-level3/tree/master/docs/diagrams) * Used as reference to understand PlantUML syntax - ## Design & implementation {Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} ### Categorising the different books by their genres -This functionality enables the categorization of books into distinct groups based on their genres, facilitating better -organization and tracking. The implementation of this feature involves interactions across multiple classes within the -system. +This functionality enables the categorization of books into distinct groups based on their genres, facilitating better +organization and tracking. The implementation of this feature involves interactions across multiple classes within the +system. #### Overview -The process of categorizing books by genre is a multi-step operation that involves the following classes: +The process of categorizing books by genre is a multistep operation that involves the following classes: 1. `BookDetails`: This class contains methods that handle the categorization of books. 2. `Book`: Individual book objects are updated with their respective genres directly in this class. 3. `Parser`: This class is responsible for parsing the input command to extract the specific index and genre. @@ -31,40 +42,40 @@ The process of categorizing books by genre is a multi-step operation that involv Below is an example usage: Here’s a step-by-step guide on how the feature works: Step 1: The user initiates the process by inputting a command like `set-genre 1 Fantasy`. Here, the `Parser` class plays -a crucial role as it interprets the command and segregates it into a manageable array. The first part of this array holds +a crucial role as it interprets the command and segregates it into a manageable array. The first part of this array holds the command `set-genre`, which indicates the action to be executed. -Step 2: The second segment of the input string is then further dissected into two components, which are the index (`1`) -and the genre (`Fantasy`). This step is essential for identifying the specific book and the genre it needs to be +Step 2: The second segment of the input string is then further dissected into two components, which are the index (`1`) +and the genre (`Fantasy`). This step is essential for identifying the specific book and the genre it needs to be associated with. -Step 3: With the index and genre clearly identified, these parameters are passed to the `setBookGenreByIndex` method -within the `BookDetails` class. This method is then responsible for assigning the specified genre to the book located at +Step 3: With the index and genre clearly identified, these parameters are passed to the `setBookGenreByIndex` method +within the `BookDetails` class. This method is then responsible for assigning the specified genre to the book located at the given index. #### Implementation and Rationale -The decision to involve multiple classes in this operation is driven by the principles of object-oriented programming, +The decision to involve multiple classes in this operation is driven by the principles of object-oriented programming, which emphasize modularity, encapsulation, and separation of concerns. By distributing responsibilities across different classes, the system remains flexible, with each class focusing on a specific aspect of the functionality. -* The `BookDetails` class is central to managing book attributes and behaviors, making it the logical location for methods +* The `BookDetails` class is central to managing book attributes and behaviors, making it the logical location for methods * that categorize books. -* The `Book` class represents individual books, and it is here that genre information is ultimately stored, aligning with +* The `Book` class represents individual books, and it is here that genre information is ultimately stored, aligning with * the principle that objects should manage their own state. -* The `Parser` class abstracts the complexity of command interpretation, ensuring that user inputs are correctly understood +* The `Parser` class abstracts the complexity of command interpretation, ensuring that user inputs are correctly understood * and acted upon by the system. #### Alternatives Considered -An alternative design could have centralized the categorization logic within a single class, such as `BookDetails` or -`Parser`. However, this approach was discarded in favor of the current design to avoid overloading a single class with -multiple responsibilities and to adhere to the Single Responsibility Principle. By distributing the tasks, the system -gains in maintainability and scalability, facilitating future enhancements and modifications. +An alternative design could have centralized the categorization logic within a single class, such as `BookDetails` or +`Parser`. However, this approach was discarded in favor of the current design to avoid overloading a single class with +multiple responsibilities and to adhere to the Single Responsibility Principle. By distributing the tasks, the system +gains in maintainability and scalability, facilitating future enhancements and modifications. ### BookList Class Component -The `BookList` class is responsible for all actions involving the list of books that the user has. +The `BookList` class is responsible for all actions involving the list of books that the user has. #### Overview -The `BookList` class contains one protected static ArrayList named books. This ArrayList will contain Book objects. The methods in +The `BookList` class contains one protected static ArrayList named books. This ArrayList will contain Book objects. The methods in this class all change the ArrayList according to the command given. #### Detailed Workflow @@ -76,30 +87,57 @@ ArrayList, throwing exceptions for invalid indexes and invalid actions based on #### Implementation and Rationale -### Parser Class Component -The `Parser` class is responsible for parsing any input from the user and making sense of them to execute the correct commands. +### ParserMain Class Component +The `ParserMain` class is responsible for parsing any input from the user and making sense of them to execute the correct commands. #### Overview -The `Parser` class contains several predefined string constants representing the valid commands and a public method to parse the -input from the user. +By importing predefined string constants from `CommandList` class representing the valid commands, the `ParserMain` class +parses the input from a user using the `parseCommand` method. The class diagram below shows how `ParserMain` interacts with other +classes. +![ParserMain.png](UML_diagrams/ParserMain.png) #### Detailed Workflow -Whenever input from the user is detected by the program, the `Parser` class will split the command into 2 parts, with the first part +Whenever input from the user is detected by the program, the `ParserMain` class will split the command into 2 parts, with the first part containing the command and the second containing details of the command (if present). The command entered is then evaluated using a -switch statement, with the value of it being compared to the values of each case. In the case of a match, the `Parser` class will then -execute the respective action associated with that command by calling other classes from the program such as `BookList` or `BookDetails`. +switch statement, with the value of it being compared to the values of each case. In the case of a match, the `ParserMain` class will then +execute the respective action associated with that command by calling other classes from the program such as `BookDisplay` or `ParserAdd`. This class also handles errors and exceptions associated with the users input. For example, if the user were to give the command `mark` without specifying an index for which book to mark, or gives a negative number, an appropriate error message will be shown and the command will be rendered -invalid. +invalid. + +The sequence diagram below shows how user input is processed to carry out the add book command. +![AddCommand.png](UML_diagrams/AddCommand.png) #### Implementation and Rationale -The `Parser` class incorporates exception handling to detect invalid or unrecognized commands. This allows the program to continue running +The `ParserMain` class incorporates exception handling to detect invalid or unrecognized commands. This allows the program to continue running while prompting the user for valid input By abstracting out the parsing functionality of BookBuddy into a separate `Parser` class, the complexity of parsing user input is removed from the main code. It is instead replaced by a simple interface for the user to work with, adhering to the abstraction concept of object-oriented programming. +### FileStorage Class Component +The `FileStorage` class is crucial for managing file operations in BookBuddy, such as reading from and writing to files, +thereby ensuring data is saved for future sessions. + +#### Overview +The `FileStorage` class interacts with the `BookList` and `BookListModifier` classes to load and save book data. It ensures that +the data directory and file exist upon initialization and provides methods for reading from and writing to the data file. +The class diagram below illustrates the relationship between `FileStorage` and other classes. +![FileStorage.png](UML_diagrams/FileStorage.png) + +#### Detailed Workflow +Upon instantiation, the `FileStorage` class checks for the existence of a predefined directory (./data) and file (books.txt), +creating them if they do not exist. Existing data, if any, is read from the file and BookBuddy's BookList is initialized +with this data. For reading data, it scans each line of the file, converting each line of text into instances of `Book` with +the appropriate details. For saving data, it iterates through the BookList, converting each book into a string format suitable +for file storage and writes this to the file. + +#### Implementation and Rationale +The creation of the `FileStorage` class ensures that all features related to reading and writing data is taken away from other +parts of the application. This separation makes the `FileStorage` class solely responsible for these features, adhering to +the Single Responsibility Principle. + ## Product scope ### Target user profile @@ -115,10 +153,22 @@ Users will also be able to search for books via keywords in book titles ## User Stories -|Version| As a ... | I want to ... | So that I can ...| -|--------|----------|---------------|------------------| -|v1.0|new user|see usage instructions|refer to them when I forget how to use the application| -|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list| +| Version | As a ... | I want to ... | So that I can ... | +|---------|----------|-----------------------------------------------|-----------------------------------------------------------| +| v1.0 | new user | see usage instructions | refer to them when I forget how to use the application | +| v1.0 | user | add books to a list | | +| v1.0 | user | remove books from a list | | +| v1.0 | user | see the list of books that are read or unread | I can keep track of my reading progress | +| v1.0 | user | change the status of a book | mark a book when I have finished reading it | +| v2.0 | user | add a summary to a book | remember what the book is about | +| v2.0 | user | provide a label for a book | provide my own personal thoughts on the book | +| v2.0 | user | provide a genre for a book | categorise books according to their genre | +| v2.0 | user | provide a rating for a book | know whether a book was good or not | +| v2.0 | user | sort books by their rating | recall which are the best books that I have read | +| v2.0 | user | view all information about a book | remember to add missing information (if any) | +| v2.0 | user | search for books according to keywords | find a book quickly without going through the entire list | +| v2.0 | user | filter books by genre | see all the books in a particular genre | + ## Non-Functional Requirements diff --git a/docs/UML_Files/AddCommand.puml b/docs/UML_Files/AddCommand.puml new file mode 100644 index 0000000000..d7ad974942 --- /dev/null +++ b/docs/UML_Files/AddCommand.puml @@ -0,0 +1,22 @@ +@startuml + +User -> Parser : parseCommand(input, books) +activate Parser + +alt input is ADD_COMMAND + Parser -> ParserAdd : executeParseAdd(books, inputArray) + activate ParserAdd + ParserAdd --> Parser : CommandExecuted + deactivate ParserAdd +alt other commands + Parser -> Parser : handleOtherCommands() +else unsupported command + Parser -> Exceptions : handleException(e, command, inputArray) + activate Exceptions + Exceptions --> Parser : ExceptionHandled + deactivate Exceptions +end + +Parser --> User : getUserInput(books) +deactivate Parser +@enduml diff --git a/docs/UML_Files/FileStorage.puml b/docs/UML_Files/FileStorage.puml new file mode 100644 index 0000000000..a46e41216f --- /dev/null +++ b/docs/UML_Files/FileStorage.puml @@ -0,0 +1,21 @@ +@startuml +class FileStorage { + - FILE_NAME : String = "books.txt" + - FILE_DIRECTORY : String = "./data" + - FILE_PATH : String = FILE_DIRECTORY + '/' + FILE_NAME + + +FileStorage(books : BookList) + +readData(books : BookList, file : File) : void + +saveData(books : BookList) : void +} + +class BookList { +} + +class BookListModifier { + +addBookFromFile(books : BookList, line : String) : void +} + +FileStorage --> BookList : uses +FileStorage --> BookListModifier : uses +@enduml diff --git a/docs/UML_Files/ParserMain.puml b/docs/UML_Files/ParserMain.puml new file mode 100644 index 0000000000..32c1a7ab49 --- /dev/null +++ b/docs/UML_Files/ParserMain.puml @@ -0,0 +1,50 @@ +@startuml +scale 2000 * 1500 +left to right direction +class ParserMain { + +parseCommand(input : String, books : BookList) : void +} + +class ParserCommands { + +execute(books : BookList, inputArray : String[]) : void +} + +class BookList { + - static availableGenres : List + - books : ArrayList + +BookList() + +getAvailableGenres() : List + +getBooks() : List + +getSize() : int + +getBook(index : int) : BookMain +} + +class BookDisplay { + +printAllBooks(books : BookList) : void +} + +class Ui { + +helpMessage() : void +} + +class Exceptions { + {static} +validateCommandArguments(inputArray : String[], requiredArgs : int, errorMessage : String) : void + {static} +handleException(e : Exception, command : String, inputArray : String[]) : void +} + +class BookRating { + +printBooksByRating(books : BookList) : void +} + +class CommandList { + {static} ADD_COMMAND : String = "add" +} + +ParserMain --> ParserCommands : uses +ParserMain --> BookList +ParserMain --> BookDisplay : uses +ParserMain --> Ui : uses +ParserMain --> Exceptions : uses +ParserMain --> BookRating : uses +ParserMain --> CommandList : uses +@enduml diff --git a/docs/UML_diagrams/AddCommand.png b/docs/UML_diagrams/AddCommand.png new file mode 100644 index 0000000000000000000000000000000000000000..ba9fdee4328d2b214988b641888839977d05ed1b GIT binary patch literal 24622 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 SL+s!y2pI`Q@h|rcy#F7BpTnsD literal 0 HcmV?d00001 diff --git a/docs/UML_diagrams/ParserMain.png b/docs/UML_diagrams/ParserMain.png new file mode 100644 index 0000000000000000000000000000000000000000..29bcc3bcbb8f0a537fed74fbf95d80d9ebfab6f8 GIT binary patch literal 26674 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

    qifcVPF%D@#psasrfnLVqhuExXSysp3EYocH8 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 literal 0 HcmV?d00001 From 95b3323a5713eba40953c54d70d288e43fc103b3 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Thu, 4 Apr 2024 02:12:47 +0800 Subject: [PATCH 153/311] Fix Developer Guide formatting issue --- docs/DeveloperGuide.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index c1d814338e..f06fb6dd71 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -143,6 +143,7 @@ thereby ensuring data is saved for future sessions. The `FileStorage` class interacts with the `BookList` and `BookListModifier` classes to load and save book data. It ensures that the data directory and file exist upon initialization and provides methods for reading from and writing to the data file. The class diagram below illustrates the relationship between `FileStorage` and other classes. + ![FileStorage.png](UML_diagrams/FileStorage.png) #### Detailed Workflow From 2db0624c2adc4f15415fdec611ef2cf33b64b117 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 4 Apr 2024 16:04:14 +0800 Subject: [PATCH 154/311] Update User Guide to show target users and value proposition --- docs/UserGuide.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 02580a659a..ab01fce3b8 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -5,6 +5,18 @@ BookBuddy is an application that helps users track and manage the list of books that they are reading. It is optimised for users that are familiar with the CLI so that the tracking and management objectives can be achieved more efficiently. +#### Target user and value proposition +BookBuddy targets people who are comfortable and proficient with using command-line-interfaces(CLI). These users +typically prefers the efficiency and flexibility of CLI environments over Graphical user Interfaces (GUIs). They are +likely to be avid readers who read multiple books simultaneously and need an effective way to organise their reading +lists. + +Therefore, BookBuddy offers a streamlined and efficient solution for tracking and managing reading lists directly from +the command line. It provides quick access to book management features for users who prefer keyboard-driven commands and +enjoy the directness and simplicity of CLI tools. With BookBuddy, users can easily add, update, and review their reading +progress without the overhead of navigating traditional graphical applications, saving time and enhancing their reading +experience. + ## Table of Contents * [Getting Started](#getting-started) * [Features](#features) @@ -256,6 +268,7 @@ list-rated ``` Example output: + ```` Books sorted by rating: The Boy in Striped Pyjamas - 5 From e37861958a60ad7e1497655db1ffcdc61a7f95b3 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Thu, 4 Apr 2024 16:08:52 +0800 Subject: [PATCH 155/311] find_status --- src/main/java/seedu/bookbuddy/Ui.java | 12 ++++++++++-- .../bookdetailsmodifier/BookDisplay.java | 17 +++++++++++++++++ .../parser/parsercommands/ParserFind.java | 3 +++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 46b9c511c4..5e6c38e549 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -78,7 +78,6 @@ public static void printBookFound(ArrayList bookTitles){ } public static void printNoBookFound(){ System.out.println("no such books added..."); - } public static void printGenresFound(ArrayList bookGenres){ for (int i = 0; i < bookGenres.size(); i++) { @@ -87,6 +86,15 @@ public static void printGenresFound(ArrayList bookGenres){ } public static void printNoGenresFound(){ System.out.println("no such books added..."); - + } + public static void printReadFound(ArrayList bookRead){ + for (int i = 0; i < bookRead.size(); i++) { + System.out.println(i + 1 + ". " + bookRead.get(i)); + } + } + public static void printUnreadFound(ArrayList bookUnread){ + for (int i = 0; i < bookUnread.size(); i++) { + System.out.println(i + 1 + ". " + bookUnread.get(i)); + } } } diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index e54f93a146..00cbc3b9ee 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -80,4 +80,21 @@ public static void findBookGenre(BookList bookList, String genre) { Ui.printGenresFound(bookGenres); } } + public static void findMarkStatus(BookList bookList, String status){ + ArrayList bookRead = new ArrayList<>(); + ArrayList bookUnread = new ArrayList<>(); + for (BookMain book : bookList.getBooks()) { + if (Read.getRead(book)) { + bookRead.add(book); + } else { + bookUnread.add(book); + } + } + if (bookRead.isEmpty() || bookUnread.isEmpty()){ + Ui.printNoGenresFound(); + } else { + + Ui.printGenresFound(bookRead); + } + } } diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java index bb11aa40e3..c7b6f42471 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java @@ -27,4 +27,7 @@ public static void parseFindGenre(BookList books) { } BookDisplay.findBookGenre(books, selectedGenre); } + public static void parseFindStatus(BookList books, String inputArray) { + BookDisplay.findMarkStatus(books, inputArray); + } } From c46ceb05c160c51ed662785a86dd8eca289b9643 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 4 Apr 2024 16:21:07 +0800 Subject: [PATCH 156/311] Checkstyle fixes --- docs/UserGuide.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index ab01fce3b8..8304fb4bb2 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -296,7 +296,6 @@ Label: very cool Genre: No genre provided Rating: 3 Summary: A book about a young boy who is invited to study at Hogwarts. - ```` ### Finding a book by title: `find-title` From 8696d31ee767022c4b62ab185df49e26fe144bf8 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Thu, 4 Apr 2024 16:59:24 +0800 Subject: [PATCH 157/311] find based on read and unread status implemented --- .../bookdetailsmodifier/BookDisplay.java | 20 ++++++++++++++----- .../seedu/bookbuddy/parser/ParserMain.java | 6 ++++++ .../parser/parsercommands/ParserFind.java | 7 +++++-- .../parser/parservalidation/CommandList.java | 2 ++ 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index 10bbe1f41c..23c1de7f18 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -87,21 +87,31 @@ public static void findBookGenre(BookList bookList, String genre) { Ui.printGenresFound(bookGenres); } } - public static void findMarkStatus(BookList bookList, String status){ + public static void findRead(BookList bookList){ ArrayList bookRead = new ArrayList<>(); - ArrayList bookUnread = new ArrayList<>(); for (BookMain book : bookList.getBooks()) { if (Read.getRead(book)) { bookRead.add(book); - } else { + } + } + if (bookRead.isEmpty()){ + Ui.printNoGenresFound(); + } else { + Ui.printReadFound(bookRead); + } + } + public static void findUnread(BookList bookList){ + ArrayList bookUnread = new ArrayList<>(); + for (BookMain book : bookList.getBooks()) { + if (!Read.getRead(book)) { bookUnread.add(book); } } - if (bookRead.isEmpty() || bookUnread.isEmpty()){ + if (bookUnread.isEmpty()){ Ui.printNoGenresFound(); } else { - Ui.printGenresFound(bookRead); + Ui.printUnreadFound(bookUnread); } } } diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index 3bfdb0a1fb..759c5306c0 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -65,6 +65,12 @@ public static void parseCommand(String input, BookList books) { case CommandList.FIND_GENRE_COMMAND: ParserFind.parseFindGenre(books); break; + case CommandList.FIND_READ_COMMAND: + ParserFind.parseFindRead(books); + break; + case CommandList.FIND_UNREAD_COMMAND: + ParserFind.parseFindUnread(books); + break; case CommandList.LABEL_COMMAND: ParserLabel.executeParseSetLabel(books, inputArray); break; diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java index 4710a043f7..97065d9ce1 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java @@ -28,7 +28,10 @@ public static void parseFindGenre(BookList books) { } BookDisplay.findBookGenre(books, selectedGenre); } - public static void parseFindStatus(BookList books, String inputArray) { - BookDisplay.findMarkStatus(books, inputArray); + public static void parseFindRead(BookList books) { + BookDisplay.findRead(books); + } + public static void parseFindUnread(BookList books) { + BookDisplay.findUnread(books); } } diff --git a/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java b/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java index 146ad4e5a5..30490adaee 100644 --- a/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java +++ b/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java @@ -9,6 +9,8 @@ public class CommandList { public static final String HELP_COMMAND = "help"; public static final String FIND_TITLE_COMMAND = "find-title"; public static final String FIND_GENRE_COMMAND = "find-genre"; + public static final String FIND_READ_COMMAND = "find-read"; + public static final String FIND_UNREAD_COMMAND = "find-unread"; public static final String LABEL_COMMAND = "label"; public static final String GENRE_COMMAND = "set-genre"; public static final String SUMMARY_COMMAND = "give-summary"; From b96b43458c84153539de4ac79972236fdc8f4f36 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Thu, 4 Apr 2024 17:30:13 +0800 Subject: [PATCH 158/311] updated UG and list of commands in help --- docs/UserGuide.md | 36 ++++++++++++++++++++++++++- src/main/java/seedu/bookbuddy/Ui.java | 3 +++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 87ca4c3a30..4da97e1dca 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -72,7 +72,9 @@ rate [BOOK_INDEX] [BOOK_RATING] -> to rate a book from 1-5 list-rated -> to sort books by rating in descending order display [BOOK_INDEX] -> to view more details about a book find-title [KEYWORD] -> to find books with keyword in their title -find-genre -> to see all books with the selected genre +find-genre -> to find books under specific genres +find-read -> to find list of books that are read +find-unread -> to find list of books that are unread bye -> to exit BookBuddy software ```` @@ -345,6 +347,36 @@ Enter the number for the desired genre: 1. [U] harry potter ```` +### Find books that are read: `find-read` +Returns all books in the saved book list that are marked read. + +Format: `find-read` + +Example of usage with expected output: +```` +//input +find-read +```` +```` +//ouput +1. [R] harry potter +```` +### Find books that are unread: `find-unread` +Returns all books in the saved book list that are marked unread. + +Format: `find-unread` + +Example of usage with expected output: +```` +//input +find-unread +```` +```` +//ouput +1. [U] geronimo stilton +2. [U] The Boy in Striped Pyjamas +```` + ### Exiting the program: `bye` Exits the application and saves all tasks in a file. @@ -390,4 +422,6 @@ to be saved.** * Display details: `display [BOOK_INDEX]` * Find books with specific title: `find-title [KEYWORD]` * Find books with specific genre: `find-genre` followed by `[NUMBER]` +* Find books that are read: `find-read` +* Find books that are unread: `find-unread` * Exit program: `bye` diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 5e6c38e549..d6418ba372 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -68,6 +68,9 @@ public static void helpMessage() { System.out.println("list-rated -> to sort books by rating in descending order"); System.out.println("display [BOOK_INDEX] -> to view more details about a book"); System.out.println("find-title [KEYWORD] -> to find books with keyword in their title"); + System.out.println("find-genre -> to find books under specific genres"); + System.out.println("find-read -> to find list of books that are read"); + System.out.println("find-unread -> to find list of books that are unread"); System.out.println("bye -> to exit BookBuddy software"); } From 84adc3c4f4a9d2144f942174fc416171a9b8bfb7 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 4 Apr 2024 19:58:55 +0800 Subject: [PATCH 159/311] Add single step operation of set-genre method for pro users --- src/main/java/seedu/bookbuddy/Ui.java | 3 + .../seedu/bookbuddy/parser/ParserMain.java | 4 +- .../parser/parsercommands/ParserGenre.java | 79 ++++++++++++++----- 3 files changed, 62 insertions(+), 24 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index d6418ba372..6cb25cb303 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -47,6 +47,9 @@ public static void setGenreBookMessage(String title, String genre) { System.out.println("okii categorised [" + title + "] as [" + genre + "]"); System.out.println("remember to read it soon...."); } + public static void exitCommandMessage() { + System.out.println("okii exitting the command now"); + } public static void setRatingBookMessage(String title, int rating) { System.out.println("okii set rating for [" + title + "] as [" + rating +"]"); System.out.println("remember to read it soon...."); diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index 759c5306c0..0b3ae03418 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -79,9 +79,7 @@ public static void parseCommand(String input, BookList books) { break; case CommandList.GENRE_COMMAND: // Exit the command if user types 'exit' - if (ParserGenre.executeParseSetGenre(books, inputArray)) { - return; - } + ParserGenre.executeParseSetGenre(books, inputArray); break; case CommandList.RATING_COMMAND: ParserRating.executeParseSetRating(books, inputArray); diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java index 3b526417aa..57a0e6b84c 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java @@ -1,5 +1,6 @@ package seedu.bookbuddy.parser.parsercommands; +import seedu.bookbuddy.Ui; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.bookdetailsmodifier.BookGenre; import seedu.bookbuddy.parser.parservalidation.Exceptions; @@ -7,30 +8,66 @@ import java.util.Scanner; public class ParserGenre { - static boolean parseSetGenre(BookList books, String[] inputArray) { + static void parseSetGenre(BookList books, String[] inputArray) { + Exceptions.validateCommandArguments(inputArray, 2, "The set-genre command requires " + + "at least a book index."); + + String[] parts = inputArray[1].split(" ", 2); // Attempt to split inputArray[1] into two parts int index; - Exceptions.validateCommandArguments(inputArray, 2, "The set-genre " + - "Command requires a book index."); - index = Integer.parseInt(inputArray[1]); - if (index < 0 || index > books.getSize()) { - throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index. " + - "Type 'list' to view the list of books."); + try { + index = Integer.parseInt(parts[0]); // The first part should be the index + } catch (NumberFormatException e) { + System.out.println("Invalid book index. Please enter a valid numeric index."); + return; + } + + if (index < 0 || index >= books.getSize()) { + System.out.println("Invalid book index. Please enter a valid index. Type 'list' to view the " + + "list of books."); + return; } - genreSelectionPrinter(); + if (parts.length > 1 && !parts[1].isEmpty()) { + // Single-step process: Set genre directly + singleStepSetGenre(books, parts, index); + } else { + // Multistep process: Follow the existing flow + multiStepSetGenre(books, index); + } + } + + private static void multiStepSetGenre(BookList books, int index) { + genreSelectionPrinter(); System.out.println("Enter the number for the desired genre, or add a new one:"); Scanner scanner = new Scanner(System.in); - String selectedGenre = null; - selectedGenre = invalidInputLooper(selectedGenre, scanner); + String selectedGenre = invalidInputLooper(null, scanner); if (selectedGenre == null) { - return true; + return; } - BookGenre.setBookGenreByIndex(index, selectedGenre, books); - return false; } + private static void singleStepSetGenre(BookList books, String[] parts, int index) { + String genreInput = parts[1]; + boolean genreExists = false; + for (String existingGenre : BookList.getAvailableGenres()) { + if (existingGenre.equalsIgnoreCase(genreInput)) { + genreExists = true; + genreInput = existingGenre; // Normalize to the existing genre's case + break; + } + } + + if (!genreExists) { + BookList.getAvailableGenres().add(genreInput); + System.out.println("Added new genre to the list: " + genreInput); + } + BookGenre.setBookGenreByIndex(index, genreInput, books); + System.out.println("Genre set to " + genreInput + " for book at index " + index); + } + + static void genreSelectionPrinter() { System.out.println("Available genres:"); for (int i = 0; i < BookList.getAvailableGenres().size(); i++) { @@ -39,11 +76,12 @@ static void genreSelectionPrinter() { System.out.println((BookList.getAvailableGenres().size() + 1) + ". Add a new genre"); } - static String invalidInputLooper(String selectedGenre, Scanner scanner) { - while (selectedGenre == null) { + static String invalidInputLooper(String input, Scanner scanner) { + while (input == null) { while (!scanner.hasNextInt()) { // Ensure the next input is an integer String newInput = scanner.nextLine(); if ("exit".equalsIgnoreCase(newInput)) { + Ui.exitCommandMessage(); return null; } else { System.out.println("Invalid input. Please enter a valid number or type 'exit'" + @@ -56,21 +94,20 @@ static String invalidInputLooper(String selectedGenre, Scanner scanner) { if (genreSelection == BookList.getAvailableGenres().size() + 1) { System.out.println("Enter the new genre:"); - selectedGenre = scanner.nextLine(); - BookList.getAvailableGenres().add(selectedGenre); // Add the new genre to the list + input = scanner.nextLine(); + BookList.getAvailableGenres().add(input); // Add the new genre to the list } else if (genreSelection > 0 && genreSelection <= BookList.getAvailableGenres().size()) { - selectedGenre = BookList.getAvailableGenres().get(genreSelection - 1); + input = BookList.getAvailableGenres().get(genreSelection - 1); } else { System.out.println("Invalid selection. Please enter a valid number " + "or type 'exit' to cancel."); // No need for the nextLine or parsing logic here, the while loop will continue } } - return selectedGenre; + return input; } - public static boolean executeParseSetGenre (BookList books, String[] inputArray) { + public static void executeParseSetGenre (BookList books, String[] inputArray) { parseSetGenre(books, inputArray); - return false; } } From de13d71a0ee1f8209647a12c08469639a824fe61 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 4 Apr 2024 22:32:33 +0800 Subject: [PATCH 160/311] Checkstyle fixes --- .../parser/parsercommands/ParserGenre.java | 2 +- .../java/seedu/bookbuddy/ParserMainTest.java | 43 ++++++++----------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java index 57a0e6b84c..206bfc0f34 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java @@ -1,8 +1,8 @@ package seedu.bookbuddy.parser.parsercommands; import seedu.bookbuddy.Ui; -import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.bookdetailsmodifier.BookGenre; +import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.parser.parservalidation.Exceptions; import java.util.Scanner; diff --git a/src/test/java/seedu/bookbuddy/ParserMainTest.java b/src/test/java/seedu/bookbuddy/ParserMainTest.java index 2c1d09bac1..3b3491625b 100644 --- a/src/test/java/seedu/bookbuddy/ParserMainTest.java +++ b/src/test/java/seedu/bookbuddy/ParserMainTest.java @@ -3,7 +3,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import seedu.bookbuddy.book.Genre; import seedu.bookbuddy.book.Label; import seedu.bookbuddy.book.Read; import seedu.bookbuddy.book.Title; @@ -12,14 +11,10 @@ import seedu.bookbuddy.booklist.BookListModifier; import seedu.bookbuddy.parser.ParserMain; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.io.InputStream; import java.io.PrintStream; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public class ParserMainTest { @@ -95,24 +90,24 @@ void parseLabelCommand() { assertEquals("Great Book", Label.getLabel(books.getBook(1))); } - @Test - void parseGenreCommand() { - BookList books = new BookList(); - BookListModifier.addBook(books, "The Great Gatsby"); - // Simulate user input for genre selection "Classic" - String simulatedUserInput = "6\nClassic\n"; // Assuming '3' is the option to add a new genre - InputStream savedStandardInputStream = System.in; - System.setIn(new ByteArrayInputStream(simulatedUserInput.getBytes())); - ParserMain.parseCommand("set-genre 1", books); // Changed to fit your updated command-handling logic - assertEquals("Classic", Genre.getGenre(books.getBook(1))); // Indexes are typically 0-based in lists - - BookListModifier.addBook(books, "Geronimo"); - String nextSimulatedUserInput = "3\n"; - System.setIn(new ByteArrayInputStream(nextSimulatedUserInput.getBytes())); - ParserMain.parseCommand("set-genre 2", books); - assertEquals("Mystery", Genre.getGenre(books.getBook(2))); - System.setIn(savedStandardInputStream); - } +// @Test +// void parseGenreCommand() { +// BookList books = new BookList(); +// BookListModifier.addBook(books, "The Great Gatsby"); +// // Simulate user input for genre selection "Classic" +// String simulatedUserInput = "6\nClassic\n"; // Assuming '3' is the option to add a new genre +// InputStream savedStandardInputStream = System.in; +// System.setIn(new ByteArrayInputStream(simulatedUserInput.getBytes())); +// ParserMain.parseCommand("set-genre 1", books); // Changed to fit your updated command-handling logic +// assertEquals("Classic", Genre.getGenre(books.getBook(1))); // Indexes are typically 0-based in lists +// +// BookListModifier.addBook(books, "Geronimo"); +// String nextSimulatedUserInput = "3\n"; +// System.setIn(new ByteArrayInputStream(nextSimulatedUserInput.getBytes())); +// ParserMain.parseCommand("set-genre 2", books); +// assertEquals("Mystery", Genre.getGenre(books.getBook(2))); +// System.setIn(savedStandardInputStream); +// } @Test void parseInvalidAddCommandThrowsException() { From 3a3fc920a2fd83ac692c7415fede02ae929062d1 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 4 Apr 2024 22:34:33 +0800 Subject: [PATCH 161/311] Checkstyle fixes --- .../java/seedu/bookbuddy/ParserMainTest.java | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/ParserMainTest.java b/src/test/java/seedu/bookbuddy/ParserMainTest.java index 3b3491625b..12df5252e1 100644 --- a/src/test/java/seedu/bookbuddy/ParserMainTest.java +++ b/src/test/java/seedu/bookbuddy/ParserMainTest.java @@ -14,7 +14,9 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; public class ParserMainTest { @@ -90,24 +92,24 @@ void parseLabelCommand() { assertEquals("Great Book", Label.getLabel(books.getBook(1))); } -// @Test -// void parseGenreCommand() { -// BookList books = new BookList(); -// BookListModifier.addBook(books, "The Great Gatsby"); -// // Simulate user input for genre selection "Classic" -// String simulatedUserInput = "6\nClassic\n"; // Assuming '3' is the option to add a new genre -// InputStream savedStandardInputStream = System.in; -// System.setIn(new ByteArrayInputStream(simulatedUserInput.getBytes())); -// ParserMain.parseCommand("set-genre 1", books); // Changed to fit your updated command-handling logic -// assertEquals("Classic", Genre.getGenre(books.getBook(1))); // Indexes are typically 0-based in lists -// -// BookListModifier.addBook(books, "Geronimo"); -// String nextSimulatedUserInput = "3\n"; -// System.setIn(new ByteArrayInputStream(nextSimulatedUserInput.getBytes())); -// ParserMain.parseCommand("set-genre 2", books); -// assertEquals("Mystery", Genre.getGenre(books.getBook(2))); -// System.setIn(savedStandardInputStream); -// } + // @Test + // void parseGenreCommand() { + // BookList books = new BookList(); + // BookListModifier.addBook(books, "The Great Gatsby"); + // // Simulate user input for genre selection "Classic" + // String simulatedUserInput = "6\nClassic\n"; // Assuming '3' is the option to add a new genre + // InputStream savedStandardInputStream = System.in; + // System.setIn(new ByteArrayInputStream(simulatedUserInput.getBytes())); + // ParserMain.parseCommand("set-genre 1", books); // Changed to fit your updated command-handling logic + // assertEquals("Classic", Genre.getGenre(books.getBook(1))); // Indexes are typically 0-based in lists + // + // BookListModifier.addBook(books, "Geronimo"); + // String nextSimulatedUserInput = "3\n"; + // System.setIn(new ByteArrayInputStream(nextSimulatedUserInput.getBytes())); + // ParserMain.parseCommand("set-genre 2", books); + // assertEquals("Mystery", Genre.getGenre(books.getBook(2))); + // System.setIn(savedStandardInputStream); + // } @Test void parseInvalidAddCommandThrowsException() { From 9412bb1806c95c1ceaf64cbf2bbcf77a19ab478d Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 4 Apr 2024 22:47:39 +0800 Subject: [PATCH 162/311] Update user guide --- docs/DeveloperGuide.md | 1 - docs/UserGuide.md | 33 ++++++++++++++++++++++----------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index f06fb6dd71..194d4ec7b9 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -14,7 +14,6 @@ ## Acknowledgements -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} Reference to AB-3 Developer Guide * [Source URL](https://se-education.org/addressbook-level3/DeveloperGuide.html#documentation-logging-testing-configuration-dev-ops) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 4da97e1dca..550e9ba56a 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -169,13 +169,17 @@ Sets the genre of a specific book based on the provided input. Format: `set-genre [BOOK_INDEX]` followed by `[NUMBER]` then `[CUSTOM_GENRE]` if 6 is entered in the previous step +Or for Pro Users: + +Format: `set-genre [BOOK_INDEX] [GENRE]` + + Example of usage with expected output: ``` //input set-genre 1 -``` -```` + //output Available genres: 1. Fiction @@ -185,25 +189,31 @@ Available genres: 5. Fantasy 6. Add a new genre Enter the number for the desired genre, or add a new one: -```` -```` + //input 6 -```` -```` + //output Enter the new genre: -```` -```` + //input satire -```` -```` + //output okii categorised [animal farm] as [satire] remember to read it soon.... ```` +```` +//input +set-genre 1 Fiction +okii categorised [Harry Potter] as [Fiction] +remember to read it soon.... + +//output +Genre set to Fiction for book at index 1 +```` + ### Labelling a book: `label` Sets the label of a specific book to the provided input. @@ -414,7 +424,8 @@ to be saved.** * View all books: `list` * Mark book as read: `mark [BOOK_INDEX]` * Mark book as unread: `unmark [BOOK_INDEX]` -* Set genre: `set-genre [BOOK_INDEX]` followed by `[NUMBER]` then `[CUSTOM_GENRE]` if necessary +* Set genre: `set-genre [BOOK_INDEX] [GENRE]` (Single-Step for Pro users) +* Set genre: `set-genre [BOOK_INDEX]` followed by `[NUMBER]` then `[CUSTOM_GENRE]` if necessary (Multi-Step) * Label book: `label [BOOK_INDEX] [LABEL]` * Add summary: `give-summary [BOOK_INDEX] [BOOK_SUMMARY]` * Rate a book: `rate [BOOK_INDEX] [BOOK_RATING]` From 4b1f6d928a3d87aebb95df8c0b519dfd07efc199 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 4 Apr 2024 22:53:06 +0800 Subject: [PATCH 163/311] Update developer guide --- docs/DeveloperGuide.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 194d4ec7b9..0557697e7b 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -189,14 +189,4 @@ Users will also be able to search for books via keywords in book titles | v2.0 | user | filter books by genre | see all the books in a particular genre | -## Non-Functional Requirements -{Give non-functional requirements} - -## Glossary - -* *glossary item* - Definition - -## Instructions for manual testing - -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} From 0609e2532b5e4151114df1fc798e3a953ba79e3a Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 4 Apr 2024 23:03:53 +0800 Subject: [PATCH 164/311] Authorship edits --- .../seedu/bookbuddy/parser/parsercommands/ParserDisplay.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserDisplay.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserDisplay.java index 595c0f453a..ae2d212982 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserDisplay.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserDisplay.java @@ -5,7 +5,7 @@ import seedu.bookbuddy.parser.parservalidation.Exceptions; public class ParserDisplay { - //@@ author joshuahoky + //@@author joshuahoky static void parseDisplay(BookList books, String[] inputArray) { int index; assert inputArray.length >= 2 : "Command requires additional arguments"; @@ -14,7 +14,7 @@ static void parseDisplay(BookList books, String[] inputArray) { index = Integer.parseInt(inputArray[1]); BookDisplay.displayDetails(index, books); } - //@@author joshuahoky + public static void executeParseAdd (BookList books, String[] inputArray) { parseDisplay(books, inputArray); } From 1c787a4953d727283afb874d6c953afe56b49925 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Thu, 4 Apr 2024 23:49:45 +0800 Subject: [PATCH 165/311] fix bug such that capitalization of letters do not matter when using find-title and find-genre --- BookBuddy.log.1 | 8 ++++++++ .../bookdetailsmodifier/BookDisplay.java | 5 +++-- .../seedu/bookbuddy/parser/ParserMain.java | 2 +- .../parser/parsercommands/ParserFind.java | 18 +++--------------- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/BookBuddy.log.1 b/BookBuddy.log.1 index 83c783b24f..2674e6549a 100644 --- a/BookBuddy.log.1 +++ b/BookBuddy.log.1 @@ -18,3 +18,11 @@ Apr 03, 2024 12:57:30 PM seedu.bookbuddy.parser.ParserMain parseCommand WARNING: Sorry but that is not a valid command. Please try again Apr 03, 2024 12:57:30 PM seedu.bookbuddy.parser.parservalidation.Exceptions handleException WARNING: Command is invalid: Sorry but that is not a valid command. Please try again or type: help +Apr 04, 2024 11:43:24 PM seedu.bookbuddy.parser.parservalidation.Exceptions validateCommandArguments +WARNING: The add Command requires a book title +Apr 04, 2024 11:43:24 PM seedu.bookbuddy.parser.parservalidation.Exceptions handleException +WARNING: Invalid command argument: The add Command requires a book title +Apr 04, 2024 11:43:24 PM seedu.bookbuddy.parser.ParserMain parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Apr 04, 2024 11:43:24 PM seedu.bookbuddy.parser.parservalidation.Exceptions handleException +WARNING: Command is invalid: Sorry but that is not a valid command. Please try again or type: help diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index 23c1de7f18..f0ec3dbf7f 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -63,7 +63,7 @@ public static void printAllBooks(BookList bookList) { public static void findBookTitle(BookList bookList, String title) { ArrayList bookTitles = new ArrayList<>(); for (BookMain book : bookList.getBooks()) { - if (Title.getTitle(book).contains(title)) { + if (Title.getTitle(book).toLowerCase().contains(title.toLowerCase())) { bookTitles.add(book); } } @@ -77,7 +77,8 @@ public static void findBookTitle(BookList bookList, String title) { public static void findBookGenre(BookList bookList, String genre) { ArrayList bookGenres = new ArrayList<>(); for (BookMain book : bookList.getBooks()) { - if (Genre.getGenre(book).contains(genre)) { + String actualGenre = Genre.getGenre(book).toLowerCase(); + if (actualGenre.contains(genre.toLowerCase())) { bookGenres.add(book); } } diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index 0b3ae03418..db6c62739c 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -63,7 +63,7 @@ public static void parseCommand(String input, BookList books) { ParserFind.parseTitle(books, inputArray[1]); break; case CommandList.FIND_GENRE_COMMAND: - ParserFind.parseFindGenre(books); + ParserFind.parseFindGenre(books, inputArray[1]); break; case CommandList.FIND_READ_COMMAND: ParserFind.parseFindRead(books); diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java index 97065d9ce1..ad590cf808 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java @@ -12,21 +12,9 @@ public static void parseTitle(BookList books, String inputArray) { BookDisplay.findBookTitle(books, inputArray); } - public static void parseFindGenre(BookList books) { - //BookDisplay.findBookGenre(books, inputArray); - System.out.println("Available genres:"); - for (int i = 0; i < BookList.getAvailableGenres().size(); i++) { - System.out.println((i + 1) + ". " + BookList.getAvailableGenres().get(i)); - } - System.out.println("Enter the number for the desired genre:"); - Scanner scanner = new Scanner(System.in); - String genre = String.valueOf(scanner); - String selectedGenre = null; - selectedGenre = invalidInputLooper(selectedGenre, scanner); - if (selectedGenre == null) { - return; - } - BookDisplay.findBookGenre(books, selectedGenre); + public static void parseFindGenre(BookList books, String inputArray) { + BookDisplay.findBookGenre(books, inputArray); + } public static void parseFindRead(BookList books) { BookDisplay.findRead(books); From 9a0e416e41d4b3ad5f8378c9dff28ea4741e20b7 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Thu, 4 Apr 2024 23:53:07 +0800 Subject: [PATCH 166/311] Update UG --- docs/UserGuide.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 550e9ba56a..e15f038386 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -8,7 +8,7 @@ the tracking and management objectives can be achieved more efficiently. #### Target user and value proposition BookBuddy targets people who are comfortable and proficient with using command-line-interfaces(CLI). These users typically prefers the efficiency and flexibility of CLI environments over Graphical user Interfaces (GUIs). They are -likely to be avid readers who read multiple books simultaneously and need an effective way to organise their reading +likely to be avid readers who read multiple books simultaneously and who need an effective way to organise their reading lists. Therefore, BookBuddy offers a streamlined and efficient solution for tracking and managing reading lists directly from @@ -94,7 +94,7 @@ remember to read it soon.... ```` ### Removing a book: `remove` -Removes a specific book from the book list. +Removes a specific book from the book list by its index. Format: `remove [BOOK_INDEX]` @@ -164,7 +164,7 @@ Successfully marked Harry Potter as unread. ### Setting the genre of a book: `set-genre` -Sets the genre of a specific book based on the provided input. +Sets the genre of a specific book based on the provided input and the provided index. Format: `set-genre [BOOK_INDEX]` followed by `[NUMBER]` then `[CUSTOM_GENRE]` if 6 is entered in the previous step @@ -216,7 +216,7 @@ Genre set to Fiction for book at index 1 ### Labelling a book: `label` -Sets the label of a specific book to the provided input. +Sets the label of a specific book to the provided input by its index. Format: `label [BOOK_INDEX] [LABEL]` @@ -411,7 +411,13 @@ Thank you for using BookBuddy! Hope to see you again keke :) **A**: All data entered is automatically saved by the program and does not require any commands from the user. Upon running the file for the first time, the `books.txt` file -will be created in the `data` folder. This folder will be in the same folder as the JAR file. +will be created in the `data` folder. This folder will be in the same folder as the JAR file (For example, if +JAR file is located in /Users/ZongYao/Desktop then the `books.txt` will be located in /Users/ZongYao/Desktop/data). + +**Q**: What happens if I give an invalid command or command argument? + +**A**: All invalid commands and invalid arguments are handled gracefully with exceptions. You will just +be prompted to re-enter the command with an instruction on how to properly type the command. **Users MUST exit the program with the `bye` command for the data in the session to be saved.** From 27e46cad6fae02b737ea7b4920894dbdfd0f0332 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Thu, 4 Apr 2024 23:53:34 +0800 Subject: [PATCH 167/311] fix checkstyle error --- .../seedu/bookbuddy/parser/parsercommands/ParserFind.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java index ad590cf808..34ad08aa6c 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java @@ -3,10 +3,6 @@ import seedu.bookbuddy.bookdetailsmodifier.BookDisplay; import seedu.bookbuddy.booklist.BookList; -import java.util.Scanner; - -import static seedu.bookbuddy.parser.parsercommands.ParserGenre.invalidInputLooper; - public class ParserFind { public static void parseTitle(BookList books, String inputArray) { BookDisplay.findBookTitle(books, inputArray); From 9c47da6bc8e8ff9041a5d9179b9034ade0747cd1 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Fri, 5 Apr 2024 00:22:15 +0800 Subject: [PATCH 168/311] Bug fix for set-genre feature --- .../java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java index 206bfc0f34..4ab15f4114 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java @@ -21,7 +21,7 @@ static void parseSetGenre(BookList books, String[] inputArray) { return; } - if (index < 0 || index >= books.getSize()) { + if (index < 0 || index > books.getSize()) { System.out.println("Invalid book index. Please enter a valid index. Type 'list' to view the " + "list of books."); return; From 63ffe2deb8607c05fcb293f418e24e7dcfe55afd Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Fri, 5 Apr 2024 00:26:20 +0800 Subject: [PATCH 169/311] Update Set-Genre junit test --- .../java/seedu/bookbuddy/ParserMainTest.java | 47 ++++++++++--------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/ParserMainTest.java b/src/test/java/seedu/bookbuddy/ParserMainTest.java index 12df5252e1..453609fe06 100644 --- a/src/test/java/seedu/bookbuddy/ParserMainTest.java +++ b/src/test/java/seedu/bookbuddy/ParserMainTest.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import seedu.bookbuddy.book.Genre; import seedu.bookbuddy.book.Label; import seedu.bookbuddy.book.Read; import seedu.bookbuddy.book.Title; @@ -11,12 +12,12 @@ import seedu.bookbuddy.booklist.BookListModifier; import seedu.bookbuddy.parser.ParserMain; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.InputStream; import java.io.PrintStream; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public class ParserMainTest { @@ -92,24 +93,28 @@ void parseLabelCommand() { assertEquals("Great Book", Label.getLabel(books.getBook(1))); } - // @Test - // void parseGenreCommand() { - // BookList books = new BookList(); - // BookListModifier.addBook(books, "The Great Gatsby"); - // // Simulate user input for genre selection "Classic" - // String simulatedUserInput = "6\nClassic\n"; // Assuming '3' is the option to add a new genre - // InputStream savedStandardInputStream = System.in; - // System.setIn(new ByteArrayInputStream(simulatedUserInput.getBytes())); - // ParserMain.parseCommand("set-genre 1", books); // Changed to fit your updated command-handling logic - // assertEquals("Classic", Genre.getGenre(books.getBook(1))); // Indexes are typically 0-based in lists - // - // BookListModifier.addBook(books, "Geronimo"); - // String nextSimulatedUserInput = "3\n"; - // System.setIn(new ByteArrayInputStream(nextSimulatedUserInput.getBytes())); - // ParserMain.parseCommand("set-genre 2", books); - // assertEquals("Mystery", Genre.getGenre(books.getBook(2))); - // System.setIn(savedStandardInputStream); - // } + @Test + void parseGenreCommand() { + BookList books = new BookList(); + BookListModifier.addBook(books, "The Great Gatsby"); + // Simulate user input for genre selection "Classic" + String simulatedUserInput = "6\nClassic\n"; // Assuming '3' is the option to add a new genre + InputStream savedStandardInputStream = System.in; + System.setIn(new ByteArrayInputStream(simulatedUserInput.getBytes())); + ParserMain.parseCommand("set-genre 1", books); // Changed to fit your updated command-handling logic + assertEquals("Classic", Genre.getGenre(books.getBook(1))); // Indexes are typically 0-based in lists + + BookListModifier.addBook(books, "Geronimo"); + String nextSimulatedUserInput = "3\n"; + System.setIn(new ByteArrayInputStream(nextSimulatedUserInput.getBytes())); + ParserMain.parseCommand("set-genre 2", books); + assertEquals("Mystery", Genre.getGenre(books.getBook(2))); + System.setIn(savedStandardInputStream); + + BookListModifier.addBook(books, "Tom And Jerry"); + ParserMain.parseCommand("set-genre 3 Fantasy", books); + assertEquals("Fantasy", Genre.getGenre(books.getBook(3))); + } @Test void parseInvalidAddCommandThrowsException() { From 428e620a5d8d217b8862616a27e6b8c52733d698 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Fri, 5 Apr 2024 00:27:06 +0800 Subject: [PATCH 170/311] Checkstyle fixes --- src/test/java/seedu/bookbuddy/ParserMainTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/seedu/bookbuddy/ParserMainTest.java b/src/test/java/seedu/bookbuddy/ParserMainTest.java index 453609fe06..9a2004c7b1 100644 --- a/src/test/java/seedu/bookbuddy/ParserMainTest.java +++ b/src/test/java/seedu/bookbuddy/ParserMainTest.java @@ -17,7 +17,9 @@ import java.io.InputStream; import java.io.PrintStream; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; public class ParserMainTest { From d73c73c2e07c4b41ff454df77bcdbc4ad745e46a Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Fri, 5 Apr 2024 00:29:24 +0800 Subject: [PATCH 171/311] Checkstyle fixes --- .../java/seedu/bookbuddy/ParserMainTest.java | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/test/java/seedu/bookbuddy/ParserMainTest.java b/src/test/java/seedu/bookbuddy/ParserMainTest.java index 9a2004c7b1..296f280c5c 100644 --- a/src/test/java/seedu/bookbuddy/ParserMainTest.java +++ b/src/test/java/seedu/bookbuddy/ParserMainTest.java @@ -95,28 +95,28 @@ void parseLabelCommand() { assertEquals("Great Book", Label.getLabel(books.getBook(1))); } - @Test - void parseGenreCommand() { - BookList books = new BookList(); - BookListModifier.addBook(books, "The Great Gatsby"); - // Simulate user input for genre selection "Classic" - String simulatedUserInput = "6\nClassic\n"; // Assuming '3' is the option to add a new genre - InputStream savedStandardInputStream = System.in; - System.setIn(new ByteArrayInputStream(simulatedUserInput.getBytes())); - ParserMain.parseCommand("set-genre 1", books); // Changed to fit your updated command-handling logic - assertEquals("Classic", Genre.getGenre(books.getBook(1))); // Indexes are typically 0-based in lists - - BookListModifier.addBook(books, "Geronimo"); - String nextSimulatedUserInput = "3\n"; - System.setIn(new ByteArrayInputStream(nextSimulatedUserInput.getBytes())); - ParserMain.parseCommand("set-genre 2", books); - assertEquals("Mystery", Genre.getGenre(books.getBook(2))); - System.setIn(savedStandardInputStream); - - BookListModifier.addBook(books, "Tom And Jerry"); - ParserMain.parseCommand("set-genre 3 Fantasy", books); - assertEquals("Fantasy", Genre.getGenre(books.getBook(3))); - } + @Test + void parseGenreCommand() { + BookList books = new BookList(); + BookListModifier.addBook(books, "The Great Gatsby"); + // Simulate user input for genre selection "Classic" + String simulatedUserInput = "6\nClassic\n"; // Assuming '3' is the option to add a new genre + InputStream savedStandardInputStream = System.in; + System.setIn(new ByteArrayInputStream(simulatedUserInput.getBytes())); + ParserMain.parseCommand("set-genre 1", books); // Changed to fit your updated command-handling logic + assertEquals("Classic", Genre.getGenre(books.getBook(1))); // Indexes are typically 0-based in lists + + BookListModifier.addBook(books, "Geronimo"); + String nextSimulatedUserInput = "3\n"; + System.setIn(new ByteArrayInputStream(nextSimulatedUserInput.getBytes())); + ParserMain.parseCommand("set-genre 2", books); + assertEquals("Mystery", Genre.getGenre(books.getBook(2))); + System.setIn(savedStandardInputStream); + + BookListModifier.addBook(books, "Tom And Jerry"); + ParserMain.parseCommand("set-genre 3 Fantasy", books); + assertEquals("Fantasy", Genre.getGenre(books.getBook(3))); + } @Test void parseInvalidAddCommandThrowsException() { From 0526880182220d282903d506084f089a8e174f20 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Fri, 5 Apr 2024 14:18:41 +0800 Subject: [PATCH 172/311] Fix bugs in genre and label commands --- .../seedu/bookbuddy/parser/parsercommands/ParserGenre.java | 2 +- .../seedu/bookbuddy/parser/parsercommands/ParserLabel.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java index 4ab15f4114..b693b9fd4d 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java @@ -21,7 +21,7 @@ static void parseSetGenre(BookList books, String[] inputArray) { return; } - if (index < 0 || index > books.getSize()) { + if (index <= 0 || index > books.getSize()) { System.out.println("Invalid book index. Please enter a valid index. Type 'list' to view the " + "list of books."); return; diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java index 9dee212030..0a97f1049e 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java @@ -16,9 +16,8 @@ static void parseSetLabel(BookList books, String[] inputArray) { Exceptions.validateCommandArguments(labelMessageParts, 2, "You " + "need to have a label message"); index = Integer.parseInt(labelMessageParts[0]); - assert index >= 0 : "Index should be non-negative"; + assert index > 0 : "Index should be non-negative"; String label = labelMessageParts[1]; - System.out.println(index); BookLabel.setBookLabelByIndex(index, label, books); } From 1b6a2229b8d8cbba6bbe54f2ef114c3b38c64d97 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Fri, 5 Apr 2024 14:54:52 +0800 Subject: [PATCH 173/311] update UG --- docs/UserGuide.md | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index e15f038386..ff1e84fb14 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -330,29 +330,15 @@ Example output: ### Finding a book by genre: `find-genre` Returns all books in the saved book list that are stored under the matching genre. -Format: `find-genre` then user will be prompted to enter a `[NUMBER]` , the index corresponding to the available genre. +Format: `find-genre [KEYWORD]` . Example of usage with expected output: ``` //input -find-genre +find-genre fiction ``` ```` -//output -Available genres: -1. Fiction -2. Non-Fiction -3. Mystery -4. Science Fiction -5. Fantasy -Enter the number for the desired genre: -```` -```` -//input -1 -```` -```` //ouput 1. [U] harry potter ```` @@ -438,7 +424,7 @@ to be saved.** * Sort books by rating: `list-rated` * Display details: `display [BOOK_INDEX]` * Find books with specific title: `find-title [KEYWORD]` -* Find books with specific genre: `find-genre` followed by `[NUMBER]` +* Find books with specific genre: `find-genre [KEYWORD]` * Find books that are read: `find-read` * Find books that are unread: `find-unread` * Exit program: `bye` From e5c0a5caa8bad8b77f18c5f2a96cd759a49839f6 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Fri, 5 Apr 2024 15:15:41 +0800 Subject: [PATCH 174/311] no message --- src/main/java/seedu/bookbuddy/Ui.java | 2 +- .../java/seedu/bookbuddy/parser/parsercommands/ParserFind.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 6cb25cb303..5b6f9213be 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -5,7 +5,7 @@ import seedu.bookbuddy.booklist.BookList; import java.util.ArrayList; - +//@@ liuzehui03 public class Ui { public static void printWelcome() { String logo = diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java index 34ad08aa6c..5bbfed57bb 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java @@ -2,7 +2,7 @@ import seedu.bookbuddy.bookdetailsmodifier.BookDisplay; import seedu.bookbuddy.booklist.BookList; - +//@@ liuzehui03 public class ParserFind { public static void parseTitle(BookList books, String inputArray) { BookDisplay.findBookTitle(books, inputArray); From b71fd500b2f7084f103ac82eb735f773b071ffbf Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Fri, 5 Apr 2024 16:12:50 +0800 Subject: [PATCH 175/311] Authorship edits --- src/main/java/seedu/bookbuddy/parser/ParserMain.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index db6c62739c..fd4bd9b7d1 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -22,6 +22,7 @@ import static seedu.bookbuddy.BookBuddy.LOGGER; +//@@author joshuahoky /** * Parses inputs from the user in order to execute the correct commands. */ From 97f98759658de8c8d3e4313e9832b3cd9a262f78 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Fri, 5 Apr 2024 16:17:01 +0800 Subject: [PATCH 176/311] find_label feature --- src/main/java/seedu/bookbuddy/Ui.java | 6 ++++++ .../bookdetailsmodifier/BookDisplay.java | 20 ++++++++++++++----- .../seedu/bookbuddy/parser/ParserMain.java | 4 ++++ .../parser/parsercommands/ParserFind.java | 4 ++++ .../parser/parservalidation/CommandList.java | 1 + 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 5b6f9213be..976e203f56 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -103,4 +103,10 @@ public static void printUnreadFound(ArrayList bookUnread){ System.out.println(i + 1 + ". " + bookUnread.get(i)); } } + + public static void printLabelFound(ArrayList bookLabel) { + for (int i = 0; i < bookLabel.size(); i++) { + System.out.println(i + 1 + ". " + bookLabel.get(i)); + } + } } diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index f0ec3dbf7f..1241e4bbae 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -67,11 +67,7 @@ public static void findBookTitle(BookList bookList, String title) { bookTitles.add(book); } } - if (bookTitles.isEmpty()){ - Ui.printNoBookFound(); - } else { - Ui.printBookFound(bookTitles); - } + } public static void findBookGenre(BookList bookList, String genre) { @@ -115,4 +111,18 @@ public static void findUnread(BookList bookList){ Ui.printUnreadFound(bookUnread); } } + + public static void findLabel(BookList bookList, String inputArray) { + ArrayList bookLabel = new ArrayList<>(); + for (BookMain book : bookList.getBooks()) { + if(!Label.getLabel(book).isEmpty()){ + bookLabel.add(book); + } + } + if (bookLabel.isEmpty()){ + Ui.printNoBookFound(); + } else { + Ui.printLabelFound(bookLabel); + } + } } diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index db6c62739c..d1c4135d1c 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -59,6 +59,7 @@ public static void parseCommand(String input, BookList books) { case CommandList.HELP_COMMAND: Ui.helpMessage(); break; + case CommandList.FIND_TITLE_COMMAND: ParserFind.parseTitle(books, inputArray[1]); break; @@ -71,6 +72,9 @@ public static void parseCommand(String input, BookList books) { case CommandList.FIND_UNREAD_COMMAND: ParserFind.parseFindUnread(books); break; + case CommandList.FIND_LABEL_COMMAND: + ParserFind.parseLabel(books, inputArray[1]); + break; case CommandList.LABEL_COMMAND: ParserLabel.executeParseSetLabel(books, inputArray); break; diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java index 5bbfed57bb..1ee8ea8b8f 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java @@ -18,4 +18,8 @@ public static void parseFindRead(BookList books) { public static void parseFindUnread(BookList books) { BookDisplay.findUnread(books); } + + public static void parseLabel(BookList books, String inputArray) { + BookDisplay.findLabel(books, inputArray); + } } diff --git a/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java b/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java index 30490adaee..55e12caacb 100644 --- a/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java +++ b/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java @@ -11,6 +11,7 @@ public class CommandList { public static final String FIND_GENRE_COMMAND = "find-genre"; public static final String FIND_READ_COMMAND = "find-read"; public static final String FIND_UNREAD_COMMAND = "find-unread"; + public static final String FIND_LABEL_COMMAND = "find-label"; public static final String LABEL_COMMAND = "label"; public static final String GENRE_COMMAND = "set-genre"; public static final String SUMMARY_COMMAND = "give-summary"; From 861e7129b4518239ab63141065bff7eeb74eed19 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Fri, 5 Apr 2024 16:22:36 +0800 Subject: [PATCH 177/311] fixed error --- .../seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index 1241e4bbae..3e56026470 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -67,7 +67,11 @@ public static void findBookTitle(BookList bookList, String title) { bookTitles.add(book); } } - + if (bookTitles.isEmpty()){ + Ui.printNoBookFound(); + } else { + Ui.printBookFound(bookTitles); + } } public static void findBookGenre(BookList bookList, String genre) { From f08aa61acf2ff4961f6a8805fe023947a63853fe Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Fri, 5 Apr 2024 16:35:36 +0800 Subject: [PATCH 178/311] update find-label feature --- .../bookdetailsmodifier/BookDisplay.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index 3e56026470..9ce3ccf985 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -60,10 +60,12 @@ public static void printAllBooks(BookList bookList) { } //@@author liuzehui03 - public static void findBookTitle(BookList bookList, String title) { + public static void findBookTitle(BookList bookList, String input) { ArrayList bookTitles = new ArrayList<>(); for (BookMain book : bookList.getBooks()) { - if (Title.getTitle(book).toLowerCase().contains(title.toLowerCase())) { + String actualTitle = Title.getTitle(book).toLowerCase(); + String title = input.toLowerCase(); + if (actualTitle.contains(title)) { bookTitles.add(book); } } @@ -74,11 +76,12 @@ public static void findBookTitle(BookList bookList, String title) { } } - public static void findBookGenre(BookList bookList, String genre) { + public static void findBookGenre(BookList bookList, String input) { ArrayList bookGenres = new ArrayList<>(); for (BookMain book : bookList.getBooks()) { String actualGenre = Genre.getGenre(book).toLowerCase(); - if (actualGenre.contains(genre.toLowerCase())) { + String genre = input.toLowerCase(); + if (actualGenre.contains(genre)) { bookGenres.add(book); } } @@ -116,10 +119,12 @@ public static void findUnread(BookList bookList){ } } - public static void findLabel(BookList bookList, String inputArray) { + public static void findLabel(BookList bookList, String input) { ArrayList bookLabel = new ArrayList<>(); for (BookMain book : bookList.getBooks()) { - if(!Label.getLabel(book).isEmpty()){ + String actualLabel = Label.getLabel(book).toLowerCase(); + String label = input.toLowerCase(); + if(actualLabel.contains(label)){ bookLabel.add(book); } } From 07002fc08b23355781611d8379c297cbe9da3b38 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Fri, 5 Apr 2024 16:43:41 +0800 Subject: [PATCH 179/311] created separate class for BookFind --- .../bookdetailsmodifier/BookDisplay.java | 75 ---------------- .../bookdetailsmodifier/BookFind.java | 86 +++++++++++++++++++ .../parser/parsercommands/ParserFind.java | 12 +-- 3 files changed, 92 insertions(+), 81 deletions(-) create mode 100644 src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index 9ce3ccf985..3944119ef9 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -59,79 +59,4 @@ public static void printAllBooks(BookList bookList) { } } - //@@author liuzehui03 - public static void findBookTitle(BookList bookList, String input) { - ArrayList bookTitles = new ArrayList<>(); - for (BookMain book : bookList.getBooks()) { - String actualTitle = Title.getTitle(book).toLowerCase(); - String title = input.toLowerCase(); - if (actualTitle.contains(title)) { - bookTitles.add(book); - } - } - if (bookTitles.isEmpty()){ - Ui.printNoBookFound(); - } else { - Ui.printBookFound(bookTitles); - } - } - - public static void findBookGenre(BookList bookList, String input) { - ArrayList bookGenres = new ArrayList<>(); - for (BookMain book : bookList.getBooks()) { - String actualGenre = Genre.getGenre(book).toLowerCase(); - String genre = input.toLowerCase(); - if (actualGenre.contains(genre)) { - bookGenres.add(book); - } - } - if (bookGenres.isEmpty()){ - Ui.printNoGenresFound(); - } else { - Ui.printGenresFound(bookGenres); - } - } - public static void findRead(BookList bookList){ - ArrayList bookRead = new ArrayList<>(); - for (BookMain book : bookList.getBooks()) { - if (Read.getRead(book)) { - bookRead.add(book); - } - } - if (bookRead.isEmpty()){ - Ui.printNoGenresFound(); - } else { - Ui.printReadFound(bookRead); - } - } - public static void findUnread(BookList bookList){ - ArrayList bookUnread = new ArrayList<>(); - for (BookMain book : bookList.getBooks()) { - if (!Read.getRead(book)) { - bookUnread.add(book); - } - } - if (bookUnread.isEmpty()){ - Ui.printNoGenresFound(); - } else { - - Ui.printUnreadFound(bookUnread); - } - } - - public static void findLabel(BookList bookList, String input) { - ArrayList bookLabel = new ArrayList<>(); - for (BookMain book : bookList.getBooks()) { - String actualLabel = Label.getLabel(book).toLowerCase(); - String label = input.toLowerCase(); - if(actualLabel.contains(label)){ - bookLabel.add(book); - } - } - if (bookLabel.isEmpty()){ - Ui.printNoBookFound(); - } else { - Ui.printLabelFound(bookLabel); - } - } } diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java new file mode 100644 index 0000000000..07c7e06ab1 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java @@ -0,0 +1,86 @@ +package seedu.bookbuddy.bookdetailsmodifier; + +import seedu.bookbuddy.Ui; +import seedu.bookbuddy.book.*; +import seedu.bookbuddy.booklist.BookList; + +import java.util.ArrayList; + +//@@author liuzehui03 + +public class BookFind { + public static void findBookTitle(BookList bookList, String input) { + ArrayList bookTitles = new ArrayList<>(); + for (BookMain book : bookList.getBooks()) { + String actualTitle = Title.getTitle(book).toLowerCase(); + String title = input.toLowerCase(); + if (actualTitle.contains(title)) { + bookTitles.add(book); + } + } + if (bookTitles.isEmpty()){ + Ui.printNoBookFound(); + } else { + Ui.printBookFound(bookTitles); + } + } + + public static void findBookGenre(BookList bookList, String input) { + ArrayList bookGenres = new ArrayList<>(); + for (BookMain book : bookList.getBooks()) { + String actualGenre = Genre.getGenre(book).toLowerCase(); + String genre = input.toLowerCase(); + if (actualGenre.contains(genre)) { + bookGenres.add(book); + } + } + if (bookGenres.isEmpty()){ + Ui.printNoGenresFound(); + } else { + Ui.printGenresFound(bookGenres); + } + } + public static void findRead(BookList bookList){ + ArrayList bookRead = new ArrayList<>(); + for (BookMain book : bookList.getBooks()) { + if (Read.getRead(book)) { + bookRead.add(book); + } + } + if (bookRead.isEmpty()){ + Ui.printNoGenresFound(); + } else { + Ui.printReadFound(bookRead); + } + } + public static void findUnread(BookList bookList){ + ArrayList bookUnread = new ArrayList<>(); + for (BookMain book : bookList.getBooks()) { + if (!Read.getRead(book)) { + bookUnread.add(book); + } + } + if (bookUnread.isEmpty()){ + Ui.printNoGenresFound(); + } else { + + Ui.printUnreadFound(bookUnread); + } + } + + public static void findLabel(BookList bookList, String input) { + ArrayList bookLabel = new ArrayList<>(); + for (BookMain book : bookList.getBooks()) { + String actualLabel = Label.getLabel(book).toLowerCase(); + String label = input.toLowerCase(); + if(actualLabel.contains(label)){ + bookLabel.add(book); + } + } + if (bookLabel.isEmpty()){ + Ui.printNoBookFound(); + } else { + Ui.printLabelFound(bookLabel); + } + } +} diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java index 1ee8ea8b8f..2ee3a9b662 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java @@ -1,25 +1,25 @@ package seedu.bookbuddy.parser.parsercommands; -import seedu.bookbuddy.bookdetailsmodifier.BookDisplay; +import seedu.bookbuddy.bookdetailsmodifier.BookFind; import seedu.bookbuddy.booklist.BookList; //@@ liuzehui03 public class ParserFind { public static void parseTitle(BookList books, String inputArray) { - BookDisplay.findBookTitle(books, inputArray); + BookFind.findBookTitle(books, inputArray); } public static void parseFindGenre(BookList books, String inputArray) { - BookDisplay.findBookGenre(books, inputArray); + BookFind.findBookGenre(books, inputArray); } public static void parseFindRead(BookList books) { - BookDisplay.findRead(books); + BookFind.findRead(books); } public static void parseFindUnread(BookList books) { - BookDisplay.findUnread(books); + BookFind.findUnread(books); } public static void parseLabel(BookList books, String inputArray) { - BookDisplay.findLabel(books, inputArray); + BookFind.findLabel(books, inputArray); } } From 3ef273784108c2e34d1eeaf8fd1d485ed78d8644 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Fri, 5 Apr 2024 17:46:23 +0800 Subject: [PATCH 180/311] fix checkstyle error --- .../java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index 3944119ef9..6a7bb3c806 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -1,6 +1,6 @@ package seedu.bookbuddy.bookdetailsmodifier; -import seedu.bookbuddy.Ui; + import seedu.bookbuddy.book.BookMain; import seedu.bookbuddy.book.Genre; import seedu.bookbuddy.book.Label; @@ -10,7 +10,6 @@ import seedu.bookbuddy.book.Title; import seedu.bookbuddy.booklist.BookList; -import java.util.ArrayList; public class BookDisplay { From b238ed121e124f9db65ec4c56ff370b4f41adaee Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Fri, 5 Apr 2024 17:50:34 +0800 Subject: [PATCH 181/311] fix checkstyle --- .../java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java index 07c7e06ab1..a98be583f7 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java @@ -1,7 +1,11 @@ package seedu.bookbuddy.bookdetailsmodifier; import seedu.bookbuddy.Ui; -import seedu.bookbuddy.book.*; +import seedu.bookbuddy.book.Label; +import seedu.bookbuddy.book.BookMain; +import seedu.bookbuddy.book.Genre; +import seedu.bookbuddy.book.Title; +import seedu.bookbuddy.book.Read; import seedu.bookbuddy.booklist.BookList; import java.util.ArrayList; From 68c93446ac698c70cc9758d59c9390f9f546c581 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Fri, 5 Apr 2024 23:47:04 +0800 Subject: [PATCH 182/311] Add exception handling for FileStorage to deal with corrupted text file --- .../java/seedu/bookbuddy/FileStorage.java | 4 ++- .../java/seedu/bookbuddy/book/BookMain.java | 27 +++++++++++-------- .../bookdetailsmodifier/BookDisplay.java | 2 +- .../bookbuddy/booklist/BookListModifier.java | 23 +++++++++------- 4 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/FileStorage.java b/src/main/java/seedu/bookbuddy/FileStorage.java index d512a36b0b..2f902363d7 100644 --- a/src/main/java/seedu/bookbuddy/FileStorage.java +++ b/src/main/java/seedu/bookbuddy/FileStorage.java @@ -44,10 +44,12 @@ public FileStorage(BookList books) { */ public void readData(BookList books, File file) throws FileNotFoundException { Scanner sc = new Scanner(file); + int lineNumber = 1; while (sc.hasNext()) { String line = sc.nextLine(); - BookListModifier.addBookFromFile(books, line); + BookListModifier.addBookFromFile(books, line, lineNumber); + lineNumber += 1; } sc.close(); diff --git a/src/main/java/seedu/bookbuddy/book/BookMain.java b/src/main/java/seedu/bookbuddy/book/BookMain.java index 9527ca8bff..34bd43573f 100644 --- a/src/main/java/seedu/bookbuddy/book/BookMain.java +++ b/src/main/java/seedu/bookbuddy/book/BookMain.java @@ -17,10 +17,10 @@ public class BookMain { */ public BookMain(String title) { this.title = title; // Description of the book - this.isRead = false; //Completion status of the book (True: Read, False: Unread) + this.isRead = false; // Completion status of the book (True: Read, False: Unread) this.label = ""; this.genre = ""; - this.rating = -1; + this.rating = 0; // Initialized to 0 this.summary = ""; } @@ -35,13 +35,18 @@ public BookMain(String title) { * @param rating The rating assigned to the book. * @param summary The summary of the book. */ - public BookMain(String title, int status, String label, String genre, int rating, String summary) { + public BookMain(String title, int status, String label, String genre, int rating, String summary, int lineNumber) { + if (rating < 0 || rating > 5 || status < 0 || status > 1) { + throw new IllegalArgumentException("Unable to load book data from line " + lineNumber + + " in books.txt as data is corrupted."); + } + this.title = title; this.isRead = status == 1; - this.label = (Objects.equals(label, "*")) ? "" : label; - this.genre = (Objects.equals(genre, "*")) ? "" : genre; + this.label = label; + this.genre = genre; + this.summary = summary; this.rating = rating; - this.summary = (Objects.equals(summary, "*")) ? "" : summary; } @Override @@ -57,10 +62,10 @@ public String toString() { */ public String saveFormat() { String status = isRead ? "1" : "0"; - String label = (this.label.isEmpty()) ? "*" : this.label; - String genre = (this.genre.isEmpty()) ? "*" : this.genre; - String summary = (this.summary.isEmpty()) ? "*" : this.summary; - return this.title + " | " + status + " | " + label + " | " + genre + " | " + this.rating - + " | " + summary; + String label = (this.label.isEmpty()) ? "" : this.label; + String genre = (this.genre.isEmpty()) ? "" : this.genre; + String summary = (this.summary.isEmpty()) ? "" : this.summary; + return this.title + " | " + status + " | " + label + " | " + genre + " | " + summary + + " | " + this.rating; } } diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index 6a7bb3c806..e98e1bcedd 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -34,7 +34,7 @@ public static void displayDetails(int index, BookList books) throws IndexOutOfBo System.out.println("Status: " + (Read.getRead(books.getBook(index)) ? "Read" : "Unread")); System.out.println("Label: " + (label.isEmpty() ? "No label provided" : label)); System.out.println("Genre: " + (genre.isEmpty() ? "No genre provided" : genre)); - System.out.println("Rating: " + ((rating == -1) ? "No rating provided" : rating)); + System.out.println("Rating: " + ((rating == 0) ? "No rating provided" : rating)); System.out.println("Summary: " + (summary.isEmpty() ? "No summary provided" : summary)); } diff --git a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java index ea0f89f353..bc5cfb2201 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java @@ -10,15 +10,20 @@ public class BookListModifier { //@@author joshuahoky - public static void addBookFromFile(BookList bookList, String inputArray) { - String[] bookDetails = inputArray.split(" \\| "); - String title = bookDetails[0]; - int status = Integer.parseInt(bookDetails[1]); - String label = bookDetails[2]; - String genre = bookDetails[3]; - int rating = Integer.parseInt(bookDetails[4]); - String summary = bookDetails[5]; - bookList.books.add(new BookMain(title, status, label, genre, rating, summary)); + public static void addBookFromFile(BookList bookList, String inputArray, int lineNumber) { + try { + String[] bookDetails = inputArray.split(" \\| "); + String title = bookDetails[0]; + int status = Integer.parseInt(bookDetails[1]); + String label = bookDetails[2]; + String genre = bookDetails[3]; + int rating = Integer.parseInt(bookDetails[5].trim()); + String summary = bookDetails[4]; + bookList.books.add(new BookMain(title, status, label, genre, rating, summary, lineNumber)); + } catch (Exception e) { + System.out.println("Unable to load book data from line " + lineNumber + " in books.txt " + + "as data is corrupted."); + } } //@@author From df7ae1562ad5d96ef03f40d1bc3befda89d78573 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Fri, 5 Apr 2024 23:50:41 +0800 Subject: [PATCH 183/311] Fix checkstyle issue --- src/main/java/seedu/bookbuddy/book/BookMain.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/book/BookMain.java b/src/main/java/seedu/bookbuddy/book/BookMain.java index 34bd43573f..2079c863b4 100644 --- a/src/main/java/seedu/bookbuddy/book/BookMain.java +++ b/src/main/java/seedu/bookbuddy/book/BookMain.java @@ -1,7 +1,5 @@ package seedu.bookbuddy.book; -import java.util.Objects; - public class BookMain { protected String title; protected boolean isRead; From e82d27056d79a3f25301afacc947c1aac884332e Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Sat, 6 Apr 2024 00:04:34 +0800 Subject: [PATCH 184/311] Add more exception handling for corrupted text file --- .../seedu/bookbuddy/booklist/BookListModifier.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java index bc5cfb2201..7b6eacc15b 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java @@ -13,12 +13,12 @@ public class BookListModifier { public static void addBookFromFile(BookList bookList, String inputArray, int lineNumber) { try { String[] bookDetails = inputArray.split(" \\| "); - String title = bookDetails[0]; - int status = Integer.parseInt(bookDetails[1]); - String label = bookDetails[2]; - String genre = bookDetails[3]; + String title = bookDetails[0].trim(); + int status = Integer.parseInt(bookDetails[1].trim()); + String label = bookDetails[2].trim(); + String genre = bookDetails[3].trim(); int rating = Integer.parseInt(bookDetails[5].trim()); - String summary = bookDetails[4]; + String summary = bookDetails[4].trim(); bookList.books.add(new BookMain(title, status, label, genre, rating, summary, lineNumber)); } catch (Exception e) { System.out.println("Unable to load book data from line " + lineNumber + " in books.txt " + From 90c9aaefcef3f87c6b53f9bfc5c1638409642ecc Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Mon, 8 Apr 2024 18:31:43 +0800 Subject: [PATCH 185/311] Add datetime read feature --- .../java/seedu/bookbuddy/FileStorage.java | 7 +++++++ .../java/seedu/bookbuddy/book/BookMain.java | 9 +++++++-- src/main/java/seedu/bookbuddy/book/Read.java | 7 +++++++ .../bookdetailsmodifier/BookMark.java | 1 + .../bookbuddy/booklist/BookListModifier.java | 5 ++++- src/main/java/utils/DateTimeUtils.java | 19 +++++++++++++++++++ 6 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 src/main/java/utils/DateTimeUtils.java diff --git a/src/main/java/seedu/bookbuddy/FileStorage.java b/src/main/java/seedu/bookbuddy/FileStorage.java index d512a36b0b..c1dea21ec8 100644 --- a/src/main/java/seedu/bookbuddy/FileStorage.java +++ b/src/main/java/seedu/bookbuddy/FileStorage.java @@ -8,12 +8,18 @@ import java.io.IOException; import java.io.FileNotFoundException; import java.io.FileWriter; +import java.util.logging.Level; + + +import java.util.logging.Logger; +import static java.util.logging.Logger.getLogger; /** * The FileStorage class handles file operations such as creating directories, * reading and writing to files and also loading data from files. */ public class FileStorage { + public static final Logger LOGGER = getLogger(BookBuddy.class.getName()); private static final String FILE_NAME = "books.txt"; private static final String FILE_DIRECTORY = "./data"; private static final String FILE_PATH = FILE_DIRECTORY + '/' + FILE_NAME; @@ -44,6 +50,7 @@ public FileStorage(BookList books) { */ public void readData(BookList books, File file) throws FileNotFoundException { Scanner sc = new Scanner(file); + LOGGER.log(Level.INFO, "starting read"); while (sc.hasNext()) { String line = sc.nextLine(); diff --git a/src/main/java/seedu/bookbuddy/book/BookMain.java b/src/main/java/seedu/bookbuddy/book/BookMain.java index 9527ca8bff..03ceb2757a 100644 --- a/src/main/java/seedu/bookbuddy/book/BookMain.java +++ b/src/main/java/seedu/bookbuddy/book/BookMain.java @@ -5,6 +5,7 @@ public class BookMain { protected String title; protected boolean isRead; + protected String datetimeread; protected String label; protected String genre; protected int rating; @@ -18,6 +19,7 @@ public class BookMain { public BookMain(String title) { this.title = title; // Description of the book this.isRead = false; //Completion status of the book (True: Read, False: Unread) + this.datetimeread = ""; this.label = ""; this.genre = ""; this.rating = -1; @@ -35,9 +37,11 @@ public BookMain(String title) { * @param rating The rating assigned to the book. * @param summary The summary of the book. */ - public BookMain(String title, int status, String label, String genre, int rating, String summary) { + public BookMain(String title, int status,String label, String genre, int rating, String summary, + String datetimeread) { this.title = title; this.isRead = status == 1; + this.datetimeread = (Objects.equals(datetimeread, "*")) ? "" : datetimeread; this.label = (Objects.equals(label, "*")) ? "" : label; this.genre = (Objects.equals(genre, "*")) ? "" : genre; this.rating = rating; @@ -57,10 +61,11 @@ public String toString() { */ public String saveFormat() { String status = isRead ? "1" : "0"; + String datetimeread = (this.datetimeread.isEmpty()) ? "*" : this.datetimeread; String label = (this.label.isEmpty()) ? "*" : this.label; String genre = (this.genre.isEmpty()) ? "*" : this.genre; String summary = (this.summary.isEmpty()) ? "*" : this.summary; return this.title + " | " + status + " | " + label + " | " + genre + " | " + this.rating - + " | " + summary; + + " | " + summary + " | " + datetimeread; } } diff --git a/src/main/java/seedu/bookbuddy/book/Read.java b/src/main/java/seedu/bookbuddy/book/Read.java index eee7ffd0f7..b95453852b 100644 --- a/src/main/java/seedu/bookbuddy/book/Read.java +++ b/src/main/java/seedu/bookbuddy/book/Read.java @@ -1,4 +1,5 @@ package seedu.bookbuddy.book; +import utils.DateTimeUtils; public class Read { //@@author lordgareth10 @@ -14,5 +15,11 @@ public static boolean getRead(BookMain book) { public static void setRead(BookMain book, boolean read) { book.isRead = read; + if (read) { + book.datetimeread = DateTimeUtils.getCurrentDateTime(); + } else { + book.datetimeread = ""; + } + } } diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java index 7d5bd25a52..fbecf05874 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java @@ -84,6 +84,7 @@ public static void markBookAsRead(BookMain book) { */ public static void markBookAsUnread(BookMain book) { Read.setRead(book, false); + System.out.println("Successfully marked " + Title.getTitle(book) + " as unread."); } } diff --git a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java index ea0f89f353..5e7a1fb43b 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java @@ -3,6 +3,7 @@ import seedu.bookbuddy.book.BookMain; import seedu.bookbuddy.Ui; +import java.util.Arrays; import java.util.logging.Level; import static seedu.bookbuddy.BookBuddy.LOGGER; @@ -12,13 +13,15 @@ public class BookListModifier { //@@author joshuahoky public static void addBookFromFile(BookList bookList, String inputArray) { String[] bookDetails = inputArray.split(" \\| "); + LOGGER.log(Level.INFO, "bookDetails: {0}", Arrays.toString(bookDetails)); String title = bookDetails[0]; int status = Integer.parseInt(bookDetails[1]); String label = bookDetails[2]; String genre = bookDetails[3]; int rating = Integer.parseInt(bookDetails[4]); String summary = bookDetails[5]; - bookList.books.add(new BookMain(title, status, label, genre, rating, summary)); + String date_time = bookDetails[6]; + bookList.books.add(new BookMain(title, status, label, genre, rating, summary, date_time)); } //@@author diff --git a/src/main/java/utils/DateTimeUtils.java b/src/main/java/utils/DateTimeUtils.java new file mode 100644 index 0000000000..d897662a54 --- /dev/null +++ b/src/main/java/utils/DateTimeUtils.java @@ -0,0 +1,19 @@ +package utils; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class DateTimeUtils { + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH-mm, dd-MM-yyyy"); + + // Private constructor to prevent instantiation + private DateTimeUtils() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + public static String getCurrentDateTime() { + LocalDateTime now = LocalDateTime.now(); + return now.format(FORMATTER); + } + +} + From e93069d511fd0d3a5cabbdb25d549cfc6f07a458 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Mon, 8 Apr 2024 19:03:58 +0800 Subject: [PATCH 186/311] Fix checkstyle --- src/main/java/seedu/bookbuddy/FileStorage.java | 1 - src/main/java/seedu/bookbuddy/book/BookMain.java | 3 ++- src/main/java/seedu/bookbuddy/book/Read.java | 4 +++- .../seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java | 3 ++- src/main/java/seedu/bookbuddy/booklist/BookListModifier.java | 5 ++--- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/FileStorage.java b/src/main/java/seedu/bookbuddy/FileStorage.java index 36e71e7ad1..6f6c2a860b 100644 --- a/src/main/java/seedu/bookbuddy/FileStorage.java +++ b/src/main/java/seedu/bookbuddy/FileStorage.java @@ -8,7 +8,6 @@ import java.io.IOException; import java.io.FileNotFoundException; import java.io.FileWriter; -import java.util.logging.Level; import java.util.logging.Logger; diff --git a/src/main/java/seedu/bookbuddy/book/BookMain.java b/src/main/java/seedu/bookbuddy/book/BookMain.java index 911c88eee3..226e9fe896 100644 --- a/src/main/java/seedu/bookbuddy/book/BookMain.java +++ b/src/main/java/seedu/bookbuddy/book/BookMain.java @@ -35,7 +35,8 @@ public BookMain(String title) { * @param rating The rating assigned to the book. * @param summary The summary of the book. */ - public BookMain(String title, int status, String label, String genre, int rating, String summary, String datetimeread, int lineNumber) { + public BookMain(String title, int status, String label, String genre, int rating, String summary, + String datetimeread, int lineNumber) { if (rating < 0 || rating > 5 || status < 0 || status > 1) { throw new IllegalArgumentException("Unable to load book data from line " + lineNumber + " in books.txt as data is corrupted."); diff --git a/src/main/java/seedu/bookbuddy/book/Read.java b/src/main/java/seedu/bookbuddy/book/Read.java index 13fa0ffd1d..4fa49f935d 100644 --- a/src/main/java/seedu/bookbuddy/book/Read.java +++ b/src/main/java/seedu/bookbuddy/book/Read.java @@ -13,7 +13,9 @@ public static boolean getRead(BookMain book) { return book.isRead; } - public static String getDateTimeRead(BookMain book){ return book.datetimeread; } + public static String getDateTimeRead(BookMain book) { + return book.datetimeread; + } public static void setRead(BookMain book, boolean read) { book.isRead = read; diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index 5c5d6ca038..e77777d6d7 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -31,7 +31,8 @@ public static void displayDetails(int index, BookList books) throws IndexOutOfBo String summary = Summary.getSummary(books.getBook(index)); System.out.println("Here are the details of your book:"); System.out.println("Title: " + Title.getTitle(books.getBook(index))); - System.out.println("Status: " + (Read.getRead(books.getBook(index)) ? "Read on " + Read.getDateTimeRead(books.getBook(index)) : "Unread")); + System.out.println("Status: " + (Read.getRead(books.getBook(index)) ? "Read on " + + Read.getDateTimeRead(books.getBook(index)) : "Unread")); System.out.println("Label: " + (label.isEmpty() ? "No label provided" : label)); System.out.println("Genre: " + (genre.isEmpty() ? "No genre provided" : genre)); System.out.println("Rating: " + ((rating == 0) ? "No rating provided" : rating)); diff --git a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java index d60d6bbfff..dee6c6e8c1 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java @@ -3,7 +3,6 @@ import seedu.bookbuddy.book.BookMain; import seedu.bookbuddy.Ui; -import java.util.Arrays; import java.util.logging.Level; import static seedu.bookbuddy.BookBuddy.LOGGER; @@ -20,8 +19,8 @@ public static void addBookFromFile(BookList bookList, String inputArray, int lin String genre = bookDetails[3].trim(); int rating = Integer.parseInt(bookDetails[5].trim()); String summary = bookDetails[4].trim(); - String date_time = bookDetails[6].trim(); - bookList.books.add(new BookMain(title, status, label, genre, rating, summary, date_time, lineNumber)); + String datetime = bookDetails[6].trim(); + bookList.books.add(new BookMain(title, status, label, genre, rating, summary, datetime, lineNumber)); } catch (Exception e) { System.out.println("Unable to load book data from line " + lineNumber + " in books.txt " + "as data is corrupted."); From 780b896e592d9d0883b417ef59832c321bd5ff75 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Mon, 8 Apr 2024 20:51:18 +0800 Subject: [PATCH 187/311] updat BookFind --- src/main/java/seedu/bookbuddy/Ui.java | 1 + .../bookdetailsmodifier/BookFind.java | 16 ++++++++++++++++ .../seedu/bookbuddy/parser/ParserMain.java | 6 +++++- .../parser/parsercommands/ParserFind.java | 19 +++++++++++++++++++ .../parser/parsercommands/ParserGenre.java | 2 +- 5 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 976e203f56..51394d2fe8 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -74,6 +74,7 @@ public static void helpMessage() { System.out.println("find-genre -> to find books under specific genres"); System.out.println("find-read -> to find list of books that are read"); System.out.println("find-unread -> to find list of books that are unread"); + System.out.println("find-label [KEYWORD] -> to find list of books that stored under a certain label"); System.out.println("bye -> to exit BookBuddy software"); } diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java index a98be583f7..8f82d2b100 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java @@ -7,8 +7,10 @@ import seedu.bookbuddy.book.Title; import seedu.bookbuddy.book.Read; import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.parser.parsercommands.ParserGenre; import java.util.ArrayList; +import java.util.Scanner; //@@author liuzehui03 @@ -44,6 +46,20 @@ public static void findBookGenre(BookList bookList, String input) { Ui.printGenresFound(bookGenres); } } + public static void findBookGenreLong(BookList bookList, String genre) { + + ArrayList bookGenres = new ArrayList<>(); + for (BookMain book : bookList.getBooks()) { + if (Genre.getGenre(book).contains(genre)) { + bookGenres.add(book); + } + } + if (bookGenres.isEmpty()){ + Ui.printNoGenresFound(); + } else { + Ui.printGenresFound(bookGenres); + } + } public static void findRead(BookList bookList){ ArrayList bookRead = new ArrayList<>(); for (BookMain book : bookList.getBooks()) { diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index 08beb39a2a..f448740d0b 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -65,7 +65,11 @@ public static void parseCommand(String input, BookList books) { ParserFind.parseTitle(books, inputArray[1]); break; case CommandList.FIND_GENRE_COMMAND: - ParserFind.parseFindGenre(books, inputArray[1]); + if (inputArray.length == 1) { // Check if there is only 'find-genre' without additional parameters + ParserFind.parseGenreLong(books); + } else { + ParserFind.parseFindGenre(books, inputArray[1]); + } break; case CommandList.FIND_READ_COMMAND: ParserFind.parseFindRead(books); diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java index 2ee3a9b662..0df61872c8 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java @@ -1,7 +1,13 @@ package seedu.bookbuddy.parser.parsercommands; +import seedu.bookbuddy.book.BookMain; +import seedu.bookbuddy.bookdetailsmodifier.BookDisplay; import seedu.bookbuddy.bookdetailsmodifier.BookFind; import seedu.bookbuddy.booklist.BookList; + +import java.util.ArrayList; +import java.util.Scanner; + //@@ liuzehui03 public class ParserFind { public static void parseTitle(BookList books, String inputArray) { @@ -12,6 +18,19 @@ public static void parseFindGenre(BookList books, String inputArray) { BookFind.findBookGenre(books, inputArray); } + public static void parseGenreLong(BookList books) { + System.out.println("Available genres:"); + for (int i = 0; i < BookList.getAvailableGenres().size(); i++) { + System.out.println((i + 1) + ". " + BookList.getAvailableGenres().get(i)); + } + System.out.println("Enter the number for the desired genre:"); + Scanner scanner = new Scanner(System.in); + String selectedGenre = ParserGenre.invalidInputLooper(null, scanner); + if (selectedGenre == null) { + return; + } + BookFind.findBookGenreLong(books, selectedGenre); + } public static void parseFindRead(BookList books) { BookFind.findRead(books); } diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java index b693b9fd4d..8e4ff3c042 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java @@ -76,7 +76,7 @@ static void genreSelectionPrinter() { System.out.println((BookList.getAvailableGenres().size() + 1) + ". Add a new genre"); } - static String invalidInputLooper(String input, Scanner scanner) { + public static String invalidInputLooper(String input, Scanner scanner) { while (input == null) { while (!scanner.hasNextInt()) { // Ensure the next input is an integer String newInput = scanner.nextLine(); From ba647d87bfdac8ef7918812ce3d87eb71479f2e6 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Mon, 8 Apr 2024 20:53:09 +0800 Subject: [PATCH 188/311] no message --- src/main/java/seedu/bookbuddy/Ui.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 51394d2fe8..51420f51e1 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -62,11 +62,7 @@ public static void helpMessage() { System.out.println("add [BOOK_TITLE] -> to add new books to the list"); System.out.println("remove [BOOK_INDEX] -> to remove a book from the list"); System.out.println("list -> to show whole list of added books"); - System.out.println("mark [BOOK_INDEX] -> to mark book as read [R]"); - System.out.println("unmark [BOOK_INDEX] -> to mark book as unread [U]"); - System.out.println("set-genre [BOOK_INDEX] -> to set a genre for a book"); - System.out.println("label [BOOK_INDEX] [LABEL] -> to set a label for a book"); - System.out.println("give-summary [BOOK_INDEX] [BOOK_SUMMARY] -> to give a book a summary"); + System.out.println("rate [BOOK_INDEX] [BOOK_RATING] -> to rate a book from 1-5"); System.out.println("list-rated -> to sort books by rating in descending order"); System.out.println("display [BOOK_INDEX] -> to view more details about a book"); From 2c78698693082fd723cca7e9959a68afbd19a879 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Mon, 8 Apr 2024 20:53:32 +0800 Subject: [PATCH 189/311] no message --- src/main/java/seedu/bookbuddy/Ui.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 51420f51e1..51394d2fe8 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -62,7 +62,11 @@ public static void helpMessage() { System.out.println("add [BOOK_TITLE] -> to add new books to the list"); System.out.println("remove [BOOK_INDEX] -> to remove a book from the list"); System.out.println("list -> to show whole list of added books"); - + System.out.println("mark [BOOK_INDEX] -> to mark book as read [R]"); + System.out.println("unmark [BOOK_INDEX] -> to mark book as unread [U]"); + System.out.println("set-genre [BOOK_INDEX] -> to set a genre for a book"); + System.out.println("label [BOOK_INDEX] [LABEL] -> to set a label for a book"); + System.out.println("give-summary [BOOK_INDEX] [BOOK_SUMMARY] -> to give a book a summary"); System.out.println("rate [BOOK_INDEX] [BOOK_RATING] -> to rate a book from 1-5"); System.out.println("list-rated -> to sort books by rating in descending order"); System.out.println("display [BOOK_INDEX] -> to view more details about a book"); From 55e12682b025e56ae453231120780bc89f8cbdf9 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Mon, 8 Apr 2024 20:58:38 +0800 Subject: [PATCH 190/311] checkstyle fixes --- .../java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java | 3 +-- .../java/seedu/bookbuddy/parser/parsercommands/ParserFind.java | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java index 8f82d2b100..57f5da8a86 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java @@ -7,10 +7,9 @@ import seedu.bookbuddy.book.Title; import seedu.bookbuddy.book.Read; import seedu.bookbuddy.booklist.BookList; -import seedu.bookbuddy.parser.parsercommands.ParserGenre; import java.util.ArrayList; -import java.util.Scanner; + //@@author liuzehui03 diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java index 0df61872c8..3f0ef36128 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java @@ -5,7 +5,6 @@ import seedu.bookbuddy.bookdetailsmodifier.BookFind; import seedu.bookbuddy.booklist.BookList; -import java.util.ArrayList; import java.util.Scanner; //@@ liuzehui03 From 9b2ab9c2a15f6c778b699b717caad559f8ea4785 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Mon, 8 Apr 2024 21:01:15 +0800 Subject: [PATCH 191/311] checkstyle fixes --- .../java/seedu/bookbuddy/parser/parsercommands/ParserFind.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java index 3f0ef36128..a4a26b5d41 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java @@ -1,7 +1,5 @@ package seedu.bookbuddy.parser.parsercommands; -import seedu.bookbuddy.book.BookMain; -import seedu.bookbuddy.bookdetailsmodifier.BookDisplay; import seedu.bookbuddy.bookdetailsmodifier.BookFind; import seedu.bookbuddy.booklist.BookList; From add22275788fe13bd90ab3515a7fbfe541e1c256 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Mon, 8 Apr 2024 21:15:43 +0800 Subject: [PATCH 192/311] update Ui --- src/main/java/seedu/bookbuddy/Ui.java | 14 ++++++++++++-- .../bookbuddy/bookdetailsmodifier/BookFind.java | 6 +++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 51394d2fe8..9f42cd5776 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -92,22 +92,32 @@ public static void printGenresFound(ArrayList bookGenres){ } } public static void printNoGenresFound(){ - System.out.println("no such books added..."); + System.out.println("no such books added under this genre"); } public static void printReadFound(ArrayList bookRead){ for (int i = 0; i < bookRead.size(); i++) { System.out.println(i + 1 + ". " + bookRead.get(i)); } } + public static void printNoReadFound(){ + System.out.println("no read books found..."); + System.out.println("do rmb to start reading soon!"); + } public static void printUnreadFound(ArrayList bookUnread){ for (int i = 0; i < bookUnread.size(); i++) { System.out.println(i + 1 + ". " + bookUnread.get(i)); } } - + public static void printNoUnreadFound(){ + System.out.println("no unread books found!"); + System.out.println("you've read everything on the list!! amazing :)"); + } public static void printLabelFound(ArrayList bookLabel) { for (int i = 0; i < bookLabel.size(); i++) { System.out.println(i + 1 + ". " + bookLabel.get(i)); } } + public static void printNoLabelFound(){ + System.out.println("oops there are no books stored under this label..."); + } } diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java index 57f5da8a86..3c5e76d9ea 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java @@ -67,7 +67,7 @@ public static void findRead(BookList bookList){ } } if (bookRead.isEmpty()){ - Ui.printNoGenresFound(); + Ui.printNoReadFound(); } else { Ui.printReadFound(bookRead); } @@ -80,7 +80,7 @@ public static void findUnread(BookList bookList){ } } if (bookUnread.isEmpty()){ - Ui.printNoGenresFound(); + Ui.printNoUnreadFound(); } else { Ui.printUnreadFound(bookUnread); @@ -97,7 +97,7 @@ public static void findLabel(BookList bookList, String input) { } } if (bookLabel.isEmpty()){ - Ui.printNoBookFound(); + Ui.printNoLabelFound(); } else { Ui.printLabelFound(bookLabel); } From cf3daab1c49154e3f1e98922c4ec0f846af23050 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Tue, 9 Apr 2024 11:40:34 +0800 Subject: [PATCH 193/311] improve ui --- src/main/java/seedu/bookbuddy/Ui.java | 16 ++++++++++++++++ .../bookdetailsmodifier/BookDisplay.java | 3 +++ .../bookbuddy/bookdetailsmodifier/BookFind.java | 12 ++++++++++++ .../bookbuddy/bookdetailsmodifier/BookMark.java | 8 +++++--- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 9f42cd5776..3e693c2596 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -32,20 +32,28 @@ public static void printExitMessage() { } public static void addBookMessage(String title) { + printShortLine(); System.out.println("okii added [" + title + "] to the list."); System.out.println("remember to read it soon...."); + printShortLine(); } public static void labelBookMessage(String title, String label) { + printShortLine(); System.out.println("okii labeled [" + title + "] as [" + label + "]"); System.out.println("remember to read it soon...."); + printShortLine(); } public static void summaryBookMessage(String title, String summary) { + printShortLine(); System.out.println("okii you have written: [" + summary + "] for the book: [" + title + "]"); System.out.println("remember to read it soon...."); + printShortLine(); } public static void setGenreBookMessage(String title, String genre) { + printShortLine(); System.out.println("okii categorised [" + title + "] as [" + genre + "]"); System.out.println("remember to read it soon...."); + printShortLine(); } public static void exitCommandMessage() { System.out.println("okii exitting the command now"); @@ -95,18 +103,26 @@ public static void printNoGenresFound(){ System.out.println("no such books added under this genre"); } public static void printReadFound(ArrayList bookRead){ + printLine(); + System.out.println("Read books: "); for (int i = 0; i < bookRead.size(); i++) { System.out.println(i + 1 + ". " + bookRead.get(i)); } + System.out.println("gd job! hope u enjoyed these books "); + printShortLine(); } public static void printNoReadFound(){ System.out.println("no read books found..."); System.out.println("do rmb to start reading soon!"); } public static void printUnreadFound(ArrayList bookUnread){ + printLine(); + System.out.println("Unread books: "); for (int i = 0; i < bookUnread.size(); i++) { System.out.println(i + 1 + ". " + bookUnread.get(i)); } + System.out.println("do rmb to read these books soon!"); + printShortLine(); } public static void printNoUnreadFound(){ System.out.println("no unread books found!"); diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index e77777d6d7..88eb20cc9d 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -1,6 +1,7 @@ package seedu.bookbuddy.bookdetailsmodifier; +import seedu.bookbuddy.Ui; import seedu.bookbuddy.book.BookMain; import seedu.bookbuddy.book.Genre; import seedu.bookbuddy.book.Label; @@ -47,6 +48,7 @@ public static void displayDetails(int index, BookList books) throws IndexOutOfBo public static void printAllBooks(BookList bookList) { assert bookList.getBooks() != null : "Books list should not be null since it has been initialised."; if (!bookList.getBooks().isEmpty()) { + Ui.printLine(); System.out.println("All books:"); for (int i = 0; i < bookList.getBooks().size(); i++) { BookMain currentBook = bookList.getBooks().get(i); @@ -54,6 +56,7 @@ public static void printAllBooks(BookList bookList) { System.out.print((i + 1) + ". "); System.out.println(currentBook.toString()); } + Ui.printShortLine(); } else { System.out.println("The list is empty. Add books by 'add [book]'"); } diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java index 3c5e76d9ea..cba99b347a 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java @@ -26,7 +26,10 @@ public static void findBookTitle(BookList bookList, String input) { if (bookTitles.isEmpty()){ Ui.printNoBookFound(); } else { + Ui.printLine(); + System.out.println("books with [" + input.toLowerCase() + "] in the title: "); Ui.printBookFound(bookTitles); + Ui.printShortLine(); } } @@ -42,7 +45,10 @@ public static void findBookGenre(BookList bookList, String input) { if (bookGenres.isEmpty()){ Ui.printNoGenresFound(); } else { + Ui.printLine(); + System.out.println(input.toLowerCase() + " books: "); Ui.printGenresFound(bookGenres); + Ui.printShortLine(); } } public static void findBookGenreLong(BookList bookList, String genre) { @@ -56,7 +62,10 @@ public static void findBookGenreLong(BookList bookList, String genre) { if (bookGenres.isEmpty()){ Ui.printNoGenresFound(); } else { + Ui.printLine(); + System.out.println(genre.toLowerCase() + " books: "); Ui.printGenresFound(bookGenres); + Ui.printShortLine(); } } public static void findRead(BookList bookList){ @@ -99,7 +108,10 @@ public static void findLabel(BookList bookList, String input) { if (bookLabel.isEmpty()){ Ui.printNoLabelFound(); } else { + Ui.printLine(); + System.out.println("books with [" + input.toLowerCase() + "] in their label:"); Ui.printLabelFound(bookLabel); + Ui.printShortLine(); } } } diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java index fbecf05874..22caf42b11 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java @@ -2,6 +2,7 @@ import exceptions.BookReadAlreadyException; import exceptions.BookUnreadAlreadyException; +import seedu.bookbuddy.Ui; import seedu.bookbuddy.book.BookMain; import seedu.bookbuddy.book.Read; import seedu.bookbuddy.book.Title; @@ -74,7 +75,8 @@ public static void markUndoneByIndex(BookList bookList, int index) throws IndexO */ public static void markBookAsRead(BookMain book) { Read.setRead(book, true); - System.out.println("Successfully marked " + Title.getTitle(book) + " as read."); + Ui.printShortLine(); + System.out.println("Successfully marked [" + Title.getTitle(book) + "] as read."); } /** @@ -84,7 +86,7 @@ public static void markBookAsRead(BookMain book) { */ public static void markBookAsUnread(BookMain book) { Read.setRead(book, false); - - System.out.println("Successfully marked " + Title.getTitle(book) + " as unread."); + Ui.printShortLine(); + System.out.println("Successfully marked [" + Title.getTitle(book) + "] as unread."); } } From d7e92ad4f52738f3f599d2504733540151575fd8 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Tue, 9 Apr 2024 11:55:36 +0800 Subject: [PATCH 194/311] added assertions --- .../seedu/bookbuddy/bookdetailsmodifier/BookFind.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java index cba99b347a..2d22d9d058 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java @@ -15,6 +15,7 @@ public class BookFind { public static void findBookTitle(BookList bookList, String input) { + assert input != null : "input should not be null"; ArrayList bookTitles = new ArrayList<>(); for (BookMain book : bookList.getBooks()) { String actualTitle = Title.getTitle(book).toLowerCase(); @@ -34,6 +35,7 @@ public static void findBookTitle(BookList bookList, String input) { } public static void findBookGenre(BookList bookList, String input) { + assert input != null : "input should not be null"; ArrayList bookGenres = new ArrayList<>(); for (BookMain book : bookList.getBooks()) { String actualGenre = Genre.getGenre(book).toLowerCase(); @@ -51,11 +53,11 @@ public static void findBookGenre(BookList bookList, String input) { Ui.printShortLine(); } } - public static void findBookGenreLong(BookList bookList, String genre) { - + public static void findBookGenreLong(BookList bookList, String input) { + assert input != null : "input should not be null"; ArrayList bookGenres = new ArrayList<>(); for (BookMain book : bookList.getBooks()) { - if (Genre.getGenre(book).contains(genre)) { + if (Genre.getGenre(book).contains(input)) { bookGenres.add(book); } } @@ -63,7 +65,7 @@ public static void findBookGenreLong(BookList bookList, String genre) { Ui.printNoGenresFound(); } else { Ui.printLine(); - System.out.println(genre.toLowerCase() + " books: "); + System.out.println(input.toLowerCase() + " books: "); Ui.printGenresFound(bookGenres); Ui.printShortLine(); } From 7c4477fc59794a28221b3fc613c8a00e8d85f2b3 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Tue, 9 Apr 2024 12:02:54 +0800 Subject: [PATCH 195/311] junit test fixes --- src/test/java/seedu/bookbuddy/BookListTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/seedu/bookbuddy/BookListTest.java b/src/test/java/seedu/bookbuddy/BookListTest.java index 7e63adfb3c..89684e3d2e 100644 --- a/src/test/java/seedu/bookbuddy/BookListTest.java +++ b/src/test/java/seedu/bookbuddy/BookListTest.java @@ -39,7 +39,9 @@ void printAllBooks() { BookDisplay.printAllBooks(testBookList); - String expectedOutput = "All books:\n1. [U] Harry Potter\n"; + String expectedOutput = "___________________________________\n"+ + "All books:\n1. [U] Harry Potter\n" + + "_____________\n"; String normalizedActualOutput = outContent.toString().replace("\r\n", "\n"); assertEquals(expectedOutput.trim(), normalizedActualOutput.trim()); From 49d219a6c4e679e43b23b6ed03ce8802eaef8c3e Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Tue, 9 Apr 2024 12:10:09 +0800 Subject: [PATCH 196/311] fix find-genre issue --- src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java index 2d22d9d058..3eafe8f660 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java @@ -40,7 +40,7 @@ public static void findBookGenre(BookList bookList, String input) { for (BookMain book : bookList.getBooks()) { String actualGenre = Genre.getGenre(book).toLowerCase(); String genre = input.toLowerCase(); - if (actualGenre.contains(genre)) { + if (actualGenre.equals(genre)) { bookGenres.add(book); } } From a1786c9ab355725f10161c38b1ce67f48df3d1ed Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Tue, 9 Apr 2024 13:41:36 +0800 Subject: [PATCH 197/311] added find-rate --- src/main/java/seedu/bookbuddy/Ui.java | 8 +++++ .../bookdetailsmodifier/BookFind.java | 29 +++++++++++++++---- .../seedu/bookbuddy/parser/ParserMain.java | 3 ++ .../parser/parsercommands/ParserFind.java | 4 +++ .../parser/parservalidation/CommandList.java | 1 + 5 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 3e693c2596..97c8681956 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -136,4 +136,12 @@ public static void printLabelFound(ArrayList bookLabel) { public static void printNoLabelFound(){ System.out.println("oops there are no books stored under this label..."); } + public static void printRateFound(ArrayList bookRate) { + for (int i = 0; i < bookRate.size(); i++) { + System.out.println(i + 1 + ". " + bookRate.get(i)); + } + } + public static void printNoRateFound() { + System.out.println("oops there are no books stored under this rating..."); + } } diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java index 3eafe8f660..c50ca2ff1f 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java @@ -1,11 +1,7 @@ package seedu.bookbuddy.bookdetailsmodifier; import seedu.bookbuddy.Ui; -import seedu.bookbuddy.book.Label; -import seedu.bookbuddy.book.BookMain; -import seedu.bookbuddy.book.Genre; -import seedu.bookbuddy.book.Title; -import seedu.bookbuddy.book.Read; +import seedu.bookbuddy.book.*; import seedu.bookbuddy.booklist.BookList; import java.util.ArrayList; @@ -116,4 +112,27 @@ public static void findLabel(BookList bookList, String input) { Ui.printShortLine(); } } + + public static void findRate(BookList bookList, String input) { + ArrayList bookRate = new ArrayList<>(); + int inputRating = Integer.parseInt(input); + for (BookMain book : bookList.getBooks()) { + + if(Rating.getRating(book) == inputRating){ + bookRate.add(book); + } + } + if (inputRating <= 5 && inputRating >= 1) { + if (bookRate.isEmpty()) { + Ui.printNoRateFound(); + } else { + Ui.printLine(); + System.out.println("books rated [" + input + "] :"); + Ui.printRateFound(bookRate); + Ui.printShortLine(); + } + } else { + System.out.println("pls enter a rating from 1-5"); + } + } } diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index f448740d0b..2257c52e2f 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -80,6 +80,9 @@ public static void parseCommand(String input, BookList books) { case CommandList.FIND_LABEL_COMMAND: ParserFind.parseLabel(books, inputArray[1]); break; + case CommandList.FIND_RATE_COMMAND: + ParserFind.parseRate(books, inputArray[1]); + break; case CommandList.LABEL_COMMAND: ParserLabel.executeParseSetLabel(books, inputArray); break; diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java index a4a26b5d41..24c6e0939b 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java @@ -38,4 +38,8 @@ public static void parseFindUnread(BookList books) { public static void parseLabel(BookList books, String inputArray) { BookFind.findLabel(books, inputArray); } + + public static void parseRate(BookList books, String input) { + BookFind.findRate(books, input); + } } diff --git a/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java b/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java index 55e12caacb..e85c29d05e 100644 --- a/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java +++ b/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java @@ -12,6 +12,7 @@ public class CommandList { public static final String FIND_READ_COMMAND = "find-read"; public static final String FIND_UNREAD_COMMAND = "find-unread"; public static final String FIND_LABEL_COMMAND = "find-label"; + public static final String FIND_RATE_COMMAND = "find-rate"; public static final String LABEL_COMMAND = "label"; public static final String GENRE_COMMAND = "set-genre"; public static final String SUMMARY_COMMAND = "give-summary"; From db5f12ab6f1bf319716ba2beb30ab0b52781ef25 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Tue, 9 Apr 2024 13:44:55 +0800 Subject: [PATCH 198/311] checkstyle fix --- .../java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java index c50ca2ff1f..b95f0fb72b 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java @@ -1,7 +1,11 @@ package seedu.bookbuddy.bookdetailsmodifier; import seedu.bookbuddy.Ui; -import seedu.bookbuddy.book.*; +import seedu.bookbuddy.book.Label; +import seedu.bookbuddy.book.BookMain; +import seedu.bookbuddy.book.Genre; +import seedu.bookbuddy.book.Title; +import seedu.bookbuddy.book.Read; import seedu.bookbuddy.booklist.BookList; import java.util.ArrayList; From 32f516bd523c18abf6f31b22b3e61d250ce81f85 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Tue, 9 Apr 2024 13:48:10 +0800 Subject: [PATCH 199/311] checkstyle fix --- src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java index b95f0fb72b..aec7093932 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java @@ -6,6 +6,7 @@ import seedu.bookbuddy.book.Genre; import seedu.bookbuddy.book.Title; import seedu.bookbuddy.book.Read; +import seedu.bookbuddy.book.Rating; import seedu.bookbuddy.booklist.BookList; import java.util.ArrayList; From 11ec90cad1d8ecadbdee2ec58eb6365615b3dcc1 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Tue, 9 Apr 2024 14:06:56 +0800 Subject: [PATCH 200/311] update UG --- docs/UserGuide.md | 71 ++++++++++++++++++++++++++- src/main/java/seedu/bookbuddy/Ui.java | 2 + 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index ff1e84fb14..ed5395f20f 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -72,9 +72,12 @@ rate [BOOK_INDEX] [BOOK_RATING] -> to rate a book from 1-5 list-rated -> to sort books by rating in descending order display [BOOK_INDEX] -> to view more details about a book find-title [KEYWORD] -> to find books with keyword in their title +(advanced)find-genre [GENRE] -> to find books under specific genres find-genre -> to find books under specific genres find-read -> to find list of books that are read find-unread -> to find list of books that are unread +find-label [KEYWORD] -> to find list of books that stored under a certain label +find-rate [RATING] -> to find list of books with specified rating bye -> to exit BookBuddy software ```` @@ -330,17 +333,48 @@ Example output: ### Finding a book by genre: `find-genre` Returns all books in the saved book list that are stored under the matching genre. -Format: `find-genre [KEYWORD]` . +Format: `find-genre [KEYWORD]` or `find-genre` where user will receive a prompt. Example of usage with expected output: +for more advanced users + +``` +//input +find-genre +``` +```` +//ouput +___________________________________ +fiction books: +1. [U] harry potter +_____________ +```` +for basic users +``` +//input +find-genre +``` +```` +//ouput +Available genres: +1. Fiction +2. Non-Fiction +3. Mystery +4. Science Fiction +5. Fantasy +Enter the number for the desired genre: +```` ``` //input -find-genre fiction +1 ``` ```` //ouput +___________________________________ +fiction books: 1. [U] harry potter +_____________ ```` ### Find books that are read: `find-read` @@ -372,7 +406,40 @@ find-unread 1. [U] geronimo stilton 2. [U] The Boy in Striped Pyjamas ```` +### Find books that are labelled: `find-label` +Returns all books in the saved book list that stored under specific label. +Format: `find-label [KEYWORD]` + +Example of usage with expected output: +```` +//input +find-label very cool +```` +```` +//ouput +___________________________________ +books with [very cool] in their label: +1. [R] harry potter +_____________ +```` +### Find books that are labelled: `find-rate` +Returns all books in the saved book list that have specific rating. + +Format: `find-rate [RATING]` + +Example of usage with expected output: +```` +//input +find-rate 3 +```` +```` +//ouput +___________________________________ +books rated [3] : +1. [R] harry potter +_____________ +```` ### Exiting the program: `bye` Exits the application and saves all tasks in a file. diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 97c8681956..c52301a555 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -79,10 +79,12 @@ public static void helpMessage() { System.out.println("list-rated -> to sort books by rating in descending order"); System.out.println("display [BOOK_INDEX] -> to view more details about a book"); System.out.println("find-title [KEYWORD] -> to find books with keyword in their title"); + System.out.println("(advanced)find-genre [GENRE] -> to find books under specific genres"); System.out.println("find-genre -> to find books under specific genres"); System.out.println("find-read -> to find list of books that are read"); System.out.println("find-unread -> to find list of books that are unread"); System.out.println("find-label [KEYWORD] -> to find list of books that stored under a certain label"); + System.out.println("find-rate [RATING] -> to find list of books with specified rating"); System.out.println("bye -> to exit BookBuddy software"); } From 0edcddd94b5f8819573e55bcf9316ffd12508a12 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Tue, 9 Apr 2024 19:02:44 +0800 Subject: [PATCH 201/311] Add list-by-date command --- .../bookdetailsmodifier/BookMark.java | 31 +++++++++++++++++++ .../seedu/bookbuddy/parser/ParserMain.java | 4 +++ .../parser/parservalidation/CommandList.java | 1 + src/main/java/utils/DateTimeUtils.java | 4 +++ 4 files changed, 40 insertions(+) diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java index 22caf42b11..b21ad5a954 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java @@ -7,7 +7,9 @@ import seedu.bookbuddy.book.Read; import seedu.bookbuddy.book.Title; import seedu.bookbuddy.booklist.BookList; +import utils.DateTimeUtils; +import java.util.ArrayList; import java.util.logging.Level; import static seedu.bookbuddy.BookBuddy.LOGGER; @@ -89,4 +91,33 @@ public static void markBookAsUnread(BookMain book) { Ui.printShortLine(); System.out.println("Successfully marked [" + Title.getTitle(book) + "] as unread."); } + + /** + * Prints all books sorted by date in descending order. + */ + public static void printBooksByDateRead(BookList books) { + if (books.getBooks().isEmpty()) { + System.out.println("The list is empty. Add books by 'add [book]'"); + return; + } + + ArrayList bookRead = new ArrayList<>(); + for (BookMain book : books.getBooks()) { + if (Read.getRead(book)) { + bookRead.add(book); + } + } + + bookRead.sort((book1, book2) -> { + String dateTimeStr1 = Read.getDateTimeRead(book1); + String dateTimeStr2 = Read.getDateTimeRead(book2); + return DateTimeUtils.convertStringToDateTime(dateTimeStr2).compareTo( + DateTimeUtils.convertStringToDateTime(dateTimeStr1)); + }); + + for (BookMain book : bookRead) { + String datetime = Read.getDateTimeRead(book); + System.out.println(Title.getTitle(book) + " : " + datetime); + } + } } diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index 2257c52e2f..9e22f0aaff 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -2,6 +2,7 @@ import exceptions.UnsupportedCommandException; import seedu.bookbuddy.bookdetailsmodifier.BookDisplay; +import seedu.bookbuddy.bookdetailsmodifier.BookMark; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.Ui; import seedu.bookbuddy.bookdetailsmodifier.BookRating; @@ -102,6 +103,9 @@ public static void parseCommand(String input, BookList books) { case CommandList.PRINT_ORDERED_COMMAND: BookRating.printBooksByRating(books); break; + case CommandList.PRINT_ORDERED_DATE_COMMAND: + BookMark.printBooksByDateRead(books); + break; default: LOGGER.log(Level.WARNING, "Sorry but that is not a valid command. Please try again", command); throw new UnsupportedCommandException("Sorry but that is not a valid command. " + diff --git a/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java b/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java index e85c29d05e..7d95b2c049 100644 --- a/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java +++ b/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java @@ -19,4 +19,5 @@ public class CommandList { public static final String DISPLAY_COMMAND = "display"; public static final String RATING_COMMAND = "rate"; public static final String PRINT_ORDERED_COMMAND = "list-rated"; + public static final String PRINT_ORDERED_DATE_COMMAND = "list-by-date"; } diff --git a/src/main/java/utils/DateTimeUtils.java b/src/main/java/utils/DateTimeUtils.java index 9509e954db..565a3ffaa3 100644 --- a/src/main/java/utils/DateTimeUtils.java +++ b/src/main/java/utils/DateTimeUtils.java @@ -15,5 +15,9 @@ public static String getCurrentDateTime() { return now.format(FORMATTER); } + public static LocalDateTime convertStringToDateTime(String datetimestring) { + return LocalDateTime.parse(datetimestring, FORMATTER); + } + } From 04f8aa4a5a2ff88b872a8ccd9b6224ed8f321fe2 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Wed, 10 Apr 2024 01:15:27 +0800 Subject: [PATCH 202/311] Update UG and add javadocs --- docs/UserGuide.md | 20 +++++++++++++ src/main/java/seedu/bookbuddy/Ui.java | 1 + src/main/java/utils/DateTimeUtils.java | 39 +++++++++++++++++++++++--- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index ed5395f20f..02c46cf4a2 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -26,6 +26,7 @@ experience. * [list](#viewing-all-books-list) * [mark](#marking-a-book-as-read-mark) * [unmark](#marking-a-book-as-unread-unmark) + * [list-by-date](#listing-read-books-by-date) * [set-genre](#setting-the-genre-of-a-book-set-genre) * [label](#labelling-a-book-label) * [give-summary](#adding-a-book-summary-give-summary) @@ -165,6 +166,25 @@ Example output: Successfully marked Harry Potter as unread. ```` +### Listing-read-books-by-date `list-by-date` + +Lists all read books by descending order of date read. + +Format: `list-by-date` + +Example of usage with expected output: + +``` +//input +list-by-date + +//output +booky : 6.57 PM, 09-04-2024 +book2 : 6.57 PM, 09-04-2024 +``` + + + ### Setting the genre of a book: `set-genre` Sets the genre of a specific book based on the provided input and the provided index. diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index c52301a555..3530fcf3fa 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -72,6 +72,7 @@ public static void helpMessage() { System.out.println("list -> to show whole list of added books"); System.out.println("mark [BOOK_INDEX] -> to mark book as read [R]"); System.out.println("unmark [BOOK_INDEX] -> to mark book as unread [U]"); + System.out.println("list-by-date -> to print out all books sorted in descending order of date"); System.out.println("set-genre [BOOK_INDEX] -> to set a genre for a book"); System.out.println("label [BOOK_INDEX] [LABEL] -> to set a label for a book"); System.out.println("give-summary [BOOK_INDEX] [BOOK_SUMMARY] -> to give a book a summary"); diff --git a/src/main/java/utils/DateTimeUtils.java b/src/main/java/utils/DateTimeUtils.java index 565a3ffaa3..7e8a2a6120 100644 --- a/src/main/java/utils/DateTimeUtils.java +++ b/src/main/java/utils/DateTimeUtils.java @@ -1,23 +1,54 @@ package utils; + import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +/** + * Provides utility methods for handling date and time operations. + * This class includes methods to get the current date and time in a predefined format + * and to convert a string representation of a date and time back into a {@link LocalDateTime} object. + * + * The class is designed to be a utility with static methods, and thus cannot be instantiated. + */ public class DateTimeUtils { + /** + * The formatter used for converting between {@link LocalDateTime} and strings. + * It follows the pattern "hour.minute AM/PM, day-month-year". + */ private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("h.mm a, dd-MM-yyyy"); - // Private constructor to prevent instantiation + /** + * Private constructor to prevent instantiation. + * Throws an exception if an attempt is made to instantiate this utility class. + * + * @throws UnsupportedOperationException if this class is attempted to be instantiated. + */ private DateTimeUtils() { throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); } + + /** + * Gets the current date and time formatted as a string. + * + * @return A {@link String} representation of the current date and time, + * formatted according to the {@link DateTimeUtils#FORMATTER}. + */ public static String getCurrentDateTime() { LocalDateTime now = LocalDateTime.now(); return now.format(FORMATTER); } - public static LocalDateTime convertStringToDateTime(String datetimestring) { - return LocalDateTime.parse(datetimestring, FORMATTER); + /** + * Converts a string representation of a date and time into a {@link LocalDateTime} object. + * The string should be in the format defined by {@link DateTimeUtils#FORMATTER}. + * + * @param datetimeString The date and time in string format to be converted. + * @return A {@link LocalDateTime} that represents the date and time specified in the input string. + * @throws java.time.format.DateTimeParseException if the text cannot be parsed. + */ + public static LocalDateTime convertStringToDateTime(String datetimeString) { + return LocalDateTime.parse(datetimeString, FORMATTER); } } - From a5e0f9a2752703ff9be88e2a142f106fe25723b0 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 10 Apr 2024 01:26:11 +0800 Subject: [PATCH 203/311] Bug fixes - 1. Set-genre does not take into trailling and leading spaces 2. Set-genre does not detect existing genres in the genre list --- .../parser/parsercommands/ParserGenre.java | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java index 8e4ff3c042..d5bf822aef 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java @@ -12,7 +12,7 @@ static void parseSetGenre(BookList books, String[] inputArray) { Exceptions.validateCommandArguments(inputArray, 2, "The set-genre command requires " + "at least a book index."); - String[] parts = inputArray[1].split(" ", 2); // Attempt to split inputArray[1] into two parts + String[] parts = inputArray[1].trim().split(" ", 2); //Attempt to split inputArray[1] into two parts int index; try { index = Integer.parseInt(parts[0]); // The first part should be the index @@ -49,25 +49,11 @@ private static void multiStepSetGenre(BookList books, int index) { } private static void singleStepSetGenre(BookList books, String[] parts, int index) { - String genreInput = parts[1]; - boolean genreExists = false; - for (String existingGenre : BookList.getAvailableGenres()) { - if (existingGenre.equalsIgnoreCase(genreInput)) { - genreExists = true; - genreInput = existingGenre; // Normalize to the existing genre's case - break; - } - } - - if (!genreExists) { - BookList.getAvailableGenres().add(genreInput); - System.out.println("Added new genre to the list: " + genreInput); - } + String genreInput = parts[1].trim(); + genreInput = duplicateChecker(genreInput); BookGenre.setBookGenreByIndex(index, genreInput, books); - System.out.println("Genre set to " + genreInput + " for book at index " + index); } - static void genreSelectionPrinter() { System.out.println("Available genres:"); for (int i = 0; i < BookList.getAvailableGenres().size(); i++) { @@ -79,7 +65,7 @@ static void genreSelectionPrinter() { public static String invalidInputLooper(String input, Scanner scanner) { while (input == null) { while (!scanner.hasNextInt()) { // Ensure the next input is an integer - String newInput = scanner.nextLine(); + String newInput = scanner.nextLine().trim(); if ("exit".equalsIgnoreCase(newInput)) { Ui.exitCommandMessage(); return null; @@ -94,8 +80,8 @@ public static String invalidInputLooper(String input, Scanner scanner) { if (genreSelection == BookList.getAvailableGenres().size() + 1) { System.out.println("Enter the new genre:"); - input = scanner.nextLine(); - BookList.getAvailableGenres().add(input); // Add the new genre to the list + input = scanner.nextLine().trim(); + input = duplicateChecker(input); } else if (genreSelection > 0 && genreSelection <= BookList.getAvailableGenres().size()) { input = BookList.getAvailableGenres().get(genreSelection - 1); } else { @@ -107,6 +93,26 @@ public static String invalidInputLooper(String input, Scanner scanner) { return input; } + private static String duplicateChecker(String input) { + boolean genreExists = false; + for (String existingGenre : BookList.getAvailableGenres()) { + if (existingGenre.equalsIgnoreCase(input)) { + genreExists = true; + input = existingGenre; // Normalize to the existing genre's case + break; + } + } + if (!genreExists) { + BookList.getAvailableGenres().add(input); + Ui.printLine(); + System.out.println("Added new genre to the list: " + input); + } else { + Ui.printLine(); + System.out.println("[" + input + "] exists in the existing genre list!"); + } + return input; + } + public static void executeParseSetGenre (BookList books, String[] inputArray) { parseSetGenre(books, inputArray); } From 0068a365bd7db53d94b1988fe325e73cd811a906 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 10 Apr 2024 01:38:10 +0800 Subject: [PATCH 204/311] Checkstyle fix - wrong indentation for javadoc --- src/main/java/utils/DateTimeUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/utils/DateTimeUtils.java b/src/main/java/utils/DateTimeUtils.java index 7e8a2a6120..fef544b693 100644 --- a/src/main/java/utils/DateTimeUtils.java +++ b/src/main/java/utils/DateTimeUtils.java @@ -32,7 +32,7 @@ private DateTimeUtils() { * Gets the current date and time formatted as a string. * * @return A {@link String} representation of the current date and time, - * formatted according to the {@link DateTimeUtils#FORMATTER}. + * formatted according to the {@link DateTimeUtils#FORMATTER}. */ public static String getCurrentDateTime() { LocalDateTime now = LocalDateTime.now(); From 3e787fe466bdcfd33420367922d6cff310d800ac Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 10 Apr 2024 01:40:55 +0800 Subject: [PATCH 205/311] Checkstyle fixes for gareth - wrong indentation for javadoc --- src/main/java/utils/DateTimeUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/utils/DateTimeUtils.java b/src/main/java/utils/DateTimeUtils.java index fef544b693..d1bd9aced6 100644 --- a/src/main/java/utils/DateTimeUtils.java +++ b/src/main/java/utils/DateTimeUtils.java @@ -32,7 +32,7 @@ private DateTimeUtils() { * Gets the current date and time formatted as a string. * * @return A {@link String} representation of the current date and time, - * formatted according to the {@link DateTimeUtils#FORMATTER}. + ***** formatted according to the {@link DateTimeUtils#FORMATTER}. */ public static String getCurrentDateTime() { LocalDateTime now = LocalDateTime.now(); From ae844d67bf42c81fb9edebd97a399bc9bc05f498 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 10 Apr 2024 01:42:36 +0800 Subject: [PATCH 206/311] Checkstyle fix for gareth - wrong indentation for javadoc --- src/main/java/utils/DateTimeUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/utils/DateTimeUtils.java b/src/main/java/utils/DateTimeUtils.java index d1bd9aced6..a78e039ef1 100644 --- a/src/main/java/utils/DateTimeUtils.java +++ b/src/main/java/utils/DateTimeUtils.java @@ -32,7 +32,7 @@ private DateTimeUtils() { * Gets the current date and time formatted as a string. * * @return A {@link String} representation of the current date and time, - ***** formatted according to the {@link DateTimeUtils#FORMATTER}. + **** formatted according to the {@link DateTimeUtils#FORMATTER}. */ public static String getCurrentDateTime() { LocalDateTime now = LocalDateTime.now(); From c9e419191e2ee5ac4f6e14e526d3c7402c5df9db Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 10 Apr 2024 01:44:49 +0800 Subject: [PATCH 207/311] Checkstyle fixes for gareth - wrong indentation for javadoc --- src/main/java/utils/DateTimeUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/utils/DateTimeUtils.java b/src/main/java/utils/DateTimeUtils.java index a78e039ef1..859e6b9da3 100644 --- a/src/main/java/utils/DateTimeUtils.java +++ b/src/main/java/utils/DateTimeUtils.java @@ -32,7 +32,7 @@ private DateTimeUtils() { * Gets the current date and time formatted as a string. * * @return A {@link String} representation of the current date and time, - **** formatted according to the {@link DateTimeUtils#FORMATTER}. + * formatted according to the {@link DateTimeUtils#FORMATTER}. */ public static String getCurrentDateTime() { LocalDateTime now = LocalDateTime.now(); From 2601b0e336c3770786ea497b2675d291395fb24e Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 10 Apr 2024 03:28:08 +0800 Subject: [PATCH 208/311] Bug fixes - 1. [PE-D][Tester C] Usablity issues caused by not being able to end the program using METHOD: bye when using METHOD: set-genre 2. [PE-D][Tester A] Set Genre Command not Taking Other Numbers into Account Enhanced exception handling and parsing process to ensure invalid inputs will not be processed. Extracted exit process in main class and created a public wrapper to perform the exit anywhere in the programme --- .../exceptions/InvalidInputException.java | 8 +++ src/main/java/seedu/bookbuddy/BookBuddy.java | 22 ++++-- .../parser/parsercommands/ParserFind.java | 3 +- .../parser/parsercommands/ParserGenre.java | 69 +++++++++++-------- .../parser/parservalidation/Exceptions.java | 2 +- 5 files changed, 68 insertions(+), 36 deletions(-) create mode 100644 src/main/java/exceptions/InvalidInputException.java diff --git a/src/main/java/exceptions/InvalidInputException.java b/src/main/java/exceptions/InvalidInputException.java new file mode 100644 index 0000000000..5801027249 --- /dev/null +++ b/src/main/java/exceptions/InvalidInputException.java @@ -0,0 +1,8 @@ +package exceptions; + +public class InvalidInputException extends IllegalArgumentException { + public InvalidInputException(String message) { + super(message); + } +} + diff --git a/src/main/java/seedu/bookbuddy/BookBuddy.java b/src/main/java/seedu/bookbuddy/BookBuddy.java index ad90c530a2..415b81fa43 100644 --- a/src/main/java/seedu/bookbuddy/BookBuddy.java +++ b/src/main/java/seedu/bookbuddy/BookBuddy.java @@ -18,7 +18,8 @@ public class BookBuddy { public static final Logger LOGGER = getLogger(BookBuddy.class.getName()); public static final String EXIT_COMMAND = "bye"; - + private static BookList books = new BookList(); + static FileStorage filestorage = new FileStorage(books); static { try { LOGGER.setUseParentHandlers(false); @@ -37,7 +38,6 @@ public class BookBuddy { } } - private static BookList books = new BookList(); public static void main(String[] args) throws IOException { LOGGER.log(Level.INFO, "BookBuddy application started."); Ui.printWelcome(); @@ -48,7 +48,6 @@ public static void main(String[] args) throws IOException { public static void getUserInput(BookList books) throws IOException { Scanner input = new Scanner(System.in); - FileStorage filestorage = new FileStorage(books); LOGGER.log(Level.INFO, "Starting to get user input."); while (true) { @@ -57,9 +56,7 @@ public static void getUserInput(BookList books) throws IOException { // If the input is empty, do not call parseCommand and just prompt for input again. continue; } else if (userInput.equals(EXIT_COMMAND)) { - filestorage.saveData(books); - Ui.printExitMessage(); - System.exit(0); + exitSystem(books, filestorage); } assert !userInput.isEmpty() : "User input should not be empty at this point"; LOGGER.log(Level.FINE, "Processing user input: {0}", userInput); @@ -76,4 +73,17 @@ public static void getUserInput(BookList books) throws IOException { } } } + //@@author joshuahoky + private static void exitSystem(BookList books, FileStorage filestorage) throws IOException { + filestorage.saveData(books); + Ui.printExitMessage(); + System.exit(0); + } + + //@@author + + + public static void performExit() throws IOException { + exitSystem(books, filestorage); + } } diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java index 24c6e0939b..662422ca82 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java @@ -3,6 +3,7 @@ import seedu.bookbuddy.bookdetailsmodifier.BookFind; import seedu.bookbuddy.booklist.BookList; +import java.io.IOException; import java.util.Scanner; //@@ liuzehui03 @@ -15,7 +16,7 @@ public static void parseFindGenre(BookList books, String inputArray) { BookFind.findBookGenre(books, inputArray); } - public static void parseGenreLong(BookList books) { + public static void parseGenreLong(BookList books) throws IOException { System.out.println("Available genres:"); for (int i = 0; i < BookList.getAvailableGenres().size(); i++) { System.out.println((i + 1) + ". " + BookList.getAvailableGenres().get(i)); diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java index d5bf822aef..dc526c08cf 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java @@ -1,14 +1,18 @@ package seedu.bookbuddy.parser.parsercommands; +import exceptions.InvalidCommandArgumentException; +import exceptions.InvalidInputException; +import seedu.bookbuddy.BookBuddy; import seedu.bookbuddy.Ui; import seedu.bookbuddy.bookdetailsmodifier.BookGenre; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.parser.parservalidation.Exceptions; +import java.io.IOException; import java.util.Scanner; public class ParserGenre { - static void parseSetGenre(BookList books, String[] inputArray) { + static void parseSetGenre(BookList books, String[] inputArray) throws IOException { Exceptions.validateCommandArguments(inputArray, 2, "The set-genre command requires " + "at least a book index."); @@ -22,9 +26,8 @@ static void parseSetGenre(BookList books, String[] inputArray) { } if (index <= 0 || index > books.getSize()) { - System.out.println("Invalid book index. Please enter a valid index. Type 'list' to view the " + + throw new InvalidCommandArgumentException("Invalid book index. Please enter a valid index. Type 'list' to view the " + "list of books."); - return; } if (parts.length > 1 && !parts[1].isEmpty()) { @@ -36,7 +39,7 @@ static void parseSetGenre(BookList books, String[] inputArray) { } } - private static void multiStepSetGenre(BookList books, int index) { + private static void multiStepSetGenre(BookList books, int index) throws IOException { genreSelectionPrinter(); System.out.println("Enter the number for the desired genre, or add a new one:"); Scanner scanner = new Scanner(System.in); @@ -62,32 +65,42 @@ static void genreSelectionPrinter() { System.out.println((BookList.getAvailableGenres().size() + 1) + ". Add a new genre"); } - public static String invalidInputLooper(String input, Scanner scanner) { + public static String invalidInputLooper(String input, Scanner scanner) throws IOException { while (input == null) { - while (!scanner.hasNextInt()) { // Ensure the next input is an integer - String newInput = scanner.nextLine().trim(); - if ("exit".equalsIgnoreCase(newInput)) { - Ui.exitCommandMessage(); - return null; - } else { - System.out.println("Invalid input. Please enter a valid number or type 'exit'" + - " to cancel."); + String newInput; + while (true) { + newInput = scanner.nextLine().trim(); + String[] parts = newInput.split("\\s+"); // Splits the input based on one or more spaces + try { + if (parts.length == 1 && parts[0].matches("-?\\d+")) { + break; + } else if ("exit".equalsIgnoreCase(newInput)) { + Ui.exitCommandMessage(); + return null; + } else if ("bye".equalsIgnoreCase(newInput)) { + BookBuddy.performExit(); + } else { + throw new InvalidInputException(newInput + " is an invalid input. Please enter a valid " + + "number or type 'exit' to cancel or 'bye' to exit the programme."); + } + } catch (InvalidInputException e) { + System.out.println(e.getMessage()); } } - - int genreSelection = scanner.nextInt(); - scanner.nextLine(); // Consume the newline after the number - - if (genreSelection == BookList.getAvailableGenres().size() + 1) { - System.out.println("Enter the new genre:"); - input = scanner.nextLine().trim(); - input = duplicateChecker(input); - } else if (genreSelection > 0 && genreSelection <= BookList.getAvailableGenres().size()) { - input = BookList.getAvailableGenres().get(genreSelection - 1); - } else { - System.out.println("Invalid selection. Please enter a valid number " + - "or type 'exit' to cancel."); - // No need for the nextLine or parsing logic here, the while loop will continue + try { + int genreSelection = Integer.parseInt(newInput); + if (genreSelection == BookList.getAvailableGenres().size() + 1) { + System.out.println("Enter the new genre:"); + input = scanner.nextLine().trim(); + input = duplicateChecker(input); + } else if (genreSelection > 0 && genreSelection <= BookList.getAvailableGenres().size()) { + input = BookList.getAvailableGenres().get(genreSelection - 1); + } else { + throw new InvalidInputException(genreSelection + " is an invalid selection. Please enter a " + + "valid number or type 'exit' to cancel or 'bye' to exit the programme."); + } + } catch (InvalidInputException e) { + System.out.println(e.getMessage()); } } return input; @@ -113,7 +126,7 @@ private static String duplicateChecker(String input) { return input; } - public static void executeParseSetGenre (BookList books, String[] inputArray) { + public static void executeParseSetGenre (BookList books, String[] inputArray) throws IOException { parseSetGenre(books, inputArray); } } diff --git a/src/main/java/seedu/bookbuddy/parser/parservalidation/Exceptions.java b/src/main/java/seedu/bookbuddy/parser/parservalidation/Exceptions.java index 6214461b2d..0c54a14ec8 100644 --- a/src/main/java/seedu/bookbuddy/parser/parservalidation/Exceptions.java +++ b/src/main/java/seedu/bookbuddy/parser/parservalidation/Exceptions.java @@ -26,7 +26,7 @@ public static void handleException(Exception e, String command, String[] inputAr } else if (e instanceof ArrayIndexOutOfBoundsException) { System.out.println("Command requires more parameters than provided. Please try again or type: help"); } else if (e instanceof IndexOutOfBoundsException) { - System.out.println("Invalid book index. Please enter a valid index."); + System.out.println(e.getMessage()); } else if (e instanceof InvalidCommandArgumentException) { LOGGER.log(Level.WARNING, "Invalid command argument: {0}", new Object[]{e.getMessage()}); System.out.println(e.getMessage()); From 6c0edaaf926729e824f537d0ab5de72efb2e52a2 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 10 Apr 2024 03:31:04 +0800 Subject: [PATCH 209/311] Checkstyle fixes - line longer than 120 characters --- .../seedu/bookbuddy/parser/parsercommands/ParserGenre.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java index dc526c08cf..3708256119 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java @@ -26,8 +26,8 @@ static void parseSetGenre(BookList books, String[] inputArray) throws IOExceptio } if (index <= 0 || index > books.getSize()) { - throw new InvalidCommandArgumentException("Invalid book index. Please enter a valid index. Type 'list' to view the " + - "list of books."); + throw new InvalidCommandArgumentException("Invalid book index. Please enter a valid " + + "index. Type 'list' to view the list of books."); } if (parts.length > 1 && !parts[1].isEmpty()) { From 2f4958c93cde972dbfaa05d76a5e5554ba4b231a Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 10 Apr 2024 03:46:12 +0800 Subject: [PATCH 210/311] Test --- .../java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java index 0a97f1049e..ce2f9fd75d 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java @@ -19,6 +19,7 @@ static void parseSetLabel(BookList books, String[] inputArray) { assert index > 0 : "Index should be non-negative"; String label = labelMessageParts[1]; BookLabel.setBookLabelByIndex(index, label, books); + //test } public static void executeParseSetLabel (BookList books, String[] inputArray) { From dd5b2d62b8548e9cdb80c3b4da7f0cdc9e10300a Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 10 Apr 2024 05:34:15 +0800 Subject: [PATCH 211/311] Bug fixes - 1. [PE-D][Tester F] Inproper Exception Handling #133 2. [PE-D][Tester B] Redundant output in the label command #112 Enhanced exception handling to capture the different wrong input scenarios --- .../bookdetailsmodifier/BookLabel.java | 7 +----- .../seedu/bookbuddy/booklist/BookList.java | 2 +- .../parser/parsercommands/ParserGenre.java | 2 +- .../parser/parsercommands/ParserLabel.java | 22 +++++++++++++------ 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookLabel.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookLabel.java index 3a2b57e342..c14e1bfc40 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookLabel.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookLabel.java @@ -1,9 +1,9 @@ package seedu.bookbuddy.bookdetailsmodifier; +import seedu.bookbuddy.Ui; import seedu.bookbuddy.book.Label; import seedu.bookbuddy.book.Title; import seedu.bookbuddy.booklist.BookList; -import seedu.bookbuddy.Ui; public class BookLabel { @@ -15,11 +15,6 @@ public class BookLabel { * @throws IndexOutOfBoundsException if the index is out of range. */ public static void setBookLabelByIndex(int index, String label, BookList books) throws IndexOutOfBoundsException { - // Check for valid index - if (index < 0 || index > books.getSize()) { - throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index. nows"); - } - // Set the label for the book at the specified index Label.setLabel(books.getBook(index), label); String title = Title.getTitle(books.getBook(index)); diff --git a/src/main/java/seedu/bookbuddy/booklist/BookList.java b/src/main/java/seedu/bookbuddy/booklist/BookList.java index 62844aa9f5..4757745904 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookList.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookList.java @@ -45,7 +45,7 @@ public int getSize(){ * @param index The index of the book to retrieve. * @return The Book at the specified index. */ - public BookMain getBook(int index) throws BookNotFoundException{ + public BookMain getBook(int index) throws BookNotFoundException { if (index < 0 || index > books.size()) { throw new BookNotFoundException("Book index out of range."); } diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java index 3708256119..3150859109 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java @@ -21,7 +21,7 @@ static void parseSetGenre(BookList books, String[] inputArray) throws IOExceptio try { index = Integer.parseInt(parts[0]); // The first part should be the index } catch (NumberFormatException e) { - System.out.println("Invalid book index. Please enter a valid numeric index."); + System.out.println("Invalid book index format. Please enter a valid numeric index."); return; } diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java index ce2f9fd75d..cdcd7e6801 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java @@ -1,25 +1,33 @@ package seedu.bookbuddy.parser.parsercommands; -import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.bookdetailsmodifier.BookLabel; +import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.parser.parservalidation.Exceptions; public class ParserLabel { static void parseSetLabel(BookList books, String[] inputArray) { - int index; + int index = -1; assert inputArray.length == 2 : "Command requires additional arguments"; Exceptions.validateCommandArguments(inputArray,2, "The Label " + "Command requires a book index and label"); - String[] labelMessageParts = inputArray[1].split(" ", 2); + String[] labelMessageParts = inputArray[1].trim().split(" ", 2); // Split the message into index and label message assert labelMessageParts.length == 2 : "Command requires an index and a label message"; + try { + index = Integer.parseInt(labelMessageParts[0].trim()); + if (index < 0 || index > books.getSize()) { + throw new IndexOutOfBoundsException("Invalid book index (out of range). Please enter a valid index."); + } + assert index > 0 : "Index should be non-negative"; + } catch (NumberFormatException e) { + System.out.println(labelMessageParts[0] + " is not a valid index format."); + } catch (IndexOutOfBoundsException e) { + System.out.println(e.getMessage()); + } Exceptions.validateCommandArguments(labelMessageParts, 2, "You " + "need to have a label message"); - index = Integer.parseInt(labelMessageParts[0]); - assert index > 0 : "Index should be non-negative"; - String label = labelMessageParts[1]; + String label = labelMessageParts[1].trim(); BookLabel.setBookLabelByIndex(index, label, books); - //test } public static void executeParseSetLabel (BookList books, String[] inputArray) { From efdff814dbe0f6a9021e61d53f077753dea17b92 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 10 Apr 2024 05:35:08 +0800 Subject: [PATCH 212/311] Checkstyle fixes - lines longer than 120 characters --- .../seedu/bookbuddy/parser/parsercommands/ParserLabel.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java index cdcd7e6801..5cb6caf424 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java @@ -16,7 +16,8 @@ static void parseSetLabel(BookList books, String[] inputArray) { try { index = Integer.parseInt(labelMessageParts[0].trim()); if (index < 0 || index > books.getSize()) { - throw new IndexOutOfBoundsException("Invalid book index (out of range). Please enter a valid index."); + throw new IndexOutOfBoundsException("Invalid book index (out of range). Please enter a valid " + + "index."); } assert index > 0 : "Index should be non-negative"; } catch (NumberFormatException e) { From beff5a657bed2933fe1a71a324b3dd6b0ee75f00 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 10 Apr 2024 05:37:48 +0800 Subject: [PATCH 213/311] Checkstyle fixes - wrong indentation --- .../bookbuddy/parser/parsercommands/ParserLabel.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java index 5cb6caf424..1e3a90f767 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserLabel.java @@ -14,12 +14,12 @@ static void parseSetLabel(BookList books, String[] inputArray) { // Split the message into index and label message assert labelMessageParts.length == 2 : "Command requires an index and a label message"; try { - index = Integer.parseInt(labelMessageParts[0].trim()); - if (index < 0 || index > books.getSize()) { - throw new IndexOutOfBoundsException("Invalid book index (out of range). Please enter a valid " + - "index."); - } - assert index > 0 : "Index should be non-negative"; + index = Integer.parseInt(labelMessageParts[0].trim()); + if (index < 0 || index > books.getSize()) { + throw new IndexOutOfBoundsException("Invalid book index (out of range). Please enter a valid " + + "index."); + } + assert index > 0 : "Index should be non-negative"; } catch (NumberFormatException e) { System.out.println(labelMessageParts[0] + " is not a valid index format."); } catch (IndexOutOfBoundsException e) { From b361ce6883afa0c47f368b8837b4a0ac4b12a7e6 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 10 Apr 2024 06:00:05 +0800 Subject: [PATCH 214/311] Bug fixes - 1. [PE-D][Tester A] Documentation Giving Misleading Instructions #144 2. [PE-D][Tester B] The user-input genre type can't be saved #123 Changed the user guide to be more specific and enhanced file storage to save the available genre list --- docs/UserGuide.md | 2 +- .../java/seedu/bookbuddy/FileStorage.java | 24 +++++++++++++++---- .../seedu/bookbuddy/booklist/BookList.java | 8 +++++++ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 02c46cf4a2..67c8f1d2af 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -189,7 +189,7 @@ book2 : 6.57 PM, 09-04-2024 Sets the genre of a specific book based on the provided input and the provided index. -Format: `set-genre [BOOK_INDEX]` followed by `[NUMBER]` then `[CUSTOM_GENRE]` if 6 is entered +Format: `set-genre [BOOK_INDEX]` followed by `[NUMBER]` then `[CUSTOM_GENRE]` if last option is selected in the previous step Or for Pro Users: diff --git a/src/main/java/seedu/bookbuddy/FileStorage.java b/src/main/java/seedu/bookbuddy/FileStorage.java index 6f6c2a860b..c1fd16386c 100644 --- a/src/main/java/seedu/bookbuddy/FileStorage.java +++ b/src/main/java/seedu/bookbuddy/FileStorage.java @@ -3,14 +3,15 @@ import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.booklist.BookListModifier; -import java.util.Scanner; import java.io.File; -import java.io.IOException; import java.io.FileNotFoundException; import java.io.FileWriter; - - +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; import java.util.logging.Logger; + import static java.util.logging.Logger.getLogger; /** @@ -49,6 +50,16 @@ public FileStorage(BookList books) { */ public void readData(BookList books, File file) throws FileNotFoundException { Scanner sc = new Scanner(file); + if (sc.hasNextLine()) { + // Read the first line to get the genres + String genresLine = sc.nextLine().trim(); + // Assuming the line starts with "Genres: " + if (genresLine.startsWith("Genres: ")) { + String genresData = genresLine.substring(8); // Skip "Genres: " + List genres = Arrays.asList(genresData.split(",")); + BookList.setAvailableGenres(genres); // Update the available genres + } + } int lineNumber = 1; while (sc.hasNext()) { String line = sc.nextLine(); @@ -68,10 +79,13 @@ public void readData(BookList books, File file) throws FileNotFoundException { public void saveData(BookList books) throws IOException { File file = new File(FILE_PATH); FileWriter fw = new FileWriter(file); + + String genresLine = BookList.saveGenresFormat(); + fw.write("Genres: " + genresLine + '\n'); + for (int i = 1; i <= books.getSize(); i += 1) { fw.write(books.getBook(i).saveFormat() + '\n'); } - fw.close(); System.out.println("Writing successful. Data has been saved."); } diff --git a/src/main/java/seedu/bookbuddy/booklist/BookList.java b/src/main/java/seedu/bookbuddy/booklist/BookList.java index 4757745904..5bd72c3bbe 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookList.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookList.java @@ -12,6 +12,10 @@ * and marking book as read or unread. */ public class BookList { + public static void setAvailableGenres(List availableGenres) { + BookList.availableGenres = availableGenres; + } + protected static List availableGenres = new ArrayList<>(Arrays.asList("Fiction", "Non-Fiction", "Mystery", "Science Fiction", "Fantasy")); protected ArrayList books; @@ -53,4 +57,8 @@ public BookMain getBook(int index) throws BookNotFoundException { assert books.get(index - 1) instanceof BookMain : "Object at index should be an instance of Book"; return books.get(index - 1); } + + public static String saveGenresFormat() { + return String.join(",", availableGenres); + } } From 925208bb756974247378d347dbef50f5181a91ba Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 10 Apr 2024 06:02:44 +0800 Subject: [PATCH 215/311] Checkstyle fixes - wrong variable declaration order --- src/main/java/seedu/bookbuddy/booklist/BookList.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/booklist/BookList.java b/src/main/java/seedu/bookbuddy/booklist/BookList.java index 5bd72c3bbe..ad74303530 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookList.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookList.java @@ -12,10 +12,6 @@ * and marking book as read or unread. */ public class BookList { - public static void setAvailableGenres(List availableGenres) { - BookList.availableGenres = availableGenres; - } - protected static List availableGenres = new ArrayList<>(Arrays.asList("Fiction", "Non-Fiction", "Mystery", "Science Fiction", "Fantasy")); protected ArrayList books; @@ -25,7 +21,9 @@ public BookList() { public static List getAvailableGenres() { return availableGenres; } - + public static void setAvailableGenres(List availableGenres) { + BookList.availableGenres = availableGenres; + } /** * Constructs a new BookList instance with an empty list. */ From 1678a54060572fc54fde54d63fbb11363f83aa31 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 10 Apr 2024 14:41:19 +0800 Subject: [PATCH 216/311] Bug fix - DG Review By TA (Jai): Incorrect Notation In Sequence Diagram #108 Missing colons in the instance name --- docs/UML_Files/SetGenreSequenceDiagram.puml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/UML_Files/SetGenreSequenceDiagram.puml b/docs/UML_Files/SetGenreSequenceDiagram.puml index 694d722f76..4c4bdfb9c3 100644 --- a/docs/UML_Files/SetGenreSequenceDiagram.puml +++ b/docs/UML_Files/SetGenreSequenceDiagram.puml @@ -1,12 +1,12 @@ @startuml -participant User -participant "ParserMain" as ParserMain -participant "ParserGenre" as ParserGenre -participant "BookList" as BookList -participant "BookGenre" as BookGenre -participant "Ui" as Ui +participant ":User" as User +participant ":ParserMain" as ParserMain +participant ":ParserGenre" as ParserGenre +participant ":BookList" as BookList +participant ":BookGenre" as BookGenre +participant ":Ui" as Ui User -> ParserMain : set-genre [BOOK_INDEX] ParserMain -> ParserGenre : executeParseSetGenre(books, inputArray) @@ -36,6 +36,8 @@ end + + Flow: 1. The user initiates the set-genre command. 2. ParserMain processes the input and delegates the command to ParserGenre. From 955f809ddad244825a84d83c21340a4301a9614d Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 10 Apr 2024 14:42:39 +0800 Subject: [PATCH 217/311] Bug fix - DG Review By TA (Jai): Incorrect Notation In Sequence Diagram - 2 #110 Name of instances mentioned at the bottom also --- docs/UML_Files/SetGenreSequenceDiagram.puml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/UML_Files/SetGenreSequenceDiagram.puml b/docs/UML_Files/SetGenreSequenceDiagram.puml index 4c4bdfb9c3..609859528e 100644 --- a/docs/UML_Files/SetGenreSequenceDiagram.puml +++ b/docs/UML_Files/SetGenreSequenceDiagram.puml @@ -1,6 +1,7 @@ @startuml +hide footbox participant ":User" as User participant ":ParserMain" as ParserMain participant ":ParserGenre" as ParserGenre From eeb669b82bf9bff670ef7220763ac3bc0ed985c3 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 10 Apr 2024 14:51:19 +0800 Subject: [PATCH 218/311] Bug fixes - DG Review By TA (Jai): Missing Activation Bar In Sequence Diagram #109 Added the activation bars --- docs/UML_Files/SetGenreSequenceDiagram.puml | 41 ++++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/docs/UML_Files/SetGenreSequenceDiagram.puml b/docs/UML_Files/SetGenreSequenceDiagram.puml index 609859528e..128934d3cc 100644 --- a/docs/UML_Files/SetGenreSequenceDiagram.puml +++ b/docs/UML_Files/SetGenreSequenceDiagram.puml @@ -1,5 +1,3 @@ - - @startuml hide footbox participant ":User" as User @@ -10,35 +8,66 @@ participant ":BookGenre" as BookGenre participant ":Ui" as Ui User -> ParserMain : set-genre [BOOK_INDEX] +activate ParserMain ParserMain -> ParserGenre : executeParseSetGenre(books, inputArray) +activate ParserGenre ParserGenre -> BookList : validate index +activate BookList +BookList --> ParserGenre : indexIsValid +deactivate BookList ParserGenre -> ParserGenre : genreSelectionPrinter() -ParserGenre -> User : Display available genres +ParserGenre --> User : Display available genres +deactivate ParserGenre alt selecting existing genre User -> ParserGenre : Select genre number + activate ParserGenre ParserGenre -> BookList : getAvailableGenres() + activate BookList + BookList --> ParserGenre : availableGenresList + deactivate BookList ParserGenre -> BookGenre : setBookGenreByIndex(index, selectedGenre, books) + activate BookGenre BookGenre -> BookList : getBook(index) + activate BookList + BookList --> BookGenre : book + deactivate BookList BookGenre -> Ui : setGenreBookMessage(title, genre) - Ui -> User : confirmation message + activate Ui + Ui --> User : confirmation message + deactivate Ui + deactivate BookGenre + deactivate ParserGenre else adding new genre User -> ParserGenre : 6 (Add new genre) + activate ParserGenre ParserGenre -> User : Enter new genre User -> ParserGenre : Input custom genre ParserGenre -> BookList : Add new genre to list + activate BookList + BookList --> ParserGenre : newGenreAdded + deactivate BookList ParserGenre -> BookGenre : setBookGenreByIndex(index, newGenre, books) + activate BookGenre BookGenre -> BookList : getBook(index) + activate BookList + BookList --> BookGenre : book + deactivate BookList BookGenre -> Ui : setGenreBookMessage(title, genre) - Ui -> User : confirmation message + activate Ui + Ui --> User : confirmation message + deactivate Ui + deactivate BookGenre + deactivate ParserGenre end - +deactivate ParserMain @enduml + Flow: 1. The user initiates the set-genre command. 2. ParserMain processes the input and delegates the command to ParserGenre. From e38893be9653658073843bc8414691314fe2abb6 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Wed, 10 Apr 2024 16:08:46 +0800 Subject: [PATCH 219/311] Update Developer Guide to include sequence diagrams for set-label feature. --- docs/DeveloperGuide.md | 36 +++++++++++++++++- docs/UML_Files/SetLabelSequenceDiagram.puml | 15 ++++---- docs/UML_diagrams/SetGenreSequenceDiagram.png | Bin 59258 -> 70314 bytes docs/UML_diagrams/SetLabelSequenceDiagram.png | Bin 0 -> 23664 bytes 4 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 docs/UML_diagrams/SetLabelSequenceDiagram.png diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 0557697e7b..4de2d5ab7a 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -26,7 +26,41 @@ Reference to AB-3 diagrams code ## Design & implementation -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +### Labeling Books by Custom Labels +The labeling feature allows users to tag books with custom labels for easy identification and categorization based on personalized criteria. + +#### Overview +The process of labeling books is streamlined through several components within the system, each responsible for a part of the task: +`ParserMain`: Interprets the initial command and delegates to the specific parser. +`ParserLabel`: Processes the label-related commands. +`BookLabel`: Applies the custom label to an individual book. + +#### Architecture-Level Design +- The book management system continues to utilize a layered architectural approach, encompassing the UI, the command parser, and the data model layers. +- The `set-label` command operates within the command parser layer and communicates with the data model layer to assign labels to books. + +#### Component-Level Design + - `UI Layer`: Serves as the interface between the system and the user, where user commands are received and results are displayed. + - `ParserMain` (Command Parser Layer): Functions as the command interpreter, identifying the `set-label` command and directing it to the appropriate parser. + - `ParserLabel` (Command Parser Layer): Dedicated to the execution of the `set-label` command, it ensures that user input is correctly interpreted and validated. + - `BookList` (Data Model Layer): Holds the collection of books and facilitates operations on them, such as accessing and modifying book details. + - `BookLabel` (Data Model Layer): Implements the logic to update a book's label in the `BookList`. + +#### Implementation Details +- The user initiates the `set-label` command with the appropriate index and label. +- `ParserMain` catches the command and involves ParserLabel for further processing. +- `ParserLabel` retrieves the relevant book object from BookList using the provided index and assigns the new label. +- Once the label is set, `Ui` is called to deliver a confirmation message back to the user. + +#### Rationale for Design +- This design maintains consistency with the `set-genre` feature, providing a uniform command structure and user experience. +- It follows the Single Responsibility Principle, allowing each component to handle a specific part of the task without unnecessary dependencies. + +#### Alternatives Considered +- An alternative considered was to have the labeling functionality within a monolithic book management class, but this would have violated the separation of concerns, leading to a less maintainable and scalable system. + +![SetLabelSequenceDiagram.png](UML_diagrams/SetLabelSequenceDiagram.png) + ### Categorising the different books by their genres This functionality enables the categorization of books into distinct groups based on their genres, facilitating better diff --git a/docs/UML_Files/SetLabelSequenceDiagram.puml b/docs/UML_Files/SetLabelSequenceDiagram.puml index 12bcc7c013..bfee5f2de3 100644 --- a/docs/UML_Files/SetLabelSequenceDiagram.puml +++ b/docs/UML_Files/SetLabelSequenceDiagram.puml @@ -1,10 +1,11 @@ @startuml -participant User -participant "ParserMain" as ParserMain -participant "ParserLabel" as ParserLabel -participant "BookList" as BookList -participant "BookLabel" as BookLabel -participant "Ui" as Ui +hide footbox +participant ":User" as User +participant ":ParserMain" as ParserMain +participant ":ParserLabel" as ParserLabel +participant ":BookList" as BookList +participant ":BookLabel" as BookLabel +participant ":Ui" as Ui User -> ParserMain : label [BOOK_INDEX] [LABEL] activate ParserMain @@ -21,7 +22,7 @@ activate BookLabel BookLabel -> Ui : labelBookMessage(title, label) activate Ui -Ui -> User : confirmation message +Ui --> User : confirmation message deactivate Ui deactivate BookLabel diff --git a/docs/UML_diagrams/SetGenreSequenceDiagram.png b/docs/UML_diagrams/SetGenreSequenceDiagram.png index 782192839e615f36d8bb7bc7ab1c80ea67eb573a..4031eb0e9800b57dad55b3914d8c5f47aa1e4dfa 100644 GIT binary patch literal 70314 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

    vQ; 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⁡CN>b@IU)sRZ)bNGRO8O`cWiBK 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%0D3OH z+owske?N_|^QDAZ(1_xaz?P@X?mXWh1^giP2KcqdVaR>DC6$_?Vvqp<^*8V*RaeW% z$V_y9$o>|29T2MmeP;zIFT-I>6TK{#dgwJNgQx+jO_C|mphV)aaLRa|^GcR# z+_#3Vm`IQppo0v*D-LNt*?xJAo}6411ABl%l$_koMKGNBM;K_N3byB%Q8Ogg39qFU z&cakv^e0eee3PI9K5qlMAdl1e9`NY^fN#V?A%sqccoaBDp+BEyv%3l#4Oak*7qKhl zs_?T;kf*@Z9~7*XrhZu~X7%#;3SdS6(Q|O9lya2x94~kVKK54e_0Zq*W~u~n$>^s^ z&wUjSbZNyfzGxOa(Cv!(1$5^KK({ZglID-p^H*khom1rjlt_Ur-=yiR?GQlI3MJ{B zu`z(+7A4=-sM$5+vjv|8Q_A7xTR3k&3~!&a5(_ezP2&(4+zE5&Q)odeeM?OEj{tR% zscW}VYHF^B6=lI-ejK?9HQpDVVcnkWDu8yvx&&7drT;H+1=-wcDoh;`abN(ra#VW3 z2y17moo(pvkZ+BVc9a&f=-iikyxIeg-PK`){33^Xrl>C6e*Ab)*PV%bTd%R~gF9|n zI9t*d82d>0^y$-EB6pLi&9WWbSRx`K$UN-2=V6XcfsU)|JmsIM`)v>T0NF>pG}2GC z&NUQg3QE}94cc5FiVB)?(+?Ua;QrzV7hy z$g1F-AV>nSp3u>(S~H=C3ATzPm&atZ$BrL&8f#>zbkuIw;!9dPT^xB3=$%0|l_tBz zAav!LWv@A3zFg)>NhptFG7>8=AZ)u^bC$*o(l#@A(Kh4CaPJTUMA zw87rN_`x9lS6E@duOZL-E^YLzo~mGiO(77{TN;s1?Wa0}g5h_E@i;A-cZ%bMTb$q1 z| z?(Lm@sr;1JQTIHVUBLNc;84Pf3>)A9CBUL|LB^LJU=b0RV zaF=xcu4zWq`I+Xx^zc+Sq|o#CF>|XL>q)=`7QiLBmEBd*%N`-sLVY_GoASZqbp_nu zM#TB4>{FHln=YU^0^oERu!(VdV{-c<=8B8JZB%j<4;q1Uy<4)bY8e;=)J(q>{loDL ztcTnad|%+;aqwUwv^E6pXZ$le;?Z?hV6#}{zII1fms4(nDX`c8t!D;q!UZnhe)Bc| z;z#5<>1(&vj9#Rm0CrV#HpPpZ0F$(u+B48%4q#aYZ;+ri34nDVsG+t}Wx|!U(cixU zOYWyr!!siPY!W>k5vKoo;nyggWXaR7Qf;@yb*Jhi-+T#N;$g7 Date: Wed, 10 Apr 2024 18:06:05 +0800 Subject: [PATCH 220/311] Documentation and refactoring --- src/main/java/seedu/bookbuddy/book/Read.java | 12 +++++++++++ .../bookdetailsmodifier/BookMark.java | 20 +++++++++++++++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/book/Read.java b/src/main/java/seedu/bookbuddy/book/Read.java index 4fa49f935d..1b1150ed31 100644 --- a/src/main/java/seedu/bookbuddy/book/Read.java +++ b/src/main/java/seedu/bookbuddy/book/Read.java @@ -13,10 +13,22 @@ public static boolean getRead(BookMain book) { return book.isRead; } + /** + * Returns the date and time read attribute of the book in string form. + * + * @param book + * @return The date and time read of the book. + */ public static String getDateTimeRead(BookMain book) { return book.datetimeread; } + /** + * Sets the books read status as unread or read + * + * @param book + * @param read the read status of the book + */ public static void setRead(BookMain book, boolean read) { book.isRead = read; if (read) { diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java index b21ad5a954..90ab41f809 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java @@ -94,13 +94,28 @@ public static void markBookAsUnread(BookMain book) { /** * Prints all books sorted by date in descending order. + * + * @param books */ public static void printBooksByDateRead(BookList books) { if (books.getBooks().isEmpty()) { System.out.println("The list is empty. Add books by 'add [book]'"); return; + } else { + ArrayList sortedlist = sortBooksByDateRead(books); + for (BookMain book : sortedlist) { + String datetime = Read.getDateTimeRead(book); + System.out.println(Title.getTitle(book) + " : " + datetime); + } } + } + /** + * Sorts all books sorted by date in descending order. + * + * @param books + */ + public static ArrayList sortBooksByDateRead(BookList books) { ArrayList bookRead = new ArrayList<>(); for (BookMain book : books.getBooks()) { if (Read.getRead(book)) { @@ -115,9 +130,6 @@ public static void printBooksByDateRead(BookList books) { DateTimeUtils.convertStringToDateTime(dateTimeStr1)); }); - for (BookMain book : bookRead) { - String datetime = Read.getDateTimeRead(book); - System.out.println(Title.getTitle(book) + " : " + datetime); - } + return bookRead; } } From d7ed9095043ff0a7404ac374c944a2bf1d46eac0 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Wed, 10 Apr 2024 22:03:32 +0800 Subject: [PATCH 221/311] Refactor printbookdbydateread method --- .../bookdetailsmodifier/BookMark.java | 5 +++-- .../seedu/bookbuddy/parser/ParserMain.java | 13 ++---------- .../parser/parsercommands/ParserList.java | 20 +++++++++++++++++++ 3 files changed, 25 insertions(+), 13 deletions(-) create mode 100644 src/main/java/seedu/bookbuddy/parser/parsercommands/ParserList.java diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java index 90ab41f809..72c755468e 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookMark.java @@ -98,11 +98,12 @@ public static void markBookAsUnread(BookMain book) { * @param books */ public static void printBooksByDateRead(BookList books) { + ArrayList sortedlist = sortBooksByDateRead(books); if (books.getBooks().isEmpty()) { System.out.println("The list is empty. Add books by 'add [book]'"); - return; + } else if (sortedlist.isEmpty()){ + System.out.println("You have not read any books yet! Marks books by 'mark [BOOK_INDEX]'"); } else { - ArrayList sortedlist = sortBooksByDateRead(books); for (BookMain book : sortedlist) { String datetime = Read.getDateTimeRead(book); System.out.println(Title.getTitle(book) + " : " + datetime); diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index 9e22f0aaff..3dff196736 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -6,16 +6,7 @@ import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.Ui; import seedu.bookbuddy.bookdetailsmodifier.BookRating; -import seedu.bookbuddy.parser.parsercommands.ParserFind; -import seedu.bookbuddy.parser.parsercommands.ParserAdd; -import seedu.bookbuddy.parser.parsercommands.ParserRemove; -import seedu.bookbuddy.parser.parsercommands.ParserMark; -import seedu.bookbuddy.parser.parsercommands.ParserGenre; -import seedu.bookbuddy.parser.parsercommands.ParserDisplay; -import seedu.bookbuddy.parser.parsercommands.ParserRating; -import seedu.bookbuddy.parser.parsercommands.ParserUnmark; -import seedu.bookbuddy.parser.parsercommands.ParserLabel; -import seedu.bookbuddy.parser.parsercommands.ParserSummary; +import seedu.bookbuddy.parser.parsercommands.*; import seedu.bookbuddy.parser.parservalidation.CommandList; import seedu.bookbuddy.parser.parservalidation.Exceptions; @@ -50,7 +41,7 @@ public static void parseCommand(String input, BookList books) { ParserRemove.executeParseRemove(books, inputArray); break; case CommandList.LIST_COMMAND: - BookDisplay.printAllBooks(books); + ParserList.executeParseList(books, inputArray); break; case CommandList.MARK_COMMAND: ParserMark.executeParseMark(books, inputArray); diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserList.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserList.java new file mode 100644 index 0000000000..9c589c2b3b --- /dev/null +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserList.java @@ -0,0 +1,20 @@ +package seedu.bookbuddy.parser.parsercommands; + +import seedu.bookbuddy.bookdetailsmodifier.BookDisplay; +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.booklist.BookListModifier; +import seedu.bookbuddy.parser.parservalidation.Exceptions; + +public class ParserList { + private static void parseList(BookList books, String[] inputArray) { + if (inputArray.length == 1) { + BookDisplay.printAllBooks(books); + } else { + System.out.println("The list command does not require any further arguments, just type `list` "); + } + } + + public static void executeParseList (BookList books, String[] inputArray) { + parseList(books, inputArray); + } +} From bb3302fae808484722a6d280db0893cba076b297 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 11 Apr 2024 00:29:45 +0800 Subject: [PATCH 222/311] Bug fix - immutable list cannot be modified. Changed it to be modifiable --- src/main/java/seedu/bookbuddy/booklist/BookList.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/booklist/BookList.java b/src/main/java/seedu/bookbuddy/booklist/BookList.java index ad74303530..501ef2d620 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookList.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookList.java @@ -22,7 +22,7 @@ public static List getAvailableGenres() { return availableGenres; } public static void setAvailableGenres(List availableGenres) { - BookList.availableGenres = availableGenres; + BookList.availableGenres = new ArrayList<>(availableGenres); } /** * Constructs a new BookList instance with an empty list. From 1ae949038ab1b483ddd3cfac575cb2736e783bd4 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Thu, 11 Apr 2024 00:34:15 +0800 Subject: [PATCH 223/311] Bug FIx: Mishandling for incorrect user commands for METHOD: list --- .../java/seedu/bookbuddy/parser/ParserMain.java | 14 ++++++++++++-- .../parser/parsercommands/ParserList.java | 9 +++------ .../parser/parservalidation/Exceptions.java | 2 +- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index 3dff196736..5333f649fb 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -1,12 +1,22 @@ package seedu.bookbuddy.parser; import exceptions.UnsupportedCommandException; -import seedu.bookbuddy.bookdetailsmodifier.BookDisplay; + import seedu.bookbuddy.bookdetailsmodifier.BookMark; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.Ui; import seedu.bookbuddy.bookdetailsmodifier.BookRating; -import seedu.bookbuddy.parser.parsercommands.*; +import seedu.bookbuddy.parser.parsercommands.ParserFind; +import seedu.bookbuddy.parser.parsercommands.ParserAdd; +import seedu.bookbuddy.parser.parsercommands.ParserRemove; +import seedu.bookbuddy.parser.parsercommands.ParserMark; +import seedu.bookbuddy.parser.parsercommands.ParserGenre; +import seedu.bookbuddy.parser.parsercommands.ParserDisplay; +import seedu.bookbuddy.parser.parsercommands.ParserRating; +import seedu.bookbuddy.parser.parsercommands.ParserUnmark; +import seedu.bookbuddy.parser.parsercommands.ParserLabel; +import seedu.bookbuddy.parser.parsercommands.ParserSummary; +import seedu.bookbuddy.parser.parsercommands.ParserList; import seedu.bookbuddy.parser.parservalidation.CommandList; import seedu.bookbuddy.parser.parservalidation.Exceptions; diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserList.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserList.java index 9c589c2b3b..0eab364a70 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserList.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserList.java @@ -2,16 +2,13 @@ import seedu.bookbuddy.bookdetailsmodifier.BookDisplay; import seedu.bookbuddy.booklist.BookList; -import seedu.bookbuddy.booklist.BookListModifier; import seedu.bookbuddy.parser.parservalidation.Exceptions; public class ParserList { private static void parseList(BookList books, String[] inputArray) { - if (inputArray.length == 1) { - BookDisplay.printAllBooks(books); - } else { - System.out.println("The list command does not require any further arguments, just type `list` "); - } + Exceptions.validateCommandArguments(inputArray,1, + "The list command does not require any further arguments, just type `list` u absolute donut "); + BookDisplay.printAllBooks(books); } public static void executeParseList (BookList books, String[] inputArray) { diff --git a/src/main/java/seedu/bookbuddy/parser/parservalidation/Exceptions.java b/src/main/java/seedu/bookbuddy/parser/parservalidation/Exceptions.java index 0c54a14ec8..47cf39e54b 100644 --- a/src/main/java/seedu/bookbuddy/parser/parservalidation/Exceptions.java +++ b/src/main/java/seedu/bookbuddy/parser/parservalidation/Exceptions.java @@ -10,7 +10,7 @@ public class Exceptions { public static void validateCommandArguments(String[] inputArray, int requiredArgs, String errorMessage) throws InvalidCommandArgumentException { - if (inputArray.length < requiredArgs) { + if (inputArray.length != requiredArgs) { LOGGER.log(Level.WARNING, errorMessage, inputArray); throw new InvalidCommandArgumentException(errorMessage); } From 264917bb36689827263789ad078ed87465c8d167 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Thu, 11 Apr 2024 00:57:10 +0800 Subject: [PATCH 224/311] FIx bugs for all list commands --- .../seedu/bookbuddy/parser/ParserMain.java | 21 ++++++----- .../parser/parsercommands/ParserList.java | 36 +++++++++++++++---- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index 5333f649fb..fa35aad46b 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -2,10 +2,8 @@ import exceptions.UnsupportedCommandException; -import seedu.bookbuddy.bookdetailsmodifier.BookMark; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.Ui; -import seedu.bookbuddy.bookdetailsmodifier.BookRating; import seedu.bookbuddy.parser.parsercommands.ParserFind; import seedu.bookbuddy.parser.parsercommands.ParserAdd; import seedu.bookbuddy.parser.parsercommands.ParserRemove; @@ -40,9 +38,11 @@ public static void parseCommand(String input, BookList books) { String[] inputArray = input.split(" ", 2); String command = inputArray[0].toLowerCase(); LOGGER.log(Level.FINE, "Parsing command: {0}", command); - int index; try { + if (command.contains("list")) { + ParserList.executeParseList(books, inputArray, command); + } switch (command) { case CommandList.ADD_COMMAND: ParserAdd.executeParseAdd(books, inputArray); @@ -51,7 +51,13 @@ public static void parseCommand(String input, BookList books) { ParserRemove.executeParseRemove(books, inputArray); break; case CommandList.LIST_COMMAND: - ParserList.executeParseList(books, inputArray); + //Empty case, all list commands handled in if block + break; + case CommandList.PRINT_ORDERED_COMMAND: + //Empty case, all list commands handled in if block + break; + case CommandList.PRINT_ORDERED_DATE_COMMAND: + //Empty case, all list commands handled in if block break; case CommandList.MARK_COMMAND: ParserMark.executeParseMark(books, inputArray); @@ -62,7 +68,6 @@ public static void parseCommand(String input, BookList books) { case CommandList.HELP_COMMAND: Ui.helpMessage(); break; - case CommandList.FIND_TITLE_COMMAND: ParserFind.parseTitle(books, inputArray[1]); break; @@ -101,12 +106,6 @@ public static void parseCommand(String input, BookList books) { case CommandList.DISPLAY_COMMAND: ParserDisplay.executeParseAdd(books, inputArray); break; - case CommandList.PRINT_ORDERED_COMMAND: - BookRating.printBooksByRating(books); - break; - case CommandList.PRINT_ORDERED_DATE_COMMAND: - BookMark.printBooksByDateRead(books); - break; default: LOGGER.log(Level.WARNING, "Sorry but that is not a valid command. Please try again", command); throw new UnsupportedCommandException("Sorry but that is not a valid command. " + diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserList.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserList.java index 0eab364a70..674e29e17e 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserList.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserList.java @@ -1,17 +1,41 @@ package seedu.bookbuddy.parser.parsercommands; +import exceptions.UnsupportedCommandException; import seedu.bookbuddy.bookdetailsmodifier.BookDisplay; +import seedu.bookbuddy.bookdetailsmodifier.BookMark; +import seedu.bookbuddy.bookdetailsmodifier.BookRating; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.parser.parservalidation.Exceptions; +import seedu.bookbuddy.parser.parservalidation.CommandList; + +import java.util.logging.Level; + +import static seedu.bookbuddy.BookBuddy.LOGGER; public class ParserList { - private static void parseList(BookList books, String[] inputArray) { - Exceptions.validateCommandArguments(inputArray,1, - "The list command does not require any further arguments, just type `list` u absolute donut "); - BookDisplay.printAllBooks(books); + private static void parseList(BookList books, String[] inputArray, String command) { + Exceptions.validateCommandArguments(inputArray, 1, + "ALl the list commands do not require any further arguments, just type `list`," + + " `list-rated` or `list-by-date` u absolute donut "); + switch (command) { + case CommandList.LIST_COMMAND: + BookDisplay.printAllBooks(books); + break; + case CommandList.PRINT_ORDERED_COMMAND: + BookRating.printBooksByRating(books); + break; + case CommandList.PRINT_ORDERED_DATE_COMMAND: + BookMark.printBooksByDateRead(books); + break; + default: + LOGGER.log(Level.WARNING, "Sorry but that is not a valid list command. Please try again", command); + throw new UnsupportedCommandException("Sorry but that is not a valid list command. " + + "Please try again or type: help"); + } } - public static void executeParseList (BookList books, String[] inputArray) { - parseList(books, inputArray); + + public static void executeParseList (BookList books, String[] inputArray, String command) { + parseList(books, inputArray, command); } } From 9427462d7e39a7ee1b6ab77e007064fd666eb7e7 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Thu, 11 Apr 2024 02:35:29 +0800 Subject: [PATCH 225/311] Update User Guide for improved readability --- docs/UserGuide.md | 320 +++++++++++++------------- docs/team/joshuahoky.md | 6 + src/main/java/seedu/bookbuddy/Ui.java | 7 +- 3 files changed, 167 insertions(+), 166 deletions(-) create mode 100644 docs/team/joshuahoky.md diff --git a/docs/UserGuide.md b/docs/UserGuide.md index 67c8f1d2af..fb54d53657 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -26,7 +26,7 @@ experience. * [list](#viewing-all-books-list) * [mark](#marking-a-book-as-read-mark) * [unmark](#marking-a-book-as-unread-unmark) - * [list-by-date](#listing-read-books-by-date) + * [list-by-date](#listing-read-books-by-date-list-by-date) * [set-genre](#setting-the-genre-of-a-book-set-genre) * [label](#labelling-a-book-label) * [give-summary](#adding-a-book-summary-give-summary) @@ -35,6 +35,10 @@ experience. * [display](#displaying-the-details-of-a-book-display) * [find-title](#finding-a-book-by-title-find-title) * [find-genre](#finding-a-book-by-genre-find-genre) + * [find-read](#find-books-that-are-read-find-read) + * [find-unread](#find-books-that-are-unread-find-unread) + * [find-label](#find-books-that-are-labelled-find-label) + * [find-rate](#find-books-that-are-labelled-find-rate) * [bye](#exiting-the-program-bye) * [FAQ](#faq) * [Command Summary](#command-summary) @@ -54,120 +58,115 @@ View all the commands available in BookBuddy and specific instructions on how th Format: `help` -Example usage: +Example of usage with expected output: ``` +//input help -``` -Example output: -```` + +//output Here's a list of commands to get you started!! add [BOOK_TITLE] -> to add new books to the list remove [BOOK_INDEX] -> to remove a book from the list list -> to show whole list of added books mark [BOOK_INDEX] -> to mark book as read [R] unmark [BOOK_INDEX] -> to mark book as unread [U] -set-genre [BOOK_INDEX] -> to set a genre for a book +list-by-date -> to print out all books sorted in descending order of date +(basic) set-genre [BOOK_INDEX] -> to set a genre for a book +(advanced) set-genre [BOOK_INDEX] [GENRE] -> to set a genre for a book label [BOOK_INDEX] [LABEL] -> to set a label for a book give-summary [BOOK_INDEX] [BOOK_SUMMARY] -> to give a book a summary rate [BOOK_INDEX] [BOOK_RATING] -> to rate a book from 1-5 list-rated -> to sort books by rating in descending order display [BOOK_INDEX] -> to view more details about a book find-title [KEYWORD] -> to find books with keyword in their title -(advanced)find-genre [GENRE] -> to find books under specific genres -find-genre -> to find books under specific genres +(basic) find-genre -> to find books under specific genres +(advanced) find-genre [GENRE] -> to find books under specific genres find-read -> to find list of books that are read find-unread -> to find list of books that are unread find-label [KEYWORD] -> to find list of books that stored under a certain label find-rate [RATING] -> to find list of books with specified rating bye -> to exit BookBuddy software -```` +``` ### Adding a book: `add` Adds a new book to the book list. Format: `add [BOOK_TITLE]` -Example usage: +Example of usage with expected output: ``` +//input add Harry Potter -``` -Example output: -```` + +//output okii added [Harry Potter] to the list. remember to read it soon.... -```` +``` ### Removing a book: `remove` Removes a specific book from the book list by its index. Format: `remove [BOOK_INDEX]` -Example of usage: +Example of usage with expected output: ``` +//input remove 1 -``` -Example output - -```` +//output alright.. i've removed Harry Potter from the list. -```` +``` ### Viewing all books: `list` Shows all books stored in the list along with their titles and read or unread status. Format: `list` -Example usage: +Example of usage with expected output: ``` +//input list -``` -Example output - -```` +//output All books: 1. [U] Harry Potter 2. [U] Geronimo Stilton -```` +``` ### Marking a book as read: `mark` -Changes the status of a specific book to read. +Changes the status of a specific book to read and records the current date +and time in which it is marked. Format: `mark [BOOK_INDEX]` -Example of usage: +Example of usage with expected output: ``` +//input mark 1 -``` -Example output: - -```` -Successfully marked Harry Potter as read. -```` +//output +Successfully marked [Harry Potter] as read. +``` ### Marking a book as unread: `unmark` Changes the status of a specific book to unread. Format: `unmark [BOOK_INDEX]` -Example of usage: +Example of usage with expected output: ``` +//input unmark 1 -``` -Example output: -```` +//output Successfully marked Harry Potter as unread. -```` - -### Listing-read-books-by-date `list-by-date` +``` +### Listing read books by date: `list-by-date` Lists all read books by descending order of date read. Format: `list-by-date` @@ -183,19 +182,13 @@ booky : 6.57 PM, 09-04-2024 book2 : 6.57 PM, 09-04-2024 ``` - - ### Setting the genre of a book: `set-genre` - Sets the genre of a specific book based on the provided input and the provided index. -Format: `set-genre [BOOK_INDEX]` followed by `[NUMBER]` then `[CUSTOM_GENRE]` if last option is selected -in the previous step - -Or for Pro Users: - -Format: `set-genre [BOOK_INDEX] [GENRE]` +For new users: +Format: `set-genre [BOOK_INDEX]` followed by `[NUMBER]` then `[GENRE]` if last option is selected +in the previous step Example of usage with expected output: @@ -227,155 +220,139 @@ okii categorised [animal farm] as [satire] remember to read it soon.... ```` +For advanced users: + +Format: `set-genre [BOOK_INDEX] [GENRE]` + +Example of usage with expected output: + ```` //input set-genre 1 Fiction -okii categorised [Harry Potter] as [Fiction] -remember to read it soon.... //output -Genre set to Fiction for book at index 1 +okii categorised [Harry Potter] as [Fiction] +remember to read it soon.... ```` ### Labelling a book: `label` - Sets the label of a specific book to the provided input by its index. Format: `label [BOOK_INDEX] [LABEL]` -Example of usage: +Example of usage with expected output: ``` +//input label 1 very cool -``` -Example output: - -```` +//output okii labeled [Harry Potter] as [very cool] remember to read it soon.... -```` +``` ### Adding a book summary: `give-summary` Provides a summary for the specified book. Format: `give-summary [BOOK_INDEX] [BOOK_SUMMARY]` -Example of usage: +Example of usage with expected output: ``` +//input give-summary 1 A book about a young boy who is invited to study at Hogwarts. -``` -Example output: -```` +//output okii you have written: [A book about a young boy who is invited to study at Hogwarts.] for the book: [Harry Potter] remember to read it soon.... -```` +``` ### Rating a book: `rate` Assigns a rating to a specific book, from a scale of 1-5. Format: `rate [BOOK_INDEX] [BOOK_RATING]` -Example of usage: +Example of usage with expected output: ``` +//input rate 1 3 -``` -Example output: - -```` +//output okii set rating for [Harry Potter] as [3] remember to read it soon.... -```` +``` ### Sorting books by rating: `list-rated` Prints a list of books and their ratings in descending order. Format: `list-rated` -Example of usage: +Example of usage with expected output: ``` +//input list-rated -``` - -Example output: -```` +//output Books sorted by rating: The Boy in Striped Pyjamas - 5 Geronimo Stilton - 4 Harry Potter - 3 -```` +``` ### Displaying the details of a book: `display` Gives more detailed information about a specific book like its genre, label and summary. Format: `display [BOOK_INDEX]` -Example of usage: +Example of usage with expected output: ``` +//input display 1 -``` -Example output: -```` +//output Here are the details of your book: Title: Harry Potter -Status: Read +Status: Read on 1.10 AM, 11-04-2024 Label: very cool Genre: No genre provided Rating: 3 Summary: A book about a young boy who is invited to study at Hogwarts. -```` +``` ### Finding a book by title: `find-title` Returns all books in the book list that contain the keyword in their title. +The keyword is not case-sensitive. -Format: `find-title` +Format: `find-title [KEYWORD]` -Example of usage: +Example of usage with expected output: ``` -find-title Harry -``` - -Example output: +//input +find-title harry -```` +//output +books with [harry] in the title: 1. [R] Harry Potter -```` +``` ### Finding a book by genre: `find-genre` Returns all books in the saved book list that are stored under the matching genre. +The keyword is not case-sensitive. -Format: `find-genre [KEYWORD]` or `find-genre` where user will receive a prompt. +For new users: -Example of usage with expected output: +Format: `find-genre` followed by `[NUMBER]` -for more advanced users +Example of usage with expected output: ``` //input find-genre -``` -```` -//ouput -___________________________________ -fiction books: -1. [U] harry potter -_____________ -```` -for basic users -``` -//input -find-genre -``` -```` + //ouput Available genres: 1. Fiction @@ -384,99 +361,110 @@ Available genres: 4. Science Fiction 5. Fantasy Enter the number for the desired genre: -```` -``` + //input 1 -``` -```` -//ouput -___________________________________ + +//output fiction books: 1. [U] harry potter -_____________ ```` +For advanced users: + +Format: `find-genre [KEYWORD]` + +Example of usage with expected output: + +``` +//input +find-genre fiction + +//output +fiction books: +1. [U] Harry Potter +``` + ### Find books that are read: `find-read` Returns all books in the saved book list that are marked read. Format: `find-read` Example of usage with expected output: -```` + +``` //input find-read -```` -```` -//ouput + +//output 1. [R] harry potter -```` +``` + ### Find books that are unread: `find-unread` Returns all books in the saved book list that are marked unread. Format: `find-unread` Example of usage with expected output: -```` + +``` //input find-unread -```` -```` -//ouput -1. [U] geronimo stilton + +//output +1. [U] Geronimo Stilton 2. [U] The Boy in Striped Pyjamas -```` +``` + ### Find books that are labelled: `find-label` Returns all books in the saved book list that stored under specific label. +The keyword is not case-sensitive. Format: `find-label [KEYWORD]` Example of usage with expected output: -```` + +``` //input find-label very cool -```` -```` -//ouput -___________________________________ + +//output books with [very cool] in their label: 1. [R] harry potter -_____________ -```` +``` + ### Find books that are labelled: `find-rate` Returns all books in the saved book list that have specific rating. Format: `find-rate [RATING]` Example of usage with expected output: -```` + +``` //input find-rate 3 -```` -```` -//ouput -___________________________________ + +//output books rated [3] : 1. [R] harry potter -_____________ -```` +``` + ### Exiting the program: `bye` Exits the application and saves all tasks in a file. Format: `bye` -Example of usage: +Example of usage with expected output: ``` +//input bye -``` -Example output: -```` +//output Writing successful. Data has been saved. Thank you for using BookBuddy! Hope to see you again keke :) -```` +``` ## FAQ @@ -497,21 +485,27 @@ to be saved.** ## Command Summary -* View commands: `help` -* Add book: `add [BOOK_TITLE]` -* Remove book: `remove [BOOK_INDEX]` -* View all books: `list` -* Mark book as read: `mark [BOOK_INDEX]` -* Mark book as unread: `unmark [BOOK_INDEX]` -* Set genre: `set-genre [BOOK_INDEX] [GENRE]` (Single-Step for Pro users) -* Set genre: `set-genre [BOOK_INDEX]` followed by `[NUMBER]` then `[CUSTOM_GENRE]` if necessary (Multi-Step) -* Label book: `label [BOOK_INDEX] [LABEL]` -* Add summary: `give-summary [BOOK_INDEX] [BOOK_SUMMARY]` -* Rate a book: `rate [BOOK_INDEX] [BOOK_RATING]` -* Sort books by rating: `list-rated` -* Display details: `display [BOOK_INDEX]` -* Find books with specific title: `find-title [KEYWORD]` -* Find books with specific genre: `find-genre [KEYWORD]` -* Find books that are read: `find-read` -* Find books that are unread: `find-unread` -* Exit program: `bye` +| Purpose of Command | Command Syntax | +|-----------------------------------------------------------------|-----------------------------------------------------------------------------| +| View all commands | `help` | +| Add a book | `add [BOOK_TITLE]` | +| Remove a book | `remove [BOOK_INDEX]` | +| View all books | `list` | +| Mark book as read | `mark [BOOK_INDEX]` | +| Mark book as unread | `unmark [BOOK_INDEX]` | +| Listing read books by date | `list-by-date` | +| Set genre (Single-step for advanced users) | `set-genre [BOOK_INDEX] [GENRE]` | +| Set genre (Multi-step for new users) | `set-genre [BOOK_INDEX]` followed by `[NUMBER]` then `[GENRE]` if necessary | +| Label book | `label [BOOK_INDEX] [LABEL]` | +| Add summary for book | `give-summary [BOOK_INDEX] [BOOK_SUMMARY]` | +| Rate book | `rate [BOOK_INDEX] [BOOK_RATING]` | +| Sort books by rating | `list-rated` | +| Display details of book | `display [BOOK_INDEX]` | +| Find books with specific title | `find-title [KEYWORD]` | +| Find books with specific genre (Single-step for advanced users) | `find-genre [GENRE]` | +| Find books with specific genre (Multi-step for new users) | `find-genre` followed by `[NUMBER]` | +| Find books that are read | `find-read` | +| Find books that are unread | `find-unread` | +| Find books with specific label | `find-label` | +| Find books with specific rating | `find-rate` | +| Exit program | `bye` | diff --git a/docs/team/joshuahoky.md b/docs/team/joshuahoky.md new file mode 100644 index 0000000000..50eb19b8ef --- /dev/null +++ b/docs/team/joshuahoky.md @@ -0,0 +1,6 @@ +# Joshua Ho - Project Portfolio Page + +## Overview + + +### Summary of Contributions diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 3530fcf3fa..ae0d79f74b 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -73,15 +73,16 @@ public static void helpMessage() { System.out.println("mark [BOOK_INDEX] -> to mark book as read [R]"); System.out.println("unmark [BOOK_INDEX] -> to mark book as unread [U]"); System.out.println("list-by-date -> to print out all books sorted in descending order of date"); - System.out.println("set-genre [BOOK_INDEX] -> to set a genre for a book"); + System.out.println("(basic) set-genre [BOOK_INDEX] -> to set a genre for a book"); + System.out.println("(advanced) set-genre [BOOK_INDEX] [GENRE] -> to set a genre for a book"); System.out.println("label [BOOK_INDEX] [LABEL] -> to set a label for a book"); System.out.println("give-summary [BOOK_INDEX] [BOOK_SUMMARY] -> to give a book a summary"); System.out.println("rate [BOOK_INDEX] [BOOK_RATING] -> to rate a book from 1-5"); System.out.println("list-rated -> to sort books by rating in descending order"); System.out.println("display [BOOK_INDEX] -> to view more details about a book"); System.out.println("find-title [KEYWORD] -> to find books with keyword in their title"); - System.out.println("(advanced)find-genre [GENRE] -> to find books under specific genres"); - System.out.println("find-genre -> to find books under specific genres"); + System.out.println("(basic) find-genre -> to find books under specific genres"); + System.out.println("(advanced) find-genre [GENRE] -> to find books under specific genres"); System.out.println("find-read -> to find list of books that are read"); System.out.println("find-unread -> to find list of books that are unread"); System.out.println("find-label [KEYWORD] -> to find list of books that stored under a certain label"); From 85274a176cb37b86ecb1cc6f222ffb6d7891e450 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Thu, 11 Apr 2024 03:02:28 +0800 Subject: [PATCH 226/311] Fix bug regarding message displayed when removing book from empty list --- .../bookbuddy/bookdetailsmodifier/BookDisplay.java | 3 --- .../seedu/bookbuddy/booklist/BookListModifier.java | 11 ++++++++--- src/main/java/seedu/bookbuddy/parser/ParserMain.java | 1 - .../bookbuddy/parser/parsercommands/ParserList.java | 1 - 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index 88eb20cc9d..987b9712f6 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -1,6 +1,5 @@ package seedu.bookbuddy.bookdetailsmodifier; - import seedu.bookbuddy.Ui; import seedu.bookbuddy.book.BookMain; import seedu.bookbuddy.book.Genre; @@ -11,7 +10,6 @@ import seedu.bookbuddy.book.Title; import seedu.bookbuddy.booklist.BookList; - public class BookDisplay { //@@author joshuahoky @@ -61,5 +59,4 @@ public static void printAllBooks(BookList bookList) { System.out.println("The list is empty. Add books by 'add [book]'"); } } - } diff --git a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java index dee6c6e8c1..5e1c8691a5 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java @@ -1,5 +1,6 @@ package seedu.bookbuddy.booklist; +import exceptions.BookNotFoundException; import seedu.bookbuddy.book.BookMain; import seedu.bookbuddy.Ui; @@ -53,9 +54,13 @@ public static void addBook(BookList bookList, String title) { */ public static void deleteBook(BookList bookList, int index) throws IndexOutOfBoundsException { try { - Ui.removeBookMessage(index, bookList); - bookList.books.remove(index - 1); - assert bookList.books.size() >= 0 : "Book list size should not be negative after deletion"; + if (bookList.getBooks().isEmpty()) { + System.out.println("Unable to remove book as the list is empty."); + } else { + Ui.removeBookMessage(index, bookList); + bookList.books.remove(index - 1); + assert bookList.books.size() >= 0 : "Book list size should not be negative after deletion"; + } } catch (IndexOutOfBoundsException e) { System.out.println("Invalid book index. Please enter a valid index"); } catch (Exception e) { diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index fa35aad46b..469c0482bc 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -115,5 +115,4 @@ public static void parseCommand(String input, BookList books) { Exceptions.handleException(e, command, inputArray); } } - } diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserList.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserList.java index 674e29e17e..bba4e90f31 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserList.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserList.java @@ -34,7 +34,6 @@ private static void parseList(BookList books, String[] inputArray, String comman } } - public static void executeParseList (BookList books, String[] inputArray, String command) { parseList(books, inputArray, command); } From 688503c6d3180ad15e2e951318e2ee4d25a9e7c4 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Thu, 11 Apr 2024 03:06:20 +0800 Subject: [PATCH 227/311] Fix checkstyle --- src/main/java/seedu/bookbuddy/booklist/BookListModifier.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java index 5e1c8691a5..9d5f2b6739 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java @@ -1,6 +1,5 @@ package seedu.bookbuddy.booklist; -import exceptions.BookNotFoundException; import seedu.bookbuddy.book.BookMain; import seedu.bookbuddy.Ui; From 6833b5ea5789a7d1b25ce2ab6751f0d5545c6495 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Thu, 11 Apr 2024 03:13:19 +0800 Subject: [PATCH 228/311] Add author for book --- src/main/java/seedu/bookbuddy/Ui.java | 6 +++++ .../java/seedu/bookbuddy/book/Author.java | 23 ++++++++++++++++ .../java/seedu/bookbuddy/book/BookMain.java | 9 +++++-- .../bookdetailsmodifier/BookAuthor.java | 25 ++++++++++++++++++ .../bookdetailsmodifier/BookDisplay.java | 10 +++---- .../bookbuddy/booklist/BookListModifier.java | 3 ++- .../seedu/bookbuddy/parser/ParserMain.java | 16 +++--------- .../parser/parsercommands/ParserAdd.java | 4 +++ .../parser/parsercommands/ParserAuthor.java | 26 +++++++++++++++++++ .../parser/parsercommands/ParserSummary.java | 4 +-- .../parser/parservalidation/CommandList.java | 1 + 11 files changed, 103 insertions(+), 24 deletions(-) create mode 100644 src/main/java/seedu/bookbuddy/book/Author.java create mode 100644 src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookAuthor.java create mode 100644 src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAuthor.java diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 3530fcf3fa..f2269278d0 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -49,6 +49,12 @@ public static void summaryBookMessage(String title, String summary) { System.out.println("remember to read it soon...."); printShortLine(); } + public static void authorBookMessage(String title, String author) { + printShortLine(); + System.out.println("okii you have have set: [" + author + "] as the author for the book: [" + title + "]"); + System.out.println("remember to read it soon...."); + printShortLine(); + } public static void setGenreBookMessage(String title, String genre) { printShortLine(); System.out.println("okii categorised [" + title + "] as [" + genre + "]"); diff --git a/src/main/java/seedu/bookbuddy/book/Author.java b/src/main/java/seedu/bookbuddy/book/Author.java new file mode 100644 index 0000000000..2634d863e9 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/book/Author.java @@ -0,0 +1,23 @@ +package seedu.bookbuddy.book; + +public class Author { + /** + * Sets the author for this book. + * + * @param book + * @param author The author to set for the book. + */ + public static void setAuthor(BookMain book, String author) { + book.author = author; + } + + /** + * Returns the author of the book. + * + * @param book + * @return The author of the book. + */ + public static String getAuthor(BookMain book) { + return book.author; + } +} diff --git a/src/main/java/seedu/bookbuddy/book/BookMain.java b/src/main/java/seedu/bookbuddy/book/BookMain.java index 226e9fe896..337fe2b0c9 100644 --- a/src/main/java/seedu/bookbuddy/book/BookMain.java +++ b/src/main/java/seedu/bookbuddy/book/BookMain.java @@ -8,6 +8,7 @@ public class BookMain { protected String genre; protected int rating; protected String summary; + protected String author; /** * Creates a new Book with the specified title. @@ -22,6 +23,7 @@ public BookMain(String title) { this.genre = ""; this.rating = 0; // Initialized to 0 this.summary = ""; + this.author = ""; } //@@author joshuahoky @@ -34,9 +36,10 @@ public BookMain(String title) { * @param genre The genre of the book. * @param rating The rating assigned to the book. * @param summary The summary of the book. + * @param author The author of the book. */ public BookMain(String title, int status, String label, String genre, int rating, String summary, - String datetimeread, int lineNumber) { + String datetimeread, int lineNumber, String author) { if (rating < 0 || rating > 5 || status < 0 || status > 1) { throw new IllegalArgumentException("Unable to load book data from line " + lineNumber + " in books.txt as data is corrupted."); @@ -49,6 +52,7 @@ public BookMain(String title, int status, String label, String genre, int rating this.genre = genre; this.summary = summary; this.rating = rating; + this.author = author; } @Override @@ -68,7 +72,8 @@ public String saveFormat() { String label = (this.label.isEmpty()) ? "" : this.label; String genre = (this.genre.isEmpty()) ? "" : this.genre; String summary = (this.summary.isEmpty()) ? "" : this.summary; + String author = (this.author.isEmpty()) ? "" : this.author; return this.title + " | " + status + " | " + label + " | " + genre + " | " + summary - + " | " + this.rating + " | " + datetimeread; + + " | " + this.rating + " | " + datetimeread + " | " + author; } } diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookAuthor.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookAuthor.java new file mode 100644 index 0000000000..c7389f1d01 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookAuthor.java @@ -0,0 +1,25 @@ +package seedu.bookbuddy.bookdetailsmodifier; + +import seedu.bookbuddy.book.Author; +import seedu.bookbuddy.book.Title; +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.Ui; + +public class BookAuthor { + /** + * Sets the author of the book at the specified index. + * + * @param index The index of the book in the list. + * @param author The author to set for the book. + * @throws IndexOutOfBoundsException if the index is out of range. + */ + public static void setBookAuthorByIndex(int index, String author, BookList books) + throws IndexOutOfBoundsException { + if (index < 0 || index > books.getSize()) { + throw new IndexOutOfBoundsException("Invalid book index. Please enter a valid index."); + } + Author.setAuthor(books.getBook(index), author); + String title = Title.getTitle(books.getBook(index)); + Ui.authorBookMessage(title, author); + } +} diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index 88eb20cc9d..6d57a82936 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -2,13 +2,7 @@ import seedu.bookbuddy.Ui; -import seedu.bookbuddy.book.BookMain; -import seedu.bookbuddy.book.Genre; -import seedu.bookbuddy.book.Label; -import seedu.bookbuddy.book.Rating; -import seedu.bookbuddy.book.Read; -import seedu.bookbuddy.book.Summary; -import seedu.bookbuddy.book.Title; +import seedu.bookbuddy.book.*; import seedu.bookbuddy.booklist.BookList; @@ -28,12 +22,14 @@ public static void displayDetails(int index, BookList books) throws IndexOutOfBo String label = Label.getLabel(books.getBook(index)); String genre = Genre.getGenre(books.getBook(index)); + String author = Author.getAuthor(books.getBook(index)); int rating = Rating.getRating(books.getBook(index)); String summary = Summary.getSummary(books.getBook(index)); System.out.println("Here are the details of your book:"); System.out.println("Title: " + Title.getTitle(books.getBook(index))); System.out.println("Status: " + (Read.getRead(books.getBook(index)) ? "Read on " + Read.getDateTimeRead(books.getBook(index)) : "Unread")); + System.out.println("Author: " + (author.isEmpty() ? "No author provided" : author)); System.out.println("Label: " + (label.isEmpty() ? "No label provided" : label)); System.out.println("Genre: " + (genre.isEmpty() ? "No genre provided" : genre)); System.out.println("Rating: " + ((rating == 0) ? "No rating provided" : rating)); diff --git a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java index dee6c6e8c1..aceb0c635e 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java @@ -20,7 +20,8 @@ public static void addBookFromFile(BookList bookList, String inputArray, int lin int rating = Integer.parseInt(bookDetails[5].trim()); String summary = bookDetails[4].trim(); String datetime = bookDetails[6].trim(); - bookList.books.add(new BookMain(title, status, label, genre, rating, summary, datetime, lineNumber)); + String author = bookDetails[7].trim(); + bookList.books.add(new BookMain(title, status, label, genre, rating, summary, datetime, lineNumber, author)); } catch (Exception e) { System.out.println("Unable to load book data from line " + lineNumber + " in books.txt " + "as data is corrupted."); diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index fa35aad46b..64f9448542 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -4,17 +4,7 @@ import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.Ui; -import seedu.bookbuddy.parser.parsercommands.ParserFind; -import seedu.bookbuddy.parser.parsercommands.ParserAdd; -import seedu.bookbuddy.parser.parsercommands.ParserRemove; -import seedu.bookbuddy.parser.parsercommands.ParserMark; -import seedu.bookbuddy.parser.parsercommands.ParserGenre; -import seedu.bookbuddy.parser.parsercommands.ParserDisplay; -import seedu.bookbuddy.parser.parsercommands.ParserRating; -import seedu.bookbuddy.parser.parsercommands.ParserUnmark; -import seedu.bookbuddy.parser.parsercommands.ParserLabel; -import seedu.bookbuddy.parser.parsercommands.ParserSummary; -import seedu.bookbuddy.parser.parsercommands.ParserList; +import seedu.bookbuddy.parser.parsercommands.*; import seedu.bookbuddy.parser.parservalidation.CommandList; import seedu.bookbuddy.parser.parservalidation.Exceptions; @@ -38,7 +28,6 @@ public static void parseCommand(String input, BookList books) { String[] inputArray = input.split(" ", 2); String command = inputArray[0].toLowerCase(); LOGGER.log(Level.FINE, "Parsing command: {0}", command); - try { if (command.contains("list")) { ParserList.executeParseList(books, inputArray, command); @@ -106,6 +95,9 @@ public static void parseCommand(String input, BookList books) { case CommandList.DISPLAY_COMMAND: ParserDisplay.executeParseAdd(books, inputArray); break; +// case CommandList.AUTHOR_COMMAND: +// ParserAuthor.executeParseAuthor(books, inputArray); +// break; default: LOGGER.log(Level.WARNING, "Sorry but that is not a valid command. Please try again", command); throw new UnsupportedCommandException("Sorry but that is not a valid command. " + diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAdd.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAdd.java index 02d87c9c7c..b8fc21e79c 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAdd.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAdd.java @@ -4,6 +4,10 @@ import seedu.bookbuddy.booklist.BookListModifier; import seedu.bookbuddy.parser.parservalidation.Exceptions; +import java.util.logging.Level; + +import static seedu.bookbuddy.BookBuddy.LOGGER; + public class ParserAdd { //@@author joshuahoky private static void parseAdd(BookList books, String[] inputArray) { diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAuthor.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAuthor.java new file mode 100644 index 0000000000..bccc9d6121 --- /dev/null +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAuthor.java @@ -0,0 +1,26 @@ +package seedu.bookbuddy.parser.parsercommands; + +import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.bookdetailsmodifier.BookAuthor; +import seedu.bookbuddy.parser.parservalidation.Exceptions; + +public class ParserAuthor { + static void parseAuthor(BookList books, String[] inputArray) { + int index; + assert inputArray.length == 2 : "Command requires additional arguments"; + Exceptions.validateCommandArguments(inputArray, 2, "The author " + + "Command requires a book index and author"); + String[] authorMessageParts = inputArray[1].split(" ", 2); + assert authorMessageParts.length == 2 : "Command requires an index and a author name"; + Exceptions.validateCommandArguments(authorMessageParts, 2, "You need " + + "to have an author name"); + index = Integer.parseInt(authorMessageParts[0]); + assert index >= 0 : "Index should be non-negative"; + String author = authorMessageParts[1]; + BookAuthor.setBookAuthorByIndex(index, author, books); + } + + public static void executeParseAuthor(BookList books, String[] inputArray) { + parseAuthor(books, inputArray); + } +} diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java index d0bfe22cc4..57d420327d 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java @@ -12,7 +12,7 @@ static void parseSummary(BookList books, String[] inputArray) { Exceptions.validateCommandArguments(inputArray,2, "The summary " + "Command requires a book index and summary"); String[] summaryMessageParts = inputArray[1].split(" ", 2); - assert summaryMessageParts.length == 2 : "Command requires an index and a label message"; + assert summaryMessageParts.length == 2 : "Command requires an index and a summary message"; Exceptions.validateCommandArguments(summaryMessageParts,2, "You need " + "to have a summary message"); index = Integer.parseInt(summaryMessageParts[0]); @@ -20,7 +20,7 @@ static void parseSummary(BookList books, String[] inputArray) { String summary = summaryMessageParts[1]; BookSummary.setBookSummaryByIndex(index, summary, books); } - //@@author + public static void executeParseSummary (BookList books, String[] inputArray) { parseSummary(books, inputArray); } diff --git a/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java b/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java index 7d95b2c049..505809e7e6 100644 --- a/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java +++ b/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java @@ -20,4 +20,5 @@ public class CommandList { public static final String RATING_COMMAND = "rate"; public static final String PRINT_ORDERED_COMMAND = "list-rated"; public static final String PRINT_ORDERED_DATE_COMMAND = "list-by-date"; + public static final String AUTHOR_COMMAND = "set-author"; } From bb458244e5661fdc5cc780dbfe27199a0ac94915 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Thu, 11 Apr 2024 03:34:07 +0800 Subject: [PATCH 229/311] Fix checkstyle --- .../bookdetailsmodifier/BookDisplay.java | 10 +++++++++- .../bookbuddy/booklist/BookListModifier.java | 3 ++- .../seedu/bookbuddy/parser/ParserMain.java | 20 +++++++++++++++---- .../parser/parsercommands/ParserAdd.java | 3 --- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index 6d57a82936..3788df57b3 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -2,7 +2,15 @@ import seedu.bookbuddy.Ui; -import seedu.bookbuddy.book.*; +import seedu.bookbuddy.book.BookMain; +import seedu.bookbuddy.book.Genre; +import seedu.bookbuddy.book.Label; +import seedu.bookbuddy.book.Rating; +import seedu.bookbuddy.book.Read; +import seedu.bookbuddy.book.Summary; +import seedu.bookbuddy.book.Title; +import seedu.bookbuddy.book.Author; + import seedu.bookbuddy.booklist.BookList; diff --git a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java index aceb0c635e..6a6379d2a8 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java @@ -21,7 +21,8 @@ public static void addBookFromFile(BookList bookList, String inputArray, int lin String summary = bookDetails[4].trim(); String datetime = bookDetails[6].trim(); String author = bookDetails[7].trim(); - bookList.books.add(new BookMain(title, status, label, genre, rating, summary, datetime, lineNumber, author)); + bookList.books.add(new BookMain(title, status, label, genre, rating, summary, + datetime, lineNumber, author)); } catch (Exception e) { System.out.println("Unable to load book data from line " + lineNumber + " in books.txt " + "as data is corrupted."); diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index 64f9448542..b5d81a18d9 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -4,7 +4,19 @@ import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.Ui; -import seedu.bookbuddy.parser.parsercommands.*; +import seedu.bookbuddy.parser.parsercommands.ParserFind; +import seedu.bookbuddy.parser.parsercommands.ParserAdd; +import seedu.bookbuddy.parser.parsercommands.ParserRemove; +import seedu.bookbuddy.parser.parsercommands.ParserMark; +import seedu.bookbuddy.parser.parsercommands.ParserGenre; +import seedu.bookbuddy.parser.parsercommands.ParserDisplay; +import seedu.bookbuddy.parser.parsercommands.ParserRating; +import seedu.bookbuddy.parser.parsercommands.ParserUnmark; +import seedu.bookbuddy.parser.parsercommands.ParserLabel; +import seedu.bookbuddy.parser.parsercommands.ParserSummary; +import seedu.bookbuddy.parser.parsercommands.ParserList; +import seedu.bookbuddy.parser.parsercommands.ParserAuthor; + import seedu.bookbuddy.parser.parservalidation.CommandList; import seedu.bookbuddy.parser.parservalidation.Exceptions; @@ -95,9 +107,9 @@ public static void parseCommand(String input, BookList books) { case CommandList.DISPLAY_COMMAND: ParserDisplay.executeParseAdd(books, inputArray); break; -// case CommandList.AUTHOR_COMMAND: -// ParserAuthor.executeParseAuthor(books, inputArray); -// break; + case CommandList.AUTHOR_COMMAND: + ParserAuthor.executeParseAuthor(books, inputArray); + break; default: LOGGER.log(Level.WARNING, "Sorry but that is not a valid command. Please try again", command); throw new UnsupportedCommandException("Sorry but that is not a valid command. " + diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAdd.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAdd.java index b8fc21e79c..b7e4dbd8f7 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAdd.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAdd.java @@ -4,9 +4,6 @@ import seedu.bookbuddy.booklist.BookListModifier; import seedu.bookbuddy.parser.parservalidation.Exceptions; -import java.util.logging.Level; - -import static seedu.bookbuddy.BookBuddy.LOGGER; public class ParserAdd { //@@author joshuahoky From dffe06e87e60b8c7b032c1db0aaf5db3b9c2b38b Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Thu, 11 Apr 2024 21:13:52 +0800 Subject: [PATCH 230/311] Add check for duplicate books for add and set-author commands --- BookBuddy.log.1 | 8 ++++ src/main/java/seedu/bookbuddy/Ui.java | 14 +++++++ .../seedu/bookbuddy/booklist/BookList.java | 41 ++++++++++++++++++ .../bookbuddy/booklist/BookListModifier.java | 42 +++++++++++-------- .../parser/parsercommands/ParserAuthor.java | 12 +++++- .../java/seedu/bookbuddy/ParserMainTest.java | 9 ++++ 6 files changed, 107 insertions(+), 19 deletions(-) diff --git a/BookBuddy.log.1 b/BookBuddy.log.1 index 2674e6549a..0e0c52a8a0 100644 --- a/BookBuddy.log.1 +++ b/BookBuddy.log.1 @@ -26,3 +26,11 @@ Apr 04, 2024 11:43:24 PM seedu.bookbuddy.parser.ParserMain parseCommand WARNING: Sorry but that is not a valid command. Please try again Apr 04, 2024 11:43:24 PM seedu.bookbuddy.parser.parservalidation.Exceptions handleException WARNING: Command is invalid: Sorry but that is not a valid command. Please try again or type: help +Apr 11, 2024 9:04:58 PM seedu.bookbuddy.parser.parservalidation.Exceptions validateCommandArguments +WARNING: The add Command requires a book title +Apr 11, 2024 9:04:58 PM seedu.bookbuddy.parser.parservalidation.Exceptions handleException +WARNING: Invalid command argument: The add Command requires a book title +Apr 11, 2024 9:04:58 PM seedu.bookbuddy.parser.ParserMain parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Apr 11, 2024 9:04:58 PM seedu.bookbuddy.parser.parservalidation.Exceptions handleException +WARNING: Command is invalid: Sorry but that is not a valid command. Please try again or type: help diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index e0868d46c0..5e9a427160 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -154,4 +154,18 @@ public static void printRateFound(ArrayList bookRate) { public static void printNoRateFound() { System.out.println("oops there are no books stored under this rating..."); } + + public static void printAuthorAlreadySet(String title, String author) { + System.out.println("oops " + title + " already has " + author + " as its author..."); + } + + public static void printDuplicateBookWarning(String title) { + System.out.println("Woah ive added " + title + " for you but there is another book with the" + + " same name in the list," + " if its a different book with " + + "the same name but by a different author " + + "please use `set-author` " + "to assign an author!"); } + + public static void printDuplicateAuthorWarning(String title, String author) { + System.out.println("Wait !!! " + title + " by: " + author + " is already in the list, " + + "you can use `remove` or `set-author` to remove that book or change the author..."); } } diff --git a/src/main/java/seedu/bookbuddy/booklist/BookList.java b/src/main/java/seedu/bookbuddy/booklist/BookList.java index 501ef2d620..18086302f5 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookList.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookList.java @@ -1,7 +1,9 @@ package seedu.bookbuddy.booklist; import exceptions.BookNotFoundException; +import seedu.bookbuddy.book.Author; import seedu.bookbuddy.book.BookMain; +import seedu.bookbuddy.book.Title; import java.util.ArrayList; import java.util.Arrays; @@ -59,4 +61,43 @@ public BookMain getBook(int index) throws BookNotFoundException { public static String saveGenresFormat() { return String.join(",", availableGenres); } + + //@@lordgareth10 + /** + * Checks whether the book title is already inside the list + * + * @param bookList The bookList arraylist + * @param title The title of the book. + */ + public static boolean checkDuplicateBookTitle(BookList bookList, String title) { + assert title != null : "title should not be null"; + for (BookMain book : bookList.getBooks()) { + String actualTitle = Title.getTitle(book).toLowerCase(); + String lowercasetitle = title.toLowerCase(); + if (actualTitle.equals(lowercasetitle)) { + return true; + } + } + return false; + } + + /** + * For books of the same title, checks whether the author is the same as well + * + * @param bookList The bookList arraylist + * @param title The title of the book. + */ + public static boolean checkDuplicateBookAuthor(BookList bookList, String author, String title) { + assert author != null : "title should not be null"; + for (BookMain book : bookList.getBooks()) { + if (title.equals(Title.getTitle(book))) { + String actualAuthor = Author.getAuthor(book).toLowerCase(); + String lowercaseauthor = author.toLowerCase(); + if (actualAuthor.equals(lowercaseauthor)) { + return true; + } + } + } + return false; + } } diff --git a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java index be224c15d0..4d1c6b3b1b 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java @@ -29,24 +29,6 @@ public static void addBookFromFile(BookList bookList, String inputArray, int lin } } - //@@author - /** - * Adds a new Book to the list. - * - * @param bookList The bookList arraylist - * @param title The title of the book. - */ - public static void addBook(BookList bookList, String title) { - try { - bookList.books.add(new BookMain(title)); - Ui.addBookMessage(title); - assert !bookList.books.isEmpty() : "Book list should not be empty after adding a book"; - } catch (Exception e) { - LOGGER.log(Level.SEVERE, "An unexpected error occurred: {0}", e.getMessage()); - throw e; // Rethrow or handle as needed - } - } - /** * Deletes a book from the list by its index. * @@ -69,4 +51,28 @@ public static void deleteBook(BookList bookList, int index) throws IndexOutOfBou throw e; // Rethrow or handle as needed } } + + //@@author + /** + * Adds a new Book to the list. + * + * @param bookList The bookList arraylist + * @param title The title of the book. + */ + public static void addBook(BookList bookList, String title) { + try { + if (BookList.checkDuplicateBookTitle(bookList, title)) { + bookList.books.add(new BookMain(title)); + Ui.printDuplicateBookWarning(title); + } else { + bookList.books.add(new BookMain(title)); + Ui.addBookMessage(title); + } + assert !bookList.books.isEmpty() : "Book list should not be empty after adding a book"; + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "An unexpected error occurred: {0}", e.getMessage()); + throw e; // Rethrow or handle as needed + } + } + } diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAuthor.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAuthor.java index bccc9d6121..603dc783e3 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAuthor.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAuthor.java @@ -1,5 +1,8 @@ package seedu.bookbuddy.parser.parsercommands; +import seedu.bookbuddy.Ui; +import seedu.bookbuddy.book.Author; +import seedu.bookbuddy.book.Title; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.bookdetailsmodifier.BookAuthor; import seedu.bookbuddy.parser.parservalidation.Exceptions; @@ -17,7 +20,14 @@ static void parseAuthor(BookList books, String[] inputArray) { index = Integer.parseInt(authorMessageParts[0]); assert index >= 0 : "Index should be non-negative"; String author = authorMessageParts[1]; - BookAuthor.setBookAuthorByIndex(index, author, books); + String title = Title.getTitle(books.getBooks().get(index - 1)); + if (author.equals(Author.getAuthor(books.getBooks().get(index - 1)))) { + Ui.printAuthorAlreadySet(title, author); + } else if (BookList.checkDuplicateBookAuthor(books, author, title)){ + Ui.printDuplicateAuthorWarning(title, author); + } else { + BookAuthor.setBookAuthorByIndex(index, author, books); + } } public static void executeParseAuthor(BookList books, String[] inputArray) { diff --git a/src/test/java/seedu/bookbuddy/ParserMainTest.java b/src/test/java/seedu/bookbuddy/ParserMainTest.java index 296f280c5c..92b170897b 100644 --- a/src/test/java/seedu/bookbuddy/ParserMainTest.java +++ b/src/test/java/seedu/bookbuddy/ParserMainTest.java @@ -7,6 +7,7 @@ import seedu.bookbuddy.book.Label; import seedu.bookbuddy.book.Read; import seedu.bookbuddy.book.Title; +import seedu.bookbuddy.book.Author; import seedu.bookbuddy.bookdetailsmodifier.BookMark; import seedu.bookbuddy.booklist.BookList; import seedu.bookbuddy.booklist.BookListModifier; @@ -58,6 +59,14 @@ void parseAddCommand() { assertEquals("The Great Gatsby", Title.getTitle(testBookList.getBook(1))); } + @Test + void parseAuthorCommand() { + BookList testBookList = new BookList(); + ParserMain.parseCommand("add The Great Gatsby", testBookList); + ParserMain.parseCommand("set-author 1 gareth", testBookList); + assertEquals(1, testBookList.getSize()); + assertEquals("gareth", Author.getAuthor(testBookList.getBook(1))); + } @Test void parseRemoveCommand() { BookList books = new BookList(); From 335817118a7884c62154ca0758e94d40e440bb12 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 11 Apr 2024 23:20:19 +0800 Subject: [PATCH 231/311] Added PPP for zong yao --- docs/team/yeozongyao.md | 59 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 docs/team/yeozongyao.md diff --git a/docs/team/yeozongyao.md b/docs/team/yeozongyao.md new file mode 100644 index 0000000000..b8c32827db --- /dev/null +++ b/docs/team/yeozongyao.md @@ -0,0 +1,59 @@ +# Yeo Zong Yao - Project Portfolio Page + +## Project : BookBuddy +BookBuddy is a CLI-based desktop app that is targeted towards users who prefer the CLI to efficiently keep track of the books that +they have read or intend to read. BookBuddy targets casual readers who read for entertainment but not on a regular basis, avid readers +who have a huge collection of books and profession/critical readers who read scholarly journals and papers for research and work. + +BookBuddy provides a one-stop solution for building a personalised reading repository,is optimised for users who are quick at typing for +efficient retrieval of book details and seeks to enable a customisable user experience to enhance the overall reading experience. + +The program was created using Java. Version control was done using Sourcetree and Git. + +## Summary of Contributions + +### Code contributed +Link should direct to my code contributions. If not, enter the link and search for "yeozongyao": +[RepoSense Link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=&sort=totalCommits%20dsc&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-02-23&tabOpen=true&tabType=authorship&tabAuthor=yeozongyao&tabRepo=AY2324S2-CS2113-F15-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + + +### New Features +1. Ability to add book to a list ([PR #9](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/9)) +2. Ability to print the books in the current list ([PR #9](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/9)) +3. Ability to categorise books with the specific genre ([PR #41](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/41)) +4. Ability to label books with personalised labels ([PR #41](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/41)) +5. Ability to rate books between 1 and 5 ([PR #58](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/58)) +6. Ability to list the book from the highest rated to the lowest rated ([PR #58](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/58)) + +### Enhancement to existing features +1. Refactored, abstracted and modified the entire codebase for code clarity ([PR #61](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/61), [PR #72](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/72)) +2. Created all the custom exceptions ([PR #19](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/19)) +3. Reformat set-genre to be both multistep for normal users and single-step for pro users ([PR #57](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/57), [PR #88](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/88)) +4. Added assertions and logging to the codebase across multiple features ([PR #20](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/20), [PR #28](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/28/files), [PR #37](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/37)) +5. Added JUnit Test cases for label and set-genre features and other features ([PR #52](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/52)) + +### Contributions to UG +1. Added expected output to all methods in user guide ([PR #80](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/80)) +2. Added target users and value proposition ([PR #84](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/84)) +3. Reformatted use case and methods for set-genre ([PR #89](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/89)) + +### Contributions to DG +1. Added references and implementation details of set-genre feature ([PR #42](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/42)) +2. Added implementation details of label feature ([PR #161](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/161)) +3. Added Sequence diagrams for set-genre and label features ([PR #81](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/81)) + +### Contribution to team-based tasks / Project Management +1. Added external logging to logging file and disable logging in command line output ([PR #27](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/27)) +2. Created and set up the GitHub team org/repo ([Repo](https://github.com/AY2324S2-CS2113-F15-4/tp)) +3. Necessary general code enhancements +4. Setting up tools e.g., GitHub, Gradle ([PR #37](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/37), ([PR #20](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/20), [PR #9](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/9)) +5. Maintain the issue tracker and assign issues ([Issues](https://github.com/AY2324S2-CS2113-F15-4/tp/issues)) +6. Release management + - Managed releases `v1.0`, `v2.0` on GitHub ([Releases](https://github.com/AY2324S2-CS2113-F15-4/tp/releases)) +7. Updating user/developer docs that are not specific to a feature e.g. documenting the target user profile ([PR #84](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/84)) + +### Contributions beyond project team +Coming soon. + +### Community +1. PRs reviewed: [#17](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/17) \ No newline at end of file From e0d1899064129c6ff642e711f2e0d3f1e5770ff0 Mon Sep 17 00:00:00 2001 From: Yeo Zong Yao Date: Thu, 11 Apr 2024 23:22:38 +0800 Subject: [PATCH 232/311] Update PPP --- docs/team/yeozongyao.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/team/yeozongyao.md b/docs/team/yeozongyao.md index b8c32827db..2fd3c9c1c9 100644 --- a/docs/team/yeozongyao.md +++ b/docs/team/yeozongyao.md @@ -51,6 +51,7 @@ Link should direct to my code contributions. If not, enter the link and search f 6. Release management - Managed releases `v1.0`, `v2.0` on GitHub ([Releases](https://github.com/AY2324S2-CS2113-F15-4/tp/releases)) 7. Updating user/developer docs that are not specific to a feature e.g. documenting the target user profile ([PR #84](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/84)) +8. Organise project meetings ### Contributions beyond project team Coming soon. From 62ae19538f6d6a5fc6a6a3caabd4c81de2cc15dd Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:26:28 +0800 Subject: [PATCH 233/311] Update UG and help command --- docs/UserGuide.md | 21 +++++++++++++++++++++ src/main/java/seedu/bookbuddy/Ui.java | 1 + 2 files changed, 22 insertions(+) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index fb54d53657..fcb69f17d8 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -28,6 +28,7 @@ experience. * [unmark](#marking-a-book-as-unread-unmark) * [list-by-date](#listing-read-books-by-date-list-by-date) * [set-genre](#setting-the-genre-of-a-book-set-genre) + * [set-author](#setting-the-author-of-a-book-set-author) * [label](#labelling-a-book-label) * [give-summary](#adding-a-book-summary-give-summary) * [rate](#rating-a-book-rate) @@ -235,6 +236,26 @@ okii categorised [Harry Potter] as [Fiction] remember to read it soon.... ```` +### Setting the author of a book: `set-author` +Sets the author of a specific book based on the provided input and the provided index. + +Format: `set-author [BOOK_INDEX] [BOOK_AUTHOR]` + +Example of usage with expected output: + +``` + +//input +set-author 1 zonyao + +//output +_____________ +okii you have have set: [zonyao] as the author for the book: [book1] +remember to read it soon.... +_____________ + +``` + ### Labelling a book: `label` Sets the label of a specific book to the provided input by its index. diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 5e9a427160..15e0f36ea0 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -81,6 +81,7 @@ public static void helpMessage() { System.out.println("list-by-date -> to print out all books sorted in descending order of date"); System.out.println("(basic) set-genre [BOOK_INDEX] -> to set a genre for a book"); System.out.println("(advanced) set-genre [BOOK_INDEX] [GENRE] -> to set a genre for a book"); + System.out.println("set-author [BOOK_INDEX] [BOOK_AUTHOR] -> to set an author for a book"); System.out.println("label [BOOK_INDEX] [LABEL] -> to set a label for a book"); System.out.println("give-summary [BOOK_INDEX] [BOOK_SUMMARY] -> to give a book a summary"); System.out.println("rate [BOOK_INDEX] [BOOK_RATING] -> to rate a book from 1-5"); From a487345d24281056bdbf6fc719d687a1eacd0e1c Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Fri, 12 Apr 2024 11:42:38 +0800 Subject: [PATCH 234/311] Update PPP --- docs/team/liuzehui03.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 docs/team/liuzehui03.md diff --git a/docs/team/liuzehui03.md b/docs/team/liuzehui03.md new file mode 100644 index 0000000000..e211168f34 --- /dev/null +++ b/docs/team/liuzehui03.md @@ -0,0 +1,14 @@ +# Liu Ze Hui - Project Portfolio Page + +## Project : BookBuddy +BookBuddy is a CLI-based desktop app that is targeted towards users who prefer the CLI to efficiently keep track of the books that +they have read or intend to read. BookBuddy targets casual readers who read for entertainment but not on a regular basis, avid readers +who have a huge collection of books and profession/critical readers who read scholarly journals and papers for research and work. + +## Overview + + +### Summary of Contributions +### Code contributed +Link should direct to my code contributions. If not, enter the link and search for "liuzehui03": +[RepoSense Link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=liuzehui03&breakdown=true&sort=groupTitle%20dsc&sortWithin=title&since=2024-02-23&timeframe=commit&mergegroup=&groupSelect=groupByRepos&checkedFileTypes=docs~functional-code~test-code~other&tabOpen=true&tabType=authorship&tabAuthor=liuzehui03&tabRepo=AY2324S2-CS2113-F15-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) \ No newline at end of file From 2a7af0143fbe31b1eccc792f441ac619ad5660a2 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Fri, 12 Apr 2024 11:58:50 +0800 Subject: [PATCH 235/311] added find-author --- src/main/java/seedu/bookbuddy/Ui.java | 10 ++++- .../bookdetailsmodifier/BookFind.java | 45 ++++++++++++++++--- .../seedu/bookbuddy/parser/ParserMain.java | 6 ++- .../parser/parsercommands/ParserFind.java | 4 ++ .../parser/parservalidation/CommandList.java | 1 + 5 files changed, 56 insertions(+), 10 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/Ui.java b/src/main/java/seedu/bookbuddy/Ui.java index 5e9a427160..1a8dcaa42f 100644 --- a/src/main/java/seedu/bookbuddy/Ui.java +++ b/src/main/java/seedu/bookbuddy/Ui.java @@ -87,8 +87,8 @@ public static void helpMessage() { System.out.println("list-rated -> to sort books by rating in descending order"); System.out.println("display [BOOK_INDEX] -> to view more details about a book"); System.out.println("find-title [KEYWORD] -> to find books with keyword in their title"); - System.out.println("(basic) find-genre -> to find books under specific genres"); - System.out.println("(advanced) find-genre [GENRE] -> to find books under specific genres"); + System.out.println("(basic) find-genre -> to find books under specified genre"); + System.out.println("(advanced) find-genre [GENRE] -> to find books under specified genre"); System.out.println("find-read -> to find list of books that are read"); System.out.println("find-unread -> to find list of books that are unread"); System.out.println("find-label [KEYWORD] -> to find list of books that stored under a certain label"); @@ -168,4 +168,10 @@ public static void printDuplicateBookWarning(String title) { public static void printDuplicateAuthorWarning(String title, String author) { System.out.println("Wait !!! " + title + " by: " + author + " is already in the list, " + "you can use `remove` or `set-author` to remove that book or change the author..."); } + + public static void printAuthorFound(ArrayList bookAuthor) { + for (int i = 0; i < bookAuthor.size(); i++) { + System.out.println(i + 1 + ". " + bookAuthor.get(i)); + } + } } diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java index aec7093932..2b4f301711 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java @@ -1,12 +1,7 @@ package seedu.bookbuddy.bookdetailsmodifier; import seedu.bookbuddy.Ui; -import seedu.bookbuddy.book.Label; -import seedu.bookbuddy.book.BookMain; -import seedu.bookbuddy.book.Genre; -import seedu.bookbuddy.book.Title; -import seedu.bookbuddy.book.Read; -import seedu.bookbuddy.book.Rating; +import seedu.bookbuddy.book.*; import seedu.bookbuddy.booklist.BookList; import java.util.ArrayList; @@ -15,6 +10,12 @@ //@@author liuzehui03 public class BookFind { + /** + * Finds books by title containing the specified input. + * + * @param bookList The list of books to search from. + * @param input The title input to match against book titles. + */ public static void findBookTitle(BookList bookList, String input) { assert input != null : "input should not be null"; ArrayList bookTitles = new ArrayList<>(); @@ -34,7 +35,12 @@ public static void findBookTitle(BookList bookList, String input) { Ui.printShortLine(); } } - + /** + * Finds books by exact genre match. + * + * @param bookList The list of books to search from. + * @param input The genre input to match against book genres. + */ public static void findBookGenre(BookList bookList, String input) { assert input != null : "input should not be null"; ArrayList bookGenres = new ArrayList<>(); @@ -54,6 +60,12 @@ public static void findBookGenre(BookList bookList, String input) { Ui.printShortLine(); } } + /** + * Finds books by genre containing the specified input. + * + * @param bookList The list of books to search from. + * @param input The partial genre input to match against book genres. + */ public static void findBookGenreLong(BookList bookList, String input) { assert input != null : "input should not be null"; ArrayList bookGenres = new ArrayList<>(); @@ -140,4 +152,23 @@ public static void findRate(BookList bookList, String input) { System.out.println("pls enter a rating from 1-5"); } } + + public static void findAuthor(BookList bookList, String input) { + ArrayList bookAuthor = new ArrayList<>(); + for (BookMain book : bookList.getBooks()) { + String actualAuthor = Author.getAuthor(book).toLowerCase(); + String author = input.toLowerCase(); + if(actualAuthor.contains(author)){ + bookAuthor.add(book); + } + } + if (bookAuthor.isEmpty()){ + Ui.printNoLabelFound(); + } else { + Ui.printLine(); + System.out.println("books written by [" + input.toLowerCase() + "] :"); + Ui.printAuthorFound(bookAuthor); + Ui.printShortLine(); + } + } } diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index d9c9f5d207..b16acca6c2 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -73,7 +73,8 @@ public static void parseCommand(String input, BookList books) { ParserFind.parseTitle(books, inputArray[1]); break; case CommandList.FIND_GENRE_COMMAND: - if (inputArray.length == 1) { // Check if there is only 'find-genre' without additional parameters + if (inputArray.length == 1) { + // Check if there is only 'find-genre' without additional parameters ParserFind.parseGenreLong(books); } else { ParserFind.parseFindGenre(books, inputArray[1]); @@ -91,6 +92,9 @@ public static void parseCommand(String input, BookList books) { case CommandList.FIND_RATE_COMMAND: ParserFind.parseRate(books, inputArray[1]); break; + case CommandList.FIND_AUTHOR_COMMAND: + ParserFind.parseAuthor(books, inputArray[1]); + break; case CommandList.LABEL_COMMAND: ParserLabel.executeParseSetLabel(books, inputArray); break; diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java index 662422ca82..dbd20b32e2 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java @@ -43,4 +43,8 @@ public static void parseLabel(BookList books, String inputArray) { public static void parseRate(BookList books, String input) { BookFind.findRate(books, input); } + + public static void parseAuthor(BookList books, String input) { + BookFind.findAuthor(books, input); + } } diff --git a/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java b/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java index 505809e7e6..26a85e2f06 100644 --- a/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java +++ b/src/main/java/seedu/bookbuddy/parser/parservalidation/CommandList.java @@ -13,6 +13,7 @@ public class CommandList { public static final String FIND_UNREAD_COMMAND = "find-unread"; public static final String FIND_LABEL_COMMAND = "find-label"; public static final String FIND_RATE_COMMAND = "find-rate"; + public static final String FIND_AUTHOR_COMMAND = "find-author"; public static final String LABEL_COMMAND = "label"; public static final String GENRE_COMMAND = "set-genre"; public static final String SUMMARY_COMMAND = "give-summary"; From f4d256859047dad8f6d5553c5ed702f556388610 Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Fri, 12 Apr 2024 12:16:05 +0800 Subject: [PATCH 236/311] Added comments --- .../bookdetailsmodifier/BookFind.java | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java index 2b4f301711..2ba210bf22 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java @@ -83,6 +83,11 @@ public static void findBookGenreLong(BookList bookList, String input) { Ui.printShortLine(); } } + /** + * Finds books that have been marked as read. + * + * @param bookList The list of books to search from. + */ public static void findRead(BookList bookList){ ArrayList bookRead = new ArrayList<>(); for (BookMain book : bookList.getBooks()) { @@ -96,6 +101,11 @@ public static void findRead(BookList bookList){ Ui.printReadFound(bookRead); } } + /** + * Finds books that have not been marked as read. + * + * @param bookList The list of books to search from. + */ public static void findUnread(BookList bookList){ ArrayList bookUnread = new ArrayList<>(); for (BookMain book : bookList.getBooks()) { @@ -110,7 +120,12 @@ public static void findUnread(BookList bookList){ Ui.printUnreadFound(bookUnread); } } - + /** + * Finds books by label containing the specified input. + * + * @param bookList The list of books to search from. + * @param input The label input to match against book labels. + */ public static void findLabel(BookList bookList, String input) { ArrayList bookLabel = new ArrayList<>(); for (BookMain book : bookList.getBooks()) { @@ -129,7 +144,12 @@ public static void findLabel(BookList bookList, String input) { Ui.printShortLine(); } } - + /** + * Finds books by exact rating match. + * + * @param bookList The list of books to search from. + * @param input The rating input to match against book ratings. + */ public static void findRate(BookList bookList, String input) { ArrayList bookRate = new ArrayList<>(); int inputRating = Integer.parseInt(input); @@ -152,7 +172,12 @@ public static void findRate(BookList bookList, String input) { System.out.println("pls enter a rating from 1-5"); } } - + /** + * Finds books by author containing the specified input. + * + * @param bookList The list of books to search from. + * @param input The author input to match against book authors. + */ public static void findAuthor(BookList bookList, String input) { ArrayList bookAuthor = new ArrayList<>(); for (BookMain book : bookList.getBooks()) { From 08eb0d2c3315dc274f966132f1183ad0176a9acf Mon Sep 17 00:00:00 2001 From: liuzehui03 Date: Fri, 12 Apr 2024 12:21:19 +0800 Subject: [PATCH 237/311] Checkstyle fixes --- .../seedu/bookbuddy/bookdetailsmodifier/BookFind.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java index 2ba210bf22..8f665f1026 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookFind.java @@ -1,7 +1,13 @@ package seedu.bookbuddy.bookdetailsmodifier; import seedu.bookbuddy.Ui; -import seedu.bookbuddy.book.*; +import seedu.bookbuddy.book.Label; +import seedu.bookbuddy.book.BookMain; +import seedu.bookbuddy.book.Genre; +import seedu.bookbuddy.book.Title; +import seedu.bookbuddy.book.Read; +import seedu.bookbuddy.book.Rating; +import seedu.bookbuddy.book.Author; import seedu.bookbuddy.booklist.BookList; import java.util.ArrayList; From 43d9261f26fd0d1e0a26162d5290b82734cefdd8 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Fri, 12 Apr 2024 15:40:17 +0800 Subject: [PATCH 238/311] PPP update --- BookBuddy.log.1 | 8 +++++++ docs/team/johndoe.md | 6 ----- docs/team/lordgareth10.md | 46 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 6 deletions(-) delete mode 100644 docs/team/johndoe.md create mode 100644 docs/team/lordgareth10.md diff --git a/BookBuddy.log.1 b/BookBuddy.log.1 index 0e0c52a8a0..40025f1465 100644 --- a/BookBuddy.log.1 +++ b/BookBuddy.log.1 @@ -34,3 +34,11 @@ Apr 11, 2024 9:04:58 PM seedu.bookbuddy.parser.ParserMain parseCommand WARNING: Sorry but that is not a valid command. Please try again Apr 11, 2024 9:04:58 PM seedu.bookbuddy.parser.parservalidation.Exceptions handleException WARNING: Command is invalid: Sorry but that is not a valid command. Please try again or type: help +Apr 12, 2024 12:26:36 AM seedu.bookbuddy.parser.parservalidation.Exceptions validateCommandArguments +WARNING: The add Command requires a book title +Apr 12, 2024 12:26:36 AM seedu.bookbuddy.parser.parservalidation.Exceptions handleException +WARNING: Invalid command argument: The add Command requires a book title +Apr 12, 2024 12:26:36 AM seedu.bookbuddy.parser.ParserMain parseCommand +WARNING: Sorry but that is not a valid command. Please try again +Apr 12, 2024 12:26:36 AM seedu.bookbuddy.parser.parservalidation.Exceptions handleException +WARNING: Command is invalid: Sorry but that is not a valid command. Please try again or type: help diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index ab75b391b8..0000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,6 +0,0 @@ -# John Doe - Project Portfolio Page - -## Overview - - -### Summary of Contributions diff --git a/docs/team/lordgareth10.md b/docs/team/lordgareth10.md new file mode 100644 index 0000000000..556e9222d9 --- /dev/null +++ b/docs/team/lordgareth10.md @@ -0,0 +1,46 @@ +# Gareth Yeo - Project Portfolio Page + +## Project : BookBuddy +BookBuddy is a CLI-based desktop app that is targeted towards users who prefer the CLI to efficiently keep track of the books that +they have read or intend to read. BookBuddy targets casual readers who read for entertainment but not on a regular basis, avid readers +who have a huge collection of books and profession/critical readers who read scholarly journals and papers for research and work. + +BookBuddy provides a one-stop solution for building a personalised reading repository,is optimised for users who are quick at typing for +efficient retrieval of book details and seeks to enable a customisable user experience to enhance the overall reading experience. + +The program was created using Java. Version control was done using Sourcetree and Git. + +## Summary of Contributions + +### Code contributed +Link should direct to my code contributions. If not, enter the link and search for "lordgareth10": +[RepoSense Link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=gareth&sort=groupTitle&sortWithin=totalCommits&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=functional-code&since=2024-02-23&tabOpen=true&tabType=authorship&tabAuthor=lordgareth10&tabRepo=AY2324S2-CS2113-F15-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=functional-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + + +### New Features +1. Ability to mark and unmark book in a list ([PR #13](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/13)) +2. Ability to record the date and time which a book was marked ([PR #146](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/146)) +3. Ability to add a summary to a book ([PR #47](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/47)) +4. Ability to add an author to a book ([PR #168](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/168)) +5. Ability to list the books according to the date and time that they were read ([PR #153](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/153)) + +### Enhancement to existing features +1. Created Utilities package for getting and converting DateTime to Strings ([PR #146](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/146)) +2. Reformat BookList to check allow checking and handling of duplicate books and authors ([PR #169](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/169) +3. Added assertions and logging to the codebase for features including Author ([PR #168](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/168) +4. Added JUnit Test cases for author feature and BookList methods ([PR #169](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/169)), ([PR #17](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/17)) + +### Contributions to UG +1. Added UG documentation for all features that I implemented ([PR #171](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/171)), ([PR #154](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/154))([PR #95](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/95)) + +### Contributions to DG +1. Added Overiew, Detailed Workflow and explanation of class components.([PR #48](https://github.com/AY2324S2-CS2113-F15-4/tp/pull/48)) + +### Contribution to team-based tasks / Project Management + + +### Contributions beyond project team +Coming soon. + +### Community +1. PRs reviewed: \ No newline at end of file From ebd04d448c69074b06c8cb2f324d3a9ddac5d83a Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Fri, 12 Apr 2024 19:22:22 +0800 Subject: [PATCH 239/311] Bug Fix: [PE-D][Tester E] Adding a space before the index makes the command invalid #136 --- .../seedu/bookbuddy/parser/parsercommands/ParserAuthor.java | 4 ++-- .../seedu/bookbuddy/parser/parsercommands/ParserGenre.java | 2 +- .../seedu/bookbuddy/parser/parsercommands/ParserMark.java | 2 +- .../seedu/bookbuddy/parser/parsercommands/ParserRemove.java | 2 +- .../seedu/bookbuddy/parser/parsercommands/ParserSummary.java | 4 ++-- .../seedu/bookbuddy/parser/parsercommands/ParserUnmark.java | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAuthor.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAuthor.java index 603dc783e3..17cc6f96c9 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAuthor.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAuthor.java @@ -13,11 +13,11 @@ static void parseAuthor(BookList books, String[] inputArray) { assert inputArray.length == 2 : "Command requires additional arguments"; Exceptions.validateCommandArguments(inputArray, 2, "The author " + "Command requires a book index and author"); - String[] authorMessageParts = inputArray[1].split(" ", 2); + String[] authorMessageParts = inputArray[1].trim().split(" ", 2); assert authorMessageParts.length == 2 : "Command requires an index and a author name"; Exceptions.validateCommandArguments(authorMessageParts, 2, "You need " + "to have an author name"); - index = Integer.parseInt(authorMessageParts[0]); + index = Integer.parseInt(authorMessageParts[0].trim()); assert index >= 0 : "Index should be non-negative"; String author = authorMessageParts[1]; String title = Title.getTitle(books.getBooks().get(index - 1)); diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java index 3150859109..02e4caf024 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserGenre.java @@ -19,7 +19,7 @@ static void parseSetGenre(BookList books, String[] inputArray) throws IOExceptio String[] parts = inputArray[1].trim().split(" ", 2); //Attempt to split inputArray[1] into two parts int index; try { - index = Integer.parseInt(parts[0]); // The first part should be the index + index = Integer.parseInt(parts[0].trim()); // The first part should be the index } catch (NumberFormatException e) { System.out.println("Invalid book index format. Please enter a valid numeric index."); return; diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserMark.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserMark.java index 4bf7d63618..5d453d25f1 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserMark.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserMark.java @@ -11,7 +11,7 @@ static void parseMark(BookList books, String[] inputArray) { assert inputArray.length >= 2 : "Command requires additional arguments"; Exceptions.validateCommandArguments(inputArray, 2, "The mark " + "Command requires a book index"); - index = Integer.parseInt(inputArray[1]); + index = Integer.parseInt(inputArray[1].trim()); assert index >= 0 : "Index should be non-negative"; BookMark.markDoneByIndex(books, index); } diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserRemove.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserRemove.java index e79a659f79..3eccc8813c 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserRemove.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserRemove.java @@ -10,7 +10,7 @@ static void parseRemove(BookList books, String[] inputArray) { assert inputArray.length >= 2 : "Command requires additional arguments"; Exceptions.validateCommandArguments(inputArray, 2, "The remove " + "Command requires a book index"); - index = Integer.parseInt(inputArray[1]); + index = Integer.parseInt(inputArray[1].trim()); BookListModifier.deleteBook(books, index); } diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java index 57d420327d..b87bf6a4e7 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java @@ -11,11 +11,11 @@ static void parseSummary(BookList books, String[] inputArray) { assert inputArray.length == 2 : "Command requires additional arguments"; Exceptions.validateCommandArguments(inputArray,2, "The summary " + "Command requires a book index and summary"); - String[] summaryMessageParts = inputArray[1].split(" ", 2); + String[] summaryMessageParts = inputArray[1].trim().split(" ", 2); assert summaryMessageParts.length == 2 : "Command requires an index and a summary message"; Exceptions.validateCommandArguments(summaryMessageParts,2, "You need " + "to have a summary message"); - index = Integer.parseInt(summaryMessageParts[0]); + index = Integer.parseInt(summaryMessageParts[0].trim()); assert index >= 0 : "Index should be non-negative"; String summary = summaryMessageParts[1]; BookSummary.setBookSummaryByIndex(index, summary, books); diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserUnmark.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserUnmark.java index ef7c94b3bd..36c02bbb93 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserUnmark.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserUnmark.java @@ -10,7 +10,7 @@ static void parseUnmark(BookList books, String[] inputArray) { assert inputArray.length == 2 : "Command requires additional arguments"; Exceptions.validateCommandArguments(inputArray, 2, "The unmark " + "Command requires a book index"); - index = Integer.parseInt(inputArray[1]); + index = Integer.parseInt(inputArray[1].trim()); assert index >= 0 : "Index should be non-negative"; BookMark.markUndoneByIndex(books, index); } From 68af54f1daf74284160d91aca0ca7308cb7f8db9 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Fri, 12 Apr 2024 19:35:25 +0800 Subject: [PATCH 240/311] Bug Fix: [PE-D][Tester D] Function accepts commands with extra parameters #122 --- .../seedu/bookbuddy/parser/ParserMain.java | 10 +++--- .../parser/parsercommands/ParserFind.java | 34 ++++++++++++++----- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/parser/ParserMain.java b/src/main/java/seedu/bookbuddy/parser/ParserMain.java index b16acca6c2..2ef38d6d5d 100644 --- a/src/main/java/seedu/bookbuddy/parser/ParserMain.java +++ b/src/main/java/seedu/bookbuddy/parser/ParserMain.java @@ -81,19 +81,19 @@ public static void parseCommand(String input, BookList books) { } break; case CommandList.FIND_READ_COMMAND: - ParserFind.parseFindRead(books); + ParserFind.parseFindRead(books, inputArray); break; case CommandList.FIND_UNREAD_COMMAND: - ParserFind.parseFindUnread(books); + ParserFind.parseFindUnread(books, inputArray); break; case CommandList.FIND_LABEL_COMMAND: - ParserFind.parseLabel(books, inputArray[1]); + ParserFind.parseLabel(books, inputArray); break; case CommandList.FIND_RATE_COMMAND: - ParserFind.parseRate(books, inputArray[1]); + ParserFind.parseRate(books, inputArray); break; case CommandList.FIND_AUTHOR_COMMAND: - ParserFind.parseAuthor(books, inputArray[1]); + ParserFind.parseAuthor(books, inputArray); break; case CommandList.LABEL_COMMAND: ParserLabel.executeParseSetLabel(books, inputArray); diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java index dbd20b32e2..4fd884a067 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserFind.java @@ -2,6 +2,7 @@ import seedu.bookbuddy.bookdetailsmodifier.BookFind; import seedu.bookbuddy.booklist.BookList; +import seedu.bookbuddy.parser.parservalidation.Exceptions; import java.io.IOException; import java.util.Scanner; @@ -29,22 +30,39 @@ public static void parseGenreLong(BookList books) throws IOException { } BookFind.findBookGenreLong(books, selectedGenre); } - public static void parseFindRead(BookList books) { + public static void parseFindRead(BookList books, String[] inputArray) { + assert inputArray.length == 1 : "Command requires additional arguments"; + Exceptions.validateCommandArguments(inputArray, 1, + "This find command does not require any further arguments, just type `find-read`" + + " u absolute donut "); BookFind.findRead(books); } - public static void parseFindUnread(BookList books) { + public static void parseFindUnread(BookList books, String[] inputArray) { + assert inputArray.length == 1 : "Command requires additional arguments"; + Exceptions.validateCommandArguments(inputArray, 1, + "This find command does not require any further arguments, just type `find-unread`" + + " u absolute donut "); BookFind.findUnread(books); } - public static void parseLabel(BookList books, String inputArray) { - BookFind.findLabel(books, inputArray); + public static void parseLabel(BookList books, String[] inputArray) { + assert inputArray.length == 2 : "Command requires additional arguments"; + Exceptions.validateCommandArguments(inputArray, 2, + "The correct command is `find-label [LABEL]"); + BookFind.findLabel(books, inputArray[1].trim()); } - public static void parseRate(BookList books, String input) { - BookFind.findRate(books, input); + public static void parseRate(BookList books, String[] input) { + assert input.length == 2 : "Command requires additional arguments"; + Exceptions.validateCommandArguments(input, 2, + "The correct command is `find-rated [RATING]"); + BookFind.findRate(books, input[1].trim()); } - public static void parseAuthor(BookList books, String input) { - BookFind.findAuthor(books, input); + public static void parseAuthor(BookList books, String[] input) { + assert input.length == 2 : "Command requires additional arguments"; + Exceptions.validateCommandArguments(input, 2, + "The correct command is `find-author [AUTHOR]"); + BookFind.findAuthor(books, input[1].trim()); } } From 4e08a33e58699580aa1f0bd4de08e10371e09879 Mon Sep 17 00:00:00 2001 From: lordgareth10 <51813599+lordgareth10@users.noreply.github.com> Date: Fri, 12 Apr 2024 19:38:16 +0800 Subject: [PATCH 241/311] Bug Fix: [PE-D][Tester F] Inproper Exception Handling #135 --- .../seedu/bookbuddy/parser/parsercommands/ParserSummary.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java index b87bf6a4e7..1a603e8447 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserSummary.java @@ -13,8 +13,8 @@ static void parseSummary(BookList books, String[] inputArray) { "Command requires a book index and summary"); String[] summaryMessageParts = inputArray[1].trim().split(" ", 2); assert summaryMessageParts.length == 2 : "Command requires an index and a summary message"; - Exceptions.validateCommandArguments(summaryMessageParts,2, "You need " + - "to have a summary message"); + Exceptions.validateCommandArguments(summaryMessageParts,2, "The command " + + "is `give-summary [INDEX] [SUMMARY]`... dont test me dawg"); index = Integer.parseInt(summaryMessageParts[0].trim()); assert index >= 0 : "Index should be non-negative"; String summary = summaryMessageParts[1]; From cf9dc87a06b5a3175fc34b5910dd205154652a2c Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Fri, 12 Apr 2024 21:13:28 +0800 Subject: [PATCH 242/311] Update PPP --- docs/team/joshuahoky.md | 48 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/docs/team/joshuahoky.md b/docs/team/joshuahoky.md index 50eb19b8ef..29e65b40de 100644 --- a/docs/team/joshuahoky.md +++ b/docs/team/joshuahoky.md @@ -1,6 +1,50 @@ # Joshua Ho - Project Portfolio Page -## Overview +## Project : BookBuddy +BookBuddy is a CLI-based desktop app that is targeted towards users who prefer the CLI to efficiently keep track of the books that +they have read or intend to read. BookBuddy targets casual readers who read for entertainment but not on a regular basis, avid readers +who have a huge collection of books and profession/critical readers who read scholarly journals and papers for research and work. +BookBuddy provides a one-stop solution for building a personalised reading repository,is optimised for users who are quick at typing for +efficient retrieval of book details and seeks to enable a customisable user experience to enhance the overall reading experience. -### Summary of Contributions +The program was created using Java. Version control was done using Sourcetree and Git. + +## Summary of Contributions + +### Code contributed +Link should direct to my code contributions. If not, enter the link and search for "joshuahoky": +[RepoSense Link](https://nus-cs2113-ay2324s2.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2024-02-23&tabOpen=true&tabType=authorship&tabAuthor=Joshuahoky&tabRepo=AY2324S2-CS2113-F15-4%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&authorshipIsIgnoredFilesChecked=false) + +### New Features +1. Ability to parse commands from the user to execute tasks +2. Ability to remove a book from a list +3. Ability to display all the details of a book +4. Ability to save data in a session to a text file +5. Ability to read in data from previous sessions and load it in + +### Enhancements to existing features +1. Added exception handling to prevent program from crashing when text file data is corrupted +2. Added exception handling for ParserMain class to handle invalid input +3. Created more specific exception messages when removing a book + +### Contributions to UG +1. Wrote the skeleton for the UG with the appropriate headers +2. Created the table of contents for easier navigability +3. Instructions on how to get started with BookBuddy +4. Ensure consistency of formatting across all commands +5. FAQ section on how file saving is carried out in BookBuddy +6. Format command summary table for easier readability + +### Contributions to DG +1. Wrote the skeleton for the DG with the appropriate headers +2. Created the table of contents for easier navigability +3. Added implementation details for FileStorage class +4. Added implementation details for ParserMain class +5. Added class diagrams for FileStorage and ParserMain classes +6. Added sequence diagram for ParserMain class +7. Added user stories section + +### Contribution to team-based tasks / Project Management +1. Created UG v1.0 report +2. Created project notes document for meetings From 6e48feff2a05fdefca493d9aeab4f4766346f1c0 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Fri, 12 Apr 2024 23:38:02 +0800 Subject: [PATCH 243/311] Add more exception handling for fileStorage class --- .../java/seedu/bookbuddy/FileStorage.java | 9 ++++++--- .../java/seedu/bookbuddy/book/BookMain.java | 18 +++++++++-------- src/main/java/seedu/bookbuddy/book/Read.java | 14 ++++++------- .../bookdetailsmodifier/BookDisplay.java | 2 +- .../bookbuddy/booklist/BookListModifier.java | 20 +++++++++---------- .../parser/parsercommands/ParserAdd.java | 1 - 6 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/main/java/seedu/bookbuddy/FileStorage.java b/src/main/java/seedu/bookbuddy/FileStorage.java index c1fd16386c..67472ef11a 100644 --- a/src/main/java/seedu/bookbuddy/FileStorage.java +++ b/src/main/java/seedu/bookbuddy/FileStorage.java @@ -23,6 +23,7 @@ public class FileStorage { private static final String FILE_NAME = "books.txt"; private static final String FILE_DIRECTORY = "./data"; private static final String FILE_PATH = FILE_DIRECTORY + '/' + FILE_NAME; + private static final String GENRE_DATA = "Genres: Fiction,Non-Fiction,Mystery,Science Fiction,Fantasy"; public FileStorage(BookList books) { try { @@ -53,14 +54,16 @@ public void readData(BookList books, File file) throws FileNotFoundException { if (sc.hasNextLine()) { // Read the first line to get the genres String genresLine = sc.nextLine().trim(); - // Assuming the line starts with "Genres: " - if (genresLine.startsWith("Genres: ")) { + // Assuming the line starts with "Genres: Fiction,Non-Fiction,Mystery,Science Fiction,Fantasy" + if (genresLine.startsWith(GENRE_DATA)) { String genresData = genresLine.substring(8); // Skip "Genres: " List genres = Arrays.asList(genresData.split(",")); BookList.setAvailableGenres(genres); // Update the available genres + } else { + System.out.println("Unable to load genres as they have been tampered with."); } } - int lineNumber = 1; + int lineNumber = 2; while (sc.hasNext()) { String line = sc.nextLine(); BookListModifier.addBookFromFile(books, line, lineNumber); diff --git a/src/main/java/seedu/bookbuddy/book/BookMain.java b/src/main/java/seedu/bookbuddy/book/BookMain.java index 337fe2b0c9..93f7482040 100644 --- a/src/main/java/seedu/bookbuddy/book/BookMain.java +++ b/src/main/java/seedu/bookbuddy/book/BookMain.java @@ -3,7 +3,7 @@ public class BookMain { protected String title; protected boolean isRead; - protected String datetimeread; + protected String dateTimeRead; protected String label; protected String genre; protected int rating; @@ -18,7 +18,7 @@ public class BookMain { public BookMain(String title) { this.title = title; // Description of the book this.isRead = false; //Completion status of the book (True: Read, False: Unread) - this.datetimeread = ""; + this.dateTimeRead = ""; this.label = ""; this.genre = ""; this.rating = 0; // Initialized to 0 @@ -34,12 +34,14 @@ public BookMain(String title) { * @param status Whether the book is read or unread. * @param label The label assigned to the book. * @param genre The genre of the book. - * @param rating The rating assigned to the book. + * @param rating The rating assigned to the book. * @param summary The summary of the book. + * @param dateTimeRead The date and time in which the book is read (if is it read). + * @param lineNumber The line in which the book is in the text file. * @param author The author of the book. */ public BookMain(String title, int status, String label, String genre, int rating, String summary, - String datetimeread, int lineNumber, String author) { + String dateTimeRead, int lineNumber, String author) { if (rating < 0 || rating > 5 || status < 0 || status > 1) { throw new IllegalArgumentException("Unable to load book data from line " + lineNumber + " in books.txt as data is corrupted."); @@ -47,7 +49,7 @@ public BookMain(String title, int status, String label, String genre, int rating this.title = title; this.isRead = status == 1; - this.datetimeread = datetimeread; + this.dateTimeRead = dateTimeRead; this.label = label; this.genre = genre; this.summary = summary; @@ -68,12 +70,12 @@ public String toString() { */ public String saveFormat() { String status = isRead ? "1" : "0"; - String datetimeread = (this.datetimeread.isEmpty()) ? "*" : this.datetimeread; + String dateTimeRead = (this.dateTimeRead.isEmpty()) ? "" : this.dateTimeRead; String label = (this.label.isEmpty()) ? "" : this.label; String genre = (this.genre.isEmpty()) ? "" : this.genre; String summary = (this.summary.isEmpty()) ? "" : this.summary; String author = (this.author.isEmpty()) ? "" : this.author; - return this.title + " | " + status + " | " + label + " | " + genre + " | " + summary - + " | " + this.rating + " | " + datetimeread + " | " + author; + return this.title + " | " + author + " | " + status + " | " + dateTimeRead + " | " + label + + " | " + genre + " | " + summary + " | " + this.rating; } } diff --git a/src/main/java/seedu/bookbuddy/book/Read.java b/src/main/java/seedu/bookbuddy/book/Read.java index 1b1150ed31..d9e8f06773 100644 --- a/src/main/java/seedu/bookbuddy/book/Read.java +++ b/src/main/java/seedu/bookbuddy/book/Read.java @@ -6,7 +6,7 @@ public class Read { /** * Checks if the book is read. * - * @param book + * @param book The book that is passed to the method. * @return True if the book is read, false otherwise. */ public static boolean getRead(BookMain book) { @@ -16,25 +16,25 @@ public static boolean getRead(BookMain book) { /** * Returns the date and time read attribute of the book in string form. * - * @param book + * @param book The book that is passed to the method. * @return The date and time read of the book. */ public static String getDateTimeRead(BookMain book) { - return book.datetimeread; + return book.dateTimeRead; } /** * Sets the books read status as unread or read * - * @param book - * @param read the read status of the book + * @param book The book that is passed to the method. + * @param read The status of the book. */ public static void setRead(BookMain book, boolean read) { book.isRead = read; if (read) { - book.datetimeread = DateTimeUtils.getCurrentDateTime(); + book.dateTimeRead = DateTimeUtils.getCurrentDateTime(); } else { - book.datetimeread = ""; + book.dateTimeRead = ""; } } diff --git a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java index 77ab759da1..0bc6e924e4 100644 --- a/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java +++ b/src/main/java/seedu/bookbuddy/bookdetailsmodifier/BookDisplay.java @@ -45,7 +45,7 @@ public static void displayDetails(int index, BookList books) throws IndexOutOfBo /** * Prints all books currently in the list. * - * @param bookList The bookList array with all the books + * @param bookList The bookList array with all the books. */ public static void printAllBooks(BookList bookList) { assert bookList.getBooks() != null : "Books list should not be null since it has been initialised."; diff --git a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java index 4d1c6b3b1b..2a4ef5729b 100644 --- a/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java +++ b/src/main/java/seedu/bookbuddy/booklist/BookListModifier.java @@ -14,15 +14,15 @@ public static void addBookFromFile(BookList bookList, String inputArray, int lin try { String[] bookDetails = inputArray.split(" \\| "); String title = bookDetails[0].trim(); - int status = Integer.parseInt(bookDetails[1].trim()); - String label = bookDetails[2].trim(); - String genre = bookDetails[3].trim(); - int rating = Integer.parseInt(bookDetails[5].trim()); - String summary = bookDetails[4].trim(); - String datetime = bookDetails[6].trim(); - String author = bookDetails[7].trim(); + String author = bookDetails[1].trim(); + int status = Integer.parseInt(bookDetails[2].trim()); + String dateTimeRead = bookDetails[3].trim(); + String label = bookDetails[4].trim(); + String genre = bookDetails[5].trim(); + String summary = bookDetails[6].trim(); + int rating = Integer.parseInt(bookDetails[7].trim()); bookList.books.add(new BookMain(title, status, label, genre, rating, summary, - datetime, lineNumber, author)); + dateTimeRead, lineNumber, author)); } catch (Exception e) { System.out.println("Unable to load book data from line " + lineNumber + " in books.txt " + "as data is corrupted."); @@ -32,7 +32,7 @@ public static void addBookFromFile(BookList bookList, String inputArray, int lin /** * Deletes a book from the list by its index. * - * @param bookList + * @param bookList The bookList arrayList. * @param index The index of the book to delete. */ public static void deleteBook(BookList bookList, int index) throws IndexOutOfBoundsException { @@ -56,7 +56,7 @@ public static void deleteBook(BookList bookList, int index) throws IndexOutOfBou /** * Adds a new Book to the list. * - * @param bookList The bookList arraylist + * @param bookList The bookList arraylist. * @param title The title of the book. */ public static void addBook(BookList bookList, String title) { diff --git a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAdd.java b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAdd.java index b7e4dbd8f7..02d87c9c7c 100644 --- a/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAdd.java +++ b/src/main/java/seedu/bookbuddy/parser/parsercommands/ParserAdd.java @@ -4,7 +4,6 @@ import seedu.bookbuddy.booklist.BookListModifier; import seedu.bookbuddy.parser.parservalidation.Exceptions; - public class ParserAdd { //@@author joshuahoky private static void parseAdd(BookList books, String[] inputArray) { From 92a7e1f001413a2cfbc15f3e6902a5b7c75876a0 Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Fri, 12 Apr 2024 23:59:29 +0800 Subject: [PATCH 244/311] Update UG --- docs/UserGuide.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index fcb69f17d8..85a40f3f12 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -74,6 +74,7 @@ unmark [BOOK_INDEX] -> to mark book as unread [U] list-by-date -> to print out all books sorted in descending order of date (basic) set-genre [BOOK_INDEX] -> to set a genre for a book (advanced) set-genre [BOOK_INDEX] [GENRE] -> to set a genre for a book +set-author [BOOK_INDEX] [BOOK_AUTHOR] -> to set an author for a book") label [BOOK_INDEX] [LABEL] -> to set a label for a book give-summary [BOOK_INDEX] [BOOK_SUMMARY] -> to give a book a summary rate [BOOK_INDEX] [BOOK_RATING] -> to rate a book from 1-5 @@ -168,7 +169,7 @@ Successfully marked Harry Potter as unread. ``` ### Listing read books by date: `list-by-date` -Lists all read books by descending order of date read. +Lists all read books by their date read, in order of most recently read. Format: `list-by-date` @@ -179,8 +180,8 @@ Example of usage with expected output: list-by-date //output -booky : 6.57 PM, 09-04-2024 -book2 : 6.57 PM, 09-04-2024 +Harry Potter : 6.57 PM, 09-04-2024 +Geronimo Stilton : 6.52 PM, 09-04-2024 ``` ### Setting the genre of a book: `set-genre` @@ -244,16 +245,12 @@ Format: `set-author [BOOK_INDEX] [BOOK_AUTHOR]` Example of usage with expected output: ``` - //input set-author 1 zonyao //output -_____________ okii you have have set: [zonyao] as the author for the book: [book1] remember to read it soon.... -_____________ - ``` ### Labelling a book: `label` @@ -517,6 +514,7 @@ to be saved.** | Listing read books by date | `list-by-date` | | Set genre (Single-step for advanced users) | `set-genre [BOOK_INDEX] [GENRE]` | | Set genre (Multi-step for new users) | `set-genre [BOOK_INDEX]` followed by `[NUMBER]` then `[GENRE]` if necessary | +| Add author for book | `set-author [BOOK_INDEX] [AUTHOR]` | | Label book | `label [BOOK_INDEX] [LABEL]` | | Add summary for book | `give-summary [BOOK_INDEX] [BOOK_SUMMARY]` | | Rate book | `rate [BOOK_INDEX] [BOOK_RATING]` | From 0aa462b6e36587002dea0e4804178cfc63bb8e1d Mon Sep 17 00:00:00 2001 From: Joshuahoky Date: Sat, 13 Apr 2024 03:02:42 +0800 Subject: [PATCH 245/311] Update DG --- docs/DeveloperGuide.md | 41 ++++++++++++++++-------------- docs/UML_Files/AddCommand.puml | 40 ++++++++++++++++++++++------- docs/UML_Files/FileStorage.puml | 3 +++ docs/UML_Files/ParserMain.puml | 9 ++++--- docs/UML_diagrams/AddCommand.png | Bin 24622 -> 35081 bytes docs/UML_diagrams/FileStorage.png | Bin 18809 -> 16406 bytes docs/UML_diagrams/ParserMain.png | Bin 26674 -> 32601 bytes 7 files changed, 62 insertions(+), 31 deletions(-) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 4de2d5ab7a..d956d281de 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -14,7 +14,6 @@ ## Acknowledgements - Reference to AB-3 Developer Guide * [Source URL](https://se-education.org/addressbook-level3/DeveloperGuide.html#documentation-logging-testing-configuration-dev-ops) * Used as template to structure this DeveloperGuide @@ -194,33 +193,37 @@ the Single Responsibility Principle. ## Product scope ### Target user profile -Users that want an all-in-one app to track the books read, progress for each book. -Progress for each book can be recorded according to the number of pages read. +Users that want an all-in-one app to track their books read and also record details relevant to the book. Users will be able to sort books according to genre. Users can sort books according to Read or Unread. Users will also be able to search for books via keywords in book titles ### Value proposition -{Describe the value proposition: what problem does it solve?} +Saves time and effort as compared to using GUI-based book tracking apps. ## User Stories -| Version | As a ... | I want to ... | So that I can ... | -|---------|----------|-----------------------------------------------|-----------------------------------------------------------| -| v1.0 | new user | see usage instructions | refer to them when I forget how to use the application | -| v1.0 | user | add books to a list | | -| v1.0 | user | remove books from a list | | -| v1.0 | user | see the list of books that are read or unread | I can keep track of my reading progress | -| v1.0 | user | change the status of a book | mark a book when I have finished reading it | -| v2.0 | user | add a summary to a book | remember what the book is about | -| v2.0 | user | provide a label for a book | provide my own personal thoughts on the book | -| v2.0 | user | provide a genre for a book | categorise books according to their genre | -| v2.0 | user | provide a rating for a book | know whether a book was good or not | -| v2.0 | user | sort books by their rating | recall which are the best books that I have read | -| v2.0 | user | view all information about a book | remember to add missing information (if any) | -| v2.0 | user | search for books according to keywords | find a book quickly without going through the entire list | -| v2.0 | user | filter books by genre | see all the books in a particular genre | +| Version | As a ... | I want to ... | So that I can ... | +|---------|----------|-------------------------------------------------|---------------------------------------------------------------| +| v1.0 | new user | see usage instructions | refer to them when I forget how to use the application | +| v1.0 | user | add books to a list | | +| v1.0 | user | remove books from a list | | +| v1.0 | user | see the list of books that are read or unread | I can keep track of my reading progress | +| v1.0 | user | change the status of a book | mark a book when I have finished reading it | +| v2.0 | user | view the date that I finished reading each book | know how long I take to finish a book | +| v2.0 | user | add an author for a book | differentiate between books with the same title | +| v2.0 | user | add a summary to a book | remember what the book is about | +| v2.0 | user | provide a label for a book | provide my own personal thoughts on the book | +| v2.0 | user | provide a genre for a book | categorise books according to their genre | +| v2.0 | user | provide a rating for a book | know whether a book was good or not | +| v2.0 | user | sort books by their rating | recall which are the best books that I have read | +| v2.0 | user | view all information about a book | remember to add missing information (if any) | +| v2.0 | user | filter books by title | find a book quickly without going through the entire list | +| v2.0 | user | filter books by genre | see all the books in a particular genre | +| v2.0 | user | filter books by status | remember which books I have read / yet to read | +| v2.0 | user | filter books by label | see the list of books which I have assigned a custom label to | +| v2.0 | user | filter books by specific rating | view a smaller list of books of a certain score | diff --git a/docs/UML_Files/AddCommand.puml b/docs/UML_Files/AddCommand.puml index d7ad974942..c00ba03a7f 100644 --- a/docs/UML_Files/AddCommand.puml +++ b/docs/UML_Files/AddCommand.puml @@ -1,22 +1,44 @@ @startuml +hide footbox +participant ":User" as User +participant ":ParserMain" as ParserMain +participant ":ParserAdd" as ParserAdd +participant ":BookListModifier" as BookListModifier +participant ":BookList" as BookList +participant ":Ui" as Ui +participant ":Exceptions" as Exceptions -User -> Parser : parseCommand(input, books) -activate Parser +User -> ParserMain : add [BOOK_TITLE] +activate ParserMain alt input is ADD_COMMAND - Parser -> ParserAdd : executeParseAdd(books, inputArray) + ParserMain -> ParserAdd : executeParseAdd(books, inputArray) activate ParserAdd - ParserAdd --> Parser : CommandExecuted + ParserAdd --> BookListModifier : addBook(books, title) + activate BookListModifier + BookListModifier --> BookList: add(new BookMain(title)) + activate BookList + BookList --> BookListModifier + deactivate BookList + BookListModifier --> Ui: addBookMessage(title) + activate Ui + Ui --> User : message + Ui --> BookListModifier + deactivate Ui + BookListModifier --> ParserAdd + deactivate BookListModifier + ParserAdd --> ParserMain deactivate ParserAdd alt other commands - Parser -> Parser : handleOtherCommands() + ParserMain -> ParserMain : handleOtherCommands() else unsupported command - Parser -> Exceptions : handleException(e, command, inputArray) + ParserMain -> Exceptions : handleException(e, command, inputArray) activate Exceptions - Exceptions --> Parser : ExceptionHandled + Exceptions --> User : exception message + Exceptions --> ParserMain deactivate Exceptions end -Parser --> User : getUserInput(books) -deactivate Parser +ParserMain --> User : getUserInput(books) + @enduml diff --git a/docs/UML_Files/FileStorage.puml b/docs/UML_Files/FileStorage.puml index a46e41216f..9b6a2d6c4b 100644 --- a/docs/UML_Files/FileStorage.puml +++ b/docs/UML_Files/FileStorage.puml @@ -1,4 +1,7 @@ @startuml +hide circle +skinparam classAttributeIconSize 0 + class FileStorage { - FILE_NAME : String = "books.txt" - FILE_DIRECTORY : String = "./data" diff --git a/docs/UML_Files/ParserMain.puml b/docs/UML_Files/ParserMain.puml index 32c1a7ab49..7b24a7197f 100644 --- a/docs/UML_Files/ParserMain.puml +++ b/docs/UML_Files/ParserMain.puml @@ -1,6 +1,9 @@ @startuml -scale 2000 * 1500 +hide circle +skinparam classAttributeIconSize 0 left to right direction +scale 2000 * 1500 + class ParserMain { +parseCommand(input : String, books : BookList) : void } @@ -37,11 +40,11 @@ class BookRating { } class CommandList { - {static} ADD_COMMAND : String = "add" + {static} +ADD_COMMAND : String = "add" } ParserMain --> ParserCommands : uses -ParserMain --> BookList +ParserMain --> BookList : uses ParserMain --> BookDisplay : uses ParserMain --> Ui : uses ParserMain --> Exceptions : uses diff --git a/docs/UML_diagrams/AddCommand.png b/docs/UML_diagrams/AddCommand.png index ba9fdee4328d2b214988b641888839977d05ed1b..5a531a689c87c133577c08cf9ab38fba56fe2d56 100644 GIT binary patch literal 35081 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 literal 24622 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 SL+s!y2pI`Q@h|rcy#F7BpTnsD diff --git a/docs/UML_diagrams/ParserMain.png b/docs/UML_diagrams/ParserMain.png index 29bcc3bcbb8f0a537fed74fbf95d80d9ebfab6f8..9d5aec829d19116865a4c8c10d232c4a72284ab6 100644 GIT binary patch literal 32601 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