Package org.gedcom4j.relationship

Source Code of org.gedcom4j.relationship.AncestryCalculator

/*
* Copyright (c) 2009-2014 Matthew R. Harrah
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package org.gedcom4j.relationship;

import java.util.HashSet;
import java.util.Set;

import org.gedcom4j.model.Family;
import org.gedcom4j.model.FamilyChild;
import org.gedcom4j.model.FamilySpouse;
import org.gedcom4j.model.Individual;

/**
* A class for doing more advanced ancestry calculations than the basic {@link Individual#getAncestors()} method
*
* @author frizbog1
*
*/
public class AncestryCalculator {

    /**
     * A flag for internal use to track whether the lowest common ancestor routine added any common ancestors to the
     * result set while it was recursing around
     */
    private boolean addedAnyCommonAncestors;

    /**
     * The set of people who have been checked already to see if they are an ancestor of the first individual. This is
     * to keep things efficient and to prevent infinite recursion and looping.
     */
    private Set<Individual> checkedAlready;

    /**
     * The "target list", or set of ancestors for the first individual. As we traverse the tree up through the second
     * individual's ancestors, we will check each one against this set (hence, "target set") for a match. The first
     * ancestor we find of individual 2 that is also some ancestor of individual 1 is our lowest common ancestor.
     */
    private Set<Individual> targetList;

    /**
     * A count of generations
     */
    private int genCount;

    /**
     * Get the "extended ancestry" of an individual. This is defined (for this method's purposes) as the individual's
     * parents (and step-parents), recursively.
     *
     * @param individual
     *            the individual whose extended ancestry is desired
     * @return the set of all ancestors for the individual, and all their spouses
     */
    public Set<Individual> getExtendedAncestry(Individual individual) {
        Set<Individual> result = new HashSet<Individual>();

        // Get every family this individual was a child of
        for (FamilyChild fc : individual.familiesWhereChild) {
            // Add father and all his wives
            Individual dad = fc.family.husband;
            if (dad != null && !result.contains(dad)) {
                result.add(dad);
                for (FamilySpouse fs : dad.familiesWhereSpouse) {
                    Individual dadsWife = fs.family.wife;
                    if (dadsWife != null) {
                        result.add(dadsWife);
                        result.addAll(getExtendedAncestry(dadsWife));
                    }
                }
                // And include his extended ancestry as well (recursively)
                result.addAll(getExtendedAncestry(dad));
            }

            // Add mother and all her husbands
            Individual mom = fc.family.wife;
            if (mom != null && !result.contains(mom)) {
                result.add(mom);
                for (FamilySpouse fs : mom.familiesWhereSpouse) {
                    Individual momsHusband = fs.family.husband;
                    if (momsHusband != null) {
                        result.add(momsHusband);
                        result.addAll(getExtendedAncestry(momsHusband));
                    }
                }
                // And include her extended ancestry as well (recursively)
                result.addAll(getExtendedAncestry(mom));
            }
        }

        return result;
    }

    /**
     * Counts the number of generations between the ancestor and descendant.
     *
     * @param descendant
     *            the descendant individual
     * @param ancestor
     *            the ancestor of the descendant
     * @return the number of generations separating descendant from ancestor. A parent-child relationship would be 1; a
     *         grandparent-child relationship would be 2. This method should always return a positive integer, or throw
     *         an exception.
     */
    public int getGenerationCount(Individual descendant, Individual ancestor) {
        genCount = 0;

        if (lookForAncestor(descendant, ancestor) && genCount > 0) {
            return genCount;
        } else {
            throw new IllegalArgumentException("Ancestor/descendant relationship not found for " + ancestor + " and  "
                    + descendant);
        }
    }

    /**
     * Get a Set of the lowest common ancestors between two individuals
     *
     * @param individual1
     *            individual 1
     * @param individual2
     *            individual 2
     * @return the set of lowest common ancestors
     */
    public Set<Individual> getLowestCommonAncestors(Individual individual1, Individual individual2) {
        Set<Individual> result = new HashSet<Individual>();

        // Initialize the first iteration of using the lowest-common-ancestor
        // process
        initializeLcaSearch(individual1);
        // All set up, get the lowest common ancestors
        addLowestCommonAncestorsToSet(individual2, result, 0);

        return result;
    }

    /**
     * <p>
     * Get a set of common ancestors between a specific individual and a person who we checked earlier.
     * </p>
     * <p>
     * The other person's ancestry is assumed to be in the field <code>ancestorsOfIndividual1</code>, which should be
     * populated prior to the first call to this recursive method.
     * </p>
     * <p>
     * As this method executes and recurses, it tracks who has already been checked in the field
     * <code>checkedAlready</code>. As people are checked, they are added to this set to prevent them from being checked
     * multiple times and causing infinite recursion and looping. This Set should be cleared and/or pre-populated prior
     * to the first call to this recursive method.
     * </p>
     *
     * @param individual
     *            the person who might have common ancestors with the original person
     * @param set
     *            the set of people we are adding to
     * @param level
     *            the level of recursion we're at
     */
    private void addLowestCommonAncestorsToSet(Individual individual, Set<Individual> set, int level) {

        if (individual == null) {
            return;
        }

        // To prevent infinite recursion and looping and other nastiness
        if (checkedAlready.contains(individual)) {
            return;
        }
        checkedAlready.add(individual);

        // Go through all the individuals parents and their spouses to see
        // if they are in the other person's set of ancestors
        for (FamilyChild fc : individual.familiesWhereChild) {
            // First check dad
            if (!checkedAlready.contains(fc.family.husband)) {
                checkParent(level, set, fc.family.husband);
            }
            // Now check mom
            if (!checkedAlready.contains(fc.family.wife)) {
                checkParent(level, set, fc.family.wife);
            }
        }

        if (!addedAnyCommonAncestors) {
            // we didn't find any common ancestors, so recurse up this
            // individual's parents
            for (FamilyChild fc : individual.familiesWhereChild) {
                Individual dad = fc.family.husband;
                if (dad != null && !checkedAlready.contains(dad)) {
                    addLowestCommonAncestorsToSet(dad, set, level + 1);
                }
                Individual mom = fc.family.wife;
                if (mom != null && !checkedAlready.contains(mom)) {
                    addLowestCommonAncestorsToSet(mom, set, level + 1);
                }
            }
        }
    }

    /**
     * Check the father in a family to see if he's a common ancestor
     *
     * @param level
     *            the level we're recursing at
     * @param set
     *            the set of common ancestors we're adding to
     * @param parent
     *            the parent being checked
     *
     */
    private void checkParent(int level, Set<Individual> set, Individual parent) {

        if (parent == null) {
            return;
        }

        if (targetList.contains(parent)) {
            // Dad is in common, add to result set
            set.add(parent);
            addedAnyCommonAncestors = true;
            return;
        }
        // Dad isn't in common, check his spouses
        for (FamilySpouse fs : parent.familiesWhereSpouse) {
            Individual spouse = getSpouse(fs, parent);
            if (spouse == null) {
                continue;
            }
            if (targetList.contains(spouse)) {
                // Dad's wife is in common, add to result set
                set.add(spouse);
                addedAnyCommonAncestors = true;
            } else if (!checkedAlready.contains(spouse) && !spouse.familiesWhereChild.isEmpty()) {
                Set<Individual> s = new HashSet<Individual>();
                addLowestCommonAncestorsToSet(spouse, s, level + 1);
                if (!s.isEmpty()) {
                    /*
                     * Parent's spouse or the spouse's ancestors in the target list, so add them to the result set
                     */
                    set.addAll(s);
                    addedAnyCommonAncestors = true;
                }
            }
        }
    }

    /**
     * Get the spouse of an individual in a family
     *
     * @param fs
     *            the family
     * @param i
     *            the individual to get the spouse for
     * @return the spouse of the individual passed in
     */
    private Individual getSpouse(FamilySpouse fs, Individual i) {
        if (fs.family.husband == i) {
            return fs.family.wife;
        }
        if (fs.family.wife == i) {
            return fs.family.husband;
        }
        return null;
    }

    /**
     * Initialize a Lowest-Common-Ancestor search
     *
     * @param individual1
     *            the first individual in the search
     */
    private void initializeLcaSearch(Individual individual1) {
        targetList = getExtendedAncestry(individual1);
        checkedAlready = new HashSet<Individual>();
        addedAnyCommonAncestors = false;
    }

    /**
     * A recursive method for counting generations between a specific person and an ancestor. Used as the workhorse for
     * {@link #getGenerationCount(Individual, Individual)}. Upon return, {@link #genCount} will equal the number of
     * generations between <code>person</code> and <code>ancestor</code>
     *
     * @param person
     *            the person currently being examined
     * @param ancestor
     *            the ancestor we are looking for and which stops the recursion
     * @return true if and only if the ancestor has been found for person
     */
    private boolean lookForAncestor(Individual person, Individual ancestor) {
        if (person != null && person.familiesWhereChild != null) {
            for (FamilyChild fc : person.familiesWhereChild) {
                Family f = fc.family;
                if (ancestor.equals(f.husband) || ancestor.equals(f.wife)) {
                    genCount = 1;
                    return true;
                } else if (lookForAncestor(f.husband, ancestor)) {
                    genCount++;
                    return true;
                } else if (lookForAncestor(f.wife, ancestor)) {
                    genCount++;
                    return true;
                } else {
                    return false;
                }
            }
        }

        return false;
    }
}
TOP

Related Classes of org.gedcom4j.relationship.AncestryCalculator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.