diff --git a/LICENSE b/LICENSE index 39b3478982c3..4d9f5c7cd00d 100644 --- a/LICENSE +++ b/LICENSE @@ -2,11 +2,11 @@ MIT License Copyright (c) 2016 Software Engineering Education - FOSS Resources -Permission is hereby granted, free of charge, to any person obtaining a copy +Permission is hereby granted, free of charge, to any book obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is +copies of the Software, and to permit books to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all diff --git a/README.adoc b/README.adoc index 03eff3a4d191..70454f849cb8 100644 --- a/README.adoc +++ b/README.adoc @@ -1,38 +1,30 @@ -= Address Book (Level 4) += Catalogue ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] -https://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] +https://travis-ci.org/CS2103JAN2018-T16-B1/main[image:https://travis-ci.org/CS2103JAN2018-T16-B1/main.svg?branch=master[Build Status]] +https://coveralls.io/github/CS2103JAN2018-T16-B1/main?branch=master[image:https://coveralls.io/repos/github/CS2103JAN2018-T16-B1/main/badge.svg?branch=master[Coverage Status]] + ifdef::env-github[] -image::docs/images/Ui.png[width="600"] +image::docs/images/Ui_1.png[width="600"] endif::[] ifndef::env-github[] -image::images/Ui.png[width="600"] +image::images/Ui_1.png[width="600"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. +* This is a desktop Catalogue application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). +* It is a Java application intended for librarians, students and guests using the library. == Site Map * <> * <> -* <> * <> * <> == Acknowledgements - +* This sample application is adapted from https://github.com/se-edu/[se-edu]. * Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by _Marco Jakob_. * Libraries used: https://github.com/TomasMikula/EasyBind[EasyBind], https://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit4[JUnit4] diff --git a/build.gradle b/build.gradle index 50cd2ae52efc..ce242b8dc822 100644 --- a/build.gradle +++ b/build.gradle @@ -57,7 +57,7 @@ dependencies { } shadowJar { - archiveName = "addressbook.jar" + archiveName = "catalogue.jar" destinationDir = file("${buildDir}/jar/") } @@ -127,8 +127,8 @@ test { boolean runNonGuiTests = gradle.taskGraph.hasTask(nonGuiTests) if (!runGuiTests && !runNonGuiTests) { - runGuiTests = true; - runNonGuiTests = true; + runGuiTests = true + runNonGuiTests = true } if (runNonGuiTests) { diff --git a/collated/functional/LeKhangTai.md b/collated/functional/LeKhangTai.md new file mode 100644 index 000000000000..2defb4b620d0 --- /dev/null +++ b/collated/functional/LeKhangTai.md @@ -0,0 +1,541 @@ +# LeKhangTai +###### /java/seedu/address/logic/parser/ReserveCommandParser.java +``` java +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.ReserveCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and create a new ReserveCommand object + */ +public class ReserveCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the ReserveCommand + * and returns an ReserveCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public ReserveCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new ReserveCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ReserveCommand.MESSAGE_USAGE)); + } + } +} + +``` +###### /java/seedu/address/logic/parser/BorrowCommandParser.java +``` java +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.BorrowCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and create a new BorrowCommand object + */ + +public class BorrowCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the BorrowCommand + * and returns an BorrowCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public BorrowCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new BorrowCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, BorrowCommand.MESSAGE_USAGE)); + } + } +} + +``` +###### /java/seedu/address/logic/parser/ReturnCommandParser.java +``` java +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.ReturnCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and create a new ReturnCommand object + */ +public class ReturnCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ReturnCommand + * and returns an ReturnCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + + public ReturnCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new ReturnCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ReturnCommand.MESSAGE_USAGE)); + } + } + +} + +``` +###### /java/seedu/address/logic/commands/ReturnCommand.java +``` java +import static java.util.Objects.requireNonNull; +import static seedu.address.model.book.Avail.AVAILABLE; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.book.Author; +import seedu.address.model.book.Avail; +import seedu.address.model.book.Book; +import seedu.address.model.book.Isbn; +import seedu.address.model.book.Title; +import seedu.address.model.book.exceptions.BookNotFoundException; +import seedu.address.model.tag.Tag; + +/** + * Un-mark a borrowed book to make it available for borrowing + */ + +public class ReturnCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "return"; + + public static final String MESSAGE_RETURN_BOOK_SUCCESS = "Book is returned: %1$s"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Returns book whose title matches the input \n" + + "Parameters: INDEX (must be a positive integer) \n" + + "Example: " + COMMAND_WORD + " Harry Potter and the Half Blood Prince"; + + public static final String MESSAGE_BOOK_CANNOT_BE_RETURNED = "Book cannot be returned as it is already available"; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_LIBRARIAN; + + private final Index targetIndex; + + private Book bookToReturn; + private Book returnedBook; + + public ReturnCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + /** + * @param bookToBorrow + * @return duplicated book with Available Availability + */ + private static Book createReturnedBook(Book bookToBorrow) { + assert bookToBorrow != null; + + Title updatedTitle = bookToBorrow.getTitle(); + Isbn updatedIsbn = bookToBorrow.getIsbn(); + Avail updatedAvail = new Avail(AVAILABLE); + Author updatedAuthor = bookToBorrow.getAuthor(); + Set updatedTags = bookToBorrow.getTags(); + + return new Book(updatedTitle, updatedAuthor, updatedIsbn, updatedAvail, updatedTags); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(bookToReturn); + try { + model.returnBook(bookToReturn, returnedBook); + return new CommandResult(String.format(MESSAGE_RETURN_BOOK_SUCCESS, bookToReturn)); + } catch (BookNotFoundException pnfe) { + throw new CommandException(MESSAGE_BOOK_CANNOT_BE_RETURNED); + } + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredBookList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + } + + bookToReturn = lastShownList.get(targetIndex.getZeroBased()); + returnedBook = createReturnedBook(bookToReturn); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ReturnCommand // instanceof handles nulls + && this.targetIndex.equals(((ReturnCommand) other).targetIndex) // state check + && Objects.equals(this.bookToReturn, ((ReturnCommand) other).bookToReturn)); + } + + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} + +``` +###### /java/seedu/address/logic/commands/BorrowCommand.java +``` java +import static java.util.Objects.requireNonNull; +import static seedu.address.model.book.Avail.BORROWED; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.book.Author; +import seedu.address.model.book.Avail; +import seedu.address.model.book.Book; +import seedu.address.model.book.Isbn; +import seedu.address.model.book.Title; +import seedu.address.model.book.exceptions.BookNotFoundException; +import seedu.address.model.tag.Tag; + +/** + * Borrows a book + */ +public class BorrowCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "borrow"; + public static final String MESSAGE_BORROW_BOOK_SUCCESS = "New book borrowed: %1$s"; + public static final String MESSAGE_FAILURE = "Book not available for borrowing!"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Borrows the book identified by the index number used in the last book listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_STUDENT; + + private final Index targetIndex; + + private Book bookToBorrow; + private Book borrowedBook; + + + public BorrowCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + /** + * @param bookToBorrow + * @return duplicated book with Borrowed Availability + */ + public Book createBorrowedBook(Book bookToBorrow) { + assert bookToBorrow != null; + + Title updatedTitle = bookToBorrow.getTitle(); + Isbn updatedIsbn = bookToBorrow.getIsbn(); + Avail updatedAvail = new Avail(BORROWED); + Author updatedAuthor = bookToBorrow.getAuthor(); + Set updatedTags = bookToBorrow.getTags(); + + return new Book(updatedTitle, updatedAuthor, updatedIsbn, updatedAvail, updatedTags); + } + + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + + try { + model.borrowBook(bookToBorrow, borrowedBook); + } catch (BookNotFoundException pnfe) { + throw new CommandException(MESSAGE_FAILURE); + } + return new CommandResult(String.format(MESSAGE_BORROW_BOOK_SUCCESS, bookToBorrow)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredBookList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + } + bookToBorrow = lastShownList.get(targetIndex.getZeroBased()); + borrowedBook = createBorrowedBook(bookToBorrow); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BorrowCommand that = (BorrowCommand) o; + return Objects.equals(targetIndex, that.targetIndex) + && Objects.equals(bookToBorrow, that.bookToBorrow); + } + + @Override + public int hashCode() { + return Objects.hash(targetIndex, bookToBorrow); + } + + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} + +``` +###### /java/seedu/address/logic/commands/ReserveCommand.java +``` java +import static java.util.Objects.requireNonNull; +import static seedu.address.model.book.Avail.RESERVED; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.book.Author; +import seedu.address.model.book.Avail; +import seedu.address.model.book.Book; +import seedu.address.model.book.Isbn; +import seedu.address.model.book.Title; +import seedu.address.model.book.exceptions.BookNotFoundException; +import seedu.address.model.tag.Tag; + +/** + * Reserves a book + */ +public class ReserveCommand extends UndoableCommand { + public static final String COMMAND_WORD = "reserve"; + public static final String MESSAGE_RESERVE_BOOK_SUCCESS = "Book reserved: %1$s"; + public static final String MESSAGE_FAILURE = "Book not available for reserving!"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Reserves the book identified by the index number used in the last book listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_STUDENT; + + private final Index targetIndex; + + private Book bookToReserve; + private Book reservedBook; + + public ReserveCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + /** + * @param bookToReserve + * @return duplicated book with Reserved Availability + */ + private static Book createReservedBook(Book bookToReserve) { + assert bookToReserve != null; + + Title updatedTitle = bookToReserve.getTitle(); + Isbn updatedIsbn = bookToReserve.getIsbn(); + Avail updatedAvail = new Avail(RESERVED); + Author updatedAuthor = bookToReserve.getAuthor(); + Set updatedTags = bookToReserve.getTags(); + + return new Book(updatedTitle, updatedAuthor, updatedIsbn, updatedAvail, updatedTags); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(bookToReserve); + try { + model.reserveBook(bookToReserve, reservedBook); + } catch (BookNotFoundException pnfe) { + throw new CommandException(MESSAGE_FAILURE); + } + return new CommandResult(String.format(MESSAGE_RESERVE_BOOK_SUCCESS, bookToReserve)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredBookList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + } + bookToReserve = lastShownList.get(targetIndex.getZeroBased()); + reservedBook = createReservedBook(bookToReserve); + } + + @Override + public int hashCode() { + return Objects.hash(targetIndex, bookToReserve); + } + + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} + +``` +###### /java/seedu/address/model/Catalogue.java +``` java + /** + * @param target book that is selected by index to return + * @param returnedBook duplicated book that will replace the original book + * @throws BookNotFoundException + */ + public void returnBook(Book target, Book returnedBook) + throws BookNotFoundException { + requireNonNull(returnedBook); + + Book syncedEditedBook = syncWithMasterTagList(returnedBook); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any book + // in the book list. + books.replaceReturnedBook(target, syncedEditedBook); + } + + /** + * @param target book that is selected by index to borrow + * @param borrowedBook duplicated book that will replace the original book + * @throws BookNotFoundException + */ + public void borrowBook(Book target, Book borrowedBook) + throws BookNotFoundException { + requireNonNull(borrowedBook); + + Book syncedEditedBook = syncWithMasterTagList(borrowedBook); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any book + // in the book list. + books.replaceBorrowedBook(target, syncedEditedBook); + } + + /** + * @param target book that is selected by index to reserve + * @param reservedBook duplicated book that will replace the original book + * @throws BookNotFoundException + */ + public void reserveBook(Book target, Book reservedBook) + throws BookNotFoundException { + requireNonNull(reservedBook); + + Book syncedEditedBook = syncWithMasterTagList(reservedBook); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any book + // in the book list. + books.replaceReservedBook(target, syncedEditedBook); + } +``` +###### /java/seedu/address/model/ModelManager.java +``` java + @Override + public synchronized void returnBook(Book target, Book returnedBook) throws BookNotFoundException { + catalogue.returnBook(target, returnedBook); + indicateCatalogueChanged(); + } + @Override + public synchronized void borrowBook(Book target, Book borrowedBook) throws BookNotFoundException { + catalogue.borrowBook(target, borrowedBook); + updateFilteredBookList(PREDICATE_SHOW_ALL_BOOKS); + indicateCatalogueChanged(); + } + @Override + public synchronized void reserveBook(Book target, Book reservedBook) throws BookNotFoundException { + catalogue.reserveBook(target, reservedBook); + updateFilteredBookList(PREDICATE_SHOW_ALL_BOOKS); + indicateCatalogueChanged(); + } +``` +###### /java/seedu/address/model/book/UniqueBookList.java +``` java + /** + * @param target select the index book to be returned + * @param returnedBook duplicated book that will replace original book + * @throws BookNotFoundException + */ + public void replaceReturnedBook(Book target, Book returnedBook) throws BookNotFoundException { + requireNonNull(returnedBook); + int index = internalList.indexOf(target); + String status = target.getAvail().toString(); + switch (status) { + case (BORROWED): + internalList.set(index, returnedBook); + break; + case (RESERVED): + internalList.set(index, returnedBook); + break; + + default: + throw new BookNotFoundException(); + } + } + + /** + * @param target select the index book to be borrowed + * @param borrowedBook duplicated book that will replace original book + * @throws BookNotFoundException + */ + public void replaceBorrowedBook(Book target, Book borrowedBook) throws BookNotFoundException { + requireNonNull(borrowedBook); + + int index = internalList.indexOf(target); + String status = target.getAvail().toString(); + switch (status) { + case (AVAILABLE): + internalList.set(index, borrowedBook); + break; + + default: + throw new BookNotFoundException(); + } + } + + /** + * @param target select the index book to be reserved + * @param reservedBook duplicated book that will replace original book + * @throws BookNotFoundException + */ + public void replaceReservedBook(Book target, Book reservedBook) throws BookNotFoundException { + + requireNonNull(reservedBook); + int index = internalList.indexOf(target); + String status = target.getAvail().toString(); + switch (status) { + case (BORROWED): + internalList.set(index, reservedBook); + break; + + default: + throw new BookNotFoundException(); + } + } +``` diff --git a/collated/functional/QiuHaohao.md b/collated/functional/QiuHaohao.md new file mode 100644 index 000000000000..ba07aa3bf5ca --- /dev/null +++ b/collated/functional/QiuHaohao.md @@ -0,0 +1,1275 @@ +# QiuHaohao +###### /java/seedu/address/commons/events/model/AccountListChangedEvent.java +``` java +package seedu.address.commons.events.model; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.account.UniqueAccountList; + +/** + * Indicates the AccountList in the model has changed + */ +public class AccountListChangedEvent extends BaseEvent { + + public final UniqueAccountList data; + + public AccountListChangedEvent(UniqueAccountList data) { + this.data = data; + } + + @Override + public String toString() { + return "Number of accounts: " + data.size(); + } +} +``` +###### /java/seedu/address/logic/parser/LoginCommandParser.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.LoginCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.account.Password; +import seedu.address.model.account.Username; + +/** + * Parses input arguments and creates a new LoginCommand object + */ +public class LoginCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the LoginCommand + * and returns an LoginCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public LoginCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + String[] nameKeywords = trimmedArgs.split("\\s+"); + if (nameKeywords.length != 2 + || !Username.isValidUsername(nameKeywords[0]) + || !Password.isValidPassword(nameKeywords[1])) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, LoginCommand.MESSAGE_USAGE)); + } + + String username = nameKeywords[0]; + String password = nameKeywords[1]; + + return new LoginCommand(username, password); + } +} +``` +###### /java/seedu/address/logic/commands/DeleteCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /java/seedu/address/logic/commands/ListCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /java/seedu/address/logic/commands/RedoCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /java/seedu/address/logic/commands/LoginCommand.java +``` java +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.model.Model; +import seedu.address.model.account.Credential; +import seedu.address.model.account.PrivilegeLevel; + +/** + * Logs in as student or librarian. + */ +public class LoginCommand extends Command { + public static final String COMMAND_WORD = "login"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Login as student or librarian.\n" + + "Parameters: USERNAME PASSWORD(both username and password should be at least 5 chars long)\n" + + "Example: " + COMMAND_WORD + " MyUsername MyPassword"; + + public static final String MESSAGE_LOGGED_IN_AS_STUTENT = "You are logged in as student"; + public static final String MESSAGE_LOGGED_IN_AS_LIBRARIAN = "You are logged in as librarian"; + public static final String MESSAGE_NOT_LOGGED_IN = "Wrong username/password, try again"; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_GUEST; + + + private final Credential credential; + + + public LoginCommand(String username, String password) { + requireNonNull(username); + requireNonNull(password); + this.credential = new Credential(username, password); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LoginCommand // instanceof handles nulls + && credential.equals(((LoginCommand) other).credential)); + } + + @Override + public CommandResult execute() { + PrivilegeLevel newPrivilegeLevel = model.authenticate(this.credential); + if (newPrivilegeLevel.equals(Model.PRIVILEGE_LEVEL_GUEST)) { + return new CommandResult(MESSAGE_NOT_LOGGED_IN); + } + if (newPrivilegeLevel.equals(Model.PRIVILEGE_LEVEL_STUDENT)) { + return new CommandResult(MESSAGE_LOGGED_IN_AS_STUTENT); + } + if (newPrivilegeLevel.equals(Model.PRIVILEGE_LEVEL_LIBRARIAN)) { + return new CommandResult(MESSAGE_LOGGED_IN_AS_LIBRARIAN); + } + return new CommandResult(MESSAGE_NOT_LOGGED_IN); + } + + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /java/seedu/address/logic/commands/ClearCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /java/seedu/address/logic/commands/Command.java +``` java + public PrivilegeLevel getPrivilegeLevel() { + return Model.PRIVILEGE_LEVEL_GUEST; + } +} +``` +###### /java/seedu/address/logic/commands/AddCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /java/seedu/address/logic/commands/HelpCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /java/seedu/address/logic/commands/HistoryCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /java/seedu/address/logic/commands/EditCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +``` +###### /java/seedu/address/logic/commands/SelectCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /java/seedu/address/logic/commands/UndoCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /java/seedu/address/logic/commands/FindCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /java/seedu/address/logic/commands/LogoutCommand.java +``` java +package seedu.address.logic.commands; + +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; + +/** + * Logs out as student or librarian. + */ + +public class LogoutCommand extends Command { + public static final String COMMAND_WORD = "logout"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Logout as student or librarian.\n" + + "Example: " + COMMAND_WORD; + + public static final String MESSAGE_LOGGED_OUT = "You are logged out."; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_STUDENT; + + @Override + public CommandResult execute() { + model.logout(); + return new CommandResult(MESSAGE_LOGGED_OUT); + } + + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LogoutCommand); + } +} +``` +###### /java/seedu/address/logic/commands/ExitCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /java/seedu/address/logic/LogicManager.java +``` java + protected boolean isPrivileged(Command command) { + return command.getPrivilegeLevel().compareTo(model.getPrivilegeLevel()) <= 0; + } +} +``` +###### /java/seedu/address/storage/Storage.java +``` java + String getAccountListFilePath(); + + Optional readAccountList() throws DataConversionException, IOException; + + void saveAccountList(UniqueAccountList accountList) throws IOException; + + void handleAccountListChangedEvent(AccountListChangedEvent event); +} +``` +###### /java/seedu/address/storage/SerialisedAccountListStorage.java +``` java +package seedu.address.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.account.UniqueAccountList; + +/** + * A class to access AccountList data stored as an .ser file on the hard disk. + */ +public class SerialisedAccountListStorage implements AccountListStorage { + private static final Logger logger = LogsCenter.getLogger(SerialisedAccountListStorage.class); + + private String filePath; + + public SerialisedAccountListStorage(String filePath) { + this.filePath = filePath; + } + + public String getAccountListFilePath() { + return filePath; + } + + @Override + public Optional readAccountList() throws DataConversionException, IOException { + return readAccountList(filePath); + } + + @Override + public Optional readAccountList(String filePath) throws DataConversionException, IOException { + requireNonNull(filePath); + FileInputStream file = new FileInputStream(filePath); + ObjectInputStream in = new ObjectInputStream(file); + + if (!new File(filePath).exists()) { + logger.info("AccountList file " + filePath + " not found"); + return Optional.empty(); + } + + UniqueAccountList accountList = SerialisedFileStorage.loadDataFromSaveFile(in); + return Optional.of(accountList); + } + + @Override + public void saveAccountList(UniqueAccountList accountList) throws IOException { + saveAccountList(accountList, filePath); + } + + @Override + public void saveAccountList(UniqueAccountList accountList, String filePath) throws IOException { + requireNonNull(accountList); + requireNonNull(filePath); + + FileOutputStream file = new FileOutputStream(filePath); + ObjectOutputStream out = new ObjectOutputStream(file); + SerialisedFileStorage.saveDataToFile(out, accountList); + out.close(); + file.close(); + } +} +``` +###### /java/seedu/address/storage/StorageManager.java +``` java + @Override + @Subscribe + public void handleAccountListChangedEvent(AccountListChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, "AccountList data changed, saving to file")); + try { + saveAccountList(event.data); + } catch (IOException e) { + raise(new DataSavingExceptionEvent(e)); + } + } + + @Override + public String getAccountListFilePath() { + return accountListStorage.getAccountListFilePath(); + } + + @Override + public Optional readAccountList() throws DataConversionException, IOException { + return readAccountList(accountListStorage.getAccountListFilePath()); + } + + @Override + public Optional readAccountList(String filePath) throws DataConversionException, IOException { + logger.fine("Attempting to read data from file: " + filePath); + return accountListStorage.readAccountList(filePath); + } + + @Override + public void saveAccountList(UniqueAccountList accountList) throws IOException { + saveAccountList(accountList, accountListStorage.getAccountListFilePath()); + } + + @Override + public void saveAccountList(UniqueAccountList accountList, String filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + accountListStorage.saveAccountList(accountList, filePath); + } + +} +``` +###### /java/seedu/address/storage/AccountListStorage.java +``` java +package seedu.address.storage; + +import java.io.IOException; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.account.UniqueAccountList; + +//import java.util.Optional; + +/** + * Represents a storage for {@link UniqueAccountList}. + */ +public interface AccountListStorage { + /** + * Returns the file path of the data file. + */ + String getAccountListFilePath(); + + /** + * Returns AccountList data as a {@link UniqueAccountList}. + * Returns {@code Optional.empty()} if storage file is not found. + * + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional readAccountList() throws DataConversionException, IOException; + + /** + * @see #getAccountListFilePath() + */ + Optional readAccountList(String filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link UniqueAccountList} to the storage. + * + * @param accountList cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveAccountList(UniqueAccountList accountList) throws IOException; + + /** + * @see #saveAccountList(UniqueAccountList) + */ + void saveAccountList(UniqueAccountList accountList, String filePath) throws IOException; + +} +``` +###### /java/seedu/address/storage/SerialisedFileStorage.java +``` java +package seedu.address.storage; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.account.UniqueAccountList; + +/** + * Stores accountList data in a .ser file + */ +public class SerialisedFileStorage { + /** + * Saves the given catalogue data to the specified file. + */ + public static void saveDataToFile(ObjectOutputStream out, UniqueAccountList accountList) { + try { + out.writeObject(accountList); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Returns catalogue in the file or an empty catalogue + */ + public static UniqueAccountList loadDataFromSaveFile(ObjectInputStream in) throws DataConversionException { + try { + return (UniqueAccountList) in.readObject(); + } catch (IOException e) { + throw new DataConversionException(e); + } catch (ClassNotFoundException e) { + throw new DataConversionException(e); + } + } +} +``` +###### /java/seedu/address/MainApp.java +``` java + try { + catalogueOptional = storage.readCatalogue(); + if (!catalogueOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample Catalogue"); + } + initialData = catalogueOptional.orElseGet(SampleDataUtil::getSampleCatalogue); + } catch (DataConversionException e) { + logger.warning("Data file not in the correct format. Will be starting with an empty Catalogue"); + initialData = new Catalogue(); + } catch (IOException e) { + logger.warning("Problem while reading from the file. Will be starting with an empty Catalogue"); + initialData = new Catalogue(); + } + + try { + accountListOptional = storage.readAccountList(); + if (!accountListOptional.isPresent()) { + logger.info("AccountList file not found. Will be starting with an accountList with only admin"); + initlaAccountList = new UniqueAccountList(); + } else { + initlaAccountList = accountListOptional.get(); + } + } catch (DataConversionException e) { + logger.warning("AccountList file not in the correct format. " + + "Will be starting with an accountList with only admin"); + initlaAccountList = new UniqueAccountList(); + } catch (IOException e) { + logger.warning("Problem while reading from the AccountList file. " + + "Will be starting with an accountList with only admin"); + System.out.print(e.getMessage()); + initlaAccountList = new UniqueAccountList(); + } + + try { + if (!initlaAccountList.contains(Account.createDefaultAdminAccount())) { + initlaAccountList.add(Account.createDefaultAdminAccount()); + } + } catch (DuplicateAccountException e) { + e.printStackTrace(); + } + return new ModelManager(initialData, initlaAccountList, userPrefs); +``` +###### /java/seedu/address/model/UserPrefs.java +``` java + public String getAccountListFilePath() { + return accountListFilePath; + } + + public void setAccountListFilePath(String accountListFilePath) { + this.accountListFilePath = accountListFilePath; + } + +``` +###### /java/seedu/address/model/ModelManager.java +``` java + + /** + * Initializes a ModelManager with the given catalogue, accountList and userPrefs. + */ + public ModelManager(ReadOnlyCatalogue catalogue, UniqueAccountList accountList, UserPrefs userPrefs) { + super(); + requireAllNonNull(catalogue, accountList, userPrefs); + + logger.fine("Initializing with catalogue: " + catalogue + + ", accountList: " + accountList + + " and user prefs " + userPrefs); + + this.catalogue = new Catalogue(catalogue); + filteredBooks = new FilteredList<>(this.catalogue.getBookList()); + this.accountList = accountList; + this.currentAccount = Account.createGuestAccount(); + } +``` +###### /java/seedu/address/model/ModelManager.java +``` java + + /** + * Adds an account to the AccountList + * + * @param account + * @throws DuplicateAccountException + */ + public void addAccount(Account account) throws DuplicateAccountException { + accountList.add(account); + indicateAccountListChanged(); + } + + /** + * Deletes an account from the AccountList + * + * @param account + * @throws AccountNotFoundException + */ + public void deleteAccount(Account account) throws AccountNotFoundException { + if (account == null) { + throw new AccountNotFoundException("Account not Found!"); + } + accountList.remove(account); + indicateAccountListChanged(); + } + + /** + * Replaces an account with a new one + * + * @param account + * @param editedAccount + * @throws DuplicateAccountException + * @throws AccountNotFoundException + */ + public void updateAccount(Account account, Account editedAccount) + throws DuplicateAccountException, AccountNotFoundException { + accountList.setAccount(account, account); + indicateAccountListChanged(); + } +``` +###### /java/seedu/address/model/ModelManager.java +``` java + + /** + * Raises an event to indicate the model has changed + */ + private void indicateAccountListChanged() { + raise(new AccountListChangedEvent(accountList)); + } +``` +###### /java/seedu/address/model/ModelManager.java +``` java + @Override + public PrivilegeLevel authenticate(Credential c) { + Account matched = accountList.authenticate(c); + if (matched != null) { + this.currentAccount = matched; + return currentAccount.getPrivilegeLevel(); + } + //if not found + return PRIVILEGE_LEVEL_GUEST; + } + + @Override + public void logout() { + currentAccount = Account.createGuestAccount(); + } + + @Override + public PrivilegeLevel getPrivilegeLevel() { + return this.currentAccount.getPrivilegeLevel(); + } +``` +###### /java/seedu/address/model/account/UniqueAccountList.java +``` java +package seedu.address.model.account; + +import static java.util.Objects.requireNonNull; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; + +import seedu.address.model.account.exceptions.AccountNotFoundException; +import seedu.address.model.account.exceptions.DuplicateAccountException; + + +/** + * A list of accounts that enforces uniqueness between its elements and does not allow nulls. + *

+ * Supports a minimal set of list operations. + * + * @see Account#equals(Object) + */ +public class UniqueAccountList implements Serializable, Iterable { + private final ArrayList internalList = new ArrayList(); + + /** + * Returns true if the list contains an equivalent account as the given argument. + */ + public boolean contains(Account toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds a account to the list. + * + * @throws DuplicateAccountException if the account to add is an account with the same username in the list. + */ + public void add(Account toAdd) throws DuplicateAccountException { + requireNonNull(toAdd); + if (containsUsername(toAdd)) { + throw new DuplicateAccountException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the account {@code target} in the list with {@code editedAccount}. + * + * @throws DuplicateAccountException if the replacement is equivalent to another existing account in the list. + * @throws AccountNotFoundException if {@code target} could not be found in the list. + */ + public void setAccount(Account target, Account editedAccount) + throws DuplicateAccountException, AccountNotFoundException { + requireNonNull(editedAccount); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new AccountNotFoundException(); + } + + if (!target.usernameMatches(editedAccount) && this.containsUsername(target)) { + throw new DuplicateAccountException(); + } + + internalList.set(index, editedAccount); + } + + /** + * Removes the equivalent account from the list. + * + * @throws AccountNotFoundException if no such account could be found in the list. + */ + public boolean remove(Account toRemove) throws AccountNotFoundException { + //if (model.getAccountList().searchIfUsernameExist(new Username(username))) { + // throw new AccountNotFoundException("Account not found!"); + //} + requireNonNull(toRemove); + final boolean accountFoundAndDeleted = internalList.remove(toRemove); + if (!accountFoundAndDeleted) { + throw new AccountNotFoundException(); + } + return accountFoundAndDeleted; + } + + /** + * Returns the account that matches with the provided credential, + * returns null if none exists + * + * @param c + * @return + */ + public Account authenticate(Credential c) { + for (Account a : internalList) { + if (a.credentialMatches(c)) { + return a; + } + } + return null; + } + + /** + * Returns true if there is an account with the username provided + * + * @param u + * @return + */ + public boolean containsUsername(Username u) { + for (Account a : internalList) { + if (a.usernameMatches(u)) { + return true; + } + } + return false; + } + + /** + * Returns true if there is an account with an username that is the + * same as that of the credential provided + * + * @param c + * @return + */ + public boolean containsUsername(Credential c) { + return containsUsername(c.getUsername()); + } + + /** + * Returns true if there is an account with an username that is the + * same as that of the account provided + * + * @param a + * @return + */ + public boolean containsUsername(Account a) { + return containsUsername(a.getCredential()); + } + + /** + * Returns the account if there is an account with the username provided + * + * @param u + * @return + */ + public Account searchByUsername(Username u) { + for (Account a : internalList) { + if (a.usernameMatches(u)) { + return a; + } + } + return null; + } + /** + * Returns true if account exists with such username provided + * + * @param u + * @return + */ + public boolean searchIfUsernameExist (Username u) { + for (Account a : internalList) { + if (a.usernameMatches(u)) { + return true; + } + } + return false; + } + + + public int size() { + return internalList.size(); + } + + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueAccountList // instanceof handles nulls + && this.internalList.equals(((UniqueAccountList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + +} +``` +###### /java/seedu/address/model/account/PrivilegeLevel.java +``` java +package seedu.address.model.account; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Represents the privilegeLevel of an account/a command + */ +public class PrivilegeLevel implements Comparable, Serializable { + + public static final int PRIVILEGE_LEVEL_GUEST = 0; + public static final int PRIVILEGE_LEVEL_STUDENT = 1; + public static final int PRIVILEGE_LEVEL_LIBRARIAN = 2; + public static final String MESSAGE_PRIVILEGE_LEVEL_CONSTRAINTS = + "Privilege Level should be an integer from 0 to 2 inclusive."; + private final int privilegeLevel; + + /** + * Constructs a PrivilegeLevel + * + * @param privilegeLevel + */ + public PrivilegeLevel(int privilegeLevel) { + requireNonNull(privilegeLevel); + checkArgument(isValidPrivilegeLevel(privilegeLevel), MESSAGE_PRIVILEGE_LEVEL_CONSTRAINTS); + this.privilegeLevel = privilegeLevel; + } + + /** + * Returns true if a given string is a valid PrivilegeLevel + */ + public static boolean isValidPrivilegeLevel(int test) { + return test >= PRIVILEGE_LEVEL_GUEST + && test <= PRIVILEGE_LEVEL_LIBRARIAN; + } + + public int getPrivilegeLevel() { + return privilegeLevel; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PrivilegeLevel that = (PrivilegeLevel) o; + return privilegeLevel == that.privilegeLevel; + } + + @Override + public int hashCode() { + + return Objects.hash(privilegeLevel); + } + + + @Override + public int compareTo(PrivilegeLevel o) { + return this.privilegeLevel - o.privilegeLevel; + } +} +``` +###### /java/seedu/address/model/account/MatricNumber.java +``` java +package seedu.address.model.account; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.io.Serializable; + +/** + * Represents a set of username and password + */ +public class MatricNumber implements Serializable { + public static final String MESSAGE_MATRIC_NUMBER_CONSTRAINTS = + "Matriculation number should start with \"A\", followed by 7 digits and end with uppercase letter."; + + public static final String MATRIC_NUMBER_VALIDATION_REGEX = "A[0-9]{7}[A-Z]"; + + + private final String matricNumber; + + /** + * Constructs a {@code Credential} + * + * @param matricNumber A valid matric number + */ + public MatricNumber(String matricNumber) { + requireNonNull(matricNumber); + checkArgument(isValidMatricNumber(matricNumber), MESSAGE_MATRIC_NUMBER_CONSTRAINTS); + this.matricNumber = matricNumber; + } + + /** + * Returns true if a given string is a valid MatricNumber. + */ + public static boolean isValidMatricNumber(String test) { + return test.matches(MATRIC_NUMBER_VALIDATION_REGEX); + } + + /** + * Returns MatricNumber. + */ + public String getMatricNumber() { + return matricNumber; + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof MatricNumber // short circuit if same obj + && this.getMatricNumber().equals(((MatricNumber) other).getMatricNumber()) //check status + ); + } + + @Override + public String toString() { + return matricNumber; + } + + @Override + public int hashCode() { + return matricNumber.hashCode(); + } +} +``` +###### /java/seedu/address/model/account/exceptions/AccountNotFoundException.java +``` java +package seedu.address.model.account.exceptions; + +/** + * Signals that the operation is unable to find the specified account. + */ +public class AccountNotFoundException extends Exception { + + /** + * @param message should contain relevant information on the failed constraint(s) + */ + public AccountNotFoundException (String message) { + super (message); + } + + public AccountNotFoundException() {} +} +``` +###### /java/seedu/address/model/account/exceptions/DuplicateAccountException.java +``` java +package seedu.address.model.account.exceptions; + +import seedu.address.commons.exceptions.DuplicateDataException; + +/** + * Signals that the operation will result in duplicate Book objects. + */ +public class DuplicateAccountException extends DuplicateDataException { + public DuplicateAccountException() { + super("Operation would result in duplicate books"); + } +} +``` +###### /java/seedu/address/model/account/Account.java +``` java +package seedu.address.model.account; + +import static java.util.Objects.requireNonNull; + +import java.io.Serializable; +import java.util.Objects; + + + +/** + * Represents an account in the accountBook + */ +public class Account implements Serializable { + private final Name name; + private final Credential credential; + private final MatricNumber matricNumber; + private final PrivilegeLevel privilegeLevel; + + /** + * Constructs an Account + * + * @param name + * @param credential + * @param matricNumber + * @param privilegeLevel + */ + public Account(Name name, Credential credential, MatricNumber matricNumber, PrivilegeLevel privilegeLevel) { + requireNonNull(name); + requireNonNull(credential); + requireNonNull(matricNumber); + requireNonNull(privilegeLevel); + this.name = name; + this.credential = credential; + this.matricNumber = matricNumber; + this.privilegeLevel = privilegeLevel; + } + + /** + * Returns a sample guest account + * + * @return + */ + public static final Account createGuestAccount() { + Name name = new Name("Guest"); + Credential credential = new Credential("Guest", "Guest"); + MatricNumber matricNumber = new MatricNumber("A0000000X"); + PrivilegeLevel privilegeLevel = new PrivilegeLevel(0); + Account guest = new Account(name, credential, matricNumber, privilegeLevel); + return guest; + } + + /** + * Returns a sample admin account + * + * @return + */ + public static final Account createDefaultAdminAccount() { + Name name = new Name("Alice"); + Credential credential = new Credential("admin", "admin"); + MatricNumber matricNumber = new MatricNumber("A0123456X"); + PrivilegeLevel privilegeLevel = new PrivilegeLevel(2); + Account admin = new Account(name, credential, matricNumber, privilegeLevel); + return admin; + } + + /** + * Returns a sample student account + * + * @return + */ + public static final Account createDefaultStudentAccount() { + Name name = new Name("Bob"); + Credential credential = new Credential("student", "student"); + MatricNumber matricNumber = new MatricNumber("A0123456X"); + PrivilegeLevel privilegeLevel = new PrivilegeLevel(1); + Account student = new Account(name, credential, matricNumber, privilegeLevel); + return student; + } + + /** + * Returns the name of the account + * + * @return + */ + public Name getName() { + return name; + } + + /** + * Returns the credential + * + * @return + */ + public Credential getCredential() { + return credential; + } + + /** + * Returns the MatricNumber + * + * @return + */ + public MatricNumber getMatricNumber() { + return matricNumber; + } + + /** + * Returns the privilegeLevel of this account + * + * @return + */ + public PrivilegeLevel getPrivilegeLevel() { + return privilegeLevel; + } + + /** + * Returns a boolean indicating whether a given credential matches with that of this account + * + * @param c + * @return + */ + public boolean credentialMatches(Credential c) { + return c.equals(this.credential); + } + + /** + * Returns true if this account's username is the same as the username provided + * + * @param username + * @return + */ + public boolean usernameMatches(Username username) { + return this.credential.usernameEquals(username); + } + + /** + * Returns true if this account's username is the same as that of the credential provided + * + * @param c + * @return + */ + public boolean usernameMatches(Credential c) { + return usernameMatches(c.getUsername()); + } + + /** + * Returns true if this account's username is the same as that of the account provided + * + * @param a + * @return + */ + public boolean usernameMatches(Account a) { + return usernameMatches(a.getCredential()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Account account = (Account) o; + return Objects.equals(name, account.name) + && Objects.equals(credential, account.credential) + && Objects.equals(matricNumber, account.matricNumber) + && Objects.equals(privilegeLevel, account.privilegeLevel); + } + + @Override + public int hashCode() { + return Objects.hash(name, credential, matricNumber, privilegeLevel); + } + + @Override + public String toString() { + return this.credential.getUsername().toString(); + } +} +``` +###### /java/seedu/address/model/account/Credential.java +``` java +package seedu.address.model.account; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Represents a set of username and password + */ +public class Credential implements Serializable { + + private Username username; + private Password password; + + /** + * Constructs a {@code Credential} + * + * @param username A valid username + * @param password A valid password + */ + public Credential(String username, String password) { + this.username = new Username(username); + this.password = new Password(password); + } + + /** + * Returns username + */ + public Username getUsername() { + return username; + } + + /** + * Returns password + */ + public Password getPassword() { + return password; + } + + /** + * Returns true if the username provided equals to this.username + * + * @param username + * @return + */ + public boolean usernameEquals(Username username) { + return this.username.equals(username); + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof Credential // short circuit if same obj + && this.username.equals(((Credential) other).username) // check username + && this.password.equals(((Credential) other).password) //check password + ); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(username, password); + } + + @Override + public String toString() { + return "Credential{" + + "username=" + username + + ", password=" + password + + '}'; + } +} + +``` +###### /java/seedu/address/model/Model.java +``` java + void addAccount(Account account) throws DuplicateAccountException; + + void deleteAccount(Account account) throws AccountNotFoundException; + + void updateAccount(Account account, Account editedAccount) + throws DuplicateAccountException, AccountNotFoundException; + + PrivilegeLevel authenticate(Credential credential); + + void logout(); + + PrivilegeLevel getPrivilegeLevel(); +``` diff --git a/collated/functional/chantiongley.md b/collated/functional/chantiongley.md new file mode 100644 index 000000000000..2c17f69f6268 --- /dev/null +++ b/collated/functional/chantiongley.md @@ -0,0 +1,487 @@ +# chantiongley +###### /java/seedu/address/logic/parser/DeleteAccountCommandParser.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.DeleteAccountCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteAccountCommand object + */ +public class DeleteAccountCommandParser implements Parser { + /** + * Parses input arguments and creates a new DeleteAccountCommand object + *

+ * /** + * Parses the given {@code String} of arguments in the context of the DeleteAccountCommand + * and returns an DeleteAccountCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + + public DeleteAccountCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteAccountCommand.MESSAGE_USAGE)); + } else { + return new DeleteAccountCommand(trimmedArgs); + } + } +} +``` +###### /java/seedu/address/logic/parser/AddAccountCommandParser.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MATRICNUMBER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIVILEGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddAccountCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.account.Account; +import seedu.address.model.account.Credential; +import seedu.address.model.account.MatricNumber; +import seedu.address.model.account.Name; +import seedu.address.model.account.Password; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.account.Username; +import seedu.address.model.account.exceptions.AccountNotFoundException; + +/** + * Parses input arguments and creates a new AddAccountCommand object + */ +public class AddAccountCommandParser implements Parser { + /** + * Returns true if none of the prefixes contains empty(@code Optional) value in the given + * (@code ArgumentMultimap). + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch((prefix -> argumentMultimap.getValue(prefix).isPresent())); + } + + /** + * + * Parses the given (@code String) of arguments in the context of the AddAccountCommand + * + * and returns an AddAccountCommand object for execution. + * + * + * + * @throws ParseException if the user input does not conform to the expected format + * + + */ + @Override + public AddAccountCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_USERNAME, PREFIX_PASSWORD, PREFIX_MATRICNUMBER, + PREFIX_PRIVILEGE); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_USERNAME, PREFIX_PASSWORD, PREFIX_MATRICNUMBER, + PREFIX_PRIVILEGE) || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddAccountCommand.MESSAGE_USAGE)); + } + try { + Name name = ParserUtil.parseAccountName(argMultimap.getValue(PREFIX_NAME).get()); + Username username = ParserUtil.parseAccountUsername(argMultimap.getValue(PREFIX_USERNAME).get()); + Password password = ParserUtil.parseAccountPassword(argMultimap.getValue(PREFIX_PASSWORD).get()); + Credential credential = new Credential(username.getUsername(), password.getPassword()); + MatricNumber matricNumber = ParserUtil.parseAccountMatricNumber + (argMultimap.getValue(PREFIX_MATRICNUMBER).get()); + PrivilegeLevel privilegeLevel = ParserUtil.parseAccountPrivilegeLevel + (argMultimap.getValue(PREFIX_PRIVILEGE).get()); + + Account account = new Account(name, credential, matricNumber, privilegeLevel); + + return new AddAccountCommand(account); + } catch (AccountNotFoundException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } +} +``` +###### /java/seedu/address/logic/parser/ParserUtil.java +``` java + /** + * Parses a {@code String name} into a {@code Name}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws AccountNotFoundException if the given {@code name} is invalid. + */ + public static Name parseAccountName(String name) throws AccountNotFoundException { + requireNonNull(name); + String trimmedName = name.trim(); + if (!Name.isValidName(trimmedName)) { + throw new AccountNotFoundException(Name.MESSAGE_NAME_CONSTRAINTS); + } + return new Name(trimmedName); + } + + /** + * Parses a {@code Optional name} into an {@code Optional} if {@code name} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseAccountName(Optional name) throws AccountNotFoundException { + requireNonNull(name); + return name.isPresent() ? Optional.of(parseAccountName(name.get())) : Optional.empty(); + } + + /** + * Parses a {@code String matricNumber} into a {@code MatricNumber}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws AccountNotFoundException if the given {@code matricNumber} is invalid. + */ + public static MatricNumber parseAccountMatricNumber(String matricNumber) throws AccountNotFoundException { + requireNonNull(matricNumber); + String trimmedMatricNumber = matricNumber.trim(); + if (!MatricNumber.isValidMatricNumber(trimmedMatricNumber)) { + throw new AccountNotFoundException(MatricNumber.MESSAGE_MATRIC_NUMBER_CONSTRAINTS); + } + return new MatricNumber(trimmedMatricNumber); + } + + /** + * Parses a {@code Optional title} into an {@code Optional} if {@code title} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<MatricNumber> parseAccountMatricNumber(Optional<String> matricNumber) + throws AccountNotFoundException { + requireNonNull(matricNumber); + return matricNumber.isPresent() ? Optional.of(parseAccountMatricNumber(matricNumber.get())) : Optional.empty(); + } + + /** + * Parses a {@code String username} into a {@code Username}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws AccountNotFoundException if the given {@code username} is invalid. + */ + public static Username parseAccountUsername(String username) throws AccountNotFoundException { + requireNonNull(username); + String trimmedUsername = username.trim(); + if (!Username.isValidUsername(trimmedUsername)) { + throw new AccountNotFoundException (Username.MESSAGE_USERNAME_CONSTRAINTS); + } + return new Username(trimmedUsername); + } + + /** + * Parses a {@code Optional<String> username} into an {@code Optional<Username>} if {@code username} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Username> parseAccountUsername(Optional<String> username) throws AccountNotFoundException { + requireNonNull(username); + return username.isPresent() ? Optional.of(parseAccountUsername(username.get())) : Optional.empty(); + } + + /** + * Parses a {@code String password} into a {@code Password}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws AccountNotFoundException if the given {@code password} is invalid. + */ + public static Password parseAccountPassword(String password) throws AccountNotFoundException { + requireNonNull(password); + String trimmedPassword = password.trim(); + if (!Password.isValidPassword(trimmedPassword)) { + throw new AccountNotFoundException(Password.MESSAGE_PASSWORD_CONSTRAINTS); + } + return new Password(trimmedPassword); + } + + /** + * Parses a {@code Optional<String> password} into an {@code Optional<Password>} if {@code password} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Password> parseAccountPassword(Optional<String> password) throws AccountNotFoundException { + requireNonNull(password); + return password.isPresent() ? Optional.of(parseAccountPassword(password.get())) : Optional.empty(); + } + + /** + * Parses a {@code String privilegeLevel} into a {@code PrivilegeLevel}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws AccountNotFoundException if the given {@code privilegeLevel} is invalid. + */ + public static PrivilegeLevel parseAccountPrivilegeLevel(String privilegeLevel) throws AccountNotFoundException { + requireNonNull(privilegeLevel); + int input = Integer.parseInt(privilegeLevel); + + if (!PrivilegeLevel.isValidPrivilegeLevel(input)) { + throw new AccountNotFoundException(PrivilegeLevel.MESSAGE_PRIVILEGE_LEVEL_CONSTRAINTS); + } + return new PrivilegeLevel(input); + } + + //============================== Book Level Parse Commands ============================== + + /** + * Parses a {@code String title} into a {@code Title}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code title} is invalid. + */ + public static Title parseTitle(String title) throws IllegalValueException { + requireNonNull(title); + String trimmedTitle = title.trim(); + if (!Title.isValidTitle(trimmedTitle)) { + throw new IllegalValueException(Title.MESSAGE_TITLE_CONSTRAINTS); + } + return new Title(trimmedTitle); + } + + /** + * Parses a {@code Optional<String> title} into an {@code Optional<Title>} if {@code title} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Title> parseTitle(Optional<String> title) throws IllegalValueException { + requireNonNull(title); + return title.isPresent() ? Optional.of(parseTitle(title.get())) : Optional.empty(); + } + + /** + * Parses a {@code String isbn} into a {@code Isbn}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code isbn} is invalid. + */ + public static Isbn parseIsbn(String isbn) throws IllegalValueException { + requireNonNull(isbn); + String trimmedIsbn = isbn.trim(); + if (!Isbn.isValidIsbn(trimmedIsbn)) { + throw new IllegalValueException(Isbn.MESSAGE_ISBN_CONSTRAINTS); + } + return new Isbn(trimmedIsbn); + } + + /** + * Parses a {@code Optional<String> isbn} into an {@code Optional<Isbn>} if {@code isbn} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Isbn> parseIsbn(Optional<String> isbn) throws IllegalValueException { + requireNonNull(isbn); + return isbn.isPresent() ? Optional.of(parseIsbn(isbn.get())) : Optional.empty(); + } + + /** + * Parses a {@code String address} into an {@code Author}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code address} is invalid. + */ + public static Author parseAuthor(String address) throws IllegalValueException { + requireNonNull(address); + String trimmedAuthor = address.trim(); + if (!Author.isValidAuthor(trimmedAuthor)) { + throw new IllegalValueException(Author.MESSAGE_AUTHOR_CONSTRAINTS); + } + return new Author(trimmedAuthor); + } + + /** + * Parses a {@code Optional<String> author} into an {@code Optional<Author>} if {@code author} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Author> parseAuthor(Optional<String> author) throws IllegalValueException { + requireNonNull(author); + return author.isPresent() ? Optional.of(parseAuthor(author.get())) : Optional.empty(); + } + + /** + * Parses a {@code String avail} into an {@code Avail}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code avail} is invalid. + */ + public static Avail parseAvail(String avail) throws IllegalValueException { + requireNonNull(avail); + String trimmedAvail = avail.trim(); + if (!Avail.isValidAvail(trimmedAvail)) { + throw new IllegalValueException(Avail.MESSAGE_AVAIL_CONSTRAINTS); + } + return new Avail(trimmedAvail); + } + + /** + * Parses a {@code Optional<String> avail} into an {@code Optional<Avail>} if {@code avail} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Avail> parseAvail(Optional<String> avail) throws IllegalValueException { + requireNonNull(avail); + return avail.isPresent() ? Optional.of(parseAvail(avail.get())) : Optional.empty(); + } + + /** + * Parses a {@code String tag} into a {@code Tag}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code tag} is invalid. + */ + public static Tag parseTag(String tag) throws IllegalValueException { + requireNonNull(tag); + String trimmedTag = tag.trim(); + if (!Tag.isValidTagName(trimmedTag)) { + throw new IllegalValueException(Tag.MESSAGE_TAG_CONSTRAINTS); + } + return new Tag(trimmedTag); + } + + /** + * Parses {@code Collection<String> tags} into a {@code Set<Tag>}. + */ + public static Set<Tag> parseTags(Collection<String> tags) throws IllegalValueException { + requireNonNull(tags); + final Set<Tag> tagSet = new HashSet<>(); + for (String tagName : tags) { + tagSet.add(parseTag(tagName)); + } + return tagSet; + } +} +``` +###### /java/seedu/address/logic/commands/AddAccountCommand.java +``` java +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MATRICNUMBER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIVILEGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.account.Account; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.account.exceptions.DuplicateAccountException; + +/** + * Adds an account + */ +public class AddAccountCommand extends UndoableCommand { + public static final String COMMAND_WORD = "addAccount"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an account to the system. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_USERNAME + "USERNAME " + + PREFIX_PASSWORD + "PASSWORD " + + PREFIX_MATRICNUMBER + "MATRICULATION NUMBER " + + PREFIX_PRIVILEGE + "PRIVILEGE LEVEL \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_USERNAME + "johndoe " + + PREFIX_PASSWORD + "johndoe123 " + + PREFIX_MATRICNUMBER + "A0123456B " + + PREFIX_PRIVILEGE + "1"; + + public static final String MESSAGE_SUCCESS = "New account added: %1$s"; + public static final String MESSAGE_DUPLICATE_ACCOUNT = "This account already exists in the system"; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_LIBRARIAN; + + private final Account toAdd; + + /** + * Creates an AddCommand to add the specified {@code Book} + */ + public AddAccountCommand(Account account) { + requireNonNull(account); + toAdd = account; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + model.addAccount(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (DuplicateAccountException e) { + throw new CommandException(MESSAGE_DUPLICATE_ACCOUNT); + } + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } + +} +``` +###### /java/seedu/address/logic/commands/DeleteAccountCommand.java +``` java +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.account.Account; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.account.Username; +import seedu.address.model.account.exceptions.AccountNotFoundException; + +/** + * Deletes a account using it's last displayed index from the account list + */ +public class DeleteAccountCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "deleteAccount"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the account identified by the name of the user stored in account list.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " John"; + public static final String MESSAGE_DELETE_ACCOUNT_SUCCESS = "Deleted Account: %1$s"; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_LIBRARIAN; + + private String username; + private Account accountToDelete; + + public DeleteAccountCommand(String username) { + requireNonNull(username); + this.username = username; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.deleteAccount(accountToDelete); + } catch (AccountNotFoundException anfe) { + throw new CommandException("Account does not exist"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_ACCOUNT_SUCCESS, accountToDelete)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + accountToDelete = model.getAccountList().searchByUsername(new Username(username)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteAccountCommand // instanceof handles nulls + && accountToDelete.equals(((DeleteAccountCommand) other).accountToDelete)); + } + + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} + +``` diff --git a/collated/functional/khiayi.md b/collated/functional/khiayi.md new file mode 100644 index 000000000000..63809cc418c6 --- /dev/null +++ b/collated/functional/khiayi.md @@ -0,0 +1,476 @@ +# khiayi +###### /java/seedu/address/logic/commands/AddCommand.java +``` java +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_AUTHOR; +import static seedu.address.logic.parser.CliSyntax.PREFIX_AVAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ISBN; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.book.Book; +import seedu.address.model.book.exceptions.DuplicateBookException; + +//import static seedu.address.logic.parser.CliSyntax.; + + +/** + * Adds a book to the catalogue. + */ +public class AddCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "add"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a book to the catalogue. " + + "Parameters: " + + PREFIX_TITLE + "TITLE " + + PREFIX_AUTHOR + "AUTHOR " + + PREFIX_ISBN + "ISBN " + + PREFIX_AVAIL + "AVAIL " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TITLE + "Animal Farm " + + PREFIX_AUTHOR + "George Orwell " + + PREFIX_ISBN + "9780736692427 " + + PREFIX_AVAIL + "Borrowed " + + PREFIX_TAG + "political " + + PREFIX_TAG + "satire "; + + public static final String MESSAGE_SUCCESS = "New book added: %1$s"; + public static final String MESSAGE_DUPLICATE_BOOK = "This book already exists in the catalogue"; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_LIBRARIAN; + + private final Book toAdd; + + /** + * Creates an AddCommand to add the specified {@code Book} + */ + public AddCommand(Book book) { + requireNonNull(book); + toAdd = book; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + model.addBook(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (DuplicateBookException e) { + throw new CommandException(MESSAGE_DUPLICATE_BOOK); + } + + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof AddCommand // instanceof handles nulls + && toAdd.equals(((AddCommand) other).toAdd)); + } + +``` +###### /java/seedu/address/logic/commands/EditCommand.java +``` java +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_AUTHOR; +import static seedu.address.logic.parser.CliSyntax.PREFIX_AVAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ISBN; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_BOOKS; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.book.Author; +import seedu.address.model.book.Avail; +import seedu.address.model.book.Book; +import seedu.address.model.book.Isbn; +import seedu.address.model.book.Title; +import seedu.address.model.book.exceptions.BookNotFoundException; +import seedu.address.model.book.exceptions.DuplicateBookException; +import seedu.address.model.tag.Tag; + +/** + * Edits the details of an existing book in the catalogue. + */ +public class EditCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "edit"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the book identified " + + "by the index number used in the last book listing. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_TITLE + "TITLE] " + + "[" + PREFIX_AUTHOR + "AUTHOR] " + + "[" + PREFIX_ISBN + "ISBN] " + + "[" + PREFIX_AVAIL + "AVAIL] " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_ISBN + "9780736692426 " + + PREFIX_AVAIL + "Borrowed"; + + public static final String MESSAGE_EDIT_BOOK_SUCCESS = "Edited Book: %1$s"; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_DUPLICATE_BOOK = "This book already exists in the catalogue."; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_LIBRARIAN; + + private final Index index; + private final EditBookDescriptor editBookDescriptor; + + private Book bookToEdit; + private Book editedBook; + + /** + * @param index of the book in the filtered book list to edit + * @param editBookDescriptor details to edit the book with + */ + public EditCommand(Index index, EditBookDescriptor editBookDescriptor) { + requireNonNull(index); + requireNonNull(editBookDescriptor); + + this.index = index; + this.editBookDescriptor = new EditBookDescriptor(editBookDescriptor); + } + + /** + * Creates and returns a {@code Book} with the details of {@code bookToEdit} + * edited with {@code editBookDescriptor}. + */ + private static Book createEditedBook(Book bookToEdit, EditBookDescriptor editBookDescriptor) { + assert bookToEdit != null; + + Title updatedTitle = editBookDescriptor.getTitle().orElse(bookToEdit.getTitle()); + Isbn updatedIsbn = editBookDescriptor.getIsbn().orElse(bookToEdit.getIsbn()); + Avail updatedAvail = editBookDescriptor.getAvail().orElse(bookToEdit.getAvail()); + Author updatedAuthor = editBookDescriptor.getAuthor().orElse(bookToEdit.getAuthor()); + Set<Tag> updatedTags = editBookDescriptor.getTags().orElse(bookToEdit.getTags()); + + return new Book(updatedTitle, updatedAuthor, updatedIsbn, updatedAvail, updatedTags); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.updateBook(bookToEdit, editedBook); + } catch (DuplicateBookException dpe) { + throw new CommandException(MESSAGE_DUPLICATE_BOOK); + } catch (BookNotFoundException pnfe) { + throw new AssertionError("The target book cannot be missing"); + } + model.updateFilteredBookList(PREDICATE_SHOW_ALL_BOOKS); + return new CommandResult(String.format(MESSAGE_EDIT_BOOK_SUCCESS, editedBook)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List<Book> lastShownList = model.getFilteredBookList(); + + if (index.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + } + + bookToEdit = lastShownList.get(index.getZeroBased()); + editedBook = createEditedBook(bookToEdit, editBookDescriptor); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditCommand)) { + return false; + } + + // state check + EditCommand e = (EditCommand) other; + return index.equals(e.index) + && editBookDescriptor.equals(e.editBookDescriptor) + && Objects.equals(bookToEdit, e.bookToEdit); + } + +``` +###### /java/seedu/address/model/book/Title.java +``` java +package seedu.address.model.book; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Book's name in the catalogue. + * Guarantees: immutable; is valid as declared in {@link #isValidTitle(String)} + */ +public class Title { +``` +###### /java/seedu/address/model/book/Title.java +``` java + public static final String MESSAGE_TITLE_CONSTRAINTS = + "Book titles should only contain alphanumeric characters and spaces, and it should not be blank"; + + /* + * The first character of the title must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String TITLE_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String fullTitle; + + /** + * Constructs a {@code Title}. + * + * @param title A valid title. + */ + public Title(String title) { + requireNonNull(title); + checkArgument(isValidTitle(title), MESSAGE_TITLE_CONSTRAINTS); + this.fullTitle = title; + } +``` +###### /java/seedu/address/model/book/Avail.java +``` java +package seedu.address.model.book; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + + +/** + * Represents a Book's availability in the catalogue. + * Guarantees: immutable; is valid as declared in {@link #isValidAvail(String)} + */ +public class Avail { + public static final String AVAILABLE = "Available"; + public static final String BORROWED = "Borrowed"; + public static final String RESERVED = "Reserved"; + public static final String MESSAGE_AVAIL_CONSTRAINTS = "Book availability should be one of the following:\n " + + "1. " + AVAILABLE + "\n" + + "2. " + BORROWED + "\n" + + "3. " + RESERVED + "\n"; + + + public final String value; + + /** + * Constructs an {@code Avail}. + * + * @param avail A valid availability . + */ + public Avail(String avail) { + requireNonNull(avail); + checkArgument(isValidAvail(avail), MESSAGE_AVAIL_CONSTRAINTS); + this.value = avail; + } + + /** + * Returns if a given string is a valid book avail. + */ + public static boolean isValidAvail(String test) { + return test.equals(AVAILABLE) + || test.equals(BORROWED) + || test.equals(RESERVED); + } + +``` +###### /java/seedu/address/model/book/Author.java +``` java +package seedu.address.model.book; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Book's author in the catalogue. + * Guarantees: immutable; is valid as declared in {@link #isValidAuthor(String)} + */ +public class Author { + + public static final String MESSAGE_AUTHOR_CONSTRAINTS = + "Book author should only contain alphanumeric characters and spaces, and it should not be blank"; + + /* + * The first character of the author must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String AUTHOR_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String value; + + /** + * Constructs an {@code Author}. + * + * @param author A valid author. + */ + public Author(String author) { + requireNonNull(author); + checkArgument(isValidAuthor(author), MESSAGE_AUTHOR_CONSTRAINTS); + this.value = author; + } +``` +###### /java/seedu/address/model/book/Book.java +``` java +package seedu.address.model.book; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +import seedu.address.model.tag.Tag; +import seedu.address.model.tag.UniqueTagList; + +/** + * Represents a Book in the catalogue. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Book { + + private final Title title; + private final Author author; + private final Isbn isbn; + private final Avail avail; + + private final UniqueTagList tags; + + /** + * Every field must be present and not null. + */ + public Book(Title title, Author author, Isbn isbn, Avail avail, Set<Tag> tags) { + requireAllNonNull(title, author, isbn, avail, tags); + this.title = title; + this.author = author; + this.isbn = isbn; + this.avail = avail; + // protect internal tags from changes in the arg list + this.tags = new UniqueTagList(tags); + } + + public Title getTitle() { + return title; + } + + public Author getAuthor() { + return author; + } + + public Isbn getIsbn() { + return isbn; + } + + public Avail getAvail() { + return avail; + } + + /** + * Returns true if this book's isbn is the same as the isbn provided + * + * @param isbn + * @return + */ + public boolean isbnMatches(Isbn isbn) { + return this.isbn.equals(isbn); + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set<Tag> getTags() { + return Collections.unmodifiableSet(tags.toSet()); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Book)) { + return false; + } + + Book otherBook = (Book) other; + return otherBook.getTitle().equals(this.getTitle()) + && otherBook.getAuthor().equals(this.getAuthor()) + && otherBook.getIsbn().equals(this.getIsbn()) + && otherBook.getAvail().equals(this.getAvail()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(title, author, isbn, avail, tags); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getTitle()) + .append(" Author: ") + .append(getAuthor()) + .append(" Isbn: ") + .append(getIsbn()) + .append(" Avail: ") + .append(getAvail()) + .append(" Tags: "); + getTags().forEach(builder::append); + return builder.toString(); + } + +} +``` +###### /java/seedu/address/model/book/Isbn.java +``` java +package seedu.address.model.book; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Book's isbn number in the catalogue. + * Guarantees: immutable; is valid as declared in {@link #isValidIsbn(String)} + */ +public class Isbn { + + public static final String MESSAGE_ISBN_CONSTRAINTS = + "Isbn numbers can only contain numbers, and should be at 13 digits long"; + public static final String ISBN_VALIDATION_REGEX = "\\d{13}"; + public final String value; + + /** + * Constructs a {@code Isbn}. + * + * @param isbn A valid isbn number. + */ + public Isbn(String isbn) { + requireNonNull(isbn); + checkArgument(isValidIsbn(isbn), MESSAGE_ISBN_CONSTRAINTS); + this.value = isbn; + } +``` diff --git a/collated/functional/victortardieu.md b/collated/functional/victortardieu.md new file mode 100644 index 000000000000..9e429257e442 --- /dev/null +++ b/collated/functional/victortardieu.md @@ -0,0 +1,370 @@ +# victortardieu +###### /java/seedu/address/ui/CommandBox.java +``` java + case TAB: + keyEvent.consume(); + replaceText(LogicManager.autoComplete(commandTextField.getText())); + break; +``` +###### /java/seedu/address/ui/BookCard.java +``` java +package seedu.address.ui; + +import java.util.Random; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.book.Book; + + +/** + * An UI component that displays information of a {@code Book}. + */ +public class BookCard extends UiPart<Region> { + + private static final String FXML = "BookListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see <a href="https://github.com/se-edu/addressbook-level4/issues/336">The issue on Catalogue</a> + */ + + public final Book book; + + @FXML + private HBox cardPane; + @FXML + private Label title; + @FXML + private Label author; + @FXML + private Label id; + @FXML + private Label isbn; + @FXML + private Label avail; + @FXML + private FlowPane tags; + + + public BookCard(Book book, int displayedIndex) { + super(FXML); + this.book = book; + id.setText(displayedIndex + ". "); + title.setText(book.getTitle().fullTitle); + author.setText(book.getAuthor().value); + isbn.setText(book.getIsbn().value); + avail.setText(book.getAvail().toString()); + colorTags(book); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof BookCard)) { + return false; + } + + // state check + BookCard card = (BookCard) other; + return id.getText().equals(card.id.getText()) + && book.equals(card.book); + } + + private String getTagColor() { + Random rand = new Random(); + int sCase = rand.nextInt(10); + switch (sCase) { + case 0: + return "-fx-background-color: blue;"; + case 1: + return "-fx-background-color: green;"; + case 2: + return "-fx-background-color: red;"; + case 3: + return "-fx-background-color: maroon;"; + case 4: + return "-fx-background-color: orange;"; + case 5: + return "-fx-background-color: violet;"; + case 6: + return "-fx-background-color: brown;"; + case 7: + return "-fx-background-color: khaki;"; + case 8: + return "-fx-background-color: olive;"; + case 9: + return "-fx-background-color: indigo;"; + default: + return "-fx-background-color: pink;"; + } + + } + + /** + * Assign a new color to each new tag + * + * @param book + */ + + private void colorTags(Book book) { + book.getTags().forEach(tag -> { + Label tagLabel = new Label(tag.tagName); + tags.getChildren().add(tagLabel); + tagLabel.setStyle(getTagColor()); + }); + } +} +``` +###### /java/seedu/address/logic/commands/ClearAccountCommand.java +``` java +package seedu.address.logic.commands; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.account.Account; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.account.UniqueAccountList; +import seedu.address.model.account.exceptions.DuplicateAccountException; + + +/** + * Checks if user is a librarian. If yes, it clears the list of accounts and logs out the current account. + */ +public class ClearAccountCommand extends UndoableCommand { + + /** + * + */ + public static final String COMMAND_WORD = "cleara"; + public static final String MESSAGE_SUCCESS = "AccountList has been cleared, and you are logged out!"; + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_LIBRARIAN; + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + UniqueAccountList blankList = new UniqueAccountList(); + try { + blankList.add(Account.createDefaultAdminAccount()); + } catch (DuplicateAccountException e) { + e.printStackTrace(); + } + model.resetAccount(blankList); + model.logout(); + return new CommandResult(MESSAGE_SUCCESS); + } + + public PrivilegeLevel getPrivilegeLevel() { + + return PRIVILEGE_LEVEL; + } +} +``` +###### /java/seedu/address/logic/LogicManager.java +``` java + /** + * @param myString + * @return auto, the string that holds the autocomplete string of the chosen command + */ + public static String autoComplete(String myString) { + /** + * The auto string will hold the autocomplete string of the chosen command + */ + String auto = ""; + switch (myString) { + case AddCommand.COMMAND_WORD: + auto = "add t/ a/ i/ av/ tag/ "; + break; + case AddAccountCommand.COMMAND_WORD: + auto = "addAccount n/ m/ u/ p/ l/ "; + break; + case EditCommand.COMMAND_WORD: + auto = "edit 1 t/ a/ i/ av/ tag/ "; + break; + case DeleteCommand.COMMAND_WORD: + auto = "delete 1"; + break; + case BorrowCommand.COMMAND_WORD: + auto = "borrow 1"; + break; + case ReturnCommand.COMMAND_WORD: + auto = "return 1"; + break; + case ReserveCommand.COMMAND_WORD: + auto = "reserve 1"; + break; + default: + auto = myString; + } + return auto; + } + + @Override + public CommandResult execute(String commandText) throws CommandException, ParseException { + logger.info("----------------[USER COMMAND][" + commandText + "]"); + try { + Command command = catalogueParser.parseCommand(commandText); + if (!isPrivileged(command)) { + return new CommandResult(Command.MESSAGE_UNPRIVILEGED); + } + command.setData(model, history, undoRedoStack); + CommandResult result = command.execute(); + undoRedoStack.push(command); + return result; + } finally { + history.add(commandText); + } + } + + @Override + public ObservableList<Book> getFilteredBookList() { + return model.getFilteredBookList(); + } + + @Override + public ListElementPointer getHistorySnapshot() { + return new ListElementPointer(history.getHistory()); + } +``` +###### /java/seedu/address/model/account/Username.java +``` java +package seedu.address.model.account; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.io.Serializable; + +/** + * Represents the username of an account + */ +public class Username implements Serializable { + + public static final String MESSAGE_USERNAME_CONSTRAINTS = + "Username should be at least 5 characters long."; + public static final String USERNAME_VALIDATION_REGEX = "\\w{5,}"; + + private final String username; + + /** + * Constructs a Username + * + * @param username + */ + public Username(String username) { + requireNonNull(username); + checkArgument(isValidUsername(username), MESSAGE_USERNAME_CONSTRAINTS); + + this.username = username; + } + + /** + * Returns true if a given string is a valid Username. + */ + public static boolean isValidUsername(String test) { + return test.matches(USERNAME_VALIDATION_REGEX); + } + + + /** + * Returns username. + */ + public String getUsername() { + return username; + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof Username // short circuit if same obj + && this.username.equals(((Username) other).username) // check username + ); + } + + @Override + public int hashCode() { + return username.hashCode(); + } + + @Override + public String toString() { + return username; + } +} +``` +###### /java/seedu/address/model/account/Password.java +``` java +package seedu.address.model.account; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.io.Serializable; + +/** + * Represents a password + */ +public class Password implements Serializable { + public static final String MESSAGE_PASSWORD_CONSTRAINTS = + "Password should be at least 5 characters long."; + public static final String PASSWORD_VALIDATION_REGEX = "\\w{5,}"; + + private final String password; + + + /** + * Construct a password + * + * @param password + */ + public Password(String password) { + requireNonNull(password); + checkArgument(isValidPassword(password), MESSAGE_PASSWORD_CONSTRAINTS); + + this.password = password; + } + + /** + * Returns true if a given string is a valid password. + */ + public static boolean isValidPassword(String test) { + return test.matches(PASSWORD_VALIDATION_REGEX); + } + + @Override + public String toString() { + return "Password{" + + "password='" + password + '\'' + + '}'; + } + + /** + * Returns password. + */ + public String getPassword() { + return password; + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof Password // short circuit if same obj + && this.password.equals(((Password) other).password)); //check password + } + + @Override + public int hashCode() { + return password.hashCode(); + } +} +``` diff --git a/collated/test/LeKhangTai.md b/collated/test/LeKhangTai.md new file mode 100644 index 000000000000..0d4bc2e9679a --- /dev/null +++ b/collated/test/LeKhangTai.md @@ -0,0 +1,447 @@ +# LeKhangTai +###### /java/seedu/address/logic/parser/BorrowCommandParserTest.java +``` java +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_BOOK; + +import org.junit.Test; + +import seedu.address.logic.commands.BorrowCommand; + + +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the BorrowCommand code. For example, inputs "1" and "1 abc" take the + * same path through the BorrowCommand, and therefore we test only one of them. + * The path variation for those two cases occur inside the ParserUtil, and + * therefore should be covered by the ParserUtilTest. + */ +public class BorrowCommandParserTest { + + private BorrowCommandParser parser = new BorrowCommandParser(); + + @Test + public void parse_validArgs_returnsBorrowCommand() { + assertParseSuccess(parser, "1", new BorrowCommand(INDEX_FIRST_BOOK)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, BorrowCommand.MESSAGE_USAGE)); + } +} +``` +###### /java/seedu/address/logic/parser/ReturnCommandParserTest.java +``` java +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_BOOK; + +import org.junit.Test; + +import seedu.address.logic.commands.ReturnCommand; +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the BorrowCommand code. For example, inputs "1" and "1 abc" take the + * same path through the BorrowCommand, and therefore we test only one of them. + * The path variation for those two cases occur inside the ParserUtil, and + * therefore should be covered by the ParserUtilTest. + */ +public class ReturnCommandParserTest { + + private ReturnCommandParser parser = new ReturnCommandParser(); + + @Test + public void parse_validArgs_returnsReturnCommand() { + assertParseSuccess(parser, "1", new ReturnCommand(INDEX_FIRST_BOOK)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ReturnCommand.MESSAGE_USAGE)); + } +} +``` +###### /java/seedu/address/logic/commands/BorrowCommandTest.java +``` java +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.prepareRedoCommand; +import static seedu.address.logic.commands.CommandTestUtil.prepareUndoCommand; +import static seedu.address.logic.commands.CommandTestUtil.showBookAtIndex; +import static seedu.address.model.book.Avail.BORROWED; +import static seedu.address.testutil.TypicalBooks.getTypicalCatalogue; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_BOOK; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_BOOK; + +import java.util.Set; + +import org.junit.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.book.Author; +import seedu.address.model.book.Avail; +import seedu.address.model.book.Book; +import seedu.address.model.book.Isbn; +import seedu.address.model.book.Title; +import seedu.address.model.tag.Tag; + + + + + +/** + * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests for + * {@code BorrowCommand}. + */ +public class BorrowCommandTest { + + private Model model = new ModelManager(getTypicalCatalogue(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() throws Exception { + Book bookToBorrow = model.getFilteredBookList().get(INDEX_FIRST_BOOK.getZeroBased()); + Book borrowedBook = createBorrowedBook(bookToBorrow); + BorrowCommand borrowCommand = prepareCommand(INDEX_FIRST_BOOK); + + String expectedMessage = String.format(BorrowCommand.MESSAGE_BORROW_BOOK_SUCCESS, bookToBorrow); + + ModelManager expectedModel = new ModelManager(model.getCatalogue(), new UserPrefs()); + expectedModel.borrowBook(bookToBorrow, borrowedBook); + + assertCommandSuccess(borrowCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredBookList().size() + 1); + BorrowCommand borrowCommand = prepareCommand(outOfBoundIndex); + + assertCommandFailure(borrowCommand, model, Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + showBookAtIndex(model, INDEX_FIRST_BOOK); + + Index outOfBoundIndex = INDEX_SECOND_BOOK; + // ensures that outOfBoundIndex is still in bounds of catalogue list + assertTrue(outOfBoundIndex.getZeroBased() < model.getCatalogue().getBookList().size()); + + BorrowCommand borrowCommand = prepareCommand(outOfBoundIndex); + + assertCommandFailure(borrowCommand, model, Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + } + + @Test + public void executeUndoRedo_validIndexUnfilteredList_success() throws Exception { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Book bookToBorrow = model.getFilteredBookList().get(INDEX_FIRST_BOOK.getZeroBased()); + Book borrowedBook = createBorrowedBook(bookToBorrow); + BorrowCommand borrowCommand = prepareCommand(INDEX_FIRST_BOOK); + Model expectedModel = new ModelManager(model.getCatalogue(), new UserPrefs()); + + // borrow -> first book borrow + borrowCommand.execute(); + undoRedoStack.push(borrowCommand); + + // undo -> reverts catalogue back to previous state and filtered book list to show all books + assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + // redo -> same first book deleted again + expectedModel.borrowBook(bookToBorrow, borrowedBook); + assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void executeUndoRedo_invalidIndexUnfilteredList_failure() { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredBookList().size() + 1); + BorrowCommand borrowCommand = prepareCommand(outOfBoundIndex); + + // execution failed -> deleteCommand not pushed into undoRedoStack + assertCommandFailure(borrowCommand, model, Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + + // no commands in undoRedoStack -> undoCommand and redoCommand fail + assertCommandFailure(undoCommand, model, UndoCommand.MESSAGE_FAILURE); + assertCommandFailure(redoCommand, model, RedoCommand.MESSAGE_FAILURE); + } + + /** + * 1. Borrows a {@code Book} from a filtered list. + * 2. Undo the borrowing. + * 3. The unfiltered list should be shown now. Verify that the index of the previously borrowed book in the + * unfiltered list is different from the index at the filtered list. + * 4. Redo the borrowing. This ensures {@code RedoCommand} borrow the book object regardless of indexing. + */ + @Test + public void executeUndoRedo_validIndexFilteredList_sameBookDeleted() throws Exception { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + BorrowCommand borrowCommand = prepareCommand(INDEX_FIRST_BOOK); + Model expectedModel = new ModelManager(model.getCatalogue(), new UserPrefs()); + + showBookAtIndex(model, INDEX_SECOND_BOOK); + Book bookToBorrow = model.getFilteredBookList().get(INDEX_FIRST_BOOK.getZeroBased()); + Book borrowedBook = createBorrowedBook(bookToBorrow); + // borrow -> borrows second book in unfiltered book list / first book in filtered book list + borrowCommand.execute(); + undoRedoStack.push(borrowCommand); + + // undo -> reverts catalogue back to previous state and filtered book list to show all books + assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + expectedModel.borrowBook(bookToBorrow, borrowedBook); + assertNotEquals(bookToBorrow, model.getFilteredBookList().get(INDEX_FIRST_BOOK.getZeroBased())); + // redo -> borrows same second book in unfiltered book list + assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void equals() throws Exception { + BorrowCommand borrowFirstCommand = prepareCommand(INDEX_FIRST_BOOK); + BorrowCommand borrowSecondCommand = prepareCommand(INDEX_FIRST_BOOK); + + // same object -> returns true + assertTrue(borrowFirstCommand.equals(borrowFirstCommand)); + + // same values -> returns true + BorrowCommand borrowFirstCommandCopy = prepareCommand(INDEX_FIRST_BOOK); + assertTrue(borrowFirstCommand.equals(borrowFirstCommandCopy)); + + // one command preprocessed when previously equal -> returns false + borrowFirstCommandCopy.preprocessUndoableCommand(); + assertFalse(borrowFirstCommand.equals(borrowFirstCommandCopy)); + + // different types -> returns false + assertFalse(borrowFirstCommand.equals(1)); + + // null -> returns false + assertFalse(borrowFirstCommand.equals(null)); + + // different book -> returns false + assertTrue(borrowFirstCommand.equals(borrowSecondCommand)); + } + + /** + * Returns a {@code BorrowCommand} with the parameter {@code index}. + */ + private BorrowCommand prepareCommand(Index index) { + BorrowCommand borrowCommand = new BorrowCommand(index); + borrowCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return borrowCommand; + } + + /** + * Updates {@code model}'s filtered list to show no one. + */ + private void showNoBook(Model model) { + model.updateFilteredBookList(p -> false); + + assertTrue(model.getFilteredBookList().isEmpty()); + } + + /** + * + * @return duplicated book with Borrowed Availability + */ + public Book createBorrowedBook(Book bookToBorrow) { + assert bookToBorrow != null; + + Title updatedTitle = bookToBorrow.getTitle(); + Isbn updatedIsbn = bookToBorrow.getIsbn(); + Avail updatedAvail = new Avail(BORROWED); + Author updatedAuthor = bookToBorrow.getAuthor(); + Set<Tag> updatedTags = bookToBorrow.getTags(); + + return new Book(updatedTitle, updatedAuthor, updatedIsbn, updatedAvail, updatedTags); + } + +} +``` +###### /java/seedu/address/logic/commands/ReturnCommandTest.java +``` java +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.prepareRedoCommand; +import static seedu.address.logic.commands.CommandTestUtil.prepareUndoCommand; +import static seedu.address.logic.commands.CommandTestUtil.showBookAtIndex; +import static seedu.address.model.book.Avail.AVAILABLE; +import static seedu.address.testutil.TypicalBooks.getTypicalCatalogue; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_BOOK; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_BOOK; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_BOOK; + +import java.util.Set; + +import org.junit.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.book.Author; +import seedu.address.model.book.Avail; +import seedu.address.model.book.Book; +import seedu.address.model.book.Isbn; +import seedu.address.model.book.Title; +import seedu.address.model.tag.Tag; + + + + + +/** + * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests for + * {@code ReturnCommand}. + */ +public class ReturnCommandTest { + + private Model model = new ModelManager(getTypicalCatalogue(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() throws Exception { + Book bookToReturn = model.getFilteredBookList().get(INDEX_THIRD_BOOK.getZeroBased()); + Book returnedBook = createReturnedBook(bookToReturn); + ReturnCommand returnCommand = prepareCommand(INDEX_THIRD_BOOK); + + String expectedMessage = String.format(ReturnCommand.MESSAGE_RETURN_BOOK_SUCCESS, bookToReturn); + + ModelManager expectedModel = new ModelManager(model.getCatalogue(), new UserPrefs()); + expectedModel.returnBook(bookToReturn, returnedBook); + + assertCommandSuccess(returnCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredBookList().size() + 1); + ReturnCommand returnCommand = prepareCommand(outOfBoundIndex); + + assertCommandFailure(returnCommand, model, Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + showBookAtIndex(model, INDEX_FIRST_BOOK); + + Index outOfBoundIndex = INDEX_SECOND_BOOK; + // ensures that outOfBoundIndex is still in bounds of catalogue list + assertTrue(outOfBoundIndex.getZeroBased() < model.getCatalogue().getBookList().size()); + + ReturnCommand returnCommand = prepareCommand(outOfBoundIndex); + + assertCommandFailure(returnCommand, model, Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + } + + @Test + public void executeUndoRedo_validIndexUnfilteredList_success() throws Exception { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Book bookToReturn = model.getFilteredBookList().get(INDEX_THIRD_BOOK.getZeroBased()); + Book returnedBook = createReturnedBook(bookToReturn); + ReturnCommand returnCommand = prepareCommand(INDEX_THIRD_BOOK); + Model expectedModel = new ModelManager(model.getCatalogue(), new UserPrefs()); + + // return -> first book return + returnCommand.execute(); + undoRedoStack.push(returnCommand); + + // undo -> reverts catalogue back to previous state and filtered book list to show all books + assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + // redo -> same first book deleted again + expectedModel.returnBook(bookToReturn, returnedBook); + assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void executeUndoRedo_invalidIndexUnfilteredList_failure() { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredBookList().size() + 1); + ReturnCommand returnCommand = prepareCommand(outOfBoundIndex); + + // execution failed -> deleteCommand not pushed into undoRedoStack + assertCommandFailure(returnCommand, model, Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + + // no commands in undoRedoStack -> undoCommand and redoCommand fail + assertCommandFailure(undoCommand, model, UndoCommand.MESSAGE_FAILURE); + assertCommandFailure(redoCommand, model, RedoCommand.MESSAGE_FAILURE); + } + + @Test + public void equals() throws Exception { + ReturnCommand returnFirstCommand = prepareCommand(INDEX_FIRST_BOOK); + ReturnCommand returnSecondCommand = prepareCommand(INDEX_SECOND_BOOK); + + // same object -> returns true + assertTrue(returnFirstCommand.equals(returnFirstCommand)); + + // same values -> returns true + ReturnCommand returnFirstCommandCopy = prepareCommand(INDEX_FIRST_BOOK); + assertTrue(returnFirstCommand.equals(returnFirstCommandCopy)); + + // one command preprocessed when previously equal -> returns false + returnFirstCommandCopy.preprocessUndoableCommand(); + assertFalse(returnFirstCommand.equals(returnFirstCommandCopy)); + + // different types -> returns false + assertFalse(returnFirstCommand.equals(1)); + + // null -> returns false + assertFalse(returnFirstCommand.equals(null)); + + // different book -> returns false + assertFalse(returnFirstCommand.equals(returnSecondCommand)); + } + + private ReturnCommand prepareCommand(Index index) { + ReturnCommand returnCommand = new ReturnCommand(index); + returnCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return returnCommand; + } + + /** + *Creates a duplicate book with a changed availability + * + */ + + public Book createReturnedBook(Book bookToBorrow) { + assert bookToBorrow != null; + + Title updatedTitle = bookToBorrow.getTitle(); + Isbn updatedIsbn = bookToBorrow.getIsbn(); + Avail updatedAvail = new Avail(AVAILABLE); + Author updatedAuthor = bookToBorrow.getAuthor(); + Set<Tag> updatedTags = bookToBorrow.getTags(); + + return new Book(updatedTitle, updatedAuthor, updatedIsbn, updatedAvail, updatedTags); + } + +} +``` diff --git a/collated/test/QiuHaohao.md b/collated/test/QiuHaohao.md new file mode 100644 index 000000000000..fce831d4a739 --- /dev/null +++ b/collated/test/QiuHaohao.md @@ -0,0 +1,475 @@ +# QiuHaohao +###### /java/systemtests/FindCommandSystemTest.java +``` java + executeCommand("login admin admin"); +``` +###### /java/seedu/address/logic/parser/LoginCommandParserTest.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseSuccess; + +import org.junit.Test; + +import seedu.address.logic.commands.LoginCommand; + +public class LoginCommandParserTest { + private LoginCommandParser parser = new LoginCommandParser(); + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, LoginCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_validArgs_returnsFindCommand() { + // no leading and trailing whitespaces + LoginCommand expectedFindCommand = + new LoginCommand("admin", "admin"); + assertParseSuccess(parser, "admin admin", expectedFindCommand); + + // multiple whitespaces between keywords + assertParseSuccess(parser, " \n admin \n \t admin \t", expectedFindCommand); + } +} +``` +###### /java/seedu/address/logic/commands/LoginCommandTest.java +``` java +package seedu.address.logic.commands; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.account.Account; + +public class LoginCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void equals() { + LoginCommand loginStudentCommand = new LoginCommand("student", "student"); + LoginCommand loginAdminCommand = new LoginCommand("admin", "admin"); + + // same object -> returns true + assertTrue(loginStudentCommand.equals(loginStudentCommand)); + + // same values -> returns true + LoginCommand loginStudentCommandCopy = new LoginCommand("student", "student"); + assertTrue(loginStudentCommand.equals(loginStudentCommandCopy)); + + // different types -> returns false + assertFalse(loginStudentCommand.equals(1)); + + // null -> returns false + assertFalse(loginStudentCommand.equals(null)); + + // different person -> returns false + assertFalse(loginStudentCommand.equals(loginAdminCommand)); + } + + @Test + public void constructor_nullUsername_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new LoginCommand(null, "admin"); + } + + @Test + public void constructor_nullPassword_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new LoginCommand("admin", null); + } + + @Test + public void execute_loginAsStudent_loginSuccessful() throws Exception { + Model model = new ModelManager(); + Account student = Account.createDefaultStudentAccount(); + model.addAccount(student); + LoginCommand studentLogin = new LoginCommand("student", "student"); + studentLogin.setData(model, null, null); + CommandResult commandResult = studentLogin.execute(); + + assertEquals(LoginCommand.MESSAGE_LOGGED_IN_AS_STUTENT, commandResult.feedbackToUser); + assertEquals(model.getPrivilegeLevel(), Model.PRIVILEGE_LEVEL_STUDENT); + } + + @Test + public void execute_loginAsLibrarian_loginSuccessful() { + Model model = new ModelManager(); + LoginCommand studentLogin = new LoginCommand("admin", "admin"); + studentLogin.setData(model, null, null); + CommandResult commandResult = studentLogin.execute(); + + assertEquals(LoginCommand.MESSAGE_LOGGED_IN_AS_LIBRARIAN, commandResult.feedbackToUser); + assertEquals(model.getPrivilegeLevel(), Model.PRIVILEGE_LEVEL_LIBRARIAN); + } +} +``` +###### /java/seedu/address/logic/commands/LogoutCommandTest.java +``` java +package seedu.address.logic.commands; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +//import static org.junit.Assert.assertEquals; +//import org.junit.Rule; +//import org.junit.rules.ExpectedException; +//import seedu.address.model.ModelManager; +//import seedu.address.model.Model; + + +public class LogoutCommandTest { + @Test + public void equals() { + LogoutCommand logoutCommand1 = new LogoutCommand(); + LogoutCommand logoutCommand2 = new LogoutCommand(); + + // same object -> returns true + assertTrue(logoutCommand1.equals(logoutCommand1)); + + // same values -> returns true + assertTrue(logoutCommand1.equals(logoutCommand2)); + + // different types -> returns false + assertFalse(logoutCommand1.equals(1)); + + // null -> returns false + assertFalse(logoutCommand1.equals(null)); + } +} +``` +###### /java/seedu/address/model/account/AccountTest.java +``` java +package seedu.address.model.account; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +public class AccountTest { + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () + -> new Account(null, null, null, null)); + Assert.assertThrows(NullPointerException.class, () + -> new Account(new Name("dummy"), null, null, null)); + Assert.assertThrows(NullPointerException.class, () + -> new Account(null, new Credential("dummy", "dummy"), null, null)); + Assert.assertThrows(NullPointerException.class, () + -> new Account(null, null, new MatricNumber("A1231231A"), null)); + Assert.assertThrows(NullPointerException.class, () + -> new Account(null, null, null, new PrivilegeLevel(0))); + } + + @Test + public void credentialMatchesTest() { + Credential studentCredential = new Credential("student", "student"); + Credential adminCredential = new Credential("admin", "admin"); + Account studentAccount = Account.createDefaultStudentAccount(); + Account adminAccount = Account.createDefaultAdminAccount(); + assertTrue(studentAccount.credentialMatches(studentCredential)); + assertTrue(adminAccount.credentialMatches(adminCredential)); + assertFalse(studentAccount.credentialMatches(adminCredential)); + assertFalse(adminAccount.credentialMatches(studentCredential)); + } + + @Test + public void equalsTest() { + Account studentAccount = Account.createDefaultStudentAccount(); + Account studentAccountCopy = Account.createDefaultStudentAccount(); + Account adminAccount = Account.createDefaultAdminAccount(); + + assertTrue(studentAccount.equals(studentAccount)); + assertTrue(studentAccount.equals(studentAccountCopy)); + assertFalse(studentAccount.equals(adminAccount)); + assertFalse(studentAccount.equals(null)); + assertFalse(studentAccount.equals(0)); + } + + @Test + public void usernameMatches() { + Name name = new Name("Ryan"); + Credential credential = new Credential("student", "student2"); + MatricNumber matricNumber = new MatricNumber("A0123256X"); + PrivilegeLevel privilegeLevel = new PrivilegeLevel(1); + Account student2 = new Account(name, credential, matricNumber, privilegeLevel); + Account student = Account.createDefaultStudentAccount(); + Account admin = Account.createDefaultAdminAccount(); + + assertTrue(student2.usernameMatches(student)); + assertFalse(student2.usernameMatches(admin)); + } +} +``` +###### /java/seedu/address/model/account/MatricNumberTest.java +``` java +package seedu.address.model.account; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +public class MatricNumberTest { + + @Test + public void isValidMatricNumber() { + // null pointer + Assert.assertThrows(NullPointerException.class, () -> MatricNumber.isValidMatricNumber(null)); + + //invalid + assertFalse(MatricNumber.isValidMatricNumber("")); // empty string + assertFalse(MatricNumber.isValidMatricNumber("123")); + assertFalse(MatricNumber.isValidMatricNumber("abc")); + assertFalse(MatricNumber.isValidMatricNumber("!!!")); + assertFalse(MatricNumber.isValidMatricNumber("!!!!!!")); + assertFalse(MatricNumber.isValidMatricNumber("A1234567XX!")); + assertFalse(MatricNumber.isValidMatricNumber("A123456723X!")); + assertFalse(MatricNumber.isValidMatricNumber("1234567XX!")); + + //valid + assertTrue(MatricNumber.isValidMatricNumber("A1234567Z")); + assertTrue(MatricNumber.isValidMatricNumber("A9992567B")); + } + + @Test + public void getMatricNumber() { + String matricNumberString = "A1234567Z"; + MatricNumber m = new MatricNumber(matricNumberString); + assertEquals(matricNumberString, m.getMatricNumber()); + } + + @Test + public void equals() { + MatricNumber p1 = new MatricNumber("A1234567Z"); + MatricNumber p1copy = new MatricNumber("A1234567Z"); + MatricNumber p2 = new MatricNumber("A9992567B"); + + //equal with itself + assertTrue(p1.equals(p1)); + + //equal with an other object with same state + assertTrue(p1.equals(p1copy)); + + //not equal with null + assertFalse(p1.equals(null)); + + //not equal with other type + assertFalse(p1.equals(1)); + + //not equal with same type with different state + assertFalse(p1.equals(p2)); + } +} +``` +###### /java/seedu/address/model/account/CredentialTest.java +``` java +package seedu.address.model.account; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +//import javax.jws.soap.SOAPBinding; + +public class CredentialTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Credential(null, null)); + Assert.assertThrows(NullPointerException.class, () -> new Credential("username", null)); + Assert.assertThrows(NullPointerException.class, () -> new Credential(null, "password")); + } + + + @Test + public void getPassword_and_getUsername() { + String passwordString = "password"; + Password p = new Password(passwordString); + String usernameString = "username"; + Username u = new Username(usernameString); + Credential c = new Credential(usernameString, passwordString); + assertEquals(c.getPassword(), p); + assertEquals(c.getUsername(), u); + } + + @Test + public void equals() { + String u1 = "username1"; + String u1copy = "username1"; + String u2 = "username2"; + String p1 = "password1"; + String p1copy = "password1"; + String p2 = "password2"; + Credential c1 = new Credential(u1, p1); + Credential c1copy = new Credential(u1copy, p1copy); + Credential c2 = new Credential(u2, p2); + + //equal with itself + assertTrue(c1.equals(c1)); + + //equal with an other object with same state + assertTrue(c1.equals(c1copy)); + + //not equal with null + assertFalse(c1.equals(null)); + + //not equal with other type + assertFalse(c1.equals(1)); + + //not equal with same type with different state + assertFalse(c1.equals(c2)); + } + + @Test + public void usernameEquals() { + String u1 = "username1"; + String u2 = "username2"; + String p1 = "password1"; + Credential u1p1 = new Credential(u1, p1); + Credential u1p2 = new Credential(u1, p1); + Credential u2p1 = new Credential(u2, p1); + Username username1 = u1p1.getUsername(); + Username username2 = u2p1.getUsername(); + + assertTrue(u1p1.usernameEquals(username1)); + assertTrue(u1p2.usernameEquals(username1)); + assertFalse(u1p1.usernameEquals(username2)); + } +} +``` +###### /java/seedu/address/model/account/UniqueAccountListTest.java +``` java +package seedu.address.model.account; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import seedu.address.model.account.exceptions.DuplicateAccountException; +import seedu.address.testutil.Assert; + +public class UniqueAccountListTest { + @Test + public void add() throws DuplicateAccountException { + UniqueAccountList accountList = new UniqueAccountList(); + + Name name = new Name("Ryan"); + Credential credential = new Credential("student", "student2"); + MatricNumber matricNumber = new MatricNumber("A0123256X"); + PrivilegeLevel privilegeLevel = new PrivilegeLevel(1); + + Account student2 = new Account(name, credential, matricNumber, privilegeLevel); + Account student = Account.createDefaultStudentAccount(); + Account admin = Account.createDefaultAdminAccount(); + + accountList.add(student); + accountList.add(admin); + + Assert.assertThrows(DuplicateAccountException.class, () + -> accountList.add(student2)); + } + + @Test + public void searchByUsername() throws DuplicateAccountException { + UniqueAccountList accountList = new UniqueAccountList(); + Account student = Account.createDefaultStudentAccount(); + Account admin = Account.createDefaultAdminAccount(); + + accountList.add(student); + accountList.add(admin); + + assertEquals(accountList.searchByUsername(new Username("student")), student); + } +} +``` +###### /java/seedu/address/model/account/PrivilegeLevelTest.java +``` java +package seedu.address.model.account; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +public class PrivilegeLevelTest { + + @Test + public void constructor_invalidPrivilegeLevel_throwsIllegalArgumentException() { + final int invalidPrivilegeLevel1 = 3; + final int invalidPrivilegeLevel2 = -1; + Assert.assertThrows(IllegalArgumentException.class, () -> new PrivilegeLevel(invalidPrivilegeLevel1)); + Assert.assertThrows(IllegalArgumentException.class, () -> new PrivilegeLevel(invalidPrivilegeLevel2)); + } + + @Test + public void isValidPrivilegeLevel_test() { + assertTrue(PrivilegeLevel.isValidPrivilegeLevel(0)); + assertTrue(PrivilegeLevel.isValidPrivilegeLevel(1)); + assertTrue(PrivilegeLevel.isValidPrivilegeLevel(2)); + assertFalse(PrivilegeLevel.isValidPrivilegeLevel(3)); + assertFalse(PrivilegeLevel.isValidPrivilegeLevel(-1)); + } + + @Test + public void equals() { + PrivilegeLevel p0 = new PrivilegeLevel(0); + PrivilegeLevel p0copy = new PrivilegeLevel(0); + PrivilegeLevel p1 = new PrivilegeLevel(1); + PrivilegeLevel p2 = new PrivilegeLevel(2); + + //equal with itself + assertTrue(p1.equals(p1)); + + //equal with an other object with same state + assertTrue(p0.equals(p0copy)); + + //not equal with null + assertFalse(p1.equals(null)); + + //not equal with other type + assertFalse(p1.equals(1)); + + //not equal with same type with different state + assertFalse(p1.equals(p2)); + } + + + @Test + public void compareTo() { + PrivilegeLevel p0 = new PrivilegeLevel(0); + PrivilegeLevel p1 = new PrivilegeLevel(1); + PrivilegeLevel p2 = new PrivilegeLevel(2); + + assertTrue(p0.compareTo(p1) < 0); + assertTrue(p1.compareTo(p2) < 0); + assertTrue(p1.compareTo(p0) > 0); + assertTrue(p1.compareTo(p1) == 0); + } +} +``` diff --git a/collated/test/chantiongley.md b/collated/test/chantiongley.md new file mode 100644 index 000000000000..292f346887dd --- /dev/null +++ b/collated/test/chantiongley.md @@ -0,0 +1,503 @@ +# chantiongley +###### /java/seedu/address/logic/parser/DeleteAccountCommandParserTest.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; + +import org.junit.Test; + +import seedu.address.logic.commands.DeleteAccountCommand; + +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the DeleteCommand code. For example, inputs "1" and "1 abc" take the + * same path through the DeleteCommand, and therefore we test only one of them. + * The path variation for those two cases occur inside the ParserUtil, and + * therefore should be covered by the ParserUtilTest. + */ + +public class DeleteAccountCommandParserTest { + + private DeleteAccountCommandParser parser = new DeleteAccountCommandParser(); + + @Test + public void parse_emptyArg_throwsParseException() { + assertParseFailure(parser, " ", String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteAccountCommand + .MESSAGE_USAGE)); + } + +} +``` +###### /java/seedu/address/logic/parser/ReserveCommandParserTest.java +``` java +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CommandParserTestUtil.assertParseFailure; + +import org.junit.Test; + +import seedu.address.logic.commands.ReserveCommand; + + +/** + * As we are only doing white-box testing, our test cases do not cover path variations + * outside of the BorrowCommand code. For example, inputs "1" and "1 abc" take the + * same path through the BorrowCommand, and therefore we test only one of them. + * The path variation for those two cases occur inside the ParserUtil, and + * therefore should be covered by the ParserUtilTest. + */ +public class ReserveCommandParserTest { + + private ReserveCommandParser parser = new ReserveCommandParser(); + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, ReserveCommand.MESSAGE_USAGE)); + } +} +``` +###### /java/seedu/address/logic/commands/AddAccountCommandIntegrationTest.java +``` java +package seedu.address.logic.commands; + +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalAccounts.getTypicalAccountList; +import static seedu.address.testutil.TypicalAccounts.getTypicalAccounts; + +import org.junit.Before; +import org.junit.Test; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.account.Account; +import seedu.address.testutil.AccountBuilder; + +public class AddAccountCommandIntegrationTest { + private Model model; + + @Before + public void setUp() { + model = getTypicalAccountList(); + } + + @Test + public void execute_newAccount_success() throws Exception { + Account validAccount = new AccountBuilder().build(); + + Model expectedModel = new ModelManager(model.getCatalogue(), new UserPrefs()); + expectedModel.addAccount(validAccount); + + assertCommandSuccess(prepareCommand(validAccount, model), model, + String.format(AddAccountCommand.MESSAGE_SUCCESS, validAccount), expectedModel); + } + + @Test + public void execute_duplicateAccount_throwsCommandException() { + Account accountInList = getTypicalAccounts().get(0); + assertCommandFailure(prepareCommand(accountInList, model), model, AddAccountCommand.MESSAGE_DUPLICATE_ACCOUNT); + } + + /** + * Generates a new {@code AddAccountCommand} which upon execution, adds {@code account} into the {@code model}. + */ + private AddAccountCommand prepareCommand(Account account, Model model) { + AddAccountCommand command = new AddAccountCommand(account); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } +} +``` +###### /java/seedu/address/logic/commands/ReserveCommandTest.java +``` java +package seedu.address.logic.commands; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.logic.commands.CommandTestUtil.prepareRedoCommand; +import static seedu.address.logic.commands.CommandTestUtil.prepareUndoCommand; +import static seedu.address.logic.commands.CommandTestUtil.showBookAtIndex; +import static seedu.address.model.book.Avail.RESERVED; +import static seedu.address.testutil.TypicalBooks.getTypicalCatalogue; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIFTH_BOOK; +import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_BOOK; +import static seedu.address.testutil.TypicalIndexes.INDEX_FOURTH_BOOK; +import static seedu.address.testutil.TypicalIndexes.INDEX_SECOND_BOOK; +import static seedu.address.testutil.TypicalIndexes.INDEX_THIRD_BOOK; + +import java.util.Set; + +import org.junit.Test; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; +import seedu.address.model.book.Author; +import seedu.address.model.book.Avail; +import seedu.address.model.book.Book; +import seedu.address.model.book.Isbn; +import seedu.address.model.book.Title; +import seedu.address.model.tag.Tag; + + +/** + * Contains integration tests (interaction with the Model, UndoCommand and RedoCommand) and unit tests for + * {@code reserveCommand}. + */ +public class ReserveCommandTest { + + private Model model = new ModelManager(getTypicalCatalogue(), new UserPrefs()); + + @Test + public void execute_validIndexUnfilteredList_success() throws Exception { + Book bookToReserve = model.getFilteredBookList().get(INDEX_FOURTH_BOOK.getZeroBased()); + Book reservedBook = createReservedBook(bookToReserve); + ReserveCommand reserveCommand = prepareCommand(INDEX_FOURTH_BOOK); + + String expectedMessage = String.format(reserveCommand.MESSAGE_RESERVE_BOOK_SUCCESS, bookToReserve); + + ModelManager expectedModel = new ModelManager(model.getCatalogue(), new UserPrefs()); + expectedModel.reserveBook(bookToReserve, reservedBook); + + assertCommandSuccess(reserveCommand, model, expectedMessage, expectedModel); + } + + @Test + public void execute_invalidIndexUnfilteredList_throwsCommandException() { + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredBookList().size() + 1); + ReserveCommand reserveCommand = prepareCommand(outOfBoundIndex); + + assertCommandFailure(reserveCommand, model, Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + } + + @Test + public void execute_invalidIndexFilteredList_throwsCommandException() { + showBookAtIndex(model, INDEX_FIRST_BOOK); + + Index outOfBoundIndex = INDEX_SECOND_BOOK; + // ensures that outOfBoundIndex is still in bounds of catalogue list + assertTrue(outOfBoundIndex.getZeroBased() < model.getCatalogue().getBookList().size()); + + ReserveCommand reserveCommand = prepareCommand(outOfBoundIndex); + + assertCommandFailure(reserveCommand, model, Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + } + + @Test + public void executeUndoRedo_validIndexUnfilteredList_success() throws Exception { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Book bookToReserve = model.getFilteredBookList().get(INDEX_FIFTH_BOOK.getZeroBased()); + Book reservedBook = createReservedBook(bookToReserve); + ReserveCommand reserveCommand = prepareCommand(INDEX_FIFTH_BOOK); + Model expectedModel = new ModelManager(model.getCatalogue(), new UserPrefs()); + + // reserve -> first book reserve + reserveCommand.execute(); + undoRedoStack.push(reserveCommand); + + // undo -> reverts catalogue back to previous state and filtered book list to show all books + assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel); + + // redo -> same first book reserved again + expectedModel.returnBook(bookToReserve, reservedBook); + assertCommandSuccess(redoCommand, model, RedoCommand.MESSAGE_SUCCESS, expectedModel); + } + + @Test + public void executeUndoRedo_invalidIndexUnfilteredList_failure() { + UndoRedoStack undoRedoStack = new UndoRedoStack(); + UndoCommand undoCommand = prepareUndoCommand(model, undoRedoStack); + RedoCommand redoCommand = prepareRedoCommand(model, undoRedoStack); + Index outOfBoundIndex = Index.fromOneBased(model.getFilteredBookList().size() + 1); + ReserveCommand reserveCommand = prepareCommand(outOfBoundIndex); + + // execution failed -> reserveCommand not pushed into undoRedoStack + assertCommandFailure(reserveCommand, model, Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + + // no commands in undoRedoStack -> undoCommand and redoCommand fail + assertCommandFailure(undoCommand, model, UndoCommand.MESSAGE_FAILURE); + assertCommandFailure(redoCommand, model, RedoCommand.MESSAGE_FAILURE); + } + + @Test + public void equals() throws Exception { + ReserveCommand reserveFirstCommand = prepareCommand(INDEX_THIRD_BOOK); + ReserveCommand reserveSecondCommand = prepareCommand(INDEX_FOURTH_BOOK); + + // same object -> returns true + assertTrue(reserveFirstCommand.equals(reserveFirstCommand)); + + // different types -> returns false + assertFalse(reserveFirstCommand.equals(1)); + + // null -> returns false + assertFalse(reserveFirstCommand.equals(null)); + + // different book -> returns false + assertFalse(reserveFirstCommand.equals(reserveSecondCommand)); + } + + private ReserveCommand prepareCommand(Index index) { + ReserveCommand reserveCommand = new ReserveCommand(index); + reserveCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return reserveCommand; + } + + /** + * @param bookToReserve + * @return duplicated book with Reserved Availability + */ + public Book createReservedBook(Book bookToReserve) { + assert bookToReserve != null; + + Title updatedTitle = bookToReserve.getTitle(); + Isbn updatedIsbn = bookToReserve.getIsbn(); + Avail updatedAvail = new Avail(RESERVED); + Author updatedAuthor = bookToReserve.getAuthor(); + Set<Tag> updatedTags = bookToReserve.getTags(); + + return new Book(updatedTitle, updatedAuthor, updatedIsbn, updatedAvail, updatedTags); + } + +} +``` +###### /java/seedu/address/logic/commands/AddAccountCommandTest.java +``` java +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; +import static junit.framework.TestCase.fail; +import static org.junit.Assert.assertFalse; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.function.Predicate; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import javafx.collections.ObservableList; +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Catalogue; +import seedu.address.model.Model; +import seedu.address.model.ReadOnlyCatalogue; +import seedu.address.model.account.Account; +import seedu.address.model.account.Credential; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.account.UniqueAccountList; +import seedu.address.model.account.exceptions.DuplicateAccountException; +import seedu.address.model.book.Book; +import seedu.address.model.book.exceptions.BookNotFoundException; +import seedu.address.model.book.exceptions.DuplicateBookException; +import seedu.address.testutil.AccountBuilder; + +public class AddAccountCommandTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void constructor_nullAccount_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new AddAccountCommand(null); + } + + @Test + public void execute_accountAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingAccountAdded modelStub = new ModelStubAcceptingAccountAdded(); + Account validAccount = new AccountBuilder().build(); + + CommandResult commandResult = getAddCommandForAccount(validAccount, modelStub).execute(); + + assertEquals(String.format(AddAccountCommand.MESSAGE_SUCCESS, validAccount), commandResult.feedbackToUser); + assertEquals(Arrays.asList(validAccount), modelStub.accountsAdded); + } + + @Test + public void execute_duplicateAccount_throwsCommandException() throws Exception { + ModelStub modelStub = new ModelStubThrowingDuplicateAccountException(); + Account validAccount = new AccountBuilder().build(); + + thrown.expect(CommandException.class); + thrown.expectMessage(AddAccountCommand.MESSAGE_DUPLICATE_ACCOUNT); + + getAddCommandForAccount(validAccount, modelStub).execute(); + } + + @Test + public void equals() { + Account alice = new AccountBuilder().withName("Alice").build(); + Account bob = new AccountBuilder().withName("Bob").build(); + AddAccountCommand addAliceCommand = new AddAccountCommand(alice); + AddAccountCommand addBobCommand = new AddAccountCommand(bob); + + // same object -> returns true + assertTrue(addAliceCommand.equals(addAliceCommand)); + + // different types -> returns false + assertFalse(addAliceCommand.equals(1)); + + // null -> returns false + assertFalse(addAliceCommand.equals(null)); + + // different account -> returns false + assertFalse(addAliceCommand.equals(addBobCommand)); + } + + /** + * Generates a new AddAccountCommand with the details of the given account. + */ + private AddAccountCommand getAddCommandForAccount(Account account, Model model) { + AddAccountCommand command = new AddAccountCommand(account); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + + /** + * A default model stub that have all of the methods failing. + */ + private class ModelStub implements Model { + @Override + public void addBook(Book book) throws DuplicateBookException { + fail("This method should not be called."); + } + + @Override + public void resetData(ReadOnlyCatalogue newData) { + fail("This method should not be called."); + } + + @Override + public void resetAccount(UniqueAccountList newData) { + + } + + @Override + public ReadOnlyCatalogue getCatalogue() { + fail("This method should not be called."); + return null; + } + + @Override + public void deleteBook(Book target) { + fail("This method should not be called."); + } + + @Override + public void updateBook(Book target, Book editedBook) { + fail("This method should not be called."); + } + + @Override + public void returnBook(Book target, Book returnedBook) throws BookNotFoundException { + } + + @Override + public void borrowBook(Book target, Book borrowedBook) throws BookNotFoundException { + + } + + @Override + public void reserveBook(Book target, Book reservedBook) throws BookNotFoundException { + + } + + @Override + public ObservableList<Book> getFilteredBookList() { + fail("This method should not be called."); + return null; + } + + @Override + public UniqueAccountList getAccountList() { + fail("This method should not be called."); + return null; + } + + @Override + public void updateFilteredBookList(Predicate<Book> predicate) { + fail("This method should not be called."); + } + + @Override + public PrivilegeLevel authenticate(Credential c) { + return Model.PRIVILEGE_LEVEL_GUEST; + } + + @Override + public void logout() { + + } + + @Override + public PrivilegeLevel getPrivilegeLevel() { + return Model.PRIVILEGE_LEVEL_GUEST; + } + + public void addAccount(Account account) throws DuplicateAccountException { + + } + + public void deleteAccount(Account account) { + + } + + public void updateAccount(Account account, Account editedAccount) { + + } + } + + /** + * A Model stub that always throw a DuplicateAccountException when trying to add a account. + */ + private class ModelStubThrowingDuplicateAccountException extends ModelStub { + @Override + public void addAccount(Account account) throws DuplicateAccountException { + throw new DuplicateAccountException(); + } + + @Override + + public ReadOnlyCatalogue getCatalogue() { + return new Catalogue(); + } + } + + /** + * A Model stub that always accept the account being added. + */ + private class ModelStubAcceptingAccountAdded extends ModelStub { + final ArrayList<Account> accountsAdded = new ArrayList<>(); + + @Override + public void addAccount(Account account) { + requireNonNull(account); + accountsAdded.add(account); + } + + @Override + public ReadOnlyCatalogue getCatalogue() { + return new Catalogue(); + } + } + +} +``` diff --git a/collated/test/khiayi.md b/collated/test/khiayi.md new file mode 100644 index 000000000000..56726784b149 --- /dev/null +++ b/collated/test/khiayi.md @@ -0,0 +1,630 @@ +# khiayi +###### /java/systemtests/AddCommandSystemTest.java +``` java + /* ------------------------ Perform add operations on the shown unfiltered list ----------------------------- */ + + /* Case: add a book without tags to a non-empty catalogue, command with leading spaces and trailing spaces + * -> added + */ + Book toAdd = XVI; + String command = " " + AddCommand.COMMAND_WORD + " " + TITLE_DESC_XVI + " " + " " + AUTHOR_DESC_XVI + + " " + ISBN_DESC_XVI + " " + + AVAIL_DESC_XVI + " " + TAG_DESC_DYSTOPIA + " "; + assertCommandSuccess(command, toAdd); + + /* Case: undo adding XVI to the list -> XVI deleted */ + command = UndoCommand.COMMAND_WORD; + String expectedResultMessage = UndoCommand.MESSAGE_SUCCESS; + assertCommandSuccess(command, model, expectedResultMessage); + + /* Case: redo adding XVI to the list -> XVI added again */ + command = RedoCommand.COMMAND_WORD; + model.addBook(toAdd); + expectedResultMessage = RedoCommand.MESSAGE_SUCCESS; + assertCommandSuccess(command, model, expectedResultMessage); + + /* Case: add a book with all fields same as another book in the catalogue except name -> not added */ + toAdd = new BookBuilder().withTitle(VALID_TITLE_YOU).withAuthor(VALID_AUTHOR_XVI) + .withIsbn(VALID_ISBN_XVI).withAvail(VALID_AVAIL_XVI) + .withTags(VALID_TAG_DYSTOPIA).build(); + command = AddCommand.COMMAND_WORD + TITLE_DESC_YOU + AUTHOR_DESC_XVI + ISBN_DESC_XVI + AVAIL_DESC_XVI + + TAG_DESC_DYSTOPIA; + assertCommandFailure(command, toAdd); + + /* Case: add a book with all fields same as another book in the catalogue except isbn -> added */ + toAdd = new BookBuilder().withTitle(VALID_TITLE_XVI).withAuthor(VALID_AUTHOR_XVI) + .withIsbn(VALID_ISBN_YOU).withAvail(VALID_AVAIL_XVI) + .withTags(VALID_TAG_DYSTOPIA).build(); + command = AddCommand.COMMAND_WORD + TITLE_DESC_XVI + AUTHOR_DESC_XVI + ISBN_DESC_YOU + AVAIL_DESC_XVI + + TAG_DESC_DYSTOPIA; + assertCommandSuccess(command, toAdd); + + /* Case: add a book with all fields same as another book in the catalogue except name and ISBN -> added */ + toAdd = new BookBuilder().withTitle(VALID_TITLE_YOU).withAuthor(VALID_AUTHOR_XVI) + .withIsbn("1111111111111").withAvail(VALID_AVAIL_XVI) + .withTags(VALID_TAG_DYSTOPIA).build(); + command = AddCommand.COMMAND_WORD + TITLE_DESC_YOU + AUTHOR_DESC_XVI + " " + PREFIX_ISBN + "1111111111111" + + AVAIL_DESC_XVI + TAG_DESC_DYSTOPIA; + assertCommandSuccess(command, toAdd); + + /* Case: add a book with all fields same as another book in the catalogue except avail -> not added */ + toAdd = new BookBuilder().withTitle(VALID_TITLE_XVI).withAuthor(VALID_AUTHOR_XVI) + .withIsbn(VALID_ISBN_XVI).withAvail(VALID_AVAIL_YOU) + .withTags(VALID_TAG_DYSTOPIA).build(); + command = AddCommand.COMMAND_WORD + TITLE_DESC_XVI + AUTHOR_DESC_XVI + ISBN_DESC_XVI + AVAIL_DESC_YOU + + TAG_DESC_DYSTOPIA; + assertCommandFailure(command, toAdd); + + /* Case: add a book with all fields same as another book in the catalogue except avail and ISBN -> added */ + toAdd = new BookBuilder().withTitle(VALID_TITLE_XVI).withAuthor(VALID_AUTHOR_XVI) + .withIsbn("2222222222222").withAvail(VALID_AVAIL_YOU) + .withTags(VALID_TAG_DYSTOPIA).build(); + command = AddCommand.COMMAND_WORD + TITLE_DESC_XVI + AUTHOR_DESC_XVI + " " + PREFIX_ISBN + "2222222222222" + + AVAIL_DESC_YOU + TAG_DESC_DYSTOPIA; + assertCommandSuccess(command, toAdd); + + /* Case: add a book with all fields same as another book in the catalogue except author -> added */ + toAdd = new BookBuilder().withTitle(VALID_TITLE_XVI).withAuthor(VALID_AUTHOR_YOU) + .withIsbn(VALID_ISBN_XVI).withAvail(VALID_AVAIL_XVI) + .withTags(VALID_TAG_DYSTOPIA).build(); + command = AddCommand.COMMAND_WORD + TITLE_DESC_XVI + AUTHOR_DESC_YOU + ISBN_DESC_XVI + AVAIL_DESC_XVI + + TAG_DESC_DYSTOPIA; + assertCommandFailure(command, toAdd); + + /* Case: add a book with all fields same as another book in the catalogue except author and ISBN -> added */ + toAdd = new BookBuilder().withTitle(VALID_TITLE_XVI).withAuthor(VALID_AUTHOR_YOU) + .withIsbn("3333333333333").withAvail(VALID_AVAIL_XVI) + .withTags(VALID_TAG_DYSTOPIA).build(); + command = AddCommand.COMMAND_WORD + TITLE_DESC_XVI + AUTHOR_DESC_YOU + " " + PREFIX_ISBN + "3333333333333" + + AVAIL_DESC_XVI + TAG_DESC_DYSTOPIA; + assertCommandSuccess(command, toAdd); + + /* Case: add to empty catalogue -> added */ + deleteAllBooks(); + assertCommandSuccess(ANIMAL); + + /* Case: add a book with tags, command with parameters in random order -> added */ + toAdd = YOU; + command = AddCommand.COMMAND_WORD + AUTHOR_DESC_YOU + ISBN_DESC_YOU + TITLE_DESC_YOU + + TAG_DESC_FICTION + AVAIL_DESC_YOU; + assertCommandSuccess(command, toAdd); + + /* Case: add a book, missing tags -> added */ + assertCommandSuccess(HOLES); + + /* -------------------------- Perform add operation on the shown filtered list ------------------------------ */ + + /* Case: filters the book list before adding -> added */ + showBooksWithTitle(KEYWORD_MATCHING_GIRL); + assertCommandSuccess(INVISIBLE); + + /* ------------------------ Perform add operation while a book card is selected --------------------------- */ + + /* Case: selects first card in the book list, add a book -> added, card selection remains unchanged */ + selectBook(Index.fromOneBased(1)); + assertCommandSuccess(CALIFORNIA); + + /* ----------------------------------- Perform invalid add operations --------------------------------------- */ + + /* Case: add a duplicate book -> rejected */ + command = BookUtil.getAddCommand(HOLES); + assertCommandFailure(command, AddCommand.MESSAGE_DUPLICATE_BOOK); + + /* Case: add a duplicate book except with different tags -> rejected */ + // "friends" is an existing tag used in the default model, see TypicalBooks#ANIMAL + // This test will fail if a new tag that is not in the model is used, see the bug documented in + // Catalogue#addBook(Book) + command = BookUtil.getAddCommand(HOLES) + " " + PREFIX_TAG.getPrefix() + "unlabelled"; + assertCommandFailure(command, AddCommand.MESSAGE_DUPLICATE_BOOK); + + /* Case: missing name -> rejected */ + command = AddCommand.COMMAND_WORD + AUTHOR_DESC_XVI + ISBN_DESC_XVI + AVAIL_DESC_XVI; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + + /* Case: missing isbn -> rejected */ + command = AddCommand.COMMAND_WORD + TITLE_DESC_XVI + AUTHOR_DESC_XVI + AVAIL_DESC_XVI; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + + /* Case: missing avail -> rejected */ + command = AddCommand.COMMAND_WORD + TITLE_DESC_XVI + AUTHOR_DESC_XVI + ISBN_DESC_XVI; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + + /* Case: missing author -> rejected */ + command = AddCommand.COMMAND_WORD + TITLE_DESC_XVI + ISBN_DESC_XVI + AVAIL_DESC_XVI; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + + /* Case: invalid keyword -> rejected */ + command = "adds " + BookUtil.getBookDetails(toAdd); + assertCommandFailure(command, Messages.MESSAGE_UNKNOWN_COMMAND); + + /* Case: invalid name -> rejected */ + command = AddCommand.COMMAND_WORD + INVALID_TITLE_DESC + AUTHOR_DESC_XVI + ISBN_DESC_XVI + AVAIL_DESC_XVI; + assertCommandFailure(command, Title.MESSAGE_TITLE_CONSTRAINTS); + + /* Case: invalid isbn -> rejected */ + command = AddCommand.COMMAND_WORD + TITLE_DESC_XVI + AUTHOR_DESC_XVI + INVALID_ISBN_DESC + AVAIL_DESC_XVI; + assertCommandFailure(command, Isbn.MESSAGE_ISBN_CONSTRAINTS); + + /* Case: invalid avail -> rejected */ + command = AddCommand.COMMAND_WORD + TITLE_DESC_XVI + AUTHOR_DESC_XVI + ISBN_DESC_XVI + INVALID_AVAIL_DESC; + assertCommandFailure(command, Avail.MESSAGE_AVAIL_CONSTRAINTS); + + /* Case: invalid author -> rejected */ + command = AddCommand.COMMAND_WORD + TITLE_DESC_XVI + INVALID_AUTHOR_DESC + ISBN_DESC_XVI + AVAIL_DESC_XVI; + assertCommandFailure(command, Author.MESSAGE_AUTHOR_CONSTRAINTS); + + /* Case: invalid tag -> rejected */ + command = AddCommand.COMMAND_WORD + TITLE_DESC_XVI + AUTHOR_DESC_XVI + ISBN_DESC_XVI + AVAIL_DESC_XVI + + INVALID_TAG_DESC; + assertCommandFailure(command, Tag.MESSAGE_TAG_CONSTRAINTS); + + + } +``` +###### /java/systemtests/DeleteCommandSystemTest.java +``` java + /* Case: filtered book list, delete index within bounds of catalogue and book list -> deleted */ + showBooksWithTitle(KEYWORD_MATCHING_BREAKING); + Index index = INDEX_FIRST_BOOK; + assertTrue(index.getZeroBased() < getModel().getFilteredBookList().size()); + assertCommandSuccess(index); + + /* Case: filtered book list, delete index within bounds of catalogue but out of bounds of book list + * -> rejected + */ + showBooksWithTitle(KEYWORD_MATCHING_BREAKING); + int invalidIndex = getModel().getCatalogue().getBookList().size(); + command = DeleteCommand.COMMAND_WORD + " " + invalidIndex; + assertCommandFailure(command, MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); +``` +###### /java/systemtests/FindCommandSystemTest.java +``` java + /* Case: find multiple books in catalogue, command with leading spaces and trailing spaces + * -> 2 books found + */ + String command = " " + FindCommand.COMMAND_WORD + " " + KEYWORD_MATCHING_GIRL + " "; + Model expectedModel = getModel(); + ModelHelper.setFilteredList(expectedModel, CALIFORNIA, GONE); // Two titles contains "Girl" + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: repeat previous find command where book list is displaying the books we are finding + * -> 2 books found + */ + command = FindCommand.COMMAND_WORD + " " + KEYWORD_MATCHING_GIRL; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find book where book list is not displaying the book we are finding -> 1 book found */ + command = FindCommand.COMMAND_WORD + " California"; + ModelHelper.setFilteredList(expectedModel, CALIFORNIA); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find multiple books in catalogue, 2 keywords -> 2 books found */ + command = FindCommand.COMMAND_WORD + " California Gone"; + ModelHelper.setFilteredList(expectedModel, CALIFORNIA, GONE); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find multiple books in catalogue, 2 keywords in reversed order -> 2 books found */ + command = FindCommand.COMMAND_WORD + " Gone California"; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find multiple books in catalogue, 2 keywords with 1 repeat -> 2 books found */ + command = FindCommand.COMMAND_WORD + " Gone California Gone"; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find multiple books in catalogue, 2 matching keywords and 1 non-matching keyword + * -> 2 books found + */ + command = FindCommand.COMMAND_WORD + " Gone California NonMatchingKeyWord"; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); +``` +###### /java/systemtests/FindCommandSystemTest.java +``` java + /* Case: find same books in catalogue after deleting 1 of them -> 1 book found */ + executeCommand(DeleteCommand.COMMAND_WORD + " 1"); + assertFalse(getModel().getCatalogue().getBookList().contains(CALIFORNIA)); + command = FindCommand.COMMAND_WORD + " " + KEYWORD_MATCHING_GIRL; + expectedModel = getModel(); + ModelHelper.setFilteredList(expectedModel, GONE); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find book in catalogue, keyword is same as name but of different case -> 1 book found */ + command = FindCommand.COMMAND_WORD + " GoNe GiRl"; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find book in catalogue, keyword is substring of name -> 0 books found */ + command = FindCommand.COMMAND_WORD + " Gon"; + ModelHelper.setFilteredList(expectedModel); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find book in catalogue, name is substring of keyword -> 0 books found */ + command = FindCommand.COMMAND_WORD + " Oliver"; + ModelHelper.setFilteredList(expectedModel); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find book not in catalogue -> 0 books found */ + command = FindCommand.COMMAND_WORD + " Mark"; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find book in catalogue, keyword is substring of author -> 0 books found */ + command = FindCommand.COMMAND_WORD + " Lau"; + ModelHelper.setFilteredList(expectedModel); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find book in catalogue, author is substring of keyword -> 0 books found */ + command = FindCommand.COMMAND_WORD + " Lauren"; + ModelHelper.setFilteredList(expectedModel); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find book not in catalogue, author not in catalogue -> 0 books found */ + command = FindCommand.COMMAND_WORD + " Mark"; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find isbn number of book in catalogue -> 0 books found */ + command = FindCommand.COMMAND_WORD + " " + DELIRIUM.getIsbn().value; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find availability of book in catalogue -> 0 books found */ + command = FindCommand.COMMAND_WORD + " " + DELIRIUM.getAvail().value; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find tags of book in catalogue -> 0 books found */ + List<Tag> tags = new ArrayList<>(DELIRIUM.getTags()); + command = FindCommand.COMMAND_WORD + " " + tags.get(0).tagName; + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: find while a book is selected -> selected card deselected */ + showAllBooks(); + selectBook(Index.fromOneBased(1)); + assertFalse(getBookListPanel().getHandleToSelectedCard().getTitle().equals(DELIRIUM.getTitle().fullTitle)); + command = FindCommand.COMMAND_WORD + " Delirium"; + ModelHelper.setFilteredList(expectedModel, DELIRIUM); + assertCommandSuccess(command, expectedModel); + assertSelectedCardDeselected(); + + /* Case: find book in empty catalogue -> 0 books found */ + deleteAllBooks(); + command = FindCommand.COMMAND_WORD + " " + KEYWORD_MATCHING_GIRL; + expectedModel = getModel(); + ModelHelper.setFilteredList(expectedModel, DELIRIUM); + assertCommandSuccess(command, expectedModel); + assertSelectedCardUnchanged(); + + /* Case: mixed case command word -> rejected */ + command = "FiNd Delirium"; + assertCommandFailure(command, MESSAGE_UNKNOWN_COMMAND); + } +``` +###### /java/systemtests/EditCommandSystemTest.java +``` java + /* ----------------- Performing edit operation while an unfiltered list is being shown ---------------------- */ + + /* Case: edit all fields, command with leading spaces, trailing spaces and multiple spaces between each field + * -> edited + */ + Index index = INDEX_FIRST_BOOK; + String command = " " + EditCommand.COMMAND_WORD + " " + index.getOneBased() + " " + TITLE_DESC_YOU + " " + + ISBN_DESC_YOU + " " + AVAIL_DESC_YOU + " " + AUTHOR_DESC_YOU + " " + TAG_DESC_FICTION + " "; + Book editedBook = new BookBuilder().withTitle(VALID_TITLE_YOU).withIsbn(VALID_ISBN_YOU) + .withAvail(VALID_AVAIL_YOU).withAuthor(VALID_AUTHOR_YOU).withTags(VALID_TAG_FICTION).build(); + assertCommandSuccess(command, index, editedBook); + + /* Case: undo editing the last book in the list -> last book restored */ + command = UndoCommand.COMMAND_WORD; + String expectedResultMessage = UndoCommand.MESSAGE_SUCCESS; + assertCommandSuccess(command, model, expectedResultMessage); + + /* Case: redo editing the last book in the list -> last book edited again */ + command = RedoCommand.COMMAND_WORD; + expectedResultMessage = RedoCommand.MESSAGE_SUCCESS; + model.updateBook( + getModel().getFilteredBookList().get(INDEX_FIRST_BOOK.getZeroBased()), editedBook); + assertCommandSuccess(command, model, expectedResultMessage); + + /* Case: edit a book with new values same as existing values -> edited */ + command = EditCommand.COMMAND_WORD + " " + index.getOneBased() + TITLE_DESC_YOU + ISBN_DESC_YOU + + AVAIL_DESC_YOU + AUTHOR_DESC_YOU + TAG_DESC_FICTION; + assertCommandSuccess(command, index, YOU); + + + /* Case: edit some fields -> edited */ + index = INDEX_FIRST_BOOK; + command = EditCommand.COMMAND_WORD + " " + index.getOneBased() + TAG_DESC_DYSTOPIA; + Book bookToEdit = getModel().getFilteredBookList().get(index.getZeroBased()); + editedBook = new BookBuilder(bookToEdit).withTags(VALID_TAG_DYSTOPIA).build(); + assertCommandSuccess(command, index, editedBook); + + /* Case: clear tags -> cleared */ + index = INDEX_FIRST_BOOK; + command = EditCommand.COMMAND_WORD + " " + index.getOneBased() + " " + PREFIX_TAG.getPrefix(); + editedBook = new BookBuilder(bookToEdit).withTags().build(); + assertCommandSuccess(command, index, editedBook); + + /* ------------------ Performing edit operation while a filtered list is being shown ------------------------ */ + /* Case: filtered book list, edit index within bounds of catalogue and book list -> edited */ + showBooksWithTitle(KEYWORD_MATCHING_GIRL); + index = INDEX_FIRST_BOOK; + assertTrue(index.getZeroBased() < getModel().getFilteredBookList().size()); + command = EditCommand.COMMAND_WORD + " " + index.getOneBased() + " " + TITLE_DESC_YOU; + bookToEdit = getModel().getFilteredBookList().get(index.getZeroBased()); + editedBook = new BookBuilder(bookToEdit).withTitle(VALID_TITLE_YOU).build(); + assertCommandSuccess(command, index, editedBook); + + /* Case: filtered book list, edit index within bounds of catalogue but out of bounds of book list + * -> rejected + */ + showBooksWithTitle(KEYWORD_MATCHING_GIRL); + int invalidIndex = getModel().getCatalogue().getBookList().size(); + assertCommandFailure(EditCommand.COMMAND_WORD + " " + invalidIndex + TITLE_DESC_YOU, + Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + + /* --------------------- Performing edit operation while a book card is selected -------------------------- */ + + /* Case: selects first card in the book list, edit a book -> edited, card selection remains unchanged but + * browser url changes + */ + showAllBooks(); + index = INDEX_FIRST_BOOK; + selectBook(index); + command = EditCommand.COMMAND_WORD + " " + index.getOneBased() + TITLE_DESC_XVI + ISBN_DESC_XVI + + AVAIL_DESC_XVI + AUTHOR_DESC_XVI + TAG_DESC_DYSTOPIA; + // this can be misleading: card selection actually remains unchanged but the + // browser's url is updated to reflect the new book's name + assertCommandSuccess(command, index, XVI, index); + /* --------------------------------- Performing invalid edit operation -------------------------------------- */ + + /* Case: invalid index (0) -> rejected */ + assertCommandFailure(EditCommand.COMMAND_WORD + " 0" + TITLE_DESC_YOU, + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + + /* Case: invalid index (-1) -> rejected */ + assertCommandFailure(EditCommand.COMMAND_WORD + " -1" + TITLE_DESC_YOU, + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + + /* Case: invalid index (size + 1) -> rejected */ + invalidIndex = getModel().getFilteredBookList().size() + 1; + assertCommandFailure(EditCommand.COMMAND_WORD + " " + invalidIndex + TITLE_DESC_YOU, + Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + + /* Case: missing index -> rejected */ + assertCommandFailure(EditCommand.COMMAND_WORD + TITLE_DESC_YOU, + String.format(Messages.MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); + + /* Case: missing all fields -> rejected */ + assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST_BOOK.getOneBased(), + EditCommand.MESSAGE_NOT_EDITED); + + /* Case: invalid name -> rejected */ + assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST_BOOK.getOneBased() + INVALID_TITLE_DESC, + Title.MESSAGE_TITLE_CONSTRAINTS); + + /* Case: invalid isbn -> rejected */ + assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST_BOOK.getOneBased() + INVALID_ISBN_DESC, + Isbn.MESSAGE_ISBN_CONSTRAINTS); + + /* Case: invalid avail -> rejected */ + assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST_BOOK.getOneBased() + INVALID_AVAIL_DESC, + Avail.MESSAGE_AVAIL_CONSTRAINTS); + + /* Case: invalid address -> rejected */ + assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST_BOOK.getOneBased() + INVALID_AUTHOR_DESC, + Author.MESSAGE_AUTHOR_CONSTRAINTS); + + /* Case: invalid tag -> rejected */ + assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST_BOOK.getOneBased() + INVALID_TAG_DESC, + Tag.MESSAGE_TAG_CONSTRAINTS); + + /* Case: edit a book with new values same as another book's values -> rejected */ + executeCommand(BookUtil.getAddCommand(YOU)); + assertTrue(getModel().getCatalogue().getBookList().contains(YOU)); + index = INDEX_FIRST_BOOK; + assertFalse(getModel().getFilteredBookList().get(index.getZeroBased()).equals(YOU)); + command = EditCommand.COMMAND_WORD + " " + index.getOneBased() + TITLE_DESC_YOU + ISBN_DESC_YOU + + AVAIL_DESC_YOU + AUTHOR_DESC_YOU + TAG_DESC_DYSTOPIA + TAG_DESC_FICTION; + assertCommandFailure(command, EditCommand.MESSAGE_DUPLICATE_BOOK); + + /* Case: edit a book with new values same as another book's values but with different tags -> rejected */ + command = EditCommand.COMMAND_WORD + " " + index.getOneBased() + TITLE_DESC_YOU + ISBN_DESC_YOU + + AVAIL_DESC_YOU + AUTHOR_DESC_YOU + TAG_DESC_FICTION; + assertCommandFailure(command, EditCommand.MESSAGE_DUPLICATE_BOOK); +``` +###### /java/seedu/address/model/book/AvailTest.java +``` java + @Test + public void isValidAvail() { + // null avail + Assert.assertThrows(NullPointerException.class, () -> Avail.isValidAvail(null)); + + // blank avail + assertFalse(Avail.isValidAvail("")); // empty string + assertFalse(Avail.isValidAvail(" ")); // spaces only + + // valid avail + assertTrue(Avail.isValidAvail("Reserved")); // Reserved + assertTrue(Avail.isValidAvail("Borrowed")); // Borrowed + assertTrue(Avail.isValidAvail("Available")); // Available + } +} +``` +###### /java/seedu/address/model/book/AuthorTest.java +``` java + @Test + public void isValidAuthor() { + // null name + Assert.assertThrows(NullPointerException.class, () -> Author.isValidAuthor(null)); + + // invalid name + assertFalse(Author.isValidAuthor("")); // empty string + assertFalse(Author.isValidAuthor(" ")); // spaces only + assertFalse(Author.isValidAuthor("^")); // only non-alphanumeric characters + assertFalse(Author.isValidAuthor("peter*")); // contains non-alphanumeric characters + + // valid name + assertTrue(Author.isValidAuthor("peter jack")); // alphabets only + assertTrue(Author.isValidAuthor("12345")); // numbers only + assertTrue(Author.isValidAuthor("peter the 2nd")); // alphanumeric characters + assertTrue(Author.isValidAuthor("Capital Tan")); // with capital letters + assertTrue(Author.isValidAuthor("David Roger Jackson Ray Jr 2nd")); // long names + } +} +``` +###### /java/seedu/address/model/book/TitleContainsKeywordsPredicateTest.java +``` java + @Test + public void test_nameContainsKeywords_returnsTrue() { + // One keyword + TitleContainsKeywordsPredicate predicate; + predicate = new TitleContainsKeywordsPredicate(Collections.singletonList("Animal")); + assertTrue(predicate.test(new BookBuilder().withTitle("Animal Breaking").build())); + + // Multiple keywords + predicate = new TitleContainsKeywordsPredicate(Arrays.asList("Animal", "Breaking")); + assertTrue(predicate.test(new BookBuilder().withTitle("Animal Breaking").build())); + + // Only one matching keyword + predicate = new TitleContainsKeywordsPredicate(Arrays.asList("Breaking", "Carol")); + assertTrue(predicate.test(new BookBuilder().withTitle("Animal Carol").build())); + + // Mixed-case keywords + predicate = new TitleContainsKeywordsPredicate(Arrays.asList("aNimal", "bREAKING")); + assertTrue(predicate.test(new BookBuilder().withTitle("Animal Breaking").build())); + } + + @Test + public void test_nameDoesNotContainKeywords_returnsFalse() { + // Zero keywords + TitleContainsKeywordsPredicate predicate = new TitleContainsKeywordsPredicate(Collections.emptyList()); + assertFalse(predicate.test(new BookBuilder().withTitle("Animal").build())); + + // Non-matching keyword + predicate = new TitleContainsKeywordsPredicate(Arrays.asList("Carol")); + assertFalse(predicate.test(new BookBuilder().withTitle("Animal Breaking").build())); + + // Keywords match isbn, avail and address, but does not match name + predicate = new TitleContainsKeywordsPredicate(Arrays.asList("9780736692427", "Borrowed", "Main", "Street")); + assertFalse(predicate.test(new BookBuilder().withTitle("Animal").withIsbn("9780736692427") + .withAvail("Borrowed").withAuthor("Main Street").build())); + } +} +``` +###### /java/seedu/address/model/book/IsbnTest.java +``` java + @Test + public void isValidIsbn() { + // null isbn + Assert.assertThrows(NullPointerException.class, () -> Isbn.isValidIsbn(null)); + + // invalid isbn numbers + assertFalse(Isbn.isValidIsbn("")); // empty string + assertFalse(Isbn.isValidIsbn(" ")); // spaces only + assertFalse(Isbn.isValidIsbn("91")); // less than 13 numbers + assertFalse(Isbn.isValidIsbn("phone")); // non-numeric + assertFalse(Isbn.isValidIsbn("978073669242a")); // alphabets within digits + assertFalse(Isbn.isValidIsbn("9780736 692427")); // spaces within digits + assertFalse(Isbn.isValidIsbn("97807366924271")); // more than 13 numbers + + // valid isbn numbers + assertTrue(Isbn.isValidIsbn("9780736692427")); // 13 isbn numbers + } +} +``` +###### /java/seedu/address/model/book/TitleTest.java +``` java + @Test + public void isValidTitle() { + // null name + Assert.assertThrows(NullPointerException.class, () -> Title.isValidTitle(null)); + + // invalid name + assertFalse(Title.isValidTitle("")); // empty string + assertFalse(Title.isValidTitle(" ")); // spaces only + assertFalse(Title.isValidTitle("^")); // only non-alphanumeric characters + assertFalse(Title.isValidTitle("peter*")); // contains non-alphanumeric characters + + // valid name + assertTrue(Title.isValidTitle("peter jack")); // alphabets only + assertTrue(Title.isValidTitle("12345")); // numbers only + assertTrue(Title.isValidTitle("peter the 2nd")); // alphanumeric characters + assertTrue(Title.isValidTitle("Capital Tan")); // with capital letters + assertTrue(Title.isValidTitle("David Roger Jackson Ray Jr 2nd")); // long names + } +} +``` +###### /java/seedu/address/testutil/TypicalBooks.java +``` java + public static final Book ANIMAL = new BookBuilder().withTitle("Animal Farm") + .withAuthor("George Orwell") + .withAvail("Available") + .withIsbn("9780736692427") + .withTags("political", "satire").build(); + public static final Book BREAKING = new BookBuilder().withTitle("Breaking Dawn") + .withAuthor("Stephenie Meyer") + .withAvail("Available") + .withIsbn("9780316067928") + .withTags("fiction").build(); + public static final Book CALIFORNIA = new BookBuilder().withTitle("California Girl") + .withAuthor("Jefferson Parker") + .withIsbn("9780060562373") + .withAvail("Borrowed") + .withTags("unlabelled").build(); + public static final Book DELIRIUM = new BookBuilder().withTitle("Delirium") + .withAuthor("Lauren Oliver") + .withIsbn("9780061726835") + .withAvail("Borrowed").build(); + public static final Book EMMA = new BookBuilder().withTitle("Emma") + .withAuthor("Jane Austen") + .withIsbn("9780141439587") + .withAvail("Borrowed").build(); + public static final Book FATEFUL = new BookBuilder().withTitle("Fateful") + .withAuthor("Claudia Gray") + .withIsbn("9780062006202") + .withAvail("Available").build(); + public static final Book GONE = new BookBuilder().withTitle("Gone Girl") + .withAuthor("Gillian Flynn") + .withIsbn("9780753827666") + .withAvail("Available").build(); + + // Manually added + public static final Book HOLES = new BookBuilder().withTitle("Holes") + .withAuthor("Louis Sachar") + .withIsbn("9780439244190") + .withAvail("Available").build(); + public static final Book INVISIBLE = new BookBuilder().withTitle("Invisible Man") + .withAuthor("Ralph Ellison") + .withIsbn("9780140023350") + .withAvail("Available").build(); + + // Manually added - Book's details found in {@code CommandTestUtil} + public static final Book XVI = new BookBuilder().withTitle(VALID_TITLE_XVI) + .withAuthor(VALID_AUTHOR_XVI) + .withIsbn(VALID_ISBN_XVI) + .withAvail(VALID_AVAIL_XVI) + .withTags(VALID_TAG_DYSTOPIA).build(); + public static final Book YOU = new BookBuilder().withTitle(VALID_TITLE_YOU) + .withAuthor(VALID_AUTHOR_YOU) + .withIsbn(VALID_ISBN_YOU) + .withAvail(VALID_AVAIL_YOU) + .withTags(VALID_TAG_FICTION) + .build(); + + public static final String KEYWORD_MATCHING_GIRL = "Girl"; // A keyword that matches GIRL + public static final String KEYWORD_MATCHING_BREAKING = "Breaking"; // A keyword that matches BREAKING + +``` diff --git a/collated/test/victortardieu.md b/collated/test/victortardieu.md new file mode 100644 index 000000000000..4bb4e6e4298b --- /dev/null +++ b/collated/test/victortardieu.md @@ -0,0 +1,412 @@ +# victortardieu +###### /java/seedu/address/logic/commands/ClearAccountCommandTest.java +``` java + +public class ClearAccountCommandTest { + + @Test + public void execute_empty_accountList() throws DuplicateAccountException { + Model model = new ModelManager(); + assertCommandSuccess(prepareCommand(model), model, ClearAccountCommand.MESSAGE_SUCCESS, model); + } + + @Test + public void execute_nonEmptyAccountList_success() throws DuplicateAccountException { + Model model = new ModelManager(); + model = getTypicalAccountList(); + assertCommandSuccess(prepareCommand(model), model, ClearAccountCommand.MESSAGE_SUCCESS, + model); + } + + private ClearAccountCommand prepareCommand(Model model) throws DuplicateAccountException { + ClearAccountCommand command = new ClearAccountCommand(); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } +} +``` +###### /java/seedu/address/logic/commands/DeleteAccountCommandTest.java +``` java +package seedu.address.logic.commands; + +import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure; +import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess; +import static seedu.address.testutil.TypicalAccounts.HARRY; +import static seedu.address.testutil.TypicalAccounts.getTypicalAccountList; +import static seedu.address.testutil.TypicalBooks.getTypicalCatalogue; + +import org.junit.Test; + +import seedu.address.logic.CommandHistory; +import seedu.address.logic.UndoRedoStack; +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.UserPrefs; + +public class DeleteAccountCommandTest { + + private Model model = new ModelManager(getTypicalCatalogue(), new UserPrefs()); + + @Test + public void execute_foundUsername_success() throws Exception { + Model actualModel = new ModelManager(); + actualModel = getTypicalAccountList(); + Model expectedModel = new ModelManager(); + expectedModel = getTypicalAccountList(); + expectedModel.deleteAccount(HARRY); + assertCommandSuccess(prepareCommand("harry123", actualModel), actualModel, + String.format(DeleteAccountCommand.MESSAGE_DELETE_ACCOUNT_SUCCESS, HARRY), expectedModel); + + } + + @Test + public void execute_usernameNotFound_failure() throws Exception { + Model model = new ModelManager(); + model = getTypicalAccountList(); + assertCommandFailure(prepareCommand("harry1234", model), model, + "Account does not exist"); + } + + + /** + * Returns a {@code DeleteAccountCommand} with the parameter {@code username}. + */ + private DeleteAccountCommand prepareCommand(String username, Model model) { + DeleteAccountCommand deleteAccountCommand = new DeleteAccountCommand(username); + deleteAccountCommand.setData(model, new CommandHistory(), new UndoRedoStack()); + return deleteAccountCommand; + } + +} +``` +###### /java/seedu/address/model/account/UsernameTest.java +``` java +package seedu.address.model.account; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +public class UsernameTest { + + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Username(null)); + } + + @Test + public void isValidUsername() { + // null pointer + Assert.assertThrows(NullPointerException.class, () -> Username.isValidUsername(null)); + + //invalid + assertFalse(Username.isValidUsername("")); // empty string + assertFalse(Username.isValidUsername("123")); // too short + assertFalse(Username.isValidUsername("abc")); // too short + assertFalse(Username.isValidUsername("!!!")); // too short and non-word characters + assertFalse(Username.isValidUsername("!!!!!!")); // non-word characters + assertFalse(Username.isValidUsername("abcasj!")); // too short and non-word characters + assertFalse(Username.isValidUsername("")); + + //valid + assertTrue(Username.isValidUsername("abcde")); + assertTrue(Username.isValidUsername("banana")); + assertTrue(Username.isValidUsername("addressbook")); + assertTrue(Username.isValidUsername("abcde123")); + assertTrue(Username.isValidUsername("FHAIgasjd123987514")); + assertTrue(Username.isValidUsername("123123123123")); + + } + + @Test + public void getUsername() { + String usernameString = "username"; + Username p = new Username(usernameString); + assertEquals(usernameString, p.getUsername()); + } + + @Test + public void equals() { + Username p1 = new Username("username1"); + Username p1copy = new Username("username1"); + Username p2 = new Username("username2"); + + //equal with itself + assertTrue(p1.equals(p1)); + + //equal with an other object with same state + assertTrue(p1.equals(p1copy)); + + //not equal with null + assertFalse(p1.equals(null)); + + //not equal with other type + assertFalse(p1.equals(1)); + + //not equal with same type with different state + assertFalse(p1.equals(p2)); + } +} +``` +###### /java/seedu/address/model/account/PasswordTest.java +``` java +package seedu.address.model.account; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import seedu.address.testutil.Assert; + +public class PasswordTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Password(null)); + } + + @Test + public void isValidPassword() { + // null pointer + Assert.assertThrows(NullPointerException.class, () -> Password.isValidPassword(null)); + + //invalid + assertFalse(Password.isValidPassword("")); // empty string + assertFalse(Password.isValidPassword("123")); // too short + assertFalse(Password.isValidPassword("abc")); // too short + assertFalse(Password.isValidPassword("!!!")); // too short and non-word characters + assertFalse(Password.isValidPassword("!!!!!!")); // non-word characters + assertFalse(Password.isValidPassword("abcasj!")); // too short and non-word characters + assertFalse(Password.isValidPassword("")); + + //valid + assertTrue(Password.isValidPassword("abcde")); + assertTrue(Password.isValidPassword("banana")); + assertTrue(Password.isValidPassword("addressbook")); + assertTrue(Password.isValidPassword("abcde123")); + assertTrue(Password.isValidPassword("FHAIgasjd123987514")); + assertTrue(Password.isValidPassword("123123123123")); + + + } + + @Test + public void getPassword() { + String passwordString = "password"; + Password p = new Password(passwordString); + assertEquals(passwordString, p.getPassword()); + } + + @Test + public void equals() { + Password p1 = new Password("password1"); + Password p1copy = new Password("password1"); + Password p2 = new Password("password2"); + + //equal with itself + assertTrue(p1.equals(p1)); + + //equal with an other object with same state + assertTrue(p1.equals(p1copy)); + + //not equal with null + assertFalse(p1.equals(null)); + + //not equal with other type + assertFalse(p1.equals(1)); + + //not equal with same type with different state + assertFalse(p1.equals(p2)); + } +} +``` +###### /java/seedu/address/testutil/TypicalAccounts.java +``` java +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import seedu.address.model.Model; +import seedu.address.model.ModelManager; +import seedu.address.model.account.Account; +import seedu.address.model.account.UniqueAccountList; +import seedu.address.model.account.exceptions.DuplicateAccountException; + +/** + * A utility class containing a list of {@code Account} objects to be used in tests. + */ +public class TypicalAccounts { + + public static final Account HARRY = new AccountBuilder() + .withName("Harry Potter") + .withCredential("harry123", "harry123") + .withMatricNumber("A1234567H") + .withPrivilegeLevel("1").build(); + + public static final Account JERRY = new AccountBuilder() + .withName("Jerry Morgan ") + .withCredential("jerry123", "jack123") + .withMatricNumber("A1234567J") + .withPrivilegeLevel("2").build(); + + public static final Account TOM = new AccountBuilder() + .withName("Tom Hanks") + .withCredential("tom123", "tom123") + .withMatricNumber("A1234567T") + .withPrivilegeLevel("1").build(); + + public static final Account EMMA = new AccountBuilder() + .withName("Emma Thorne") + .withCredential("emma123", "emma123") + .withMatricNumber("A1234567E") + .withPrivilegeLevel("2").build(); + + public static final Account LARY = new AccountBuilder() + .withName("Lary Knot") + .withCredential("lary123", "lary123") + .withMatricNumber("A1234567L") + .withPrivilegeLevel("1").build(); + + public static final Account MARIE = new AccountBuilder() + .withName("Marie Johnson") + .withCredential("marie", "marie123") + .withMatricNumber("A1234567M") + .withPrivilegeLevel("1").build(); + + public static final Account NICOLE = new AccountBuilder() + .withName("Nicole Soley") + .withCredential("nicole", "nicole123") + .withMatricNumber("A1234567N") + .withPrivilegeLevel("1").build(); + + private TypicalAccounts() { + } // prevents instantiation + + public static Model getTypicalAccountList() { + Model model = new ModelManager(); + for (Account account : getTypicalAccounts()) { + try { + model.addAccount(account); + } catch (DuplicateAccountException e) { + throw new AssertionError("not possible"); + } + } + return model; + } + + public static UniqueAccountList getTypicalAccountListUniqueAccountList() { + UniqueAccountList uniqueAccountList = new UniqueAccountList(); + for (Account account : getTypicalAccounts()) { + try { + uniqueAccountList.add(account); + } catch (DuplicateAccountException e) { + throw new AssertionError("not possible"); + } + } + return uniqueAccountList; + } + + + public static List<Account> getTypicalAccounts() { + return new ArrayList<>(Arrays.asList(HARRY, JERRY, TOM, EMMA, LARY, MARIE, NICOLE)); + } +} +``` +###### /java/seedu/address/testutil/AccountBuilder.java +``` java +package seedu.address.testutil; + +import seedu.address.model.account.Account; +import seedu.address.model.account.Credential; +import seedu.address.model.account.MatricNumber; +import seedu.address.model.account.Name; +import seedu.address.model.account.PrivilegeLevel; + +/** + * A utility class to help with building Account objects. + */ +public class AccountBuilder { + + public static final String DEFAULT_NAME = "Victor Tardieu"; + public static final String DEFAULT_MATRIC_NUMBER = "A1234567N"; + public static final String DEFAULT_PRIVILEGE_LEVEL = "2"; + public static final String DEFAULT_USERNAME = "victor"; + public static final String DEFAULT_PASSWORD = "victor123"; + + private Name name; + private Credential credential; + private MatricNumber matricNumber; + private PrivilegeLevel privilegeLevel; + + /** + * Constructor for account with default values + */ + public AccountBuilder() { + name = new Name(DEFAULT_NAME); + credential = new Credential(DEFAULT_USERNAME, DEFAULT_PASSWORD); + matricNumber = new MatricNumber(DEFAULT_MATRIC_NUMBER); + privilegeLevel = new PrivilegeLevel(Integer.parseInt(DEFAULT_PRIVILEGE_LEVEL)); + } + + /** + * Sets the {@code Name} of the {@code Account} that we are building. + * + * @param name + * @return + */ + public AccountBuilder withName(String name) { + this.name = new Name(name); + return this; + } + + /** + * Sets the {@code Credential} of the {@code Account} that we are building. + * + * @param username + * @param password + * @return + */ + public AccountBuilder withCredential(String username, String password) { + this.credential = new Credential(username, password); + return this; + } + + /** + * Sets the {@code MatricNumber} of the {@code Account} that we are building. + * + * @param matricNumber + * @return + */ + public AccountBuilder withMatricNumber(String matricNumber) { + this.matricNumber = new MatricNumber(matricNumber); + return this; + } + + /** + * Sets the {@code PrivilegeLevel} of the {@code Account} that we are building. + * + * @param privilegeLevel + * @return + */ + public AccountBuilder withPrivilegeLevel(String privilegeLevel) { + this.privilegeLevel = new PrivilegeLevel(Integer.parseInt(privilegeLevel)); + return this; + } + + /** + * Create an account + * + * @return + */ + public Account build() { + return new Account(name, credential, matricNumber, privilegeLevel); + } +} +``` diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index 0f0a8e7ab51e..7ba8ee3f7a3d 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -3,53 +3,53 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + -{empty} + +Catalogue was developed by the https://github.com/orgs/CS2103JAN2018-T16-B1/people[CS2103JAN2018-T16-B1] team. + + + We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<<johndoe#, portfolio>>] +=== Chan Tiong Ley +image::chantiongley.jpg[width="150", align="left"] +{empty} [https://github.com/chantiongley[github]] [<<chantiongley#, portfolio>>] -Role: Project Advisor +Role: Developer +Responsibilities: Logic ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<<johndoe#, portfolio>>] +=== Le Khang Tai +image::LeKhangTai.jpg[width="150", align="left"] +{empty}[https://github.com/LeKhangTai[github]] [<<lekhangtai#, portfolio>>] Role: Team Lead + -Responsibilities: UI +Responsibilities: Logic ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<<johndoe#, portfolio>>] +=== Qiu Hao Ze +image::QiuHaohao.jpg[width="150", align="left"] +{empty}[https://github.com/QiuHaohao[github]] [<<qiuhaoze#, portfolio>>] Role: Developer + -Responsibilities: Data +Responsibilities: Storage + Model ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<<johndoe#, portfolio>>] +=== Victor Tardieu +image::victortardieu.jpg[width="150", align="left"] +{empty}[https://github.com/victortardieu[github]] [<<victortardieu#, portfolio>>] Role: Developer + -Responsibilities: Dev Ops + Threading +Responsibilities: UI + Logic ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<<johndoe#, portfolio>>] +=== Wong Khia Yi +image::khiayi.png[width="150", align="left"] +{empty}[https://github.com/khiayi[github]] [<<wongkhiayi#, portfolio>>] Role: Developer + -Responsibilities: UI +Responsibilities: Model ''' diff --git a/docs/ContactUs.adoc b/docs/ContactUs.adoc index eafdc9574a50..9f9410c14eae 100644 --- a/docs/ContactUs.adoc +++ b/docs/ContactUs.adoc @@ -1,6 +1,6 @@ = Contact Us :stylesDir: stylesheets -* *Bug reports, Suggestions* : Post in our https://github.com/se-edu/addressbook-level4/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. +* *Bug reports, Suggestions* : Post in our https://github.com/CS2103JAN2018-T16-B1/main/issues[issue tracker] if you noticed bugs or have suggestions on how to improve. * *Contributing* : We welcome pull requests. Follow the process described https://github.com/oss-generic/process[here] -* *Email us* : You can also reach us at `damith [at] comp.nus.edu.sg` +* *Email us* : You can also reach us at `e0031399 [at] u.nus.edu` diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 1733af113b29..8f5266496942 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += Catalogue - Developer Guide :toc: :toc-title: :toc-placement: preamble @@ -10,10 +10,18 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master +:repoURL: https://github.com/CS2103JAN2018-T16-B1/main -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `Team CS2103JAN2018-T16-B1`      Since: `Mar 2018`      Licence: `MIT` +// tag::devguideintroduction[] +== Introduction + +Catalogue is a desktop application that allows book and account management for the Library. + +This developer guide has been written to help any possible contributor to improve the application in the future. + +It is also very useful to understand how the different features work and how to run tests on them. + +Finally, it will explain you how the idea for Catalogue came. +// end::devguideintroduction[] == Setting up === Prerequisites @@ -68,7 +76,7 @@ Optionally, you can follow the <<UsingCheckstyle#, UsingCheckstyle.adoc>> docume ==== Updating documentation to match your fork -After forking the repo, links in the documentation will still point to the `se-edu/addressbook-level4` repo. If you plan to develop this as a separate product (i.e. instead of contributing to the `se-edu/addressbook-level4`) , you should replace the URL in the variable `repoURL` in `DeveloperGuide.adoc` and `UserGuide.adoc` with the URL of your fork. +After forking the repo, links in the documentation will still point to the `CS2103JAN2018-T16-B1/main` repo. If you plan to develop this as a separate product (i.e. instead of contributing to the `CS2103JAN2018-T16-B1/main`) , you should replace the URL in the variable `repoURL` in `DeveloperGuide.adoc` and `UserGuide.adoc` with the URL of your fork. ==== Setting up CI @@ -77,7 +85,7 @@ Set up Travis to perform Continuous Integration (CI) for your fork. See <<UsingT After setting up Travis, you can optionally set up coverage reporting for your team fork (see <<UsingCoveralls#, UsingCoveralls.adoc>>). [NOTE] -Coverage reporting could be useful for a team repository that hosts the final version but it is not that useful for your personal fork. +Coverage reporting could be useful for a team repository that hosts the final version but it is not that useful for your bookal fork. Optionally, you can set up AppVeyor as a second CI (see <<UsingAppVeyor#, UsingAppVeyor.adoc>>). @@ -94,6 +102,7 @@ When you are ready to start coding, == Design [[Design-Architecture]] +// tag::Design[] === Architecture .Architecture Diagram @@ -102,9 +111,9 @@ image::Architecture.png[width="600"] The *_Architecture Diagram_* given above explains the high-level design of the App. Given below is a quick overview of each component. [TIP] -The `.pptx` files used to create diagrams in this document can be found in the link:{repoURL}/docs/diagrams/[diagrams] folder. To update a diagram, modify the diagram in the pptx file, select the objects of the diagram, and choose `Save as picture`. +The `.pptx` files used to create diagrams in this document can be found in the link:https://github.com/CS2103JAN2018-T16-B1/main/tree/master/docs/diagrams[diagrams] folder. To update a diagram, modify the diagram in the pptx file, select the objects of the diagram, and choose `Save as picture`. -`Main` has only one class called link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp`]. It is responsible for, +`Main` has only one class called link:{repoURL}/src/main/java/seedu/address/tree/master/MainApp.java[`MainApp`]. It is responsible for, * At app launch: Initializes the components in the correct sequence, and connects them up with each other. * At shut down: Shuts down the components and invokes cleanup method where necessary. @@ -137,20 +146,22 @@ image::LogicClassDiagram.png[width="800"] The _Sequence Diagram_ below shows how the components interact for the scenario where the user issues the command `delete 1`. .Component interactions for `delete 1` command (part 1) -image::SDforDeletePerson.png[width="800"] +image::SDforDeleteBook.png[width="800"] [NOTE] -Note how the `Model` simply raises a `AddressBookChangedEvent` when the Address Book data are changed, instead of asking the `Storage` to save the updates to the hard disk. +Note how the `Model` simply raises a `CatalogueChangedEvent` when the Catalogue data are changed, instead of asking the `Storage` to save the updates to the hard disk. The diagram below shows how the `EventsCenter` reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time. .Component interactions for `delete 1` command (part 2) -image::SDforDeletePersonEventHandling.png[width="800"] +image::SDforDeleteBookEventHandling.png[width="800"] [NOTE] Note how the event is propagated through the `EventsCenter` to the `Storage` and `UI` without `Model` having to be coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct coupling between components. The sections below give more details of each component. +// end::Design[] + [[Design-Ui]] === UI component @@ -158,11 +169,11 @@ The sections below give more details of each component. .Structure of the UI Component image::UiClassDiagram.png[width="800"] -*API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] +*API* : link:{repoURL}/tree/master/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] -The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. +The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `BookListPanel`, `StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. -The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] +The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the link:{repoURL}/tree/master/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/tree/master/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] The `UI` component, @@ -171,6 +182,7 @@ The `UI` component, * Responds to events raised from various parts of the App and updates the UI accordingly. [[Design-Logic]] +// tag::logicComponent[] === Logic component [[fig-LogicClassDiagram]] @@ -181,50 +193,59 @@ image::LogicClassDiagram.png[width="800"] image::LogicCommandClassDiagram.png[width="800"] *API* : -link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] +link:{repoURL}/tree/master/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] -. `Logic` uses the `AddressBookParser` class to parse the user command. +. `Logic` uses the `CatalogueParser` class to parse the user command. . This results in a `Command` object which is executed by the `LogicManager`. -. The command execution can affect the `Model` (e.g. adding a person) and/or raise events. +. The command execution can affect the `Model` (e.g. adding a book) and/or raise events. . The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. .Interactions Inside the Logic Component for the `delete 1` Command -image::DeletePersonSdForLogic.png[width="800"] +image::DeleteBookSdforLogic.png[width="800"] +// end::logicComponent[] +//tag::model [[Design-Model]] === Model component .Structure of the Model Component image::ModelClassDiagram.png[width="800"] -*API* : link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model.java`] +*API* : link:{repoURL}/blob/master/src/main/java/seedu/address/model/Model.java[`Model.java`] The `Model`, * stores a `UserPref` object that represents the user's preferences. -* stores the Address Book data. -* exposes an unmodifiable `ObservableList<Person>` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* stores the Catalogue data. +* stores the AccountList data +* exposes an unmodifiable `ObservableList<Book>` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* maintains the privilege level +* handles authentication * does not depend on any of the other three components. +//end::model [[Design-Storage]] +// tag::storagepic[] === Storage component .Structure of the Storage Component image::StorageClassDiagram.png[width="800"] +// end::storagepic[] -*API* : link:{repoURL}/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] +*API* : link:{repoURL}/tree/master/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] The `Storage` component, * can save `UserPref` objects in json format and read it back. -* can save the Address Book data in xml format and read it back. +* can save the Catalogue data in xml format and read it back. +* can save the AccountList data in binary format and read it back [[Design-Commons]] === Common classes -Classes used by multiple components are in the `seedu.addressbook.commons` package. +Classes used by multiple components are in the `seedu.address.book.commons` package. == Implementation @@ -233,14 +254,15 @@ This section describes some noteworthy details on how certain features are imple // tag::undoredo[] === Undo/Redo feature ==== Current Implementation +===== Undo/Redo -The undo/redo mechanism is facilitated by an `UndoRedoStack`, which resides inside `LogicManager`. It supports undoing and redoing of commands that modifies the state of the address book (e.g. `add`, `edit`). Such commands will inherit from `UndoableCommand`. +The undo/redo mechanism is facilitated by an `UndoRedoStack`, which resides inside `LogicManager`. It supports undoing and redoing of commands that modifies the state of the catalogue (e.g. `add`, `edit`). Such commands will inherit from `UndoableCommand`. `UndoRedoStack` only deals with `UndoableCommands`. Commands that cannot be undone will inherit from `Command` instead. The following diagram shows the inheritance diagram for commands: image::LogicCommandClassDiagram.png[width="800"] -As you can see from the diagram, `UndoableCommand` adds an extra layer between the abstract `Command` class and concrete commands that can be undone, such as the `DeleteCommand`. Note that extra tasks need to be done when executing a command in an _undoable_ way, such as saving the state of the address book before execution. `UndoableCommand` contains the high-level algorithm for those extra tasks while the child classes implements the details of how to execute the specific command. Note that this technique of putting the high-level algorithm in the parent class and lower-level steps of the algorithm in child classes is also known as the https://www.tutorialspoint.com/design_pattern/template_pattern.htm[template pattern]. +As you can see from the diagram, `UndoableCommand` adds an extra layer between the abstract `Command` class and concrete commands that can be undone, such as the `DeleteCommand`. Note that extra tasks need to be done when executing a command in an _undoable_ way, such as saving the state of the catalogue before execution. `UndoableCommand` contains the high-level algorithm for those extra tasks while the child classes implements the details of how to execute the specific command. Note that this technique of putting the high-level algorithm in the parent class and lower-level steps of the algorithm in child classes is also known as the https://www.tutorialspoint.com/design_pattern/template_pattern.htm[template pattern]. Commands that are not undoable are implemented this way: [source,java] @@ -275,20 +297,20 @@ public class DeleteCommand extends UndoableCommand { Suppose that the user has just launched the application. The `UndoRedoStack` will be empty at the beginning. -The user executes a new `UndoableCommand`, `delete 5`, to delete the 5th person in the address book. The current state of the address book is saved before the `delete 5` command executes. The `delete 5` command will then be pushed onto the `undoStack` (the current state is saved together with the command). +The user executes a new `UndoableCommand`, `delete 5`, to delete the 5th book in the catalogue. The current state of the catalogue is saved before the `delete 5` command executes. The `delete 5` command will then be pushed onto the `undoStack` (the current state is saved together with the command). image::UndoRedoStartingStackDiagram.png[width="800"] -As the user continues to use the program, more commands are added into the `undoStack`. For example, the user may execute `add n/David ...` to add a new person. +As the user continues to use the program, more commands are added into the `undoStack`. For example, the user may execute `add n/David ...` to add a new book. image::UndoRedoNewCommand1StackDiagram.png[width="800"] [NOTE] If a command fails its execution, it will not be pushed to the `UndoRedoStack` at all. -The user now decides that adding the person was a mistake, and decides to undo that action using `undo`. +The user now decides that adding the book was a mistake, and decides to undo that action using `undo`. -We will pop the most recent command out of the `undoStack` and push it back to the `redoStack`. We will restore the address book to the state before the `add` command executed. +We will pop the most recent command out of the `undoStack` and push it back to the `redoStack`. We will restore the catalogue to the state before the `add` command executed. image::UndoRedoExecuteUndoStackDiagram.png[width="800"] @@ -299,7 +321,7 @@ The following sequence diagram shows how the undo operation works: image::UndoRedoSequenceDiagram.png[width="800"] -The redo does the exact opposite (pops from `redoStack`, push to `undoStack`, and restores the address book to the state after the command is executed). +The redo does the exact opposite (pops from `redoStack`, push to `undoStack`, and restores the catalogue to the state after the command is executed). [NOTE] If the `redoStack` is empty, then there are no other commands left to be redone, and an `Exception` will be thrown when popping the `redoStack`. @@ -316,6 +338,9 @@ The following activity diagram summarize what happens inside the `UndoRedoStack` image::UndoRedoActivityDiagram.png[width="650"] +===== Login/Logout +The authentication mechanism is managed by Model and Logic, where Model maintains a privilege level and Logic checks the required privilege is satisfied every time a command is run. + ==== Design Considerations ===== Aspect: Implementation of `UndoableCommand` @@ -329,22 +354,22 @@ image::UndoRedoActivityDiagram.png[width="650"] ===== Aspect: How undo & redo executes -* **Alternative 1 (current choice):** Saves the entire address book. +* **Alternative 1 (current choice):** Saves the entire catalogue. ** Pros: Easy to implement. ** Cons: May have performance issues in terms of memory usage. * **Alternative 2:** Individual command knows how to undo/redo by itself. -** Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). +** Pros: Will use less memory (e.g. for `delete`, just save the book being deleted). ** Cons: We must ensure that the implementation of each individual command are correct. ===== Aspect: Type of commands that can be undone/redone -* **Alternative 1 (current choice):** Only include commands that modifies the address book (`add`, `clear`, `edit`). +* **Alternative 1 (current choice):** Only include commands that modifies the catalogue (`add`, `clear`, `edit`). ** Pros: We only revert changes that are hard to change back (the view can easily be re-modified as no data are * lost). ** Cons: User might think that undo also applies when the list is modified (undoing filtering for example), * only to realize that it does not do that, after executing `undo`. * **Alternative 2:** Include all commands. ** Pros: Might be more intuitive for the user. -** Cons: User have no way of skipping such commands if he or she just want to reset the state of the address * book and not the view. +** Cons: User have no way of skipping such commands if he or she just want to reset the state of the catalogue book and not the view. **Additional Info:** See our discussion https://github.com/se-edu/addressbook-level4/issues/390#issuecomment-298936672[here]. @@ -358,6 +383,35 @@ image::UndoRedoActivityDiagram.png[width="650"] ** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two * different things. // end::undoredo[] +=== Account Functions +// tag::addaccount[] +==== Add Account +===== Current Implementation +1. The addAccount function helps to add new accounts with different privileges into the database. It includes the accounts with privilege level 1 for students and privilege level 2 for administrators or librarians. +2. Only accounts with privilege level 2 (administrators and librarians) can access the addAccount function. + +===== Future Implementation + +1. The addAccount function will be able to add accounts based on an excel sheet that is provided instead of having to manually add the accounts one by one. + +2. The addAccount function will be provided to the students such that they can create their own account as long as they have a registered matriculation number in the system without having a administrator privilege level. +3. An edit account function will be created to allow borrowers/users to edit their own passwords if necessary instead of having to go through the librarian to delete and add a new account with a different password. +// end::addaccount[] + +// tag::deleteaccount[] +==== Delete Account +===== Current Implementation +1. The deleteAccount function helps to delete accounts in the existing account database. +2. Only accounts with privilege level 2 (administrators and librarians) can access the deleteAccount function. + +===== Future Implementation +1. When the list account function is set up, the administrators will be able to use the addAccount function by clicking on the the shown list to delete the accounts in large scales. +2. The deleteAccount function will be able to delete the account by range of date created so that students that are registered into the school library can be deleted in bulk based on their matriculation and graduation date information. +// end::deleteaccount[] + + + + + // tag::dataencryption[] === [Proposed] Data Encryption @@ -486,371 +540,520 @@ When a pull request has changes to asciidoc files, you can use https://www.netli Here are the steps to create a new release. -. Update the version number in link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp.java`]. +. Update the version number in link:{repoURL}/blob/master/src/main/java/seedu/address/MainApp.java[`MainApp.java`]. . Generate a JAR file <<UsingGradle#creating-the-jar-file, using Gradle>>. . Tag the repo with the version number. e.g. `v0.1` . https://help.github.com/articles/creating-releases/[Create a new release using GitHub] and upload the JAR file you created. === Managing Dependencies -A project often depends on third-party libraries. For example, Address Book depends on the http://wiki.fasterxml.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + +A project often depends on third-party libraries. For example, Catalogue depends on the http://wiki.fasterxml.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + a. Include those libraries in the repo (this bloats the repo size) + b. Require developers to download those libraries manually (this creates extra work for developers) -[[GetStartedProgramming]] -[appendix] -== Suggested Programming Tasks to Get Started +=== Features Proposed -Suggested path for new programmers: +Unallocated: +. Renew Command +.. Allows students to renew borrowed books. -1. First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in <<GetStartedProgramming-EachComponent>>. +Chan Tiong Ley: +. bookLogs command +.. Shows the past history of the book. +. userLogs command +.. Shows the past activities of the user. -2. Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. <<GetStartedProgramming-RemarkCommand>> explains how to go about adding such a feature. +Lucas Le: +. Auto-complete UI +.. Helps users with completing command lines inputs. -[[GetStartedProgramming-EachComponent]] -=== Improving each component +Qiu Hao Ze: +. Login Command +.. Create levels of privileges for different target groups. i.e. Guests, Students, Librarians -Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work). +Victor Tardieu: +. Book Class with bookList +.. Creates book objects. +. Clear Account Command +.. List the books in the catalogue in order. -[discrete] -==== `Logic` component +Wong Khia Yi: +. Borrow Command +.. Allows students to borrow books. +. Reserve Command +.. Allows students to reserve books. -*Scenario:* You are in charge of `logic`. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases. +=== Future Features -[TIP] -Do take a look at <<Design-Logic>> before attempting to modify the `Logic` component. +* Borrow and reserve feature will implement an association class called loan and reserve respectively. This association class would include the details of the user borrowing the book and the due date. This will allow the librarian to access the details of the user borrowing the book and its due date. +* Function to see the number of books that a student has borrowed +* Implement a calendar so that the student can be reminded when the book is due. +* System to send a fine to the student account when a book is overdue -. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing `clear`, the user can also type `c` to remove all persons in the list. -+ -**** -* Hints -** Just like we store each individual command word constant `COMMAND_WORD` inside `*Command.java` (e.g. link:{repoURL}/src/main/java/seedu/address/logic/commands/FindCommand.java[`FindCommand#COMMAND_WORD`], link:{repoURL}/src/main/java/seedu/address/logic/commands/DeleteCommand.java[`DeleteCommand#COMMAND_WORD`]), you need a new constant for aliases as well (e.g. `FindCommand#COMMAND_ALIAS`). -** link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] is responsible for analyzing command words. -* Solution -** Modify the switch statement in link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser#parseCommand(String)`] such that both the proper command word and alias can be used to execute the same intended command. -** Add new tests for each of the aliases that you have added. -** Update the user guide to document the new aliases. -** See this https://github.com/se-edu/addressbook-level4/pull/785[PR] for the full solution. -**** +[[GetStartedProgramming]] +[appendix] +== Product Scope -[discrete] -==== `Model` component +*Target user profile*: -*Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the address book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. +* has a need to manage a significant number of books +* has a need to manage a significant number of student and librarian accounts +* NUS Librarians +* NUS Students +* prefer desktop apps over other types +* can type fast +* prefers typing over mouse input +* is reasonably comfortable using CLI apps -[TIP] -Do take a look at <<Design-Model>> before attempting to modify the `Model` component. - -. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the address book. -+ -**** -* Hints -** The link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] API need to be updated. -** Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags? -** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] allows you to update a person, and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] allows you to update the tags. -* Solution -** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. Loop through each person, and remove the `tag` from each person. -** Add a new API method `deleteTag(Tag)` in link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`]. Your link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`] should call `AddressBook#removeTag(Tag)`. -** Add new tests for each of the new public methods that you have added. -** See this https://github.com/se-edu/addressbook-level4/pull/790[PR] for the full solution. -*** The current codebase has a flaw in tags management. Tags no longer in use by anyone may still exist on the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. This may cause some tests to fail. See issue https://github.com/se-edu/addressbook-level4/issues/753[`#753`] for more information about this flaw. -*** The solution PR has a temporary fix for the flaw mentioned above in its first commit. -**** +*Value proposition*: Fast and efficient book and accounts management application. -[discrete] -==== `Ui` component +[appendix] +== User Stories +Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` -*Scenario:* You are in charge of `ui`. During a beta testing session, your team is observing how the users use your address book application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn't prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last person in the list. Your job is to implement improvements to the UI to solve all these problems. +[width="59%",cols="22%,<23%,<25%,<30%",options="header",] +|======================================================================= +|Priority |As a ... |I want to ... |So that I can... +|`* * *` |As a librarian |I want to add a new book title into the database |So that I can constantly update the database for borrowers +|`* * *` |As a librarian |I want to delete a book title from the database |So that I can constantly update the database for borrowers +|`* * *` |As a librarian |I want to find a book title in the database |So that I can see the status of the book +|`* * *` |As a librarian |I want to add accounts to the system |So that more students can read and use books +|`* * *` |As a librarian |I want to delete accounts from the system |So I don't keep unused accounts +|`* * *` |As a librarian |I want to edit a book of the library |So I can correct a mistake in its information +|`* *` |As a librarian |I want to confirmation in notification before deleting a book |So that I will not accidentally delete the wrong book title +|`* * `|As a librarian |I want to view the history of borrowed books from each borrower |So that I can keep track of books availability +|`* *` |As a librian |I want to list all the accounts |So I can visually see all the student information +|`* * *` |As a student |I want to borrow a book from the library |So I can increase my knowledge +|`* * *` |As a student |I want to return a book to the library |So I can discover new subjects +|`* * *` |As a student |I want to find a book title in the database by it's title |So I can facilitate my search process +|`* * *` |As a student |I want to check for the availability of the book |So that I can know it' loan status +|`* * *` |As a student |I want to loan a book |So that I can use the book for higher learning +|`* * *` |As a student |I want to reserve a book currently being loaned |So that I will be able to loan the book as soon as it is available +|`* *` |As a student |I want to renew existing loaned book |So that I have more time to finish the book +|`* *` |As a student |I want to find a book title in the database by it's tag |So that I can facilitate my search process +|`* *` |As a student |I want to receive notification when my current book is due soon |So that I will not miss the due date +|`*` |As a student |I want to know which book is mandatory for which course |So that I can study in the best conditions +|`*` |As a student |I want to be able to ask for a hint with my password |So that I don't forget it +|`*` |As a student |I want to be able to change the dark theme to my preference |So that I can fully personalize my account +|`* * *` |As a guest |I want to find a book title in the database |So that I can see the status of the book +|`* * *` |As a guest |I want to check for the availability of the book |So that I can see it's loan status +|======================================================================= -[TIP] -Do take a look at <<Design-Ui>> before attempting to modify the `UI` component. -. Use different colors for different tags inside person cards. For example, `friends` tags can be all in brown, and `colleagues` tags can be all in yellow. -+ -**Before** +[appendix] +== Use Cases + +(For all use cases below, the *System* is the `Catalogue` and the *Actor* is the `user`, unless specified otherwise) + +[discrete] +=== Use case: Delete book + +*MSS* + +1. User requests to list books +2. Catalogue shows a list of books +3. User requests to delete a specific book in the list +4. Catalogue deletes the book +5. Catalogue lists remaining books + -image::getting-started-ui-tag-before.png[width="300"] +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty. + -**After** +Use case ends. + +* 3a. The given index is invalid. + -image::getting-started-ui-tag-after.png[width="300"] +[none] +** 3a1. Catalogue shows an error message. + -**** -* Hints -** The tag labels are created inside link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[the `PersonCard` constructor] (`new Label(tag.tagName)`). https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Label.html[JavaFX's `Label` class] allows you to modify the style of each Label, such as changing its color. -** Use the .css attribute `-fx-background-color` to add a color. -** You may wish to modify link:{repoURL}/src/main/resources/view/DarkTheme.css[`DarkTheme.css`] to include some pre-defined colors using css, especially if you have experience with web-based css. -* Solution -** You can modify the existing test methods for `PersonCard` 's to include testing the tag's color as well. -** See this https://github.com/se-edu/addressbook-level4/pull/798[PR] for the full solution. -*** The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes. -**** +Use case resumes at step 2. -. Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] such that link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] can show a different style on error (currently it shows the same regardless of errors). -+ -**Before** +// tag::usecaseloginoutlistfind[] +[discrete] +=== Use case: Login/Logout + +*MSS* + +1. User logs in with credentials +2. Catalogue authenticates with the credentials entered +3. Catalogue shows successful login message +4. User issues commands with privilege +5. Catalogue checks the privilege of the user against the command issued +6. Catalogue executes the command + -image::getting-started-ui-result-before.png[width="200"] +Step 4-6 are repeated until the user finishes using the application + +7. User issues logout command +8. Catalogue change the current account to guest + -**After** +Use case ends. + +*Extensions* + +[none] +* 2a. The credentials entered does not match any record in the AccountList + -image::getting-started-ui-result-after.png[width="200"] +[none] +** 2a1. Catalogue shows an error message. + -**** -* Hints -** link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] is raised by link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] which also knows whether the result is a success or failure, and is caught by link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] which is where we want to change the style to. -** Refer to link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] for an example on how to display an error. -* Solution -** Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] 's constructor so that users of the event can indicate whether an error has occurred. -** Modify link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay#handleNewResultAvailableEvent(NewResultAvailableEvent)`] to react to this event appropriately. -** You can write two different kinds of tests to ensure that the functionality works: -*** The unit tests for `ResultDisplay` can be modified to include verification of the color. -*** The system tests link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest#assertCommandBoxShowsDefaultStyle() and AddressBookSystemTest#assertCommandBoxShowsErrorStyle()`] to include verification for `ResultDisplay` as well. -** See this https://github.com/se-edu/addressbook-level4/pull/799[PR] for the full solution. -*** Do read the commits one at a time if you feel overwhelmed. -**** +Use case resumes at step 1. -. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the address book. +* 4a. The privilege level of the account logged in is lower than the required level of the command + -**Before** -+ -image::getting-started-ui-status-before.png[width="500"] +[none] +** 4a1. Catalogue shows an error message. + -**After** +Use case resumes at step 4 or step 7. + +[discrete] +=== Use case: List Book + +*MSS* + +1. User requests to list all books +2. Catalogue shows a list of all books in the book list + -image::getting-started-ui-status-after.png[width="500"] +Use case ends. + +*Extensions* + +[none] +* 2a. The list is empty + -**** -* Hints -** link:{repoURL}/src/main/resources/view/StatusBarFooter.fxml[`StatusBarFooter.fxml`] will need a new `StatusBar`. Be sure to set the `GridPane.columnIndex` properly for each `StatusBar` to avoid misalignment! -** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated. -* Solution -** Modify the constructor of link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to take in the number of persons when the application just started. -** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handleAddressBookChangedEvent(AddressBookChangedEvent)`] to update the number of persons whenever there are new changes to the addressbook. -** For tests, modify link:{repoURL}/src/test/java/guitests/guihandles/StatusBarFooterHandle.java[`StatusBarFooterHandle`] by adding a state-saving functionality for the total number of people status, just like what we did for save location and sync status. -** For system tests, modify link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest`] to also verify the new total number of persons status bar. -** See this https://github.com/se-edu/addressbook-level4/pull/803[PR] for the full solution. -**** +Use case ends. [discrete] -==== `Storage` component +=== Use case: Find Book + +*MSS* -*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the address book to the cloud. However, the current implementation of the application constantly saves the address book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the address book storage. +1. User requests to find books with some keywords +2. Catalogue shows a list of books with at least one keyword in the title ++ +Use case ends. -[TIP] -Do take a look at <<Design-Storage>> before attempting to modify the `Storage` component. +*Extensions* -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. +[none] +* 2a. The list is empty + -**** -* Hint -** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/AddressBookStorage.java[`AddressBookStorage`] interface. -** Implement the logic in link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/address/storage/XmlAddressBookStorage.java[`XmlAddressBookStorage`] class. -* Solution -** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. -**** +Use case ends. -[[GetStartedProgramming-RemarkCommand]] -=== Creating a new command: `remark` +[discrete] +// end::usecaseloginoutlistfind[] -By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app. +[discrete] +=== Use case: Add Account +*MSS* -*Scenario:* You are a software maintainer for `addressbook`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. +1. Administrator requests to add a new student or admin account into the database. +2. A new account of specified privilege level(student 1 OR admin 2) is created. + +3. The added account will be unique to other accounts based on the username only. If there is another account in the database that already has the same username, the command display box will throw back an error to indicate that the account cannot be added. -==== Description -Edits the remark for a person specified in the `INDEX`. + -Format: `remark INDEX r/[REMARK]` ++ +Use case ends. -Examples: +*Extensions* -* `remark 1 r/Likes to drink coffee.` + -Edits the remark for the first person to `Likes to drink coffee.` -* `remark 1 r/` + -Removes the remark for the first person. +[none] +* 2a. The account is added into the database. ++ +Use case ends. -==== Step-by-step Instructions +[discrete] +=== Use case: Delete Account +*MSS* -===== [Step 1] Logic: Teach the app to accept 'remark' which does nothing -Let's start by teaching the application how to parse a `remark` command. We will add the logic of `remark` later. +1. Administrator requests to delete an existing account from the database. +2. The account will be deleted if the username that is required to be deleted exists in the database. If not, the account will not be deleted and command display will show that the desired account cannot be deleted. -**Main:** ++ +Use case ends. -. Add a `RemarkCommand` that extends link:{repoURL}/src/main/java/seedu/address/logic/commands/UndoableCommand.java[`UndoableCommand`]. Upon execution, it should just throw an `Exception`. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to accept a `RemarkCommand`. +*Extensions* -**Tests:** +[none] +* 2a. The account is deleted from the database. ++ +Use case ends. -. Add `RemarkCommandTest` that tests that `executeUndoableCommand()` throws an Exception. -. Add new test method to link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. +[discrete] +=== Use case: Exit +*MSS* -===== [Step 2] Logic: Teach the app to accept 'remark' arguments -Let's teach the application to parse arguments that our `remark` command will accept. E.g. `1 r/Likes to drink coffee.` +1. User requests to exit the programme. +2. The programme will be cancelled when given the command regardless of whichever privilege level. -**Main:** ++ +Use case ends. -. Modify `RemarkCommand` to take in an `Index` and `String` and print those two parameters as the error message. -. Add `RemarkCommandParser` that knows how to parse two arguments, one index and one with prefix 'r/'. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to use the newly implemented `RemarkCommandParser`. +*Extensions* -**Tests:** +[none] +* 2a. The programme is cancelled. ++ +Use case ends. -. Modify `RemarkCommandTest` to test the `RemarkCommand#equals()` method. -. Add `RemarkCommandParserTest` that tests different boundary values -for `RemarkCommandParser`. -. Modify link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`] to test that the correct command is generated according to the user input. +[discrete] +=== Use case: Undo +*MSS* -===== [Step 3] Ui: Add a placeholder for remark in `PersonCard` -Let's add a placeholder on all our link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] s to display a remark for each person later. +1. Administrator requests to undo the last command executed in the system. +2. Only commands that change the state of the book catalogue or the account database can be undone. ++ +Use case ends. -**Main:** +*Extensions* -. Add a `Label` with any random text inside link:{repoURL}/src/main/resources/view/PersonListCard.fxml[`PersonListCard.fxml`]. -. Add FXML annotation in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] to tie the variable to the actual label. +[none] +* 2a. The previous executed command is undone. ++ +Use case ends. -**Tests:** +[discrete] +=== Use case: Redo +*MSS* -. Modify link:{repoURL}/src/test/java/guitests/guihandles/PersonCardHandle.java[`PersonCardHandle`] so that future tests can read the contents of the remark label. +1. Administrator requests to redo the last command that has been undone in the system. +2. Only commands that have been undone can be redone. ++ +Use case ends. -===== [Step 4] Model: Add `Remark` class -We have to properly encapsulate the remark in our link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] class. Instead of just using a `String`, let's follow the conventional class structure that the codebase already uses by adding a `Remark` class. +*Extensions* +[none] -**Main:** +* 2a. The previous executed command is redone. ++ +Use case ends. -. Add `Remark` to model component (you can copy from link:{repoURL}/src/main/java/seedu/address/model/person/Address.java[`Address`], remove the regex and change the names accordingly). -. Modify `RemarkCommand` to now take in a `Remark` instead of a `String`. +[discrete] +// tag::usecaseAddEditHelpSelect[] +=== Use case: Add book -**Tests:** +*MSS* + +1. User requests to add book with TITLE, AUTHOR, ISBN, AVAIL, [TAGS] +2. Catalogue adds book +3. Catalogue list books -. Add test for `Remark`, to test the `Remark#equals()` method. +*Extensions* +[none] -===== [Step 5] Model: Modify `Person` to support a `Remark` field -Now we have the `Remark` class, we need to actually use it inside link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +* 1a. The TITLE or AUTHOR or ISBN or AVAIL is not valid or ISBN is not unique. ++ +[none] +** 1a1. Catalogue shows an error message. ++ +Use case ends. -**Main:** +[discrete] +=== Use case: Edit book -. Add `getRemark()` in link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. -. You may assume that the user will not be able to use the `add` and `edit` commands to modify the remarks field (i.e. the person will be created without a remark). -. Modify link:{repoURL}/src/main/java/seedu/address/model/util/SampleDataUtil.java/[`SampleDataUtil`] to add remarks for the sample data (delete your `addressBook.xml` so that the application will load the sample data when you launch it.) +*MSS* -===== [Step 6] Storage: Add `Remark` field to `XmlAdaptedPerson` class -We now have `Remark` s for `Person` s, but they will be gone when we exit the application. Let's modify link:{repoURL}/src/main/java/seedu/address/storage/XmlAdaptedPerson.java[`XmlAdaptedPerson`] to include a `Remark` field so that it will be saved. +1. User requests to list books +2. Catalogue shows a list of books +3. User requests to edit a specific book in the list with [TITLE], [AUTHOR], [ISBN], [AVAIL], [TAGS] +4. Catalogue edit book +5. Catalogue list books ++ +Use case ends. -**Main:** +*Extensions* +[none] -. Add a new Xml field for `Remark`. +* 2a. The list is empty ++ +Use case ends. +[none] +* 3a. The given index is invalid or the [TITLE], [AUTHOR], [ISBN], [AVAIL], [TAGS] is invalid ++ +** 3a1. Catalogue shows an error message. ++ +Use case ends. -**Tests:** +[discrete] +=== Use case: Help -. Fix `invalidAndValidPersonAddressBook.xml`, `typicalPersonsAddressBook.xml`, `validAddressBook.xml` etc., such that the XML tests will not fail due to a missing `<remark>` element. +*MSS* -===== [Step 6b] Test: Add withRemark() for `PersonBuilder` -Since `Person` can now have a `Remark`, we should add a helper method to link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`], so that users are able to create remarks when building a link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +1. User requests to help +2. Help window pops out ++ +Use case ends. -**Tests:** +[discrete] +=== Use case: Select -. Add a new method `withRemark()` for link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`]. This method will create a new `Remark` for the person that it is currently building. -. Try and use the method on any sample `Person` in link:{repoURL}/src/test/java/seedu/address/testutil/TypicalPersons.java[`TypicalPersons`]. +*MSS* -===== [Step 7] Ui: Connect `Remark` field to `PersonCard` -Our remark label in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] is still a placeholder. Let's bring it to life by binding it with the actual `remark` field. +1. User requests to list books +2. Catalogue shows a list of books +3. User requests to select books with index +4. Catalogue shows Goodreads search of the selected book. ++ +Use case ends. -**Main:** +*Extensions* +[none] -. Modify link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`]'s constructor to bind the `Remark` field to the `Person` 's remark. +* 2a. The list is empty ++ +Use case ends. +[none] +* 3a. The given index is invalid ++ +** 3a1. Catalogue shows an error message. ++ +[none] +* 4a. There is no internet access. ++ +** 4a1. Catalogue shows an error message. ++ +Use case ends. -**Tests:** +[discrete] +// end::usecaseAddEditHelpSelect[] -. Modify link:{repoURL}/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java[`GuiTestAssert#assertCardDisplaysPerson(...)`] so that it will compare the now-functioning remark label. +== Use case: Borrow book -===== [Step 8] Logic: Implement `RemarkCommand#execute()` logic -We now have everything set up... but we still can't modify the remarks. Let's finish it up by adding in actual logic for our `remark` command. +*MSS* -**Main:** +1. User login an account with librarian privilege +2. User requests to list books +3. User requests to borrow a specific book in the list +4. List will book with 'Borrowed' availability -. Replace the logic in `RemarkCommand#execute()` (that currently just throws an `Exception`), with the actual logic to modify the remarks of a person. +*Extensions* -**Tests:** +[none] +* 2a. The book is already borrowed or reserved ++ +Catalogue shows an error message. -. Update `RemarkCommandTest` to test that the `execute()` logic works. +* 3a. The given index is invalid. ++ +[none] +** 3a1. Catalogue shows an error message. ++ +Use case resumes at step 2. -==== Full Solution +[discrete] +== Use case: Return book -See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step-by-step solution. +*MSS* -[appendix] -== Product Scope +1. User login an account with librarian privilege +2. User requests to list books +3. User requests to return a specific book in the list +4. List will book with 'Available' availability -*Target user profile*: +*Extensions* -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing over mouse input -* is reasonably comfortable using CLI apps +[none] +* 2a. The book is already available ++ +Catalogue shows an error message. -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +* 3a. The given index is invalid. ++ +[none] +** 3a1. Catalogue shows an error message. ++ +Use case resumes at step 2. -[appendix] -== User Stories +[discrete] +== Use case: Reserve book -Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` +*MSS* -[width="59%",cols="22%,<23%,<25%,<30%",options="header",] -|======================================================================= -|Priority |As a ... |I want to ... |So that I can... -|`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App +1. User login an account with librarian privilege +2. User requests to list books +3. User requests to reserve a specific book in the list +4. List will book with 'Reserved' availability -|`* * *` |user |add a new person | +*Extensions* -|`* * *` |user |delete a person |remove entries that I no longer need +[none] +* 2a. The book is available ++ +Catalogue shows an error message. -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +* 3a. The given index is invalid. ++ +[none] +** 3a1. Catalogue shows an error message. ++ +Use case resumes at step 2. -|`* *` |user |hide <<private-contact-detail,private contact details>> by default |minimize chance of someone else seeing them by accident +// tag::usecaseclearaclearhistory[] +[discrete] +=== Use case: Clear accounts -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +*MSS* -_{More to be added}_ +1. User logs in with credentials +2. Catalogue authenticates with the credentials entered +3. Catalogue shows successful login message +4. Librarian types `cleara` in the command box +5. Catalogue checks the privilege of the user against the privilege level associated with `cleara` +6. If successful, Catalogue clears all the accounts from the list of accounts, logs out the current user and add the default admin user. ++ -[appendix] -== Use Cases +*Extensions* -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +[none] +* 6a. The list of accounts is empty ++ +Use case ends. [discrete] -=== Use case: Delete person +=== Use case: Clear books *MSS* -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. Librarian logs in with credentials +2. Catalogue shows successful login message +3. Librarian types `clear` in the command box to clear Catalogue +4. Catalogue clears all the books from Catalogue + -Use case ends. *Extensions* [none] -* 2a. The list is empty. +* 4a. Catalogue is empty + Use case ends. -* 3a. The given index is invalid. +[discrete] +=== Use case: History Command + +*MSS* + +1. User logs in with credentials +2. Catalogue shows successful login message +3. Librarian types `history` in the command box to see the history of entered commands +4. The application shows the previously entered commands, most recent first + + +*Extensions* + [none] -** 3a1. AddressBook shows an error message. +* 5a. No commands have been entered as for now + -Use case resumes at step 2. +Use case ends. +// end::usecaseclearaclearhistory[] -_{More to be added}_ [appendix] == Non Functional Requirements . Should work on any <<mainstream-os,mainstream OS>> as long as it has Java `1.8.0_60` or higher installed. -. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. +. Should be able to hold up to 1000 books without a noticeable sluggishness in performance for typical usage. . A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. _{More to be added}_ @@ -864,23 +1067,6 @@ Windows, Linux, Unix, OS-X [[private-contact-detail]] Private contact detail:: A contact detail that is not meant to be shared with others -[appendix] -== Product Survey - -*Product Name* - -Author: ... - -Pros: - -* ... -* ... - -Cons: - -* ... -* ... - [appendix] == Instructions for Manual Testing @@ -903,26 +1089,256 @@ These instructions only provide a starting point for testers to work on; testers .. Re-launch the app by double-clicking the jar file. + Expected: The most recent window size and location is retained. -_{ more test cases ... }_ +// tag::manualtestloginoutlistfind[] +=== Login + +. Initial login + +.. Prerequisites: The account-password combination `wrong acount` is not in the AccountList +.. Test case: `login admin admin` + + Expected: The user is logged in as an librarian. The user will be privileged to execute any command. +.. Test case: `login wrong account` + + Expected: An error message will be shown. The user is not logged in. + +=== Logout + +. Logout after logged in +.. Prerequisites: The current account logged in is a student or a librarian +.. Test case: `logout` + + Expected: The user is logged out. The current account will become guest account. + +. Logout without logging in + +.. Prerequisites: The current account logged in is a guest +.. Test case: `logout` + + Expected: An error message will be shown. The user will stay as a guest. -=== Deleting a person +=== List -. Deleting a person while all persons are listed +. List all books +.. Prerequisites: There is at least one book in the book list. +.. Test case: `list` + + Expected: A list of all books will be shown. -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +. List all books when the book list is empty +.. Prerequisites: There is no book in the book list. +.. Test case: `list` + + Expected: An empty list will be shown. + +=== Find + +. Find books with the keyword in title +.. Prerequisites: The book list contains default sample data. +.. Test case: `find animal` + + Expected: The book 'Animal Farm' will be shown. +.. Test case: `find animal girl` + + Expected: The book 'Animal Farm' and 'California Girl' will be shown. +.. Test case: `find catalog` + + Expected: An empty list will be shown. +// end::manualtestloginoutlistfind[] + + +=== Deleting a book + +. Deleting a book while all books are listed + +.. Prerequisites: List all books using the `list` command. Multiple books in the list. .. Test case: `delete 1` + - Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. + Expected: First book is deleted from the list. Details of the deleted book shown in the status message. Timestamp in the status bar is updated. .. Test case: `delete 0` + - Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + Expected: No book is deleted. Error details shown in the status message. Status bar remains the same. .. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + Expected: Similar to previous. -_{ more test cases ... }_ +// tag::manualtestAddEditHelpSelect[] +=== Adding a book + +. Adding a book + +.. Prerequisites: Log in using the `log in` command. +.. Test case: `add t/You a/Caroline Kepnes i/9781476785592 av/Borrowed` + + Expected: Book is added to the list at last index. Details of the added book shown in the status message. Timestamp in the status bar is updated. +.. Test case: `add t/You a/Caroline Kepnes i/9781476785592 av/Borrowed` again + + Expected: No book is added. Error details shown in the status message. Status bar remains the same. +.. Test case: `add t/Yo$ a/Caroline Kepnes i/0000000000001 av/Borrowed` again + + Expected: No book is added. Error details shown in the status message. Status bar remains the same. +.. Test case: `add t/You a/Caroline Kepne$ i/0000000000002 av/Borrowed` again + + Expected: No book is added. Error details shown in the status message. Status bar remains the same. +.. Test case: `add t/You a/Caroline Kepnes i/000000000000a av/Borrowed` again + + Expected: No book is added. Error details shown in the status message. Status bar remains the same. +.. Test case: `add t/You a/Caroline Kepnes i/0000000000004 av/not sure` again + + Expected: No book is added. Error details shown in the status message. Status bar remains the same. +.. Other incorrect add commands to try: `add`, `add t/` + Expected: Similar to previous. + +=== Editing a book + +. Editing a book while all books are listed + +.. Prerequisites: List all books using the `list` command. Multiple books in the list. +.. Test case: `edit 1 t/Animal` + + Expected: Title of first book is edit in the list. Details of the edited book shown in the status message. Timestamp in the status bar is updated. +.. Test case: `delete 0 t/Animal` + + Expected: No book is edited. Error details shown in the status message. Status bar remains the same. +.. Other incorrect delete commands to try: `edit t/Animal`, `edit x t/Animal` (where x is larger than the list size) + + Expected: Similar to previous. + +=== Help + +. Display Help window + +.. Test case: `help` + + Expected: Help window pops out. Action shown in the status message. Status bar remains the same. +.. Test case: 'F1' + + Expected: Help window pops out. Action shown in the status message. Status bar remains the same. + +=== Selecting a book + +. Selecting a book while all books are listed + +.. Prerequisites: List all books using the `list` command. Multiple books in the list. +.. Test case: `select 1` (With internet) + + Expected: First book is selected from the list. Action shown in the status message. Goodreads webpage of the book is loaded in the information section. Timestamp in the status bar is updated. +.. Test case: `select 1` (Without internet) + + Expected: First book is selected from the list. Action shown in the status message. Error message shown in information system. Timestamp in the status bar is updated. +.. Test case: `select 0` + + Expected: No book is selected. Error details shown in the status message. Status bar remains the same. +.. Other incorrect delete commands to try: `select`, `select x` (where x is larger than the list size) + + Expected: Similar to previous. +// end::manualtestAddEditHelpSelect[] + +=== Adding an account + +. Adding an account + +.. Prerequisites: The account with that specific username must not be added in the first place or else the account cannot be added. +.. Test case: `addAccount n/Tiong Ley u/tiongley p/tiongley123 m/A0123456B l/1` + + Expected: Account with usename "tiongley" is added to the account database. Timestamp in the status bar is updated. +.. Test case: `addAccount n/Johnny Marconi u/tiongley p/tiongley123 m/A0123456C l/1` + + Expected: No account is added. Error details shown in the status message. An error of "This account already exists in the system" will be shown. +.. Other incorrect delete commands to try: `addAcCount`, `addAccount 1` , `addAccount tiongley` + + Expected: Similar to previous. + +=== Deleting an account + +. Deleting an account + +.. Prerequisites: The account with that specific username must already exist in the first place or else the account cannot be deleted. +.. Test case: `deleteAccount tiongley` + + Expected: Account with usename "tiongley" is deleted from the account database. Timestamp in the status bar is updated. +.. Test case: `deleteAccount` + + Expected: No account is deleted. Error details shown in the status message. An error of "Account does not exist" will be shown. +.. Other incorrect delete commands to try: `deleteAcCount tiongley`, `deleteAccount ` + + Expected: Similar to previous. + + +=== Exit + +. Exiting the programme + +.. Prerequisites: The application programme must be opened. +.. Test case: `exit` + + Expected: The application programme will close. +.. Test case: `exit 123` + + Expected: The application will close. + +=== Undo + +. Undo the previous command + +.. Prerequisites: An undoable command (such as add, delete, edit) must have been executed before using the 'undo' command. +.. Test case: `deleteAccount tiongley` followed by `undo` + Expected: Account with usename "tiongley" will be added back to the account database. Timestamp in the status bar is updated. +.. Test case: (Given that no command was executed before this) `undo` + + Expected: Nothing happens. Error details shown in the status message. An error of "No more commands to undo!" will be shown. + + +=== Redo + +. Redo the previous command that has been undone + +.. Prerequisites: An undoable command (such as add, delete, edit) must have been undone by the "undo" command before using the 'redo' command. +.. Test case: `deleteAccount tiongley` followed by `undo` followed by `redo` + Expected: Account with username "tiongley" will be deleted from the account database. Timestamp in the status bar is updated. +.. Test case: (Given that no undoable command was undone before this) `redo` + + Expected: Nothing happens. Error details shown in the status message. An error of "No more commands to redo!" will be shown. + +=== Borrowing a book + +. Borrowing a book while all books are listed + +.. Prerequisites: List all books using the `list` command. Multiple books in the list. +.. Test case: `borrow 1` + + Expected: First book will be borrowed from the list, given that it is available. Details of the borrowed book will be shown in the status message. +.. Test case: `borrow 0` + + Expected: No book is borrowed. Error details shown in the status message. Status bar remains the same. + .. Other incorrect borrow commands to try: `borrow`, `borrow x`(where x is larger than list size or not an integer), `borrow [valid index]`(where the book is not available) + Expected: Similar to previous. + +=== Returning a book + +. Returning a book while all books are listed + +.. Prerequisites: List all books using the `list` command. Multiple books in the list. +.. Test case: `return 1` + + Expected: First book will be returned from the list, given that it is borrowed or reserved. Details of the returned book will be shown in the status message. +.. Test case: `return 0` + + Expected: No book is returned. Error details shown in the status message. Status bar remains the same. + .. Other incorrect return commands to try: `return`, `return x`(where x is larger than list size or not an integer), `return [valid index]`(where the book is available) + Expected: Similar to previous. + +=== Reserving a book + +. Reserving a book while all books are listed + +.. Prerequisites: List all books using the `list` command. Multiple books in the list. +.. Test case: `reserve 1` + + Expected: First book will be reserved from the list, given that it is borrowed. Details of the reserved book will be shown in the status message. +.. Test case: `reserve 0` + + Expected: No book is reserved. Error details shown in the status message. Status bar remains the same. + .. Other incorrect reserve commands to try: `reserve`, `reserve x`(where x is larger than list size or not an integer), `reserve [valid index]`(where the book is not borrowed) + Expected: Similar to previous. + +// tag::manualtestclearaclearhistory[] +=== Clearing the list of accounts + +. Clear all accounts + +.. Prerequisites: There is at least one account in the list of accounts +.. Test Case: `cleara` + + Expected: All the accounts present in the list of accounts are erased. The message "AccountList has been cleared, and you are logged out!" is displayed in the result display. + +. Clear all accounts from empty list + +.. Prerequisites: No accounts have been entered in the list of accounts +.. Test Case: `cleara` + + Expected: The empty list of account will be replaced by another empty list of accounts. The message "AccountList has been cleared, and you are logged out!" is displayed in the result display. + +=== Clearing Catalogue + +. Clear all books from Catalogue + +.. Prerequisites: There is at least one book in Catalogue +.. Test Case: `clear` + + Expected: All the books present in Catalogue are erased. The message "Catalogue has been cleared!" is displayed in the result display. + +. Clear all books from empty Catalogue + +.. Prerequisites: No book have been entered in Catalogue +.. Test Case: `clear` + + Expected: The empty Catalogue will be replaced by another empty Catalogue. The message "Catalogue has been cleared!" is displayed in the result display. + +=== Getting the history of entered commands -=== Saving data +. Get the history -. Dealing with missing/corrupted data files +.. Prerequisites: At least one command has been entered +.. Test Case: `history` + + Expected: All the commands previously entered are shown from earliest to oldest. The message "Entered commands (from most recent to earliest) is displayed in the result display. -.. _{explain how to simulate a missing/corrupted file and the expected behavior}_ +. Get the history without any entered commands -_{ more test cases ... }_ +.. Prerequisites: No commands have been typed in +.. Test Case: `history` + + Expected: No commands are shown. The message "You have not yet entered any commands." is displayed in the result display. +// end::manualtestclearaclearhistory[] diff --git a/docs/DummySearchPage.html b/docs/DummySearchPage.html index 1607d4c57291..32036bd97ed9 100644 --- a/docs/DummySearchPage.html +++ b/docs/DummySearchPage.html @@ -9,6 +9,7 @@ var result = {}; query.split("&").forEach(function(part) { var item = part.split("="); + result[item[0]] = decodeURIComponent(item[1]); }); return result; @@ -20,7 +21,7 @@ </script> </head> <body> - Hi <span id="name"></span>: This is a placeholder page for se-edu/addressbook-level4.<br> + Hi <span id="name"></span>: This is a placeholder page for CS2103JAN2018-T16-B1/main<br> You may update the code to load a page from a real service (e.g., Google search).<br> This dummy page is used here because, given the high number of forks of this repo, loading a page from a real third-party service by default can result in that service taking counter-measures (e.g., redirecting to captcha pages) due to the high number of rapid requests received from a single IP.<br> When you have made the change, please remove: diff --git a/docs/LearningOutcomes.adoc b/docs/LearningOutcomes.adoc index cf153ba8b38f..8eb21c01d624 100644 --- a/docs/LearningOutcomes.adoc +++ b/docs/LearningOutcomes.adoc @@ -37,7 +37,7 @@ Note how the <<DeveloperGuide#architecture, Developer Guide>> uses events to com == Use API Design `[LO-ApiDesign]` -Note how components of AddressBook have well-defined APIs. For example, the API of the `Logic` component is given in the link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] +Note how components of Catalogue have well-defined APIs. For example, the API of the `Logic` component is given in the link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] image:LogicClassDiagram.png[width="800"] *Resources* @@ -48,7 +48,7 @@ image:LogicClassDiagram.png[width="800"] == Use Assertions `[LO-Assertions]` -Note how the AddressBook app uses Java ``assert``s to verify assumptions. +Note how the Catalogue app uses Java ``assert``s to verify assumptions. *Resources* @@ -57,14 +57,14 @@ Note how the AddressBook app uses Java ``assert``s to verify assumptions. === Exercise: Add more assertions * Make sure assertions are enabled in your IDE by forcing an assertion failure (e.g. add `assert false;` somewhere in the code and run the code to ensure the runtime reports an assertion failure). -* Add more assertions to AddressBook as you see fit. +* Add more assertions to Catalogue as you see fit. ''' == Use Logging `[LO-Logging]` -Note <<DeveloperGuide#logging, how the AddressBook app uses Java's `java.util.log` package to do logging>>. +Note <<DeveloperGuide#logging, how the Catalogue app uses Java's `java.util.log` package to do logging>>. *Resources* @@ -72,14 +72,14 @@ Note <<DeveloperGuide#logging, how the AddressBook app uses Java's `java.util.lo === Exercise: Add more logging -Add more logging to AddressBook as you see fit. +Add more logging to Catalogue as you see fit. ''' == Use Defensive Coding `[LO-DefensiveCoding]` -Note how AddressBook uses the `ReadOnly*` interfaces to prevent objects being modified by clients who are not supposed to modify them. +Note how Catalogue uses the `ReadOnly*` interfaces to prevent objects being modified by clients who are not supposed to modify them. *Resources* @@ -87,7 +87,7 @@ Note how AddressBook uses the `ReadOnly*` interfaces to prevent objects being mo === Exercise: identify more places for defensive coding -Analyze the AddressBook code/design to identify, +Analyze the Catalogue code/design to identify, * where defensive coding is used * where the code can be more defensive @@ -96,7 +96,7 @@ Analyze the AddressBook code/design to identify, == Use Build Automation `[LO-BuildAutomation]` -Note <<UsingGradle#, how the AddressBook app uses Gradle to automate build tasks>>. +Note <<UsingGradle#, how the Catalogue app uses Gradle to automate build tasks>>. *Resources* @@ -115,7 +115,7 @@ Note <<UsingGradle#, how the AddressBook app uses Gradle to automate build tasks == Use Continuous Integration `[LO-ContinuousIntegration]` -Note <<UsingTravis#, how the AddressBook app uses Travis to perform Continuous Integration>>. (https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]]) +Note <<UsingTravis#, how the Catalogue app uses Travis to perform Continuous Integration>>. (https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]]) *Resources* @@ -161,8 +161,8 @@ class gives some examples of how to use _Equivalence Partitions_, _Boundary Valu Consider the link:{repoURL}/src/test/java/seedu/address/storage/StorageManagerTest.java[`StorageManagerTest.java`] class. -* Test methods `prefsReadSave()` and `addressBookReadSave()` are integration tests. Note how they simply test if The `StorageManager` class is correctly wired to its dependencies. -* Test method `handleAddressBookChangedEvent_exceptionThrown_eventRaised()` is a unit test because it uses _dependency injection_ to isolate the SUT `StorageManager#handleAddressBookChangedEvent(...)` from its dependencies. +* Test methods `prefsReadSave()` and `catalogueReadSave()` are integration tests. Note how they simply test if The `StorageManager` class is correctly wired to its dependencies. +* Test method `handleCatalogueChangedEvent_exceptionThrown_eventRaised()` is a unit test because it uses _dependency injection_ to isolate the SUT `StorageManager#handleCatalogueChangedEvent(...)` from its dependencies. Compare the above with link:{repoURL}/src/test/java/seedu/address/logic/LogicManagerTest.java[`LogicManagerTest`]. Some of the tests in that class (e.g. `execute_*` methods) are neither integration nor unit tests. They are _integration + unit_ tests because they not only check if the LogicManager is correctly wired to its dependencies, but also checks the working of its dependencies. For example, the following two lines test the `LogicManager` but also the `Parser`. @@ -222,7 +222,7 @@ Here are some example design patterns used in the code base. * *MVC Pattern* : ** The 'View' part of the application is mostly in the `.fxml` files in the `src/main/resources/view` folder. ** `Model` component contains the 'Model'. However, note that it is possible to view the `Logic` as the model because it hides the `Model` behind it and the view has to go through the `Logic` to access the `Model`. -** Sub classes of link:{repoURL}/src/main/java/seedu/address/ui/UiPart.java[`UiPart`] (e.g. `PersonListPanel` ) act as 'Controllers', each controlling some part of the UI and communicating with the 'Model' (via the `Logic` component which sits between the 'Controller' and the 'Model'). +** Sub classes of link:{repoURL}/src/main/java/seedu/address/ui/UiPart.java[`UiPart`] (e.g. `BookListPanel` ) act as 'Controllers', each controlling some part of the UI and communicating with the 'Model' (via the `Logic` component which sits between the 'Controller' and the 'Model'). * *Abstraction Occurrence Pattern* : Not currently used in the app. *Resources* diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 74248917e438..a753364265c3 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - User Guide += Catalogue - User Guide :toc: :toc-title: :toc-placement: preamble @@ -11,13 +11,30 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4 +:repoURL: https://github.com/CS2103JAN2018-T16-B1/main -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `Team CS2103JAN2018-T16-B1` Since: `Jan 2018` Licence: `MIT` + +// tag::defaultaccount[] +== Default Account +``` +Username: admin +Password: admin +``` +You can login the default account with this command: 'login admin admin' +// end::defaultaccount[] == Introduction -AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB4 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB4 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <<Quick Start>> to get started. Enjoy! +Catalogue is for *NUS students* who prefer to use a desktop app for finding, borrowing and returning english books at +the Central Library + +Catalogue is for *NUS librarian* who prefer to use a desktop app for managing books, students accounts and loans at +the Central Library. + +More importantly, Catalogue is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still + having the benefits of a Graphical User Interface (GUI). + +If you can type fast, Catalogue can get your contact management tasks done faster than traditional GUI apps. + +This guide will help you and give you an overview of the different features of Catalogue. + +Interested? Jump to the <<Quick Start>> to get started. Enjoy! + == Quick Start @@ -27,19 +44,25 @@ AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for mana Having any Java 8 version is not enough. + This app will not work with earlier versions of Java 8. + -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. +. Download the latest `catalogue.jar` link:{repoURL}/releases[here]. +. Copy the file to the folder you want to use as the home folder for your Catalogue. . Double-click the file to start the app. The GUI should appear in a few seconds. + -image::Ui.png[width="790"] +image::UserInterfaceFinal.png[width="790"] + +1 - Command box: type the commands in this section + +2 - Result Display: Show the result of what you typed above + +3 - Book List: List of all the books in the Central Library + +4 – Information Section: when you click on a book, the webpage on goodreads will open and display additional +information + + . Type the command in the command box and press kbd:[Enter] to execute it. + e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. . Some example commands you can try: -* *`list`* : lists all contacts -* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to the Address Book. -* **`delete`**`3` : deletes the 3rd contact shown in the current list +* *`list`* : lists all books +* **`add`**`t/Animal Farm a/George Orwell i/9780736692427 av/Borrowed` : adds a book titled `Animal Farm` to the Catalogue. +* **`delete`**`3` : deletes the 3rd book shown in the current list * *`exit`* : exits the app . Refer to <<Features>> for details of each command. @@ -50,81 +73,159 @@ e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. ==== *Command Format* -* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. -* Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. -* Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. -* Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. +* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add t/TITLE`, `TITLE` is a parameter which can be used as `add n/Animal Farm`. +* Items in square brackets are optional e.g `t/TITLE [tag/TAG]` can be used as `t/Animal Farm tag/satire` or as `t/Animal Farm`. +* Items with `…`​ after them can be used multiple times including zero times e.g. `[tag/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `tag/satire`, `tag/satire tag/political` etc. +* Parameters can be in any order e.g. if the command specifies `t/TITLE i/ISBN`, `i/ISBN t/TITLE` is also acceptable. ==== +// tag::loginout[] +=== Login as Student/Librarian : `Login` + +Format: `login [username] [password]` + +Privilege Level: 0(Guest) + +Privilege Level: 1 (Student) + +Privilege Level: 2 (Librarian or Administrator) + +Default administrator account: + +Username: admin + +Password: admin + +Login with different accounts: + +1. Add account using "addAccount" command + +2. Use the username and password that you entered for the above account + +e.g. addAccount n/Victor Tardieu m/ A1234567N u/e12345 p/secretpassword l/1 + +Access based on different privilege levels: + +1. At privilege level of "guest", you can only browse the catalogue of library books inside. + +2. At privilege level of "student", you can browse the catalogue, borrow a book, return a book and reserve a book that you want. + +3. At privilege level of "librarian", you can browse, change the status of a borrower's borrowing status, add/delete/edit new books in the catalogue, and add/delete the accounts in the database. + +[NOTE] +Different accounts might have different privilege levels. A command can only be executed when the privilege level of the current user is greater or equal to the command's required level. The default admin account can be logged in with 'login admin admin'. + +=== Logout from Student/Librarian : `Logout` + +Format: `logout` + +Privilege Level: + +From 2(Librarian) you are going back to 0(Guest) + +From 1(Student) you are going back to 0(Guest) + + +[NOTE] +Remember that when one logs out from your account, some commands (such as add a book for a librarian) won’t be available anymore. To be able to do them again, one will have to login again with his unique credentials. +// end::loginout[] + +//tag::help[] === Viewing help : `help` -Format: `help` +Format: `help` + +Format: kbd:[F1] + +Privilege Level: 0(Guest) +//end::help[] + +// tag::add[] +[[Add]] +=== Adding a book: `add` -=== Adding a person: `add` -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +Format: `add n/TITLE a/AUTHOR i/ISBN av/AVAIL [t/TAG]...` +Privilege Level: 2(Librarian) [TIP] -A person can have any number of tags (including 0) +A book can have any number of tags (including 0) + +**** +* All of the fields must be provided. +* The provided field must be valid. +** TITLE must be alphanumeric. +** AUTHOR must be alphanumeric. +** ISBN must contain 13 numbers and be unique. +** AVAIL must be `Available`, `Borrowed` or `Reserved`. +**** + +Examples: + +* `add t/Animal Farm a/George Orwell i/9780736692427 av/Borrowed` +* `add t/Breaking Dawn tag/fiction av/Borrowed a/Stephenie Meyer i/9780316067928 tag/young adults` +// end::add[] +[TIP] +Press TAB after writing `add` to use the auto-complete function. Examples: -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` +* `add` --> (press TAB) --> `add t/ a/ i/ av/ tag/ ` -=== Listing all persons : `list` +=== Listing all books : `list` -Shows a list of all persons in the address book. + -Format: `list` -=== Editing a person : `edit` +Shows a list of all books in the catalogue. + +Format: `list` + +Privilege Level: 0(Guest) -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +// tag::edit[] +=== Editing a book : `edit` + +Edits an existing book in the catalogue. + +Format: `edit INDEX t/TITLE a/AUTHOR i/ISBN av/AVAIL tag/TAG...` + +Privilege Level: 2(Librarian) **** -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the last person listing. The index *must be a positive integer* 1, 2, 3, ... +* Edits the book at the specified `INDEX`. The index refers to the index number shown in the last book listing. The index *must be a positive integer* 1, 2, 3, ... * At least one of the optional fields must be provided. +* The provided field must be valid. (Refer to <<Add>>) * Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person's tags by typing `t/` without specifying any tags after it. +* When editing tags, the existing tags of the book will be removed i.e adding of tags is not cumulative. +* You can remove all the book's tags by typing `t/` without specifying any tags after it. **** Examples: -* `edit 1 p/91234567 e/johndoe@example.com` + -Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` + -Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. +* `edit 1 i/9780736692426 av/Available` + +Edits the isbn number and availability author of the 1st book to be `9780736692426` and `Available` respectively. +* `edit 2 t/Breaking Dawn tag/` + +Edits the title of the 2nd book to be `Breaking Dawn` and clears all existing tags. +// end::edit[] + +[TIP] +Press TAB after writing `edit` to use the auto-complete function. + +Examples: + +* `edit` --> (press TAB) --> `edit 1 t/ a/ i/ av/ tag/ ` + -=== Locating persons by name: `find` +=== Locating books by title: `find` + +Finds books whose titles contain any of the given keywords. + +Format: `find KEYWORD [MORE_KEYWORDS]` + + Privilege Level: 0(Guest) -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` **** -* The search is case insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` +* The search is case insensitive. e.g `girl` will match `Girl` +* The order of the keywords does not matter. e.g. `Dawn Girl` will match `Girl Dawn` +* Only the title is searched. +* Only full words will be matched e.g. `gir` will not match `Girl` +* Books matching at least one keyword will be returned (i.e. `OR` search). e.g. `Farm Dawn` will return `Animal Farm`, `Breaking Dawn` **** Examples: -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +* `find Animal` + +Returns `Animal Farm` +* `find Animal Breaking California` + +Returns any book having titles `Animal`, `Breaking`, or `California` + +// tag::delete[] +=== Deleting a book : `delete` -=== Deleting a person : `delete` +Deletes the specified book from the catalogue. + +Format: `delete INDEX` + +Privilege Level: 2(Librarian) -Deletes the specified person from the address book. + -Format: `delete INDEX` **** -* Deletes the person at the specified `INDEX`. +* Deletes the book at the specified `INDEX`. * The index refers to the index number shown in the most recent listing. * The index *must be a positive integer* 1, 2, 3, ... **** @@ -133,18 +234,29 @@ Examples: * `list` + `delete 2` + -Deletes the 2nd person in the address book. -* `find Betsy` + +Deletes the 2nd book in the catalogue. +* `find Breaking` + `delete 1` + -Deletes the 1st person in the results of the `find` command. +Deletes the 1st book in the results of the `find` command. + +[TIP] +Press TAB after writing `delete` to use the auto-complete function. + +Examples: + +* `delete` --> (press TAB) --> `delete 1` +// end::delete[] -=== Selecting a person : `select` +// tag::select[] +=== Selecting a book : `select` -Selects the person identified by the index number used in the last person listing. + -Format: `select INDEX` +Selects the book identified by the index number used in the last book listing. + +Format: `select INDEX` + +Privilege Level: 0(Guest) **** -* Selects the person and loads the Google search page the person at the specified `INDEX`. +* Selects the book and loads the Goodreads webpage of the book at the specified `INDEX` with internet connection. +* Selects the book and display error message specified `INDEX` without internet connection. * The index refers to the index number shown in the most recent listing. * The index *must be a positive integer* `1, 2, 3, ...` **** @@ -153,15 +265,16 @@ Examples: * `list` + `select 2` + -Selects the 2nd person in the address book. -* `find Betsy` + +Selects the 2nd book in the catalogue. +* `find Breaking` + `select 1` + -Selects the 1st person in the results of the `find` command. +Selects the 1st book in the results of the `find` command. === Listing entered commands : `history` Lists all the commands that you have entered in reverse chronological order. + -Format: `history` +Format: `history` + +Privilege Level: 2(Librarian) [NOTE] ==== @@ -171,12 +284,13 @@ Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and // tag::undoredo[] === Undoing previous command : `undo` -Restores the address book to the state before the previous _undoable_ command was executed. + -Format: `undo` +Restores the catalogue to the state before the previous _undoable_ command was executed. + +Format: `undo` + +Privilege Level: 2(Librarian) [NOTE] ==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). +Undoable commands: those commands that modify the catalogue's content (`add`, `delete`, `edit` and `clear`). ==== Examples: @@ -198,7 +312,8 @@ The `undo` command fails as there are no undoable commands executed previously. === Redoing the previously undone command : `redo` Reverses the most recent `undo` command. + -Format: `redo` +Format: `redo` + +Privilege Level: 2(Librarian) Examples: @@ -218,19 +333,155 @@ The `redo` command fails as there are no `undo` commands executed previously. `redo` (reapplies the `clear` command) + // end::undoredo[] -=== Clearing all entries : `clear` +=== Clearing all books : `clear` -Clears all entries from the address book. + -Format: `clear` + +Clears all books from the catalogue. + +Format: `clear` + +Privilege Level: 2(Librarian) + +// tag::Borrow[] +=== Borrow a book: `borrow` + +Borrow the specified book from the catalogue. + +Format: `borrow INDEX` + +Privilege Level: 2(Librarian) + +**** +* Borrow the book at the specified `INDEX`. +* The index refers to the index number shown in the most recent listing. +* The index *must be a positive integer* 1, 2, 3, ... +**** + +Examples: + +* `list` + +`borrow 2` + +Borrow the 2nd book in the catalogue. + +[TIP] +Press TAB after writing `borrow` to use the auto-complete function. + +Examples: +* `borrow` -> (press TAB) -> `borrow 1` + +=== Return a book : `return` + +Return a specified book to the catalogue + +Format: `return INDEX` + +Privilege Level: 2(Librarian) + +[NOTE] +Can only return book that has been borrowed + +**** +* Return the book at the specified `INDEX`. +* The index refers to the index number shown in the most recent listing. +* The index *must be a positive integer* 1, 2, 3, ... +**** + +[TIP] +Press TAB after writing `return` to use the auto-complete function. + +Examples: + +* `return` -> (press TAB) -> `return 1` + +=== Reserve a book : `reserve` + +Renew a specific book from the Catalogue + +Format: `list` + +`reserve INDEX` + +Privilege Level: 2(Librarian) + +[NOTE] +Can only reserve book that has been borrowed + +**** +* Reserve the book at the specified `INDEX`. +* The index refers to the index number shown in the most recent listing. +* The index *must be a positive integer* 1, 2, 3, ... +**** + +[TIP] +Press TAB after writing `reserve` to use the auto-complete function. + +Examples: + +* `list` + +`reserve` -> (press TAB) -> `reserve 1` +// end::Borrow[] + +// tag::cleara[] +=== Clearing all accounts : `cleara` + +Clears all accounts from AccountList. + +Format: `clear` + +Privilege Level: 2(Librarian) + +**** +* Removes all the accounts from the list of accounts +* Adds the default administrator account: admin admin +**** + +[NOTE] +When an librarian clears the list of accounts, it is automatically logged out. + +Example: +* `list` + +`cleara` + +All accounts removed from the list of accounts +// end::cleara[] + +// tag::addaccount[] +=== Add an account : `addAccount` + +Add a given account to the list of accounts + +Format: `addAccount n/NAME u/USERNAME p/PASSWORD m/MATRICNUMBER l/PRIVILEGE LEVEL` + +Privilege Level: 2(Librarian) + +Examples: + +* `addAccount n/Jack Morgan u/jacky p/jack123 m/A0123456J l/1` +* `addAccount n/Tom Madison u/tommy p/tom123 m/A1234567T l/2` + +[TIP] +Press TAB after writing `addAccount` to use the auto-complete function. + +Examples: + +* `addAccount` --> (press TAB) --> `addAccount n/ m/ u/ p/ l/ ` +// end::addaccount[] + +// tag::deleteaccount[] +=== Deleting an account : `deleteAccount` + +Deletes the specified account from the list of accounts. + +Format: `delete USERNAME` + +Privilege Level: 2(Librarian) + +**** +* Deletes the account with the specified `USERNAME`. +* The USERNAME must be a from a created account in the list of accounts +**** + +Examples: + +* `delete jackmorgan` + +Deletes account linked to Jack Morgan. +* `delete tommy` + +Deletes the account linked to Tommy. +// end::deleteaccount[] === Exiting the program : `exit` Exits the program. + -Format: `exit` +Format: `exit` + +Privilege Level: 2(Librarian) === Saving the data -Address book data are saved in the hard disk automatically after any command that changes the data. + +Catalogue data are saved in the hard disk automatically after any command that changes the data. + There is no need to save manually. // tag::dataencryption[] @@ -242,23 +493,42 @@ _{explain how the user can enable/disable data encryption}_ == FAQ *Q*: How do I transfer my data to another Computer? + -*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Address Book folder. +*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Catalogue folder. +// tag::commandsummary[] == Command Summary -* *Add* `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + -e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -* *Clear* : `clear` -* *Delete* : `delete INDEX` + -e.g. `delete 3` -* *Edit* : `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]...` + -e.g. `edit 2 n/James Lee e/jameslee@example.com` -* *Find* : `find KEYWORD [MORE_KEYWORDS]` + -e.g. `find James Jake` -* *List* : `list` -* *Help* : `help` -* *Select* : `select INDEX` + -e.g.`select 2` -* *History* : `history` -* *Undo* : `undo` -* *Redo* : `redo` +[width="59%",cols="30%, 20%,<50%",options="header",] +|======================================================================= +|Privileges |Command |Format +|All |*Help* |`help` +|All |*Find Book* |`find KEYWORD [MORE_KEYWORDS]` + + e.g. `find Animal Dawn` +|All |*List Book* |`list` +|All |*Select Book* |`select INDEX` + + e.g.`select 2` +|Librarian |*Add Book* |`add t/TITLE a/AUTHOR i/ISBN av/AVAIL [tag/TAG]...` + + e.g. `add n/Animal Farm a/George Orwell i/9780736692427 av/Borrowed t/political t/satire` +|Librarian |*Delete Book* |`delete INDEX` + + e.g. `delete 3` +|Librarian |*Edit Book* |`edit INDEX [n/TITLE] [a/AUTHOR] [i/ISBN] [av/AVAIL] [t/TAG]...` + + e.g. `edit 2 n/Animal Farm e/Available` +|Librarian |*Borrow Book* |`borrow INDEX` + + e.g. `borrow 1` +|Librarian |*Return Book* |`return INDEX` + + e.g. `return 2` +|Librarian |*Reserve Book* |`reserve INDEX` + + e.g. `reserve 2` +|Librarian |*Clear Catalogue* |`clear` +|Librarian |*Add Account* |`addAccount n/NAME u/USERNAME p/PASSWORD m/MATRICNUMBER l/PRIVILEGE LEVEL` + + e.g. `addAccount n/Jack Morgan u/jacky p/jack123 m/A0123456J l/1` +|Librarian |*Delete Account* |`deleteAccount USERNAME` + + e.g. `deleteAccount tiongley` +|Librarian |*Clear Account List* |`cleara` +|Librarian |*History* |`history` +|Librarian |*Undo* |`undo` +|Librarian |*Redo* |`redo` +|======================================================================= +// end::commandsummary[] + + diff --git a/docs/UsingCheckstyle.adoc b/docs/UsingCheckstyle.adoc index ba9b76d3918d..71cabd050948 100644 --- a/docs/UsingCheckstyle.adoc +++ b/docs/UsingCheckstyle.adoc @@ -18,7 +18,7 @@ Restart the IDE to complete the installation. + image::checkstyle-idea-scan-scope.png[width="500"] . Click the plus sign under `Configuration File` -. Enter an arbitrary description e.g. addressbook +. Enter an arbitrary description e.g. Catalogue . Select `Use a local Checkstyle file` . Use the checkstyle configuration file found at `config/checkstyle/checkstyle.xml` . Click `Next` > `Finish` diff --git a/docs/UsingGradle.adoc b/docs/UsingGradle.adoc index 84c9dd193491..23b126565983 100644 --- a/docs/UsingGradle.adoc +++ b/docs/UsingGradle.adoc @@ -41,7 +41,7 @@ When running a Gradle task, Gradle will try to figure out if the task needs runn == Creating the JAR file * *`shadowJar`* + -Creates the `addressbook.jar` file in the `build/jar` folder, _if the current file is outdated_. + +Creates the `catalogue.jar` file in the `build/jar` folder, _if the current file is outdated_. + e.g. `./gradlew shadowJar` **** diff --git a/docs/UsingTravis.adoc b/docs/UsingTravis.adoc index 78109fb1436c..149e13561467 100644 --- a/docs/UsingTravis.adoc +++ b/docs/UsingTravis.adoc @@ -61,7 +61,7 @@ image:travis_build.png[Travis build] Using this account, generate a personal access token https://github.com/settings/tokens/new[here]. + [NOTE] -Personal access tokens are like passwords so make sure you keep them secret! If the personal access token is leaked, please delete it and generate a new one. +Bookal access tokens are like passwords so make sure you keep them secret! If the personal access token is leaked, please delete it and generate a new one. + [NOTE] We use a new user account to generate the token for team projects to prevent team members from gaining access to other team members' repos. + diff --git a/docs/diagrams/HighLevelSequenceDiagrams.pptx b/docs/diagrams/HighLevelSequenceDiagrams.pptx index 38332090a79a..8678598d9e54 100644 Binary files a/docs/diagrams/HighLevelSequenceDiagrams.pptx and b/docs/diagrams/HighLevelSequenceDiagrams.pptx differ diff --git a/docs/diagrams/LogicComponentClassDiagram.pptx b/docs/diagrams/LogicComponentClassDiagram.pptx index e38e3de020a3..85bd64a60766 100644 Binary files a/docs/diagrams/LogicComponentClassDiagram.pptx and b/docs/diagrams/LogicComponentClassDiagram.pptx differ diff --git a/docs/diagrams/LogicComponentCommandClassDiagram.pptx b/docs/diagrams/LogicComponentCommandClassDiagram.pptx index 50631ba75bf0..13190e735559 100644 Binary files a/docs/diagrams/LogicComponentCommandClassDiagram.pptx and b/docs/diagrams/LogicComponentCommandClassDiagram.pptx differ diff --git a/docs/diagrams/LogicComponentSequenceDiagram.pptx b/docs/diagrams/LogicComponentSequenceDiagram.pptx index c5b6d5fad6e3..d3610661bb1b 100644 Binary files a/docs/diagrams/LogicComponentSequenceDiagram.pptx and b/docs/diagrams/LogicComponentSequenceDiagram.pptx differ diff --git a/docs/diagrams/ModelComponentClassDiagram.pptx b/docs/diagrams/ModelComponentClassDiagram.pptx index 418360e10b26..2789e2a97d61 100644 Binary files a/docs/diagrams/ModelComponentClassDiagram.pptx and b/docs/diagrams/ModelComponentClassDiagram.pptx differ diff --git a/docs/diagrams/StorageComponentClassDiagram.pptx b/docs/diagrams/StorageComponentClassDiagram.pptx index be29a9de7ca6..7b680a75855b 100644 Binary files a/docs/diagrams/StorageComponentClassDiagram.pptx and b/docs/diagrams/StorageComponentClassDiagram.pptx differ diff --git a/docs/diagrams/UiComponentClassDiagram.pptx b/docs/diagrams/UiComponentClassDiagram.pptx index 384d0a00e6ea..77e4ece19664 100644 Binary files a/docs/diagrams/UiComponentClassDiagram.pptx and b/docs/diagrams/UiComponentClassDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoExecuteUndoStackDiagram.pptx b/docs/diagrams/UndoRedoExecuteUndoStackDiagram.pptx index 12d5a1202331..337b43dc74ad 100644 Binary files a/docs/diagrams/UndoRedoExecuteUndoStackDiagram.pptx and b/docs/diagrams/UndoRedoExecuteUndoStackDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand1StackDiagram.pptx b/docs/diagrams/UndoRedoNewCommand1StackDiagram.pptx index 51b39f25b2b8..f046d9511faf 100644 Binary files a/docs/diagrams/UndoRedoNewCommand1StackDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand1StackDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand2StackDiagram.pptx b/docs/diagrams/UndoRedoNewCommand2StackDiagram.pptx index a505f5b3cafd..cb519d8f1765 100644 Binary files a/docs/diagrams/UndoRedoNewCommand2StackDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand2StackDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoNewCommand3StackDiagram.pptx b/docs/diagrams/UndoRedoNewCommand3StackDiagram.pptx index fa018c1b3d7b..5da7de5b08c3 100644 Binary files a/docs/diagrams/UndoRedoNewCommand3StackDiagram.pptx and b/docs/diagrams/UndoRedoNewCommand3StackDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoSequenceDiagram.pptx b/docs/diagrams/UndoRedoSequenceDiagram.pptx index 2d313a5d1654..a883b6f558e9 100644 Binary files a/docs/diagrams/UndoRedoSequenceDiagram.pptx and b/docs/diagrams/UndoRedoSequenceDiagram.pptx differ diff --git a/docs/diagrams/UndoRedoStartingStackDiagram.pptx b/docs/diagrams/UndoRedoStartingStackDiagram.pptx index 81a9b770cca8..3c390eb8dc6a 100644 Binary files a/docs/diagrams/UndoRedoStartingStackDiagram.pptx and b/docs/diagrams/UndoRedoStartingStackDiagram.pptx differ diff --git a/docs/images/DeleteBookSdforLogic.png b/docs/images/DeleteBookSdforLogic.png new file mode 100644 index 000000000000..b31c005ad204 Binary files /dev/null and b/docs/images/DeleteBookSdforLogic.png differ diff --git a/docs/images/DeletePersonSdForLogic.png b/docs/images/DeletePersonSdForLogic.png deleted file mode 100644 index 0462b9b7be6e..000000000000 Binary files a/docs/images/DeletePersonSdForLogic.png and /dev/null differ diff --git a/docs/images/LeKhangTai.jpg b/docs/images/LeKhangTai.jpg new file mode 100644 index 000000000000..f89da349a813 Binary files /dev/null and b/docs/images/LeKhangTai.jpg differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index e0b17014bb47..d434ffa1899a 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/LogicCommandClassDiagram.png b/docs/images/LogicCommandClassDiagram.png index 09d61cc1b401..11360aea7147 100644 Binary files a/docs/images/LogicCommandClassDiagram.png and b/docs/images/LogicCommandClassDiagram.png differ diff --git a/docs/images/ModelClassDiagram.png b/docs/images/ModelClassDiagram.png index 7ea5b4b42fb2..a9b1cc22ee89 100644 Binary files a/docs/images/ModelClassDiagram.png and b/docs/images/ModelClassDiagram.png differ diff --git a/docs/images/QiuHaohao.jpg b/docs/images/QiuHaohao.jpg new file mode 100644 index 000000000000..0fef1d393785 Binary files /dev/null and b/docs/images/QiuHaohao.jpg differ diff --git a/docs/images/SDforDeleteBook.png b/docs/images/SDforDeleteBook.png new file mode 100644 index 000000000000..abfe697f69a0 Binary files /dev/null and b/docs/images/SDforDeleteBook.png differ diff --git a/docs/images/SDforDeleteBookEventHandling.png b/docs/images/SDforDeleteBookEventHandling.png new file mode 100644 index 000000000000..9bdab99713b4 Binary files /dev/null and b/docs/images/SDforDeleteBookEventHandling.png differ diff --git a/docs/images/SDforDeletePerson.png b/docs/images/SDforDeletePerson.png deleted file mode 100644 index 1e836f10dcd8..000000000000 Binary files a/docs/images/SDforDeletePerson.png and /dev/null differ diff --git a/docs/images/SDforDeletePersonEventHandling.png b/docs/images/SDforDeletePersonEventHandling.png deleted file mode 100644 index ecec0805d32c..000000000000 Binary files a/docs/images/SDforDeletePersonEventHandling.png and /dev/null differ diff --git a/docs/images/StorageClassDiagram.png b/docs/images/StorageClassDiagram.png index 7a4cd2700cbf..7dae0f1c4523 100644 Binary files a/docs/images/StorageClassDiagram.png and b/docs/images/StorageClassDiagram.png differ diff --git a/docs/images/Ui_1.png b/docs/images/Ui_1.png new file mode 100644 index 000000000000..0c2319a0e4c8 Binary files /dev/null and b/docs/images/Ui_1.png differ diff --git a/docs/images/UndoRedoExecuteUndoStackDiagram.png b/docs/images/UndoRedoExecuteUndoStackDiagram.png index 2acedd2e1fa8..aa89126ad403 100644 Binary files a/docs/images/UndoRedoExecuteUndoStackDiagram.png and b/docs/images/UndoRedoExecuteUndoStackDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand1StackDiagram.png b/docs/images/UndoRedoNewCommand1StackDiagram.png index ed6327e90ceb..450250e87c0e 100644 Binary files a/docs/images/UndoRedoNewCommand1StackDiagram.png and b/docs/images/UndoRedoNewCommand1StackDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand2StackDiagram.png b/docs/images/UndoRedoNewCommand2StackDiagram.png index bee02191a56b..cb54c462580c 100644 Binary files a/docs/images/UndoRedoNewCommand2StackDiagram.png and b/docs/images/UndoRedoNewCommand2StackDiagram.png differ diff --git a/docs/images/UndoRedoNewCommand3StackDiagram.png b/docs/images/UndoRedoNewCommand3StackDiagram.png index bb125b2ccf4c..992695c64bdd 100644 Binary files a/docs/images/UndoRedoNewCommand3StackDiagram.png and b/docs/images/UndoRedoNewCommand3StackDiagram.png differ diff --git a/docs/images/UndoRedoSequenceDiagram.png b/docs/images/UndoRedoSequenceDiagram.png index b69611998e6c..bf44d79d78c5 100644 Binary files a/docs/images/UndoRedoSequenceDiagram.png and b/docs/images/UndoRedoSequenceDiagram.png differ diff --git a/docs/images/UndoRedoStartingStackDiagram.png b/docs/images/UndoRedoStartingStackDiagram.png index 37ae62e8046c..02140ea1ca7e 100644 Binary files a/docs/images/UndoRedoStartingStackDiagram.png and b/docs/images/UndoRedoStartingStackDiagram.png differ diff --git a/docs/images/UserInterfaceFinal.png b/docs/images/UserInterfaceFinal.png new file mode 100644 index 000000000000..5584e0f4011d Binary files /dev/null and b/docs/images/UserInterfaceFinal.png differ diff --git a/docs/images/chantiongley.jpg b/docs/images/chantiongley.jpg new file mode 100644 index 000000000000..37e7b19b6400 Binary files /dev/null and b/docs/images/chantiongley.jpg differ diff --git a/docs/images/khiayi.png b/docs/images/khiayi.png new file mode 100644 index 000000000000..aaef9dac713b Binary files /dev/null and b/docs/images/khiayi.png differ diff --git a/docs/images/victortardieu.jpg b/docs/images/victortardieu.jpg new file mode 100644 index 000000000000..a9ab2e6939bb Binary files /dev/null and b/docs/images/victortardieu.jpg differ diff --git a/docs/team/chantiongley.adoc b/docs/team/chantiongley.adoc new file mode 100644 index 000000000000..feb8caf70a60 --- /dev/null +++ b/docs/team/chantiongley.adoc @@ -0,0 +1,64 @@ += Chan Tiong Ley - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Catalogue + +--- + +== Overview + +* This is a desktop Catalogue application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). +* It is a Java application intended for librarians, students and guests using the library. +* We started the project based on an AddressBook sample application(https://github.com/nus-cs2103-AY1718S2/addressbook-level4) that is around 10KLoC. + +== Summary of contributions + +* *Major enhancement*: Added the ability to add and delete account Commands and the add/delete command parsers +** What it does: allows the user to add a new account into the existing account list and delete an account based on the username inside the account list. +** Justification: This feature improves the product significantly because a librarian can create borrowers (with privilege level of student) and create new administrators (with privilege level of librarian). The database of students will allow the students of National University of Singapore to borrow, return and reserve a book of their desire inside the library. +** Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. +** Credits: NIL + +* *Minor enhancement*: Improved the CLIsyntax to parse Account Level commands and created the ReserveCommandTests +** What it does: The improved CLISyntax parses the account name, account matriculation number, account Username, account Password and account privilege level to create an account in the system database. For the ReserveCommand tests, the ReserveCommandParserTest was created to check for invalid reserve command word tests and ReserveCommandTest checks if the command created can effectively reserve a "borrowed" book and return an invalid command if we try to reserve an available book. +** Justification: This feature allows the system to parse the parameters necessary to create the user account in the system. The reserve command tests ensures that the reserveCommand can properly serve its function to reserve a "borrowed" book. +** Highlights: This enhancement affects existing commands (addAccount Command and deleteAccount Command) and future commands such as an “edit Account Command”. The implementation required creation of new methods inside the model manager and account sub-classes. + +* *Code contributed*: https://github.com/CS2103JAN2018-T16-B1/main/blob/master/collated/functional/chantiongley.md[Functional] | https://github.com/CS2103JAN2018-T16-B1/main/blob/master/collated/test/chantiongley.md[Test] + +* *Other contributions*: + +** Project management: + +** Enhancements to existing features: + +** Documentation: Updated documentation on my enhancements. + +_{you can add/remove categories in the list above}_ + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=addaccount] + +include::../UserGuide.adoc[tag=deleteaccount] + + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + + +include::../DeveloperGuide.adoc[tag=addaccount] + +include::../DeveloperGuide.adoc[tag=deleteaccount] + +--- + diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc index 0dfa757e454b..d9eb6f7f7383 100644 --- a/docs/team/johndoe.adoc +++ b/docs/team/johndoe.adoc @@ -8,7 +8,7 @@ == Overview -AddressBook - Level 4 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. +AddressBook - Level 4 is a desktop catalogue application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. == Summary of contributions diff --git a/docs/team/lekhangtai.adoc b/docs/team/lekhangtai.adoc new file mode 100644 index 000000000000..9661b64335db --- /dev/null +++ b/docs/team/lekhangtai.adoc @@ -0,0 +1,52 @@ += Le Khang Tai - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Catalogue + +--- + +== Overview + +* This is a desktop Catalogue application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). +* It is a Java application intended for librarians, students and guests using the library. + +== Summary of contributions + +* *Major enhancement*: Added the ability to borrow and return a book, using `borrow` and `return` commands respectively. +** What it does: Allows the librarian to be able to change the availability status of the book when a student borrows/returns it. Only books that are available can be borrowed, and books that are borrowed or reserved can be returned. +** Justification: This feature is of fundamental importance to this application. The Catalogue App main purpose is to serve as a library application for the borrowing and returning of books for students. By implementing the borrow and return functions, the librarian will be able to keep track of the books availability. +** Highlights:This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it involves addition of new commands to the existing application. +** Credits: NIL + +* *Minor enhancement*: Added the ability to reserve a book, using `reserve` command. +** What it does: Allows the librarian to be able to change the availability status of the book to 'Reserved' when a student decides to reserve a book. Only borrowed book can be reserved. +** Justification: This feature is of secondary importance as compared to `borrow` and `return` command, as reserving a book should be a additional feature that the library provides, up to the discretion of the librarian. +** Highlights:This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it involves addition of new command to the existing application. + + +* *Code contributed*: https://github.com/CS2103JAN2018-T16-B1/main/blob/master/collated/functional/LeKhangTai.md[Functional] | https://github.com/CS2103JAN2018-T16-B1/main/blob/master/collated/test/LeKhangTai.md[Test] + +* *Other contributions*: + +** Documentation: Edited Logic Component Class and Sequence Diagrams. Also made appropriate changes to Logic Architecture Diagrams. + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=Borrow] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=Design] + +include::../DeveloperGuide.adoc[tag=logicComponent] + diff --git a/docs/team/qiuhaoze.adoc b/docs/team/qiuhaoze.adoc new file mode 100644 index 000000000000..1d26834dac6d --- /dev/null +++ b/docs/team/qiuhaoze.adoc @@ -0,0 +1,55 @@ += Qiu Haoze - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Catalogue + +--- + +== Overview + +* This is a desktop Catalogue application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). +* It is a Java application intended for librarians, students and guests using the library. +* We started the project based on an AddressBook sample application(https://github.com/nus-cs2103-AY1718S2/addressbook-level4) that is around 10KLoC. + +== Summary of contributions + +* *Major enhancement*: Create Account related models, implemented authentication(login/logout) and privilege and serialised file storage for UniqueAccountList +** What it does: It provides authentication mechanism for the app. +** Justification: This feature enables the app the authenticate users and give them different privilege. +** Highlights: This enhancement is related to all components of the program except UI, it required a careful analysis of the whole system before it is implemented. +** Credits: Name Class under model/account and its test is borrowed from AddressBook Level 4. + +* *Minor enhancement*: Changed browser panel to display a GoodReads page of the selected book(A page indicating no Internet if there is no Internet Connectivity) + +* *Code contributed*: https://github.com/CS2103JAN2018-T16-B1/main/blob/master/collated/functional/QiuHaohao.md[Functional] | https://github.com/CS2103JAN2018-T16-B1/main/blob/master/collated/test/QiuHaohao.md[Test] + +* *Other contributions*: + +** Project management: Managed release v1.3 - v1.5(4 releases) on GitHub, set up Travis and Coverall in team repo. + +** Documentation: Updated documentation of my enhancements + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=defaultaccount] + +include::../UserGuide.adoc[tag=loginout] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=storagepic] +include::../DeveloperGuide.adoc[tag=usecaseloginoutlistfind] +include::../DeveloperGuide.adoc[tag=manualtestloginoutlistfind] + + + diff --git a/docs/team/victortardieu.adoc b/docs/team/victortardieu.adoc new file mode 100644 index 000000000000..a0e7ede1a749 --- /dev/null +++ b/docs/team/victortardieu.adoc @@ -0,0 +1,116 @@ += Victor Tardieu - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Catalogue + +--- + +== Overview + +* This is a desktop Catalogue application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). +* It is a Java application intended for librarians, students and guests using the library. + +== Summary of contributions + +* *Major enhancement*: Added the ability to clear all the accounts of list of accounts +** What it does: Allows the user (logged in as a librarian) to clear in one click all the created accounts. +** Justification: This feature improves the product significantly because a user can quickly and efficiently remove all the accounts. It helps save time. +** Highlights: This enhancement requires an in-depth analysis of logic and storage in order to access the latter and apply a reset function with the logic component.This enhancement required an in-depth analysis of logic and storage in order to access the latter and apply a reset function with the logic component. +** Credits: N/A + +* *Minor enhancement*: Added an autocomplete function. +** What it does: After pressing TAB, it will add the prefixes for the required function. +** Justification: This feature significantly improve the product since it allows the user to type even faster and to remind him what to write for the function. +** Highlights: This enhancement is related to the Logic and UI component since the autocomplete function is in Logic but it prints out in UI. It requires to take time to understand how Logic and UI interacts. + +* *Code contributed*: https://github.com/CS2103JAN2018-T16-B1/main/blob/master/collated/functional/victortardieu.md[Functional] | https://github.com/CS2103JAN2018-T16-B1/main/blob/master/collated/test/victortardieu.md[Test] + +* *Other contributions*: + +** Project management: + +** Enhancements to existing features: + +UI enhancement --> Tag colors: Assign a new color each time a new tag is created so that the tags have different colors. + +Test enhancement --> Created the data files for the tests of the account functions + +** Documentation: + +Fixed the issues in User Guide from in-class testing session (see issues from bot). + +Added in User Guide the new functions (AddAccount, ClearAccount, DeleteAccount, Borrow, Return, Reserve, Logout) + +Wrote the User Stories, Use cases + +** Community: + +Assigned issues to different team members after testing session + +** Others: +Created the first book system. + + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + + +** Quick Start Section +Modify it to explain the new user interface. Write sentence for every section of the UI. +Create the new user interface picture. + +** Logout +Add the following information (format, example, tips, notes) on how to use this function. + +** AddAccount +Add the following information (format, example, tips, notes) on how to use this function. + +** DeleteAccount +Add the following information (format, example, tips, notes) on how to use this function. + +** ClearAccount +Add the following information (format, example, tips, notes) on how to use this function. + +** Borrow +Add the following information (format, example, tips, notes) on how to use this function. + +** Return +Add the following information (format, example, tips, notes) on how to use this function. + +** Renew +Add the following information (format, example, tips, notes) on how to use this function. + + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + + +**Introduction +include::../DeveloperGuide.adoc[tag=devguideintroduction] + +** UI component +Modify the UI diagram to make it about our project. + +** Appendix B +Change the scope of the project + +** Appendix C +Write the user stories +Rank them by user and then by importance (*** first and * last) + +** Appendix D +Write Use Cases for ClearCommand, ClearAccountCommand, HistoryCommand +include::../DeveloperGuide.adoc[tag=usecaseclearaclearhistory] + +** Appendix H +Write instructions for manual testing for ClearCommand, ClearAccountCommand, HistoryCommand +include::../DeveloperGuide.adoc[tag=manualtestclearaclearhistory] + + +== PROJECT: PowerPointLabs + +--- + +_{Optionally, you may include other projects in your portfolio.}_ diff --git a/docs/team/wongkhiayi.adoc b/docs/team/wongkhiayi.adoc new file mode 100644 index 000000000000..185d49e44072 --- /dev/null +++ b/docs/team/wongkhiayi.adoc @@ -0,0 +1,330 @@ += Wong Khia Yi - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: Catalogue + +--- + +== Overview + +* This is a desktop Catalogue application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). +* It is a Java application intended for librarians, students and guests using the library. +* We started the project based on an AddressBook sample application(https://github.com/nus-cs2103-AY1718S2/addressbook-level4) that is around 10KLoC. + +== Summary of contributions + +* *Major enhancement*: Added the model for catalogue for books and its relevant tests in command, model, system and xml. +** What it does: Allows the app to store books. +** Justification: This makes the program relevant to our project and rejects invalid inputs to keep the Catalogue clean. +** Highlights: This enhancement is related to all components of the program including the data files and tests, it required a careful analysis of the whole system before it is implemented. + +* *Minor enhancement*: Updated message outputs and sample data. + +* *Code contributed*: https://github.com/CS2103JAN2018-T16-B1/main/blob/master/collated/functional/khiayi.md[Functional] | https://github.com/CS2103JAN2018-T16-B1/main/blob/master/collated/test/khiayi.md[Test] + +** Project management: set up Travis and Coverall in team repo. + +** Documentation: Updated documentation of my enhancements + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +// tag::add[] +[[Add]] +=== Adding a book: `add` + + +Format: `add n/TITLE a/AUTHOR i/ISBN av/AVAIL [t/TAG]...` +Privilege Level: 2(Librarian) + +[TIP] +A book can have any number of tags (including 0) + +**** +* All of the fields must be provided. +* The provided field must be valid. +** TITLE must be alphanumeric. +** AUTHOR must be alphanumeric. +** ISBN must contain 13 numbers and be unique. +** AVAIL must be `Available`, `Borrowed` or `Reserved`. +**** + +Examples: + +* `add t/Animal Farm a/George Orwell i/9780736692427 av/Borrowed` +* `add t/Breaking Dawn tag/fiction av/Borrowed a/Stephenie Meyer i/9780316067928 tag/young adults` +// end::add[] + +// tag::edit[] +=== Editing a book : `edit` + +Edits an existing book in the catalogue. + +Format: `edit INDEX t/TITLE a/AUTHOR i/ISBN av/AVAIL tag/TAG...` + +Privilege Level: 2(Librarian) + +**** +* Edits the book at the specified `INDEX`. The index refers to the index number shown in the last book listing. The index *must be a positive integer* 1, 2, 3, ... +* At least one of the optional fields must be provided. +* The provided field must be valid. (Refer to <<Add>>) +* Existing values will be updated to the input values. +* When editing tags, the existing tags of the book will be removed i.e adding of tags is not cumulative. +* You can remove all the book's tags by typing `t/` without specifying any tags after it. +**** + +Examples: + +* `edit 1 i/9780736692426 av/Available` + +Edits the isbn number and availability author of the 1st book to be `9780736692426` and `Available` respectively. +* `edit 2 t/Breaking Dawn tag/` + +Edits the title of the 2nd book to be `Breaking Dawn` and clears all existing tags. +// end::edit[] + + +//tag::help[] +=== Viewing help : `help` + +Format: `help` + +Format: kbd:[F1] + +Privilege Level: 0(Guest) +//end::help[] + +// tag::select[] +=== Selecting a book : `select` + +Selects the book identified by the index number used in the last book listing. + +Format: `select INDEX` + +Privilege Level: 0(Guest) + +**** +* Selects the book and loads the Goodreads webpage of the book at the specified `INDEX` with internet connection. +* Selects the book and display error message specified `INDEX` without internet connection. +* The index refers to the index number shown in the most recent listing. +* The index *must be a positive integer* `1, 2, 3, ...` +**** + +Examples: + +* `list` + +`select 2` + +Selects the 2nd book in the catalogue. +* `find Breaking` + +`select 1` + +Selects the 1st book in the results of the `find` command. + +[TIP] +Press TAB after writing `select` to use the auto-complete function. + +Examples: + +* `select` --> (press TAB) --> `select 1` +// end::select[] + +// tag::commandsummary[] +== Command Summary + +[width="59%",cols="30%, 20%,<50%",options="header",] +|======================================================================= +|Privileges |Command |Format +|All |*Help* |`help` +|All |*Find Book* |`find KEYWORD [MORE_KEYWORDS]` + + e.g. `find Animal Dawn` +|All |*List Book* |`list` +|All |*Select Book* |`select INDEX` + + e.g.`select 2` +|Librarian |*Add Book* |`add t/TITLE a/AUTHOR i/ISBN av/AVAIL [tag/TAG]...` + + e.g. `add n/Animal Farm a/George Orwell i/9780736692427 av/Borrowed t/political t/satire` +|Librarian |*Delete Book* |`delete INDEX` + + e.g. `delete 3` +|Librarian |*Edit Book* |`edit INDEX [n/TITLE] [a/AUTHOR] [i/ISBN] [av/AVAIL] [t/TAG]...` + + e.g. `edit 2 n/Animal Farm e/Available` +|Librarian |*Borrow Book* |`borrow INDEX` + + e.g. `borrow 1` +|Librarian |*Return Book* |`return INDEX` + + e.g. `return 2` +|Librarian |*Reserve Book* |`reserve INDEX` + + e.g. `reserve 2` +|Librarian |*Clear Catalogue* |`clear` +|Librarian |*Add Account* |`addAccount n/NAME u/USERNAME p/PASSWORD m/MATRICNUMBER l/PRIVILEGE LEVEL` + + e.g. `addAccount n/Jack Morgan u/jacky p/jack123 m/A0123456J l/1` +|Librarian |*Delete Account* |`deleteAccount USERNAME` + + e.g. `deleteAccount tiongley` +|Librarian |*Clear Account List* |`cleara` +|Librarian |*History* |`history` +|Librarian |*Undo* |`undo` +|Librarian |*Redo* |`redo` +|======================================================================= +// end::commandsummary[] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== +//tag::model +[[Design-Model]] +=== Model component + +.Structure of the Model Component + +*API* : link:{repoURL}/blob/master/src/main/java/seedu/address/model/Model.java[`Model.java`] + +The `Model`, + +* stores a `UserPref` object that represents the user's preferences. +* stores the Catalogue data. +* stores the AccountList data +* exposes an unmodifiable `ObservableList<Book>` that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change. +* maintains the privilege level +* handles authentication +* does not depend on any of the other three components. +//end::model + +// tag::usecaseAddEditHelpSelect[] +=== Use case: Add book + +*MSS* + +1. User requests to add book with TITLE, AUTHOR, ISBN, AVAIL, [TAGS] +2. Catalogue adds book +3. Catalogue list books + +*Extensions* +[none] + +* 1a. The TITLE or AUTHOR or ISBN or AVAIL is not valid or ISBN is not unique. ++ +[none] +** 1a1. Catalogue shows an error message. ++ +Use case ends. + +[discrete] +=== Use case: Edit book + +*MSS* + +1. User requests to list books +2. Catalogue shows a list of books +3. User requests to edit a specific book in the list with [TITLE], [AUTHOR], [ISBN], [AVAIL], [TAGS] +4. Catalogue edit book +5. Catalogue list books ++ +Use case ends. + +*Extensions* +[none] + +* 2a. The list is empty ++ +Use case ends. +[none] +* 3a. The given index is invalid or the [TITLE], [AUTHOR], [ISBN], [AVAIL], [TAGS] is invalid ++ +** 3a1. Catalogue shows an error message. ++ +Use case ends. + +[discrete] +=== Use case: Help + +*MSS* + +1. User requests to help +2. Help window pops out ++ +Use case ends. + +[discrete] +=== Use case: Select + +*MSS* + +1. User requests to list books +2. Catalogue shows a list of books +3. User requests to select books with index +4. Catalogue shows Goodreads search of the selected book. ++ +Use case ends. + +*Extensions* +[none] + +* 2a. The list is empty ++ +Use case ends. +[none] +* 3a. The given index is invalid ++ +** 3a1. Catalogue shows an error message. ++ +[none] +* 4a. There is no internet access. ++ +** 4a1. Catalogue shows an error message. ++ +Use case ends. + +[discrete] +// end::usecaseAddEditHelpSelect[] + +// tag::manualtestAddEditHelpSelect[] +=== Adding a book + +. Adding a book + +.. Prerequisites: Log in using the `log in` command. +.. Test case: `add t/You a/Caroline Kepnes i/9781476785592 av/Borrowed` + + Expected: Book is added to the list at last index. Details of the added book shown in the status message. Timestamp in the status bar is updated. +.. Test case: `add t/You a/Caroline Kepnes i/9781476785592 av/Borrowed` again + + Expected: No book is added. Error details shown in the status message. Status bar remains the same. +.. Test case: `add t/Yo$ a/Caroline Kepnes i/0000000000001 av/Borrowed` again + + Expected: No book is added. Error details shown in the status message. Status bar remains the same. +.. Test case: `add t/You a/Caroline Kepne$ i/0000000000002 av/Borrowed` again + + Expected: No book is added. Error details shown in the status message. Status bar remains the same. +.. Test case: `add t/You a/Caroline Kepnes i/000000000000a av/Borrowed` again + + Expected: No book is added. Error details shown in the status message. Status bar remains the same. +.. Test case: `add t/You a/Caroline Kepnes i/0000000000004 av/not sure` again + + Expected: No book is added. Error details shown in the status message. Status bar remains the same. +.. Other incorrect add commands to try: `add`, `add t/` + Expected: Similar to previous. + +=== Editing a book + +. Editing a book while all books are listed + +.. Prerequisites: List all books using the `list` command. Multiple books in the list. +.. Test case: `edit 1 t/Animal` + + Expected: Title of first book is edit in the list. Details of the edited book shown in the status message. Timestamp in the status bar is updated. +.. Test case: `delete 0 t/Animal` + + Expected: No book is edited. Error details shown in the status message. Status bar remains the same. +.. Other incorrect delete commands to try: `edit t/Animal`, `edit x t/Animal` (where x is larger than the list size) + + Expected: Similar to previous. + +=== Help + +. Display Help window + +.. Test case: `help` + + Expected: Help window pops out. Action shown in the status message. Status bar remains the same. +.. Test case: 'F1' + + Expected: Help window pops out. Action shown in the status message. Status bar remains the same. + +=== Selecting a book + +. Selecting a book while all books are listed + +.. Prerequisites: List all books using the `list` command. Multiple books in the list. +.. Test case: `select 1` (With internet) + + Expected: First book is selected from the list. Action shown in the status message. Goodreads webpage of the book is loaded in the information section. Timestamp in the status bar is updated. +.. Test case: `select 1` (Without internet) + + Expected: First book is selected from the list. Action shown in the status message. Error message shown in information system. Timestamp in the status bar is updated. +.. Test case: `select 0` + + Expected: No book is selected. Error details shown in the status message. Status bar remains the same. +.. Other incorrect delete commands to try: `select`, `select x` (where x is larger than the list size) + + Expected: Similar to previous. +// end::manualtestAddEditHelpSelect[] diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index fa0800d55cb9..1d5ed6fd3a12 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -20,27 +20,34 @@ import seedu.address.commons.util.StringUtil; import seedu.address.logic.Logic; import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; +import seedu.address.model.Catalogue; import seedu.address.model.Model; import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyCatalogue; import seedu.address.model.UserPrefs; +import seedu.address.model.account.Account; +import seedu.address.model.account.UniqueAccountList; +import seedu.address.model.account.exceptions.DuplicateAccountException; import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; +import seedu.address.storage.AccountListStorage; +import seedu.address.storage.CatalogueStorage; import seedu.address.storage.JsonUserPrefsStorage; +import seedu.address.storage.SerialisedAccountListStorage; import seedu.address.storage.Storage; import seedu.address.storage.StorageManager; import seedu.address.storage.UserPrefsStorage; -import seedu.address.storage.XmlAddressBookStorage; +import seedu.address.storage.XmlCatalogueStorage; import seedu.address.ui.Ui; import seedu.address.ui.UiManager; + + /** * The main entry point to the application. */ public class MainApp extends Application { - public static final Version VERSION = new Version(0, 6, 0, true); + public static final Version VERSION = new Version(1, 5, 0, true); private static final Logger logger = LogsCenter.getLogger(MainApp.class); @@ -51,23 +58,28 @@ public class MainApp extends Application { protected Config config; protected UserPrefs userPrefs; + public static void main(String[] args) { + launch(args); + } @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing Catalogue ]==========================="); super.init(); config = initConfig(getApplicationParameter("config")); UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new XmlAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + CatalogueStorage catalogueStorage = new XmlCatalogueStorage(userPrefs.getCatalogueFilePath()); + AccountListStorage accountListStorage = new SerialisedAccountListStorage(userPrefs.getAccountListFilePath()); + storage = new StorageManager(catalogueStorage, userPrefsStorage, accountListStorage); initLogging(config); model = initModelManager(storage, userPrefs); + logic = new LogicManager(model); ui = new UiManager(logic, config, userPrefs); @@ -81,28 +93,58 @@ private String getApplicationParameter(String parameterName) { } /** - * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}. <br> - * The data from the sample address book will be used instead if {@code storage}'s address book is not found, - * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. + * Returns a {@code ModelManager} with the data from {@code storage}'s C\catalogue and {@code userPrefs}. <br> + * The data from the sample Catalogue will be used instead if {@code storage}'s Catalogue is not found, + * or an empty Catalogue will be used instead if errors occur when reading {@code storage}'s Catalogue. */ private Model initModelManager(Storage storage, UserPrefs userPrefs) { - Optional<ReadOnlyAddressBook> addressBookOptional; - ReadOnlyAddressBook initialData; + Optional<ReadOnlyCatalogue> catalogueOptional; + Optional<UniqueAccountList> accountListOptional; + ReadOnlyCatalogue initialData; + UniqueAccountList initlaAccountList; + //@@author QiuHaohao try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + catalogueOptional = storage.readCatalogue(); + if (!catalogueOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample Catalogue"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = catalogueOptional.orElseGet(SampleDataUtil::getSampleCatalogue); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Data file not in the correct format. Will be starting with an empty Catalogue"); + initialData = new Catalogue(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Problem while reading from the file. Will be starting with an empty Catalogue"); + initialData = new Catalogue(); } - return new ModelManager(initialData, userPrefs); + try { + accountListOptional = storage.readAccountList(); + if (!accountListOptional.isPresent()) { + logger.info("AccountList file not found. Will be starting with an accountList with only admin"); + initlaAccountList = new UniqueAccountList(); + } else { + initlaAccountList = accountListOptional.get(); + } + } catch (DataConversionException e) { + logger.warning("AccountList file not in the correct format. " + + "Will be starting with an accountList with only admin"); + initlaAccountList = new UniqueAccountList(); + } catch (IOException e) { + logger.warning("Problem while reading from the AccountList file. " + + "Will be starting with an accountList with only admin"); + System.out.print(e.getMessage()); + initlaAccountList = new UniqueAccountList(); + } + + try { + if (!initlaAccountList.contains(Account.createDefaultAdminAccount())) { + initlaAccountList.add(Account.createDefaultAdminAccount()); + } + } catch (DuplicateAccountException e) { + e.printStackTrace(); + } + return new ModelManager(initialData, initlaAccountList, userPrefs); + //@@author } private void initLogging(Config config) { @@ -132,7 +174,7 @@ protected Config initConfig(String configFilePath) { initializedConfig = configOptional.orElse(new Config()); } catch (DataConversionException e) { logger.warning("Config file at " + configFilePathUsed + " is not in the correct format. " - + "Using default config properties"); + + "Using default config properties"); initializedConfig = new Config(); } @@ -160,10 +202,10 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { initializedPrefs = prefsOptional.orElse(new UserPrefs()); } catch (DataConversionException e) { logger.warning("UserPrefs file at " + prefsFilePath + " is not in the correct format. " - + "Using default user prefs"); + + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty Catalogue"); initializedPrefs = new UserPrefs(); } @@ -183,13 +225,13 @@ private void initEventsCenter() { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting Catalogue " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping Catalogue ] ============================="); ui.stop(); try { storage.saveUserPrefs(userPrefs); @@ -205,8 +247,4 @@ public void handleExitAppRequestEvent(ExitAppRequestEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); this.stop(); } - - public static void main(String[] args) { - launch(args); - } } diff --git a/src/main/java/seedu/address/commons/core/ComponentManager.java b/src/main/java/seedu/address/commons/core/ComponentManager.java index 05a400773ae8..165311fe21c2 100644 --- a/src/main/java/seedu/address/commons/core/ComponentManager.java +++ b/src/main/java/seedu/address/commons/core/ComponentManager.java @@ -4,7 +4,7 @@ /** * Base class for *Manager classes - * + * <p> * Registers the class' event handlers in eventsCenter */ public abstract class ComponentManager { diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/address/commons/core/Config.java index 8f4d737d0e24..629be847158b 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/address/commons/core/Config.java @@ -11,7 +11,7 @@ public class Config { public static final String DEFAULT_CONFIG_FILE = "config.json"; // Config values customizable through config file - private String appTitle = "Address App"; + private String appTitle = "Catalogue App"; private Level logLevel = Level.INFO; private String userPrefsFilePath = "preferences.json"; @@ -51,8 +51,8 @@ public boolean equals(Object other) { Config o = (Config) other; return Objects.equals(appTitle, o.appTitle) - && Objects.equals(logLevel, o.logLevel) - && Objects.equals(userPrefsFilePath, o.userPrefsFilePath); + && Objects.equals(logLevel, o.logLevel) + && Objects.equals(userPrefsFilePath, o.userPrefsFilePath); } @Override diff --git a/src/main/java/seedu/address/commons/core/EventsCenter.java b/src/main/java/seedu/address/commons/core/EventsCenter.java index 799b976f7eb7..aaf69185469c 100644 --- a/src/main/java/seedu/address/commons/core/EventsCenter.java +++ b/src/main/java/seedu/address/commons/core/EventsCenter.java @@ -6,6 +6,7 @@ import seedu.address.commons.events.BaseEvent; + /** * Manages the event dispatching of the app. */ diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/address/commons/core/GuiSettings.java index 846d714375e4..b9ea0900c3a8 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/address/commons/core/GuiSettings.java @@ -1,6 +1,7 @@ package seedu.address.commons.core; import java.awt.Point; + import java.io.Serializable; import java.util.Objects; @@ -52,9 +53,9 @@ public boolean equals(Object other) { GuiSettings o = (GuiSettings) other; return Objects.equals(windowWidth, o.windowWidth) - && Objects.equals(windowHeight, o.windowHeight) - && Objects.equals(windowCoordinates.x, o.windowCoordinates.x) - && Objects.equals(windowCoordinates.y, o.windowCoordinates.y); + && Objects.equals(windowHeight, o.windowHeight) + && Objects.equals(windowCoordinates.x, o.windowCoordinates.x) + && Objects.equals(windowCoordinates.y, o.windowCoordinates.y); } @Override diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/address/commons/core/LogsCenter.java index 46e4c3aac468..95f826d5041a 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/address/commons/core/LogsCenter.java @@ -14,17 +14,17 @@ * Configures and manages loggers and handlers, including their logging level * Named {@link Logger}s can be obtained from this class<br> * These loggers have been configured to output messages to the console and a {@code .log} file by default, - * at the {@code INFO} level. A new {@code .log} file with a new numbering will be created after the log - * file reaches 5MB big, up to a maximum of 5 files.<br> + * at the {@code INFO} level. A new {@code .log} file with a new numbering will be created after the log + * file reaches 5MB big, up to a maximum of 5 files.<br> */ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "catalogue.log"; private static Level currentLogLevel = Level.INFO; - private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; private static ConsoleHandler consoleHandler; + private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); /** * Initializes with a custom log level (specified in the {@code config} object) @@ -99,6 +99,7 @@ private static void addFileHandler(Logger logger) { /** * Creates a {@code FileHandler} for the log file. + * * @throws IOException if there are problems opening the file. */ private static FileHandler createFileHandler() throws IOException { diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/address/commons/core/Messages.java index 1deb3a1e4695..87cc97753813 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/address/commons/core/Messages.java @@ -7,7 +7,7 @@ public class Messages { public static final String MESSAGE_UNKNOWN_COMMAND = "Unknown command"; public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; - public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; - public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_INVALID_BOOK_DISPLAYED_INDEX = "The book index provided is invalid"; + public static final String MESSAGE_BOOKS_LISTED_OVERVIEW = "%1$d books listed!"; } diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/address/commons/core/Version.java index e8fe0d3e6299..7937b29e03e2 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/seedu/address/commons/core/Version.java @@ -29,24 +29,9 @@ public Version(int major, int minor, int patch, boolean isEarlyAccess) { this.isEarlyAccess = isEarlyAccess; } - public int getMajor() { - return major; - } - - public int getMinor() { - return minor; - } - - public int getPatch() { - return patch; - } - - public boolean isEarlyAccess() { - return isEarlyAccess; - } - /** * Parses a version number string in the format V1.2.3. + * * @param versionString version number string * @return a Version object */ @@ -59,9 +44,25 @@ public static Version fromString(String versionString) throws IllegalArgumentExc } return new Version(Integer.parseInt(versionMatcher.group(1)), - Integer.parseInt(versionMatcher.group(2)), - Integer.parseInt(versionMatcher.group(3)), - versionMatcher.group(4) == null ? false : true); + Integer.parseInt(versionMatcher.group(2)), + Integer.parseInt(versionMatcher.group(3)), + versionMatcher.group(4) != null); + } + + public int getMajor() { + return major; + } + + public int getMinor() { + return minor; + } + + public int getPatch() { + return patch; + } + + public boolean isEarlyAccess() { + return isEarlyAccess; } @JsonValue diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/address/commons/core/index/Index.java index fd119bc926fd..1ab8bc09a30b 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/address/commons/core/index/Index.java @@ -2,7 +2,7 @@ /** * Represents a zero-based or one-based index. - * + * <p> * {@code Index} should be used right from the start (when parsing in a new user input), so that if the current * component wants to communicate with another component, it can send an {@code Index} to avoid having to know what * base the other component is using for its index. However, after receiving the {@code Index}, that component can @@ -23,14 +23,6 @@ private Index(int zeroBasedIndex) { this.zeroBasedIndex = zeroBasedIndex; } - public int getZeroBased() { - return zeroBasedIndex; - } - - public int getOneBased() { - return zeroBasedIndex + 1; - } - /** * Creates a new {@code Index} using a zero-based index. */ @@ -45,10 +37,18 @@ public static Index fromOneBased(int oneBasedIndex) { return new Index(oneBasedIndex - 1); } + public int getZeroBased() { + return zeroBasedIndex; + } + + public int getOneBased() { + return zeroBasedIndex + 1; + } + @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof Index // instanceof handles nulls - && this.zeroBasedIndex == ((Index) other).zeroBasedIndex); // state check + || (other instanceof Index // instanceof handles nulls + && this.zeroBasedIndex == ((Index) other).zeroBasedIndex); // state check } } diff --git a/src/main/java/seedu/address/commons/events/BaseEvent.java b/src/main/java/seedu/address/commons/events/BaseEvent.java index 85e71cbb6b62..fe6d85ab72ed 100644 --- a/src/main/java/seedu/address/commons/events/BaseEvent.java +++ b/src/main/java/seedu/address/commons/events/BaseEvent.java @@ -8,7 +8,7 @@ public abstract class BaseEvent { /** * All Events should have a clear unambiguous custom toString message so that feedback message creation * stays consistent and reusable. - * + * <p> * For example, the event manager post method will call any posted event's toString and print it in the console. */ public abstract String toString(); diff --git a/src/main/java/seedu/address/commons/events/model/AccountListChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AccountListChangedEvent.java new file mode 100644 index 000000000000..d6d00b403c6e --- /dev/null +++ b/src/main/java/seedu/address/commons/events/model/AccountListChangedEvent.java @@ -0,0 +1,22 @@ +//@@author QiuHaohao +package seedu.address.commons.events.model; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.account.UniqueAccountList; + +/** + * Indicates the AccountList in the model has changed + */ +public class AccountListChangedEvent extends BaseEvent { + + public final UniqueAccountList data; + + public AccountListChangedEvent(UniqueAccountList data) { + this.data = data; + } + + @Override + public String toString() { + return "Number of accounts: " + data.size(); + } +} diff --git a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java deleted file mode 100644 index 7db9b5c48ed6..000000000000 --- a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package seedu.address.commons.events.model; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.ReadOnlyAddressBook; - -/** Indicates the AddressBook in the model has changed*/ -public class AddressBookChangedEvent extends BaseEvent { - - public final ReadOnlyAddressBook data; - - public AddressBookChangedEvent(ReadOnlyAddressBook data) { - this.data = data; - } - - @Override - public String toString() { - return "number of persons " + data.getPersonList().size() + ", number of tags " + data.getTagList().size(); - } -} diff --git a/src/main/java/seedu/address/commons/events/model/CatalogueChangedEvent.java b/src/main/java/seedu/address/commons/events/model/CatalogueChangedEvent.java new file mode 100644 index 000000000000..5e91e22cf2d5 --- /dev/null +++ b/src/main/java/seedu/address/commons/events/model/CatalogueChangedEvent.java @@ -0,0 +1,21 @@ +package seedu.address.commons.events.model; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.model.ReadOnlyCatalogue; + +/** + * Indicates the Catalogue in the model has changed + */ +public class CatalogueChangedEvent extends BaseEvent { + + public final ReadOnlyCatalogue data; + + public CatalogueChangedEvent(ReadOnlyCatalogue data) { + this.data = data; + } + + @Override + public String toString() { + return "number of books " + data.getBookList().size() + ", number of tags " + data.getTagList().size(); + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/BookPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/BookPanelSelectionChangedEvent.java new file mode 100644 index 000000000000..e12a78cff46e --- /dev/null +++ b/src/main/java/seedu/address/commons/events/ui/BookPanelSelectionChangedEvent.java @@ -0,0 +1,26 @@ +package seedu.address.commons.events.ui; + +import seedu.address.commons.events.BaseEvent; +import seedu.address.ui.BookCard; + +/** + * Represents a selection change in the Book List Panel + */ +public class BookPanelSelectionChangedEvent extends BaseEvent { + + + private final BookCard newSelection; + + public BookPanelSelectionChangedEvent(BookCard newSelection) { + this.newSelection = newSelection; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public BookCard getNewSelection() { + return newSelection; + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java b/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java index 4fc32183f074..cf40af55ba67 100644 --- a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java +++ b/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java @@ -4,7 +4,7 @@ import seedu.address.commons.events.BaseEvent; /** - * Indicates a request to jump to the list of persons + * Indicates a request to jump to the list of books */ public class JumpToListRequestEvent extends BaseEvent { diff --git a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java b/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java deleted file mode 100644 index 56c1c9d987f1..000000000000 --- a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java +++ /dev/null @@ -1,26 +0,0 @@ -package seedu.address.commons.events.ui; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.ui.PersonCard; - -/** - * Represents a selection change in the Person List Panel - */ -public class PersonPanelSelectionChangedEvent extends BaseEvent { - - - private final PersonCard newSelection; - - public PersonPanelSelectionChangedEvent(PersonCard newSelection) { - this.newSelection = newSelection; - } - - @Override - public String toString() { - return this.getClass().getSimpleName(); - } - - public PersonCard getNewSelection() { - return newSelection; - } -} diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java index 19124db485c9..651ce2902085 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java @@ -13,7 +13,7 @@ public IllegalValueException(String message) { /** * @param message should contain relevant information on the failed constraint(s) - * @param cause of the main exception + * @param cause of the main exception */ public IllegalValueException(String message, Throwable cause) { super(message, cause); diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/seedu/address/commons/util/AppUtil.java index da90201dfd64..43c940170943 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/seedu/address/commons/util/AppUtil.java @@ -5,6 +5,7 @@ import javafx.scene.image.Image; import seedu.address.MainApp; + /** * A container for App specific utility functions */ @@ -21,7 +22,10 @@ public static Image getImage(String imagePath) { * @throws IllegalArgumentException if {@code condition} is false. */ public static void checkArgument(Boolean condition) { + if (!condition) { + + throw new IllegalArgumentException(); } } diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/seedu/address/commons/util/CollectionUtil.java index 52d209e778dd..6f9e006225bf 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/seedu/address/commons/util/CollectionUtil.java @@ -9,12 +9,15 @@ import java.util.Set; import java.util.stream.Stream; + /** * Utility methods related to Collections */ public class CollectionUtil { - /** @see #requireAllNonNull(Collection) */ + /** + * @see #requireAllNonNull(Collection) + */ public static void requireAllNonNull(Object... items) { requireNonNull(items); Stream.of(items).forEach(Objects::requireNonNull); diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/address/commons/util/FileUtil.java index 73575030d7dd..a4d40a79b2bc 100644 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ b/src/main/java/seedu/address/commons/util/FileUtil.java @@ -19,6 +19,7 @@ public static boolean isFileExists(File file) { /** * Creates a file if it does not exist along with its missing parent directories. + * * @throws IOException if the file or directory cannot be created. */ public static void createIfMissing(File file) throws IOException { @@ -82,6 +83,7 @@ public static void writeToFile(File file, String content) throws IOException { /** * Converts a string to a platform-specific file path + * * @param pathWithForwardSlash A String representing a file path but using '/' as the separator * @return {@code pathWithForwardSlash} but '/' replaced with {@code File.separator} */ diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/address/commons/util/JsonUtil.java index 1c629b0d4a16..3afa6ade0ac5 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/address/commons/util/JsonUtil.java @@ -22,6 +22,7 @@ import seedu.address.commons.core.LogsCenter; import seedu.address.commons.exceptions.DataConversionException; + /** * Converts a Java object instance to JSON and vice versa */ @@ -30,37 +31,38 @@ public class JsonUtil { private static final Logger logger = LogsCenter.getLogger(JsonUtil.class); private static ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules() - .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) - .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) - .registerModule(new SimpleModule("SimpleModule") - .addSerializer(Level.class, new ToStringSerializer()) - .addDeserializer(Level.class, new LevelDeserializer(Level.class))); + .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) + .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) + .registerModule(new SimpleModule("SimpleModule") + .addSerializer(Level.class, new ToStringSerializer()) + .addDeserializer(Level.class, new LevelDeserializer(Level.class))); static <T> void serializeObjectToJsonFile(File jsonFile, T objectToSerialize) throws IOException { FileUtil.writeToFile(jsonFile, toJsonString(objectToSerialize)); } static <T> T deserializeObjectFromJsonFile(File jsonFile, Class<T> classOfObjectToDeserialize) - throws IOException { + throws IOException { return fromJsonString(FileUtil.readFromFile(jsonFile), classOfObjectToDeserialize); } /** * Returns the Json object from the given file or {@code Optional.empty()} object if the file is not found. * If any values are missing from the file, default values will be used, as long as the file is a valid json file. - * @param filePath cannot be null. + * + * @param filePath cannot be null. * @param classOfObjectToDeserialize Json file has to correspond to the structure in the class given here. * @throws DataConversionException if the file format is not as expected. */ public static <T> Optional<T> readJsonFile( - String filePath, Class<T> classOfObjectToDeserialize) throws DataConversionException { + String filePath, Class<T> classOfObjectToDeserialize) throws DataConversionException { requireNonNull(filePath); File file = new File(filePath); if (!file.exists()) { - logger.info("Json file " + file + " not found"); + logger.info("Json file " + file + " not found"); return Optional.empty(); } @@ -79,6 +81,7 @@ public static <T> Optional<T> readJsonFile( /** * Saves the Json object to the specified file. * Overwrites existing file if it exists, creates a new file if it doesn't. + * * @param jsonFile cannot be null * @param filePath cannot be null * @throws IOException if there was an error during writing to the file @@ -93,6 +96,7 @@ public static <T> void saveJsonFile(T jsonFile, String filePath) throws IOExcept /** * Converts a given string representation of a JSON data to instance of a class + * * @param <T> The generic type to create an instance of * @return The instance of T with the specified values in the JSON string */ @@ -102,8 +106,9 @@ public static <T> T fromJsonString(String json, Class<T> instanceClass) throws I /** * Converts a given instance of a class into its JSON data string representation + * * @param instance The T object to be converted into the JSON string - * @param <T> The generic type to create an instance of + * @param <T> The generic type to create an instance of * @return JSON data representation of the given class instance, in string */ public static <T> String toJsonString(T instance) throws JsonProcessingException { @@ -120,7 +125,7 @@ protected LevelDeserializer(Class<?> vc) { } @Override - protected Level _deserialize(String value, DeserializationContext ctxt) throws IOException { + protected Level _deserialize(String value, DeserializationContext ctxt) { return getLoggingLevel(value); } @@ -128,7 +133,6 @@ protected Level _deserialize(String value, DeserializationContext ctxt) throws I * Gets the logging level that matches loggingLevelString * <p> * Returns null if there are no matches - * */ private Level getLoggingLevel(String loggingLevelString) { return Level.parse(loggingLevelString); diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/address/commons/util/StringUtil.java index 6e403c17c96e..4475c504ba0c 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/address/commons/util/StringUtil.java @@ -13,14 +13,15 @@ public class StringUtil { /** * Returns true if the {@code sentence} contains the {@code word}. - * Ignores case, but a full word match is required. - * <br>examples:<pre> + * Ignores case, but a full word match is required. + * <br>examples:<pre> * containsWordIgnoreCase("ABc def", "abc") == true * containsWordIgnoreCase("ABc def", "DEF") == true * containsWordIgnoreCase("ABc def", "AB") == false //not a full word match * </pre> + * * @param sentence cannot be null - * @param word cannot be null, cannot be empty, must be a single word + * @param word cannot be null, cannot be empty, must be a single word */ public static boolean containsWordIgnoreCase(String sentence, String word) { requireNonNull(sentence); @@ -33,7 +34,7 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { String preppedSentence = sentence; String[] wordsInPreppedSentence = preppedSentence.split("\\s+"); - for (String wordInSentence: wordsInPreppedSentence) { + for (String wordInSentence : wordsInPreppedSentence) { if (wordInSentence.equalsIgnoreCase(preppedWord)) { return true; } @@ -56,6 +57,7 @@ public static String getDetails(Throwable t) { * e.g. 1, 2, 3, ..., {@code Integer.MAX_VALUE} <br> * Will return false for any other non-null string input * e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters) + * * @throws NullPointerException if {@code s} is null. */ public static boolean isNonZeroUnsignedInteger(String s) { diff --git a/src/main/java/seedu/address/commons/util/XmlUtil.java b/src/main/java/seedu/address/commons/util/XmlUtil.java index 5f61738627cc..4edf0f59de9e 100644 --- a/src/main/java/seedu/address/commons/util/XmlUtil.java +++ b/src/main/java/seedu/address/commons/util/XmlUtil.java @@ -10,6 +10,7 @@ import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; + /** * Helps with reading from and writing to XML files. */ @@ -27,7 +28,7 @@ public class XmlUtil { */ @SuppressWarnings("unchecked") public static <T> T getDataFromFile(File file, Class<T> classToConvert) - throws FileNotFoundException, JAXBException { + throws FileNotFoundException, JAXBException { requireNonNull(file); requireNonNull(classToConvert); diff --git a/src/main/java/seedu/address/logic/ListElementPointer.java b/src/main/java/seedu/address/logic/ListElementPointer.java index ca4085d98a11..ea0d86ccab2e 100644 --- a/src/main/java/seedu/address/logic/ListElementPointer.java +++ b/src/main/java/seedu/address/logic/ListElementPointer.java @@ -61,6 +61,7 @@ private boolean isWithinBounds(int index) { /** * Returns the next element in the list and advances the cursor position. + * * @throws NoSuchElementException if there is no more next element in the list. */ public String next() { @@ -72,6 +73,7 @@ public String next() { /** * Returns the previous element in the list and moves the cursor position backwards. + * * @throws NoSuchElementException if there is no more previous element in the list. */ public String previous() { @@ -83,6 +85,7 @@ public String previous() { /** * Returns the current element in the list. + * * @throws NoSuchElementException if the list is empty. */ public String current() { diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/address/logic/Logic.java index 8b34b862039a..681bf2b1a793 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/address/logic/Logic.java @@ -4,7 +4,7 @@ import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Person; +import seedu.address.model.book.Book; /** * API of the Logic component @@ -12,16 +12,21 @@ public interface Logic { /** * Executes the command and returns the result. + * * @param commandText The command as entered by the user. * @return the result of the command execution. * @throws CommandException If an error occurs during command execution. - * @throws ParseException If an error occurs during parsing. + * @throws ParseException If an error occurs during parsing. */ CommandResult execute(String commandText) throws CommandException, ParseException; - /** Returns an unmodifiable view of the filtered list of persons */ - ObservableList<Person> getFilteredPersonList(); + /** + * Returns an unmodifiable view of the filtered list of books + */ + ObservableList<Book> getFilteredBookList(); - /** Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object */ + /** + * Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object + */ ListElementPointer getHistorySnapshot(); } diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java index 9f6846bdfc74..a8ec30d3aae8 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -5,37 +5,87 @@ import javafx.collections.ObservableList; import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; +import seedu.address.logic.commands.AddAccountCommand; +import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.BorrowCommand; import seedu.address.logic.commands.Command; import seedu.address.logic.commands.CommandResult; +import seedu.address.logic.commands.DeleteCommand; +import seedu.address.logic.commands.EditCommand; +import seedu.address.logic.commands.ReserveCommand; +import seedu.address.logic.commands.ReturnCommand; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; +import seedu.address.logic.parser.CatalogueParser; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.Model; -import seedu.address.model.person.Person; +import seedu.address.model.book.Book; /** * The main LogicManager of the app. */ + public class LogicManager extends ComponentManager implements Logic { private final Logger logger = LogsCenter.getLogger(LogicManager.class); private final Model model; + private final CommandHistory history; - private final AddressBookParser addressBookParser; + private final CatalogueParser catalogueParser; private final UndoRedoStack undoRedoStack; public LogicManager(Model model) { this.model = model; history = new CommandHistory(); - addressBookParser = new AddressBookParser(); + catalogueParser = new CatalogueParser(); undoRedoStack = new UndoRedoStack(); } + //@@author victortardieu + /** + * @param myString + * @return auto, the string that holds the autocomplete string of the chosen command + */ + public static String autoComplete(String myString) { + /** + * The auto string will hold the autocomplete string of the chosen command + */ + String auto = ""; + switch (myString) { + case AddCommand.COMMAND_WORD: + auto = "add t/ a/ i/ av/ tag/ "; + break; + case AddAccountCommand.COMMAND_WORD: + auto = "addAccount n/ m/ u/ p/ l/ "; + break; + case EditCommand.COMMAND_WORD: + auto = "edit 1 t/ a/ i/ av/ tag/ "; + break; + case DeleteCommand.COMMAND_WORD: + auto = "delete 1"; + break; + case BorrowCommand.COMMAND_WORD: + auto = "borrow 1"; + break; + case ReturnCommand.COMMAND_WORD: + auto = "return 1"; + break; + case ReserveCommand.COMMAND_WORD: + auto = "reserve 1"; + break; + default: + auto = myString; + } + return auto; + } + @Override public CommandResult execute(String commandText) throws CommandException, ParseException { logger.info("----------------[USER COMMAND][" + commandText + "]"); try { - Command command = addressBookParser.parseCommand(commandText); + Command command = catalogueParser.parseCommand(commandText); + if (!isPrivileged(command)) { + return new CommandResult(Command.MESSAGE_UNPRIVILEGED); + } command.setData(model, history, undoRedoStack); CommandResult result = command.execute(); undoRedoStack.push(command); @@ -46,12 +96,18 @@ public CommandResult execute(String commandText) throws CommandException, ParseE } @Override - public ObservableList<Person> getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList<Book> getFilteredBookList() { + return model.getFilteredBookList(); } @Override public ListElementPointer getHistorySnapshot() { return new ListElementPointer(history.getHistory()); } + //@@author + + //@@author QiuHaohao + protected boolean isPrivileged(Command command) { + return command.getPrivilegeLevel().compareTo(model.getPrivilegeLevel()) <= 0; + } } diff --git a/src/main/java/seedu/address/logic/UndoRedoStack.java b/src/main/java/seedu/address/logic/UndoRedoStack.java index ddb62ef0ea87..28a39e87ca37 100644 --- a/src/main/java/seedu/address/logic/UndoRedoStack.java +++ b/src/main/java/seedu/address/logic/UndoRedoStack.java @@ -13,6 +13,8 @@ */ public class UndoRedoStack { private Stack<UndoableCommand> undoStack; + + private Stack<UndoableCommand> redoStack; public UndoRedoStack() { @@ -84,6 +86,6 @@ public boolean equals(Object other) { // state check return undoStack.equals(stack.undoStack) - && redoStack.equals(stack.redoStack); + && redoStack.equals(stack.redoStack); } } diff --git a/src/main/java/seedu/address/logic/commands/AddAccountCommand.java b/src/main/java/seedu/address/logic/commands/AddAccountCommand.java new file mode 100644 index 000000000000..81289ec2d81a --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/AddAccountCommand.java @@ -0,0 +1,73 @@ +//@@author chantiongley +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MATRICNUMBER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIVILEGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.account.Account; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.account.exceptions.DuplicateAccountException; + +/** + * Adds an account + */ +public class AddAccountCommand extends UndoableCommand { + public static final String COMMAND_WORD = "addAccount"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds an account to the system. " + + "Parameters: " + + PREFIX_NAME + "NAME " + + PREFIX_USERNAME + "USERNAME " + + PREFIX_PASSWORD + "PASSWORD " + + PREFIX_MATRICNUMBER + "MATRICULATION NUMBER " + + PREFIX_PRIVILEGE + "PRIVILEGE LEVEL \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_NAME + "John Doe " + + PREFIX_USERNAME + "johndoe " + + PREFIX_PASSWORD + "johndoe123 " + + PREFIX_MATRICNUMBER + "A0123456B " + + PREFIX_PRIVILEGE + "1"; + + public static final String MESSAGE_SUCCESS = "New account added: %1$s"; + public static final String MESSAGE_DUPLICATE_ACCOUNT = "This account already exists in the system"; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_LIBRARIAN; + + private final Account toAdd; + + /** + * Creates an AddCommand to add the specified {@code Book} + */ + public AddAccountCommand(Account account) { + requireNonNull(account); + toAdd = account; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + try { + model.addAccount(toAdd); + return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); + } catch (DuplicateAccountException e) { + throw new CommandException(MESSAGE_DUPLICATE_ACCOUNT); + } + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } + + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } + +} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index c334710c0ea3..9de928df613e 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -1,59 +1,67 @@ +//@@author khiayi package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_AUTHOR; +import static seedu.address.logic.parser.CliSyntax.PREFIX_AVAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ISBN; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.person.Person; -import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.book.Book; +import seedu.address.model.book.exceptions.DuplicateBookException; + +//import static seedu.address.logic.parser.CliSyntax.; + /** - * Adds a person to the address book. + * Adds a book to the catalogue. */ public class AddCommand extends UndoableCommand { public static final String COMMAND_WORD = "add"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " - + "Parameters: " - + PREFIX_NAME + "NAME " - + PREFIX_PHONE + "PHONE " - + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " " - + PREFIX_NAME + "John Doe " - + PREFIX_PHONE + "98765432 " - + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " - + PREFIX_TAG + "friends " - + PREFIX_TAG + "owesMoney"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a book to the catalogue. " + + "Parameters: " + + PREFIX_TITLE + "TITLE " + + PREFIX_AUTHOR + "AUTHOR " + + PREFIX_ISBN + "ISBN " + + PREFIX_AVAIL + "AVAIL " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TITLE + "Animal Farm " + + PREFIX_AUTHOR + "George Orwell " + + PREFIX_ISBN + "9780736692427 " + + PREFIX_AVAIL + "Borrowed " + + PREFIX_TAG + "political " + + PREFIX_TAG + "satire "; + + public static final String MESSAGE_SUCCESS = "New book added: %1$s"; + public static final String MESSAGE_DUPLICATE_BOOK = "This book already exists in the catalogue"; - public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_LIBRARIAN; - private final Person toAdd; + private final Book toAdd; /** - * Creates an AddCommand to add the specified {@code Person} + * Creates an AddCommand to add the specified {@code Book} */ - public AddCommand(Person person) { - requireNonNull(person); - toAdd = person; + public AddCommand(Book book) { + requireNonNull(book); + toAdd = book; } @Override public CommandResult executeUndoableCommand() throws CommandException { requireNonNull(model); try { - model.addPerson(toAdd); + model.addBook(toAdd); return new CommandResult(String.format(MESSAGE_SUCCESS, toAdd)); - } catch (DuplicatePersonException e) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); + } catch (DuplicateBookException e) { + throw new CommandException(MESSAGE_DUPLICATE_BOOK); } } @@ -61,7 +69,13 @@ public CommandResult executeUndoableCommand() throws CommandException { @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof AddCommand // instanceof handles nulls - && toAdd.equals(((AddCommand) other).toAdd)); + || (other instanceof AddCommand // instanceof handles nulls + && toAdd.equals(((AddCommand) other).toAdd)); + } + + //@@author QiuHaohao + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; } } diff --git a/src/main/java/seedu/address/logic/commands/BorrowCommand.java b/src/main/java/seedu/address/logic/commands/BorrowCommand.java new file mode 100644 index 000000000000..1e0d82ba44da --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/BorrowCommand.java @@ -0,0 +1,111 @@ +package seedu.address.logic.commands; +//@@author LeKhangTai +import static java.util.Objects.requireNonNull; +import static seedu.address.model.book.Avail.BORROWED; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.book.Author; +import seedu.address.model.book.Avail; +import seedu.address.model.book.Book; +import seedu.address.model.book.Isbn; +import seedu.address.model.book.Title; +import seedu.address.model.book.exceptions.BookNotFoundException; +import seedu.address.model.tag.Tag; + +/** + * Borrows a book + */ +public class BorrowCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "borrow"; + public static final String MESSAGE_BORROW_BOOK_SUCCESS = "New book borrowed: %1$s"; + public static final String MESSAGE_FAILURE = "Book not available for borrowing!"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Borrows the book identified by the index number used in the last book listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_STUDENT; + + private final Index targetIndex; + + private Book bookToBorrow; + private Book borrowedBook; + + + public BorrowCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + /** + * @param bookToBorrow + * @return duplicated book with Borrowed Availability + */ + public Book createBorrowedBook(Book bookToBorrow) { + assert bookToBorrow != null; + + Title updatedTitle = bookToBorrow.getTitle(); + Isbn updatedIsbn = bookToBorrow.getIsbn(); + Avail updatedAvail = new Avail(BORROWED); + Author updatedAuthor = bookToBorrow.getAuthor(); + Set<Tag> updatedTags = bookToBorrow.getTags(); + + return new Book(updatedTitle, updatedAuthor, updatedIsbn, updatedAvail, updatedTags); + } + + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(model); + + try { + model.borrowBook(bookToBorrow, borrowedBook); + } catch (BookNotFoundException pnfe) { + throw new CommandException(MESSAGE_FAILURE); + } + return new CommandResult(String.format(MESSAGE_BORROW_BOOK_SUCCESS, bookToBorrow)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List<Book> lastShownList = model.getFilteredBookList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + } + bookToBorrow = lastShownList.get(targetIndex.getZeroBased()); + borrowedBook = createBorrowedBook(bookToBorrow); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BorrowCommand that = (BorrowCommand) o; + return Objects.equals(targetIndex, that.targetIndex) + && Objects.equals(bookToBorrow, that.bookToBorrow); + } + + @Override + public int hashCode() { + return Objects.hash(targetIndex, bookToBorrow); + } + + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} + diff --git a/src/main/java/seedu/address/logic/commands/ClearAccountCommand.java b/src/main/java/seedu/address/logic/commands/ClearAccountCommand.java new file mode 100644 index 000000000000..c125d91459b0 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ClearAccountCommand.java @@ -0,0 +1,42 @@ +//@@author victortardieu +package seedu.address.logic.commands; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.account.Account; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.account.UniqueAccountList; +import seedu.address.model.account.exceptions.DuplicateAccountException; + + +/** + * Checks if user is a librarian. If yes, it clears the list of accounts and logs out the current account. + */ +public class ClearAccountCommand extends UndoableCommand { + + /** + * + */ + public static final String COMMAND_WORD = "cleara"; + public static final String MESSAGE_SUCCESS = "AccountList has been cleared, and you are logged out!"; + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_LIBRARIAN; + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + UniqueAccountList blankList = new UniqueAccountList(); + try { + blankList.add(Account.createDefaultAdminAccount()); + } catch (DuplicateAccountException e) { + e.printStackTrace(); + } + model.resetAccount(blankList); + model.logout(); + return new CommandResult(MESSAGE_SUCCESS); + } + + public PrivilegeLevel getPrivilegeLevel() { + + return PRIVILEGE_LEVEL; + } +} +//@@author diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/address/logic/commands/ClearCommand.java index ceeb7ba913c6..2be03af9d6e3 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/address/logic/commands/ClearCommand.java @@ -2,21 +2,29 @@ import static java.util.Objects.requireNonNull; -import seedu.address.model.AddressBook; +import seedu.address.model.Catalogue; +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; /** - * Clears the address book. + * Clears the catalogue. */ public class ClearCommand extends UndoableCommand { public static final String COMMAND_WORD = "clear"; - public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; - + public static final String MESSAGE_SUCCESS = "Catalogue has been cleared!"; + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_LIBRARIAN; @Override public CommandResult executeUndoableCommand() { requireNonNull(model); - model.resetData(new AddressBook()); + model.resetData(new Catalogue()); return new CommandResult(MESSAGE_SUCCESS); } + + //@@author QiuHaohao + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } } diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/address/logic/commands/Command.java index 6580e0b51c90..81f0f6a5e719 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/address/logic/commands/Command.java @@ -5,23 +5,27 @@ import seedu.address.logic.UndoRedoStack; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; /** * Represents a command with hidden internal logic and the ability to be executed. */ public abstract class Command { + public static final String MESSAGE_UNPRIVILEGED = "You are not allowed to execute this command," + + " login and try again"; + protected Model model; protected CommandHistory history; protected UndoRedoStack undoRedoStack; /** - * Constructs a feedback message to summarise an operation that displayed a listing of persons. + * Constructs a feedback message to summarise an operation that displayed a listing of books. * * @param displaySize used to generate summary - * @return summary message for persons displayed + * @return summary message for books displayed */ - public static String getMessageForPersonListShownSummary(int displaySize) { - return String.format(Messages.MESSAGE_PERSONS_LISTED_OVERVIEW, displaySize); + public static String getMessageForBookListShownSummary(int displaySize) { + return String.format(Messages.MESSAGE_BOOKS_LISTED_OVERVIEW, displaySize); } /** @@ -30,6 +34,7 @@ public static String getMessageForPersonListShownSummary(int displaySize) { * @return feedback message of the operation result for display * @throws CommandException If an error occurs during command execution. */ + public abstract CommandResult execute() throws CommandException; /** @@ -40,4 +45,10 @@ public static String getMessageForPersonListShownSummary(int displaySize) { public void setData(Model model, CommandHistory history, UndoRedoStack undoRedoStack) { this.model = model; } + + + //@@author QiuHaohao + public PrivilegeLevel getPrivilegeLevel() { + return Model.PRIVILEGE_LEVEL_GUEST; + } } diff --git a/src/main/java/seedu/address/logic/commands/DeleteAccountCommand.java b/src/main/java/seedu/address/logic/commands/DeleteAccountCommand.java new file mode 100644 index 000000000000..41eac55dc11f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/DeleteAccountCommand.java @@ -0,0 +1,63 @@ +//@@author chantiongley +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.account.Account; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.account.Username; +import seedu.address.model.account.exceptions.AccountNotFoundException; + +/** + * Deletes a account using it's last displayed index from the account list + */ +public class DeleteAccountCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "deleteAccount"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Deletes the account identified by the name of the user stored in account list.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " John"; + public static final String MESSAGE_DELETE_ACCOUNT_SUCCESS = "Deleted Account: %1$s"; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_LIBRARIAN; + + private String username; + private Account accountToDelete; + + public DeleteAccountCommand(String username) { + requireNonNull(username); + this.username = username; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.deleteAccount(accountToDelete); + } catch (AccountNotFoundException anfe) { + throw new CommandException("Account does not exist"); + } + + return new CommandResult(String.format(MESSAGE_DELETE_ACCOUNT_SUCCESS, accountToDelete)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + accountToDelete = model.getAccountList().searchByUsername(new Username(username)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof DeleteAccountCommand // instanceof handles nulls + && accountToDelete.equals(((DeleteAccountCommand) other).accountToDelete)); + } + + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} + diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/address/logic/commands/DeleteCommand.java index b539d240001a..80a00a84b583 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/address/logic/commands/DeleteCommand.java @@ -8,26 +8,30 @@ import seedu.address.commons.core.Messages; import seedu.address.commons.core.index.Index; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.person.Person; -import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.book.Book; +import seedu.address.model.book.exceptions.BookNotFoundException; /** - * Deletes a person identified using it's last displayed index from the address book. + * Deletes a book identified using it's last displayed index from the catalogue. */ public class DeleteCommand extends UndoableCommand { public static final String COMMAND_WORD = "delete"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Deletes the person identified by the index number used in the last person listing.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; + + ": Deletes the book identified by the index number used in the last book listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; - public static final String MESSAGE_DELETE_PERSON_SUCCESS = "Deleted Person: %1$s"; + public static final String MESSAGE_DELETE_BOOK_SUCCESS = "Deleted Book: %1$s"; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_LIBRARIAN; private final Index targetIndex; - private Person personToDelete; + private Book bookToDelete; public DeleteCommand(Index targetIndex) { this.targetIndex = targetIndex; @@ -36,32 +40,38 @@ public DeleteCommand(Index targetIndex) { @Override public CommandResult executeUndoableCommand() { - requireNonNull(personToDelete); + requireNonNull(bookToDelete); try { - model.deletePerson(personToDelete); - } catch (PersonNotFoundException pnfe) { - throw new AssertionError("The target person cannot be missing"); + model.deleteBook(bookToDelete); + } catch (BookNotFoundException pnfe) { + throw new AssertionError("The target book cannot be missing"); } - return new CommandResult(String.format(MESSAGE_DELETE_PERSON_SUCCESS, personToDelete)); + return new CommandResult(String.format(MESSAGE_DELETE_BOOK_SUCCESS, bookToDelete)); } @Override protected void preprocessUndoableCommand() throws CommandException { - List<Person> lastShownList = model.getFilteredPersonList(); + List<Book> lastShownList = model.getFilteredBookList(); if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); } - personToDelete = lastShownList.get(targetIndex.getZeroBased()); + bookToDelete = lastShownList.get(targetIndex.getZeroBased()); } @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof DeleteCommand // instanceof handles nulls - && this.targetIndex.equals(((DeleteCommand) other).targetIndex) // state check - && Objects.equals(this.personToDelete, ((DeleteCommand) other).personToDelete)); + || (other instanceof DeleteCommand // instanceof handles nulls + && this.targetIndex.equals(((DeleteCommand) other).targetIndex) // state check + && Objects.equals(this.bookToDelete, ((DeleteCommand) other).bookToDelete)); + } + + //@@author QiuHaohao + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; } } diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/address/logic/commands/EditCommand.java index e6c3a3e034bc..f3ca92236701 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -1,12 +1,13 @@ +//@@author khiayi package seedu.address.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_AUTHOR; +import static seedu.address.logic.parser.CliSyntax.PREFIX_AVAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ISBN; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_BOOKS; import java.util.Collections; import java.util.HashSet; @@ -19,96 +20,100 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.util.CollectionUtil; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.book.Author; +import seedu.address.model.book.Avail; +import seedu.address.model.book.Book; +import seedu.address.model.book.Isbn; +import seedu.address.model.book.Title; +import seedu.address.model.book.exceptions.BookNotFoundException; +import seedu.address.model.book.exceptions.DuplicateBookException; import seedu.address.model.tag.Tag; /** - * Edits the details of an existing person in the address book. + * Edits the details of an existing book in the catalogue. */ public class EditCommand extends UndoableCommand { public static final String COMMAND_WORD = "edit"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " - + "by the index number used in the last person listing. " - + "Existing values will be overwritten by the input values.\n" - + "Parameters: INDEX (must be a positive integer) " - + "[" + PREFIX_NAME + "NAME] " - + "[" + PREFIX_PHONE + "PHONE] " - + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " - + "[" + PREFIX_TAG + "TAG]...\n" - + "Example: " + COMMAND_WORD + " 1 " - + PREFIX_PHONE + "91234567 " - + PREFIX_EMAIL + "johndoe@example.com"; - - public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the book identified " + + "by the index number used in the last book listing. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_TITLE + "TITLE] " + + "[" + PREFIX_AUTHOR + "AUTHOR] " + + "[" + PREFIX_ISBN + "ISBN] " + + "[" + PREFIX_AVAIL + "AVAIL] " + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " 1 " + + PREFIX_ISBN + "9780736692426 " + + PREFIX_AVAIL + "Borrowed"; + + public static final String MESSAGE_EDIT_BOOK_SUCCESS = "Edited Book: %1$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + public static final String MESSAGE_DUPLICATE_BOOK = "This book already exists in the catalogue."; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_LIBRARIAN; private final Index index; - private final EditPersonDescriptor editPersonDescriptor; + private final EditBookDescriptor editBookDescriptor; - private Person personToEdit; - private Person editedPerson; + private Book bookToEdit; + private Book editedBook; /** - * @param index of the person in the filtered person list to edit - * @param editPersonDescriptor details to edit the person with + * @param index of the book in the filtered book list to edit + * @param editBookDescriptor details to edit the book with */ - public EditCommand(Index index, EditPersonDescriptor editPersonDescriptor) { + public EditCommand(Index index, EditBookDescriptor editBookDescriptor) { requireNonNull(index); - requireNonNull(editPersonDescriptor); + requireNonNull(editBookDescriptor); this.index = index; - this.editPersonDescriptor = new EditPersonDescriptor(editPersonDescriptor); + this.editBookDescriptor = new EditBookDescriptor(editBookDescriptor); + } + + /** + * Creates and returns a {@code Book} with the details of {@code bookToEdit} + * edited with {@code editBookDescriptor}. + */ + private static Book createEditedBook(Book bookToEdit, EditBookDescriptor editBookDescriptor) { + assert bookToEdit != null; + + Title updatedTitle = editBookDescriptor.getTitle().orElse(bookToEdit.getTitle()); + Isbn updatedIsbn = editBookDescriptor.getIsbn().orElse(bookToEdit.getIsbn()); + Avail updatedAvail = editBookDescriptor.getAvail().orElse(bookToEdit.getAvail()); + Author updatedAuthor = editBookDescriptor.getAuthor().orElse(bookToEdit.getAuthor()); + Set<Tag> updatedTags = editBookDescriptor.getTags().orElse(bookToEdit.getTags()); + + return new Book(updatedTitle, updatedAuthor, updatedIsbn, updatedAvail, updatedTags); } @Override public CommandResult executeUndoableCommand() throws CommandException { try { - model.updatePerson(personToEdit, editedPerson); - } catch (DuplicatePersonException dpe) { - throw new CommandException(MESSAGE_DUPLICATE_PERSON); - } catch (PersonNotFoundException pnfe) { - throw new AssertionError("The target person cannot be missing"); - } - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - return new CommandResult(String.format(MESSAGE_EDIT_PERSON_SUCCESS, editedPerson)); + model.updateBook(bookToEdit, editedBook); + } catch (DuplicateBookException dpe) { + throw new CommandException(MESSAGE_DUPLICATE_BOOK); + } catch (BookNotFoundException pnfe) { + throw new AssertionError("The target book cannot be missing"); + } + model.updateFilteredBookList(PREDICATE_SHOW_ALL_BOOKS); + return new CommandResult(String.format(MESSAGE_EDIT_BOOK_SUCCESS, editedBook)); } @Override protected void preprocessUndoableCommand() throws CommandException { - List<Person> lastShownList = model.getFilteredPersonList(); + List<Book> lastShownList = model.getFilteredBookList(); if (index.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); } - personToEdit = lastShownList.get(index.getZeroBased()); - editedPerson = createEditedPerson(personToEdit, editPersonDescriptor); - } - - /** - * Creates and returns a {@code Person} with the details of {@code personToEdit} - * edited with {@code editPersonDescriptor}. - */ - private static Person createEditedPerson(Person personToEdit, EditPersonDescriptor editPersonDescriptor) { - assert personToEdit != null; - - Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); - Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); - Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); - Set<Tag> updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + bookToEdit = lastShownList.get(index.getZeroBased()); + editedBook = createEditedBook(bookToEdit, editBookDescriptor); } @Override @@ -126,32 +131,40 @@ public boolean equals(Object other) { // state check EditCommand e = (EditCommand) other; return index.equals(e.index) - && editPersonDescriptor.equals(e.editPersonDescriptor) - && Objects.equals(personToEdit, e.personToEdit); + && editBookDescriptor.equals(e.editBookDescriptor) + && Objects.equals(bookToEdit, e.bookToEdit); } + //@@author QiuHaohao + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } + //@@author + /** - * Stores the details to edit the person with. Each non-empty field value will replace the - * corresponding field value of the person. + * Stores the details to edit the book with. Each non-empty field value will replace the + * corresponding field value of the book. */ - public static class EditPersonDescriptor { - private Name name; - private Phone phone; - private Email email; - private Address address; + public static class EditBookDescriptor { + private Title title; + private Author author; + private Isbn isbn; + private Avail avail; private Set<Tag> tags; - public EditPersonDescriptor() {} + public EditBookDescriptor() { + } /** * Copy constructor. * A defensive copy of {@code tags} is used internally. */ - public EditPersonDescriptor(EditPersonDescriptor toCopy) { - setName(toCopy.name); - setPhone(toCopy.phone); - setEmail(toCopy.email); - setAddress(toCopy.address); + public EditBookDescriptor(EditBookDescriptor toCopy) { + setTitle(toCopy.title); + setAuthor(toCopy.author); + setIsbn(toCopy.isbn); + setAvail(toCopy.avail); setTags(toCopy.tags); } @@ -159,47 +172,39 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(this.name, this.phone, this.email, this.address, this.tags); + return CollectionUtil.isAnyNonNull(this.title, this.author, this.isbn, this.avail, this.tags); } - public void setName(Name name) { - this.name = name; + public Optional<Title> getTitle() { + return Optional.ofNullable(title); } - public Optional<Name> getName() { - return Optional.ofNullable(name); + public void setTitle(Title title) { + this.title = title; } - public void setPhone(Phone phone) { - this.phone = phone; + public Optional<Author> getAuthor() { + return Optional.ofNullable(author); } - public Optional<Phone> getPhone() { - return Optional.ofNullable(phone); + public void setAuthor(Author author) { + this.author = author; } - public void setEmail(Email email) { - this.email = email; + public Optional<Isbn> getIsbn() { + return Optional.ofNullable(isbn); } - public Optional<Email> getEmail() { - return Optional.ofNullable(email); + public void setIsbn(Isbn isbn) { + this.isbn = isbn; } - public void setAddress(Address address) { - this.address = address; + public Optional<Avail> getAvail() { + return Optional.ofNullable(avail); } - public Optional<Address> getAddress() { - return Optional.ofNullable(address); - } - - /** - * Sets {@code tags} to this object's {@code tags}. - * A defensive copy of {@code tags} is used internally. - */ - public void setTags(Set<Tag> tags) { - this.tags = (tags != null) ? new HashSet<>(tags) : null; + public void setAvail(Avail avail) { + this.avail = avail; } /** @@ -211,6 +216,14 @@ public Optional<Set<Tag>> getTags() { return (tags != null) ? Optional.of(Collections.unmodifiableSet(tags)) : Optional.empty(); } + /** + * Sets {@code tags} to this object's {@code tags}. + * A defensive copy of {@code tags} is used internally. + */ + public void setTags(Set<Tag> tags) { + this.tags = (tags != null) ? new HashSet<>(tags) : null; + } + @Override public boolean equals(Object other) { // short circuit if same object @@ -219,18 +232,18 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof EditPersonDescriptor)) { + if (!(other instanceof EditBookDescriptor)) { return false; } // state check - EditPersonDescriptor e = (EditPersonDescriptor) other; + EditBookDescriptor e = (EditBookDescriptor) other; - return getName().equals(e.getName()) - && getPhone().equals(e.getPhone()) - && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) - && getTags().equals(e.getTags()); + return getTitle().equals(e.getTitle()) + && getAuthor().equals(e.getAuthor()) + && getIsbn().equals(e.getIsbn()) + && getAvail().equals(e.getAvail()) + && getTags().equals(e.getTags()); } } } diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/address/logic/commands/ExitCommand.java index fbd1beb2966e..867c8e798100 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/address/logic/commands/ExitCommand.java @@ -2,6 +2,8 @@ import seedu.address.commons.core.EventsCenter; import seedu.address.commons.events.ui.ExitAppRequestEvent; +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; /** * Terminates the program. @@ -10,7 +12,9 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; - public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; + public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Catalogue as requested ..."; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_GUEST; @Override public CommandResult execute() { @@ -18,4 +22,9 @@ public CommandResult execute() { return new CommandResult(MESSAGE_EXIT_ACKNOWLEDGEMENT); } + //@@author QiuHaohao + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } } diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/address/logic/commands/FindCommand.java index b1e671f633d2..435432cd089d 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/address/logic/commands/FindCommand.java @@ -1,36 +1,46 @@ package seedu.address.logic.commands; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.book.TitleContainsKeywordsPredicate; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Finds and lists all books in catalogue whose title contains any of the argument keywords. * Keyword matching is case sensitive. */ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " - + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n" - + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" - + "Example: " + COMMAND_WORD + " alice bob charlie"; + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all books whose titles contain any of " + + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n" + + "Parameters: KEYWORD [MORE_KEYWORDS]...\n" + + "Example: " + COMMAND_WORD + " animal george"; - private final NameContainsKeywordsPredicate predicate; + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_GUEST; - public FindCommand(NameContainsKeywordsPredicate predicate) { + private final TitleContainsKeywordsPredicate predicate; + + public FindCommand(TitleContainsKeywordsPredicate predicate) { this.predicate = predicate; } @Override public CommandResult execute() { - model.updateFilteredPersonList(predicate); - return new CommandResult(getMessageForPersonListShownSummary(model.getFilteredPersonList().size())); + model.updateFilteredBookList(predicate); + return new CommandResult(getMessageForBookListShownSummary(model.getFilteredBookList().size())); } @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof FindCommand // instanceof handles nulls - && this.predicate.equals(((FindCommand) other).predicate)); // state check + || (other instanceof FindCommand // instanceof handles nulls + && this.predicate.equals(((FindCommand) other).predicate)); // state check + } + + //@@author QiuHaohao + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; } } diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/address/logic/commands/HelpCommand.java index 10febf6d9136..12114d0713a2 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/address/logic/commands/HelpCommand.java @@ -2,6 +2,8 @@ import seedu.address.commons.core.EventsCenter; import seedu.address.commons.events.ui.ShowHelpRequestEvent; +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; /** * Format full help instructions for every command for display. @@ -11,13 +13,21 @@ public class HelpCommand extends Command { public static final String COMMAND_WORD = "help"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" - + "Example: " + COMMAND_WORD; + + "Example: " + COMMAND_WORD; public static final String SHOWING_HELP_MESSAGE = "Opened help window."; + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_GUEST; + @Override public CommandResult execute() { EventsCenter.getInstance().post(new ShowHelpRequestEvent()); return new CommandResult(SHOWING_HELP_MESSAGE); } + + //@@author QiuHaohao + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } } diff --git a/src/main/java/seedu/address/logic/commands/HistoryCommand.java b/src/main/java/seedu/address/logic/commands/HistoryCommand.java index f87abee5511d..6dcdb4528ac9 100644 --- a/src/main/java/seedu/address/logic/commands/HistoryCommand.java +++ b/src/main/java/seedu/address/logic/commands/HistoryCommand.java @@ -8,6 +8,7 @@ import seedu.address.logic.CommandHistory; import seedu.address.logic.UndoRedoStack; import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; /** * Lists all the commands entered by user from the start of app launch. @@ -17,6 +18,7 @@ public class HistoryCommand extends Command { public static final String COMMAND_WORD = "history"; public static final String MESSAGE_SUCCESS = "Entered commands (from most recent to earliest):\n%1$s"; public static final String MESSAGE_NO_HISTORY = "You have not yet entered any commands."; + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_LIBRARIAN; @Override public CommandResult execute() { @@ -35,4 +37,10 @@ public void setData(Model model, CommandHistory history, UndoRedoStack undoRedoS requireNonNull(history); this.history = history; } + + //@@author QiuHaohao + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } } diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/address/logic/commands/ListCommand.java index 7b6463780824..ed10ad2185fd 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/address/logic/commands/ListCommand.java @@ -1,20 +1,31 @@ package seedu.address.logic.commands; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_BOOKS; + +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; /** - * Lists all persons in the address book to the user. + * Lists all books in the catalogue to the user. */ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; - public static final String MESSAGE_SUCCESS = "Listed all persons"; + public static final String MESSAGE_SUCCESS = "Listed all books"; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_GUEST; @Override public CommandResult execute() { - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredBookList(PREDICATE_SHOW_ALL_BOOKS); return new CommandResult(MESSAGE_SUCCESS); } + + //@@author QiuHaohao + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } } diff --git a/src/main/java/seedu/address/logic/commands/LoginCommand.java b/src/main/java/seedu/address/logic/commands/LoginCommand.java new file mode 100644 index 000000000000..dbddbeddf52f --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/LoginCommand.java @@ -0,0 +1,62 @@ +//@@author QiuHaohao +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; + +import seedu.address.model.Model; +import seedu.address.model.account.Credential; +import seedu.address.model.account.PrivilegeLevel; + +/** + * Logs in as student or librarian. + */ +public class LoginCommand extends Command { + public static final String COMMAND_WORD = "login"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Login as student or librarian.\n" + + "Parameters: USERNAME PASSWORD(both username and password should be at least 5 chars long)\n" + + "Example: " + COMMAND_WORD + " MyUsername MyPassword"; + + public static final String MESSAGE_LOGGED_IN_AS_STUTENT = "You are logged in as student"; + public static final String MESSAGE_LOGGED_IN_AS_LIBRARIAN = "You are logged in as librarian"; + public static final String MESSAGE_NOT_LOGGED_IN = "Wrong username/password, try again"; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_GUEST; + + + private final Credential credential; + + + public LoginCommand(String username, String password) { + requireNonNull(username); + requireNonNull(password); + this.credential = new Credential(username, password); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LoginCommand // instanceof handles nulls + && credential.equals(((LoginCommand) other).credential)); + } + + @Override + public CommandResult execute() { + PrivilegeLevel newPrivilegeLevel = model.authenticate(this.credential); + if (newPrivilegeLevel.equals(Model.PRIVILEGE_LEVEL_GUEST)) { + return new CommandResult(MESSAGE_NOT_LOGGED_IN); + } + if (newPrivilegeLevel.equals(Model.PRIVILEGE_LEVEL_STUDENT)) { + return new CommandResult(MESSAGE_LOGGED_IN_AS_STUTENT); + } + if (newPrivilegeLevel.equals(Model.PRIVILEGE_LEVEL_LIBRARIAN)) { + return new CommandResult(MESSAGE_LOGGED_IN_AS_LIBRARIAN); + } + return new CommandResult(MESSAGE_NOT_LOGGED_IN); + } + + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} diff --git a/src/main/java/seedu/address/logic/commands/LogoutCommand.java b/src/main/java/seedu/address/logic/commands/LogoutCommand.java new file mode 100644 index 000000000000..710b98e0b2f6 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/LogoutCommand.java @@ -0,0 +1,37 @@ +//@@author QiuHaohao +package seedu.address.logic.commands; + +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; + +/** + * Logs out as student or librarian. + */ + +public class LogoutCommand extends Command { + public static final String COMMAND_WORD = "logout"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Logout as student or librarian.\n" + + "Example: " + COMMAND_WORD; + + public static final String MESSAGE_LOGGED_OUT = "You are logged out."; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_STUDENT; + + @Override + public CommandResult execute() { + model.logout(); + return new CommandResult(MESSAGE_LOGGED_OUT); + } + + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof LogoutCommand); + } +} diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/address/logic/commands/RedoCommand.java index 7b99d0f372fc..18d7fccaa4a1 100644 --- a/src/main/java/seedu/address/logic/commands/RedoCommand.java +++ b/src/main/java/seedu/address/logic/commands/RedoCommand.java @@ -6,6 +6,7 @@ import seedu.address.logic.UndoRedoStack; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; /** * Redo the previously undone command. @@ -16,6 +17,8 @@ public class RedoCommand extends Command { public static final String MESSAGE_SUCCESS = "Redo success!"; public static final String MESSAGE_FAILURE = "No more commands to redo!"; + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_LIBRARIAN; + @Override public CommandResult execute() throws CommandException { requireAllNonNull(model, undoRedoStack); @@ -33,4 +36,10 @@ public void setData(Model model, CommandHistory commandHistory, UndoRedoStack un this.model = model; this.undoRedoStack = undoRedoStack; } + + //@@author QiuHaohao + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } } diff --git a/src/main/java/seedu/address/logic/commands/ReserveCommand.java b/src/main/java/seedu/address/logic/commands/ReserveCommand.java new file mode 100644 index 000000000000..a5985b8f97ea --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ReserveCommand.java @@ -0,0 +1,94 @@ +package seedu.address.logic.commands; +//@@author LeKhangTai +import static java.util.Objects.requireNonNull; +import static seedu.address.model.book.Avail.RESERVED; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.book.Author; +import seedu.address.model.book.Avail; +import seedu.address.model.book.Book; +import seedu.address.model.book.Isbn; +import seedu.address.model.book.Title; +import seedu.address.model.book.exceptions.BookNotFoundException; +import seedu.address.model.tag.Tag; + +/** + * Reserves a book + */ +public class ReserveCommand extends UndoableCommand { + public static final String COMMAND_WORD = "reserve"; + public static final String MESSAGE_RESERVE_BOOK_SUCCESS = "Book reserved: %1$s"; + public static final String MESSAGE_FAILURE = "Book not available for reserving!"; + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Reserves the book identified by the index number used in the last book listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_STUDENT; + + private final Index targetIndex; + + private Book bookToReserve; + private Book reservedBook; + + public ReserveCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + /** + * @param bookToReserve + * @return duplicated book with Reserved Availability + */ + private static Book createReservedBook(Book bookToReserve) { + assert bookToReserve != null; + + Title updatedTitle = bookToReserve.getTitle(); + Isbn updatedIsbn = bookToReserve.getIsbn(); + Avail updatedAvail = new Avail(RESERVED); + Author updatedAuthor = bookToReserve.getAuthor(); + Set<Tag> updatedTags = bookToReserve.getTags(); + + return new Book(updatedTitle, updatedAuthor, updatedIsbn, updatedAvail, updatedTags); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(bookToReserve); + try { + model.reserveBook(bookToReserve, reservedBook); + } catch (BookNotFoundException pnfe) { + throw new CommandException(MESSAGE_FAILURE); + } + return new CommandResult(String.format(MESSAGE_RESERVE_BOOK_SUCCESS, bookToReserve)); + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List<Book> lastShownList = model.getFilteredBookList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + } + bookToReserve = lastShownList.get(targetIndex.getZeroBased()); + reservedBook = createReservedBook(bookToReserve); + } + + @Override + public int hashCode() { + return Objects.hash(targetIndex, bookToReserve); + } + + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} + diff --git a/src/main/java/seedu/address/logic/commands/ReturnCommand.java b/src/main/java/seedu/address/logic/commands/ReturnCommand.java new file mode 100644 index 000000000000..aa3ca1c386b7 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ReturnCommand.java @@ -0,0 +1,102 @@ +package seedu.address.logic.commands; +//@@author LeKhangTai +import static java.util.Objects.requireNonNull; +import static seedu.address.model.book.Avail.AVAILABLE; + +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import seedu.address.commons.core.Messages; +import seedu.address.commons.core.index.Index; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.book.Author; +import seedu.address.model.book.Avail; +import seedu.address.model.book.Book; +import seedu.address.model.book.Isbn; +import seedu.address.model.book.Title; +import seedu.address.model.book.exceptions.BookNotFoundException; +import seedu.address.model.tag.Tag; + +/** + * Un-mark a borrowed book to make it available for borrowing + */ + +public class ReturnCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "return"; + + public static final String MESSAGE_RETURN_BOOK_SUCCESS = "Book is returned: %1$s"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Returns book whose title matches the input \n" + + "Parameters: INDEX (must be a positive integer) \n" + + "Example: " + COMMAND_WORD + " Harry Potter and the Half Blood Prince"; + + public static final String MESSAGE_BOOK_CANNOT_BE_RETURNED = "Book cannot be returned as it is already available"; + + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_LIBRARIAN; + + private final Index targetIndex; + + private Book bookToReturn; + private Book returnedBook; + + public ReturnCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + /** + * @param bookToBorrow + * @return duplicated book with Available Availability + */ + private static Book createReturnedBook(Book bookToBorrow) { + assert bookToBorrow != null; + + Title updatedTitle = bookToBorrow.getTitle(); + Isbn updatedIsbn = bookToBorrow.getIsbn(); + Avail updatedAvail = new Avail(AVAILABLE); + Author updatedAuthor = bookToBorrow.getAuthor(); + Set<Tag> updatedTags = bookToBorrow.getTags(); + + return new Book(updatedTitle, updatedAuthor, updatedIsbn, updatedAvail, updatedTags); + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(bookToReturn); + try { + model.returnBook(bookToReturn, returnedBook); + return new CommandResult(String.format(MESSAGE_RETURN_BOOK_SUCCESS, bookToReturn)); + } catch (BookNotFoundException pnfe) { + throw new CommandException(MESSAGE_BOOK_CANNOT_BE_RETURNED); + } + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List<Book> lastShownList = model.getFilteredBookList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); + } + + bookToReturn = lastShownList.get(targetIndex.getZeroBased()); + returnedBook = createReturnedBook(bookToReturn); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ReturnCommand // instanceof handles nulls + && this.targetIndex.equals(((ReturnCommand) other).targetIndex) // state check + && Objects.equals(this.bookToReturn, ((ReturnCommand) other).bookToReturn)); + } + + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} + diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/address/logic/commands/SelectCommand.java index 9e3840a9dde6..fbdedb8b9792 100644 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ b/src/main/java/seedu/address/logic/commands/SelectCommand.java @@ -7,21 +7,26 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.events.ui.JumpToListRequestEvent; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.person.Person; +import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.book.Book; + /** - * Selects a person identified using it's last displayed index from the address book. + * Selects a book identified using it's last displayed index from the catalogue. */ public class SelectCommand extends Command { public static final String COMMAND_WORD = "select"; public static final String MESSAGE_USAGE = COMMAND_WORD - + ": Selects the person identified by the index number used in the last person listing.\n" - + "Parameters: INDEX (must be a positive integer)\n" - + "Example: " + COMMAND_WORD + " 1"; + + ": Selects the book identified by the index number used in the last book listing.\n" + + "Parameters: INDEX (must be a positive integer)\n" + + "Example: " + COMMAND_WORD + " 1"; + + public static final String MESSAGE_SELECT_BOOK_SUCCESS = "Selected Book: %1$s"; - public static final String MESSAGE_SELECT_PERSON_SUCCESS = "Selected Person: %1$s"; + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_GUEST; private final Index targetIndex; @@ -32,21 +37,27 @@ public SelectCommand(Index targetIndex) { @Override public CommandResult execute() throws CommandException { - List<Person> lastShownList = model.getFilteredPersonList(); + List<Book> lastShownList = model.getFilteredBookList(); if (targetIndex.getZeroBased() >= lastShownList.size()) { - throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + throw new CommandException(Messages.MESSAGE_INVALID_BOOK_DISPLAYED_INDEX); } EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); - return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, targetIndex.getOneBased())); + return new CommandResult(String.format(MESSAGE_SELECT_BOOK_SUCCESS, targetIndex.getOneBased())); } @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof SelectCommand // instanceof handles nulls - && this.targetIndex.equals(((SelectCommand) other).targetIndex)); // state check + || (other instanceof SelectCommand // instanceof handles nulls + && this.targetIndex.equals(((SelectCommand) other).targetIndex)); // state check + } + + //@@author QiuHaohao + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; } } diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/address/logic/commands/UndoCommand.java index 1f3dcea8bbaa..008196269d56 100644 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ b/src/main/java/seedu/address/logic/commands/UndoCommand.java @@ -6,6 +6,7 @@ import seedu.address.logic.UndoRedoStack; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.model.Model; +import seedu.address.model.account.PrivilegeLevel; /** * Undo the previous {@code UndoableCommand}. @@ -16,6 +17,8 @@ public class UndoCommand extends Command { public static final String MESSAGE_SUCCESS = "Undo success!"; public static final String MESSAGE_FAILURE = "No more commands to undo!"; + public static final PrivilegeLevel PRIVILEGE_LEVEL = Model.PRIVILEGE_LEVEL_LIBRARIAN; + @Override public CommandResult execute() throws CommandException { requireAllNonNull(model, undoRedoStack); @@ -33,4 +36,10 @@ public void setData(Model model, CommandHistory commandHistory, UndoRedoStack un this.model = model; this.undoRedoStack = undoRedoStack; } + + //@@author QiuHaohao + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } } diff --git a/src/main/java/seedu/address/logic/commands/UndoableCommand.java b/src/main/java/seedu/address/logic/commands/UndoableCommand.java index c107ffcd9cb3..db76bea2e2f6 100644 --- a/src/main/java/seedu/address/logic/commands/UndoableCommand.java +++ b/src/main/java/seedu/address/logic/commands/UndoableCommand.java @@ -2,48 +2,49 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.address.model.Model.PREDICATE_SHOW_ALL_BOOKS; import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.Catalogue; +import seedu.address.model.ReadOnlyCatalogue; /** * Represents a command which can be undone and redone. */ public abstract class UndoableCommand extends Command { - private ReadOnlyAddressBook previousAddressBook; + private ReadOnlyCatalogue previousCatalogue; protected abstract CommandResult executeUndoableCommand() throws CommandException; /** - * Stores the current state of {@code model#addressBook}. + * Stores the current state of {@code model#catalogue}. */ - private void saveAddressBookSnapshot() { + private void saveCatalogueSnapshot() { requireNonNull(model); - this.previousAddressBook = new AddressBook(model.getAddressBook()); + this.previousCatalogue = new Catalogue(model.getCatalogue()); } /** * This method is called before the execution of {@code UndoableCommand}. * {@code UndoableCommand}s that require this preprocessing step should override this method. */ - protected void preprocessUndoableCommand() throws CommandException {} + protected void preprocessUndoableCommand() throws CommandException { + } /** - * Reverts the AddressBook to the state before this command - * was executed and updates the filtered person list to - * show all persons. + * Reverts the Catalogue to the state before this command + * was executed and updates the filtered book list to + * show all books. */ protected final void undo() { - requireAllNonNull(model, previousAddressBook); - model.resetData(previousAddressBook); - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + requireAllNonNull(model, previousCatalogue); + model.resetData(previousCatalogue); + model.updateFilteredBookList(PREDICATE_SHOW_ALL_BOOKS); } /** - * Executes the command and updates the filtered person - * list to show all persons. + * Executes the command and updates the filtered book + * list to show all books. */ protected final void redo() { requireNonNull(model); @@ -51,14 +52,14 @@ protected final void redo() { executeUndoableCommand(); } catch (CommandException ce) { throw new AssertionError("The command has been successfully executed previously; " - + "it should not fail now"); + + "it should not fail now"); } - model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + model.updateFilteredBookList(PREDICATE_SHOW_ALL_BOOKS); } @Override public final CommandResult execute() throws CommandException { - saveAddressBookSnapshot(); + saveCatalogueSnapshot(); preprocessUndoableCommand(); return executeUndoableCommand(); } diff --git a/src/main/java/seedu/address/logic/parser/AddAccountCommandParser.java b/src/main/java/seedu/address/logic/parser/AddAccountCommandParser.java new file mode 100644 index 000000000000..6b8488247494 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/AddAccountCommandParser.java @@ -0,0 +1,70 @@ +//@@author chantiongley +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.address.logic.parser.CliSyntax.PREFIX_MATRICNUMBER; +import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PASSWORD; +import static seedu.address.logic.parser.CliSyntax.PREFIX_PRIVILEGE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_USERNAME; + +import java.util.stream.Stream; + +import seedu.address.logic.commands.AddAccountCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.account.Account; +import seedu.address.model.account.Credential; +import seedu.address.model.account.MatricNumber; +import seedu.address.model.account.Name; +import seedu.address.model.account.Password; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.account.Username; +import seedu.address.model.account.exceptions.AccountNotFoundException; + +/** + * Parses input arguments and creates a new AddAccountCommand object + */ +public class AddAccountCommandParser implements Parser<AddAccountCommand> { + /** + * Returns true if none of the prefixes contains empty(@code Optional) value in the given + * (@code ArgumentMultimap). + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch((prefix -> argumentMultimap.getValue(prefix).isPresent())); + } + + /** + * + * Parses the given (@code String) of arguments in the context of the AddAccountCommand + * + * and returns an AddAccountCommand object for execution. + * + * + * + * @throws ParseException if the user input does not conform to the expected format + * + + */ + @Override + public AddAccountCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_USERNAME, PREFIX_PASSWORD, PREFIX_MATRICNUMBER, + PREFIX_PRIVILEGE); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_USERNAME, PREFIX_PASSWORD, PREFIX_MATRICNUMBER, + PREFIX_PRIVILEGE) || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddAccountCommand.MESSAGE_USAGE)); + } + try { + Name name = ParserUtil.parseAccountName(argMultimap.getValue(PREFIX_NAME).get()); + Username username = ParserUtil.parseAccountUsername(argMultimap.getValue(PREFIX_USERNAME).get()); + Password password = ParserUtil.parseAccountPassword(argMultimap.getValue(PREFIX_PASSWORD).get()); + Credential credential = new Credential(username.getUsername(), password.getPassword()); + MatricNumber matricNumber = ParserUtil.parseAccountMatricNumber + (argMultimap.getValue(PREFIX_MATRICNUMBER).get()); + PrivilegeLevel privilegeLevel = ParserUtil.parseAccountPrivilegeLevel + (argMultimap.getValue(PREFIX_PRIVILEGE).get()); + + Account account = new Account(name, credential, matricNumber, privilegeLevel); + + return new AddAccountCommand(account); + } catch (AccountNotFoundException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java index 3c729b388554..b669ddad776b 100644 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AddCommandParser.java @@ -1,11 +1,11 @@ package seedu.address.logic.parser; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_AUTHOR; +import static seedu.address.logic.parser.CliSyntax.PREFIX_AVAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ISBN; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; import java.util.Set; import java.util.stream.Stream; @@ -13,11 +13,11 @@ import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.logic.commands.AddCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; +import seedu.address.model.book.Author; +import seedu.address.model.book.Avail; +import seedu.address.model.book.Book; +import seedu.address.model.book.Isbn; +import seedu.address.model.book.Title; import seedu.address.model.tag.Tag; /** @@ -25,41 +25,42 @@ */ public class AddCommandParser implements Parser<AddCommand> { + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + /** * Parses the given {@code String} of arguments in the context of the AddCommand * and returns an AddCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public AddCommand parse(String args) throws ParseException { ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_TITLE, PREFIX_ISBN, PREFIX_AVAIL, PREFIX_AUTHOR, PREFIX_TAG); - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { + if (!arePrefixesPresent(argMultimap, PREFIX_TITLE, PREFIX_AUTHOR, PREFIX_ISBN, PREFIX_AVAIL) + || !argMultimap.getPreamble().isEmpty()) { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); } try { - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME)).get(); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE)).get(); - Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL)).get(); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS)).get(); + Title title = ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE)).get(); + Author author = ParserUtil.parseAuthor(argMultimap.getValue(PREFIX_AUTHOR)).get(); + Isbn isbn = ParserUtil.parseIsbn(argMultimap.getValue(PREFIX_ISBN)).get(); + Avail avail = ParserUtil.parseAvail(argMultimap.getValue(PREFIX_AVAIL)).get(); Set<Tag> tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - Person person = new Person(name, phone, email, address, tagList); + Book book = new Book(title, author, isbn, avail, tagList); - return new AddCommand(person); + return new AddCommand(book); } catch (IllegalValueException ive) { throw new ParseException(ive.getMessage(), ive); } } - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - } diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java index 954c8e18f8ea..5711b786037e 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java @@ -15,7 +15,9 @@ */ public class ArgumentMultimap { - /** Prefixes mapped to their respective arguments**/ + /** + * Prefixes mapped to their respective arguments + **/ private final Map<Prefix, List<String>> argMultimap = new HashMap<>(); /** diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java index a1bddbb6b979..7131578c9cd6 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java @@ -5,11 +5,11 @@ /** * Tokenizes arguments string of the form: {@code preamble <prefix>value <prefix>value ...}<br> - * e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.<br> + * e.g. {@code some preamble text t/ 11.00 t/12.00 k/ m/ July} where prefixes are {@code t/ k/ m/}.<br> * 1. An argument's value can be an empty string e.g. the value of {@code k/} in the above example.<br> * 2. Leading and trailing whitespaces of an argument value will be discarded.<br> * 3. An argument may be repeated and all its values will be accumulated e.g. the value of {@code t/} - * in the above example.<br> + * in the above example.<br> */ public class ArgumentTokenizer { @@ -19,7 +19,7 @@ public class ArgumentTokenizer { * * @param argsString Arguments string of the form: {@code preamble <prefix>value <prefix>value ...} * @param prefixes Prefixes to tokenize the arguments string with - * @return ArgumentMultimap object that maps prefixes to their arguments + * @return ArgumentMultimap object that maps prefixes to their arguments */ public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) { List<PrefixPosition> positions = findAllPrefixPositions(argsString, prefixes); @@ -31,7 +31,7 @@ public static ArgumentMultimap tokenize(String argsString, Prefix... prefixes) { * * @param argsString Arguments string of the form: {@code preamble <prefix>value <prefix>value ...} * @param prefixes Prefixes to find in the arguments string - * @return List of zero-based prefix positions in the given arguments string + * @return List of zero-based prefix positions in the given arguments string */ private static List<PrefixPosition> findAllPrefixPositions(String argsString, Prefix... prefixes) { List<PrefixPosition> positions = new ArrayList<>(); @@ -64,7 +64,7 @@ private static List<PrefixPosition> findPrefixPositions(String argsString, Prefi * {@code argsString} starting from index {@code fromIndex}. An occurrence * is valid if there is a whitespace before {@code prefix}. Returns -1 if no * such occurrence can be found. - * + * <p> * E.g if {@code argsString} = "e/hip/900", {@code prefix} = "p/" and * {@code fromIndex} = 0, this method returns -1 as there are no valid * occurrences of "p/" with whitespace before it. However, if @@ -74,7 +74,7 @@ private static List<PrefixPosition> findPrefixPositions(String argsString, Prefi private static int findPrefixPosition(String argsString, String prefix, int fromIndex) { int prefixIndex = argsString.indexOf(" " + prefix, fromIndex); return prefixIndex == -1 ? -1 - : prefixIndex + 1; // +1 as offset for whitespace + : prefixIndex + 1; // +1 as offset for whitespace } /** @@ -84,7 +84,7 @@ private static int findPrefixPosition(String argsString, String prefix, int from * * @param argsString Arguments string of the form: {@code preamble <prefix>value <prefix>value ...} * @param prefixPositions Zero-based positions of all prefixes in {@code argsString} - * @return ArgumentMultimap object that maps prefixes to their arguments + * @return ArgumentMultimap object that maps prefixes to their arguments */ private static ArgumentMultimap extractArguments(String argsString, List<PrefixPosition> prefixPositions) { @@ -116,8 +116,8 @@ private static ArgumentMultimap extractArguments(String argsString, List<PrefixP * The end position of the value is determined by {@code nextPrefixPosition}. */ private static String extractArgumentValue(String argsString, - PrefixPosition currentPrefixPosition, - PrefixPosition nextPrefixPosition) { + PrefixPosition currentPrefixPosition, + PrefixPosition nextPrefixPosition) { Prefix prefix = currentPrefixPosition.getPrefix(); int valueStartPos = currentPrefixPosition.getStartPosition() + prefix.getPrefix().length(); @@ -130,8 +130,8 @@ private static String extractArgumentValue(String argsString, * Represents a prefix's position in an arguments string. */ private static class PrefixPosition { - private int startPosition; private final Prefix prefix; + private int startPosition; PrefixPosition(Prefix prefix, int startPosition) { this.prefix = prefix; diff --git a/src/main/java/seedu/address/logic/parser/BorrowCommandParser.java b/src/main/java/seedu/address/logic/parser/BorrowCommandParser.java new file mode 100644 index 000000000000..ed6d7ab88d13 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/BorrowCommandParser.java @@ -0,0 +1,31 @@ +package seedu.address.logic.parser; +//@@author LeKhangTai +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.BorrowCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and create a new BorrowCommand object + */ + +public class BorrowCommandParser implements Parser<BorrowCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the BorrowCommand + * and returns an BorrowCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public BorrowCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new BorrowCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, BorrowCommand.MESSAGE_USAGE)); + } + } +} + diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/CatalogueParser.java similarity index 70% rename from src/main/java/seedu/address/logic/parser/AddressBookParser.java rename to src/main/java/seedu/address/logic/parser/CatalogueParser.java index b7d57f5db86a..665c835ab4f9 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/CatalogueParser.java @@ -6,9 +6,13 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import seedu.address.logic.commands.AddAccountCommand; import seedu.address.logic.commands.AddCommand; +import seedu.address.logic.commands.BorrowCommand; +import seedu.address.logic.commands.ClearAccountCommand; import seedu.address.logic.commands.ClearCommand; import seedu.address.logic.commands.Command; +import seedu.address.logic.commands.DeleteAccountCommand; import seedu.address.logic.commands.DeleteCommand; import seedu.address.logic.commands.EditCommand; import seedu.address.logic.commands.ExitCommand; @@ -16,7 +20,11 @@ import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.HistoryCommand; import seedu.address.logic.commands.ListCommand; +import seedu.address.logic.commands.LoginCommand; +import seedu.address.logic.commands.LogoutCommand; import seedu.address.logic.commands.RedoCommand; +import seedu.address.logic.commands.ReserveCommand; +import seedu.address.logic.commands.ReturnCommand; import seedu.address.logic.commands.SelectCommand; import seedu.address.logic.commands.UndoCommand; import seedu.address.logic.parser.exceptions.ParseException; @@ -24,7 +32,7 @@ /** * Parses user input. */ -public class AddressBookParser { +public class CatalogueParser { /** * Used for initial separation of command word and args. @@ -84,6 +92,30 @@ public Command parseCommand(String userInput) throws ParseException { case RedoCommand.COMMAND_WORD: return new RedoCommand(); + case LoginCommand.COMMAND_WORD: + return new LoginCommandParser().parse(arguments); + + case LogoutCommand.COMMAND_WORD: + return new LogoutCommand(); + + case AddAccountCommand.COMMAND_WORD: + return new AddAccountCommandParser().parse(arguments); + + case BorrowCommand.COMMAND_WORD: + return new BorrowCommandParser().parse(arguments); + + case ReturnCommand.COMMAND_WORD: + return new ReturnCommandParser().parse(arguments); + + case ReserveCommand.COMMAND_WORD: + return new ReserveCommandParser().parse(arguments); + + case ClearAccountCommand.COMMAND_WORD: + return new ClearAccountCommand(); + + case DeleteAccountCommand.COMMAND_WORD: + return new DeleteAccountCommandParser().parse(arguments); + default: throw new ParseException(MESSAGE_UNKNOWN_COMMAND); } diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java index 75b1a9bf1190..71f1b31b3f53 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -6,10 +6,16 @@ public class CliSyntax { /* Prefix definitions */ + public static final Prefix PREFIX_TITLE = new Prefix("t/"); + public static final Prefix PREFIX_AUTHOR = new Prefix("a/"); + public static final Prefix PREFIX_ISBN = new Prefix("i/"); + public static final Prefix PREFIX_AVAIL = new Prefix("av/"); + public static final Prefix PREFIX_TAG = new Prefix("tag/"); + public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); - public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_USERNAME = new Prefix("u/"); + public static final Prefix PREFIX_MATRICNUMBER = new Prefix("m/"); + public static final Prefix PREFIX_PASSWORD = new Prefix("p/"); + public static final Prefix PREFIX_PRIVILEGE = new Prefix("l/"); } diff --git a/src/main/java/seedu/address/logic/parser/DeleteAccountCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteAccountCommandParser.java new file mode 100644 index 000000000000..e6611aceb1ca --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/DeleteAccountCommandParser.java @@ -0,0 +1,31 @@ +//@@author chantiongley +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.DeleteAccountCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and creates a new DeleteAccountCommand object + */ +public class DeleteAccountCommandParser implements Parser<DeleteAccountCommand> { + /** + * Parses input arguments and creates a new DeleteAccountCommand object + * <p> + * /** + * Parses the given {@code String} of arguments in the context of the DeleteAccountCommand + * and returns an DeleteAccountCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + + public DeleteAccountCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + if (trimmedArgs.isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteAccountCommand.MESSAGE_USAGE)); + } else { + return new DeleteAccountCommand(trimmedArgs); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java index fe9c1653850e..f3d2386f8f46 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java @@ -15,6 +15,7 @@ public class DeleteCommandParser implements Parser<DeleteCommand> { /** * Parses the given {@code String} of arguments in the context of the DeleteCommand * and returns an DeleteCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public DeleteCommand parse(String args) throws ParseException { @@ -23,7 +24,7 @@ public DeleteCommand parse(String args) throws ParseException { return new DeleteCommand(index); } catch (IllegalValueException ive) { throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); + String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); } } diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/address/logic/parser/EditCommandParser.java index c9cdbed26cf1..6aecd086acd6 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/EditCommandParser.java @@ -2,11 +2,11 @@ import static java.util.Objects.requireNonNull; import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.address.logic.parser.CliSyntax.PREFIX_AUTHOR; +import static seedu.address.logic.parser.CliSyntax.PREFIX_AVAIL; +import static seedu.address.logic.parser.CliSyntax.PREFIX_ISBN; import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.address.logic.parser.CliSyntax.PREFIX_TITLE; import java.util.Collection; import java.util.Collections; @@ -16,7 +16,7 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.address.logic.commands.EditCommand.EditBookDescriptor; import seedu.address.logic.parser.exceptions.ParseException; import seedu.address.model.tag.Tag; @@ -28,12 +28,13 @@ public class EditCommandParser implements Parser<EditCommand> { /** * Parses the given {@code String} of arguments in the context of the EditCommand * and returns an EditCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public EditCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_TITLE, PREFIX_ISBN, PREFIX_AVAIL, PREFIX_AUTHOR, PREFIX_TAG); Index index; @@ -43,22 +44,22 @@ public EditCommand parse(String args) throws ParseException { throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE)); } - EditPersonDescriptor editPersonDescriptor = new EditPersonDescriptor(); + EditBookDescriptor editBookDescriptor = new EditBookDescriptor(); try { - ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME)).ifPresent(editPersonDescriptor::setName); - ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE)).ifPresent(editPersonDescriptor::setPhone); - ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL)).ifPresent(editPersonDescriptor::setEmail); - ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS)).ifPresent(editPersonDescriptor::setAddress); - parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); + ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE)).ifPresent(editBookDescriptor::setTitle); + ParserUtil.parseIsbn(argMultimap.getValue(PREFIX_ISBN)).ifPresent(editBookDescriptor::setIsbn); + ParserUtil.parseAvail(argMultimap.getValue(PREFIX_AVAIL)).ifPresent(editBookDescriptor::setAvail); + ParserUtil.parseAuthor(argMultimap.getValue(PREFIX_AUTHOR)).ifPresent(editBookDescriptor::setAuthor); + parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editBookDescriptor::setTags); } catch (IllegalValueException ive) { throw new ParseException(ive.getMessage(), ive); } - if (!editPersonDescriptor.isAnyFieldEdited()) { + if (!editBookDescriptor.isAnyFieldEdited()) { throw new ParseException(EditCommand.MESSAGE_NOT_EDITED); } - return new EditCommand(index, editPersonDescriptor); + return new EditCommand(index, editBookDescriptor); } /** diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/address/logic/parser/FindCommandParser.java index b186a967cb94..5a35495be354 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/FindCommandParser.java @@ -6,7 +6,7 @@ import seedu.address.logic.commands.FindCommand; import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.address.model.book.TitleContainsKeywordsPredicate; /** * Parses input arguments and creates a new FindCommand object @@ -16,18 +16,19 @@ public class FindCommandParser implements Parser<FindCommand> { /** * Parses the given {@code String} of arguments in the context of the FindCommand * and returns an FindCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public FindCommand parse(String args) throws ParseException { String trimmedArgs = args.trim(); if (trimmedArgs.isEmpty()) { throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); + String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE)); } - String[] nameKeywords = trimmedArgs.split("\\s+"); + String[] titleKeywords = trimmedArgs.split("\\s+"); - return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords))); + return new FindCommand(new TitleContainsKeywordsPredicate(Arrays.asList(titleKeywords))); } } diff --git a/src/main/java/seedu/address/logic/parser/LoginCommandParser.java b/src/main/java/seedu/address/logic/parser/LoginCommandParser.java new file mode 100644 index 000000000000..5db22be7301f --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/LoginCommandParser.java @@ -0,0 +1,36 @@ +//@@author QiuHaohao +package seedu.address.logic.parser; + +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.logic.commands.LoginCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.account.Password; +import seedu.address.model.account.Username; + +/** + * Parses input arguments and creates a new LoginCommand object + */ +public class LoginCommandParser implements Parser<LoginCommand> { + /** + * Parses the given {@code String} of arguments in the context of the LoginCommand + * and returns an LoginCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public LoginCommand parse(String args) throws ParseException { + String trimmedArgs = args.trim(); + String[] nameKeywords = trimmedArgs.split("\\s+"); + if (nameKeywords.length != 2 + || !Username.isValidUsername(nameKeywords[0]) + || !Password.isValidPassword(nameKeywords[1])) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, LoginCommand.MESSAGE_USAGE)); + } + + String username = nameKeywords[0]; + String password = nameKeywords[1]; + + return new LoginCommand(username, password); + } +} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/address/logic/parser/Parser.java index d6551ad8e3ff..ce644a9c6fdc 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/seedu/address/logic/parser/Parser.java @@ -10,6 +10,7 @@ public interface Parser<T extends Command> { /** * Parses {@code userInput} into a command and returns it. + * * @throws ParseException if {@code userInput} does not conform the expected format */ T parse(String userInput) throws ParseException; diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java index 5d6d4ae3f7b1..b87ef1ec6aa8 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -10,10 +10,16 @@ import seedu.address.commons.core.index.Index; import seedu.address.commons.exceptions.IllegalValueException; import seedu.address.commons.util.StringUtil; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; +import seedu.address.model.account.MatricNumber; +import seedu.address.model.account.Name; +import seedu.address.model.account.Password; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.account.Username; +import seedu.address.model.account.exceptions.AccountNotFoundException; +import seedu.address.model.book.Author; +import seedu.address.model.book.Avail; +import seedu.address.model.book.Isbn; +import seedu.address.model.book.Title; import seedu.address.model.tag.Tag; /** @@ -33,6 +39,7 @@ public class ParserUtil { /** * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be * trimmed. + * * @throws IllegalValueException if the specified index is invalid (not non-zero unsigned integer). */ public static Index parseIndex(String oneBasedIndex) throws IllegalValueException { @@ -42,18 +49,20 @@ public static Index parseIndex(String oneBasedIndex) throws IllegalValueExceptio } return Index.fromOneBased(Integer.parseInt(trimmedIndex)); } + //============================== Account Level Parse Commands =========================== + //@@author chantiongley /** * Parses a {@code String name} into a {@code Name}. * Leading and trailing whitespaces will be trimmed. * - * @throws IllegalValueException if the given {@code name} is invalid. + * @throws AccountNotFoundException if the given {@code name} is invalid. */ - public static Name parseName(String name) throws IllegalValueException { + public static Name parseAccountName(String name) throws AccountNotFoundException { requireNonNull(name); String trimmedName = name.trim(); if (!Name.isValidName(trimmedName)) { - throw new IllegalValueException(Name.MESSAGE_NAME_CONSTRAINTS); + throw new AccountNotFoundException(Name.MESSAGE_NAME_CONSTRAINTS); } return new Name(trimmedName); } @@ -62,81 +71,196 @@ public static Name parseName(String name) throws IllegalValueException { * Parses a {@code Optional<String> name} into an {@code Optional<Name>} if {@code name} is present. * See header comment of this class regarding the use of {@code Optional} parameters. */ - public static Optional<Name> parseName(Optional<String> name) throws IllegalValueException { + public static Optional<Name> parseAccountName(Optional<String> name) throws AccountNotFoundException { requireNonNull(name); - return name.isPresent() ? Optional.of(parseName(name.get())) : Optional.empty(); + return name.isPresent() ? Optional.of(parseAccountName(name.get())) : Optional.empty(); + } + + /** + * Parses a {@code String matricNumber} into a {@code MatricNumber}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws AccountNotFoundException if the given {@code matricNumber} is invalid. + */ + public static MatricNumber parseAccountMatricNumber(String matricNumber) throws AccountNotFoundException { + requireNonNull(matricNumber); + String trimmedMatricNumber = matricNumber.trim(); + if (!MatricNumber.isValidMatricNumber(trimmedMatricNumber)) { + throw new AccountNotFoundException(MatricNumber.MESSAGE_MATRIC_NUMBER_CONSTRAINTS); + } + return new MatricNumber(trimmedMatricNumber); + } + + /** + * Parses a {@code Optional<String> title} into an {@code Optional<Title>} if {@code title} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<MatricNumber> parseAccountMatricNumber(Optional<String> matricNumber) + throws AccountNotFoundException { + requireNonNull(matricNumber); + return matricNumber.isPresent() ? Optional.of(parseAccountMatricNumber(matricNumber.get())) : Optional.empty(); + } + + /** + * Parses a {@code String username} into a {@code Username}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws AccountNotFoundException if the given {@code username} is invalid. + */ + public static Username parseAccountUsername(String username) throws AccountNotFoundException { + requireNonNull(username); + String trimmedUsername = username.trim(); + if (!Username.isValidUsername(trimmedUsername)) { + throw new AccountNotFoundException (Username.MESSAGE_USERNAME_CONSTRAINTS); + } + return new Username(trimmedUsername); } /** - * Parses a {@code String phone} into a {@code Phone}. + * Parses a {@code Optional<String> username} into an {@code Optional<Username>} if {@code username} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Username> parseAccountUsername(Optional<String> username) throws AccountNotFoundException { + requireNonNull(username); + return username.isPresent() ? Optional.of(parseAccountUsername(username.get())) : Optional.empty(); + } + + /** + * Parses a {@code String password} into a {@code Password}. * Leading and trailing whitespaces will be trimmed. * - * @throws IllegalValueException if the given {@code phone} is invalid. + * @throws AccountNotFoundException if the given {@code password} is invalid. */ - public static Phone parsePhone(String phone) throws IllegalValueException { - requireNonNull(phone); - String trimmedPhone = phone.trim(); - if (!Phone.isValidPhone(trimmedPhone)) { - throw new IllegalValueException(Phone.MESSAGE_PHONE_CONSTRAINTS); + public static Password parseAccountPassword(String password) throws AccountNotFoundException { + requireNonNull(password); + String trimmedPassword = password.trim(); + if (!Password.isValidPassword(trimmedPassword)) { + throw new AccountNotFoundException(Password.MESSAGE_PASSWORD_CONSTRAINTS); } - return new Phone(trimmedPhone); + return new Password(trimmedPassword); } /** - * Parses a {@code Optional<String> phone} into an {@code Optional<Phone>} if {@code phone} is present. + * Parses a {@code Optional<String> password} into an {@code Optional<Password>} if {@code password} is present. * See header comment of this class regarding the use of {@code Optional} parameters. */ - public static Optional<Phone> parsePhone(Optional<String> phone) throws IllegalValueException { - requireNonNull(phone); - return phone.isPresent() ? Optional.of(parsePhone(phone.get())) : Optional.empty(); + public static Optional<Password> parseAccountPassword(Optional<String> password) throws AccountNotFoundException { + requireNonNull(password); + return password.isPresent() ? Optional.of(parseAccountPassword(password.get())) : Optional.empty(); } /** - * Parses a {@code String address} into an {@code Address}. + * Parses a {@code String privilegeLevel} into a {@code PrivilegeLevel}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws AccountNotFoundException if the given {@code privilegeLevel} is invalid. + */ + public static PrivilegeLevel parseAccountPrivilegeLevel(String privilegeLevel) throws AccountNotFoundException { + requireNonNull(privilegeLevel); + int input = Integer.parseInt(privilegeLevel); + + if (!PrivilegeLevel.isValidPrivilegeLevel(input)) { + throw new AccountNotFoundException(PrivilegeLevel.MESSAGE_PRIVILEGE_LEVEL_CONSTRAINTS); + } + return new PrivilegeLevel(input); + } + + //============================== Book Level Parse Commands ============================== + + /** + * Parses a {@code String title} into a {@code Title}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code title} is invalid. + */ + public static Title parseTitle(String title) throws IllegalValueException { + requireNonNull(title); + String trimmedTitle = title.trim(); + if (!Title.isValidTitle(trimmedTitle)) { + throw new IllegalValueException(Title.MESSAGE_TITLE_CONSTRAINTS); + } + return new Title(trimmedTitle); + } + + /** + * Parses a {@code Optional<String> title} into an {@code Optional<Title>} if {@code title} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Title> parseTitle(Optional<String> title) throws IllegalValueException { + requireNonNull(title); + return title.isPresent() ? Optional.of(parseTitle(title.get())) : Optional.empty(); + } + + /** + * Parses a {@code String isbn} into a {@code Isbn}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code isbn} is invalid. + */ + public static Isbn parseIsbn(String isbn) throws IllegalValueException { + requireNonNull(isbn); + String trimmedIsbn = isbn.trim(); + if (!Isbn.isValidIsbn(trimmedIsbn)) { + throw new IllegalValueException(Isbn.MESSAGE_ISBN_CONSTRAINTS); + } + return new Isbn(trimmedIsbn); + } + + /** + * Parses a {@code Optional<String> isbn} into an {@code Optional<Isbn>} if {@code isbn} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Isbn> parseIsbn(Optional<String> isbn) throws IllegalValueException { + requireNonNull(isbn); + return isbn.isPresent() ? Optional.of(parseIsbn(isbn.get())) : Optional.empty(); + } + + /** + * Parses a {@code String address} into an {@code Author}. * Leading and trailing whitespaces will be trimmed. * * @throws IllegalValueException if the given {@code address} is invalid. */ - public static Address parseAddress(String address) throws IllegalValueException { + public static Author parseAuthor(String address) throws IllegalValueException { requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new IllegalValueException(Address.MESSAGE_ADDRESS_CONSTRAINTS); + String trimmedAuthor = address.trim(); + if (!Author.isValidAuthor(trimmedAuthor)) { + throw new IllegalValueException(Author.MESSAGE_AUTHOR_CONSTRAINTS); } - return new Address(trimmedAddress); + return new Author(trimmedAuthor); } /** - * Parses a {@code Optional<String> address} into an {@code Optional<Address>} if {@code address} is present. + * Parses a {@code Optional<String> author} into an {@code Optional<Author>} if {@code author} is present. * See header comment of this class regarding the use of {@code Optional} parameters. */ - public static Optional<Address> parseAddress(Optional<String> address) throws IllegalValueException { - requireNonNull(address); - return address.isPresent() ? Optional.of(parseAddress(address.get())) : Optional.empty(); + public static Optional<Author> parseAuthor(Optional<String> author) throws IllegalValueException { + requireNonNull(author); + return author.isPresent() ? Optional.of(parseAuthor(author.get())) : Optional.empty(); } /** - * Parses a {@code String email} into an {@code Email}. + * Parses a {@code String avail} into an {@code Avail}. * Leading and trailing whitespaces will be trimmed. * - * @throws IllegalValueException if the given {@code email} is invalid. + * @throws IllegalValueException if the given {@code avail} is invalid. */ - public static Email parseEmail(String email) throws IllegalValueException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new IllegalValueException(Email.MESSAGE_EMAIL_CONSTRAINTS); + public static Avail parseAvail(String avail) throws IllegalValueException { + requireNonNull(avail); + String trimmedAvail = avail.trim(); + if (!Avail.isValidAvail(trimmedAvail)) { + throw new IllegalValueException(Avail.MESSAGE_AVAIL_CONSTRAINTS); } - return new Email(trimmedEmail); + return new Avail(trimmedAvail); } /** - * Parses a {@code Optional<String> email} into an {@code Optional<Email>} if {@code email} is present. + * Parses a {@code Optional<String> avail} into an {@code Optional<Avail>} if {@code avail} is present. * See header comment of this class regarding the use of {@code Optional} parameters. */ - public static Optional<Email> parseEmail(Optional<String> email) throws IllegalValueException { - requireNonNull(email); - return email.isPresent() ? Optional.of(parseEmail(email.get())) : Optional.empty(); + public static Optional<Avail> parseAvail(Optional<String> avail) throws IllegalValueException { + requireNonNull(avail); + return avail.isPresent() ? Optional.of(parseAvail(avail.get())) : Optional.empty(); } /** diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/seedu/address/logic/parser/Prefix.java index c859d5fa5db1..7f3b4d2f3a62 100644 --- a/src/main/java/seedu/address/logic/parser/Prefix.java +++ b/src/main/java/seedu/address/logic/parser/Prefix.java @@ -2,7 +2,7 @@ /** * A prefix that marks the beginning of an argument in an arguments string. - * E.g. 't/' in 'add James t/ friend'. + * E.g. 'n/' in 'add James n/ friend'. */ public class Prefix { private final String prefix; diff --git a/src/main/java/seedu/address/logic/parser/ReserveCommandParser.java b/src/main/java/seedu/address/logic/parser/ReserveCommandParser.java new file mode 100644 index 000000000000..9a8d769be79a --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ReserveCommandParser.java @@ -0,0 +1,29 @@ +package seedu.address.logic.parser; +//@@author LeKhangTai +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.ReserveCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and create a new ReserveCommand object + */ +public class ReserveCommandParser implements Parser<ReserveCommand> { + /** + * Parses the given {@code String} of arguments in the context of the ReserveCommand + * and returns an ReserveCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + public ReserveCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new ReserveCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, ReserveCommand.MESSAGE_USAGE)); + } + } +} + diff --git a/src/main/java/seedu/address/logic/parser/ReturnCommandParser.java b/src/main/java/seedu/address/logic/parser/ReturnCommandParser.java new file mode 100644 index 000000000000..849e9cdd3e85 --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ReturnCommandParser.java @@ -0,0 +1,33 @@ +package seedu.address.logic.parser; +//@@author LeKhangTai +import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.address.commons.core.index.Index; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.logic.commands.ReturnCommand; +import seedu.address.logic.parser.exceptions.ParseException; + +/** + * Parses input arguments and create a new ReturnCommand object + */ +public class ReturnCommandParser implements Parser<ReturnCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the ReturnCommand + * and returns an ReturnCommand object for execution. + * + * @throws ParseException if the user input does not conform the expected format + */ + + public ReturnCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new ReturnCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ReturnCommand.MESSAGE_USAGE)); + } + } + +} + diff --git a/src/main/java/seedu/address/logic/parser/SelectCommandParser.java b/src/main/java/seedu/address/logic/parser/SelectCommandParser.java index bbcae8f4b588..e81f591cc349 100644 --- a/src/main/java/seedu/address/logic/parser/SelectCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/SelectCommandParser.java @@ -15,6 +15,7 @@ public class SelectCommandParser implements Parser<SelectCommand> { /** * Parses the given {@code String} of arguments in the context of the SelectCommand * and returns an SelectCommand object for execution. + * * @throws ParseException if the user input does not conform the expected format */ public SelectCommand parse(String args) throws ParseException { @@ -23,7 +24,7 @@ public SelectCommand parse(String args) throws ParseException { return new SelectCommand(index); } catch (IllegalValueException ive) { throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE)); + String.format(MESSAGE_INVALID_COMMAND_FORMAT, SelectCommand.MESSAGE_USAGE)); } } } diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index f8d0260de159..000000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,187 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; -import seedu.address.model.tag.Tag; -import seedu.address.model.tag.UniqueTagList; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .equals comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - private final UniqueTagList tags; - - /* - * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html - * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. - */ - { - persons = new UniquePersonList(); - tags = new UniqueTagList(); - } - - public AddressBook() {} - - /** - * Creates an AddressBook using the Persons and Tags in the {@code toBeCopied} - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(); - resetData(toBeCopied); - } - - //// list overwrite operations - - public void setPersons(List<Person> persons) throws DuplicatePersonException { - this.persons.setPersons(persons); - } - - public void setTags(Set<Tag> tags) { - this.tags.setTags(tags); - } - - /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. - */ - public void resetData(ReadOnlyAddressBook newData) { - requireNonNull(newData); - setTags(new HashSet<>(newData.getTagList())); - List<Person> syncedPersonList = newData.getPersonList().stream() - .map(this::syncWithMasterTagList) - .collect(Collectors.toList()); - - try { - setPersons(syncedPersonList); - } catch (DuplicatePersonException e) { - throw new AssertionError("AddressBooks should not have duplicate persons"); - } - } - - //// person-level operations - - /** - * Adds a person to the address book. - * Also checks the new person's tags and updates {@link #tags} with any new tags found, - * and updates the Tag objects in the person to point to those in {@link #tags}. - * - * @throws DuplicatePersonException if an equivalent person already exists. - */ - public void addPerson(Person p) throws DuplicatePersonException { - Person person = syncWithMasterTagList(p); - // TODO: the tags master list will be updated even though the below line fails. - // This can cause the tags master list to have additional tags that are not tagged to any person - // in the person list. - persons.add(person); - } - - /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code AddressBook}'s tag list will be updated with the tags of {@code editedPerson}. - * - * @throws DuplicatePersonException if updating the person's details causes the person to be equivalent to - * another existing person in the list. - * @throws PersonNotFoundException if {@code target} could not be found in the list. - * - * @see #syncWithMasterTagList(Person) - */ - public void updatePerson(Person target, Person editedPerson) - throws DuplicatePersonException, PersonNotFoundException { - requireNonNull(editedPerson); - - Person syncedEditedPerson = syncWithMasterTagList(editedPerson); - // TODO: the tags master list will be updated even though the below line fails. - // This can cause the tags master list to have additional tags that are not tagged to any person - // in the person list. - persons.setPerson(target, syncedEditedPerson); - } - - /** - * Updates the master tag list to include tags in {@code person} that are not in the list. - * @return a copy of this {@code person} such that every tag in this person points to a Tag object in the master - * list. - */ - private Person syncWithMasterTagList(Person person) { - final UniqueTagList personTags = new UniqueTagList(person.getTags()); - tags.mergeFrom(personTags); - - // Create map with values = tag object references in the master list - // used for checking person tag references - final Map<Tag, Tag> masterTagObjects = new HashMap<>(); - tags.forEach(tag -> masterTagObjects.put(tag, tag)); - - // Rebuild the list of person tags to point to the relevant tags in the master tag list. - final Set<Tag> correctTagReferences = new HashSet<>(); - personTags.forEach(tag -> correctTagReferences.add(masterTagObjects.get(tag))); - return new Person( - person.getName(), person.getPhone(), person.getEmail(), person.getAddress(), correctTagReferences); - } - - /** - * Removes {@code key} from this {@code AddressBook}. - * @throws PersonNotFoundException if the {@code key} is not in this {@code AddressBook}. - */ - public boolean removePerson(Person key) throws PersonNotFoundException { - if (persons.remove(key)) { - return true; - } else { - throw new PersonNotFoundException(); - } - } - - //// tag-level operations - - public void addTag(Tag t) throws UniqueTagList.DuplicateTagException { - tags.add(t); - } - - //// util methods - - @Override - public String toString() { - return persons.asObservableList().size() + " persons, " + tags.asObservableList().size() + " tags"; - // TODO: refine later - } - - @Override - public ObservableList<Person> getPersonList() { - return persons.asObservableList(); - } - - @Override - public ObservableList<Tag> getTagList() { - return tags.asObservableList(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && this.persons.equals(((AddressBook) other).persons) - && this.tags.equalsOrderInsensitive(((AddressBook) other).tags)); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(persons, tags); - } -} diff --git a/src/main/java/seedu/address/model/Catalogue.java b/src/main/java/seedu/address/model/Catalogue.java new file mode 100644 index 000000000000..cbbd58f7f5af --- /dev/null +++ b/src/main/java/seedu/address/model/Catalogue.java @@ -0,0 +1,237 @@ +package seedu.address.model; + +import static java.util.Objects.requireNonNull; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import javafx.collections.ObservableList; +import seedu.address.model.book.Book; +import seedu.address.model.book.UniqueBookList; +import seedu.address.model.book.exceptions.BookNotFoundException; +import seedu.address.model.book.exceptions.DuplicateBookException; +import seedu.address.model.tag.Tag; +import seedu.address.model.tag.UniqueTagList; + +/** + * Wraps all data at the catalogue level + * Duplicates are not allowed (by .equals comparison) + */ +public class Catalogue implements ReadOnlyCatalogue { + + private final UniqueBookList books; + private final UniqueTagList tags; + + /* + * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication + * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html + * + * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication + * among constructors. + */ { + books = new UniqueBookList(); + tags = new UniqueTagList(); + } + + public Catalogue() { + } + + /** + * Creates an Catalogue using the Books and Tags in the {@code toBeCopied} + */ + public Catalogue(ReadOnlyCatalogue toBeCopied) { + this(); + resetData(toBeCopied); + } + + //// list overwrite operations + + public void setBooks(List<Book> books) throws DuplicateBookException { + this.books.setBooks(books); + } + + public void setTags(Set<Tag> tags) { + this.tags.setTags(tags); + } + + /** + * Resets the existing data of this {@code Catalogue} with {@code newData}. + */ + public void resetData(ReadOnlyCatalogue newData) { + requireNonNull(newData); + setTags(new HashSet<>(newData.getTagList())); + List<Book> syncedBookList = newData.getBookList().stream() + .map(this::syncWithMasterTagList) + .collect(Collectors.toList()); + + try { + setBooks(syncedBookList); + } catch (DuplicateBookException e) { + throw new AssertionError("Catalogue should not have duplicate books"); + } + } + + //// book-level operations + + /** + * Adds a book to the catalogue. + * Also checks the new book's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the book to point to those in {@link #tags}. + * + * @throws DuplicateBookException if an equivalent book already exists. + */ + public void addBook(Book p) throws DuplicateBookException { + Book book = syncWithMasterTagList(p); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any book + // in the book list. + books.add(book); + } + + /** + * Replaces the given book {@code target} in the list with {@code editedBook}. + * {@code Catalogue}'s tag list will be updated with the tags of {@code editedBook}. + * + * @throws DuplicateBookException if updating the book's details causes the book to be equivalent to + * another existing book in the list. + * @throws BookNotFoundException if {@code target} could not be found in the list. + * @see #syncWithMasterTagList(Book) + */ + public void updateBook(Book target, Book editedBook) + throws DuplicateBookException, BookNotFoundException { + requireNonNull(editedBook); + + Book syncedEditedBook = syncWithMasterTagList(editedBook); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any book + // in the book list. + books.setBook(target, syncedEditedBook); + } + + /** + * Updates the master tag list to include tags in {@code book} that are not in the list. + * + * @return a copy of this {@code book} such that every tag in this book points to a Tag object in the master + * list. + */ + private Book syncWithMasterTagList(Book book) { + final UniqueTagList bookTags = new UniqueTagList(book.getTags()); + tags.mergeFrom(bookTags); + + // Create map with values = tag object references in the master list + // used for checking book tag references + final Map<Tag, Tag> masterTagObjects = new HashMap<>(); + tags.forEach(tag -> masterTagObjects.put(tag, tag)); + + // Rebuild the list of book tags to point to the relevant tags in the master tag list. + final Set<Tag> correctTagReferences = new HashSet<>(); + bookTags.forEach(tag -> correctTagReferences.add(masterTagObjects.get(tag))); + return new Book( + book.getTitle(), book.getAuthor(), book.getIsbn(), book.getAvail(), correctTagReferences); + } + + /** + * Removes {@code key} from this {@code Catalogue}. + * + * @throws BookNotFoundException if the {@code key} is not in this {@code Catalogue}. + */ + public boolean removeBook(Book key) throws BookNotFoundException { + if (books.remove(key)) { + return true; + } else { + throw new BookNotFoundException(); + } + } + + //// tag-level operations + + public void addTag(Tag t) throws UniqueTagList.DuplicateTagException { + tags.add(t); + } + //@@author LeKhangTai + /** + * @param target book that is selected by index to return + * @param returnedBook duplicated book that will replace the original book + * @throws BookNotFoundException + */ + public void returnBook(Book target, Book returnedBook) + throws BookNotFoundException { + requireNonNull(returnedBook); + + Book syncedEditedBook = syncWithMasterTagList(returnedBook); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any book + // in the book list. + books.replaceReturnedBook(target, syncedEditedBook); + } + + /** + * @param target book that is selected by index to borrow + * @param borrowedBook duplicated book that will replace the original book + * @throws BookNotFoundException + */ + public void borrowBook(Book target, Book borrowedBook) + throws BookNotFoundException { + requireNonNull(borrowedBook); + + Book syncedEditedBook = syncWithMasterTagList(borrowedBook); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any book + // in the book list. + books.replaceBorrowedBook(target, syncedEditedBook); + } + + /** + * @param target book that is selected by index to reserve + * @param reservedBook duplicated book that will replace the original book + * @throws BookNotFoundException + */ + public void reserveBook(Book target, Book reservedBook) + throws BookNotFoundException { + requireNonNull(reservedBook); + + Book syncedEditedBook = syncWithMasterTagList(reservedBook); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any book + // in the book list. + books.replaceReservedBook(target, syncedEditedBook); + } + //@@author + + //// util methods + + @Override + public String toString() { + return books.asObservableList().size() + " books, " + tags.asObservableList().size() + " tags"; + // TODO: refine later + } + + @Override + public ObservableList<Book> getBookList() { + return books.asObservableList(); + } + + @Override + public ObservableList<Tag> getTagList() { + return tags.asObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Catalogue // instanceof handles nulls + && this.books.equals(((Catalogue) other).books) + && this.tags.equalsOrderInsensitive(((Catalogue) other).tags)); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(books, tags); + } +} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java index 4a6079ce0199..1452714bc1d9 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -3,46 +3,100 @@ import java.util.function.Predicate; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.model.account.Account; +import seedu.address.model.account.Credential; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.account.UniqueAccountList; +import seedu.address.model.account.exceptions.AccountNotFoundException; +import seedu.address.model.account.exceptions.DuplicateAccountException; +import seedu.address.model.book.Book; +import seedu.address.model.book.exceptions.BookNotFoundException; +import seedu.address.model.book.exceptions.DuplicateBookException; + /** * The API of the Model component. */ public interface Model { - /** {@code Predicate} that always evaluate to true */ - Predicate<Person> PREDICATE_SHOW_ALL_PERSONS = unused -> true; + /** + * {@code Predicate} that always evaluate to true + */ + Predicate<Book> PREDICATE_SHOW_ALL_BOOKS = unused -> true; - /** Clears existing backing model and replaces with the provided new data. */ - void resetData(ReadOnlyAddressBook newData); + PrivilegeLevel PRIVILEGE_LEVEL_GUEST = new PrivilegeLevel(0); + PrivilegeLevel PRIVILEGE_LEVEL_STUDENT = new PrivilegeLevel(1); + PrivilegeLevel PRIVILEGE_LEVEL_LIBRARIAN = new PrivilegeLevel(2); - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); + /** + * Clears existing backing model and replaces with the provided new data. + */ + void resetData(ReadOnlyCatalogue newData); - /** Deletes the given person. */ - void deletePerson(Person target) throws PersonNotFoundException; + /** + * Clears existing backing model and replaces with the provided new data. + */ + void resetAccount(UniqueAccountList newData); - /** Adds the given person */ - void addPerson(Person person) throws DuplicatePersonException; + /** + * Returns the Catalogue + */ + ReadOnlyCatalogue getCatalogue(); + + /** + * Deletes the given book. + */ + void deleteBook(Book target) throws BookNotFoundException; + + /** + * Adds the given book + */ + void addBook(Book book) throws DuplicateBookException; /** - * Replaces the given person {@code target} with {@code editedPerson}. + * Replaces the given book {@code target} with {@code editedBook}. * - * @throws DuplicatePersonException if updating the person's details causes the person to be equivalent to - * another existing person in the list. - * @throws PersonNotFoundException if {@code target} could not be found in the list. + * @throws DuplicateBookException if updating the book's details causes the book to be equivalent to + * another existing book in the list. + * @throws BookNotFoundException if {@code target} could not be found in the list. */ - void updatePerson(Person target, Person editedPerson) - throws DuplicatePersonException, PersonNotFoundException; + void updateBook(Book target, Book editedBook) + throws DuplicateBookException, BookNotFoundException; - /** Returns an unmodifiable view of the filtered person list */ - ObservableList<Person> getFilteredPersonList(); + void returnBook(Book target, Book returnedBook) throws BookNotFoundException; + + void borrowBook(Book target, Book borrowedBook) throws BookNotFoundException; + + void reserveBook(Book target, Book reservedBook) throws BookNotFoundException; + + /** + * Returns an unmodifiable view of the filtered book list + */ + ObservableList<Book> getFilteredBookList(); + + /** + * Returns an unmodifiable view of the account list + */ + UniqueAccountList getAccountList(); /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * Updates the filter of the filtered book list to filter by the given {@code predicate}. + * * @throws NullPointerException if {@code predicate} is null. */ - void updateFilteredPersonList(Predicate<Person> predicate); + void updateFilteredBookList(Predicate<Book> predicate); + + //@@author QiuHaohao + void addAccount(Account account) throws DuplicateAccountException; + + void deleteAccount(Account account) throws AccountNotFoundException; + + void updateAccount(Account account, Account editedAccount) + throws DuplicateAccountException, AccountNotFoundException; + + PrivilegeLevel authenticate(Credential credential); + + void logout(); + PrivilegeLevel getPrivilegeLevel(); + //@@author } diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java index 22a7d0eb3f4d..0d87a51a66d0 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -11,92 +11,248 @@ import javafx.collections.transformation.FilteredList; import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.model.person.Person; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.address.commons.events.model.AccountListChangedEvent; +import seedu.address.commons.events.model.CatalogueChangedEvent; +import seedu.address.model.account.Account; +import seedu.address.model.account.Credential; +import seedu.address.model.account.PrivilegeLevel; +import seedu.address.model.account.UniqueAccountList; +import seedu.address.model.account.exceptions.AccountNotFoundException; +import seedu.address.model.account.exceptions.DuplicateAccountException; +import seedu.address.model.book.Book; +import seedu.address.model.book.exceptions.BookNotFoundException; +import seedu.address.model.book.exceptions.DuplicateBookException; + + + /** - * Represents the in-memory model of the address book data. + * Represents the in-memory model of the catalogue data. * All changes to any model should be synchronized. */ public class ModelManager extends ComponentManager implements Model { private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - private final AddressBook addressBook; - private final FilteredList<Person> filteredPersons; + private final Catalogue catalogue; + private final FilteredList<Book> filteredBooks; + private UniqueAccountList accountList; + private Account currentAccount; /** - * Initializes a ModelManager with the given addressBook and userPrefs. + * Initializes a ModelManager with the given catalogue and userPrefs. */ - public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { + public ModelManager(ReadOnlyCatalogue catalogue, UserPrefs userPrefs) { super(); - requireAllNonNull(addressBook, userPrefs); + requireAllNonNull(catalogue, userPrefs); + + logger.fine("Initializing with catalogue: " + catalogue + " and user prefs " + userPrefs); - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); + this.catalogue = new Catalogue(catalogue); + filteredBooks = new FilteredList<>(this.catalogue.getBookList()); + this.accountList = new UniqueAccountList(); + this.currentAccount = Account.createGuestAccount(); + addFirstAccount(); - this.addressBook = new AddressBook(addressBook); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); } + //@@author QiuHaohao + + /** + * Initializes a ModelManager with the given catalogue, accountList and userPrefs. + */ + public ModelManager(ReadOnlyCatalogue catalogue, UniqueAccountList accountList, UserPrefs userPrefs) { + super(); + requireAllNonNull(catalogue, accountList, userPrefs); + + logger.fine("Initializing with catalogue: " + catalogue + + ", accountList: " + accountList + + " and user prefs " + userPrefs); + + this.catalogue = new Catalogue(catalogue); + filteredBooks = new FilteredList<>(this.catalogue.getBookList()); + this.accountList = accountList; + this.currentAccount = Account.createGuestAccount(); + } + //@@author + public ModelManager() { - this(new AddressBook(), new UserPrefs()); + this(new Catalogue(), new UserPrefs()); + } + + //@@author QiuHaohao + + /** + * Adds an account to the AccountList + * + * @param account + * @throws DuplicateAccountException + */ + public void addAccount(Account account) throws DuplicateAccountException { + accountList.add(account); + indicateAccountListChanged(); + } + + /** + * Deletes an account from the AccountList + * + * @param account + * @throws AccountNotFoundException + */ + public void deleteAccount(Account account) throws AccountNotFoundException { + if (account == null) { + throw new AccountNotFoundException("Account not Found!"); + } + accountList.remove(account); + indicateAccountListChanged(); } + /** + * Replaces an account with a new one + * + * @param account + * @param editedAccount + * @throws DuplicateAccountException + * @throws AccountNotFoundException + */ + public void updateAccount(Account account, Account editedAccount) + throws DuplicateAccountException, AccountNotFoundException { + accountList.setAccount(account, account); + indicateAccountListChanged(); + } + //@@author LeKhangTai + @Override + public synchronized void returnBook(Book target, Book returnedBook) throws BookNotFoundException { + catalogue.returnBook(target, returnedBook); + indicateCatalogueChanged(); + } @Override - public void resetData(ReadOnlyAddressBook newData) { - addressBook.resetData(newData); - indicateAddressBookChanged(); + public synchronized void borrowBook(Book target, Book borrowedBook) throws BookNotFoundException { + catalogue.borrowBook(target, borrowedBook); + updateFilteredBookList(PREDICATE_SHOW_ALL_BOOKS); + indicateCatalogueChanged(); } + @Override + public synchronized void reserveBook(Book target, Book reservedBook) throws BookNotFoundException { + catalogue.reserveBook(target, reservedBook); + updateFilteredBookList(PREDICATE_SHOW_ALL_BOOKS); + indicateCatalogueChanged(); + } + //@@author + /** + * Adds the initial admin account to the accountList + */ + private void addFirstAccount() { + Account admin = Account.createDefaultAdminAccount(); + if (!this.accountList.contains(admin)) { + try { + this.accountList.add(admin); + } catch (DuplicateAccountException e) { + e.printStackTrace(); + } + } + } + //@@author @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; + public void resetData(ReadOnlyCatalogue newData) { + catalogue.resetData(newData); + indicateCatalogueChanged(); } - /** Raises an event to indicate the model has changed */ - private void indicateAddressBookChanged() { - raise(new AddressBookChangedEvent(addressBook)); + @Override + public void resetAccount(UniqueAccountList newData) { + this.accountList = newData; + indicateAccountListChanged(); } @Override - public synchronized void deletePerson(Person target) throws PersonNotFoundException { - addressBook.removePerson(target); - indicateAddressBookChanged(); + public UniqueAccountList getAccountList() { + return accountList; } @Override - public synchronized void addPerson(Person person) throws DuplicatePersonException { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - indicateAddressBookChanged(); + public ReadOnlyCatalogue getCatalogue() { + return catalogue; + } + + /** + * Raises an event to indicate the model has changed + */ + private void indicateCatalogueChanged() { + raise(new CatalogueChangedEvent(catalogue)); } + //@@author QiuHaohao + + /** + * Raises an event to indicate the model has changed + */ + private void indicateAccountListChanged() { + raise(new AccountListChangedEvent(accountList)); + } + //@@author + @Override - public void updatePerson(Person target, Person editedPerson) - throws DuplicatePersonException, PersonNotFoundException { - requireAllNonNull(target, editedPerson); + public synchronized void deleteBook(Book target) throws BookNotFoundException { + catalogue.removeBook(target); + indicateCatalogueChanged(); + } - addressBook.updatePerson(target, editedPerson); - indicateAddressBookChanged(); + @Override + public synchronized void addBook(Book book) throws DuplicateBookException { + catalogue.addBook(book); + updateFilteredBookList(PREDICATE_SHOW_ALL_BOOKS); + indicateCatalogueChanged(); } - //=========== Filtered Person List Accessors ============================================================= + @Override + public void updateBook(Book target, Book editedBook) + throws DuplicateBookException, BookNotFoundException { + requireAllNonNull(target, editedBook); + + catalogue.updateBook(target, editedBook); + indicateCatalogueChanged(); + } + + //=========== Filtered Book List Accessors ============================================================= /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code addressBook} + * Returns an unmodifiable view of the list of {@code Book} backed by the internal list of + * {@code catalogue} */ @Override - public ObservableList<Person> getFilteredPersonList() { - return FXCollections.unmodifiableObservableList(filteredPersons); + public ObservableList<Book> getFilteredBookList() { + return FXCollections.unmodifiableObservableList(filteredBooks); } @Override - public void updateFilteredPersonList(Predicate<Person> predicate) { + public void updateFilteredBookList(Predicate<Book> predicate) { requireNonNull(predicate); - filteredPersons.setPredicate(predicate); + filteredBooks.setPredicate(predicate); + } + + //@@author QiuHaohao + @Override + public PrivilegeLevel authenticate(Credential c) { + Account matched = accountList.authenticate(c); + if (matched != null) { + this.currentAccount = matched; + return currentAccount.getPrivilegeLevel(); + } + //if not found + return PRIVILEGE_LEVEL_GUEST; + } + + @Override + public void logout() { + currentAccount = Account.createGuestAccount(); + } + + @Override + public PrivilegeLevel getPrivilegeLevel() { + return this.currentAccount.getPrivilegeLevel(); } + //@@author @Override public boolean equals(Object obj) { @@ -112,8 +268,8 @@ public boolean equals(Object obj) { // state check ModelManager other = (ModelManager) obj; - return addressBook.equals(other.addressBook) - && filteredPersons.equals(other.filteredPersons); + return catalogue.equals(other.catalogue) + && filteredBooks.equals(other.filteredBooks); } } diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyCatalogue.java similarity index 51% rename from src/main/java/seedu/address/model/ReadOnlyAddressBook.java rename to src/main/java/seedu/address/model/ReadOnlyCatalogue.java index 1f4e49a37d67..34536d7263cd 100644 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ b/src/main/java/seedu/address/model/ReadOnlyCatalogue.java @@ -1,19 +1,19 @@ package seedu.address.model; import javafx.collections.ObservableList; -import seedu.address.model.person.Person; +import seedu.address.model.book.Book; import seedu.address.model.tag.Tag; /** - * Unmodifiable view of an address book + * Unmodifiable view of an catalogue */ -public interface ReadOnlyAddressBook { +public interface ReadOnlyCatalogue { /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. + * Returns an unmodifiable view of the books list. + * This list will not contain any duplicate books. */ - ObservableList<Person> getPersonList(); + ObservableList<Book> getBookList(); /** * Returns an unmodifiable view of the tags list. diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/address/model/UserPrefs.java index 8c8a071876eb..b4cc0bf625ee 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/address/model/UserPrefs.java @@ -10,8 +10,9 @@ public class UserPrefs { private GuiSettings guiSettings; - private String addressBookFilePath = "data/addressbook.xml"; - private String addressBookName = "MyAddressBook"; + private String catalogueFilePath = "data/catalogue.xml"; + private String catalogueBookTitle = "MyCatalogue"; + private String accountListFilePath = "data/accountList.ser"; public UserPrefs() { this.setGuiSettings(500, 500, 0, 0); @@ -29,22 +30,32 @@ public void setGuiSettings(double width, double height, int x, int y) { guiSettings = new GuiSettings(width, height, x, y); } - public String getAddressBookFilePath() { - return addressBookFilePath; + public String getCatalogueFilePath() { + return catalogueFilePath; } - public void setAddressBookFilePath(String addressBookFilePath) { - this.addressBookFilePath = addressBookFilePath; + public void setCatalogueFilePath(String catalogueFilePath) { + this.catalogueFilePath = catalogueFilePath; } - public String getAddressBookName() { - return addressBookName; + public String getCatalogueBookTitle() { + return catalogueBookTitle; } - public void setAddressBookName(String addressBookName) { - this.addressBookName = addressBookName; + public void setCatalogueBookTitle(String catalogueBookTitle) { + this.catalogueBookTitle = catalogueBookTitle; } + //@@author QiuHaohao + public String getAccountListFilePath() { + return accountListFilePath; + } + + public void setAccountListFilePath(String accountListFilePath) { + this.accountListFilePath = accountListFilePath; + } + + //@@author @Override public boolean equals(Object other) { if (other == this) { @@ -57,21 +68,21 @@ public boolean equals(Object other) { UserPrefs o = (UserPrefs) other; return Objects.equals(guiSettings, o.guiSettings) - && Objects.equals(addressBookFilePath, o.addressBookFilePath) - && Objects.equals(addressBookName, o.addressBookName); + && Objects.equals(catalogueFilePath, o.catalogueFilePath) + && Objects.equals(catalogueBookTitle, o.catalogueBookTitle); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath, addressBookName); + return Objects.hash(guiSettings, catalogueFilePath, catalogueBookTitle); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Gui Settings : " + guiSettings.toString()); - sb.append("\nLocal data file location : " + addressBookFilePath); - sb.append("\nAddressBook name : " + addressBookName); + sb.append("\nLocal data file location : " + catalogueFilePath); + sb.append("\nCatalogue name : " + catalogueBookTitle); return sb.toString(); } diff --git a/src/main/java/seedu/address/model/account/Account.java b/src/main/java/seedu/address/model/account/Account.java new file mode 100644 index 000000000000..cb1877015e40 --- /dev/null +++ b/src/main/java/seedu/address/model/account/Account.java @@ -0,0 +1,181 @@ +//@@author QiuHaohao +package seedu.address.model.account; + +import static java.util.Objects.requireNonNull; + +import java.io.Serializable; +import java.util.Objects; + + + +/** + * Represents an account in the accountBook + */ +public class Account implements Serializable { + private final Name name; + private final Credential credential; + private final MatricNumber matricNumber; + private final PrivilegeLevel privilegeLevel; + + /** + * Constructs an Account + * + * @param name + * @param credential + * @param matricNumber + * @param privilegeLevel + */ + public Account(Name name, Credential credential, MatricNumber matricNumber, PrivilegeLevel privilegeLevel) { + requireNonNull(name); + requireNonNull(credential); + requireNonNull(matricNumber); + requireNonNull(privilegeLevel); + this.name = name; + this.credential = credential; + this.matricNumber = matricNumber; + this.privilegeLevel = privilegeLevel; + } + + /** + * Returns a sample guest account + * + * @return + */ + public static final Account createGuestAccount() { + Name name = new Name("Guest"); + Credential credential = new Credential("Guest", "Guest"); + MatricNumber matricNumber = new MatricNumber("A0000000X"); + PrivilegeLevel privilegeLevel = new PrivilegeLevel(0); + Account guest = new Account(name, credential, matricNumber, privilegeLevel); + return guest; + } + + /** + * Returns a sample admin account + * + * @return + */ + public static final Account createDefaultAdminAccount() { + Name name = new Name("Alice"); + Credential credential = new Credential("admin", "admin"); + MatricNumber matricNumber = new MatricNumber("A0123456X"); + PrivilegeLevel privilegeLevel = new PrivilegeLevel(2); + Account admin = new Account(name, credential, matricNumber, privilegeLevel); + return admin; + } + + /** + * Returns a sample student account + * + * @return + */ + public static final Account createDefaultStudentAccount() { + Name name = new Name("Bob"); + Credential credential = new Credential("student", "student"); + MatricNumber matricNumber = new MatricNumber("A0123456X"); + PrivilegeLevel privilegeLevel = new PrivilegeLevel(1); + Account student = new Account(name, credential, matricNumber, privilegeLevel); + return student; + } + + /** + * Returns the name of the account + * + * @return + */ + public Name getName() { + return name; + } + + /** + * Returns the credential + * + * @return + */ + public Credential getCredential() { + return credential; + } + + /** + * Returns the MatricNumber + * + * @return + */ + public MatricNumber getMatricNumber() { + return matricNumber; + } + + /** + * Returns the privilegeLevel of this account + * + * @return + */ + public PrivilegeLevel getPrivilegeLevel() { + return privilegeLevel; + } + + /** + * Returns a boolean indicating whether a given credential matches with that of this account + * + * @param c + * @return + */ + public boolean credentialMatches(Credential c) { + return c.equals(this.credential); + } + + /** + * Returns true if this account's username is the same as the username provided + * + * @param username + * @return + */ + public boolean usernameMatches(Username username) { + return this.credential.usernameEquals(username); + } + + /** + * Returns true if this account's username is the same as that of the credential provided + * + * @param c + * @return + */ + public boolean usernameMatches(Credential c) { + return usernameMatches(c.getUsername()); + } + + /** + * Returns true if this account's username is the same as that of the account provided + * + * @param a + * @return + */ + public boolean usernameMatches(Account a) { + return usernameMatches(a.getCredential()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Account account = (Account) o; + return Objects.equals(name, account.name) + && Objects.equals(credential, account.credential) + && Objects.equals(matricNumber, account.matricNumber) + && Objects.equals(privilegeLevel, account.privilegeLevel); + } + + @Override + public int hashCode() { + return Objects.hash(name, credential, matricNumber, privilegeLevel); + } + + @Override + public String toString() { + return this.credential.getUsername().toString(); + } +} diff --git a/src/main/java/seedu/address/model/account/Credential.java b/src/main/java/seedu/address/model/account/Credential.java new file mode 100644 index 000000000000..23f5e2cc4da8 --- /dev/null +++ b/src/main/java/seedu/address/model/account/Credential.java @@ -0,0 +1,73 @@ +//@@author QiuHaohao +package seedu.address.model.account; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Represents a set of username and password + */ +public class Credential implements Serializable { + + private Username username; + private Password password; + + /** + * Constructs a {@code Credential} + * + * @param username A valid username + * @param password A valid password + */ + public Credential(String username, String password) { + this.username = new Username(username); + this.password = new Password(password); + } + + /** + * Returns username + */ + public Username getUsername() { + return username; + } + + /** + * Returns password + */ + public Password getPassword() { + return password; + } + + /** + * Returns true if the username provided equals to this.username + * + * @param username + * @return + */ + public boolean usernameEquals(Username username) { + return this.username.equals(username); + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof Credential // short circuit if same obj + && this.username.equals(((Credential) other).username) // check username + && this.password.equals(((Credential) other).password) //check password + ); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(username, password); + } + + @Override + public String toString() { + return "Credential{" + + "username=" + username + + ", password=" + password + + '}'; + } +} + diff --git a/src/main/java/seedu/address/model/account/MatricNumber.java b/src/main/java/seedu/address/model/account/MatricNumber.java new file mode 100644 index 000000000000..7fe2442806f2 --- /dev/null +++ b/src/main/java/seedu/address/model/account/MatricNumber.java @@ -0,0 +1,63 @@ +//@@author QiuHaohao +package seedu.address.model.account; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.io.Serializable; + +/** + * Represents a set of username and password + */ +public class MatricNumber implements Serializable { + public static final String MESSAGE_MATRIC_NUMBER_CONSTRAINTS = + "Matriculation number should start with \"A\", followed by 7 digits and end with uppercase letter."; + + public static final String MATRIC_NUMBER_VALIDATION_REGEX = "A[0-9]{7}[A-Z]"; + + + private final String matricNumber; + + /** + * Constructs a {@code Credential} + * + * @param matricNumber A valid matric number + */ + public MatricNumber(String matricNumber) { + requireNonNull(matricNumber); + checkArgument(isValidMatricNumber(matricNumber), MESSAGE_MATRIC_NUMBER_CONSTRAINTS); + this.matricNumber = matricNumber; + } + + /** + * Returns true if a given string is a valid MatricNumber. + */ + public static boolean isValidMatricNumber(String test) { + return test.matches(MATRIC_NUMBER_VALIDATION_REGEX); + } + + /** + * Returns MatricNumber. + */ + public String getMatricNumber() { + return matricNumber; + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof MatricNumber // short circuit if same obj + && this.getMatricNumber().equals(((MatricNumber) other).getMatricNumber()) //check status + ); + } + + @Override + public String toString() { + return matricNumber; + } + + @Override + public int hashCode() { + return matricNumber.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/address/model/account/Name.java similarity index 71% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/address/model/account/Name.java index 8e632943c4cf..63f6b98db925 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/account/Name.java @@ -1,19 +1,21 @@ -package seedu.address.model.person; +package seedu.address.model.account; import static java.util.Objects.requireNonNull; import static seedu.address.commons.util.AppUtil.checkArgument; +import java.io.Serializable; + /** - * Represents a Person's name in the address book. + * Represents a Person's name in the account. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ -public class Name { +public class Name implements Serializable { public static final String MESSAGE_NAME_CONSTRAINTS = - "Person names should only contain alphanumeric characters and spaces, and it should not be blank"; + "Person names should only contain alphanumeric characters and spaces, and it should not be blank"; /* - * The first character of the address must not be a whitespace, + * The first character of the name must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ public static final String NAME_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; @@ -47,8 +49,8 @@ public String toString() { @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof Name // instanceof handles nulls - && this.fullName.equals(((Name) other).fullName)); // state check + || (other instanceof Name // instanceof handles nulls + && this.fullName.equals(((Name) other).fullName)); // state check } @Override @@ -57,3 +59,4 @@ public int hashCode() { } } + diff --git a/src/main/java/seedu/address/model/account/Password.java b/src/main/java/seedu/address/model/account/Password.java new file mode 100644 index 000000000000..d5d8e449b470 --- /dev/null +++ b/src/main/java/seedu/address/model/account/Password.java @@ -0,0 +1,64 @@ +//@@author victortardieu +package seedu.address.model.account; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.io.Serializable; + +/** + * Represents a password + */ +public class Password implements Serializable { + public static final String MESSAGE_PASSWORD_CONSTRAINTS = + "Password should be at least 5 characters long."; + public static final String PASSWORD_VALIDATION_REGEX = "\\w{5,}"; + + private final String password; + + + /** + * Construct a password + * + * @param password + */ + public Password(String password) { + requireNonNull(password); + checkArgument(isValidPassword(password), MESSAGE_PASSWORD_CONSTRAINTS); + + this.password = password; + } + + /** + * Returns true if a given string is a valid password. + */ + public static boolean isValidPassword(String test) { + return test.matches(PASSWORD_VALIDATION_REGEX); + } + + @Override + public String toString() { + return "Password{" + + "password='" + password + '\'' + + '}'; + } + + /** + * Returns password. + */ + public String getPassword() { + return password; + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof Password // short circuit if same obj + && this.password.equals(((Password) other).password)); //check password + } + + @Override + public int hashCode() { + return password.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/account/PrivilegeLevel.java b/src/main/java/seedu/address/model/account/PrivilegeLevel.java new file mode 100644 index 000000000000..8d2400604620 --- /dev/null +++ b/src/main/java/seedu/address/model/account/PrivilegeLevel.java @@ -0,0 +1,68 @@ +//@@author QiuHaohao +package seedu.address.model.account; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Represents the privilegeLevel of an account/a command + */ +public class PrivilegeLevel implements Comparable<PrivilegeLevel>, Serializable { + + public static final int PRIVILEGE_LEVEL_GUEST = 0; + public static final int PRIVILEGE_LEVEL_STUDENT = 1; + public static final int PRIVILEGE_LEVEL_LIBRARIAN = 2; + public static final String MESSAGE_PRIVILEGE_LEVEL_CONSTRAINTS = + "Privilege Level should be an integer from 0 to 2 inclusive."; + private final int privilegeLevel; + + /** + * Constructs a PrivilegeLevel + * + * @param privilegeLevel + */ + public PrivilegeLevel(int privilegeLevel) { + requireNonNull(privilegeLevel); + checkArgument(isValidPrivilegeLevel(privilegeLevel), MESSAGE_PRIVILEGE_LEVEL_CONSTRAINTS); + this.privilegeLevel = privilegeLevel; + } + + /** + * Returns true if a given string is a valid PrivilegeLevel + */ + public static boolean isValidPrivilegeLevel(int test) { + return test >= PRIVILEGE_LEVEL_GUEST + && test <= PRIVILEGE_LEVEL_LIBRARIAN; + } + + public int getPrivilegeLevel() { + return privilegeLevel; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PrivilegeLevel that = (PrivilegeLevel) o; + return privilegeLevel == that.privilegeLevel; + } + + @Override + public int hashCode() { + + return Objects.hash(privilegeLevel); + } + + + @Override + public int compareTo(PrivilegeLevel o) { + return this.privilegeLevel - o.privilegeLevel; + } +} diff --git a/src/main/java/seedu/address/model/account/UniqueAccountList.java b/src/main/java/seedu/address/model/account/UniqueAccountList.java new file mode 100644 index 000000000000..c6602c02abda --- /dev/null +++ b/src/main/java/seedu/address/model/account/UniqueAccountList.java @@ -0,0 +1,190 @@ +//@@author QiuHaohao +package seedu.address.model.account; + +import static java.util.Objects.requireNonNull; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Iterator; + +import seedu.address.model.account.exceptions.AccountNotFoundException; +import seedu.address.model.account.exceptions.DuplicateAccountException; + + +/** + * A list of accounts that enforces uniqueness between its elements and does not allow nulls. + * <p> + * Supports a minimal set of list operations. + * + * @see Account#equals(Object) + */ +public class UniqueAccountList implements Serializable, Iterable<Account> { + private final ArrayList<Account> internalList = new ArrayList<Account>(); + + /** + * Returns true if the list contains an equivalent account as the given argument. + */ + public boolean contains(Account toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds a account to the list. + * + * @throws DuplicateAccountException if the account to add is an account with the same username in the list. + */ + public void add(Account toAdd) throws DuplicateAccountException { + requireNonNull(toAdd); + if (containsUsername(toAdd)) { + throw new DuplicateAccountException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the account {@code target} in the list with {@code editedAccount}. + * + * @throws DuplicateAccountException if the replacement is equivalent to another existing account in the list. + * @throws AccountNotFoundException if {@code target} could not be found in the list. + */ + public void setAccount(Account target, Account editedAccount) + throws DuplicateAccountException, AccountNotFoundException { + requireNonNull(editedAccount); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new AccountNotFoundException(); + } + + if (!target.usernameMatches(editedAccount) && this.containsUsername(target)) { + throw new DuplicateAccountException(); + } + + internalList.set(index, editedAccount); + } + + /** + * Removes the equivalent account from the list. + * + * @throws AccountNotFoundException if no such account could be found in the list. + */ + public boolean remove(Account toRemove) throws AccountNotFoundException { + //if (model.getAccountList().searchIfUsernameExist(new Username(username))) { + // throw new AccountNotFoundException("Account not found!"); + //} + requireNonNull(toRemove); + final boolean accountFoundAndDeleted = internalList.remove(toRemove); + if (!accountFoundAndDeleted) { + throw new AccountNotFoundException(); + } + return accountFoundAndDeleted; + } + + /** + * Returns the account that matches with the provided credential, + * returns null if none exists + * + * @param c + * @return + */ + public Account authenticate(Credential c) { + for (Account a : internalList) { + if (a.credentialMatches(c)) { + return a; + } + } + return null; + } + + /** + * Returns true if there is an account with the username provided + * + * @param u + * @return + */ + public boolean containsUsername(Username u) { + for (Account a : internalList) { + if (a.usernameMatches(u)) { + return true; + } + } + return false; + } + + /** + * Returns true if there is an account with an username that is the + * same as that of the credential provided + * + * @param c + * @return + */ + public boolean containsUsername(Credential c) { + return containsUsername(c.getUsername()); + } + + /** + * Returns true if there is an account with an username that is the + * same as that of the account provided + * + * @param a + * @return + */ + public boolean containsUsername(Account a) { + return containsUsername(a.getCredential()); + } + + /** + * Returns the account if there is an account with the username provided + * + * @param u + * @return + */ + public Account searchByUsername(Username u) { + for (Account a : internalList) { + if (a.usernameMatches(u)) { + return a; + } + } + return null; + } + /** + * Returns true if account exists with such username provided + * + * @param u + * @return + */ + public boolean searchIfUsernameExist (Username u) { + for (Account a : internalList) { + if (a.usernameMatches(u)) { + return true; + } + } + return false; + } + + + public int size() { + return internalList.size(); + } + + + @Override + public Iterator<Account> iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueAccountList // instanceof handles nulls + && this.internalList.equals(((UniqueAccountList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } + + +} diff --git a/src/main/java/seedu/address/model/account/Username.java b/src/main/java/seedu/address/model/account/Username.java new file mode 100644 index 000000000000..a4f74cfaced6 --- /dev/null +++ b/src/main/java/seedu/address/model/account/Username.java @@ -0,0 +1,64 @@ +//@@author victortardieu +package seedu.address.model.account; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +import java.io.Serializable; + +/** + * Represents the username of an account + */ +public class Username implements Serializable { + + public static final String MESSAGE_USERNAME_CONSTRAINTS = + "Username should be at least 5 characters long."; + public static final String USERNAME_VALIDATION_REGEX = "\\w{5,}"; + + private final String username; + + /** + * Constructs a Username + * + * @param username + */ + public Username(String username) { + requireNonNull(username); + checkArgument(isValidUsername(username), MESSAGE_USERNAME_CONSTRAINTS); + + this.username = username; + } + + /** + * Returns true if a given string is a valid Username. + */ + public static boolean isValidUsername(String test) { + return test.matches(USERNAME_VALIDATION_REGEX); + } + + + /** + * Returns username. + */ + public String getUsername() { + return username; + } + + @Override + public boolean equals(Object other) { + return other == this + || (other instanceof Username // short circuit if same obj + && this.username.equals(((Username) other).username) // check username + ); + } + + @Override + public int hashCode() { + return username.hashCode(); + } + + @Override + public String toString() { + return username; + } +} diff --git a/src/main/java/seedu/address/model/account/exceptions/AccountNotFoundException.java b/src/main/java/seedu/address/model/account/exceptions/AccountNotFoundException.java new file mode 100644 index 000000000000..f17e37507ac7 --- /dev/null +++ b/src/main/java/seedu/address/model/account/exceptions/AccountNotFoundException.java @@ -0,0 +1,17 @@ +//@@author QiuHaohao +package seedu.address.model.account.exceptions; + +/** + * Signals that the operation is unable to find the specified account. + */ +public class AccountNotFoundException extends Exception { + + /** + * @param message should contain relevant information on the failed constraint(s) + */ + public AccountNotFoundException (String message) { + super (message); + } + + public AccountNotFoundException() {} +} diff --git a/src/main/java/seedu/address/model/account/exceptions/DuplicateAccountException.java b/src/main/java/seedu/address/model/account/exceptions/DuplicateAccountException.java new file mode 100644 index 000000000000..6b9be53941c4 --- /dev/null +++ b/src/main/java/seedu/address/model/account/exceptions/DuplicateAccountException.java @@ -0,0 +1,13 @@ +//@@author QiuHaohao +package seedu.address.model.account.exceptions; + +import seedu.address.commons.exceptions.DuplicateDataException; + +/** + * Signals that the operation will result in duplicate Book objects. + */ +public class DuplicateAccountException extends DuplicateDataException { + public DuplicateAccountException() { + super("Operation would result in duplicate books"); + } +} diff --git a/src/main/java/seedu/address/model/book/Author.java b/src/main/java/seedu/address/model/book/Author.java new file mode 100644 index 000000000000..9fbbab7995d9 --- /dev/null +++ b/src/main/java/seedu/address/model/book/Author.java @@ -0,0 +1,60 @@ +//@@author khiayi +package seedu.address.model.book; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Book's author in the catalogue. + * Guarantees: immutable; is valid as declared in {@link #isValidAuthor(String)} + */ +public class Author { + + public static final String MESSAGE_AUTHOR_CONSTRAINTS = + "Book author should only contain alphanumeric characters and spaces, and it should not be blank"; + + /* + * The first character of the author must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String AUTHOR_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String value; + + /** + * Constructs an {@code Author}. + * + * @param author A valid author. + */ + public Author(String author) { + requireNonNull(author); + checkArgument(isValidAuthor(author), MESSAGE_AUTHOR_CONSTRAINTS); + this.value = author; + } + //@@author + + /** + * Returns true if a given string is a valid book author. + */ + public static boolean isValidAuthor(String test) { + return test.matches(AUTHOR_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Author // instanceof handles nulls + && this.value.equals(((Author) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/book/Avail.java b/src/main/java/seedu/address/model/book/Avail.java new file mode 100644 index 000000000000..35122e6dbcdb --- /dev/null +++ b/src/main/java/seedu/address/model/book/Avail.java @@ -0,0 +1,62 @@ +//@@author khiayi +package seedu.address.model.book; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + + +/** + * Represents a Book's availability in the catalogue. + * Guarantees: immutable; is valid as declared in {@link #isValidAvail(String)} + */ +public class Avail { + public static final String AVAILABLE = "Available"; + public static final String BORROWED = "Borrowed"; + public static final String RESERVED = "Reserved"; + public static final String MESSAGE_AVAIL_CONSTRAINTS = "Book availability should be one of the following:\n " + + "1. " + AVAILABLE + "\n" + + "2. " + BORROWED + "\n" + + "3. " + RESERVED + "\n"; + + + public final String value; + + /** + * Constructs an {@code Avail}. + * + * @param avail A valid availability . + */ + public Avail(String avail) { + requireNonNull(avail); + checkArgument(isValidAvail(avail), MESSAGE_AVAIL_CONSTRAINTS); + this.value = avail; + } + + /** + * Returns if a given string is a valid book avail. + */ + public static boolean isValidAvail(String test) { + return test.equals(AVAILABLE) + || test.equals(BORROWED) + || test.equals(RESERVED); + } + + //@@author + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Avail // instanceof handles nulls + && this.value.equals(((Avail) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/book/Book.java b/src/main/java/seedu/address/model/book/Book.java new file mode 100644 index 000000000000..7f239f988b64 --- /dev/null +++ b/src/main/java/seedu/address/model/book/Book.java @@ -0,0 +1,111 @@ +//@@author khiayi +package seedu.address.model.book; + +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +import seedu.address.model.tag.Tag; +import seedu.address.model.tag.UniqueTagList; + +/** + * Represents a Book in the catalogue. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Book { + + private final Title title; + private final Author author; + private final Isbn isbn; + private final Avail avail; + + private final UniqueTagList tags; + + /** + * Every field must be present and not null. + */ + public Book(Title title, Author author, Isbn isbn, Avail avail, Set<Tag> tags) { + requireAllNonNull(title, author, isbn, avail, tags); + this.title = title; + this.author = author; + this.isbn = isbn; + this.avail = avail; + // protect internal tags from changes in the arg list + this.tags = new UniqueTagList(tags); + } + + public Title getTitle() { + return title; + } + + public Author getAuthor() { + return author; + } + + public Isbn getIsbn() { + return isbn; + } + + public Avail getAvail() { + return avail; + } + + /** + * Returns true if this book's isbn is the same as the isbn provided + * + * @param isbn + * @return + */ + public boolean isbnMatches(Isbn isbn) { + return this.isbn.equals(isbn); + } + + /** + * Returns an immutable tag set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + */ + public Set<Tag> getTags() { + return Collections.unmodifiableSet(tags.toSet()); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof Book)) { + return false; + } + + Book otherBook = (Book) other; + return otherBook.getTitle().equals(this.getTitle()) + && otherBook.getAuthor().equals(this.getAuthor()) + && otherBook.getIsbn().equals(this.getIsbn()) + && otherBook.getAvail().equals(this.getAvail()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(title, author, isbn, avail, tags); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getTitle()) + .append(" Author: ") + .append(getAuthor()) + .append(" Isbn: ") + .append(getIsbn()) + .append(" Avail: ") + .append(getAvail()) + .append(" Tags: "); + getTags().forEach(builder::append); + return builder.toString(); + } + +} diff --git a/src/main/java/seedu/address/model/book/Isbn.java b/src/main/java/seedu/address/model/book/Isbn.java new file mode 100644 index 000000000000..8ec3c0902bde --- /dev/null +++ b/src/main/java/seedu/address/model/book/Isbn.java @@ -0,0 +1,54 @@ +//@@author khiayi +package seedu.address.model.book; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Book's isbn number in the catalogue. + * Guarantees: immutable; is valid as declared in {@link #isValidIsbn(String)} + */ +public class Isbn { + + public static final String MESSAGE_ISBN_CONSTRAINTS = + "Isbn numbers can only contain numbers, and should be at 13 digits long"; + public static final String ISBN_VALIDATION_REGEX = "\\d{13}"; + public final String value; + + /** + * Constructs a {@code Isbn}. + * + * @param isbn A valid isbn number. + */ + public Isbn(String isbn) { + requireNonNull(isbn); + checkArgument(isValidIsbn(isbn), MESSAGE_ISBN_CONSTRAINTS); + this.value = isbn; + } + //@@author + + /** + * Returns true if a given string is a valid book isbn number. + */ + public static boolean isValidIsbn(String test) { + return test.matches(ISBN_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Isbn // instanceof handles nulls + && this.value.equals(((Isbn) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/book/Title.java b/src/main/java/seedu/address/model/book/Title.java new file mode 100644 index 000000000000..979c32c30b6b --- /dev/null +++ b/src/main/java/seedu/address/model/book/Title.java @@ -0,0 +1,60 @@ +//@@author khiayi +package seedu.address.model.book; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.AppUtil.checkArgument; + +/** + * Represents a Book's name in the catalogue. + * Guarantees: immutable; is valid as declared in {@link #isValidTitle(String)} + */ +public class Title { + //@@author khiayi + public static final String MESSAGE_TITLE_CONSTRAINTS = + "Book titles should only contain alphanumeric characters and spaces, and it should not be blank"; + + /* + * The first character of the title must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String TITLE_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String fullTitle; + + /** + * Constructs a {@code Title}. + * + * @param title A valid title. + */ + public Title(String title) { + requireNonNull(title); + checkArgument(isValidTitle(title), MESSAGE_TITLE_CONSTRAINTS); + this.fullTitle = title; + } + //@@author + + /** + * Returns true if a given string is a valid book name. + */ + public static boolean isValidTitle(String test) { + return test.matches(TITLE_VALIDATION_REGEX); + } + + @Override + public String toString() { + return fullTitle; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Title // instanceof handles nulls + && this.fullTitle.equals(((Title) other).fullTitle)); // state check + } + + @Override + public int hashCode() { + return fullTitle.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/book/TitleContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/book/TitleContainsKeywordsPredicate.java new file mode 100644 index 000000000000..f630e25075a2 --- /dev/null +++ b/src/main/java/seedu/address/model/book/TitleContainsKeywordsPredicate.java @@ -0,0 +1,31 @@ +package seedu.address.model.book; + +import java.util.List; +import java.util.function.Predicate; + +import seedu.address.commons.util.StringUtil; + +/** + * Tests that a {@code Book}'s {@code Title} matches any of the keywords given. + */ +public class TitleContainsKeywordsPredicate implements Predicate<Book> { + private final List<String> keywords; + + public TitleContainsKeywordsPredicate(List<String> keywords) { + this.keywords = keywords; + } + + @Override + public boolean test(Book book) { + return keywords.stream() + .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(book.getTitle().fullTitle, keyword)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof TitleContainsKeywordsPredicate // instanceof handles nulls + && this.keywords.equals(((TitleContainsKeywordsPredicate) other).keywords)); // state check + } + +} diff --git a/src/main/java/seedu/address/model/book/UniqueBookList.java b/src/main/java/seedu/address/model/book/UniqueBookList.java new file mode 100644 index 000000000000..bf82d081d8b0 --- /dev/null +++ b/src/main/java/seedu/address/model/book/UniqueBookList.java @@ -0,0 +1,213 @@ +package seedu.address.model.book; + +import static java.util.Objects.requireNonNull; +import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.address.model.book.Avail.AVAILABLE; +import static seedu.address.model.book.Avail.BORROWED; +import static seedu.address.model.book.Avail.RESERVED; + +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.address.commons.util.CollectionUtil; +import seedu.address.model.book.exceptions.BookNotFoundException; +import seedu.address.model.book.exceptions.DuplicateBookException; + +/** + * A list of books that enforces uniqueness between its elements and does not allow nulls. + * <p> + * Supports a minimal set of list operations. + * + * @see Book#equals(Object) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class UniqueBookList implements Iterable<Book> { + + private final ObservableList<Book> internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent book as the given argument. + */ + public boolean contains(Book toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Returns true if there is a book with the same isbn provided + * + * @param p + * @return + */ + public boolean containsIsbn(Isbn p) { + for (Book b : internalList) { + if (b.isbnMatches(p)) { + return true; + } + } + return false; + } + + /** + * Returns true if there is a book with a isbn that is the + * same as that of the book provided + * + * @param toCheck + * @return + */ + public boolean containsSameIsbn(Book toCheck) { + requireNonNull(toCheck); + return containsIsbn(toCheck.getIsbn()); + } + + /** + * Adds a book to the list. + * + * @throws DuplicateBookException if the book to add is a duplicate of an existing book in the list. + */ + public void add(Book toAdd) throws DuplicateBookException { + requireNonNull(toAdd); + if (contains(toAdd) || containsSameIsbn(toAdd)) { + throw new DuplicateBookException(); + } + internalList.add(toAdd); + } + + + /** + * Replaces the book {@code target} in the list with {@code editedBook}. + * + * @throws DuplicateBookException if the replacement is equivalent to another existing book in the list. + * @throws BookNotFoundException if {@code target} could not be found in the list. + */ + public void setBook(Book target, Book editedBook) + throws DuplicateBookException, BookNotFoundException { + requireNonNull(editedBook); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new BookNotFoundException(); + } + + if (!target.equals(editedBook) && internalList.contains(editedBook)) { + throw new DuplicateBookException(); + } + + internalList.set(index, editedBook); + } + + /** + * Removes the equivalent book from the list. + * + * @throws BookNotFoundException if no such book could be found in the list. + */ + public boolean remove(Book toRemove) throws BookNotFoundException { + requireNonNull(toRemove); + final boolean bookFoundAndDeleted = internalList.remove(toRemove); + if (!bookFoundAndDeleted) { + throw new BookNotFoundException(); + } + return bookFoundAndDeleted; + } + + public void setBooks(UniqueBookList replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setBooks(List<Book> books) throws DuplicateBookException { + requireAllNonNull(books); + final UniqueBookList replacement = new UniqueBookList(); + for (final Book book : books) { + replacement.add(book); + } + setBooks(replacement); + } + //@@author LeKhangTai + /** + * @param target select the index book to be returned + * @param returnedBook duplicated book that will replace original book + * @throws BookNotFoundException + */ + public void replaceReturnedBook(Book target, Book returnedBook) throws BookNotFoundException { + requireNonNull(returnedBook); + int index = internalList.indexOf(target); + String status = target.getAvail().toString(); + switch (status) { + case (BORROWED): + internalList.set(index, returnedBook); + break; + case (RESERVED): + internalList.set(index, returnedBook); + break; + + default: + throw new BookNotFoundException(); + } + } + + /** + * @param target select the index book to be borrowed + * @param borrowedBook duplicated book that will replace original book + * @throws BookNotFoundException + */ + public void replaceBorrowedBook(Book target, Book borrowedBook) throws BookNotFoundException { + requireNonNull(borrowedBook); + + int index = internalList.indexOf(target); + String status = target.getAvail().toString(); + switch (status) { + case (AVAILABLE): + internalList.set(index, borrowedBook); + break; + + default: + throw new BookNotFoundException(); + } + } + + /** + * @param target select the index book to be reserved + * @param reservedBook duplicated book that will replace original book + * @throws BookNotFoundException + */ + public void replaceReservedBook(Book target, Book reservedBook) throws BookNotFoundException { + + requireNonNull(reservedBook); + int index = internalList.indexOf(target); + String status = target.getAvail().toString(); + switch (status) { + case (BORROWED): + internalList.set(index, reservedBook); + break; + + default: + throw new BookNotFoundException(); + } + } + //@@author + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList<Book> asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator<Book> iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueBookList // instanceof handles nulls + && this.internalList.equals(((UniqueBookList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} diff --git a/src/main/java/seedu/address/model/book/exceptions/BookNotFoundException.java b/src/main/java/seedu/address/model/book/exceptions/BookNotFoundException.java new file mode 100644 index 000000000000..58a878fea6bd --- /dev/null +++ b/src/main/java/seedu/address/model/book/exceptions/BookNotFoundException.java @@ -0,0 +1,7 @@ +package seedu.address.model.book.exceptions; + +/** + * Signals that the operation is unable to find the specified book. + */ +public class BookNotFoundException extends Exception { +} diff --git a/src/main/java/seedu/address/model/book/exceptions/DuplicateBookException.java b/src/main/java/seedu/address/model/book/exceptions/DuplicateBookException.java new file mode 100644 index 000000000000..c926dff56092 --- /dev/null +++ b/src/main/java/seedu/address/model/book/exceptions/DuplicateBookException.java @@ -0,0 +1,12 @@ +package seedu.address.model.book.exceptions; + +import seedu.address.commons.exceptions.DuplicateDataException; + +/** + * Signals that the operation will result in duplicate Book objects. + */ +public class DuplicateBookException extends DuplicateDataException { + public DuplicateBookException() { + super("Operation would result in duplicate books"); + } +} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index 5e981f07790a..000000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,58 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_ADDRESS_CONSTRAINTS = - "Person addresses can take any values, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String ADDRESS_VALIDATION_REGEX = "[^\\s].*"; - - public final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_ADDRESS_CONSTRAINTS); - this.value = address; - } - - /** - * Returns true if a given string is a valid person email. - */ - public static boolean isValidAddress(String test) { - return test.matches(ADDRESS_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && this.value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/address/model/person/Email.java deleted file mode 100644 index 3759a577ec59..000000000000 --- a/src/main/java/seedu/address/model/person/Email.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's email in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} - */ -public class Email { - - private static final String SPECIAL_CHARACTERS = "!#$%&'*+/=?`{|}~^.-"; - public static final String MESSAGE_EMAIL_CONSTRAINTS = "Person emails should be of the format local-part@domain " - + "and adhere to the following constraints:\n" - + "1. The local-part should only contain alphanumeric characters and these special characters, excluding " - + "the parentheses, (" + SPECIAL_CHARACTERS + ") .\n" - + "2. This is followed by a '@' and then a domain name. " - + "The domain name must:\n" - + " - be at least 2 characters long\n" - + " - start and end with alphanumeric characters\n" - + " - consist of alphanumeric characters, a period or a hyphen for the characters in between, if any."; - // alphanumeric and special characters - private static final String LOCAL_PART_REGEX = "^[\\w" + SPECIAL_CHARACTERS + "]+"; - private static final String DOMAIN_FIRST_CHARACTER_REGEX = "[^\\W_]"; // alphanumeric characters except underscore - private static final String DOMAIN_MIDDLE_REGEX = "[a-zA-Z0-9.-]*"; // alphanumeric, period and hyphen - private static final String DOMAIN_LAST_CHARACTER_REGEX = "[^\\W_]$"; - public static final String EMAIL_VALIDATION_REGEX = LOCAL_PART_REGEX + "@" - + DOMAIN_FIRST_CHARACTER_REGEX + DOMAIN_MIDDLE_REGEX + DOMAIN_LAST_CHARACTER_REGEX; - - public final String value; - - /** - * Constructs an {@code Email}. - * - * @param email A valid email address. - */ - public Email(String email) { - requireNonNull(email); - checkArgument(isValidEmail(email), MESSAGE_EMAIL_CONSTRAINTS); - this.value = email; - } - - /** - * Returns if a given string is a valid person email. - */ - public static boolean isValidEmail(String test) { - return test.matches(EMAIL_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Email // instanceof handles nulls - && this.value.equals(((Email) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java deleted file mode 100644 index 827e2cc106bd..000000000000 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ /dev/null @@ -1,31 +0,0 @@ -package seedu.address.model.person; - -import java.util.List; -import java.util.function.Predicate; - -import seedu.address.commons.util.StringUtil; - -/** - * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. - */ -public class NameContainsKeywordsPredicate implements Predicate<Person> { - private final List<String> keywords; - - public NameContainsKeywordsPredicate(List<String> keywords) { - this.keywords = keywords; - } - - @Override - public boolean test(Person person) { - return keywords.stream() - .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof NameContainsKeywordsPredicate // instanceof handles nulls - && this.keywords.equals(((NameContainsKeywordsPredicate) other).keywords)); // state check - } - -} diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/address/model/person/Person.java deleted file mode 100644 index ec9f2aa5e919..000000000000 --- a/src/main/java/seedu/address/model/person/Person.java +++ /dev/null @@ -1,100 +0,0 @@ -package seedu.address.model.person; - -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Collections; -import java.util.Objects; -import java.util.Set; - -import seedu.address.model.tag.Tag; -import seedu.address.model.tag.UniqueTagList; - -/** - * Represents a Person in the address book. - * Guarantees: details are present and not null, field values are validated, immutable. - */ -public class Person { - - private final Name name; - private final Phone phone; - private final Email email; - private final Address address; - - private final UniqueTagList tags; - - /** - * Every field must be present and not null. - */ - public Person(Name name, Phone phone, Email email, Address address, Set<Tag> tags) { - requireAllNonNull(name, phone, email, address, tags); - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - // protect internal tags from changes in the arg list - this.tags = new UniqueTagList(tags); - } - - public Name getName() { - return name; - } - - public Phone getPhone() { - return phone; - } - - public Email getEmail() { - return email; - } - - public Address getAddress() { - return address; - } - - /** - * Returns an immutable tag set, which throws {@code UnsupportedOperationException} - * if modification is attempted. - */ - public Set<Tag> getTags() { - return Collections.unmodifiableSet(tags.toSet()); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof Person)) { - return false; - } - - Person otherPerson = (Person) other; - return otherPerson.getName().equals(this.getName()) - && otherPerson.getPhone().equals(this.getPhone()) - && otherPerson.getEmail().equals(this.getEmail()) - && otherPerson.getAddress().equals(this.getAddress()); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - builder.append(getName()) - .append(" Phone: ") - .append(getPhone()) - .append(" Email: ") - .append(getEmail()) - .append(" Address: ") - .append(getAddress()) - .append(" Tags: "); - getTags().forEach(builder::append); - return builder.toString(); - } - -} diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/address/model/person/Phone.java deleted file mode 100644 index 11b5435ac247..000000000000 --- a/src/main/java/seedu/address/model/person/Phone.java +++ /dev/null @@ -1,53 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's phone number in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} - */ -public class Phone { - - - public static final String MESSAGE_PHONE_CONSTRAINTS = - "Phone numbers can only contain numbers, and should be at least 3 digits long"; - public static final String PHONE_VALIDATION_REGEX = "\\d{3,}"; - public final String value; - - /** - * Constructs a {@code Phone}. - * - * @param phone A valid phone number. - */ - public Phone(String phone) { - requireNonNull(phone); - checkArgument(isValidPhone(phone), MESSAGE_PHONE_CONSTRAINTS); - this.value = phone; - } - - /** - * Returns true if a given string is a valid person phone number. - */ - public static boolean isValidPhone(String test) { - return test.matches(PHONE_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Phone // instanceof handles nulls - && this.value.equals(((Phone) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/address/model/person/UniquePersonList.java deleted file mode 100644 index f2c4c4c585e4..000000000000 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ /dev/null @@ -1,120 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.Iterator; -import java.util.List; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * A list of persons that enforces uniqueness between its elements and does not allow nulls. - * - * Supports a minimal set of list operations. - * - * @see Person#equals(Object) - * @see CollectionUtil#elementsAreUnique(Collection) - */ -public class UniquePersonList implements Iterable<Person> { - - private final ObservableList<Person> internalList = FXCollections.observableArrayList(); - - /** - * Returns true if the list contains an equivalent person as the given argument. - */ - public boolean contains(Person toCheck) { - requireNonNull(toCheck); - return internalList.contains(toCheck); - } - - /** - * Adds a person to the list. - * - * @throws DuplicatePersonException if the person to add is a duplicate of an existing person in the list. - */ - public void add(Person toAdd) throws DuplicatePersonException { - requireNonNull(toAdd); - if (contains(toAdd)) { - throw new DuplicatePersonException(); - } - internalList.add(toAdd); - } - - /** - * Replaces the person {@code target} in the list with {@code editedPerson}. - * - * @throws DuplicatePersonException if the replacement is equivalent to another existing person in the list. - * @throws PersonNotFoundException if {@code target} could not be found in the list. - */ - public void setPerson(Person target, Person editedPerson) - throws DuplicatePersonException, PersonNotFoundException { - requireNonNull(editedPerson); - - int index = internalList.indexOf(target); - if (index == -1) { - throw new PersonNotFoundException(); - } - - if (!target.equals(editedPerson) && internalList.contains(editedPerson)) { - throw new DuplicatePersonException(); - } - - internalList.set(index, editedPerson); - } - - /** - * Removes the equivalent person from the list. - * - * @throws PersonNotFoundException if no such person could be found in the list. - */ - public boolean remove(Person toRemove) throws PersonNotFoundException { - requireNonNull(toRemove); - final boolean personFoundAndDeleted = internalList.remove(toRemove); - if (!personFoundAndDeleted) { - throw new PersonNotFoundException(); - } - return personFoundAndDeleted; - } - - public void setPersons(UniquePersonList replacement) { - this.internalList.setAll(replacement.internalList); - } - - public void setPersons(List<Person> persons) throws DuplicatePersonException { - requireAllNonNull(persons); - final UniquePersonList replacement = new UniquePersonList(); - for (final Person person : persons) { - replacement.add(person); - } - setPersons(replacement); - } - - /** - * Returns the backing list as an unmodifiable {@code ObservableList}. - */ - public ObservableList<Person> asObservableList() { - return FXCollections.unmodifiableObservableList(internalList); - } - - @Override - public Iterator<Person> iterator() { - return internalList.iterator(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof UniquePersonList // instanceof handles nulls - && this.internalList.equals(((UniquePersonList) other).internalList)); - } - - @Override - public int hashCode() { - return internalList.hashCode(); - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java deleted file mode 100644 index fce401885dc8..000000000000 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ /dev/null @@ -1,12 +0,0 @@ -package seedu.address.model.person.exceptions; - -import seedu.address.commons.exceptions.DuplicateDataException; - -/** - * Signals that the operation will result in duplicate Person objects. - */ -public class DuplicatePersonException extends DuplicateDataException { - public DuplicatePersonException() { - super("Operation would result in duplicate persons"); - } -} diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java deleted file mode 100644 index f757e25f5566..000000000000 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ /dev/null @@ -1,6 +0,0 @@ -package seedu.address.model.person.exceptions; - -/** - * Signals that the operation is unable to find the specified person. - */ -public class PersonNotFoundException extends Exception {} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/address/model/tag/Tag.java index 65bdd769995d..75ea3e9796e5 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -4,13 +4,13 @@ import static seedu.address.commons.util.AppUtil.checkArgument; /** - * Represents a Tag in the address book. + * Represents a Tag in the catalogue. * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} */ public class Tag { public static final String MESSAGE_TAG_CONSTRAINTS = "Tags names should be alphanumeric"; - public static final String TAG_VALIDATION_REGEX = "\\p{Alnum}+"; + public static final String TAG_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; public final String tagName; @@ -35,8 +35,8 @@ public static boolean isValidTagName(String test) { @Override public boolean equals(Object other) { return other == this // short circuit if same object - || (other instanceof Tag // instanceof handles nulls - && this.tagName.equals(((Tag) other).tagName)); // state check + || (other instanceof Tag // instanceof handles nulls + && this.tagName.equals(((Tag) other).tagName)); // state check } @Override diff --git a/src/main/java/seedu/address/model/tag/UniqueTagList.java b/src/main/java/seedu/address/model/tag/UniqueTagList.java index e9a74947fc3f..eef36c412cf6 100644 --- a/src/main/java/seedu/address/model/tag/UniqueTagList.java +++ b/src/main/java/seedu/address/model/tag/UniqueTagList.java @@ -14,7 +14,7 @@ /** * A list of tags that enforces no nulls and uniqueness between its elements. - * + * <p> * Supports minimal set of list operations for the app's features. * * @see Tag#equals(Object) @@ -26,7 +26,8 @@ public class UniqueTagList implements Iterable<Tag> { /** * Constructs empty TagList. */ - public UniqueTagList() {} + public UniqueTagList() { + } /** * Creates a UniqueTagList using given tags. @@ -63,8 +64,8 @@ public void setTags(Set<Tag> tags) { public void mergeFrom(UniqueTagList from) { final Set<Tag> alreadyInside = this.toSet(); from.internalList.stream() - .filter(tag -> !alreadyInside.contains(tag)) - .forEach(internalList::add); + .filter(tag -> !alreadyInside.contains(tag)) + .forEach(internalList::add); assert CollectionUtil.elementsAreUnique(internalList); } @@ -110,8 +111,8 @@ public ObservableList<Tag> asObservableList() { public boolean equals(Object other) { assert CollectionUtil.elementsAreUnique(internalList); return other == this // short circuit if same object - || (other instanceof UniqueTagList // instanceof handles nulls - && this.internalList.equals(((UniqueTagList) other).internalList)); + || (other instanceof UniqueTagList // instanceof handles nulls + && this.internalList.equals(((UniqueTagList) other).internalList)); } /** diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java index aea96bfb31f3..b07ecda8dd4e 100644 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ b/src/main/java/seedu/address/model/util/SampleDataUtil.java @@ -3,52 +3,49 @@ import java.util.HashSet; import java.util.Set; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.address.model.Catalogue; +import seedu.address.model.ReadOnlyCatalogue; +import seedu.address.model.book.Author; +import seedu.address.model.book.Avail; +import seedu.address.model.book.Book; +import seedu.address.model.book.Isbn; +import seedu.address.model.book.Title; +import seedu.address.model.book.exceptions.DuplicateBookException; import seedu.address.model.tag.Tag; /** - * Contains utility methods for populating {@code AddressBook} with sample data. + * Contains utility methods for populating {@code Catalogue} with sample data. */ public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) + public static Book[] getSampleBooks() { + return new Book[] { + new Book(new Title("Animal Farm"), new Author("George Orwell"), new Isbn("9780736692427"), + new Avail("Available"), getTagSet("political", "satire")), + new Book(new Title("Breaking Dawn"), new Author("Stephenie Meyer"), new Isbn("9780316067928"), + new Avail("Available"), getTagSet("fiction")), + new Book(new Title("California Girl"), new Author("Jefferson Parker"), new Isbn("9780060562373"), + new Avail("Borrowed"), + getTagSet("fiction", "mystery")), + new Book(new Title("Delirium"), new Author("Lauren Oliver"), new Isbn("9780061726835"), + new Avail("Borrowed"), getTagSet("dystopian", "fiction")), + new Book(new Title("Invisible Man"), new Author("Ralph Ellison"), new Isbn("9780140023350"), + new Avail("Borrowed"), + getTagSet("fiction")), + new Book(new Title("Romeo and Juliet"), new Author("William Shakespeare"), new Isbn("9780743477116"), + new Avail("Borrowed"), + getTagSet("classics", "romance")) }; } - public static ReadOnlyAddressBook getSampleAddressBook() { + public static ReadOnlyCatalogue getSampleCatalogue() { try { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); + Catalogue sampleAb = new Catalogue(); + for (Book sampleBook : getSampleBooks()) { + sampleAb.addBook(sampleBook); } return sampleAb; - } catch (DuplicatePersonException e) { - throw new AssertionError("sample data cannot contain duplicate persons", e); + } catch (DuplicateBookException e) { + throw new AssertionError("sample data cannot contain duplicate books", e); } } diff --git a/src/main/java/seedu/address/storage/AccountListStorage.java b/src/main/java/seedu/address/storage/AccountListStorage.java new file mode 100644 index 000000000000..ef70207aaa7a --- /dev/null +++ b/src/main/java/seedu/address/storage/AccountListStorage.java @@ -0,0 +1,48 @@ +//@@author QiuHaohao +package seedu.address.storage; + +import java.io.IOException; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.account.UniqueAccountList; + +//import java.util.Optional; + +/** + * Represents a storage for {@link UniqueAccountList}. + */ +public interface AccountListStorage { + /** + * Returns the file path of the data file. + */ + String getAccountListFilePath(); + + /** + * Returns AccountList data as a {@link UniqueAccountList}. + * Returns {@code Optional.empty()} if storage file is not found. + * + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional<UniqueAccountList> readAccountList() throws DataConversionException, IOException; + + /** + * @see #getAccountListFilePath() + */ + Optional<UniqueAccountList> readAccountList(String filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link UniqueAccountList} to the storage. + * + * @param accountList cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveAccountList(UniqueAccountList accountList) throws IOException; + + /** + * @see #saveAccountList(UniqueAccountList) + */ + void saveAccountList(UniqueAccountList accountList, String filePath) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index cf5b527c063a..000000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,44 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - String getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. - */ - Optional<ReadOnlyAddressBook> readAddressBook() throws DataConversionException, IOException; - - /** - * @see #getAddressBookFilePath() - */ - Optional<ReadOnlyAddressBook> readAddressBook(String filePath) throws DataConversionException, IOException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/CatalogueStorage.java b/src/main/java/seedu/address/storage/CatalogueStorage.java new file mode 100644 index 000000000000..b0cb42599f74 --- /dev/null +++ b/src/main/java/seedu/address/storage/CatalogueStorage.java @@ -0,0 +1,47 @@ +package seedu.address.storage; + +import java.io.IOException; +import java.util.Optional; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.Catalogue; +import seedu.address.model.ReadOnlyCatalogue; + +/** + * Represents a storage for {@link Catalogue}. + */ +public interface CatalogueStorage { + + /** + * Returns the file path of the data file. + */ + String getCatalogueFilePath(); + + /** + * Returns Catalogue data as a {@link ReadOnlyCatalogue}. + * Returns {@code Optional.empty()} if storage file is not found. + * + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional<ReadOnlyCatalogue> readCatalogue() throws DataConversionException, IOException; + + /** + * @see #getCatalogueFilePath() + */ + Optional<ReadOnlyCatalogue> readCatalogue(String filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyCatalogue} to the storage. + * + * @param catalogue cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveCatalogue(ReadOnlyCatalogue catalogue) throws IOException; + + /** + * @see #saveCatalogue(ReadOnlyCatalogue) + */ + void saveCatalogue(ReadOnlyCatalogue catalogue, String filePath) throws IOException; + +} diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java index 4f41aff81251..41cec91d7303 100644 --- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java +++ b/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java @@ -24,12 +24,13 @@ public String getUserPrefsFilePath() { } @Override - public Optional<UserPrefs> readUserPrefs() throws DataConversionException, IOException { + public Optional<UserPrefs> readUserPrefs() throws DataConversionException { return readUserPrefs(filePath); } /** * Similar to {@link #readUserPrefs()} + * * @param prefsFilePath location of the data. Cannot be null. * @throws DataConversionException if the file format is not as expected. */ diff --git a/src/main/java/seedu/address/storage/SerialisedAccountListStorage.java b/src/main/java/seedu/address/storage/SerialisedAccountListStorage.java new file mode 100644 index 000000000000..ae7127603083 --- /dev/null +++ b/src/main/java/seedu/address/storage/SerialisedAccountListStorage.java @@ -0,0 +1,71 @@ +//@@author QiuHaohao +package seedu.address.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.account.UniqueAccountList; + +/** + * A class to access AccountList data stored as an .ser file on the hard disk. + */ +public class SerialisedAccountListStorage implements AccountListStorage { + private static final Logger logger = LogsCenter.getLogger(SerialisedAccountListStorage.class); + + private String filePath; + + public SerialisedAccountListStorage(String filePath) { + this.filePath = filePath; + } + + public String getAccountListFilePath() { + return filePath; + } + + @Override + public Optional<UniqueAccountList> readAccountList() throws DataConversionException, IOException { + return readAccountList(filePath); + } + + @Override + public Optional<UniqueAccountList> readAccountList(String filePath) throws DataConversionException, IOException { + requireNonNull(filePath); + FileInputStream file = new FileInputStream(filePath); + ObjectInputStream in = new ObjectInputStream(file); + + if (!new File(filePath).exists()) { + logger.info("AccountList file " + filePath + " not found"); + return Optional.empty(); + } + + UniqueAccountList accountList = SerialisedFileStorage.loadDataFromSaveFile(in); + return Optional.of(accountList); + } + + @Override + public void saveAccountList(UniqueAccountList accountList) throws IOException { + saveAccountList(accountList, filePath); + } + + @Override + public void saveAccountList(UniqueAccountList accountList, String filePath) throws IOException { + requireNonNull(accountList); + requireNonNull(filePath); + + FileOutputStream file = new FileOutputStream(filePath); + ObjectOutputStream out = new ObjectOutputStream(file); + SerialisedFileStorage.saveDataToFile(out, accountList); + out.close(); + file.close(); + } +} diff --git a/src/main/java/seedu/address/storage/SerialisedFileStorage.java b/src/main/java/seedu/address/storage/SerialisedFileStorage.java new file mode 100644 index 000000000000..5539be27284e --- /dev/null +++ b/src/main/java/seedu/address/storage/SerialisedFileStorage.java @@ -0,0 +1,38 @@ +//@@author QiuHaohao +package seedu.address.storage; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.model.account.UniqueAccountList; + +/** + * Stores accountList data in a .ser file + */ +public class SerialisedFileStorage { + /** + * Saves the given catalogue data to the specified file. + */ + public static void saveDataToFile(ObjectOutputStream out, UniqueAccountList accountList) { + try { + out.writeObject(accountList); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Returns catalogue in the file or an empty catalogue + */ + public static UniqueAccountList loadDataFromSaveFile(ObjectInputStream in) throws DataConversionException { + try { + return (UniqueAccountList) in.readObject(); + } catch (IOException e) { + throw new DataConversionException(e); + } catch (ClassNotFoundException e) { + throw new DataConversionException(e); + } + } +} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java index c0881a5a6483..8f80c0962957 100644 --- a/src/main/java/seedu/address/storage/Storage.java +++ b/src/main/java/seedu/address/storage/Storage.java @@ -3,16 +3,18 @@ import java.io.IOException; import java.util.Optional; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.AccountListChangedEvent; +import seedu.address.commons.events.model.CatalogueChangedEvent; import seedu.address.commons.events.storage.DataSavingExceptionEvent; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyCatalogue; import seedu.address.model.UserPrefs; +import seedu.address.model.account.UniqueAccountList; /** * API of the Storage component */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { +public interface Storage extends CatalogueStorage, UserPrefsStorage, AccountListStorage { @Override Optional<UserPrefs> readUserPrefs() throws DataConversionException, IOException; @@ -21,18 +23,27 @@ public interface Storage extends AddressBookStorage, UserPrefsStorage { void saveUserPrefs(UserPrefs userPrefs) throws IOException; @Override - String getAddressBookFilePath(); + String getCatalogueFilePath(); @Override - Optional<ReadOnlyAddressBook> readAddressBook() throws DataConversionException, IOException; + Optional<ReadOnlyCatalogue> readCatalogue() throws DataConversionException, IOException; @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; + void saveCatalogue(ReadOnlyCatalogue catalogue) throws IOException; /** - * Saves the current version of the Address Book to the hard disk. - * Creates the data file if it is missing. + * Saves the current version of the Catalogue to the hard disk. + * Creates the data file if it is missing. * Raises {@link DataSavingExceptionEvent} if there was an error during saving. */ - void handleAddressBookChangedEvent(AddressBookChangedEvent abce); + void handleCatalogueChangedEvent(CatalogueChangedEvent abce); + + //@@author QiuHaohao + String getAccountListFilePath(); + + Optional<UniqueAccountList> readAccountList() throws DataConversionException, IOException; + + void saveAccountList(UniqueAccountList accountList) throws IOException; + + void handleAccountListChangedEvent(AccountListChangedEvent event); } diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java index 53967b391a5a..ca0eb57eb79c 100644 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ b/src/main/java/seedu/address/storage/StorageManager.java @@ -8,26 +8,31 @@ import seedu.address.commons.core.ComponentManager; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.AccountListChangedEvent; +import seedu.address.commons.events.model.CatalogueChangedEvent; import seedu.address.commons.events.storage.DataSavingExceptionEvent; import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.address.model.ReadOnlyCatalogue; import seedu.address.model.UserPrefs; +import seedu.address.model.account.UniqueAccountList; /** - * Manages storage of AddressBook data in local storage. + * Manages storage of Catalogue data in local storage. */ public class StorageManager extends ComponentManager implements Storage { private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; + private CatalogueStorage catalogueStorage; private UserPrefsStorage userPrefsStorage; + private AccountListStorage accountListStorage; - - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { + public StorageManager(CatalogueStorage catalogueStorage, + UserPrefsStorage userPrefsStorage, + AccountListStorage accountListStorage) { super(); - this.addressBookStorage = addressBookStorage; + this.catalogueStorage = catalogueStorage; this.userPrefsStorage = userPrefsStorage; + this.accountListStorage = accountListStorage; } // ================ UserPrefs methods ============================== @@ -48,45 +53,84 @@ public void saveUserPrefs(UserPrefs userPrefs) throws IOException { } - // ================ AddressBook methods ============================== + // ================ Catalogue methods ============================== @Override - public String getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); + public String getCatalogueFilePath() { + return catalogueStorage.getCatalogueFilePath(); } @Override - public Optional<ReadOnlyAddressBook> readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); + public Optional<ReadOnlyCatalogue> readCatalogue() throws DataConversionException, IOException { + return readCatalogue(catalogueStorage.getCatalogueFilePath()); } @Override - public Optional<ReadOnlyAddressBook> readAddressBook(String filePath) throws DataConversionException, IOException { + public Optional<ReadOnlyCatalogue> readCatalogue(String filePath) throws DataConversionException, IOException { logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); + return catalogueStorage.readCatalogue(filePath); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); + public void saveCatalogue(ReadOnlyCatalogue catalogue) throws IOException { + saveCatalogue(catalogue, catalogueStorage.getCatalogueFilePath()); } @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) throws IOException { + public void saveCatalogue(ReadOnlyCatalogue catalogue, String filePath) throws IOException { logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); + catalogueStorage.saveCatalogue(catalogue, filePath); } @Override @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent event) { + public void handleCatalogueChangedEvent(CatalogueChangedEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event, "Local data changed, saving to file")); try { - saveAddressBook(event.data); + saveCatalogue(event.data); } catch (IOException e) { raise(new DataSavingExceptionEvent(e)); } } + //@@author QiuHaohao + @Override + @Subscribe + public void handleAccountListChangedEvent(AccountListChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, "AccountList data changed, saving to file")); + try { + saveAccountList(event.data); + } catch (IOException e) { + raise(new DataSavingExceptionEvent(e)); + } + } + + @Override + public String getAccountListFilePath() { + return accountListStorage.getAccountListFilePath(); + } + + @Override + public Optional<UniqueAccountList> readAccountList() throws DataConversionException, IOException { + return readAccountList(accountListStorage.getAccountListFilePath()); + } + + @Override + public Optional<UniqueAccountList> readAccountList(String filePath) throws DataConversionException, IOException { + logger.fine("Attempting to read data from file: " + filePath); + return accountListStorage.readAccountList(filePath); + } + + @Override + public void saveAccountList(UniqueAccountList accountList) throws IOException { + saveAccountList(accountList, accountListStorage.getAccountListFilePath()); + } + + @Override + public void saveAccountList(UniqueAccountList accountList, String filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + accountListStorage.saveAccountList(accountList, filePath); + } + } diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/seedu/address/storage/UserPrefsStorage.java index 146477fad976..f488646f77c9 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/seedu/address/storage/UserPrefsStorage.java @@ -18,14 +18,16 @@ public interface UserPrefsStorage { /** * Returns UserPrefs data from storage. - * Returns {@code Optional.empty()} if storage file is not found. + * Returns {@code Optional.empty()} if storage file is not found. + * * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. + * @throws IOException if there was any problem when reading from the storage. */ Optional<UserPrefs> readUserPrefs() throws DataConversionException, IOException; /** * Saves the given {@link seedu.address.model.UserPrefs} to the storage. + * * @param userPrefs cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/seedu/address/storage/XmlAdaptedBook.java b/src/main/java/seedu/address/storage/XmlAdaptedBook.java new file mode 100644 index 000000000000..afe884ba6f13 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlAdaptedBook.java @@ -0,0 +1,139 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.book.Author; +import seedu.address.model.book.Avail; +import seedu.address.model.book.Book; +import seedu.address.model.book.Isbn; +import seedu.address.model.book.Title; +import seedu.address.model.tag.Tag; + +/** + * JAXB-friendly version of the Book. + */ +public class XmlAdaptedBook { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Book's %s field is missing!"; + + @XmlElement(required = true) + private String title; + @XmlElement(required = true) + private String author; + @XmlElement(required = true) + private String isbn; + @XmlElement(required = true) + private String avail; + + @XmlElement + private List<XmlAdaptedTag> tagged = new ArrayList<>(); + + /** + * Constructs an XmlAdaptedBook. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedBook() { + } + + /** + * Constructs an {@code XmlAdaptedBook} with the given book details. + */ + public XmlAdaptedBook(String title, String author, String isbn, String avail, List<XmlAdaptedTag> tagged) { + this.title = title; + this.author = author; + this.isbn = isbn; + this.avail = avail; + if (tagged != null) { + this.tagged = new ArrayList<>(tagged); + } + } + + /** + * Converts a given Book into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedBook + */ + public XmlAdaptedBook(Book source) { + title = source.getTitle().fullTitle; + author = source.getAuthor().value; + isbn = source.getIsbn().value; + avail = source.getAvail().value; + tagged = new ArrayList<>(); + for (Tag tag : source.getTags()) { + tagged.add(new XmlAdaptedTag(tag)); + } + } + + /** + * Converts this jaxb-friendly adapted book object into the model's Book object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted book + */ + public Book toModelType() throws IllegalValueException { + final List<Tag> bookTags = new ArrayList<>(); + for (XmlAdaptedTag tag : tagged) { + bookTags.add(tag.toModelType()); + } + + if (this.title == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Title.class.getSimpleName())); + } + if (!Title.isValidTitle(this.title)) { + throw new IllegalValueException(Title.MESSAGE_TITLE_CONSTRAINTS); + } + final Title title = new Title(this.title); + + if (this.author == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Author.class.getSimpleName())); + } + if (!Author.isValidAuthor(this.author)) { + throw new IllegalValueException(Author.MESSAGE_AUTHOR_CONSTRAINTS); + } + final Author author = new Author(this.author); + + if (this.isbn == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Isbn.class.getSimpleName())); + } + if (!Isbn.isValidIsbn(this.isbn)) { + throw new IllegalValueException(Isbn.MESSAGE_ISBN_CONSTRAINTS); + } + final Isbn isbn = new Isbn(this.isbn); + + if (this.avail == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, Avail.class.getSimpleName())); + } + if (!Avail.isValidAvail(this.avail)) { + throw new IllegalValueException(Avail.MESSAGE_AVAIL_CONSTRAINTS); + } + final Avail avail = new Avail(this.avail); + + final Set<Tag> tags = new HashSet<>(bookTags); + return new Book(title, author, isbn, avail, tags); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedBook)) { + return false; + } + + XmlAdaptedBook otherBook = (XmlAdaptedBook) other; + return Objects.equals(title, otherBook.title) + && Objects.equals(author, otherBook.author) + && Objects.equals(isbn, otherBook.isbn) + && Objects.equals(avail, otherBook.avail) + && tagged.equals(otherBook.tagged); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java b/src/main/java/seedu/address/storage/XmlAdaptedPerson.java deleted file mode 100644 index 2cd92dc4fd20..000000000000 --- a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java +++ /dev/null @@ -1,137 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Objects; -import java.util.Set; - -import javax.xml.bind.annotation.XmlElement; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * JAXB-friendly version of the Person. - */ -public class XmlAdaptedPerson { - - public static final String MISSING_FIELD_MESSAGE_FORMAT = "Person's %s field is missing!"; - - @XmlElement(required = true) - private String name; - @XmlElement(required = true) - private String phone; - @XmlElement(required = true) - private String email; - @XmlElement(required = true) - private String address; - - @XmlElement - private List<XmlAdaptedTag> tagged = new ArrayList<>(); - - /** - * Constructs an XmlAdaptedPerson. - * This is the no-arg constructor that is required by JAXB. - */ - public XmlAdaptedPerson() {} - - /** - * Constructs an {@code XmlAdaptedPerson} with the given person details. - */ - public XmlAdaptedPerson(String name, String phone, String email, String address, List<XmlAdaptedTag> tagged) { - this.name = name; - this.phone = phone; - this.email = email; - this.address = address; - if (tagged != null) { - this.tagged = new ArrayList<>(tagged); - } - } - - /** - * Converts a given Person into this class for JAXB use. - * - * @param source future changes to this will not affect the created XmlAdaptedPerson - */ - public XmlAdaptedPerson(Person source) { - name = source.getName().fullName; - phone = source.getPhone().value; - email = source.getEmail().value; - address = source.getAddress().value; - tagged = new ArrayList<>(); - for (Tag tag : source.getTags()) { - tagged.add(new XmlAdaptedTag(tag)); - } - } - - /** - * Converts this jaxb-friendly adapted person object into the model's Person object. - * - * @throws IllegalValueException if there were any data constraints violated in the adapted person - */ - public Person toModelType() throws IllegalValueException { - final List<Tag> personTags = new ArrayList<>(); - for (XmlAdaptedTag tag : tagged) { - personTags.add(tag.toModelType()); - } - - if (this.name == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Name.class.getSimpleName())); - } - if (!Name.isValidName(this.name)) { - throw new IllegalValueException(Name.MESSAGE_NAME_CONSTRAINTS); - } - final Name name = new Name(this.name); - - if (this.phone == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); - } - if (!Phone.isValidPhone(this.phone)) { - throw new IllegalValueException(Phone.MESSAGE_PHONE_CONSTRAINTS); - } - final Phone phone = new Phone(this.phone); - - if (this.email == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Email.class.getSimpleName())); - } - if (!Email.isValidEmail(this.email)) { - throw new IllegalValueException(Email.MESSAGE_EMAIL_CONSTRAINTS); - } - final Email email = new Email(this.email); - - if (this.address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); - } - if (!Address.isValidAddress(this.address)) { - throw new IllegalValueException(Address.MESSAGE_ADDRESS_CONSTRAINTS); - } - final Address address = new Address(this.address); - - final Set<Tag> tags = new HashSet<>(personTags); - return new Person(name, phone, email, address, tags); - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof XmlAdaptedPerson)) { - return false; - } - - XmlAdaptedPerson otherPerson = (XmlAdaptedPerson) other; - return Objects.equals(name, otherPerson.name) - && Objects.equals(phone, otherPerson.phone) - && Objects.equals(email, otherPerson.email) - && Objects.equals(address, otherPerson.address) - && tagged.equals(otherPerson.tagged); - } -} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedTag.java b/src/main/java/seedu/address/storage/XmlAdaptedTag.java index d3e2d8be9c4f..f8e33bc87446 100644 --- a/src/main/java/seedu/address/storage/XmlAdaptedTag.java +++ b/src/main/java/seedu/address/storage/XmlAdaptedTag.java @@ -17,7 +17,8 @@ public class XmlAdaptedTag { * Constructs an XmlAdaptedTag. * This is the no-arg constructor that is required by JAXB. */ - public XmlAdaptedTag() {} + public XmlAdaptedTag() { + } /** * Constructs a {@code XmlAdaptedTag} with the given {@code tagName}. @@ -38,7 +39,7 @@ public XmlAdaptedTag(Tag source) { /** * Converts this jaxb-friendly adapted tag object into the model's Tag object. * - * @throws IllegalValueException if there were any data constraints violated in the adapted person + * @throws IllegalValueException if there were any data constraints violated in the adapted book */ public Tag toModelType() throws IllegalValueException { if (!Tag.isValidTagName(tagName)) { diff --git a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java b/src/main/java/seedu/address/storage/XmlAddressBookStorage.java deleted file mode 100644 index c77ebe67435c..000000000000 --- a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java +++ /dev/null @@ -1,82 +0,0 @@ -package seedu.address.storage; - -import static java.util.Objects.requireNonNull; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.FileUtil; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * A class to access AddressBook data stored as an xml file on the hard disk. - */ -public class XmlAddressBookStorage implements AddressBookStorage { - - private static final Logger logger = LogsCenter.getLogger(XmlAddressBookStorage.class); - - private String filePath; - - public XmlAddressBookStorage(String filePath) { - this.filePath = filePath; - } - - public String getAddressBookFilePath() { - return filePath; - } - - @Override - public Optional<ReadOnlyAddressBook> readAddressBook() throws DataConversionException, IOException { - return readAddressBook(filePath); - } - - /** - * Similar to {@link #readAddressBook()} - * @param filePath location of the data. Cannot be null - * @throws DataConversionException if the file is not in the correct format. - */ - public Optional<ReadOnlyAddressBook> readAddressBook(String filePath) throws DataConversionException, - FileNotFoundException { - requireNonNull(filePath); - - File addressBookFile = new File(filePath); - - if (!addressBookFile.exists()) { - logger.info("AddressBook file " + addressBookFile + " not found"); - return Optional.empty(); - } - - XmlSerializableAddressBook xmlAddressBook = XmlFileStorage.loadDataFromSaveFile(new File(filePath)); - try { - return Optional.of(xmlAddressBook.toModelType()); - } catch (IllegalValueException ive) { - logger.info("Illegal values found in " + addressBookFile + ": " + ive.getMessage()); - throw new DataConversionException(ive); - } - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); - } - - /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)} - * @param filePath location of the data. Cannot be null - */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) throws IOException { - requireNonNull(addressBook); - requireNonNull(filePath); - - File file = new File(filePath); - FileUtil.createIfMissing(file); - XmlFileStorage.saveDataToFile(file, new XmlSerializableAddressBook(addressBook)); - } - -} diff --git a/src/main/java/seedu/address/storage/XmlCatalogueStorage.java b/src/main/java/seedu/address/storage/XmlCatalogueStorage.java new file mode 100644 index 000000000000..d294b9dddf57 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlCatalogueStorage.java @@ -0,0 +1,84 @@ +package seedu.address.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.exceptions.DataConversionException; +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.commons.util.FileUtil; +import seedu.address.model.ReadOnlyCatalogue; + +/** + * A class to access Catalogue data stored as an xml file on the hard disk. + */ +public class XmlCatalogueStorage implements CatalogueStorage { + + private static final Logger logger = LogsCenter.getLogger(XmlCatalogueStorage.class); + + private String filePath; + + public XmlCatalogueStorage(String filePath) { + this.filePath = filePath; + } + + public String getCatalogueFilePath() { + return filePath; + } + + @Override + public Optional<ReadOnlyCatalogue> readCatalogue() throws DataConversionException, IOException { + return readCatalogue(filePath); + } + + /** + * Similar to {@link #readCatalogue()} + * + * @param filePath location of the data. Cannot be null + * @throws DataConversionException if the file is not in the correct format. + */ + public Optional<ReadOnlyCatalogue> readCatalogue(String filePath) throws DataConversionException, + FileNotFoundException { + requireNonNull(filePath); + + File catalogueFile = new File(filePath); + + if (!catalogueFile.exists()) { + logger.info("Catalogue file " + catalogueFile + " not found"); + return Optional.empty(); + } + + XmlSerializableCatalogue xmlCatalogue = XmlFileStorage.loadDataFromSaveFile(new File(filePath)); + try { + return Optional.of(xmlCatalogue.toModelType()); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + catalogueFile + ": " + ive.getMessage()); + throw new DataConversionException(ive); + } + } + + @Override + public void saveCatalogue(ReadOnlyCatalogue catalogue) throws IOException { + saveCatalogue(catalogue, filePath); + } + + /** + * Similar to {@link #saveCatalogue(ReadOnlyCatalogue)} + * + * @param filePath location of the data. Cannot be null + */ + public void saveCatalogue(ReadOnlyCatalogue catalogue, String filePath) throws IOException { + requireNonNull(catalogue); + requireNonNull(filePath); + + File file = new File(filePath); + FileUtil.createIfMissing(file); + XmlFileStorage.saveDataToFile(file, new XmlSerializableCatalogue(catalogue)); + } + +} diff --git a/src/main/java/seedu/address/storage/XmlFileStorage.java b/src/main/java/seedu/address/storage/XmlFileStorage.java index 289fcb63038e..a01fb3960602 100644 --- a/src/main/java/seedu/address/storage/XmlFileStorage.java +++ b/src/main/java/seedu/address/storage/XmlFileStorage.java @@ -9,28 +9,28 @@ import seedu.address.commons.util.XmlUtil; /** - * Stores addressbook data in an XML file + * Stores catalogue data in an XML file */ public class XmlFileStorage { /** - * Saves the given addressbook data to the specified file. + * Saves the given catalogue data to the specified file. */ - public static void saveDataToFile(File file, XmlSerializableAddressBook addressBook) - throws FileNotFoundException { + public static void saveDataToFile(File file, XmlSerializableCatalogue catalogue) + throws FileNotFoundException { try { - XmlUtil.saveDataToFile(file, addressBook); + XmlUtil.saveDataToFile(file, catalogue); } catch (JAXBException e) { throw new AssertionError("Unexpected exception " + e.getMessage()); } } /** - * Returns address book in the file or an empty address book + * Returns catalogue in the file or an empty catalogue */ - public static XmlSerializableAddressBook loadDataFromSaveFile(File file) throws DataConversionException, - FileNotFoundException { + public static XmlSerializableCatalogue loadDataFromSaveFile(File file) throws DataConversionException, + FileNotFoundException { try { - return XmlUtil.getDataFromFile(file, XmlSerializableAddressBook.class); + return XmlUtil.getDataFromFile(file, XmlSerializableCatalogue.class); } catch (JAXBException e) { throw new DataConversionException(e); } diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java deleted file mode 100644 index dc820896c312..000000000000 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ /dev/null @@ -1,73 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * An Immutable AddressBook that is serializable to XML format - */ -@XmlRootElement(name = "addressbook") -public class XmlSerializableAddressBook { - - @XmlElement - private List<XmlAdaptedPerson> persons; - @XmlElement - private List<XmlAdaptedTag> tags; - - /** - * Creates an empty XmlSerializableAddressBook. - * This empty constructor is required for marshalling. - */ - public XmlSerializableAddressBook() { - persons = new ArrayList<>(); - tags = new ArrayList<>(); - } - - /** - * Conversion - */ - public XmlSerializableAddressBook(ReadOnlyAddressBook src) { - this(); - persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); - tags.addAll(src.getTagList().stream().map(XmlAdaptedTag::new).collect(Collectors.toList())); - } - - /** - * Converts this addressbook into the model's {@code AddressBook} object. - * - * @throws IllegalValueException if there were any data constraints violated or duplicates in the - * {@code XmlAdaptedPerson} or {@code XmlAdaptedTag}. - */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); - for (XmlAdaptedTag t : tags) { - addressBook.addTag(t.toModelType()); - } - for (XmlAdaptedPerson p : persons) { - addressBook.addPerson(p.toModelType()); - } - return addressBook; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof XmlSerializableAddressBook)) { - return false; - } - - XmlSerializableAddressBook otherAb = (XmlSerializableAddressBook) other; - return persons.equals(otherAb.persons) && tags.equals(otherAb.tags); - } -} diff --git a/src/main/java/seedu/address/storage/XmlSerializableCatalogue.java b/src/main/java/seedu/address/storage/XmlSerializableCatalogue.java new file mode 100644 index 000000000000..1fa628e14203 --- /dev/null +++ b/src/main/java/seedu/address/storage/XmlSerializableCatalogue.java @@ -0,0 +1,73 @@ +package seedu.address.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import seedu.address.commons.exceptions.IllegalValueException; +import seedu.address.model.Catalogue; +import seedu.address.model.ReadOnlyCatalogue; + +/** + * An Immutable Catalogue that is serializable to XML format + */ +@XmlRootElement(name = "catalogue") +public class XmlSerializableCatalogue { + + @XmlElement + private List<XmlAdaptedBook> books; + @XmlElement + private List<XmlAdaptedTag> tags; + + /** + * Creates an empty XmlSerializableCatalogue. + * This empty constructor is required for marshalling. + */ + public XmlSerializableCatalogue() { + books = new ArrayList<>(); + tags = new ArrayList<>(); + } + + /** + * Conversion + */ + public XmlSerializableCatalogue(ReadOnlyCatalogue src) { + this(); + books.addAll(src.getBookList().stream().map(XmlAdaptedBook::new).collect(Collectors.toList())); + tags.addAll(src.getTagList().stream().map(XmlAdaptedTag::new).collect(Collectors.toList())); + } + + /** + * Converts this catalogue into the model's {@code Catalogue} object. + * + * @throws IllegalValueException if there were any data constraints violated or duplicates in the + * {@code XmlAdaptedBook} or {@code XmlAdaptedTag}. + */ + public Catalogue toModelType() throws IllegalValueException { + Catalogue catalogue = new Catalogue(); + for (XmlAdaptedTag t : tags) { + catalogue.addTag(t.toModelType()); + } + for (XmlAdaptedBook p : books) { + catalogue.addBook(p.toModelType()); + } + return catalogue; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlSerializableCatalogue)) { + return false; + } + + XmlSerializableCatalogue otherAb = (XmlSerializableCatalogue) other; + return books.equals(otherAb.books) && tags.equals(otherAb.tags); + } +} diff --git a/src/main/java/seedu/address/ui/BookCard.java b/src/main/java/seedu/address/ui/BookCard.java new file mode 100644 index 000000000000..c28c3518a67e --- /dev/null +++ b/src/main/java/seedu/address/ui/BookCard.java @@ -0,0 +1,120 @@ +//@@author victortardieu +package seedu.address.ui; + +import java.util.Random; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.address.model.book.Book; + + +/** + * An UI component that displays information of a {@code Book}. + */ +public class BookCard extends UiPart<Region> { + + private static final String FXML = "BookListCard.fxml"; + + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see <a href="https://github.com/se-edu/addressbook-level4/issues/336">The issue on Catalogue</a> + */ + + public final Book book; + + @FXML + private HBox cardPane; + @FXML + private Label title; + @FXML + private Label author; + @FXML + private Label id; + @FXML + private Label isbn; + @FXML + private Label avail; + @FXML + private FlowPane tags; + + + public BookCard(Book book, int displayedIndex) { + super(FXML); + this.book = book; + id.setText(displayedIndex + ". "); + title.setText(book.getTitle().fullTitle); + author.setText(book.getAuthor().value); + isbn.setText(book.getIsbn().value); + avail.setText(book.getAvail().toString()); + colorTags(book); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof BookCard)) { + return false; + } + + // state check + BookCard card = (BookCard) other; + return id.getText().equals(card.id.getText()) + && book.equals(card.book); + } + + private String getTagColor() { + Random rand = new Random(); + int sCase = rand.nextInt(10); + switch (sCase) { + case 0: + return "-fx-background-color: blue;"; + case 1: + return "-fx-background-color: green;"; + case 2: + return "-fx-background-color: red;"; + case 3: + return "-fx-background-color: maroon;"; + case 4: + return "-fx-background-color: orange;"; + case 5: + return "-fx-background-color: violet;"; + case 6: + return "-fx-background-color: brown;"; + case 7: + return "-fx-background-color: khaki;"; + case 8: + return "-fx-background-color: olive;"; + case 9: + return "-fx-background-color: indigo;"; + default: + return "-fx-background-color: pink;"; + } + + } + + /** + * Assign a new color to each new tag + * + * @param book + */ + + private void colorTags(Book book) { + book.getTags().forEach(tag -> { + Label tagLabel = new Label(tag.tagName); + tags.getChildren().add(tagLabel); + tagLabel.setStyle(getTagColor()); + }); + } +} +//@@author diff --git a/src/main/java/seedu/address/ui/BookListPanel.java b/src/main/java/seedu/address/ui/BookListPanel.java new file mode 100644 index 000000000000..94b97ba8c904 --- /dev/null +++ b/src/main/java/seedu/address/ui/BookListPanel.java @@ -0,0 +1,88 @@ +package seedu.address.ui; + +import java.util.logging.Logger; + +import org.fxmisc.easybind.EasyBind; + +import com.google.common.eventbus.Subscribe; + +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.address.commons.core.LogsCenter; +import seedu.address.commons.events.ui.BookPanelSelectionChangedEvent; +import seedu.address.commons.events.ui.JumpToListRequestEvent; +import seedu.address.model.book.Book; + +/** + * Panel containing the list of books. + */ +public class BookListPanel extends UiPart<Region> { + private static final String FXML = "BookListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(BookListPanel.class); + + @FXML + private ListView<BookCard> bookListView; + + public BookListPanel(ObservableList<Book> bookList) { + super(FXML); + setConnections(bookList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList<Book> bookList) { + ObservableList<BookCard> mappedList = EasyBind.map( + bookList, (book) -> new BookCard(book, bookList.indexOf(book) + 1)); + bookListView.setItems(mappedList); + bookListView.setCellFactory(listView -> new BookListViewCell()); + setEventHandlerForSelectionChangeEvent(); + } + + private void setEventHandlerForSelectionChangeEvent() { + bookListView.getSelectionModel().selectedItemProperty() + .addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.fine("Selection in book list panel changed to : '" + newValue + "'"); + raise(new BookPanelSelectionChangedEvent(newValue)); + } + }); + } + + /** + * Scrolls to the {@code BookCard} at the {@code index} and selects it. + */ + private void scrollTo(int index) { + Platform.runLater(() -> { + bookListView.scrollTo(index); + bookListView.getSelectionModel().clearAndSelect(index); + }); + } + + @Subscribe + private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollTo(event.targetIndex); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code BookCard}. + */ + class BookListViewCell extends ListCell<BookCard> { + + @Override + protected void updateItem(BookCard book, boolean empty) { + super.updateItem(book, empty); + + if (empty || book == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(book.getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/BrowserPanel.java b/src/main/java/seedu/address/ui/BrowserPanel.java index bb0d61380d5a..5fe81a1eb000 100644 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ b/src/main/java/seedu/address/ui/BrowserPanel.java @@ -1,5 +1,8 @@ package seedu.address.ui; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; import java.net.URL; import java.util.logging.Logger; @@ -12,8 +15,8 @@ import javafx.scene.web.WebView; import seedu.address.MainApp; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; +import seedu.address.commons.events.ui.BookPanelSelectionChangedEvent; +import seedu.address.model.book.Book; /** * The Browser Panel of the App. @@ -21,8 +24,10 @@ public class BrowserPanel extends UiPart<Region> { public static final String DEFAULT_PAGE = "default.html"; + public static final String NO_INTERNET_PAGE = "NoInternet.html"; + public static final String GOOD_READS_URL = "www.goodreads.com"; public static final String SEARCH_PAGE_URL = - "https://se-edu.github.io/addressbook-level4/DummySearchPage.html?name="; + "https://www.goodreads.com/search?utf8=%E2%9C%93&query="; private static final String FXML = "BrowserPanel.fxml"; @@ -39,10 +44,20 @@ public BrowserPanel() { loadDefaultPage(); registerAsAnEventHandler(this); + browser.getEngine().setUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) " + + "AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A5297c Safari/602.1"); } - private void loadPersonPage(Person person) { - loadPage(SEARCH_PAGE_URL + person.getName().fullName); + /** + * Loads the GoodReads page for a book + * @param book + */ + private void loadBookPage(Book book) { + if (pingHost(GOOD_READS_URL, 80 , 3000)) { + loadPage(SEARCH_PAGE_URL + book.getIsbn().toString()); + } else { + loadNoInternetPage(); + } } public void loadPage(String url) { @@ -57,6 +72,14 @@ private void loadDefaultPage() { loadPage(defaultPage.toExternalForm()); } + /** + * Loads a HTML indicating that there is no Internet. + */ + private void loadNoInternetPage() { + URL defaultPage = MainApp.class.getResource(FXML_FILE_FOLDER + NO_INTERNET_PAGE); + loadPage(defaultPage.toExternalForm()); + } + /** * Frees resources allocated to the browser. */ @@ -65,8 +88,24 @@ public void freeResources() { } @Subscribe - private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event) { + private void handleBookPanelSelectionChangedEvent(BookPanelSelectionChangedEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); - loadPersonPage(event.getNewSelection().person); + loadBookPage(event.getNewSelection().book); + } + + /** + * Connects to a host and returns a boolean indicating whether the connection is successful + * @param host + * @param port + * @param timeout + * @return + */ + public static boolean pingHost(String host, int port, int timeout) { + try (Socket socket = new Socket()) { + socket.connect(new InetSocketAddress(host, port), timeout); + return true; + } catch (IOException e) { + return false; // Either timeout or unreachable or failed DNS lookup. + } } } diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/address/ui/CommandBox.java index 9cef588df3c3..d3f1e66e5030 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/address/ui/CommandBox.java @@ -11,10 +11,12 @@ import seedu.address.commons.events.ui.NewResultAvailableEvent; import seedu.address.logic.ListElementPointer; import seedu.address.logic.Logic; +import seedu.address.logic.LogicManager; import seedu.address.logic.commands.CommandResult; import seedu.address.logic.commands.exceptions.CommandException; import seedu.address.logic.parser.exceptions.ParseException; + /** * The UI component that is responsible for receiving user command inputs. */ @@ -27,6 +29,8 @@ public class CommandBox extends UiPart<Region> { private final Logic logic; private ListElementPointer historySnapshot; + private boolean isFindNextField = false; + @FXML private TextField commandTextField; @@ -55,6 +59,12 @@ private void handleKeyPress(KeyEvent keyEvent) { keyEvent.consume(); navigateToNextInput(); break; + //@@author victortardieu + case TAB: + keyEvent.consume(); + replaceText(LogicManager.autoComplete(commandTextField.getText())); + break; + //@@author default: // let JavaFx handle the keypress } @@ -148,4 +158,5 @@ private void setStyleToIndicateCommandFailure() { styleClass.add(ERROR_STYLE_CLASS); } + } diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/address/ui/HelpWindow.java index 5254a1b3bbcb..9d7329b050dc 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/address/ui/HelpWindow.java @@ -41,21 +41,21 @@ public HelpWindow() { /** * Shows the help window. - * @throws IllegalStateException - * <ul> - * <li> - * if this method is called on a thread other than the JavaFX Application Thread. - * </li> - * <li> - * if this method is called during animation or layout processing. - * </li> - * <li> - * if this method is called on the primary stage. - * </li> - * <li> - * if {@code dialogStage} is already showing. - * </li> - * </ul> + * + * @throws IllegalStateException <ul> + * <li> + * if this method is called on a thread other than the JavaFX Application Thread. + * </li> + * <li> + * if this method is called during animation or layout processing. + * </li> + * <li> + * if this method is called on the primary stage. + * </li> + * <li> + * if {@code dialogStage} is already showing. + * </li> + * </ul> */ public void show() { logger.fine("Showing help page about the application."); diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/address/ui/MainWindow.java index 20ad5fee906a..31e857c1d101 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/address/ui/MainWindow.java @@ -35,7 +35,7 @@ public class MainWindow extends UiPart<Stage> { // Independent Ui parts residing in this Ui container private BrowserPanel browserPanel; - private PersonListPanel personListPanel; + private BookListPanel bookListPanel; private Config config; private UserPrefs prefs; @@ -49,7 +49,7 @@ public class MainWindow extends UiPart<Stage> { private MenuItem helpMenuItem; @FXML - private StackPane personListPanelPlaceholder; + private StackPane bookListPanelPlaceholder; @FXML private StackPane resultDisplayPlaceholder; @@ -84,6 +84,7 @@ private void setAccelerators() { /** * Sets the accelerator of a MenuItem. + * * @param keyCombination the KeyCombination value of the accelerator */ private void setAccelerator(MenuItem menuItem, KeyCombination keyCombination) { @@ -119,13 +120,13 @@ void fillInnerParts() { browserPanel = new BrowserPanel(); browserPlaceholder.getChildren().add(browserPanel.getRoot()); - personListPanel = new PersonListPanel(logic.getFilteredPersonList()); - personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); + bookListPanel = new BookListPanel(logic.getFilteredBookList()); + bookListPanelPlaceholder.getChildren().add(bookListPanel.getRoot()); ResultDisplay resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getAddressBookFilePath()); + StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getCatalogueFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(logic); @@ -157,7 +158,7 @@ private void setWindowDefaultSize(UserPrefs prefs) { */ GuiSettings getCurrentGuiSetting() { return new GuiSettings(primaryStage.getWidth(), primaryStage.getHeight(), - (int) primaryStage.getX(), (int) primaryStage.getY()); + (int) primaryStage.getX(), (int) primaryStage.getY()); } /** @@ -181,8 +182,8 @@ private void handleExit() { raise(new ExitAppRequestEvent()); } - public PersonListPanel getPersonListPanel() { - return this.personListPanel; + public BookListPanel getBookListPanel() { + return this.bookListPanel; } void releaseResources() { diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/address/ui/PersonCard.java deleted file mode 100644 index f6727ea83abd..000000000000 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ /dev/null @@ -1,70 +0,0 @@ -package seedu.address.ui; - -import javafx.fxml.FXML; -import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; -import seedu.address.model.person.Person; - -/** - * An UI component that displays information of a {@code Person}. - */ -public class PersonCard extends UiPart<Region> { - - private static final String FXML = "PersonListCard.fxml"; - - /** - * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. - * As a consequence, UI elements' variable names cannot be set to such keywords - * or an exception will be thrown by JavaFX during runtime. - * - * @see <a href="https://github.com/se-edu/addressbook-level4/issues/336">The issue on AddressBook level 4</a> - */ - - public final Person person; - - @FXML - private HBox cardPane; - @FXML - private Label name; - @FXML - private Label id; - @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML - private FlowPane tags; - - public PersonCard(Person person, int displayedIndex) { - super(FXML); - this.person = person; - id.setText(displayedIndex + ". "); - name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); - } - - @Override - public boolean equals(Object other) { - // short circuit if same object - if (other == this) { - return true; - } - - // instanceof handles nulls - if (!(other instanceof PersonCard)) { - return false; - } - - // state check - PersonCard card = (PersonCard) other; - return id.getText().equals(card.id.getText()) - && person.equals(card.person); - } -} diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/address/ui/PersonListPanel.java deleted file mode 100644 index 60a4f70f4e71..000000000000 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ /dev/null @@ -1,88 +0,0 @@ -package seedu.address.ui; - -import java.util.logging.Logger; - -import org.fxmisc.easybind.EasyBind; - -import com.google.common.eventbus.Subscribe; - -import javafx.application.Platform; -import javafx.collections.ObservableList; -import javafx.fxml.FXML; -import javafx.scene.control.ListCell; -import javafx.scene.control.ListView; -import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.JumpToListRequestEvent; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; - -/** - * Panel containing the list of persons. - */ -public class PersonListPanel extends UiPart<Region> { - private static final String FXML = "PersonListPanel.fxml"; - private final Logger logger = LogsCenter.getLogger(PersonListPanel.class); - - @FXML - private ListView<PersonCard> personListView; - - public PersonListPanel(ObservableList<Person> personList) { - super(FXML); - setConnections(personList); - registerAsAnEventHandler(this); - } - - private void setConnections(ObservableList<Person> personList) { - ObservableList<PersonCard> mappedList = EasyBind.map( - personList, (person) -> new PersonCard(person, personList.indexOf(person) + 1)); - personListView.setItems(mappedList); - personListView.setCellFactory(listView -> new PersonListViewCell()); - setEventHandlerForSelectionChangeEvent(); - } - - private void setEventHandlerForSelectionChangeEvent() { - personListView.getSelectionModel().selectedItemProperty() - .addListener((observable, oldValue, newValue) -> { - if (newValue != null) { - logger.fine("Selection in person list panel changed to : '" + newValue + "'"); - raise(new PersonPanelSelectionChangedEvent(newValue)); - } - }); - } - - /** - * Scrolls to the {@code PersonCard} at the {@code index} and selects it. - */ - private void scrollTo(int index) { - Platform.runLater(() -> { - personListView.scrollTo(index); - personListView.getSelectionModel().clearAndSelect(index); - }); - } - - @Subscribe - private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event)); - scrollTo(event.targetIndex); - } - - /** - * Custom {@code ListCell} that displays the graphics of a {@code PersonCard}. - */ - class PersonListViewCell extends ListCell<PersonCard> { - - @Override - protected void updateItem(PersonCard person, boolean empty) { - super.updateItem(person, empty); - - if (empty || person == null) { - setGraphic(null); - setText(null); - } else { - setGraphic(person.getRoot()); - } - } - } - -} diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/address/ui/StatusBarFooter.java index 06fb7e50c935..be9533751c7d 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/address/ui/StatusBarFooter.java @@ -12,7 +12,7 @@ import javafx.fxml.FXML; import javafx.scene.layout.Region; import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.address.commons.events.model.CatalogueChangedEvent; /** * A ui for the status bar that is displayed at the footer of the application. @@ -21,21 +21,17 @@ public class StatusBarFooter extends UiPart<Region> { public static final String SYNC_STATUS_INITIAL = "Not updated yet in this session"; public static final String SYNC_STATUS_UPDATED = "Last Updated: %s"; - + private static final Logger logger = LogsCenter.getLogger(StatusBarFooter.class); + private static final String FXML = "StatusBarFooter.fxml"; /** * Used to generate time stamps. - * + * <p> * TODO: change clock to an instance variable. * We leave it as a static variable because manual dependency injection * will require passing down the clock reference all the way from MainApp, * but it should be easier once we have factories/DI frameworks. */ private static Clock clock = Clock.systemDefaultZone(); - - private static final Logger logger = LogsCenter.getLogger(StatusBarFooter.class); - - private static final String FXML = "StatusBarFooter.fxml"; - @FXML private StatusBar syncStatus; @FXML @@ -50,17 +46,17 @@ public StatusBarFooter(String saveLocation) { } /** - * Sets the clock used to determine the current time. + * Returns the clock currently in use. */ - public static void setClock(Clock clock) { - StatusBarFooter.clock = clock; + public static Clock getClock() { + return clock; } /** - * Returns the clock currently in use. + * Sets the clock used to determine the current time. */ - public static Clock getClock() { - return clock; + public static void setClock(Clock clock) { + StatusBarFooter.clock = clock; } private void setSaveLocation(String location) { @@ -72,7 +68,7 @@ private void setSyncStatus(String status) { } @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { + public void handleCatalogueChangedEvent(CatalogueChangedEvent abce) { long now = clock.millis(); String lastUpdated = new Date(now).toString(); logger.info(LogsCenter.getEventHandlingLogMessage(abce, "Setting last updated status to " + lastUpdated)); diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/seedu/address/ui/Ui.java index e6a67fe8c027..c97d61a72f9e 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/seedu/address/ui/Ui.java @@ -7,10 +7,14 @@ */ public interface Ui { - /** Starts the UI (and the App). */ + /** + * Starts the UI (and the App). + */ void start(Stage primaryStage); - /** Stops the UI. */ + /** + * Stops the UI. + */ void stop(); } diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/address/ui/UiManager.java index 3fd3c17be156..daeac8b6ead2 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -30,7 +30,7 @@ public class UiManager extends ComponentManager implements Ui { public static final String FILE_OPS_ERROR_DIALOG_CONTENT_MESSAGE = "Could not save data to file"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/catalogue_icon.png"; private Logic logic; private Config config; @@ -44,6 +44,26 @@ public UiManager(Logic logic, Config config, UserPrefs prefs) { this.prefs = prefs; } + /** + * Shows an alert dialog on {@code owner} with the given parameters. + * This method only returns after the user has closed the alert dialog. + */ + private static void showAlertDialogAndWait(Stage owner, AlertType type, String title, String headerText, + String contentText) { + final Alert alert = new Alert(type); + alert.getDialogPane().getStylesheets().add("view/DarkTheme.css"); + alert.initOwner(owner); + alert.setTitle(title); + alert.setHeaderText(headerText); + alert.setContentText(contentText); + alert.getDialogPane().setId(ALERT_DIALOG_PANE_FIELD_ID); + alert.showAndWait(); + } + + void showAlertDialogAndWait(Alert.AlertType type, String title, String headerText, String contentText) { + showAlertDialogAndWait(mainWindow.getPrimaryStage(), type, title, headerText, contentText); + } + @Override public void start(Stage primaryStage) { logger.info("Starting UI..."); @@ -78,26 +98,6 @@ private Image getImage(String imagePath) { return new Image(MainApp.class.getResourceAsStream(imagePath)); } - void showAlertDialogAndWait(Alert.AlertType type, String title, String headerText, String contentText) { - showAlertDialogAndWait(mainWindow.getPrimaryStage(), type, title, headerText, contentText); - } - - /** - * Shows an alert dialog on {@code owner} with the given parameters. - * This method only returns after the user has closed the alert dialog. - */ - private static void showAlertDialogAndWait(Stage owner, AlertType type, String title, String headerText, - String contentText) { - final Alert alert = new Alert(type); - alert.getDialogPane().getStylesheets().add("view/DarkTheme.css"); - alert.initOwner(owner); - alert.setTitle(title); - alert.setHeaderText(headerText); - alert.setContentText(contentText); - alert.getDialogPane().setId(ALERT_DIALOG_PANE_FIELD_ID); - alert.showAndWait(); - } - /** * Shows an error alert dialog with {@code title} and error message, {@code e}, * and exits the application after the user has closed the alert dialog. @@ -115,6 +115,6 @@ private void showFatalErrorDialogAndShutdown(String title, Throwable e) { private void handleDataSavingExceptionEvent(DataSavingExceptionEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); showFileOperationAlertAndWait(FILE_OPS_ERROR_DIALOG_HEADER_MESSAGE, FILE_OPS_ERROR_DIALOG_CONTENT_MESSAGE, - event.exception); + event.exception); } } diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/seedu/address/ui/UiPart.java index 5c237e57154b..2e83c35625e1 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/seedu/address/ui/UiPart.java @@ -16,7 +16,9 @@ */ public abstract class UiPart<T> { - /** Resource folder where FXML files are stored. */ + /** + * Resource folder where FXML files are stored. + */ public static final String FXML_FILE_FOLDER = "/view/"; private final FXMLLoader fxmlLoader = new FXMLLoader(); @@ -31,6 +33,7 @@ public UiPart(URL fxmlFileUrl) { /** * Constructs a UiPart using the specified FXML file within {@link #FXML_FILE_FOLDER}. + * * @see #UiPart(URL) */ public UiPart(String fxmlFileName) { @@ -47,12 +50,23 @@ public UiPart(URL fxmlFileUrl, T root) { /** * Constructs a UiPart with the specified FXML file within {@link #FXML_FILE_FOLDER} and root object. + * * @see #UiPart(URL, T) */ public UiPart(String fxmlFileName, T root) { this(getFxmlFileUrl(fxmlFileName), root); } + /** + * Returns the FXML file URL for the specified FXML file name within {@link #FXML_FILE_FOLDER}. + */ + private static URL getFxmlFileUrl(String fxmlFileName) { + requireNonNull(fxmlFileName); + String fxmlFileNameWithFolder = FXML_FILE_FOLDER + fxmlFileName; + URL fxmlFileUrl = MainApp.class.getResource(fxmlFileNameWithFolder); + return requireNonNull(fxmlFileUrl); + } + /** * Returns the root object of the scene graph of this UiPart. */ @@ -62,6 +76,7 @@ public T getRoot() { /** * Raises the event via {@link EventsCenter#post(BaseEvent)} + * * @param event */ protected void raise(BaseEvent event) { @@ -70,6 +85,7 @@ protected void raise(BaseEvent event) { /** * Registers the object as an event handler at the {@link EventsCenter} + * * @param handler usually {@code this} */ protected void registerAsAnEventHandler(Object handler) { @@ -78,8 +94,9 @@ protected void registerAsAnEventHandler(Object handler) { /** * Loads the object hierarchy from a FXML document. + * * @param location Location of the FXML document. - * @param root Specifies the root of the object hierarchy. + * @param root Specifies the root of the object hierarchy. */ private void loadFxmlFile(URL location, T root) { requireNonNull(location); @@ -93,14 +110,4 @@ private void loadFxmlFile(URL location, T root) { } } - /** - * Returns the FXML file URL for the specified FXML file name within {@link #FXML_FILE_FOLDER}. - */ - private static URL getFxmlFileUrl(String fxmlFileName) { - requireNonNull(fxmlFileName); - String fxmlFileNameWithFolder = FXML_FILE_FOLDER + fxmlFileName; - URL fxmlFileUrl = MainApp.class.getResource(fxmlFileNameWithFolder); - return requireNonNull(fxmlFileUrl); - } - } diff --git a/src/main/resources/images/catalogue_icon.png b/src/main/resources/images/catalogue_icon.png new file mode 100644 index 000000000000..c2cc077c5409 Binary files /dev/null and b/src/main/resources/images/catalogue_icon.png differ diff --git a/src/main/resources/view/BookListCard.fxml b/src/main/resources/view/BookListCard.fxml new file mode 100644 index 000000000000..22d40e6d31e1 --- /dev/null +++ b/src/main/resources/view/BookListCard.fxml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.layout.ColumnConstraints?> +<?import javafx.scene.layout.FlowPane?> +<?import javafx.scene.layout.GridPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.Region?> +<?import javafx.scene.layout.VBox?> +<HBox xmlns:fx="http://javafx.com/fxml/1" id="cardPane" fx:id="cardPane" xmlns="http://javafx.com/javafx/8"> + <GridPane HBox.hgrow="ALWAYS"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="150"/> + </columnConstraints> + <VBox alignment="CENTER_LEFT" minHeight="105" GridPane.columnIndex="0"> + <padding> + <Insets top="5" right="5" bottom="5" left="15"/> + </padding> + <HBox spacing="5" alignment="CENTER_LEFT"> + <Label fx:id="id" styleClass="cell_big_label"> + <minWidth> + <!-- Ensures that the label text is never truncated --> + <Region fx:constant="USE_PREF_SIZE"/> + </minWidth> + </Label> + <Label fx:id="title" text="\$first" styleClass="cell_big_label"/> + </HBox> + <FlowPane fx:id="tags"/> + <Label fx:id="author" styleClass="cell_small_label" text="\$author"/> + <Label fx:id="isbn" styleClass="cell_small_label" text="\$isbn"/> + <Label fx:id="avail" styleClass="cell_small_label" text="\$avail"/> + </VBox> + </GridPane> +</HBox> diff --git a/src/main/resources/view/BookListPanel.fxml b/src/main/resources/view/BookListPanel.fxml new file mode 100644 index 000000000000..44e04199bfd6 --- /dev/null +++ b/src/main/resources/view/BookListPanel.fxml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.ListView?> +<?import javafx.scene.layout.VBox?> +<VBox xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8"> + <ListView fx:id="bookListView" VBox.vgrow="ALWAYS"/> +</VBox> diff --git a/src/main/resources/view/BrowserPanel.fxml b/src/main/resources/view/BrowserPanel.fxml index 31670827e3da..4ae84568b38a 100644 --- a/src/main/resources/view/BrowserPanel.fxml +++ b/src/main/resources/view/BrowserPanel.fxml @@ -2,7 +2,6 @@ <?import javafx.scene.layout.StackPane?> <?import javafx.scene.web.WebView?> - <StackPane xmlns:fx="http://javafx.com/fxml/1"> - <WebView fx:id="browser"/> + <WebView fx:id="browser"/> </StackPane> diff --git a/src/main/resources/view/CommandBox.fxml b/src/main/resources/view/CommandBox.fxml index 16553ce8eadb..6875abc05e83 100644 --- a/src/main/resources/view/CommandBox.fxml +++ b/src/main/resources/view/CommandBox.fxml @@ -2,8 +2,8 @@ <?import javafx.scene.control.TextField?> <?import javafx.scene.layout.StackPane?> - -<StackPane styleClass="anchor-pane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> - <TextField fx:id="commandTextField" onAction="#handleCommandInputChanged" onKeyPressed="#handleKeyPress" promptText="Enter command here..."/> +<StackPane xmlns:fx="http://javafx.com/fxml/1" styleClass="anchor-pane" xmlns="http://javafx.com/javafx/8"> + <TextField fx:id="commandTextField" onAction="#handleCommandInputChanged" onKeyPressed="#handleKeyPress" + promptText="Enter command here..."/> </StackPane> diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index d06336391cca..3103c0f8677e 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -56,11 +56,7 @@ -fx-size: 35; -fx-border-width: 0 0 1 0; -fx-background-color: transparent; - -fx-border-color: - transparent - transparent - derive(-fx-base, 80%) - transparent; + -fx-border-color: transparent transparent derive(-fx-base, 80%) transparent; -fx-border-insets: 0 10 1 0; } @@ -95,7 +91,7 @@ .list-cell { -fx-label-padding: 0 0 0 0; - -fx-graphic-text-gap : 0; + -fx-graphic-text-gap: 0; -fx-padding: 0 0 0 0; } @@ -133,13 +129,13 @@ } .anchor-pane { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: derive(#1d1d1d, 20%); } .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); - -fx-border-top-width: 1px; + -fx-background-color: derive(#1d1d1d, 20%); + -fx-border-color: derive(#1d1d1d, 10%); + -fx-border-top-width: 1px; } .status-bar { @@ -228,8 +224,8 @@ } .button:pressed, .button:default:hover:pressed { - -fx-background-color: white; - -fx-text-fill: #1d1d1d; + -fx-background-color: white; + -fx-text-fill: #1d1d1d; } .button:focused { @@ -327,7 +323,7 @@ -fx-text-fill: white; } -#filterField, #personListPanel, #personWebpage { +#filterField, #bookListPanel, #bookWebpage { -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); } diff --git a/src/main/resources/view/HelpWindow.fxml b/src/main/resources/view/HelpWindow.fxml index c07e8e685014..9c60c3ded02e 100644 --- a/src/main/resources/view/HelpWindow.fxml +++ b/src/main/resources/view/HelpWindow.fxml @@ -1,18 +1,18 @@ <?xml version="1.0" encoding="UTF-8"?> -<?import javafx.scene.Scene?> +<!-- TODO: set a more appropriate initial size --> <?import javafx.scene.image.Image?> +<?import javafx.scene.Scene?> <?import javafx.scene.web.WebView?> - -<!-- TODO: set a more appropriate initial size --> -<fx:root type="javafx.stage.Stage" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" +<?import javafx.stage.Stage?> +<fx:root xmlns:fx="http://javafx.com/fxml/1" type="javafx.stage.Stage" xmlns="http://javafx.com/javafx/8" title="Help" maximized="true"> - <icons> - <Image url="@/images/help_icon.png" /> - </icons> - <scene> - <Scene> - <WebView fx:id="browser" /> - </Scene> - </scene> + <icons> + <Image url="@/images/help_icon.png"/> + </icons> + <scene> + <Scene> + <WebView fx:id="browser"/> + </Scene> + </scene> </fx:root> diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 1dadb95b6ffe..9e48420471f6 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -2,7 +2,6 @@ <?import java.net.URL?> <?import javafx.geometry.Insets?> -<?import javafx.scene.Scene?> <?import javafx.scene.control.Menu?> <?import javafx.scene.control.MenuBar?> <?import javafx.scene.control.MenuItem?> @@ -10,59 +9,60 @@ <?import javafx.scene.image.Image?> <?import javafx.scene.layout.StackPane?> <?import javafx.scene.layout.VBox?> - -<fx:root type="javafx.stage.Stage" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" +<?import javafx.scene.Scene?> +<?import javafx.stage.Stage?> +<fx:root xmlns:fx="http://javafx.com/fxml/1" type="javafx.stage.Stage" xmlns="http://javafx.com/javafx/8" minWidth="450" minHeight="600"> - <icons> - <Image url="@/images/address_book_32.png" /> - </icons> - <scene> - <Scene> - <stylesheets> - <URL value="@DarkTheme.css" /> - <URL value="@Extensions.css" /> - </stylesheets> + <icons> + <Image url="@/images/address_book_32.png"/> + </icons> + <scene> + <Scene> + <stylesheets> + <URL value="@DarkTheme.css"/> + <URL value="@Extensions.css"/> + </stylesheets> - <VBox> - <MenuBar fx:id="menuBar" VBox.vgrow="NEVER"> - <Menu mnemonicParsing="false" text="File"> - <MenuItem mnemonicParsing="false" onAction="#handleExit" text="Exit" /> - </Menu> - <Menu mnemonicParsing="false" text="Help"> - <MenuItem fx:id="helpMenuItem" mnemonicParsing="false" onAction="#handleHelp" text="Help" /> - </Menu> - </MenuBar> + <VBox> + <MenuBar fx:id="menuBar" VBox.vgrow="NEVER"> + <Menu mnemonicParsing="false" text="File"> + <MenuItem mnemonicParsing="false" onAction="#handleExit" text="Exit"/> + </Menu> + <Menu mnemonicParsing="false" text="Help"> + <MenuItem fx:id="helpMenuItem" mnemonicParsing="false" onAction="#handleHelp" text="Help"/> + </Menu> + </MenuBar> - <StackPane VBox.vgrow="NEVER" fx:id="commandBoxPlaceholder" styleClass="pane-with-border"> - <padding> - <Insets top="5" right="10" bottom="5" left="10" /> - </padding> - </StackPane> + <StackPane VBox.vgrow="NEVER" fx:id="commandBoxPlaceholder" styleClass="pane-with-border"> + <padding> + <Insets top="5" right="10" bottom="5" left="10"/> + </padding> + </StackPane> - <StackPane VBox.vgrow="NEVER" fx:id="resultDisplayPlaceholder" styleClass="pane-with-border" - minHeight="100" prefHeight="100" maxHeight="100"> - <padding> - <Insets top="5" right="10" bottom="5" left="10" /> - </padding> - </StackPane> + <StackPane VBox.vgrow="NEVER" fx:id="resultDisplayPlaceholder" styleClass="pane-with-border" + minHeight="100" prefHeight="100" maxHeight="100"> + <padding> + <Insets top="5" right="10" bottom="5" left="10"/> + </padding> + </StackPane> - <SplitPane id="splitPane" fx:id="splitPane" dividerPositions="0.4" VBox.vgrow="ALWAYS"> - <VBox fx:id="personList" minWidth="340" prefWidth="340" SplitPane.resizableWithParent="false"> - <padding> - <Insets top="10" right="10" bottom="10" left="10" /> - </padding> - <StackPane fx:id="personListPanelPlaceholder" VBox.vgrow="ALWAYS"/> - </VBox> + <SplitPane id="splitPane" fx:id="splitPane" dividerPositions="0.4" VBox.vgrow="ALWAYS"> + <VBox fx:id="bookList" minWidth="340" prefWidth="340" SplitPane.resizableWithParent="false"> + <padding> + <Insets top="10" right="10" bottom="10" left="10"/> + </padding> + <StackPane fx:id="bookListPanelPlaceholder" VBox.vgrow="ALWAYS"/> + </VBox> - <StackPane fx:id="browserPlaceholder" prefWidth="340" > - <padding> - <Insets top="10" right="10" bottom="10" left="10" /> - </padding> - </StackPane> - </SplitPane> + <StackPane fx:id="browserPlaceholder" prefWidth="340"> + <padding> + <Insets top="10" right="10" bottom="10" left="10"/> + </padding> + </StackPane> + </SplitPane> - <StackPane fx:id="statusbarPlaceholder" VBox.vgrow="NEVER" /> - </VBox> - </Scene> - </scene> + <StackPane fx:id="statusbarPlaceholder" VBox.vgrow="NEVER"/> + </VBox> + </Scene> + </scene> </fx:root> diff --git a/src/main/resources/view/NoInternet.html b/src/main/resources/view/NoInternet.html new file mode 100644 index 000000000000..fd25aa4f95fe --- /dev/null +++ b/src/main/resources/view/NoInternet.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> + <link rel="stylesheet" href="DarkTheme.css"> +</head> + +<body class="background"> + <font size="3" color="white">No Internet connection, please try again later!</font> +</body> +</html> diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml deleted file mode 100644 index f08ea32ad558..000000000000 --- a/src/main/resources/view/PersonListCard.fxml +++ /dev/null @@ -1,36 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<?import javafx.geometry.Insets?> -<?import javafx.scene.control.Label?> -<?import javafx.scene.layout.ColumnConstraints?> -<?import javafx.scene.layout.FlowPane?> -<?import javafx.scene.layout.GridPane?> -<?import javafx.scene.layout.HBox?> -<?import javafx.scene.layout.Region?> -<?import javafx.scene.layout.VBox?> - -<HBox id="cardPane" fx:id="cardPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> - <GridPane HBox.hgrow="ALWAYS"> - <columnConstraints> - <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="150" /> - </columnConstraints> - <VBox alignment="CENTER_LEFT" minHeight="105" GridPane.columnIndex="0"> - <padding> - <Insets top="5" right="5" bottom="5" left="15" /> - </padding> - <HBox spacing="5" alignment="CENTER_LEFT"> - <Label fx:id="id" styleClass="cell_big_label"> - <minWidth> - <!-- Ensures that the label text is never truncated --> - <Region fx:constant="USE_PREF_SIZE" /> - </minWidth> - </Label> - <Label fx:id="name" text="\$first" styleClass="cell_big_label" /> - </HBox> - <FlowPane fx:id="tags" /> - <Label fx:id="phone" styleClass="cell_small_label" text="\$phone" /> - <Label fx:id="address" styleClass="cell_small_label" text="\$address" /> - <Label fx:id="email" styleClass="cell_small_label" text="\$email" /> - </VBox> - </GridPane> -</HBox> diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml deleted file mode 100644 index 8836d323cc5d..000000000000 --- a/src/main/resources/view/PersonListPanel.fxml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<?import javafx.scene.control.ListView?> -<?import javafx.scene.layout.VBox?> - -<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> - <ListView fx:id="personListView" VBox.vgrow="ALWAYS" /> -</VBox> diff --git a/src/main/resources/view/ResultDisplay.fxml b/src/main/resources/view/ResultDisplay.fxml index 58d5ad3dc56c..ec006c4c173f 100644 --- a/src/main/resources/view/ResultDisplay.fxml +++ b/src/main/resources/view/ResultDisplay.fxml @@ -2,8 +2,7 @@ <?import javafx.scene.control.TextArea?> <?import javafx.scene.layout.StackPane?> - -<StackPane fx:id="placeHolder" styleClass="pane-with-border" xmlns="http://javafx.com/javafx/8" - xmlns:fx="http://javafx.com/fxml/1"> - <TextArea fx:id="resultDisplay" editable="false" styleClass="result-display"/> +<StackPane xmlns:fx="http://javafx.com/fxml/1" fx:id="placeHolder" styleClass="pane-with-border" + xmlns="http://javafx.com/javafx/8"> + <TextArea fx:id="resultDisplay" editable="false" styleClass="result-display"/> </StackPane> diff --git a/src/main/resources/view/StatusBarFooter.fxml b/src/main/resources/view/StatusBarFooter.fxml index 5a9c5a65e43f..15cf67ef602b 100644 --- a/src/main/resources/view/StatusBarFooter.fxml +++ b/src/main/resources/view/StatusBarFooter.fxml @@ -3,12 +3,12 @@ <?import org.controlsfx.control.StatusBar?> <?import javafx.scene.layout.ColumnConstraints?> <?import javafx.scene.layout.GridPane?> - -<GridPane styleClass="grid-pane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> - <columnConstraints> - <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="100" /> - <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="100" /> - </columnConstraints> - <StatusBar styleClass="anchor-pane" fx:id="syncStatus" /> - <StatusBar styleClass="anchor-pane" fx:id="saveLocationStatus" GridPane.columnIndex="1" nodeOrientation="RIGHT_TO_LEFT" /> +<GridPane xmlns:fx="http://javafx.com/fxml/1" styleClass="grid-pane" xmlns="http://javafx.com/javafx/8"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="100"/> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="100"/> + </columnConstraints> + <StatusBar styleClass="anchor-pane" fx:id="syncStatus"/> + <StatusBar styleClass="anchor-pane" fx:id="saveLocationStatus" GridPane.columnIndex="1" + nodeOrientation="RIGHT_TO_LEFT"/> </GridPane> diff --git a/src/test/data/ConfigUtilTest/EmptyConfig.json b/src/test/data/ConfigUtilTest/EmptyConfig.json index 0db3279e44b0..2c63c0851048 100644 --- a/src/test/data/ConfigUtilTest/EmptyConfig.json +++ b/src/test/data/ConfigUtilTest/EmptyConfig.json @@ -1,3 +1,2 @@ { - } diff --git a/src/test/data/ConfigUtilTest/ExtraValuesConfig.json b/src/test/data/ConfigUtilTest/ExtraValuesConfig.json index 7e5fa599efdc..70c66171be63 100644 --- a/src/test/data/ConfigUtilTest/ExtraValuesConfig.json +++ b/src/test/data/ConfigUtilTest/ExtraValuesConfig.json @@ -1,6 +1,6 @@ { - "appTitle" : "Typical App Title", - "logLevel" : "INFO", - "userPrefsFilePath" : "C:\\preferences.json", - "extra" : "extra value" + "appTitle": "Typical App Title", + "logLevel": "INFO", + "userPrefsFilePath": "C:\\preferences.json", + "extra": "extra value" } diff --git a/src/test/data/ConfigUtilTest/TypicalConfig.json b/src/test/data/ConfigUtilTest/TypicalConfig.json index 6d035659d7cc..10efead2622e 100644 --- a/src/test/data/ConfigUtilTest/TypicalConfig.json +++ b/src/test/data/ConfigUtilTest/TypicalConfig.json @@ -1,5 +1,5 @@ { - "appTitle" : "Typical App Title", - "logLevel" : "INFO", - "userPrefsFilePath" : "C:\\preferences.json" + "appTitle": "Typical App Title", + "logLevel": "INFO", + "userPrefsFilePath": "C:\\preferences.json" } diff --git a/src/test/data/JsonUserPrefsStorageTest/EmptyUserPrefs.json b/src/test/data/JsonUserPrefsStorageTest/EmptyUserPrefs.json index 0db3279e44b0..2c63c0851048 100644 --- a/src/test/data/JsonUserPrefsStorageTest/EmptyUserPrefs.json +++ b/src/test/data/JsonUserPrefsStorageTest/EmptyUserPrefs.json @@ -1,3 +1,2 @@ { - } diff --git a/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json b/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json index 87e25c850ade..cb8710758137 100644 --- a/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json +++ b/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json @@ -1,14 +1,14 @@ { - "guiSettings" : { - "windowWidth" : 1000.0, - "windowHeight" : 500.0, - "extra" : "some value ", - "windowCoordinates" : { - "x" : 300, - "y" : 100, - "z" : 99 + "guiSettings": { + "windowWidth": 1000.0, + "windowHeight": 500.0, + "extra": "some value ", + "windowCoordinates": { + "x": 300, + "y": 100, + "z": 99 } }, - "addressBookFilePath" : "addressbook.xml", - "addressBookName" : "TypicalAddressBookName" + "catalogueFilePath": "catalogue.xml", + "catalogueName": "TypicalCatalogueName" } diff --git a/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json b/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json index ea6836466914..98e71d3f5781 100644 --- a/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json +++ b/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json @@ -1,12 +1,12 @@ { - "guiSettings" : { - "windowWidth" : 1000.0, - "windowHeight" : 500.0, - "windowCoordinates" : { - "x" : 300, - "y" : 100 + "guiSettings": { + "windowWidth": 1000.0, + "windowHeight": 500.0, + "windowCoordinates": { + "x": 300, + "y": 100 } }, - "addressBookFilePath" : "addressbook.xml", - "addressBookName" : "TypicalAddressBookName" + "catalogueFilePath": "catalogue.xml", + "catalogueName": "TypicalCatalogueName" } diff --git a/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml b/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml deleted file mode 100644 index 41e411568a5f..000000000000 --- a/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="yes"?> -<addressbook> - <!-- Valid Person --> - <persons> - <name>Hans Muster</name> - <phone isPrivate="false">9482424</phone> - <email isPrivate="false">hans@example.com</email> - <address isPrivate="false">4th street</address> - </persons> - <!-- Person with invalid phone field --> - <persons> - <name>Hans Muster</name> - <phone isPrivate="false">948asdf2424</phone> - <email isPrivate="false">hans@example.com</email> - <address isPrivate="false">4th street</address> - </persons> -</addressbook> diff --git a/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml b/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml deleted file mode 100644 index cfa128e72828..000000000000 --- a/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="yes"?> -<addressbook> - <!-- Person with invalid name field --> - <persons> - <name>Ha!ns Mu@ster</name> - <phone isPrivate="false">9482424</phone> - <email isPrivate="false">hans@example.com</email> - <address isPrivate="false">4th street</address> - </persons> -</addressbook> diff --git a/src/test/data/XmlAddressBookStorageTest/NotXmlFormatAddressBook.xml b/src/test/data/XmlCatalogueStorageTest/NotXmlFormatCatalogue.xml similarity index 100% rename from src/test/data/XmlAddressBookStorageTest/NotXmlFormatAddressBook.xml rename to src/test/data/XmlCatalogueStorageTest/NotXmlFormatCatalogue.xml diff --git a/src/test/data/XmlCatalogueStorageTest/invalidAndValidBookCatalogue.xml b/src/test/data/XmlCatalogueStorageTest/invalidAndValidBookCatalogue.xml new file mode 100644 index 000000000000..85c3fc431da0 --- /dev/null +++ b/src/test/data/XmlCatalogueStorageTest/invalidAndValidBookCatalogue.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<catalogue> + <!-- @@author khiayi --> + <!-- Valid Book --> + <books> + <title>Animal Farm + George Orwell + 9780736692427 + Available + + + + Animal Farm + George Orwell + 978073669242a + Available + + diff --git a/src/test/data/XmlCatalogueStorageTest/invalidBookCatalogue.xml b/src/test/data/XmlCatalogueStorageTest/invalidBookCatalogue.xml new file mode 100644 index 000000000000..f756c8677d09 --- /dev/null +++ b/src/test/data/XmlCatalogueStorageTest/invalidBookCatalogue.xml @@ -0,0 +1,11 @@ + + + + + + Ha!ns Mu@ster + Muster Hans + 9482424 + Available + + diff --git a/src/test/data/XmlSerializableAddressBookTest/invalidPersonAddressBook.xml b/src/test/data/XmlSerializableAddressBookTest/invalidPersonAddressBook.xml deleted file mode 100644 index 13d5b1cb1c8a..000000000000 --- a/src/test/data/XmlSerializableAddressBookTest/invalidPersonAddressBook.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Hans Muster - 9482424 - hans@exam!32ple -

4th street
- - diff --git a/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml b/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml deleted file mode 100644 index c778cccc4c89..000000000000 --- a/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - Alice Pauline - 85355255 - alice@example.com -
123, Jurong West Ave 6, #08-111
- friends -
- - Benson Meier - 98765432 - johnd@example.com -
311, Clementi Ave 2, #02-25
- owesMoney - friends -
- - Carl Kurz - 95352563 - heinz@example.com -
wall street
-
- - Daniel Meier - 87652533 - cornelia@example.com -
10th street
-
- - Elle Meyer - 9482224 - werner@example.com -
michegan ave
-
- - Fiona Kunz - 9482427 - lydia@example.com -
little tokyo
-
- - George Best - 9482442 - anna@example.com -
4th street
-
- friends - owesMoney -
diff --git a/src/test/data/XmlSerializableCatalogueTest/invalidBookCatalogue.xml b/src/test/data/XmlSerializableCatalogueTest/invalidBookCatalogue.xml new file mode 100644 index 000000000000..c953786c04e7 --- /dev/null +++ b/src/test/data/XmlSerializableCatalogueTest/invalidBookCatalogue.xml @@ -0,0 +1,11 @@ + + + + + + Animal Farm + George Orwell + 9780736692427 + not sure + + diff --git a/src/test/data/XmlSerializableAddressBookTest/invalidTagAddressBook.xml b/src/test/data/XmlSerializableCatalogueTest/invalidTagCatalogue.xml similarity index 78% rename from src/test/data/XmlSerializableAddressBookTest/invalidTagAddressBook.xml rename to src/test/data/XmlSerializableCatalogueTest/invalidTagCatalogue.xml index 5fa697c22c4c..a75e25b9c0dc 100644 --- a/src/test/data/XmlSerializableAddressBookTest/invalidTagAddressBook.xml +++ b/src/test/data/XmlSerializableCatalogueTest/invalidTagCatalogue.xml @@ -1,5 +1,5 @@ - + frie!nds - + diff --git a/src/test/data/XmlSerializableCatalogueTest/typicalBooksCatalogue.xml b/src/test/data/XmlSerializableCatalogueTest/typicalBooksCatalogue.xml new file mode 100644 index 000000000000..1a57ba23e735 --- /dev/null +++ b/src/test/data/XmlSerializableCatalogueTest/typicalBooksCatalogue.xml @@ -0,0 +1,56 @@ + + + + + + Animal Farm + George Orwell + 9780736692427 + Available + political + satire + + + Breaking Dawn + Stephenie Meyer + 9780316067928 + Available + fiction + + + California Girl + Jefferson Parker + 9780060562373 + Available + unlabelled + + + Delirium + Lauren Oliver + 9780061726835 + Available + + + Emma + Jane Austen + 9780141439587 + Available + + + Fateful + Claudia Gray + 9780062006202 + Available + + + Gone Girl + Gillian Flynn + 9780753827666 + Available + + political + satire + fiction + unlabelled + diff --git a/src/test/data/XmlUtilTest/invalidBookField.xml b/src/test/data/XmlUtilTest/invalidBookField.xml new file mode 100644 index 000000000000..7ef593b61c4a --- /dev/null +++ b/src/test/data/XmlUtilTest/invalidBookField.xml @@ -0,0 +1,10 @@ + + + + + Animal Farm + George Orwell + 9482asf424 + Borrowed + political + diff --git a/src/test/data/XmlUtilTest/invalidPersonField.xml b/src/test/data/XmlUtilTest/invalidPersonField.xml deleted file mode 100644 index ba49c971e884..000000000000 --- a/src/test/data/XmlUtilTest/invalidPersonField.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - Hans Muster - 9482asf424 - hans@example -
4th street
- friends -
diff --git a/src/test/data/XmlUtilTest/missingBookField.xml b/src/test/data/XmlUtilTest/missingBookField.xml new file mode 100644 index 000000000000..c4bbf8f8a527 --- /dev/null +++ b/src/test/data/XmlUtilTest/missingBookField.xml @@ -0,0 +1,9 @@ + + + + + 9780736692427 + George Orwell + Borrowed + political + diff --git a/src/test/data/XmlUtilTest/missingPersonField.xml b/src/test/data/XmlUtilTest/missingPersonField.xml deleted file mode 100644 index c0da5c86d080..000000000000 --- a/src/test/data/XmlUtilTest/missingPersonField.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - 9482424 - hans@example -
4th street
- friends -
diff --git a/src/test/data/XmlUtilTest/tempAddressBook.xml b/src/test/data/XmlUtilTest/tempCatalogue.xml similarity index 84% rename from src/test/data/XmlUtilTest/tempAddressBook.xml rename to src/test/data/XmlUtilTest/tempCatalogue.xml index 41eeb8eb391a..8657ddd207c3 100644 --- a/src/test/data/XmlUtilTest/tempAddressBook.xml +++ b/src/test/data/XmlUtilTest/tempCatalogue.xml @@ -1,6 +1,6 @@ - - + + 1 John Doe @@ -8,8 +8,8 @@ - + Friends - + diff --git a/src/test/data/XmlUtilTest/validAddressBook.xml b/src/test/data/XmlUtilTest/validAddressBook.xml deleted file mode 100644 index 6265778674d3..000000000000 --- a/src/test/data/XmlUtilTest/validAddressBook.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - Hans Muster - 9482424 - hans@example.com -
4th street
-
- - Ruth Mueller - 87249245 - ruth@example.com -
81th street
-
- - Heinz Kurz - 95352563 - heinz@example.com -
wall street
-
- - Cornelia Meier - 87652533 - cornelia@example.com -
10th street
-
- - Werner Meyer - 9482224 - werner@example.com -
michegan ave
-
- - Lydia Kunz - 9482427 - lydia@example.com -
little tokyo
-
- - Anna Best - 9482442 - anna@example.com -
4th street
-
- - Stefan Meier - 8482424 - stefan@example.com -
little india
-
- - Martin Mueller - 8482131 - hans@example.com -
chicago ave
-
-
diff --git a/src/test/data/XmlUtilTest/validBook.xml b/src/test/data/XmlUtilTest/validBook.xml new file mode 100644 index 000000000000..9da2493c1c0b --- /dev/null +++ b/src/test/data/XmlUtilTest/validBook.xml @@ -0,0 +1,9 @@ + + + + Animal Farm + George Orwell + 9780736692427 + Borrowed + political + diff --git a/src/test/data/XmlUtilTest/validCatalogue.xml b/src/test/data/XmlUtilTest/validCatalogue.xml new file mode 100644 index 000000000000..09fc104ec950 --- /dev/null +++ b/src/test/data/XmlUtilTest/validCatalogue.xml @@ -0,0 +1,59 @@ + + + + + Animal Farm + George Orwell + 9780736692427 + Available + + + Breaking Dawn + Stephenie Meyer + 9780316067928 + Available + + + California Girl + Jefferson Parker + 9780060562373 + Available + + + Delirium + Lauren Oliver + 9780061726835 + Available + + + Emma + Jane Austen + 9780141439587 + Available + + + Fateful + Claudia Gray + 9780062006202 + Available + + + + Gone Girl + Gillian Flynn + 9780753827666 + Available + + + Holes + Louis Sachar + 9780439244190 + Available + + + Invisible Man + Ralph Ellison + 9780140023350 + Available + + diff --git a/src/test/data/XmlUtilTest/validPerson.xml b/src/test/data/XmlUtilTest/validPerson.xml deleted file mode 100644 index c029008d54f4..000000000000 --- a/src/test/data/XmlUtilTest/validPerson.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - Hans Muster - 9482424 - hans@example -
4th street
- friends -
diff --git a/src/test/java/guitests/GuiRobot.java b/src/test/java/guitests/GuiRobot.java index 965e6ebed63c..6bff7e7e853f 100644 --- a/src/test/java/guitests/GuiRobot.java +++ b/src/test/java/guitests/GuiRobot.java @@ -15,7 +15,7 @@ public class GuiRobot extends FxRobot { private static final int PAUSE_FOR_HUMAN_DELAY_MILLISECONDS = 250; - private static final int DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MILLISECONDS = 5000; + private static final int DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MILLISECONDS = 20000; private static final String PROPERTY_TESTFX_HEADLESS = "testfx.headless"; @@ -43,7 +43,7 @@ public void pauseForHuman() { * Waits for {@code event} to be true by {@code DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MILLISECONDS} milliseconds. * * @throws EventTimeoutException if the time taken exceeds {@code DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MILLISECONDS} - * milliseconds. + * milliseconds. */ public void waitForEvent(BooleanSupplier event) { waitForEvent(event, DEFAULT_WAIT_FOR_EVENT_TIMEOUT_MILLISECONDS); @@ -76,8 +76,8 @@ public void waitForEvent(BooleanSupplier event, int timeOut) { */ public boolean isWindowShown(String stageTitle) { return listTargetWindows().stream() - .filter(window -> window instanceof Stage && ((Stage) window).getTitle().equals(stageTitle)) - .count() >= 1; + .filter(window -> window instanceof Stage && ((Stage) window).getTitle().equals(stageTitle)) + .count() >= 1; } /** @@ -89,10 +89,10 @@ public boolean isWindowShown(String stageTitle) { */ public Stage getStage(String stageTitle) { Optional targetStage = listTargetWindows().stream() - .filter(Stage.class::isInstance) // checks that the window is of type Stage - .map(Stage.class::cast) - .filter(stage -> stage.getTitle().equals(stageTitle)) - .findFirst(); + .filter(Stage.class::isInstance) // checks that the window is of type Stage + .map(Stage.class::cast) + .filter(stage -> stage.getTitle().equals(stageTitle)) + .findFirst(); return targetStage.orElseThrow(StageNotFoundException::new); } diff --git a/src/test/java/guitests/guihandles/BookCardHandle.java b/src/test/java/guitests/guihandles/BookCardHandle.java new file mode 100644 index 000000000000..103bba8058c1 --- /dev/null +++ b/src/test/java/guitests/guihandles/BookCardHandle.java @@ -0,0 +1,71 @@ +package guitests.guihandles; + +import java.util.List; +import java.util.stream.Collectors; + +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; + +/** + * Provides a handle to a book card in the book list panel. + */ +public class BookCardHandle extends NodeHandle { + private static final String ID_FIELD_ID = "#id"; + private static final String TITLE_FIELD_ID = "#title"; + private static final String AUTHOR_FIELD_ID = "#author"; + private static final String ISBN_FIELD_ID = "#isbn"; + private static final String AVAIL_FIELD_ID = "#avail"; + private static final String TAGS_FIELD_ID = "#tags"; + + private final Label idLabel; + private final Label titleLabel; + private final Label authorLabel; + private final Label isbnLabel; + private final Label availLabel; + private final List