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 }