001package edu.pdx.cs.joy.family; 002 003import java.sql.*; 004import java.util.ArrayList; 005import java.util.List; 006 007/** 008 * Data Access Object implementation for managing Person entities in the database. 009 */ 010public class PersonDAOImpl implements PersonDAO { 011 012 private final Connection connection; 013 014 /** 015 * Creates a new PersonDAOImpl with the specified database connection. 016 * 017 * @param connection the database connection to use 018 */ 019 public PersonDAOImpl(Connection connection) { 020 this.connection = connection; 021 } 022 023 /** 024 * Drops the persons table from the database if it exists. 025 * 026 * @param connection the database connection to use 027 * @throws SQLException if a database error occurs 028 */ 029 public static void dropTable(Connection connection) throws SQLException { 030 try (Statement statement = connection.createStatement()) { 031 statement.execute("DROP TABLE IF EXISTS persons"); 032 } 033 } 034 035 /** 036 * Creates the persons table in the database. 037 * 038 * @param connection the database connection to use 039 * @throws SQLException if a database error occurs 040 */ 041 public static void createTable(Connection connection) throws SQLException { 042 try (Statement statement = connection.createStatement()) { 043 statement.execute( 044 "CREATE TABLE IF NOT EXISTS persons (" + 045 " id INTEGER PRIMARY KEY," + 046 " gender VARCHAR(10) NOT NULL," + 047 " first_name VARCHAR(255)," + 048 " middle_name VARCHAR(255)," + 049 " last_name VARCHAR(255)," + 050 " father_id INTEGER," + 051 " mother_id INTEGER," + 052 " date_of_birth TIMESTAMP," + 053 " date_of_death TIMESTAMP," + 054 " FOREIGN KEY (father_id) REFERENCES persons(id)," + 055 " FOREIGN KEY (mother_id) REFERENCES persons(id)" + 056 ")" 057 ); 058 } 059 } 060 061 /** 062 * Saves a person to the database. 063 * 064 * @param person the person to save 065 * @throws SQLException if a database error occurs 066 */ 067 @Override 068 public void save(Person person) throws SQLException { 069 String sql = "INSERT INTO persons (id, gender, first_name, middle_name, last_name, " + 070 "father_id, mother_id, date_of_birth, date_of_death) " + 071 "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"; 072 073 try (PreparedStatement statement = connection.prepareStatement(sql)) { 074 statement.setInt(1, person.getId()); 075 statement.setString(2, person.getGender().name()); 076 statement.setString(3, person.getFirstName()); 077 statement.setString(4, person.getMiddleName()); 078 statement.setString(5, person.getLastName()); 079 080 if (person.getFatherId() == Person.UNKNOWN) { 081 statement.setNull(6, Types.INTEGER); 082 } else { 083 statement.setInt(6, person.getFatherId()); 084 } 085 086 if (person.getMotherId() == Person.UNKNOWN) { 087 statement.setNull(7, Types.INTEGER); 088 } else { 089 statement.setInt(7, person.getMotherId()); 090 } 091 092 if (person.getDateOfBirth() == null) { 093 statement.setNull(8, Types.TIMESTAMP); 094 } else { 095 statement.setTimestamp(8, new Timestamp(person.getDateOfBirth().getTime())); 096 } 097 098 if (person.getDateOfDeath() == null) { 099 statement.setNull(9, Types.TIMESTAMP); 100 } else { 101 statement.setTimestamp(9, new Timestamp(person.getDateOfDeath().getTime())); 102 } 103 104 statement.executeUpdate(); 105 } 106 } 107 108 /** 109 * Finds a person by their ID. 110 * 111 * @param id the ID to search for 112 * @return the person with the given ID, or null if not found 113 * @throws SQLException if a database error occurs 114 */ 115 @Override 116 public Person findById(int id) throws SQLException { 117 String sql = "SELECT id, gender, first_name, middle_name, last_name, " + 118 "father_id, mother_id, date_of_birth, date_of_death " + 119 "FROM persons WHERE id = ?"; 120 121 try (PreparedStatement statement = connection.prepareStatement(sql)) { 122 statement.setInt(1, id); 123 124 try (ResultSet resultSet = statement.executeQuery()) { 125 if (resultSet.next()) { 126 return extractPersonFromResultSet(resultSet); 127 } 128 } 129 } 130 131 return null; 132 } 133 134 /** 135 * Finds all persons in the database. 136 * 137 * @return a list of all persons 138 * @throws SQLException if a database error occurs 139 */ 140 @Override 141 public List<Person> findAll() throws SQLException { 142 List<Person> persons = new ArrayList<>(); 143 String sql = "SELECT id, gender, first_name, middle_name, last_name, " + 144 "father_id, mother_id, date_of_birth, date_of_death " + 145 "FROM persons ORDER BY id"; 146 147 try (Statement statement = connection.createStatement(); 148 ResultSet resultSet = statement.executeQuery(sql)) { 149 while (resultSet.next()) { 150 persons.add(extractPersonFromResultSet(resultSet)); 151 } 152 } 153 154 return persons; 155 } 156 157 /** 158 * Updates an existing person in the database. 159 * 160 * @param person the person to update 161 * @throws SQLException if a database error occurs 162 */ 163 @Override 164 public void update(Person person) throws SQLException { 165 String sql = "UPDATE persons SET gender = ?, first_name = ?, middle_name = ?, " + 166 "last_name = ?, father_id = ?, mother_id = ?, " + 167 "date_of_birth = ?, date_of_death = ? WHERE id = ?"; 168 169 try (PreparedStatement statement = connection.prepareStatement(sql)) { 170 statement.setString(1, person.getGender().name()); 171 statement.setString(2, person.getFirstName()); 172 statement.setString(3, person.getMiddleName()); 173 statement.setString(4, person.getLastName()); 174 175 if (person.getFatherId() == Person.UNKNOWN) { 176 statement.setNull(5, Types.INTEGER); 177 } else { 178 statement.setInt(5, person.getFatherId()); 179 } 180 181 if (person.getMotherId() == Person.UNKNOWN) { 182 statement.setNull(6, Types.INTEGER); 183 } else { 184 statement.setInt(6, person.getMotherId()); 185 } 186 187 if (person.getDateOfBirth() == null) { 188 statement.setNull(7, Types.TIMESTAMP); 189 } else { 190 statement.setTimestamp(7, new Timestamp(person.getDateOfBirth().getTime())); 191 } 192 193 if (person.getDateOfDeath() == null) { 194 statement.setNull(8, Types.TIMESTAMP); 195 } else { 196 statement.setTimestamp(8, new Timestamp(person.getDateOfDeath().getTime())); 197 } 198 199 statement.setInt(9, person.getId()); 200 201 int rowsAffected = statement.executeUpdate(); 202 if (rowsAffected == 0) { 203 throw new SQLException("Update failed, no person found with ID: " + person.getId()); 204 } 205 } 206 } 207 208 /** 209 * Deletes a person from the database by ID. 210 * 211 * @param id the ID of the person to delete 212 * @throws SQLException if a database error occurs 213 */ 214 @Override 215 public void delete(int id) throws SQLException { 216 String sql = "DELETE FROM persons WHERE id = ?"; 217 218 try (PreparedStatement statement = connection.prepareStatement(sql)) { 219 statement.setInt(1, id); 220 int rowsAffected = statement.executeUpdate(); 221 222 if (rowsAffected == 0) { 223 throw new SQLException("Delete failed, no person found with ID: " + id); 224 } 225 } 226 } 227 228 /** 229 * Extracts a Person object from the current row of a ResultSet. 230 * Note: This creates a Person without resolving parent references. 231 * Parent IDs are stored in the database but parent Person objects 232 * must be resolved by the caller. 233 * 234 * @param resultSet the result set positioned at a person row 235 * @return a Person object with data from the result set 236 * @throws SQLException if a database error occurs 237 */ 238 private Person extractPersonFromResultSet(ResultSet resultSet) throws SQLException { 239 int id = resultSet.getInt("id"); 240 String genderStr = resultSet.getString("gender"); 241 Person.Gender gender = Person.Gender.valueOf(genderStr); 242 243 Person person = new Person(id, gender); 244 person.setFirstName(resultSet.getString("first_name")); 245 person.setMiddleName(resultSet.getString("middle_name")); 246 person.setLastName(resultSet.getString("last_name")); 247 248 // Note: Parent relationships will be resolved by FamilyTreeDAO 249 // We cannot call package-protected setFatherId/setMotherId from here 250 251 Timestamp dob = resultSet.getTimestamp("date_of_birth"); 252 if (dob != null) { 253 person.setDateOfBirth(new java.util.Date(dob.getTime())); 254 } 255 256 Timestamp dod = resultSet.getTimestamp("date_of_death"); 257 if (dod != null) { 258 person.setDateOfDeath(new java.util.Date(dod.getTime())); 259 } 260 261 return person; 262 } 263 264 /** 265 * Helper method to get father ID from result set. 266 * Package-protected to allow FamilyTreeDAO to resolve relationships. 267 * 268 * @param resultSet the result set 269 * @return the father ID or Person.UNKNOWN if null 270 * @throws SQLException if a database error occurs 271 */ 272 int getFatherIdFromResultSet(ResultSet resultSet) throws SQLException { 273 int fatherId = resultSet.getInt("father_id"); 274 return resultSet.wasNull() ? Person.UNKNOWN : fatherId; 275 } 276 277 /** 278 * Helper method to get mother ID from result set. 279 * Package-protected to allow FamilyTreeDAO to resolve relationships. 280 * 281 * @param resultSet the result set 282 * @return the mother ID or Person.UNKNOWN if null 283 * @throws SQLException if a database error occurs 284 */ 285 int getMotherIdFromResultSet(ResultSet resultSet) throws SQLException { 286 int motherId = resultSet.getInt("mother_id"); 287 return resultSet.wasNull() ? Person.UNKNOWN : motherId; 288 } 289} 290