Refactor and expand core functionalities with new modules
Added robust utility modules: Tone generation, image scaling, and sound tone mapping. Developed an encryption domain with new abstractions. Updated and annotated the SQL query class for clarity and functionality. Removed unused `@Config` annotation.
This commit is contained in:
parent
8694899e57
commit
77ea70f4e6
155
core/src/main/java/de/neitzel/core/image/ImageScaler.java
Normal file
155
core/src/main/java/de/neitzel/core/image/ImageScaler.java
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
package de.neitzel.core.image;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for scaling images while maintaining the aspect ratio.
|
||||||
|
* Provides methods to create scaled images either as byte arrays or InputStreams.
|
||||||
|
*/
|
||||||
|
public class ImageScaler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor to prevent instantiation of this utility class.
|
||||||
|
*/
|
||||||
|
private ImageScaler() {
|
||||||
|
throw new UnsupportedOperationException("Utility class cannot be instantiated");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the target image format for scaling operations.
|
||||||
|
*
|
||||||
|
* This variable defines the file format that will be used when writing
|
||||||
|
* or encoding the scaled image. The format must be compatible with
|
||||||
|
* Java's ImageIO framework (e.g., "png", "jpg", "bmp").
|
||||||
|
*
|
||||||
|
* In this case, the target format is set to "png", allowing images
|
||||||
|
* to be scaled and saved or returned as PNG files. It ensures
|
||||||
|
* consistent output format across the image scaling methods in the class.
|
||||||
|
*/
|
||||||
|
private static String TARGET_FORMAT="png";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a scaled image from the given byte array representation and returns it as a byte array.
|
||||||
|
*
|
||||||
|
* If enforceSize is set to false, the given dimensions are used as maximum values for scaling.
|
||||||
|
* The resulting image may retain its aspect ratio and be smaller than the specified dimensions.
|
||||||
|
* If enforceSize is true, the resulting image will match the exact dimensions, potentially adding
|
||||||
|
* transparent areas on the top/bottom or right/left sides to maintain the original aspect ratio.
|
||||||
|
*
|
||||||
|
* @param originalImageBytes The byte array representing the original image.
|
||||||
|
* @param width The (maximum) width of the target image.
|
||||||
|
* @param height The (maximum) height of the target image.
|
||||||
|
* @param enforceSize A flag indicating whether the resulting image should strictly adhere to specified dimensions.
|
||||||
|
* If false, the image will retain its aspect ratio without empty borders.
|
||||||
|
* @return A byte array representing the scaled image, or null if the operation failed or the input was invalid.
|
||||||
|
*/
|
||||||
|
public static byte[] createScaledImage(final byte[] originalImageBytes, final int width, final int height, final boolean enforceSize) {
|
||||||
|
// Validation
|
||||||
|
if (originalImageBytes == null) return null;
|
||||||
|
if (originalImageBytes.length==0) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create the image from a byte array.
|
||||||
|
BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(originalImageBytes));
|
||||||
|
return createScaledImage(originalImage, width, height, enforceSize);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scales an image provided as a byte array to the specified dimensions and returns an InputStream of the scaled image.
|
||||||
|
*
|
||||||
|
* The scaling behavior is determined by the enforceSize parameter:
|
||||||
|
* - If enforceSize is false, the provided dimensions are treated as maximum values, maintaining the original aspect ratio.
|
||||||
|
* - If enforceSize is true, the resulting image will match the exact dimensions, potentially with transparent borders to keep the original aspect ratio.
|
||||||
|
*
|
||||||
|
* @param originalImageBytes The byte array representing the original image.
|
||||||
|
* @param width The (maximum) width of the target scaled image.
|
||||||
|
* @param height The (maximum) height of the target scaled image.
|
||||||
|
* @param enforceSize A flag indicating whether to enforce the exact scaled dimensions. If false, the resulting image retains its aspect ratio.
|
||||||
|
* @return An InputStream representing the scaled image, or null if the operation fails or the input image is invalid.
|
||||||
|
*/
|
||||||
|
public static InputStream scaledImage(final byte[] originalImageBytes, final int width, final int height, final boolean enforceSize) {
|
||||||
|
// Get the scaled image.
|
||||||
|
byte[] bytes = createScaledImage(originalImageBytes, width, height, enforceSize);
|
||||||
|
|
||||||
|
// Validation of result.
|
||||||
|
if (bytes == null || bytes.length == 0) return null;
|
||||||
|
|
||||||
|
// Return new InputStream.
|
||||||
|
return new ByteArrayInputStream(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a scaled version of the given {@link BufferedImage} and returns it as a byte array.
|
||||||
|
*
|
||||||
|
* The scaling behavior is determined by the enforceSize parameter:
|
||||||
|
* - If enforceSize is false, the provided dimensions are treated as maximum values, and the image
|
||||||
|
* will maintain its original aspect ratio. The resulting image may be smaller than the specified
|
||||||
|
* dimensions.
|
||||||
|
* - If enforceSize is true, the resulting image will match the exact specified dimensions, potentially
|
||||||
|
* adding transparent padding to fit the aspect ratio.
|
||||||
|
*
|
||||||
|
* @param originalImage The original image to be scaled. Must not be null.
|
||||||
|
* @param width The specified (maximum) width of the scaled image.
|
||||||
|
* @param height The specified (maximum) height of the scaled image.
|
||||||
|
* @param enforceSize A flag indicating whether the scaled image should strictly conform to the specified
|
||||||
|
* dimensions. If true, the image may include transparent areas to maintain the aspect ratio.
|
||||||
|
* @return A byte array representing the scaled image, or null if the scaling operation fails or the input image is invalid.
|
||||||
|
*/
|
||||||
|
protected static byte[] createScaledImage(final BufferedImage originalImage, final int width, final int height, final boolean enforceSize) {
|
||||||
|
// Validation
|
||||||
|
if (originalImage == null) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get the scale factor.
|
||||||
|
double scaleWidth = (double) width / (double) originalImage.getWidth();
|
||||||
|
double scaleHeight = (double) height / (double) originalImage.getHeight();
|
||||||
|
|
||||||
|
double scaleFactor;
|
||||||
|
if (scaleWidth > scaleHeight) {
|
||||||
|
scaleFactor = scaleHeight;
|
||||||
|
} else {
|
||||||
|
scaleFactor = scaleWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate target size of scaled image.
|
||||||
|
int newHeight = (int) (scaleFactor * originalImage.getHeight());
|
||||||
|
int newWidth = (int) (scaleFactor * originalImage.getWidth());
|
||||||
|
|
||||||
|
// Cooordinates of new picture and size of new picture.
|
||||||
|
int x, y, usedHeight, usedWidth;
|
||||||
|
if (enforceSize) {
|
||||||
|
usedHeight = height;
|
||||||
|
usedWidth = width;
|
||||||
|
x = (width - newWidth) / 2;
|
||||||
|
y = (height - newHeight) / 2;
|
||||||
|
} else {
|
||||||
|
x = 0;
|
||||||
|
y = 0;
|
||||||
|
usedHeight = newHeight;
|
||||||
|
usedWidth = newWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scale the image
|
||||||
|
BufferedImage scaledImage = new BufferedImage(usedWidth, usedHeight, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D graphics = scaledImage.createGraphics();
|
||||||
|
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
|
graphics.drawImage(originalImage, x, y, newWidth, newHeight, null);
|
||||||
|
|
||||||
|
// Get the bytes of the image
|
||||||
|
try (ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
|
||||||
|
ImageIO.write(scaledImage, TARGET_FORMAT, stream);
|
||||||
|
return stream.toByteArray();
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,30 +8,93 @@ import java.util.*;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* InjectableComponents scans a package for classes annotated with {@link Component}.
|
* InjectableComponentScanner is responsible for scanning packages to detect classes annotated
|
||||||
* It determines which components can be instantiated and manages type mappings for dependency injection.
|
* with @FXMLComponent and analyzing their compatibility for instantiation and dependency injection.
|
||||||
|
* The resulting analysis identifies unique and shared interfaces/superclasses as well as
|
||||||
|
* potentially instantiable components, collecting relevant errors if instantiation is not feasible.
|
||||||
*/
|
*/
|
||||||
public class InjectableComponentScanner {
|
public class InjectableComponentScanner {
|
||||||
|
|
||||||
/** Set of all detected @FXMLComponent classes within the given package. */
|
/**
|
||||||
|
* A set that stores classes annotated with {@code @Component}, representing
|
||||||
|
* FXML-compatible components detected during the scanning process.
|
||||||
|
* These components are used as part of the dependency injection mechanism
|
||||||
|
* within the {@code InjectableComponentScanner}.
|
||||||
|
*
|
||||||
|
* The {@code fxmlComponents} set is populated during the component scanning
|
||||||
|
* phase by identifying all classes within a specified package hierarchy
|
||||||
|
* annotated with the {@code @Component} annotation. These classes serve
|
||||||
|
* as the primary source for further analysis, resolution, and instantiation
|
||||||
|
* in the scanning workflow.
|
||||||
|
*
|
||||||
|
* This field is immutable, ensuring thread safety and consistent state
|
||||||
|
* throughout the lifetime of the {@code InjectableComponentScanner}.
|
||||||
|
*/
|
||||||
private final Set<Class<?>> fxmlComponents = new HashSet<>();
|
private final Set<Class<?>> fxmlComponents = new HashSet<>();
|
||||||
|
|
||||||
/** Set of all superclasses and interfaces that are implemented by multiple @FXMLComponent classes. */
|
/**
|
||||||
|
* A set of component types that are not uniquely associated with a single implementation.
|
||||||
|
*
|
||||||
|
* This collection is populated during the analysis of discovered components to identify
|
||||||
|
* types that have multiple implementations, preventing them from being uniquely resolved.
|
||||||
|
* For example, if multiple components implement the same interface, that interface will be
|
||||||
|
* added to this set.
|
||||||
|
*
|
||||||
|
* It is primarily used during the component registration process to avoid ambiguities
|
||||||
|
* in the dependency injection system, ensuring that only resolvable, uniquely identifiable
|
||||||
|
* components can be instantiated and injected.
|
||||||
|
*/
|
||||||
private final Set<Class<?>> notUniqueTypes = new HashSet<>();
|
private final Set<Class<?>> notUniqueTypes = new HashSet<>();
|
||||||
|
|
||||||
/** Map of unique superclasses/interfaces to a single corresponding @FXMLComponent class. */
|
/**
|
||||||
|
* A mapping of unique super types to their corresponding component classes.
|
||||||
|
* The key represents a super type (interface or abstract class), and the value
|
||||||
|
* is the specific implementation or subclass that is uniquely identified among
|
||||||
|
* the scanned components.
|
||||||
|
*
|
||||||
|
* This map is populated during the component analysis process. For each super type
|
||||||
|
* in the scanned components, if exactly one implementation is found, it is
|
||||||
|
* considered unique and added to this mapping. In case multiple implementations
|
||||||
|
* exist for a given super type, it is excluded from this map and classified as
|
||||||
|
* a non-unique type.
|
||||||
|
*
|
||||||
|
* This mapping is utilized for resolving dependencies and determining which components
|
||||||
|
* can be instantiated based on unique type information.
|
||||||
|
*/
|
||||||
private final Map<Class<?>, Class<?>> uniqueTypeToComponent = new HashMap<>();
|
private final Map<Class<?>, Class<?>> uniqueTypeToComponent = new HashMap<>();
|
||||||
|
|
||||||
/** Map of instantiable @FXMLComponent classes and their corresponding interfaces/superclasses (if unique). */
|
/**
|
||||||
|
* A mapping of components that can be instantiated with their corresponding types.
|
||||||
|
* This map links a component's class (key) to the specific implementation class (value)
|
||||||
|
* that can be instantiated. The entries are resolved through the scanning and analysis
|
||||||
|
* of available component types, ensuring that only components with resolvable
|
||||||
|
* dependencies are included.
|
||||||
|
*
|
||||||
|
* This field is part of the dependency injection process, where components annotated
|
||||||
|
* with {@code @Component} or similar annotations are scanned, analyzed, and registered.
|
||||||
|
* The resolution process checks if a component can be instantiated based on its
|
||||||
|
* constructor dependencies being resolvable using known types or other registered components.
|
||||||
|
*/
|
||||||
private final Map<Class<?>, Class<?>> instantiableComponents = new HashMap<>();
|
private final Map<Class<?>, Class<?>> instantiableComponents = new HashMap<>();
|
||||||
|
|
||||||
/** List of error messages generated when resolving component instantiability. */
|
/**
|
||||||
|
* A list of error messages encountered during the scanning and analysis
|
||||||
|
* of components in the dependency injection process.
|
||||||
|
*
|
||||||
|
* This list is populated when components cannot be resolved due to issues
|
||||||
|
* such as multiple implementations of a superclass or interface,
|
||||||
|
* unmet construction requirements, or unresolved dependencies.
|
||||||
|
*
|
||||||
|
* Errors added to this list provide detailed descriptions of the specific
|
||||||
|
* issues that prevent certain components from being instantiated correctly.
|
||||||
|
*/
|
||||||
private final List<String> errors = new ArrayList<>();
|
private final List<String> errors = new ArrayList<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an InjectableComponents instance and scans the given package for @FXMLComponent classes.
|
* Initializes a new instance of the {@code InjectableComponentScanner} class, which scans for injectable
|
||||||
|
* components within the specified base package and resolves the set of recognizable and instantiable components.
|
||||||
*
|
*
|
||||||
* @param basePackage The base package to scan for @FXMLComponent classes.
|
* @param basePackage the base package to scan for injectable components
|
||||||
*/
|
*/
|
||||||
public InjectableComponentScanner(String basePackage) {
|
public InjectableComponentScanner(String basePackage) {
|
||||||
scanForComponents(basePackage);
|
scanForComponents(basePackage);
|
||||||
@ -40,9 +103,10 @@ public class InjectableComponentScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scans the specified package for classes annotated with @FXMLComponent.
|
* Scans the specified base package for classes annotated with {@link Component}.
|
||||||
|
* Identified component classes are added to a collection for further processing.
|
||||||
*
|
*
|
||||||
* @param basePackage The package to scan.
|
* @param basePackage the package to scan for component annotations
|
||||||
*/
|
*/
|
||||||
private void scanForComponents(String basePackage) {
|
private void scanForComponents(String basePackage) {
|
||||||
Reflections reflections = new Reflections(basePackage);
|
Reflections reflections = new Reflections(basePackage);
|
||||||
@ -50,7 +114,23 @@ public class InjectableComponentScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Analyzes the collected @FXMLComponent classes to determine unique and non-unique superclasses/interfaces.
|
* Analyzes component types within the injected components and classifies them based on their
|
||||||
|
* inheritance hierarchy and relationships.
|
||||||
|
*
|
||||||
|
* This method performs the following operations:
|
||||||
|
* 1. Maps each component to all of its superclasses and interfaces.
|
||||||
|
* 2. Identifies which superclasses or interfaces have multiple implementing components.
|
||||||
|
* 3. Populates:
|
||||||
|
* - A list of superclasses or interfaces that are not uniquely linked to a single component.
|
||||||
|
* - A map where unique superclasses or interfaces are associated with a specific implementing component.
|
||||||
|
*
|
||||||
|
* The mappings are built using the following data structures:
|
||||||
|
* - A map from superclasses/interfaces to the list of components that implement or extend them.
|
||||||
|
* - A list of non-unique types where multiple components exist for the same superclass or interface.
|
||||||
|
* - A map of unique superclasses/interfaces to their corresponding component.
|
||||||
|
*
|
||||||
|
* This method is a key part of the component scanning and resolution process, facilitating
|
||||||
|
* the identification of potential instantiation ambiguities or conflicts.
|
||||||
*/
|
*/
|
||||||
private void analyzeComponentTypes() {
|
private void analyzeComponentTypes() {
|
||||||
Map<Class<?>, List<Class<?>>> superTypesMap = new HashMap<>();
|
Map<Class<?>, List<Class<?>>> superTypesMap = new HashMap<>();
|
||||||
@ -76,8 +156,23 @@ public class InjectableComponentScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines which @FXMLComponent classes can be instantiated based on known dependencies.
|
* Resolves components from the set of scanned classes that can be instantiated based on their constructors
|
||||||
* It registers valid components and collects errors for components that cannot be instantiated.
|
* and existing known types. The method iteratively processes classes to identify those whose dependencies
|
||||||
|
* can be satisfied, marking them as resolved and registering them alongside their supertypes.
|
||||||
|
*
|
||||||
|
* If progress is made during a pass (i.e., a component is resolved), the process continues until no more
|
||||||
|
* components can be resolved. Any components that remain unresolved are recorded for further inspection.
|
||||||
|
*
|
||||||
|
* The resolved components are stored in a map where each type and its supertypes are associated
|
||||||
|
* with the component class itself. This map allows for subsequent lookups when verifying dependency satisfiability.
|
||||||
|
*
|
||||||
|
* If unresolved components remain after the resolution process, detailed instantiation errors are collected
|
||||||
|
* 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<>();
|
||||||
@ -108,10 +203,11 @@ public class InjectableComponentScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a component along with its unique superclasses and interfaces in the known types map.
|
* Registers the given component class in the provided map. The component is registered along with all of its
|
||||||
|
* accessible superclasses and interfaces, unless those types are identified as non-unique.
|
||||||
*
|
*
|
||||||
* @param component The component class.
|
* @param component the component class to register
|
||||||
* @param knownTypes The map of known instantiable types.
|
* @param knownTypes the map where the component and its types will be registered
|
||||||
*/
|
*/
|
||||||
private void registerComponentWithSuperTypes(Class<?> component, Map<Class<?>, Class<?>> knownTypes) {
|
private void registerComponentWithSuperTypes(Class<?> component, Map<Class<?>, Class<?>> knownTypes) {
|
||||||
knownTypes.put(component, component);
|
knownTypes.put(component, component);
|
||||||
@ -124,11 +220,13 @@ public class InjectableComponentScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a given @FXMLComponent class can be instantiated based on known dependencies.
|
* Determines whether a given class can be instantiated based on its constructors and
|
||||||
|
* the provided known types. A class is considered instantiable if it has a parameterless
|
||||||
|
* constructor or if all the parameter types of its constructors are present in the known types.
|
||||||
*
|
*
|
||||||
* @param component The component class to check.
|
* @param component the class to check for instantiation eligibility
|
||||||
* @param knownTypes Set of known instantiable types.
|
* @param knownTypes the set of types known to be instantiable and available for constructor injection
|
||||||
* @return {@code true} if the component can be instantiated, otherwise {@code false}.
|
* @return true if the class can be instantiated; false otherwise
|
||||||
*/
|
*/
|
||||||
private boolean canInstantiate(Class<?> component, Set<Class<?>> knownTypes) {
|
private boolean canInstantiate(Class<?> component, Set<Class<?>> knownTypes) {
|
||||||
for (Constructor<?> constructor : component.getConstructors()) {
|
for (Constructor<?> constructor : component.getConstructors()) {
|
||||||
@ -141,9 +239,12 @@ public class InjectableComponentScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collects error messages for components that cannot be instantiated and adds them to the error list.
|
* Collects the instantiation errors for a set of unresolved classes and appends
|
||||||
|
* detailed error messages to the internal error list. This method analyzes unresolved
|
||||||
|
* components based on their constructors, determining if all required types are
|
||||||
|
* instantiable or if there are conflicting types that could prevent instantiation.
|
||||||
*
|
*
|
||||||
* @param unresolved Set of components that could not be instantiated.
|
* @param unresolved the set of classes for which instantiation errors are collected
|
||||||
*/
|
*/
|
||||||
private void collectInstantiationErrors(Set<Class<?>> unresolved) {
|
private void collectInstantiationErrors(Set<Class<?>> unresolved) {
|
||||||
for (Class<?> component : unresolved) {
|
for (Class<?> component : unresolved) {
|
||||||
@ -175,10 +276,13 @@ public class InjectableComponentScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a comma-separated list of conflicting types that prevent instantiation.
|
* Identifies and retrieves a comma-separated string of conflicting types for a given component.
|
||||||
|
* A conflicting type is a parameter type in the constructor of the given component
|
||||||
|
* that is already marked as not unique within the application context.
|
||||||
*
|
*
|
||||||
* @param component The component class.
|
* @param component the class for which conflicting types need to be identified.
|
||||||
* @return A string listing the conflicting types.
|
* @return a comma-separated string of fully qualified names of conflicting parameter types,
|
||||||
|
* or an empty string if no conflicts are found.
|
||||||
*/
|
*/
|
||||||
private String getConflictingTypes(Class<?> component) {
|
private String getConflictingTypes(Class<?> component) {
|
||||||
return Arrays.stream(component.getConstructors())
|
return Arrays.stream(component.getConstructors())
|
||||||
@ -189,10 +293,10 @@ public class InjectableComponentScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves all superclasses and interfaces of a given class.
|
* Retrieves all superclasses and interfaces of the specified class, excluding the {@code Object} class.
|
||||||
*
|
*
|
||||||
* @param clazz The class to analyze.
|
* @param clazz the class for which to retrieve all superclasses and interfaces
|
||||||
* @return A set of all superclasses and interfaces of the given class.
|
* @return a set of all superclasses and interfaces implemented by the specified class
|
||||||
*/
|
*/
|
||||||
private Set<Class<?>> getAllSuperTypes(Class<?> clazz) {
|
private Set<Class<?>> getAllSuperTypes(Class<?> clazz) {
|
||||||
Set<Class<?>> result = new HashSet<>();
|
Set<Class<?>> result = new HashSet<>();
|
||||||
@ -208,36 +312,46 @@ public class InjectableComponentScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a map of instantiable @FXMLComponent classes and their associated interfaces/superclasses.
|
* Retrieves a map of classes representing component types to their corresponding instantiable implementations.
|
||||||
*
|
*
|
||||||
* @return A map of instantiable components.
|
* @return A map where the key is a class type and the value is the corresponding class implementation
|
||||||
|
* that can be instantiated.
|
||||||
*/
|
*/
|
||||||
public Map<Class<?>, Class<?>> getInstantiableComponents() {
|
public Map<Class<?>, Class<?>> getInstantiableComponents() {
|
||||||
return instantiableComponents;
|
return instantiableComponents;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the set of non-unique types (superclasses/interfaces with multiple implementations).
|
* Retrieves a set of types that have multiple implementations, making them
|
||||||
|
* non-unique. These types could not be uniquely resolved during the
|
||||||
|
* component scanning and analysis process.
|
||||||
*
|
*
|
||||||
* @return A set of non-unique types.
|
* @return a set of classes representing the types that have multiple
|
||||||
|
* implementations and cannot be resolved uniquely
|
||||||
*/
|
*/
|
||||||
public Set<Class<?>> getNotUniqueTypes() {
|
public Set<Class<?>> getNotUniqueTypes() {
|
||||||
return notUniqueTypes;
|
return notUniqueTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the map of unique types to corresponding @FXMLComponent implementations.
|
* Returns a mapping of types to their unique component implementations.
|
||||||
|
* This map includes only those types that have a single corresponding component
|
||||||
|
* implementation, ensuring no ambiguity in resolution.
|
||||||
*
|
*
|
||||||
* @return A map of unique types.
|
* @return a map where the keys are types (e.g., interfaces or superclasses)
|
||||||
|
* and the values are their unique component implementations.
|
||||||
*/
|
*/
|
||||||
public Map<Class<?>, Class<?>> getUniqueTypeToComponent() {
|
public Map<Class<?>, Class<?>> getUniqueTypeToComponent() {
|
||||||
return uniqueTypeToComponent;
|
return uniqueTypeToComponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of errors encountered during instantiation resolution.
|
* Retrieves a list of error messages recorded during the scanning and analysis process.
|
||||||
|
* The errors typically indicate issues such as components that cannot be instantiated due to
|
||||||
|
* missing dependencies, unresolvable component types, or multiple implementations of a type
|
||||||
|
* that are not uniquely resolved.
|
||||||
*
|
*
|
||||||
* @return A list of error messages.
|
* @return a list of error messages explaining the issues encountered.
|
||||||
*/
|
*/
|
||||||
public List<String> getErrors() {
|
public List<String> getErrors() {
|
||||||
return errors;
|
return errors;
|
||||||
|
|||||||
@ -5,6 +5,22 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that an annotated class is a "component" within a dependency injection framework.
|
||||||
|
* Classes annotated with {@code @Component} are recognized during the component scanning process
|
||||||
|
* as candidates for instantiation and management by the dependency injection framework.
|
||||||
|
*
|
||||||
|
* This annotation is typically used on classes that represent application-specific components,
|
||||||
|
* such as service implementations, controllers, or other objects intended to be instantiated
|
||||||
|
* and injected into other components during runtime.
|
||||||
|
*
|
||||||
|
* The annotation must be placed at the type level, and it is retained at runtime
|
||||||
|
* to allow for reflection-based scanning of classes within specified packages.
|
||||||
|
*
|
||||||
|
* Usage of this annotation assumes that there exists a component scanning mechanism
|
||||||
|
* that processes annotated classes and identifies their roles, dependencies, and hierarchy
|
||||||
|
* within the application's structure.
|
||||||
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public @interface Component {
|
public @interface Component {
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target(ElementType.TYPE)
|
|
||||||
public @interface Config {
|
|
||||||
String value() default "";
|
|
||||||
}
|
|
||||||
69
core/src/main/java/de/neitzel/core/sound/ToneGenerator.java
Normal file
69
core/src/main/java/de/neitzel/core/sound/ToneGenerator.java
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package de.neitzel.core.sound;
|
||||||
|
|
||||||
|
import javax.sound.sampled.AudioFormat;
|
||||||
|
import javax.sound.sampled.AudioSystem;
|
||||||
|
import javax.sound.sampled.LineUnavailableException;
|
||||||
|
import javax.sound.sampled.SourceDataLine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ToneGenerator class provides methods to generate and play tones.
|
||||||
|
* It allows playing tones based on frequency or predefined tone names.
|
||||||
|
*/
|
||||||
|
public class ToneGenerator {
|
||||||
|
/**
|
||||||
|
* Plays a tone based on a predefined tone name for a specified duration.
|
||||||
|
*
|
||||||
|
* @param tone The name of the tone to play. Must correspond to a predefined tone in the tone table.
|
||||||
|
* @param duration The duration of the tone in milliseconds.
|
||||||
|
* @throws LineUnavailableException If a line for audio playback cannot be opened.
|
||||||
|
*/
|
||||||
|
public static void playTone(String tone, int duration) throws LineUnavailableException {
|
||||||
|
playTone(ToneTable.getFrequency(tone), duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays a tone at the specified frequency and duration.
|
||||||
|
*
|
||||||
|
* @param frequency the frequency of the tone in Hertz (Hz)
|
||||||
|
* @param duration the duration of the tone in milliseconds
|
||||||
|
* @throws LineUnavailableException if the audio line cannot be opened
|
||||||
|
*/
|
||||||
|
public static void playTone(double frequency, int duration) throws LineUnavailableException {
|
||||||
|
// Get the audio format
|
||||||
|
AudioFormat format = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 16, 2, 4, 44100, false);
|
||||||
|
|
||||||
|
// Open the audio line
|
||||||
|
try (SourceDataLine line = AudioSystem.getSourceDataLine(format)) {
|
||||||
|
|
||||||
|
line.open(format);
|
||||||
|
line.start();
|
||||||
|
|
||||||
|
// Generate the tone data
|
||||||
|
byte[] toneBuffer = new byte[2 * duration * 44100 / 1000];
|
||||||
|
for (int i = 0; i < toneBuffer.length; i += 2) {
|
||||||
|
double angle = i / (44100.0 / frequency) * 2.0 * Math.PI;
|
||||||
|
short sample = (short) (Math.sin(angle) * 32767);
|
||||||
|
toneBuffer[i] = (byte) (sample & 0xFF);
|
||||||
|
toneBuffer[i + 1] = (byte) ((sample >> 8) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Play the tone
|
||||||
|
line.write(toneBuffer, 0, toneBuffer.length);
|
||||||
|
line.drain();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays one or more specified tones for a given duration.
|
||||||
|
* Each tone is played sequentially in the order they are provided.
|
||||||
|
*
|
||||||
|
* @param duration the duration of each tone in milliseconds
|
||||||
|
* @param tones the names of the tones to play, specified as a variable-length argument
|
||||||
|
* @throws LineUnavailableException if the audio line cannot be opened or accessed
|
||||||
|
*/
|
||||||
|
public static void playTone(int duration, String... tones) throws LineUnavailableException {
|
||||||
|
for (String tone : tones) {
|
||||||
|
playTone(tone, duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
198
core/src/main/java/de/neitzel/core/sound/ToneTable.java
Normal file
198
core/src/main/java/de/neitzel/core/sound/ToneTable.java
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
package de.neitzel.core.sound;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ToneTable class provides a static lookup tool for associating musical tone names
|
||||||
|
* with their corresponding frequencies in hertz (Hz). It allows users to
|
||||||
|
* retrieve the frequency of a tone based on its standard notation name,
|
||||||
|
* such as "C3", "A4", or "G#5".
|
||||||
|
*
|
||||||
|
* This class can be particularly useful in applications related to sound synthesis,
|
||||||
|
* music theory, signal processing, and other audio-related domains that require precise
|
||||||
|
* frequency information for specific tones.
|
||||||
|
*/
|
||||||
|
public class ToneTable {
|
||||||
|
/**
|
||||||
|
* A static map that associates written tone names with their corresponding frequencies in hertz (Hz).
|
||||||
|
* The keys represent tone names (e.g., "C4", "D#5"), and the values are their respective frequencies.
|
||||||
|
* This map serves as a reference for converting tone names into their precise frequency values,
|
||||||
|
* which are used in applications such as tone generation or audio playback.
|
||||||
|
*
|
||||||
|
* The map is a crucial component of the ToneTable class, providing quick lookup of frequencies
|
||||||
|
* for predefined tone names.
|
||||||
|
*/
|
||||||
|
private static final HashMap<String, Double> toneMap = new HashMap<>();
|
||||||
|
static {
|
||||||
|
toneMap.put("C0", 16.35);
|
||||||
|
toneMap.put("C#0", 17.32);
|
||||||
|
toneMap.put("Db0", 17.32);
|
||||||
|
toneMap.put("D0", 18.35);
|
||||||
|
toneMap.put("D#0", 19.45);
|
||||||
|
toneMap.put("Eb0", 19.45);
|
||||||
|
toneMap.put("E0", 20.60);
|
||||||
|
toneMap.put("F0", 21.83);
|
||||||
|
toneMap.put("F#0", 23.12);
|
||||||
|
toneMap.put("Gb0", 23.12);
|
||||||
|
toneMap.put("G0", 24.50);
|
||||||
|
toneMap.put("G#0", 25.96);
|
||||||
|
toneMap.put("Ab0", 25.96);
|
||||||
|
toneMap.put("A0", 27.50);
|
||||||
|
toneMap.put("A#0", 29.14);
|
||||||
|
toneMap.put("Bb0", 29.14);
|
||||||
|
toneMap.put("B0", 30.87);
|
||||||
|
toneMap.put("C1", 32.70);
|
||||||
|
toneMap.put("C#1", 34.65);
|
||||||
|
toneMap.put("Db1", 34.65);
|
||||||
|
toneMap.put("D1", 36.71);
|
||||||
|
toneMap.put("D#1", 38.89);
|
||||||
|
toneMap.put("Eb1", 38.89);
|
||||||
|
toneMap.put("E1", 41.20);
|
||||||
|
toneMap.put("F1", 43.65);
|
||||||
|
toneMap.put("F#1", 46.25);
|
||||||
|
toneMap.put("Gb1", 46.25);
|
||||||
|
toneMap.put("G1", 49.00);
|
||||||
|
toneMap.put("G#1", 51.91);
|
||||||
|
toneMap.put("Ab1", 51.91);
|
||||||
|
toneMap.put("A1", 55.00);
|
||||||
|
toneMap.put("A#1", 58.27);
|
||||||
|
toneMap.put("Bb1", 58.27);
|
||||||
|
toneMap.put("B1", 61.74);
|
||||||
|
toneMap.put("C2", 65.41);
|
||||||
|
toneMap.put("C#2", 69.30);
|
||||||
|
toneMap.put("Db2", 69.30);
|
||||||
|
toneMap.put("D2", 73.42);
|
||||||
|
toneMap.put("D#2", 77.78);
|
||||||
|
toneMap.put("Eb2", 77.78);
|
||||||
|
toneMap.put("E2", 82.41);
|
||||||
|
toneMap.put("F2", 87.31);
|
||||||
|
toneMap.put("F#2", 92.50);
|
||||||
|
toneMap.put("Gb2", 92.50);
|
||||||
|
toneMap.put("G2", 98.00);
|
||||||
|
toneMap.put("G#2", 103.83);
|
||||||
|
toneMap.put("Ab2", 103.83);
|
||||||
|
toneMap.put("A2", 110.00);
|
||||||
|
toneMap.put("A#2", 116.54);
|
||||||
|
toneMap.put("Bb2", 116.54);
|
||||||
|
toneMap.put("B2", 123.47);
|
||||||
|
toneMap.put("C3", 130.81);
|
||||||
|
toneMap.put("C#3", 138.59);
|
||||||
|
toneMap.put("Db3", 138.59);
|
||||||
|
toneMap.put("D3", 146.83);
|
||||||
|
toneMap.put("D#3", 155.56);
|
||||||
|
toneMap.put("Eb3", 155.56);
|
||||||
|
toneMap.put("E3", 164.81);
|
||||||
|
toneMap.put("F3", 174.61);
|
||||||
|
toneMap.put("F#3", 185.00);
|
||||||
|
toneMap.put("Gb3", 185.00);
|
||||||
|
toneMap.put("G3", 196.00);
|
||||||
|
toneMap.put("G#3", 207.65);
|
||||||
|
toneMap.put("Ab3", 207.65);
|
||||||
|
toneMap.put("A3", 220.00);
|
||||||
|
toneMap.put("A#3", 233.08);
|
||||||
|
toneMap.put("Bb3", 233.08);
|
||||||
|
toneMap.put("B3", 246.94);
|
||||||
|
toneMap.put("C4", 261.63);
|
||||||
|
toneMap.put("C#4", 277.18);
|
||||||
|
toneMap.put("Db4", 277.18);
|
||||||
|
toneMap.put("D4", 293.66);
|
||||||
|
toneMap.put("D#4", 311.13);
|
||||||
|
toneMap.put("Eb4", 311.13);
|
||||||
|
toneMap.put("E4", 329.63);
|
||||||
|
toneMap.put("F4", 349.23);
|
||||||
|
toneMap.put("F#4", 369.99);
|
||||||
|
toneMap.put("Gb4", 369.99);
|
||||||
|
toneMap.put("G4", 392.00);
|
||||||
|
toneMap.put("G#4", 415.30);
|
||||||
|
toneMap.put("Ab4", 415.30);
|
||||||
|
toneMap.put("A4", 440.00);
|
||||||
|
toneMap.put("A#4", 466.16);
|
||||||
|
toneMap.put("Bb4", 466.16);
|
||||||
|
toneMap.put("B4", 493.88);
|
||||||
|
toneMap.put("C5", 523.25);
|
||||||
|
toneMap.put("C#5", 554.37);
|
||||||
|
toneMap.put("Db5", 554.37);
|
||||||
|
toneMap.put("D5", 587.33);
|
||||||
|
toneMap.put("D#5", 622.25);
|
||||||
|
toneMap.put("Eb5", 622.25);
|
||||||
|
toneMap.put("E5", 659.25);
|
||||||
|
toneMap.put("F5", 698.46);
|
||||||
|
toneMap.put("F#5", 739.99);
|
||||||
|
toneMap.put("Gb5", 739.99);
|
||||||
|
toneMap.put("G5", 783.99);
|
||||||
|
toneMap.put("G#5", 830.61);
|
||||||
|
toneMap.put("Ab5", 830.61);
|
||||||
|
toneMap.put("A5", 880.00);
|
||||||
|
toneMap.put("A#5", 932.33);
|
||||||
|
toneMap.put("Bb5", 932.33);
|
||||||
|
toneMap.put("B5", 987.77);
|
||||||
|
toneMap.put("C6", 1046.50);
|
||||||
|
toneMap.put("C#6", 1108.73);
|
||||||
|
toneMap.put("Db6", 1108.73);
|
||||||
|
toneMap.put("D6", 1174.66);
|
||||||
|
toneMap.put("D#6", 1244.51);
|
||||||
|
toneMap.put("Eb6", 1244.51);
|
||||||
|
toneMap.put("E6", 1318.51);
|
||||||
|
toneMap.put("F6", 1396.91);
|
||||||
|
toneMap.put("F#6", 1479.98);
|
||||||
|
toneMap.put("Gb6", 1479.98);
|
||||||
|
toneMap.put("G6", 1567.98);
|
||||||
|
toneMap.put("G#6", 1661.22);
|
||||||
|
toneMap.put("Ab6", 1661.22);
|
||||||
|
toneMap.put("A6", 1760.00);
|
||||||
|
toneMap.put("A#6", 1864.66);
|
||||||
|
toneMap.put("Bb6", 1864.66);
|
||||||
|
toneMap.put("B6", 1975.53);
|
||||||
|
toneMap.put("C7", 2093.00);
|
||||||
|
toneMap.put("C#7", 2217.46);
|
||||||
|
toneMap.put("Db7", 2217.46);
|
||||||
|
toneMap.put("D7", 2349.32);
|
||||||
|
toneMap.put("D#7", 2489.02);
|
||||||
|
toneMap.put("Eb7", 2489.02);
|
||||||
|
toneMap.put("E7", 2637.02);
|
||||||
|
toneMap.put("F7", 2793.83);
|
||||||
|
toneMap.put("F#7", 2959.96);
|
||||||
|
toneMap.put("Gb7", 2959.96);
|
||||||
|
toneMap.put("G7", 3135.96);
|
||||||
|
toneMap.put("G#7", 3322.44);
|
||||||
|
toneMap.put("Ab7", 3322.44);
|
||||||
|
toneMap.put("A7", 3520.00);
|
||||||
|
toneMap.put("A#7", 3729.31);
|
||||||
|
toneMap.put("Bb7", 3729.31);
|
||||||
|
toneMap.put("B7", 3951.07);
|
||||||
|
toneMap.put("C8", 4186.01);
|
||||||
|
toneMap.put("C#8", 4434.92);
|
||||||
|
toneMap.put("Db8", 4434.92);
|
||||||
|
toneMap.put("D8", 4698.63);
|
||||||
|
toneMap.put("D#8", 4978.03);
|
||||||
|
toneMap.put("Eb8", 4978.03);
|
||||||
|
toneMap.put("E8", 5274.04);
|
||||||
|
toneMap.put("F8", 5587.65);
|
||||||
|
toneMap.put("F#8", 5919.91);
|
||||||
|
toneMap.put("Gb8", 5919.91);
|
||||||
|
toneMap.put("G8", 6271.93);
|
||||||
|
toneMap.put("G#8", 6644.88);
|
||||||
|
toneMap.put("Ab8", 6644.88);
|
||||||
|
toneMap.put("A8", 7040.00);
|
||||||
|
toneMap.put("A#8", 7458.62);
|
||||||
|
toneMap.put("Bb8", 7458.62);
|
||||||
|
toneMap.put("B8", 7902.13);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the frequency of the tone based on the given tone name.
|
||||||
|
* If the tone name exists in the tone map, its corresponding frequency is returned.
|
||||||
|
* Otherwise, an exception is thrown.
|
||||||
|
*
|
||||||
|
* @param toneName the name of the tone whose frequency is to be retrieved
|
||||||
|
* @return the frequency associated with the specified tone name
|
||||||
|
* @throws IllegalArgumentException if the tone name is not found in the tone map
|
||||||
|
*/
|
||||||
|
public static double getFrequency(String toneName) {
|
||||||
|
if (toneMap.containsKey(toneName)) {
|
||||||
|
return toneMap.get(toneName);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unknown tone name: " + toneName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,14 +15,22 @@ import java.util.stream.Stream;
|
|||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class to build and execute parameterized SQL queries with factory-based result mapping.
|
* A generic class for managing SQL queries and their execution using JDBC.
|
||||||
|
* The class supports parameterized queries, allows loading queries from resources,
|
||||||
|
* and facilitates streaming of result sets.
|
||||||
|
*
|
||||||
|
* @param <T> The type of the objects returned by the query.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class Query<T> {
|
public class Query<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Integer Factory that can be used to read Integer Values of a ResultSet that only has Integers (e.g. a min oder max query)
|
* A factory function that extracts an Integer from the first column of a given ResultSet.
|
||||||
|
* <p>
|
||||||
|
* This function is designed to facilitate the conversion of a ResultSet row into an Integer
|
||||||
|
* by accessing the value in the first column of the result set. If an SQL exception is encountered
|
||||||
|
* during the retrieval of the integer value, it is wrapped and re-thrown as a runtime exception.
|
||||||
*/
|
*/
|
||||||
public static final Function<ResultSet, Integer> INTEGER_FACTORY = rs -> {
|
public static final Function<ResultSet, Integer> INTEGER_FACTORY = rs -> {
|
||||||
try {
|
try {
|
||||||
@ -32,11 +40,42 @@ public class Query<T> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a database connection instance.
|
||||||
|
* This connection is used to interact with a database
|
||||||
|
* through executing queries and retrieving results.
|
||||||
|
* It is immutable and initialized once.
|
||||||
|
*/
|
||||||
private final Connection connection;
|
private final Connection connection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of ParameterSetter objects used to configure parameters.
|
||||||
|
* This collection serves as a storage for managing parameter-setting logic dynamically.
|
||||||
|
* It is initialized as an empty ArrayList and is immutable since it is declared as final.
|
||||||
|
*/
|
||||||
private final List<ParameterSetter> parameterSetters = new ArrayList<>();
|
private final List<ParameterSetter> parameterSetters = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A functional interface representing a factory method used to produce instances of type T.
|
||||||
|
* This factory is typically implemented to map rows from a {@link ResultSet} to objects of type T.
|
||||||
|
* The input parameter is a {@link ResultSet}, which provides access to database query results.
|
||||||
|
* The resulting output is an instance of type T created based on the data in the provided ResultSet.
|
||||||
|
*/
|
||||||
private Function<ResultSet, T> factory;
|
private Function<ResultSet, T> factory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the text of a query.
|
||||||
|
* This variable stores the query string used for processing or execution purposes within the application.
|
||||||
|
*/
|
||||||
private String queryText;
|
private String queryText;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the given SQL query using the provided database connection.
|
||||||
|
*
|
||||||
|
* @param connection the database connection to be used for executing the query
|
||||||
|
* @param query the SQL query to be executed
|
||||||
|
* @throws SQLException if a database access error occurs or the query execution fails
|
||||||
|
*/
|
||||||
public static void execute(final Connection connection, final String query) throws SQLException {
|
public static void execute(final Connection connection, final String query) throws SQLException {
|
||||||
new Query<Object>(connection)
|
new Query<Object>(connection)
|
||||||
.setQueryText(query)
|
.setQueryText(query)
|
||||||
@ -44,7 +83,10 @@ public class Query<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sets the query.
|
* Sets the query text for the current query instance.
|
||||||
|
*
|
||||||
|
* @param queryText the text of the query to be set
|
||||||
|
* @return the current instance of the query
|
||||||
*/
|
*/
|
||||||
public Query<T> setQueryText(final String queryText) {
|
public Query<T> setQueryText(final String queryText) {
|
||||||
this.queryText = queryText;
|
this.queryText = queryText;
|
||||||
@ -52,7 +94,11 @@ public class Query<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads the query content from a resource URL.
|
* Sets the query resource by loading its content from the given resource path.
|
||||||
|
* The resource content is read and stored as the query text for the query object.
|
||||||
|
*
|
||||||
|
* @param resourcePath the path to the resource file containing the query text
|
||||||
|
* @return the current Query object with the loaded query text
|
||||||
*/
|
*/
|
||||||
public Query<T> setQueryResource(final String resourcePath) {
|
public Query<T> setQueryResource(final String resourcePath) {
|
||||||
log.info("loading resource: {}", resourcePath);
|
log.info("loading resource: {}", resourcePath);
|
||||||
@ -65,7 +111,11 @@ public class Query<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces placeholders in the query.
|
* Replaces all occurrences of the specified placeholder in the query text with the provided value.
|
||||||
|
*
|
||||||
|
* @param placeholder the placeholder string to be replaced in the query text
|
||||||
|
* @param value the value to replace the placeholder with
|
||||||
|
* @return the updated Query instance with the modified query text
|
||||||
*/
|
*/
|
||||||
public Query<T> replace(final String placeholder, final String value) {
|
public Query<T> replace(final String placeholder, final String value) {
|
||||||
this.queryText = this.queryText.replace(placeholder, value);
|
this.queryText = this.queryText.replace(placeholder, value);
|
||||||
@ -74,13 +124,13 @@ public class Query<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds another parameter setter
|
* Adds a parameter to the query at the specified index with a custom setter function.
|
||||||
*
|
*
|
||||||
* @param index Index of parameter in prepared statement
|
* @param index The position of the parameter in the query starting from 1.
|
||||||
* @param value Value to set.
|
* @param value The value of the parameter to be set.
|
||||||
* @param setter Setter to use on prepared statement.
|
* @param setter A TriSqlFunction that defines how to set the parameter in the PreparedStatement.
|
||||||
* @param <X> Type of the Parameter.
|
* @param <X> The type of the parameter value.
|
||||||
* @return The Query.
|
* @return The current query instance for method chaining.
|
||||||
*/
|
*/
|
||||||
public <X> Query<T> addParameter(final int index, final X value, final TriSqlFunction<PreparedStatement, Integer, X> setter) {
|
public <X> Query<T> addParameter(final int index, final X value, final TriSqlFunction<PreparedStatement, Integer, X> setter) {
|
||||||
parameterSetters.add(ps -> setter.apply(ps, index, value));
|
parameterSetters.add(ps -> setter.apply(ps, index, value));
|
||||||
@ -88,10 +138,10 @@ public class Query<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a ParameterSetter/
|
* Adds a parameter to the query using the specified ParameterSetter.
|
||||||
*
|
*
|
||||||
* @param setter Setter for a parameter.
|
* @param setter the ParameterSetter used to configure the parameter
|
||||||
* @return the Qyery.
|
* @return the current Query instance for method chaining
|
||||||
*/
|
*/
|
||||||
public Query<T> addParameter(ParameterSetter setter) {
|
public Query<T> addParameter(ParameterSetter setter) {
|
||||||
parameterSetters.add(setter);
|
parameterSetters.add(setter);
|
||||||
@ -99,7 +149,10 @@ public class Query<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a result factory by name.
|
* Sets the factory function used to convert a {@link ResultSet} into an instance of type {@code T}.
|
||||||
|
*
|
||||||
|
* @param factory the function responsible for mapping a {@code ResultSet} to an object of type {@code T}
|
||||||
|
* @return the current {@code Query} instance with the specified factory function set
|
||||||
*/
|
*/
|
||||||
public Query<T> factory(final Function<ResultSet, T> factory) {
|
public Query<T> factory(final Function<ResultSet, T> factory) {
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
@ -107,7 +160,11 @@ public class Query<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the query and returns a stream of results. This Stream must be closed so that the underlying ResultSet is closed.
|
* Creates a {@link Stream} from the result set obtained by executing a prepared statement.
|
||||||
|
* The result set is wrapped in a {@link TrimmingResultSet} to handle data trimming.
|
||||||
|
*
|
||||||
|
* @return a {@link Stream} of type T populated with the query results processed using the specified factory
|
||||||
|
* @throws SQLException if an SQL error occurs while executing the prepared statement or creating the stream
|
||||||
*/
|
*/
|
||||||
private Stream<T> stream() throws SQLException {
|
private Stream<T> stream() throws SQLException {
|
||||||
ResultSet rs = createPreparedStatement().executeQuery();
|
ResultSet rs = createPreparedStatement().executeQuery();
|
||||||
@ -116,10 +173,11 @@ public class Query<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a PreparedStatement and applies all Parameter.
|
* Creates and returns a PreparedStatement object configured with the query text
|
||||||
|
* and parameters defined for the current instance.
|
||||||
*
|
*
|
||||||
* @return the created PreparedStatement
|
* @return a PreparedStatement object initialized with the query and parameter values
|
||||||
* @throws SQLException thrown if the PreparedStatement could not be created.
|
* @throws SQLException if a database access error occurs or the PreparedStatement cannot be created
|
||||||
*/
|
*/
|
||||||
private PreparedStatement createPreparedStatement() throws SQLException {
|
private PreparedStatement createPreparedStatement() throws SQLException {
|
||||||
PreparedStatement ps = connection.prepareStatement(queryText);
|
PreparedStatement ps = connection.prepareStatement(queryText);
|
||||||
@ -130,9 +188,12 @@ public class Query<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes a query wich has no result.
|
* Executes the SQL statement represented by the PreparedStatement created in
|
||||||
|
* the method {@code createPreparedStatement()}. This method is used to perform
|
||||||
|
* database operations such as updates or other actions that do not produce a
|
||||||
|
* result set.
|
||||||
*
|
*
|
||||||
* @throws SQLException Thrown when query cannot be executed.
|
* @throws SQLException if a database access error occurs or the SQL statement is invalid.
|
||||||
*/
|
*/
|
||||||
public void execute() throws SQLException {
|
public void execute() throws SQLException {
|
||||||
try (PreparedStatement ps = createPreparedStatement()) {
|
try (PreparedStatement ps = createPreparedStatement()) {
|
||||||
@ -141,17 +202,26 @@ public class Query<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Streams the results of a ResultSet using a factory that creates instances for each row of the ResultSet
|
* Converts a {@link ResultSet} into a {@link Stream} of objects created using the provided {@link Function}
|
||||||
|
* factory. The stream ensures resources such as the {@code ResultSet} and its associated statement are
|
||||||
|
* closed when the stream is closed.
|
||||||
*
|
*
|
||||||
* @param rs ResultSet to stream from
|
* @param rs the {@link ResultSet} to be transformed into a stream
|
||||||
* @param factory Factory to create instances of T.
|
* @param factory a {@link Function} that maps rows of the {@link ResultSet} to objects of type T
|
||||||
* @return Stream of T instances.
|
* @return a {@link Stream} of objects of type T created from the {@code ResultSet} rows
|
||||||
*/
|
*/
|
||||||
private Stream<T> streamFromResultSet(final ResultSet rs, final Function<ResultSet, T> factory) {
|
private Stream<T> streamFromResultSet(final ResultSet rs, final Function<ResultSet, T> factory) {
|
||||||
Iterator<T> iterator = new Iterator<>() {
|
Iterator<T> iterator = new Iterator<>() {
|
||||||
private boolean hasNextChecked = false;
|
private boolean hasNextChecked = false;
|
||||||
private boolean hasNext;
|
private boolean hasNext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the iteration has more elements. This method verifies if the
|
||||||
|
* underlying {@link ResultSet} contains another row that has not yet been processed.
|
||||||
|
*
|
||||||
|
* @return {@code true} if there are more elements in the {@link ResultSet},
|
||||||
|
* otherwise {@code false}.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public boolean hasNext() {
|
public boolean hasNext() {
|
||||||
@ -162,6 +232,14 @@ public class Query<T> {
|
|||||||
return hasNext;
|
return hasNext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next element in the iteration. This method applies the provided
|
||||||
|
* factory function to the current row of the {@link ResultSet} to create an object of type T.
|
||||||
|
* It throws {@link NoSuchElementException} if there are no more elements to iterate.
|
||||||
|
*
|
||||||
|
* @return the next element of type T as created by the factory function from the current row of the {@link ResultSet}
|
||||||
|
* @throws NoSuchElementException if the iteration has no more elements
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public T next() {
|
public T next() {
|
||||||
if (!hasNext()) {
|
if (!hasNext()) {
|
||||||
@ -187,6 +265,16 @@ public class Query<T> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a managed stream to the given handler and ensures proper resource closing after use.
|
||||||
|
* A {@code Stream} is opened and passed to the specified handler as an argument.
|
||||||
|
* The stream will be automatically closed when the handler finishes execution or throws an exception.
|
||||||
|
*
|
||||||
|
* @param handler a function which receives a {@code Stream<T>} and processes it, returning a result of type {@code R}.
|
||||||
|
* @param <R> the type of the result produced by the handler.
|
||||||
|
* @return the result returned by the handler after processing the stream.
|
||||||
|
* @throws SQLException if an SQL error occurs during the creation or handling of the stream.
|
||||||
|
*/
|
||||||
public <R> R withStream(Function<Stream<T>, R> handler) throws SQLException {
|
public <R> R withStream(Function<Stream<T>, R> handler) throws SQLException {
|
||||||
try (Stream<T> stream = stream()) {
|
try (Stream<T> stream = stream()) {
|
||||||
return handler.apply(stream);
|
return handler.apply(stream);
|
||||||
@ -194,37 +282,44 @@ public class Query<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function with three parameters and no result which can throw an SQLException
|
* Represents a functional interface that accepts three input arguments and allows for operations
|
||||||
|
* that can throw an {@link SQLException}. This can be useful in scenarios where SQL-related
|
||||||
|
* logic requires processing with three parameters.
|
||||||
*
|
*
|
||||||
* @param <T> type of first parameter
|
* @param <T> the type of the first input to the operation
|
||||||
* @param <U> type of second parameter
|
* @param <U> the type of the second input to the operation
|
||||||
* @param <V> type of third parameter
|
* @param <V> the type of the third input to the operation
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface TriSqlFunction<T, U, V> {
|
public interface TriSqlFunction<T, U, V> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls the thre parameter function.
|
* Executes an operation on three input arguments and allows for the operation to throw
|
||||||
|
* an {@link SQLException}. This method is intended to facilitate SQL-related processes
|
||||||
|
* where three parameters are involved.
|
||||||
*
|
*
|
||||||
* @param t first parameter.
|
* @param t the first input argument
|
||||||
* @param u second parameter.
|
* @param u the second input argument
|
||||||
* @param v third parameter.
|
* @param v the third input argument
|
||||||
* @throws SQLException Function could throw an SQLException.
|
* @throws SQLException if an SQL error occurs during execution
|
||||||
*/
|
*/
|
||||||
void apply(T t, U u, V v) throws SQLException;
|
void apply(T t, U u, V v) throws SQLException;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ParameterSetter is a function Interface that could be used to alter a PreparedStatement.
|
* Functional interface designed to handle operations on a PreparedStatement.
|
||||||
|
* Represents a single abstract method that performs specific work on the
|
||||||
|
* provided PreparedStatement instance.
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface ParameterSetter {
|
public interface ParameterSetter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the required work on PreparedStatement.
|
* Applies an operation to the given PreparedStatement object.
|
||||||
|
* This method is intended to set parameters or perform other modifications on a PreparedStatement.
|
||||||
*
|
*
|
||||||
* @param ps PreparedStatement to work on.
|
* @param ps the PreparedStatement instance to be modified or configured
|
||||||
* @throws SQLException Could be thrown when working on PreparedStatement.
|
* @throws SQLException if a database access error occurs while applying the operation
|
||||||
*/
|
*/
|
||||||
void apply(PreparedStatement ps) throws SQLException;
|
void apply(PreparedStatement ps) throws SQLException;
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
46
encryption/pom.xml
Normal file
46
encryption/pom.xml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?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>encryption</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>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.idealista</groupId>
|
||||||
|
<artifactId>format-preserving-encryption</artifactId>
|
||||||
|
<version>1.0.0</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,41 @@
|
|||||||
|
package de.neitzel.encryption;
|
||||||
|
|
||||||
|
import com.idealista.fpe.config.Alphabet;
|
||||||
|
|
||||||
|
public abstract class BaseAlphabet implements Alphabet {
|
||||||
|
/**
|
||||||
|
* Replaces illegal characters of a string with a given replacement character
|
||||||
|
* @param text Text to check.
|
||||||
|
* @param replacement Replacement character.
|
||||||
|
* @return String in which all characters was replaced.
|
||||||
|
*/
|
||||||
|
public String replaceIllegalCharacters(String text, char replacement) {
|
||||||
|
// Validate
|
||||||
|
if (!isValidCharacter(replacement)) throw new IllegalArgumentException("replacement");
|
||||||
|
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
for (char ch: text.toCharArray()) {
|
||||||
|
if (isValidCharacter(ch)){
|
||||||
|
result.append(ch);
|
||||||
|
} else {
|
||||||
|
result.append(replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a given Character is part of the alphabet.
|
||||||
|
* @param character Character to check.
|
||||||
|
* @return true if character is valid, else false.
|
||||||
|
*/
|
||||||
|
public boolean isValidCharacter(char character) {
|
||||||
|
// Compare with all characters
|
||||||
|
for (char ch : availableCharacters()) {
|
||||||
|
if (ch == character) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Character not found.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,140 @@
|
|||||||
|
package de.neitzel.encryption;
|
||||||
|
|
||||||
|
import com.idealista.fpe.FormatPreservingEncryption;
|
||||||
|
import com.idealista.fpe.builder.FormatPreservingEncryptionBuilder;
|
||||||
|
import com.idealista.fpe.config.Domain;
|
||||||
|
import com.idealista.fpe.config.LengthRange;
|
||||||
|
|
||||||
|
import javax.crypto.KeyGenerator;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to deal with FF1Encryption using com.idealista:format-preserving-encryption:1.0.0.
|
||||||
|
*/
|
||||||
|
public class FF1Encryption {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key to use for all encryption / unencryption
|
||||||
|
*/
|
||||||
|
private byte[] key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should to small strings be ignored?
|
||||||
|
*
|
||||||
|
* If this is set to true, small strings (less than 2 characters) will not be encrypted!
|
||||||
|
*/
|
||||||
|
private boolean ignoreToShortStrings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tweak to use for encryption
|
||||||
|
*/
|
||||||
|
private byte[] tweak;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Domain used for encryption
|
||||||
|
*/
|
||||||
|
private Domain domain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format preserving encryption to use.
|
||||||
|
*/
|
||||||
|
private FormatPreservingEncryption encryption;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum length of a string.
|
||||||
|
*/
|
||||||
|
private int minLength;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of FF1Encryption
|
||||||
|
* @param key AES key to use.
|
||||||
|
* @param tweak tweak to use for encryption / decryption
|
||||||
|
* @param ignoreToShortStrings Ignore strings that are to short.
|
||||||
|
*/
|
||||||
|
public FF1Encryption(byte[] key, byte[] tweak, boolean ignoreToShortStrings) {
|
||||||
|
this(key, tweak, ignoreToShortStrings, new MainDomain(), 2, 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of FF1Encryption
|
||||||
|
* @param key AES key to use.
|
||||||
|
* @param tweak tweak to use for encryption / decryption
|
||||||
|
* @param ignoreToShortStrings Ignore strings that are to short.
|
||||||
|
* @param domain Domain to use for encryption
|
||||||
|
* @param minLength Minimum length of string.
|
||||||
|
* @param maxLength Maximum length of string.
|
||||||
|
*/
|
||||||
|
public FF1Encryption(byte[] key, byte[] tweak, boolean ignoreToShortStrings, Domain domain, int minLength, int maxLength) {
|
||||||
|
this.key = key;
|
||||||
|
this.tweak = tweak;
|
||||||
|
this.ignoreToShortStrings = ignoreToShortStrings;
|
||||||
|
this.domain = domain;
|
||||||
|
this.minLength = minLength;
|
||||||
|
|
||||||
|
encryption = FormatPreservingEncryptionBuilder
|
||||||
|
.ff1Implementation().withDomain(domain)
|
||||||
|
.withDefaultPseudoRandomFunction(key)
|
||||||
|
.withLengthRange(new LengthRange(minLength, maxLength))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts a given text.
|
||||||
|
* @param plainText Unencrypted text.
|
||||||
|
* @return Encrypted text.
|
||||||
|
*/
|
||||||
|
public String encrypt(String plainText) {
|
||||||
|
// Handle null
|
||||||
|
if (plainText == null) return null;
|
||||||
|
|
||||||
|
// Handle short strings
|
||||||
|
if (plainText.length() < minLength && ignoreToShortStrings) return plainText;
|
||||||
|
|
||||||
|
// Return encrypted text.
|
||||||
|
return encryption.encrypt(plainText, tweak);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt a given text.
|
||||||
|
* @param cipherText Encrypted text.
|
||||||
|
* @return Decrypted text.
|
||||||
|
*/
|
||||||
|
public String decrypt(String cipherText) {
|
||||||
|
// Handle null
|
||||||
|
if (cipherText == null) return null;
|
||||||
|
|
||||||
|
// Handle short strings
|
||||||
|
if (cipherText.length() < minLength && ignoreToShortStrings) return cipherText;
|
||||||
|
|
||||||
|
// Return decrypted text.
|
||||||
|
return encryption.decrypt(cipherText, tweak);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new AES key woth the given length.
|
||||||
|
* @param length Length of the key in bits. Must be 128, 192 or 256 bits
|
||||||
|
* @return Byte array of the new key.
|
||||||
|
*/
|
||||||
|
public static byte[] createNewKey(int length) {
|
||||||
|
try {
|
||||||
|
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||||
|
keyGen.init(length); // for example
|
||||||
|
return keyGen.generateKey().getEncoded();
|
||||||
|
} catch (NoSuchAlgorithmException ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new tweak of the given length.
|
||||||
|
* @param length Number of bytes the new teeak should have.
|
||||||
|
* @return byte array with the new tweak.
|
||||||
|
*/
|
||||||
|
public static byte[] createNewTweak(int length) {
|
||||||
|
Random random = new Random();
|
||||||
|
byte[] key = new byte[length];
|
||||||
|
random.nextBytes(key);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package de.neitzel.encryption;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main characters of the Alphabet.
|
||||||
|
*/
|
||||||
|
public class MainAlphabet extends BaseAlphabet {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All characters we want to use.
|
||||||
|
*/
|
||||||
|
private static final char[] ALL_CHARS = new char[] {
|
||||||
|
// lowercase characters
|
||||||
|
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
|
||||||
|
's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||||
|
|
||||||
|
// uppercase characters
|
||||||
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
|
||||||
|
'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||||
|
|
||||||
|
// numbers
|
||||||
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||||
|
|
||||||
|
// special characters
|
||||||
|
'-', '_', '?', ' ', '#', '+', '/', '*', '!', '\"', '§', '$', '%', '&', '(', ')', '=', '@',
|
||||||
|
'€', ',', ';', '.', ':', '<', '>', '|', '\'', '\\', '{', '}', '[', ']',
|
||||||
|
|
||||||
|
// german special characters
|
||||||
|
'ä', 'Ä', 'ö', 'Ö', 'ü', 'Ü', 'ß'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the available characters.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public char[] availableCharacters() {
|
||||||
|
return ALL_CHARS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the radix of the alphabet.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Integer radix() {
|
||||||
|
return ALL_CHARS.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package de.neitzel.encryption;
|
||||||
|
|
||||||
|
import com.idealista.fpe.config.Alphabet;
|
||||||
|
import com.idealista.fpe.config.Domain;
|
||||||
|
import com.idealista.fpe.config.GenericTransformations;
|
||||||
|
|
||||||
|
public class MainDomain implements Domain {
|
||||||
|
|
||||||
|
private Alphabet _alphabet;
|
||||||
|
private GenericTransformations transformations;
|
||||||
|
|
||||||
|
public MainDomain() {
|
||||||
|
_alphabet = new MainAlphabet();
|
||||||
|
transformations = new GenericTransformations(_alphabet.availableCharacters());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Alphabet alphabet() {
|
||||||
|
return _alphabet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] transform(String data) {
|
||||||
|
return transformations.transform(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String transform(int[] data) {
|
||||||
|
return transformations.transform(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
fx/pom.xml
30
fx/pom.xml
@ -21,15 +21,33 @@
|
|||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<!-- JavaFX dependencies -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-base</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-controls</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-graphics</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-fxml</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- NeitzelLib dependencies -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>de.neitzel.lib</groupId>
|
<groupId>de.neitzel.lib</groupId>
|
||||||
<artifactId>core</artifactId>
|
<artifactId>core</artifactId>
|
||||||
<version>1.0-SNAPSHOT</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.reflections</groupId>
|
|
||||||
<artifactId>reflections</artifactId>
|
|
||||||
<version>${reflections.version}</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
49
net/pom.xml
Normal file
49
net/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>net</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 -->
|
||||||
|
<javax.mail.version>1.6.2</javax.mail.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sun.mail</groupId>
|
||||||
|
<artifactId>javax.mail</artifactId>
|
||||||
|
<version>${javax.mail.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>
|
||||||
128
net/src/main/java/de/neitzel/net/imap/ImapAccount.java
Normal file
128
net/src/main/java/de/neitzel/net/imap/ImapAccount.java
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package de.neitzel.net.imap;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an IMAP account with server configuration, user credentials, and protocol details.
|
||||||
|
* This class provides methods to set or get the account details and overrides methods for string
|
||||||
|
* representation, equality checks, and hashing.
|
||||||
|
*/
|
||||||
|
public class ImapAccount {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the server address for the IMAP account.
|
||||||
|
* This typically represents the hostname or IP address
|
||||||
|
* of the mail server that handles IMAP communication.
|
||||||
|
*/
|
||||||
|
protected String server;
|
||||||
|
/**
|
||||||
|
* Retrieves the server address associated with this IMAP*/
|
||||||
|
public String getServer() { return server; }
|
||||||
|
/**
|
||||||
|
* Sets the server address for the IMAP account.
|
||||||
|
*
|
||||||
|
* @param server the server address to be configured for the IMAP account
|
||||||
|
*/
|
||||||
|
public void setServer(String server) { this.server = server; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the username or identifier used to authenticate the IMAP account.
|
||||||
|
* This value is associated with the user's credentials required to log in
|
||||||
|
* to the IMAP server.
|
||||||
|
*/
|
||||||
|
protected String user;
|
||||||
|
/**
|
||||||
|
* Retrieves the username associated with this IMAP account.
|
||||||
|
*
|
||||||
|
* @return the username of the IMAP account as a String.
|
||||||
|
*/
|
||||||
|
public String getUser() { return user; }
|
||||||
|
/**
|
||||||
|
* Sets the username associated with the IMAP account.
|
||||||
|
*
|
||||||
|
* @param user The username to be set for this IMAP account.
|
||||||
|
*/
|
||||||
|
public void setUser(String user) { this.user = user; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The password associated with the IMAP account.
|
||||||
|
* This is used to authenticate the user when connecting to the IMAP server.
|
||||||
|
*/
|
||||||
|
protected String password;
|
||||||
|
/**
|
||||||
|
* Retrieves the password associated with this IMAP account.
|
||||||
|
*
|
||||||
|
* @return the password of the IMAP account.
|
||||||
|
*/
|
||||||
|
public String getPassword() { return password; }
|
||||||
|
/**
|
||||||
|
* Sets the password for the IMAP account.
|
||||||
|
*
|
||||||
|
* @param password The password to be set for the account.
|
||||||
|
*/
|
||||||
|
public void setPassword(String password) { this.password = password; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the protocol used for the IMAP account, such as "IMAP" or "IMAPS".
|
||||||
|
* This field determines the communication protocol for interacting with the server.
|
||||||
|
*/
|
||||||
|
protected String protocol;
|
||||||
|
/**
|
||||||
|
* Retrieves the protocol used by this IMAP account.
|
||||||
|
*
|
||||||
|
* @return the protocol as a String, which defines the communication method for the IMAP account.
|
||||||
|
*/
|
||||||
|
public String getProtocol() { return protocol; }
|
||||||
|
/**
|
||||||
|
* Sets the protocol used by the IMAP account.
|
||||||
|
* @param protocol the protocol to be used, e.g., "IMAP" or "IMAPS".
|
||||||
|
*/
|
||||||
|
public void setProtocol(String protocol) { this.protocol = protocol; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a string representation of the ImapAccount object including its server, user,
|
||||||
|
* a masked password, and protocol information. The password is represented as '########' if it is not empty.
|
||||||
|
*
|
||||||
|
* @return A formatted string that describes the ImapAccount object with its server, user, masked password, and protocol.
|
||||||
|
*/
|
||||||
|
@Override public String toString() {
|
||||||
|
return String.format("ImapAccount(%s, %s, %s, %s",
|
||||||
|
server,
|
||||||
|
user,
|
||||||
|
password.length()==0 ? "''" : "########" ,
|
||||||
|
protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the hash code for this instance of the ImapAccount class.
|
||||||
|
* The hash code is based on the values of the server, user, password, and protocol fields.
|
||||||
|
* This ensures that instances with the same field values produce the same hash code.
|
||||||
|
*
|
||||||
|
* @return An integer hash code representing this ImapAccount instance.
|
||||||
|
*/
|
||||||
|
@Override public int hashCode() {
|
||||||
|
return Objects.hash(server, user, password, protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares this ImapAccount instance with the specified object for equality.
|
||||||
|
* The comparison is based on the equality of all relevant fields: server, user, password, and protocol.
|
||||||
|
*
|
||||||
|
* @param obj the object to compare with this instance
|
||||||
|
* @return true if the specified object is equal to this ImapAccount instance; false otherwise
|
||||||
|
*/
|
||||||
|
@Override public boolean equals(Object obj) {
|
||||||
|
// Check type of argument, includes check of null.
|
||||||
|
if (!(obj instanceof ImapAccount)) return false;
|
||||||
|
|
||||||
|
// Check Reference equals
|
||||||
|
if (this == obj) return true;
|
||||||
|
|
||||||
|
// Check the comparison of all field
|
||||||
|
ImapAccount other = (ImapAccount) obj;
|
||||||
|
return (server == other.server || (server != null && server.equals(other.server))) &&
|
||||||
|
(user == other.user || (user != null && user.equals(other.user))) &&
|
||||||
|
(password == other.password || (password != null && password.equals(other.password))) &&
|
||||||
|
(protocol == other.protocol || (protocol != null && protocol.equals(other.protocol)));
|
||||||
|
}
|
||||||
|
}
|
||||||
256
net/src/main/java/de/neitzel/net/imap/ImapAccountMonitor.java
Normal file
256
net/src/main/java/de/neitzel/net/imap/ImapAccountMonitor.java
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
package de.neitzel.net.imap;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.mail.*;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ImapAccountMonitor class is responsible for monitoring an IMAP email account
|
||||||
|
* by maintaining a connection to the server, tracking specific folders, and
|
||||||
|
* listening for new emails. This class allows managing the set of monitored folders,
|
||||||
|
* processing unseen and undeleted messages, and notifying registered listeners when
|
||||||
|
* new messages are received.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class ImapAccountMonitor {
|
||||||
|
/**
|
||||||
|
* Represents an instance of an IMAP account, encapsulating details about
|
||||||
|
* server configuration, user credentials, and protocol settings.
|
||||||
|
* This variable is used to connect and authenticate the application's interaction
|
||||||
|
* with an IMAP email server.
|
||||||
|
*/
|
||||||
|
protected ImapAccount account;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the current session for interaction with an IMAP server.
|
||||||
|
* The session encapsulates the configuration and context required
|
||||||
|
* to establish a connection and perform operations on the IMAP server.
|
||||||
|
* It is initialized and utilized internally within the application.
|
||||||
|
*/
|
||||||
|
protected Session session;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the mail store associated with the IMAP account.
|
||||||
|
* The store is used to interact with the IMAP server for email retrieval and management.
|
||||||
|
*/
|
||||||
|
protected Store store;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a collection of folders associated with an IMAP account.
|
||||||
|
* The map's keys are folder names (String), and the values are Folder objects.
|
||||||
|
* This variable is used to store and manage the hierarchy or collection
|
||||||
|
* of email folders for an IMAP account.
|
||||||
|
*/
|
||||||
|
protected Map<String, Folder> folders = new HashMap<String, Folder>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitors an IMAP account by establishing a connection to the specified mail server
|
||||||
|
* using the provided account credentials and protocol.
|
||||||
|
*
|
||||||
|
* @param account the IMAP account containing details such as server, user, password, and protocol
|
||||||
|
* @throws NoSuchProviderException if the specified mail store protocol is not available
|
||||||
|
* @throws MessagingException if the connection to the mail server fails
|
||||||
|
*/
|
||||||
|
public ImapAccountMonitor(ImapAccount account) throws NoSuchProviderException, MessagingException {
|
||||||
|
log.trace("Constructor ({})", account.toString());
|
||||||
|
this.account = account;
|
||||||
|
|
||||||
|
Properties props = System.getProperties();
|
||||||
|
props.setProperty("mail.store.protocol", account.getProtocol());
|
||||||
|
session = Session.getDefaultInstance(props);
|
||||||
|
try {
|
||||||
|
store = session.getStore("imaps");
|
||||||
|
store.connect(account.server, account.user, account.password);
|
||||||
|
} catch (NoSuchProviderException ex) {
|
||||||
|
log.error("Unable to get imaps Store.", ex);
|
||||||
|
throw ex;
|
||||||
|
} catch (MessagingException ex) {
|
||||||
|
log.error("Unable to connect.", ex);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.trace("Constructor done.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the resources associated with the current instance, including
|
||||||
|
* IMAP folders and the store. The method ensures that all folders are
|
||||||
|
* closed properly, followed by the closure of the store.
|
||||||
|
*
|
||||||
|
* Any exceptions encountered during the closure of folders or the store
|
||||||
|
* are caught and logged without interrupting the overall closing process.
|
||||||
|
* This is to ensure that all resources are attempted to be closed even if
|
||||||
|
* an error occurs with one of them.
|
||||||
|
*
|
||||||
|
* The method logs the start and end of the closing process for traceability.
|
||||||
|
*/
|
||||||
|
public void close() {
|
||||||
|
log.trace("close() called.");
|
||||||
|
|
||||||
|
// Close the folders
|
||||||
|
for (Folder folder: folders.values()) {
|
||||||
|
try {
|
||||||
|
folder.close(false);
|
||||||
|
} catch (MessagingException ex) {
|
||||||
|
// Only log the exception.
|
||||||
|
log.warn("Exception when closing folder.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the store
|
||||||
|
try {
|
||||||
|
store.close();
|
||||||
|
} catch (MessagingException ex) {
|
||||||
|
// Only log the error.
|
||||||
|
log.warn("Exception when closing the store.", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.trace("close() call ended.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new folder to the IMAP account and opens it in read-write mode.
|
||||||
|
* The folder is added to an internal collection for future access.
|
||||||
|
*
|
||||||
|
* @param name the name of the folder to be added and opened
|
||||||
|
* @throws MessagingException if any error occurs while accessing or opening the folder
|
||||||
|
*/
|
||||||
|
public void addFolder(String name) throws MessagingException {
|
||||||
|
log.trace("addFolder(%s) called.", name);
|
||||||
|
Folder folder = store.getFolder(name);
|
||||||
|
folder.open(Folder.READ_WRITE);
|
||||||
|
folders.put(name, folder);
|
||||||
|
log.trace("addFolder({}) call ended.", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the folder with the specified name from the account's folder list.
|
||||||
|
* If the folder is not found, the method does nothing.
|
||||||
|
* After removing the folder, it attempts to close any associated resources.
|
||||||
|
*
|
||||||
|
* @param name the name of the folder to be removed
|
||||||
|
*/
|
||||||
|
public void removeFolder(String name) {
|
||||||
|
log.trace("removeFolder({}) called.", name);
|
||||||
|
|
||||||
|
// Validate folder is known.
|
||||||
|
if (!folders.containsKey(name)) return;
|
||||||
|
|
||||||
|
Folder folder = folders.get(name);
|
||||||
|
folders.remove(name);
|
||||||
|
try {
|
||||||
|
folder.close(false);
|
||||||
|
} catch (MessagingException ex) {
|
||||||
|
// TODO: Handle exception ...
|
||||||
|
}
|
||||||
|
|
||||||
|
log.trace("removeFolder({}) call ended.", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks all monitored folders for new email messages, processes unseen and undeleted messages,
|
||||||
|
* and raises a NewMailEvent for each new email discovered. The method ensures processed messages
|
||||||
|
* are marked as seen.
|
||||||
|
*
|
||||||
|
* The method iterates through all folders currently being monitored, fetching their messages and
|
||||||
|
* evaluating the state of each. If a message is neither marked as seen nor deleted, it is marked
|
||||||
|
* as seen and a NewMailEvent is raised for it.
|
||||||
|
*
|
||||||
|
* Proper error handling is implemented to log any MessagingException that occurs while processing
|
||||||
|
* the messages of a folder without interrupting the processing of other folders.
|
||||||
|
*
|
||||||
|
* Note: This method relies on external logging (via `log`) and appropriate event handling
|
||||||
|
* via `raiseNewEmailEvent`. The `folders` collection holds the monitored folders required.
|
||||||
|
*
|
||||||
|
* The folder processing stops only in case of irrecoverable exceptions outside this method's scope.
|
||||||
|
*/
|
||||||
|
public void check() {
|
||||||
|
log.trace("check() called.");
|
||||||
|
|
||||||
|
// Loop through all folders that are monitored
|
||||||
|
for (Folder folder: folders.values()) {
|
||||||
|
try {
|
||||||
|
// Loop through all messages.
|
||||||
|
Message[] messages = folder.getMessages();
|
||||||
|
for (Message message: messages) {
|
||||||
|
// Check if message wasn't seen and wasn't deleted
|
||||||
|
if (!message.isSet(Flags.Flag.SEEN) && !message.isSet(Flags.Flag.DELETED)) {
|
||||||
|
// Mark message as seen.
|
||||||
|
message.setFlag(Flags.Flag.SEEN, true);
|
||||||
|
|
||||||
|
// Raise NewMailEvent
|
||||||
|
raiseNewEmailEvent(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (MessagingException ex) {
|
||||||
|
log.error("Exception when reading messages of folder {}.", folder.getName(), ex);
|
||||||
|
// So far no handling of this error except logging it.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.trace("check() call ended.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of listeners that respond to IMAP-related events.
|
||||||
|
* Each listener in this collection can perform specific actions when triggered
|
||||||
|
* by events such as receiving new email notifications.
|
||||||
|
*/
|
||||||
|
private Collection<ImapListener> imapListeners = new ArrayList<ImapListener>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an IMAP listener to receive notifications about IMAP-related events such as new email arrivals.
|
||||||
|
*
|
||||||
|
* @param listener the ImapListener instance to be added to the list of listeners
|
||||||
|
*/
|
||||||
|
public void addImapListener(ImapListener listener) {
|
||||||
|
log.trace("addImapListener() called!");
|
||||||
|
imapListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the specified IMAP listener from the list of active listeners.
|
||||||
|
* If the listener is present, it will be removed; otherwise, no action is taken.
|
||||||
|
*
|
||||||
|
* @param listener The IMAP listener to be removed.
|
||||||
|
*/
|
||||||
|
public void removeImapListener(ImapListener listener) {
|
||||||
|
log.trace("removeImapListener() called!");
|
||||||
|
if (imapListeners.contains(listener)) {
|
||||||
|
log.info("Removing the IMAP listener.");
|
||||||
|
imapListeners.remove(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raises a new email event and notifies all registered listeners.
|
||||||
|
* The method loops through the listeners and invokes their {@code NewEmailReceived}
|
||||||
|
* method with a newly created {@link NewEmailEvent}.
|
||||||
|
* If a listener marks the event as handled, the remaining listeners will
|
||||||
|
* not be notified.
|
||||||
|
*
|
||||||
|
* @param message The email message that triggered the event. This message
|
||||||
|
* is included in the {@link NewEmailEvent} passed to the listeners.
|
||||||
|
*/
|
||||||
|
protected void raiseNewEmailEvent(Message message) {
|
||||||
|
log.trace("raiseNewEmailEvent() called!");
|
||||||
|
|
||||||
|
// Create the event.
|
||||||
|
NewEmailEvent event = new NewEmailEvent(this, message);
|
||||||
|
|
||||||
|
// Call all listeners.
|
||||||
|
for (ImapListener listener: imapListeners) {
|
||||||
|
log.trace("Calling listener in {} ....", listener.getClass().getName());
|
||||||
|
listener.newEmailReceived(event);
|
||||||
|
|
||||||
|
// Check if the event was handled so no further ImaListeners will be called.
|
||||||
|
if (event.isHandled()) {
|
||||||
|
log.trace("raiseNewEmailEvent(): Breaking out of listener loop because event was handled.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.trace("raiseNewEmailEvent() call ended!");
|
||||||
|
}
|
||||||
|
}
|
||||||
17
net/src/main/java/de/neitzel/net/imap/ImapListener.java
Normal file
17
net/src/main/java/de/neitzel/net/imap/ImapListener.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package de.neitzel.net.imap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener interface for receiving notifications about new emails.
|
||||||
|
* Classes interested in processing new emails should implement this interface and
|
||||||
|
* register themselves to receive events via supported mechanisms.
|
||||||
|
*/
|
||||||
|
public interface ImapListener {
|
||||||
|
/**
|
||||||
|
* This method gets invoked when a new email is received. Implementers of this method
|
||||||
|
* handle the processing of new email events triggered by the associated source.
|
||||||
|
*
|
||||||
|
* @param event the NewEmailEvent object that contains information about the received email,
|
||||||
|
* including the email message and its source.
|
||||||
|
*/
|
||||||
|
void newEmailReceived(NewEmailEvent event);
|
||||||
|
}
|
||||||
48
net/src/main/java/de/neitzel/net/imap/NewEmailEvent.java
Normal file
48
net/src/main/java/de/neitzel/net/imap/NewEmailEvent.java
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package de.neitzel.net.imap;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import javax.mail.Message;
|
||||||
|
import java.util.EventObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event class representing the occurrence of a new email.
|
||||||
|
* It is typically used to encapsulate information about the received email
|
||||||
|
* and provide a means for event listeners to handle the event.
|
||||||
|
*/
|
||||||
|
public class NewEmailEvent extends EventObject {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the email message associated with the NewEmailEvent.
|
||||||
|
* This field encapsulates the content and metadata of the email
|
||||||
|
* received, allowing event listeners to process the message details.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
protected Message message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the event has already been handled or processed.
|
||||||
|
* When true, it signifies that the event has been handled and no further
|
||||||
|
* processing is required. It is used to prevent multiple handling
|
||||||
|
* of the same event.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
protected boolean handled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new NewEmailEvent instance.
|
||||||
|
* This event encapsulates information about a newly received email,
|
||||||
|
* including its associated message and the source of the event.
|
||||||
|
*
|
||||||
|
* @param source the object on which the event initially occurred; typically represents
|
||||||
|
* the source of the email event, such as an email client or server.
|
||||||
|
* @param message the Message object representing the email associated with this event.
|
||||||
|
*/
|
||||||
|
public NewEmailEvent(Object source, Message message) {
|
||||||
|
super(source);
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
82
net/src/main/java/de/neitzel/net/mail/MessageUtils.java
Normal file
82
net/src/main/java/de/neitzel/net/mail/MessageUtils.java
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package de.neitzel.net.mail;
|
||||||
|
|
||||||
|
import javax.mail.BodyPart;
|
||||||
|
import javax.mail.Message;
|
||||||
|
import javax.mail.MessagingException;
|
||||||
|
import javax.mail.internet.ContentType;
|
||||||
|
import javax.mail.internet.MimeMultipart;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility functions to use with javax.mail.Message instances.
|
||||||
|
*/
|
||||||
|
public class MessageUtils {
|
||||||
|
/**
|
||||||
|
* Gets the text of the email message.
|
||||||
|
* @param message Message to get the text from.
|
||||||
|
* @return Text of the email message.
|
||||||
|
* @throws IOException
|
||||||
|
* @throws MessagingException
|
||||||
|
*/
|
||||||
|
public static String getTextFromMessage(Message message) throws IOException, MessagingException {
|
||||||
|
String result = "";
|
||||||
|
if (message.isMimeType("text/plain")) {
|
||||||
|
result = message.getContent().toString();
|
||||||
|
} else if (message.isMimeType("multipart/*")) {
|
||||||
|
MimeMultipart mimeMultipart = (MimeMultipart) message.getContent();
|
||||||
|
result = getTextFromMimeMultipart(mimeMultipart);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the text of a MimeMultipart.
|
||||||
|
* @param mimeMultipart MimeMultipart to get the text from.
|
||||||
|
* @return Text of the MimeMultipart instance.
|
||||||
|
* @throws IOException
|
||||||
|
* @throws MessagingException
|
||||||
|
*/
|
||||||
|
private static String getTextFromMimeMultipart(
|
||||||
|
MimeMultipart mimeMultipart) throws IOException, MessagingException {
|
||||||
|
|
||||||
|
int count = mimeMultipart.getCount();
|
||||||
|
if (count == 0)
|
||||||
|
throw new MessagingException("Multipart with no body parts not supported.");
|
||||||
|
boolean multipartAlt = new ContentType(mimeMultipart.getContentType()).match("multipart/alternative");
|
||||||
|
if (multipartAlt)
|
||||||
|
// alternatives appear in an order of increasing
|
||||||
|
// faithfulness to the original content. Customize as req'd.
|
||||||
|
return getTextFromBodyPart(mimeMultipart.getBodyPart(count - 1));
|
||||||
|
String result = "";
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
BodyPart bodyPart = mimeMultipart.getBodyPart(i);
|
||||||
|
result += getTextFromBodyPart(bodyPart);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the text of a BodyPart
|
||||||
|
* @param bodyPart BodyPart to get text from.
|
||||||
|
* @return Text of body part or empty string if not a known type.
|
||||||
|
* @throws IOException
|
||||||
|
* @throws MessagingException
|
||||||
|
*/
|
||||||
|
private static String getTextFromBodyPart(
|
||||||
|
BodyPart bodyPart) throws IOException, MessagingException {
|
||||||
|
|
||||||
|
String result = "";
|
||||||
|
if (bodyPart.isMimeType("text/plain")) {
|
||||||
|
result = (String) bodyPart.getContent();
|
||||||
|
} else if (bodyPart.isMimeType("text/html")) {
|
||||||
|
result = (String) bodyPart.getContent();
|
||||||
|
//String html = (String) bodyPart.getContent();
|
||||||
|
// Not parsing the html right now!
|
||||||
|
// result = org.jsoup.Jsoup.parse(html).text();
|
||||||
|
} else if (bodyPart.getContent() instanceof MimeMultipart){
|
||||||
|
result = getTextFromMimeMultipart((MimeMultipart)bodyPart.getContent());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
75
pom.xml
75
pom.xml
@ -12,7 +12,9 @@
|
|||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>core</module>
|
<module>core</module>
|
||||||
|
<module>encryption</module>
|
||||||
<module>fx</module>
|
<module>fx</module>
|
||||||
|
<module>net</module>
|
||||||
<module>fx-example</module>
|
<module>fx-example</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
@ -27,7 +29,7 @@
|
|||||||
<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>
|
<reflections.version>0.10.2</reflections.version>
|
||||||
<slf4j.version></slf4j.version>
|
<slf4j.version>2.0.17</slf4j.version>
|
||||||
|
|
||||||
<!-- Plugin dependencies -->
|
<!-- Plugin dependencies -->
|
||||||
<codehaus.version.plugin>2.16.2</codehaus.version.plugin>
|
<codehaus.version.plugin>2.16.2</codehaus.version.plugin>
|
||||||
@ -60,34 +62,44 @@
|
|||||||
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencyManagement>
|
||||||
<!-- JavaFX dependencies -->
|
<dependencies>
|
||||||
<dependency>
|
<!-- JavaFX dependencies -->
|
||||||
<groupId>org.openjfx</groupId>
|
<dependency>
|
||||||
<artifactId>javafx-base</artifactId>
|
<groupId>org.openjfx</groupId>
|
||||||
<version>${javafx.version}</version>
|
<artifactId>javafx-base</artifactId>
|
||||||
</dependency>
|
<version>${javafx.version}</version>
|
||||||
<dependency>
|
</dependency>
|
||||||
<groupId>org.openjfx</groupId>
|
<dependency>
|
||||||
<artifactId>javafx-controls</artifactId>
|
<groupId>org.openjfx</groupId>
|
||||||
<version>${javafx.version}</version>
|
<artifactId>javafx-controls</artifactId>
|
||||||
</dependency>
|
<version>${javafx.version}</version>
|
||||||
<dependency>
|
</dependency>
|
||||||
<groupId>org.openjfx</groupId>
|
<dependency>
|
||||||
<artifactId>javafx-graphics</artifactId>
|
<groupId>org.openjfx</groupId>
|
||||||
<version>${javafx.version}</version>
|
<artifactId>javafx-graphics</artifactId>
|
||||||
</dependency>
|
<version>${javafx.version}</version>
|
||||||
<dependency>
|
</dependency>
|
||||||
<groupId>org.openjfx</groupId>
|
<dependency>
|
||||||
<artifactId>javafx-fxml</artifactId>
|
<groupId>org.openjfx</groupId>
|
||||||
<version>${javafx.version}</version>
|
<artifactId>javafx-fxml</artifactId>
|
||||||
</dependency>
|
<version>${javafx.version}</version>
|
||||||
<dependency>
|
</dependency>
|
||||||
<groupId>org.openjfx</groupId>
|
<dependency>
|
||||||
<artifactId>javafx-web</artifactId>
|
<groupId>org.openjfx</groupId>
|
||||||
<version>${javafx.version}</version>
|
<artifactId>javafx-web</artifactId>
|
||||||
</dependency>
|
<version>${javafx.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Logging implementation -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
<!-- Lombok -->
|
<!-- Lombok -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
@ -96,6 +108,13 @@
|
|||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Logging API -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- JUnit 5 -->
|
<!-- JUnit 5 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user