Property Spec (draft 3)
Forewords
"when I design a class, I think of properties, not
getters and setters. I want that intent reflected in the code. I don't
want to piece together a dozen lines of getter/setter code, checking that
all the names and parameters are just so."
Cay Horstmann
"Over the years I've written a lot of Components and POJOs and thus I've
written even more of these property access methods. There are without a
doubt certain patterns that are recurring. Recurring patterns means
boring boiler plate code, and that's a bad thing, especially since
these tend to be hard to refactor to a utility method."
Mikael Grev
"Currently, many many tools, especially frameworks, have to access beans
in a dynamic way. By this I mean where a String property name is used
to identify a property and reflection is used to access it. But this design is really utter madness!!! A key strength of Java is
static typing, allowing compile time detection of errors and
refactoring. With a design like this, all these features of Java are
lost. "
Stephen Colebourne
"Property Literals (aka Property References, etc) which I think is a really, really great thing to be thinking about."
Richard Bair
"The mighty buffalo of the Java Beans
boilerplate animal kingdom are bound properties. They're the
kind of properties we write so that our beans can be
automatically and dynamically synchronized with a GUI or with
each other."
Hans Muller
Why adding property support in Java ?
Property is a well know design concept widely used but that don't have any support in Java language.
Currently the way to represent a property is to use the getter/setter pattern defined by the Bean Specification
and to rely on bean Introspector to obtain an object describing the property (a PropertyDescriptor).
This pattern has many problems, such as:
- Not typesafe - Since a property descriptor is a String and access is done by reflection.
- Boiler plate code - You have to write getter and setter even if they just get and setter a private field. It is worse when you want to declare a bound property.
- Error prone - Because property is not feature of the language, it's easy to change the name/type of a getter without changing the name/type of the corresponding setter.
Property Syntax
A type that defines at least a property is called a bean type.
Allowed bean types are classes, abstract classes, interfaces and enum types.
It is a compile-time error to declare a property in an annotation type.
A property is declared using the following syntax:
MethodOrFieldDecl:
Type Identifier MethodOrFieldRest
property Type Identifier {[]} {bound} {Accessor}
Accessor:
get {GetRest}
set {SetRest}
GetRest:
MethodBody {set SetRest}
set
SetRest:
( [final] [Annotations] Type VariableDeclaratorId {[]} ) MethodBody
'Property', 'get', 'set' and 'bean' are not real keyword but local keyword. They are recognized
not as keyword but as identifier by the scanner and their value are checked during the parsing.
So 'property', 'get', 'set 'and 'bean' can freely be used anywhere in a code as identifier.
Open issues :
- Is the semicolon mandatory in case of user-defined property ?
- Should we add use get() instead of get for user-defined get.
Examples:
public class MyBean {
public property String name1; // read-write
public property String name2 get; // read-only
public property String name3 set; // write-only
public property String name4 bound; // bound
public property String name5 get set; // illegal
public property String name6 get { return "hello"; }; // read-only
public property String name7 set(String name) { }; // write-only
private boolean isReal;
public property String real
get {
return String.valueOf(isReal);
}
set(String real) {
isReal= String.decode(real);
}; // read-write
}
A compile time error occurs when accessing to a property symbol in its getter or setter.
The property symbol may not appear in the symbol table.
public property String name8 get { return name8; }; // illegalDiscussion:
this rule exists to avoid beginners to create infinite recursive accessors.
Modifiers
Properties like any other members allow one of the following access modifiers (jls3 6.6) :
public, private, package visible or protected.
Abstract or native property:
A property can be declared abstract or native.
These keywords are transferred on generated getter/setter.
A compile-time error occurs if the property is declared with getter or setter body.
Volatile and transient property:
A property can be declared volatile or transient.
These keywords are transferred on generated field.
A compile-time error occurs if the property is declared with getter or setter body.
Strictfp, synchronized, final or static property:
A property can be declared strictfp , synchronized, final or static.
These keywords are transferred on getter/setter (generated or not).
Accessing to a property
A property like a field is accessible using the dot ('.') operator.
MyBean bean = new MyBean();
bean.name3 = bean.name2;
But unlike field, property access are translated to method call:
- Get a property value is equivalent to call its getter.
-
Set a property value is equivalent to call its setter
unless the property is accessed in the bean's constructor.
Accessing to a property in a constructor:
- A compile-time warning is raised in case of property access to
- a property declared in another class that the current one
if the property is not private or final.
- a property access in a constructor to a property which is not user-defined and declared
in the current class is equivalent to an access to the underlying field
(so getter and setter are bypassed)
There is a snag with constructor and property access. If a property is not private or final, an access
to a property in a constructor is a call to a potentially overridable method in a constructor
which can lead to a publication of a not fully initialized this.
It's a compile-time error to declare a bound property without a method
<T> void propertyChanged(Property<beanType, ?> prop, T oldValue, T newValue)
accessible in the bean class or in its supertypes.
It's a compile time error to:
- try to assign an expression to a read-only property
- try to retrieve the value of a write-only property
Open Issue:
Should we support indexed properties ?
Abstract properties, inheritance and overriding rules
Like methods, properties can be abstract:
- A type that declare an abstract property must be maled as abstract.
- A type that implements an interface or a class that declare an abstract property must implement
that property by providing a non-abstract property.
Like methods, properties can be overriden if the following rules hold:
- Properties must have the same name
- Propertties must have the same type
- A read-only property can be overriden by a get or get/set property.
- A write-only property can be overriden by a set or get/set property.
- A bound property must be overriden by a bound property.
The property object
A property object (or property literal, property reference) is a runtime object that
stands for a property and allows to query or modify the value of a property.
A property object is accessible using '#' (sharp), with the following grammar:
IdentifierSuffix:
[ ( ] {[]} . class | Expression ])
Arguments
. ( class | ExplicitGenericInvocation | this | super Arguments |
new [NonWildcardTypeArguments] InnerCreator )
# Identifier
This object is a subtype of java.lang.Property and is parametrized
by the type of the bean type and the type of the property.
By example if the bean Author declares a property named lastname of type String,
Author#lastname is an instance of java.lang.Property typed Property<Author, String>
and its method get and set allows to query/modify the value of a bean object
taken as argument.
public class Author {
property String name;
property String lastname;
...
}
...
Author author = new Author("joe", "forax");
Property<Author, String> name = Author#name;
name.set(author, "rémi");
String myname= Author#lastname.get(author);Property object are interned, it is legal to test the identity of a properties using ==
Author#name == Author#name // is true
Open issue:
- Supporting property objects means generates lot of code that will increase
the size of the application.
We perhaps need to generate inner classes at runtime like reflection does.
Proposed code for java.lang.Property:
package java.lang;public abstract class Property<B, T> { protected Property(int flags, String name) { ... } public abstract T get(B bean); public abstract void set(B bean, T value); public final property String name get; public final property Class<T> type get {
...
}
public final property Class<B> declaringType get {
...
} public final property boolean bound get { ... } public final property boolean getterOnly get {
...
} public final property boolean setterOnly get {
...
}}Open issue:
Should class Property belong to package java.lang or java.lang.reflect ?
Discussion:
Why use java.lang.Property and don't reuse java.beans.PropertyDescriptor ?
Because PropertyDescriptor:
- is not type safe
- is mutable
- is string based
- relies on java.lang.reflect.Method.
Property object and switch
Properties can be used like enums in a switch statement.
SwitchStatement:
switch ( Expression ) SwitchBlock
SwitchLabel:
case ConstantExpression :
case EnumOrPropertyConstantName :
default :
EnumOrPropertyConstantName:
Identifier
The type of the
Expression can be a subtype of java.lang.Property<? extends
BeanType, ?>
where
BeanType is a specific type.
It's a compile-time error if a case label is not a property declared in
BeanType.
Bean bean=new Bean();
bean.addPropertyListener(new PropertyListener<Bean>() {
public <T> propertyChanged(Property<Bean, T> property, T oldValue, T newValue) {
switch(property) {
case name:
frame.setTitle((String)newValue);
break;
case value:
slider.setValue((Integer)newValue);
break;
}
}
}
Like enums, case items are not qualified.
The compiler verifies that
the properties Bean.name and
Bean.value exist.
Open issue:
this feature will be withdrawn if
bug 5029289 (switch on strings) is not implemented.
Translation and backward compatibility
The translation by the compiler needs to be binary compatible with code written using the Bean pattern.
Translations rules:
- acessor translation:
- getter of a property x is translated to a method named "x$get".
- setter of a property x is translated to a method named "x$set".
in order to avoid conflicts, the compiler could append any '$'.
- the setter code of a bound property contains a call to propertyChanged.
- Property access are translated to call to getter and setter.
- an access to a property object BeanType#name is translated to
BeanType$number.INSTANCE where number is a number incremented by the compiler.
public class Author {
public property String firstname get {
return fullname.split(" ")[0];
};
public property String fullname bound;
private <T> void propertyChanged(Property<Author, ?> property, T oldValue, T newValue) {
System.out.println(property.name+" "+oldValue+" "+newValue);
}
}a possible translation (written in Java code):
public class Author {
public String firstname$get() {
return getFullname().split(" ")[0];
}
public static class Author$1 extends Property<Author, String> {
Author$1() {
super(GETTER_ONLY, "name");
}
public String get(Author author) {
return author.firstname$get();
}
public void set(Author author, String name) {
throw new UnsupportedOperationException();
}
public static final Author$1 INSTANCE = new Author$1();
}
private String fullname;
public String fullname$get() {
return fullname;
}
public void fullname$set(String fullname) {
String oldValue = this.fullname;
this.fullname = fullname;
propertyChanged(Author$2.INSTANCE, oldValue, fullname);
}
public static class Author$2 extends Property<Author, String> {
Author$2() {
super(0, "fullname");
}
public String get(Author author) {
return author.fullname$get();
}
public void set(Author author, String fullname) {
author.fullname$set(fullname);
}
public static final Author$2 INSTANCE = new Author$2();
}
private <T> void propertyChanged(Property<Author, ?> property, T oldValue, T newValue) {
System.out.println(property.getName()+" changed "+oldValue+" "+newValue);
}
}Open issue:
- bound property setter doesn't use the getter like AbstractBean of SwingX
see http://javadesktop.org/swinglabs/build/weekly/latest/swingx-HEAD/javadoc/org/jdesktop/beans/AbstractBean.html
The Properties Attribute
In order to enable cross-compilation a new class level attribute named Properties
is needed.
Properties_attribute {
u2 attribute_name_index;
u4 attribute_length
u2 number_of_properties;
{ u2 property_name_index;
u2 property_access_flags;
u2 property_getter_name_info_index;
u2 property_setter_name_info_index;
} properties[number_of_properties];
}
The items of the Properties_attribute structure are as follows:
attribute_name_index
The value of the attribute_name_index item must be a valid
index into the constant_pool table. The constant_pool entry
at that index must be a CONSTANT_Utf8_info (§4.5.7) structure
representing the string "Properties".
attribute_length
The value of the attribute_length item indicates the length of
the attribute, excluding the initial six bytes.
number_of_properties
The value of the number_of_properties item indicates the number
of entries in the properties array.
properties[]
Each properties array entry contains the following four items:
property_name_index
The value of the property_name_index item must be a valid index into the
constant_pool table, and the entry at that index must be a
CONSTANT_Utf8_info (§4.5.7) structure that represents the
original simple name of the property, as given in the source code from
which this class file was compiled.
property_access_flags
The value of the property_class_access_flags item is a
mask of flags used to denote access permissions to and
properties of the current property as declared in the source
code from which this class file was compiled. It is used by
compilers to recover the original information when source
code is not available.
Flag Name
| Value
| Meaning
|
ACC_PUBLIC
| 0x0001
| Marked public in source
|
ACC_PRIVATE
| 0x0002
| Marked private in source
|
ACC_PROTECTED
| 0x0004
| Marked protected in source
|
ACC_STATIC
| 0x0008
| Marked static in source
|
ACC_FINAL
| 0x0010
| Marked final in source
|
ACC_SYNCHRONIZED
| 0x0020
| Marked synchronized in source
|
ACC_VOLATILE
| 0x0040
| Marked volatile in source
|
ACC_TRANSIENT
| 0x0080
| Marked transient in source
|
ACC_NATIVE
| 0x0100
| Marked native in source
|
ACC_ABSTRACT
| 0x0400
| Marked abstract in source
|
ACC_STRICT
| 0x0800
| Marked strictfp in source
|
ACC_USER_DEFINED
| 0x2000
| User defined property
|
ACC_BOUND
| 0x4000
| Marked bound in source
|
property_getter_name_info_index
If the property has no getter, the value of the
property_getter_name_info_index item must be zero. Otherwise
it must be a valid index into the constant_pool table, and the entry at that index
must be a CONSTANT_Utf8_info (§4.5.7) structure that represents the
name of the getter of the property given by the compiler.
property_setter_name_info_index
If the property has no setter, the value of the
property_setter_name_info_index item must be zero. Otherwise
it must be a valid index into the constant_pool table, and the entry at that index
must be a CONSTANT_Utf8_info (§4.5.7) structure that represents the
name of the setter of the property given by the compiler.
java.lang.Class update
java.lang.Class need to be updated by adding to new methods specified as follow:
/**
* Returns an array containing the properties declared by this
* type. This method may be used to iterate over the properties
* as follows:
*
* Bean bean = ...
* for(Property<Bean, ?> prop: bean.getClass().getProperties())
* System.out.println(prop.name + " = "+ prop.get(bean));
*
* @return an array containing the properties of this type
* or an empty array if the current type doesn't define any
* property.
*/
public Property<Bean, ?>[] getProperties();
/**
* Returns a properties of this type with a specified name.
* The string must match an identifier used to declare a property without extraneous whitespace.
*
* @param name the name of the property to be returned.
* @return a property with the specified name
* @throws IllegalArgumentException if this type has no property with the specified type.
* @throws NullPointerException if name is null.
*/
public Property<Bean, ?> getProperty(String name);
The signature of the method getProperties is not currently legal, technically it's legal
but unsafe, but we hope this signature will be legal at the time properties will be
included into the spec.
JSR 199 and properties
TBF (two new nodes Property and SharpAccess)
Javadoc
Javadoc needs to be updated to nicely display properties
Bean spec update
The bean spec should be updated to rely on property instead of String and reflection.
By example by:
- retrofitting java.beans.PropertyChangeEvent to use a java.lang.Property instead of a String.
public class PropertyChangeEvent { public PropertyChangeEvent(Object source, String propertyName, Object oldValue, Object newValue) { super(source); this.propName = propertyName; this.oldValue = oldValue; this.newValue = newValue; } /** * @since 1.7 */ public PropertyChangeEvent(Object source, Property<?, ?> property, Object oldValue, Object newValue) {
super(source);
this.property = property;
this.oldValue = oldValue;
this.newValue = newValue;
} private Property<?, ?> prop; /** * @since 1.7 */ public property Property<?, ?> property get { if (prop != null) return prop; return prop = getSource().getClass().getProperty(propertyName); }; private String propName; public property String propertyName get { if (propName !=null) return propName; return prop.name; }; public property oldValue get; public property newValue get;}