Person.java
/*
* Copyright (C) 2020-2023 Dipl.-Inform. Kai Hofmann. All rights reserved!
*/
package de.powerstat.validation.entities;
import java.time.OffsetDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import de.powerstat.validation.containers.HistoryOf;
import de.powerstat.validation.interfaces.IEntity;
import de.powerstat.validation.values.BloodGroup;
import de.powerstat.validation.values.Firstname;
import de.powerstat.validation.values.Gender;
import de.powerstat.validation.values.Lastname;
import de.powerstat.validation.values.UUID;
/**
* Person.
*
* DSGVO relevant.
*
* TODO birth place (optional)
* TODO death place (optional)
* TODO history of citizenships
* TODO Identity card number at different times
* TODO eye color
* TODO Skin color
* TODO body height at different times (24cm - 272cm)
* TODO body weight at different times (212g - 635kg)
* TODO body temperature at different times
* TODO characteristics
* TODO Zahnstatus at differet times
* TODO DNA
* TODO faceid, fingerid, eyeid at different times
* TODO Signature(s)
* TODO Namenszusätze: Adelstitel, Academic title since, Work titles/qualifications since
* TODO ... Nicknames at different times
*
* TODO Address(es) at different times
* TODO Communication typ(s) at different times
* TODO photo(s) at different times
* TODO voice recordings at different times
* TODO ? marriage(s) / divorce(s) at different times
* TODO relationship(s), children, parents, siblings, ... at different times
* TODO languages (reading, writing, speeking, understanding, level) since
* TODO ownership at different times
* TODO knowledge since
* TODO get vcard ?
*
* TODO Change DateTime of an entry to an earlier entry
*/
public final class Person implements Comparable<Person>, IEntity
{
/**
* Logger.
*/
private static final Logger LOGGER = LogManager.getLogger(Person.class);
/**
* Universally Unique Identifier.
*/
private final UUID uuid = UUID.of();
/**
* Lastnames at different times.
*/
private final HistoryOf<Lastname> lastname = new HistoryOf<>();
/**
* Gender/sex at different times.
*/
private final HistoryOf<Gender> sex = new HistoryOf<>();
/**
* First names at different times.
*/
private final HistoryOf<List<Firstname>> firstnames = new HistoryOf<>();
/**
* Call name at different times.
*/
private final HistoryOf<Integer> callname = new HistoryOf<>();
/**
* Birthday if known with time or 00:00:00.
*/
private Optional<OffsetDateTime> birthday = Optional.empty();
/**
* Death date if not still alive, with time or 00:00:00.
*/
private Optional<OffsetDateTime> deathdate = Optional.empty();
/**
* Blood group.
*/
private Optional<BloodGroup> bloodGroup = Optional.empty();
/**
* Private default constructor.
*/
private Person()
{
super();
}
/**
* Person factory.
*
* @return Person object
*/
public static Person of()
{
return new Person();
}
/**
* Person factory.
*
* @param lastname Lastname
* @param gender Gender
* @return Person object
*/
public static Person of(final Lastname lastname, final Gender gender)
{
final var person = Person.of();
person.addLastname(OffsetDateTime.now(), lastname);
person.addGender(OffsetDateTime.now(), gender);
return person;
}
/**
* Person factory.
*
* @param lastname Lastname
* @param gender Gender
* @param firstnames Firstnames
* @return Person object
*/
public static Person of(final Lastname lastname, final Gender gender, final List<Firstname> firstnames)
{
final var person = Person.of(lastname, gender);
person.addFirstnames(OffsetDateTime.now(), firstnames);
return person;
}
/**
* Person factory.
*
* @param lastname Lastname
* @param gender Gender
* @param firstnames Firstnames
* @param birthdate Birthdate
* @return Person object
*/
public static Person of(final Lastname lastname, final Gender gender, final List<Firstname> firstnames, final OffsetDateTime birthdate)
{
final var person = Person.of(lastname, gender, firstnames);
person.setBirthday(birthdate);
return person;
}
/**
* Returns the value of this Person as a string.
*
* @return The text value represented by this object after conversion to type string (lastname, firstnames).
*/
@Override
public String stringValue()
{
final var builder = new StringBuilder(75);
builder.append(this.lastname).append(", ").append(this.firstnames); //$NON-NLS-1$
return builder.toString();
}
/**
* Calculate hash code.
*
* @return Hash
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
return Objects.hash(this.lastname, this.sex, this.firstnames, this.birthday, this.deathdate, this.bloodGroup);
}
/**
* Is equal with another object.
*
* @param obj Object
* @return true when equal, false otherwise
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(final Object obj)
{
if (this == obj)
{
return true;
}
if (!(obj instanceof Person))
{
return false;
}
final Person other = (Person)obj;
boolean result = this.lastname.equals(other.lastname);
if (result)
{
result = this.sex.equals(other.sex);
if (result)
{
result = this.firstnames.equals(other.firstnames);
if (result)
{
result = this.birthday.equals(other.birthday);
if (result)
{
result = this.deathdate.equals(other.deathdate);
if (result)
{
result = this.bloodGroup.equals(other.bloodGroup);
}
}
}
}
}
return result;
}
/**
* Returns the string representation of this Person.
*
* The exact details of this representation are unspecified and subject to change, but the following may be regarded as typical:
*
* "Person[person=]"
*
* @return String representation of this Person
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
final var builder = new StringBuilder(75);
builder.append("Person[lastname=").append(this.lastname.getLatestEntry()).append(", gender=").append(this.sex.getLatestEntry());
if (!this.firstnames.isEmpty())
{
builder.append(", firstnames=").append(this.firstnames.getLatestEntry());
}
if (this.birthday.isPresent())
{
builder.append(", birthday=").append(this.birthday);
}
if (this.deathdate.isPresent())
{
builder.append(", deathdate=").append(this.deathdate);
}
if (this.bloodGroup.isPresent())
{
builder.append(", bloodGroup=").append(this.bloodGroup);
}
builder.append(']');
return builder.toString();
}
/**
* Get firstnames as string.
*
* @param firstnames Firstnames
* @return Firstnames as string
*/
private static String getFirstnames(final HistoryOf<List<Firstname>> firstnames)
{
try
{
return firstnames.getLatestEntry().stream().map(Firstname::stringValue).collect(Collectors.joining(" ")); //$NON-NLS-1$
}
catch (final NoSuchElementException e)
{
// LOGGER.debug("NoSuchElementException", e);
return ""; //$NON-NLS-1$
}
}
/**
* Compare with another object.
*
* @param obj Object to compare with
* @return 0: equal; 1: greater; -1: smaller
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@SuppressWarnings("PMD.ConfusingTernary")
@Override
public int compareTo(final Person obj)
{
Objects.requireNonNull(obj, "obj"); //$NON-NLS-1$
int result = (!this.lastname.isEmpty() && !obj.lastname.isEmpty()) ? this.lastname.getLatestEntry().compareTo(obj.lastname.getLatestEntry()) : Boolean.compare(this.lastname.isEmpty(), obj.lastname.isEmpty());
if (result == 0)
{
result = (!this.sex.isEmpty() && !obj.sex.isEmpty()) ? this.sex.getLatestEntry().compareTo(obj.sex.getLatestEntry()) : Boolean.compare(this.sex.isEmpty(), obj.sex.isEmpty());
if (result == 0)
{
final String thisName = Person.getFirstnames(this.firstnames);
final String thatName = Person.getFirstnames(obj.firstnames);
result = thisName.compareTo(thatName);
if (result == 0)
{
if (this.birthday.isPresent() && obj.birthday.isPresent())
{
result = this.birthday.get().compareTo(obj.birthday.get());
}
else if (!this.birthday.isPresent() && !obj.birthday.isPresent())
{
result = 0;
}
else
{
result = this.birthday.isPresent() ? 1 : -1;
}
if (result == 0)
{
if (this.deathdate.isPresent() && obj.deathdate.isPresent())
{
result = this.deathdate.get().compareTo(obj.deathdate.get());
}
else if (!this.deathdate.isPresent() && !obj.deathdate.isPresent())
{
result = 0;
}
else
{
result = this.deathdate.isPresent() ? 1 : -1;
}
if (result == 0)
{
if (this.bloodGroup.isPresent() && obj.bloodGroup.isPresent())
{
result = this.bloodGroup.get().compareTo(obj.bloodGroup.get());
}
else if (!this.bloodGroup.isPresent() && !obj.bloodGroup.isPresent())
{
result = 0;
}
else
{
result = this.bloodGroup.isPresent() ? 1 : -1;
}
}
}
}
}
}
return result;
}
/**
* Get lastname at birth.
*
* @return First known lastname
*/
public Lastname getLastnameAtBirth()
{
return this.lastname.getFirstEntry();
}
/**
* Get actual lastname.
*
* @return Last known lastname
*/
public Lastname getLastnameActual()
{
return this.lastname.getLatestEntry();
}
/**
* Get previous lastname.
*
* @return Previous lastname or actual lastname/lastname at birth
*/
public Lastname getLastnamePrevious()
{
return this.lastname.getPreviousEntry();
}
/**
* Add lastname.
*
* @param since Since datetime
* @param name Lastname
* @throws NullPointerException If since and/or lastname is/are null
* @throws IndexOutOfBoundsException If since lies in the future
* @throws IllegalArgumentException If lastname is larger than 40 characters or includes illegal characters
*/
public void addLastname(final OffsetDateTime since, final Lastname name)
{
this.lastname.addEntry(since, name);
}
/**
* Get gender at birth.
*
* @return First known gender
*/
public Gender getGenderAtBirth()
{
return this.sex.getFirstEntry();
}
/**
* Get actual gender.
*
* @return Last known gender
*/
public Gender getGenderActual()
{
return this.sex.getLatestEntry();
}
/**
* Get previous gender.
*
* @return Previous gender or actual gender/gender at birth
*/
public Gender getGenderPrevious()
{
return this.sex.getPreviousEntry();
}
/**
* Add gender.
*
* @param since Since datetime
* @param gender Gender
* @throws IndexOutOfBoundsException If since lies in the future
*/
public void addGender(final OffsetDateTime since, final Gender gender)
{
this.sex.addEntry(since, gender);
}
/**
* Get firstnames at birth.
*
* @return List og first known firstnames
*/
public List<Firstname> getFirstnamesAtBirth()
{
return new ArrayList<>(this.firstnames.getFirstEntry());
}
/**
* Get actual firstnames.
*
* @return List pf last known firstnames
*/
public List<Firstname> getFirstnamesActual()
{
return new ArrayList<>(this.firstnames.getLatestEntry());
}
/**
* Get previous firstnames.
*
* @return Previous firstnames or actual firstnames/firstnames at birth
*/
public List<Firstname> getFirstnamesPrevious()
{
return new ArrayList<>(this.firstnames.getPreviousEntry());
}
/**
* Add firstnames.
*
* @param since Since datetime
* @param names Firstnames
* @throws NullPointerException If since and/or firstnames is/are null
* @throws IndexOutOfBoundsException If since lies in the future
* @throws IllegalArgumentException If firstnames is larger than 32 characters or includes illegal characters
*/
public void addFirstnames(final OffsetDateTime since, final List<Firstname> names)
{
this.firstnames.addEntry(since, new ArrayList<>(names));
}
/**
* Set birthday.
*
* @param date Birthday date with 00:00:00 or with exact birth time or null.
*/
public void setBirthday(final OffsetDateTime date)
{
this.birthday = Optional.ofNullable(date);
}
/**
* Get birthday.
*
* @return Optional OffsetDateTime
*/
@SuppressWarnings("PMD.NullAssignment")
public Optional<OffsetDateTime> getBirthday()
{
return Optional.ofNullable(this.birthday.isPresent() ? this.birthday.get() : null);
}
/**
* Set death date.
*
* @param date Death date date with 00:00:00 or with exact death time or null.
* @throws IllegalArgumentException If birthday > death date
*/
public void setDeathdate(final OffsetDateTime date)
{
if (this.birthday.isPresent() && this.birthday.get().isAfter(date))
{
throw new IllegalArgumentException("birthday > deathdate"); //$NON-NLS-1$
}
this.deathdate = Optional.ofNullable(date);
}
/**
* Get death date.
*
* @return Optional OffsetDateTime
*/
@SuppressWarnings("PMD.NullAssignment")
public Optional<OffsetDateTime> getDeathdate()
{
return Optional.ofNullable(this.deathdate.isPresent() ? this.deathdate.get() : null);
}
/**
* Set blood group.
*
* @param bloodGroup Blood group
*/
public void setBloodGroup(final BloodGroup bloodGroup)
{
this.bloodGroup = Optional.ofNullable(bloodGroup);
}
/**
* Get blood group.
*
* @return Optional BloodGroup
*/
@SuppressWarnings("PMD.NullAssignment")
public Optional<BloodGroup> getBloodGroup()
{
return Optional.ofNullable(this.bloodGroup.isPresent() ? this.bloodGroup.get() : null);
}
}