001package edu.pdx.cs.joy.family; 002 003import java.sql.Connection; 004import java.sql.SQLException; 005import java.util.HashMap; 006import java.util.List; 007import java.util.Map; 008 009/** 010 * Data Access Object implementation for managing FamilyTree entities in the database. 011 * Coordinates persistence of Person and Marriage objects. 012 */ 013public class FamilyTreeDAOImpl implements FamilyTreeDAO { 014 015 private final Connection connection; 016 private final PersonDAO personDAO; 017 018 /** 019 * Creates a new FamilyTreeDAOImpl with the specified database connection. 020 * 021 * @param connection the database connection to use 022 */ 023 public FamilyTreeDAOImpl(Connection connection) { 024 this.connection = connection; 025 this.personDAO = new PersonDAOImpl(connection); 026 } 027 028 /** 029 * Drops all family tree related tables from the database if they exist. 030 * Note: Must drop marriages first due to foreign key constraints. 031 * 032 * @param connection the database connection to use 033 * @throws SQLException if a database error occurs 034 */ 035 public static void dropTables(Connection connection) throws SQLException { 036 MarriageDAO.dropTable(connection); 037 PersonDAO.dropTable(connection); 038 } 039 040 /** 041 * Creates all family tree related tables in the database. 042 * Note: Must create persons first due to foreign key constraints. 043 * 044 * @param connection the database connection to use 045 * @throws SQLException if a database error occurs 046 */ 047 public static void createTables(Connection connection) throws SQLException { 048 PersonDAO.createTable(connection); 049 MarriageDAO.createTable(connection); 050 } 051 052 /** 053 * Saves a complete family tree to the database. 054 * This includes all persons and marriages in the tree. 055 * 056 * @param familyTree the family tree to save 057 * @throws SQLException if a database error occurs 058 */ 059 @Override 060 public void save(FamilyTree familyTree) throws SQLException { 061 // First, save all persons 062 for (Person person : familyTree.getPeople()) { 063 personDAO.save(person); 064 } 065 066 // Build a person cache for marriage persistence 067 Map<Integer, Person> personCache = new HashMap<>(); 068 for (Person person : familyTree.getPeople()) { 069 personCache.put(person.getId(), person); 070 } 071 072 // Then, save all marriages 073 MarriageDAO marriageDAO = new MarriageDAOImpl(connection, personCache); 074 for (Person person : familyTree.getPeople()) { 075 for (Marriage marriage : person.getMarriages()) { 076 // Only save each marriage once (from husband's perspective to avoid duplicates) 077 if (person.equals(marriage.getHusband())) { 078 marriageDAO.save(marriage); 079 } 080 } 081 } 082 } 083 084 /** 085 * Loads a complete family tree from the database. 086 * This includes all persons and marriages, with relationships properly resolved. 087 * 088 * @return the family tree loaded from the database 089 * @throws SQLException if a database error occurs 090 */ 091 @Override 092 public FamilyTree load() throws SQLException { 093 FamilyTree familyTree = new FamilyTree(); 094 095 // First pass: Load all persons and build cache 096 Map<Integer, Person> personCache = new HashMap<>(); 097 Map<Integer, ParentIds> parentIdsMap = new HashMap<>(); 098 099 String sql = "SELECT id, gender, first_name, middle_name, last_name, " + 100 "father_id, mother_id, date_of_birth, date_of_death FROM persons ORDER BY id"; 101 102 try (java.sql.Statement statement = connection.createStatement(); 103 java.sql.ResultSet resultSet = statement.executeQuery(sql)) { 104 105 while (resultSet.next()) { 106 int id = resultSet.getInt("id"); 107 String genderStr = resultSet.getString("gender"); 108 Person.Gender gender = Person.Gender.valueOf(genderStr); 109 110 Person person = new Person(id, gender); 111 person.setFirstName(resultSet.getString("first_name")); 112 person.setMiddleName(resultSet.getString("middle_name")); 113 person.setLastName(resultSet.getString("last_name")); 114 115 // Store parent IDs for later resolution 116 int fatherId = resultSet.getInt("father_id"); 117 boolean fatherIsNull = resultSet.wasNull(); 118 int motherId = resultSet.getInt("mother_id"); 119 boolean motherIsNull = resultSet.wasNull(); 120 121 if (!fatherIsNull || !motherIsNull) { 122 parentIdsMap.put(id, new ParentIds( 123 fatherIsNull ? Person.UNKNOWN : fatherId, 124 motherIsNull ? Person.UNKNOWN : motherId 125 )); 126 } 127 128 java.sql.Timestamp dob = resultSet.getTimestamp("date_of_birth"); 129 if (dob != null) { 130 person.setDateOfBirth(new java.util.Date(dob.getTime())); 131 } 132 133 java.sql.Timestamp dod = resultSet.getTimestamp("date_of_death"); 134 if (dod != null) { 135 person.setDateOfDeath(new java.util.Date(dod.getTime())); 136 } 137 138 familyTree.addPerson(person); 139 personCache.put(id, person); 140 } 141 } 142 143 // Second pass: Resolve parent relationships 144 for (Map.Entry<Integer, ParentIds> entry : parentIdsMap.entrySet()) { 145 Person person = personCache.get(entry.getKey()); 146 ParentIds parentIds = entry.getValue(); 147 148 if (parentIds.fatherId != Person.UNKNOWN) { 149 Person father = personCache.get(parentIds.fatherId); 150 if (father != null) { 151 person.setFather(father); 152 } 153 } 154 155 if (parentIds.motherId != Person.UNKNOWN) { 156 Person mother = personCache.get(parentIds.motherId); 157 if (mother != null) { 158 person.setMother(mother); 159 } 160 } 161 } 162 163 // Third pass: Load all marriages 164 MarriageDAO marriageDAO = new MarriageDAOImpl(connection, personCache); 165 List<Marriage> marriages = marriageDAO.findAll(); 166 167 for (Marriage marriage : marriages) { 168 // Add marriage to both spouses 169 marriage.getHusband().addMarriage(marriage); 170 marriage.getWife().addMarriage(marriage); 171 } 172 173 return familyTree; 174 } 175 176 /** 177 * Helper class to store parent IDs during loading. 178 */ 179 private static class ParentIds { 180 final int fatherId; 181 final int motherId; 182 183 ParentIds(int fatherId, int motherId) { 184 this.fatherId = fatherId; 185 this.motherId = motherId; 186 } 187 } 188} 189