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;
|
||||
|
||||
/**
|
||||
* InjectableComponents scans a package for classes annotated with {@link Component}.
|
||||
* It determines which components can be instantiated and manages type mappings for dependency injection.
|
||||
* InjectableComponentScanner is responsible for scanning packages to detect classes annotated
|
||||
* 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 {
|
||||
|
||||
/** 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<>();
|
||||
|
||||
/** 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<>();
|
||||
|
||||
/** 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<>();
|
||||
|
||||
/** 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<>();
|
||||
|
||||
/** 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<>();
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
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) {
|
||||
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() {
|
||||
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.
|
||||
* It registers valid components and collects errors for components that cannot be instantiated.
|
||||
* Resolves components from the set of scanned classes that can be instantiated based on their constructors
|
||||
* 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() {
|
||||
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 knownTypes The map of known instantiable types.
|
||||
* @param component the component class to register
|
||||
* @param knownTypes the map where the component and its types will be registered
|
||||
*/
|
||||
private void registerComponentWithSuperTypes(Class<?> component, Map<Class<?>, Class<?>> knownTypes) {
|
||||
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 knownTypes Set of known instantiable types.
|
||||
* @return {@code true} if the component can be instantiated, otherwise {@code false}.
|
||||
* @param component the class to check for instantiation eligibility
|
||||
* @param knownTypes the set of types known to be instantiable and available for constructor injection
|
||||
* @return true if the class can be instantiated; false otherwise
|
||||
*/
|
||||
private boolean canInstantiate(Class<?> component, Set<Class<?>> knownTypes) {
|
||||
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) {
|
||||
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.
|
||||
* @return A string listing the conflicting types.
|
||||
* @param component the class for which conflicting types need to be identified.
|
||||
* @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) {
|
||||
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.
|
||||
* @return A set of all superclasses and interfaces of the given class.
|
||||
* @param clazz the class for which to retrieve all superclasses and interfaces
|
||||
* @return a set of all superclasses and interfaces implemented by the specified class
|
||||
*/
|
||||
private Set<Class<?>> getAllSuperTypes(Class<?> clazz) {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
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() {
|
||||
return errors;
|
||||
|
||||
@ -5,6 +5,22 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
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)
|
||||
@Target(ElementType.TYPE)
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@RequiredArgsConstructor
|
||||
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 -> {
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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<>();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Represents the text of a query.
|
||||
* This variable stores the query string used for processing or execution purposes within the application.
|
||||
*/
|
||||
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 {
|
||||
new Query<Object>(connection)
|
||||
.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) {
|
||||
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) {
|
||||
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) {
|
||||
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 value Value to set.
|
||||
* @param setter Setter to use on prepared statement.
|
||||
* @param <X> Type of the Parameter.
|
||||
* @return The Query.
|
||||
* @param index The position of the parameter in the query starting from 1.
|
||||
* @param value The value of the parameter to be set.
|
||||
* @param setter A TriSqlFunction that defines how to set the parameter in the PreparedStatement.
|
||||
* @param <X> The type of the parameter value.
|
||||
* @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) {
|
||||
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.
|
||||
* @return the Qyery.
|
||||
* @param setter the ParameterSetter used to configure the parameter
|
||||
* @return the current Query instance for method chaining
|
||||
*/
|
||||
public Query<T> addParameter(ParameterSetter 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) {
|
||||
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 {
|
||||
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
|
||||
* @throws SQLException thrown if the PreparedStatement could not be created.
|
||||
* @return a PreparedStatement object initialized with the query and parameter values
|
||||
* @throws SQLException if a database access error occurs or the PreparedStatement cannot be created
|
||||
*/
|
||||
private PreparedStatement createPreparedStatement() throws SQLException {
|
||||
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 {
|
||||
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 factory Factory to create instances of T.
|
||||
* @return Stream of T instances.
|
||||
* @param rs the {@link ResultSet} to be transformed into a stream
|
||||
* @param factory a {@link Function} that maps rows of the {@link ResultSet} to objects of type T
|
||||
* @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) {
|
||||
Iterator<T> iterator = new Iterator<>() {
|
||||
private boolean hasNextChecked = false;
|
||||
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
|
||||
@SneakyThrows
|
||||
public boolean hasNext() {
|
||||
@ -162,6 +232,14 @@ public class Query<T> {
|
||||
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
|
||||
public T next() {
|
||||
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 {
|
||||
try (Stream<T> stream = 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 <U> type of second parameter
|
||||
* @param <V> type of third parameter
|
||||
* @param <T> the type of the first input to the operation
|
||||
* @param <U> the type of the second input to the operation
|
||||
* @param <V> the type of the third input to the operation
|
||||
*/
|
||||
@FunctionalInterface
|
||||
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 u second parameter.
|
||||
* @param v third parameter.
|
||||
* @throws SQLException Function could throw an SQLException.
|
||||
* @param t the first input argument
|
||||
* @param u the second input argument
|
||||
* @param v the third input argument
|
||||
* @throws SQLException if an SQL error occurs during execution
|
||||
*/
|
||||
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
|
||||
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.
|
||||
* @throws SQLException Could be thrown when working on PreparedStatement.
|
||||
* @param ps the PreparedStatement instance to be modified or configured
|
||||
* @throws SQLException if a database access error occurs while applying the operation
|
||||
*/
|
||||
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>
|
||||
|
||||
<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>
|
||||
<groupId>de.neitzel.lib</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.reflections</groupId>
|
||||
<artifactId>reflections</artifactId>
|
||||
<version>${reflections.version}</version>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</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>
|
||||
<module>core</module>
|
||||
<module>encryption</module>
|
||||
<module>fx</module>
|
||||
<module>net</module>
|
||||
<module>fx-example</module>
|
||||
</modules>
|
||||
|
||||
@ -27,7 +29,7 @@
|
||||
<lombok.version>1.18.32</lombok.version>
|
||||
<mockito.version>5.12.0</mockito.version>
|
||||
<reflections.version>0.10.2</reflections.version>
|
||||
<slf4j.version></slf4j.version>
|
||||
<slf4j.version>2.0.17</slf4j.version>
|
||||
|
||||
<!-- Plugin dependencies -->
|
||||
<codehaus.version.plugin>2.16.2</codehaus.version.plugin>
|
||||
@ -60,34 +62,44 @@
|
||||
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- JavaFX dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-base</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-graphics</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-web</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- JavaFX dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-base</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-graphics</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-web</artifactId>
|
||||
<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 -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
@ -96,6 +108,13 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Logging API -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JUnit 5 -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user