diff --git a/src/main/java/fr/d2factory/libraryapp/book/Book.java b/src/main/java/fr/d2factory/libraryapp/book/Book.java index d9073c9..89ca756 100644 --- a/src/main/java/fr/d2factory/libraryapp/book/Book.java +++ b/src/main/java/fr/d2factory/libraryapp/book/Book.java @@ -1,12 +1,43 @@ package fr.d2factory.libraryapp.book; +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..8d2def5 100644 --- a/src/main/java/fr/d2factory/libraryapp/book/BookRepository.java +++ b/src/main/java/fr/d2factory/libraryapp/book/BookRepository.java @@ -1,9 +1,12 @@ package fr.d2factory.libraryapp.book; +import fr.d2factory.libraryapp.member.Member; + import java.time.LocalDate; 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 +14,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..ef7f86a 100644 --- a/src/main/java/fr/d2factory/libraryapp/book/ISBN.java +++ b/src/main/java/fr/d2factory/libraryapp/book/ISBN.java @@ -3,7 +3,34 @@ 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..383394c 100644 --- a/src/main/java/fr/d2factory/libraryapp/library/Library.java +++ b/src/main/java/fr/d2factory/libraryapp/library/Library.java @@ -37,4 +37,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..63fe924 --- /dev/null +++ b/src/main/java/fr/d2factory/libraryapp/library/LibraryManagmentSystem.java @@ -0,0 +1,71 @@ +package fr.d2factory.libraryapp.library; + +import fr.d2factory.libraryapp.book.Book; +import fr.d2factory.libraryapp.book.BookRepository; +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 { + + 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); + 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; + } + + @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