Package org.gedcom4j.relationship

Source Code of org.gedcom4j.relationship.RelationshipCalculator

/*
* 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 static org.gedcom4j.relationship.RelationshipName.*;

import java.util.*;

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

/**
* <p>
* A class for calculating relationships between individuals.
* </p>
* <p>
* Note that the idea of relationships between individuals can be a complex one. Sometimes people can have more than one
* possible relationship between them, and the relationships between intermediates can be through marriage or blood or
* adoption.
* </p>
* <p>
* The relationships returned from this class do not have String names like "Father" or "Mother" for several reasons:
* <ol>
* <li>The String descriptions would need to be in a language, and I only know English and a <i>very</i> little German,
* and I don't have the resources or knowledge to do a full internationalization into many languages.</li>
* <li>There are matters of preference in string descriptions. For example, is my brother's grandson my "great-nephew"
* or my "grand-nephew?" Does it include a space, a hyphen, or is it all rammed together?</li>
* <li>Some users may care about step- relationships or adoptive relationships for their purposes, and others may not. A
* string description could not serve both purposes simultaneously.</li>
* </ol>
* Ultimately, how the relationship is described is a presentation-layer concern, and as gedcom4j is a library with no
* presentation layer, the descriptions are left to the consumer of the library.
* </p>
*
* @author frizbog1
*/
public class RelationshipCalculator {

    /**
     * The person we are starting from
     */
    private Individual startingIndividual;

    /**
     * The person we are looking for as we traverse the tree
     */
    private Individual targetIndividual;

    /**
     * The list of relationships we've found that matched
     */
    public List<Relationship> relationshipsFound;

    /**
     * The current chain of relationships between individuals we're considering as we traverse the tree. Stated
     * differently, how did we get from individual 1 to the person we're currently working with as we recurse?
     */
    private List<SimpleRelationship> currentChain;

    /**
     * People we have looked at already
     */
    private Set<Individual> lookedAt = new HashSet<Individual>();

    /**
     * <p>
     * Calculate the relationship(s) between two individuals, based on common ancestors (people with no common
     * ancestors, either by blood, marriage, or adoption are considered unrelated). The relationships are then sorted by
     * the number of "hops" between people, and the shortest relationship is found. Then, any relationship longer than
     * that shortest one is removed from the result set, <code>relationshipsFound</code>.
     * </p>
     * <p>
     * Typical usage would be to instantiate a <code>RelationshipCalculator</code> object, call this method with the two
     * people of interest, and then check the <code>relationshipsFound</code> collection to find the most direct
     * relationship(s) between the individuals. If that collection is empty, either the people are not related or the
     * two individuals are the same person.
     * </p>
     *
     * @param individual1
     *            the first individual
     * @param individual2
     *            the second individual
     * @param simplified
     *            should the list be reduced to a simplified form (for example, should Father of Father be collapsed to
     *            Grandfather)
     */
    public void calculateRelationships(Individual individual1, Individual individual2, boolean simplified) {

        // Clear out the results from last time
        relationshipsFound = new ArrayList<Relationship>();

        // We are starting with the first individual
        startingIndividual = individual1;

        // We are looking for the second individual;
        targetIndividual = individual2;

        // We currently have taken no steps away from individual 1
        currentChain = new ArrayList<SimpleRelationship>();

        lookedAt = new HashSet<Individual>();

        // Start with individual 1 and recurse
        if (individual1 != individual2) { // NOPMD - Deliberately comparing with !=
            examine(individual1);
        }

        if (simplified) {
            for (Relationship r : relationshipsFound) {
                simplifyRelationship(r);
            }
        }
        if (relationshipsFound.size() > 1) {
            // Remove duplicates
            relationshipsFound = new ArrayList<Relationship>(new HashSet<Relationship>(relationshipsFound));
            Collections.sort(relationshipsFound);

            // Of the unique chains, the shortest ones are preferred
            int shortestLength = relationshipsFound.get(0).chain.size();
            for (int i = relationshipsFound.size() - 1; i >= 0; i--) {
                if (relationshipsFound.get(i).chain.size() > shortestLength) {
                    relationshipsFound.remove(i);
                }
            }

            // Of chains of equal lengths, the simplest ones are preferred
            int simplestSimplicity = Integer.MAX_VALUE;
            List<Relationship> keepers = new ArrayList<Relationship>();
            for (int i = relationshipsFound.size() - 1; i >= 0; i--) {
                Relationship r = relationshipsFound.get(i);
                int totalSimplicity = r.getTotalSimplicity();
                if (totalSimplicity == simplestSimplicity) {
                    keepers.add(r);
                } else if (totalSimplicity < simplestSimplicity) {
                    keepers.clear();
                    simplestSimplicity = totalSimplicity;
                    keepers.add(r);
                }
            }
            relationshipsFound = keepers;
        }

    }

    /**
     * <p>
     * Collapse down two steps in a chain to a simpler form of it (for example, the son of a father is a brother).
     * </p>
     * <p>
     * Algorithm: Look for two steps in a row with relationships <code>rel1</code> and <code>rel2</code>, describing 3
     * people (A, B, and C). If person A is the <code>rel1</code> of person B, and person B is the <code>rel2</code> of
     * person C, then A is the <code>newRel</code> C, C is the <code>newReverseRel</code> of A), and B drops out
     * entirely.
     * </p>
     * <p>
     * Note that this method stops collapsing after the first collapse, or once the end of the chain is reached. This
     * method should be called repeatedly to completely collapse the chain until no shortening is achieved.
     * </p>
     *
     * @param chain
     *            the chain being collapsed. Assumed to already be of length 2 or greater. Will fail if shorter than
     *            that.
     * @param rel1
     *            relationship to look for between the first two people
     * @param rel2
     *            relationship to look for between the second two people
     * @param newRel
     *            the new relationship to use instead, collapsing out the 'middleman'
     */
    private void collapse(List<SimpleRelationship> chain, RelationshipName rel1, RelationshipName rel2, RelationshipName newRel) {

        for (int i = 0; i < chain.size() - 1; i++) {
            SimpleRelationship s1 = chain.get(i);
            SimpleRelationship s2 = chain.get(i + 1);

            if (s1.name == rel1 && s2.name == rel2 && s1.individual2 == s2.individual1) {
                // Get the reverse relationship
                RelationshipName rr = getReverseRelationship(newRel, s1.individual1.sex);
                if (rr != null) {
                    // Only collapse if we actually could derive a reverse
                    // relationship
                    s1.individual2 = s2.individual2;
                    s1.name = newRel;
                    s1.reverseName = rr;
                    chain.remove(i + 1);
                }
            }
        }

    }

    /**
     * Check if the person being examined is the target person. If not, start looking through everyone that person is
     * related to.
     *
     * @param personBeingExamined
     *            the person who is currently being examined
     */
    private void examine(Individual personBeingExamined) {
        if (personBeingExamined == null) {
            return;
        }
        if (lookedAt.contains(personBeingExamined)) {
            // prevent wasting time checking the same people over and over
            return;
        }
        /*
         * Here I am deliberately using == and not equals(). Rationale: 1) In a given gedcom, there won't be two
         * individuals who are exactly equal without being the same instance...the XREF's would ensure this. 2) Doing an
         * equals() compare is a deep compare, including all its subcollections (notes, etc.), which aren't really what
         * we're trying to do. We aren't looking for someone who evaluates the same as the person - we are looking FOR
         * that exact person. == is a better fit.
         */
        if (personBeingExamined == targetIndividual) { // NOPMD - deliberate use of ==
            /*
             * We've found our target, so make a Relationship object out of the current chain and add it to our result
             * set.
             */
            Relationship r = new Relationship(startingIndividual, targetIndividual, currentChain);
            relationshipsFound.add(r);
        } else {
            lookedAt.add(personBeingExamined);
            /* Not our target, so check relatives, starting with spouses */
            for (FamilySpouse fs : personBeingExamined.familiesWhereSpouse) {
                if (fs.family.husband == personBeingExamined) {
                    if (lookedAt.contains(fs.family.wife)) {
                        continue;
                    }
                    examineWife(personBeingExamined, fs);
                } else if (fs.family.wife == personBeingExamined) {
                    if (lookedAt.contains(fs.family.husband)) {
                        continue;
                    }
                    examineHusband(personBeingExamined, fs);
                }
                /* and check the children */
                for (Individual c : fs.family.children) {
                    if (lookedAt.contains(c)) {
                        continue;
                    }
                    if (fs.family.husband == personBeingExamined) { // NOPMD - deliberately using ==, want to check if
                                                                    // same instance
                        examineChild(personBeingExamined, c, FATHER);
                    } else if (fs.family.wife == personBeingExamined) { // NOPMD - deliberately using ==, want to check
                                                                        // if same instance
                        examineChild(personBeingExamined, c, MOTHER);
                    }
                }
            }
            /* Next check parents */
            for (FamilyChild fc : personBeingExamined.familiesWhereChild) {
                if (!lookedAt.contains(fc.family.husband)) {
                    examineFather(personBeingExamined, fc.family.husband);
                }
                if (!lookedAt.contains(fc.family.wife)) {
                    examineMother(personBeingExamined, fc.family.wife);
                }
            }
        }
    }

    /**
     * Examine the child of a person
     *
     * @param personBeingExamined
     *            the parent, who
     * @param child
     *            the child
     * @param reverseRelationship
     *            the relationship from the child to the parent
     */
    private void examineChild(Individual personBeingExamined, Individual child, RelationshipName reverseRelationship) {
        SimpleRelationship r = new SimpleRelationship();
        r.individual1 = personBeingExamined;
        r.individual2 = child;
        if ("M".equals(child.sex.value)) {
            r.name = SON;
        } else if ("F".equals(child.sex.value)) {
            r.name = DAUGHTER;
        } else {
            r.name = CHILD;
        }
        r.reverseName = reverseRelationship;
        currentChain.add(r);
        examine(r.individual2);
        currentChain.remove(currentChain.size() - 1);
    }

    /**
     * Examine the father
     *
     * @param personBeingExamined
     *            the person whose parents are being examined
     * @param father
     *            the father
     */
    private void examineFather(Individual personBeingExamined, Individual father) {
        SimpleRelationship r = new SimpleRelationship();
        r.individual1 = personBeingExamined;
        r.individual2 = father;
        if ("M".equals(personBeingExamined.sex.value)) {
            r.reverseName = SON;
        } else if ("F".equals(personBeingExamined.sex.value)) {
            r.reverseName = DAUGHTER;
        } else {
            r.reverseName = CHILD;
        }
        r.name = FATHER;
        currentChain.add(r);
        examine(r.individual2);
        currentChain.remove(currentChain.size() - 1);
    }

    /**
     * Examine the wife of the person being examined
     *
     * @param personBeingExamined
     *            the person being examined
     * @param fs
     *            the {@link FamilySpouse} record to which the personBeingExamined is the husband
     */
    private void examineHusband(Individual personBeingExamined, FamilySpouse fs) {
        SimpleRelationship r = new SimpleRelationship();
        r.individual1 = personBeingExamined;
        r.individual2 = fs.family.husband;
        r.name = RelationshipName.HUSBAND;
        currentChain.add(r);
        examine(r.individual2);
        currentChain.remove(currentChain.size() - 1);
    }

    /**
     * Examine the mother
     *
     * @param personBeingExamined
     *            the person whose parents are being examined
     * @param mother
     *            the mother
     */
    private void examineMother(Individual personBeingExamined, Individual mother) {
        SimpleRelationship r = new SimpleRelationship();
        r.individual1 = personBeingExamined;
        r.individual2 = mother;
        if ("M".equals(personBeingExamined.sex.value)) {
            r.reverseName = SON;
        } else if ("F".equals(personBeingExamined.sex.value)) {
            r.reverseName = DAUGHTER;
        } else {
            r.reverseName = CHILD;
        }
        r.name = MOTHER;
        currentChain.add(r);
        examine(r.individual2);
        currentChain.remove(currentChain.size() - 1);
    }

    /**
     * Examine the wife of the person being examined
     *
     * @param personBeingExamined
     *            the person being examined
     * @param fs
     *            the {@link FamilySpouse} record to which the personBeingExamined is the husband
     */
    private void examineWife(Individual personBeingExamined, FamilySpouse fs) {
        SimpleRelationship r = new SimpleRelationship();
        r.individual1 = personBeingExamined;
        r.individual2 = fs.family.wife;
        r.name = RelationshipName.WIFE;
        currentChain.add(r);
        examine(r.individual2);
        currentChain.remove(currentChain.size() - 1);
    }

    /**
     * Get the reverse of a given relationship, based on the gender of the original person. For example, if person A has
     * a brother, the brother's relationship back to person A is either brother (if A is male), sister (if A is female),
     * or sibling (if A's gender is unknown).
     *
     * @param relationship
     *            original relationship from person to someone else
     * @param sex
     *            the sex of original person
     * @return what the relationship would be back to the original person
     */
    private RelationshipName getReverseRelationship(RelationshipName relationship, StringWithCustomTags sex) {
        if ("M".equals(sex.value)) {
            return relationship.reverseForMale;
        }
        if ("F".equals(sex.value)) {
            return relationship.reverseForFemale;
        }
        return relationship.reverseForUnknown;
    }

    /**
     * Go through pairs of steps in the chain, seeing if they can be collapsed. Only basic, immediate family
     * relationships are collapsed (like, "my father's son" is "my brother").
     *
     * @param relationship
     *            the relationship being simplified
     */
    private void simplifyRelationship(Relationship relationship) {

        int previousLength = Integer.MAX_VALUE;
        // You can only simplify a chain that's two or more steps!
        while (relationship.chain.size() > 1) {
            if (relationship.chain.size() >= previousLength) {
                // Didn't make any improvement, so we're done simplifying this
                // relationship
                return;
            }
            // Save how long the chain is now, so we can see if we made an
            // improvement.
            previousLength = relationship.chain.size();

            for (RelationshipName[] rule : SimplificationRules.rules) {
                collapse(relationship.chain, rule[0], rule[1], rule[2]);
            }
        }
    }
}
TOP

Related Classes of org.gedcom4j.relationship.RelationshipCalculator

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.