I'm working on an android app and Realm, and I need to create an enum attribute for one of my objects; but I discovered in this post that Realm doesn't support enum yet.
My object is like this:
public class ShuttleOption extends RealmObject {
private int Id;
private String Label;
private ShuttleTypes OriginShuttleType;
}
and my enum class (ShuttleTypes) corresponds with:
HOME = 1;
and
WORK = 2;
Can anybody suggest me how to do it?
You can use the pattern described in the issue: https://github.com/realm/realm-java/issues/776#issuecomment-190147079
Basically save it as a String in Realm and convert it going in and out:
public enum MyEnum {
FOO, BAR;
}
public class Foo extends RealmObject {
private String enumDescription;
public void saveEnum(MyEnum val) {
this.enumDescription = val.toString();
}
public MyEnum getEnum() {
return MyEnum.valueOf(enumDescription);
}
}
If you need a solution that works on Kotlin you can use the following:
open class Foo: RealmObject() {
var enum: MyEnum
get() { return MyEnum.valueOf(enumDescription) }
set(newMyEum) { enumDescription = newMyEnum.name }
private var enumDescription: String = MyEnum.FOO.name
}
MyEnum is the enum declared in #ChristianMelchior answer.
It is worth mentioning that since enum doesn't have a backing field,it won't be persisted into Realm. There is no need to use the #Ignore annotation on it
i created a Kotlin delegate, which means a little less repitition
usage:
open class SomeDbModel : RealmObject() {
#delegate:Ignore
var variableEnum: MyEnum by enum(::variable)
private var variable: String = MyEnum.Default.name
}
delegate implementation:
package com.github.ericytsang
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty0
import kotlin.reflect.KProperty
inline fun <R, reified T : Enum<T>> enum(
backingField: KMutableProperty0<Int>
) = OrdinalToEnumDelegate<R, T>(T::class, backingField)
val <T : Enum<T>> KClass<out T>.enumValues get() = java.enumConstants!!.toList()
class StringToEnumDelegate<R, T : Enum<T>>(
/**
* enum class to convert the ordinal values in [backingField] to.
*/
enumClass: KClass<T>,
/**
* the property containing [T]'s ordinal value.
*/
private val backingField: KMutableProperty0<String>
) : ReadWriteProperty<R, T> {
private val enumValues = enumClass.enumValues.associateBy { it.name }
override fun getValue(thisRef: R, property: KProperty<*>): T {
return enumValues[backingField.get()]
?: error("no corresponding enum found for ${backingField.get()} in ${enumValues.keys}")
}
override fun setValue(thisRef: R, property: KProperty<*>, value: T) {
backingField.set(value.name)
}
}
Related
I want to create a method, that:
Takes the type of an enum and a String as arguments
The String is the name of one specific enum instance
Returns the enum instance that fits that name.
What I have tried:
In class TestUtil.java:
public static <E extends Enum<E>> E mapToEnum(Enum<E> mappingEnum, String data) {
return mappingEnum.valueOf(E, data); // Not working, needs Class of Enum and String value
}
The enum:
public enum TestEnum {
TEST1("A"),
TEST2("B");
private String value;
private TestEnum(String value) {
this.value = value;
}
}
How it should work (For example in main method):
TestEnum x = TestUtil.mapToEnum(TestEnum.class, "TEST1"); // TEST1 is the name of the first enum instance
The problem is, that I can't figure out what I need to pass into the mapToEnum method, so that I can get the valueOf from that Enum.
If the code you provided is acceptable:
public static <E extends Enum<E>> E mapToEnum(Enum<E> mappingEnum, String data) {
return mappingEnum.valueOf(E, data); // Not working, needs Class of Enum and String value
}
Then all you have to do is fix it.
Here's the code I tested:
static <T extends Enum<T>> T mapToEnum(Class<T> mappingEnum, String data) {
return Enum.valueOf(mappingEnum, data);
}
Usage:
#Test
public void test() {
TestEnum myEnum = mapToEnum(TestEnum.class, "TEST1");
System.out.println(myEnum.value); //prints "A"
}
Strongly suggest using Apache commons-lang library for boiler plate function like this ...
TestEnum x = EnumUtils.getEnum(TestEnum.class, "TEST1");
... which is exactly the code #Fenio demonstrates but handles null or wrong input with a null instead of throwing an Exception.
If you didn't know about this then check out what the rest of the lang3 library holds. I view it as a de-facto standard, saving millions of devs from re-writing minor plumbing utilities.
This is how you can iterate the enum class value and match with the parameter you have passed in the method, please check the below-mentioned code.
enum TestEnum {
TEST1("test1"),
TEST2("test2");
private String value;
private TestEnum(String value) {
this.value = value;
}
public String getName() {
return value;
}
public static TestEnum mapToEnum(String data) {
for (TestEnum userType : TestEnum.values()) {
if (userType.getName().equals(data)) {
return userType;
}
}
return null;
}
}
Currently I have several enums defined over several classes. They all look similar to the one shown below:
public class ApaMessage {
private String apaMessage;
private final int FIXED_LENGTH_SIZE=39;
public enum ApaFields {
FIELD1(ApaUtils.ApaFieldTypes.POSITION_BASED, null, "field1", 2, 3, false, false),
private final ApaUtils.ApaFieldTypes type;
private final String ApaName;
private final String jsonName;
private final int start;
private final int finish;
private boolean required = false;
private boolean withDelimiter = false;
ApaFields(ApaUtils.ApaFieldTypes type, String ApaName, String jsonName, int start, int finish, boolean required, boolean withDelimiter) {
this.type = type;
this.ApaName = ApaName;
this.jsonName = jsonName;
this.start = start;
this.finish = finish;
this.required = required;
this.withDelimiter = withDelimiter;
}
}
There is also a method defined in ApaMessage:
private HashMap<String,Object> getApaJsonFieldsAndValues() {
HashMap<String, Object> jsonApaData = new HashMap<String, Object>();
for (ApaFields field : ApaFields.values()) {
jsonApaData.put(field.jsonName, getApaFieldValue(field));
}
return jsonApaData;
}
The problem is although there isn't a lot of code, I will soon have 10-20 of these enums. I would like to create an abstract base class where the HashMap method, and other similar methods can be part of. The base class should accept an ApaFields enum and other enums and do what the getApaJsonFieldsAndValues does. The problem is, how can the base class access the passed enum values and the internal fields such as jsonName to do the loop?
I have tried different approaches but the main problem is that the base class cannot seem to access the values. Is there any way around this? Alternatively, is there a better approach? Thanks
EDIT:
Basically I would like something like this in the base class. Note the below doesn't compile.
public abstract class ApaRequestMessage {
private Class<? extends Enum<?>> apaRequestMessageFields;
private String apaMessage;
public <T extends Enum<T>> void ApaRequest(Object apaRequestFields, String apaMessage) {
apaRequestMessageFields = (Class<? extends Enum<?>>) apaRequestFields;
this.apaMessage = apaMessage;
for (Field field: apaRequestMessageFields.values()) {
//this doesn't work because it cannot access the values of apaRequestMessageFields
}
}
}
And then call the base method as follows, although not sure if this is correct, where ApaFields is the inner enum defined above.
ApaRequest(ApaFields.class, somestringmessage);
I came across something similar when trying to define a db schema using enums as columns in the table. I eventually took this route.
Define a base class with sufficient generic signature to ensure the enum is properly built.
public class Table<Column extends Enum<? extends Column>> {
// Name of the table.
protected final String tableName;
// All of the columns in the table. This is actually an EnumSet so very efficient.
protected final Set<Column> columns;
/**
* The base interface for all Column enums.
*/
public interface Columns {
// What type does it have in the database?
public Type getType();
}
// Small list of database types.
public enum Type {
String, Number, Date;
}
public Table(String tableName,
Set<Column> columns) {
this.tableName = tableName;
this.columns = columns;
}
}
Now extend this for each table - here is a simple VersionTable:
public class VersionTable extends Table<VersionTable.Column> {
public enum Column implements Table.Columns {
Version(Table.Type.String),
ReleaseDate(Table.Type.Date);
final Table.Type type;
Column(Table.Type type) {
this.type = type;
}
#Override
public Type getType() {
return type;
}
}
public VersionTable() {
super("Versions", EnumSet.allOf(Column.class));
}
}
Now you have all of the core functionality in the base class and all the sub-classes need to do is implement the interface on the enum.
I realise this does not address the issue of duplicated bolierplate code in all of your enums but it does move alomst all of it elsewhere.
For instance i have some entity - Product
public class Product {
...
private String name;
private int count;
private Product associatedProduct;
...
// GETTERS & SETTERS
}
And also i have product finder which allows to find the product by filters:
public interface Finder<T> {
Set<T> find(Filter... filters);
}
And now i can execute the following code:
Finder<Product> finder = ...;
// find all products with name 'cucumber'
Set<Product> finder.find(Filter.equals("name", "cucumber"));
We don't like this code because i should have the 'soft' link to field name "name" and i can't have compile time exception in case misprint or any other mistake.
For this reason i have created the code generator which generate static links to properties.
The generated class looks like:
public final class $Product {
private final String context;
// some factory is used to instance creation
$PostEntity() {this.context = "";}
$PostEntity(String context) {this.context = context;}
public String name() { return context + "name";}
public String count() { return context + "count";}
public String associatedProduct() { return context + "associatedProduct";}
public $Product associatedProductDot() { return new $Product( this.context + "associatedProduct.");}
}
For now i can make the following:
Set<Product> finder.find(Filter.equals(Links.PRODUCT.name() , "cucumber"));
//or
Set<Product> finder.find(Filter.equals(Links.PRODUCT.associatedProductDot().name() , "cucumber"));
It works like a charm and i happy.
I know alternative approach with using proxy objects, but it imposes additional overhead in runtime and adds some magical moment in code, so this variant does not suit me.
And finally my question:
There is a more elegant approach to implement this functionality with using java 8?
Java 8 has everything you need:
public static <C,P> Predicate<C> byProperty(Function<C,P> f, P value) {
return component->Objects.equals(f.apply(component), value);
}
public static <C> Set<C> find(Collection<? extends C> c, Predicate<? super C> p) {
return c.stream().filter(p).collect(Collectors.<C>toSet());
}
The standard interface for filtering is called Predicate and the first method above allows you to create arbitrary Predicates for matching a property of a component type C. The second method shows how you can get a Set of matching components out of a Collection using the Stream API. Then you can use it like this:
List<Product> list;
…
Set<Product> set=find(list, byProperty(Product::getName, "foo"));
or
Set<Product> set=find(list, byProperty(Product::getCount, 42));
Note that this is type safe and contains compile-time checked references (your “hard links”) to your properties. The only difference to what you have asked for is that they refer to the getter method rather than to the field names, as a) field references are not supported and b) your fields are private anyway.
Note that you can augment these methods by another factory allowing to provide a value-predicate rather than a constant:
public static <C,P> Predicate<C> matchProp(
Function<C,P> f, Predicate<? super P> value) {
return component->value.test(f.apply(component));
}
This allows use cases like:
Set<Product> set=find(list, matchProp(Product::getCount, count -> count>100));
See Lambda Expressions
or
Set<Product> set=find(list, matchProp(Product::getName, String::isEmpty));
The fastest thing is to provide your own implementation of the Filter interface. Since I don't know your Filter interface, I have to make an assumption about how it looks like. Here is my assumption:
public interface Filter<T> {
boolean matches(T t);
}
By the way, I think the interface Finder should instead look like this:
public interface Finder<T> {
Set<T> find(Filter<? super T>... filters);
}
So, you could have a class like this:
public final class ProductFilters {
private ProductFilters() { /* Utility class */ }
public static Filter<Product> byName(final String name) {
return new Filter() {
public boolean matches(Product t) {
return name.equals(t.getName());
}
}
}
}
You could even put this inside class Product, which can make it a little bit nicer:
public class Product {
private String name;
public static final class Filters {
private Filters() { /* Utility Class */ }
public static Filter<Product> byName(final String name) {
return new Filter() {
public boolean matches(final Product t) {
return name.equals(t.name);
}
};
}
}
}
And yes, Java 8 makes this stuff nicer, the explicit anonymous class can syntactically be replaced by a lambda, like this:
public class Product {
private String name;
public static final class Filters {
private Filters() { /* Utility Class */ }
public static Filter<Product> byName(final String name) {
return t -> name.equals(t.name);
}
}
}
Your code that uses the filters could now look like this:
Set<Product> cucumbers = finder.find(Product.Filters.byName("cucumber"));
The Filter<T> interface is present in Java 8 in package java.util.function. It's name there is Predicate<T>, and the essential part looks like this:
public interface Predicate<T> {
boolean test(T t);
}
If the products that are to be filtered can be made available as Stream either directly, or via a Collection, you can use the new java.util.stream API for filtering. For the example I assume that the products to be filtered are in a Set, too. The code that filters products could look like this:
Set<Product> potentialCucumbers = ...;
// Inline lambda:
Set<Product> cucumbers = potentialCucumbers.stream().filter(p -> "cucumber".equals(p.getName())).collect(Collectors.toSet());
// Stored lambda as above:
Set<Product> cucumbers = potentialCucumbers.stream().filter(Product.Filters.byName("cucumber")).collect(Collectors.toSet());
I really like static imports for that stuff as they can significantly reduce line length. With static imports it looks like this:
Set<Product> potentialCucumbers = ...;
// Inline lambda:
Set<Product> cucumbers = potentialCucumbers.stream().filter(p -> "cucumber".equals(p.getName())).collect(toSet());
// Stored lambda as above:
Set<Product> cucumbers = potentialCucumbers.stream().filter(byName("cucumber")).collect(toSet());
My suggestion would be to use predicates rather than your Filter classes. They make for cleaner code. I would also suggest making commonly used properties like "name" or "owner" into interfaces that provide predicates for searchability. For instance, for the "name" and "owner" properties you might have two interfaces called "Named" and "Owned":
public interface Named {
public String getName();
public void setName(String name);
static <T extends Named> Predicate<T> nameEquals(Class<T> clazz, String s){
return ((p) -> {
if (s == null){
return p.getName() == null;
}
return s.equals(p.getName());
});
}
}
public interface Owned {
public String getOwner();
public void setOwner(String owner);
public static <T extends Owned> Predicate<T> ownerEquals(Class<T> clazz, String s){
return ((p) -> {
if (s == null){
return p.getOwner() == null;
}
return s.equals(p.getOwner());
});
}
}
Then your Product class implements these interfaces, along with a couple simple convenience methods for calling the interface static methods:
public class Product implements Named, Owned{
private String name;
private String owner;
public String getOwner() {
return owner;
}
public String getName() {
return name;
}
public void setOwner(String owner){
this.owner = owner;
}
public void setName(String name){
this.name = name;
}
public static Predicate<Product> nameEquals(String s){
return Named.nameEquals(Product.class, s);
}
public static Predicate<Product> ownerEquals(String s){
return Owned.ownerEquals(Product.class, s);
}
}
And voila, your Product is searchable. Then your find() method's signature changes to take a predicate:
public interface Finder<T> {
Set<T> find(Predicate p);
}
One of the wonderful things about predicates is how easy they are to combine and compound with one another. For example, let's say we want to find() any products named "cucumber" who aren't owned by "john", or any products owned by "john" with any other names. The call to find() is pretty clean and understandable:
finder.find(
Product.nameEquals("cucumber")
.and(Product.ownerEquals("john").negate())
.or(
Product.ownerEquals("john")
.and(Product.nameEquals("cucumber").negate())
)
);
I should be pretty clear what this block of code is doing. I used the indentation to try and make it clearer how they combine. We can combine the different predicates to our hearts' content.
I would like to have a class that emulates the EnumMap, but stores int values instead of some kind of object. Now obviously you could make an EnumMap that maps to Integers, but that's a lot of autoboxing that I'd like to avoid if it's possible to do so.
So I'd like a class like this:
public class EnumIntAttributeMap
{
enum Attribute
{
Height, Weight;
}
private final int[] values;
public EnumIntAttributeMap()
{
this.values = new int[Attribute.values().length];
}
public int getValue(Attribute a)
{
return this.values[a.ordinal()];
}
public void setValue(Attribute a, int value)
{
this.values[a.ordinal()] = value;
}
}
Except I'd like to make a version that's generic across all enums. Now, since the .values() and .ordinal() methods are implicitly added by the compiler, it seems like the only way to access them would be with reflection, which would eat up the performance gains I'm trying to gain by avoiding auto-boxing, but maybe there's something I'm missing.
Any thoughts?
EDIT:
I think my initial question was unclear. I would like a class that takes (as a generic parameter) an enum, and then can use the same operations.
So I could use it with any kind of enum without needing to write the class for each kind of enum each time. Such as:
enum Attribute { Height, Weight }
enum AbilityScore {Str, Dex, Con, Int, Wis, Cha}
IdealClass<Attribute> attributeVersion;
IdealClass<AbilityScore> abilityScoreVersion;
and so on.
This is a solution:
public class EnumIntMap<E extends Enum<E>> {
private final int[] values;
public EnumIntMap(Class<E> cls)
{
this.values = new int[cls.getEnumConstants().length];
}
public int getValue(E a)
{
return this.values[a.ordinal()];
}
public void setValue(E a, int value)
{
this.values[a.ordinal()] = value;
}
}
You will have to initialize the map with the class of the enum as well.
enum Attribute { Height, Weight }
enum AbilityScore {Str, Dex, Con, Int, Wis, Cha}
EnumIntMap<Attribute> attributeVersion = new EnumIntMap(Attribute.class);
EnumIntMap<AbilityScore> abilityScoreVersion = new EnumIntMap(AbilityScore.class);
The key to doing this genetically is knowing the generic bound for an enum:
<T extends Enum<T>>
Your class can work for any enum as follows:
public class EnumIntAttributeMap<T extends Enum<T>> {
private final int[] values;
private final Class<T> clazz;
public EnumIntAttributeMap(Class<T> clazz) {
this.clazz = clazz;
this.values = new int[clazz.getEnumConstants().length];
}
public int getValue(T a) {
return this.values[a.ordinal()];
}
public void setValue(T a, int value) {
this.values[a.ordinal()] = value;
}
}
Note that the constructor requires a type token, which is required due to java's runtime type erasure.
All java Enum implicitly extend java.lang.Enum, so I think what you are looking for is:
public class EnumIntAttributeMap<E extends java.lang.Enum<E>> {
// [...]
}
Read this: Oracle's Doc on Enums.
I think you could do something that is similar to the Planets example:
public enum YourEnum{
HEIGHT (0),
WIDTH (1);
private final int index;
YourEnum(int index) {
this.index = index;
}
public int index() { return index; }
}
This is what I personally use and gives me nice standardization in the APIs
Common Interface :
public interface IValueEnum<T> {
T value();
}
Enums implement it like this:
public enum MyEnum implements IValueEnum<Integer> {
WIDTH(1),
HEIGHT(2);
private final int value;
MyEnum(int value) { this.value = value; }
#Override
public Integer value() {
return value;
}
}
Now you can get the final value by going MyEnum.WIDTH.value(), etc. Since it's using generics other enums could use Longs or Strings or what have you. It allows you to have a single simple Enum based interface in the application. It also allows you to be completely generic in your Map, etc.
EDIT: Based on New Example
public class IdealClass<T extends Enum> {
T enumValue;
public IdealClass() {
}
}
I am using Tapestry 5.3.6 for a web application and I want the user to edit an instance of a Java class (a "bean", or POJO) using a web form (which immediately suggests the use of beaneditform) - however the Java class to be edited has a fairly complex structure. I am looking for the simplest way of doing this in Tapestry 5.
Firstly, lets define some utility classes e.g.
public class ModelObject {
private URI uri;
private boolean modified;
// the usual constructors, getters and setters ...
}
public class Literal<T> extends ModelObject {
private Class<?> valueClass;
private T value;
public Literal(Class<?> valueClass) {
this.valueClass = valueClass;
}
public Literal(Class<?> valueClass, T value) {
this.valueClass = valueClass;
this.value = value;
}
// the usual getters and setters ...
}
public class Link<T extends ModelObject> extends ModelObject {
private Class<?> targetClass;
private T target;
public Link(Class<?> targetClass) {
this.targetClass = targetClass;
}
public Link(Class<?> targetClass, T target) {
this.targetClass = targetClass;
this.target = target;
}
// the usual getters and setters ...
}
Now you can create some fairly complex data structures, for example:
public class HumanBeing extends ModelObject {
private Literal<String> name;
// ... other stuff
public HumanBeing() {
name = new Literal<String>(String.class);
}
// the usual getters and setters ...
}
public class Project extends ModelObject {
private Literal<String> projectName;
private Literal<Date> startDate;
private Literal<Date> endDate;
private Literal<Integer> someCounter;
private Link<HumanBeing> projectLeader;
private Link<HumanBeing> projectManager;
// ... other stuff, including lists of things, that may be Literals or
// Links ... e.g. (ModelObjectList is an enhanced ArrayList that remembers
// the type(s) of the objects it contains - to get around type erasure ...
private ModelObjectList<Link<HumanBeing>> projectMembers;
private ModelObjectList<Link<Project>> relatedProjects;
private ModelObjectList<Literal<String>> projectAliases;
// the usual constructors, getters and setters for all of the above ...
public Project() {
projectName = new Literal<String>(String.class);
startDate = new Literal<Date>(Date.class);
endDate = new Literal<Date>(Date.class);
someCounter = new Literal<Integer>(Integer.class);
projectLeader = new Link<HumanBeing>(HumanBeing.class);
projectManager = new Link<HumanBeing>(HumanBeing.class);
projectMembers = new ModelObjectList<Link<HumanBeing>>(Link.class, HumanBeing.class);
// ... more ...
}
}
If you point beaneditform at an instance of Project.class, you will not get very far before you have to supply a lot of custom coercers, translators, valueencoders, etc - and then you still run into the problem that you can't use generics when "contributing" said coercers, translators, valueencoders, etc.
I then started writing my own components to get around these problems (e.g. ModelObjectDisplay and ModelObjectEdit) but this would require me to understand a lot more of the guts of Tapestry than I have time to learn ... it feels like I might be able to do what I want using the standard components and liberal use of "delegate" etc. Can anyone see a simple path for me to take with this?
Thanks for reading this far.
PS: if you are wondering why I have done things like this, it is because the model represents linked data from an RDF graph database (aka triple-store) - I need to remember the URI of every bit of data and how it relates (links) to other bits of data (you are welcome to suggest better ways of doing this too :-)
EDIT:
#uklance suggested using display and edit blocks - here is what I had already tried:
Firstly, I had the following in AppPropertyDisplayBlocks.tml ...
<t:block id="literal">
<t:delegate to="literalType" t:value="literalValue" />
</t:block>
<t:block id="link">
<t:delegate to="linkType" t:value="linkValue" />
</t:block>
and in AppPropertyDisplayBlocks.java ...
public Block getLiteralType() {
Literal<?> literal = (Literal<?>) context.getPropertyValue();
Class<?> valueClass = literal.getValueClass();
if (!AppModule.modelTypes.containsKey(valueClass))
return null;
String blockId = AppModule.modelTypes.get(valueClass);
return resources.getBlock(blockId);
}
public Object getLiteralValue() {
Literal<?> literal = (Literal<?>) context.getPropertyValue();
return literal.getValue();
}
public Block getLinkType() {
Link<?> link = (Link<?>) context.getPropertyValue();
Class<?> targetClass = link.getTargetClass();
if (!AppModule.modelTypes.containsKey(targetClass))
return null;
String blockId = AppModule.modelTypes.get(targetClass);
return resources.getBlock(blockId);
}
public Object getLinkValue() {
Link<?> link = (Link<?>) context.getPropertyValue();
return link.getTarget();
}
AppModule.modelTypes is a map from java class to a String to be used by Tapestry e.g. Link.class -> "link" and Literal.class -> "literal" ... in AppModule I had the following code ...
public static void contributeDefaultDataTypeAnalyzer(
MappedConfiguration<Class<?>, String> configuration) {
for (Class<?> type : modelTypes.keySet()) {
String name = modelTypes.get(type);
configuration.add(type, name);
}
}
public static void contributeBeanBlockSource(
Configuration<BeanBlockContribution> configuration) {
// using HashSet removes duplicates ...
for (String name : new HashSet<String>(modelTypes.values())) {
configuration.add(new DisplayBlockContribution(name,
"blocks/AppPropertyDisplayBlocks", name));
configuration.add(new EditBlockContribution(name,
"blocks/AppPropertyEditBlocks", name));
}
}
I had similar code for the edit blocks ... however none of this seemed to work - I think because the original object was passed to the "delegate" rather than the de-referenced object which was either the value stored in the literal or the object the link pointed to (hmm... should be [Ll]inkTarget in the above, not [Ll]inkValue). I also kept running into errors where Tapestry couldn't find a suitable "translator", "valueencoder" or "coercer" ... I am under some time pressure so it is difficult to follow these twisty passages through in order to get out of the maze :-)
I would suggest to build a thin wrapper around the Objects you would like to edit though the BeanEditForm and pass those into it. So something like:
public class TapestryProject {
private Project project;
public TapestryProject(Project proj){
this.project = proj;
}
public String getName(){
this.project.getProjectName().getValue();
}
public void setName(String name){
this.project.getProjectName().setValue(name);
}
etc...
}
This way tapestry will deal with all the types it knows about leaving you free of having to create your own coersions (which is quite simple in itself by the way).
You can contribute blocks to display and edit your "link" and "literal" datatypes.
The beaneditform, beaneditor and beandisplay are backed by the BeanBlockSource service. BeanBlockSource is responsible for providing display and edit blocks for various datatypes.
If you download the tapestry source code and have a look at the following files:
tapestry-core\src\main\java\org\apache\tapestry5\corelib\pages\PropertyEditBlocks.java
tapestry-core\src\main\resources\org\apache\tapestry5\corelib\pages\PropertyEditBlocks.tml
tapestry-core\src\main\java\org\apache\tapestry5\services\TapestryModule.java
You will see how tapestry contributes EditBlockContribution and DisplayBlockContribution to provide default blocks (eg for a "date" datatype).
If you contribute to BeanBlockSource, you could provide display and edit blocks for your custom datatypes. This will require you reference blocks by id in a page. The page can be hidden from your users by annotating it with #WhitelistAccessOnly.
http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/BeanBlockSource.html
http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/DisplayBlockContribution.html
http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/services/EditBlockContribution.html
http://tapestry.apache.org/current/apidocs/org/apache/tapestry5/annotations/WhitelistAccessOnly.html
Here's an example of using an interface and a proxy to hide the implementation details from your model. Note how the proxy takes care of updating the modified flag and is able to map URI's from the Literal array to properties in the HumanBeing interface.
package com.github.uklance.triplestore;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
public class TripleStoreOrmTest {
public static class Literal<T> {
public String uri;
public boolean modified;
public Class<T> type;
public T value;
public Literal(String uri, Class<T> type, T value) {
super();
this.uri = uri;
this.type = type;
this.value = value;
}
#Override
public String toString() {
return "Literal [uri=" + uri + ", type=" + type + ", value=" + value + ", modified=" + modified + "]";
}
}
public interface HumanBeing {
public String getName();
public void setName(String name);
public int getAge();
public void setAge();
}
public interface TripleStoreProxy {
public Map<String, Literal<?>> getLiteralMap();
}
#Test
public void testMockTripleStore() {
Literal<?>[] literals = {
new Literal<String>("http://humanBeing/1/Name", String.class, "Henry"),
new Literal<Integer>("http://humanBeing/1/Age", Integer.class, 21)
};
System.out.println("Before " + Arrays.asList(literals));
HumanBeing humanBeingProxy = createProxy(literals, HumanBeing.class);
System.out.println("Before Name: " + humanBeingProxy.getName());
System.out.println("Before Age: " + humanBeingProxy.getAge());
humanBeingProxy.setName("Adam");
System.out.println("After Name: " + humanBeingProxy.getName());
System.out.println("After Age: " + humanBeingProxy.getAge());
Map<String, Literal<?>> literalMap = ((TripleStoreProxy) humanBeingProxy).getLiteralMap();
System.out.println("After " + literalMap);
}
protected <T> T createProxy(Literal<?>[] literals, Class<T> type) {
Class<?>[] proxyInterfaces = { type, TripleStoreProxy.class };
final Map<String, Literal> literalMap = new HashMap<String, Literal>();
for (Literal<?> literal : literals) {
String name = literal.uri.substring(literal.uri.lastIndexOf("/") + 1);
literalMap.put(name, literal);
}
InvocationHandler handler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass().equals(TripleStoreProxy.class)) {
return literalMap;
}
if (method.getName().startsWith("get")) {
String name = method.getName().substring(3);
return literalMap.get(name).value;
} else if (method.getName().startsWith("set")) {
String name = method.getName().substring(3);
Literal<Object> literal = literalMap.get(name);
literal.value = args[0];
literal.modified = true;
}
return null;
}
};
return type.cast(Proxy.newProxyInstance(getClass().getClassLoader(), proxyInterfaces, handler));
}
}