141 lines
5.2 KiB
Java
141 lines
5.2 KiB
Java
package de.neitzel.neitzelfx.mvvm;
|
|
|
|
import javafx.beans.property.*;
|
|
|
|
import java.beans.BeanInfo;
|
|
import java.beans.IntrospectionException;
|
|
import java.beans.Introspector;
|
|
import java.beans.PropertyDescriptor;
|
|
import java.lang.reflect.*;
|
|
import java.util.*;
|
|
|
|
/**
|
|
* A generic ViewModel for JavaFX using reflection to automatically create properties for model attributes.
|
|
* Supports binding between JavaFX controls and the model via JavaFX properties.
|
|
*
|
|
* @param <T> the type of the underlying model
|
|
*/
|
|
public class GenericViewModel<T> {
|
|
/**
|
|
* The original model instance.
|
|
*/
|
|
private final T model;
|
|
|
|
/**
|
|
* A map holding JavaFX properties corresponding to the model's fields.
|
|
*/
|
|
private final Map<String, Property<?>> properties = new HashMap<>();
|
|
|
|
/**
|
|
* Constructs a GenericViewModel for the given model instance.
|
|
*
|
|
* @param model the model to wrap
|
|
*/
|
|
public GenericViewModel(T model) {
|
|
this.model = model;
|
|
initProperties();
|
|
}
|
|
|
|
/**
|
|
* Initializes JavaFX properties for all readable and writable model fields using reflection.
|
|
*/
|
|
private void initProperties() {
|
|
Class<?> clazz = model.getClass();
|
|
for (PropertyDescriptor pd : getPropertyDescriptors(clazz)) {
|
|
Method getter = pd.getReadMethod();
|
|
Method setter = pd.getWriteMethod();
|
|
String name = pd.getName();
|
|
|
|
if (getter != null && setter != null) {
|
|
Class<?> propertyType = pd.getPropertyType();
|
|
try {
|
|
Object value = getter.invoke(model);
|
|
Property<?> property = createJavaFXProperty(propertyType, value);
|
|
properties.put(name, property);
|
|
} catch (Exception e) {
|
|
throw new RuntimeException("Failed to initialize property: " + name, e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves all readable/writable property descriptors of the given class.
|
|
*
|
|
* @param clazz the class to introspect
|
|
* @return a list of property descriptors
|
|
*/
|
|
private List<PropertyDescriptor> getPropertyDescriptors(Class<?> clazz) {
|
|
try {
|
|
BeanInfo info = Introspector.getBeanInfo(clazz, Object.class);
|
|
return Arrays.asList(info.getPropertyDescriptors());
|
|
} catch (IntrospectionException e) {
|
|
throw new RuntimeException("Cannot introspect model class: " + clazz, e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates an appropriate JavaFX Property instance for the given type and initial value.
|
|
*
|
|
* @param type the type of the property (e.g., String.class, int.class)
|
|
* @param initialValue the initial value of the property
|
|
* @return a new JavaFX Property instance
|
|
*/
|
|
private Property<?> createJavaFXProperty(Class<?> type, Object initialValue) {
|
|
if (type == String.class) return new SimpleStringProperty((String) initialValue);
|
|
if (type == int.class || type == Integer.class) return new SimpleIntegerProperty((Integer) initialValue);
|
|
if (type == double.class || type == Double.class) return new SimpleDoubleProperty((Double) initialValue);
|
|
if (type == boolean.class || type == Boolean.class) return new SimpleBooleanProperty((Boolean) initialValue);
|
|
if (type == long.class || type == Long.class) return new SimpleLongProperty((Long) initialValue);
|
|
// Fallback for unsupported types:
|
|
return new SimpleObjectProperty<>(initialValue);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the JavaFX property associated with the given name and type.
|
|
*
|
|
* @param propertyClass the expected property type (e.g., StringProperty.class)
|
|
* @param name the name of the model field
|
|
* @param <P> the type of the Property
|
|
* @return the corresponding JavaFX property
|
|
* @throws IllegalArgumentException if the property doesn't exist or the type mismatches
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public <P extends Property<?>> P property(Class<P> propertyClass, String name) {
|
|
Property<?> prop = properties.get(name);
|
|
if (prop == null) {
|
|
throw new IllegalArgumentException("No property found for name: " + name);
|
|
}
|
|
if (!propertyClass.isInstance(prop)) {
|
|
throw new IllegalArgumentException("Property type mismatch: expected " + propertyClass.getSimpleName());
|
|
}
|
|
return (P) prop;
|
|
}
|
|
|
|
/**
|
|
* Writes the current values of all properties back into the model using their respective setters.
|
|
*/
|
|
public void save() {
|
|
for (PropertyDescriptor pd : getPropertyDescriptors(model.getClass())) {
|
|
String name = pd.getName();
|
|
Method setter = pd.getWriteMethod();
|
|
if (setter != null && properties.containsKey(name)) {
|
|
try {
|
|
Object value = properties.get(name).getValue();
|
|
setter.invoke(model, value);
|
|
} catch (Exception e) {
|
|
throw new RuntimeException("Failed to save property: " + name, e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the wrapped model instance.
|
|
*
|
|
* @return the model
|
|
*/
|
|
public T getModel() {
|
|
return model;
|
|
}
|
|
} |