/*
* Copyright [2014] [Christian Loehnert, krampenschiesser@gmail.com]
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.ks.binding;
import de.ks.reflection.PropertyPath;
import javafx.beans.property.*;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.collections.ObservableSet;
import javafx.scene.control.Label;
import javafx.scene.control.TextInputControl;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
public class Binding {
private static final Logger log = LoggerFactory.getLogger(Binding.class);
private final Map<PropertyPath, Property<?>> properties = new HashMap<>();
private final Map<Property<?>, Pair<Function, Function>> converters = new HashMap<>();
private final Set<StringProperty> clearOnRefresh = new HashSet<>();
public void bindChangedModel(ObservableValue<?> observable, Object oldValue, Object newValue) {
clearOnRefresh.forEach(c -> c.set(""));
log.debug("Binding changed model old={}, new={}", oldValue, newValue);
if (newValue != null) {
applyModelToProperties(newValue);
} else {
resetProperties();
}
}
protected void resetProperties() {
properties.entrySet().forEach(entry -> {
@SuppressWarnings("unchecked") Property<Object> property = (Property<Object>) entry.getValue();
if (!property.isBound()) {
Object defaultValue = getDefaultValue(property);
property.setValue(defaultValue);
}
});
}
private Object getDefaultValue(Property property) {
if (property instanceof DoubleProperty) {
return 0.0D;
}
if (property instanceof FloatProperty) {
return 0.0F;
}
if (property instanceof LongProperty) {
return 0L;
}
if (property instanceof IntegerProperty) {
return 0;
}
if (property instanceof BooleanProperty) {
return false;
}
if (property instanceof StringProperty) {
return "";
}
return null;
}
@SuppressWarnings("unchecked")
protected void applyModelToProperties(Object model) {
properties.entrySet().forEach(entry -> {
Object value = entry.getKey().getValue(model);
@SuppressWarnings("unchecked") Property<Object> property = (Property<Object>) entry.getValue();
Pair<Function, Function> converter = converters.get(property);
if (converter != null && value != null) {
value = converter.getKey().apply(value);
}
if (!property.isBound()) {
property.setValue(value);
}
});
}
public void applyControllerContent(Object model) {
properties.entrySet().forEach(entry -> {
@SuppressWarnings("unchecked") Property<Object> property = (Property<Object>) entry.getValue();
Object value = property.getValue();
String valueString = "";
if (value != null) {
valueString = String.valueOf(value);
if (valueString.length() > 100) {
valueString = valueString.substring(0, 100);
}
}
log.debug("For key '{}' setting property {} to {}", entry.getKey().getPropertyPath(), property, valueString);
entry.getKey().setValue(model, value);
});
}
public <T extends Object> StringProperty getStringProperty(Class<T> clazz, Function<T, String> propertyResolution) {
return addProperty(clazz, propertyResolution, SimpleStringProperty::new, null, null);
}
public <T extends Object> BooleanProperty getBooleanProperty(Class<T> clazz, Function<T, Boolean> propertyResolution) {
return addProperty(clazz, propertyResolution, SimpleBooleanProperty::new, null, null);
}
public <T extends Object> IntegerProperty getIntegerProperty(Class<T> clazz, Function<T, Integer> propertyResolution) {
return addProperty(clazz, propertyResolution, SimpleIntegerProperty::new, null, null);
}
public <T extends Object> LongProperty getLongProperty(Class<T> clazz, Function<T, Long> propertyResolution) {
return addProperty(clazz, propertyResolution, SimpleLongProperty::new, null, null);
}
public <T extends Object> FloatProperty getFloatProperty(Class<T> clazz, Function<T, Float> propertyResolution) {
return addProperty(clazz, propertyResolution, SimpleFloatProperty::new, null, null);
}
public <T extends Object> DoubleProperty getDoubleProperty(Class<T> clazz, Function<T, Double> propertyResolution) {
return addProperty(clazz, propertyResolution, SimpleDoubleProperty::new, null, null);
}
public <T extends Object, V> ObjectProperty<V> getObjectProperty(Class<T> clazz, Function<T, V> propertyResolution) {
return addProperty(clazz, propertyResolution, SimpleObjectProperty::new, null, null);
}
public <T extends Object, V> ListProperty<V> getListProperty(Class<T> clazz, Function<T, List<V>> propertyResolution) {
return addProperty(clazz, propertyResolution, SimpleListProperty::new, FXCollections::observableList, (ObservableList<V> l) -> l);
}
public <T extends Object, V> SetProperty<V> getSetProperty(Class<T> clazz, Function<T, Set<V>> propertyResolution) {
return addProperty(clazz, propertyResolution, SimpleSetProperty::new, FXCollections::observableSet, (ObservableSet<V> l) -> l);
}
public <T extends Object, K, V> MapProperty<K, V> getMapProperty(Class<T> clazz, Function<T, Map<K, V>> propertyResolution) {
return addProperty(clazz, propertyResolution, SimpleMapProperty::new, FXCollections::observableMap, (ObservableMap<K, V> m) -> m);
}
@SuppressWarnings("unchecked")
protected <T extends Property, V, K, O> T addProperty(Class<V> clazz, Function<V, K> function, Supplier<T> supplier, Function<K, O> model2Controller, Function<O, K> controller2Model) {
PropertyPath path = PropertyPath.ofTypeSafe(clazz, function);
if (!properties.containsKey(path)) {
T value = supplier.get();
properties.put(path, value);
if (model2Controller != null && controller2Model != null) {
converters.put(value, Pair.of(model2Controller, controller2Model));
} else if (model2Controller != null || controller2Model != null) {
throw new IllegalArgumentException("Please specify both converters");
}
}
return (T) properties.get(path);
}
public void registerClearOnRefresh(TextInputControl control) {
clearOnRefresh.add(control.textProperty());
}
public void registerClearOnRefresh(Label overTime) {
clearOnRefresh.add(overTime.textProperty());
}
}