From 7dd164525b28bfd1707d70c9455d01b689966242 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Wed, 8 Jul 2009 22:10:07 +0000 Subject: [PATCH] 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). --- .../samskivert/depot/util/RuntimeUtil.java | 199 +++++++++++++++++- 1 file changed, 191 insertions(+), 8 deletions(-) diff --git a/src/java/com/samskivert/depot/util/RuntimeUtil.java b/src/java/com/samskivert/depot/util/RuntimeUtil.java index 8744207..bb3ea50 100644 --- a/src/java/com/samskivert/depot/util/RuntimeUtil.java +++ b/src/java/com/samskivert/depot/util/RuntimeUtil.java @@ -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: + * + *

{@link Timestamp} and {@link java.sql.Date} are automatically converted to and from {@link + * Date} if necessary. + * + *

You can define custom getter and setter methods in your persistent class like so: + *

+ * public class FooRecord extends PersistentRecord
+ * {
+ *     public long monkeyStamp;
+ *
+ *     public Date getMonkeyStamp ()
+ *     {
+ *         return new Date(monkeyStamp);
+ *     }
+ *
+ *     public void setMonkeyStamp (Date value)
+ *     {
+ *         monkeyStamp = value.getTime();
+ *     }
+ * }
+ * 
+ * + *

The conversion methods must be named get and set 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

Function makeToRuntime ( Class

pclass, final Class rclass) { final Field[] rfields = getRuntimeFields(rclass); - final Field[] pfields = getPersistentFields(pclass, rfields); + final Getter[] getters = getPersistentGetters(pclass, rfields); return new Function() { 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 rclass, final Class

pclass) { final Field[] rfields = getRuntimeFields(rclass); - final Field[] pfields = getPersistentFields(pclass, rfields); + final Setter[] setters = getPersistentSetters(pclass, rfields); return new Function() { 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 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 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 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 getconv (Class fc, Class tc) + { + return _converters.get(Tuple.newTuple(fc, tc)); + } + + protected static void regconv (Class fc, Class tc, Function conv) + { + @SuppressWarnings("unchecked") Function value = + (Function)conv; + _converters.put(Tuple.newTuple(fc, tc), value); + } + + protected static Map> _converters = + Maps.newHashMap(); + static { + regconv(Timestamp.class, Date.class, new Function() { + public Date apply (Timestamp object) { + return (object == null) ? null : new Date(object.getTime()); + } + }); + regconv(Date.class, Timestamp.class, new Function() { + public Timestamp apply (Date object) { + return (object == null) ? null : new Timestamp(object.getTime()); + } + }); + regconv(java.sql.Date.class, Date.class, new Function() { + public Date apply (java.sql.Date object) { + return (object == null) ? null : new Date(object.getTime()); + } + }); + regconv(Date.class, java.sql.Date.class, new Function() { + public java.sql.Date apply (Date object) { + return (object == null) ? null : new java.sql.Date(object.getTime()); + } + }); + } }