1 /*
   2  * Copyright (c) 2011, 2014, 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.property;
  27 
  28 import javafx.beans.InvalidationListener;
  29 import javafx.beans.Observable;
  30 import javafx.beans.binding.BooleanBinding;
  31 import javafx.beans.value.ChangeListener;
  32 import javafx.beans.value.ObservableBooleanValue;
  33 import javafx.beans.value.ObservableValue;
  34 
  35 import com.sun.javafx.binding.ExpressionHelper;
  36 import java.lang.ref.WeakReference;
  37 import javafx.beans.WeakListener;
  38 
  39 /**
  40  * The class {@code BooleanPropertyBase} is the base class for a property
  41  * wrapping a {@code boolean} value.
  42  *
  43  * It provides all the functionality required for a property except for the
  44  * {@link #getBean()} and {@link #getName()} methods, which must be implemented
  45  * by extending classes.
  46  *
  47  * @see BooleanProperty
  48  * @since JavaFX 2.0
  49  */
  50 public abstract class BooleanPropertyBase extends BooleanProperty {
  51 
  52     private boolean value;
  53     private ObservableBooleanValue observable = null;
  54     private InvalidationListener listener = null;
  55     private boolean valid = true;
  56     private ExpressionHelper<Boolean> helper = null;
  57 
  58     /**
  59      * The constructor of the {@code BooleanPropertyBase}.
  60      */
  61     public BooleanPropertyBase() {
  62     }
  63 
  64     /**
  65      * The constructor of the {@code BooleanPropertyBase}.
  66      *
  67      * @param initialValue
  68      *            the initial value of the wrapped value
  69      */
  70     public BooleanPropertyBase(boolean initialValue) {
  71         this.value = initialValue;
  72     }
  73 
  74     @Override
  75     public void addListener(InvalidationListener listener) {
  76         helper = ExpressionHelper.addListener(helper, this, listener);
  77     }
  78 
  79     @Override
  80     public void removeListener(InvalidationListener listener) {
  81         helper = ExpressionHelper.removeListener(helper, listener);
  82     }
  83 
  84     @Override
  85     public void addListener(ChangeListener<? super Boolean> listener) {
  86         helper = ExpressionHelper.addListener(helper, this, listener);
  87     }
  88 
  89     @Override
  90     public void removeListener(ChangeListener<? super Boolean> listener) {
  91         helper = ExpressionHelper.removeListener(helper, listener);
  92     }
  93 
  94     /**
  95      * Sends notifications to all attached
  96      * {@link javafx.beans.InvalidationListener InvalidationListeners} and
  97      * {@link javafx.beans.value.ChangeListener ChangeListeners}.
  98      *
  99      * This method is called when the value is changed, either manually by
 100      * calling {@link #set(boolean)} or in case of a bound property, if the
 101      * binding becomes invalid.
 102      */
 103     protected void fireValueChangedEvent() {
 104         ExpressionHelper.fireValueChangedEvent(helper);
 105     }
 106 
 107     private void markInvalid() {
 108         if (valid) {
 109             valid = false;
 110             invalidated();
 111             fireValueChangedEvent();
 112         }
 113     }
 114 
 115     /**
 116      * The method {@code invalidated()} can be overridden to receive
 117      * invalidation notifications. This is the preferred option in
 118      * {@code Objects} defining the property, because it requires less memory.
 119      *
 120      * The default implementation is empty.
 121      */
 122     protected void invalidated() {
 123     }
 124 
 125     /**
 126      * {@inheritDoc}
 127      */
 128     @Override
 129     public boolean get() {
 130         valid = true;
 131         return observable == null ? value : observable.get();
 132     }
 133 
 134     /**
 135      * {@inheritDoc}
 136      */
 137     @Override
 138     public void set(boolean newValue) {
 139         if (isBound()) {
 140             throw new java.lang.RuntimeException((getBean() != null && getName() != null ?
 141                     getBean().getClass().getSimpleName() + "." + getName() + " : ": "") + "A bound value cannot be set.");
 142         }
 143         if (value != newValue) {
 144             value = newValue;
 145             markInvalid();
 146         }
 147     }
 148 
 149     /**
 150      * {@inheritDoc}
 151      */
 152     @Override
 153     public boolean isBound() {
 154         return observable != null;
 155     }
 156 
 157     /**
 158      * {@inheritDoc}
 159      * Note:
 160      */
 161     @Override
 162     public void bind(final ObservableValue<? extends Boolean> rawObservable) {
 163         if (rawObservable == null) {
 164             throw new NullPointerException("Cannot bind to null");
 165         }
 166 
 167         final ObservableBooleanValue newObservable = (rawObservable instanceof ObservableBooleanValue) ? (ObservableBooleanValue) rawObservable
 168                 : new ValueWrapper(rawObservable);
 169 
 170         if (!newObservable.equals(observable)) {
 171             unbind();
 172             observable = newObservable;
 173             if (listener == null) {
 174                 listener = new Listener(this);
 175             }
 176             observable.addListener(listener);
 177             markInvalid();
 178         }
 179     }
 180 
 181     /**
 182      * {@inheritDoc}
 183      */
 184     @Override
 185     public void unbind() {
 186         if (observable != null) {
 187             value = observable.get();
 188             observable.removeListener(listener);
 189             if (observable instanceof ValueWrapper) {
 190                 ((ValueWrapper)observable).dispose();
 191             }
 192             observable = null;
 193         }
 194     }
 195 
 196     /**
 197      * Returns a string representation of this {@code BooleanPropertyBase} object.
 198      * @return a string representation of this {@code BooleanPropertyBase} object.
 199      */
 200     @Override
 201     public String toString() {
 202         final Object bean = getBean();
 203         final String name = getName();
 204         final StringBuilder result = new StringBuilder("BooleanProperty [");
 205         if (bean != null) {
 206             result.append("bean: ").append(bean).append(", ");
 207         }
 208         if ((name != null) && (!name.equals(""))) {
 209             result.append("name: ").append(name).append(", ");
 210         }
 211         if (isBound()) {
 212             result.append("bound, ");
 213             if (valid) {
 214                 result.append("value: ").append(get());
 215             } else {
 216                 result.append("invalid");
 217             }
 218         } else {
 219             result.append("value: ").append(get());
 220         }
 221         result.append("]");
 222         return result.toString();
 223     }
 224 
 225     private static class Listener implements InvalidationListener, WeakListener {
 226 
 227         private final WeakReference<BooleanPropertyBase> wref;
 228 
 229         public Listener(BooleanPropertyBase ref) {
 230             this.wref = new WeakReference<>(ref);
 231         }
 232 
 233         @Override
 234         public void invalidated(Observable observable) {
 235             BooleanPropertyBase ref = wref.get();
 236             if (ref == null) {
 237                 observable.removeListener(this);
 238             } else {
 239                 ref.markInvalid();
 240             }
 241         }
 242 
 243         @Override
 244         public boolean wasGarbageCollected() {
 245             return wref.get() == null;
 246         }
 247     }
 248 
 249     private class ValueWrapper extends BooleanBinding {
 250         private ObservableValue<? extends Boolean> observable;
 251 
 252         public ValueWrapper(ObservableValue<? extends Boolean> observable) {
 253             this.observable = observable;
 254             bind(observable);
 255         }
 256 
 257         @Override
 258         protected boolean computeValue() {
 259             final Boolean value = observable.getValue();
 260             return (value == null) ? false : value;
 261         }
 262 
 263         @Override
 264         public void dispose() {
 265             unbind(observable);
 266         }        
 267     }
 268 }