diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultRecordMapper.java b/jOOQ/src/main/java/org/jooq/impl/DefaultRecordMapper.java index 013962139c..72dd74bc41 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultRecordMapper.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultRecordMapper.java @@ -40,6 +40,9 @@ */ package org.jooq.impl; +import static java.util.Collections.nCopies; +import static org.jooq.impl.DSL.field; +import static org.jooq.impl.DSL.name; import static org.jooq.impl.Utils.getAnnotatedGetter; import static org.jooq.impl.Utils.getAnnotatedMembers; import static org.jooq.impl.Utils.getAnnotatedSetters; @@ -57,9 +60,13 @@ import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.sql.Timestamp; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import javax.persistence.Column; @@ -146,6 +153,19 @@ *
  • Public non-final instance member field myField
  • * *

    + * If {@link Field#getName()} is MY_field.MY_nested_field + * (case-sensitive!), then this field's value will be considered a nested value + * MY_nested_field, which is set on a nested POJO that is passed to + * all of these: + *

    + *

    *

    If no default constructor is available, but at least one constructor * annotated with ConstructorProperties is available, that one is * used
    @@ -432,6 +452,37 @@ public final AbstractRecord map(R record) { } } + /** + * A mapper that keeps only fields with a certain prefix prior to applying a + * delegate mapper. + */ + private class RemovingPrefixRecordMapper implements RecordMapper { + + private final RecordMapper d; + private final Field[] f; + + RemovingPrefixRecordMapper(RecordMapper d, Field[] fields, String prefix) { + this.d = d; + this.f = new Field[fields.length]; + + String dotted = prefix + "."; + for (int i = 0; i < fields.length; i++) + if (fields[i].getName().startsWith(dotted)) + f[i] = field(name(fields[i].getName().substring(dotted.length() + 1)), fields[i].getDataType()); + } + + @Override + public Object map(R record) { + AbstractRecord copy = (AbstractRecord) DSL.using(configuration).newRecord(f); + + for (int i = 0; i < f.length; i++) + if (f[i] != null) + copy.setValue(i, record.getValue(i)); + + return d.map(record); + } + } + /** * Convert a record into a mutable POJO type *

    @@ -440,32 +491,86 @@ public final AbstractRecord map(R record) { */ private class MutablePOJOMapper implements RecordMapper { - private final Constructor constructor; - private final boolean useAnnotations; - private final List[] members; - private final List[] methods; + private final Constructor constructor; + private final boolean useAnnotations; + private final List[] members; + private final List[] methods; + private final Map>> nested; MutablePOJOMapper(Constructor constructor) { this.constructor = accessible(constructor); this.useAnnotations = hasColumnAnnotations(configuration, type); this.members = new List[fields.length]; this.methods = new List[fields.length]; + this.nested = new HashMap>>(); + Map[]> nestedFields = new HashMap[]>(); for (int i = 0; i < fields.length; i++) { Field field = fields[i]; + String name = field.getName(); // Annotations are available and present if (useAnnotations) { - members[i] = getAnnotatedMembers(configuration, type, field.getName()); - methods[i] = getAnnotatedSetters(configuration, type, field.getName()); + members[i] = getAnnotatedMembers(configuration, type, name); + methods[i] = getAnnotatedSetters(configuration, type, name); } // No annotations are present else { - members[i] = getMatchingMembers(configuration, type, field.getName()); - methods[i] = getMatchingSetters(configuration, type, field.getName()); + int dot = name.indexOf('.'); + + // A nested mapping is applied + if (dot > -1) { + String prefix = name.substring(0, dot); + + Field[] f = nestedFields.get(prefix); + if (f == null) { + f = nCopies(fields.length, field("")).toArray(new Field[fields.length]); + nestedFields.put(prefix, f); + } + + f[i] = field(name(name.substring(prefix.length() + 1)), field.getDataType()); + + members[i] = Collections.emptyList(); + methods[i] = Collections.emptyList(); + } + + // A top-level mapping is applied + else { + members[i] = getMatchingMembers(configuration, type, name); + methods[i] = getMatchingSetters(configuration, type, name); + } } } + + for (Entry[]> entry : nestedFields.entrySet()) { + String prefix = entry.getKey(); + List> list = new ArrayList>(); + + for (java.lang.reflect.Field member : getMatchingMembers(configuration, type, prefix)) { + list.add(new RemovingPrefixRecordMapper( + new DefaultRecordMapper( + new Fields(entry.getValue()), + member.getType(), + instance, + configuration + ), fields, prefix + )); + } + + for (Method method : getMatchingSetters(configuration, type, prefix)) { + list.add(new RemovingPrefixRecordMapper( + new DefaultRecordMapper( + new Fields(entry.getValue()), + method.getParameterTypes()[0], + instance, + configuration + ), fields, prefix + )); + } + + nested.put(prefix, list); + } } @Override @@ -487,6 +592,26 @@ public final E map(R record) { } } + for (Entry>> entry : nested.entrySet()) { + String prefix = entry.getKey(); + + for (RecordMapper mapper : entry.getValue()) { + Object value = mapper.map(record); + + for (java.lang.reflect.Field member : getMatchingMembers(configuration, type, prefix)) { + + // [#935] Avoid setting final fields + if ((member.getModifiers() & Modifier.FINAL) == 0) { + map(value, result, member); + } + } + + for (Method method : getMatchingSetters(configuration, type, prefix)) { + method.invoke(result, value); + } + } + } + return result; } catch (Exception e) { @@ -499,32 +624,66 @@ private final void map(Record record, Object result, java.lang.reflect.Field mem if (mType.isPrimitive()) { if (mType == byte.class) { - member.setByte(result, record.getValue(index, byte.class)); + map(record.getValue(index, byte.class), result, member); + } + else if (mType == short.class) { + map(record.getValue(index, short.class), result, member); + } + else if (mType == int.class) { + map(record.getValue(index, int.class), result, member); + } + else if (mType == long.class) { + map(record.getValue(index, long.class), result, member); + } + else if (mType == float.class) { + map(record.getValue(index, float.class), result, member); + } + else if (mType == double.class) { + map(record.getValue(index, double.class), result, member); + } + else if (mType == boolean.class) { + map(record.getValue(index, boolean.class), result, member); + } + else if (mType == char.class) { + map(record.getValue(index, char.class), result, member); + } + } + else { + map(record.getValue(index, mType), result, member); + } + } + + private final void map(Object value, Object result, java.lang.reflect.Field member) throws IllegalAccessException { + Class mType = member.getType(); + + if (mType.isPrimitive()) { + if (mType == byte.class) { + member.setByte(result, (Byte) value); } else if (mType == short.class) { - member.setShort(result, record.getValue(index, short.class)); + member.setShort(result, (Short) value); } else if (mType == int.class) { - member.setInt(result, record.getValue(index, int.class)); + member.setInt(result, (Integer) value); } else if (mType == long.class) { - member.setLong(result, record.getValue(index, long.class)); + member.setLong(result, (Long) value); } else if (mType == float.class) { - member.setFloat(result, record.getValue(index, float.class)); + member.setFloat(result, (Float) value); } else if (mType == double.class) { - member.setDouble(result, record.getValue(index, double.class)); + member.setDouble(result, (Double) value); } else if (mType == boolean.class) { - member.setBoolean(result, record.getValue(index, boolean.class)); + member.setBoolean(result, (Boolean) value); } else if (mType == char.class) { - member.setChar(result, record.getValue(index, char.class)); + member.setChar(result, (Character) value); } } else { - member.set(result, record.getValue(index, mType)); + member.set(result, value); } } }