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.property;
027
028import com.sun.javafx.binding.MapExpressionHelper;
029import javafx.beans.InvalidationListener;
030import javafx.beans.Observable;
031import javafx.beans.value.ChangeListener;
032import javafx.beans.value.ObservableValue;
033import javafx.collections.*;
034
035/**
036 * The class {@code MapPropertyBase} is the base class for a property
037 * wrapping an {@link javafx.collections.ObservableMap}.
038 *
039 * It provides all the functionality required for a property except for the
040 * {@link #getBean()} and {@link #getName()} methods, which must be implemented
041 * by extending classes.
042 *
043 * @see javafx.collections.ObservableMap
044 * @see MapProperty
045 *
046 * @param <K> the type of the key elements of the {@code Map}
047 * @param <V> the type of the value elements of the {@code Map}
048 */
049public abstract class MapPropertyBase<K, V> extends MapProperty<K, V> {
050
051    private final MapChangeListener<K, V> mapChangeListener = new MapChangeListener<K, V>() {
052        @Override
053        public void onChanged(Change<? extends K, ? extends V> change) {
054            invalidateProperties();
055            invalidated();
056            fireValueChangedEvent(change);
057        }
058    };
059
060    private ObservableMap<K, V> value;
061    private ObservableValue<? extends ObservableMap<K, V>> observable = null;
062    private InvalidationListener listener = null;
063    private boolean valid = true;
064    private MapExpressionHelper<K, V> helper = null;
065
066    private SizeProperty size0;
067    private EmptyProperty empty0;
068
069    /**
070     * The Constructor of {@code MapPropertyBase}
071     */
072    public MapPropertyBase() {}
073
074    /**
075     * The constructor of the {@code MapPropertyBase}.
076     *
077     * @param initialValue
078     *            the initial value of the wrapped value
079     */
080    public MapPropertyBase(ObservableMap<K, V> initialValue) {
081        this.value = initialValue;
082        if (initialValue != null) {
083            initialValue.addListener(mapChangeListener);
084        }
085    }
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 MapPropertyBase.this;
104        }
105
106        @Override
107        public String getName() {
108            return "size";
109        }
110
111        @Override
112        protected void fireValueChangedEvent() {
113            super.fireValueChangedEvent();
114        }
115    }
116
117    @Override
118    public ReadOnlyBooleanProperty emptyProperty() {
119        if (empty0 == null) {
120            empty0 = new EmptyProperty();
121        }
122        return empty0;
123    }
124
125    private class EmptyProperty extends ReadOnlyBooleanPropertyBase {
126
127        @Override
128        public boolean get() {
129            return isEmpty();
130        }
131
132        @Override
133        public Object getBean() {
134            return MapPropertyBase.this;
135        }
136
137        @Override
138        public String getName() {
139            return "empty";
140        }
141
142        @Override
143        protected void fireValueChangedEvent() {
144            super.fireValueChangedEvent();
145        }
146    }
147
148    @Override
149    public void addListener(InvalidationListener listener) {
150        helper = MapExpressionHelper.addListener(helper, this, listener);
151    }
152
153    @Override
154    public void removeListener(InvalidationListener listener) {
155        helper = MapExpressionHelper.removeListener(helper, listener);
156    }
157
158    @Override
159    public void addListener(ChangeListener<? super ObservableMap<K, V>> listener) {
160        helper = MapExpressionHelper.addListener(helper, this, listener);
161    }
162
163    @Override
164    public void removeListener(ChangeListener<? super ObservableMap<K, V>> listener) {
165        helper = MapExpressionHelper.removeListener(helper, listener);
166    }
167
168    @Override
169    public void addListener(MapChangeListener<? super K, ? super V> listener) {
170        helper = MapExpressionHelper.addListener(helper, this, listener);
171    }
172
173    @Override
174    public void removeListener(MapChangeListener<? super K, ? super V> listener) {
175        helper = MapExpressionHelper.removeListener(helper, listener);
176    }
177
178    /**
179     * Sends notifications to all attached
180     * {@link javafx.beans.InvalidationListener InvalidationListeners},
181     * {@link javafx.beans.value.ChangeListener ChangeListeners}, and
182     * {@link javafx.collections.MapChangeListener}.
183     *
184     * This method is called when the value is changed, either manually by
185     * calling {@link #set(javafx.collections.ObservableMap)} or in case of a bound property, if the
186     * binding becomes invalid.
187     */
188    protected void fireValueChangedEvent() {
189        MapExpressionHelper.fireValueChangedEvent(helper);
190    }
191
192    /**
193     * Sends notifications to all attached
194     * {@link javafx.beans.InvalidationListener InvalidationListeners},
195     * {@link javafx.beans.value.ChangeListener ChangeListeners}, and
196     * {@link javafx.collections.MapChangeListener}.
197     *
198     * This method is called when the content of the list changes.
199     *
200     * @param change the change that needs to be propagated
201     */
202    protected void fireValueChangedEvent(MapChangeListener.Change<? extends K, ? extends V> change) {
203        MapExpressionHelper.fireValueChangedEvent(helper, change);
204    }
205
206    private void invalidateProperties() {
207        if (size0 != null) {
208            size0.fireValueChangedEvent();
209        }
210        if (empty0 != null) {
211            empty0.fireValueChangedEvent();
212        }
213    }
214
215    private void markInvalid(ObservableMap<K, V> oldValue) {
216        if (valid) {
217            if (oldValue != null) {
218                oldValue.removeListener(mapChangeListener);
219            }
220            valid = false;
221            invalidateProperties();
222            invalidated();
223            fireValueChangedEvent();
224        }
225    }
226
227
228
229    /**
230     * The method {@code invalidated()} can be overridden to receive
231     * invalidation notifications. This is the preferred option in
232     * {@code Objects} defining the property, because it requires less memory.
233     *
234     * The default implementation is empty.
235     */
236    protected void invalidated() {
237    }
238
239    @Override
240    public ObservableMap<K, V> get() {
241        if (!valid) {
242            value = observable == null ? value : observable.getValue();
243            valid = true;
244            if (value != null) {
245                value.addListener(mapChangeListener);
246            }
247        }
248        return value;
249    }
250
251    @Override
252    public void set(ObservableMap<K, V> newValue) {
253        if (isBound()) {
254            throw new RuntimeException("A bound value cannot be set.");
255        }
256        if (value != newValue) {
257            final ObservableMap<K, V> oldValue = value;
258            value = newValue;
259            markInvalid(oldValue);
260        }
261    }
262
263    @Override
264    public boolean isBound() {
265        return observable != null;
266    }
267
268    @Override
269    public void bind(final ObservableValue<? extends ObservableMap<K, V>> newObservable) {
270        if (newObservable == null) {
271            throw new NullPointerException("Cannot bind to null");
272        }
273        if (!newObservable.equals(observable)) {
274            unbind();
275            observable = newObservable;
276            if (listener == null) {
277                listener = new Listener();
278            }
279            observable.addListener(listener);
280            markInvalid(value);
281        }
282    }
283
284    @Override
285    public void unbind() {
286        if (observable != null) {
287            value = observable.getValue();
288            observable.removeListener(listener);
289            observable = null;
290        }
291    }
292
293    /**
294     * Returns a string representation of this {@code MapPropertyBase} object.
295     * @return a string representation of this {@code MapPropertyBase} object.
296     */
297    @Override
298    public String toString() {
299        final Object bean = getBean();
300        final String name = getName();
301        final StringBuilder result = new StringBuilder("MapProperty [");
302        if (bean != null) {
303            result.append("bean: ").append(bean).append(", ");
304        }
305        if ((name != null) && (!name.equals(""))) {
306            result.append("name: ").append(name).append(", ");
307        }
308        if (isBound()) {
309            result.append("bound, ");
310            if (valid) {
311                result.append("value: ").append(get());
312            } else {
313                result.append("invalid");
314            }
315        } else {
316            result.append("value: ").append(get());
317        }
318        result.append("]");
319        return result.toString();
320    }
321
322    private class Listener implements InvalidationListener {
323        @Override
324        public void invalidated(Observable valueModel) {
325            markInvalid(value);
326        }
327    }
328
329}