LAB#SE00-3: Maven Library model

javase
lab

Introduction

Basic understanding of Java programming language is required, as well as some familiarity with Maven or Gradle for managing dependencies and building the project. ssl encryption and is intended to run behind a Knowledge of algotighms and data structures to implement the required classes.

Describe the required models that might define the solution to a Library implementation. Create multiple classes in Java using the most convenient entity relationship between them to im.

Test these classes using JUnit.

  1. Create a new Maven or Gradle project and setting up the project structure
  2. Modify the project’s pom.xml or build.gradle file to import necessary dependencies, including JUnit for testing
  3. Implement the required classes in Java
  4. Implement two basic patter-designs: singleton and think about factory
  5. Write JUnit tests to verify that classes work as expected
  • Allow the user to input data via the console, rather than using hard-coded test data in JUnit tests

Mock-up

User interface

A terminal menu will show the available options of the Library application

# The interface asks if the user wants to create a borrow
borrow? YES
# If answered YES, then proceed with the borrow
# The interface asks for the user identifier
User? user@mail.com
# If the user doesn't exist, then ask again for the user
Unknown user, try again. User? john.smith@goodmail.com
# If the user exists, then show info regarding that user
User exists
- Name: John Smith
- User id: XXX
# The interface asks for the title of the book to borrow
Book to borrow? "Fake book"
# If the book doesn't exist, then ask again for another book
Unknown book, try again. Book? "Title of the book"
# If the book exists, then show the available issues
Book exists, there are X available issues
- Book ID: 001
- Book ID: 002
{...}
- Book ID: 054
Proceed to borrow? YES
# Use the first available issue of that book and show the Borrow information
New borrow created:
- Borrow ID: 001
- Book: "Title of the book" (Book ID: 001)
- Initial Borrow: 2023-01-31
- Due Date: 2023-02-14
# Ask again if the user wants to borrow something new
borrow? NO
# If answered NO, then exit the program
BYE!
flowchart
    direction TB
    id1{Borrow?} -- "YES" --> id2[User?]
    id2 -- "user@fakemail.com" -->id3[Unknown user, try again]
    id3 -- "Ask the user again" --> id2
    id2 -- "john.smith@goodmail.com" --> id4[User exists:\n- Username: John Smith\n- User ID: XXX]
    id4 -- "Ask for a Book name" --> id5[Book?]
    id5 -- "Fake book" --> id6[Unknown book, try again] --> id5
    id5 -- "Title of the book" --> id7[Title of the book exists, there are X available issues:\n- Book ID: 001\n- Book ID: 002\n...\n- Book ID: XXX]
    id7 --> id8{Proceed to\nborrow?}
    id8 -- "YES" --> id9[New borrow created:\n- Borrow ID: 001\n- Book: Title of the book Book ID: 001]

Domains

Core domain

Logic domain

User interface domain

UML

Core classes

Book

Person

User

Author

Borrow

classDiagram
    class Book {
    }

    class User {
    <<Person>>
        -String userId
    }

    class Borrow {
        -Date initialBorrow
        -Date dueDate
        -Date returnDate
        -Book borrowedBook
    }

    Borrow "1..*" o-- "1" Book : lends
    Book "1" -- "1..*" Borrow : can be lent
    User "1" o-- "*" Borrow : can make

Management classes

BookManager

BookManager.java
package org.labse03part1.logic;

import com.github.javafaker.Faker;
import org.labse03part1.domain.Author;
import org.labse03part1.domain.Book;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Scanner;
import java.util.stream.Stream;

import static org.labse03part1.utils.InterfaceUtils.askInt;
import static org.labse03part1.utils.InterfaceUtils.askString;

public class BookManager {
    private static final HashMap<String, Book> books = new HashMap<>();

    private enum bookOptionsEnum {
        ADD_BOOK("Add book") {
            @Override
            void action(Scanner reader) {
                addBook(reader);
            }

            void addBook(Scanner reader) {
                // Create a book
                String bookTitle = askString(reader, "[" + bookOptionsEnum.ADD_BOOK.getDescription() + "] Enter name of the new book ('Quit' to exit): ");
                if (bookTitle.equals("Quit")) {
                    System.out.println("[" + bookOptionsEnum.ADD_BOOK.getDescription() + "] Add book cancelled");
                    return;
                }

                int bookYear = askInt(reader, "[" + bookOptionsEnum.ADD_BOOK.getDescription() + "] Enter year of the new book: ");
                int bookPages = askInt(reader, "[" + bookOptionsEnum.ADD_BOOK.getDescription() + "] Enter number of pages of the book: ");
                String bookISBN = askString(reader, "[" + bookOptionsEnum.ADD_BOOK.getDescription() + "] Enter book ISBN: ");

                // The user selects one of the available authors
                Author bookAuthor = AuthorManager.getAuthor(reader);
                // Add the book into the system
                Book newBook = new Book(bookTitle, bookPages, bookYear, bookISBN, bookAuthor, true);
                books.put(newBook.getBookID(), newBook);

                System.out.println("[" + bookOptionsEnum.ADD_BOOK.getDescription() + "] Book " + bookTitle + " added! ID: " + newBook.getBookID());
            }
        },
        DELETE_BOOK("Delete book") {
            @Override
            void action(Scanner reader) {
                deleteBook(reader);
            }

            void deleteBook(Scanner reader) {
                // List the available books
                bookOptionsEnum.LIST_BOOKS.action(reader);
                String bookID = askString(reader, "[" + bookOptionsEnum.DELETE_BOOK.getDescription() + "] Enter ID of the book to delete ('Quit' to exit): ");
                while (!books.containsKey(bookID)) {
                    if (bookID.equals("Quit")) {
                        System.out.println("[" + bookOptionsEnum.DELETE_BOOK.getDescription() + "] Delete book cancelled");
                        return;
                    }
                    System.out.println("[" + bookOptionsEnum.DELETE_BOOK.getDescription() + "] Book " + bookID + "doesn't exist in the system!");
                    bookID = askString(reader, "[" + bookOptionsEnum.DELETE_BOOK.getDescription() + "] Enter name of the book to delete ('Quit' to exit): ");
                }
                books.remove(bookID);
                System.out.println("[" + bookOptionsEnum.DELETE_BOOK.getDescription() + "] Book " + bookID + " deleted!");
            }
        },
        CHECK_BOOK("Check book") {
            @Override
            void action(Scanner reader) {
                checkBook(reader);
            }

            void checkBook(Scanner reader) {
                // List the available books
                bookOptionsEnum.LIST_BOOKS.action(reader);
                String bookID = askString(reader, "[" + bookOptionsEnum.CHECK_BOOK.getDescription() + "] Enter book ID ('Quit' to exit): ");
                while (!books.containsKey(bookID)) {
                    if (bookID.equals("Quit")) {
                        System.out.println("[" + bookOptionsEnum.CHECK_BOOK.getDescription() + "] Check book cancelled");
                        return;
                    }
                    System.out.println("[" + bookOptionsEnum.CHECK_BOOK.getDescription() + "] Book " + bookID + "doesn't exist in the system!");
                    bookID = askString(reader, "[" + bookOptionsEnum.CHECK_BOOK.getDescription() + "] Enter book ID ('Quit' to exit): ");
                }
                // Print the book information
                System.out.println(books.get(bookID));
            }
        },
        LIST_BOOKS("List books") {
            @Override
            void action(Scanner reader) {
                listBooks();
            }

            void listBooks() {
                System.out.println("[" + bookOptionsEnum.LIST_BOOKS.getDescription() + "] Available books:");
                books.forEach((bookID, book) -> System.out.println(bookID + ": " + book.getTitle()));
            }
        },
        UPDATE_BOOK("Update book") {
            @Override
            void action(Scanner reader) {
                updateBook(reader);
            }

            void updateBook(Scanner reader) {
                // List the available books
                bookOptionsEnum.LIST_BOOKS.action(reader);
                String bookID = askString(reader, "[" + bookOptionsEnum.UPDATE_BOOK.getDescription() + "] Enter book ID to update ('Quit' to exit): ");
                while (!books.containsKey(bookID)) {
                    if (bookID.equals("Quit")) {
                        System.out.println("[" + bookOptionsEnum.UPDATE_BOOK.getDescription() + "] Update book cancelled");
                        return;
                    }
                    System.out.println("[" + bookOptionsEnum.UPDATE_BOOK.getDescription() + "] Book " + bookID + " doesn't exist in the system!");
                    bookID = askString(reader, "[" + bookOptionsEnum.UPDATE_BOOK.getDescription() + "] Enter book ID to update ('Quit' to exit): ");
                }
                Book bookToUpdate = books.get(bookID);
                System.out.println(bookToUpdate);

                // Once book is found, ask for the parameter to change
                String parameter = askString(reader, "[" + bookOptionsEnum.UPDATE_BOOK.getDescription() + "] Enter the parameter to modify ('Quit' to exit): ");
                Object value;
                while (!parameter.equals("Quit")) {
                    switch (parameter) {
                        case "title" -> {
                            value = askString(reader, "[" + bookOptionsEnum.UPDATE_BOOK.getDescription() + "] Enter the new title of the book: ");
                            bookToUpdate.setTitle(value.toString());
                            System.out.println("[" + bookOptionsEnum.UPDATE_BOOK.getDescription() + "] Title set to " + value);
                        }
                        case "pages" -> {
                            value = askInt(reader, "[" + bookOptionsEnum.UPDATE_BOOK.getDescription() + "] Enter the new pages of the book: ");
                            bookToUpdate.setPages((int) value);
                            System.out.println("[" + bookOptionsEnum.UPDATE_BOOK.getDescription() + "] Pages set to " + value);
                        }
                        case "year" -> {
                            value = askInt(reader, "[" + bookOptionsEnum.UPDATE_BOOK.getDescription() + "] Enter the new year of the book: ");
                            bookToUpdate.setYear((int) value);
                            System.out.println("[" + bookOptionsEnum.UPDATE_BOOK.getDescription() + "] Year set to " + value);
                        }
                        case "author" -> {
                            // Ask the user for a new author
                            Author newAuthor = AuthorManager.getAuthor(reader);
                            // Set the new author
                            bookToUpdate.setAuthor(newAuthor);
                            System.out.println("[" + bookOptionsEnum.UPDATE_BOOK.getDescription() + "] Author set to " + bookToUpdate.getAuthor().getFirstName() + " " + bookToUpdate.getAuthor().getLastName());
                        }
                        case "available" -> {
                            String newAvailable = askString(reader, "[" + bookOptionsEnum.UPDATE_BOOK.getDescription() + "] Enter new availability of the book (true or false)");
                            // Check that the input is a boolean
                            while (!Boolean.getBoolean(newAvailable)) {
                                newAvailable = askString(reader, "[" + bookOptionsEnum.UPDATE_BOOK.getDescription() + "] Invalid value. Enter new availability of the book (true or false)");
                            }
                            // Set the new boolean value
                            bookToUpdate.setAvailable(Boolean.getBoolean(newAvailable));
                        }
                        default -> System.out.println("[Manage books] " + parameter + " is a read-only parameter, choose another one");
                    }
                    parameter = askString(reader, "[" + bookOptionsEnum.UPDATE_BOOK.getDescription() + "] Insert the parameter to modify ('Quit' to exit): ");
                }
            }
        };

        abstract void action(Scanner reader);
        private final String description;

        bookOptionsEnum(String description) {
            this.description = description;
        }
        private String getDescription() {
            return this.description;
        }

        static void printOptions() {
            // Print all the available Borrow options
            System.out.println("[Manage books] Available options:");
            bookOptionsEnum.stream()
                    .map(bookOptionsEnum::getDescription)
                    .forEach(System.out::println);
        }

        static bookOptionsEnum getOption(String action) {
            for (bookOptionsEnum option : bookOptionsEnum.values()) {
                if (option.getDescription().equals(action)) {
                    return option;
                }
            }
            return null;
        }

        private static void executeOption(Scanner reader, String action) {
            // compare the action with the available enum and see if it is a valid option
            bookOptionsEnum option = getOption(action);
            // execute the desired option
            if (option != null) {
                option.action(reader);
            }
            else {
                System.out.println("[Manage books] Invalid option, try again");
            }
        }
        public static Stream<bookOptionsEnum> stream() {
            return Stream.of(bookOptionsEnum.values());
        }
    }

    public static void initializeRandomBooks() {
        if (books.isEmpty()) {
            Random randomNum = new Random();
            createFakeBooks(randomNum.nextInt(1, 10));
        }
    }

    public static void start(Scanner reader) {
        // Print the available options
        bookOptionsEnum.printOptions();
        String description = askString(reader, "[Manage books] - Select option: ");
        while (!description.equals("Quit")) {
            bookOptionsEnum.executeOption(reader, description);
            System.out.println();
            bookOptionsEnum.printOptions();
            description = askString(reader, "[Manage books] - Select option: ");
        }
    }

    // Private methods

    private static void createFakeBooks(int number) {
        Faker faker = new Faker();
        // Initialize Random authors if the list is empty
        AuthorManager.initializeRandomActors();
        Book newBook;
        // Create as many new Books as requested
        for (int i = 0; i < number; i++) {
            com.github.javafaker.Book fakeBook = faker.book();
            newBook = new Book();
            newBook.setTitle(fakeBook.title());
            newBook.setPages(faker.number().numberBetween(10, 2000));
            newBook.setYear(faker.number().numberBetween(0, 2023));
            newBook.setISBN(faker.code().isbn13());
            // Get a fake author
            // Mandatory initialization of AuthorManager prior to this
            newBook.setAuthor(AuthorManager.getRandomAuthor());
            // Set a random availability status
            newBook.setAvailable(faker.bool().bool());
            // Put the fake book to the storage
            books.put(newBook.getBookID(), newBook);
        }
    }

    // Public methods
    public static String getAvailableBookID(Scanner reader) {
        //bookOptionsEnum.LIST_BOOKS.action(reader);
        // List only the available books
        System.out.println("[Book Manager] Available books:");
        for (Map.Entry<String, Book> entry : books.entrySet()) {
            Book book = entry.getValue();
            if (book.isAvailable()) {
                System.out.println(book.getBookID() + " - " + book.getTitle());
            }
        }
        String bookID = askString(reader, "[Manage books] Enter book ID: ");
        while (!books.containsKey(bookID)) {
            bookID = askString(reader, "[Manage books] Invalid book ID. Enter book ID: ");
        }
        return bookID;
    }

    // TODO: Analyse if this method is correct compared to the enum approach
    public static String getBookTitle(String bookID) {
        return books.getOrDefault(bookID, null).getTitle();
    }

    public static void setBookAvailability(String bookID, boolean newAvailability) {
        books.get(bookID).setAvailable(newAvailability);
    }

}

AuthorManager

BookManager.java
package org.labse03part1.logic;

import com.github.javafaker.Faker;
import org.labse03part1.domain.Author;
import org.labse03part1.utils.InterfaceUtils;

import java.util.*;
import java.util.stream.Stream;

import static org.labse03part1.utils.InterfaceUtils.*;
import static org.labse03part1.utils.InterfaceUtils.askString;

public class AuthorManager {
    private static final Map<String, Author> authors = new HashMap<>();

    private enum authorOptionsEnum {
        ADD_AUTHOR("Add author") {
            @Override
            void action(Scanner reader) {
                addAuthor(reader);
            }
        },
        DELETE_AUTHOR("Delete author") {
            @Override
            void action(Scanner reader) {
                deleteAuthor(reader);
            }
        },
        CHECK_AUTHOR("Check author") {
            @Override
            void action(Scanner reader) {
                checkAuthor(reader);
            }
        },
        LIST_AUTHORS("List authors") {
            @Override
            void action(Scanner reader) {
                listAuthors();
            }
        },
        UPDATE_AUTHOR("Update author") {
            @Override
            void action(Scanner reader) {
                updateAuthor(reader);
            }
        };

        abstract void action(Scanner reader);
        private final String description;
        authorOptionsEnum(String description) {
            this.description = description;
        }

        private String getDescription() {
            return this.description;
        }

        public static Stream<authorOptionsEnum> stream() {
            return Stream.of(authorOptionsEnum.values());
        }
    }

    // Entrypoint of the manager
    public static void start(Scanner reader) {
        printOptions();
        String action = askString(reader, "[Manage authors] Select option ('Quit' to exit): ");
        while (!action.equals("Quit")) {
            executeOption(reader, action);
            System.out.println();
            printOptions();
            action = InterfaceUtils.askString(reader, "[Manage authors] Select option ('Quit' to exit): ");
        }
    }

    // Required public methods for external managers
    public static Author getAuthor(Scanner reader) {
        listAuthors();
        String authorFullName = askString(reader, "Enter author's full name: ");
        while(!authors.containsKey(authorFullName)) {
            System.out.println("Invalid author name!");
            authorFullName = askString(reader, "Enter author's full name: ");
        }

        return authors.get(authorFullName);
    }

    public static void initializeRandomActors() {
        if (authors.isEmpty()) {
            Random randomNum = new Random();
            createFakeAuthors(randomNum.nextInt(0, 10));
        }
    }

    public static Author getRandomAuthor() {
        Random randomNum = new Random();
        List<String> authorNames = authors.keySet().stream().toList();
        String randomAuthor = authorNames.get(randomNum.nextInt(0, authorNames.size()));
        return authors.get(randomAuthor);
    }

    public static void clear(Scanner reader) {
        String response = askString(reader, "Are you sure you want to clear all authors? This action can't be undone [YES/NO]");
        if (response.equals("YES")) {
            clear();
            System.out.println("All authors removed");
        } else {
            System.out.println("Aborting clear");
        }
    }

    // Private methods
    private static authorOptionsEnum getOption(String action) {
        for (authorOptionsEnum option : authorOptionsEnum.values()) {
            if (option.getDescription().equals(action)) {
                return option;
            }
        }
        return null;
    }

    private static void executeOption(Scanner reader, String action) {
        authorOptionsEnum option = getOption(action);
        if (option != null) {
            option.action(reader);
        }
        else {
            System.out.println("[Manage authors] Invalid option! Try again.");
        }
    }

    private static void printOptions() {
        // Print all the available Borrow options
        System.out.println("[Manage authors] Available options:");
        authorOptionsEnum.stream()
                .map(authorOptionsEnum::getDescription)
                .forEach(System.out::println);
    }

    private static void createFakeAuthors(int number) {
        Faker faker = new Faker();
        Author newAuthor;
        // Create as many new Authors as requested
        for (int i = 0; i < number; i++) {
            newAuthor = new Author();
            newAuthor.setFirstName(faker.name().firstName());
            newAuthor.setLastName(faker.name().lastName());
            newAuthor.setAge(faker.number().numberBetween(0, 100));
            newAuthor.setGenre(faker.book().genre());
            String authorFullName = newAuthor.getFirstName() + " " + newAuthor.getLastName();
            // Store the new fake author
            authors.put(authorFullName, newAuthor);
        }
    }

    private static void addAuthor(Scanner reader) {
        while (true) {
            // Ask for the author's details
            String authorFirstName = askString(reader, "[" + authorOptionsEnum.ADD_AUTHOR.getDescription() + "] Enter author's first name ('Quit' to exit): ");
            if (authorFirstName.equals("Quit")) {
                break;
            }
            String authorLastName = askString(reader, "[" + authorOptionsEnum.ADD_AUTHOR.getDescription() + "] Enter author's last name: ");
            String authorFullName = authorFirstName + " " + authorLastName;
            // Check if the author already exists in the system
            if (!authors.containsKey(authorFullName)) {
                int authorAge = askInt(reader, "[" + authorOptionsEnum.ADD_AUTHOR.getDescription() + "] Enter author's age: ");
                String authorGenre = askString(reader, "[" + authorOptionsEnum.ADD_AUTHOR.getDescription() + "] Enter author's main genre: ");
                authors.put(authorFirstName + " " + authorLastName, new Author(authorFirstName, authorLastName, authorAge, authorGenre));
                System.out.println("[" + authorOptionsEnum.ADD_AUTHOR.getDescription() + "] Author " + authorFullName + " added!");
                break;
            }
            System.out.println("[" + authorOptionsEnum.ADD_AUTHOR.getDescription() + "] Author" + authorFullName + " already exists! Enter a new one");
        }
    }
    private static void checkAuthor(Scanner reader) {
        listAuthors();
        String authorFullName = askString(reader, "[" + authorOptionsEnum.CHECK_AUTHOR.getDescription() + "] Enter author's full name: ");
        while(!authors.containsKey(authorFullName)) {
            System.out.println("[" + authorOptionsEnum.CHECK_AUTHOR.getDescription() + "] Invalid author name!");
            authorFullName = askString(reader, "[" + authorOptionsEnum.CHECK_AUTHOR.getDescription() + "] Enter author's full name: ");
        }

        System.out.println(authors.get(authorFullName));
    }

    private static void listAuthors() {
        System.out.println("[" + authorOptionsEnum.LIST_AUTHORS.getDescription() + "] Available authors:");
        authors.keySet()
                .forEach(System.out::println);
    }

    private static void updateAuthor(Scanner reader) {
        // Ask for the author name
        String authorFullName = getAuthorFullName(reader);
        while (!authors.containsKey(authorFullName)) {
            if (authorFullName.equals("Quit")) {
                System.out.println("[" + authorOptionsEnum.UPDATE_AUTHOR.getDescription() + "] Update author cancelled");
                break;
            }
            System.out.println("[" + authorOptionsEnum.UPDATE_AUTHOR.getDescription() + "] Author " + authorFullName + " doesn't exist in the system. Try again");
            authorFullName = getAuthorFullName(reader);

        }

        // Retrieve the Author object to update
        Author authorToUpdate = authors.get(authorFullName);
        System.out.println(authorToUpdate);

        // Once book is found, ask for the parameter to change
        String parameter = askString(reader, "[" + authorOptionsEnum.UPDATE_AUTHOR.getDescription() + "] Enter the parameter to modify ('Quit' to exit): ");
        Object value;

        while(!parameter.equals("Quit")) {
            switch (parameter) {
                case "firstName" -> {
                    value = askString(reader, "[" + authorOptionsEnum.UPDATE_AUTHOR.getDescription() + "] Enter the author's new first name: ");
                    authorToUpdate.setFirstName(value.toString());
                    System.out.println("[" + authorOptionsEnum.UPDATE_AUTHOR.getDescription() + "] First name set to " + value);
                }
                case "lastName" -> {
                    value = askString(reader, "[" + authorOptionsEnum.UPDATE_AUTHOR.getDescription() + "] Enter the author's new last name: ");
                    authorToUpdate.setLastName(value.toString());
                    System.out.println("[" + authorOptionsEnum.UPDATE_AUTHOR.getDescription() + "] Last name set to " + value);
                }
                case "age" -> {
                    value = askInt(reader, "[" + authorOptionsEnum.UPDATE_AUTHOR.getDescription() + "] Enter the author's new age: ");
                    authorToUpdate.setAge((int) value);
                    System.out.println("[" + authorOptionsEnum.UPDATE_AUTHOR.getDescription() + "] Age set to " + value);
                }
                case "genre" -> {
                    value = askString(reader, "[" + authorOptionsEnum.UPDATE_AUTHOR.getDescription() + "] Enter the author's new main genre: ");
                    authorToUpdate.setGenre(value.toString());
                    System.out.println("[" + authorOptionsEnum.UPDATE_AUTHOR.getDescription() + "] Genre set to " + value);
                }
                default -> System.out.println("[Manage authors] " + parameter + " is a read-only parameter, choose another one");
            }
            // Ask for another parameter to update
            parameter = askString(reader, "[Manage authors] Insert the parameter to modify ('Quit' to exit): ");
        }
    }

    private static String getAuthorFullName(Scanner reader) {
        String authorFirstName = askString(reader, "Enter author's first name ('Quit' to exit): ");
        if (authorFirstName.equals("Quit")) {
            return "Quit";
        }
        String authorLastName = askString(reader, "Enter author's last name: ");
        return authorFirstName + " " + authorLastName;
    }

    private static void deleteAuthor(Scanner reader) {
        String authorFullName = getAuthorFullName(reader);
        while(!authors.containsKey(authorFullName)) {
            if (authorFullName.equals("Quit")) {
                System.out.println("[" + authorOptionsEnum.DELETE_AUTHOR.getDescription() + "] Delete author cancelled");
                break;
            }
            System.out.println("[" + authorOptionsEnum.DELETE_AUTHOR.getDescription() + "] Author " + authorFullName + " doesn't exist in the system!");
            authorFullName = getAuthorFullName(reader);
        }
        // Remove the author from the database
        authors.remove(authorFullName);
        System.out.println("[" + authorOptionsEnum.DELETE_AUTHOR.getDescription() + "] Author " + authorFullName + " deleted!");
    }

    protected static void clear() {
        authors.clear();
    }
}

StudentManager

BookManager.java
package org.labse03part1.logic;

import com.github.javafaker.Faker;
import org.labse03part1.domain.Book;
import org.labse03part1.domain.Student;

import java.util.*;
import java.util.stream.Stream;

import static org.labse03part1.utils.InterfaceUtils.askInt;
import static org.labse03part1.utils.InterfaceUtils.askString;

public class StudentManager {
    private static final Map<String, Student> students = new HashMap<>();

    public static void initializeRandomStudents() {
        if (students.isEmpty()) {
            Random randomNum = new Random();
            createFakeStudents(randomNum.nextInt(1, 10));
        }
    }

    private static void createFakeStudents(int number) {
        Faker faker = new Faker();
        Student newStudent;
        // Create as many new Students as requested
        for (int i = 0; i < number; i++) {
            newStudent = new Student();
            newStudent.setFirstName(faker.name().firstName());
            newStudent.setLastName(faker.name().lastName());
            newStudent.setAge(faker.number().numberBetween(10, 99));
            newStudent.setUniversity(faker.university().name());
            newStudent.setBooks(new ArrayList<>());

            // Put the fake student into the storage
            students.put(newStudent.getStudentID(), newStudent);
        }
    }

    private enum studentOptionsEnum {
        ADD_STUDENT("Add student") {
            @Override
            void action(Scanner reader) {
                addStudent(reader);
            }

            private void addStudent(Scanner reader) {
                while (true) {
                    // Ask for the student's details
                    String studentFirstName = askString(reader, "[" + studentOptionsEnum.ADD_STUDENT.getDescription() + "] Enter student's first name ('Quit' to exit): ");
                    if (studentFirstName.equals("Quit")) {
                        break;
                    }
                    // Ask for the rest of the student's information
                    String studentLastName = askString(reader, "[" + studentOptionsEnum.ADD_STUDENT.getDescription() + "] Enter student's last name ('Quit' to exit): ");
                    String studentFullName = studentFirstName + " " + studentLastName;
                    int studentAge = askInt(reader, "[" + studentOptionsEnum.ADD_STUDENT.getDescription() + "] Enter student's age ('Quit' to exit): ");
                    String studentUniversity = askString(reader, "[" + studentOptionsEnum.ADD_STUDENT.getDescription() + "] Enter student's university ('Quit' to exit): ");
                    List<Book> studentBooks = new ArrayList<>();
                    // Create the new student object
                    Student newStudent = new Student(studentFirstName, studentLastName, studentAge, studentUniversity, studentBooks);
                    // Check if the student already exists inside the students hashmap
                    if (!students.containsValue(newStudent)) {
                        // Insert the student into the storage - Key: studentID
                        students.put(newStudent.getStudentID(), newStudent);
                        System.out.println("[" + studentOptionsEnum.ADD_STUDENT.getDescription() + "] Student " + studentFullName + " added!");
                        break;
                    }
                    System.out.println("[" + studentOptionsEnum.ADD_STUDENT.getDescription() + "] Student " + studentFullName + " already exists! Enter a new one");
                }
            }
        },
        DELETE_STUDENT("Delete student") {
            @Override
            void action(Scanner reader) {
                deleteStudent(reader);
            }

            private void deleteStudent(Scanner reader) {
                studentOptionsEnum.LIST_STUDENTS.action(reader);
                // Ask for the student ID
                String studentID = askString(reader, "Enter the student ID: ");
                while (!students.containsKey(studentID)) {
                    if (studentID.equals("Quit")) {
                        System.out.println("Delete student cancelled");
                        return;
                    }
                    studentID = askString(reader, "Incorrect ID. Enter the student ID: ");
                }
                Student deletedStudent = students.remove(studentID);
                System.out.println("[" + studentOptionsEnum.DELETE_STUDENT.getDescription() + "] Student " + deletedStudent.getFirstName() + " " + deletedStudent.getLastName() + " deleted!");
            }

        },
        CHECK_STUDENT("Check student") {
            @Override
            void action(Scanner reader) {
                checkStudent(reader);
            }

            private void checkStudent(Scanner reader) {
                studentOptionsEnum.LIST_STUDENTS.action(reader);
                // Ask for the student ID
                String studentID = askString(reader, "Enter the student ID: ");

                System.out.println(students.get(studentID));
            }
        },
        LIST_STUDENTS("List students") {
            @Override
            void action(Scanner reader) {
                listStudents(reader);
            }

            private void listStudents(Scanner reader) {
                System.out.println("[" + studentOptionsEnum.CHECK_STUDENT.getDescription() + "] Available students:");
                students.forEach((studentID, student) -> System.out.println(studentID + ": " + student.getFirstName() + " " + student.getLastName()));
            }
        },
        UPDATE_STUDENT("Update student") {
            @Override
            void action(Scanner reader) {
                updateStudent(reader);
            }

            private void updateStudent(Scanner reader) {
                // TODO: Ask for the student ID instead of the full name
                // Ask for the student name
                studentOptionsEnum.LIST_STUDENTS.action(reader);
                //String studentFullName = getStudentFullName(reader);
                String studentID = askString(reader, "Enter the student ID: ");

                // Retrieve the student to update
                Student studentToUpdate = students.get(studentID);
                System.out.println(studentToUpdate);

                // Once the student is found, ask for the parameter to change
                String parameter = askString(reader, "[" + studentOptionsEnum.UPDATE_STUDENT.getDescription() + "] Enter the parameter to modify ('Quit' to exit): ");

                // Define an Object variable to store the different types of answers
                Object value;

                while (!parameter.equals("Quit")) {
                    switch (parameter) {
                        case "firstName" -> {
                            value = askString(reader, "[" + studentOptionsEnum.UPDATE_STUDENT.getDescription() + "] Enter the student's new first name: ");
                            studentToUpdate.setFirstName(value.toString());
                            System.out.println("[" + studentOptionsEnum.UPDATE_STUDENT.getDescription() + "] First name set to " + value);
                        }
                        case "lastName" -> {
                            value = askString(reader, "[" + studentOptionsEnum.UPDATE_STUDENT.getDescription() + "] Enter the student's new last name: ");
                            studentToUpdate.setLastName(value.toString());
                            System.out.println("[" + studentOptionsEnum.UPDATE_STUDENT.getDescription() + "] Last name set to " + value);
                        }
                        case "age" -> {
                            value = askInt(reader, "[" + studentOptionsEnum.UPDATE_STUDENT.getDescription() + "] Enter the student's new age: ");
                            studentToUpdate.setAge((int) value);
                            System.out.println("[" + studentOptionsEnum.UPDATE_STUDENT.getDescription() + "] Age set to " + value);
                        }
                        case "university" -> {
                            value = askString(reader, "[" + studentOptionsEnum.UPDATE_STUDENT.getDescription() + "] Enter the student's new university: ");
                            studentToUpdate.setUniversity(value.toString());
                            System.out.println("[" + studentOptionsEnum.UPDATE_STUDENT.getDescription() + "] University set to " + value);
                        }
                        case "books" -> {
                            // TODO: Call a book manager for the student's list of books. Maybe a public static method in BookManager that accepts an ArrayList of Books?
                            System.out.println("Books Management coming soon!");
                            //value = askString(reader, "[" + studentOptionsEnum.UPDATE_STUDENT.getDescription() + "] Enter the student's new car: ");
                            // Some BookManager logic
                            //System.out.println("[" + studentOptionsEnum.UPDATE_STUDENT.getDescription() + "] Car set to " + value);
                        }
                        default -> System.out.println("[Manage students] " + parameter + " is a read-only parameter, choose another one");
                    }
                }
            }
        };
        
        abstract void action(Scanner reader);

        private final String description;

        studentOptionsEnum(String description) {
            this.description = description;
        }

        private String getDescription() {
            return this.description;
        }

        private static void printOptions() {
            // Print all the available Student options
            System.out.println("[Manage students] Available options:");
            studentOptionsEnum.stream()
                    .map(studentOptionsEnum::getDescription)
                    .forEach(System.out::println);
        }

        private static studentOptionsEnum getOption(String action) {
            for (studentOptionsEnum option : studentOptionsEnum.values()) {
                if (option.getDescription().equals(action)) {
                    return option;
                }
            }
            return null;
        }

        private static void executeOption(Scanner reader, String description) {
            studentOptionsEnum option = getOption(description);
            if (option != null) {
                option.action(reader);
            }
            else {
                System.out.println("[Manage students] Invalid option! Try again.");
            }
        }

        private static Stream<studentOptionsEnum> stream() {
            return Stream.of(studentOptionsEnum.values());
        }
    }

    // Entrypoint of Student Manager
    public static void start(Scanner reader) {
        // Print available options
        studentOptionsEnum.printOptions();
        String description = askString(reader, "[Manage students] Select option ('Quit' to exit): ");
        while (!description.equals("Quit")) {
            studentOptionsEnum.executeOption(reader, description);
            System.out.println();
            studentOptionsEnum.printOptions();
            description = askString(reader, "[Manage students] Select option ('Quit' to exit): ");
        }
    }

    // StudentManager utilities
    public static String getStudentID(Scanner reader) {
        studentOptionsEnum.LIST_STUDENTS.action(reader);
        String studentID = askString(reader, "- Enter student ID: ");
        while (!students.containsKey(studentID)) {;
            studentID = askString(reader, "- Invalid student ID! Enter student ID: ");
        }
        return studentID;
    }


}

BorrowManager

BookManager.java
package org.labse03part1.logic;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.labse03part1.domain.Borrow;
import org.labse03part1.utils.InterfaceUtils;

import java.util.*;
import java.util.stream.Stream;

import static org.labse03part1.utils.InterfaceUtils.askString;

@Data
@Getter
@Setter
public class BorrowManager {

    private static Map<String, Borrow> borrows = new HashMap<>();

    private enum borrowOptionsEnum {
        CREATE_BORROW("Create borrow") {
            @Override
            void action(Scanner reader) {
                createBorrow(reader);
            }

            void createBorrow(Scanner reader) {
                System.out.println("[" + borrowOptionsEnum.CREATE_BORROW.getDescription() + "] Creating new borrow...");
                // Ask for the Student name through the StudentManager?
                String studentID = StudentManager.getStudentID(reader);
                // Ask for the Book name through the BookManager
                // - Only show the available books
                String bookID = BookManager.getAvailableBookID(reader);
                // Create a borrow
                Borrow newBorrow = new Borrow(studentID, bookID);
                // Store the borrow
                borrows.put(newBorrow.getBorrowID(), newBorrow);
                System.out.println("[" + borrowOptionsEnum.CREATE_BORROW.getDescription() + "] New borrow created:");
                System.out.println("- Borrow ID: " + newBorrow.getBorrowID());
                System.out.println("- Borrow details: " + newBorrow);
                // Set the book availability to false through the BookManager
                BookManager.setBookAvailability(bookID, false);

            }
        },
        DELETE_BORROW("Delete borrow") {
            @Override
            void action(Scanner reader) {
                deleteBorrow(reader);
            }

            void deleteBorrow(Scanner reader) {
                borrowOptionsEnum.LIST_BORROWS.action(reader);
                // Option 1.- Ask for the borrow ID
                String borrowID = InterfaceUtils.askString(reader, "[" + borrowOptionsEnum.DELETE_BORROW.getDescription() + "] Enter borrow ID ('Quit' to exit): ");
                while (!borrows.containsKey(borrowID)) {
                    if (borrowID.equals("Quit")) {
                        System.out.println("[" + borrowOptionsEnum.DELETE_BORROW.getDescription() + "] Delete borrow cancelled.");
                        return;
                    }
                    borrowID = InterfaceUtils.askString(reader, "[" + borrowOptionsEnum.DELETE_BORROW.getDescription() + "] Unknown ID. Enter borrow ID ('Quit' to exit): ");
                }

                Borrow deletedBorrow = borrows.remove(borrowID);
                // TODO: Set the book availability to true
                BookManager.setBookAvailability(deletedBorrow.getBookID(), false);
                System.out.println("[" + borrowOptionsEnum.DELETE_BORROW.getDescription() + "] Borrow " + deletedBorrow.getBorrowID() + " deleted!");
            }
        },
        CHECK_BORROW("Check borrow") {
            @Override
            void action(Scanner reader) {
                checkBorrow(reader);
            }

            void checkBorrow(Scanner reader) {
                borrowOptionsEnum.LIST_BORROWS.action(reader);
                // Option 1.- Ask for the borrow ID
                String borrowID = InterfaceUtils.askString(reader, "[" + borrowOptionsEnum.CHECK_BORROW.getDescription() + "] Enter borrow ID ('Quit' to exit): ");
                while (!borrows.containsKey(borrowID)) {
                    if (borrowID.equals("Quit")) {
                        System.out.println("[" + borrowOptionsEnum.CHECK_BORROW.getDescription() + "] Check borrow cancelled.");
                        return;
                    }
                    borrowID = InterfaceUtils.askString(reader, "[" + borrowOptionsEnum.CHECK_BORROW.getDescription() + "] Unknown ID. Enter borrow ID ('Quit' to exit): ");
                }
                // Print the borrow information
                System.out.println(borrows.get(borrowID));

            }
        },
        LIST_BORROWS("List borrows") {
            @Override
            void action(Scanner reader) {
                listBorrows();
            }

            void listBorrows() {
                System.out.println("[" + borrowOptionsEnum.LIST_BORROWS.getDescription() + "] Available 'In progress' borrows:");
                // Print all the borrows in a defined format
                borrows.entrySet().stream()
                        .filter(entry -> entry.getValue().getStatusDescription().equals("In progress"))
                        .map(x -> x.getKey() + ": " + x.getValue())
                        .forEach(x -> System.out.println(x));
            }
        },
        UPDATE_BORROW("Update borrow") {
            @Override
            void action(Scanner reader) {
                updateBorrow(reader);
            }

            void updateBorrow(Scanner reader) {

                // Ask for the borrow id
                borrowOptionsEnum.LIST_BORROWS.action(reader);
                // Option 1. Ask for the borrow ID
                /*
                String borrowID = InterfaceUtils.askString(reader, "[" + borrowOptionsEnum.CHECK_BORROW.getDescription() + "] Enter borrow ID ('Quit' to exit): ");
                while (!borrows.containsKey(borrowID)) {
                    if (borrowID.equals("Quit")) {
                        System.out.println("[" + borrowOptionsEnum.CHECK_BORROW.getDescription() + "] Check borrow cancelled.");
                        return;
                    }
                    borrowID = InterfaceUtils.askString(reader, "[" + borrowOptionsEnum.CHECK_BORROW.getDescription() + "] Unknown ID. Enter borrow ID ('Quit' to exit): ");
                }
                Borrow borrowToUpdate = borrows.get(borrowID);
                */
                // Option 2. Invoke a borrow finding method
                Borrow borrowToUpdate = findBorrow(reader);
                if (borrowToUpdate == null) {
                    System.out.println("Error finding the borrow. Exiting...");
                }

                System.out.println(borrowToUpdate);
                // Ask what field to change
                // - Allowed fields to update: status, due date, return date, etc...
                System.out.println("[" + borrowOptionsEnum.UPDATE_BORROW.getDescription() + "] Coming soon!");
                String parameter = askString(reader, "[" + borrowOptionsEnum.UPDATE_BORROW.getDescription() + "] Enter the parameter to modify ('Quit' to exit): ");
                Object value;
                while (!parameter.equals("Quit")) {
                    switch(parameter) {
                        case "status" -> {
                            value = askString(reader, "[" + borrowOptionsEnum.UPDATE_BORROW.getDescription() + "] Change the status to 'In Progress', 'Late' or 'Closed': ");
                            borrowToUpdate.setStatus(String.valueOf(value));
                            // TODO: If status is set to "Closed", then book availability must be set to true
                            if (borrowToUpdate.getStatusDescription().equals("Closed")) {
                                BookManager.setBookAvailability(borrowToUpdate.getBookID(), true);
                            }
                        }
                        case "dueBorrowDate" -> {
                            // The due borrow date is updated to a later date
                            value = askString(reader, "[" + borrowOptionsEnum.UPDATE_BORROW.getDescription() + "] Enter the new due date in a YYYY/mm/dd format: ");
                            borrowToUpdate.setDueBorrowDate(String.valueOf(value));
                            //
                        }
                        default -> System.out.println("[" + borrowOptionsEnum.UPDATE_BORROW.getDescription() + "] " + parameter + "is a read-only parameter, choose another one");
                    }
                    parameter = askString(reader, "[" + borrowOptionsEnum.UPDATE_BORROW.getDescription() + "] Insert the parameter to modify ('Quit' to exit): ");
                }
            }
        },
        RETURN_BOOK("Return book") {
            @Override
            void action(Scanner reader) {
                returnBook(reader);
            }

            private void returnBook(Scanner reader) {
                // Find the borrow of the book
                Borrow borrowToUpdate = findBorrow(reader);
                if (borrowToUpdate == null) {
                    System.out.println("Error finding the borrow. Exiting...");
                    return;
                }
                // Set the borrow status to "Close"
                borrowToUpdate.setStatus("Closed");
                // Set the book availability to True
                BookManager.setBookAvailability(borrowToUpdate.getBookID(), true);
                System.out.println("[" + borrowOptionsEnum.RETURN_BOOK.getDescription() + "] Book " + BookManager.getBookTitle(borrowToUpdate.getBookID()));
            }
        };

        abstract void action(Scanner reader);
        private final String description;
        borrowOptionsEnum(String action) {
            this.description = action;
        }

        private String getDescription() {
            return this.description;
        }

        private static void printOptions() {
            // Print all the available Borrow options
            System.out.println("[Manage borrows] Available options:");
            borrowOptionsEnum.stream()
                    .map(borrowOptionsEnum::getDescription)
                    .forEach(System.out::println);
        }

        // Use private methods to manipulate borrows as they have sensitive data
        private static borrowOptionsEnum getOption(String action) {
            for (borrowOptionsEnum option : borrowOptionsEnum.values()) {
                if (option.getDescription().equals(action)) {
                    return option;
                }
            }
            return null;
        }

        private static void executeOption(Scanner reader, String action) {
            // compare the action with the available enum and see if it is a valid option
            borrowOptionsEnum option = getOption(action);
            // execute the desired option
            if (option != null) {
                option.action(reader);
            }
            else {
                System.out.println("[Manage borrows] Unknown option, try again");
            }
        }

        public static Stream<borrowOptionsEnum> stream() {
            return Stream.of(borrowOptionsEnum.values());
        }
    }

    public static void start(Scanner reader) {
        // Print the available options
        borrowOptionsEnum.printOptions();
        String action = InterfaceUtils.askString(reader, "[Manage borrows] Select option ('Quit' to exit): ");
        while (!action.equals("Quit")) {
            borrowOptionsEnum.executeOption(reader, action);
            System.out.println();
            borrowOptionsEnum.printOptions();
            action = InterfaceUtils.askString(reader, "[Manage borrows] Select option ('Quit' to exit): ");
        }
    }

    public static Borrow findBorrow(Scanner reader) {
        String knownObject = askString(reader, "[Borrow Manager] Enter known object name ('Quit' to exit): ");
        while (!knownObject.equals("book") && !knownObject.equals("user") && !knownObject.equals("borrow")) {
            if (knownObject.equals("Quit")) {
                System.out.println("Find Borrow cancelled");
                return null;
            }
            knownObject = askString(reader, "[Borrow Manager] Unknown object. Enter known object name ('Quit' to exit): ");
        }

        String objectID = askString(reader, "[Book Manager] Enter " + knownObject + " ID: ");
        switch(knownObject) {
            case "book" -> {
                return findBorrowByBookID(objectID);
            }
            case "user" -> {
                return findBorrowByUserID(reader, objectID);
            }
            case "borrow" ->  {
                // Just return the borrow if it exists
                return borrows.getOrDefault(objectID, null);
            }
            default -> {
                return null;
            }
        }
    }

    public static Borrow findBorrowByBookID(String bookID) {
        for (Map.Entry<String, Borrow> entry : borrows.entrySet()) {
            Borrow borrow = entry.getValue();
            if (borrow.getBookID().equals(bookID)) {
                return borrow;
            }
        }
        return null;
    }

    public static Borrow findBorrowByUserID(Scanner reader, String userID) {
        // 1. Find all borrows of that user
        List<Borrow> userBorrows = new ArrayList<>();
        for (Map.Entry<String, Borrow> entry : borrows.entrySet()) {
            Borrow borrow = entry.getValue();
            if (borrow.getStudentID().equals(userID)) {
                userBorrows.add(borrow);
            }
        }

        // 2.- Text-only: show all available borrows
        for (Borrow borrow : userBorrows) {
            if (borrow.getStatusDescription().equals("In progress") || borrow.getStatusDescription().equals("Late")) {
                System.out.println("Borrow ID: " + borrow.getBorrowID() + ", book: " + BookManager.getBookTitle(borrow.getBookID()) + ", status: " + borrow.getStatusDescription());
            }
        }

        // 3. Let the user decide what borrow to get
        String selectedBorrow = askString(reader, "[Borrow Manager] Enter the user's borrow ID: ");
        while (!borrows.containsKey(selectedBorrow)) {
            if (selectedBorrow.equals("Quit")) {
                System.out.println("[Borrow Manager] Find borrow cancelled.");
                return null;
            }
            selectedBorrow = askString(reader, "[Borrow Manager] Unknown ID. Enter the user's borrow ID: ");
        }

        // 4. Return the borrow
        return borrows.get(selectedBorrow);
    }
}

Storage classes

BookStorage

UserStorage

BorrowStorage

Object generation classes

Tests

Objects creation

CRUD operations

Integration tests

Borrow a book

Define the required steps to borrow a book:

  • Create a User
  • Create a Book
  • Create a Borrow for that User and Book

Create an Input of Borrow by console

Define the required steps to replicate the Borrow test using an input from console.