[jOOQ/jOOQ#10071] DefaultRecordMapper should apply ConverterProvider for Record1.into(...) calls

This commit is contained in:
Lukas Eder 2020-04-15 15:39:57 +02:00
parent 2afcfe084f
commit 0f3a641588
6 changed files with 167 additions and 38 deletions

View File

@ -48,10 +48,6 @@ import org.jooq.impl.DefaultConverterProvider;
* {@link RecordMapper}, e.g. when mapping {@link JSON} or {@link XML} data
* types onto POJO types using third party libraries like Jackson, Gson, JAXB,
* or others.
* <p>
* It is recommended to delegate all calls to
* {@link DefaultConverterProvider#provide(Class, Class)} for pairs of classes
* that are not handled by this converter provider.
*
* @author Lukas Eder
*/
@ -61,6 +57,10 @@ public interface ConverterProvider {
/**
* Provide a converter that can convert between <code>&lt;T&gt;</code> and
* <code>&lt;U&gt;</code> types.
*
* @return The converter for <code>&lt;T, U&gt;</code>, or <code>null</code>
* if no such converter could be provided, in case of which jOOQ's
* {@link DefaultConverterProvider} applies.
*/
<T, U> Converter<T, U> provide(Class<T> tType, Class<U> uType);
}

View File

@ -41,6 +41,7 @@ package org.jooq.impl;
import static java.util.Arrays.asList;
import static org.jooq.conf.SettingsTools.updatablePrimaryKeys;
import static org.jooq.impl.Tools.EMPTY_FIELD;
import static org.jooq.impl.Tools.converterOrFail;
import static org.jooq.impl.Tools.embeddedFields;
import static org.jooq.impl.Tools.indexOrFail;
import static org.jooq.impl.Tools.isEmbeddable;
@ -254,7 +255,7 @@ abstract class AbstractRecord extends AbstractStore implements Record {
@Override
public final <T> T get(Field<?> field, Class<? extends T> type) {
return (T) Tools.converter(this, field.getType(), (Class) type).from(get(field));
return (T) converterOrFail(this, field.getType(), (Class) type).from(get(field));
}
@Override
@ -269,7 +270,7 @@ abstract class AbstractRecord extends AbstractStore implements Record {
@Override
public final <T> T get(int index, Class<? extends T> type) {
return (T) Tools.converter(this, field(safeIndex(index)).getType(), (Class) type).from(get(index));
return (T) converterOrFail(this, field(safeIndex(index)).getType(), (Class) type).from(get(index));
}
@Override

View File

@ -37,8 +37,27 @@
*/
package org.jooq.impl;
import static org.jooq.tools.reflect.Reflect.wrapper;
import java.io.File;
import java.net.URI;
import java.net.URL;
import java.sql.Struct;
import java.time.temporal.Temporal;
import java.util.Calendar;
import java.util.Collection;
import java.util.Optional;
import java.util.UUID;
import javax.persistence.EnumType;
// ...
import org.jooq.Converter;
import org.jooq.ConverterProvider;
import org.jooq.JSON;
import org.jooq.JSONB;
import org.jooq.Record;
import org.jooq.UDTRecord;
import org.jooq.tools.Convert;
/**
@ -50,32 +69,101 @@ public final class DefaultConverterProvider implements ConverterProvider {
@Override
public final <T, U> Converter<T, U> provide(final Class<T> tType, final Class<U> uType) {
return new Converter<T, U>() {
Class<?> tWrapper = wrapper(tType);
Class<?> uWrapper = wrapper(uType);
/**
* Generated UID.
*/
private static final long serialVersionUID = 8011099590775678430L;
// TODO: [#10071] These checks are required to be able to return null in
// case this implementation cannot produce a Converter.
// It corresponds to a super set of what org.jooq.tools.Convert
// can do. There is certainly room for refactoring the two
// classes.
if (tWrapper == uWrapper
|| uWrapper.isAssignableFrom(tWrapper)
|| isCollection(tWrapper) && isCollection(uWrapper)
|| tWrapper == Optional.class
|| uWrapper == Optional.class
|| uWrapper == String.class
|| uWrapper == byte[].class
|| Number.class.isAssignableFrom(uWrapper) // No fail-fast implemented yet!
|| Boolean.class.isAssignableFrom(uWrapper) // No fail-fast implemented yet!
|| Character.class.isAssignableFrom(uWrapper)
|| uWrapper == URI.class && tWrapper == String.class
|| uWrapper == URL.class && tWrapper == String.class
|| uWrapper == File.class && tWrapper == String.class
|| isDate(tWrapper) && isDate(uWrapper)
|| isEnum(tWrapper) && isEnum(uWrapper)
|| isUUID(tWrapper) && isUUID(uWrapper)
|| isJSON(tWrapper) && isJSON(uWrapper)
|| Record.class.isAssignableFrom(tWrapper)
|| Struct.class.isAssignableFrom(tWrapper) && UDTRecord.class.isAssignableFrom(uWrapper)
) {
return new Converter<T, U>() {
@Override
public U from(T t) {
return Convert.convert(t, uType);
}
/**
* Generated UID.
*/
private static final long serialVersionUID = 8011099590775678430L;
@Override
public T to(U u) {
return Convert.convert(u, tType);
}
@Override
public U from(T t) {
return Convert.convert(t, uType);
}
@Override
public Class<T> fromType() {
return tType;
}
@Override
public T to(U u) {
return Convert.convert(u, tType);
}
@Override
public Class<U> toType() {
return uType;
}
};
@Override
public Class<T> fromType() {
return tType;
}
@Override
public Class<U> toType() {
return uType;
}
};
}
else
return null;
}
private final boolean isJSON(Class<?> type) {
return type == JSON.class
|| type == JSONB.class
|| type == String.class;
}
private final boolean isUUID(Class<?> type) {
return type == String.class
|| type == byte[].class
|| type == UUID.class;
}
private final boolean isEnum(Class<?> type) {
return Enum.class.isAssignableFrom(type)
|| type == String.class
|| EnumType.class.isAssignableFrom(type);
}
private final boolean isDate(Class<?> type) {
return java.util.Date.class.isAssignableFrom(type)
|| Calendar.class.isAssignableFrom(type)
|| Temporal.class.isAssignableFrom(type)
|| type == Long.class
|| type == String.class;
}
private final boolean isCollection(Class<?> type) {
return type.isArray()
|| Collection.class.isAssignableFrom(type)
// [#3443] Conversion from Object[] to JDBC Array
|| type == java.sql.Array.class;
}
}

View File

@ -337,11 +337,8 @@ public class DefaultRecordMapper<R extends Record, E> implements RecordMapper<R,
}
// [#3212] [#5154] "Value types" can be mapped from single-field Record1
// types for convenience
if (type.isPrimitive()
|| DefaultDataType.types().contains(type)
|| Enum.class.isAssignableFrom(type)) {
// [#10071] Single-field Record1 types can be mapped if there is a ConverterProvider allowing for this mapping
if (fields.length == 1 && Tools.converter(configuration, fields[0].getType(), type) != null) {
delegate = new ValueTypeMapper();
return;
}

View File

@ -38,6 +38,7 @@
package org.jooq.impl;
import static org.jooq.impl.Tools.converterOrFail;
import static org.jooq.impl.Tools.indexOrFail;
import java.lang.reflect.Array;
@ -220,7 +221,7 @@ final class ResultImpl<R extends Record> extends AbstractCursor<R> implements Re
@Override
public final <U> List<U> getValues(int fieldIndex, Class<? extends U> type) {
List<U> result = new ArrayList<>(size());
Converter converter = Tools.converter(this, field(safeIndex(fieldIndex)).getType(), (Class) type);
Converter converter = converterOrFail(this, field(safeIndex(fieldIndex)).getType(), (Class) type);
for (R record : this)
result.add((U) converter.from(record.get(fieldIndex)));

View File

@ -1082,17 +1082,59 @@ final class Tools {
}
/**
* Get a converter from a {@link ConverterProvider}.
* Get a converter from a {@link ConverterProvider} or <code>null</code> if
* no converter could be provided.
*/
static final <T, U> Converter<T, U> converter(Configuration configuration, Class<T> tType, Class<U> uType) {
Converter<T, U> result = configuration(configuration).converterProvider().provide(tType, uType);
if (result == null)
result = CTX.configuration().converterProvider().provide(tType, uType);
return result;
}
/**
* Get a converter from a {@link ConverterProvider} or <code>null</code> if
* no converter could be provided.
*/
static final <T, U> Converter<T, U> converter(Scope scope, Class<T> tType, Class<U> uType) {
return converter(configuration(scope), tType, uType);
}
/**
* Get a converter from a {@link ConverterProvider} or <code>null</code> if
* no converter could be provided.
*/
static final <T, U> Converter<T, U> converter(Attachable attachable, Class<T> tType, Class<U> uType) {
return configuration(attachable).converterProvider().provide(tType, uType);
return converter(configuration(attachable), tType, uType);
}
/**
* Get a converter from a {@link ConverterProvider} or <code>null</code> if
* no converter could be provided.
*/
static final <T, U> Converter<T, U> converterOrFail(Configuration configuration, Class<T> tType, Class<U> uType) {
Converter<T, U> result = converter(configuration, tType, uType);
if (result == null)
throw new DataTypeException("No Converter found for types " + tType.getName() + " and " + uType.getName());
return result;
}
/**
* Get a converter from a {@link ConverterProvider}.
*/
static final <T, U> Converter<T, U> converter(Scope scope, Class<T> tType, Class<U> uType) {
return configuration(scope).converterProvider().provide(tType, uType);
static final <T, U> Converter<T, U> converterOrFail(Scope scope, Class<T> tType, Class<U> uType) {
return converterOrFail(configuration(scope), tType, uType);
}
/**
* Get a converter from a {@link ConverterProvider}.
*/
static final <T, U> Converter<T, U> converterOrFail(Attachable attachable, Class<T> tType, Class<U> uType) {
return converterOrFail(configuration(attachable), tType, uType);
}
/**