001package org.itsallcode.jdbc.resultset.generic; 002 003import java.sql.ResultSet; 004import java.sql.SQLException; 005import java.util.*; 006 007import org.itsallcode.jdbc.UncheckedSQLException; 008import org.itsallcode.jdbc.dialect.DbDialect; 009import org.itsallcode.jdbc.resultset.*; 010 011/** 012 * This {@link ContextRowMapper} converts a row to the generic {@link Row} type. 013 * 014 * @param <T> generic row type 015 */ 016public class GenericRowMapper<T> implements RowMapper<T> { 017 private ResultSetRowBuilder rowBuilder; 018 private final ColumnValuesConverter<T> converter; 019 private final DbDialect dialect; 020 021 /** 022 * Create a new instance. 023 * 024 * @param dialect database dialect 025 * @param converter optionally converts each generic {@link Row} to a different 026 * type. 027 */ 028 public GenericRowMapper(final DbDialect dialect, final ColumnValuesConverter<T> converter) { 029 this.dialect = Objects.requireNonNull(dialect, "dialect"); 030 this.converter = Objects.requireNonNull(converter, "converter"); 031 } 032 033 @Override 034 public T mapRow(final ResultSet resultSet, final int rowNum) throws SQLException { 035 if (rowBuilder == null) { 036 rowBuilder = new ResultSetRowBuilder(SimpleMetaData.create(resultSet)); 037 } 038 final Row row = rowBuilder.buildRow(ConvertingResultSet.create(dialect, resultSet), rowNum); 039 return converter.mapRow(row); 040 } 041 042 private static final class ResultSetRowBuilder { 043 private final SimpleMetaData metadata; 044 045 private ResultSetRowBuilder(final SimpleMetaData metaData) { 046 this.metadata = metaData; 047 } 048 049 private Row buildRow(final ResultSet resultSet, final int rowIndex) { 050 final List<ColumnMetaData> columns = metadata.columns(); 051 final List<ColumnValue> fields = new ArrayList<>(columns.size()); 052 for (final ColumnMetaData column : columns) { 053 final ColumnValue field = getField(resultSet, column, rowIndex); 054 fields.add(field); 055 } 056 return new Row(rowIndex, columns, fields); 057 } 058 059 private static ColumnValue getField(final ResultSet resultSet, final ColumnMetaData column, 060 final int rowIndex) { 061 final Object value = getValue(resultSet, column, rowIndex); 062 return new ColumnValue(column.type(), value); 063 } 064 065 private static Object getValue(final ResultSet resultSet, final ColumnMetaData column, final int rowIndex) { 066 try { 067 return resultSet.getObject(column.columnIndex()); 068 } catch (final SQLException e) { 069 throw new UncheckedSQLException("Error extracting value for row " + rowIndex + " / column " + column, 070 e); 071 } 072 } 073 } 074 075 /** 076 * A simplified row mapper that gets a list of column values as input. 077 * 078 * @param <T> generic row type 079 */ 080 @FunctionalInterface 081 @SuppressWarnings("java:S1711") // Explicit interface instead of generic Function<> 082 public interface ColumnValuesConverter<T> { 083 /** 084 * Convert a single row. 085 * 086 * @param row column values 087 * @return converted object 088 */ 089 T mapRow(Row row); 090 } 091}