package org.jdbi.v3.core.mapper.reflect.internal;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jdbi.v3.core.config.ConfigRegistry;
import org.jdbi.v3.core.config.JdbiCache;
import org.jdbi.v3.core.config.JdbiCaches;
import org.jdbi.v3.core.generic.GenericTypes;
import org.jdbi.v3.core.mapper.reflect.internal.BeanPropertiesFactory.BeanPojoProperties.BeanPojoProperty;
import org.jdbi.v3.core.mapper.reflect.internal.PojoProperties.PojoProperty;
import org.jdbi.v3.core.qualifier.QualifiedType;
import org.jdbi.v3.core.qualifier.Qualifiers;
import org.jdbi.v3.core.statement.UnableToCreateStatementException;
public class BeanPropertiesFactory {
private static final JdbiCache<Type, Map<String, BeanPojoProperty<?>>> PROPERTY_CACHE =
JdbiCaches.declare(BeanPropertiesFactory::getProperties0);
private static final String TYPE_NOT_INSTANTIABLE =
"A bean, %s, was mapped which was not instantiable";
private static final String MISSING_SETTER =
"No appropriate method to write property %s";
private static final String SETTER_NOT_ACCESSIBLE =
"Unable to access setter for property, %s";
private static final String INVOCATION_TARGET_EXCEPTION =
"Invocation target exception trying to invoker setter for the %s property";
private static final String REFLECTION_ILLEGAL_ARGUMENT_EXCEPTION =
"Write method of %s for property %s is not compatible with the value passed";
private BeanPropertiesFactory() {}
public static PojoProperties<?> propertiesFor(Type t, ConfigRegistry config) {
return new BeanPojoProperties<>(t, config);
}
private static boolean shouldSeeProperty(PropertyDescriptor pd) {
final Method read = pd.getReadMethod();
return read == null || read.getDeclaringClass() != Object.class;
}
private static Map<String, BeanPojoProperty<?>> getProperties0(Type t) {
try {
return Arrays.stream(Introspector.getBeanInfo(GenericTypes.getErasedType(t)).getPropertyDescriptors())
.filter(BeanPropertiesFactory::shouldSeeProperty)
.map(BeanPojoProperty::new)
.collect(Collectors.toMap(PojoProperty::getName, Function.identity()));
} catch (IntrospectionException e) {
throw new IllegalArgumentException("Failed to inspect bean " + t, e);
}
}
static class BeanPojoProperties<T> extends PojoProperties<T> {
private final ConfigRegistry config;
BeanPojoProperties(Type type, ConfigRegistry config) {
super(type);
this.config = config;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Map<String, BeanPojoProperty<T>> getProperties() {
return (Map) PROPERTY_CACHE.get(getType(), config);
}
@SuppressWarnings("unchecked")
@Override
public PojoBuilder<T> create() {
final Class<?> type = GenericTypes.getErasedType(getType());
final T instance;
try {
instance = (T) type.newInstance();
} catch (Exception e) {
throw new IllegalArgumentException(String.format(TYPE_NOT_INSTANTIABLE, type.getName()), e);
}
return new PojoBuilder<T>() {
@Override
public void set(String property, Object value) {
final BeanPojoProperty<T> prop = getProperties().get(property);
try {
Method writeMethod = prop.descriptor.getWriteMethod();
if (writeMethod == null) {
throw new IllegalArgumentException(String.format(MISSING_SETTER, property));
}
writeMethod.invoke(instance, value);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(String.format(SETTER_NOT_ACCESSIBLE, property), e);
} catch (InvocationTargetException e) {
throw new IllegalArgumentException(String.format(INVOCATION_TARGET_EXCEPTION, property), e);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(String.format(REFLECTION_ILLEGAL_ARGUMENT_EXCEPTION,
prop.getQualifiedType(), prop.getName()), e);
}
}
@Override
public T build() {
return instance;
}
};
}
static class BeanPojoProperty<T> implements PojoProperty<T> {
final PropertyDescriptor descriptor;
BeanPojoProperty(PropertyDescriptor property) {
this.descriptor = property;
}
@Override
public String getName() {
return descriptor.getName();
}
@Override
public QualifiedType<?> getQualifiedType() {
Parameter setterParam = Optional.ofNullable(descriptor.getWriteMethod())
.map(m -> m.getParameterCount() > 0 ? m.getParameters()[0] : null)
.orElse(null);
return QualifiedType.of(
Optional.ofNullable(descriptor.getReadMethod())
.map(Method::getGenericReturnType)
.orElseGet(() -> descriptor.getWriteMethod().getGenericParameterTypes()[0]))
.withAnnotations(
new Qualifiers().findFor(descriptor.getReadMethod(), descriptor.getWriteMethod(), setterParam));
}
@Override
public <A extends Annotation> Optional<A> getAnnotation(Class<A> anno) {
return Stream.of(descriptor.getReadMethod(), descriptor.getWriteMethod())
.filter(Objects::nonNull)
.map(m -> m.getAnnotation(anno))
.filter(Objects::nonNull)
.findFirst();
}
@Override
public Object get(T pojo) {
Method getter = descriptor.getReadMethod();
if (getter == null) {
throw new UnableToCreateStatementException(String.format("No getter method found for "
+ "bean property [%s] on [%s]",
getName(), pojo));
}
try {
return getter.invoke(pojo);
} catch (IllegalAccessException e) {
throw new UnableToCreateStatementException(String.format("Access exception invoking "
+ "method [%s] on [%s]",
getter.getName(), pojo), e);
} catch (InvocationTargetException e) {
throw new UnableToCreateStatementException(String.format("Invocation target exception invoking "
+ "method [%s] on [%s]",
getter.getName(), pojo), e);
}
}
}
}
}