Extensible Properties for the JVM, refined
Remi and Richard are talking about properties again, refining their ideas. I figured I should organize my thoughts as well.FIrst off, how about some aggressive requirements:
- Properties should support arbitrary implementations! It should be possible to have properties backed by fields, HashMaps, databases, calculations, devices etcetera. Using such a property should require minimal amounts of code.
- Properties should support pluggable behaviours. Properties should be able to be made Observable, non-nullable, validated, copied-on-write, etcetera. Using such a behaviour should require minimal amounts of code.
- Properties should not increase per-instance memory usage.
- Properties should have a minimal impact on performance
- Properties should be toolable.
- Properties should interact cleanly with legacy getters and setters.
Iterable
interface, I think the properties for the JVM should have a Property
interface like this:public interface java.lang.Property<B,V> {
void setName(String name);
void setDelegate(Property other);
void set(B bean, V value);
V get(B bean);
}
Note that this interface provides a delegate to allow Properties
to be chained. This allows behaviours like observable to be stacked.When defining a property, the coder provides a list of properties objects to be chained in varargs style:
class Foo {
private property(new ObservableProperty(), new NonNullProperty()) String bar = "phils";
}
...is syntactic sugar for:class Foo {
private String bar = "phils";
private static Property<Foo,String> $PROPERTY_bar = new ObservableProperty();
static {
Property<Foo,String> p1 = new NonNullProperty();
Property<Foo,String> p2 = new DefaultProperty<Foo,String>() {
public String get(Foo foo) { return bar; }
public String set(Foo foo, String bar) { foo.bar = bar; }
};
p1.setDelegate(p2);
$PROPERTY_bar.setDelegate(p1);
}
}
Let's disect the generated code:
private static Property<Foo,String> $PROPERTY_bar
The property literal is a static member of the class. The property is static so we don't increase memory usage per-instance. The name starts with a$
dollar sign to denote the field is compiler-generated and cannot be referenced normally.= new ObservableProperty()
The property literal instance is the outermost decorated value. This code is the first argument after theproperty
token in the original source.static { Property<Foo,String> p1 = new NonNullProperty(); p2 = ...
Properties are constructed in order, from outermost to innermost. They're created in a static initializer because the property literals are shared between all instances.= new DefaultProperty{Foo,String}() { ... }
Default code to retrieve the field is generated. Because actual bytecode is created, field access is significantly faster than reflection.p1.setDelegate(p2);
The chain of property literals is linked together, innermost first
Foo foo = new Foo();
foo#bar = "bomber";
System.out.println(foo#bar);
... and property literals Property<Foo,String> barProperty = Foo##bar;
barProperty.set(new Foo(), "revolution");
System.out.println(barProperty.get(new Foo()))
Noting of course that the symbols #
and ##
are totally arbitrary and could be exchanged with any symbols, such as :
, $
, []
. I think that multiple symbols are necessary to differentiate between a particular instance's property value and a property literal, otherwise the symbol #bar
is ambiguous inside the Foo.java source file.