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);
}
}