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;
|
package com.samskivert.depot.util;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
import com.samskivert.depot.PersistentRecord;
|
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
|
public class RuntimeUtil
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Creates a function that creates an instance of R and initializes all accessible (ie. public)
|
* 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
|
* 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 (
|
public static <P extends PersistentRecord, R> Function<P, R> makeToRuntime (
|
||||||
Class<P> pclass, final Class<R> rclass)
|
Class<P> pclass, final Class<R> rclass)
|
||||||
{
|
{
|
||||||
final Field[] rfields = getRuntimeFields(rclass);
|
final Field[] rfields = getRuntimeFields(rclass);
|
||||||
final Field[] pfields = getPersistentFields(pclass, rfields);
|
final Getter[] getters = getPersistentGetters(pclass, rfields);
|
||||||
return new Function<P, R>() {
|
return new Function<P, R>() {
|
||||||
public R apply (P record) {
|
public R apply (P record) {
|
||||||
try {
|
try {
|
||||||
R object = rclass.newInstance();
|
R object = rclass.newInstance();
|
||||||
for (int ii = 0, ll = rfields.length; ii < ll; ii++) {
|
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;
|
return object;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -68,13 +101,13 @@ public class RuntimeUtil
|
|||||||
Class<R> rclass, final Class<P> pclass)
|
Class<R> rclass, final Class<P> pclass)
|
||||||
{
|
{
|
||||||
final Field[] rfields = getRuntimeFields(rclass);
|
final Field[] rfields = getRuntimeFields(rclass);
|
||||||
final Field[] pfields = getPersistentFields(pclass, rfields);
|
final Setter[] setters = getPersistentSetters(pclass, rfields);
|
||||||
return new Function<R, P>() {
|
return new Function<R, P>() {
|
||||||
public P apply (R object) {
|
public P apply (R object) {
|
||||||
try {
|
try {
|
||||||
P record = pclass.newInstance();
|
P record = pclass.newInstance();
|
||||||
for (int ii = 0, ll = rfields.length; ii < ll; ii++) {
|
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;
|
return record;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -96,8 +129,104 @@ public class RuntimeUtil
|
|||||||
return fields.toArray(new Field[fields.size()]);
|
return fields.toArray(new Field[fields.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Field[] getPersistentFields (
|
protected static Getter[] getPersistentGetters (Class<?> pclass, Field[] rfields)
|
||||||
Class<? extends PersistentRecord> 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];
|
Field[] pfields = new Field[rfields.length];
|
||||||
for (int ii = 0; ii < rfields.length; ii++) {
|
for (int ii = 0; ii < rfields.length; ii++) {
|
||||||
@@ -115,4 +244,58 @@ public class RuntimeUtil
|
|||||||
}
|
}
|
||||||
return pfields;
|
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