More magic! We now automatically convert between java.sql.Timestamp,
java.sql.Date and java.util.Date and we support custom getters and setters to do fancier aggregate conversion (multiple persistent fields into a single runtime field and/or creation of partially initialized runtime objects from the available persistent data).
This commit is contained in:
@@ -21,35 +21,68 @@
|
||||
package com.samskivert.depot.util;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.samskivert.depot.PersistentRecord;
|
||||
import com.samskivert.util.Tuple;
|
||||
|
||||
/**
|
||||
* Creates functions that map persistent records to runtime counterparts and vice versa.
|
||||
* Creates functions that map persistent records to runtime counterparts and vice versa. A few bits
|
||||
* of magic are provided to help in mapping from the persistent world to the runtime work. Namely:
|
||||
*
|
||||
* <p> {@link Timestamp} and {@link java.sql.Date} are automatically converted to and from {@link
|
||||
* Date} if necessary.
|
||||
*
|
||||
* <p> You can define custom getter and setter methods in your persistent class like so:
|
||||
* <pre>
|
||||
* public class FooRecord extends PersistentRecord
|
||||
* {
|
||||
* public long monkeyStamp;
|
||||
*
|
||||
* public Date getMonkeyStamp ()
|
||||
* {
|
||||
* return new Date(monkeyStamp);
|
||||
* }
|
||||
*
|
||||
* public void setMonkeyStamp (Date value)
|
||||
* {
|
||||
* monkeyStamp = value.getTime();
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p> The conversion methods must be named <code>get</code> and <code>set</code> followed by the
|
||||
* name of your field with the first letter changed to a capital and their type signatures must
|
||||
* exactly match those of the persistent and runtime types.
|
||||
*/
|
||||
public class RuntimeUtil
|
||||
{
|
||||
/**
|
||||
* Creates a function that creates an instance of R and initializes all accessible (ie. public)
|
||||
* fields of R from fields of P with matching name. Fields of P that do not exist in R will be
|
||||
* ignored. Note: the types of the fields must match exactly.
|
||||
* ignored. See the class documentation for a list of field conversions that will be made
|
||||
* automatically and the mechanism for performing other conversions.
|
||||
*/
|
||||
public static <P extends PersistentRecord, R> Function<P, R> makeToRuntime (
|
||||
Class<P> pclass, final Class<R> rclass)
|
||||
{
|
||||
final Field[] rfields = getRuntimeFields(rclass);
|
||||
final Field[] pfields = getPersistentFields(pclass, rfields);
|
||||
final Getter[] getters = getPersistentGetters(pclass, rfields);
|
||||
return new Function<P, R>() {
|
||||
public R apply (P record) {
|
||||
try {
|
||||
R object = rclass.newInstance();
|
||||
for (int ii = 0, ll = rfields.length; ii < ll; ii++) {
|
||||
rfields[ii].set(object, pfields[ii].get(record));
|
||||
rfields[ii].set(object, getters[ii].get(record));
|
||||
}
|
||||
return object;
|
||||
} catch (Exception e) {
|
||||
@@ -68,13 +101,13 @@ public class RuntimeUtil
|
||||
Class<R> rclass, final Class<P> pclass)
|
||||
{
|
||||
final Field[] rfields = getRuntimeFields(rclass);
|
||||
final Field[] pfields = getPersistentFields(pclass, rfields);
|
||||
final Setter[] setters = getPersistentSetters(pclass, rfields);
|
||||
return new Function<R, P>() {
|
||||
public P apply (R object) {
|
||||
try {
|
||||
P record = pclass.newInstance();
|
||||
for (int ii = 0, ll = rfields.length; ii < ll; ii++) {
|
||||
pfields[ii].set(record, rfields[ii].get(object));
|
||||
setters[ii].set(record, rfields[ii].get(object));
|
||||
}
|
||||
return record;
|
||||
} catch (Exception e) {
|
||||
@@ -96,8 +129,104 @@ public class RuntimeUtil
|
||||
return fields.toArray(new Field[fields.size()]);
|
||||
}
|
||||
|
||||
protected static Field[] getPersistentFields (
|
||||
Class<? extends PersistentRecord> pclass, Field[] rfields)
|
||||
protected static Getter[] getPersistentGetters (Class<?> pclass, Field[] rfields)
|
||||
{
|
||||
Field[] pfields = getPersistentFields(pclass, rfields);
|
||||
Getter[] getters = new Getter[rfields.length];
|
||||
for (int ii = 0; ii < rfields.length; ii++) {
|
||||
getters[ii] = makeGetter(pclass, pfields[ii], rfields[ii]);
|
||||
}
|
||||
return getters;
|
||||
}
|
||||
|
||||
protected static Setter[] getPersistentSetters (Class<?> pclass, Field[] rfields)
|
||||
{
|
||||
Field[] pfields = getPersistentFields(pclass, rfields);
|
||||
Setter[] setters = new Setter[rfields.length];
|
||||
for (int ii = 0; ii < rfields.length; ii++) {
|
||||
setters[ii] = makeSetter(pclass, pfields[ii], rfields[ii]);
|
||||
}
|
||||
return setters;
|
||||
}
|
||||
|
||||
protected static Getter makeGetter (Class<?> pclass, final Field pfield, Field rfield)
|
||||
{
|
||||
// if there's a custom getter method for the field, use that foremost
|
||||
try {
|
||||
final Method method = pclass.getMethod(makeMethodName("get", pfield.getName()));
|
||||
if (method.getReturnType().equals(rfield.getType())) {
|
||||
return new Getter() {
|
||||
public Object get (Object object) throws Exception {
|
||||
return method.invoke(object);
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (NoSuchMethodException nsme) {
|
||||
// no problem, keep on truckin'
|
||||
}
|
||||
|
||||
// if the fields match exactly, return a getter that just gets the field
|
||||
if (rfield.getType().equals(pfield.getType())) {
|
||||
return new Getter() {
|
||||
public Object get (Object object) throws Exception {
|
||||
return pfield.get(object);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// if we can convert from the persistent type to the runtime type, do that
|
||||
final Function<Object, Object> converter = getconv(pfield.getType(), rfield.getType());
|
||||
if (converter != null) {
|
||||
return new Getter() {
|
||||
public Object get (Object object) throws Exception {
|
||||
return converter.apply(pfield.get(object));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// if we have exhausted all other approaches, we're SOL
|
||||
throw new IllegalArgumentException("Cannot map " + pfield + " to " + rfield + ".");
|
||||
}
|
||||
|
||||
protected static Setter makeSetter (Class<?> pclass, Field pfield, final Field rfield)
|
||||
{
|
||||
// check for a custom setter method for the field (with the correct argument type)
|
||||
try {
|
||||
final Method method = pclass.getMethod(
|
||||
makeMethodName("set", pfield.getName()), rfield.getType());
|
||||
return new Setter() {
|
||||
public void set (Object object, Object value) throws Exception {
|
||||
method.invoke(object, value);
|
||||
}
|
||||
};
|
||||
} catch (NoSuchMethodException nsme) {
|
||||
// no problem, keep on truckin'
|
||||
}
|
||||
|
||||
// if the fields match exactly, return a setter that just sets the field
|
||||
if (rfield.getType().equals(pfield.getType())) {
|
||||
return new Setter() {
|
||||
public void set (Object object, Object value) throws Exception {
|
||||
rfield.set(object, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// if we can convert from the runtime type to the persistent type, do that
|
||||
final Function<Object, Object> converter = getconv(rfield.getType(), pfield.getType());
|
||||
if (converter != null) {
|
||||
return new Setter() {
|
||||
public void set (Object object, Object value) throws Exception {
|
||||
rfield.set(object, converter.apply(value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// if we have exhausted all other approaches, we're SOL
|
||||
throw new IllegalArgumentException("Cannot map " + rfield + " to " + pfield + ".");
|
||||
}
|
||||
|
||||
protected static Field[] getPersistentFields (Class<?> pclass, Field[] rfields)
|
||||
{
|
||||
Field[] pfields = new Field[rfields.length];
|
||||
for (int ii = 0; ii < rfields.length; ii++) {
|
||||
@@ -115,4 +244,58 @@ public class RuntimeUtil
|
||||
}
|
||||
return pfields;
|
||||
}
|
||||
|
||||
protected static String makeMethodName (String prefix, String fieldName)
|
||||
{
|
||||
return new StringBuilder().append(prefix).
|
||||
append(Character.toUpperCase(fieldName.charAt(0))).
|
||||
append(fieldName.substring(1)).toString();
|
||||
}
|
||||
|
||||
protected static interface Getter
|
||||
{
|
||||
public Object get (Object object) throws Exception;
|
||||
}
|
||||
|
||||
protected static interface Setter
|
||||
{
|
||||
public void set (Object object, Object value) throws Exception;
|
||||
}
|
||||
|
||||
protected static Function<Object, Object> getconv (Class<?> fc, Class<?> tc)
|
||||
{
|
||||
return _converters.get(Tuple.newTuple(fc, tc));
|
||||
}
|
||||
|
||||
protected static <F, T> void regconv (Class<F> fc, Class<T> tc, Function<F, T> conv)
|
||||
{
|
||||
@SuppressWarnings("unchecked") Function<Object, Object> value =
|
||||
(Function<Object, Object>)conv;
|
||||
_converters.put(Tuple.newTuple(fc, tc), value);
|
||||
}
|
||||
|
||||
protected static Map<Object, Function<Object, Object>> _converters =
|
||||
Maps.newHashMap();
|
||||
static {
|
||||
regconv(Timestamp.class, Date.class, new Function<Timestamp, Date>() {
|
||||
public Date apply (Timestamp object) {
|
||||
return (object == null) ? null : new Date(object.getTime());
|
||||
}
|
||||
});
|
||||
regconv(Date.class, Timestamp.class, new Function<Date, Timestamp>() {
|
||||
public Timestamp apply (Date object) {
|
||||
return (object == null) ? null : new Timestamp(object.getTime());
|
||||
}
|
||||
});
|
||||
regconv(java.sql.Date.class, Date.class, new Function<java.sql.Date, Date>() {
|
||||
public Date apply (java.sql.Date object) {
|
||||
return (object == null) ? null : new Date(object.getTime());
|
||||
}
|
||||
});
|
||||
regconv(Date.class, java.sql.Date.class, new Function<Date, java.sql.Date>() {
|
||||
public java.sql.Date apply (Date object) {
|
||||
return (object == null) ? null : new java.sql.Date(object.getTime());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user