View Javadoc
1   /*
2    * *************************************************************************************************************************************************************
3    *
4    * SteelBlue: DCI User Interfaces
5    * http://tidalwave.it/projects/steelblue
6    *
7    * Copyright (C) 2015 - 2025 by Tidalwave s.a.s. (http://tidalwave.it)
8    *
9    * *************************************************************************************************************************************************************
10   *
11   * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
12   * You may obtain a copy of the License at
13   *
14   *     http://www.apache.org/licenses/LICENSE-2.0
15   *
16   * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
17   * CONDITIONS OF ANY KIND, either express or implied.  See the License for the specific language governing permissions and limitations under the License.
18   *
19   * *************************************************************************************************************************************************************
20   *
21   * git clone https://bitbucket.org/tidalwave/steelblue-src
22   * git clone https://github.com/tidalwave-it/steelblue-src
23   *
24   * *************************************************************************************************************************************************************
25   */
26  package it.tidalwave.ui.javafx.impl.common;
27  
28  import jakarta.annotation.Nonnull;
29  import java.beans.PropertyChangeListener;
30  import java.util.ArrayList;
31  import java.util.List;
32  import java.util.Objects;
33  import java.util.concurrent.Executor;
34  import javafx.beans.InvalidationListener;
35  import javafx.beans.property.Property;
36  import javafx.beans.value.ChangeListener;
37  import javafx.beans.value.ObservableValue;
38  import javafx.application.Platform;
39  import it.tidalwave.ui.core.BoundProperty;
40  import lombok.extern.slf4j.Slf4j;
41  
42  /***************************************************************************************************************************************************************
43   *
44   * Adapts a {@link BoundProperty} to a JavaFX {@link Property}. It also takes care of threaring issues, making sure that
45   * the JavaFX {@code Property} is updated in the JavaFX UI thread. Conversely, updates on the JavaFX 
46   * {@code BoundProperty} are executed in a separated thread provided by an {@link Executor}.
47   *
48   * TODO: javafx.beans.binding.BooleanExpression.booleanExpression(source)? Does it do threading?
49   * 
50   * @author  Fabrizio Giudici
51   *
52   **************************************************************************************************************************************************************/
53  @Slf4j
54  public class PropertyAdapter<T> implements Property<T>
55    {
56      @Nonnull
57      private final Executor executor;
58  
59      @Nonnull
60      private final BoundProperty<T> delegate;
61  
62      // FIXME: WEAK LISTENER!!
63      private final List<ChangeListener<? super T>> changeListeners = new ArrayList<>();
64      
65      // FIXME: WEAK LISTENER!!
66      private final List<InvalidationListener> invalidationListeners = new ArrayList<>();
67  
68      private T boundValue;
69  
70      private final PropertyChangeListener propertyChangeListener = (event) -> 
71        {
72          log.trace("propertyChange({}) - bound value: {}", event, boundValue);
73          
74          if (!Objects.equals(boundValue, event.getNewValue()))
75            {
76              boundValue = (T)event.getNewValue();
77              Platform.runLater(() ->
78                {
79                  new ArrayList<>(invalidationListeners)
80                          .forEach(listener -> listener.invalidated(this));
81                  new ArrayList<>(changeListeners)
82                          .forEach(listener -> listener.changed(this,
83                                  (T)event.getOldValue(), (T)event.getNewValue()));
84                });
85            }
86      };
87  
88      public PropertyAdapter (@Nonnull final Executor executor, @Nonnull final BoundProperty<T> delegate)
89        {
90          this.executor = executor;
91          this.delegate = delegate;
92          this.boundValue = delegate.get();
93          delegate.addPropertyChangeListener(propertyChangeListener);
94        }
95  
96      @Override
97      public T getValue()
98        {
99          return delegate.get();
100       }
101 
102     @Override
103     public void setValue (final T value)
104       {
105         log.debug("setValue({})", value);
106         boundValue = value;
107 
108         if (!Objects.equals(value, delegate.get()))
109           {
110             executor.execute(() -> delegate.set(value));
111           }
112       }
113 
114     @Override
115     public void addListener (@Nonnull final ChangeListener<? super T> listener)
116       {
117         changeListeners.add(listener);
118       }
119 
120     @Override
121     public void removeListener (@Nonnull final ChangeListener<? super T> listener)
122       {
123         changeListeners.remove(listener);
124       }
125 
126     @Override
127     public void addListener (@Nonnull final InvalidationListener listener)
128       {
129         invalidationListeners.add(listener);
130       }
131 
132     @Override
133     public void removeListener (@Nonnull final InvalidationListener listener)
134       {
135         invalidationListeners.remove(listener);
136       }
137 
138     @Override
139     public void bind (final ObservableValue<? extends T> observable)
140       {
141         log.warn("bind({})", observable);
142         throw new UnsupportedOperationException("Not supported yet.");
143       }
144 
145     @Override
146     public void unbind()
147       {
148         log.warn("unbind()");
149         throw new UnsupportedOperationException("Not supported yet.");
150       }
151 
152     @Override
153     public boolean isBound()
154       {
155         log.warn("isBound()");
156         throw new UnsupportedOperationException("Not supported yet.");
157       }
158 
159     @Override
160     public void bindBidirectional (final Property<T> other)
161       {
162         log.warn("bindBidirectional({})", other);
163         throw new UnsupportedOperationException("Not supported yet.");
164       }
165 
166     @Override
167     public void unbindBidirectional (final Property<T> other)
168       {
169         log.warn("unbindBidirectional({})", other);
170         throw new UnsupportedOperationException("Not supported yet.");
171       }
172 
173     @Override
174     public Object getBean()
175       {
176         log.warn("getBean()");
177         throw new UnsupportedOperationException("Not supported yet.");
178       }
179 
180     @Override
181     public String getName()
182       {
183         log.warn("getName()");
184         throw new UnsupportedOperationException("Not supported yet.");
185       }
186   }