diff --git a/src/main/java/com/samskivert/depot/Transformer.java b/src/main/java/com/samskivert/depot/Transformer.java index 6005aee..9e89ad0 100644 --- a/src/main/java/com/samskivert/depot/Transformer.java +++ b/src/main/java/com/samskivert/depot/Transformer.java @@ -22,6 +22,8 @@ package com.samskivert.depot; import java.lang.reflect.Type; +import com.samskivert.depot.annotation.Transform; + /** * Transforms a persistent record field into a format that can be read and written by the * underlying database. For example, one might transform an enum into a byte, short or integer. Or @@ -30,8 +32,16 @@ import java.lang.reflect.Type; * * @see Transformers */ -public interface Transformer +public abstract class Transformer { + /** + * Initialize this Transformer. + */ + public void init (Type fieldType, Transform annotation) + { + // nada by default + } + /** * Transforms a runtime value into a value that can be persisted. * @@ -39,16 +49,14 @@ public interface Transformer * * @return the transformed value, which will be written to the database. */ - T toPersistent (F value); + public abstract T toPersistent (F value); /** * Transforms a persisted value into a value that can be store in a runtime field. * - * @param fieldType the type of the persistent record field to which the transformed value will - * be written. * @param value the value just read from the database. * * @return the transformed value, which will be stored in a field of the persistent record. */ - F fromPersistent (Type fieldType, T value); + public abstract F fromPersistent (T value); } diff --git a/src/main/java/com/samskivert/depot/Transformers.java b/src/main/java/com/samskivert/depot/Transformers.java index 16412e7..915f204 100644 --- a/src/main/java/com/samskivert/depot/Transformers.java +++ b/src/main/java/com/samskivert/depot/Transformers.java @@ -26,6 +26,7 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -35,9 +36,14 @@ import com.samskivert.util.ByteEnum; import com.samskivert.util.ByteEnumUtil; import com.samskivert.depot.annotation.Column; +import com.samskivert.depot.annotation.Transform; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Interner; +import com.google.common.collect.Interners; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -54,6 +60,35 @@ import com.google.common.collect.Sets; */ public class Transformers { + /** + * Transform a Set of ByteEnums into an Integer. + * Does not presently support the immutable or interning hints. + */ + public static class ByteEnumSet & ByteEnum> + extends Transformer, Integer> + { + @Override @SuppressWarnings("unchecked") + public void init (Type fieldType, Transform annotation) + { + _eclass = (Class) ((ParameterizedType) fieldType).getActualTypeArguments()[0]; + } + + @Override + public Integer toPersistent (Set value) + { + return (value == null) ? null : ByteEnumUtil.setToInt(value); + } + + @Override + public Set fromPersistent (Integer encoded) + { + return (encoded == null) ? null : ByteEnumUtil.intToSet(_eclass, encoded); + } + + /** The enum class token. */ + protected Class _eclass; + } + /** * Combines the contents of a String[] column into a single String, terminating each String * element with a newline. A backslash ('\') in Strings will be prefixed by another backslash, @@ -62,14 +97,24 @@ public class Transformers */ public static class StringArray extends StringBase { - public String toPersistent (String[] value) + protected Iterable asIterable (String[] value) { - return (value == null) ? null : encode(Arrays.asList(value)); + return Arrays.asList(value); } - public String[] fromPersistent (Type ftype, String encoded) + protected Builder createBuilder (String encoded) { - return (encoded == null) ? null : Iterables.toArray(decode(encoded), String.class); + // jog through and count the elements so that we can populate the array directly + final String[] result = new String[countElements(encoded)]; + return new Builder() { + public void add (String s) { + result[idx++] = s; + } + public String[] build () { + return result; + } + protected int idx = 0; + }; } } @@ -81,61 +126,77 @@ public class Transformers */ public static class StringIterable extends StringBase> { - public String toPersistent (Iterable value) + protected Iterable asIterable (Iterable value) { - return (value == null) ? null : encode(value); - } - - public Iterable fromPersistent (Type ftype, String encoded) - { - if (encoded == null) { - return null; - } - - ArrayList value = decode(encoded); - Type fclass = (ftype instanceof ParameterizedType) ? - ((ParameterizedType)ftype).getRawType() : ftype; - if (fclass == ArrayList.class || fclass == List.class || - fclass == Collection.class || fclass == Iterable.class) { - return value; - } - if (fclass == LinkedList.class) { - return Lists.newLinkedList(value); - } - if (fclass == HashSet.class || fclass == Set.class) { - return Sets.newHashSet(value); - } - // else: reflection? See if it's a collection, call the 0-arg constructor, add all - // and return? Something? return value; } - } - public static class ByteEnumSet & ByteEnum> - implements Transformer, Integer> - { - public Integer toPersistent (Set value) + protected Builder> createBuilder (String encoded) { - return (value == null) ? null : ByteEnumUtil.setToInt(value); + Collection adder; + Collection retval = null; + Type clazz = (_ftype instanceof ParameterizedType) ? + ((ParameterizedType)_ftype).getRawType() : _ftype; + // TODO: fill out the collection types + if (clazz == HashSet.class || clazz == Set.class) { + Set set = Sets.newHashSet(); + adder = set; + if (_immutable && (clazz == Set.class)) { + retval = Collections.unmodifiableSet(set); + } + + } else if (clazz == LinkedList.class) { + adder = Lists.newLinkedList(); + + } else { + List list = Lists.newArrayList(); + adder = list; + if (_immutable && + ((clazz == List.class) || + (clazz == Iterable.class) || + (clazz == Collection.class))) { + retval = Collections.unmodifiableList(list); + } + } + return createBuilder(adder, (retval == null) ? adder : retval); } - public Set fromPersistent (Type ftype, Integer encoded) + protected Builder> createBuilder ( + final Collection adder, final Collection retval) { - if (encoded == null) { + Preconditions.checkNotNull(adder); + return new Builder>() { + public void add (String s) { + adder.add(s); + } + public Iterable build () { + return (_immutable && _intern && (adder != retval)) + ? INTERNER.intern(retval) + : retval; + } + }; + } + } + + protected abstract static class StringBase extends Transformer + { + @Override + public void init (Type fieldType, Transform annotation) + { + _ftype = fieldType; + _immutable = annotation.immutable(); + _intern = annotation.intern(); + } + + @Override + public String toPersistent (F value) + { + if (value == null) { return null; } - @SuppressWarnings("unchecked") - Class eclass = (Class) ((ParameterizedType) ftype).getActualTypeArguments()[0]; - return ByteEnumUtil.intToSet(eclass, encoded); - } - } - protected abstract static class StringBase implements Transformer - { - protected static String encode (Iterable value) - { StringBuilder buf = new StringBuilder(); - for (String s : value) { + for (String s : asIterable(value)) { if (s == null) { buf.append("\\\n"); // encode nulls as slash followed by the terminator } else { @@ -147,15 +208,21 @@ public class Transformers return buf.toString(); } - protected static ArrayList decode (String encoded) + @Override + public F fromPersistent (String encoded) { - ArrayList value = Lists.newArrayList(); + if (encoded == null) { + return null; + } + + Builder builder = createBuilder(encoded); StringBuilder buf = new StringBuilder(encoded.length()); for (int ii = 0, nn = encoded.length(); ii < nn; ii++) { char c = encoded.charAt(ii); switch (c) { case '\n': - value.add(buf.toString()); // TODO: intern? + String s = buf.toString(); + builder.add(_intern ? s.intern() : s); buf.setLength(0); break; @@ -165,7 +232,7 @@ public class Transformers switch (slashed) { case '\n': // turn back into a null element Preconditions.checkArgument(buf.length() == 0, "Invalid encoded string"); - value.add(null); + builder.add(null); break; case 'n': // turn \n back into a newline buf.append('\n'); @@ -181,10 +248,41 @@ public class Transformers break; } } - // make sure the last element was terminated Preconditions.checkArgument(buf.length() == 0, "Invalid encoded string"); - return value; + return builder.build(); } + + protected abstract Iterable asIterable (F value); + + protected abstract Builder createBuilder (String encoded); + + /** + * Utility tount the number of elements in the encoded non-null string. + */ + protected static int countElements (String encoded) + { + int count = 0; + for (int pos = 0; 0 != (pos = 1 + encoded.indexOf('\n', pos)); count++) {} + return count; + } + + protected interface Builder + { + void add (String s); + + F build (); + } + + protected Type _ftype; + + /** Immutable hint. */ + protected boolean _immutable; + + /** Interning hint.*/ + protected boolean _intern; + + /** The interner we use for immutable values. */ + protected static final Interner> INTERNER = Interners.newWeakInterner(); } } diff --git a/src/main/java/com/samskivert/depot/annotation/Transform.java b/src/main/java/com/samskivert/depot/annotation/Transform.java index 8ea7c5a..c694b9c 100644 --- a/src/main/java/com/samskivert/depot/annotation/Transform.java +++ b/src/main/java/com/samskivert/depot/annotation/Transform.java @@ -39,7 +39,7 @@ import com.samskivert.depot.Transformer; * *
  * public class MyRecord extends PersistentRecord {
- *     @Transform(Transformers.CommaSeparatedString.class)
+ *     @Transform(Transformers.StringArray.class)
  *     public String[] cities;
  * }
  * 
@@ -67,4 +67,17 @@ public @interface Transform * Specifies a transformer to be used when persisting the target of this annotation. */ Class value (); + + /** + * Hint to the transformer whether it should return an immutable result. + * The transformer is free to ignore this hint. + */ + boolean immutable () default false; + + /** + * Hint to the transformer whether it should return an interned result, which should only + * be honored if the result is also immutable. + * The transformer is free to ignore this hint. + */ + boolean intern () default false; } diff --git a/src/main/java/com/samskivert/depot/impl/FieldMarshaller.java b/src/main/java/com/samskivert/depot/impl/FieldMarshaller.java index 9f80e22..b0f7ccf 100644 --- a/src/main/java/com/samskivert/depot/impl/FieldMarshaller.java +++ b/src/main/java/com/samskivert/depot/impl/FieldMarshaller.java @@ -97,9 +97,10 @@ public abstract class FieldMarshaller } try { - @SuppressWarnings("unchecked") Transformer xformer = xform.value().newInstance(); + @SuppressWarnings("unchecked") Transformer xformer = + xform.value().newInstance(); @SuppressWarnings("unchecked") TransformingMarshaller xmarsh = - new TransformingMarshaller(xformer, field); + new TransformingMarshaller(xformer, field, xform); xmarsh.create(field); return xmarsh; } catch (InstantiationException e) { @@ -690,7 +691,8 @@ public abstract class FieldMarshaller } protected static class TransformingMarshaller extends FieldMarshaller { - public TransformingMarshaller (Transformer xformer, Field field) { + public TransformingMarshaller ( + Transformer xformer, Field field, Transform annotation) { Class pojoType = getTransformerType(xformer, "from"); if (!pojoType.isAssignableFrom(field.getType())) { throw new IllegalArgumentException( @@ -700,6 +702,7 @@ public abstract class FieldMarshaller } @SuppressWarnings("unchecked") FieldMarshaller delegate = (FieldMarshaller)createMarshaller(getTransformerType(xformer, "to")); + xformer.init(field.getGenericType(), annotation); _delegate = delegate; _xformer = xformer; } @@ -722,7 +725,7 @@ public abstract class FieldMarshaller } @Override public void writeToObject (Object po, T value) throws IllegalArgumentException, IllegalAccessException { - _field.set(po, _xformer.fromPersistent(_field.getGenericType(), value)); + _field.set(po, _xformer.fromPersistent(value)); } @Override public void writeToStatement (PreparedStatement ps, int column, T value) throws SQLException {