Updated Injection Classes. Unit Tests still required.

This commit is contained in:
Konrad Neitzel
2025-04-06 14:57:52 +02:00
parent f37d943cba
commit 8e05dce0d5
25 changed files with 601 additions and 94 deletions
@@ -1,8 +1,11 @@
package de.neitzel.fx.component;
import javafx.beans.property.*;
import java.lang.reflect.*;
import java.util.*;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
public class AutoViewModel<T> {
@@ -1,9 +1,13 @@
package de.neitzel.fx.injectfx;
import de.neitzel.core.inject.InjectableComponentScanner;
import de.neitzel.core.inject.ComponentData;
import de.neitzel.core.inject.ComponentScanner;
import lombok.Getter;
import java.lang.reflect.Constructor;
import java.util.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* Manages the creation and storage of singleton instances for all @FXMLComponent classes.
@@ -11,18 +15,19 @@ import java.util.*;
*/
public class FXMLComponentInstances {
@Getter
/** Map holding instances of all @FXMLComponent classes, indexed by class and its unique superclasses/interfaces. */
private final Map<Class<?>, Object> instanceMap = new HashMap<>();
/** The InjectableComponents instance that provides information about instantiable components. */
private final InjectableComponentScanner injectableScanner;
private final ComponentScanner injectableScanner;
/**
* Constructs an FXMLComponentInstances manager and initializes all component instances.
*
* @param injectableScanner The InjectableComponents instance containing resolved component types.
*/
public FXMLComponentInstances(InjectableComponentScanner injectableScanner) {
public FXMLComponentInstances(ComponentScanner injectableScanner) {
this.injectableScanner = injectableScanner;
createAllInstances();
}
@@ -47,7 +52,7 @@ public class FXMLComponentInstances {
return instanceMap.get(componentClass);
}
Class<?> concreteClass = injectableScanner.getInstantiableComponents().get(componentClass);
Class<?> concreteClass = injectableScanner.getInstantiableComponents().get(componentClass).getType();
if (concreteClass == null) {
throw new IllegalStateException("No concrete implementation found for: " + componentClass.getName());
}
@@ -122,19 +127,10 @@ public class FXMLComponentInstances {
private void registerInstance(Class<?> concreteClass, Object instance) {
instanceMap.put(concreteClass, instance);
for (Map.Entry<Class<?>, Class<?>> entry : injectableScanner.getInstantiableComponents().entrySet()) {
if (entry.getValue().equals(concreteClass)) {
for (Map.Entry<Class<?>, ComponentData> entry : injectableScanner.getInstantiableComponents().entrySet()) {
if (entry.getValue().getType().equals(concreteClass)) {
instanceMap.put(entry.getKey(), instance);
}
}
}
/**
* Retrieves the instance map containing all component instances.
*
* @return A map of class types to their instances.
*/
public Map<Class<?>, Object> getInstanceMap() {
return instanceMap;
}
}
@@ -8,13 +8,52 @@ import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;
/**
* The InjectingControllerFactory is responsible for creating controller instances for JavaFX that
* support dependency injection. It uses a parameter map to resolve and supply dependencies to
* controller constructors dynamically during instantiation.
*
* This class simplifies the process of injecting dependencies into JavaFX controllers by analyzing
* the constructors of the given controller classes at runtime. It selects the constructor that best
* matches the available dependencies in the parameter map and creates an instance of the controller.
*
* It implements the Callback interface to provide compatibility with the JavaFX FXMLLoader, allowing
* controllers with dependencies to be injected seamlessly during the FXML loading process.
*/
public class InjectingControllerFactory implements Callback<Class<?>, Object> {
/**
* A map that stores class-to-object mappings used for dependency injection
* in controller instantiation. This map is utilized to resolve and supply
* the required dependencies for constructors during the creation of controller
* instances.
*
* Each key in the map represents a class type, and the corresponding value
* is the instance of that type. This allows the {@link InjectingControllerFactory}
* to use the stored instances to dynamically match and inject dependencies
* into controllers at runtime.
*/
private final Map<Class<?>, Object> parameterMap = new HashMap<>();
/**
* Adds a mapping between a class and its corresponding object instance
* to the parameter map used for dependency injection.
*
* @param clazz The class type to be associated with the provided object instance.
* @param object The object instance to be injected for the specified class type.
*/
public void addInjectingData(Class<?> clazz, Object object) {
parameterMap.put(clazz, object);
}
/**
* Creates an instance of a controller class using a constructor that matches the dependencies
* defined in the parameter map. The method dynamically analyzes the constructors of the given
* class and attempts to instantiate the class by injecting required dependencies.
*
* @param controllerClass the class of the controller to be instantiated
* @return an instance of the specified controller class
* @throws RuntimeException if an error occurs while creating the controller instance or if no suitable constructor is found
*/
@Override
public Object call(Class<?> controllerClass) {
try {
@@ -37,6 +76,17 @@ public class InjectingControllerFactory implements Callback<Class<?>, Object> {
}
}
/**
* Determines if a given constructor can be instantiated using the provided parameter map.
* This method checks if the parameter map contains entries for all parameter types required
* by the specified constructor.
*
* @param constructor The constructor to be evaluated for instantiability.
* @param parameterMap A map where keys are parameter types and values are the corresponding
* instances available for injection.
* @return {@code true} if the constructor can be instantiated with the given parameter map;
* {@code false} otherwise.
*/
private boolean canBeInstantiated(Constructor<?> constructor, Map<Class<?>, Object> parameterMap) {
return Stream.of(constructor.getParameterTypes()).allMatch(parameterMap::containsKey);
}
@@ -1,42 +1,131 @@
package de.neitzel.fx.injectfx;
import de.neitzel.core.inject.InjectableComponentScanner;
import de.neitzel.core.inject.ComponentScanner;
import javafx.fxml.FXMLLoader;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.net.URL;
/**
* The InjectingFXMLLoader class provides a custom implementation of JavaFX's FXMLLoader
* that supports dependency injection. It facilitates the loading of FXML files while
* dynamically injecting dependencies into controllers during the loading process.
*
* This class utilizes the InjectingControllerFactory to enable seamless integration
* of dependency injection with JavaFX controllers. Dependencies can be added to the
* controller factory, and the loader will use this factory to instantiate controllers
* with the appropriate dependencies.
*
* Features of this loader include:
* - Support for dependency injection into JavaFX controllers by using a custom factory.
* - Adding custom dependency mappings at runtime.
* - Scanning and initializing injectable components from a specified package.
*/
@Slf4j
public class InjectingFXMLLoader {
/**
* Represents an instance of the {@link InjectingControllerFactory}, which is used for creating
* and managing controller instances with dependency injection capabilities in a JavaFX application.
*
* This factory facilitates the injection of dependencies by dynamically resolving and supplying
* required objects during controller instantiation. It plays a critical role in enabling seamless
* integration of dependency injection with JavaFX's {@link FXMLLoader}.
*
* The `controllerFactory` is used internally by the {@link InjectingFXMLLoader} to provide a consistent
* and extensible mechanism for controller creation while maintaining loose coupling and enhancing testability.
*
* Key responsibilities:
* - Manages a mapping of classes to their injectable instances required for controller instantiation.
* - Dynamically analyzes and invokes appropriate constructors for controllers based on the availability
* of dependencies.
* - Ensures that controllers are created with required dependencies, preventing manual resolution of injections.
*
* This variable is initialized in the {@link InjectingFXMLLoader} and can be extended with additional
* mappings at runtime using relevant methods.
*/
private final InjectingControllerFactory controllerFactory;
/**
* Default constructor for the InjectingFXMLLoader class.
* This initializes the loader with a new instance of the {@link InjectingControllerFactory},
* which is used to provide dependency injection capabilities for JavaFX controllers.
*
* The {@link InjectingControllerFactory} allows for the registration and dynamic injection
* of dependencies into controllers when they are instantiated during the FXML loading process.
*/
public InjectingFXMLLoader() {
controllerFactory = new InjectingControllerFactory();
}
/**
* Constructs a new instance of the InjectingFXMLLoader class with a specified
* InjectingControllerFactory for dependency injection. This enables the FXMLLoader
* to use the provided controller factory to instantiate controllers with their
* required dependencies during the FXML loading process.
*
* @param controllerFactory The controller factory responsible for creating controller
* instances with dependency injection support.
*/
public InjectingFXMLLoader(InjectingControllerFactory controllerFactory) {
this.controllerFactory = controllerFactory;
}
/**
* Constructs an instance of `InjectingFXMLLoader` and initializes the component scanner
* for dependency injection based on components found within a specified package.
* The scanned components are analyzed and their instances are prepared for injection
* using the internal `InjectingControllerFactory`.
*
* @param packageName The package name to be scanned for injectable components.
* Classes within the specified package will be identified
* as potential dependencies and made available for injection
* into JavaFX controllers during FXML loading.
*/
public InjectingFXMLLoader(String packageName) {
controllerFactory = new InjectingControllerFactory();
InjectableComponentScanner scanner = new InjectableComponentScanner(packageName);
ComponentScanner scanner = new ComponentScanner(packageName);
FXMLComponentInstances instances = new FXMLComponentInstances(scanner);
addInjectingData(instances);
}
/**
* Adds all injectable data from the given {@code FXMLComponentInstances} to the controller factory.
* Iterates through the classes in the instance map and delegates adding each class-instance pair
* to the {@link #addInjectingData(Class, Object)} method.
*
* @param instances An {@code FXMLComponentInstances} object containing the mapping of classes
* to their respective singleton instances. This data represents the components
* available for dependency injection.
*/
private void addInjectingData(FXMLComponentInstances instances) {
for (var clazz: instances.getInstanceMap().keySet()) {
addInjectingData(clazz, instances.getInstance(clazz));
}
}
/**
* Adds a specific class-object mapping to the controller factory for dependency injection.
* This method allows the association of a given class type with its corresponding instance,
* enabling dependency injection into controllers during the FXML loading process.
*
* @param clazz The class type to be associated with the provided object instance.
* @param object The object instance to be injected for the specified class type.
*/
public void addInjectingData(Class<?> clazz, Object object) {
controllerFactory.addInjectingData(clazz, object);
}
/**
* Loads an FXML resource from the provided URL and injects dependencies into its controller
* using the configured {@code InjectingControllerFactory}.
*
* @param url the URL of the FXML file to be loaded
* @param <T> the type of the root element defined in the FXML
* @return the root element of the FXML file
* @throws IOException if an error occurs during the loading of the FXML file
*/
public <T> T load(URL url) throws IOException {
FXMLLoader loader = new FXMLLoader(url);
loader.setControllerFactory(controllerFactory);
@@ -1,6 +1,7 @@
package de.neitzel.fx.mvvm;
import javafx.fxml.Initializable;
import java.net.URL;
import java.util.ResourceBundle;
@@ -6,8 +6,11 @@ import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.*;
import java.util.*;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* A generic ViewModel for JavaFX using reflection to automatically create properties for model attributes.