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