← Back to Lecture
Practical Work 4

Library Management System - Book Management

Implement book status management using enums and switch expressions

Duration 3 hours
Difficulty Beginner
Session 4 - JVM and Conditional Statements

Objectives

By the end of this practical work, you will be able to:

  • Create and use enums with values and methods
  • Implement decision logic using if/else and switch statements
  • Use modern switch expressions with arrow syntax
  • Create a console menu system for user interaction
  • Debug code using IntelliJ IDEA debugger

Prerequisites

  • Completed Practical Work 3 (Library System Foundation)
  • Book, Member, and BookCategory classes from PW3
  • Understanding of basic Java syntax

Part 1: Enhanced BookStatus Enum

Step 1.1: Create BookStatus Enum with Values

Replace the simple available boolean with a rich enum:

package com.library.model;

public enum BookStatus {
    AVAILABLE("Available for borrowing", true),  // (#1:Enum constant with values)
    BORROWED("Currently on loan", false),
    RESERVED("Reserved by a member", false),
    DAMAGED("Damaged - under repair", false),
    LOST("Reported lost", false),
    REFERENCE_ONLY("Cannot be borrowed", false);  // (#2:Last constant ends with semicolon)

    private final String description;  // (#3:Enum fields)
    private final boolean canBeBorrowed;

    BookStatus(String description, boolean canBeBorrowed) {  // (#4:Enum constructor)
        this.description = description;
        this.canBeBorrowed = canBeBorrowed;
    }

    public String getDescription() {
        return description;
    }

    public boolean canBeBorrowed() {
        return canBeBorrowed;
    }

    public String getDisplayText() {  // (#5:Custom method)
        return name() + " - " + description;
    }
}

Step 1.2: Update Book Class

Modify the Book class to use BookStatus:

// Replace the 'available' boolean field with:
private BookStatus status;

// Update constructor
public Book(String isbn, String title, String author, int publicationYear, BookCategory category) {
    this.isbn = isbn;
    this.title = title;
    this.author = author;
    this.publicationYear = publicationYear;
    this.category = category;
    this.status = BookStatus.AVAILABLE;  // Default status
}

// Replace isAvailable() with:
public BookStatus getStatus() {
    return status;
}

public void setStatus(BookStatus status) {
    this.status = status;
}

public boolean canBeBorrowed() {
    return status.canBeBorrowed();
}

// Update displayInfo()
public void displayInfo() {
    System.out.println("========== Book Info ==========");
    System.out.println("ISBN: " + isbn);
    System.out.println("Title: " + title);
    System.out.println("Author: " + author);
    System.out.println("Year: " + publicationYear);
    System.out.println("Category: " + category);
    System.out.println("Status: " + status.getDisplayText());
    System.out.println("===============================");
}

Part 2: Switch Expressions

Step 2.1: Status-Based Actions with Switch Expression

Create a method that returns the appropriate action based on book status:

// Add to Book class
public String getAvailableAction() {
    return switch (status) {  // (#1:Switch expression)
        case AVAILABLE -> "Borrow this book";  // (#2:Arrow syntax - no break needed)
        case BORROWED -> "Join waiting list";
        case RESERVED -> "This book is reserved";
        case DAMAGED -> "Book under repair - check back later";
        case LOST -> "Book is lost - consider alternatives";
        case REFERENCE_ONLY -> "Read in library only";
    };  // (#3:Switch expression returns a value, ends with semicolon)
}

Step 2.2: Calculate Late Fee with Switch

Create a utility class for fee calculations:

package com.library.service;

import com.library.model.BookCategory;

public class FeeCalculator {

    // Late fee depends on book category
    public static double calculateLateFee(BookCategory category, int daysLate) {
        if (daysLate <= 0) return 0.0;

        double dailyRate = switch (category) {
            case FICTION, CHILDREN -> 0.25;  // (#1:Multiple cases with comma)
            case NON_FICTION, BIOGRAPHY, HISTORY -> 0.50;
            case SCIENCE, TECHNOLOGY -> 0.75;  // (#2:Higher value items)
        };

        double fee = dailyRate * daysLate;
        return Math.min(fee, 25.0);  // Cap at 25 euros
    }

    // Borrow duration depends on category
    public static int getBorrowDuration(BookCategory category) {
        return switch (category) {
            case FICTION, NON_FICTION, BIOGRAPHY -> 21;  // 3 weeks
            case SCIENCE, TECHNOLOGY -> 14;  // 2 weeks - high demand
            case HISTORY -> 28;  // 4 weeks
            case CHILDREN -> 14;  // 2 weeks
        };
    }
}

Step 2.3: Switch with yield for Complex Logic

Use yield when you need multiple statements:

public String getStatusMessage() {
    return switch (status) {
        case AVAILABLE -> "Ready to borrow!";
        case BORROWED -> {  // (#1:Block for complex logic)
            System.out.println("Checking return date...");
            yield "Currently on loan";  // (#2:yield returns the value)
        }
        case RESERVED -> {
            System.out.println("Notifying reservation holder...");
            yield "Reserved - waiting for pickup";
        }
        default -> status.getDescription();  // (#3:Default case)
    };
}

Part 3: Console Menu System

Step 3.1: Create LibraryConsole Class

Create an interactive menu:

package com.library;

import com.library.model.*;
import com.library.service.FeeCalculator;
import java.util.Scanner;

public class LibraryConsole {
    private Scanner scanner;
    private Book[] books;
    private int bookCount;

    public LibraryConsole() {
        scanner = new Scanner(System.in);
        books = new Book[100];  // Simple array for now
        bookCount = 0;
        initializeSampleBooks();
    }

    private void initializeSampleBooks() {
        books[bookCount++] = new Book("978-0-13-468599-1", "Effective Java",
            "Joshua Bloch", 2018, BookCategory.TECHNOLOGY);
        books[bookCount++] = new Book("978-0-06-112008-4", "To Kill a Mockingbird",
            "Harper Lee", 1960, BookCategory.FICTION);
        books[bookCount++] = new Book("978-0-7432-7356-5", "1776",
            "David McCullough", 2005, BookCategory.HISTORY);
    }

    public void run() {
        boolean running = true;

        while (running) {
            displayMenu();
            int choice = getIntInput("Enter your choice: ");

            switch (choice) {
                case 1 -> listAllBooks();
                case 2 -> searchBook();
                case 3 -> addNewBook();
                case 4 -> changeBookStatus();
                case 5 -> calculateFee();
                case 0 -> {
                    running = false;
                    System.out.println("Goodbye!");
                }
                default -> System.out.println("Invalid choice. Please try again.");
            }
        }
        scanner.close();
    }

    private void displayMenu() {
        System.out.println("\n====== Library Management System ======");
        System.out.println("1. List all books");
        System.out.println("2. Search for a book");
        System.out.println("3. Add a new book");
        System.out.println("4. Change book status");
        System.out.println("5. Calculate late fee");
        System.out.println("0. Exit");
        System.out.println("========================================");
    }

    private int getIntInput(String prompt) {
        System.out.print(prompt);
        while (!scanner.hasNextInt()) {
            System.out.println("Please enter a valid number.");
            scanner.next();
            System.out.print(prompt);
        }
        int value = scanner.nextInt();
        scanner.nextLine();  // Consume newline
        return value;
    }

    private String getStringInput(String prompt) {
        System.out.print(prompt);
        return scanner.nextLine();
    }

    private void listAllBooks() {
        System.out.println("\n--- All Books ---");
        if (bookCount == 0) {
            System.out.println("No books in the library.");
            return;
        }
        for (int i = 0; i < bookCount; i++) {
            System.out.println((i + 1) + ". " + books[i].getTitle() +
                " [" + books[i].getStatus() + "]");
        }
    }

    private void searchBook() {
        String searchTerm = getStringInput("Enter search term (title or author): ");
        System.out.println("\n--- Search Results ---");
        boolean found = false;

        for (int i = 0; i < bookCount; i++) {
            if (books[i].getTitle().toLowerCase().contains(searchTerm.toLowerCase()) ||
                books[i].getAuthor().toLowerCase().contains(searchTerm.toLowerCase())) {
                books[i].displayInfo();
                found = true;
            }
        }

        if (!found) {
            System.out.println("No books found matching: " + searchTerm);
        }
    }

    private void addNewBook() {
        System.out.println("\n--- Add New Book ---");
        String isbn = getStringInput("ISBN: ");
        String title = getStringInput("Title: ");
        String author = getStringInput("Author: ");
        int year = getIntInput("Publication Year: ");

        System.out.println("Categories: ");
        BookCategory[] categories = BookCategory.values();
        for (int i = 0; i < categories.length; i++) {
            System.out.println((i + 1) + ". " + categories[i]);
        }
        int catChoice = getIntInput("Choose category (1-" + categories.length + "): ");

        if (catChoice >= 1 && catChoice <= categories.length) {
            books[bookCount++] = new Book(isbn, title, author, year,
                categories[catChoice - 1]);
            System.out.println("Book added successfully!");
        } else {
            System.out.println("Invalid category choice.");
        }
    }

    private void changeBookStatus() {
        listAllBooks();
        int bookChoice = getIntInput("Select book number: ");

        if (bookChoice < 1 || bookChoice > bookCount) {
            System.out.println("Invalid selection.");
            return;
        }

        Book book = books[bookChoice - 1];
        System.out.println("Current status: " + book.getStatus().getDisplayText());
        System.out.println("\nAvailable statuses:");

        BookStatus[] statuses = BookStatus.values();
        for (int i = 0; i < statuses.length; i++) {
            System.out.println((i + 1) + ". " + statuses[i].getDisplayText());
        }

        int statusChoice = getIntInput("Choose new status: ");
        if (statusChoice >= 1 && statusChoice <= statuses.length) {
            book.setStatus(statuses[statusChoice - 1]);
            System.out.println("Status updated to: " + book.getStatus());
        }
    }

    private void calculateFee() {
        listAllBooks();
        int bookChoice = getIntInput("Select book number: ");

        if (bookChoice < 1 || bookChoice > bookCount) {
            System.out.println("Invalid selection.");
            return;
        }

        Book book = books[bookChoice - 1];
        int daysLate = getIntInput("Days late: ");

        double fee = FeeCalculator.calculateLateFee(book.getCategory(), daysLate);
        System.out.printf("Late fee for '%s': %.2f euros%n", book.getTitle(), fee);
    }
}

Step 3.2: Update Main Class

Update Main.java to run the console:

package com.library;

public class Main {
    public static void main(String[] args) {
        LibraryConsole console = new LibraryConsole();
        console.run();
    }
}

Part 4: Debugging Exercise

Step 4.1: Buggy Code to Debug

The following method has bugs. Use the debugger to find and fix them:

// Add to FeeCalculator class
public static String getMembershipDiscount(int yearsAsMember, double fee) {
    double discount;

    if (yearsAsMember > 5) {
        discount = 0.20;  // 20% discount
    } else if (yearsAsMember > 2) {
        discount = 0.10;  // 10% discount
    } else if (yearsAsMember >= 1) {
        discount = 0.05;  // 5% discount
    }
    // BUG 1: What if yearsAsMember < 1?

    double discountedFee = fee - (fee * discount);
    // BUG 2: discount might not be initialized!

    if (discountedFee < 0) {
        discountedFee = 0;  // This is correct
    }

    return String.format("Original: %.2f, Discount: %.0f%%, Final: %.2f",
        fee, discount * 100, discountedFee);
}

Your Task: Set breakpoints and step through the code to find the bugs. Fix them!

Step 4.2: Debug Instructions

  1. Click in the gutter (left margin) next to line numbers to set breakpoints
  2. Right-click on Main and select Debug 'Main'
  3. Use the debug controls:
    • F8 - Step Over (next line)
    • F7 - Step Into (enter method)
    • Shift+F8 - Step Out (exit method)
    • F9 - Resume (continue to next breakpoint)
  4. Watch the Variables panel to see values
  5. Use Evaluate Expression (Alt+F8) to test fixes

Exercises

Exercise 1: Member Type Enum

Create a MemberType enum with different membership levels:

  • BASIC - 5 books max, no renewal
  • STANDARD - 10 books max, 1 renewal
  • PREMIUM - 20 books max, unlimited renewals
  • STUDENT - 15 books max, 2 renewals, 50% off fees

Exercise 2: Book Availability Check

Implement a method that uses switch expression to return availability message:

public String checkAvailability(BookStatus status, int waitlistSize) {
    // Return appropriate message based on status and waitlist
    // AVAILABLE + waitlist=0 -> "Available now!"
    // AVAILABLE + waitlist>0 -> "Available, but X people waiting"
    // BORROWED -> "Due back in X days" (assume 14 days)
    // etc.
}

Exercise 3: Fix the Bugs

Debug and fix the getMembershipDiscount method. Submit:

  • Screenshot of your debugging session
  • The fixed code
  • Explanation of each bug found

Exercise 4: Enhanced Menu

Add these features to the console menu:

  • Filter books by status (show only available, only borrowed, etc.)
  • Filter books by category
  • Sort books by title or year

Deliverables

  • Source Code: Updated project with enums and menu system
  • Debug Report: Screenshot + explanation of bugs found
  • Console Output: Screenshots showing menu interactions

Bonus Challenges

  • Challenge 1: Add pattern matching in switch (Java 21) to handle different input types
  • Challenge 2: Create a ReservationStatus enum with transitions (PENDING -> CONFIRMED -> FULFILLED)
  • Challenge 3: Implement input validation with custom error messages

Resources