Feature Literals + Enhancements + Blocks == Properties++

Stephen Colebourne recently commented on the lack of formal properties in Java:

  http://www.jroller.com/scolebourne/entry/beans_and_properties

I completely agree with Stephen that properties should have been introduced to Java long ago: they were one of the first things that Scott added to Gosu and they have been incredibly useful at Guidewire. They aren’t really sexy or novel, but they solve a lot of practical problems.

In the latest release of Gosu, we incorporated feature literals, which allow you to reference a property (or method) using the # operator, making properties even easier to work with. A commenter on Stephen’s blog mentioned Gosu’s feature literals and had some nice things to say about our implementation, but mentioned that they do not support a listener infrastructure, where you can register a listener with a property to be invoked when the property is written or read.

Gosu obviously can’t provide a general listener infrastructure: the Open Type System works with all sorts of different types, each with different property implementations, storage models and so on. *However*, using feature literals, blocks, generics and enhancements, you can add some pretty cool support for property listeners on types that can support them.

I’ve thrown together a quick example that shows how this can be accomplished.

The entirety of the code in this example is up on github here:

  https://github.com/carsongross/Properties-Callback-Example

First, let’s create a simple class that encapsules property storage and supports callbacks:

package props
uses java.util.HashMap
uses java.util.ArrayList

class PropHolder {
  var _props = new HashMap<String, Object>()
  var _callback = new HashMap<String, List<block(val:Object):Object>>()
                    .toAutoMap( \ s -> new ArrayList<block(val:Object):Object>() )
  
  function setProp( name : String, val : Object ) {
    for( cb in _callback[name] ) {
      val = cb( val )
    }
    _props[name] = val
  }
  
  function getProp( name : String ) : Object {
    return _props[name]
  }
  
  function addPropListener( name : String, blk : block(val:Object):Object ) {
    _callback[name].add( blk )
  }
}

This is some simple infrastructure that just wraps a hash map to store properties in, and maintains a list of listeners to execute when a property is invoked.

Next, let’s create a simple gosu class that uses this infrastructure to store a property:

package props

class ExampleGosuClass implements IHaveListenableProperties {
  var _props = new PropHolder() 
  
  property get Prop1() : String {
    return _props.getProp( "Prop1" ) as String
  }

  property set Prop1( s : String ) {
    _props.setProp( "Prop1", s )
  }
  
  override function addListener( propName : String, listener : block(Object):Object ) {
    _props.addPropListener( propName, listener )
  }
  
  property get ThisAsProp() : ExampleGosuClass {
    return this
  }
}

This class implements a simple interface, IHaveListenableProperties:

package props

interface IHaveListenableProperties {
  function addListener( propName : String, listener : block(Object):Object );
}

Finally, and this is where we get tricky, so follow along closely, we are going to enhance a specific parameterization of the return type of the expression foo#Bar, which is the API class gw.lang.reflect.features.BoundPropertyReference:

package props

uses gw.lang.reflect.features.BoundPropertyReference

enhancement BoundPropertyRefEnhancement : BoundPropertyReference<IHaveListenableProperties, Object>  
{
  function addListener( blk : block(obj:Object):Object ) {
    var root = this.Ctx as IHaveListenableProperties
    root.addListener( this.PropertyInfo.Name, blk )
  }
}

Note that this method will only appear on BoundPropertyReference‘s that have a IHaveListenableProperties object at their root, due to the particular parameterization that we have chosen to enhance. (Cool trick, eh?)

So, with all that infrastructure in place, we can now write this code:

uses props.*

var ex1 = new ExampleGosuClass()
var ex2 = new ExampleGosuClass()

ex1#Prop1.addListener( \ o -> { print( o ); return o + " Yay!" }  )
ex1.Prop1 = "Hello Listeners!" // prints "Hello Listeners!" 
print( ex1.Prop1 )             // prints "Hello Listeners! Yay!"

ex2.Prop1 = "Nope, no listener..."  // no listener, so no printing
print( ex2.Prop1 ) 

In this code, we create two instances of ExampleGosuClass and add a block-based listener to the Prop1 property on ex1. We then set the property on ex1, which invokes the block and prints out the original value, then transforms the value before storing it. Finally, we do the same with ex2 to demonstrate that the listener is bound only to the Prop1 property on ex1.

So we’ve used feature literals, enhancements and blocks to implement a slick little properties listening system in Gosu. A system like this could be integrated into, for example, an O/R framework or a web framework even more elegantly since the listener infrastructure could be entirely internal.

Just another example of the excellent tools in Gosu available for framework builders. I think you’ll see a lot of innovation in this space in the next year or so, as our tools mature and Gosu becomes more widely known.


3 Comments on “Feature Literals + Enhancements + Blocks == Properties++”

  1. Rich says:

    Looks like the parameterization on the enhancement got swallowed (eg interpreted as an html tag?) Or maybe it’s my browser…I see it on github but not the blog post.

  2. Stephen Haberman says:

    Wow, that’s crazy. I’m (pleasantly) surprised that the syntax “ex1#Prop1.addListener” works. But, after thinking about it, it makes a lot of sense.

    Also, it seems to indicate you guys did a good job implementing each of these individual features such that they just magically/elegantly work together like that. :-)


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 38 other followers