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 }