/*
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to <http://unlicense.org/>
*/
package jxtn.jfx.axi;
import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.function.Function;
import javafx.beans.WeakListener;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import com.sun.javafx.binding.BidirectionalBinding;
/**
* 支援型態轉換的{@link BidirectionalBinding}
*
* @author AqD
* @param <T1> 屬性1型態
* @param <T2> 屬性2型態
*/
public abstract class BidirectionalBinding2<T1, T2> implements ChangeListener<Object>, WeakListener
{
public static <T1, T2> BidirectionalBinding2<T1, T2> bind(
Property<T1> property1, Property<T2> property2,
Function<? super T1, ? extends T2> convert1to2,
Function<? super T2, ? extends T1> convert2to1)
{
Objects.requireNonNull(property1);
Objects.requireNonNull(property2);
if (property1 == property2)
throw new IllegalArgumentException();
Objects.requireNonNull(convert1to2);
Objects.requireNonNull(convert2to1);
GenericConversionBidirectionalBinding2<T1, T2> binding = new GenericConversionBidirectionalBinding2<T1, T2>(property1, property2, convert1to2, convert2to1);
property1.setValue(convert2to1.apply(property2.getValue()));
property1.addListener(binding);
property2.addListener(binding);
return binding;
}
public static <T1, T2> void unbind(Property<T1> property1, Property<T2> property2)
{
Objects.requireNonNull(property1);
Objects.requireNonNull(property2);
if (property1 == property2)
throw new IllegalArgumentException();
final BidirectionalBinding2<T1, T2> binding = new GenericStubBidirectionalBinding2<>(property1, property2);
property1.removeListener(binding);
property2.removeListener(binding);
}
private final int cachedHashCode;
private BidirectionalBinding2(Property<T1> property1, Property<T2> property2)
{
this.cachedHashCode = property1.hashCode() * property2.hashCode();
}
protected abstract Object getProperty1();
protected abstract Object getProperty2();
@Override
public int hashCode()
{
return this.cachedHashCode;
}
@Override
public boolean wasGarbageCollected()
{
return (this.getProperty1() == null) || (this.getProperty2() == null);
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
final Object propertyA1 = this.getProperty1();
final Object propertyA2 = this.getProperty2();
if ((propertyA1 == null) || (propertyA2 == null))
{
return false;
}
if (obj instanceof BidirectionalBinding2)
{
final BidirectionalBinding2<?, ?> otherBinding = (BidirectionalBinding2<?, ?>) 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 GenericStubBidirectionalBinding2<T1, T2> extends BidirectionalBinding2<T1, T2>
{
private final Object property1;
private final Object property2;
public GenericStubBidirectionalBinding2(Property<T1> property1, Property<T2> property2)
{
super(property1, property2);
this.property1 = property1;
this.property2 = property2;
}
@Override
protected Object getProperty1()
{
return this.property1;
}
@Override
protected Object getProperty2()
{
return this.property2;
}
@Override
public void changed(ObservableValue<? extends Object> sourceProperty, Object oldValue, Object newValue)
{
throw new RuntimeException("Should not reach here");
}
}
private static class GenericConversionBidirectionalBinding2<T1, T2> extends BidirectionalBinding2<T1, T2>
{
private final WeakReference<Property<T1>> propertyRef1;
private final WeakReference<Property<T2>> propertyRef2;
private boolean updating = false;
private Function<? super T1, ? extends T2> convert1to2;
private Function<? super T2, ? extends T1> convert2to1;
private GenericConversionBidirectionalBinding2(
Property<T1> property1, Property<T2> property2,
Function<? super T1, ? extends T2> convert1to2,
Function<? super T2, ? extends T1> convert2to1)
{
super(property1, property2);
this.propertyRef1 = new WeakReference<Property<T1>>(property1);
this.propertyRef2 = new WeakReference<Property<T2>>(property2);
this.convert1to2 = convert1to2;
this.convert2to1 = convert2to1;
}
@Override
protected Property<T1> getProperty1()
{
return this.propertyRef1.get();
}
@Override
protected Property<T2> getProperty2()
{
return this.propertyRef2.get();
}
@Override
@SuppressWarnings("unchecked")
public void changed(ObservableValue<? extends Object> sourceProperty, Object oldValue, Object newValue)
{
if (!this.updating)
{
final Property<T1> property1 = this.propertyRef1.get();
final Property<T2> property2 = this.propertyRef2.get();
if ((property1 == null) || (property2 == null))
{
if (property1 != null)
{
property1.removeListener(this);
}
if (property2 != null)
{
property2.removeListener(this);
}
}
else
{
try
{
this.updating = true;
if (property1 == sourceProperty)
{
property2.setValue(this.convert1to2.apply((T1) newValue));
}
else
{
property1.setValue(this.convert2to1.apply((T2) newValue));
}
}
catch (RuntimeException e)
{
try
{
if (property1 == sourceProperty)
{
property1.setValue((T1) oldValue);
}
else
{
property2.setValue((T2) oldValue);
}
}
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
{
this.updating = false;
}
}
}
}
}
}