Overview
A comprehensive Library Management System that handles books, members, borrowing, returns, and administrative functions with proper data persistence and user interfaces.
System Architecture
1. Core Domain Models
// Base Entity class import java.io.Serializable; import java.time.LocalDateTime; import java.util.Objects; public abstract class BaseEntity<ID> implements Serializable { private ID id; private LocalDateTime createdAt; private LocalDateTime updatedAt; public BaseEntity() { this.createdAt = LocalDateTime.now(); this.updatedAt = LocalDateTime.now(); } // Getters and setters public ID getId() { return id; } public void setId(ID id) { this.id = id; } public LocalDateTime getCreatedAt() { return createdAt; } public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } public LocalDateTime getUpdatedAt() { return updatedAt; } public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; BaseEntity<?> that = (BaseEntity<?>) o; return Objects.equals(id, that.id); } @Override public int hashCode() { return Objects.hash(id); } } // Book entity import java.time.Year; import java.util.ArrayList; import java.util.List; public class Book extends BaseEntity<String> { private String isbn; private String title; private String author; private String publisher; private Year publicationYear; private String genre; private int totalCopies; private int availableCopies; private List<BookCopy> copies; public Book() { this.copies = new ArrayList<>(); } public Book(String isbn, String title, String author, String publisher, Year publicationYear, String genre, int totalCopies) { this(); this.isbn = isbn; this.title = title; this.author = author; this.publisher = publisher; this.publicationYear = publicationYear; this.genre = genre; this.totalCopies = totalCopies; this.availableCopies = totalCopies; generateCopies(); } private void generateCopies() { for (int i = 1; i <= totalCopies; i++) { copies.add(new BookCopy(this, i)); } } // Getters and setters public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getPublisher() { return publisher; } public void setPublisher(String publisher) { this.publisher = publisher; } public Year getPublicationYear() { return publicationYear; } public void setPublicationYear(Year publicationYear) { this.publicationYear = publicationYear; } public String getGenre() { return genre; } public void setGenre(String genre) { this.genre = genre; } public int getTotalCopies() { return totalCopies; } public void setTotalCopies(int totalCopies) { this.totalCopies = totalCopies; updateAvailableCopies(); } public int getAvailableCopies() { return availableCopies; } public void setAvailableCopies(int availableCopies) { this.availableCopies = availableCopies; } public List<BookCopy> getCopies() { return copies; } public void setCopies(List<BookCopy> copies) { this.copies = copies; } public void updateAvailableCopies() { this.availableCopies = (int) copies.stream() .filter(BookCopy::isAvailable) .count(); } public boolean isAvailable() { return availableCopies > 0; } @Override public String toString() { return String.format("Book{isbn='%s', title='%s', author='%s', available=%d/%d}", isbn, title, author, availableCopies, totalCopies); } } // Book Copy entity public class BookCopy extends BaseEntity<Long> { private Book book; private int copyNumber; private boolean available; private String condition; // NEW, GOOD, FAIR, POOR private String location; // Shelf location public BookCopy() { this.available = true; this.condition = "NEW"; } public BookCopy(Book book, int copyNumber) { this(); this.book = book; this.copyNumber = copyNumber; } // Getters and setters public Book getBook() { return book; } public void setBook(Book book) { this.book = book; } public int getCopyNumber() { return copyNumber; } public void setCopyNumber(int copyNumber) { this.copyNumber = copyNumber; } public boolean isAvailable() { return available; } public void setAvailable(boolean available) { this.available = available; } public String getCondition() { return condition; } public void setCondition(String condition) { this.condition = condition; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public String getUniqueId() { return book.getIsbn() + "-" + copyNumber; } @Override public String toString() { return String.format("BookCopy{book='%s', copyNumber=%d, available=%s}", book.getTitle(), copyNumber, available); } } // Member entity import java.time.LocalDate; import java.util.ArrayList; import java.util.List; public class Member extends BaseEntity<Long> { private String memberId; private String firstName; private String lastName; private String email; private String phone; private LocalDate joinDate; private MembershipType membershipType; private int maxBooksAllowed; private double outstandingFines; private List<BorrowRecord> borrowHistory; public enum MembershipType { STUDENT(5, 14), FACULTY(10, 30), STAFF(5, 21), PREMIUM(15, 45); private final int maxBooks; private final int loanPeriodDays; MembershipType(int maxBooks, int loanPeriodDays) { this.maxBooks = maxBooks; this.loanPeriodDays = loanPeriodDays; } public int getMaxBooks() { return maxBooks; } public int getLoanPeriodDays() { return loanPeriodDays; } } public Member() { this.borrowHistory = new ArrayList<>(); this.joinDate = LocalDate.now(); this.outstandingFines = 0.0; } public Member(String memberId, String firstName, String lastName, String email, String phone, MembershipType membershipType) { this(); this.memberId = memberId; this.firstName = firstName; this.lastName = lastName; this.email = email; this.phone = phone; this.membershipType = membershipType; this.maxBooksAllowed = membershipType.getMaxBooks(); } // Getters and setters public String getMemberId() { return memberId; } public void setMemberId(String memberId) { this.memberId = memberId; } 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 getFullName() { return firstName + " " + lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public LocalDate getJoinDate() { return joinDate; } public void setJoinDate(LocalDate joinDate) { this.joinDate = joinDate; } public MembershipType getMembershipType() { return membershipType; } public void setMembershipType(MembershipType membershipType) { this.membershipType = membershipType; this.maxBooksAllowed = membershipType.getMaxBooks(); } public int getMaxBooksAllowed() { return maxBooksAllowed; } public void setMaxBooksAllowed(int maxBooksAllowed) { this.maxBooksAllowed = maxBooksAllowed; } public double getOutstandingFines() { return outstandingFines; } public void setOutstandingFines(double outstandingFines) { this.outstandingFines = outstandingFines; } public List<BorrowRecord> getBorrowHistory() { return borrowHistory; } public void setBorrowHistory(List<BorrowRecord> borrowHistory) { this.borrowHistory = borrowHistory; } public int getCurrentlyBorrowedCount() { return (int) borrowHistory.stream() .filter(record -> record.getReturnDate() == null) .count(); } public boolean canBorrowMoreBooks() { return getCurrentlyBorrowedCount() < maxBooksAllowed && outstandingFines == 0; } public void addFine(double amount) { this.outstandingFines += amount; } public void payFine(double amount) { this.outstandingFines = Math.max(0, this.outstandingFines - amount); } @Override public String toString() { return String.format("Member{id='%s', name='%s %s', type=%s, fines=$%.2f}", memberId, firstName, lastName, membershipType, outstandingFines); } } // Borrow Record entity import java.time.LocalDate; public class BorrowRecord extends BaseEntity<Long> { private Member member; private BookCopy bookCopy; private LocalDate borrowDate; private LocalDate dueDate; private LocalDate returnDate; private double fineAmount; private String status; // ACTIVE, RETURNED, OVERDUE public BorrowRecord() { this.borrowDate = LocalDate.now(); this.status = "ACTIVE"; } public BorrowRecord(Member member, BookCopy bookCopy) { this(); this.member = member; this.bookCopy = bookCopy; this.dueDate = borrowDate.plusDays(member.getMembershipType().getLoanPeriodDays()); } // Getters and setters public Member getMember() { return member; } public void setMember(Member member) { this.member = member; } public BookCopy getBookCopy() { return bookCopy; } public void setBookCopy(BookCopy bookCopy) { this.bookCopy = bookCopy; } public LocalDate getBorrowDate() { return borrowDate; } public void setBorrowDate(LocalDate borrowDate) { this.borrowDate = borrowDate; } public LocalDate getDueDate() { return dueDate; } public void setDueDate(LocalDate dueDate) { this.dueDate = dueDate; } public LocalDate getReturnDate() { return returnDate; } public void setReturnDate(LocalDate returnDate) { this.returnDate = returnDate; } public double getFineAmount() { return fineAmount; } public void setFineAmount(double fineAmount) { this.fineAmount = fineAmount; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public boolean isOverdue() { return LocalDate.now().isAfter(dueDate) && returnDate == null; } public long getDaysOverdue() { if (!isOverdue()) return 0; return LocalDate.now().toEpochDay() - dueDate.toEpochDay(); } public void calculateFine(double dailyFineRate) { if (isOverdue()) { long overdueDays = getDaysOverdue(); this.fineAmount = overdueDays * dailyFineRate; } } @Override public String toString() { return String.format("BorrowRecord{member=%s, book=%s, due=%s, returned=%s}", member.getFullName(), bookCopy.getBook().getTitle(), dueDate, returnDate != null ? returnDate.toString() : "Not returned"); } } 2. Repository Layer
// Generic Repository Interface import java.util.List; import java.util.Optional; public interface Repository<T extends BaseEntity<ID>, ID> { Optional<T> findById(ID id); List<T> findAll(); T save(T entity); void delete(T entity); void deleteById(ID id); boolean existsById(ID id); long count(); } // Book Repository import java.util.*; public class BookRepository implements Repository<Book, String> { private final Map<String, Book> books = new HashMap<>(); private final Map<String, List<BookCopy>> copiesByIsbn = new HashMap<>(); @Override public Optional<Book> findById(String isbn) { return Optional.ofNullable(books.get(isbn)); } @Override public List<Book> findAll() { return new ArrayList<>(books.values()); } @Override public Book save(Book book) { books.put(book.getIsbn(), book); copiesByIsbn.put(book.getIsbn(), book.getCopies()); return book; } @Override public void delete(Book book) { books.remove(book.getIsbn()); copiesByIsbn.remove(book.getIsbn()); } @Override public void deleteById(String isbn) { books.remove(isbn); copiesByIsbn.remove(isbn); } @Override public boolean existsById(String isbn) { return books.containsKey(isbn); } @Override public long count() { return books.size(); } // Custom queries public List<Book> findByTitle(String title) { return books.values().stream() .filter(book -> book.getTitle().toLowerCase().contains(title.toLowerCase())) .toList(); } public List<Book> findByAuthor(String author) { return books.values().stream() .filter(book -> book.getAuthor().toLowerCase().contains(author.toLowerCase())) .toList(); } public List<Book> findByGenre(String genre) { return books.values().stream() .filter(book -> book.getGenre().equalsIgnoreCase(genre)) .toList(); } public List<Book> findAvailableBooks() { return books.values().stream() .filter(Book::isAvailable) .toList(); } public Optional<BookCopy> findAvailableCopy(String isbn) { return copiesByIsbn.getOrDefault(isbn, Collections.emptyList()).stream() .filter(BookCopy::isAvailable) .findFirst(); } } // Member Repository import java.util.*; public class MemberRepository implements Repository<Member, Long> { private final Map<Long, Member> members = new HashMap<>(); private final Map<String, Member> membersById = new HashMap<>(); private long nextId = 1; @Override public Optional<Member> findById(Long id) { return Optional.ofNullable(members.get(id)); } public Optional<Member> findByMemberId(String memberId) { return Optional.ofNullable(membersById.get(memberId)); } @Override public List<Member> findAll() { return new ArrayList<>(members.values()); } @Override public Member save(Member member) { if (member.getId() == null) { member.setId(nextId++); } members.put(member.getId(), member); membersById.put(member.getMemberId(), member); return member; } @Override public void delete(Member member) { members.remove(member.getId()); membersById.remove(member.getMemberId()); } @Override public void deleteById(Long id) { Member member = members.get(id); if (member != null) { membersById.remove(member.getMemberId()); members.remove(id); } } @Override public boolean existsById(Long id) { return members.containsKey(id); } public boolean existsByMemberId(String memberId) { return membersById.containsKey(memberId); } @Override public long count() { return members.size(); } // Custom queries public List<Member> findByName(String name) { return members.values().stream() .filter(member -> member.getFullName().toLowerCase().contains(name.toLowerCase())) .toList(); } public List<Member> findMembersWithFines() { return members.values().stream() .filter(member -> member.getOutstandingFines() > 0) .toList(); } public List<Member> findByMembershipType(Member.MembershipType type) { return members.values().stream() .filter(member -> member.getMembershipType() == type) .toList(); } } // Borrow Record Repository import java.time.LocalDate; import java.util.*; public class BorrowRecordRepository implements Repository<BorrowRecord, Long> { private final Map<Long, BorrowRecord> records = new HashMap<>(); private long nextId = 1; @Override public Optional<BorrowRecord> findById(Long id) { return Optional.ofNullable(records.get(id)); } @Override public List<BorrowRecord> findAll() { return new ArrayList<>(records.values()); } @Override public BorrowRecord save(BorrowRecord record) { if (record.getId() == null) { record.setId(nextId++); } records.put(record.getId(), record); return record; } @Override public void delete(BorrowRecord record) { records.remove(record.getId()); } @Override public void deleteById(Long id) { records.remove(id); } @Override public boolean existsById(Long id) { return records.containsKey(id); } @Override public long count() { return records.size(); } // Custom queries public List<BorrowRecord> findByMember(Long memberId) { return records.values().stream() .filter(record -> record.getMember().getId().equals(memberId)) .toList(); } public List<BorrowRecord> findByBookCopy(Long bookCopyId) { return records.values().stream() .filter(record -> record.getBookCopy().getId().equals(bookCopyId)) .toList(); } public List<BorrowRecord> findOverdueRecords() { return records.values().stream() .filter(BorrowRecord::isOverdue) .toList(); } public List<BorrowRecord> findActiveRecords() { return records.values().stream() .filter(record -> record.getReturnDate() == null) .toList(); } public Optional<BorrowRecord> findActiveRecordByBookCopy(Long bookCopyId) { return records.values().stream() .filter(record -> record.getBookCopy().getId().equals(bookCopyId)) .filter(record -> record.getReturnDate() == null) .findFirst(); } } 3. Service Layer
// Library Service Interface import java.util.List; import java.util.Optional; public interface LibraryService { // Book operations Book addBook(Book book); Optional<Book> findBookByIsbn(String isbn); List<Book> searchBooksByTitle(String title); List<Book> searchBooksByAuthor(String author); List<Book> getAvailableBooks(); // Member operations Member registerMember(Member member); Optional<Member> findMemberById(String memberId); List<Member> searchMembersByName(String name); // Borrow operations BorrowResult borrowBook(String memberId, String isbn); ReturnResult returnBook(String memberId, String isbn, int copyNumber); List<BorrowRecord> getMemberBorrowHistory(String memberId); List<BorrowRecord> getOverdueRecords(); // Fine operations double calculateMemberFines(String memberId); PaymentResult payFine(String memberId, double amount); // Reports LibraryReport generateReport(); } // Main Library Service Implementation import java.time.LocalDate; import java.util.*; public class LibraryServiceImpl implements LibraryService { private final BookRepository bookRepository; private final MemberRepository memberRepository; private final BorrowRecordRepository borrowRecordRepository; private final double dailyFineRate = 0.50; // $0.50 per day private final int maxRenewals = 2; public LibraryServiceImpl(BookRepository bookRepository, MemberRepository memberRepository, BorrowRecordRepository borrowRecordRepository) { this.bookRepository = bookRepository; this.memberRepository = memberRepository; this.borrowRecordRepository = borrowRecordRepository; } // Book operations @Override public Book addBook(Book book) { if (bookRepository.existsById(book.getIsbn())) { throw new LibraryException("Book with ISBN " + book.getIsbn() + " already exists"); } return bookRepository.save(book); } @Override public Optional<Book> findBookByIsbn(String isbn) { return bookRepository.findById(isbn); } @Override public List<Book> searchBooksByTitle(String title) { return bookRepository.findByTitle(title); } @Override public List<Book> searchBooksByAuthor(String author) { return bookRepository.findByAuthor(author); } @Override public List<Book> getAvailableBooks() { return bookRepository.findAvailableBooks(); } // Member operations @Override public Member registerMember(Member member) { if (memberRepository.existsByMemberId(member.getMemberId())) { throw new LibraryException("Member with ID " + member.getMemberId() + " already exists"); } return memberRepository.save(member); } @Override public Optional<Member> findMemberById(String memberId) { return memberRepository.findByMemberId(memberId); } @Override public List<Member> searchMembersByName(String name) { return memberRepository.findByName(name); } // Borrow operations @Override public BorrowResult borrowBook(String memberId, String isbn) { Member member = memberRepository.findByMemberId(memberId) .orElseThrow(() -> new LibraryException("Member not found: " + memberId)); Book book = bookRepository.findById(isbn) .orElseThrow(() -> new LibraryException("Book not found: " + isbn)); // Validation checks if (!member.canBorrowMoreBooks()) { return BorrowResult.failure("Member cannot borrow more books. Current: " + member.getCurrentlyBorrowedCount() + "/" + member.getMaxBooksAllowed() + ", Fines: $" + member.getOutstandingFines()); } if (!book.isAvailable()) { return BorrowResult.failure("No available copies of: " + book.getTitle()); } BookCopy availableCopy = bookRepository.findAvailableCopy(isbn) .orElseThrow(() -> new LibraryException("No available copy found")); // Create borrow record BorrowRecord record = new BorrowRecord(member, availableCopy); availableCopy.setAvailable(false); book.updateAvailableCopies(); borrowRecordRepository.save(record); member.getBorrowHistory().add(record); return BorrowResult.success(record, "Book borrowed successfully. Due date: " + record.getDueDate()); } @Override public ReturnResult returnBook(String memberId, String isbn, int copyNumber) { Member member = memberRepository.findByMemberId(memberId) .orElseThrow(() -> new LibraryException("Member not found: " + memberId)); Book book = bookRepository.findById(isbn) .orElseThrow(() -> new LibraryException("Book not found: " + isbn)); // Find the active borrow record BorrowRecord activeRecord = borrowRecordRepository.findActiveRecords().stream() .filter(record -> record.getMember().getMemberId().equals(memberId)) .filter(record -> record.getBookCopy().getBook().getIsbn().equals(isbn)) .filter(record -> record.getBookCopy().getCopyNumber() == copyNumber) .findFirst() .orElseThrow(() -> new LibraryException("No active borrow record found")); // Update record activeRecord.setReturnDate(LocalDate.now()); activeRecord.setStatus("RETURNED"); // Calculate fine if overdue if (activeRecord.isOverdue()) { activeRecord.calculateFine(dailyFineRate); member.addFine(activeRecord.getFineAmount()); } // Make book copy available again activeRecord.getBookCopy().setAvailable(true); book.updateAvailableCopies(); return ReturnResult.success(activeRecord, "Book returned successfully." + (activeRecord.getFineAmount() > 0 ? " Overdue fine: $" + activeRecord.getFineAmount() : "")); } @Override public List<BorrowRecord> getMemberBorrowHistory(String memberId) { Member member = memberRepository.findByMemberId(memberId) .orElseThrow(() -> new LibraryException("Member not found: " + memberId)); return borrowRecordRepository.findByMember(member.getId()); } @Override public List<BorrowRecord> getOverdueRecords() { return borrowRecordRepository.findOverdueRecords(); } // Fine operations @Override public double calculateMemberFines(String memberId) { Member member = memberRepository.findByMemberId(memberId) .orElseThrow(() -> new LibraryException("Member not found: " + memberId)); // Calculate fines for active overdue records double totalFines = member.getOutstandingFines(); List<BorrowRecord> activeRecords = borrowRecordRepository.findByMember(member.getId()) .stream() .filter(record -> record.getReturnDate() == null) .filter(BorrowRecord::isOverdue) .toList(); for (BorrowRecord record : activeRecords) { record.calculateFine(dailyFineRate); totalFines += record.getFineAmount(); } return totalFines; } @Override public PaymentResult payFine(String memberId, double amount) { Member member = memberRepository.findByMemberId(memberId) .orElseThrow(() -> new LibraryException("Member not found: " + memberId)); if (amount <= 0) { return PaymentResult.failure("Payment amount must be positive"); } double currentFines = member.getOutstandingFines(); if (amount > currentFines) { return PaymentResult.failure("Payment amount exceeds outstanding fines"); } member.payFine(amount); return PaymentResult.success(amount, "Payment of $" + amount + " successful. Remaining fines: $" + member.getOutstandingFines()); } // Reports @Override public LibraryReport generateReport() { return new LibraryReport( bookRepository.count(), memberRepository.count(), borrowRecordRepository.findActiveRecords().size(), borrowRecordRepository.findOverdueRecords().size(), memberRepository.findMembersWithFines().stream() .mapToDouble(Member::getOutstandingFines) .sum() ); } } // Result classes public class BorrowResult { private final boolean success; private final String message; private final BorrowRecord record; private BorrowResult(boolean success, String message, BorrowRecord record) { this.success = success; this.message = message; this.record = record; } public static BorrowResult success(BorrowRecord record, String message) { return new BorrowResult(true, message, record); } public static BorrowResult failure(String message) { return new BorrowResult(false, message, null); } // Getters public boolean isSuccess() { return success; } public String getMessage() { return message; } public BorrowRecord getRecord() { return record; } } public class ReturnResult { private final boolean success; private final String message; private final BorrowRecord record; private final double fineAmount; private ReturnResult(boolean success, String message, BorrowRecord record, double fineAmount) { this.success = success; this.message = message; this.record = record; this.fineAmount = fineAmount; } public static ReturnResult success(BorrowRecord record, String message) { return new ReturnResult(true, message, record, record.getFineAmount()); } public static ReturnResult failure(String message) { return new ReturnResult(false, message, null, 0); } // Getters public boolean isSuccess() { return success; } public String getMessage() { return message; } public BorrowRecord getRecord() { return record; } public double getFineAmount() { return fineAmount; } } public class PaymentResult { private final boolean success; private final String message; private final double amountPaid; private final double remainingFines; private PaymentResult(boolean success, String message, double amountPaid, double remainingFines) { this.success = success; this.message = message; this.amountPaid = amountPaid; this.remainingFines = remainingFines; } public static PaymentResult success(double amountPaid, String message) { return new PaymentResult(true, message, amountPaid, 0); // remaining calculated separately } public static PaymentResult failure(String message) { return new PaymentResult(false, message, 0, 0); } // Getters public boolean isSuccess() { return success; } public String getMessage() { return message; } public double getAmountPaid() { return amountPaid; } public double getRemainingFines() { return remainingFines; } } // Report class public class LibraryReport { private final long totalBooks; private final long totalMembers; private final long currentlyBorrowed; private final long overdueBooks; private final double totalFines; private final LocalDate generatedDate; public LibraryReport(long totalBooks, long totalMembers, long currentlyBorrowed, long overdueBooks, double totalFines) { this.totalBooks = totalBooks; this.totalMembers = totalMembers; this.currentlyBorrowed = currentlyBorrowed; this.overdueBooks = overdueBooks; this.totalFines = totalFines; this.generatedDate = LocalDate.now(); } // Getters public long getTotalBooks() { return totalBooks; } public long getTotalMembers() { return totalMembers; } public long getCurrentlyBorrowed() { return currentlyBorrowed; } public long getOverdueBooks() { return overdueBooks; } public double getTotalFines() { return totalFines; } public LocalDate getGeneratedDate() { return generatedDate; } @Override public String toString() { return String.format( "Library Report (%s)\n" + "Total Books: %d\n" + "Total Members: %d\n" + "Currently Borrowed: %d\n" + "Overdue Books: %d\n" + "Total Outstanding Fines: $%.2f", generatedDate, totalBooks, totalMembers, currentlyBorrowed, overdueBooks, totalFines ); } } // Custom Exception public class LibraryException extends RuntimeException { public LibraryException(String message) { super(message); } public LibraryException(String message, Throwable cause) { super(message, cause); } } 4. Console User Interface
import java.util.*; import java.time.Year; public class LibraryConsoleUI { private final LibraryService libraryService; private final Scanner scanner; public LibraryConsoleUI(LibraryService libraryService) { this.libraryService = libraryService; this.scanner = new Scanner(System.in); } public void start() { System.out.println("=== Library Management System ==="); while (true) { displayMainMenu(); int choice = getIntInput("Enter your choice: "); switch (choice) { case 1 -> manageBooks(); case 2 -> manageMembers(); case 3 -> manageBorrowing(); case 4 -> manageReturns(); case 5 -> generateReports(); case 6 -> manageFines(); case 0 -> { System.out.println("Thank you for using Library Management System!"); return; } default -> System.out.println("Invalid choice. Please try again."); } } } private void displayMainMenu() { System.out.println("\n--- Main Menu ---"); System.out.println("1. Manage Books"); System.out.println("2. Manage Members"); System.out.println("3. Borrow Books"); System.out.println("4. Return Books"); System.out.println("5. Reports"); System.out.println("6. Manage Fines"); System.out.println("0. Exit"); } private void manageBooks() { while (true) { System.out.println("\n--- Book Management ---"); System.out.println("1. Add New Book"); System.out.println("2. Search Books by Title"); System.out.println("3. Search Books by Author"); System.out.println("4. View Available Books"); System.out.println("5. View All Books"); System.out.println("0. Back to Main Menu"); int choice = getIntInput("Enter your choice: "); switch (choice) { case 1 -> addNewBook(); case 2 -> searchBooksByTitle(); case 3 -> searchBooksByAuthor(); case 4 -> viewAvailableBooks(); case 5 -> viewAllBooks(); case 0 -> { return; } default -> System.out.println("Invalid choice."); } } } private void addNewBook() { System.out.println("\n--- Add New Book ---"); String isbn = getStringInput("ISBN: "); String title = getStringInput("Title: "); String author = getStringInput("Author: "); String publisher = getStringInput("Publisher: "); int publicationYear = getIntInput("Publication Year: "); String genre = getStringInput("Genre: "); int totalCopies = getIntInput("Total Copies: "); try { Book book = new Book(isbn, title, author, publisher, Year.of(publicationYear), genre, totalCopies); libraryService.addBook(book); System.out.println("Book added successfully!"); } catch (LibraryException e) { System.out.println("Error: " + e.getMessage()); } } private void searchBooksByTitle() { String title = getStringInput("Enter title to search: "); List<Book> books = libraryService.searchBooksByTitle(title); displayBooks(books); } private void searchBooksByAuthor() { String author = getStringInput("Enter author to search: "); List<Book> books = libraryService.searchBooksByAuthor(author); displayBooks(books); } private void viewAvailableBooks() { List<Book> books = libraryService.getAvailableBooks(); displayBooks(books); } private void viewAllBooks() { // This would require adding a method to get all books System.out.println("Feature not implemented in this version."); } private void displayBooks(List<Book> books) { if (books.isEmpty()) { System.out.println("No books found."); return; } System.out.println("\n--- Books Found ---"); for (int i = 0; i < books.size(); i++) { Book book = books.get(i); System.out.printf("%d. %s\n", i + 1, book); } } private void manageMembers() { while (true) { System.out.println("\n--- Member Management ---"); System.out.println("1. Register New Member"); System.out.println("2. Search Members by Name"); System.out.println("3. View Member Details"); System.out.println("0. Back to Main Menu"); int choice = getIntInput("Enter your choice: "); switch (choice) { case 1 -> registerNewMember(); case 2 -> searchMembersByName(); case 3 -> viewMemberDetails(); case 0 -> { return; } default -> System.out.println("Invalid choice."); } } } private void registerNewMember() { System.out.println("\n--- Register New Member ---"); String memberId = getStringInput("Member ID: "); String firstName = getStringInput("First Name: "); String lastName = getStringInput("Last Name: "); String email = getStringInput("Email: "); String phone = getStringInput("Phone: "); System.out.println("Membership Types:"); for (Member.MembershipType type : Member.MembershipType.values()) { System.out.printf("- %s (Max books: %d, Loan period: %d days)\n", type, type.getMaxBooks(), type.getLoanPeriodDays()); } String typeInput = getStringInput("Membership Type: ").toUpperCase(); try { Member.MembershipType membershipType = Member.MembershipType.valueOf(typeInput); Member member = new Member(memberId, firstName, lastName, email, phone, membershipType); libraryService.registerMember(member); System.out.println("Member registered successfully!"); } catch (IllegalArgumentException e) { System.out.println("Invalid membership type."); } catch (LibraryException e) { System.out.println("Error: " + e.getMessage()); } } private void searchMembersByName() { String name = getStringInput("Enter name to search: "); List<Member> members = libraryService.searchMembersByName(name); displayMembers(members); } private void viewMemberDetails() { String memberId = getStringInput("Enter Member ID: "); Optional<Member> memberOpt = libraryService.findMemberById(memberId); if (memberOpt.isPresent()) { Member member = memberOpt.get(); System.out.println("\n--- Member Details ---"); System.out.println("ID: " + member.getMemberId()); System.out.println("Name: " + member.getFullName()); System.out.println("Email: " + member.getEmail()); System.out.println("Phone: " + member.getPhone()); System.out.println("Membership Type: " + member.getMembershipType()); System.out.println("Currently Borrowed: " + member.getCurrentlyBorrowedCount() + "/" + member.getMaxBooksAllowed()); System.out.println("Outstanding Fines: $" + member.getOutstandingFines()); // Show borrow history List<BorrowRecord> history = libraryService.getMemberBorrowHistory(memberId); if (!history.isEmpty()) { System.out.println("\nBorrow History:"); for (BorrowRecord record : history) { System.out.println(" - " + record.getBookCopy().getBook().getTitle() + " (Due: " + record.getDueDate() + ", Returned: " + (record.getReturnDate() != null ? record.getReturnDate() : "Not yet") + ")"); } } } else { System.out.println("Member not found."); } } private void displayMembers(List<Member> members) { if (members.isEmpty()) { System.out.println("No members found."); return; } System.out.println("\n--- Members Found ---"); for (int i = 0; i < members.size(); i++) { Member member = members.get(i); System.out.printf("%d. %s\n", i + 1, member); } } private void manageBorrowing() { System.out.println("\n--- Borrow Book ---"); String memberId = getStringInput("Member ID: "); String isbn = getStringInput("Book ISBN: "); BorrowResult result = libraryService.borrowBook(memberId, isbn); if (result.isSuccess()) { System.out.println("Success: " + result.getMessage()); } else { System.out.println("Error: " + result.getMessage()); } } private void manageReturns() { System.out.println("\n--- Return Book ---"); String memberId = getStringInput("Member ID: "); String isbn = getStringInput("Book ISBN: "); int copyNumber = getIntInput("Copy Number: "); ReturnResult result = libraryService.returnBook(memberId, isbn, copyNumber); if (result.isSuccess()) { System.out.println("Success: " + result.getMessage()); } else { System.out.println("Error: " + result.getMessage()); } } private void manageFines() { while (true) { System.out.println("\n--- Fine Management ---"); System.out.println("1. Calculate Member Fines"); System.out.println("2. Pay Fines"); System.out.println("0. Back to Main Menu"); int choice = getIntInput("Enter your choice: "); switch (choice) { case 1 -> calculateFines(); case 2 -> payFines(); case 0 -> { return; } default -> System.out.println("Invalid choice."); } } } private void calculateFines() { String memberId = getStringInput("Enter Member ID: "); double fines = libraryService.calculateMemberFines(memberId); System.out.printf("Total fines for member %s: $%.2f\n", memberId, fines); } private void payFines() { String memberId = getStringInput("Enter Member ID: "); double amount = getDoubleInput("Enter payment amount: "); PaymentResult result = libraryService.payFine(memberId, amount); if (result.isSuccess()) { System.out.println("Success: " + result.getMessage()); } else { System.out.println("Error: " + result.getMessage()); } } private void generateReports() { System.out.println("\n--- Library Reports ---"); LibraryReport report = libraryService.generateReport(); System.out.println(report); // Show overdue records List<BorrowRecord> overdueRecords = libraryService.getOverdueRecords(); if (!overdueRecords.isEmpty()) { System.out.println("\n--- Overdue Books ---"); for (BorrowRecord record : overdueRecords) { System.out.printf("- %s borrowed by %s (Due: %s, Overdue: %d days)\n", record.getBookCopy().getBook().getTitle(), record.getMember().getFullName(), record.getDueDate(), record.getDaysOverdue()); } } } // Utility methods for input private String getStringInput(String prompt) { System.out.print(prompt); return scanner.nextLine().trim(); } private int getIntInput(String prompt) { while (true) { try { System.out.print(prompt); return Integer.parseInt(scanner.nextLine().trim()); } catch (NumberFormatException e) { System.out.println("Please enter a valid number."); } } } private double getDoubleInput(String prompt) { while (true) { try { System.out.print(prompt); return Double.parseDouble(scanner.nextLine().trim()); } catch (NumberFormatException e) { System.out.println("Please enter a valid amount."); } } } } 5. Main Application Class
public class LibraryManagementSystem { public static void main(String[] args) { // Initialize repositories BookRepository bookRepository = new BookRepository(); MemberRepository memberRepository = new MemberRepository(); BorrowRecordRepository borrowRecordRepository = new BorrowRecordRepository(); // Initialize service LibraryService libraryService = new LibraryServiceImpl( bookRepository, memberRepository, borrowRecordRepository); // Initialize UI LibraryConsoleUI ui = new LibraryConsoleUI(libraryService); // Add some sample data initializeSampleData(libraryService); // Start the application ui.start(); } private static void initializeSampleData(LibraryService libraryService) { try { // Add sample books libraryService.addBook(new Book("978-0134685991", "Effective Java", "Joshua Bloch", "Addison-Wesley", Year.of(2018), "Programming", 3)); libraryService.addBook(new Book("978-1617293290", "Spring in Action", "Craig Walls", "Manning", Year.of(2020), "Programming", 2)); libraryService.addBook(new Book("978-0596009205", "Head First Design Patterns", "Eric Freeman", "O'Reilly", Year.of(2020), "Programming", 4)); // Register sample members libraryService.registerMember(new Member("MEM001", "John", "Doe", "[email protected]", "555-0101", Member.MembershipType.FACULTY)); libraryService.registerMember(new Member("MEM002", "Jane", "Smith", "[email protected]", "555-0102", Member.MembershipType.STUDENT)); System.out.println("Sample data initialized successfully!"); } catch (LibraryException e) { System.out.println("Error initializing sample data: " + e.getMessage()); } } } Features Included
- Book Management
- Add new books with multiple copies
- Search books by title, author, genre
- Track available copies
- Member Management
- Member registration with different membership types
- Search members by name
- View member details and borrowing history
- Borrowing System
- Borrow books with validation checks
- Automatic due date calculation based on membership type
- Prevent over-borrowing and track fines
- Return System
- Process book returns
- Calculate overdue fines automatically
- Update book availability
- Fine Management
- Calculate outstanding fines
- Process fine payments
- Prevent borrowing when fines exist
- Reporting
- Library statistics
- Overdue books tracking
- Financial reports
Key Design Patterns Used
- Repository Pattern for data access
- Service Layer Pattern for business logic
- DTO Pattern for data transfer
- Factory Pattern for object creation
- Strategy Pattern for different membership types
This Library Management System provides a solid foundation that can be extended with database persistence, web interfaces, or additional features like reservations, renewals, and email notifications.