package org.tessell.model.properties;
import static org.tessell.model.properties.NewProperty.basicProperty;
import static org.tessell.model.properties.NewProperty.booleanProperty;
import static org.tessell.util.ObjectUtils.eq;
import org.tessell.model.events.PropertyChangedEvent;
import org.tessell.model.events.PropertyChangedHandler;
import org.tessell.model.values.DerivedValue;
/** Methods that can be used cross the value-based {@link AbstractProperty} and also derived properties like {@link FormattedProperty} and {@link ConvertedProperty}. */
abstract class AbstractAbstractProperty<P> implements Property<P> {
private BooleanProperty invalid = null;
/** Track {@code other} as derived on us, so we'll forward changed/changing events to it. */
@Override
public <P1 extends Property<?>> P1 addDerived(final P1 other) {
return addDerived(other, this, true);
}
/** Remove {@code other} as derived on us. */
@Override
public <P1 extends Property<?>> P1 removeDerived(final P1 other) {
return removeDerived(other, this);
}
/** @return a new derived property by applying {@code formatter} to our value */
@Override
public <T1> FormattedProperty<T1, P> formatted(final PropertyFormatter<P, T1> formatter) {
return new FormattedProperty<T1, P>(this, formatter);
}
/** @return a new derived property by applying {@code formatter} to our value */
@Override
public <T1> FormattedProperty<T1, P> formatted(final String invalidMessage, final PropertyFormatter<P, T1> formatter) {
return new FormattedProperty<T1, P>(this, formatter, invalidMessage);
}
/** @return a new derived property by applying {@code converter} to our value */
@Override
public <T1> Property<T1> as(final PropertyConverter<P, T1> converter) {
return new ConvertedProperty<T1, P>(this, converter);
}
@Override
public Property<String> asString() {
return as(new PropertyConverter<P, String>() {
public String to(P a) {
return a.toString();
}
});
}
@Override
public Property<Boolean> is(final P value) {
return is(value, null);
}
@Override
public Property<Boolean> is(final P value, final P whenUnsetValue) {
final BooleanProperty is = booleanProperty(getName() + "Is" + value);
is.setInitialValue(isEqual(get(), value));
final boolean[] changing = { false };
// is -> this
is.addPropertyChangedHandler(new PropertyChangedHandler<Boolean>() {
public void onPropertyChanged(PropertyChangedEvent<Boolean> event) {
if (isReadOnly()) {
changing[0] = true;
is.set(isEqual(get(), value));
changing[0] = false;
} else if (event.getNewValue() != null && event.getNewValue()) {
AbstractAbstractProperty.this.set(value);
} else if (!changing[0]) {
AbstractAbstractProperty.this.set(whenUnsetValue);
}
}
});
// this -> is
addPropertyChangedHandler(new PropertyChangedHandler<P>() {
public void onPropertyChanged(PropertyChangedEvent<P> event) {
changing[0] = true;
is.set(isEqual(get(), value));
changing[0] = false;
}
});
return is;
}
@Override
public Property<Boolean> is(final Property<P> other) {
return is(other, null);
}
@Override
public Property<Boolean> is(final Property<P> other, final P whenUnsetValue) {
final BooleanProperty is = booleanProperty(getName() + "Is" + other.getName());
is.setInitialValue(isEqual(get(), other.get()));
final boolean[] changing = { false };
// is -> this
is.addPropertyChangedHandler(new PropertyChangedHandler<Boolean>() {
public void onPropertyChanged(PropertyChangedEvent<Boolean> event) {
if (isReadOnly()) {
changing[0] = true;
is.set(isEqual(get(), other.get()));
changing[0] = false;
} else if (event.getNewValue() != null && event.getNewValue()) {
AbstractAbstractProperty.this.set(other.get());
} else if (!changing[0]) {
AbstractAbstractProperty.this.set(whenUnsetValue);
}
}
});
// this -> is
addPropertyChangedHandler(new PropertyChangedHandler<P>() {
public void onPropertyChanged(PropertyChangedEvent<P> event) {
changing[0] = true;
is.set(isEqual(get(), other.get()));
changing[0] = false;
}
});
// other -> is
other.addPropertyChangedHandler(new PropertyChangedHandler<P>() {
public void onPropertyChanged(PropertyChangedEvent<P> event) {
changing[0] = true;
is.set(isEqual(get(), other.get()));
changing[0] = false;
}
});
return is;
}
@Override
public Property<Boolean> is(final Condition<P> condition) {
return booleanProperty(new DerivedValue<Boolean>() {
public Boolean get() {
return condition.evaluate(AbstractAbstractProperty.this.get());
}
});
}
@Override
public Property<P> orIfNull(final P ifNullValue) {
return basicProperty(new DerivedValue<P>(getName()) {
public P get() {
P thisValue = AbstractAbstractProperty.this.get();
return thisValue == null ? ifNullValue : thisValue;
}
});
}
@Override
public Property<Boolean> isSet() {
return as(new PropertyConverter<P, Boolean>() {
@Override
public Boolean nullValue() {
return false;
}
@Override
public Boolean to(P a) {
return a != null;
}
});
}
@Override
public BooleanProperty invalid() {
if (invalid == null) {
invalid = NewProperty.and(valid().is(false), touched());
}
return invalid;
}
/** Checks equality between a and b for the {@link #is} methods. Overrideable by subclasses. */
protected boolean isEqual(P a, P b) {
return eq(a, b);
}
}