Book.java

package fr.univtln.bruno.samples.java101.tp3;

import lombok.With;

import java.time.LocalDate;
import java.util.Comparator;

/**
 * Immutable value object representing a book used in TP3 sorting and grouping examples.
 *
 * <p>Provides a static factory {@link #of(String, String, String, LocalDate, double)} which validates inputs.
 * Equality and hash code are based on all fields (generated by the Java record implementation).
 * Natural ordering is implemented in a functional style using a Comparator chain and is consistent with equals()/hashCode().</p>
 *
 * <p>Note pédagogique sur <code>@With</code> :
 * - Le code utilise Lombok's <code>@With</code> annotation sur les components du <code>record</code> pour générer
 *   des méthodes immuables de type <code>withX(...)</code> (p. ex. <code>book.withPrice(12.0)</code>), facilitant
 *   la modification immuable d'instances.
 * - Pour que Lombok génère ces méthodes, l'annotation processing doit être activée dans l'IDE (ou lors de la compilation).
 * - Alternative sans Lombok : écrire explicitement une méthode de copie (p. ex. <code>withPrice</code>) qui retourne
 *   une nouvelle instance du <code>record</code> avec le champ modifié.
 * - Note : les records Java fournissent déjà equals/hashCode/toString automatiquement ; Lombok n'est pas nécessaire
 *   pour ces comportements mais propose des utilitaires pratiques comme <code>@With</code>.</p>
 */
public record Book(@With String isbn, @With String title, @With String author, @With LocalDate publishedDate,
                   @With double price) implements Comparable<Book> {
  /**
   * Create a validated Book instance.
   *
   * @param isbn          non-null, non-blank ISBN
   * @param title         non-null, non-blank title
   * @param author        non-null, non-blank author
   * @param publishedDate non-null publication date
   * @param price         non-negative price
   * @return a new Book instance
   */
  public static Book of(String isbn, String title, String author, LocalDate publishedDate, double price) {
    if (isbn == null || isbn.isBlank()) throw new IllegalArgumentException("ISBN cannot be null or blank");
    if (title == null || title.isBlank()) throw new IllegalArgumentException("Title cannot be null or blank");
    if (author == null || author.isBlank()) throw new IllegalArgumentException("Author cannot be null or blank");
    if (publishedDate == null) throw new IllegalArgumentException("Published date cannot be null");
    if (price < 0) throw new IllegalArgumentException("Price cannot be negative");
    return new Book(isbn, title, author, publishedDate, price);
  }

  /**
   * Natural ordering implemented in a functional style: compare by ISBN, then title, then author,
   * then published date, then price. Implemented using a Comparator chain for clarity and
   * conciseness. The ordering is chosen to be consistent with equals()/hashCode() produced by
   * the Java record implementation to avoid surprises when Books are used in sorted collections.
   */
  @Override
  public int compareTo(Book other) {
    if (other == null) return 1;
    return Comparator
      .comparing(Book::isbn, Comparator.nullsFirst(String::compareTo))
      .thenComparing(Book::title, Comparator.nullsFirst(String::compareTo))
      .thenComparing(Book::author, Comparator.nullsFirst(String::compareTo))
      .thenComparing(Book::publishedDate, Comparator.nullsFirst(LocalDate::compareTo))
      .thenComparingDouble(Book::price)
      .compare(this, other);
  }

  /**
   * Return a short description of the book (title, author and price).
   *
   * @return human readable description
   */
  public String getDescription() {
    return String.format("%s by %s (%.2f€)", title, author, price);
  }

  /**
   * Small demo showing the Lombok-generated `with` methods on Book record components.
   * It illustrates creating a Book, then creating modified copies using withPrice and withTitle.
   */
  public static void main(String[] args) {
    Book b = Book.of("978-0134685991", "Effective Java", "Joshua Bloch", LocalDate.of(2018, 1, 6), 45.99);
    System.out.println("Original: " + b);

    Book cheaper = b.withPrice(39.99);
    System.out.println("After withPrice(39.99): " + cheaper);

    Book renamed = b.withTitle("Effective Java - 3rd Edition");
    System.out.println("After withTitle(...): " + renamed);

    // original instance remains unchanged
    System.out.println("Original still: " + b);
  }
}