View Javadoc
1   /*
2    * Copyright (C) 2020-2023 Dipl.-Inform. Kai Hofmann. All rights reserved!
3    */
4   package de.powerstat.validation.entities;
5   
6   
7   import java.time.OffsetDateTime;
8   import java.util.ArrayList;
9   import java.util.List;
10  import java.util.NoSuchElementException;
11  import java.util.Objects;
12  import java.util.Optional;
13  import java.util.stream.Collectors;
14  
15  import org.apache.logging.log4j.LogManager;
16  import org.apache.logging.log4j.Logger;
17  
18  import de.powerstat.validation.containers.HistoryOf;
19  import de.powerstat.validation.interfaces.IEntity;
20  import de.powerstat.validation.values.BloodGroup;
21  import de.powerstat.validation.values.Firstname;
22  import de.powerstat.validation.values.Gender;
23  import de.powerstat.validation.values.Lastname;
24  import de.powerstat.validation.values.UUID;
25  
26  
27  /**
28   * Person.
29   *
30   * DSGVO relevant.
31   *
32   * TODO birth place (optional)
33   * TODO death place (optional)
34   * TODO history of citizenships
35   * TODO Identity card number at different times
36   * TODO eye color
37   * TODO Skin color
38   * TODO body height at different times (24cm - 272cm)
39   * TODO body weight at different times (212g - 635kg)
40   * TODO body temperature at different times
41   * TODO characteristics
42   * TODO Zahnstatus at differet times
43   * TODO DNA
44   * TODO faceid, fingerid, eyeid at different times
45   * TODO Signature(s)
46   * TODO Namenszusätze: Adelstitel, Academic title since, Work titles/qualifications since
47   * TODO ... Nicknames at different times
48   *
49   * TODO Address(es) at different times
50   * TODO Communication typ(s) at different times
51   * TODO photo(s) at different times
52   * TODO voice recordings at different times
53   * TODO ? marriage(s) / divorce(s) at different times
54   * TODO relationship(s), children, parents, siblings, ... at different times
55   * TODO languages (reading, writing, speeking, understanding, level) since
56   * TODO ownership at different times
57   * TODO knowledge since
58   * TODO get vcard ?
59   *
60   * TODO Change DateTime of an entry to an earlier entry
61   */
62  public final class Person implements Comparable<Person>, IEntity
63   {
64    /**
65     * Logger.
66     */
67    private static final Logger LOGGER = LogManager.getLogger(Person.class);
68  
69    /**
70     * Universally Unique Identifier.
71     */
72    private final UUID uuid = UUID.of();
73  
74    /**
75     * Lastnames at different times.
76     */
77    private final HistoryOf<Lastname> lastname = new HistoryOf<>();
78  
79    /**
80     * Gender/sex at different times.
81     */
82    private final HistoryOf<Gender> sex = new HistoryOf<>();
83  
84    /**
85     * First names at different times.
86     */
87    private final HistoryOf<List<Firstname>> firstnames = new HistoryOf<>();
88  
89    /**
90     * Call name at different times.
91     */
92    private final HistoryOf<Integer> callname = new HistoryOf<>();
93  
94    /**
95     * Birthday if known with time or 00:00:00.
96     */
97    private Optional<OffsetDateTime> birthday = Optional.empty();
98  
99    /**
100    * Death date if not still alive, with time or 00:00:00.
101    */
102   private Optional<OffsetDateTime> deathdate = Optional.empty();
103 
104   /**
105    * Blood group.
106    */
107   private Optional<BloodGroup> bloodGroup = Optional.empty();
108 
109 
110   /**
111    * Private default constructor.
112    */
113   private Person()
114    {
115     super();
116    }
117 
118 
119   /**
120    * Person factory.
121    *
122    * @return Person object
123    */
124   public static Person of()
125    {
126     return new Person();
127    }
128 
129 
130   /**
131    * Person factory.
132    *
133    * @param lastname Lastname
134    * @param gender Gender
135    * @return Person object
136    */
137   public static Person of(final Lastname lastname, final Gender gender)
138    {
139     final var person = Person.of();
140     person.addLastname(OffsetDateTime.now(), lastname);
141     person.addGender(OffsetDateTime.now(), gender);
142     return person;
143    }
144 
145 
146   /**
147    * Person factory.
148    *
149    * @param lastname Lastname
150    * @param gender Gender
151    * @param firstnames Firstnames
152    * @return Person object
153    */
154   public static Person of(final Lastname lastname, final Gender gender, final List<Firstname> firstnames)
155    {
156     final var person = Person.of(lastname, gender);
157     person.addFirstnames(OffsetDateTime.now(), firstnames);
158     return person;
159    }
160 
161 
162   /**
163    * Person factory.
164    *
165    * @param lastname Lastname
166    * @param gender Gender
167    * @param firstnames Firstnames
168    * @param birthdate Birthdate
169    * @return Person object
170    */
171   public static Person of(final Lastname lastname, final Gender gender, final List<Firstname> firstnames, final OffsetDateTime birthdate)
172    {
173     final var person = Person.of(lastname, gender, firstnames);
174     person.setBirthday(birthdate);
175     return person;
176    }
177 
178 
179   /**
180    * Returns the value of this Person as a string.
181    *
182    * @return The text value represented by this object after conversion to type string (lastname, firstnames).
183    */
184   @Override
185   public String stringValue()
186    {
187     final var builder = new StringBuilder(75);
188     builder.append(this.lastname).append(", ").append(this.firstnames); //$NON-NLS-1$
189     return builder.toString();
190    }
191 
192 
193   /**
194    * Calculate hash code.
195    *
196    * @return Hash
197    * @see java.lang.Object#hashCode()
198    */
199   @Override
200   public int hashCode()
201    {
202     return Objects.hash(this.lastname, this.sex, this.firstnames, this.birthday, this.deathdate, this.bloodGroup);
203    }
204 
205 
206   /**
207    * Is equal with another object.
208    *
209    * @param obj Object
210    * @return true when equal, false otherwise
211    * @see java.lang.Object#equals(java.lang.Object)
212    */
213   @Override
214   public boolean equals(final Object obj)
215    {
216     if (this == obj)
217      {
218       return true;
219      }
220     if (!(obj instanceof Person))
221      {
222       return false;
223      }
224     final Person other = (Person)obj;
225     boolean result = this.lastname.equals(other.lastname);
226     if (result)
227      {
228       result = this.sex.equals(other.sex);
229       if (result)
230        {
231         result = this.firstnames.equals(other.firstnames);
232         if (result)
233          {
234           result = this.birthday.equals(other.birthday);
235           if (result)
236            {
237             result = this.deathdate.equals(other.deathdate);
238             if (result)
239              {
240               result = this.bloodGroup.equals(other.bloodGroup);
241              }
242            }
243          }
244        }
245      }
246     return result;
247    }
248 
249 
250   /**
251    * Returns the string representation of this Person.
252    *
253    * The exact details of this representation are unspecified and subject to change, but the following may be regarded as typical:
254    *
255    * "Person[person=]"
256    *
257    * @return String representation of this Person
258    * @see java.lang.Object#toString()
259    */
260   @Override
261   public String toString()
262    {
263     final var builder = new StringBuilder(75);
264     builder.append("Person[lastname=").append(this.lastname.getLatestEntry()).append(", gender=").append(this.sex.getLatestEntry());
265     if (!this.firstnames.isEmpty())
266      {
267       builder.append(", firstnames=").append(this.firstnames.getLatestEntry());
268      }
269     if (this.birthday.isPresent())
270      {
271       builder.append(", birthday=").append(this.birthday);
272      }
273     if (this.deathdate.isPresent())
274      {
275       builder.append(", deathdate=").append(this.deathdate);
276      }
277     if (this.bloodGroup.isPresent())
278      {
279       builder.append(", bloodGroup=").append(this.bloodGroup);
280      }
281     builder.append(']');
282     return builder.toString();
283    }
284 
285 
286   /**
287    * Get firstnames as string.
288    *
289    * @param firstnames Firstnames
290    * @return Firstnames as string
291    */
292   private static String getFirstnames(final HistoryOf<List<Firstname>> firstnames)
293    {
294     try
295      {
296       return firstnames.getLatestEntry().stream().map(Firstname::stringValue).collect(Collectors.joining(" ")); //$NON-NLS-1$
297      }
298     catch (final NoSuchElementException e)
299      {
300       // LOGGER.debug("NoSuchElementException", e);
301       return ""; //$NON-NLS-1$
302      }
303    }
304 
305 
306   /**
307    * Compare with another object.
308    *
309    * @param obj Object to compare with
310    * @return 0: equal; 1: greater; -1: smaller
311    * @see java.lang.Comparable#compareTo(java.lang.Object)
312    */
313   @SuppressWarnings("PMD.ConfusingTernary")
314   @Override
315   public int compareTo(final Person obj)
316    {
317     Objects.requireNonNull(obj, "obj"); //$NON-NLS-1$
318     int result = (!this.lastname.isEmpty() && !obj.lastname.isEmpty()) ? this.lastname.getLatestEntry().compareTo(obj.lastname.getLatestEntry()) : Boolean.compare(this.lastname.isEmpty(), obj.lastname.isEmpty());
319     if (result == 0)
320      {
321       result = (!this.sex.isEmpty() && !obj.sex.isEmpty()) ? this.sex.getLatestEntry().compareTo(obj.sex.getLatestEntry()) : Boolean.compare(this.sex.isEmpty(), obj.sex.isEmpty());
322       if (result == 0)
323        {
324         final String thisName = Person.getFirstnames(this.firstnames);
325         final String thatName = Person.getFirstnames(obj.firstnames);
326         result = thisName.compareTo(thatName);
327         if (result == 0)
328          {
329           if (this.birthday.isPresent() && obj.birthday.isPresent())
330            {
331             result = this.birthday.get().compareTo(obj.birthday.get());
332            }
333           else if (!this.birthday.isPresent() && !obj.birthday.isPresent())
334            {
335             result = 0;
336            }
337           else
338            {
339             result = this.birthday.isPresent() ? 1 : -1;
340            }
341           if (result == 0)
342            {
343             if (this.deathdate.isPresent() && obj.deathdate.isPresent())
344              {
345               result = this.deathdate.get().compareTo(obj.deathdate.get());
346              }
347             else if (!this.deathdate.isPresent() && !obj.deathdate.isPresent())
348              {
349               result = 0;
350              }
351             else
352              {
353               result = this.deathdate.isPresent() ? 1 : -1;
354              }
355             if (result == 0)
356              {
357               if (this.bloodGroup.isPresent() && obj.bloodGroup.isPresent())
358                {
359                 result = this.bloodGroup.get().compareTo(obj.bloodGroup.get());
360                }
361               else if (!this.bloodGroup.isPresent() && !obj.bloodGroup.isPresent())
362                {
363                 result = 0;
364                }
365               else
366                {
367                 result = this.bloodGroup.isPresent() ? 1 : -1;
368                }
369 
370              }
371            }
372          }
373        }
374      }
375     return result;
376    }
377 
378 
379   /**
380    * Get lastname at birth.
381    *
382    * @return First known lastname
383    */
384   public Lastname getLastnameAtBirth()
385    {
386     return this.lastname.getFirstEntry();
387    }
388 
389 
390   /**
391    * Get actual lastname.
392    *
393    * @return Last known lastname
394    */
395   public Lastname getLastnameActual()
396    {
397     return this.lastname.getLatestEntry();
398    }
399 
400 
401   /**
402    * Get previous lastname.
403    *
404    * @return Previous lastname or actual lastname/lastname at birth
405    */
406   public Lastname getLastnamePrevious()
407    {
408     return this.lastname.getPreviousEntry();
409    }
410 
411 
412   /**
413    * Add lastname.
414    *
415    * @param since Since datetime
416    * @param name Lastname
417    * @throws NullPointerException If since and/or lastname is/are null
418    * @throws IndexOutOfBoundsException If since lies in the future
419    * @throws IllegalArgumentException If lastname is larger than 40 characters or includes illegal characters
420    */
421   public void addLastname(final OffsetDateTime since, final Lastname name)
422    {
423     this.lastname.addEntry(since, name);
424    }
425 
426 
427   /**
428    * Get gender at birth.
429    *
430    * @return First known gender
431    */
432   public Gender getGenderAtBirth()
433    {
434     return this.sex.getFirstEntry();
435    }
436 
437 
438   /**
439    * Get actual gender.
440    *
441    * @return Last known gender
442    */
443   public Gender getGenderActual()
444    {
445     return this.sex.getLatestEntry();
446    }
447 
448 
449   /**
450    * Get previous gender.
451    *
452    * @return Previous gender or actual gender/gender at birth
453    */
454   public Gender getGenderPrevious()
455    {
456     return this.sex.getPreviousEntry();
457    }
458 
459 
460   /**
461    * Add gender.
462    *
463    * @param since Since datetime
464    * @param gender Gender
465    * @throws IndexOutOfBoundsException If since lies in the future
466    */
467   public void addGender(final OffsetDateTime since, final Gender gender)
468    {
469     this.sex.addEntry(since, gender);
470    }
471 
472 
473   /**
474    * Get firstnames at birth.
475    *
476    * @return List og first known firstnames
477    */
478   public List<Firstname> getFirstnamesAtBirth()
479    {
480     return new ArrayList<>(this.firstnames.getFirstEntry());
481    }
482 
483 
484   /**
485    * Get actual firstnames.
486    *
487    * @return List pf last known firstnames
488    */
489   public List<Firstname> getFirstnamesActual()
490    {
491     return new ArrayList<>(this.firstnames.getLatestEntry());
492    }
493 
494 
495   /**
496    * Get previous firstnames.
497    *
498    * @return Previous firstnames or actual firstnames/firstnames at birth
499    */
500   public List<Firstname> getFirstnamesPrevious()
501    {
502     return new ArrayList<>(this.firstnames.getPreviousEntry());
503    }
504 
505 
506   /**
507    * Add firstnames.
508    *
509    * @param since Since datetime
510    * @param names Firstnames
511    * @throws NullPointerException If since and/or firstnames is/are null
512    * @throws IndexOutOfBoundsException If since lies in the future
513    * @throws IllegalArgumentException If firstnames is larger than 32 characters or includes illegal characters
514    */
515   public void addFirstnames(final OffsetDateTime since, final List<Firstname> names)
516    {
517     this.firstnames.addEntry(since, new ArrayList<>(names));
518    }
519 
520 
521   /**
522    * Set birthday.
523    *
524    * @param date Birthday date with 00:00:00 or with exact birth time or null.
525    */
526   public void setBirthday(final OffsetDateTime date)
527    {
528     this.birthday = Optional.ofNullable(date);
529    }
530 
531 
532   /**
533    * Get birthday.
534    *
535    * @return Optional OffsetDateTime
536    */
537   @SuppressWarnings("PMD.NullAssignment")
538   public Optional<OffsetDateTime> getBirthday()
539    {
540     return Optional.ofNullable(this.birthday.isPresent() ? this.birthday.get() : null);
541    }
542 
543 
544   /**
545    * Set death date.
546    *
547    * @param date Death date date with 00:00:00 or with exact death time or null.
548    * @throws IllegalArgumentException If birthday &gt; death date
549    */
550   public void setDeathdate(final OffsetDateTime date)
551    {
552     if (this.birthday.isPresent() && this.birthday.get().isAfter(date))
553      {
554       throw new IllegalArgumentException("birthday > deathdate"); //$NON-NLS-1$
555      }
556     this.deathdate = Optional.ofNullable(date);
557    }
558 
559 
560   /**
561    * Get death date.
562    *
563    * @return Optional OffsetDateTime
564    */
565   @SuppressWarnings("PMD.NullAssignment")
566   public Optional<OffsetDateTime> getDeathdate()
567    {
568     return Optional.ofNullable(this.deathdate.isPresent() ? this.deathdate.get() : null);
569    }
570 
571 
572   /**
573    * Set blood group.
574    *
575    * @param bloodGroup Blood group
576    */
577   public void setBloodGroup(final BloodGroup bloodGroup)
578    {
579     this.bloodGroup = Optional.ofNullable(bloodGroup);
580    }
581 
582 
583   /**
584    * Get blood group.
585    *
586    * @return Optional BloodGroup
587    */
588   @SuppressWarnings("PMD.NullAssignment")
589   public Optional<BloodGroup> getBloodGroup()
590    {
591     return Optional.ofNullable(this.bloodGroup.isPresent() ? this.bloodGroup.get() : null);
592    }
593 
594  }