001package edu.pdx.cs410J.family;
002
003import java.util.ArrayList;
004import java.util.Collection;
005import java.util.Date;
006import java.io.Serializable;
007
008/**
009 * This class represents a person in a family tree.  Each person has a
010 * required unique id.  Additionally, a person may have a first,
011 * middle, and last name, a date of birth, a date of death, and may be
012 * involved in one or more marriages.
013 *
014 * @author David Whitlock
015 */
016public class Person implements Serializable {
017  public static Gender MALE = Gender.MALE;
018  public static Gender FEMALE = Gender.FEMALE;
019
020  public enum Gender { FEMALE, MALE, UNKNOWN };
021
022  /** A constant representing the id of an unknown person */
023  public static final int UNKNOWN = -1;
024
025  private int id;
026  private Gender gender;
027  private String firstName;
028  private String middleName;
029  private String lastName;
030  private Person father;
031  private Person mother;
032  private Collection<Marriage> marriages;
033  private Date dob;             // Date of birth
034  private Date dod;             // Date of death
035
036  /** The id of this person's mother.  We need this for the parsers
037      who read a person's id before the Person is created. */
038  private int motherId = UNKNOWN;
039
040  /** The id of this person's father.  We need this for the parsers
041      who read a person's id before the Person is created. */
042  private int fatherId = UNKNOWN;
043
044  /**
045   * Creates a new <code>Person</code> with a given id and gender.
046   * An {@link #UNKNOWN} person cannot be created. 
047   *
048   * @throws FamilyTreeException
049   *         <code>id</code> is less than 1 or <code>gender</code> is
050   *         neither {@link #MALE} nor {@link #FEMALE}
051   */
052  public Person(int id, Gender gender) {
053    if (id < 1) {
054      String m = "A person's id must be greater than 1: " + id;
055      throw new FamilyTreeException(m);
056    }
057
058    if (gender != MALE && gender != FEMALE) {
059      String s = "Gender must be MALE or FEMALE";
060      throw new FamilyTreeException(s);
061    }
062
063    this.id = id;
064    this.gender = gender;
065    this.marriages = new ArrayList<Marriage>();
066  }
067
068  /**
069   * Creates a person of unknown gender.  Note that this method is
070   * package protected and is meant to be invoked by parsers, etc. in
071   * which we have to create a person before we know its gender.
072   */
073  Person(int id) {
074    if (id < 1) {
075      String m = "A person's id must be greater than 1: " + id;
076      throw new FamilyTreeException(m);
077    }
078
079    this.id = id;
080    this.gender = Gender.UNKNOWN;
081    this.marriages = new ArrayList<Marriage>();
082  }
083
084  /**
085   * Default constructor for deserialization
086   */
087  private Person() {
088    
089  }
090
091  ///////////////////////  Instance Methods  ///////////////////////
092
093  /**
094   * Returns this person's id.
095   */
096  public int getId() {
097    return this.id;
098  }
099
100  /**
101   * Sets this person's gender.  Note that this method is package
102   * protected.
103   *
104   * @throws FamilyTreeException
105   *         The <code>gender</code> is neither {@link #MALE} nor
106   *         {@link #FEMALE}.
107   * 
108   * @see #Person(int)
109   */
110  void setGender(Gender gender) {
111    if (gender == MALE || gender == FEMALE) {
112      this.gender = gender;
113
114    } else {
115      String s = "Invalid gender: " + gender;
116      throw new FamilyTreeException(s);
117    }
118  }
119
120  /**
121   * Returns this person's gender
122   */
123  public Gender getGender() {
124    return this.gender;
125  }
126
127  /**
128   * Sets this person's first name.
129   */
130  public void setFirstName(String firstName) {
131    this.firstName = firstName;
132  }
133
134  /**
135   * Returns this person's first name.
136   */
137  public String getFirstName() {
138    return this.firstName;
139  }
140
141  /**
142   * Sets this person's middle name.
143   */
144  public void setMiddleName(String middleName) {
145    this.middleName = middleName;
146  }
147
148  /**
149   * Returns this person's middle name.
150   */
151  public String getMiddleName() {
152    return this.middleName;
153  }
154
155  /**
156   * Sets this person's last name.
157   */
158  public void setLastName(String lastName) {
159    this.lastName = lastName;
160  }
161
162  /**
163   * Returns this person's last name.
164   */
165  public String getLastName() {
166    return this.lastName;
167  }
168
169  /**
170   * Returns this person's full (first, middle, and last) name.
171   */
172  public String getFullName() {
173    StringBuffer fullName = new StringBuffer();
174
175    if (this.firstName != null) {
176      fullName.append(this.firstName);
177      fullName.append(' ');
178    }
179
180    if (this.middleName != null) {
181      fullName.append(this.middleName);
182      fullName.append(' ');
183    }
184
185    if (this.lastName != null) {
186      fullName.append(this.lastName);
187    }
188
189    return fullName.toString().trim();
190  }
191
192  /**
193   * Sets this person's father.
194   *
195   * @throws FamilyTreeException
196   *         <code>father</code> is not {@link #MALE}
197   */
198  public void setFather(Person father) {
199    if (father.getGender() != Person.MALE) {
200      String s = "Father " + father + " must be MALE";
201      throw new FamilyTreeException(s);
202    }
203
204    this.fatherId = father.getId();
205    this.father = father;
206  }
207
208  /**
209   * Returns the id of this person's father.
210   *
211   * @return {@link #UNKNOWN}, if this person's father is not known
212   */
213  public int getFatherId() {
214    if (this.father == null) {
215      return UNKNOWN;
216
217    } else {
218//       assert this.fatherId != UNKNOWN;
219      return this.father.getId();
220    }
221  }
222
223  /**
224   * Returns this person's father.
225   */
226  public Person getFather() {
227    return this.father;
228  }
229
230  /**
231   * Sets the id of this person's father.  This method is package
232   * protected because it is only intended to be access by parsers and
233   * other objects that would see a peron's id before the person is
234   * created.
235   *
236   * @see #patchUp
237   */
238  void setFatherId(int id) {
239    this.fatherId = id;
240  }
241
242  /**
243   * Sets this person's mother.
244   *
245   * @throws FamilyTreeException
246   *         <code>mother</code>'s gender is not {@link #FEMALE}
247   */
248  public void setMother(Person mother) {
249    if (mother.getGender() != Person.FEMALE) {
250      String s = "Person " + mother.getId() + "(mother of " +
251        this.getId() + ") must be FEMALE";
252      throw new FamilyTreeException(s);
253    }
254
255    this.mother = mother;
256  }
257
258  /**
259   * Returns the id of this person's mother.
260   *
261   * @return {@link #UNKNOWN}, if this person's father is not known
262   */
263  public int getMotherId() {
264    if (this.mother == null) {
265      return UNKNOWN;
266
267    } else {
268      return this.mother.getId();
269    }
270  }
271
272  /**
273   * Returns this person's mother.
274   */
275  public Person getMother() {
276    return this.mother;
277  }
278
279  /**
280   * Sets the id of this person's mother.  This method is package
281   * protected because it is only intended to be accessed by parsers
282   * and other objects that would see a peron's id before the person
283   * is created.
284   *
285   * @see #patchUp
286   */
287  void setMotherId(int id) {
288    this.motherId = id;
289  }
290
291  /**
292   * "Patches up" a person's mother and father <code>Person</code>
293   * objects.  This method is package protected because it is only
294   * intended to be accessed by parsers and other objects that would
295   * see a peron's id before the person is created.
296   *
297   * @throws FamilyTreeException
298   *         Either the mother or father does not existin in
299   *         <code>tree</code> or if the gender has not been set
300   */
301  void patchUp(FamilyTree tree) {
302    if (this.father == null && this.fatherId != UNKNOWN) {
303      Person father = tree.getPerson(this.fatherId);
304      if (father == null) {
305        String s = "Father " + this.fatherId + " does not exist";
306        throw new FamilyTreeException(s);
307      }
308      this.setFather(father);
309    }
310
311    if (this.mother == null && this.motherId != UNKNOWN) {
312      Person mother = tree.getPerson(this.motherId);
313      if (mother == null) {
314        String s = "Mother " + this.motherId + " does not exist";
315        throw new FamilyTreeException(s);
316      }
317      this.setMother(mother);
318    }
319
320    if (this.gender == Gender.UNKNOWN) {
321      String s = "Gender has not been set yet!";
322      throw new FamilyTreeException(s);
323    }
324  }
325
326  /**
327   * Sets this person's date of birth.
328   */
329  public void setDateOfBirth(Date dob) {
330    this.dob = dob;
331  }
332
333  /**
334   * Returns this person's date of birth.
335   */
336  public Date getDateOfBirth() {
337    return this.dob;
338  }
339
340  /**
341   * Sets this person's date of death.
342   *
343   * @throws FamilyTreeException
344   *         If this person's data of birth is known and
345   *         <code>dod</code> occurs before it.
346   */
347  public void setDateOfDeath(Date dod) {
348    if (this.dob != null && dod != null && this.dob.after(dod)) {
349      String s = "Date of death (" + dod + 
350        ") cannot occur before date of birth (" + this.dob + ")";
351      throw new FamilyTreeException(s);
352    }
353
354    this.dod = dod;
355  }
356
357  /**
358   * Returns this person's date of death.
359   */
360  public Date getDateOfDeath() {
361    return this.dod;
362  }
363
364  /**
365   * Makes note of a marriage this person was involved in.
366   *
367   * @throws FamilyTreeException
368   *         If this person is not one of the spouses in the marriage
369   */
370  public void addMarriage(Marriage marriage) {
371    if (this.getGender() == Person.MALE) {
372      if (!marriage.getHusband().equals(this)) {
373        String s = "This person (" + this.getFullName() + 
374          ") is not the husband in " + marriage;
375        throw new FamilyTreeException(s);
376      }
377
378    } else {
379      if (!marriage.getWife().equals(this)) {
380        String s = "This person (" + this.getFullName() + 
381          ") is not the wife in " + marriage;
382        throw new FamilyTreeException(s);
383      }
384    }
385
386    this.marriages.add(marriage);
387  }
388
389  /**
390   * Returns the marriages that this person was involved in.
391   */
392  public Collection<Marriage> getMarriages() {
393    return this.marriages;
394  }
395
396  //////////////////////  Utility Methods  ////////////////////////
397
398  /**
399   * Determines whether or not this <code>Person</code> is equal to
400   * another <code>Person</code>. Two <code>Person</code>s are
401   * considered equal if they have the same id.
402   */
403  public boolean equals(Object o) {
404    if (o == null) {
405      return false;
406    }
407
408    if (!(o instanceof Person)) {
409      return false;
410    }
411
412    Person other = (Person) o;
413    return this.getId() == other.getId();
414  }
415
416  /**
417   * Returns a brief description of this person.
418   */
419  public String toString() {
420    StringBuffer sb = new StringBuffer();
421
422    sb.append("Person ").append(this.id).append(": ").append(this.getFullName());
423    if (this.dob != null) {
424      sb.append("\nBorn: ");
425      sb.append(this.dob);
426    }
427    if (this.dod != null) {
428      sb.append(", Died: ");
429      sb.append(this.dod);
430    }
431
432    if (this.mother != null) {
433      sb.append("\nMother: ");
434      sb.append(this.mother.getFullName());
435    }
436    if (this.father != null) {
437      sb.append(", Father: ");
438      sb.append(this.father.getFullName());
439    }
440
441    sb.append("\nMarried ");
442    sb.append(this.marriages.size());
443    sb.append(" times");
444
445    return sb.toString();
446  }
447
448}