/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jdbi.v3.core.mapper;
import static org.jdbi.v3.core.generic.GenericTypes.getErasedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.jdbi.v3.core.config.ConfigRegistry;
import org.jdbi.v3.core.statement.StatementContext;
/**
* Column mapper factory which knows how to map JDBC-recognized types, along with some other well-known types
* from the JDK.
*/
public class BuiltInMapperFactory implements ColumnMapperFactory {
private static final Map<Class<?>, ColumnMapper<?>> mappers = new HashMap<>();
static {
mappers.put(boolean.class, primitiveMapper(ResultSet::getBoolean));
mappers.put(byte.class, primitiveMapper(ResultSet::getByte));
mappers.put(char.class, primitiveMapper(BuiltInMapperFactory::getChar));
mappers.put(short.class, primitiveMapper(ResultSet::getShort));
mappers.put(int.class, primitiveMapper(ResultSet::getInt));
mappers.put(long.class, primitiveMapper(ResultSet::getLong));
mappers.put(float.class, primitiveMapper(ResultSet::getFloat));
mappers.put(double.class, primitiveMapper(ResultSet::getDouble));
mappers.put(Boolean.class, referenceMapper(ResultSet::getBoolean));
mappers.put(Byte.class, referenceMapper(ResultSet::getByte));
mappers.put(Character.class, referenceMapper(BuiltInMapperFactory::getCharacter));
mappers.put(Short.class, referenceMapper(ResultSet::getShort));
mappers.put(Integer.class, referenceMapper(ResultSet::getInt));
mappers.put(Long.class, referenceMapper(ResultSet::getLong));
mappers.put(Float.class, referenceMapper(ResultSet::getFloat));
mappers.put(Double.class, referenceMapper(ResultSet::getDouble));
mappers.put(BigDecimal.class, referenceMapper(ResultSet::getBigDecimal));
mappers.put(String.class, referenceMapper(ResultSet::getString));
mappers.put(byte[].class, referenceMapper(ResultSet::getBytes));
mappers.put(Timestamp.class, referenceMapper(ResultSet::getTimestamp));
mappers.put(InetAddress.class, BuiltInMapperFactory::getInetAddress);
mappers.put(URL.class, referenceMapper(ResultSet::getURL));
mappers.put(URI.class, referenceMapper(BuiltInMapperFactory::getURI));
mappers.put(UUID.class, BuiltInMapperFactory::getUUID);
mappers.put(Instant.class, referenceMapper(BuiltInMapperFactory::getInstant));
mappers.put(LocalDate.class, referenceMapper(BuiltInMapperFactory::getLocalDate));
mappers.put(LocalDateTime.class, referenceMapper(BuiltInMapperFactory::getLocalDateTime));
mappers.put(OffsetDateTime.class, referenceMapper(BuiltInMapperFactory::getOffsetDateTime));
mappers.put(ZonedDateTime.class, referenceMapper(BuiltInMapperFactory::getZonedDateTime));
mappers.put(LocalTime.class, referenceMapper(BuiltInMapperFactory::getLocalTime));
}
@Override
public Optional<ColumnMapper<?>> build(Type type, ConfigRegistry config) {
Class<?> rawType = getErasedType(type);
if (rawType.isEnum()) {
return Optional.of(EnumMapper.byName(rawType.asSubclass(Enum.class)));
}
return Optional.ofNullable(mappers.get(rawType));
}
@FunctionalInterface
interface ColumnGetter<T> {
T get(ResultSet rs, int i) throws SQLException;
}
private static <T> ColumnMapper<T> primitiveMapper(ColumnGetter<T> getter) {
return (r, i, ctx) -> getter.get(r, i);
}
private static <T> ColumnMapper<T> referenceMapper(ColumnGetter<T> getter) {
return (r, i, ctx) -> {
T value = getter.get(r, i);
return r.wasNull() ? null : value;
};
}
private static char getChar(ResultSet r, int i) throws SQLException {
Character character = getCharacter(r, i);
return character == null ? '\000' : character;
}
private static Character getCharacter(ResultSet r, int i) throws SQLException {
String s = r.getString(i);
if (s != null && !s.isEmpty()) {
return s.charAt(0);
}
return null;
}
private static URI getURI(ResultSet r, int i) throws SQLException {
String s = r.getString(i);
try {
return (s != null) ? (new URI(s)) : null;
} catch(URISyntaxException e) {
throw new SQLException("Failed to convert data to URI", e);
}
}
private static UUID getUUID(ResultSet r, int i, StatementContext ctx) throws SQLException {
String s = r.getString(i);
if (s == null) {
return null;
}
return UUID.fromString(s);
}
private static Instant getInstant(ResultSet r, int i) throws SQLException {
Timestamp ts = r.getTimestamp(i);
return ts == null ? null : ts.toInstant();
}
private static LocalDate getLocalDate(ResultSet r, int i) throws SQLException {
Timestamp ts = r.getTimestamp(i);
return ts == null ? null : ts.toLocalDateTime().toLocalDate();
}
private static LocalDateTime getLocalDateTime(ResultSet r, int i) throws SQLException {
Timestamp ts = r.getTimestamp(i);
return ts == null ? null : ts.toLocalDateTime();
}
private static OffsetDateTime getOffsetDateTime(ResultSet r, int i) throws SQLException {
Timestamp ts = r.getTimestamp(i);
return ts == null ? null : OffsetDateTime.ofInstant(ts.toInstant(), ZoneId.systemDefault());
}
private static ZonedDateTime getZonedDateTime(ResultSet r, int i) throws SQLException {
Timestamp ts = r.getTimestamp(i);
return ts == null ? null : ZonedDateTime.ofInstant(ts.toInstant(), ZoneId.systemDefault());
}
private static LocalTime getLocalTime(ResultSet r, int i) throws SQLException {
Time time = r.getTime(i);
return time == null ? null : time.toLocalTime();
}
private static InetAddress getInetAddress(ResultSet r, int i, StatementContext ctx) throws SQLException {
String hostname = r.getString(i);
try {
return hostname == null ? null : InetAddress.getByName(hostname);
} catch (UnknownHostException e) {
throw new MappingException("Could not map InetAddress", e);
}
}
}