org.openmrs.Person.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.Person.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public License,
 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
 * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under
 * the terms of the Healthcare Disclaimer located at http://openmrs.org/license.
 *
 * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS
 * graphic logo is a trademark of OpenMRS Inc.
 */
package org.openmrs;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.api.UserService;
import org.openmrs.util.OpenmrsUtil;
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.Element;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Root;
import org.simpleframework.xml.load.Replace;
import org.springframework.util.StringUtils;

/**
 * A Person in the system. This can be either a small person stub, or indicative of an actual
 * Patient in the system. This class holds the generic person things that both the stubs and
 * patients share. Things like birthdate, names, addresses, and attributes are all generified into
 * the person table (and hence this super class)
 * 
 * @see org.openmrs.Patient
 */
@Root(strict = false)
public class Person extends BaseOpenmrsData implements java.io.Serializable {

    public static final long serialVersionUID = 2L;

    protected final Log log = LogFactory.getLog(getClass());

    protected Integer personId;

    private Set<PersonAddress> addresses = null;

    private Set<PersonName> names = null;

    private Set<PersonAttribute> attributes = null;

    private String gender;

    private Date birthdate;

    private Date birthtime;

    private Boolean birthdateEstimated = false;

    private Boolean deathdateEstimated = false;

    private Boolean dead = false;

    private Date deathDate;

    private Concept causeOfDeath;

    private User personCreator;

    private Date personDateCreated;

    private User personChangedBy;

    private Date personDateChanged;

    private Boolean personVoided = false;

    private User personVoidedBy;

    private Date personDateVoided;

    private String personVoidReason;

    private boolean isPatient;

    /**
     * Convenience map from PersonAttributeType.name to PersonAttribute.<br>
     * <br>
     * This is "cached" for each user upon first load. When an attribute is changed, the cache is
     * cleared and rebuilt on next access.
     */
    Map<String, PersonAttribute> attributeMap = null;

    private Map<String, PersonAttribute> allAttributeMap = null;

    /**
     * default empty constructor
     */
    public Person() {
    }

    /**
     * This constructor is used to build a new Person object copy from another person object
     * (usually a patient or a user subobject). All attributes are copied over to the new object.
     * NOTE! All child collection objects are copied as pointers, each individual element is not
     * copied. <br>
     * 
     * @param person Person to create this person object from
     */
    public Person(Person person) {
        if (person == null) {
            return;
        }

        personId = person.getPersonId();
        setUuid(person.getUuid());
        addresses = person.getAddresses();
        names = person.getNames();
        attributes = person.getAttributes();

        gender = person.getGender();
        birthdate = person.getBirthdate();
        birthtime = person.getBirthDateTime();
        birthdateEstimated = person.getBirthdateEstimated();
        deathdateEstimated = person.getDeathdateEstimated();
        dead = person.isDead();
        deathDate = person.getDeathDate();
        causeOfDeath = person.getCauseOfDeath();

        // base creator/voidedBy/changedBy info is not copied here
        // because that is specific to and will be recreated
        // by the subobject upon save

        setPersonCreator(person.getPersonCreator());
        setPersonDateCreated(person.getPersonDateCreated());
        setPersonChangedBy(person.getPersonChangedBy());
        setPersonDateChanged(person.getPersonDateChanged());
        setPersonVoided(person.isPersonVoided());
        setPersonVoidedBy(person.getPersonVoidedBy());
        setPersonDateVoided(person.getPersonDateVoided());
        setPersonVoidReason(person.getPersonVoidReason());
    }

    /**
     * Default constructor taking in the primary key personId value
     * 
     * @param personId Integer internal id for this person
     * @should set person id
     */
    public Person(Integer personId) {
        this.personId = personId;
    }

    // Property accessors

    /**
     * @return Returns the personId.
     */
    @Attribute(required = true)
    public Integer getPersonId() {
        return personId;
    }

    /**
     * @param personId The personId to set.
     */
    @Attribute(required = true)
    public void setPersonId(Integer personId) {
        this.personId = personId;
    }

    /**
     * @return person's gender
     */
    @Attribute(required = false)
    public String getGender() {
        return this.gender;
    }

    /**
     * @param gender person's gender
     */
    @Attribute(required = false)
    public void setGender(String gender) {
        this.gender = gender;
    }

    /**
     * @return person's date of birth
     */
    @Element(required = false)
    public Date getBirthdate() {
        return this.birthdate;
    }

    /**
     * @param birthdate person's date of birth
     */
    @Element(required = false)
    public void setBirthdate(Date birthdate) {
        this.birthdate = birthdate;
    }

    /**
     * @return true if person's birthdate is estimated
     */
    public Boolean isBirthdateEstimated() {
        // if (this.birthdateEstimated == null) {
        // return new Boolean(false);
        // }
        return this.birthdateEstimated;
    }

    @Attribute(required = true)
    public Boolean getBirthdateEstimated() {
        return isBirthdateEstimated();
    }

    /**
     * @param birthdateEstimated true if person's birthdate is estimated
     */
    @Attribute(required = true)
    public void setBirthdateEstimated(Boolean birthdateEstimated) {
        this.birthdateEstimated = birthdateEstimated;
    }

    public Boolean getDeathdateEstimated() {
        return this.deathdateEstimated;
    }

    /**
     * @param deathdateEstimated true if person's deathdate is estimated
     */
    public void setDeathdateEstimated(Boolean deathdateEstimated) {
        this.deathdateEstimated = deathdateEstimated;
    }

    /**
     * @param birthtime person's time of birth
     */
    @Element(required = false)
    public void setBirthtime(Date birthtime) {
        this.birthtime = birthtime;
    }

    /**
     * @return person's time of birth with the date portion set to the date from person's birthdate
     */
    @Element(required = false)
    public Date getBirthDateTime() {
        if (birthdate != null && birthtime != null) {
            String birthDateString = new SimpleDateFormat("yyyy-MM-dd").format(birthdate);
            String birthTimeString = new SimpleDateFormat("HH:mm:ss").format(birthtime);

            try {
                return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(birthDateString + " " + birthTimeString);
            } catch (ParseException e) {
                log.error(e);
            }
        }
        return null;
    }

    /**
     * @return person's time of birth.
     */
    @Element(required = false)
    public Date getBirthtime() {
        return this.birthtime;
    }

    /**
     * @return Returns the death status.
     */
    public Boolean isDead() {
        return dead;
    }

    /**
     * @return Returns the death status.
     */
    @Attribute(required = true)
    public Boolean getDead() {
        return isDead();
    }

    /**
     * @param dead The dead to set.
     */
    @Attribute(required = true)
    public void setDead(Boolean dead) {
        this.dead = dead;
    }

    /**
     * @return date of person's death
     */
    @Element(required = false)
    public Date getDeathDate() {
        return this.deathDate;
    }

    /**
     * @param deathDate date of person's death
     */
    @Element(required = false)
    public void setDeathDate(Date deathDate) {
        this.deathDate = deathDate;
    }

    /**
     * @return cause of person's death
     */
    @Element(required = false)
    public Concept getCauseOfDeath() {
        return this.causeOfDeath;
    }

    /**
     * @param causeOfDeath cause of person's death
     */
    @Element(required = false)
    public void setCauseOfDeath(Concept causeOfDeath) {
        this.causeOfDeath = causeOfDeath;
    }

    /**
     * @return list of known addresses for person
     * @see org.openmrs.PersonAddress
     * @should not get voided addresses
     * @should not fail with null addresses
     */
    @ElementList(required = false)
    public Set<PersonAddress> getAddresses() {
        if (addresses == null) {
            addresses = new TreeSet<PersonAddress>();
        }
        return this.addresses;
    }

    /**
     * @param addresses Set&lt;PersonAddress&gt; list of known addresses for person
     * @see org.openmrs.PersonAddress
     */
    @ElementList(required = false)
    public void setAddresses(Set<PersonAddress> addresses) {
        this.addresses = addresses;
    }

    /**
     * @return all known names for person
     * @see org.openmrs.PersonName
     * @should not get voided names
     * @should not fail with null names
     */
    @ElementList
    public Set<PersonName> getNames() {
        if (names == null) {
            names = new TreeSet<PersonName>();
        }
        return this.names;
    }

    /**
     * @param names update all known names for person
     * @see org.openmrs.PersonName
     */
    @ElementList
    public void setNames(Set<PersonName> names) {
        this.names = names;
    }

    /**
     * @return all known attributes for person
     * @see org.openmrs.PersonAttribute
     * @should not get voided attributes
     * @should not fail with null attributes
     */
    @ElementList
    public Set<PersonAttribute> getAttributes() {
        if (attributes == null) {
            attributes = new TreeSet<PersonAttribute>();
        }
        return this.attributes;
    }

    /**
     * Returns only the non-voided attributes for this person
     * 
     * @return list attributes
     * @should not get voided attributes
     * @should not fail with null attributes
     */
    public List<PersonAttribute> getActiveAttributes() {
        List<PersonAttribute> attrs = new Vector<PersonAttribute>();
        for (PersonAttribute attr : getAttributes()) {
            if (!attr.isVoided()) {
                attrs.add(attr);
            }
        }
        return attrs;
    }

    /**
     * @param attributes update all known attributes for person
     * @see org.openmrs.PersonAttribute
     */
    @ElementList
    public void setAttributes(Set<PersonAttribute> attributes) {
        this.attributes = attributes;
        attributeMap = null;
        allAttributeMap = null;
    }

    // Convenience methods

    /**
     * Convenience method to add the <code>attribute</code> to this person's attribute list if the
     * attribute doesn't exist already.<br>
     * <br>
     * Voids any current attribute with type = <code>newAttribute.getAttributeType()</code><br>
     * <br>
     * NOTE: This effectively limits persons to only one attribute of any given type **
     * 
     * @param newAttribute PersonAttribute to add to the Person
     * @should fail when new attribute exist
     * @should fail when new atribute are the same type with same value
     * @should void old attribute when new attribute are the same type with different value
     * @should remove attribute when old attribute are temporary
     * @should not save an attribute with a null value
     * @should not save an attribute with a blank string value
     * @should void old attribute when a null or blank string value is added
     */
    public void addAttribute(PersonAttribute newAttribute) {
        newAttribute.setPerson(this);
        boolean newIsNull = !StringUtils.hasText(newAttribute.getValue());

        for (PersonAttribute currentAttribute : getActiveAttributes()) {
            if (currentAttribute.equals(newAttribute)) {
                return; // if we have the same PersonAttributeId, don't add the new attribute
            } else if (currentAttribute.getAttributeType().equals(newAttribute.getAttributeType())) {
                if (currentAttribute.getValue() != null
                        && currentAttribute.getValue().equals(newAttribute.getValue())) {
                    // this person already has this attribute
                    return;
                }

                // if the to-be-added attribute isn't already voided itself
                // and if we have the same type, different value
                if (!newAttribute.isVoided() || newIsNull) {
                    if (currentAttribute.getCreator() != null) {
                        currentAttribute.voidAttribute("New value: " + newAttribute.getValue());
                    } else {
                        // remove the attribute if it was just temporary (didn't have a creator
                        // attached to it yet)
                        removeAttribute(currentAttribute);
                    }
                }
            }
        }
        attributeMap = null;
        allAttributeMap = null;
        if (!OpenmrsUtil.collectionContains(attributes, newAttribute) && !newIsNull) {
            attributes.add(newAttribute);
        }
    }

    /**
     * Convenience method to get the <code>attribute</code> from this person's attribute list if the
     * attribute exists already.
     * 
     * @param attribute
     * @should not fail when person attribute is null
     * @should not fail when person attribute is not exist
     * @should remove attribute when exist
     */
    public void removeAttribute(PersonAttribute attribute) {
        if (attributes != null) {
            if (attributes.remove(attribute)) {
                attributeMap = null;
                allAttributeMap = null;
            }
        }
    }

    /**
     * Convenience Method to return the first non-voided person attribute matching a person
     * attribute type. <br>
     * <br>
     * Returns null if this person has no non-voided {@link PersonAttribute} with the given
     * {@link PersonAttributeType}, the given {@link PersonAttributeType} is null, or this person
     * has no attributes.
     * 
     * @param pat the PersonAttributeType to look for (can be a stub, see
     *            {@link PersonAttributeType#equals(Object)} for how its compared)
     * @return PersonAttribute that matches the given type
     * @should not fail when attribute type is null
     * @should not return voided attribute
     * @should return null when given attribute type is not exist
     */
    public PersonAttribute getAttribute(PersonAttributeType pat) {
        if (pat != null) {
            for (PersonAttribute attribute : getAttributes()) {
                if (pat.equals(attribute.getAttributeType()) && !attribute.isVoided()) {
                    return attribute;
                }
            }
        }
        return null;
    }

    /**
     * Convenience method to get this person's first attribute that has a PersonAttributeType.name
     * equal to <code>attributeName</code>.<br>
     * <br>
     * Returns null if this person has no non-voided {@link PersonAttribute} with the given type
     * name, the given name is null, or this person has no attributes.
     * 
     * @param attributeName the name string to match on
     * @return PersonAttribute whose {@link PersonAttributeType#getName()} matchs the given name
     *         string
     */
    public PersonAttribute getAttribute(String attributeName) {
        if (attributeName != null) {
            for (PersonAttribute attribute : getAttributes()) {
                PersonAttributeType type = attribute.getAttributeType();
                if (type != null && attributeName.equals(type.getName()) && !attribute.isVoided()) {
                    return attribute;
                }
            }
        }

        return null;
    }

    /**
     * Convenience method to get this person's first attribute that has a PersonAttributeTypeId
     * equal to <code>attributeTypeId</code>.<br>
     * <br>
     * Returns null if this person has no non-voided {@link PersonAttribute} with the given type id
     * or this person has no attributes.<br>
     * <br>
     * The given id cannot be null.
     * 
     * @param attributeTypeId the id of the {@link PersonAttributeType} to look for
     * @return PersonAttribute whose {@link PersonAttributeType#getId()} equals the given Integer id
     */
    public PersonAttribute getAttribute(Integer attributeTypeId) {
        for (PersonAttribute attribute : getActiveAttributes()) {
            if (attributeTypeId.equals(attribute.getAttributeType().getPersonAttributeTypeId())) {
                return attribute;
            }
        }
        return null;
    }

    /**
     * Convenience method to get all of this person's attributes that have a
     * PersonAttributeType.name equal to <code>attributeName</code>.
     * 
     * @param attributeName
     */
    public List<PersonAttribute> getAttributes(String attributeName) {
        List<PersonAttribute> ret = new Vector<PersonAttribute>();

        for (PersonAttribute attribute : getActiveAttributes()) {
            PersonAttributeType type = attribute.getAttributeType();
            if (type != null && attributeName.equals(type.getName())) {
                ret.add(attribute);
            }
        }

        return ret;
    }

    /**
     * Convenience method to get all of this person's attributes that have a PersonAttributeType.id
     * equal to <code>attributeTypeId</code>.
     * 
     * @param attributeTypeId
     */
    public List<PersonAttribute> getAttributes(Integer attributeTypeId) {
        List<PersonAttribute> ret = new Vector<PersonAttribute>();

        for (PersonAttribute attribute : getActiveAttributes()) {
            if (attributeTypeId.equals(attribute.getAttributeType().getPersonAttributeTypeId())) {
                ret.add(attribute);
            }
        }

        return ret;
    }

    /**
     * Convenience method to get all of this person's attributes that have a PersonAttributeType
     * equal to <code>personAttributeType</code>.
     * 
     * @param personAttributeType
     */
    public List<PersonAttribute> getAttributes(PersonAttributeType personAttributeType) {
        List<PersonAttribute> ret = new Vector<PersonAttribute>();
        for (PersonAttribute attribute : getAttributes()) {
            if (personAttributeType.equals(attribute.getAttributeType()) && !attribute.isVoided()) {
                ret.add(attribute);
            }
        }
        return ret;
    }

    /**
     * Convenience method to get this person's active attributes in map form: &lt;String,
     * PersonAttribute&gt;.
     */
    public Map<String, PersonAttribute> getAttributeMap() {
        if (attributeMap != null) {
            return attributeMap;
        }

        if (log.isDebugEnabled()) {
            log.debug("Current Person Attributes: \n" + printAttributes());
        }

        attributeMap = new HashMap<String, PersonAttribute>();
        for (PersonAttribute attribute : getActiveAttributes()) {
            attributeMap.put(attribute.getAttributeType().getName(), attribute);
        }

        return attributeMap;
    }

    /**
     * Convenience method to get all of this person's attributes (including voided ones) in map
     * form: &lt;String, PersonAttribute&gt;.
     * 
     * @return All person's attributes in map form
     * @since 1.12
     */
    public Map<String, PersonAttribute> getAllAttributeMap() {
        if (allAttributeMap != null) {
            return allAttributeMap;
        }

        if (log.isDebugEnabled()) {
            log.debug("Current Person Attributes: \n" + printAttributes());
        }

        allAttributeMap = new HashMap<String, PersonAttribute>();
        for (PersonAttribute attribute : getAttributes()) {
            allAttributeMap.put(attribute.getAttributeType().getName(), attribute);
        }

        return allAttributeMap;
    }

    /**
     * Convenience method for viewing all of the person's current attributes
     * 
     * @return Returns a string with all the attributes
     */
    public String printAttributes() {
        StringBuilder s = new StringBuilder("");

        for (PersonAttribute attribute : getAttributes()) {
            s.append(attribute.getAttributeType()).append(" : ").append(attribute.getValue()).append(" : voided? ")
                    .append(attribute.isVoided()).append("\n");
        }

        return s.toString();
    }

    /**
     * Convenience method to add the <code>name</code> to this person's name list if the name
     * doesn't exist already.
     * 
     * @param name
     */
    public void addName(PersonName name) {
        if (name != null) {
            name.setPerson(this);
            if (names == null) {
                names = new TreeSet<PersonName>();
            }
            if (!OpenmrsUtil.collectionContains(names, name)) {
                names.add(name);
            }
        }
    }

    /**
     * Convenience method remove the <code>name</code> from this person's name list if the name
     * exists already.
     * 
     * @param name
     */
    public void removeName(PersonName name) {
        if (names != null) {
            names.remove(name);
        }
    }

    /**
     * Convenience method to add the <code>address</code> to this person's address list if the
     * address doesn't exist already.
     * 
     * @param address
     * @should not add a person address with blank fields
     */
    public void addAddress(PersonAddress address) {
        if (address != null) {
            address.setPerson(this);
            if (addresses == null) {
                addresses = new TreeSet<PersonAddress>();
            }
            if (!OpenmrsUtil.collectionContains(addresses, address) && !address.isBlank()) {
                addresses.add(address);
            }
        }
    }

    /**
     * Convenience method to remove the <code>address</code> from this person's address list if the
     * address exists already.
     * 
     * @param address
     */
    public void removeAddress(PersonAddress address) {
        if (addresses != null) {
            addresses.remove(address);
        }
    }

    /**
     * Convenience method to get the {@link PersonName} object that is marked as "preferred". <br>
     * <br>
     * If two names are marked as preferred (or no names), the database ordering comes into effect
     * and the one that was created most recently will be returned. <br>
     * <br>
     * This method will never return a voided name, even if it is marked as preferred. <br>
     * <br>
     * Null is returned if this person has no names or all voided names.
     * 
     * @return the "preferred" person name.
     * @see #getNames()
     * @see PersonName#isPreferred()
     * @should get preferred and not-voided person name if exist
     * @should get not-voided person name if preferred address does not exist
     * @should get voided person address if person is voided and not-voided address does not exist
     * @should return null if person is not-voided and have voided names
     */
    public PersonName getPersonName() {
        // normally the DAO layer returns these in the correct order, i.e. preferred and non-voided first, but it's possible that someone
        // has fetched a Person, changed their names around, and then calls this method, so we have to be careful.
        if (getNames() != null && getNames().size() > 0) {
            for (PersonName name : getNames()) {
                if (name.isPreferred() && !name.isVoided()) {
                    return name;
                }
            }
            for (PersonName name : getNames()) {
                if (!name.isVoided()) {
                    return name;
                }
            }

            if (isVoided()) {
                return getNames().iterator().next();
            }
        }
        return null;
    }

    /**
     * Convenience method to get the given name attribute on this person's preferred PersonName
     * 
     * @return String given name of the person
     */
    public String getGivenName() {
        PersonName personName = getPersonName();
        if (personName == null) {
            return "";
        } else {
            return personName.getGivenName();
        }
    }

    /**
     * Convenience method to get the middle name attribute on this person's preferred PersonName
     * 
     * @return String middle name of the person
     */
    public String getMiddleName() {
        PersonName personName = getPersonName();
        if (personName == null) {
            return "";
        } else {
            return personName.getMiddleName();
        }
    }

    /**
     * Convenience method to get the family name attribute on this person's preferred PersonName
     * 
     * @return String family name of the person
     */
    public String getFamilyName() {
        PersonName personName = getPersonName();
        if (personName == null) {
            return "";
        } else {
            return personName.getFamilyName();
        }
    }

    /**
     * Convenience method to get the {@link PersonAddress} object that is marked as "preferred". <br>
     * <br>
     * If two addresses are marked as preferred (or no addresses), the database ordering comes into
     * effect and the one that was created most recently will be returned. <br>
     * <br>
     * This method will never return a voided address, even if it is marked as preferred. <br>
     * <br>
     * Null is returned if this person has no addresses or all voided addresses.
     * 
     * @return the "preferred" person address.
     * @see #getAddresses()
     * @see PersonAddress#isPreferred()
     * @should get preferred and not-voided person address if exist
     * @should get not-voided person address if preferred address does not exist
     * @should get voided person address if person is voided and not-voided address does not exist
     * @should return null if person is not-voided and have voided address
     */
    public PersonAddress getPersonAddress() {
        // normally the DAO layer returns these in the correct order, i.e. preferred and non-voided first, but it's possible that someone
        // has fetched a Person, changed their addresses around, and then calls this method, so we have to be careful.
        if (getAddresses() != null && getAddresses().size() > 0) {
            for (PersonAddress addr : getAddresses()) {
                if (addr.isPreferred() && !addr.isVoided()) {
                    return addr;
                }
            }
            for (PersonAddress addr : getAddresses()) {
                if (!addr.isVoided()) {
                    return addr;
                }
            }

            if (isVoided()) {
                return getAddresses().iterator().next();
            }
        }
        return null;
    }

    /**
     * Convenience method to calculate this person's age based on the birthdate For a person who
     * lived 1990 to 2000, age would be -5 in 1985, 5 in 1995, 10 in 2000, and 10 2010.
     * 
     * @return Returns age as an Integer.
     * @should get correct age after death
     */
    public Integer getAge() {
        return getAge(null);
    }

    /**
     * Convenience method: calculates the person's age on a given date based on the birthdate
     * 
     * @param onDate (null defaults to today)
     * @return int value of the person's age
     * @should get age before birthday
     * @should get age on birthday with no minutes defined
     * @should get age on birthday with minutes defined
     * @should get age after birthday
     * @should get age after death
     * @should get age with given date after death
     * @should get age with given date before death
     * @should get age with given date before birth
     */
    public Integer getAge(Date onDate) {
        if (birthdate == null) {
            return null;
        }

        // Use default end date as today.
        Calendar today = Calendar.getInstance();
        // But if given, use the given date.
        if (onDate != null) {
            today.setTime(onDate);
        }

        // If date given is after date of death then use date of death as end date
        if (getDeathDate() != null && today.getTime().after(getDeathDate())) {
            today.setTime(getDeathDate());
        }

        Calendar bday = Calendar.getInstance();
        bday.setTime(birthdate);

        int age = today.get(Calendar.YEAR) - bday.get(Calendar.YEAR);

        // Adjust age when today's date is before the person's birthday
        int todaysMonth = today.get(Calendar.MONTH);
        int bdayMonth = bday.get(Calendar.MONTH);
        int todaysDay = today.get(Calendar.DAY_OF_MONTH);
        int bdayDay = bday.get(Calendar.DAY_OF_MONTH);

        if (todaysMonth < bdayMonth) {
            age--;
        } else if (todaysMonth == bdayMonth && todaysDay < bdayDay) {
            // we're only comparing on month and day, not minutes, etc
            age--;
        }

        return age;
    }

    /**
     * Convenience method: sets a person's birth date from an age as of the given date Also sets
     * flag indicating that the birth date is inexact. This sets the person's birth date to January
     * 1 of the year that matches this age and date
     * 
     * @param age (the age to set)
     * @param ageOnDate (null defaults to today)
     */
    public void setBirthdateFromAge(int age, Date ageOnDate) {
        Calendar c = Calendar.getInstance();
        c.setTime(ageOnDate == null ? new Date() : ageOnDate);
        c.set(Calendar.DATE, 1);
        c.set(Calendar.MONTH, Calendar.JANUARY);
        c.add(Calendar.YEAR, -1 * age);
        setBirthdate(c.getTime());
        setBirthdateEstimated(true);

    }

    public User getPersonChangedBy() {
        return personChangedBy;
    }

    public void setPersonChangedBy(User changedBy) {
        this.personChangedBy = changedBy;
        this.setChangedBy(changedBy);
    }

    public Date getPersonDateChanged() {
        return personDateChanged;
    }

    public void setPersonDateChanged(Date dateChanged) {
        this.personDateChanged = dateChanged;
        this.setDateChanged(dateChanged);
    }

    public User getPersonCreator() {
        return personCreator;
    }

    public void setPersonCreator(User creator) {
        this.personCreator = creator;
        this.setCreator(creator);
    }

    public Date getPersonDateCreated() {
        return personDateCreated;
    }

    public void setPersonDateCreated(Date dateCreated) {
        this.personDateCreated = dateCreated;
        this.setDateCreated(dateCreated);
    }

    public Date getPersonDateVoided() {
        return personDateVoided;
    }

    public void setPersonDateVoided(Date dateVoided) {
        this.personDateVoided = dateVoided;
        this.setDateVoided(dateVoided);
    }

    public void setPersonVoided(Boolean voided) {
        this.personVoided = voided;
        this.setVoided(voided);
    }

    public Boolean getPersonVoided() {
        return isPersonVoided();
    }

    public Boolean isPersonVoided() {
        return personVoided;
    }

    public User getPersonVoidedBy() {
        return personVoidedBy;
    }

    public void setPersonVoidedBy(User voidedBy) {
        this.personVoidedBy = voidedBy;
        this.setVoidedBy(voidedBy);
    }

    public String getPersonVoidReason() {
        return personVoidReason;
    }

    public void setPersonVoidReason(String voidReason) {
        this.personVoidReason = voidReason;
        this.setVoidReason(voidReason);
    }

    /**
     * @return true/false whether this person is a patient or not
     */
    public boolean isPatient() {
        return isPatient;
    }

    /**
     * This should only be set by the database layer by looking at whether a row exists in the
     * patient table
     * 
     * @param isPatient whether this person is a patient or not
     */
    @SuppressWarnings("unused")
    private void setPatient(boolean isPatient) {
        this.isPatient = isPatient;
    }

    /**
     * @return true/false whether this person is a user or not
     * @deprecated use {@link UserService#getUsersByPerson(Person, boolean)}
     */
    @Deprecated
    public boolean isUser() {
        return false;
    }

    /**
     * @see java.lang.Object#toString()
     */
    public String toString() {
        return "Person(personId=" + personId + ")";
    }

    /**
     * If the serializer wishes, don't serialize this entire object, just the important parts
     * 
     * @param sessionMap serialization session information
     * @return Person object to serialize
     * @see OpenmrsUtil#isShortSerialization(Map)
     */
    @Replace
    public Person replaceSerialization(Map<?, ?> sessionMap) {
        if (OpenmrsUtil.isShortSerialization(sessionMap)) {
            // only serialize the person id
            return new Person(getPersonId());
        }

        // don't do short serialization
        return this;
    }

    /**
     * @since 1.5
     * @see org.openmrs.OpenmrsObject#getId()
     */
    public Integer getId() {

        return getPersonId();
    }

    /**
     * @since 1.5
     * @see org.openmrs.OpenmrsObject#setId(java.lang.Integer)
     */
    public void setId(Integer id) {
        setPersonId(id);

    }

}