property improvement suggestion
Michael Heinrichs
michael.heinrichs at oracle.com
Tue Oct 9 08:10:06 PDT 2012
The Binding API is full of hidden treasures. ;-)
Please file JIRA issues for all features you are missing.
Allowing constraints on properties is a much more challenging task. But first we need to decide what we actually want. Here are my thoughts on the topics mentioned so far:
1) Class vs. Instance
I think being able to define constraints on class level, i.e. the constraints apply to all instances of a bean, is a must. Being able to define constraints for specific instances is a nice-to-have. It would remove the need for a lot of boiler plate code on one hand, but there is also a danger to make things really complex. Some experimentation is required IMO before a decision can be made.
2) Exceptions vs. Auto-Correction
There are valid use cases that require Exceptions, but Exceptions do not play nicely with lazy evaluation. IMO both are required. Exceptions would enforce eager evaluation, i.e. the Exception is thrown on setting the value. We have this differentiation already with InvalidationListeners vs. ChangeListeners, it seems natural to have two types of constraints.
3) Specification of Constraints
Overriding the setter would only be possible if constraints are defined on class level only. The behavior of properties is usually defined by overriding its methods, so this would be a good choice then. If we want to allow defining constraints per instance, we need to pass it in. We probably want to accept some SAM type and provide predefined helpers for common case, e.g. for setting a range on a NumberProperty, specifying a list of legal values for an ObjectProperty etc.
- Michael
On 09.10.2012, at 14:58, Tom Eugelink wrote:
> Aha. I did not know that "when" existed. I'm sorry, an oversight. That indeed covers the whole constraint part of binding, a clamp and otherwiseWhen would indeed be nice additions.
>
> Leaves the situation where it is preferable to set constraints on the property instead of having to repeat it in every binding.
>
> I'll see if I run into any other situations.
>
> Tom
>
>
>
> On 2012-10-09 14:19, Michael Heinrichs wrote:
>> Hi Tom,
>>
>> in my opinion your first case is already covered by the Binding API itself. The Binding API allows you to define all kind of expressions. For the most common ones there are methods in the JavaFX library and you can always add your own implementation. E.g. you could rewrite your example like this:
>>
>> appointment.duration().bind(
>> when(area.height().greaterThan(0))
>> .then(area.height().multiply(pixelToSecondsFactor))
>> .otherwise(0));
>>
>> If possible, we should avoid a new concept to express something that could be expressed straightforward using an existing concepts. Adding a new concept increases the complexity tremendously, because you have to understand how the new concept works with all possible combinations of existing concepts.
>>
>> What I would suggest instead is to think about additions to the Binding API that make your code easier. Maybe we should add clampTo(min, max) to NumberExpression, so that you could write
>>
>> area.height().multiply(pixelToSecondsFactor).clampTo(0, Integer.MAX)
>>
>> Looking at your other examples for further ideas, maybe we need to add a otherwiseWhen() method to write something like when().then().otherwiseWhen().then().otherwise(). Maybe we need switch-case within bindings.
>>
>> Do you have any examples of constraints that can not be covered by the binding API or some small addition to it?
>>
>> - Michael
>>
>>
>>
>>
>> On 08.10.2012, at 19:08, Tom Eugelink wrote:
>>
>>> Hmmmm, not sure we are on the same line here. The essence of the proposal is that the user of a property can specify a constraint. Let's see how I can make a good argument for this.
>>>
>>> There are two angles. The first being the binding; I recently recoded my Agenda (Google Calendar) control to use binding and really liked that, but I ran into some omissions IMHO. Suppose I bind an appointment height to the duration of the appointment. So, basically you get something like:
>>>
>>> appointment.duration().bind(area.height().multiply(pixelToSecondsFactor));
>>>
>>> A UI feature is that the user can resize an area by dragging the end time (bottom of the rectangle) with the mouse. Theoretically he can drag the mouse above the start time, resulting in a negative height. The binding will then result in a negative duration, and that is something I do not want. The problem is that I am not the owner of the appointment (it comes from the business model), nor the owner of the area (it's a rectangle; and even if I extend Rectangle, I cannot extend the height property). So I need a way to prevent that situation to occur, because the BM code handling the appointment will never expect a negative duration, or an end time that falls before the start time (the constraints of the BM entity :-).
>>>
>>> Because the binding allows calculation, it almost requires constraints, or a decent hook system. Or I must find a way to get a man-in-the-middle setup (I did that with JGoodies binding), but native constraints are better.
>>>
>>> The second angle is that the properties concept is a powerful one, and I expect them to become popular in none JavaFX contexts as well. Formal properties is something the community have been begging for for a long time. These other context, say a mortgage business model, may be less lenient towards the possible values a property can have and require a strict handling of the values, and maybe (yes) throw exceptions. Further more these constraints may be fairly complex, so a simple "if a then b" might not cut it. So if we're contemplating constraints, then let's look at the bigger picture.
>>>
>>> Now, I'm not knowledgable enough on the implementation that I can discuss the implementation details, but I understand the problem of the lazy binding your describing. Maybe, in light of the bigger picture, eager bindings are important. I only know that being able to hook into binding to make sure a value never goes out of range, both directly on a property (as a permanent constraint) or inside a binding chain (as a local constraint), seems like a very natural thing.
>>>
>>> Tom
>>>
>>>
>>>
>>> On 2012-10-08 16:26, Richard Bair wrote:
>>>> 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