[jOOQ/jOOQ#10071] DefaultRecordMapper should apply ConverterProvider for Record1.into(...) calls
This commit is contained in:
parent
2afcfe084f
commit
0f3a641588
@ -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><T></code> and
|
||||
* <code><U></code> types.
|
||||
*
|
||||
* @return The converter for <code><T, U></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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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)));
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user