1 /* 2 * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.beans.binding; 27 28 import com.sun.javafx.binding.StringFormatter; 29 import javafx.beans.InvalidationListener; 30 import javafx.beans.property.ReadOnlyBooleanProperty; 31 import javafx.beans.property.ReadOnlyIntegerProperty; 32 import javafx.beans.value.ObservableMapValue; 33 import javafx.beans.value.ObservableValue; 34 import javafx.collections.FXCollections; 35 import javafx.collections.MapChangeListener; 36 import javafx.collections.ObservableList; 37 import javafx.collections.ObservableMap; 38 39 import java.util.*; 40 41 /** 42 * A {@code MapExpression} is a 43 * {@link javafx.beans.value.ObservableMapValue} plus additional convenience 44 * methods to generate bindings in a fluent style. 45 * <p> 46 * A concrete sub-class of {@code MapExpression} has to implement the method 47 * {@link javafx.beans.value.ObservableMapValue#get()}, which provides the 48 * actual value of this expression. 49 * <p> 50 * If the wrapped list of a {@code MapExpression} is {@code null}, all methods implementing the {@code Map} 51 * interface will behave as if they were applied to an immutable empty list. 52 * 53 * @param <K> the type of the key elements 54 * @param <V> the type of the value elements 55 * @since JavaFX 2.1 56 */ 57 public abstract class MapExpression<K, V> implements ObservableMapValue<K, V> { 58 59 private static final ObservableMap EMPTY_MAP = new EmptyObservableMap(); 60 61 private static class EmptyObservableMap<K, V> extends AbstractMap<K, V> implements ObservableMap<K, V> { 62 63 @Override 64 public Set<Entry<K, V>> entrySet() { 65 return Collections.emptySet(); 66 } 67 68 @Override 69 public void addListener(MapChangeListener<? super K, ? super V> mapChangeListener) { 70 // no-op 71 } 72 73 @Override 74 public void removeListener(MapChangeListener<? super K, ? super V> mapChangeListener) { 75 // no-op 76 } 77 78 @Override 79 public void addListener(InvalidationListener listener) { 80 // no-op 81 } 82 83 @Override 84 public void removeListener(InvalidationListener listener) { 85 // no-op 86 } 87 } 88 89 @Override 90 public ObservableMap<K, V> getValue() { 91 return get(); 92 } 93 94 /** 95 * Returns a {@code MapExpression} that wraps a 96 * {@link javafx.beans.value.ObservableMapValue}. If the 97 * {@code ObservableMapValue} is already a {@code MapExpression}, it 98 * will be returned. Otherwise a new 99 * {@link javafx.beans.binding.MapBinding} is created that is bound to 100 * the {@code ObservableMapValue}. 101 * 102 * @param value 103 * The source {@code ObservableMapValue} 104 * @return A {@code MapExpression} that wraps the 105 * {@code ObservableMapValue} if necessary 106 * @throws NullPointerException 107 * if {@code value} is {@code null} 108 */ 109 public static <K, V> MapExpression<K, V> mapExpression(final ObservableMapValue<K, V> value) { 110 if (value == null) { 111 throw new NullPointerException("Map must be specified."); 112 } 113 return value instanceof MapExpression ? (MapExpression<K, V>) value 114 : new MapBinding<K, V>() { 115 { 116 super.bind(value); 117 } 118 119 @Override 120 public void dispose() { 121 super.unbind(value); 122 } 123 124 @Override 125 protected ObservableMap<K, V> computeValue() { 126 return value.get(); 127 } 128 129 @Override 130 public ObservableList<?> getDependencies() { 131 return FXCollections.singletonObservableList(value); 132 } 133 }; 134 } 135 136 /** 137 * The size of the map 138 */ 139 public int getSize() { 140 return size(); 141 } 142 143 /** 144 * An integer property that represents the size of the map. 145 * @return the property 146 */ 147 public abstract ReadOnlyIntegerProperty sizeProperty(); 148 149 /** 150 * A boolean property that is {@code true}, if the map is empty. 151 */ 152 public abstract ReadOnlyBooleanProperty emptyProperty(); 153 154 /** 155 * Creates a new {@link ObjectBinding} that contains the mapping of the specified key. 156 * 157 * @param key the key of the mapping 158 * @return the {@code ObjectBinding} 159 */ 160 public ObjectBinding<V> valueAt(K key) { 161 return Bindings.valueAt(this, key); 162 } 163 164 /** 165 * Creates a new {@link ObjectBinding} that contains the mapping of the specified key. 166 * 167 * @param key the key of the mapping 168 * @return the {@code ObjectBinding} 169 * @throws NullPointerException if {@code key} is {@code null} 170 */ 171 public ObjectBinding<V> valueAt(ObservableValue<K> key) { 172 return Bindings.valueAt(this, key); 173 } 174 175 /** 176 * Creates a new {@link BooleanBinding} that holds {@code true} if this map is equal to 177 * another {@link javafx.collections.ObservableMap}. 178 * 179 * @param other 180 * the other {@code ObservableMap} 181 * @return the new {@code BooleanBinding} 182 * @throws NullPointerException 183 * if {@code other} is {@code null} 184 */ 185 public BooleanBinding isEqualTo(final ObservableMap<?, ?> other) { 186 return Bindings.equal(this, other); 187 } 188 189 /** 190 * Creates a new {@link BooleanBinding} that holds {@code true} if this map is not equal to 191 * another {@link javafx.collections.ObservableMap}. 192 * 193 * @param other 194 * the other {@code ObservableMap} 195 * @return the new {@code BooleanBinding} 196 * @throws NullPointerException 197 * if {@code other} is {@code null} 198 */ 199 public BooleanBinding isNotEqualTo(final ObservableMap<?, ?> other) { 200 return Bindings.notEqual(this, other); 201 } 202 203 /** 204 * Creates a new {@link BooleanBinding} that holds {@code true} if the wrapped map is {@code null}. 205 * 206 * @return the new {@code BooleanBinding} 207 */ 208 public BooleanBinding isNull() { 209 return Bindings.isNull(this); 210 } 211 212 /** 213 * Creates a new {@link BooleanBinding} that holds {@code true} if the wrapped map is not {@code null}. 214 * 215 * @return the new {@code BooleanBinding} 216 */ 217 public BooleanBinding isNotNull() { 218 return Bindings.isNotNull(this); 219 } 220 221 /** 222 * Creates a {@link javafx.beans.binding.StringBinding} that holds the value 223 * of the {@code MapExpression} turned into a {@code String}. If the 224 * value of this {@code MapExpression} changes, the value of the 225 * {@code StringBinding} will be updated automatically. 226 * 227 * @return the new {@code StringBinding} 228 */ 229 public StringBinding asString() { 230 return (StringBinding) StringFormatter.convert(this); 231 } 232 233 @Override 234 public int size() { 235 final ObservableMap<K, V> map = get(); 236 return (map == null)? EMPTY_MAP.size() : map.size(); 237 } 238 239 @Override 240 public boolean isEmpty() { 241 final ObservableMap<K, V> map = get(); 242 return (map == null)? EMPTY_MAP.isEmpty() : map.isEmpty(); 243 } 244 245 @Override 246 public boolean containsKey(Object obj) { 247 final ObservableMap<K, V> map = get(); 248 return (map == null)? EMPTY_MAP.containsKey(obj) : map.containsKey(obj); 249 } 250 251 @Override 252 public boolean containsValue(Object obj) { 253 final ObservableMap<K, V> map = get(); 254 return (map == null)? EMPTY_MAP.containsValue(obj) : map.containsValue(obj); 255 } 256 257 @Override 258 public V put(K key, V value) { 259 final ObservableMap<K, V> map = get(); 260 return (map == null)? (V) EMPTY_MAP.put(key, value) : map.put(key, value); 261 } 262 263 @Override 264 public V remove(Object obj) { 265 final ObservableMap<K, V> map = get(); 266 return (map == null)? (V) EMPTY_MAP.remove(obj) : map.remove(obj); 267 } 268 269 @Override 270 public void putAll(Map<? extends K, ? extends V> elements) { 271 final ObservableMap<K, V> map = get(); 272 if (map == null) { 273 EMPTY_MAP.putAll(elements); 274 } else { 275 map.putAll(elements); 276 } 277 } 278 279 @Override 280 public void clear() { 281 final ObservableMap<K, V> map = get(); 282 if (map == null) { 283 EMPTY_MAP.clear(); 284 } else { 285 map.clear(); 286 } 287 } 288 289 @Override 290 public Set<K> keySet() { 291 final ObservableMap<K, V> map = get(); 292 return (map == null)? EMPTY_MAP.keySet() : map.keySet(); 293 } 294 295 @Override 296 public Collection<V> values() { 297 final ObservableMap<K, V> map = get(); 298 return (map == null)? EMPTY_MAP.values() : map.values(); 299 } 300 301 @Override 302 public Set<Entry<K, V>> entrySet() { 303 final ObservableMap<K, V> map = get(); 304 return (map == null)? EMPTY_MAP.entrySet() : map.entrySet(); 305 } 306 307 @Override 308 public V get(Object key) { 309 final ObservableMap<K, V> map = get(); 310 return (map == null)? (V) EMPTY_MAP.get(key) : map.get(key); 311 } 312 313 }