/*
* 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));
}
}