Person.java

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

import lombok.With;
import lombok.NonNull;

import java.util.Comparator;
import java.util.Objects;

/**
 * Immutable value object representing a person used in TP3 collection examples.
 *
 * <p>Instances are created via the static factory {@link #of(String, String, int)} which
 * performs basic validation. The class implements {@link Comparable} to provide a
 * natural ordering (lastName, firstName, age).</p>
 *
 * <p>Pedagogical note on <code>@With</code> and Java records:
 * - Lombok's <code>@With</code> is applied to record components to generate immutable
 *   "withX(...)" methods that are convenient in exercises where you want to
 *   create a slightly modified copy (for example <code>p.withAge(31)</code>).
 * - Java records already provide <code>equals</code>, <code>hashCode</code> and
 *   <code>toString</code>. Lombok is used only to generate helper methods such as
 *   the <code>with</code> methods. Ensure annotation processing is enabled in the IDE
 *   or build so generated methods are visible.
 *
 * Important edge cases to illustrate in class:
 * - Null handling: prefer factories (e.g. <code>of</code>) that validate parameters
 *   instead of silently accepting null fields.
 * - equals / compareTo consistency: ensure that <code>compareTo(a,b)==0</code>
 *   implies <code>a.equals(b)</code> to avoid surprising behavior in sorted
 *   collections (e.g. <code>TreeSet</code> / <code>TreeMap</code>).
 * - Concurrent modification: modifying a collection while iterating can throw
 *   <code>ConcurrentModificationException</code>. Demonstrate safe patterns such as
 *   using an explicit <code>Iterator</code> and <code>iterator.remove()</code>, or
 *   using concurrent collections like <code>CopyOnWriteArrayList</code> when appropriate.
 * </p>
 */
public record Person(@With String firstName, @With String lastName, @With int age) implements Comparable<Person> {
  /**
   * Create a new {@link Person} instance after validating arguments.
   *
   * @param firstName non-null, non-blank first name
   * @param lastName  non-null, non-blank last name
   * @param age       non-negative age
   * @return a new immutable Person
   * @throws IllegalArgumentException if any parameter is invalid
   */
  public static Person of(String firstName, String lastName, int age) {
    Objects.requireNonNull(firstName, "First name cannot be null");
    Objects.requireNonNull(lastName, "Last name cannot be null");
    String fn = firstName.trim();
    String ln = lastName.trim();
    if (fn.isBlank()) throw new IllegalArgumentException("First name cannot be blank");
    if (ln.isBlank()) throw new IllegalArgumentException("Last name cannot be blank");
    if (age < 0) throw new IllegalArgumentException("Age cannot be negative");
    return new Person(fn, ln, age);
  }

  /**
   * Natural ordering: by last name, then first name, then age.
   *
   * @param other the other person to compare with
   * @return negative/zero/positive as this is less/equal/greater than other
   */
  @Override
  public int compareTo(@NonNull Person other) {
    // Comparable contract expects non-null argument; Lombok @NonNull documents and enforces this
    return Comparator
      .comparing(Person::lastName)
      .thenComparing(Person::firstName)
      .thenComparingInt(Person::age)
      .compare(this, other);
  }

  /**
   * Return the human-friendly full name combining first and last name.
   *
   * @return full name ("First Last")
   */
  public String getFullName() {
    return firstName + " " + lastName;
  }

  /**
   * Small demo for students showing the Lombok-generated `with` methods on record components.
   * This main method is intentionally minimal: it creates a Person, uses the generated
   * withAge / withFirstName methods to create modified copies, and prints results so
   * students can see the immutability behavior.
   */
  public static void main(String[] args) {
    Person p = Person.of("Alice", "Smith", 30);
    System.out.println("Original: " + p);

    // Lombok generates withAge(int) and withFirstName(String) because of @With on components
    Person older = p.withAge(31);
    System.out.println("After withAge(31): " + older);

    Person renamed = p.withFirstName("Alicia");
    System.out.println("After withFirstName(\"Alicia\"): " + renamed);

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