ResultSetRowTransformer.java
package org.codefilarete.stalactite.sql.result;
import java.io.Serializable;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
import org.danekja.java.util.function.serializable.SerializableFunction;
import org.danekja.java.util.function.serializable.SerializableSupplier;
import org.codefilarete.tool.bean.Factory;
import org.codefilarete.stalactite.sql.statement.binder.ResultSetReader;
import static org.codefilarete.tool.Nullable.nullable;
/**
* A class aimed at creating flat (no graph) beans from a {@link ResultSet} row.
* Will read a main/root column that determines if a bean can be created from it (is null ?), then applies some more "bean filler" that are
* defined with {@link ColumnConsumer}.
* Instances of this class can be reused over multiple {@link ResultSet} (supposed to have same columns).
* They can also be adapted to other {@link ResultSet}s that haven't the exact same column names by duplicating them with {@link #copyWithAliases(Function)}.
* Moreover they can also be cloned to another type of bean which uses the same column names with {@link #copyFor(Class, SerializableFunction)}.
*
* @param <I> the type of the bean key (Input)
* @param <C> the type of the bean
*
* @author Guillaume Mary
*/
public class ResultSetRowTransformer<C, I> implements ResultSetTransformer<C, I>, ResultSetRowAssembler<C> {
private final BeanFactory<C> beanFactory;
private final Class<C> beanType;
private final Set<ColumnConsumer<C, Object>> consumers = new HashSet<>();
private final Map<BeanRelationFixer<C, Object>, ResultSetRowTransformer<Object, Object>> relations = new HashMap<>();
/**
* Constructor focused on simple cases where beans are built only from one column key.
* Prefer {@link #ResultSetRowTransformer(Class, ColumnReader, SerializableFunction)} for more general purpose cases (multiple columns key)
*
* @param columnName the name of the column that contains bean key
* @param reader object to ease column reading, indicates column type
* @param beanFactory the bean creator, bean key will be passed as argument. Not called if bean key is null (no instantiation needed)
*/
public ResultSetRowTransformer(Class<C> beanType, String columnName, ResultSetReader<I> reader, SerializableFunction<I, C> beanFactory) {
this(beanType, new IdentifierArgBeanFactory<>(new SingleColumnReader<>(columnName, reader), beanFactory));
}
/**
* Constructor with main and mandatory arguments
*
* @param beanType type of built instances
* @param reader object to ease column reading, indicates column type
* @param beanFactory the bean creator, bean key will be passed as argument. Not called if bean key is null (no instantiation needed)
*/
public ResultSetRowTransformer(Class<C> beanType, ColumnReader<I> reader, SerializableFunction<I, C> beanFactory) {
this(beanType, new IdentifierArgBeanFactory<>(reader, beanFactory));
}
/**
* Constructor with main and mandatory arguments.
* With this constructor an instance per {@link ResultSet} row will be created since there's no mean to distinguish a row instance from another
* because no key reader is given
*
* @param beanType type of built instances
* @param beanFactory the bean creator
*/
public ResultSetRowTransformer(Class<C> beanType, SerializableSupplier<C> beanFactory) {
this(beanType, new NoIdentifierBeanFactory<>(beanFactory));
}
/**
* For internal usage
* @param beanType type of built instances
* @param beanFactory bean factory to use as constructor
*/
ResultSetRowTransformer(Class<C> beanType, BeanFactory<C> beanFactory) {
this.beanType = beanType;
this.beanFactory = beanFactory;
}
public Class<C> getBeanType() {
return beanType;
}
public BeanFactory<C> getBeanFactory() {
return beanFactory;
}
/**
* Gives {@link ColumnConsumer}s of this instances
*
* @return not null
*/
public Set<ColumnConsumer<C, Object>> getConsumers() {
return consumers;
}
public Map<BeanRelationFixer<C, Object>, ResultSetRowTransformer<Object, Object>> getRelations() {
return relations;
}
/**
* Defines a complementary column that will be mapped on a bean property.
* Null values will be passed to the consumer, hence the property mapper must be "null-value proof".
*
* @param columnConsumer the object that will do reading and mapping
* @return this
*/
@Override
public <O> ResultSetRowTransformer<C, I> add(ColumnConsumer<C, O> columnConsumer) {
this.consumers.add((ColumnConsumer<C, Object>) columnConsumer);
return this;
}
@Override
public <K, V> ResultSetRowTransformer<C, I> add(BeanRelationFixer<C, V> combiner, ResultSetRowTransformer<V, K> relatedBeanCreator) {
this.relations.put((BeanRelationFixer) combiner, (ResultSetRowTransformer) relatedBeanCreator);
return this;
}
@Override
public <T extends C> ResultSetRowTransformer<T, I> copyFor(Class<T> beanType, SerializableFunction<I, T> beanFactory) {
if (!(this.beanFactory instanceof IdentifierArgBeanFactory)) {
throw new UnsupportedOperationException("This instance can only be cloned with an identifier-arg constructor because it was created with one");
}
ResultSetRowTransformer<T, I> result = new ResultSetRowTransformer<>(beanType, ((IdentifierArgBeanFactory) this.beanFactory).copyFor(beanFactory));
result.consumers.addAll((Set) this.consumers);
this.relations.forEach((consumer, transformer) -> {
result.relations.put((BeanRelationFixer) consumer, transformer.copyFor(transformer.beanType, ((SerializableFunction) transformer.getBeanFactory().getFactory())));
});
return result;
}
@Override
public <T extends C> ResultSetRowTransformer<T, I> copyFor(Class<T> beanType, SerializableSupplier<T> beanFactory) {
if (!(this.beanFactory instanceof NoIdentifierBeanFactory)) {
throw new UnsupportedOperationException("This instance can only be cloned with a no-arg constructor because it was created with one");
}
ResultSetRowTransformer<T, I> result = new ResultSetRowTransformer<>(beanType, ((NoIdentifierBeanFactory) this.beanFactory).copyFor(beanFactory));
result.consumers.addAll((Set) this.consumers);
this.relations.forEach((consumer, transformer) -> {
result.relations.put((BeanRelationFixer) consumer, transformer.copyFor(transformer.beanType, ((SerializableSupplier) transformer.getBeanFactory().getFactory())));
});
return result;
}
@Override
public ResultSetRowTransformer<C, I> copyWithAliases(Function<String, String> columnMapping) {
ResultSetRowTransformer<C, I> result = new ResultSetRowTransformer<>(this.beanType, this.beanFactory.copyWithAliases(columnMapping));
this.consumers.forEach(c -> result.add(c.copyWithAliases(columnMapping)));
this.relations.forEach((consumer, transformer) -> result.add(consumer, transformer.copyWithAliases(columnMapping)));
return result;
}
@Override // for adhoc return type
public ResultSetRowTransformer<C, I> copyWithAliases(Map<String, String> columnMapping) {
// equivalent to super.copyWithAlias(..) but because interface default method can't be invoked we have to copy/paste its code ;(
return copyWithAliases(columnMapping::get);
}
/**
* Converts the current {@link ResultSet} row into a bean.
* Depending on implementation of factory, it may return a brand new instance or a cached one (if the bean key is already known for instance).
* Consumers will be applied to instance returned by the factory, as a consequence if bean comes from a cache it will be completed again, this may
* do some extra work in case of simple property.
*
* @param resultSet not null
* @return an instance of T, newly created or not according to implementation
*/
@Override
public C transform(ResultSet resultSet) {
return nullable(this.beanFactory.createInstance(resultSet))
.invoke(b -> assemble(b, resultSet))
.get();
}
/**
* Implementation that applies all {@link ColumnConsumer} to the given {@link ResultSet}
*
* @param rootBean the bean built for the row
* @param input any {@link ResultSet} positioned at row that must be read
*/
@Override
public void assemble(C rootBean, ResultSet input) {
// we consume simple properties
for (ColumnConsumer<C, ?> consumer : consumers) {
consumer.assemble(rootBean, input);
}
// we set related beans
for (Entry<BeanRelationFixer<C, Object>, ResultSetRowTransformer<Object, Object>> entry : relations.entrySet()) {
Object relatedBean = entry.getValue().transform(input);
if (relatedBean != null) { // null related bean is considered as no relation
entry.getKey().apply(rootBean, relatedBean);
}
}
}
/**
* Definition of a factory focused on {@link ResultSetRowTransformer} usage
* @param <C> created type
*/
public interface BeanFactory<C> extends Factory<ResultSet, C>, CopiableForAnotherQuery<C> {
Serializable getFactory();
@Override
BeanFactory<C> copyWithAliases(Function<String, String> columnMapping);
}
/**
* Specialization of {@link BeanFactory} for no-arg constructor classes.
*
* @param <C> created type
*/
public static class NoIdentifierBeanFactory<C> implements BeanFactory<C> {
private final SerializableSupplier<C> factory;
public NoIdentifierBeanFactory(SerializableSupplier<C> factory) {
this.factory = factory;
}
public C createInstance() {
return factory.get();
}
public SerializableSupplier<C> getFactory() {
return factory;
}
@Override
public C createInstance(ResultSet input) {
return factory.get();
}
@Override
public BeanFactory<C> copyWithAliases(Function<String, String> columnMapping) {
return new NoIdentifierBeanFactory<>(factory);
}
/**
* Used to copy this instance for a subclass of its bean type, required for inheritance implementation.
* @param beanFactory subclass no-arg constructor
* @param <T> subclass type
* @return a new {@link BeanFactory} for a subclass bean which read same column as this instance
*/
public <T extends C> NoIdentifierBeanFactory<T> copyFor(SerializableSupplier<T> beanFactory) {
return new NoIdentifierBeanFactory<>(beanFactory);
}
}
/**
* Specialization of {@link BeanFactory} for one-arg constructor classes
*
* @param <I> constructor input type (also column type)
* @param <C> bean type
*/
public static class IdentifierArgBeanFactory<I, C> implements BeanFactory<C> {
/** {@link ResultSet} reader */
private final ColumnReader<I> reader;
/** one-arg bean constructor */
private final SerializableFunction<I, C> factory;
public IdentifierArgBeanFactory(ColumnReader<I> reader, SerializableFunction<I, C> factory) {
this.reader = reader;
this.factory = factory;
}
public SerializableFunction<I, C> getFactory() {
return factory;
}
@Override
public C createInstance(ResultSet resultSet) {
return nullable(readBeanKey(resultSet)).map(this::newInstance).get();
}
protected I readBeanKey(ResultSet resultSet) {
return reader.read(resultSet);
}
protected C newInstance(I beanKey) {
return factory.apply(beanKey);
}
@Override
public IdentifierArgBeanFactory<I, C> copyWithAliases(Function<String, String> columnMapping) {
return new IdentifierArgBeanFactory<>(reader.copyWithAliases(columnMapping), this.factory);
}
/**
* Used to copy this instance for a subclass of its bean type, required for inheritance implementation.
* @param beanFactory subclass one-arg constructor
* @param <T> subclass type
* @return a new {@link BeanFactory} for a subclass bean which read same column as this instance
*/
public <T extends C> IdentifierArgBeanFactory<I, T> copyFor(SerializableFunction<I, T> beanFactory) {
return new IdentifierArgBeanFactory<>(this.reader, beanFactory);
}
}
}