Merge pull request #5 from kneitzel/feature/first-neitzellib-content
Feature/first neitzellib content
This commit is contained in:
commit
5798955c65
12
README.md
12
README.md
@ -2,11 +2,9 @@
|
|||||||
|
|
||||||
## A Java Utilities Library
|
## A Java Utilities Library
|
||||||
|
|
||||||
This repository is **not** a production-ready library, but rather a loose collection of small helpers, utilities, and ideas that might come in handy for Java developers.
|
This repository is **not** a production-ready library, but rather a personal collection of small helpers, utilities, and ideas that I find useful or interesting as a Java developer.
|
||||||
|
|
||||||
The code in this project is intended as a **starting point** or **inspiration** for your own solutions. It is often easier to copy and adapt individual classes as needed instead of using the whole library as a dependency.
|
It serves primarily as a **knowledge base** and **inspiration pool** for my own development work. You're welcome to explore, copy, modify, or improve whatever you find helpful.
|
||||||
|
|
||||||
Feel free to explore, copy, modify, and improve whatever you find useful.
|
|
||||||
|
|
||||||
> ⚠️ Use at your own discretion — no guarantees of stability, backwards compatibility, or completeness.
|
> ⚠️ Use at your own discretion — no guarantees of stability, backwards compatibility, or completeness.
|
||||||
|
|
||||||
@ -17,6 +15,12 @@ This is free and unencumbered software released into the public domain. Please s
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Note on Content
|
||||||
|
|
||||||
|
The content of this repository — including source code, documentation, and examples — was partially created with the assistance of ChatGPT (an AI model by OpenAI). The generated content has been reviewed, edited, and integrated into the overall context by me.
|
||||||
|
|
||||||
|
Responsibility for all content lies entirely with me. Any generated content is subject to [OpenAI’s Terms of Use](https://openai.com/policies/terms-of-use) and is — where possible — either in the public domain or usable under a license compatible with this project's license.
|
||||||
|
|
||||||
## Components
|
## Components
|
||||||
|
|
||||||
### core
|
### core
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
package de.neitzel.core.commandline;
|
package de.neitzel.core.commandline;
|
||||||
|
|
||||||
import lombok.*;
|
import lombok.Builder;
|
||||||
import lombok.extern.log4j.Log4j;
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.Singular;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|||||||
@ -48,7 +48,7 @@ public class Configuration {
|
|||||||
*
|
*
|
||||||
* @param properties the Properties object containing configuration key-value pairs
|
* @param properties the Properties object containing configuration key-value pairs
|
||||||
*/
|
*/
|
||||||
public Configuration(Properties properties) {
|
public Configuration(final Properties properties) {
|
||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,20 @@
|
|||||||
package de.neitzel.core.image;
|
package de.neitzel.core.image;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for scaling images while maintaining the aspect ratio.
|
* Utility class for scaling images while maintaining the aspect ratio.
|
||||||
* Provides methods to create scaled images either as byte arrays or InputStreams.
|
* Provides methods to create scaled images either as byte arrays or InputStreams.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class ImageScaler {
|
public class ImageScaler {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -148,7 +152,8 @@ public class ImageScaler {
|
|||||||
ImageIO.write(scaledImage, TARGET_FORMAT, stream);
|
ImageIO.write(scaledImage, TARGET_FORMAT, stream);
|
||||||
return stream.toByteArray();
|
return stream.toByteArray();
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (IOException ex) {
|
||||||
|
log.error("IOException while scaling image.", ex);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,141 @@
|
|||||||
|
package de.neitzel.core.inject;
|
||||||
|
|
||||||
|
import de.neitzel.core.inject.annotation.Config;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the context of the application and serves as the foundation of a dependency
|
||||||
|
* injection framework.
|
||||||
|
*
|
||||||
|
* The {@code ApplicationContext} is responsible for scanning, instantiating, and managing
|
||||||
|
* the lifecycle of components. Components are identified through the specified base package
|
||||||
|
* (or derived from a configuration class) and instantiated based on their dependency
|
||||||
|
* requirements and scope. The framework supports constructor-based dependency injection
|
||||||
|
* for resolving and creating components.
|
||||||
|
*/
|
||||||
|
public class ApplicationContext {
|
||||||
|
private final Map<Class<?>, ComponentData> components;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen ApplicationContext auf Basis eines expliziten Package-Namens.
|
||||||
|
*
|
||||||
|
* @param basePackage das Basis-Package, das nach Komponenten durchsucht werden soll
|
||||||
|
*/
|
||||||
|
public ApplicationContext(String basePackage) {
|
||||||
|
ComponentScanner scanner = new ComponentScanner(basePackage);
|
||||||
|
this.components = scanner.getInstantiableComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt einen ApplicationContext auf Basis einer Startklasse.
|
||||||
|
* Das zu scannende Package wird aus der @Config Annotation gelesen (basePackage).
|
||||||
|
* Wenn keine Annotation vorhanden ist oder kein basePackage gesetzt wurde, wird
|
||||||
|
* das Package der übergebenen Klasse verwendet.
|
||||||
|
*
|
||||||
|
* @param configClass Klasse mit oder ohne @Config Annotation
|
||||||
|
*/
|
||||||
|
public ApplicationContext(Class<?> configClass) {
|
||||||
|
String basePackage;
|
||||||
|
Config config = configClass.getAnnotation(Config.class);
|
||||||
|
if (config != null && !config.basePackage().isEmpty()) {
|
||||||
|
basePackage = config.basePackage();
|
||||||
|
} else {
|
||||||
|
basePackage = configClass.getPackageName();
|
||||||
|
}
|
||||||
|
ComponentScanner scanner = new ComponentScanner(basePackage);
|
||||||
|
this.components = scanner.getInstantiableComponents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves an instance of the specified component type from the application context.
|
||||||
|
*
|
||||||
|
* This method uses dependency injection to construct the component if it is not already
|
||||||
|
* a singleton instance. The component's type and scope are determined by the framework,
|
||||||
|
* and the appropriate initialization and lifecycle management are performed.
|
||||||
|
*
|
||||||
|
* @param <T> the type of the component to retrieve
|
||||||
|
* @param type the {@code Class} object representing the type of the component
|
||||||
|
* @return an instance of the requested component type
|
||||||
|
* @throws IllegalArgumentException if no component is found for the specified type
|
||||||
|
* @throws IllegalStateException if no suitable constructor is found for the component
|
||||||
|
* @throws RuntimeException if any error occurs during instantiation
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T getComponent(Class<? extends T> type) {
|
||||||
|
ComponentData data = components.get(type);
|
||||||
|
if (data == null) {
|
||||||
|
throw new IllegalArgumentException("No component found for type: " + type.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Scope scope = data.getScope();
|
||||||
|
Object instance = data.getInstance();
|
||||||
|
|
||||||
|
if (scope == Scope.SINGLETON && instance != null) {
|
||||||
|
return (T) instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Constructor<?>[] constructors = data.getType().getConstructors();
|
||||||
|
for (Constructor<?> constructor : constructors) {
|
||||||
|
if (canBeInstantiated(constructor, components)) {
|
||||||
|
Class<?>[] paramTypes = constructor.getParameterTypes();
|
||||||
|
Object[] parameters = new Object[paramTypes.length];
|
||||||
|
for (int i = 0; i < paramTypes.length; i++) {
|
||||||
|
parameters[i] = getComponent(paramTypes[i]);
|
||||||
|
}
|
||||||
|
instance = constructor.newInstance(parameters);
|
||||||
|
if (scope == Scope.SINGLETON) {
|
||||||
|
data.setInstance(instance);
|
||||||
|
}
|
||||||
|
return (T) instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalStateException("Kein passender Konstruktor gefunden für " + type.getName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Fehler beim Erstellen der Instanz für " + type.getName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a given constructor can be instantiated using the provided parameter map.
|
||||||
|
*
|
||||||
|
* The method evaluates whether every parameter type required by the constructor
|
||||||
|
* is available in the given parameter map. If all parameter types are present,
|
||||||
|
* the constructor is considered instantiable.
|
||||||
|
*
|
||||||
|
* @param constructor the constructor to check for instantiation feasibility
|
||||||
|
* @param parameterMap a map containing available parameter types and their associated component data
|
||||||
|
* @return true if all parameter types required by the constructor are contained in the parameter map, false otherwise
|
||||||
|
*/
|
||||||
|
private boolean canBeInstantiated(Constructor<?> constructor, Map<Class<?>, ComponentData> parameterMap) {
|
||||||
|
return Stream.of(constructor.getParameterTypes()).allMatch(parameterMap::containsKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a singleton instance of a specific type into the application context.
|
||||||
|
*
|
||||||
|
* This method allows manual addition of singleton components to the context by
|
||||||
|
* providing a concrete instance of the required type. If the type is already mapped
|
||||||
|
* to a different instance or type within the context, an {@link IllegalStateException}
|
||||||
|
* is thrown to prevent replacement of an existing singleton.
|
||||||
|
*
|
||||||
|
* @param <T> the type of the component to register
|
||||||
|
* @param type the class type of the component being added
|
||||||
|
* @param instance the singleton instance of the component to register
|
||||||
|
* @throws IllegalStateException if the type is already registered with a different instance
|
||||||
|
*/
|
||||||
|
public <T> void addSingleton(Class<? extends T> type, T instance) {
|
||||||
|
if (components.get(type) == null ||
|
||||||
|
!Objects.equals(components.get(type).getType().getName(), type.getName())) {
|
||||||
|
|
||||||
|
components.put(type, new ComponentData(type, instance));
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Type cannot be replaced: " + type.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
89
core/src/main/java/de/neitzel/core/inject/ComponentData.java
Normal file
89
core/src/main/java/de/neitzel/core/inject/ComponentData.java
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package de.neitzel.core.inject;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a component in a dependency injection framework.
|
||||||
|
*
|
||||||
|
* A component is a unit of functionality that is managed by the framework, allowing
|
||||||
|
* for controlled instantiation and lifecycle management. The component is associated
|
||||||
|
* with a specific type and a scope. The scope determines whether the component is
|
||||||
|
* instantiated as a singleton or as a prototype.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class ComponentData {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the type of the component being managed by the dependency injection framework.
|
||||||
|
*
|
||||||
|
* This variable holds the class object corresponding to the specific type of the component.
|
||||||
|
* It is used to identify and instantiate the component during the dependency injection process.
|
||||||
|
* The type is immutable and is specified when the component is created.
|
||||||
|
*/
|
||||||
|
private final Class<?> type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the lifecycle and instantiation rules for the associated component.
|
||||||
|
*
|
||||||
|
* The {@code Scope} determines whether the component is created as a singleton
|
||||||
|
* (a single shared instance) or as a prototype (a new instance for each request).
|
||||||
|
*
|
||||||
|
* This variable is immutable and represents the specific {@code Scope} assigned
|
||||||
|
* to the component, influencing its behavior within the dependency injection framework.
|
||||||
|
*/
|
||||||
|
private final Scope scope;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores the instantiated object associated with the component.
|
||||||
|
*
|
||||||
|
* This field holds the actual instance of the component's type within the dependency
|
||||||
|
* injection framework. For components with a {@code SINGLETON} scope, this field is
|
||||||
|
* set only once and shared across the entire application. For components with a
|
||||||
|
* {@code PROTOTYPE} scope, this field may be null since a new instance is created
|
||||||
|
* upon each request.
|
||||||
|
*/
|
||||||
|
private Object instance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new Component instance with the specified type and scope.
|
||||||
|
*
|
||||||
|
* @param type the class type of the component
|
||||||
|
* @param scope the scope of the component, which determines its lifecycle
|
||||||
|
*/
|
||||||
|
public ComponentData(Class<?> type, Scope scope) {
|
||||||
|
this.type = type;
|
||||||
|
this.scope = scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new ComponentData instance with the specified type and an initial instance.
|
||||||
|
*
|
||||||
|
* This can be used to add Singletons manually without scanning for them.
|
||||||
|
*
|
||||||
|
* @param type the class type of the component
|
||||||
|
* @param instance the initial instance of the component
|
||||||
|
*/
|
||||||
|
public ComponentData(Class<?> type, Object instance) {
|
||||||
|
this.type = type;
|
||||||
|
this.scope = Scope.SINGLETON;
|
||||||
|
this.instance = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the instance for this component if it is configured with a {@code SINGLETON} scope.
|
||||||
|
* This method ensures that the instance is only set once for a {@code SINGLETON} component.
|
||||||
|
* If an instance has already been set, it throws an {@link IllegalStateException}.
|
||||||
|
*
|
||||||
|
* @param instance the object instance to associate with this component
|
||||||
|
* @throws IllegalStateException if an instance has already been set for this {@code SINGLETON} component
|
||||||
|
*/
|
||||||
|
public void setInstance(Object instance) {
|
||||||
|
if (scope != Scope.SINGLETON) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.instance != null) {
|
||||||
|
throw new IllegalStateException("Instance already set for singleton: " + type.getName());
|
||||||
|
}
|
||||||
|
this.instance = instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package de.neitzel.core.inject;
|
package de.neitzel.core.inject;
|
||||||
|
|
||||||
import de.neitzel.core.inject.annotation.Component;
|
import de.neitzel.core.inject.annotation.Component;
|
||||||
|
import de.neitzel.core.inject.annotation.Inject;
|
||||||
import org.reflections.Reflections;
|
import org.reflections.Reflections;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
@ -13,7 +14,7 @@ import java.util.stream.Collectors;
|
|||||||
* The resulting analysis identifies unique and shared interfaces/superclasses as well as
|
* The resulting analysis identifies unique and shared interfaces/superclasses as well as
|
||||||
* potentially instantiable components, collecting relevant errors if instantiation is not feasible.
|
* potentially instantiable components, collecting relevant errors if instantiation is not feasible.
|
||||||
*/
|
*/
|
||||||
public class InjectableComponentScanner {
|
public class ComponentScanner {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A set that stores classes annotated with {@code @Component}, representing
|
* A set that stores classes annotated with {@code @Component}, representing
|
||||||
@ -30,7 +31,7 @@ public class InjectableComponentScanner {
|
|||||||
* This field is immutable, ensuring thread safety and consistent state
|
* This field is immutable, ensuring thread safety and consistent state
|
||||||
* throughout the lifetime of the {@code InjectableComponentScanner}.
|
* throughout the lifetime of the {@code InjectableComponentScanner}.
|
||||||
*/
|
*/
|
||||||
private final Set<Class<?>> fxmlComponents = new HashSet<>();
|
private final Set<Class<?>> components = new HashSet<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A set of component types that are not uniquely associated with a single implementation.
|
* A set of component types that are not uniquely associated with a single implementation.
|
||||||
@ -75,7 +76,7 @@ public class InjectableComponentScanner {
|
|||||||
* The resolution process checks if a component can be instantiated based on its
|
* The resolution process checks if a component can be instantiated based on its
|
||||||
* constructor dependencies being resolvable using known types or other registered components.
|
* constructor dependencies being resolvable using known types or other registered components.
|
||||||
*/
|
*/
|
||||||
private final Map<Class<?>, Class<?>> instantiableComponents = new HashMap<>();
|
private final Map<Class<?>, ComponentData> instantiableComponents = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of error messages encountered during the scanning and analysis
|
* A list of error messages encountered during the scanning and analysis
|
||||||
@ -96,7 +97,7 @@ public class InjectableComponentScanner {
|
|||||||
*
|
*
|
||||||
* @param basePackage the base package to scan for injectable components
|
* @param basePackage the base package to scan for injectable components
|
||||||
*/
|
*/
|
||||||
public InjectableComponentScanner(String basePackage) {
|
public ComponentScanner(String basePackage) {
|
||||||
scanForComponents(basePackage);
|
scanForComponents(basePackage);
|
||||||
analyzeComponentTypes();
|
analyzeComponentTypes();
|
||||||
resolveInstantiableComponents();
|
resolveInstantiableComponents();
|
||||||
@ -110,7 +111,7 @@ public class InjectableComponentScanner {
|
|||||||
*/
|
*/
|
||||||
private void scanForComponents(String basePackage) {
|
private void scanForComponents(String basePackage) {
|
||||||
Reflections reflections = new Reflections(basePackage);
|
Reflections reflections = new Reflections(basePackage);
|
||||||
fxmlComponents.addAll(reflections.getTypesAnnotatedWith(Component.class));
|
components.addAll(reflections.getTypesAnnotatedWith(Component.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -135,7 +136,7 @@ public class InjectableComponentScanner {
|
|||||||
private void analyzeComponentTypes() {
|
private void analyzeComponentTypes() {
|
||||||
Map<Class<?>, List<Class<?>>> superTypesMap = new HashMap<>();
|
Map<Class<?>, List<Class<?>>> superTypesMap = new HashMap<>();
|
||||||
|
|
||||||
for (Class<?> component : fxmlComponents) {
|
for (Class<?> component : components) {
|
||||||
Set<Class<?>> allSuperTypes = getAllSuperTypes(component);
|
Set<Class<?>> allSuperTypes = getAllSuperTypes(component);
|
||||||
|
|
||||||
for (Class<?> superType : allSuperTypes) {
|
for (Class<?> superType : allSuperTypes) {
|
||||||
@ -156,44 +157,48 @@ public class InjectableComponentScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves components from the set of scanned classes that can be instantiated based on their constructors
|
* Resolves and identifies instantiable components from a set of scanned components.
|
||||||
* and existing known types. The method iteratively processes classes to identify those whose dependencies
|
* This process determines which components can be instantiated based on their dependencies
|
||||||
* can be satisfied, marking them as resolved and registering them alongside their supertypes.
|
* and class relationships, while tracking unresolved types and potential conflicts.
|
||||||
*
|
*
|
||||||
* If progress is made during a pass (i.e., a component is resolved), the process continues until no more
|
* The resolution process involves:
|
||||||
* components can be resolved. Any components that remain unresolved are recorded for further inspection.
|
* 1. Iteratively determining which components can be instantiated using the known types
|
||||||
|
* map. A component is resolvable if its dependencies can be satisfied by the current
|
||||||
|
* set of known types.
|
||||||
|
* 2. Registering resolvable components and their superclasses/interfaces into the known
|
||||||
|
* types map for future iterations.
|
||||||
|
* 3. Removing successfully resolved components from the unresolved set.
|
||||||
|
* 4. Repeating the process until no further components can be resolved in a given iteration.
|
||||||
*
|
*
|
||||||
* The resolved components are stored in a map where each type and its supertypes are associated
|
* At the end of the resolution process:
|
||||||
* with the component class itself. This map allows for subsequent lookups when verifying dependency satisfiability.
|
* - Resolvable components are added to the `instantiableComponents` map, which maps types
|
||||||
|
* to their corresponding instantiable implementations.
|
||||||
|
* - Unresolved components are identified, and error details are collected to highlight
|
||||||
|
* dependencies or conflicts preventing their instantiation.
|
||||||
*
|
*
|
||||||
* If unresolved components remain after the resolution process, detailed instantiation errors are collected
|
* If errors are encountered due to unresolved components, they are logged for further analysis.
|
||||||
* for debugging or logging purposes.
|
|
||||||
*
|
|
||||||
* This method depends on the following utility methods:
|
|
||||||
* - {@link #canInstantiate(Class, Set)}: Determines if a component can be instantiated based on existing known types.
|
|
||||||
* - {@link #registerComponentWithSuperTypes(Class, Map)}: Registers a component and its supertypes in the known types map.
|
|
||||||
* - {@link #collectInstantiationErrors(Set)}: Collects detailed error messages for unresolved components.
|
|
||||||
*/
|
*/
|
||||||
private void resolveInstantiableComponents() {
|
private void resolveInstantiableComponents() {
|
||||||
Set<Class<?>> resolved = new HashSet<>();
|
Set<Class<?>> resolved = new HashSet<>();
|
||||||
Set<Class<?>> unresolved = new HashSet<>(fxmlComponents);
|
Set<Class<?>> unresolved = new HashSet<>(components);
|
||||||
Map<Class<?>, Class<?>> knownTypes = new HashMap<>();
|
Map<Class<?>, ComponentData> knownTypes = new HashMap<>();
|
||||||
|
Set<Class<?>> resolvableNow;
|
||||||
|
|
||||||
boolean progress;
|
|
||||||
do {
|
do {
|
||||||
progress = false;
|
resolvableNow = unresolved.stream()
|
||||||
Iterator<Class<?>> iterator = unresolved.iterator();
|
.filter(c -> canInstantiate(c, knownTypes.keySet()))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
while (iterator.hasNext()) {
|
for (Class<?> clazz : resolvableNow) {
|
||||||
Class<?> component = iterator.next();
|
Component annotation = clazz.getAnnotation(Component.class);
|
||||||
if (canInstantiate(component, knownTypes.keySet())) {
|
ComponentData componentInfo = new ComponentData(clazz, annotation.scope());
|
||||||
resolved.add(component);
|
|
||||||
registerComponentWithSuperTypes(component, knownTypes);
|
resolved.add(clazz);
|
||||||
iterator.remove();
|
registerComponentWithSuperTypes(componentInfo, knownTypes);
|
||||||
progress = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} while (progress);
|
|
||||||
|
unresolved.removeAll(resolvableNow);
|
||||||
|
} while (!resolvableNow.isEmpty());
|
||||||
|
|
||||||
instantiableComponents.putAll(knownTypes);
|
instantiableComponents.putAll(knownTypes);
|
||||||
|
|
||||||
@ -203,16 +208,17 @@ public class InjectableComponentScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers the given component class in the provided map. The component is registered along with all of its
|
* Registers a component and its superclasses or interfaces in the provided map of known types.
|
||||||
* accessible superclasses and interfaces, unless those types are identified as non-unique.
|
* This method ensures that the component and its inheritance hierarchy are associated with the
|
||||||
|
* component's data unless the supertype is marked as non-unique.
|
||||||
*
|
*
|
||||||
* @param component the component class to register
|
* @param component the {@code ComponentData} instance representing the component to be registered
|
||||||
* @param knownTypes the map where the component and its types will be registered
|
* @param knownTypes the map where component types and their data are stored
|
||||||
*/
|
*/
|
||||||
private void registerComponentWithSuperTypes(Class<?> component, Map<Class<?>, Class<?>> knownTypes) {
|
private void registerComponentWithSuperTypes(ComponentData component, Map<Class<?>, ComponentData> knownTypes) {
|
||||||
knownTypes.put(component, component);
|
knownTypes.put(component.getType(), component);
|
||||||
|
|
||||||
for (Class<?> superType : getAllSuperTypes(component)) {
|
for (Class<?> superType : getAllSuperTypes(component.getType())) {
|
||||||
if (!notUniqueTypes.contains(superType)) {
|
if (!notUniqueTypes.contains(superType)) {
|
||||||
knownTypes.put(superType, component);
|
knownTypes.put(superType, component);
|
||||||
}
|
}
|
||||||
@ -220,22 +226,38 @@ public class InjectableComponentScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether a given class can be instantiated based on its constructors and
|
* Determines whether the specified component class can be instantiated based on the provided set
|
||||||
* the provided known types. A class is considered instantiable if it has a parameterless
|
* of known types. A component is considered instantiable if it has at least one constructor where
|
||||||
* constructor or if all the parameter types of its constructors are present in the known types.
|
* all parameter types are contained in the known types set or if it has a no-argument constructor.
|
||||||
|
* Additionally, all fields annotated with {@code @Inject} must have their types present in the
|
||||||
|
* known types set.
|
||||||
*
|
*
|
||||||
* @param component the class to check for instantiation eligibility
|
* @param component the class to check for instantiability
|
||||||
* @param knownTypes the set of types known to be instantiable and available for constructor injection
|
* @param knownTypes the set of currently known types that can be used to satisfy dependencies
|
||||||
* @return true if the class can be instantiated; false otherwise
|
* @return {@code true} if the component can be instantiated; {@code false} otherwise
|
||||||
*/
|
*/
|
||||||
private boolean canInstantiate(Class<?> component, Set<Class<?>> knownTypes) {
|
private boolean canInstantiate(Class<?> component, Set<Class<?>> knownTypes) {
|
||||||
|
boolean hasValidConstructor = false;
|
||||||
|
|
||||||
for (Constructor<?> constructor : component.getConstructors()) {
|
for (Constructor<?> constructor : component.getConstructors()) {
|
||||||
Class<?>[] paramTypes = constructor.getParameterTypes();
|
Class<?>[] paramTypes = constructor.getParameterTypes();
|
||||||
if (paramTypes.length == 0 || Arrays.stream(paramTypes).allMatch(knownTypes::contains)) {
|
if (paramTypes.length == 0 || Arrays.stream(paramTypes).allMatch(knownTypes::contains)) {
|
||||||
return true;
|
hasValidConstructor = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
if (!hasValidConstructor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var field : component.getDeclaredFields()) {
|
||||||
|
if (field.isAnnotationPresent(Inject.class) && !knownTypes.contains(field.getType())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -256,7 +278,7 @@ public class InjectableComponentScanner {
|
|||||||
Class<?>[] paramTypes = constructor.getParameterTypes();
|
Class<?>[] paramTypes = constructor.getParameterTypes();
|
||||||
List<Class<?>> problematicTypes = Arrays.stream(paramTypes)
|
List<Class<?>> problematicTypes = Arrays.stream(paramTypes)
|
||||||
.filter(t -> !instantiableComponents.containsKey(t) && !notUniqueTypes.contains(t))
|
.filter(t -> !instantiableComponents.containsKey(t) && !notUniqueTypes.contains(t))
|
||||||
.collect(Collectors.toList());
|
.toList();
|
||||||
|
|
||||||
if (problematicTypes.isEmpty()) {
|
if (problematicTypes.isEmpty()) {
|
||||||
possibleWithUniqueTypes = true;
|
possibleWithUniqueTypes = true;
|
||||||
@ -317,7 +339,7 @@ public class InjectableComponentScanner {
|
|||||||
* @return A map where the key is a class type and the value is the corresponding class implementation
|
* @return A map where the key is a class type and the value is the corresponding class implementation
|
||||||
* that can be instantiated.
|
* that can be instantiated.
|
||||||
*/
|
*/
|
||||||
public Map<Class<?>, Class<?>> getInstantiableComponents() {
|
public Map<Class<?>, ComponentData> getInstantiableComponents() {
|
||||||
return instantiableComponents;
|
return instantiableComponents;
|
||||||
}
|
}
|
||||||
|
|
||||||
41
core/src/main/java/de/neitzel/core/inject/Scope.java
Normal file
41
core/src/main/java/de/neitzel/core/inject/Scope.java
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package de.neitzel.core.inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the scope of a component in a dependency injection framework.
|
||||||
|
* <p>
|
||||||
|
* The scope determines the lifecycle and visibility of a component instance
|
||||||
|
* within the framework. The available scope types are:
|
||||||
|
* <p>
|
||||||
|
* - {@code SINGLETON}: A single instance of the component is created and
|
||||||
|
* shared across the entire application.
|
||||||
|
* - {@code PROTOTYPE}: A new instance of the component is created
|
||||||
|
* each time it is requested or injected.
|
||||||
|
* <p>
|
||||||
|
* This enumeration is typically used in conjunction with the {@code @Component}
|
||||||
|
* annotation to specify the instantiation behavior of a component.
|
||||||
|
*/
|
||||||
|
public enum Scope {
|
||||||
|
/**
|
||||||
|
* Specifies that the component should be instantiated as a singleton within the
|
||||||
|
* dependency injection framework. A single instance of the component is created
|
||||||
|
* and shared across the entire application lifecycle, ensuring efficient reuse
|
||||||
|
* of resources and consistent behavior for the component throughout the application.
|
||||||
|
* <p>
|
||||||
|
* This scope is typically applied to components where maintaining a single shared
|
||||||
|
* instance is necessary or beneficial, such as managing shared state or providing
|
||||||
|
* utility services.
|
||||||
|
*/
|
||||||
|
SINGLETON,
|
||||||
|
/**
|
||||||
|
* Specifies that the component should be instantiated as a prototype within the
|
||||||
|
* dependency injection framework. A new instance of the component is created
|
||||||
|
* each time it is requested or injected, ensuring that no two requests or injections
|
||||||
|
* share the same instance.
|
||||||
|
* <p>
|
||||||
|
* This scope is typically applied to components where maintaining unique instances
|
||||||
|
* per request is necessary, such as request-specific data processing or handling
|
||||||
|
* of transient state. It allows for complete isolation between multiple users
|
||||||
|
* or operations requiring individual resources.
|
||||||
|
*/
|
||||||
|
PROTOTYPE
|
||||||
|
}
|
||||||
@ -1,5 +1,7 @@
|
|||||||
package de.neitzel.core.inject.annotation;
|
package de.neitzel.core.inject.annotation;
|
||||||
|
|
||||||
|
import de.neitzel.core.inject.Scope;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
@ -24,4 +26,12 @@ import java.lang.annotation.Target;
|
|||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public @interface Component {
|
public @interface Component {
|
||||||
|
/**
|
||||||
|
* Defines the scope of a component within the dependency injection framework.
|
||||||
|
* The scope determines whether the component is instantiated as a singleton or
|
||||||
|
* as a prototype.
|
||||||
|
*
|
||||||
|
* @return the scope of the component, defaulting to {@code Scope.SINGLETON}.
|
||||||
|
*/
|
||||||
|
Scope scope() default Scope.SINGLETON;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,37 @@
|
|||||||
|
package de.neitzel.core.inject.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that an annotated class is a configuration class within a dependency
|
||||||
|
* injection framework. Classes annotated with {@code @Config} are used as markers
|
||||||
|
* for defining settings and application-specific configurations required by the
|
||||||
|
* dependency injection mechanism.
|
||||||
|
*
|
||||||
|
* Typically, configuration classes provide metadata required for setting up the
|
||||||
|
* framework, such as specifying the base package to scan for components.
|
||||||
|
*
|
||||||
|
* This annotation must be applied at the type level and is retained at runtime to
|
||||||
|
* facilitate reflection-based processing. It is intended to serve as a declarative
|
||||||
|
* representation of configuration options for the dependency injection container.
|
||||||
|
*
|
||||||
|
* Attributes:
|
||||||
|
* - {@code basePackage}: Specifies the package name where the framework should scan
|
||||||
|
* for classes annotated with dependency injection annotations such as {@code @Component}.
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
public @interface Config {
|
||||||
|
/**
|
||||||
|
* Specifies the base package for component scanning within the dependency
|
||||||
|
* injection framework. Classes within the defined package and its sub-packages
|
||||||
|
* can be scanned and identified as candidates for dependency injection.
|
||||||
|
*
|
||||||
|
* @return the base package name as a string; returns an empty string by default
|
||||||
|
* if no specific package is defined.
|
||||||
|
*/
|
||||||
|
String basePackage() default "";
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
package de.neitzel.core.inject.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that a field is a candidate for dependency injection within
|
||||||
|
* a dependency injection framework. Fields annotated with {@code @Inject}
|
||||||
|
* are automatically populated with the required component instance during runtime,
|
||||||
|
* typically by the dependency injection container.
|
||||||
|
*
|
||||||
|
* This annotation must be applied at the field level and is retained at runtime
|
||||||
|
* to enable reflection-based identification and assignment of dependencies.
|
||||||
|
*
|
||||||
|
* The framework's dependency resolution mechanism identifies the appropriate
|
||||||
|
* instance to inject based on the field's type or custom configuration,
|
||||||
|
* ensuring loose coupling and easier testability.
|
||||||
|
*/
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Inject {
|
||||||
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
package de.neitzel.core.sql;
|
package de.neitzel.core.sql;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
package de.neitzel.core.sql;
|
package de.neitzel.core.sql;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package de.neitzel.core.util;
|
package de.neitzel.core.util;
|
||||||
|
|
||||||
import lombok.extern.log4j.Log4j;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
|||||||
128
core/src/main/java/de/neitzel/core/util/StopWatch.java
Normal file
128
core/src/main/java/de/neitzel/core/util/StopWatch.java
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package de.neitzel.core.util;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility class for measuring elapsed time. The StopWatch can be started, stopped, and queried for the elapsed time in various formats.
|
||||||
|
*/
|
||||||
|
public class StopWatch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the starting time of the stopwatch. This variable is set when the stopwatch is started and is used to calculate the elapsed
|
||||||
|
* duration between the start and the stop or the current time.
|
||||||
|
*/
|
||||||
|
private Instant startTime;
|
||||||
|
/**
|
||||||
|
* Represents the stopping time of the stopwatch. This variable is set when the stopwatch is stopped and is used to calculate the elapsed
|
||||||
|
* duration between the start and stop times.
|
||||||
|
*/
|
||||||
|
private Instant stopTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the stopwatch by recording the current time as the starting time. If the stopwatch was previously stopped or not yet started,
|
||||||
|
* this method resets the starting time to the current time and clears the stopping time. This method is typically called before measuring
|
||||||
|
* elapsed time.
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
this.startTime = Instant.now();
|
||||||
|
this.stopTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the stopwatch by recording the current time as the stopping time.
|
||||||
|
* <p>
|
||||||
|
* This method should only be called after the stopwatch has been started using the {@code start()} method. If the stopwatch has not been
|
||||||
|
* started, an {@code IllegalStateException} will be thrown. Once this method is called, the elapsed time can be calculated as the
|
||||||
|
* duration between the starting and stopping times.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException if the stopwatch has not been started prior to calling this method.
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
|
if (startTime == null) {
|
||||||
|
throw new IllegalStateException("StopWatch must be started before stopping.");
|
||||||
|
}
|
||||||
|
this.stopTime = Instant.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the amount of time elapsed between the stopwatch's starting time and either its stopping time or the current time if the
|
||||||
|
* stopwatch is still running.
|
||||||
|
*
|
||||||
|
* @return the duration representing the elapsed time between the start and end points of the stopwatch.
|
||||||
|
* @throws IllegalStateException if the stopwatch has not been started before calling this method.
|
||||||
|
*/
|
||||||
|
public Duration getUsedTime() {
|
||||||
|
if (startTime == null) {
|
||||||
|
throw new IllegalStateException("StopWatch has not been started.");
|
||||||
|
}
|
||||||
|
Instant end = (stopTime != null) ? stopTime : Instant.now();
|
||||||
|
return Duration.between(startTime, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the duration of elapsed time measured by the stopwatch into a human-readable string. The formatted output may include days,
|
||||||
|
* hours, minutes, seconds, and milliseconds, depending on the parameters. The method retrieves the elapsed time via the
|
||||||
|
* {@code getUsedTime} method and formats it based on the specified flags.
|
||||||
|
*
|
||||||
|
* @param includeMillis whether to include milliseconds in the formatted output
|
||||||
|
* @param includeSeconds whether to include seconds in the formatted output
|
||||||
|
* @param includeMinutes whether to include minutes in the formatted output
|
||||||
|
* @param includeHours whether to include hours in the formatted output
|
||||||
|
* @param includeDays whether to include days in the formatted output
|
||||||
|
* @return a formatted string representing the elapsed time, including the components specified by the parameters
|
||||||
|
*/
|
||||||
|
public String getUsedTimeFormatted(boolean includeMillis, boolean includeSeconds, boolean includeMinutes, boolean includeHours,
|
||||||
|
boolean includeDays) {
|
||||||
|
Duration duration = getUsedTime();
|
||||||
|
|
||||||
|
long millis = duration.toMillis();
|
||||||
|
long days = millis / (24 * 60 * 60 * 1000);
|
||||||
|
millis %= (24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
long hours = millis / (60 * 60 * 1000);
|
||||||
|
millis %= (60 * 60 * 1000);
|
||||||
|
long minutes = millis / (60 * 1000);
|
||||||
|
millis %= (60 * 1000);
|
||||||
|
long seconds = millis / 1000;
|
||||||
|
millis %= 1000;
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if (includeDays && days > 0) {
|
||||||
|
sb.append(days).append("d ");
|
||||||
|
}
|
||||||
|
if (includeHours && (hours > 0 || sb.length() > 0)) {
|
||||||
|
sb.append(hours).append("h ");
|
||||||
|
}
|
||||||
|
if (includeMinutes && (minutes > 0 || sb.length() > 0)) {
|
||||||
|
sb.append(minutes).append("m ");
|
||||||
|
}
|
||||||
|
if (includeSeconds && (seconds > 0 || sb.length() > 0)) {
|
||||||
|
sb.append(seconds);
|
||||||
|
}
|
||||||
|
if (includeMillis) {
|
||||||
|
if (includeSeconds) {
|
||||||
|
sb.append(String.format(Locale.US, ".%03d", millis));
|
||||||
|
} else {
|
||||||
|
sb.append(millis).append("ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String result = sb.toString().trim();
|
||||||
|
return result.isEmpty() ? "0ms" : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a compact and human-readable formatted string representing the elapsed time measured by the stopwatch. The method formats the
|
||||||
|
* duration using a combination of days, hours, minutes, seconds, and milliseconds. This compact format removes additional verbosity in
|
||||||
|
* favor of a shorter, more concise representation.
|
||||||
|
*
|
||||||
|
* @return a compact formatted string representing the elapsed time, including days, hours, minutes, seconds, and milliseconds as
|
||||||
|
* applicable
|
||||||
|
*/
|
||||||
|
public String getUsedTimeFormattedCompact() {
|
||||||
|
return getUsedTimeFormatted(true, true, true, true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,6 @@
|
|||||||
package de.neitzel.core.util;
|
package de.neitzel.core.util;
|
||||||
|
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.extern.log4j.Log4j;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
|
|||||||
@ -16,7 +16,7 @@ class InjectableComponentScannerTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testLoadComponents() {
|
void testLoadComponents() {
|
||||||
InjectableComponentScanner scanner = new InjectableComponentScanner("de.neitzel.core.inject.testcomponents.test1ok");
|
ComponentScanner scanner = new ComponentScanner("de.neitzel.core.inject.testcomponents.test1ok");
|
||||||
var instantiableComponents = scanner.getInstantiableComponents();
|
var instantiableComponents = scanner.getInstantiableComponents();
|
||||||
var nonUniqueTypes = scanner.getNotUniqueTypes();
|
var nonUniqueTypes = scanner.getNotUniqueTypes();
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ class InjectableComponentScannerTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testComponentsFailWithUnknownParameters() {
|
void testComponentsFailWithUnknownParameters() {
|
||||||
InjectableComponentScanner scanner = new InjectableComponentScanner("de.neitzel.core.inject.testcomponents.test2fail");
|
ComponentScanner scanner = new ComponentScanner("de.neitzel.core.inject.testcomponents.test2fail");
|
||||||
var instantiableComponents = scanner.getInstantiableComponents();
|
var instantiableComponents = scanner.getInstantiableComponents();
|
||||||
var nonUniqueTypes = scanner.getNotUniqueTypes();
|
var nonUniqueTypes = scanner.getNotUniqueTypes();
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
package de.neitzel.core.inject.testcomponents.test1ok.sub;
|
package de.neitzel.core.inject.testcomponents.test1ok.sub;
|
||||||
|
|
||||||
|
import de.neitzel.core.inject.annotation.Component;
|
||||||
import de.neitzel.core.inject.testcomponents.test1ok.SuperClass;
|
import de.neitzel.core.inject.testcomponents.test1ok.SuperClass;
|
||||||
import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_1;
|
import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_1;
|
||||||
import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_2;
|
import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_2;
|
||||||
import de.neitzel.core.inject.annotation.Component;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class TestComponent1_2 extends SuperClass implements TestInterface1_1, TestInterface1_2 {
|
public class TestComponent1_2 extends SuperClass implements TestInterface1_1, TestInterface1_2 {
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.control.TextField?>
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
|
|
||||||
<AnchorPane xmlns="http://javafx.com/javafx"
|
<AnchorPane xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns:nfx="http://example.com/nfx"
|
xmlns:nfx="http://example.com/nfx"
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.control.Button?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.control.TextField?>
|
||||||
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
<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.fx.injectfx.example.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.fx.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" />
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.control.TextField?>
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
|
<?import javafx.scene.layout.Pane?>
|
||||||
<AnchorPane xmlns="http://javafx.com/javafx"
|
<AnchorPane xmlns="http://javafx.com/javafx"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns:nfx="http://example.com/nfx"
|
xmlns:nfx="http://example.com/nfx"
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
package de.neitzel.fx.component;
|
package de.neitzel.fx.component;
|
||||||
|
|
||||||
import javafx.beans.property.*;
|
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> {
|
public class AutoViewModel<T> {
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
package de.neitzel.fx.injectfx;
|
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.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.
|
* Manages the creation and storage of singleton instances for all @FXMLComponent classes.
|
||||||
@ -11,18 +15,19 @@ import java.util.*;
|
|||||||
*/
|
*/
|
||||||
public class FXMLComponentInstances {
|
public class FXMLComponentInstances {
|
||||||
|
|
||||||
|
@Getter
|
||||||
/** Map holding instances of all @FXMLComponent classes, indexed by class and its unique superclasses/interfaces. */
|
/** Map holding instances of all @FXMLComponent classes, indexed by class and its unique superclasses/interfaces. */
|
||||||
private final Map<Class<?>, Object> instanceMap = new HashMap<>();
|
private final Map<Class<?>, Object> instanceMap = new HashMap<>();
|
||||||
|
|
||||||
/** The InjectableComponents instance that provides information about instantiable components. */
|
/** 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.
|
* Constructs an FXMLComponentInstances manager and initializes all component instances.
|
||||||
*
|
*
|
||||||
* @param injectableScanner The InjectableComponents instance containing resolved component types.
|
* @param injectableScanner The InjectableComponents instance containing resolved component types.
|
||||||
*/
|
*/
|
||||||
public FXMLComponentInstances(InjectableComponentScanner injectableScanner) {
|
public FXMLComponentInstances(ComponentScanner injectableScanner) {
|
||||||
this.injectableScanner = injectableScanner;
|
this.injectableScanner = injectableScanner;
|
||||||
createAllInstances();
|
createAllInstances();
|
||||||
}
|
}
|
||||||
@ -47,7 +52,7 @@ public class FXMLComponentInstances {
|
|||||||
return instanceMap.get(componentClass);
|
return instanceMap.get(componentClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<?> concreteClass = injectableScanner.getInstantiableComponents().get(componentClass);
|
Class<?> concreteClass = injectableScanner.getInstantiableComponents().get(componentClass).getType();
|
||||||
if (concreteClass == null) {
|
if (concreteClass == null) {
|
||||||
throw new IllegalStateException("No concrete implementation found for: " + componentClass.getName());
|
throw new IllegalStateException("No concrete implementation found for: " + componentClass.getName());
|
||||||
}
|
}
|
||||||
@ -122,19 +127,10 @@ public class FXMLComponentInstances {
|
|||||||
private void registerInstance(Class<?> concreteClass, Object instance) {
|
private void registerInstance(Class<?> concreteClass, Object instance) {
|
||||||
instanceMap.put(concreteClass, instance);
|
instanceMap.put(concreteClass, instance);
|
||||||
|
|
||||||
for (Map.Entry<Class<?>, Class<?>> entry : injectableScanner.getInstantiableComponents().entrySet()) {
|
for (Map.Entry<Class<?>, ComponentData> entry : injectableScanner.getInstantiableComponents().entrySet()) {
|
||||||
if (entry.getValue().equals(concreteClass)) {
|
if (entry.getValue().getType().equals(concreteClass)) {
|
||||||
instanceMap.put(entry.getKey(), instance);
|
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.Optional;
|
||||||
import java.util.stream.Stream;
|
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> {
|
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<>();
|
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) {
|
public void addInjectingData(Class<?> clazz, Object object) {
|
||||||
parameterMap.put(clazz, 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
|
@Override
|
||||||
public Object call(Class<?> controllerClass) {
|
public Object call(Class<?> controllerClass) {
|
||||||
try {
|
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) {
|
private boolean canBeInstantiated(Constructor<?> constructor, Map<Class<?>, Object> parameterMap) {
|
||||||
return Stream.of(constructor.getParameterTypes()).allMatch(parameterMap::containsKey);
|
return Stream.of(constructor.getParameterTypes()).allMatch(parameterMap::containsKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,42 +1,131 @@
|
|||||||
package de.neitzel.fx.injectfx;
|
package de.neitzel.fx.injectfx;
|
||||||
|
|
||||||
import de.neitzel.core.inject.InjectableComponentScanner;
|
import de.neitzel.core.inject.ComponentScanner;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
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
|
@Slf4j
|
||||||
public class InjectingFXMLLoader {
|
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;
|
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() {
|
public InjectingFXMLLoader() {
|
||||||
controllerFactory = new InjectingControllerFactory();
|
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) {
|
public InjectingFXMLLoader(InjectingControllerFactory controllerFactory) {
|
||||||
this.controllerFactory = 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) {
|
public InjectingFXMLLoader(String packageName) {
|
||||||
controllerFactory = new InjectingControllerFactory();
|
controllerFactory = new InjectingControllerFactory();
|
||||||
InjectableComponentScanner scanner = new InjectableComponentScanner(packageName);
|
ComponentScanner scanner = new ComponentScanner(packageName);
|
||||||
FXMLComponentInstances instances = new FXMLComponentInstances(scanner);
|
FXMLComponentInstances instances = new FXMLComponentInstances(scanner);
|
||||||
addInjectingData(instances);
|
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) {
|
private void addInjectingData(FXMLComponentInstances instances) {
|
||||||
for (var clazz: instances.getInstanceMap().keySet()) {
|
for (var clazz: instances.getInstanceMap().keySet()) {
|
||||||
addInjectingData(clazz, instances.getInstance(clazz));
|
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) {
|
public void addInjectingData(Class<?> clazz, Object object) {
|
||||||
controllerFactory.addInjectingData(clazz, 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 {
|
public <T> T load(URL url) throws IOException {
|
||||||
FXMLLoader loader = new FXMLLoader(url);
|
FXMLLoader loader = new FXMLLoader(url);
|
||||||
loader.setControllerFactory(controllerFactory);
|
loader.setControllerFactory(controllerFactory);
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package de.neitzel.fx.mvvm;
|
package de.neitzel.fx.mvvm;
|
||||||
|
|
||||||
import javafx.fxml.Initializable;
|
import javafx.fxml.Initializable;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
|||||||
@ -6,8 +6,11 @@ import java.beans.BeanInfo;
|
|||||||
import java.beans.IntrospectionException;
|
import java.beans.IntrospectionException;
|
||||||
import java.beans.Introspector;
|
import java.beans.Introspector;
|
||||||
import java.beans.PropertyDescriptor;
|
import java.beans.PropertyDescriptor;
|
||||||
import java.lang.reflect.*;
|
import java.lang.reflect.Method;
|
||||||
import java.util.*;
|
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.
|
* A generic ViewModel for JavaFX using reflection to automatically create properties for model attributes.
|
||||||
|
|||||||
49
gson/pom.xml
Normal file
49
gson/pom.xml
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>de.neitzel.lib</groupId>
|
||||||
|
<artifactId>neitzellib</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<!-- Application Properties -->
|
||||||
|
<link.name>${project.artifactId}</link.name>
|
||||||
|
<launcher>${project.artifactId}</launcher>
|
||||||
|
<appName>${project.artifactId}</appName>
|
||||||
|
<jar.filename>${project.artifactId}-${project.version}</jar.filename>
|
||||||
|
|
||||||
|
<!-- Dependency Versions -->
|
||||||
|
<gson.version>2.11.0</gson.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
<version>${gson.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>${jar.filename}</finalName>
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.github.spotbugs</groupId>
|
||||||
|
<artifactId>spotbugs-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-pmd-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@ -0,0 +1,136 @@
|
|||||||
|
package de.neitzel.gson.adapter;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.TypeAdapter;
|
||||||
|
import com.google.gson.internal.Streams;
|
||||||
|
import com.google.gson.stream.JsonReader;
|
||||||
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom {@link TypeAdapter} implementation for selectively excluding specified fields during JSON serialization.
|
||||||
|
*
|
||||||
|
* This adapter facilitates both serialization and deserialization of objects while providing functionality to remove specified
|
||||||
|
* fields from the serialized output. The set of excluded field names is provided during the adapter's initialization.
|
||||||
|
*
|
||||||
|
* @param <T> the type of objects that this adapter can serialize and deserialize
|
||||||
|
*/
|
||||||
|
public class ExcludeFieldsAdapter<T> extends TypeAdapter<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of objects that this adapter handles for serialization and deserialization.
|
||||||
|
*
|
||||||
|
* This is a generic class type parameter representing the class of objects that the
|
||||||
|
* {@link ExcludeFieldsAdapter} instance is designed to process. It ensures type
|
||||||
|
* safety and guarantees the compatibility of the adapter with a specific object type.
|
||||||
|
*/
|
||||||
|
private final Class<T> type;
|
||||||
|
/**
|
||||||
|
* A set containing the names of fields to be excluded during JSON serialization.
|
||||||
|
*
|
||||||
|
* This collection is used in the {@link ExcludeFieldsAdapter} to determine which fields should be removed from the
|
||||||
|
* serialized JSON representation of an object. The fields to be excluded are specified during the initialization
|
||||||
|
* of the adapter.
|
||||||
|
*/
|
||||||
|
private final Set<String> excludedFields;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An instance of the Gson library used for JSON serialization and deserialization.
|
||||||
|
*
|
||||||
|
* This variable holds the Gson object that is utilized to handle the conversion processes within the
|
||||||
|
* custom serialization and deserialization logic. If not explicitly provided during initialization,
|
||||||
|
* it can be lazily initialized using a GsonBuilder.
|
||||||
|
*/
|
||||||
|
private Gson gson;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link GsonBuilder} instance used for configuring and creating {@link Gson} objects within the adapter.
|
||||||
|
*
|
||||||
|
* This variable serves as a reference to a GsonBuilder that allows customization of the Gson instance,
|
||||||
|
* including registering custom type adapters, serializers, and deserializers, as well as adjusting serialization policies.
|
||||||
|
*
|
||||||
|
* The {@link #gsonBuilder} is primarily utilized when an existing {@link Gson} instance is not directly provided.
|
||||||
|
* It ensures that the adapter can defer the creation of a Gson object until it is explicitly required.
|
||||||
|
*/
|
||||||
|
private GsonBuilder gsonBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of {@code ExcludeFieldsAdapter} for managing the serialization and deserialization
|
||||||
|
* of objects of the specified {@code type}, while excluding certain fields during serialization.
|
||||||
|
*
|
||||||
|
* @param type the class type of the objects to be serialized and deserialized
|
||||||
|
* @param excludedFields the set of field names to be excluded during serialization
|
||||||
|
* @param gson the Gson instance to be used for serialization and deserialization
|
||||||
|
*/
|
||||||
|
public ExcludeFieldsAdapter(Class<T> type, Set<String> excludedFields, Gson gson) {
|
||||||
|
this.type = type;
|
||||||
|
this.excludedFields = new HashSet<>(excludedFields);
|
||||||
|
this.gsonBuilder = null;
|
||||||
|
this.gson = gson;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance of {@code ExcludeFieldsAdapter} that selectively excludes specified fields from
|
||||||
|
* the JSON output based on the configuration provided during initialization.
|
||||||
|
*
|
||||||
|
* @param type the class of the type to be serialized and deserialized
|
||||||
|
* @param excludedFields a set of field names to be excluded from the serialized JSON output
|
||||||
|
* @param gsonBuilder an instance of {@code GsonBuilder} used to create a custom {@code Gson} instance if needed
|
||||||
|
*/
|
||||||
|
public ExcludeFieldsAdapter(Class<T> type, Set<String> excludedFields, GsonBuilder gsonBuilder) {
|
||||||
|
this.type = type;
|
||||||
|
this.excludedFields = new HashSet<>(excludedFields);
|
||||||
|
this.gsonBuilder = gsonBuilder;
|
||||||
|
this.gson = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily initializes and retrieves the {@link Gson} instance. If the instance is null, it creates a new {@link Gson}
|
||||||
|
* object using the {@link GsonBuilder} provided during the adapter's initialization.
|
||||||
|
*
|
||||||
|
* @return the {@link Gson} instance used for serialization and deserialization
|
||||||
|
*/
|
||||||
|
private Gson getGson() {
|
||||||
|
if (gson == null) {
|
||||||
|
gson = gsonBuilder.create();
|
||||||
|
}
|
||||||
|
return gson;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes an object of type {@code T} into JSON format, excluding certain fields specified during the initialization of this adapter.
|
||||||
|
* The object is first converted into a {@link JsonObject}, then the fields listed in the excluded fields set are removed from the JSON
|
||||||
|
* representation before writing it to the provided {@link JsonWriter}.
|
||||||
|
*
|
||||||
|
* @param out the {@link JsonWriter} to write the serialized JSON data to
|
||||||
|
* @param value the object of type {@code T} to serialize into JSON
|
||||||
|
* @throws IOException if an I/O error occurs during writing
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void write(JsonWriter out, T value) throws IOException {
|
||||||
|
JsonObject obj = getGson().toJsonTree(value).getAsJsonObject();
|
||||||
|
|
||||||
|
for (String field : excludedFields) {
|
||||||
|
obj.remove(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
Streams.write(obj, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a JSON input stream and deserializes it into an object of the specified type.
|
||||||
|
*
|
||||||
|
* @param in the {@link JsonReader} to read the JSON input from
|
||||||
|
* @return an instance of type {@code T} deserialized from the JSON input
|
||||||
|
* @throws IOException if an error occurs during reading or deserialization
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T read(JsonReader in) throws IOException {
|
||||||
|
return getGson().fromJson(in, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,52 @@
|
|||||||
|
package de.neitzel.gson.adapter;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom Gson type adapter for serializing and deserializing {@link Instant} objects. This adapter handles conversion between
|
||||||
|
* {@code Instant} and its ISO-8601 formatted string representation.
|
||||||
|
* <p>
|
||||||
|
* Implementation details: - Serialization: Converts {@link Instant} to its ISO-8601 string representation. - Deserialization: Parses a
|
||||||
|
* valid ISO-8601 string to an {@link Instant} instance.
|
||||||
|
* <p>
|
||||||
|
* The adapter ensures compliance with the ISO-8601 standard for interoperability. A {@link JsonParseException} is thrown if the provided
|
||||||
|
* JSON element during deserialization does not conform to the supported {@code Instant} format.
|
||||||
|
*/
|
||||||
|
public class InstantTypeAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes an {@link Instant} object into its ISO-8601 string representation as a {@link JsonElement}.
|
||||||
|
*
|
||||||
|
* @param src the {@link Instant} object to be serialized
|
||||||
|
* @param typeOfSrc the specific genericized type of {@code src} (typically ignored in this implementation)
|
||||||
|
* @param context the context of the serialization process to provide custom serialization logic
|
||||||
|
* @return a {@link JsonPrimitive} containing the ISO-8601 formatted string representation of the {@link Instant}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
|
return new JsonPrimitive(src.toString()); // ISO-8601: "2025-04-02T10:15:30Z"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserializes a JSON element into an {@link Instant} object.
|
||||||
|
*
|
||||||
|
* @param json the JSON data being deserialized
|
||||||
|
* @param typeOfT the specific genericized type of the object to deserialize
|
||||||
|
* @param context the deserialization context
|
||||||
|
* @return the deserialized {@link Instant} object
|
||||||
|
* @throws JsonParseException if the JSON element does not represent a valid ISO-8601 instant format
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Instant deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
|
||||||
|
throws JsonParseException {
|
||||||
|
try {
|
||||||
|
return Instant.parse(json.getAsString());
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
throw new JsonParseException("Invalid Instant format", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
pom.xml
1
pom.xml
@ -14,6 +14,7 @@
|
|||||||
<module>core</module>
|
<module>core</module>
|
||||||
<module>encryption</module>
|
<module>encryption</module>
|
||||||
<module>fx</module>
|
<module>fx</module>
|
||||||
|
<module>gson</module>
|
||||||
<module>net</module>
|
<module>net</module>
|
||||||
<module>log4j</module>
|
<module>log4j</module>
|
||||||
<module>fx-example</module>
|
<module>fx-example</module>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user