1 /* 2 * Copyright (c) 2011, 2016, 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 * {@code MapExpression} is an 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 <K> the type of the key elements 103 * @param <V> the type of the value elements 104 * @param value 105 * The source {@code ObservableMapValue} 106 * @return A {@code MapExpression} that wraps the 107 * {@code ObservableMapValue} if necessary 108 * @throws NullPointerException 109 * if {@code value} is {@code null} 110 */ 111 public static <K, V> MapExpression<K, V> mapExpression(final ObservableMapValue<K, V> value) { 112 if (value == null) { 113 throw new NullPointerException("Map must be specified."); 114 } 115 return value instanceof MapExpression ? (MapExpression<K, V>) value 116 : new MapBinding<K, V>() { 117 { 118 super.bind(value); 119 } 120 121 @Override 122 public void dispose() { 123 super.unbind(value); 124 } 125 126 @Override 127 protected ObservableMap<K, V> computeValue() { 128 return value.get(); 129 } 130 131 @Override 132 public ObservableList<?> getDependencies() { 133 return FXCollections.singletonObservableList(value); 134 } 135 }; 136 } 137 138 /** 139 * The size of the map 140 * @return the size 141 */ 142 public int getSize() { 143 return size(); 144 } 145 146 /** 147 * An integer property that represents the size of the map. 148 * @return the property 149 */ 150 public abstract ReadOnlyIntegerProperty sizeProperty(); 151 152 /** 153 * A boolean property that is {@code true}, if the map is empty. 154 * @return the {@code ReadOnlyBooleanProperty} 155 */ 156 public abstract ReadOnlyBooleanProperty emptyProperty(); 157 158 /** 159 * Creates a new {@link ObjectBinding} that contains the mapping of the specified key. 160 * 161 * @param key the key of the mapping 162 * @return the {@code ObjectBinding} 163 */ 164 public ObjectBinding<V> valueAt(K key) { 165 return Bindings.valueAt(this, key); 166 } 167 168 /** 169 * Creates a new {@link ObjectBinding} that contains the mapping of the specified key. 170 * 171 * @param key the key of the mapping 172 * @return the {@code ObjectBinding} 173 * @throws NullPointerException if {@code key} is {@code null} 174 */ 175 public ObjectBinding<V> valueAt(ObservableValue<K> key) { 176 return Bindings.valueAt(this, key); 177 } 178 179 /** 180 * Creates a new {@link BooleanBinding} that holds {@code true} if this map is equal to 181 * another {@link javafx.collections.ObservableMap}. 182 * 183 * @param other 184 * the other {@code ObservableMap} 185 * @return the new {@code BooleanBinding} 186 * @throws NullPointerException 187 * if {@code other} is {@code null} 188 */ 189 public BooleanBinding isEqualTo(final ObservableMap<?, ?> other) { 190 return Bindings.equal(this, other); 191 } 192 193 /** 194 * Creates a new {@link BooleanBinding} that holds {@code true} if this map is not equal to 195 * another {@link javafx.collections.ObservableMap}. 196 * 197 * @param other 198 * the other {@code ObservableMap} 199 * @return the new {@code BooleanBinding} 200 * @throws NullPointerException 201 * if {@code other} is {@code null} 202 */ 203 public BooleanBinding isNotEqualTo(final ObservableMap<?, ?> other) { 204 return Bindings.notEqual(this, other); 205 } 206 207 /** 208 * Creates a new {@link BooleanBinding} that holds {@code true} if the wrapped map is {@code null}. 209 * 210 * @return the new {@code BooleanBinding} 211 */ 212 public BooleanBinding isNull() { 213 return Bindings.isNull(this); 214 } 215 216 /** 217 * Creates a new {@link BooleanBinding} that holds {@code true} if the wrapped map is not {@code null}. 218 * 219 * @return the new {@code BooleanBinding} 220 */ 221 public BooleanBinding isNotNull() { 222 return Bindings.isNotNull(this); 223 } 224 225 /** 226 * Creates a {@link javafx.beans.binding.StringBinding} that holds the value 227 * of the {@code MapExpression} turned into a {@code String}. If the 228 * value of this {@code MapExpression} changes, the value of the 229 * {@code StringBinding} will be updated automatically. 230 * 231 * @return the new {@code StringBinding} 232 */ 233 public StringBinding asString() { 234 return (StringBinding) StringFormatter.convert(this); 235 } 236 237 @Override 238 public int size() { 239 final ObservableMap<K, V> map = get(); 240 return (map == null)? EMPTY_MAP.size() : map.size(); 241 } 242 243 @Override 244 public boolean isEmpty() { 245 final ObservableMap<K, V> map = get(); 246 return (map == null)? EMPTY_MAP.isEmpty() : map.isEmpty(); 247 } 248 249 @Override 250 public boolean containsKey(Object obj) { 251 final ObservableMap<K, V> map = get(); 252 return (map == null)? EMPTY_MAP.containsKey(obj) : map.containsKey(obj); 253 } 254 255 @Override 256 public boolean containsValue(Object obj) { 257 final ObservableMap<K, V> map = get(); 258 return (map == null)? EMPTY_MAP.containsValue(obj) : map.containsValue(obj); 259 } 260 261 @Override 262 public V put(K key, V value) { 263 final ObservableMap<K, V> map = get(); 264 return (map == null)? (V) EMPTY_MAP.put(key, value) : map.put(key, value); 265 } 266 267 @Override 268 public V remove(Object obj) { 269 final ObservableMap<K, V> map = get(); 270 return (map == null)? (V) EMPTY_MAP.remove(obj) : map.remove(obj); 271 } 272 273 @Override 274 public void putAll(Map<? extends K, ? extends V> elements) { 275 final ObservableMap<K, V> map = get(); 276 if (map == null) { 277 EMPTY_MAP.putAll(elements); 278 } else { 279 map.putAll(elements); 280 } 281 } 282 283 @Override 284 public void clear() { 285 final ObservableMap<K, V> map = get(); 286 if (map == null) { 287 EMPTY_MAP.clear(); 288 } else { 289 map.clear(); 290 } 291 } 292 293 @Override 294 public Set<K> keySet() { 295 final ObservableMap<K, V> map = get(); 296 return (map == null)? EMPTY_MAP.keySet() : map.keySet(); 297 } 298 299 @Override 300 public Collection<V> values() { 301 final ObservableMap<K, V> map = get(); 302 return (map == null)? EMPTY_MAP.values() : map.values(); 303 } 304 305 @Override 306 public Set<Entry<K, V>> entrySet() { 307 final ObservableMap<K, V> map = get(); 308 return (map == null)? EMPTY_MAP.entrySet() : map.entrySet(); 309 } 310 311 @Override 312 public V get(Object key) { 313 final ObservableMap<K, V> map = get(); 314 return (map == null)? (V) EMPTY_MAP.get(key) : map.get(key); 315 } 316 317 }