com.sun.javafx.binding.BidirectionalBinding Maven / Gradle / Ivy
/*
* Copyright (c) 2011, 2023, 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 com.sun.javafx.binding;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.WeakListener;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.util.StringConverter;
import java.lang.ref.WeakReference;
import java.text.Format;
import java.text.ParseException;
import java.util.Objects;
/**
* @implNote Bidirectional bindings are implemented with InvalidationListeners, which are fired once
* when the changed property was valid, but elided for any future changes until the property
* is again validated by calling its getValue()/get() method.
* Since bidirectional bindings require that we observe all property changes, independent
* of whether the property was validated by user code, we manually validate both properties
* by calling their getter method in all relevant places.
*/
public abstract class BidirectionalBinding implements InvalidationListener, WeakListener {
private static void checkParameters(Object property1, Object property2) {
Objects.requireNonNull(property1, "Both properties must be specified.");
Objects.requireNonNull(property2, "Both properties must be specified.");
if (property1 == property2) {
throw new IllegalArgumentException("Cannot bind property to itself");
}
}
public static BidirectionalBinding bind(Property property1, Property property2) {
checkParameters(property1, property2);
final BidirectionalBinding binding =
((property1 instanceof DoubleProperty) && (property2 instanceof DoubleProperty)) ?
new BidirectionalDoubleBinding((DoubleProperty) property1, (DoubleProperty) property2)
: ((property1 instanceof FloatProperty) && (property2 instanceof FloatProperty)) ?
new BidirectionalFloatBinding((FloatProperty) property1, (FloatProperty) property2)
: ((property1 instanceof IntegerProperty) && (property2 instanceof IntegerProperty)) ?
new BidirectionalIntegerBinding((IntegerProperty) property1, (IntegerProperty) property2)
: ((property1 instanceof LongProperty) && (property2 instanceof LongProperty)) ?
new BidirectionalLongBinding((LongProperty) property1, (LongProperty) property2)
: ((property1 instanceof BooleanProperty) && (property2 instanceof BooleanProperty)) ?
new BidirectionalBooleanBinding((BooleanProperty) property1, (BooleanProperty) property2)
: new TypedGenericBidirectionalBinding<>(property1, property2);
property1.setValue(property2.getValue());
property1.getValue();
property1.addListener(binding);
property2.addListener(binding);
return binding;
}
public static Object bind(Property stringProperty, Property> otherProperty, Format format) {
checkParameters(stringProperty, otherProperty);
Objects.requireNonNull(format, "Format cannot be null");
final var binding = new StringFormatBidirectionalBinding<>(stringProperty, otherProperty, format);
stringProperty.setValue(format.format(otherProperty.getValue()));
stringProperty.getValue();
stringProperty.addListener(binding);
otherProperty.addListener(binding);
return binding;
}
public static Object bind(Property stringProperty, Property otherProperty, StringConverter converter) {
checkParameters(stringProperty, otherProperty);
Objects.requireNonNull(converter, "Converter cannot be null");
final var binding = new StringConverterBidirectionalBinding<>(stringProperty, otherProperty, converter);
stringProperty.setValue(converter.toString(otherProperty.getValue()));
stringProperty.getValue();
stringProperty.addListener(binding);
otherProperty.addListener(binding);
return binding;
}
public static void unbind(Property property1, Property property2) {
checkParameters(property1, property2);
final BidirectionalBinding binding = new UntypedGenericBidirectionalBinding(property1, property2);
property1.removeListener(binding);
property2.removeListener(binding);
}
public static void unbind(Object property1, Object property2) {
checkParameters(property1, property2);
final BidirectionalBinding binding = new UntypedGenericBidirectionalBinding(property1, property2);
if (property1 instanceof ObservableValue) {
((ObservableValue>)property1).removeListener(binding);
}
if (property2 instanceof ObservableValue) {
((ObservableValue>)property2).removeListener(binding);
}
}
public static BidirectionalBinding bindNumber(Property property1, IntegerProperty property2) {
return bindNumber(property1, (Property)property2);
}
public static BidirectionalBinding bindNumber(Property property1, LongProperty property2) {
return bindNumber(property1, (Property)property2);
}
public static BidirectionalBinding bindNumber(Property property1, FloatProperty property2) {
return bindNumber(property1, (Property)property2);
}
public static BidirectionalBinding bindNumber(Property property1, DoubleProperty property2) {
return bindNumber(property1, (Property)property2);
}
public static BidirectionalBinding bindNumber(IntegerProperty property1, Property property2) {
return bindNumberObject(property1, property2);
}
public static BidirectionalBinding bindNumber(LongProperty property1, Property property2) {
return bindNumberObject(property1, property2);
}
public static BidirectionalBinding bindNumber(FloatProperty property1, Property property2) {
return bindNumberObject(property1, property2);
}
public static BidirectionalBinding bindNumber(DoubleProperty property1, Property property2) {
return bindNumberObject(property1, property2);
}
private static BidirectionalBinding bindNumberObject(Property property1, Property property2) {
checkParameters(property1, property2);
final BidirectionalBinding binding = new TypedNumberBidirectionalBinding<>(property2, property1);
property1.setValue(property2.getValue());
property1.getValue();
property1.addListener(binding);
property2.addListener(binding);
return binding;
}
private static BidirectionalBinding bindNumber(Property property1, Property property2) {
checkParameters(property1, property2);
final BidirectionalBinding binding = new TypedNumberBidirectionalBinding<>(property1, property2);
property1.setValue((T)property2.getValue());
property1.getValue();
property1.addListener(binding);
property2.addListener(binding);
return binding;
}
private final int cachedHashCode;
private BidirectionalBinding(Object property1, Object property2) {
cachedHashCode = property1.hashCode() * property2.hashCode();
}
protected abstract Object getProperty1();
protected abstract Object getProperty2();
@Override
public int hashCode() {
return cachedHashCode;
}
@Override
public boolean wasGarbageCollected() {
return (getProperty1() == null) || (getProperty2() == null);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
final Object propertyA1 = getProperty1();
final Object propertyA2 = getProperty2();
if ((propertyA1 == null) || (propertyA2 == null)) {
return false;
}
if (obj instanceof BidirectionalBinding) {
final BidirectionalBinding otherBinding = (BidirectionalBinding) obj;
final Object propertyB1 = otherBinding.getProperty1();
final Object propertyB2 = otherBinding.getProperty2();
if ((propertyB1 == null) || (propertyB2 == null)) {
return false;
}
if (propertyA1 == propertyB1 && propertyA2 == propertyB2) {
return true;
}
if (propertyA1 == propertyB2 && propertyA2 == propertyB1) {
return true;
}
}
return false;
}
private static class BidirectionalBooleanBinding extends BidirectionalBinding {
private final WeakReference propertyRef1;
private final WeakReference propertyRef2;
private boolean oldValue;
private boolean updating;
private BidirectionalBooleanBinding(BooleanProperty property1, BooleanProperty property2) {
super(property1, property2);
oldValue = property1.get();
propertyRef1 = new WeakReference<>(property1);
propertyRef2 = new WeakReference<>(property2);
}
@Override
protected Property getProperty1() {
return propertyRef1.get();
}
@Override
protected Property getProperty2() {
return propertyRef2.get();
}
@Override
public void invalidated(Observable sourceProperty) {
if (!updating) {
final BooleanProperty property1 = propertyRef1.get();
final BooleanProperty property2 = propertyRef2.get();
if ((property1 == null) || (property2 == null)) {
if (property1 != null) {
property1.removeListener(this);
}
if (property2 != null) {
property2.removeListener(this);
}
} else {
try {
updating = true;
if (property1 == sourceProperty) {
boolean newValue = property1.get();
property2.set(newValue);
property2.get();
oldValue = newValue;
} else {
boolean newValue = property2.get();
property1.set(newValue);
property1.get();
oldValue = newValue;
}
} catch (RuntimeException e) {
try {
if (property1 == sourceProperty) {
property1.set(oldValue);
property1.get();
} else {
property2.set(oldValue);
property2.get();
}
} catch (Exception e2) {
e2.addSuppressed(e);
unbind(property1, property2);
throw new RuntimeException(
"Bidirectional binding failed together with an attempt"
+ " to restore the source property to the previous value."
+ " Removing the bidirectional binding from properties " +
property1 + " and " + property2, e2);
}
throw new RuntimeException(
"Bidirectional binding failed, setting to the previous value", e);
} finally {
updating = false;
}
}
}
}
}
private static class BidirectionalDoubleBinding extends BidirectionalBinding {
private final WeakReference propertyRef1;
private final WeakReference propertyRef2;
private double oldValue;
private boolean updating = false;
private BidirectionalDoubleBinding(DoubleProperty property1, DoubleProperty property2) {
super(property1, property2);
oldValue = property1.get();
propertyRef1 = new WeakReference<>(property1);
propertyRef2 = new WeakReference<>(property2);
}
@Override
protected Property getProperty1() {
return propertyRef1.get();
}
@Override
protected Property getProperty2() {
return propertyRef2.get();
}
@Override
public void invalidated(Observable sourceProperty) {
if (!updating) {
final DoubleProperty property1 = propertyRef1.get();
final DoubleProperty property2 = propertyRef2.get();
if ((property1 == null) || (property2 == null)) {
if (property1 != null) {
property1.removeListener(this);
}
if (property2 != null) {
property2.removeListener(this);
}
} else {
try {
updating = true;
if (property1 == sourceProperty) {
double newValue = property1.get();
property2.set(newValue);
property2.get();
oldValue = newValue;
} else {
double newValue = property2.get();
property1.set(newValue);
property1.get();
oldValue = newValue;
}
} catch (RuntimeException e) {
try {
if (property1 == sourceProperty) {
property1.set(oldValue);
property1.get();
} else {
property2.set(oldValue);
property2.get();
}
} catch (Exception e2) {
e2.addSuppressed(e);
unbind(property1, property2);
throw new RuntimeException(
"Bidirectional binding failed together with an attempt"
+ " to restore the source property to the previous value."
+ " Removing the bidirectional binding from properties " +
property1 + " and " + property2, e2);
}
throw new RuntimeException(
"Bidirectional binding failed, setting to the previous value", e);
} finally {
updating = false;
}
}
}
}
}
private static class BidirectionalFloatBinding extends BidirectionalBinding {
private final WeakReference propertyRef1;
private final WeakReference propertyRef2;
private float oldValue;
private boolean updating = false;
private BidirectionalFloatBinding(FloatProperty property1, FloatProperty property2) {
super(property1, property2);
oldValue = property1.get();
propertyRef1 = new WeakReference<>(property1);
propertyRef2 = new WeakReference<>(property2);
}
@Override
protected Property getProperty1() {
return propertyRef1.get();
}
@Override
protected Property getProperty2() {
return propertyRef2.get();
}
@Override
public void invalidated(Observable sourceProperty) {
if (!updating) {
final FloatProperty property1 = propertyRef1.get();
final FloatProperty property2 = propertyRef2.get();
if ((property1 == null) || (property2 == null)) {
if (property1 != null) {
property1.removeListener(this);
}
if (property2 != null) {
property2.removeListener(this);
}
} else {
try {
updating = true;
if (property1 == sourceProperty) {
float newValue = property1.get();
property2.set(newValue);
property2.get();
oldValue = newValue;
} else {
float newValue = property2.get();
property1.set(newValue);
property1.get();
oldValue = newValue;
}
} catch (RuntimeException e) {
try {
if (property1 == sourceProperty) {
property1.set(oldValue);
property1.get();
} else {
property2.set(oldValue);
property2.get();
}
} catch (Exception e2) {
e2.addSuppressed(e);
unbind(property1, property2);
throw new RuntimeException(
"Bidirectional binding failed together with an attempt"
+ " to restore the source property to the previous value."
+ " Removing the bidirectional binding from properties " +
property1 + " and " + property2, e2);
}
throw new RuntimeException(
"Bidirectional binding failed, setting to the previous value", e);
} finally {
updating = false;
}
}
}
}
}
private static class BidirectionalIntegerBinding extends BidirectionalBinding {
private final WeakReference propertyRef1;
private final WeakReference propertyRef2;
private int oldValue;
private boolean updating = false;
private BidirectionalIntegerBinding(IntegerProperty property1, IntegerProperty property2) {
super(property1, property2);
oldValue = property1.get();
propertyRef1 = new WeakReference<>(property1);
propertyRef2 = new WeakReference<>(property2);
}
@Override
protected Property getProperty1() {
return propertyRef1.get();
}
@Override
protected Property getProperty2() {
return propertyRef2.get();
}
@Override
public void invalidated(Observable sourceProperty) {
if (!updating) {
final IntegerProperty property1 = propertyRef1.get();
final IntegerProperty property2 = propertyRef2.get();
if ((property1 == null) || (property2 == null)) {
if (property1 != null) {
property1.removeListener(this);
}
if (property2 != null) {
property2.removeListener(this);
}
} else {
try {
updating = true;
if (property1 == sourceProperty) {
int newValue = property1.get();
property2.set(newValue);
property2.get();
oldValue = newValue;
} else {
int newValue = property2.get();
property1.set(newValue);
property1.get();
oldValue = newValue;
}
} catch (RuntimeException e) {
try {
if (property1 == sourceProperty) {
property1.set(oldValue);
property1.get();
} else {
property2.set(oldValue);
property2.get();
}
} catch (Exception e2) {
e2.addSuppressed(e);
unbind(property1, property2);
throw new RuntimeException(
"Bidirectional binding failed together with an attempt"
+ " to restore the source property to the previous value."
+ " Removing the bidirectional binding from properties " +
property1 + " and " + property2, e2);
}
throw new RuntimeException(
"Bidirectional binding failed, setting to the previous value", e);
} finally {
updating = false;
}
}
}
}
}
private static class BidirectionalLongBinding extends BidirectionalBinding {
private final WeakReference propertyRef1;
private final WeakReference propertyRef2;
private long oldValue;
private boolean updating = false;
private BidirectionalLongBinding(LongProperty property1, LongProperty property2) {
super(property1, property2);
oldValue = property1.get();
propertyRef1 = new WeakReference<>(property1);
propertyRef2 = new WeakReference<>(property2);
}
@Override
protected Property getProperty1() {
return propertyRef1.get();
}
@Override
protected Property getProperty2() {
return propertyRef2.get();
}
@Override
public void invalidated(Observable sourceProperty) {
if (!updating) {
final LongProperty property1 = propertyRef1.get();
final LongProperty property2 = propertyRef2.get();
if ((property1 == null) || (property2 == null)) {
if (property1 != null) {
property1.removeListener(this);
}
if (property2 != null) {
property2.removeListener(this);
}
} else {
try {
updating = true;
if (property1 == sourceProperty) {
long newValue = property1.get();
property2.set(newValue);
property2.get();
oldValue = newValue;
} else {
long newValue = property2.get();
property1.set(newValue);
property1.get();
oldValue = newValue;
}
} catch (RuntimeException e) {
try {
if (property1 == sourceProperty) {
property1.set(oldValue);
property1.get();
} else {
property2.set(oldValue);
property2.get();
}
} catch (Exception e2) {
e2.addSuppressed(e);
unbind(property1, property2);
throw new RuntimeException(
"Bidirectional binding failed together with an attempt"
+ " to restore the source property to the previous value."
+ " Removing the bidirectional binding from properties " +
property1 + " and " + property2, e2);
}
throw new RuntimeException(
"Bidirectional binding failed, setting to the previous value", e);
} finally {
updating = false;
}
}
}
}
}
private static class TypedGenericBidirectionalBinding extends BidirectionalBinding {
private final WeakReference> propertyRef1;
private final WeakReference> propertyRef2;
private T oldValue;
private boolean updating = false;
private TypedGenericBidirectionalBinding(Property property1, Property property2) {
super(property1, property2);
oldValue = property1.getValue();
propertyRef1 = new WeakReference<>(property1);
propertyRef2 = new WeakReference<>(property2);
}
@Override
protected Property getProperty1() {
return propertyRef1.get();
}
@Override
protected Property getProperty2() {
return propertyRef2.get();
}
@Override
public void invalidated(Observable sourceProperty) {
if (!updating) {
final Property property1 = propertyRef1.get();
final Property property2 = propertyRef2.get();
if ((property1 == null) || (property2 == null)) {
if (property1 != null) {
property1.removeListener(this);
}
if (property2 != null) {
property2.removeListener(this);
}
} else {
try {
updating = true;
if (property1 == sourceProperty) {
T newValue = property1.getValue();
property2.setValue(newValue);
property2.getValue();
oldValue = newValue;
} else {
T newValue = property2.getValue();
property1.setValue(newValue);
property1.getValue();
oldValue = newValue;
}
} catch (RuntimeException e) {
try {
if (property1 == sourceProperty) {
property1.setValue(oldValue);
property1.getValue();
} else {
property2.setValue(oldValue);
property2.getValue();
}
} catch (Exception e2) {
e2.addSuppressed(e);
unbind(property1, property2);
throw new RuntimeException(
"Bidirectional binding failed together with an attempt"
+ " to restore the source property to the previous value."
+ " Removing the bidirectional binding from properties " +
property1 + " and " + property2, e2);
}
throw new RuntimeException(
"Bidirectional binding failed, setting to the previous value", e);
} finally {
updating = false;
}
}
}
}
}
private static class TypedNumberBidirectionalBinding extends BidirectionalBinding {
private final WeakReference> propertyRef1;
private final WeakReference> propertyRef2;
private T oldValue;
private boolean updating = false;
private TypedNumberBidirectionalBinding(Property property1, Property property2) {
super(property1, property2);
oldValue = property1.getValue();
propertyRef1 = new WeakReference<>(property1);
propertyRef2 = new WeakReference<>(property2);
}
@Override
protected Property getProperty1() {
return propertyRef1.get();
}
@Override
protected Property getProperty2() {
return propertyRef2.get();
}
@Override
public void invalidated(Observable sourceProperty) {
if (!updating) {
final Property property1 = propertyRef1.get();
final Property property2 = propertyRef2.get();
if ((property1 == null) || (property2 == null)) {
if (property1 != null) {
property1.removeListener(this);
}
if (property2 != null) {
property2.removeListener(this);
}
} else {
try {
updating = true;
if (property1 == sourceProperty) {
T newValue = property1.getValue();
property2.setValue(newValue);
property2.getValue();
oldValue = newValue;
} else {
T newValue = (T)property2.getValue();
property1.setValue(newValue);
property1.getValue();
oldValue = newValue;
}
} catch (RuntimeException e) {
try {
if (property1 == sourceProperty) {
property1.setValue(oldValue);
property1.getValue();
} else {
property2.setValue(oldValue);
property2.getValue();
}
} catch (Exception e2) {
e2.addSuppressed(e);
unbind(property1, property2);
throw new RuntimeException(
"Bidirectional binding failed together with an attempt"
+ " to restore the source property to the previous value."
+ " Removing the bidirectional binding from properties " +
property1 + " and " + property2, e2);
}
throw new RuntimeException(
"Bidirectional binding failed, setting to the previous value", e);
} finally {
updating = false;
}
}
}
}
}
private static class UntypedGenericBidirectionalBinding extends BidirectionalBinding {
private final Object property1;
private final Object property2;
public UntypedGenericBidirectionalBinding(Object property1, Object property2) {
super(property1, property2);
this.property1 = property1;
this.property2 = property2;
}
@Override
protected Object getProperty1() {
return property1;
}
@Override
protected Object getProperty2() {
return property2;
}
@Override
public void invalidated(Observable sourceProperty) {
throw new RuntimeException("Should not reach here");
}
}
public abstract static class StringConversionBidirectionalBinding extends BidirectionalBinding {
private final WeakReference> stringPropertyRef;
private final WeakReference> otherPropertyRef;
private boolean updating;
public StringConversionBidirectionalBinding(Property stringProperty, Property otherProperty) {
super(stringProperty, otherProperty);
stringPropertyRef = new WeakReference<>(stringProperty);
otherPropertyRef = new WeakReference<>(otherProperty);
}
protected abstract String toString(T value);
protected abstract T fromString(String value) throws ParseException;
@Override
protected Object getProperty1() {
return stringPropertyRef.get();
}
@Override
protected Object getProperty2() {
return otherPropertyRef.get();
}
@Override
public void invalidated(Observable observable) {
if (!updating) {
final Property property1 = stringPropertyRef.get();
final Property property2 = otherPropertyRef.get();
if ((property1 == null) || (property2 == null)) {
if (property1 != null) {
property1.removeListener(this);
}
if (property2 != null) {
property2.removeListener(this);
}
} else {
try {
updating = true;
if (property1 == observable) {
try {
property2.setValue(fromString(property1.getValue()));
property2.getValue();
} catch (Exception e) {
Logging.getLogger().warning("Exception while parsing String in bidirectional binding", e);
property2.setValue(null);
property2.getValue();
}
} else {
try {
property1.setValue(toString(property2.getValue()));
property1.getValue();
} catch (Exception e) {
Logging.getLogger().warning("Exception while converting Object to String in bidirectional binding", e);
property1.setValue("");
property1.getValue();
}
}
} finally {
updating = false;
}
}
}
}
}
private static class StringFormatBidirectionalBinding extends StringConversionBidirectionalBinding {
private final Format format;
public StringFormatBidirectionalBinding(Property stringProperty, Property otherProperty, Format format) {
super(stringProperty, otherProperty);
this.format = format;
}
@Override
protected String toString(Object value) {
return format.format(value);
}
@SuppressWarnings("unchecked")
@Override
protected T fromString(String value) throws ParseException {
return (T) format.parseObject(value); // May result in ClassCastException, this is expected
}
}
private static class StringConverterBidirectionalBinding extends StringConversionBidirectionalBinding {
private final StringConverter converter;
public StringConverterBidirectionalBinding(Property stringProperty, Property otherProperty, StringConverter converter) {
super(stringProperty, otherProperty);
this.converter = converter;
}
@Override
protected String toString(T value) {
return converter.toString(value);
}
@Override
protected T fromString(String value) throws ParseException {
return converter.fromString(value);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy