[prev in list] [next in list] [prev in thread] [next in thread] 

List:       openjdk-openjfx-dev
Subject:    Re: ObservableValue::map called twice
From:       John Hendrikx <john.hendrikx () gmail ! com>
Date:       2022-09-29 14:33:38
Message-ID: b3fa1bdb-1f2e-2bc0-6578-a8b12d45e5c1 () gmail ! com
[Download RAW message or body]

Hi Nir,

This is sort of normal behavior, and is a consequence of how listeners 
are only registered when themselves observed.  This interacts with the 
concept of being valid/invalid.  There is a long comment about it in 
LazyObjectBinding, but this is the code that is causing this:

     private void updateSubscriptionAfterAdd() {
         if (!wasObserved) { // was first observer registered?
             subscription = observeSources(); // start observing source

             /*
              * Although the act of registering a listener already 
attempts to make
              * this binding valid, allowValidation won't allow it as 
the binding is
              * not observed yet. This is because isObserved will not 
yet return true
              * when the process of registering the listener hasn't 
completed yet.
              *
              * As the binding must be valid after it becomes observed 
the first time
              * 'get' is called again.
              *
              * See com.sun.javafx.binding.ExpressionHelper (which is used
              * by ObjectBinding) where it will do a call to 
ObservableValue#getValue
              * BEFORE adding the actual listener. This results in 
ObjectBinding#get
              * to be called in which the #allowValidation call will 
block it from
              * becoming valid as the condition is "isObserved()"; this 
is technically
              * correct as the listener wasn't added yet, but means we 
must call
              * #get again to make this binding valid.
              */

             get(); // make binding valid as source wasn't tracked until now

             wasObserved = true;
         }
     }

Basically the first "map" call in your sample is triggered by 
`observeSources` in the snippet above (triggered in turn by 
`ExpressionHelper` call to getValue just before it adds the listener).  
The 2nd one is triggered by the `get()` call.

It may be possible to change this but will likely require a change in 
the parent class (ObjectBinding) to explicitely set it to valid (which 
breaks the encapsulation it does there to guard this).

Alternatively, it may be possible to change the order of calls in 
`ExpressionListener`, first add the listener, then make it valid with 
`getValue` instead of the other way around. At the time I didn't want to 
risk changing that order as I'm unsure if that could have any 
consequences, given that it is such an important part of JavaFX properties.

It may also be possible to somehow override what `allowValidation` 
returns during the call to `observeSources`.

--John

On 28/09/2022 20:44, Nir Lisker wrote:
> Hi,
>
> Running this code:
>
> var label = new Label();
> var tf = new TextField();
> label.textProperty().bind(tf.textProperty().map(t -> {
>     System.out.println("in");
>     return t;
> }));
>
> produces the output::
> in
> in
>
> From what I see, the lambda should be called once, but it's called 
> twice. Looks like improper behavior to me.
>
> - Nir
[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic