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}