/* * 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()); } }