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..03aedd2db9e3 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,9 @@ -= 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"] @@ -15,24 +13,18 @@ ifndef::env-github[] image::images/Ui.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/QiuHaohao.md b/collated/functional/QiuHaohao.md new file mode 100644 index 000000000000..f35bb9857120 --- /dev/null +++ b/collated/functional/QiuHaohao.md @@ -0,0 +1,1978 @@ +# QiuHaohao +###### /src/test/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); + } +} +``` +###### /src/test/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); + } +} +``` +###### /src/test/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)); + } +} +``` +###### /src/test/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)); + } +} +``` +###### /src/test/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)); + } +} +``` +###### /src/test/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)); + } +} +``` +###### /src/test/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)); + } +} +``` +###### /src/test/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); + } +} +``` +###### /src/test/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); + } +} +``` +###### /src/test/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)); + } +} +``` +###### /src/main/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(); + } +} +``` +###### /src/main/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); + } +} +``` +###### /src/main/java/seedu/address/logic/commands/DeleteCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /src/main/java/seedu/address/logic/commands/ListCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /src/main/java/seedu/address/logic/commands/RedoCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /src/main/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; + } +} +``` +###### /src/main/java/seedu/address/logic/commands/ClearCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /src/main/java/seedu/address/logic/commands/Command.java +``` java + public PrivilegeLevel getPrivilegeLevel() { + return Model.PRIVILEGE_LEVEL_GUEST; + } +} +``` +###### /src/main/java/seedu/address/logic/commands/AddCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /src/main/java/seedu/address/logic/commands/HelpCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /src/main/java/seedu/address/logic/commands/HistoryCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /src/main/java/seedu/address/logic/commands/EditCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +``` +###### /src/main/java/seedu/address/logic/commands/SelectCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /src/main/java/seedu/address/logic/commands/UndoCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /src/main/java/seedu/address/logic/commands/FindCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /src/main/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); + } +} +``` +###### /src/main/java/seedu/address/logic/commands/ExitCommand.java +``` java + @Override + public PrivilegeLevel getPrivilegeLevel() { + return PRIVILEGE_LEVEL; + } +} +``` +###### /src/main/java/seedu/address/logic/LogicManager.java +``` java + protected boolean isPrivileged(Command command) { + return command.getPrivilegeLevel().compareTo(model.getPrivilegeLevel()) <= 0; + } +``` +###### /src/main/java/seedu/address/storage/Storage.java +``` java + String getAccountListFilePath(); + + Optional readAccountList() throws DataConversionException, IOException; + + void saveAccountList(UniqueAccountList accountList) throws IOException; + + void handleAccountListChangedEvent(AccountListChangedEvent event); +} +``` +###### /src/main/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(); + } +} +``` +###### /src/main/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); + } + +} +``` +###### /src/main/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; + +} +``` +###### /src/main/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); + } + } +} +``` +###### /src/main/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); +``` +###### /src/main/java/seedu/address/model/UserPrefs.java +``` java + public String getAccountListFilePath() { + return accountListFilePath; + } + + public void setAccountListFilePath(String accountListFilePath) { + this.accountListFilePath = accountListFilePath; + } +``` +###### /src/main/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(); + } +``` +###### /src/main/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 { + 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(); + } + + /** + * 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(); + } + } + } +``` +###### /src/main/java/seedu/address/model/ModelManager.java +``` java + /** + * Raises an event to indicate the model has changed + */ + private void indicateAccountListChanged() { + raise(new AccountListChangedEvent(accountList)); + } +``` +###### /src/main/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(); + } +``` +###### /src/main/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 { + 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; + } + + + 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(); + } + + +} +``` +###### /src/main/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; + } +} +``` +###### /src/main/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(); + } +} +``` +###### /src/main/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{" + + "username='" + username + '\'' + + '}'; + } +} +``` +###### /src/main/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 { +} +``` +###### /src/main/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"); + } +} +``` +###### /src/main/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); + } +} +``` +###### /src/main/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 + + '}'; + } +} + +``` +###### /src/main/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(); + } +} +``` +###### /src/main/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/test/QiuHaohao.md b/collated/test/QiuHaohao.md new file mode 100644 index 000000000000..5b6bbadc71ac --- /dev/null +++ b/collated/test/QiuHaohao.md @@ -0,0 +1,619 @@ +# QiuHaohao +###### /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/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/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); + } +} +``` +###### /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)); + } +} +``` diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index 0f0a8e7ab51e..02fff88da07b 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -3,51 +3,51 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + +Catalogue was developed by the https://CS2103JAN2018-T16-B1.github.io/docs/Team.html[CS2103JAN2018-T16-B1] team. + _{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + {empty} + 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]] [<>] +=== Chan Tiong Ley +image::chantiongley.jpg[width="150", align="left"] +{empty} [https://github.com/chantiongley[github]] [<>] Role: Project Advisor ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<>] +=== Le Khang Tai +image::LeKhangTai.jpg[width="150", align="left"] +{empty}[https://github.com/LeKhangTai[github]] [<>] Role: Team Lead + Responsibilities: UI ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<>] +=== Qiu Hao Ze +image::QiuHaohao.jpg[width="150", align="left"] +{empty}[https://github.com/QiuHaohao[github]] [<>] Role: Developer + Responsibilities: Data ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<>] +=== Victor Tardieu +image::victortardieu.jpg[width="150", align="left"] +{empty}[https://github.com/victortardieu[github]] [<>] Role: Developer + Responsibilities: Dev Ops + Threading ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<>] +=== Wong Khia Yi +image::khiayi.png[width="150", align="left"] +{empty}[https://github.com/khiayi[github]] [<>] Role: Developer + Responsibilities: UI 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..a2d30f879c0d 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,9 +10,9 @@ 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` == Setting up @@ -77,7 +77,7 @@ Set up Travis to perform Continuous Integration (CI) for your fork. See <>). [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 <>). @@ -137,15 +137,15 @@ 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. @@ -160,7 +160,7 @@ image::UiClassDiagram.png[width="800"] *API* : link:{repoURL}/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`] @@ -183,15 +183,15 @@ image::LogicCommandClassDiagram.png[width="800"] *API* : link:{repoURL}/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"] [[Design-Model]] === Model component @@ -204,8 +204,11 @@ image::ModelClassDiagram.png[width="800"] The `Model`, * stores a `UserPref` object that represents the user's preferences. -* stores the Address Book data. -* exposes an unmodifiable `ObservableList` 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` 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. [[Design-Storage]] @@ -219,12 +222,13 @@ image::StorageClassDiagram.png[width="800"] 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 +237,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 +280,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 +304,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 +321,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,17 +337,17 @@ 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. @@ -493,10 +501,43 @@ Here are the steps to create a new release. === 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) +=== Features Proposed + +Unallocated: +. Renew Command +.. Allows students to renew borrowed books. + +Chan Tiong Ley: +. bookLogs command +.. Shows the past history of the book. +. userLogs command +.. Shows the past activities of the user. + +Lucas Le: +. Auto-complete UI +.. Helps users with completing command lines inputs. + +Qiu Hao Ze: +. Login Command +.. Create levels of privileges for different target groups. i.e. Guests, Students, Librarians + +Victor Tardieu: +. Book Class with bookList +.. Creates book objects. +. List Command +.. List the books in the catalogue in order. + +Wong Khia Yi: +. Borrow Command +.. Allows students to borrow books. +. Reserve Command +.. Allows students to reserve books. + + [[GetStartedProgramming]] [appendix] == Suggested Programming Tasks to Get Started @@ -520,14 +561,14 @@ Each individual exercise in this section is component-based (i.e. you would not [TIP] Do take a look at <> before attempting to modify the `Logic` component. -. 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. +. 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 books 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. +** link:{repoURL}/src/main/java/seedu/address/logic/parser/CatalogueParser.java[`CatalogueParser`] 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. +** Modify the switch statement in link:{repoURL}/src/main/java/seedu/address/logic/parser/CatalogueParser.java[`CatalogueParser#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. @@ -536,36 +577,36 @@ Do take a look at <> before attempting to modify the `Logic` compo [discrete] ==== `Model` component -*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. +*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 catalogue, 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. [TIP] Do take a look at <> before attempting to modify the `Model` component. -. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the address book. +. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the catalogue. + **** * 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. +** The link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/address/model/Catalogue.java[`Catalogue`] 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. +** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/address/model/Catalogue.java[`Catalogue`] and link:{repoURL}/src/main/java/seedu/address/model/book/Book.java[`Book`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/address/model/Catalogue.java[`Catalogue`] allows you to update a book, and link:{repoURL}/src/main/java/seedu/address/model/book/Book.java[`Book`] 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)`. +** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/address/model/Catalogue.java[`Catalogue`]. Loop through each book, and remove the `tag` from each book. +** 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 `Catalogue#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 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/Catalogue.java[`Catalogue`]. 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. **** [discrete] ==== `Ui` component -*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. +*Scenario:* You are in charge of `ui`. During a beta testing session, your team is observing how the users use your catalogue 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 book in the list. Your job is to implement improvements to the UI to solve all these problems. [TIP] Do take a look at <> 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. +. Use different colors for different tags inside book cards. For example, `friends` tags can be all in brown, and `colleagues` tags can be all in yellow. + **Before** + @@ -577,11 +618,11 @@ image::getting-started-ui-tag-after.png[width="300"] + **** * 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. +** The tag labels are created inside link:{repoURL}/src/main/java/seedu/address/ui/BookCard.java[the `BookCard` 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. +** You can modify the existing test methods for `BookCard` '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. **** @@ -605,12 +646,12 @@ image::getting-started-ui-result-after.png[width="200"] ** 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. +*** The system tests link:{repoURL}/src/test/java/systemtests/CatalogueSystemTest.java[`CatalogueSystemTest#assertCommandBoxShowsDefaultStyle() and CatalogueSystemTest#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. **** -. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the address book. +. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the catalogue. + **Before** + @@ -623,29 +664,29 @@ image::getting-started-ui-status-after.png[width="500"] **** * 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. +** 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 catalogue 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. +** Modify the constructor of link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to take in the number of books when the application just started. +** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handlCatalogueChangedEvent(CatalogueChangedEvent)`] to update the number of books whenever there are new changes to the catalogue. ** 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. +** For system tests, modify link:{repoURL}/src/test/java/systemtests/CatalogueSystemTest.java[`CatalogueSystemTest`] to also verify the new total number of books status bar. ** See this https://github.com/se-edu/addressbook-level4/pull/803[PR] for the full solution. **** [discrete] ==== `Storage` component -*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. +*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the catalogue to the cloud. However, the current implementation of the application constantly saves the catalogue 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 catalogue storage. [TIP] Do take a look at <> before attempting to modify the `Storage` component. -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. +. Add a new method `backupCatalogue(ReadOnlyCatalogue)`, so that the catalogue can be saved in a fixed temporary location. + **** * 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. +** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/CatalogueStorage.java[`CatalogueStorage`] 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/XmlCatalogueStorage.java[`XmlCatalogueStorage`] class. * Solution ** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. **** @@ -658,15 +699,15 @@ By creating this command, you will get a chance to learn how to implement a feat *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. ==== Description -Edits the remark for a person specified in the `INDEX`. + +Edits the remark for a book specified in the `INDEX`. + Format: `remark INDEX r/[REMARK]` Examples: * `remark 1 r/Likes to drink coffee.` + -Edits the remark for the first person to `Likes to drink coffee.` +Edits the remark for the first book to `Likes to drink coffee.` * `remark 1 r/` + -Removes the remark for the first person. +Removes the remark for the first book. ==== Step-by-step Instructions @@ -676,12 +717,12 @@ Let's start by teaching the application how to parse a `remark` command. We will **Main:** . 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`. +. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/CatalogueParser.java[`CatalogueParser`] to accept a `RemarkCommand`. **Tests:** . 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`. +. Add new test method to link:{repoURL}/src/test/java/seedu/address/logic/parser/CatalogueParserTest.java[`CatalogueParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. ===== [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.` @@ -690,50 +731,50 @@ Let's teach the application to parse arguments that our `remark` command will ac . 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`. +. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/CatalogueParser.java[`CatalogueParser`] to use the newly implemented `RemarkCommandParser`. **Tests:** . 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. +. Modify link:{repoURL}/src/test/java/seedu/address/logic/parser/CatalogueParserTest.java[`CatalogueParserTest`] to test that the correct command is generated according to the user input. -===== [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. +===== [Step 3] Ui: Add a placeholder for remark in `BookCard` +Let's add a placeholder on all our link:{repoURL}/src/main/java/seedu/address/ui/BookCard.java[`BookCard`] s to display a remark for each book later. **Main:** -. 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. +. Add a `Label` with any random text inside link:{repoURL}/src/main/resources/view/BookListCard.fxml[`BookListCard.fxml`]. +. Add FXML annotation in link:{repoURL}/src/main/java/seedu/address/ui/BookCard.java[`BookCard`] to tie the variable to the actual label. **Tests:** -. Modify link:{repoURL}/src/test/java/guitests/guihandles/PersonCardHandle.java[`PersonCardHandle`] so that future tests can read the contents of the remark label. +. Modify link:{repoURL}/src/test/java/guitests/guihandles/BookCardHandle.java[`BookCardHandle`] so that future tests can read the contents of the remark label. ===== [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. +We have to properly encapsulate the remark in our link:{repoURL}/src/main/java/seedu/address/model/book/Book.java[`Book`] class. Instead of just using a `String`, let's follow the conventional class structure that the codebase already uses by adding a `Remark` class. **Main:** -. 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). +. Add `Remark` to model component (you can copy from link:{repoURL}/src/main/java/seedu/address/model/book/Author.java[`Author`], remove the regex and change the names accordingly). . Modify `RemarkCommand` to now take in a `Remark` instead of a `String`. **Tests:** . Add test for `Remark`, to test the `Remark#equals()` method. -===== [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`]. +===== [Step 5] Model: Modify `Book` 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/book/Book.java[`Book`]. **Main:** -. 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.) +. Add `getRemark()` in link:{repoURL}/src/main/java/seedu/address/model/book/Book.java[`Book`]. +. 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 book 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 `catalogue.xml` so that the application will load the sample data when you launch it.) -===== [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. +===== [Step 6] Storage: Add `Remark` field to `XmlAdaptedBook` class +We now have `Remark` s for `Book` s, but they will be gone when we exit the application. Let's modify link:{repoURL}/src/main/java/seedu/address/storage/XmlAdaptedBook.java[`XmlAdaptedBook`] to include a `Remark` field so that it will be saved. **Main:** @@ -741,33 +782,33 @@ We now have `Remark` s for `Person` s, but they will be gone when we exit the ap **Tests:** -. Fix `invalidAndValidPersonAddressBook.xml`, `typicalPersonsAddressBook.xml`, `validAddressBook.xml` etc., such that the XML tests will not fail due to a missing `` element. +. Fix `invalidAndValidBookCatalogue.xml`, `typicalBooksCatalogue.xml`, `validCatalogue.xml` etc., such that the XML tests will not fail due to a missing `` element. -===== [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`]. +===== [Step 6b] Test: Add withRemark() for `BookBuilder` +Since `Book` can now have a `Remark`, we should add a helper method to link:{repoURL}/src/test/java/seedu/address/testutil/BookBuilder.java[`BookBuilder`], so that users are able to create remarks when building a link:{repoURL}/src/main/java/seedu/address/model/book/Book.java[`Book`]. **Tests:** -. 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`]. +. Add a new method `withRemark()` for link:{repoURL}/src/test/java/seedu/address/testutil/BookBuilder.java[`BookBuilder`]. This method will create a new `Remark` for the book that it is currently building. +. Try and use the method on any sample `Book` in link:{repoURL}/src/test/java/seedu/address/testutil/TypicalBooks.java[`TypicalBooks`]. -===== [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. +===== [Step 7] Ui: Connect `Remark` field to `BookCard` +Our remark label in link:{repoURL}/src/main/java/seedu/address/ui/BookCard.java[`BookCard`] is still a placeholder. Let's bring it to life by binding it with the actual `remark` field. **Main:** -. Modify link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`]'s constructor to bind the `Remark` field to the `Person` 's remark. +. Modify link:{repoURL}/src/main/java/seedu/address/ui/BookCard.java[`BookCard`]'s constructor to bind the `Remark` field to the `Book` 's remark. **Tests:** -. Modify link:{repoURL}/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java[`GuiTestAssert#assertCardDisplaysPerson(...)`] so that it will compare the now-functioning remark label. +. Modify link:{repoURL}/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java[`GuiTestAssert#assertCardDisplaysBook(...)`] so that it will compare the now-functioning remark label. ===== [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. **Main:** -. Replace the logic in `RemarkCommand#execute()` (that currently just throws an `Exception`), with the actual logic to modify the remarks of a person. +. Replace the logic in `RemarkCommand#execute()` (that currently just throws an `Exception`), with the actual logic to modify the remarks of a book. **Tests:** @@ -792,23 +833,40 @@ See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step- [appendix] == User Stories +As a librarian, I can add new book titles into the database +As a librarian, I can delete book titles in the database +As a librarian, I want confirmation in notification before deleting a book +As a librarian, I can find a book title using the database +As a librarian, I can view the history of borrowed books from each borrower +As a student, I can find a book title in the database by it's title +As a student, I can find a book in the database by it's tag +As a student, I can check for the availability of the book +As a student, I can loan an available book +As a student, I can renew existing loaned book +As a student, I can reserve a book that is on on loan by someone else and not available in the library currently +As a student, I will receive notification when my current book loan duration is coming to an end +As a guest, I can find a book title in the database +As a guest, I can check for the availability of the book Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` [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 - -|`* * *` |user |add a new person | - -|`* * *` |user |delete a person |remove entries that I no longer need - -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list - -|`* *` |user |hide <> by default |minimize chance of someone else seeing them by accident - -|`*` |user with many persons in the address book |sort persons by name |locate a person easily +|`* * *` |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 confirmation in notification before deleting a book |So that I will not accidentally delete the wrong book title +|`* * *` |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 view the history of borrowed books from each borrower |So that I can keep track of books availability +|`* * *` |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 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 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 renew existing loaned book |So that I have more time to finish the book +|`* *` |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 receive notification when my current book is due soon |So that I will not miss the due date +|`* * *` |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 |======================================================================= _{More to be added}_ @@ -816,17 +874,17 @@ _{More to be added}_ [appendix] == Use Cases -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +(For all use cases below, the *System* is the `Catalogue` and the *Actor* is the `user`, unless specified otherwise) [discrete] -=== Use case: Delete person +=== Use case: Delete book *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. 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 + Use case ends. @@ -840,7 +898,7 @@ Use case ends. * 3a. The given index is invalid. + [none] -** 3a1. AddressBook shows an error message. +** 3a1. Catalogue shows an error message. + Use case resumes at step 2. @@ -850,7 +908,7 @@ _{More to be added}_ == Non Functional Requirements . Should work on any <> 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}_ @@ -905,15 +963,15 @@ These instructions only provide a starting point for testers to work on; testers _{ more test cases ... }_ -=== Deleting a person +=== Deleting a book -. Deleting a person while all persons are listed +. Deleting a book while all books are listed -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +.. 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. .. 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. 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 <> 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 <>. +Note <>. *Resources* @@ -72,14 +72,14 @@ Note <>. +Note <>. *Resources* @@ -115,7 +115,7 @@ Note <>. (https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]]) +Note <>. (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..e9568cb91b68 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,25 @@ 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` + +== Default Account +``` +Username: admin +Password: admin +``` +You can login the default account with this command: 'login admin admin' == 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 <> to get started. Enjoy! +Catalogue is for *NUS students* who prefer to use a desktop app for finding, borrowing and returning 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 <> to get started. Enjoy! == Quick Start @@ -27,8 +39,8 @@ 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"] @@ -37,9 +49,9 @@ image::Ui.png[width="790"] 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`**`n/Animal Farm a/George Orwell p/9780736692427 e/Borrowed and Reserved ` : 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 <> for details of each command. @@ -50,81 +62,100 @@ 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 n/TITLE`, `TITLE` is a parameter which can be used as `add n/Animal Farm`. +* Items in square brackets are optional e.g `n/TITLE [t/TAG]` can be used as `n/Animal Farm t/satire` or as `n/Animal Farm`. +* 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/satire`, `t/satire t/political` etc. +* Parameters can be in any order e.g. if the command specifies `n/TITLE p/ISBN`, `p/ISBN n/TITLE` is also acceptable. ==== +=== Login as Student/Librarian : `Login` + +Format: `login [username] [password]` + +Privilege Level: 0(Guest) + +[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' + === Viewing help : `help` -Format: `help` +Format: `help` + +Privilege Level: 0(Guest) + +=== 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]...` +Adds a book to the catalogue + +Format: `add n/TITLE a/AUTHOR p/ISBN e/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) 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 n/Animal Farm a/George Orwell p/9780736692427 e/Borrowed and Reserved` +* `add n/Breaking Dawn t/fiction e/Borrowed and Reserved a/Stephenie Meyer p/9780316067928 t/young adults` + +=== Listing all books : `list` + -=== Listing all persons : `list` +Shows a list of all books in the catalogue. + +Format: `list` + +Privilege Level: 0(Guest) -Shows a list of all persons in the address book. + -Format: `list` -=== Editing a person : `edit` +=== Editing a book : `edit` -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +Edits an existing book in the catalogue. + +Format: `edit INDEX [n/TITLE] [a/AUTHOR] [p/ISBN] [e/AVAIL] [t/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. * 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 p/9780736692426 e/Borrowed and Reserved` + +Edits the isbn number and availability author of the 1st book to be `9780736692426` and `Available` respectively. +* `edit 2 n/Breaking Dawn t/` + +Edits the title of the 2nd book to be `Breaking Dawn` and clears all existing tags. -=== 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` + +=== 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 +164,19 @@ 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. -=== Selecting a person : `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 Google search page 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, ...` **** @@ -153,15 +185,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 +204,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 +232,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 +253,58 @@ 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 books from the catalogue. + +Format: `clear` + +Privilege Level: 2(Librarian) -Clears all entries from the address book. + -Format: `clear` +=== Borrow a book: `borrow`[TO BE IMPLEMENTED] + +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. + +=== Clearing all accounts : `cleara`[TO BE IMPLEMENTED] + +Clears all accounts from AccountList. + +Format: `clear` + +Privilege Level: 2(Librarian) + +=== Return a book : `return`[TO BE IMPLEMENTED] + +Return a specified book to the catalogue + +Format: `return INDEX` + +Privilege Level: 2(Librarian) + +**** +* 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, ... +**** === 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,19 +316,19 @@ _{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. == 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` +* *Add* `add n/TITLE a/AUTHOR p/ISBN e/AVAIL [t/TAG]...` + +e.g. `add n/Animal Farm a/George Orwell p/9780736692427 e/Borrowed and Reserved t/political t/satire` * *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` +* *Edit* : `edit INDEX [n/TITLE] [a/AUTHOR] [p/ISBN] [e/AVAIL] [t/TAG]...` + +e.g. `edit 2 n/Animal Farm e/Available` * *Find* : `find KEYWORD [MORE_KEYWORDS]` + -e.g. `find James Jake` +e.g. `find Animal Dawn` * *List* : `list` * *Help* : `help` * *Select* : `select INDEX` + @@ -262,3 +336,7 @@ e.g.`select 2` * *History* : `history` * *Undo* : `undo` * *Redo* : `redo` +* *Borrow* : `borrow INDEX` + +e.g. `borrow 1` +* *Return* : `return INDEX` + +e.g. `delete 2` 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/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/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/Tiong_photo.jpg b/docs/images/Tiong_photo.jpg new file mode 100644 index 000000000000..37e7b19b6400 Binary files /dev/null and b/docs/images/Tiong_photo.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..775129e9f871 --- /dev/null +++ b/docs/team/chantiongley.adoc @@ -0,0 +1,66 @@ += 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. + +== Summary of contributions + +* *Major enhancement*: +** What it does: +** Justification: +** Highlights: +** Credits: + +* *Minor enhancement*: + +* *Code contributed*: + +* *Other contributions*: + +** Project management: + +** Enhancements to existing features: + +** Documentation: + +** Community: + +** Tools: + +_{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=undoredo] + +include::../UserGuide.adoc[tag=dataencryption] + +== 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=undoredo] + +include::../DeveloperGuide.adoc[tag=dataencryption] + + +== PROJECT: PowerPointLabs + +--- + +_{Optionally, you may include other projects in your portfolio.}_ 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..ec192bb61ed3 --- /dev/null +++ b/docs/team/lekhangtai.adoc @@ -0,0 +1,66 @@ += 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*: +** What it does: +** Justification: +** Highlights: +** Credits: + +* *Minor enhancement*: + +* *Code contributed*: + +* *Other contributions*: + +** Project management: + +** Enhancements to existing features: + +** Documentation: + +** Community: + +** Tools: + +_{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=undoredo] + +include::../UserGuide.adoc[tag=dataencryption] + +== 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=undoredo] + +include::../DeveloperGuide.adoc[tag=dataencryption] + + +== PROJECT: PowerPointLabs + +--- + +_{Optionally, you may include other projects in your portfolio.}_ diff --git a/docs/team/qiuhaoze.adoc b/docs/team/qiuhaoze.adoc new file mode 100644 index 000000000000..41e38f9bf5d6 --- /dev/null +++ b/docs/team/qiuhaoze.adoc @@ -0,0 +1,66 @@ += Qiu Hao Ze - 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*: +** What it does: +** Justification: +** Highlights: +** Credits: + +* *Minor enhancement*: + +* *Code contributed*: + +* *Other contributions*: + +** Project management: + +** Enhancements to existing features: + +** Documentation: + +** Community: + +** Tools: + +_{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=undoredo] + +include::../UserGuide.adoc[tag=dataencryption] + +== 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=undoredo] + +include::../DeveloperGuide.adoc[tag=dataencryption] + + +== PROJECT: PowerPointLabs + +--- + +_{Optionally, you may include other projects in your portfolio.}_ diff --git a/docs/team/victortardieu.adoc b/docs/team/victortardieu.adoc new file mode 100644 index 000000000000..a38d92d2928b --- /dev/null +++ b/docs/team/victortardieu.adoc @@ -0,0 +1,66 @@ += 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*: +** What it does: +** Justification: +** Highlights: +** Credits: + +* *Minor enhancement*: + +* *Code contributed*: + +* *Other contributions*: + +** Project management: + +** Enhancements to existing features: + +** Documentation: + +** Community: + +** Tools: + +_{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=undoredo] + +include::../UserGuide.adoc[tag=dataencryption] + +== 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=undoredo] + +include::../DeveloperGuide.adoc[tag=dataencryption] + + +== 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..127b929dce13 --- /dev/null +++ b/docs/team/wongkhiayi.adoc @@ -0,0 +1,66 @@ += 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. + +== Summary of contributions + +* *Major enhancement*: +** What it does: +** Justification: +** Highlights: +** Credits: + +* *Minor enhancement*: + +* *Code contributed*: + +* *Other contributions*: + +** Project management: + +** Enhancements to existing features: + +** Documentation: + +** Community: + +** Tools: + +_{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=undoredo] + +include::../UserGuide.adoc[tag=dataencryption] + +== 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=undoredo] + +include::../DeveloperGuide.adoc[tag=dataencryption] + + +== PROJECT: PowerPointLabs + +--- + +_{Optionally, you may include other projects in your portfolio.}_ diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/address/MainApp.java index fa0800d55cb9..71bb471385f0 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/address/MainApp.java @@ -20,21 +20,27 @@ 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. */ @@ -51,23 +57,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 +92,58 @@ private String getApplicationParameter(String parameterName) { } /** - * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}.
- * 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}.
+ * 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 addressBookOptional; - ReadOnlyAddressBook initialData; + Optional catalogueOptional; + Optional accountListOptional; + ReadOnlyCatalogue initialData; + UniqueAccountList initlaAccountList; + //@@author QiuHaohao + 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 { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + 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(); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + 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 file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + 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(); } - return new ModelManager(initialData, userPrefs); + 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 +173,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 +201,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 +224,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 +246,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 - * + *

* 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..389b6bf1f7ef 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/address/commons/core/Config.java @@ -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..c177897a7511 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/address/commons/core/GuiSettings.java @@ -52,9 +52,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
* 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.
+ * 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.
*/ 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. - * + *

* {@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. - * + *

* 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 void serializeObjectToJsonFile(File jsonFile, T objectToSerialize) throws IOException { FileUtil.writeToFile(jsonFile, toJsonString(objectToSerialize)); } static T deserializeObjectFromJsonFile(File jsonFile, Class 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 Optional readJsonFile( - String filePath, Class classOfObjectToDeserialize) throws DataConversionException { + String filePath, Class 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 Optional 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 void saveJsonFile(T jsonFile, String filePath) throws IOExcept /** * Converts a given string representation of a JSON data to instance of a class + * * @param 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 fromJsonString(String json, Class 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 The generic type to create an instance of + * @param The generic type to create an instance of * @return JSON data representation of the given class instance, in string */ public static 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 *

* 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. - *
examples:

+     * Ignores case, but a full word match is required.
+     * 
examples:
      *       containsWordIgnoreCase("ABc def", "abc") == true
      *       containsWordIgnoreCase("ABc def", "DEF") == true
      *       containsWordIgnoreCase("ABc def", "AB") == false //not a full word match
      *       
+ * * @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}
* 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 getDataFromFile(File file, Class 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 getFilteredPersonList(); + /** + * Returns an unmodifiable view of the filtered list of books + */ + ObservableList 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..95a074646cfe 100644 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ b/src/main/java/seedu/address/logic/LogicManager.java @@ -8,26 +8,29 @@ import seedu.address.logic.commands.Command; import seedu.address.logic.commands.CommandResult; 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(); } @@ -35,7 +38,10 @@ public LogicManager(Model model) { 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 +52,18 @@ public CommandResult execute(String commandText) throws CommandException, ParseE } @Override - public ObservableList getFilteredPersonList() { - return model.getFilteredPersonList(); + public ObservableList getFilteredBookList() { + return model.getFilteredBookList(); } @Override public ListElementPointer getHistorySnapshot() { return new ListElementPointer(history.getHistory()); } + + //@@author QiuHaohao + protected boolean isPrivileged(Command command) { + return command.getPrivilegeLevel().compareTo(model.getPrivilegeLevel()) <= 0; + } + //@@author } 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 undoStack; + + private Stack 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/AddCommand.java b/src/main/java/seedu/address/logic/commands/AddCommand.java index c334710c0ea3..0a0cdc3a3c57 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/address/logic/commands/AddCommand.java @@ -1,59 +1,64 @@ 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; + /** - * 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 +66,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/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..42ec209d0878 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,9 @@ 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/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 lastShownList = model.getFilteredPersonList(); + List 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..a696d7641d31 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/address/logic/commands/EditCommand.java @@ -1,12 +1,12 @@ 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 +19,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 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 lastShownList = model.getFilteredPersonList(); + List 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 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 +130,39 @@ 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 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 +170,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 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 +214,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 +230,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..7aaedbdb4498 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_LIBRARIAN; @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/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/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/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/CatalogueParser.java similarity index 90% rename from src/main/java/seedu/address/logic/parser/AddressBookParser.java rename to src/main/java/seedu/address/logic/parser/CatalogueParser.java index b7d57f5db86a..178cd1af745d 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/CatalogueParser.java @@ -16,6 +16,8 @@ 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.SelectCommand; import seedu.address.logic.commands.UndoCommand; @@ -24,7 +26,7 @@ /** * Parses user input. */ -public class AddressBookParser { +public class CatalogueParser { /** * Used for initial separation of command word and args. @@ -84,6 +86,12 @@ 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(); + 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..48ed16df0687 100644 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ b/src/main/java/seedu/address/logic/parser/CliSyntax.java @@ -6,10 +6,10 @@ public class CliSyntax { /* Prefix definitions */ - 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_TITLE = new Prefix("n/"); + public static final Prefix PREFIX_AUTHOR = new Prefix("a/"); + public static final Prefix PREFIX_ISBN = new Prefix("p/"); + public static final Prefix PREFIX_AVAIL = new Prefix("e/"); public static final Prefix PREFIX_TAG = new Prefix("t/"); } 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..1e1fd88f212b 100644 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ b/src/main/java/seedu/address/logic/parser/ParserUtil.java @@ -10,10 +10,10 @@ 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.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 +33,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 { @@ -44,99 +45,99 @@ public static Index parseIndex(String oneBasedIndex) throws IllegalValueExceptio } /** - * Parses a {@code String name} into a {@code Name}. + * Parses a {@code String title} into a {@code Title}. * Leading and trailing whitespaces will be trimmed. * - * @throws IllegalValueException if the given {@code name} is invalid. + * @throws IllegalValueException if the given {@code title} is invalid. */ - public static Name parseName(String name) throws IllegalValueException { - requireNonNull(name); - String trimmedName = name.trim(); - if (!Name.isValidName(trimmedName)) { - throw new IllegalValueException(Name.MESSAGE_NAME_CONSTRAINTS); + 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 Name(trimmedName); + return new Title(trimmedTitle); } /** - * Parses a {@code Optional<String> name} into an {@code Optional<Name>} if {@code name} is present. + * 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<Name> parseName(Optional<String> name) throws IllegalValueException { - requireNonNull(name); - return name.isPresent() ? Optional.of(parseName(name.get())) : Optional.empty(); + 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 phone} into a {@code Phone}. + * Parses a {@code String isbn} into a {@code Isbn}. * Leading and trailing whitespaces will be trimmed. * - * @throws IllegalValueException if the given {@code phone} is invalid. + * @throws IllegalValueException if the given {@code isbn} 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 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 Phone(trimmedPhone); + return new Isbn(trimmedIsbn); } /** - * Parses a {@code Optional<String> phone} into an {@code Optional<Phone>} if {@code phone} is present. + * 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<Phone> parsePhone(Optional<String> phone) throws IllegalValueException { - requireNonNull(phone); - return phone.isPresent() ? Optional.of(parsePhone(phone.get())) : Optional.empty(); + 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 Address}. + * 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/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..d00961f35785 --- /dev/null +++ b/src/main/java/seedu/address/model/Catalogue.java @@ -0,0 +1,189 @@ +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); + } + + //// 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..111681b44e47 100644 --- a/src/main/java/seedu/address/model/Model.java +++ b/src/main/java/seedu/address/model/Model.java @@ -3,46 +3,83 @@ 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.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); + + /** + * Clears existing backing model and replaces with the provided new data. + */ + void resetData(ReadOnlyCatalogue newData); - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); + /** + * Returns the Catalogue + */ + ReadOnlyCatalogue getCatalogue(); - /** Deletes the given person. */ - void deletePerson(Person target) throws PersonNotFoundException; + /** + * Deletes the given book. + */ + void deleteBook(Book target) throws BookNotFoundException; - /** Adds the given person */ - void addPerson(Person person) throws DuplicatePersonException; + /** + * 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(); + /** + * Returns an unmodifiable view of the filtered book list + */ + ObservableList<Book> getFilteredBookList(); /** - * 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..f5f49ba9a3af 100644 --- a/src/main/java/seedu/address/model/ModelManager.java +++ b/src/main/java/seedu/address/model/ModelManager.java @@ -11,92 +11,208 @@ 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 UniqueAccountList accountList; + private final FilteredList<Book> filteredBooks; + 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 { + 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(); } + /** + * 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 void resetData(ReadOnlyAddressBook newData) { - addressBook.resetData(newData); - indicateAddressBookChanged(); + public void resetData(ReadOnlyCatalogue newData) { + catalogue.resetData(newData); + indicateCatalogueChanged(); } @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; + public ReadOnlyCatalogue getCatalogue() { + return catalogue; } - /** Raises an event to indicate the model has changed */ - private void indicateAddressBookChanged() { - raise(new AddressBookChangedEvent(addressBook)); + /** + * 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 synchronized void deletePerson(Person target) throws PersonNotFoundException { - addressBook.removePerson(target); - indicateAddressBookChanged(); + public synchronized void deleteBook(Book target) throws BookNotFoundException { + catalogue.removeBook(target); + indicateCatalogueChanged(); } @Override - public synchronized void addPerson(Person person) throws DuplicatePersonException { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - indicateAddressBookChanged(); + public synchronized void addBook(Book book) throws DuplicateBookException { + catalogue.addBook(book); + updateFilteredBookList(PREDICATE_SHOW_ALL_BOOKS); + indicateCatalogueChanged(); } @Override - public void updatePerson(Person target, Person editedPerson) - throws DuplicatePersonException, PersonNotFoundException { - requireAllNonNull(target, editedPerson); + public void updateBook(Book target, Book editedBook) + throws DuplicateBookException, BookNotFoundException { + requireAllNonNull(target, editedBook); - addressBook.updatePerson(target, editedPerson); - indicateAddressBookChanged(); + catalogue.updateBook(target, editedBook); + indicateCatalogueChanged(); } - //=========== Filtered Person List Accessors ============================================================= + //=========== 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 +228,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..72448b6f7ce1 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,31 @@ 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 +67,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..e3bf1fd988a0 --- /dev/null +++ b/src/main/java/seedu/address/model/account/Account.java @@ -0,0 +1,163 @@ +//@@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); + } +} 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..e47b6a30376d --- /dev/null +++ b/src/main/java/seedu/address/model/account/Credential.java @@ -0,0 +1,71 @@ +//@@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..c3267be79503 --- /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 78% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/address/model/account/Name.java index 8e632943c4cf..f8a59a99b1f1 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/address/model/account/Name.java @@ -1,16 +1,18 @@ -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. * 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, @@ -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..4c7d25977eda --- /dev/null +++ b/src/main/java/seedu/address/model/account/Password.java @@ -0,0 +1,64 @@ +//@@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 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..be29adace58e --- /dev/null +++ b/src/main/java/seedu/address/model/account/UniqueAccountList.java @@ -0,0 +1,167 @@ +//@@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 { + 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; + } + + + 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..80de2f621ad0 --- /dev/null +++ b/src/main/java/seedu/address/model/account/Username.java @@ -0,0 +1,66 @@ +//@@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 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{" + + "username='" + 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..ac96273ec25d --- /dev/null +++ b/src/main/java/seedu/address/model/account/exceptions/AccountNotFoundException.java @@ -0,0 +1,8 @@ +//@@author QiuHaohao +package seedu.address.model.account.exceptions; + +/** + * Signals that the operation is unable to find the specified account. + */ +public class AccountNotFoundException extends Exception { +} 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..95f89824f9c6 --- /dev/null +++ b/src/main/java/seedu/address/model/book/Author.java @@ -0,0 +1,58 @@ +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 address 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; + } + + /** + * 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..d6021f439c56 --- /dev/null +++ b/src/main/java/seedu/address/model/book/Avail.java @@ -0,0 +1,60 @@ +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 BORROWED_AND_RESERVED = "Borrowed and 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" + + "4. " + BORROWED_AND_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) || test.equals(BORROWED_AND_RESERVED); + } + + @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..3c33d5fedb00 --- /dev/null +++ b/src/main/java/seedu/address/model/book/Book.java @@ -0,0 +1,101 @@ +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 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..fd1f3b78541d --- /dev/null +++ b/src/main/java/seedu/address/model/book/Isbn.java @@ -0,0 +1,53 @@ +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 least 3 digits long"; + public static final String ISBN_VALIDATION_REGEX = "\\d{3,}"; + 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; + } + + /** + * 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..77339cddf0d1 --- /dev/null +++ b/src/main/java/seedu/address/model/book/Title.java @@ -0,0 +1,58 @@ +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 { + + 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 address 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; + } + + /** + * 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..3f86e85d1416 --- /dev/null +++ b/src/main/java/seedu/address/model/book/UniqueBookList.java @@ -0,0 +1,120 @@ +package seedu.address.model.book; + +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.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); + } + + /** + * 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)) { + 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); + } + + /** + * 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..92b454161c9b 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/address/model/tag/Tag.java @@ -4,7 +4,7 @@ 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 { @@ -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..1afec3156d5d --- /dev/null +++ b/src/main/java/seedu/address/storage/SerialisedFileStorage.java @@ -0,0 +1,39 @@ + +//@@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/PersonCard.java b/src/main/java/seedu/address/ui/BookCard.java similarity index 53% rename from src/main/java/seedu/address/ui/PersonCard.java rename to src/main/java/seedu/address/ui/BookCard.java index f6727ea83abd..4c40ab5f7c6a 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/address/ui/BookCard.java @@ -5,49 +5,49 @@ import javafx.scene.layout.FlowPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Region; -import seedu.address.model.person.Person; +import seedu.address.model.book.Book; /** - * An UI component that displays information of a {@code Person}. + * An UI component that displays information of a {@code Book}. */ -public class PersonCard extends UiPart<Region> { +public class BookCard extends UiPart<Region> { - private static final String FXML = "PersonListCard.fxml"; + 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 AddressBook level 4</a> + * @see <a href="https://github.com/se-edu/addressbook-level4/issues/336">The issue on Catalogue level 4</a> */ - public final Person person; + public final Book book; @FXML private HBox cardPane; @FXML - private Label name; + private Label title; @FXML - private Label id; + private Label author; @FXML - private Label phone; + private Label id; @FXML - private Label address; + private Label isbn; @FXML - private Label email; + private Label avail; @FXML private FlowPane tags; - public PersonCard(Person person, int displayedIndex) { + public BookCard(Book book, int displayedIndex) { super(FXML); - this.person = person; + this.book = book; 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))); + title.setText(book.getTitle().fullTitle); + author.setText(book.getAuthor().value); + isbn.setText(book.getIsbn().value); + avail.setText(book.getAvail().value); + book.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); } @Override @@ -58,13 +58,13 @@ public boolean equals(Object other) { } // instanceof handles nulls - if (!(other instanceof PersonCard)) { + if (!(other instanceof BookCard)) { return false; } // state check - PersonCard card = (PersonCard) other; + BookCard card = (BookCard) other; return id.getText().equals(card.id.getText()) - && person.equals(card.person); + && book.equals(card.book); } } 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..8deeee9a6d77 100644 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ b/src/main/java/seedu/address/ui/BrowserPanel.java @@ -12,8 +12,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. @@ -22,7 +22,7 @@ public class BrowserPanel extends UiPart<Region> { public static final String DEFAULT_PAGE = "default.html"; 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 +39,12 @@ 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); + private void loadBookPage(Book book) { + loadPage(SEARCH_PAGE_URL + book.getIsbn().toString()); } public void loadPage(String url) { @@ -65,8 +67,8 @@ 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); } } 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/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..14b60c40a347 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/address/ui/UiManager.java @@ -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/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/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..e3462f242679 --- /dev/null +++ b/src/test/data/XmlCatalogueStorageTest/invalidAndValidBookCatalogue.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<catalogue> + <!-- Valid Book --> + <books> + <title>Hans Muster + Muster Hans + 9482424 + Available + + + + Hans Muster + Muster HAns + 948asdf2424 + Available + + diff --git a/src/test/data/XmlCatalogueStorageTest/invalidBookCatalogue.xml b/src/test/data/XmlCatalogueStorageTest/invalidBookCatalogue.xml new file mode 100644 index 000000000000..5efcc59363f1 --- /dev/null +++ b/src/test/data/XmlCatalogueStorageTest/invalidBookCatalogue.xml @@ -0,0 +1,10 @@ + + + + + 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..1b8ba3fb1303 --- /dev/null +++ b/src/test/data/XmlSerializableCatalogueTest/invalidBookCatalogue.xml @@ -0,0 +1,10 @@ + + + + + Hans Muster + Muster Hans + 9482424 + 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..2d07b8e4caa7 --- /dev/null +++ b/src/test/data/XmlSerializableCatalogueTest/typicalBooksCatalogue.xml @@ -0,0 +1,55 @@ + + + + + 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..230a7dc7e2cc --- /dev/null +++ b/src/test/data/XmlUtilTest/invalidBookField.xml @@ -0,0 +1,9 @@ + + + + Hans Muster + Muster Hans + 9482asf424 + Borrowed + friends + 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..fcf750b68420 --- /dev/null +++ b/src/test/data/XmlUtilTest/missingBookField.xml @@ -0,0 +1,8 @@ + + + + 9482424 + Muster Hans + Borrowed + friends + 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..df8efa866084 --- /dev/null +++ b/src/test/data/XmlUtilTest/validBook.xml @@ -0,0 +1,8 @@ + + + Hans Muster + Muster Hans + 9482424 + Borrowed + friends + diff --git a/src/test/data/XmlUtilTest/validCatalogue.xml b/src/test/data/XmlUtilTest/validCatalogue.xml new file mode 100644 index 000000000000..8bcf7d6e9499 --- /dev/null +++ b/src/test/data/XmlUtilTest/validCatalogue.xml @@ -0,0 +1,58 @@ + + + + Hans Muster + Muster Hans + 9482424 + Available + + + Ruth Mueller + Mueller Ruth + 87249245 + Available + + + Heinz Kurz + Kurz Heinz + 95352563 + Borrowed + + + Cornelia Meier + Meier Cornerlia + 87652533 + Borrowed + + + Werner Meyer + Meyer Werner + 9482224 + Reserved + + + Lydia Kunz + Kunz Lydia + 9482427 + Reserved + + + + Anna Best + Best Anna + 9482442 + Reserved + + + Stefan Meier + Stefan Meier + 8482424 + Borrowed and Reserved + + + Martin Mueller + chicago ave + 8482131 + Borrowed and Reserved + + 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