org.panteleyev.fx.PredicateProperty Maven / Gradle / Ivy
/*
Copyright © 2020 Petr Panteleyev
SPDX-License-Identifier: BSD-2-Clause
*/
package org.panteleyev.fx;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WeakChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Predicate;
/**
* This class implements property that wraps {@link Predicate} instance.
*
* Default value is x -> true
i.e. {@link PredicateProperty#test(Object)} will return
* true until the predicate is altered via {@link PredicateProperty#set(Predicate)}.
*
* @param the type of the input to the predicate
*/
public class PredicateProperty extends SimpleObjectProperty> implements Predicate {
private enum Op {
AND,
OR
}
private final Collection> inputs = new ArrayList<>();
private final Op op;
@SuppressWarnings("FieldCanBeLocal")
private final ChangeListener> LISTENER = (x, y, newValue) -> onInputUpdate();
/**
* Creates an instance with default predicate.
*/
public PredicateProperty() {
super(x -> true);
this.op = null;
}
/**
* Creates an instance with default predicate.
*
* @param bean bean of this property
* @param name name of this property
*/
public PredicateProperty(Object bean, String name) {
super(bean, name, x -> true);
this.op = null;
}
/**
* Creates an instance with the specified predicate value.
*
* @param initialValue initial predicate value
*/
public PredicateProperty(Predicate initialValue) {
super(initialValue);
this.op = null;
}
/**
* Creates an instance with the specified predicate value.
*
* @param bean bean of this property
* @param name name of this property
* @param initialValue initial predicate value
*/
public PredicateProperty(Object bean, String name, Predicate initialValue) {
super(bean, name, initialValue);
Objects.requireNonNull(initialValue, "Predicate value cannot be null");
this.op = null;
}
private PredicateProperty(Op op, Collection> inputs) {
this.op = op;
this.inputs.addAll(inputs);
super.set(buildPredicate(op, inputs));
for (var p : inputs) {
p.addListener(new WeakChangeListener<>(LISTENER));
}
}
private void onInputUpdate() {
super.set(buildPredicate(op, inputs));
}
private static Predicate buildPredicate(Op op, Collection> inputs) {
Predicate result = op == Op.AND ? x -> true : x -> false;
for (var p : inputs) {
result = switch (op) {
case AND -> result.and(p.get());
case OR -> result.or(p.get());
};
}
return result;
}
/**
* Creates calculated predicate property that produces logical AND from its arguments.
*
* @param args arguments
* @param type of the input to the predicate
* @return predicate property
*/
public static PredicateProperty and(Collection> args) {
return new PredicateProperty<>(Op.AND, args);
}
/**
* Creates calculated predicate property that produces logical OR from its arguments.
*
* @param args arguments
* @param type of the input to the predicate
* @return predicate property
*/
public static PredicateProperty or(Collection> args) {
return new PredicateProperty<>(Op.OR, args);
}
/**
* Sets the wrapped predicate value.
*
* @param predicate predicate value.
* @throws IllegalStateException if property is calculated
*/
@Override
public void set(Predicate predicate) {
if (op != null) {
throw new IllegalStateException("Calculated property cannot be set");
}
super.set(predicate);
}
/**
* Create a unidirectional binding for this Property.
*
* @param observableValue observable this {@link javafx.beans.Observable} should be bound to
*/
@Override
public void bind(ObservableValue> observableValue) {
if (op != null) {
throw new IllegalStateException("Calculated property cannot be bound");
}
super.bind(observableValue);
}
/**
* Sets the wrapped predicate value.
*
* @param predicate predicate value.
* @throws IllegalStateException if property is calculated
*/
@Override
public void setValue(Predicate predicate) {
if (op != null) {
throw new IllegalStateException("Calculated property cannot be set");
}
super.setValue(predicate);
}
/**
* Changes predicate to its initial value, i.e. x -> true.
*
* @throws IllegalStateException if property is calculated
*/
public void reset() {
if (op != null) {
throw new IllegalStateException("Calculated property cannot be reset");
}
super.setValue(x -> true);
}
// Predicate methods
@Override
public boolean test(T t) {
return get().test(t);
}
@Override
public Predicate and(Predicate other) {
return get().and(other);
}
@Override
public Predicate negate() {
return get().negate();
}
@Override
public Predicate or(Predicate other) {
return get().or(other);
}
}