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:
Michael Bayne
2009-07-08 22:10:07 +00:00
parent a113b5d85b
commit 7dd164525b
@@ -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());
}
});
}
}