Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package javafx.beans.binding;
027
028import com.sun.javafx.binding.BindingHelperObserver;
029import com.sun.javafx.binding.MapExpressionHelper;
030import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection;
031import javafx.beans.InvalidationListener;
032import javafx.beans.Observable;
033import javafx.beans.property.ReadOnlyBooleanProperty;
034import javafx.beans.property.ReadOnlyBooleanPropertyBase;
035import javafx.beans.property.ReadOnlyIntegerProperty;
036import javafx.beans.property.ReadOnlyIntegerPropertyBase;
037import javafx.beans.value.ChangeListener;
038import javafx.collections.FXCollections;
039import javafx.collections.MapChangeListener;
040import javafx.collections.ObservableList;
041import javafx.collections.ObservableMap;
042
043/**
044 * Base class that provides most of the functionality needed to implement a
045 * {@link Binding} of an {@link javafx.collections.ObservableMap}.
046 * <p>
047 * {@code MapBinding} provides a simple invalidation-scheme. An extending
048 * class can register dependencies by calling {@link #bind(Observable...)}.
049 * If one of the registered dependencies becomes invalid, this
050 * {@code MapBinding} is marked as invalid. With
051 * {@link #unbind(Observable...)} listening to dependencies can be stopped.
052 * <p>
053 * To provide a concrete implementation of this class, the method
054 * {@link #computeValue()} has to be implemented to calculate the value of this
055 * binding based on the current state of the dependencies. It is called when
056 * {@link #get()} is called for an invalid binding.
057 * <p>
058 * See {@link DoubleBinding} for an example how this base class can be extended.
059 *
060 * @see Binding
061 * @see MapExpression
062 *
063 * @param <K>
064 *            the type of the key elements
065 * @param <V>
066 *            the type of the value elements
067 */
068public abstract class MapBinding<K, V> extends MapExpression<K, V> implements Binding<ObservableMap<K, V>> {
069
070    private final MapChangeListener<K, V> mapChangeListener = new MapChangeListener<K, V>() {
071        @Override
072        public void onChanged(Change<? extends K, ? extends V> change) {
073            invalidateProperties();
074            onInvalidating();
075            MapExpressionHelper.fireValueChangedEvent(helper, change);
076        }
077    };
078
079    private ObservableMap<K, V> value;
080    private boolean valid = false;
081    private BindingHelperObserver observer;
082    private MapExpressionHelper<K, V> helper = null;
083
084    private SizeProperty size0;
085    private EmptyProperty empty0;
086
087    @Override
088    public ReadOnlyIntegerProperty sizeProperty() {
089        if (size0 == null) {
090            size0 = new SizeProperty();
091        }
092        return size0;
093    }
094
095    private class SizeProperty extends ReadOnlyIntegerPropertyBase {
096        @Override
097        public int get() {
098            return size();
099        }
100
101        @Override
102        public Object getBean() {
103            return MapBinding.this;
104        }
105
106        @Override
107        public String getName() {
108            return "size";
109        }
110
111        protected void fireValueChangedEvent() {
112            super.fireValueChangedEvent();
113        }
114    }
115
116    @Override
117    public ReadOnlyBooleanProperty emptyProperty() {
118        if (empty0 == null) {
119            empty0 = new EmptyProperty();
120        }
121        return empty0;
122    }
123
124    private class EmptyProperty extends ReadOnlyBooleanPropertyBase {
125
126        @Override
127        public boolean get() {
128            return isEmpty();
129        }
130
131        @Override
132        public Object getBean() {
133            return MapBinding.this;
134        }
135
136        @Override
137        public String getName() {
138            return "empty";
139        }
140
141        protected void fireValueChangedEvent() {
142            super.fireValueChangedEvent();
143        }
144    }
145
146    @Override
147    public void addListener(InvalidationListener listener) {
148        helper = MapExpressionHelper.addListener(helper, this, listener);
149    }
150
151    @Override
152    public void removeListener(InvalidationListener listener) {
153        helper = MapExpressionHelper.removeListener(helper, listener);
154    }
155
156    @Override
157    public void addListener(ChangeListener<? super ObservableMap<K, V>> listener) {
158        helper = MapExpressionHelper.addListener(helper, this, listener);
159    }
160
161    @Override
162    public void removeListener(ChangeListener<? super ObservableMap<K, V>> listener) {
163        helper = MapExpressionHelper.removeListener(helper, listener);
164    }
165
166    @Override
167    public void addListener(MapChangeListener<? super K, ? super V> listener) {
168        helper = MapExpressionHelper.addListener(helper, this, listener);
169    }
170
171    @Override
172    public void removeListener(MapChangeListener<? super K, ? super V> listener) {
173        helper = MapExpressionHelper.removeListener(helper, listener);
174    }
175
176    /**
177     * Start observing the dependencies for changes. If the value of one of the
178     * dependencies changes, the binding is marked as invalid.
179     *
180     * @param dependencies
181     *            the dependencies to observe
182     */
183    protected final void bind(Observable... dependencies) {
184        if ((dependencies != null) && (dependencies.length > 0)) {
185            if (observer == null) {
186                observer = new BindingHelperObserver(this);
187            }
188            for (final Observable dep : dependencies) {
189                if (dep != null) {
190                    dep.addListener(observer);
191                }
192            }
193        }
194    }
195
196    /**
197     * Stop observing the dependencies for changes.
198     *
199     * @param dependencies
200     *            the dependencies to stop observing
201     */
202    protected final void unbind(Observable... dependencies) {
203        if (observer != null) {
204            for (final Observable dep : dependencies) {
205                if (dep != null) {
206                    dep.removeListener(observer);
207                }
208            }
209            observer = null;
210        }
211    }
212
213    /**
214     * A default implementation of {@code dispose()} that is empty.
215     */
216    @Override
217    public void dispose() {
218    }
219
220    /**
221     * A default implementation of {@code getDependencies()} that returns an
222     * empty {@link javafx.collections.ObservableList}.
223     *
224     * @return an empty {@code ObservableList}
225     */
226    @Override
227    @ReturnsUnmodifiableCollection
228    public ObservableList<?> getDependencies() {
229        return FXCollections.emptyObservableList();
230    }
231
232    /**
233     * Returns the result of {@link #computeValue()}. The method
234     * {@code computeValue()} is only called if the binding is invalid. The
235     * result is cached and returned if the binding did not become invalid since
236     * the last call of {@code get()}.
237     *
238     * @return the current value
239     */
240    @Override
241    public final ObservableMap<K, V> get() {
242        if (!valid) {
243            value = computeValue();
244            valid = true;
245            if (value != null) {
246                value.addListener(mapChangeListener);
247            }
248        }
249        return value;
250    }
251
252    /**
253     * The method onInvalidating() can be overridden by extending classes to
254     * react, if this binding becomes invalid. The default implementation is
255     * empty.
256     */
257    protected void onInvalidating() {
258    }
259
260    private void invalidateProperties() {
261        if (size0 != null) {
262            size0.fireValueChangedEvent();
263        }
264        if (empty0 != null) {
265            empty0.fireValueChangedEvent();
266        }
267    }
268
269    @Override
270    public final void invalidate() {
271        if (valid) {
272            if (value != null) {
273                value.removeListener(mapChangeListener);
274            }
275            valid = false;
276            invalidateProperties();
277            onInvalidating();
278            MapExpressionHelper.fireValueChangedEvent(helper);
279        }
280    }
281
282    @Override
283    public final boolean isValid() {
284        return valid;
285    }
286
287    /**
288     * Calculates the current value of this binding.
289     * <p>
290     * Classes extending {@code MapBinding} have to provide an implementation
291     * of {@code computeValue}.
292     *
293     * @return the current value
294     */
295    protected abstract ObservableMap<K, V> computeValue();
296
297    /**
298     * Returns a string representation of this {@code MapBinding} object.
299     * @return a string representation of this {@code MapBinding} object.
300     */
301    @Override
302    public String toString() {
303        return valid ? "MapBinding [value: " + get() + "]"
304                : "MapBinding [invalid]";
305    }
306
307}