property improvement suggestion

Richard Bair richard.bair at oracle.com
Mon Oct 8 07:26:22 PDT 2012


It looks like there are two elements of this proposal. The first is that the author of a bean might want to specify some inherent constraint
on the property -- such as, it should never be null, or it should never be negative. The second is that a user of the property might want to constrain the property in some way -- such as, in my app the width should never be negative.

I see the value immediately in the first (in fact, this has always been a problem with our properties, going all the way back to JavaFX Script), but I struggle to see much value in the second. Not because you might not want to do this sort of thing, but because the first (inherent constraints) is not presently possible at all, while the second (app constraints) you can already do in your application code, one way or another. I am inclined not to add API for cases that are already solvable, unless it turns out to be so compelling and widely used.

From a semantic point of view, binding was a "mathematical" relationship between two properties in JavaFX Script, such that when you read code like:

x: bind 10 * someProperty

you new that "x" was going to be 10 * someProperty, and not anything else. Of course as an API designer I have never liked that because I have to handle exceptional conditions in my code rather than being able to verify that a property can never go outside its valid range of values.

I am pleased to see your proposal doesn't include throwing exceptions -- I think throwing an exception from a property on invalid input is a very bad idea. The problem is in lazy evaluation of properties. Suppose:

widthProperty().bind(someProperty)

Further suppose width is constrained to never be < 0, and somebody were to try to set it to be < 0 it would throw an exception. In this case, someProperty might be set to -10 at some time, and the width property is invalidated. But until somebody tries to read width, it won't throw the exception. So the problem with bound properties & exceptions is that the exception is not thrown at the time the error is introduced, but rather at the time the error is discovered, which turns out to be an awful behavior.

However your proposal instead says "if it is outside the allowable range, I will just adjust the value to keep it within the allowable range". I think this is a really good idea. The property implementations would have to be modified such that on read, instead of:

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean get() {
        valid = true;
        return observable == null ? value : observable.get();
    }

we instead do something like:

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean get() {
        if (!valid && observable != null) {
           value = constrain(observable.get());
        }
        valid = true;
        return observable == null ? value : observable.get();
    }


And add a protected 'constrain' method. Or perhaps we can just call the "set" method and let the set method deal with constraining. This would require taking some care, however, because it means set methods will now be called even when the property is bound which is different than at present, and calling super.set() would throw an exception in the case that it is bound. But still, as a Java developer I'm used to doing constraining from within the setter, so doing it inside the set seems the most natural, but this would be an area that needs to be figured out.

Richard

On Oct 7, 2012, at 12:10 AM, Tom Eugelink wrote:

> As far as I understand, the current the approach is that properties should accept any value, and the usage of these properties have to work with that. Personally I'm prefer a different approach, because I feel the responsibility to have sensible values should be constrained to one place. Therefore I often override the set method of my properties to make sure the value is within the range that my remaining code expect it to be; either by throwing an exception or by clamping the value to a range.
> 
> But this I can only do with my own properties. It would be nice to also do this to existing properties and therefor I would like to suggest to extend the property concept with constraints or conditions. The important point being that these constraints are applied before the value is set, and not (like a change listener approach) afterwards and then correct value; a value should never be outside it allowed range.
> 
> The constraints or conditions could have an if-then structure: if value < 0 then 0. An example in Java 8 lambda notation:
> someNode.widthProperty().*constraint*( w -> w < 0, 0);
> 
> This would also be something relevant to binding, especially when doing calculations:
> someNode.widthProperty().bind( otherNode.widthProperty().substract(10).*constraint*( w -> w < 0, 0) );
> 
> There is a difference between a permanent constraint on the property and a constraint only applicable to a binding. The notation above is is too similar to my taste and too easily a binding constraint can become a property constaint. To make this difference more explicit, the word "if" could be better on binding:
> someNode.widthProperty().*constraint*( w -> w < 0, 0);
> someNode.widthProperty().bind( otherNode.widthProperty().substract(10).*if*( w -> w < 0, 0) );
> 
> Something to think about is how far these constraints should be taken. These are simple if-then constraints. But what if "else", "elsif" constructions or "case" statements are needed to express the range? In order to keep all options open, maybe the best approach would be to provide constraining listeners. A constraining listener would get the to-be-set value as a parameter and return the new value. For example:
> 
> widthProperty.addConstrainingListener( v -> { if (v < 0.0) return 0.0; return v; } );
> 
> or in normal notation:
> 
> widthProperty.addConstrainingListener( new ConstaintListener<Double>() {
>    public Double constaint<Double>(Double value) {
>        if (value < 0.0) return 0.0;
>        return value;
>    }
> });
> 
> (NB: I'm not sure how to use Java 8 lambda with the addListener method, differentiating between a change, invalidation and constraint listener.)
> 
> This constraining listener approach would allow for all scenario's to be handled. So that is the final improvement I would like to suggest: constrainting listeners.
> someNode.widthProperty().*constraint*( v -> { if (v < 0.0) return 0.0; return v; } );
> someNode.widthProperty().bind( otherNode.widthProperty().substract(10).*if*( v -> { if (v < 0.0) return 0.0; return v; } ) );
> 
> And if possible normal constraints, because that is a easier notation.
> 
> I'd like to hear if I'm making sense or am missing the ball completely.
> 
> Tom



More information about the openjfx-dev mailing list