From a580d22e1088f4b4bda5a88ecbbf34b504660390 Mon Sep 17 00:00:00 2001 From: Hicham Moustaid Date: Mon, 9 Mar 2020 19:36:19 +0100 Subject: [PATCH 1/2] Library Managment System-Solution --- .../fr/d2factory/libraryapp/book/Book.java | 38 +++++++++- .../libraryapp/book/BookRepository.java | 39 ++++++++-- .../fr/d2factory/libraryapp/book/ISBN.java | 29 +++++++ .../d2factory/libraryapp/library/Library.java | 2 + .../library/LibraryManagmentSystem.java | 70 +++++++++++++++++ .../library/NoEnoughFundInTheWallet.java | 4 + .../d2factory/libraryapp/member/Member.java | 75 +++++++++++++++++++ .../d2factory/libraryapp/member/Profil.java | 36 +++++++++ .../libraryapp/member/ResidentMember.java | 25 +++++++ .../libraryapp/member/StudentMember.java | 21 ++++++ .../libraryapp/library/LibraryTest.java | 55 +++++++++++--- townsville-library.iml | 11 ++- 12 files changed, 384 insertions(+), 21 deletions(-) create mode 100644 src/main/java/fr/d2factory/libraryapp/library/LibraryManagmentSystem.java create mode 100644 src/main/java/fr/d2factory/libraryapp/library/NoEnoughFundInTheWallet.java create mode 100644 src/main/java/fr/d2factory/libraryapp/member/Profil.java create mode 100644 src/main/java/fr/d2factory/libraryapp/member/ResidentMember.java create mode 100644 src/main/java/fr/d2factory/libraryapp/member/StudentMember.java diff --git a/src/main/java/fr/d2factory/libraryapp/book/Book.java b/src/main/java/fr/d2factory/libraryapp/book/Book.java index d9073c9..e56b0ee 100644 --- a/src/main/java/fr/d2factory/libraryapp/book/Book.java +++ b/src/main/java/fr/d2factory/libraryapp/book/Book.java @@ -1,12 +1,44 @@ package fr.d2factory.libraryapp.book; +import java.io.Serializable; +import java.util.Objects; + /** * A simple representation of a book */ public class Book { - String title; - String author; - ISBN isbn; + private String title; + private String author; + private ISBN isbn; public Book() {} + + public String getTitle() { + return title; + } + + public String getAuthor() { + return author; + } + + public ISBN getIsbn() { + return isbn; + } + + @Override + public boolean equals(Object o) { + boolean result = false; + if(o instanceof Book) { + Book book = (Book) o; + result = Objects.equals(title, book.title) && + Objects.equals(author, book.author) && + Objects.equals(isbn, book.isbn); + } + return result; + } + + @Override + public int hashCode() { + return Objects.hash(title, author, isbn); + } } diff --git a/src/main/java/fr/d2factory/libraryapp/book/BookRepository.java b/src/main/java/fr/d2factory/libraryapp/book/BookRepository.java index df71508..8f25bb6 100644 --- a/src/main/java/fr/d2factory/libraryapp/book/BookRepository.java +++ b/src/main/java/fr/d2factory/libraryapp/book/BookRepository.java @@ -1,9 +1,13 @@ package fr.d2factory.libraryapp.book; +import fr.d2factory.libraryapp.member.Member; + import java.time.LocalDate; +import java.time.temporal.ChronoUnit; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * The book repository emulates a database via 2 HashMaps @@ -11,22 +15,45 @@ public class BookRepository { private Map availableBooks = new HashMap<>(); private Map borrowedBooks = new HashMap<>(); + private Map borrower = new HashMap<>(); public void addBooks(List books){ - //TODO implement the missing feature + books.stream().filter(b->!availableBooks.containsKey(b.getIsbn())) + .forEach(b->availableBooks.put(b.getIsbn(), b)); } public Book findBook(long isbnCode) { - //TODO implement the missing feature - return null; + return availableBooks.get(new ISBN(isbnCode)); + } + + public Member findBorrower(Book book) { + return borrower.get(book); } public void saveBookBorrow(Book book, LocalDate borrowedAt){ - //TODO implement the missing feature + availableBooks.remove(book.getIsbn()); + borrowedBooks.put(book, borrowedAt); + } + + public void saveBorrower(Book book, Member member){ + borrower.put(book, member); } public LocalDate findBorrowedBookDate(Book book) { - //TODO implement the missing feature - return null; + return borrowedBooks.get(book); } + + public void returnBook(Book book){ + borrowedBooks.remove(book); + availableBooks.put(book.getIsbn(), book); + borrower.remove(book); + } + + + public Map> booksBorrowedByMember(){ + return borrower.entrySet() + .stream() + .collect(Collectors.groupingBy(Map.Entry::getValue, Collectors.mapping(Map.Entry::getKey, Collectors.toList()))); + } + } diff --git a/src/main/java/fr/d2factory/libraryapp/book/ISBN.java b/src/main/java/fr/d2factory/libraryapp/book/ISBN.java index bf694c0..3843736 100644 --- a/src/main/java/fr/d2factory/libraryapp/book/ISBN.java +++ b/src/main/java/fr/d2factory/libraryapp/book/ISBN.java @@ -1,9 +1,38 @@ package fr.d2factory.libraryapp.book; +import java.io.Serializable; + public class ISBN { long isbnCode; + public ISBN(){ + } + public ISBN(long isbnCode) { this.isbnCode = isbnCode; } + + + public long getIsbnCode() { + return isbnCode; + } + + public void setIsbnCode(long isbnCode) { + this.isbnCode = isbnCode; + } + + @Override + public int hashCode(){ + return Long.hashCode(isbnCode); + } + + @Override + public boolean equals(Object o){ + boolean result = false; + if(o instanceof ISBN){ + ISBN isbn = (ISBN)o; + result = isbnCode == isbn.getIsbnCode(); + } + return result; + } } diff --git a/src/main/java/fr/d2factory/libraryapp/library/Library.java b/src/main/java/fr/d2factory/libraryapp/library/Library.java index f3a5797..1b70ce7 100644 --- a/src/main/java/fr/d2factory/libraryapp/library/Library.java +++ b/src/main/java/fr/d2factory/libraryapp/library/Library.java @@ -4,6 +4,7 @@ import fr.d2factory.libraryapp.member.Member; import java.time.LocalDate; +import java.util.List; /** * The library class is in charge of stocking the books and managing the return delays and members @@ -37,4 +38,5 @@ public interface Library { * @see Member#payBook(int) */ void returnBook(Book book, Member member); + } diff --git a/src/main/java/fr/d2factory/libraryapp/library/LibraryManagmentSystem.java b/src/main/java/fr/d2factory/libraryapp/library/LibraryManagmentSystem.java new file mode 100644 index 0000000..8c01bd8 --- /dev/null +++ b/src/main/java/fr/d2factory/libraryapp/library/LibraryManagmentSystem.java @@ -0,0 +1,70 @@ +package fr.d2factory.libraryapp.library; + +import fr.d2factory.libraryapp.book.Book; +import fr.d2factory.libraryapp.book.BookRepository; +import fr.d2factory.libraryapp.book.ISBN; +import fr.d2factory.libraryapp.member.Member; + +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.List; + +public class LibraryManagmentSystem implements Library { + + private BookRepository bookRepository; + + public LibraryManagmentSystem(BookRepository bookRepository) { + this.bookRepository = bookRepository; + } + + @Override + public Book borrowBook(long isbnCode, Member member, LocalDate borrowedAt){ + Book book = null; + if(isLate(member)){ + throw new HasLateBooksException(); + } + book = bookRepository.findBook(isbnCode); + if(book != null){ + bookRepository.saveBookBorrow(book, borrowedAt); + bookRepository.saveBorrower(book, member); + } + return book; + } + + @Override + public void returnBook(Book book, Member member){ + int numberOfDays = (int)bookRepository.findBorrowedBookDate(book).until(LocalDate.now(), ChronoUnit.DAYS); + bookRepository.returnBook(book); + member.payBook(numberOfDays); + } + + + private boolean isLate(Member member){ + boolean result = false; + List booksBorrowedByTheMember = bookRepository.booksBorrowedByMember().get(member); + if(booksBorrowedByTheMember != null){ + int memberMaxPeriod = member.getConfig().getMaxPeriod(); + result = booksBorrowedByTheMember.stream() + .anyMatch(b->bookNotReturned(b, memberMaxPeriod)); + } + return result; + } + + private boolean bookNotReturned(Book book, int maxPeriod){ + boolean result = false; + int daysBorrowed = daysBorrowed(book); + result = daysBorrowed > maxPeriod; + return result; + } + + + private int daysBorrowed(Book book){ + int daysBorrowed = 0; + LocalDate now = LocalDate.now(); + LocalDate borrowedAt = bookRepository.findBorrowedBookDate(book); + daysBorrowed = (int)borrowedAt.until(now, ChronoUnit.DAYS); + return daysBorrowed; + } + + +} diff --git a/src/main/java/fr/d2factory/libraryapp/library/NoEnoughFundInTheWallet.java b/src/main/java/fr/d2factory/libraryapp/library/NoEnoughFundInTheWallet.java new file mode 100644 index 0000000..bd60f3c --- /dev/null +++ b/src/main/java/fr/d2factory/libraryapp/library/NoEnoughFundInTheWallet.java @@ -0,0 +1,4 @@ +package fr.d2factory.libraryapp.library; + +public class NoEnoughFundInTheWallet extends RuntimeException { +} diff --git a/src/main/java/fr/d2factory/libraryapp/member/Member.java b/src/main/java/fr/d2factory/libraryapp/member/Member.java index 973da7e..132e081 100644 --- a/src/main/java/fr/d2factory/libraryapp/member/Member.java +++ b/src/main/java/fr/d2factory/libraryapp/member/Member.java @@ -2,16 +2,37 @@ import fr.d2factory.libraryapp.library.Library; +import java.util.Objects; + /** * A member is a person who can borrow and return books to a {@link Library} * A member can be either a student or a resident */ public abstract class Member { + + + private String id; + + private String firstName; + + private String lastName; + /** * An initial sum of money the member has */ private float wallet; + private Profil config; + + + public Member( String id, String firstName, String lastName,float wallet, Profil config) { + this.id = id; + this.firstName = firstName; + this.lastName = lastName; + this.wallet = wallet; + this.config = config; + } + /** * The member should pay their books when they are returned to the library * @@ -19,11 +40,65 @@ public abstract class Member { */ public abstract void payBook(int numberOfDays); + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public void setConfig(Profil config) { + this.config = config; + } + + public float getWallet() { + return wallet; } public void setWallet(float wallet) { + this.wallet = wallet; } + + public Profil getConfig() { + return config; + } + + + @Override + public boolean equals(Object o) { + boolean result = false; + if(o instanceof Member){ + Member member = (Member)o; + result = id.equals(member.id) && + firstName.equals(member.firstName) && + lastName.equals(member.lastName); + } + return result; + } + + @Override + public int hashCode() { + return Objects.hash(id, firstName, lastName); + } } diff --git a/src/main/java/fr/d2factory/libraryapp/member/Profil.java b/src/main/java/fr/d2factory/libraryapp/member/Profil.java new file mode 100644 index 0000000..3b71cb3 --- /dev/null +++ b/src/main/java/fr/d2factory/libraryapp/member/Profil.java @@ -0,0 +1,36 @@ +package fr.d2factory.libraryapp.member; + +public enum Profil { + + RESIDENT(60, 0, 0.10f, 0.20f), + STUDENT(30,0,0.10f,0.10f), + STUDENT_1ST_YEAR(30,15,0.10f,0.10f); + + private final int maxPeriod; + private final int freePeriod; + private final float amountChargedBefore; + private final float amountChargedAfter; + + Profil(int maxPeriod, int freePeriod, float amountChargedBefore, float amountChargedAfter) { + this.maxPeriod = maxPeriod; + this.freePeriod = freePeriod; + this.amountChargedBefore = amountChargedBefore; + this.amountChargedAfter = amountChargedAfter; + } + + public int getMaxPeriod() { + return maxPeriod; + } + + public int getFreePeriod() { + return freePeriod; + } + + public float getAmountChargedBefore() { + return amountChargedBefore; + } + + public float getAmountChargedAfter() { + return amountChargedAfter; + } +} diff --git a/src/main/java/fr/d2factory/libraryapp/member/ResidentMember.java b/src/main/java/fr/d2factory/libraryapp/member/ResidentMember.java new file mode 100644 index 0000000..a22a341 --- /dev/null +++ b/src/main/java/fr/d2factory/libraryapp/member/ResidentMember.java @@ -0,0 +1,25 @@ +package fr.d2factory.libraryapp.member; + +import fr.d2factory.libraryapp.library.NoEnoughFundInTheWallet; + +public class ResidentMember extends Member { + + + public ResidentMember(String id, String firstName, String lastName, float wallet, Profil config) { + super(id, firstName, lastName, wallet, config); + } + + + @Override + public void payBook(int numberOfDays) { + int maxPeriod = getConfig().getMaxPeriod(); + float amountToPay = Math.min(maxPeriod, numberOfDays ) * getConfig().getAmountChargedBefore() + + Math.max(0, numberOfDays - maxPeriod) * getConfig().getAmountChargedAfter(); + if(amountToPay < getWallet()){ + setWallet(getWallet()-amountToPay); + }else{ + throw new NoEnoughFundInTheWallet(); + + } + } +} diff --git a/src/main/java/fr/d2factory/libraryapp/member/StudentMember.java b/src/main/java/fr/d2factory/libraryapp/member/StudentMember.java new file mode 100644 index 0000000..1b94788 --- /dev/null +++ b/src/main/java/fr/d2factory/libraryapp/member/StudentMember.java @@ -0,0 +1,21 @@ +package fr.d2factory.libraryapp.member; + +import fr.d2factory.libraryapp.library.NoEnoughFundInTheWallet; + +public class StudentMember extends Member { + + public StudentMember(String id, String firstName, String lastName, float wallet, Profil config) { + super(id, firstName, lastName, wallet, config); + } + + + @Override + public void payBook(int numberOfDays) { + float amountToPay = Math.max(0, numberOfDays-getConfig().getFreePeriod()) * getConfig().getAmountChargedBefore(); + if(amountToPay < getWallet()){ + setWallet(getWallet()-amountToPay); + }else{ + throw new NoEnoughFundInTheWallet(); + } + } +} diff --git a/src/test/java/fr/d2factory/libraryapp/library/LibraryTest.java b/src/test/java/fr/d2factory/libraryapp/library/LibraryTest.java index 73afac9..6365307 100644 --- a/src/test/java/fr/d2factory/libraryapp/library/LibraryTest.java +++ b/src/test/java/fr/d2factory/libraryapp/library/LibraryTest.java @@ -8,7 +8,15 @@ import fr.d2factory.libraryapp.book.BookRepository; import java.io.File; import java.io.IOException; +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.List; + +import fr.d2factory.libraryapp.member.Member; +import fr.d2factory.libraryapp.member.Profil; +import fr.d2factory.libraryapp.member.ResidentMember; +import fr.d2factory.libraryapp.member.StudentMember; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -17,9 +25,10 @@ * Do not forget to consult the README.md :) */ public class LibraryTest { - private Library library ; - private BookRepository bookRepository; - private static List books; + + private BookRepository bookRepository = new BookRepository(); + private Library library = new LibraryManagmentSystem(bookRepository) ; + private static List books = new ArrayList<>(); @BeforeEach @@ -28,40 +37,66 @@ void setup() throws JsonParseException, JsonMappingException, IOException { File booksJson = new File("src/test/resources/books.json"); books = mapper.readValue(booksJson, new TypeReference>() { }); + bookRepository.addBooks(books); + } @Test void member_can_borrow_a_book_if_book_is_available(){ - Assertions.fail("Implement me"); + Member member = new StudentMember("Y2037978P", "Hicham", "Moustaid", 20.0f, Profil.STUDENT); + Book book = library.borrowBook(46578964513L, member, LocalDate.now()); + Assertions.assertNotNull(book); } @Test void borrowed_book_is_no_longer_available(){ - Assertions.fail("Implement me"); + Member member1 = new StudentMember("Y2037978P", "Hicham", "Moustaid", 20.0f, Profil.STUDENT); + Member member2 = new StudentMember("TR56553D", "Simo", "Fenjero", 30.0f, Profil.RESIDENT); + Book bookAvailable = library.borrowBook(46578964513L, member1, LocalDate.now()); + Book bookNotAvailable = library.borrowBook(46578964513L, member2, LocalDate.now()); + Assertions.assertNull(bookNotAvailable); } @Test void residents_are_taxed_10cents_for_each_day_they_keep_a_book(){ - Assertions.fail("Implement me"); + Member member = new ResidentMember("Y2037978P", "Hicham", "Moustaid", 20.0f, Profil.RESIDENT); + LocalDate dateBorrowed = LocalDate.now().minus(10, ChronoUnit.DAYS); + Book book = library.borrowBook(46578964513L, member, dateBorrowed); + library.returnBook(book, member); + Assertions.assertEquals(19f, member.getWallet()); } @Test void students_pay_10_cents_the_first_30days(){ - Assertions.fail("Implement me"); + Member member = new StudentMember("Y2037978P", "Hicham", "Moustaid", 20.0f, Profil.STUDENT); + LocalDate dateBorrowed = LocalDate.now().minus(30, ChronoUnit.DAYS); + Book book = library.borrowBook(46578964513L, member, dateBorrowed); + library.returnBook(book, member); + Assertions.assertEquals(17f, member.getWallet()); } @Test void students_in_1st_year_are_not_taxed_for_the_first_15days(){ - Assertions.fail("Implement me"); + Member member = new StudentMember("Y2037978P", "Hicham", "Moustaid", 20.0f, Profil.STUDENT_1ST_YEAR); + LocalDate dateBorrowed = LocalDate.now().minus(27, ChronoUnit.DAYS); + Book book = library.borrowBook(46578964513L, member, dateBorrowed); + library.returnBook(book, member); + Assertions.assertEquals(18.8f, member.getWallet()); } @Test void residents_pay_20cents_for_each_day_they_keep_a_book_after_the_initial_60days(){ - Assertions.fail("Implement me"); + Member member = new ResidentMember("Y2037978P", "Hicham", "Moustaid", 20.0f, Profil.RESIDENT); + member.payBook(65); + Assertions.assertEquals(13f, member.getWallet()); } @Test void members_cannot_borrow_book_if_they_have_late_books(){ - Assertions.fail("Implement me"); + Member member = new ResidentMember("Y2037978P", "Hicham", "Moustaid", 20.0f, Profil.STUDENT); + LocalDate dateBorrowed = LocalDate.now().minus(31, ChronoUnit.DAYS); + Book book = library.borrowBook(46578964513L, member, dateBorrowed); + Assertions.assertThrows(HasLateBooksException.class, ()-> library.borrowBook(3326456467846L, member, LocalDate.now())); } + } diff --git a/townsville-library.iml b/townsville-library.iml index e6900b7..cbf9f68 100644 --- a/townsville-library.iml +++ b/townsville-library.iml @@ -11,7 +11,14 @@ - - + + + + + + + + + \ No newline at end of file From 2b9bf336057dfdbbde2e1f2a56933a760d75b5a6 Mon Sep 17 00:00:00 2001 From: Hicham Moustaid Date: Mon, 9 Mar 2020 19:57:44 +0100 Subject: [PATCH 2/2] Use of Optional --- src/main/java/fr/d2factory/libraryapp/book/Book.java | 1 - .../fr/d2factory/libraryapp/book/BookRepository.java | 1 - src/main/java/fr/d2factory/libraryapp/book/ISBN.java | 2 -- .../java/fr/d2factory/libraryapp/library/Library.java | 1 - .../libraryapp/library/LibraryManagmentSystem.java | 11 ++++++----- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/java/fr/d2factory/libraryapp/book/Book.java b/src/main/java/fr/d2factory/libraryapp/book/Book.java index e56b0ee..89ca756 100644 --- a/src/main/java/fr/d2factory/libraryapp/book/Book.java +++ b/src/main/java/fr/d2factory/libraryapp/book/Book.java @@ -1,6 +1,5 @@ package fr.d2factory.libraryapp.book; -import java.io.Serializable; import java.util.Objects; /** diff --git a/src/main/java/fr/d2factory/libraryapp/book/BookRepository.java b/src/main/java/fr/d2factory/libraryapp/book/BookRepository.java index 8f25bb6..8d2def5 100644 --- a/src/main/java/fr/d2factory/libraryapp/book/BookRepository.java +++ b/src/main/java/fr/d2factory/libraryapp/book/BookRepository.java @@ -3,7 +3,6 @@ import fr.d2factory.libraryapp.member.Member; import java.time.LocalDate; -import java.time.temporal.ChronoUnit; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/src/main/java/fr/d2factory/libraryapp/book/ISBN.java b/src/main/java/fr/d2factory/libraryapp/book/ISBN.java index 3843736..ef7f86a 100644 --- a/src/main/java/fr/d2factory/libraryapp/book/ISBN.java +++ b/src/main/java/fr/d2factory/libraryapp/book/ISBN.java @@ -1,7 +1,5 @@ package fr.d2factory.libraryapp.book; -import java.io.Serializable; - public class ISBN { long isbnCode; diff --git a/src/main/java/fr/d2factory/libraryapp/library/Library.java b/src/main/java/fr/d2factory/libraryapp/library/Library.java index 1b70ce7..383394c 100644 --- a/src/main/java/fr/d2factory/libraryapp/library/Library.java +++ b/src/main/java/fr/d2factory/libraryapp/library/Library.java @@ -4,7 +4,6 @@ import fr.d2factory.libraryapp.member.Member; import java.time.LocalDate; -import java.util.List; /** * The library class is in charge of stocking the books and managing the return delays and members diff --git a/src/main/java/fr/d2factory/libraryapp/library/LibraryManagmentSystem.java b/src/main/java/fr/d2factory/libraryapp/library/LibraryManagmentSystem.java index 8c01bd8..63fe924 100644 --- a/src/main/java/fr/d2factory/libraryapp/library/LibraryManagmentSystem.java +++ b/src/main/java/fr/d2factory/libraryapp/library/LibraryManagmentSystem.java @@ -2,12 +2,13 @@ import fr.d2factory.libraryapp.book.Book; import fr.d2factory.libraryapp.book.BookRepository; -import fr.d2factory.libraryapp.book.ISBN; import fr.d2factory.libraryapp.member.Member; import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.List; +import java.util.Optional; +import java.util.function.Consumer; public class LibraryManagmentSystem implements Library { @@ -24,10 +25,10 @@ public Book borrowBook(long isbnCode, Member member, LocalDate borrowedAt){ throw new HasLateBooksException(); } book = bookRepository.findBook(isbnCode); - if(book != null){ - bookRepository.saveBookBorrow(book, borrowedAt); - bookRepository.saveBorrower(book, member); - } + Optional opBook = Optional.ofNullable(book); + Consumer c1= b -> bookRepository.saveBookBorrow(b, borrowedAt); + Consumer c2 = c1.andThen(b-> bookRepository.saveBorrower(b, member)); + opBook.ifPresent(c2); return book; }