Proper Properties - A Concrete Proposal for Java 7

Author

Mikael Grev - MiG InfoCom
Uppsala, Sweden

Proposal Version History

0.1 Pre-release

Implementation

Downloads

Change Log

JavaDoc

UML Class Diagram over Property hierarchy

All files are also available from a java.net Subversion repository with the following URL:

https://properties.dev.java.net/svn/properties/trunk Username and password are same as your java.net user

Discussion and Contact Information

This forum will be used to discuss everything concerning this proposal.

For private messages and discussions you can contact me here: properties (at) miginfocom (dot) com

1. Background

Properties are available in Java 6 and earlier in the form of a naming convention. This can be read in the JavaBean Spec 1.01 . The definition of a property is the implementation of a get[propertyName]/set[propertyName] pair where the logic of the property is the code in these two methods. Which properties a class defines is only denoted by this naming convention. Metadata for using the property in an IDE may optionally be provided for a property using BeanInfos. An enhancement for this metadata is much needed but not provided by this proposal. There are several problems with this naming convention approach, all of which this proposal solves.

This property proposal provides a concrete solution, including a ready made and fully working implementation, to properties that strengthens the contract between the property writer and the Java framework. The three main goals for Proper Properties are:

Note that it is not a primary goal to make it easier to manually set and get values to and from a property. It might be a good target of opportunity though and is therefore added as suggestions at the end of this proposal.

The following objectives are to be fulfilled by this proposal. Properties should (in order of importance)

2. Solution

2.1. Class Hierarchy

You can view an UML diagram over the property hierarchy here.

A concrete class Property defines the general functionality of a property. It has support for observing value changes, validation and provides access to its meta data. Four specific subclasses are provided.

2.1.1. Property Classes and Interfaces

2.2. Metadata

Metadata such as if reading and/or writing of the value is supported is provided using a single annotation class, @BeanProperty. It has values that define some functionality of the built in property types. The values of this annotation is only read and stored when the class is first loaded. This information is then accessible through a PropertyInfo instance referenced from the property. This info class contains all information about the property and is only created once for every property (not for every instance of that property).

2.3. Validators

Validators are connected to properties using specific annotations. Which validators are created for a certain property is configurable using validator factories and is thus completely extensible. The most common validators are provided in the base implementation: NullValidator, SizeValidator, RangeValidator and RegexValidator. The Validator contains a single method providing everything needed to validate a property. They are chainable and optionally stateless. They are provided with the context in which to validate, for instance if it is a set, add, put or remove operation. The result of the validation(s) is returned from from the operation (for instance set returns a Result object and property.addAll(Collection) returns a ResultList object). Validators can be dynamically created for any annotation, even without modifying the annotation, through registering ValidatorFactory objects.

2.3. Result and ResultList

The setting of a value on a property always return a Result object denoting the actual result of the operation. Typical results are OK, OK_NO_CHANGE or REFUSE. The Result object optionally contains a status object.

2.4. Binding

Binding properties is not a formal part of this proposal since that is already handled in JSR 295. Proper Properties will work with any kind of binding. This is done through the top most interface PropertyAdapter which supports both stateless and bean connected properties.

3. Examples (Without Language Syntax Enhancements)

3.1. Basic Usage

A simple String property that is initialized to "Joe".
public final Property<String> name = new Property<String>("Joe");

3.2. Validated

A percentage that will not accept a value below 0.0 or above 100.0. Type will be created from the string by using the generic parameter type's constructor that takes a single String argument. Any Comparable object that takes a single String as argument in one of its constructors can be used.
@Range(min = "0.0", max = "100.0") public final Property<Double> percentage = new Property<Double>(50.0);
An email property string that will always contain a valid email address enforced with a Regular Expression.
@Regex("\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b") public final Property<String> email = new Property<String>();
or with a custom email validator
@Email public final Property<String> email = new Property<String>();

3.3. Property Groups

Property groups is used to group properties together so that a listener easily can perform an action for a whole group of properties. For instance the REPAINT action will repaint the bean if it is a Component. The values are Strings, but should normally be pointers to static strings.
@BeanProperty(groups = REPAINT) public final Property<Paint> background = new Property<Paint>(); @BeanProperty(groups = {INVALIDATE_CACHE, REPAINT}) public final Property<Paint> shadowColor = new Property<Paint>();

3.4. Collection Properties

Collection properties can model any collection of values using any Collection derived class.

The null validator is here configured to not allow a null collection or null values.

The collection is lazily created by default.

@Null(value = false, element = false) public final CollectionProperty<String> animals = new CollectionProperty<String>();
IndexedProperty is a sub class of CollectionProperty and is used instead of arrays. The advantage is that they are more flexible and can be wrapped in an unmodifiable List instead of being defensively cloned when returned, as is necessary for arrays. The List implementation can be replaced and lazily created.

The @BeanProperty annotation is here specifying that the collection should be wrapped in an unmodifiable collection when returned and setting a new collection is prohibited. The size of the collection can never be zero, it is here validated to always contain at least one element.

@Size(min = 1) @BeanProperty(get = UNMODIFIABLE, set = NONE) public final IndexedProperty<String> inMyEars = new IndexedProperty<String>( {"Tor", "Carl", "Dick", "Joe", "Cote", "Charles"});
MapProperty model a collection of key/value relationships and the property can use any Map derived as the actual storage, wither lazily created by overriding protected T create() or prociding an instance in the constructor.

The null validator is here configured to not allow a null map, null keys or null values.

The property will fire an PropertyEvent if the set value is not the same as the old value (i.e. referential equality). Other possible values are NOT_EQUAL and ALWAYS.

@Null(value = false, mapKey = false, mapValue = false) @BeanProperty(fire = NOT_SAME) public final MapProperty<Integer, Decorator> decorators = new MapProperty<Integer, Decorator>(new TreeMap());

3.5. Property Usage

This shows the most common usage patterns for changing the value(s) of a property.
name.set("Dilbert"); myBean.percentage.set(10.0); myBean.inMyEars.add("Wife", 0) myBean.decorators.put(10, new MirrorDecorator()); Decorator d = myBean.decorators.get(10); myBean.activeLevels.add("Donkey"); myBean.activeLevels.addAll(Arrays.asList(1, 2, 3));

3.6. Lazy Init

Property values can be lazily created when first used. This is done by overriding the create method to return the value.
public final IndexedProperty<String> largeLazyArray = new IndexedProperty<String>() { protected List<T> create() { return new ArrayList<T>(100000); } };

3.7. Result of a Modifying Operation

All modifying calls to the Property always returns a Result object which contains the result of the operation. A ResultList is returned from actions that do multiple operations.
if (email.set("a@b.com").changed()) doExpensiveRepaint();
or for an operation that does multiple operations
. if (activeLevels.addAll(Arrays.asList(1, 2, 3)).allOk() == false) notyfyUserOfFailure();

3.8. Observing Properties and Beans

It is possible to listen for single properties as easily as listening for a whole bean. The bean need not have any special methods defined or implement any specific interfaces.
email.addPropertyListener(new PropertyListener<String>() { public void propertyChange(PropertyEvent<String> e) { System.out.println("Email Length = " + e.getSource().get().length()); } });
Listening to all properties of a bean
PropertyHandler.addBeanListener(this, new PropertyListener() { public void propertyChange(PropertyEvent e) { System.out.println("Property: " + e.getSource()); } });

3.9. Property Hierarchies

Properties can be defined in a hierarchy using PropertyBean as a property holding the bean that holds other properties. The great advantage to this is that it is possible to listen for changes on any parent property node and get notifications about sub properties changed. This means that a whole property tree can be built and it is very easy to both keep everything in sync and increase performance by listening only for specific changes to do time consuming updates, for instance.
public final PropertyBean<String> mySubBean = new PropertyBean<String>(new MyBean());
Listening on the class that holds this PropertyBean or directly on that property is now possible. The events are even wrapped so that all events in the chain is retrievable and thus the full property path can be recreated.

4. Proposed Language Enhancements

Even though this property proposal will work in any Java 5.0 environment there are some non-intrusive and easy to contain language syntax enhancements that would greatly simplify properties and increase adoption. They are here listed in order of importance.

4.1. Legacy Interaction

Many smaller things can be done to decrease the transition from old type properties to this new type. It is important that they are mixable in a new environment.

4.2. Property Bean Initialization

Before any properties in an instance can be used every it must make a call to PropertyHandler.init(this). This should normally be done first in the constructor as it is not required that the bean is properly constructed, only that the class is loaded and properties created.

It should therefore be simple to for any class that contains a Property call this method automatically, just as an empty super constructor is always implicitly called if none is called manually.

4.3. Property Declaration and Definition

Properties of a bean must be declared public final and they must be declared using type parameters (generics) to ensure type safety. The declaration could thus unambiguously be shortened by introducing a context specific property keyword that is 100% backwards compatible. These two declarations/definitions would translate to the exact same byte code:
public final Property<String> name = new Property<String>();
and
property name = new Property<String>();

4.4. Property Value Interaction Enhancement

To increase with adoption, and to make properties easier to use by the manually coding end user, some help should be given to interact with the value of the property. This proposal will intentionally be vague about exactly how this syntax enhancement should be crafted. What's important is that it is done or excluded for good reasons. Here is one idea on how it can look.

Note that the use of a special operator (#, but it could be -> or @ just as well) is used to get the property value instead of the property object as is more commonly proposed. This is to make it clear that the operation might have side effects. Using the normal dot operator to get the value would actually be very bug prone for collection properties since the CollectionProperty API is very similar to the Collection interface.

Using the dot operator will reference the Property object which is exactly as it would pre Java 7, thus enforcing "least surprise".

Value Assignment

myBean#background = Color.WHITE;
is exactly the same, and would produce the same byte code, as
myBean.background.set(Color.WHITE);

Value Retrieval

Color c = myBean#background;
is exactly the same, and would produce the same byte code, as
Color c = myBean.background.get();

4.5. Type Safe Method Pointers

To make listening to properties and beans easier, more light weight and faster way to reference a method would be advantageous to add listeners using method pointers. It is outside the scope of this proposal to fully outline how this should be done but it should be possible to write code like:
email.addPropertyListener(printEmail)
instead of
email.addPropertyListener(new PropertyListener<String>() { public void propertyChange(PropertyEvent<String> e) { printEmail(e); } });
or for listening to all properties of a bean write
PropertyHandler.addBeanListener(this, myDispatchMethod);
instead of
PropertyHandler.addBeanListener(this, new PropertyListener() { public void propertyChange(PropertyEvent e) { myDispatchMethod(e); } });
This would lead to cleaner looking code, smaller code bases since less anonymous inner classes need to be created and better performance since less classes will be loaded at startup.

If closures are added to the language and with them every instance isn't creating a new class they would serve the same purpose and this section is void.

5. Unresolved Issues

6. Further Refinement and Tasks

6.1. Naming

Some class names and packages will probably need to be changed. This is to be decided upon actual inclusion into the JDK.

6.2. True Statelessness

The properties can be made completely stateless by providing the bean instance to every operation on the property (get, set, add, remove, etc.). This is too cumbersome to do generally though as the syntax for every operation would be:
myBean.set(mybean, value);
instead of the current
myBean.set(value);
To have stateless properties would be very interesting since it means that memory consumption would go down some and that the risk for memory leaks for custom non-field based properties are reduced (not for the provided property implementations in this proposal though as they handle this correctly anyway).

Because of the redundant parameter needed would make the property access harder than the normal setValue(..) syntax it is my belief that this statelessness would reduce the adoption and I am therefore not endorsing it in this proposal. That said I would consider it if a language enhancement provided the parameter automatically in some way for FieldProperty objects and the syntax and interfaces can be refactored to be correct.

7. Proposed IDE Enhancements

A lot can be done in this arena, both with and without language enhancements. Examples will be provided in the next revision of this proposal.

8. JSR Proposal

TBD