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.SetExpressionHelper;
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.ObservableList;
040import javafx.collections.ObservableSet;
041import javafx.collections.SetChangeListener;
042
043/**
044 * Base class that provides most of the functionality needed to implement a
045 * {@link Binding} of an {@link javafx.collections.ObservableSet}.
046 * <p>
047 * {@code SetBinding} 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 SetBinding} 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 SetExpression
062 *
063 * @param <E>
064 *            the type of the {@code Set} elements
065 */
066public abstract class SetBinding<E> extends SetExpression<E> implements Binding<ObservableSet<E>> {
067
068    private final SetChangeListener<E> setChangeListener = new SetChangeListener<E>() {
069        @Override
070        public void onChanged(Change<? extends E> change) {
071            invalidateProperties();
072            onInvalidating();
073            SetExpressionHelper.fireValueChangedEvent(helper, change);
074        }
075    };
076
077    private ObservableSet<E> value;
078    private boolean valid = false;
079    private BindingHelperObserver observer;
080    private SetExpressionHelper<E> helper = null;
081
082    private SizeProperty size0;
083    private EmptyProperty empty0;
084
085    @Override
086    public ReadOnlyIntegerProperty sizeProperty() {
087        if (size0 == null) {
088            size0 = new SizeProperty();
089        }
090        return size0;
091    }
092
093    private class SizeProperty extends ReadOnlyIntegerPropertyBase {
094        @Override
095        public int get() {
096            return size();
097        }
098
099        @Override
100        public Object getBean() {
101            return SetBinding.this;
102        }
103
104        @Override
105        public String getName() {
106            return "size";
107        }
108
109        protected void fireValueChangedEvent() {
110            super.fireValueChangedEvent();
111        }
112    }
113
114    @Override
115    public ReadOnlyBooleanProperty emptyProperty() {
116        if (empty0 == null) {
117            empty0 = new EmptyProperty();
118        }
119        return empty0;
120    }
121
122    private class EmptyProperty extends ReadOnlyBooleanPropertyBase {
123
124        @Override
125        public boolean get() {
126            return isEmpty();
127        }
128
129        @Override
130        public Object getBean() {
131            return SetBinding.this;
132        }
133
134        @Override
135        public String getName() {
136            return "empty";
137        }
138
139        protected void fireValueChangedEvent() {
140            super.fireValueChangedEvent();
141        }
142    }
143
144    @Override
145    public void addListener(InvalidationListener listener) {
146        helper = SetExpressionHelper.addListener(helper, this, listener);
147    }
148
149    @Override
150    public void removeListener(InvalidationListener listener) {
151        helper = SetExpressionHelper.removeListener(helper, listener);
152    }
153
154    @Override
155    public void addListener(ChangeListener<? super ObservableSet<E>> listener) {
156        helper = SetExpressionHelper.addListener(helper, this, listener);
157    }
158
159    @Override
160    public void removeListener(ChangeListener<? super ObservableSet<E>> listener) {
161        helper = SetExpressionHelper.removeListener(helper, listener);
162    }
163
164    @Override
165    public void addListener(SetChangeListener<? super E> listener) {
166        helper = SetExpressionHelper.addListener(helper, this, listener);
167    }
168
169    @Override
170    public void removeListener(SetChangeListener<? super E> listener) {
171        helper = SetExpressionHelper.removeListener(helper, listener);
172    }
173
174    /**
175     * Start observing the dependencies for changes. If the value of one of the
176     * dependencies changes, the binding is marked as invalid.
177     *
178     * @param dependencies
179     *            the dependencies to observe
180     */
181    protected final void bind(Observable... dependencies) {
182        if ((dependencies != null) && (dependencies.length > 0)) {
183            if (observer == null) {
184                observer = new BindingHelperObserver(this);
185            }
186            for (final Observable dep : dependencies) {
187                if (dep != null) {
188                    dep.addListener(observer);
189                }
190            }
191        }
192    }
193
194    /**
195     * Stop observing the dependencies for changes.
196     *
197     * @param dependencies
198     *            the dependencies to stop observing
199     */
200    protected final void unbind(Observable... dependencies) {
201        if (observer != null) {
202            for (final Observable dep : dependencies) {
203                if (dep != null) {
204                    dep.removeListener(observer);
205                }
206            }
207            observer = null;
208        }
209    }
210
211    /**
212     * A default implementation of {@code dispose()} that is empty.
213     */
214    @Override
215    public void dispose() {
216    }
217
218    /**
219     * A default implementation of {@code getDependencies()} that returns an
220     * empty {@link javafx.collections.ObservableList}.
221     *
222     * @return an empty {@code ObservableList}
223     */
224    @Override
225    @ReturnsUnmodifiableCollection
226    public ObservableList<?> getDependencies() {
227        return FXCollections.emptyObservableList();
228    }
229
230    /**
231     * Returns the result of {@link #computeValue()}. The method
232     * {@code computeValue()} is only called if the binding is invalid. The
233     * result is cached and returned if the binding did not become invalid since
234     * the last call of {@code get()}.
235     *
236     * @return the current value
237     */
238    @Override
239    public final ObservableSet<E> get() {
240        if (!valid) {
241            value = computeValue();
242            valid = true;
243            if (value != null) {
244                value.addListener(setChangeListener);
245            }
246        }
247        return value;
248    }
249
250    /**
251     * The method onInvalidating() can be overridden by extending classes to
252     * react, if this binding becomes invalid. The default implementation is
253     * empty.
254     */
255    protected void onInvalidating() {
256    }
257
258    private void invalidateProperties() {
259        if (size0 != null) {
260            size0.fireValueChangedEvent();
261        }
262        if (empty0 != null) {
263            empty0.fireValueChangedEvent();
264        }
265    }
266
267    @Override
268    public final void invalidate() {
269        if (valid) {
270            if (value != null) {
271                value.removeListener(setChangeListener);
272            }
273            valid = false;
274            invalidateProperties();
275            onInvalidating();
276            SetExpressionHelper.fireValueChangedEvent(helper);
277        }
278    }
279
280    @Override
281    public final boolean isValid() {
282        return valid;
283    }
284
285    /**
286     * Calculates the current value of this binding.
287     * <p>
288     * Classes extending {@code SetBinding} have to provide an implementation
289     * of {@code computeValue}.
290     *
291     * @return the current value
292     */
293    protected abstract ObservableSet<E> computeValue();
294
295    /**
296     * Returns a string representation of this {@code SetBinding} object.
297     * @return a string representation of this {@code SetBinding} object.
298     */
299    @Override
300    public String toString() {
301        return valid ? "SetBinding [value: " + get() + "]"
302                : "SetBinding [invalid]";
303    }
304
305}