Skip to content

Commit

Permalink
[#3897] Add support for nested "to-one" POJOs in DefaultRecordMapper
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaseder committed Jan 5, 2015
1 parent 02f2037 commit 348a803
Showing 1 changed file with 176 additions and 17 deletions.
193 changes: 176 additions & 17 deletions jOOQ/src/main/java/org/jooq/impl/DefaultRecordMapper.java
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -146,6 +153,19 @@
* <li>Public non-final instance member field <code>myField</code></li>
* </ul>
* <p>
* If {@link Field#getName()} is <code>MY_field.MY_nested_field</code>
* (case-sensitive!), then this field's value will be considered a nested value
* <code>MY_nested_field</code>, which is set on a nested POJO that is passed to
* all of these:
* <ul>
* <li>Public single-argument instance method <code>MY_field(...)</code></li>
* <li>Public single-argument instance method <code>myField(...)</code></li>
* <li>Public single-argument instance method <code>setMY_field(...)</code></li>
* <li>Public single-argument instance method <code>setMyField(...)</code></li>
* <li>Public non-final instance member field <code>MY_field</code></li>
* <li>Public non-final instance member field <code>myField</code></li>
* </ul>
* <p>
* <h5>If no default constructor is available, but at least one constructor
* annotated with <code>ConstructorProperties</code> is available, that one is
* used</h5>
Expand Down Expand Up @@ -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<R, Object> {

private final RecordMapper<R, Object> d;
private final Field<?>[] f;

RemovingPrefixRecordMapper(RecordMapper<R, Object> 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
* <p>
Expand All @@ -440,32 +491,86 @@ public final AbstractRecord map(R record) {
*/
private class MutablePOJOMapper implements RecordMapper<R, E> {

private final Constructor<? extends E> constructor;
private final boolean useAnnotations;
private final List<java.lang.reflect.Field>[] members;
private final List<java.lang.reflect.Method>[] methods;
private final Constructor<? extends E> constructor;
private final boolean useAnnotations;
private final List<java.lang.reflect.Field>[] members;
private final List<java.lang.reflect.Method>[] methods;
private final Map<String, List<RecordMapper<R, Object>>> nested;

MutablePOJOMapper(Constructor<? extends E> 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<String, List<RecordMapper<R, Object>>>();

Map<String, Field<?>[]> nestedFields = new HashMap<String, Field<?>[]>();
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<String, Field<?>[]> entry : nestedFields.entrySet()) {
String prefix = entry.getKey();
List<RecordMapper<R, Object>> list = new ArrayList<RecordMapper<R, Object>>();

for (java.lang.reflect.Field member : getMatchingMembers(configuration, type, prefix)) {
list.add(new RemovingPrefixRecordMapper(
new DefaultRecordMapper<R, Object>(
new Fields<R>(entry.getValue()),
member.getType(),
instance,
configuration
), fields, prefix
));
}

for (Method method : getMatchingSetters(configuration, type, prefix)) {
list.add(new RemovingPrefixRecordMapper(
new DefaultRecordMapper<R, Object>(
new Fields<R>(entry.getValue()),
method.getParameterTypes()[0],
instance,
configuration
), fields, prefix
));
}

nested.put(prefix, list);
}
}

@Override
Expand All @@ -487,6 +592,26 @@ public final E map(R record) {
}
}

for (Entry<String, List<RecordMapper<R, Object>>> entry : nested.entrySet()) {
String prefix = entry.getKey();

for (RecordMapper<R, Object> 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) {
Expand All @@ -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);
}
}
}
Expand Down

0 comments on commit 348a803

Please sign in to comment.