/* * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javafx.beans.binding; import java.lang.ref.WeakReference; import java.util.Objects; import java.util.function.BiFunction; import java.util.function.Function; import javafx.beans.InvalidationListener; import javafx.beans.NamedArg; import javafx.beans.Observable; import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableDoubleValue; import javafx.beans.value.ObservableFloatValue; import javafx.beans.value.ObservableLongValue; import javafx.beans.value.ObservableNumberValue; import javafx.beans.value.ObservableObjectValue; import javafx.beans.value.ObservableStringValue; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import com.sun.javafx.binding.BooleanConstant; import com.sun.javafx.binding.DoubleConstant; import com.sun.javafx.binding.FloatConstant; import com.sun.javafx.binding.IntegerConstant; import com.sun.javafx.binding.Logging; import com.sun.javafx.binding.LongConstant; import com.sun.javafx.binding.ObjectConstant; import com.sun.javafx.binding.StringConstant; /** * Starting point for a binding that calculates a ternary expression. *

* A ternary expression has the basic form * {@code new When(cond).then(value1).otherwise(value2);}. The expression * {@code cond} needs to be an {@link javafx.beans.value.ObservableBooleanValue}. * Based on the value of {@code cond}, the binding contains the value of * {@code value1} (if {@code cond.getValue() == true}) or {@code value2} (if * {@code cond.getValue() == false}). The values {@code value1} and * {@code value2} have to be of the same type. They can be constant values or * implementations of {@link javafx.beans.value.ObservableValue}. * @since JavaFX 2.0 */ public class When { private static final String VALUE_MUST_BE_SPECIFIED = "Value must be specified"; private final ObservableBooleanValue condition; /** * The constructor of {@code When}. * * @param condition * the condition of the ternary expression */ public When(final @NamedArg("condition") ObservableBooleanValue condition) { this.condition = Objects.requireNonNull(condition, "Condition must be specified."); } private static class WhenListener implements InvalidationListener { private final ObservableBooleanValue condition; private final ObservableValue thenValue; private final ObservableValue otherwiseValue; private final WeakReference> ref; private WhenListener(Binding binding, ObservableBooleanValue condition, ObservableValue thenValue, ObservableValue otherwiseValue) { this.ref = new WeakReference>(binding); this.condition = condition; this.thenValue = thenValue; this.otherwiseValue = otherwiseValue; } @Override public void invalidated(Observable observable) { final Binding binding = ref.get(); if (binding == null) { condition.removeListener(this); if (thenValue != null) { thenValue.removeListener(this); } if (otherwiseValue != null) { otherwiseValue.removeListener(this); } } else { // short-circuit invalidation. This Binding becomes // only invalid if the condition changes or the // active branch. if (condition.equals(observable) || (binding.isValid() && (condition.get() == observable.equals(thenValue)))) { binding.invalidate(); } } } } private abstract class ConditionHolder> { protected B binding; private ObservableValue thenValue; private ObservableValue otherwiseValue; private InvalidationListener observer; protected void registerListeners(final ObservableValue then, final ObservableValue otherwise) { thenValue = then; otherwiseValue = otherwise; observer = new WhenListener(binding, condition, thenValue, otherwiseValue); condition.addListener(observer); thenValue.addListener(observer); otherwiseValue.addListener(observer); } // TODO: unnecessary - can access field directly protected B getBinding() { return binding; } protected ObservableList> getDependencies() { return FXCollections.unmodifiableObservableList( FXCollections.observableArrayList(condition, thenValue, otherwiseValue)); } protected V computeValue() { final boolean conditionValue = condition.get(); Logging.getLogger().finest("Condition of ternary binding expression was evaluated: {0}", conditionValue); return conditionValue ? thenValue.getValue() : otherwiseValue.getValue(); } protected void dispose() { condition.removeListener(observer); thenValue.removeListener(observer); otherwiseValue.removeListener(observer); } } private class DoubleConditionHolder extends ConditionHolder { protected DoubleConditionHolder(ObservableNumberValue then, ObservableNumberValue otherwise) { binding = new DoubleBinding() { @Override protected double computeValue() { return DoubleConditionHolder.super.computeValue().doubleValue(); } @Override public ObservableList getDependencies() { return DoubleConditionHolder.super.getDependencies(); } @Override public void dispose() { DoubleConditionHolder.super.dispose(); } }; registerListeners(then, otherwise); } } /** * If-then-else expression returning a number. * @since JavaFX 2.0 */ public class NumberConditionBuilder { private ObservableNumberValue thenValue; private NumberConditionBuilder(final ObservableNumberValue thenValue) { this.thenValue = thenValue; } /** * Defines the {@link javafx.beans.value.ObservableNumberValue} whose * value is returned by the ternary expression if the condition is * {@code false}. * * @param otherwiseValue * the value * @return the complete {@link DoubleBinding} */ public NumberBinding otherwise(ObservableNumberValue otherwiseValue) { Objects.requireNonNull(otherwiseValue, VALUE_MUST_BE_SPECIFIED); // Arranged by widening conventions if ((thenValue instanceof ObservableDoubleValue) || (otherwiseValue instanceof ObservableDoubleValue)) { return new DoubleConditionHolder(thenValue, otherwiseValue).getBinding(); } else if ((thenValue instanceof ObservableFloatValue) || (otherwiseValue instanceof ObservableFloatValue)) { return new DoubleConditionHolder(thenValue, otherwiseValue).getBinding(); } else if ((thenValue instanceof ObservableLongValue) || (otherwiseValue instanceof ObservableLongValue)) { return new DoubleConditionHolder(thenValue, otherwiseValue).getBinding(); } else { return new DoubleConditionHolder(thenValue, otherwiseValue).getBinding(); } } /** * Defines a constant value of the ternary expression that is returned * if the condition is {@code false}. * * @param otherwiseValue * the value * @return the complete {@link DoubleBinding} */ public DoubleBinding otherwise(double otherwiseValue) { return (DoubleBinding) otherwise(DoubleConstant.valueOf(otherwiseValue)); } /** * Defines a constant value of the ternary expression that is returned * if the condition is {@code false}. * * @param otherwiseValue * the value * @return the complete {@link NumberBinding} */ public NumberBinding otherwise(float otherwiseValue) { return otherwise(FloatConstant.valueOf(otherwiseValue)); // TODO: add note about runtime type of binding? } /** * Defines a constant value of the ternary expression that is returned * if the condition is {@code false}. * * @param otherwiseValue * the value * @return the complete {@link NumberBinding} */ public NumberBinding otherwise(long otherwiseValue) { return otherwise(LongConstant.valueOf(otherwiseValue)); } /** * Defines a constant value of the ternary expression that is returned * if the condition is {@code false}. * * @param otherwiseValue * the value * @return the complete {@link NumberBinding} */ public NumberBinding otherwise(int otherwiseValue) { return otherwise(IntegerConstant.valueOf(otherwiseValue)); } } /** * Defines the {@link javafx.beans.value.ObservableNumberValue} which value * is returned by the ternary expression if the condition is {@code true}. * * @param thenValue * the value * @return the intermediate result which still requires the otherwise-branch */ public NumberConditionBuilder then(final ObservableNumberValue thenValue) { Objects.requireNonNull(thenValue, VALUE_MUST_BE_SPECIFIED); return new NumberConditionBuilder(thenValue); } /** * Defines a constant value of the ternary expression that is returned if * the condition is {@code true}. * * @param thenValue * the value * @return the intermediate result which still requires the otherwise-branch */ public NumberConditionBuilder then(double thenValue) { return then(DoubleConstant.valueOf(thenValue)); } /** * Defines a constant value of the ternary expression that is returned if * the condition is {@code true}. * * @param thenValue * the value * @return the intermediate result which still requires the otherwise-branch */ public NumberConditionBuilder then(float thenValue) { return then(FloatConstant.valueOf(thenValue)); } /** * Defines a constant value of the ternary expression that is returned if * the condition is {@code true}. * * @param thenValue * the value * @return the intermediate result which still requires the otherwise-branch */ public NumberConditionBuilder then(long thenValue) { return then(LongConstant.valueOf(thenValue)); } /** * Defines a constant value of the ternary expression that is returned if * the condition is {@code true}. * * @param thenValue * the value * @return the intermediate result which still requires the otherwise-branch */ public NumberConditionBuilder then(int thenValue) { return then(IntegerConstant.valueOf(thenValue)); } private class BooleanConditionHolder extends ConditionHolder { private BooleanConditionHolder(ObservableValue then, ObservableValue otherwise) { binding = new BooleanBinding() { @Override protected boolean computeValue() { return BooleanConditionHolder.super.computeValue(); } @Override public void dispose() { BooleanConditionHolder.super.dispose(); } @Override public ObservableList> getDependencies() { return BooleanConditionHolder.super.getDependencies(); } }; registerListeners(then, otherwise); } } private abstract class ConditionBuilder> { private ObservableValue thenValue; protected BiFunction, ObservableValue, ? extends ConditionHolder> conditionHolderGenerator; protected Function> primitiveWrapper; protected ConditionBuilder(final ObservableValue thenValue) { this.thenValue = thenValue; } public B otherwise(final ObservableValue otherwiseValue) { Objects.requireNonNull(otherwiseValue, VALUE_MUST_BE_SPECIFIED); return conditionHolderGenerator.apply(thenValue, otherwiseValue).getBinding(); } public B otherwise(final V otherwiseValue) { return otherwise(primitiveWrapper.apply(otherwiseValue)); } } /** * An intermediate class needed while assembling the ternary expression. It * should not be used in another context. * @since JavaFX 2.0 */ public class BooleanConditionBuilder2 extends ConditionBuilder { private BooleanConditionBuilder2(ObservableBooleanValue then) { super(then); conditionHolderGenerator = (thenValue, otherwiseValue) -> new BooleanConditionHolder(thenValue, otherwiseValue); primitiveWrapper = otherwiseValue -> BooleanConstant.valueOf(otherwiseValue); } /** * Defines the {@link javafx.beans.value.ObservableBooleanValue} whose * value is returned by the ternary expression if the condition is * {@code false}. * * @param otherwiseValue * the value * @return the complete {@link BooleanBinding} */ public BooleanBinding otherwise(final ObservableBooleanValue otherwiseValue) { return super.otherwise(otherwiseValue); } /** * Defines a constant value of the ternary expression that is returned * if the condition is {@code false}. * * @param otherwiseValue * the value * @return the complete {@link BooleanBinding} */ public BooleanBinding otherwise(final boolean otherwiseValue) { return super.otherwise(otherwiseValue); } } /** * An intermediate class needed while assembling the ternary expression. It * should not be used in another context. * @since JavaFX 2.0 */ public class BooleanConditionBuilder { private ObservableValue thenValue; private BooleanConditionBuilder(final ObservableBooleanValue thenValue) { this.thenValue = thenValue; } /** * Defines the {@link javafx.beans.value.ObservableBooleanValue} whose * value is returned by the ternary expression if the condition is * {@code false}. * * @param otherwiseValue * the value * @return the complete {@link BooleanBinding} */ public BooleanBinding otherwise(final ObservableBooleanValue otherwiseValue) { Objects.requireNonNull(otherwiseValue, VALUE_MUST_BE_SPECIFIED); return new BooleanConditionHolder(thenValue, otherwiseValue).getBinding(); } /** * Defines a constant value of the ternary expression that is returned * if the condition is {@code false}. * * @param otherwiseValue * the value * @return the complete {@link BooleanBinding} */ public BooleanBinding otherwise(final boolean otherwiseValue) { return otherwise(BooleanConstant.valueOf(otherwiseValue)); } } /** * Defines the {@link javafx.beans.value.ObservableBooleanValue} which value * is returned by the ternary expression if the condition is {@code true}. * * @param thenValue * the value * @return the intermediate result which still requires the otherwise-branch */ public BooleanConditionBuilder then(final ObservableBooleanValue thenValue) { Objects.requireNonNull(thenValue, VALUE_MUST_BE_SPECIFIED); return new BooleanConditionBuilder(thenValue); } /** * Defines a constant value of the ternary expression that is returned if * the condition is {@code true}. * * @param thenValue * the value * @return the intermediate result which still requires the otherwise-branch */ public BooleanConditionBuilder then(final boolean thenValue) { return then(BooleanConstant.valueOf(thenValue)); } private class StringConditionHolder extends ConditionHolder { protected StringConditionHolder(ObservableStringValue then, ObservableStringValue otherwise) { binding = new StringBinding() { @Override protected String computeValue() { return StringConditionHolder.super.computeValue(); } @Override public void dispose() { StringConditionHolder.super.dispose(); } @Override public ObservableList> getDependencies() { return StringConditionHolder.super.getDependencies(); } }; registerListeners(then, otherwise); } } /** * An intermediate class needed while assembling the ternary expression. It * should not be used in another context. * @since JavaFX 2.0 */ public class StringConditionBuilder { private ObservableStringValue thenValue; private StringConditionBuilder(final ObservableStringValue thenValue) { this.thenValue = thenValue; } /** * Defines the {@link javafx.beans.value.ObservableStringValue} whose * value is returned by the ternary expression if the condition is * {@code false}. * * @param otherwiseValue * the value * @return the complete {@link StringBinding} */ public StringBinding otherwise(final ObservableStringValue otherwiseValue) { Objects.requireNonNull(otherwiseValue, VALUE_MUST_BE_SPECIFIED); return new StringConditionHolder(thenValue, otherwiseValue).getBinding(); } /** * Defines a constant value of the ternary expression that is returned * if the condition is {@code false}. * * @param otherwiseValue * the value * @return the complete {@link StringBinding} */ public StringBinding otherwise(final String otherwiseValue) { return otherwise(StringConstant.valueOf(otherwiseValue)); } } /** * Defines the {@link javafx.beans.value.ObservableStringValue} which value * is returned by the ternary expression if the condition is {@code true}. * * @param thenValue * the value * @return the intermediate result which still requires the otherwise-branch */ public StringConditionBuilder then(final ObservableStringValue thenValue) { Objects.requireNonNull(thenValue, VALUE_MUST_BE_SPECIFIED); return new StringConditionBuilder(thenValue); } /** * Defines a constant value of the ternary expression that is returned if * the condition is {@code true}. * * @param thenValue * the value * @return the intermediate result which still requires the otherwise-branch */ public StringConditionBuilder then(final String thenValue) { return then(StringConstant.valueOf(thenValue)); } private class ObjectConditionHolder extends ConditionHolder> { protected ObjectConditionHolder(ObservableObjectValue then, ObservableObjectValue otherwise) { binding = new ObjectBinding() { @Override protected T computeValue() { return ObjectConditionHolder.super.computeValue(); } @Override public void dispose() { ObjectConditionHolder.super.dispose(); } @Override public ObservableList> getDependencies() { return ObjectConditionHolder.super.getDependencies(); } }; registerListeners(then, otherwise); } } /** * An intermediate class needed while assembling the ternary expression. It * should not be used in another context. * @since JavaFX 2.0 */ public class ObjectConditionBuilder { private ObservableObjectValue thenValue; private ObjectConditionBuilder(final ObservableObjectValue thenValue) { this.thenValue = thenValue; } /** * Defines the {@link javafx.beans.value.ObservableObjectValue} which * value is returned by the ternary expression if the condition is * {@code false}. * * @param otherwiseValue * the value * @return the complete {@link ObjectBinding} */ public ObjectBinding otherwise(final ObservableObjectValue otherwiseValue) { Objects.requireNonNull(otherwiseValue, VALUE_MUST_BE_SPECIFIED); return new ObjectConditionHolder<>(thenValue, otherwiseValue).getBinding(); } /** * Defines a constant value of the ternary expression that is returned * if the condition is {@code false}. * * @param otherwiseValue * the value * @return the complete {@link ObjectBinding} */ public ObjectBinding otherwise(final T otherwiseValue) { return otherwise(ObjectConstant.valueOf(otherwiseValue)); } } /** * Defines the {@link javafx.beans.value.ObservableObjectValue} which value * is returned by the ternary expression if the condition is {@code true}. * * @param the type of the intermediate result * @param thenValue * the value * @return the intermediate result which still requires the otherwise-branch */ public ObjectConditionBuilder then(final ObservableObjectValue thenValue) { Objects.requireNonNull(thenValue, VALUE_MUST_BE_SPECIFIED); return new ObjectConditionBuilder(thenValue); } /** * Defines a constant value of the ternary expression that is returned if * the condition is {@code true}. * * @param the type of the intermediate result * @param thenValue * the value * @return the intermediate result which still requires the otherwise-branch */ public ObjectConditionBuilder then(final T thenValue) { return then(ObjectConstant.valueOf(thenValue)); } }