Erste schnelle Version
This commit is contained in:
parent
7117bdfdcc
commit
eb14cb7994
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,4 +3,5 @@
|
|||||||
target
|
target
|
||||||
debug.out
|
debug.out
|
||||||
/nbactions.xml
|
/nbactions.xml
|
||||||
dependency-reduced-pom.xml
|
dependency-reduced-pom.xml
|
||||||
|
.DS_Store
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
<link.name>${project.artifactId}</link.name>
|
<link.name>${project.artifactId}</link.name>
|
||||||
<launcher>${project.artifactId}</launcher>
|
<launcher>${project.artifactId}</launcher>
|
||||||
<appName>${project.artifactId}</appName>
|
<appName>${project.artifactId}</appName>
|
||||||
<main.class>de.kneitzel.Main</main.class>
|
<main.class>de.neitzel.injectfx.example.Main</main.class>
|
||||||
<jar.filename>${project.artifactId}-${project.version}</jar.filename>
|
<jar.filename>${project.artifactId}-${project.version}</jar.filename>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package de.kneitzel;
|
package de.neitzel.injectfx.example;
|
||||||
|
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package de.kneitzel;
|
package de.neitzel.injectfx.example;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Another Main class as workaround when the JavaFX Application ist started without
|
* Another Main class as workaround when the JavaFX Application ist started without
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package de.kneitzel;
|
package de.neitzel.injectfx.example;
|
||||||
|
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
@ -10,7 +10,6 @@ import java.util.ResourceBundle;
|
|||||||
|
|
||||||
public class MainWindow implements Initializable {
|
public class MainWindow implements Initializable {
|
||||||
|
|
||||||
|
|
||||||
private int counter = 0;
|
private int counter = 0;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
@ -3,7 +3,7 @@
|
|||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.control.*?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
|
|
||||||
<AnchorPane prefHeight="127.0" prefWidth="209.0" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.kneitzel.MainWindow">
|
<AnchorPane prefHeight="127.0" prefWidth="209.0" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.neitzel.injectfx.example.MainWindow">
|
||||||
<children>
|
<children>
|
||||||
<Button fx:id="button" layoutX="44.0" layoutY="70.0" mnemonicParsing="false" onAction="#onButtonClick" text="Click Me" />
|
<Button fx:id="button" layoutX="44.0" layoutY="70.0" mnemonicParsing="false" onAction="#onButtonClick" text="Click Me" />
|
||||||
<TextField fx:id="textField" layoutX="14.0" layoutY="24.0" />
|
<TextField fx:id="textField" layoutX="14.0" layoutY="24.0" />
|
||||||
BIN
injectfx-example/src/test/resources/testlevel.png
Normal file
BIN
injectfx-example/src/test/resources/testlevel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
@ -17,10 +17,17 @@
|
|||||||
<link.name>${project.artifactId}</link.name>
|
<link.name>${project.artifactId}</link.name>
|
||||||
<launcher>${project.artifactId}</launcher>
|
<launcher>${project.artifactId}</launcher>
|
||||||
<appName>${project.artifactId}</appName>
|
<appName>${project.artifactId}</appName>
|
||||||
<main.class>de.kneitzel.Main</main.class>
|
<main.class>de.neitzel.injectfx.example.Main</main.class>
|
||||||
<jar.filename>${project.artifactId}-${project.version}</jar.filename>
|
<jar.filename>${project.artifactId}-${project.version}</jar.filename>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.reflections</groupId>
|
||||||
|
<artifactId>reflections</artifactId>
|
||||||
|
<version>${reflections.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<finalName>${jar.filename}</finalName>
|
<finalName>${jar.filename}</finalName>
|
||||||
|
|||||||
@ -0,0 +1,138 @@
|
|||||||
|
package de.neitzel.injectfx;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the creation and storage of singleton instances for all @FXMLComponent classes.
|
||||||
|
* It ensures that each component is instantiated only once and resolves dependencies recursively.
|
||||||
|
*/
|
||||||
|
public class FXMLComponentInstances {
|
||||||
|
|
||||||
|
/** 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an FXMLComponentInstances manager and initializes all component instances.
|
||||||
|
*
|
||||||
|
* @param injectableComponents The InjectableComponents instance containing resolved component types.
|
||||||
|
*/
|
||||||
|
public FXMLComponentInstances(InjectableComponentScanner injectableScanner) {
|
||||||
|
this.injectableScanner = injectableScanner;
|
||||||
|
createAllInstances();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates instances for all registered @FXMLComponent classes.
|
||||||
|
*/
|
||||||
|
private void createAllInstances() {
|
||||||
|
for (Class<?> componentClass : injectableScanner.getInstantiableComponents().keySet()) {
|
||||||
|
getInstance(componentClass); // Ensures each component is instantiated once
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an instance of a given component class, creating it if necessary.
|
||||||
|
*
|
||||||
|
* @param componentClass The class for which an instance is needed.
|
||||||
|
* @return An instance of the requested class.
|
||||||
|
*/
|
||||||
|
public Object getInstance(Class<?> componentClass) {
|
||||||
|
if (instanceMap.containsKey(componentClass)) {
|
||||||
|
return instanceMap.get(componentClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> concreteClass = injectableScanner.getInstantiableComponents().get(componentClass);
|
||||||
|
if (concreteClass == null) {
|
||||||
|
throw new IllegalStateException("No concrete implementation found for: " + componentClass.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Object instance = createInstance(concreteClass);
|
||||||
|
registerInstance(concreteClass, instance);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of the given class by selecting the best constructor.
|
||||||
|
*
|
||||||
|
* @param concreteClass The class to instantiate.
|
||||||
|
* @return A new instance of the class.
|
||||||
|
*/
|
||||||
|
private Object createInstance(Class<?> concreteClass) {
|
||||||
|
try {
|
||||||
|
Constructor<?> bestConstructor = findBestConstructor(concreteClass);
|
||||||
|
if (bestConstructor == null) {
|
||||||
|
throw new IllegalStateException("No suitable constructor found for: " + concreteClass.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?>[] paramTypes = bestConstructor.getParameterTypes();
|
||||||
|
Object[] params = Arrays.stream(paramTypes)
|
||||||
|
.map(this::getInstance) // Recursively resolve dependencies
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
return bestConstructor.newInstance(params);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to instantiate " + concreteClass.getName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the best constructor for a given class by checking which one has resolvable parameters.
|
||||||
|
*
|
||||||
|
* @param concreteClass The class to analyze.
|
||||||
|
* @return The best constructor or {@code null} if none can be used.
|
||||||
|
*/
|
||||||
|
private Constructor<?> findBestConstructor(Class<?> concreteClass) {
|
||||||
|
Constructor<?>[] constructors = concreteClass.getConstructors();
|
||||||
|
|
||||||
|
// Prefer constructors with all parameters resolvable
|
||||||
|
for (Constructor<?> constructor : constructors) {
|
||||||
|
if (canUseConstructor(constructor)) {
|
||||||
|
return constructor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a given constructor can be used by ensuring all parameters are known.
|
||||||
|
*
|
||||||
|
* @param constructor The constructor to check.
|
||||||
|
* @return {@code true} if all parameters can be resolved, otherwise {@code false}.
|
||||||
|
*/
|
||||||
|
private boolean canUseConstructor(Constructor<?> constructor) {
|
||||||
|
return Arrays.stream(constructor.getParameterTypes())
|
||||||
|
.allMatch(injectableScanner.getInstantiableComponents()::containsKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers an instance in the map for all unique interfaces and superclasses it implements.
|
||||||
|
*
|
||||||
|
* @param concreteClass The concrete component class.
|
||||||
|
* @param instance The instance to store.
|
||||||
|
*/
|
||||||
|
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)) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,245 @@
|
|||||||
|
package de.neitzel.injectfx;
|
||||||
|
|
||||||
|
import de.neitzel.injectfx.annotation.FXMLComponent;
|
||||||
|
import org.reflections.Reflections;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InjectableComponents scans a package for classes annotated with {@link FXMLComponent}.
|
||||||
|
* It determines which components can be instantiated and manages type mappings for dependency injection.
|
||||||
|
*/
|
||||||
|
public class InjectableComponentScanner {
|
||||||
|
|
||||||
|
/** Set of all detected @FXMLComponent classes within the given package. */
|
||||||
|
private final Set<Class<?>> fxmlComponents = new HashSet<>();
|
||||||
|
|
||||||
|
/** Set of all superclasses and interfaces that are implemented by multiple @FXMLComponent classes. */
|
||||||
|
private final Set<Class<?>> notUniqueTypes = new HashSet<>();
|
||||||
|
|
||||||
|
/** Map of unique superclasses/interfaces to a single corresponding @FXMLComponent class. */
|
||||||
|
private final Map<Class<?>, Class<?>> uniqueTypeToComponent = new HashMap<>();
|
||||||
|
|
||||||
|
/** Map of instantiable @FXMLComponent classes and their corresponding interfaces/superclasses (if unique). */
|
||||||
|
private final Map<Class<?>, Class<?>> instantiableComponents = new HashMap<>();
|
||||||
|
|
||||||
|
/** List of error messages generated when resolving component instantiability. */
|
||||||
|
private final List<String> errors = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an InjectableComponents instance and scans the given package for @FXMLComponent classes.
|
||||||
|
*
|
||||||
|
* @param basePackage The base package to scan for @FXMLComponent classes.
|
||||||
|
*/
|
||||||
|
public InjectableComponentScanner(String basePackage) {
|
||||||
|
scanForComponents(basePackage);
|
||||||
|
analyzeComponentTypes();
|
||||||
|
resolveInstantiableComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans the specified package for classes annotated with @FXMLComponent.
|
||||||
|
*
|
||||||
|
* @param basePackage The package to scan.
|
||||||
|
*/
|
||||||
|
private void scanForComponents(String basePackage) {
|
||||||
|
Reflections reflections = new Reflections(basePackage);
|
||||||
|
fxmlComponents.addAll(reflections.getTypesAnnotatedWith(FXMLComponent.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyzes the collected @FXMLComponent classes to determine unique and non-unique superclasses/interfaces.
|
||||||
|
*/
|
||||||
|
private void analyzeComponentTypes() {
|
||||||
|
Map<Class<?>, List<Class<?>>> superTypesMap = new HashMap<>();
|
||||||
|
|
||||||
|
for (Class<?> component : fxmlComponents) {
|
||||||
|
Set<Class<?>> allSuperTypes = getAllSuperTypes(component);
|
||||||
|
|
||||||
|
for (Class<?> superType : allSuperTypes) {
|
||||||
|
superTypesMap.computeIfAbsent(superType, k -> new ArrayList<>()).add(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<Class<?>, List<Class<?>>> entry : superTypesMap.entrySet()) {
|
||||||
|
Class<?> superType = entry.getKey();
|
||||||
|
List<Class<?>> implementations = entry.getValue();
|
||||||
|
|
||||||
|
if (implementations.size() > 1) {
|
||||||
|
notUniqueTypes.add(superType);
|
||||||
|
} else {
|
||||||
|
uniqueTypeToComponent.put(superType, implementations.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines which @FXMLComponent classes can be instantiated based on known dependencies.
|
||||||
|
* It registers valid components and collects errors for components that cannot be instantiated.
|
||||||
|
*/
|
||||||
|
private void resolveInstantiableComponents() {
|
||||||
|
Set<Class<?>> resolved = new HashSet<>();
|
||||||
|
Set<Class<?>> unresolved = new HashSet<>(fxmlComponents);
|
||||||
|
Map<Class<?>, Class<?>> knownTypes = new HashMap<>();
|
||||||
|
|
||||||
|
boolean progress;
|
||||||
|
do {
|
||||||
|
progress = false;
|
||||||
|
Iterator<Class<?>> iterator = unresolved.iterator();
|
||||||
|
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Class<?> component = iterator.next();
|
||||||
|
if (canInstantiate(component, knownTypes.keySet())) {
|
||||||
|
resolved.add(component);
|
||||||
|
registerComponentWithSuperTypes(component, knownTypes);
|
||||||
|
iterator.remove();
|
||||||
|
progress = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (progress);
|
||||||
|
|
||||||
|
instantiableComponents.putAll(knownTypes);
|
||||||
|
|
||||||
|
if (!unresolved.isEmpty()) {
|
||||||
|
collectInstantiationErrors(unresolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a component along with its unique superclasses and interfaces in the known types map.
|
||||||
|
*
|
||||||
|
* @param component The component class.
|
||||||
|
* @param knownTypes The map of known instantiable types.
|
||||||
|
*/
|
||||||
|
private void registerComponentWithSuperTypes(Class<?> component, Map<Class<?>, Class<?>> knownTypes) {
|
||||||
|
knownTypes.put(component, component);
|
||||||
|
|
||||||
|
for (Class<?> superType : getAllSuperTypes(component)) {
|
||||||
|
if (!notUniqueTypes.contains(superType)) {
|
||||||
|
knownTypes.put(superType, component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a given @FXMLComponent class can be instantiated based on known dependencies.
|
||||||
|
*
|
||||||
|
* @param component The component class to check.
|
||||||
|
* @param knownTypes Set of known instantiable types.
|
||||||
|
* @return {@code true} if the component can be instantiated, otherwise {@code false}.
|
||||||
|
*/
|
||||||
|
private boolean canInstantiate(Class<?> component, Set<Class<?>> knownTypes) {
|
||||||
|
for (Constructor<?> constructor : component.getConstructors()) {
|
||||||
|
Class<?>[] paramTypes = constructor.getParameterTypes();
|
||||||
|
if (paramTypes.length == 0 || Arrays.stream(paramTypes).allMatch(knownTypes::contains)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects error messages for components that cannot be instantiated and adds them to the error list.
|
||||||
|
*
|
||||||
|
* @param unresolved Set of components that could not be instantiated.
|
||||||
|
*/
|
||||||
|
private void collectInstantiationErrors(Set<Class<?>> unresolved) {
|
||||||
|
for (Class<?> component : unresolved) {
|
||||||
|
StringBuilder errorMsg = new StringBuilder("Component cannot be instantiated: " + component.getName());
|
||||||
|
|
||||||
|
boolean possibleWithUniqueTypes = false;
|
||||||
|
|
||||||
|
for (Constructor<?> constructor : component.getConstructors()) {
|
||||||
|
Class<?>[] paramTypes = constructor.getParameterTypes();
|
||||||
|
List<Class<?>> problematicTypes = Arrays.stream(paramTypes)
|
||||||
|
.filter(t -> !instantiableComponents.containsKey(t) && !notUniqueTypes.contains(t))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (problematicTypes.isEmpty()) {
|
||||||
|
possibleWithUniqueTypes = true;
|
||||||
|
} else {
|
||||||
|
errorMsg.append("\n ➤ Requires unknown types: ").append(problematicTypes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (possibleWithUniqueTypes) {
|
||||||
|
errorMsg.append("\n ➤ Could be instantiated if multiple implementations of ")
|
||||||
|
.append("interfaces/superclasses were resolved uniquely: ")
|
||||||
|
.append(getConflictingTypes(component));
|
||||||
|
}
|
||||||
|
|
||||||
|
errors.add(errorMsg.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a comma-separated list of conflicting types that prevent instantiation.
|
||||||
|
*
|
||||||
|
* @param component The component class.
|
||||||
|
* @return A string listing the conflicting types.
|
||||||
|
*/
|
||||||
|
private String getConflictingTypes(Class<?> component) {
|
||||||
|
return Arrays.stream(component.getConstructors())
|
||||||
|
.flatMap(constructor -> Arrays.stream(constructor.getParameterTypes()))
|
||||||
|
.filter(notUniqueTypes::contains)
|
||||||
|
.map(Class::getName)
|
||||||
|
.collect(Collectors.joining(", "));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all superclasses and interfaces of a given class.
|
||||||
|
*
|
||||||
|
* @param clazz The class to analyze.
|
||||||
|
* @return A set of all superclasses and interfaces of the given class.
|
||||||
|
*/
|
||||||
|
private Set<Class<?>> getAllSuperTypes(Class<?> clazz) {
|
||||||
|
Set<Class<?>> result = new HashSet<>();
|
||||||
|
Class<?> superClass = clazz.getSuperclass();
|
||||||
|
|
||||||
|
while (superClass != null && superClass != Object.class) {
|
||||||
|
result.add(superClass);
|
||||||
|
superClass = superClass.getSuperclass();
|
||||||
|
}
|
||||||
|
|
||||||
|
result.addAll(Arrays.asList(clazz.getInterfaces()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map of instantiable @FXMLComponent classes and their associated interfaces/superclasses.
|
||||||
|
*
|
||||||
|
* @return A map of instantiable components.
|
||||||
|
*/
|
||||||
|
public Map<Class<?>, Class<?>> getInstantiableComponents() {
|
||||||
|
return instantiableComponents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the set of non-unique types (superclasses/interfaces with multiple implementations).
|
||||||
|
*
|
||||||
|
* @return A set of non-unique types.
|
||||||
|
*/
|
||||||
|
public Set<Class<?>> getNotUniqueTypes() {
|
||||||
|
return notUniqueTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the map of unique types to corresponding @FXMLComponent implementations.
|
||||||
|
*
|
||||||
|
* @return A map of unique types.
|
||||||
|
*/
|
||||||
|
public Map<Class<?>, Class<?>> getUniqueTypeToComponent() {
|
||||||
|
return uniqueTypeToComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of errors encountered during instantiation resolution.
|
||||||
|
*
|
||||||
|
* @return A list of error messages.
|
||||||
|
*/
|
||||||
|
public List<String> getErrors() {
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,15 +3,16 @@ package de.neitzel.injectfx;
|
|||||||
import javafx.util.Callback;
|
import javafx.util.Callback;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class InjectingControllerFactory implements Callback<Class<?>, Object> {
|
public class InjectingControllerFactory implements Callback<Class<?>, Object> {
|
||||||
private final Map<Class<?>, Object> parameterMap;
|
private final Map<Class<?>, Object> parameterMap = new HashMap<>();
|
||||||
|
|
||||||
public InjectingControllerFactory(Map<Class<?>, Object> parameterMap) {
|
public void addInjectingData(Class<?> clazz, Object object) {
|
||||||
this.parameterMap = parameterMap;
|
parameterMap.put(clazz, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -0,0 +1,44 @@
|
|||||||
|
package de.neitzel.injectfx;
|
||||||
|
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class InjectingFXMLLoader {
|
||||||
|
|
||||||
|
private final InjectingControllerFactory controllerFactory;
|
||||||
|
|
||||||
|
public InjectingFXMLLoader() {
|
||||||
|
controllerFactory = new InjectingControllerFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InjectingFXMLLoader(InjectingControllerFactory controllerFactory) {
|
||||||
|
this.controllerFactory = controllerFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InjectingFXMLLoader(String packageName) {
|
||||||
|
controllerFactory = new InjectingControllerFactory();
|
||||||
|
InjectableComponentScanner scanner = new InjectableComponentScanner(packageName);
|
||||||
|
FXMLComponentInstances instances = new FXMLComponentInstances(scanner);
|
||||||
|
addInjectingData(instances);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addInjectingData(FXMLComponentInstances instances) {
|
||||||
|
for (var clazz: instances.getInstanceMap().keySet()) {
|
||||||
|
addInjectingData(clazz, instances.getInstance(clazz));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addInjectingData(Class<?> clazz, Object object) {
|
||||||
|
controllerFactory.addInjectingData(clazz, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T load(URL url) throws IOException {
|
||||||
|
FXMLLoader loader = new FXMLLoader(url);
|
||||||
|
loader.setControllerFactory(controllerFactory);
|
||||||
|
return loader.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package de.neitzel.injectfx.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
public @interface FXMLComponent {
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package de.neitzel.injectfx.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
public @interface FXMLConfig {
|
||||||
|
String value() default "";
|
||||||
|
}
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
package de.neitzel.injectfx;
|
||||||
|
|
||||||
|
import de.neitzel.injectfx.testcomponents.test1ok.SuperClass;
|
||||||
|
import de.neitzel.injectfx.testcomponents.test1ok.TestComponent1_1;
|
||||||
|
import de.neitzel.injectfx.testcomponents.test1ok.TestInterface1_1;
|
||||||
|
import de.neitzel.injectfx.testcomponents.test1ok.TestInterface1_2;
|
||||||
|
import de.neitzel.injectfx.testcomponents.test1ok.sub.TestComponent1_2;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class InjectableComponentScannerTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests loading of multiple FXMLComponents including sub packages.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testLoadComponents() {
|
||||||
|
InjectableComponentScanner scanner = new InjectableComponentScanner("de.neitzel.injectfx.testcomponents.test1ok");
|
||||||
|
var instantiableComponents = scanner.getInstantiableComponents();
|
||||||
|
var nonUniqueTypes = scanner.getNotUniqueTypes();
|
||||||
|
|
||||||
|
assertAll(
|
||||||
|
() -> assertNotNull(instantiableComponents),
|
||||||
|
() -> assertEquals(3, instantiableComponents.size()),
|
||||||
|
() -> assertTrue(instantiableComponents.containsKey(TestComponent1_1.class)),
|
||||||
|
() -> assertTrue(instantiableComponents.containsKey(TestComponent1_2.class)),
|
||||||
|
() -> assertTrue(instantiableComponents.containsKey(TestInterface1_1.class)),
|
||||||
|
() -> assertTrue(nonUniqueTypes.contains(SuperClass.class)),
|
||||||
|
() -> assertTrue(nonUniqueTypes.contains(TestInterface1_2.class)),
|
||||||
|
() -> assertTrue(scanner.getErrors().isEmpty())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests failing to load a FXMLComponent which has an unknwown parameter.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testComponentsFailWithUnknownParameters() {
|
||||||
|
InjectableComponentScanner scanner = new InjectableComponentScanner("de.neitzel.injectfx.testcomponents.test2fail");
|
||||||
|
var instantiableComponents = scanner.getInstantiableComponents();
|
||||||
|
var nonUniqueTypes = scanner.getNotUniqueTypes();
|
||||||
|
|
||||||
|
assertAll(
|
||||||
|
() -> assertNotNull(instantiableComponents),
|
||||||
|
() -> assertEquals(0, instantiableComponents.size()),
|
||||||
|
() -> assertFalse(scanner.getErrors().isEmpty())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
package de.neitzel.injectfx.testcomponents.test1ok;
|
||||||
|
|
||||||
|
public class SuperClass {
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package de.neitzel.injectfx.testcomponents.test1ok;
|
||||||
|
|
||||||
|
import de.neitzel.injectfx.annotation.FXMLComponent;
|
||||||
|
|
||||||
|
@FXMLComponent
|
||||||
|
public class TestComponent1_1 extends SuperClass implements TestInterface1_2 {
|
||||||
|
public TestComponent1_1() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
package de.neitzel.injectfx.testcomponents.test1ok;
|
||||||
|
|
||||||
|
public interface TestInterface1_1 {
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
package de.neitzel.injectfx.testcomponents.test1ok;
|
||||||
|
|
||||||
|
public interface TestInterface1_2 {
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package de.neitzel.injectfx.testcomponents.test1ok.sub;
|
||||||
|
|
||||||
|
import de.neitzel.injectfx.annotation.FXMLComponent;
|
||||||
|
import de.neitzel.injectfx.testcomponents.test1ok.SuperClass;
|
||||||
|
import de.neitzel.injectfx.testcomponents.test1ok.TestInterface1_1;
|
||||||
|
import de.neitzel.injectfx.testcomponents.test1ok.TestInterface1_2;
|
||||||
|
|
||||||
|
@FXMLComponent
|
||||||
|
public class TestComponent1_2 extends SuperClass implements TestInterface1_1, TestInterface1_2 {
|
||||||
|
public TestComponent1_2() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package de.neitzel.injectfx.testcomponents.test2fail;
|
||||||
|
|
||||||
|
import de.neitzel.injectfx.annotation.FXMLComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TestComponent1 that should fail.
|
||||||
|
*/
|
||||||
|
@FXMLComponent
|
||||||
|
public class TestComponent2_1 {
|
||||||
|
public TestComponent2_1(String test) {
|
||||||
|
}
|
||||||
|
}
|
||||||
2
pom.xml
2
pom.xml
@ -25,6 +25,8 @@
|
|||||||
<junit.version>5.10.2</junit.version>
|
<junit.version>5.10.2</junit.version>
|
||||||
<lombok.version>1.18.32</lombok.version>
|
<lombok.version>1.18.32</lombok.version>
|
||||||
<mockito.version>5.12.0</mockito.version>
|
<mockito.version>5.12.0</mockito.version>
|
||||||
|
<reflections.version>0.10.2</reflections.version>
|
||||||
|
<slf4j.version></slf4j.version>
|
||||||
|
|
||||||
<!-- Plugin dependencies -->
|
<!-- Plugin dependencies -->
|
||||||
<codehaus.version.plugin>2.16.2</codehaus.version.plugin>
|
<codehaus.version.plugin>2.16.2</codehaus.version.plugin>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user