Skip to content

Commit a558fb7

Browse files
authored
Merge branch 'master' into update-developer-guide
2 parents 71f2876 + 70bbe75 commit a558fb7

File tree

11 files changed

+261
-29
lines changed

11 files changed

+261
-29
lines changed

docs/DeveloperGuide.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,23 @@ The following UML sequence diagram shows how the `delete-note BOOK_TITLE` comman
599599

600600
`InputHandler` uses `Formatter` to print a message indicating that the note was successfully deleted
601601

602+
### Search Title
603+
604+
The `search-title` feature allows the user to search for books in the inventory by providing a keyword. The system returns a list of books whose titles contain the specified keyword.
605+
606+
`InputHandler` coordinates with `InputParser`, `BookList`, and `Formatter` classes to implement the feature.
607+
608+
1. User issues command:
609+
The user inputs the command in the CLI with the required keyword, e.g., `search-title Gatsby`.
610+
2. `InputHandler` first calls `InputParser.extractCommandArgs(...)` to split the user input into command arguments.
611+
3. `InputHandler` checks if the keyword is valid (non-empty). If invalid, an error message is displayed.
612+
4. InputHandler calls `BookList.findBooksByKeyword(keyword)` to retrieve a list of books whose titles contain the keyword.
613+
5. `InputHandler` uses `Formatter` to print the list of matching books.
614+
615+
Design Considerations:
616+
- Case Insensitivity: The search is case-insensitive to improve usability.
617+
618+
602619
### Save Inventory
603620

604621
The save inventory feature automatically saves the inventory each time the user makes a change.
@@ -682,6 +699,53 @@ The following UML sequence diagram shows the relevant behaviour:
682699
- A message is printed indicating the number of books loaded.
683700
- The populated `bookList` is returned.
684701

702+
703+
### Save Loans
704+
705+
The save loans feature ensures that all loans are saved to persistent storage whenever there is a change to the `LoanList`. If no existing persistent storage file is detected, it will be created in the default location `./data/bookKeeper_loanList.txt`. The file path can also be customized using the setLoanFilePath() method.
706+
707+
The method `saveLoans(loanList)` is invoked by `InputHandler` after any method call that makes changes to the current loan list.
708+
709+
The following UML sequence diagram shows the relevant behavior:
710+
711+
![saveLoans.png](images/saveLoans.png)
712+
713+
1. Initialization: `InputHandler` invokes `Storage.saveLoans(loanList)`.
714+
2. Directory Check: A `File` object is created for the directory. If the directory does not exist, it is created using `mkdirs()`.
715+
3. FileWriter Creation: A new `FileWriter` is created for the file at the path specified by `loanListFilePath`.
716+
4. Retrieving Loan List: `getLoanList()` is called on the `LoanList` instance passed into `saveLoans(loanList)` to obtain the list of Loan objects.
717+
5. Writing Each Loan: For each `Loan` in the list, `toFileString()` is called to get a string representation. This string is then written to the file via `FileWriter`.
718+
6. Closing: After writing all loans, `FileWriter` is closed to complete the writing process.
719+
720+
Error Handling: If an `IOException` occurs during any file operations, an error message is displayed via `Formatter.printBorderedMessage()`.
721+
722+
### Load Loans
723+
724+
The load loans feature loads the loan list from the existing persistent data storage file if it exists. If it does not exist, an empty loan list is used. The file path defaults to `./data/bookKeeper_loanList.txt` but can be customized using the `setLoanFilePath()` method.
725+
726+
The method `loadLoans(bookList)` is called once by `InputHandler` at the start of the program.
727+
728+
The following UML sequence diagram shows the relevant behavior:
729+
730+
![loadLoans.png](images/loadLoans.png)
731+
732+
1. Initialization:
733+
734+
`InputHandler` invokes `Storage.loadLoans(bookList)`, which initializes an empty `ArrayList<Loan>`.
735+
736+
2. File Existence Check: A `File` object is created for the loan file path.
737+
- If the file does not exist:
738+
A message is printed using `Formatter.printBorderedMessage()` indicating no saved loans were found. A new file is created, and an empty `ArrayList<Loan>` is returned.
739+
740+
3. File Reading: If the file exists, a `Scanner` reads the file line by line. Each line is passed to `parseLoanFromString(line, bookList)` to convert it into a Loan object.
741+
742+
4. Loan Validation: If the `Loan` is null, a message is printed indicating the entry was skipped.
743+
- If valid, duplicates are checked using `loanList.stream().anyMatch(...)`.
744+
- If a duplicate is found, a message is printed, and the loan is skipped.
745+
- Otherwise, the loan is added to the loanList.
746+
747+
5. Completion: After processing all lines in the file, the `Scanner` is closed. A message is printed indicating the number of loans loaded. The populated `loanList` is returned.
748+
685749
## Appendix A: Product scope
686750

687751
### Target user profile

docs/diagrams/loadInventory.puml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ participant Storage as ":Storage"
44
participant File as ":File"
55
participant Formatter as ":Formatter"
66
participant Scanner as ":Scanner"
7+
participant Book as ":Book"
78

89

910
InputHandler -> Storage: loadInventory()

docs/diagrams/loadLoans.puml

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
@startuml
2+
participant InputHandler as ":InputHandler"
3+
participant Storage as ":Storage"
4+
participant File as ":File"
5+
participant Formatter as ":Formatter"
6+
participant Scanner as ":Scanner"
7+
participant loan as ":Loan"
8+
9+
10+
InputHandler -> Storage: loadInventory()
11+
activate Storage
12+
13+
' Create a new empty loanList
14+
create loanList as ":loanList"
15+
Storage -> loanList: new ArrayList<loan>()
16+
activate loanList
17+
loanList --> Storage: loanList
18+
deactivate loanList
19+
20+
' Create File object for LOAN_LIST_FILE_PATH
21+
Create File
22+
Storage -> File: new File(LOAN_LIST_FILE_PATH)
23+
activate File
24+
File --> Storage: file
25+
deactivate File
26+
27+
opt !file.exists()
28+
Storage -> Formatter: printBorderedMessage("No saved loans found...")
29+
activate Formatter
30+
31+
Formatter --> Storage
32+
deactivate Formatter
33+
34+
Storage --> InputHandler: empty loanList
35+
end
36+
' Open the file using a Scanner
37+
create Scanner
38+
Storage -> Scanner: new Scanner(file)
39+
activate Scanner
40+
Scanner --> Storage: scanner
41+
deactivate Scanner
42+
43+
loop scanner.hasNextLine()
44+
Storage -> Scanner: nextLine()
45+
activate Scanner
46+
47+
Scanner --> Storage: line
48+
deactivate Scanner
49+
50+
Storage -> Storage: parseloanFromString(line)
51+
activate Storage
52+
alt line has invalid format
53+
note over File
54+
returns null if the line has invalid format
55+
end note
56+
else else
57+
create loan
58+
Storage -> loan: create loan with arguments provided in line
59+
activate loan
60+
loan --> Storage: loan
61+
deactivate loan
62+
end
63+
Storage --> Storage: loan
64+
deactivate Storage
65+
66+
opt loan != null
67+
' Check for duplicate loan
68+
Storage -> loanList: Check for duplicate loan objects using stream
69+
activate loanList
70+
loanList --> Storage: isDuplicate
71+
deactivate loanList
72+
alt isDuplicate
73+
' loan already exists, skip adding it
74+
Storage -> Formatter: printBorderedMessage("Duplicate loan found...")
75+
activate Formatter
76+
Formatter --> Storage
77+
deactivate Formatter
78+
79+
else else
80+
' Add loan to loanList
81+
Storage -> loanList: add(loan)
82+
activate loanList
83+
loanList --> Storage
84+
deactivate loanList
85+
end
86+
end
87+
end
88+
89+
90+
91+
Storage -> Scanner: close()
92+
activate Scanner
93+
Scanner --> Storage
94+
deactivate Scanner
95+
destroy Scanner
96+
' Print message with number of loans loaded
97+
Storage -> Formatter: printBorderedMessage("Loaded...")
98+
activate Formatter
99+
Formatter --> Storage
100+
deactivate Formatter
101+
102+
103+
Storage --> InputHandler: loanList
104+
destroy File
105+
deactivate Storage
106+
107+
deactivate Storage
108+
@enduml

docs/diagrams/saveLoans.puml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
@startuml
2+
3+
participant InputHandler as ":InputHandler"
4+
participant Storage as ":Storage"
5+
participant LoanList as ":LoanList"
6+
participant File as ":File"
7+
participant FileWriter as ":FileWriter"
8+
participant Loan as ":Loan"
9+
participant Formatter as ":Formatter"
10+
11+
activate InputHandler
12+
13+
InputHandler -> Storage: saveLoans(loanList)
14+
activate Storage
15+
16+
' Check if directory exists
17+
Storage -> File ** : new File(FOLDER_PATH)
18+
activate File
19+
File --> Storage: directory
20+
deactivate File
21+
opt Directory does not exist
22+
Storage -> File: mkdirs()
23+
activate File
24+
File --> Storage:
25+
deactivate File
26+
end
27+
28+
deactivate File
29+
30+
' Create FileWriter for LOAN_LIST_FILE_PATH
31+
Storage -> FileWriter ** : new FileWriter(LOAN_LIST_FILE_PATH)
32+
activate FileWriter
33+
34+
FileWriter --> Storage: fileWriter
35+
deactivate FileWriter
36+
37+
' Retrieve list of Loans from LoanList
38+
Storage -> LoanList: getLoanList()
39+
activate LoanList
40+
LoanList --> Storage: List<Loan>
41+
deactivate LoanList
42+
43+
' Loop through each Loan and write its file string
44+
loop for each Loan in LoanList
45+
Storage -> Loan: toFileString()
46+
activate Loan
47+
Loan --> Storage: fileString
48+
deactivate Loan
49+
Storage -> FileWriter: write(fileString + lineSeparator)
50+
activate FileWriter
51+
FileWriter --> Storage:
52+
deactivate FileWriter
53+
end
54+
55+
' Close the FileWriter
56+
Storage -> FileWriter: close()
57+
activate FileWriter
58+
59+
FileWriter --> Storage
60+
deactivate FileWriter
61+
62+
destroy FileWriter
63+
Storage --> InputHandler
64+
deactivate Storage
65+
66+
destroy File
67+
68+
@enduml

docs/images/loadLoans.png

90.8 KB
Loading

docs/images/saveLoans.png

47.5 KB
Loading

src/main/java/bookkeeper/exceptions/ErrorMessages.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public final class ErrorMessages {
7878
public static final String INVALID_FORMAT_DELETE_NOTE = "Invalid format for delete-note.\n" +
7979
"Expected format: delete-note BOOK_TITLE";
8080

81-
public static final String INVALID_FORMAT_SEARCH_BOOK = "Invalid format for search-title.\n" +
81+
public static final String INVALID_FORMAT_SEARCH_TITLE = "Invalid format for search-title.\n" +
8282
"Expected format: search-title KEYWORD";
8383

8484
public static final String INVALID_FORMAT_LIST_CATEGORY = "Invalid format for list-category.\n" +

src/main/java/bookkeeper/list/BookList.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,6 @@ public void addBook(Book book) {
2727
bookList.add(book);
2828
}
2929

30-
public Book findBookByTitle(String title) {
31-
for (Book book : bookList) {
32-
if (book.getTitle().equalsIgnoreCase(title)) { // Use equalsIgnoreCase for case-insensitive comparison
33-
return book;
34-
}
35-
}
36-
return null;
37-
}
38-
3930
public Book searchBook(String title) {
4031
for (Book book : bookList) {
4132
if (book.getTitle().equals(title)) {

src/main/java/bookkeeper/logic/InputHandler.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public void askInput() {
7878
updateBook(commandArgs);
7979
break;
8080
case "search-title":
81-
searchBook(commandArgs);
81+
searchTitle(commandArgs);
8282
break;
8383
case "list-category":
8484
listCategory(commandArgs);
@@ -172,7 +172,7 @@ private void addLoan(String[] commandArgs) throws IncorrectFormatException, Book
172172
}
173173
try {
174174
String[] loanArgs = InputParser.extractAddLoanArgs(commandArgs[1]);
175-
Book loanedBook = bookList.findBookByTitle(loanArgs[0]);
175+
Book loanedBook = bookList.searchBook(loanArgs[0]);
176176
if (loanedBook == null) {
177177
Formatter.printBorderedMessage("Book not found in inventory: " + loanArgs[0]);
178178
} else if (loanedBook.isOnLoan()) {
@@ -205,7 +205,7 @@ private void deleteNote(String[] commandArgs) throws IncorrectFormatException, B
205205

206206
String bookTitle = commandArgs[1].trim();
207207

208-
Book book = bookList.findBookByTitle(bookTitle);
208+
Book book = bookList.searchBook(bookTitle);
209209
if (book == null) {
210210
throw new BookNotFoundException("Book not found in inventory: " + bookTitle);
211211
}
@@ -236,7 +236,7 @@ private void addBook(String[] commandArgs) throws IncorrectFormatException, Ille
236236
String bookTitle = bookArgs[0]; //Already trimmed whitespaces in extractAddBookArgs
237237

238238
// Check if book already exists in the inventory
239-
if (bookList.findBookByTitle(bookTitle) != null) {
239+
if (bookList.searchBook(bookTitle) != null) {
240240
Formatter.printBorderedMessage("Book already exists in inventory: " + bookTitle);
241241
return;
242242
}
@@ -268,7 +268,7 @@ private void removeBook(String[] commandArgs) throws IncorrectFormatException, B
268268
throw new IncorrectFormatException(ErrorMessages.INVALID_FORMAT_REMOVE_BOOK);
269269
}
270270
String bookTitle = commandArgs[1].trim();
271-
Book toRemove = bookList.findBookByTitle(bookTitle);
271+
Book toRemove = bookList.searchBook(bookTitle);
272272

273273
if (toRemove == null) {
274274
Formatter.printBorderedMessage("Book not found in inventory: " + bookTitle);
@@ -295,7 +295,7 @@ private void deleteLoan(String[] commandArgs) throws IncorrectFormatException, B
295295
}
296296
try {
297297
String bookTitle = commandArgs[1];
298-
Book loanedBook = bookList.findBookByTitle(bookTitle);
298+
Book loanedBook = bookList.searchBook(bookTitle);
299299
Loan loan = loanList.findLoan(loanedBook);
300300
if (loanedBook == null) {
301301
Formatter.printBorderedMessage("Book not found in inventory: " + bookTitle);
@@ -322,9 +322,9 @@ private void deleteLoan(String[] commandArgs) throws IncorrectFormatException, B
322322
* @param commandArgs The parsed command arguments
323323
* @throws IncorrectFormatException If the input format is invalid
324324
*/
325-
private void searchBook(String[] commandArgs) throws IncorrectFormatException {
325+
private void searchTitle(String[] commandArgs) throws IncorrectFormatException {
326326
if (commandArgs.length < 2) {
327-
throw new IncorrectFormatException(ErrorMessages.INVALID_FORMAT_SEARCH_BOOK);
327+
throw new IncorrectFormatException(ErrorMessages.INVALID_FORMAT_SEARCH_TITLE);
328328
}
329329

330330
String keyword = commandArgs[1].trim();
@@ -417,12 +417,12 @@ private void updateTitle(String[] commandArgs) throws IncorrectFormatException,
417417
throw new IncorrectFormatException(ErrorMessages.INVALID_FORMAT_SAME_TITLE);
418418
}
419419

420-
if(bookList.findBookByTitle(newTitle) != null) {
420+
if(bookList.searchBook(newTitle) != null) {
421421
throw new IncorrectFormatException(ErrorMessages.INVALID_FORMAT_DUPLICATE_TITLE);
422422
}
423423

424424
// Check if book already exists in the inventory
425-
Book book = bookList.findBookByTitle(oldTitle);
425+
Book book = bookList.searchBook(oldTitle);
426426
if (book == null) {
427427
throw new BookNotFoundException("Book not found in inventory: " + oldTitle);
428428
}
@@ -449,7 +449,7 @@ private void editLoan(String[] commandArgs) throws IncorrectFormatException, Boo
449449
String email = editLoanArgs[4];
450450

451451
// Check if book already exists in the inventory
452-
Book book = bookList.findBookByTitle(bookTitle);
452+
Book book = bookList.searchBook(bookTitle);
453453
Loan loan = loanList.findLoan(book);
454454

455455
if (book == null) {

src/main/java/bookkeeper/storage/Storage.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public static ArrayList<Book> loadInventory() {
117117

118118
// Skip duplicate books
119119
boolean isDuplicate = bookList.stream()
120-
.anyMatch(existingBook -> existingBook.getTitle().equalsIgnoreCase(book.getTitle()));
120+
.anyMatch(existingBook -> existingBook.getTitle().equals(book.getTitle()));
121121
if (isDuplicate) {
122122
Formatter.printBorderedMessage("Duplicate book found and skipped: " + book.getTitle());
123123
continue;
@@ -225,7 +225,7 @@ private static Loan parseLoanFromString(String line, BookList bookList) {
225225
String email = parts[4].trim();
226226

227227
// Find the book in the inventory
228-
Book loanedBook = bookList.findBookByTitle(title);
228+
Book loanedBook = bookList.searchBook(title);
229229
if (loanedBook == null) {
230230
Formatter.printBorderedMessage("Invalid loan: Book not found in inventory - " + title);
231231
return null; // Skip this loan

0 commit comments

Comments
 (0)