From 0950fdd4ea7dd479d23c12081426bb8b373bcc43 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Thu, 17 Jun 2010 19:11:35 +0000 Subject: [PATCH] Transformers: more than meets the eye. This is an unimplemented proposal for handling non-basic types in Depot in an extensible way, instead of continuing down the road of the ByteEnum-style hackery that we've been doing to date. The basic design is as follows: Annotate either a field or a class with @Transform and specify the classname of a Transformer in that annotation. For example: public class MyRecord extends PersistentRecord { @Transform(Transformers.CommaSeparatedString.class) public String[] cities; } will cause "cities" to be combined into a single string (comma separated) before storing in the database, and split back into a string array from the single string when read from the database. Alternatively, one can annotate a type: @Transform(ByteEnumTransformer.class) public interface ByteEnum { ... } If Depot sees a field of non-basic type, it will look for a @Transform annotation on the field's type (and any interfaces implemented by that type or its parent types, which is not auto-magic). If it finds one, it will use it. Otherwise it will fall back to what it does now (which I think is to just foist the object off to JDBC and hope for the best). Comments and suggestions are invited. --- .../com/samskivert/depot/Transformer.java | 38 ++++++++++ .../com/samskivert/depot/Transformers.java | 53 ++++++++++++++ .../depot/annotation/Transform.java | 69 +++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 src/java/com/samskivert/depot/Transformer.java create mode 100644 src/java/com/samskivert/depot/Transformers.java create mode 100644 src/java/com/samskivert/depot/annotation/Transform.java diff --git a/src/java/com/samskivert/depot/Transformer.java b/src/java/com/samskivert/depot/Transformer.java new file mode 100644 index 0000000..eb0fa2a --- /dev/null +++ b/src/java/com/samskivert/depot/Transformer.java @@ -0,0 +1,38 @@ +// +// $Id$ +// +// Depot library - a Java relational persistence library +// Copyright (C) 2006-2010 Michael Bayne and Pär Winzell +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.depot; + +/** + * 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 + * one might transform a string array into a single string, using a separator known to be + * appropriate for the contents. + * + * @see Transformers + */ +public interface Transformer +{ + /** Transforms a runtime value into a value that can be persisted. */ + T toPersistent (F value); + + /** Transforms a persisted value into a value that can be store in a runtime field. */ + F fromPersistent (T value); +} diff --git a/src/java/com/samskivert/depot/Transformers.java b/src/java/com/samskivert/depot/Transformers.java new file mode 100644 index 0000000..0238bbb --- /dev/null +++ b/src/java/com/samskivert/depot/Transformers.java @@ -0,0 +1,53 @@ +// +// $Id$ +// +// Depot library - a Java relational persistence library +// Copyright (C) 2006-2010 Michael Bayne and Pär Winzell +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.depot; + +import com.google.common.base.Joiner; + +import com.samskivert.depot.annotation.Column; + +/** + * Contains various generally useful {@link Transformer} implementations. To use a transformer, you + * specify it via a {@link Column} annotation. For example: + *
+ * public class MyRecord extends PersistentRecord {
+ *     @Transform(Transformers.CommaSeparatedString.class)
+ *     public String[] cities;
+ * }
+ * 
+ * Because of limitations of Java's annotation system, configuration of transformers is not + * possible (without complex machinations) and separate classes must be created for any particular + * transformation. + */ +public class Transformers +{ + /** + * Combines the contents of a String[] column into a single string, separated by commas. + */ + public static class CommaSeparatedString implements Transformer { + public String toPersistent (String[] value) { + return Joiner.on(",").join(value); + } + public String[] fromPersistent (String value) { + return value.split(","); + } + } +} diff --git a/src/java/com/samskivert/depot/annotation/Transform.java b/src/java/com/samskivert/depot/annotation/Transform.java new file mode 100644 index 0000000..9e47366 --- /dev/null +++ b/src/java/com/samskivert/depot/annotation/Transform.java @@ -0,0 +1,69 @@ +// +// $Id$ +// +// Depot library - a Java relational persistence library +// Copyright (C) 2006-2010 Michael Bayne and Pär Winzell +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.samskivert.depot.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.samskivert.depot.Transformer; + +/** + * Declares that the target of this annotation, either a field in a {@link PersistentRecord} or a + * type (which will eventually be stored in a persistent record), should be transformed before + * saving to the database and after loading from the database. + * + *

For example, one may choose to transform a particular persistent record field into a type + * supported directly by Depot: + * + *

+ * public class MyRecord extends PersistentRecord {
+ *     @Transform(Transformers.CommaSeparatedString.class)
+ *     public String[] cities;
+ * }
+ * 
+ * + * or one may opt to specify a transformation for all fields that contain a value of a particular + * type: + * + *
+ * @Transform(ByteEnumTransformer.class)
+ * public interface ByteEnum { ... }
+ * 
+ * + * Note that because Depot honors @Transform annotations on any interface implemented by a type, or + * on a type's superclass, it is possible that conflicting transformations may be specified. In + * this case, Depot will fail with a runtime error during initialization, to indicate the problem. + * + * @see Transformer + */ +@Target(value={ElementType.TYPE, ElementType.FIELD}) +@Retention(value=RetentionPolicy.RUNTIME) +@Inherited +public @interface Transform +{ + /** + * Specifies a transformer to be used when persisting the target of this annotation. + */ + Class value (); +}