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