/*
* 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.argument;
import static org.jdbi.v3.core.generic.GenericTypes.findGenericParameter;
import static org.jdbi.v3.core.generic.GenericTypes.getErasedType;
import java.lang.reflect.Array;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.URI;
import java.net.URL;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.IdentityHashMap;
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.SqlStatement;
import org.jdbi.v3.core.statement.StatementContext;
/**
* The BuiltInArgumentFactory provides instances of {@link Argument} for
* many core Java types. Generally you should not need to use this
* class directly, but instead should bind your object with the
* {@link SqlStatement} convenience methods.
*/
public class BuiltInArgumentFactory implements ArgumentFactory {
// Care for the initialization order here, there's a fair number of statics. Create the builders before the factory instance.
private static final ArgBuilder<String> STR_BUILDER = v -> new BuiltInArgument<>(String.class, Types.VARCHAR, PreparedStatement::setString, v);
private static final Map<Class<?>, ArgBuilder<?>> BUILDERS = createInternalBuilders();
public static final ArgumentFactory INSTANCE = new BuiltInArgumentFactory();
private static <T> void register(Map<Class<?>, ArgBuilder<?>> map, Class<T> klass, int type, StatementBinder<T> binder) {
register(map, klass, v -> new BuiltInArgument<>(klass, type, binder, v));
}
private static <T> void register(Map<Class<?>, ArgBuilder<?>> map, Class<T> klass, ArgBuilder<T> builder) {
map.put(klass, builder);
}
/** Create a binder which calls String.valueOf on its argument and then delegates to another binder. */
private static <T> StatementBinder<T> stringifyValue(StatementBinder<String> real) {
return (p, i, v) -> real.bind(p, i, String.valueOf(v));
}
private static Map<Class<?>, ArgBuilder<?>> createInternalBuilders() {
final Map<Class<?>, ArgBuilder<?>> map = new IdentityHashMap<>();
register(map, BigDecimal.class, Types.NUMERIC, PreparedStatement::setBigDecimal);
register(map, Blob.class, Types.BLOB, PreparedStatement::setBlob);
register(map, Boolean.class, Types.BOOLEAN, PreparedStatement::setBoolean);
register(map, boolean.class, Types.BOOLEAN, PreparedStatement::setBoolean);
register(map, Byte.class, Types.TINYINT, PreparedStatement::setByte);
register(map, byte.class, Types.TINYINT, PreparedStatement::setByte);
register(map, byte[].class, Types.VARBINARY, PreparedStatement::setBytes);
register(map, Character.class, Types.CHAR, stringifyValue(PreparedStatement::setString));
register(map, char.class, Types.CHAR, stringifyValue(PreparedStatement::setString));
register(map, Clob.class, Types.CLOB, PreparedStatement::setClob);
register(map, Double.class, Types.DOUBLE, PreparedStatement::setDouble);
register(map, double.class, Types.DOUBLE, PreparedStatement::setDouble);
register(map, Float.class, Types.FLOAT, PreparedStatement::setFloat);
register(map, float.class, Types.FLOAT, PreparedStatement::setFloat);
register(map, Inet4Address.class, Types.OTHER, (p, i, v) -> p.setString(i, v.getHostAddress()));
register(map, Inet6Address.class, Types.OTHER, (p, i, v) -> p.setString(i, v.getHostAddress()));
register(map, Integer.class, Types.INTEGER, PreparedStatement::setInt);
register(map, int.class, Types.INTEGER, PreparedStatement::setInt);
register(map, java.util.Date.class, Types.TIMESTAMP, (p, i, v) -> p.setTimestamp(i, new Timestamp(v.getTime())));
register(map, Long.class, Types.INTEGER, PreparedStatement::setLong);
register(map, long.class, Types.INTEGER, PreparedStatement::setLong);
register(map, Short.class, Types.SMALLINT, PreparedStatement::setShort);
register(map, short.class, Types.SMALLINT, PreparedStatement::setShort);
register(map, java.sql.Date.class, Types.DATE, PreparedStatement::setDate);
register(map, String.class, STR_BUILDER);
register(map, Time.class, Types.TIME, PreparedStatement::setTime);
register(map, Timestamp.class, Types.TIMESTAMP, PreparedStatement::setTimestamp);
register(map, URL.class, Types.DATALINK, PreparedStatement::setURL);
register(map, URI.class, Types.VARCHAR, stringifyValue(PreparedStatement::setString));
register(map, UUID.class, Types.VARCHAR, PreparedStatement::setObject);
register(map, Instant.class, Types.TIMESTAMP, (p, i, v) -> p.setTimestamp(i, Timestamp.from(v)));
register(map, LocalDate.class, Types.TIMESTAMP, (p, i, v) -> p.setTimestamp(i, Timestamp.valueOf(v.atStartOfDay())));
register(map, LocalDateTime.class, Types.TIMESTAMP, (p, i, v) -> p.setTimestamp(i, Timestamp.valueOf(v)));
register(map, OffsetDateTime.class, Types.TIMESTAMP, (p, i, v) -> p.setTimestamp(i, Timestamp.from(v.toInstant())));
register(map, ZonedDateTime.class, Types.TIMESTAMP, (p, i, v) -> p.setTimestamp(i, Timestamp.from(v.toInstant())));
register(map, LocalTime.class, Types.TIME, (p, i, v) -> p.setTime(i, Time.valueOf(v)));
return Collections.unmodifiableMap(map);
}
@Override
@SuppressWarnings("unchecked")
public Optional<Argument> build(Type expectedType, Object value, ConfigRegistry config)
{
Class<?> expectedClass = getErasedType(expectedType);
if (value != null && expectedClass == Object.class) {
expectedClass = value.getClass();
}
@SuppressWarnings("rawtypes")
ArgBuilder v = BUILDERS.get(expectedClass);
if (v != null) {
return Optional.of(v.build(value));
}
// Enums must be bound as VARCHAR.
if (value instanceof Enum) {
Enum<?> enumValue = (Enum<?>) value;
return Optional.of(STR_BUILDER.build(enumValue.name()));
}
if (value instanceof Optional) {
Object nestedValue = ((Optional<?>)value).orElse(null);
Type nestedType = findOptionalType(expectedType, nestedValue);
return config.get(Arguments.class).findFor(nestedType, nestedValue);
}
return value == null
? Optional.of(config.get(Arguments.class).getUntypedNullArgument())
: Optional.empty();
}
private Type findOptionalType(Type wrapperType, Object nestedValue) {
if (getErasedType(wrapperType).equals(Optional.class)) {
Optional<Type> nestedType = findGenericParameter(wrapperType, Optional.class);
if (nestedType.isPresent()) {
return nestedType.get();
}
}
return nestedValue == null ? Object.class : nestedValue.getClass();
}
@FunctionalInterface
interface StatementBinder<T> {
void bind(PreparedStatement p, int index, T value) throws SQLException;
}
@FunctionalInterface
interface ArgBuilder<T> {
Argument build(final T value);
}
static final class BuiltInArgument<T> implements Argument {
private final T value;
private final Class<T> klass;
private final int type;
private final StatementBinder<T> binder;
private BuiltInArgument(Class<T> klass, int type, StatementBinder<T> binder, T value) {
this.binder = binder;
this.klass = klass;
this.type = type;
this.value = value;
}
@Override
public void apply(int position, PreparedStatement statement, StatementContext ctx) throws SQLException {
if (value == null) {
statement.setNull(position, type);
return;
}
binder.bind(statement, position, value);
}
@Override
public String toString() {
if (klass.isArray()) {
return String.format("{array of %s length %s}", klass.getComponentType(), Array.getLength(value));
}
return String.valueOf(value);
}
StatementBinder<T> getStatementBinder() {
return binder;
}
}
}