Skip to content

Commit

Permalink
Further code review
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenschlansker committed Jan 5, 2019
1 parent ad8f789 commit 2b8737d
Show file tree
Hide file tree
Showing 21 changed files with 319 additions and 148 deletions.
1 change: 1 addition & 0 deletions 4.0_TODO
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
* Require @HStore annotation for HSTORE mapping / binding (remove legacy support for unqualified
maps in Postgres plugin)
* Throwables throwOnlyException only exists because of the above <X extends Exception> issue, so clean that up too
* BeanMapper should throw if it can't find a nested type rather than default to `rs.getObject` (strictColumnMapping)
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
/**
* Inspect a {@link java.beans} style object and bind parameters
* based on each of its discovered properties.
*
* @deprecated this should never have been public API
*/
@Deprecated
public class BeanPropertyArguments extends MethodReturnValueNamedArgumentFinder {
private final PojoProperties<?> properties;

Expand All @@ -37,6 +40,15 @@ public BeanPropertyArguments(String prefix, Object bean) {
properties = BeanPropertiesFactory.propertiesFor(obj.getClass());
}

/**
* @param prefix an optional prefix (we insert a '.' as a separator)
* @param bean the bean to inspect and bind
*/
protected BeanPropertyArguments(String prefix, Object bean, PojoProperties<?> properties) {
super(prefix, bean);
this.properties = properties;
}

@Override
protected Optional<TypedValue> getValue(String name, StatementContext ctx) {
@SuppressWarnings("unchecked")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,57 +13,26 @@
*/
package org.jdbi.v3.core.argument.internal;

import java.util.Map;
import java.util.Optional;

import org.jdbi.v3.core.argument.BeanPropertyArguments;
import org.jdbi.v3.core.argument.NamedArgumentFinder;
import org.jdbi.v3.core.mapper.RowMapper;
import org.jdbi.v3.core.mapper.reflect.BeanMapper;
import org.jdbi.v3.core.mapper.reflect.internal.PojoProperties.PojoProperty;
import org.jdbi.v3.core.mapper.reflect.internal.PojoPropertiesFactories;
import org.jdbi.v3.core.statement.StatementContext;
import org.jdbi.v3.core.statement.UnableToCreateStatementException;

/**
* Inspect a object and bind parameters via {@link BeanMapper}'s properties.
* This class only exists to use the protected BeanPropertyArguments constructor.
* When we can remove that class from public API, this class will easily merge into it.
*/
public class PojoPropertyArguments extends MethodReturnValueNamedArgumentFinder {
private final Map<String, ? extends PojoProperty<?>> properties;
@SuppressWarnings("deprecation")
public class PojoPropertyArguments extends BeanPropertyArguments {
private final StatementContext ctx;

/**
* @param prefix an optional prefix (we insert a '.' as a separator)
* @param bean the bean to inspect and bind
* @param ctx the statement context
*/
public PojoPropertyArguments(String prefix, Object bean, StatementContext ctx) {
super(prefix, bean);
super(prefix, bean, ctx.getConfig(PojoPropertiesFactories.class).propertiesOf(bean.getClass()));
this.ctx = ctx;
final RowMapper<? extends Object> mapper = ctx.findRowMapperFor(bean.getClass())
.orElseThrow(() -> new UnableToCreateStatementException("Couldn't find registered property mapper for " + bean.getClass()));
if (!(mapper instanceof BeanMapper<?>)) {
throw new UnableToCreateStatementException("Registered mapper for " + bean.getClass() + " is not a property based mapper");
}
properties = ((BeanMapper<?>) mapper).getBeanInfo().getProperties();
}

@Override
protected Optional<TypedValue> getValue(String name, StatementContext ctx2) {
@SuppressWarnings("unchecked")
PojoProperty<Object> property = (PojoProperty<Object>) properties.get(name);
if (property == null) {
return Optional.empty();
}

return Optional.of(new TypedValue(property.getQualifiedType(), property.get(obj)));
}

@Override
protected NamedArgumentFinder getNestedArgumentFinder(Object o) {
return new PojoPropertyArguments(null, o, ctx);
}

@Override
public String toString() {
return "{lazy bean property arguments \"" + obj + "\"}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* 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.immutables;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Type;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;

import org.jdbi.v3.core.Jdbi;
import org.jdbi.v3.core.internal.JdbiOptionals;
import org.jdbi.v3.core.internal.exceptions.Unchecked;
import org.jdbi.v3.core.mapper.immutables.internal.ImmutablesMapperFactory;
import org.jdbi.v3.core.mapper.reflect.internal.ImmutablesPropertiesFactory;
import org.jdbi.v3.core.mapper.reflect.internal.PojoProperties;
import org.jdbi.v3.core.mapper.reflect.internal.PojoPropertiesFactories;
import org.jdbi.v3.core.spi.JdbiPlugin;
import org.jdbi.v3.meta.Beta;

/**
* Install support for an Immutables generated {@code Immutable} or {@code Modifiable} value type.
* Note that unlike most plugins, this plugin is expected to be installed multiple times, once for each value type you wish to handle.
*/
@Beta
public class ImmutablesPlugin<S> implements JdbiPlugin {
private final Class<S> spec;
private final Class<? extends S> impl;
private final Function<Type, ? extends PojoProperties<S>> properties;

private ImmutablesPlugin(Class<S> spec, Class<? extends S> impl, Function<Type, ? extends PojoProperties<S>> properties) {
this.spec = spec;
this.impl = impl;
this.properties = properties;
}

/**
* Register bean arguments and row mapping for an {@code Immutable*} value class, expecting the default generated class and builder names.
* @param spec the specification interface or abstract class
* @param <S> the specification class
* @return a plugin that configures type mapping for the given class
*/
public static <S> ImmutablesPlugin<S> forImmutable(Class<S> spec) {
final Class<? extends S> impl = classByPrefix("Immutable", spec);
return forImmutable(spec, impl, JdbiOptionals.findFirstPresent(
() -> nullaryMethodOf(spec, "builder"),
() -> nullaryMethodOf(impl, "builder"))
.orElseThrow(() -> new IllegalArgumentException("Neither " + spec + " nor " + impl + " have a 'builder' method")));
}

/**
* Register bean arguments and row mapping for an {@code Immutable*} value class, using a supplied implementation and builder.
* @param spec the specification interface or abstract class
* @param impl the generated implementation class
* @param builder a supplier of new Builder instances
* @param <S> the specification class
* @param <I> the implementation class
* @return a plugin that configures type mapping for the given class
*/
public static <S, I extends S> ImmutablesPlugin<S> forImmutable(Class<S> spec, Class<I> impl, Supplier<?> builder) {
return new ImmutablesPlugin<S>(spec, impl, ImmutablesPropertiesFactory.immutable(spec, builder));
}

/**
* Register bean arguments and row mapping for an {@code Modifiable*} value class, expecting the default generated class and public nullary constructor.
* @param spec the specification interface or abstract class
* @param <S> the specification class
* @return a plugin that configures type mapping for the given class
*/
public static <S> ImmutablesPlugin<S> forModifiable(Class<S> spec) {
final Class<? extends S> impl = classByPrefix("Modifiable", spec);
return forModifiable(spec, impl,
nullaryMethodOf(impl, "create")
.orElseGet(() -> constructorOf(impl)));
}

/**
* Register bean arguments and row mapping for an {@code Modifiable*} value class, using a supplied implementation and constructor.
* @param spec the specification interface or abstract class
* @param impl the modifiable class
* @param constructor a supplier of new Modifiable instances
* @param <S> the specification class
* @param <M> the modifiable class
* @return a plugin that configures type mapping for the given class
*/
public static <S, M extends S> ImmutablesPlugin<S> forModifiable(Class<S> spec, Class<M> impl, Supplier<?> constructor) {
return new ImmutablesPlugin<S>(spec, impl, ImmutablesPropertiesFactory.modifiable(spec, () -> impl.cast(constructor.get())));
}

private static Optional<Supplier<?>> nullaryMethodOf(Class<?> impl, String methodName) {
try {
return Optional.of(Unchecked.supplier(MethodHandles.lookup()
.unreflect(impl.getMethod(methodName))::invoke));
} catch (ReflectiveOperationException e) {
return Optional.empty();
}
}

@SuppressWarnings("unchecked")
private static <S> Supplier<S> constructorOf(Class<S> impl) {
try {
return (Supplier<S>) Unchecked.supplier(MethodHandles.lookup().findConstructor(impl, MethodType.methodType(void.class))::invoke);
} catch (ReflectiveOperationException e) {
throw new IllegalArgumentException("Couldn't find public constructor of " + impl, e);
}
}

private static <S, Sub extends S> Class<? extends S> classByPrefix(String prefix, Class<S> spec) {
final String implName = spec.getPackage().getName() + '.' + prefix + spec.getSimpleName();
try {
return Class.forName(implName).asSubclass(spec);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Couldn't locate default implementation class " + implName, e);
}
}

@Override
public void customizeJdbi(Jdbi jdbi) {
jdbi.getConfig(PojoPropertiesFactories.class).register(new Factory());
jdbi.registerRowMapper(new ImmutablesMapperFactory<S>(spec, impl, properties));
}

class Factory implements Function<Type, Optional<PojoProperties<?>>> {
@Override
public Optional<PojoProperties<?>> apply(Type t) {
if (t == spec || t == impl) {
return Optional.of(properties.apply(t));
}
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,48 +11,41 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jdbi.v3.core.mapper.reflect;
package org.jdbi.v3.core.mapper.immutables.internal;

import java.lang.reflect.Type;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;

import org.jdbi.v3.core.config.ConfigRegistry;
import org.jdbi.v3.core.generic.GenericTypes;
import org.jdbi.v3.core.mapper.RowMapper;
import org.jdbi.v3.core.mapper.RowMapperFactory;
import org.jdbi.v3.core.mapper.reflect.internal.ImmutablesPropertiesFactory;
import org.jdbi.v3.core.mapper.reflect.internal.PojoMapper;
import org.jdbi.v3.core.mapper.reflect.internal.PojoProperties;
import org.jdbi.v3.meta.Beta;

@Beta
/**
* Row mapper that inspects an {@code immutables}-style Immutable or Modifiable value class for properties
* and binds them in the style of {@link org.jdbi.v3.core.mapper.reflect.BeanMapper}.
* @param <T> the mapped value type
*/
public class ImmutablesMapperFactory<T> implements RowMapperFactory {

private final Class<T> defn;
private final Class<? extends T> impl;
private final Function<Type, PojoProperties<T>> properties;
private final Function<Type, ? extends PojoProperties<T>> properties;

private ImmutablesMapperFactory(Class<T> defn, Class<? extends T> impl, Function<Type, PojoProperties<T>> properties) {
public ImmutablesMapperFactory(Class<T> defn, Class<? extends T> impl, Function<Type, ? extends PojoProperties<T>> properties) {
this.defn = defn;
this.impl = impl;
this.properties = properties;
}

public static <T, I extends T, B> RowMapperFactory mapImmutable(Class<T> defn, Class<I> immutable, Supplier<B> builder) {
return new ImmutablesMapperFactory<>(defn, immutable, ImmutablesPropertiesFactory.immutable(defn, builder));
}

public static <T, M extends T> RowMapperFactory mapModifiable(Class<T> defn, Class<M> modifiable, Supplier<M> constructor) {
return new ImmutablesMapperFactory<>(defn, modifiable, ImmutablesPropertiesFactory.modifiable(defn, constructor));
}

@SuppressWarnings("unchecked")
@Override
public Optional<RowMapper<?>> build(Type type, ConfigRegistry config) {
Class<?> erasedType = GenericTypes.getErasedType(type);
if (defn.equals(erasedType) || impl.equals(erasedType)) {
return Optional.of(PropertiesMapper.of((Class<T>) erasedType, properties.apply(type)));
return Optional.of(new PojoMapper<>((Class<T>) erasedType, properties.apply(type), ""));
}
return Optional.empty();
}
Expand Down
20 changes: 11 additions & 9 deletions core/src/main/java/org/jdbi/v3/core/mapper/reflect/BeanMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.jdbi.v3.core.generic.GenericTypes;
import org.jdbi.v3.core.mapper.ColumnMapper;
import org.jdbi.v3.core.mapper.Nested;
import org.jdbi.v3.core.mapper.NoSuchMapperException;
import org.jdbi.v3.core.mapper.RowMapper;
import org.jdbi.v3.core.mapper.RowMapperFactory;
import org.jdbi.v3.core.mapper.SingleColumnMapper;
Expand All @@ -32,8 +33,6 @@
import org.jdbi.v3.core.mapper.reflect.internal.PojoProperties.PojoBuilder;
import org.jdbi.v3.core.mapper.reflect.internal.PojoProperties.PojoProperty;
import org.jdbi.v3.core.statement.StatementContext;
import org.jdbi.v3.meta.Beta;

import static org.jdbi.v3.core.mapper.reflect.ReflectionMapperUtil.anyColumnsStartWithPrefix;
import static org.jdbi.v3.core.mapper.reflect.ReflectionMapperUtil.findColumnIndex;
import static org.jdbi.v3.core.mapper.reflect.ReflectionMapperUtil.getColumnNames;
Expand All @@ -46,7 +45,10 @@
* properties.
*
* The mapped class must have a default constructor.
*
* @deprecated this class should not be public API, use {@link org.jdbi.v3.core.statement.SqlStatement#bindBean(Object)} instead.
*/
@Deprecated
public class BeanMapper<T> implements RowMapper<T> {
static final String DEFAULT_PREFIX = "";

Expand Down Expand Up @@ -100,6 +102,7 @@ public static <T> RowMapper<T> of(Class<T> type, String prefix) {
return new BeanMapper<>(type, prefix);
}

protected boolean strictColumnMapping; // this should be default (only?) behavior but that's a breaking change
protected final Class<T> type;
protected final String prefix;
private final PojoProperties<T> properties;
Expand All @@ -110,7 +113,7 @@ public static <T> RowMapper<T> of(Class<T> type, String prefix) {
this(type, (PojoProperties<T>) BeanPropertiesFactory.propertiesFor(type), prefix);
}

BeanMapper(Class<T> type, PojoProperties<T> properties, String prefix) {
protected BeanMapper(Class<T> type, PojoProperties<T> properties, String prefix) {
this.type = type;
this.properties = properties;
this.prefix = prefix.toLowerCase();
Expand Down Expand Up @@ -198,15 +201,14 @@ private Optional<RowMapper<T>> specialize0(StatementContext ctx,
});
}

ColumnMapper<?> defaultColumnMapper(PojoProperty<T> property) {
private ColumnMapper<?> defaultColumnMapper(PojoProperty<T> property) {
if (strictColumnMapping) {
throw new NoSuchMapperException(String.format(
"Couldn't find mapper for property '%s' of type '%s' from %s", property.getName(), property.getQualifiedType(), type));
}
return (r, n, c) -> r.getObject(n);
}

@Beta
public PojoProperties<T> getBeanInfo() {
return properties;
}

private String getName(PojoProperty<T> property) {
return property.getAnnotation(ColumnName.class)
.map(ColumnName::value)
Expand Down
Loading

0 comments on commit 2b8737d

Please sign in to comment.