From dcd4046c60429669664f1448e02afe88c77da6fb Mon Sep 17 00:00:00 2001 From: Ray Greenwell Date: Tue, 12 Oct 2010 19:35:07 +0000 Subject: [PATCH] Transformer rejigger. Transformer is now an abstract class with a default init() method. The Type parameter has been removed from fromPersistent(). The Transformer annotation now has some new attributes that can be used to hint whether you want a result that is immutable or interned, the meaning and support of both is completely up to the Transformer. Brought back my uber Iterable transformers, with immutable/intern support in a single Transformer. - interning will intern the strings - immutable will wrap the collections in Collections.unmodifiable* (using a guava Immutable collection ends up creating a temporary builder collection and then copying the elements) - if immutable AND interning then the collection itself is "interned". At some point I'll add support for nearly any Iterable of an otherwise supported type, so that our Records can have List and other such fields. --- .../com/samskivert/depot/Transformer.java | 18 +- .../com/samskivert/depot/Transformers.java | 204 +++++++++++++----- .../depot/annotation/Transform.java | 15 +- .../depot/impl/FieldMarshaller.java | 11 +- 4 files changed, 185 insertions(+), 63 deletions(-) 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 {