Work in progress ...
This commit is contained in:
parent
9bbddaaae9
commit
5503dd192e
21
README.md
21
README.md
@ -2,24 +2,32 @@
|
|||||||
|
|
||||||
## A Java Utilities Library
|
## A Java Utilities Library
|
||||||
|
|
||||||
This repository is **not** a production-ready library, but rather a personal collection of small helpers, utilities, and ideas that I find useful or interesting as a Java developer.
|
This repository is **not** a production-ready library, but rather a personal collection of small helpers, utilities, and
|
||||||
|
ideas that I find useful or interesting as a Java developer.
|
||||||
|
|
||||||
It serves primarily as a **knowledge base** and **inspiration pool** for my own development work. You're welcome to explore, copy, modify, or improve whatever you find helpful.
|
It serves primarily as a **knowledge base** and **inspiration pool** for my own development work. You're welcome to
|
||||||
|
explore, copy, modify, or improve whatever you find helpful.
|
||||||
|
|
||||||
> ⚠️ Use at your own discretion — no guarantees of stability, backwards compatibility, or completeness.
|
> ⚠️ Use at your own discretion — no guarantees of stability, backwards compatibility, or completeness.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## License
|
## License
|
||||||
This is free and unencumbered software released into the public domain. Please see the [License](LICENSE.md) for details.
|
|
||||||
|
This is free and unencumbered software released into the public domain. Please see the [License](LICENSE.md) for
|
||||||
|
details.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Note on Content
|
## Note on Content
|
||||||
|
|
||||||
The content of this repository — including source code, documentation, and examples — was partially created with the assistance of ChatGPT (an AI model by OpenAI). The generated content has been reviewed, edited, and integrated into the overall context by me.
|
The content of this repository — including source code, documentation, and examples — was partially created with the
|
||||||
|
assistance of ChatGPT (an AI model by OpenAI). The generated content has been reviewed, edited, and integrated into the
|
||||||
|
overall context by me.
|
||||||
|
|
||||||
Responsibility for all content lies entirely with me. Any generated content is subject to [OpenAI’s Terms of Use](https://openai.com/policies/terms-of-use) and is — where possible — either in the public domain or usable under a license compatible with this project's license.
|
Responsibility for all content lies entirely with me. Any generated content is subject
|
||||||
|
to [OpenAI’s Terms of Use](https://openai.com/policies/terms-of-use) and is — where possible — either in the public
|
||||||
|
domain or usable under a license compatible with this project's license.
|
||||||
|
|
||||||
## Components
|
## Components
|
||||||
|
|
||||||
@ -47,7 +55,8 @@ TypeAdapter for gson.
|
|||||||
|
|
||||||
### log4j
|
### log4j
|
||||||
|
|
||||||
Stuff for log4j (Version 1). Please use log4j2 if you want to use log4j! (Or log with slf4j as I do in all other modules!)
|
Stuff for log4j (Version 1). Please use log4j2 if you want to use log4j! (Or log with slf4j as I do in all other
|
||||||
|
modules!)
|
||||||
|
|
||||||
### net
|
### net
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@ -6,10 +6,10 @@ import java.util.Iterator;
|
|||||||
* The ArgumentProvider class is a helper utility for iterating over an array
|
* The ArgumentProvider class is a helper utility for iterating over an array
|
||||||
* of command-line arguments. It provides methods to check for available arguments
|
* of command-line arguments. It provides methods to check for available arguments
|
||||||
* and retrieve them in sequence.
|
* and retrieve them in sequence.
|
||||||
*
|
* <p>
|
||||||
* This class is designed to work seamlessly with a command-line parser
|
* This class is designed to work seamlessly with a command-line parser and handle
|
||||||
* and handle tokenized inputs efficiently. It implements the Iterator interface
|
* tokenized inputs efficiently. It implements the {@link Iterator} interface for ease
|
||||||
* for ease of iteration.
|
* of iteration and provides convenience methods for peeking ahead.
|
||||||
*/
|
*/
|
||||||
public class ArgumentProvider implements Iterator<String> {
|
public class ArgumentProvider implements Iterator<String> {
|
||||||
|
|
||||||
@ -19,18 +19,15 @@ public class ArgumentProvider implements Iterator<String> {
|
|||||||
* and processing by the application logic. It is used within the {@code ArgumentProvider}
|
* and processing by the application logic. It is used within the {@code ArgumentProvider}
|
||||||
* class to iterate through and retrieve arguments systematically.
|
* class to iterate through and retrieve arguments systematically.
|
||||||
*/
|
*/
|
||||||
private String[] arguments;
|
private final String[] arguments;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks the current position within an array of arguments.
|
* Tracks the current position within an array of arguments.
|
||||||
*
|
* This variable is used to index into the arguments array, enabling sequential access
|
||||||
* This variable is used to index into the arguments array, enabling
|
* to command-line tokens. It is incremented as arguments are processed or retrieved
|
||||||
* sequential access to command-line tokens. It is incremented as arguments
|
* using methods such as {@code next()} or {@code peek()} in the {@link ArgumentProvider}
|
||||||
* are processed or retrieved using methods such as {@code next()} or {@code peek()}
|
* class. Its value starts at 0 and increases until it reaches the length of the provided
|
||||||
* in the {@link ArgumentProvider} class.
|
* arguments array, at which point iteration ends.
|
||||||
*
|
|
||||||
* Its value starts at 0 and increases until it reaches the length of
|
|
||||||
* the provided arguments array, at which point iteration ends.
|
|
||||||
*/
|
*/
|
||||||
private int current = 0;
|
private int current = 0;
|
||||||
|
|
||||||
@ -43,21 +40,12 @@ public class ArgumentProvider implements Iterator<String> {
|
|||||||
*/
|
*/
|
||||||
public ArgumentProvider(final String[] arguments) {
|
public ArgumentProvider(final String[] arguments) {
|
||||||
if (arguments == null) {
|
if (arguments == null) {
|
||||||
this.arguments = new String[] {};
|
this.arguments = new String[]{};
|
||||||
} else {
|
} else {
|
||||||
this.arguments = arguments;
|
this.arguments = arguments;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if there are more arguments available to iterate over.
|
|
||||||
*
|
|
||||||
* @return true if there are additional arguments available; false otherwise.
|
|
||||||
*/
|
|
||||||
public boolean hasNext() {
|
|
||||||
return current < arguments.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if the specified number of additional arguments are available in the collection.
|
* Determines if the specified number of additional arguments are available in the collection.
|
||||||
*
|
*
|
||||||
@ -68,6 +56,26 @@ public class ArgumentProvider implements Iterator<String> {
|
|||||||
return current + count <= arguments.length;
|
return current + count <= arguments.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next argument in the sequence without advancing the iterator.
|
||||||
|
* If there are no more arguments available, this method returns null.
|
||||||
|
*
|
||||||
|
* @return the next argument in the sequence or null if no arguments are available
|
||||||
|
*/
|
||||||
|
public String peek() {
|
||||||
|
if (!hasNext()) return null;
|
||||||
|
return arguments[current];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if there are more arguments available to iterate over.
|
||||||
|
*
|
||||||
|
* @return true if there are additional arguments available; false otherwise.
|
||||||
|
*/
|
||||||
|
public boolean hasNext() {
|
||||||
|
return current < arguments.length;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the next argument in the sequence.
|
* Retrieves the next argument in the sequence.
|
||||||
* If no more arguments are available, returns {@code null}.
|
* If no more arguments are available, returns {@code null}.
|
||||||
@ -81,15 +89,4 @@ public class ArgumentProvider implements Iterator<String> {
|
|||||||
current++;
|
current++;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the next argument in the sequence without advancing the iterator.
|
|
||||||
* If there are no more arguments available, this method returns null.
|
|
||||||
*
|
|
||||||
* @return the next argument in the sequence or null if no arguments are available
|
|
||||||
*/
|
|
||||||
public String peek() {
|
|
||||||
if (!hasNext()) return null;
|
|
||||||
return arguments[current];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import java.util.function.Consumer;
|
|||||||
* The `Parameter` class defines a command-line parameter with associated metadata,
|
* The `Parameter` class defines a command-line parameter with associated metadata,
|
||||||
* such as constraints on the number of values it accepts, descriptions, and aliases.
|
* such as constraints on the number of values it accepts, descriptions, and aliases.
|
||||||
* It also allows specifying callbacks to handle parameter processing.
|
* It also allows specifying callbacks to handle parameter processing.
|
||||||
*
|
* <p>
|
||||||
* This class is intended to be used as part of a command-line parser, enabling
|
* This class is intended to be used as part of a command-line parser, enabling
|
||||||
* structured handling of arguments provided via the command line.
|
* structured handling of arguments provided via the command line.
|
||||||
*/
|
*/
|
||||||
@ -23,14 +23,141 @@ import java.util.function.Consumer;
|
|||||||
@Setter
|
@Setter
|
||||||
public class Parameter {
|
public class Parameter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `name` variable represents the primary identifier for a parameter.
|
||||||
|
* This string is used to uniquely identify a command-line parameter when parsing
|
||||||
|
* arguments. It serves as the key for registering and retrieving parameters in a
|
||||||
|
* command-line parser.
|
||||||
|
* <p>
|
||||||
|
* The value of `name` is case-insensitive and can be used alongside aliases to
|
||||||
|
* recognize and process parameters provided in the command-line input.
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the minimum number of values required for this parameter.
|
||||||
|
* <p>
|
||||||
|
* This variable enforces a constraint on the number of arguments that must
|
||||||
|
* be provided for the parameter during command-line parsing. If the number of
|
||||||
|
* provided arguments is less than this value, an exception will be thrown during parsing.
|
||||||
|
* <p>
|
||||||
|
* It is primarily used in validation logic within the command-line parser
|
||||||
|
* to ensure the required input is received for proper processing of the parameter.
|
||||||
|
*/
|
||||||
|
private int minNumberValues;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the maximum number of values that the corresponding parameter can accept.
|
||||||
|
* <p>
|
||||||
|
* This field is used to validate and enforce constraints during command-line parsing.
|
||||||
|
* If more values than specified are provided for a parameter, the parser throws an exception
|
||||||
|
* to indicate a violation of this constraint.
|
||||||
|
* <p>
|
||||||
|
* A value of 0 implies that the parameter does not support any values, while a positive
|
||||||
|
* value indicates the exact maximum limit of acceptable values.
|
||||||
|
*/
|
||||||
|
private int maxNumberValues;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the parameter allows multiple entries.
|
||||||
|
* <p>
|
||||||
|
* If set to {@code true}, the parameter can be specified multiple times
|
||||||
|
* on the command-line. This allows the association of multiple values
|
||||||
|
* or options with the same parameter name.
|
||||||
|
* <p>
|
||||||
|
* If set to {@code false}, the parameter can be specified only once
|
||||||
|
* during command-line parsing.
|
||||||
|
*/
|
||||||
|
private boolean multipleEntries;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A concise, human-readable description of the parameter's purpose and functionality.
|
||||||
|
* <p>
|
||||||
|
* This description provides a brief summary to help users understand the
|
||||||
|
* essential role of the parameter within the command-line parser. It is typically
|
||||||
|
* displayed in usage instructions, help messages, or command summaries.
|
||||||
|
*/
|
||||||
|
private String shortDescription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A detailed, extended description of the parameter's purpose and usage.
|
||||||
|
* <p>
|
||||||
|
* This description provides deeper context about what the parameter represents,
|
||||||
|
* how it fits into the overall command-line application, and any nuances
|
||||||
|
* associated with its use. It is displayed when help or additional documentation
|
||||||
|
* for a specific parameter is requested.
|
||||||
|
*/
|
||||||
|
private String longDescription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback function that processes a list of string arguments for a specific parameter.
|
||||||
|
* The callback is invoked during command-line parsing when a parameter is recognized,
|
||||||
|
* passing the associated values of the parameter for further processing.
|
||||||
|
* <p>
|
||||||
|
* The provided {@link Consumer} implementation defines the logic to
|
||||||
|
* handle or validate the values supplied for a command-line parameter. The values
|
||||||
|
* passed are determined based on the parameter's configuration (e.g., minimum and
|
||||||
|
* maximum number of associated values).
|
||||||
|
* <p>
|
||||||
|
* This field is typically set for the `Parameter` class to enable custom handling
|
||||||
|
* of parameter-specific logic, such as invoking a help command or performing
|
||||||
|
* business logic with the values parsed from the command-line arguments.
|
||||||
|
*/
|
||||||
|
private Consumer<List<String>> callback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the parameter is designated as the help command.
|
||||||
|
* <p>
|
||||||
|
* When set to {@code true}, this parameter is treated as the special help command
|
||||||
|
* within the command-line parser. A parameter marked as the help command typically
|
||||||
|
* provides users with information about available options or detailed descriptions
|
||||||
|
* of specific commands when invoked. If this flag is enabled, a custom callback
|
||||||
|
* method for displaying help context is automatically associated with the parameter.
|
||||||
|
*/
|
||||||
|
private boolean isHelpCommand;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether the parameter is designated as the default parameter.
|
||||||
|
* <p>
|
||||||
|
* The default parameter is a special type of parameter that can accept arguments
|
||||||
|
* without an explicit prefix or identifier. It is used when a provided command-line
|
||||||
|
* argument does not match any defined parameter and does not start with a special
|
||||||
|
* character (e.g., '-') typically used to denote named parameters.
|
||||||
|
* <p>
|
||||||
|
* If this field is set to {@code true}, the parameter is treated as the default
|
||||||
|
* parameter. Only one parameter can be assigned this role within the parser.
|
||||||
|
* Attempting to assign multiple default parameters or a default parameter without
|
||||||
|
* accepting values (both `minNumberValues` and `maxNumberValues` set to 0) will
|
||||||
|
* result in an exception during configuration.
|
||||||
|
* <p>
|
||||||
|
* This property is utilized during the parsing process to determine whether an
|
||||||
|
* unmatched argument should be handled as a default parameter value, simplifying
|
||||||
|
* the handling of positional arguments or other unnamed input data.
|
||||||
|
*/
|
||||||
|
private boolean isDefaultParameter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of alias names associated with a parameter.
|
||||||
|
* <p>
|
||||||
|
* This variable holds alternative names that can be used
|
||||||
|
* to reference the parameter in command-line input. Each alias
|
||||||
|
* functions as an equivalent to the primary parameter name.
|
||||||
|
* Aliases are case-insensitive when used within the command-line parser.
|
||||||
|
* <p>
|
||||||
|
* The aliases associated with a parameter allow greater flexibility
|
||||||
|
* and user convenience when specifying parameters during command-line execution.
|
||||||
|
*/
|
||||||
|
@Singular("alias")
|
||||||
|
private List<String> aliasList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a command-line parameter definition within the application.
|
* Represents a command-line parameter definition within the application.
|
||||||
*
|
* <p>
|
||||||
* This class encapsulates the metadata and configuration of a command-line parameter,
|
* This class encapsulates the metadata and configuration of a command-line parameter,
|
||||||
* including its name, aliases, descriptions, value constraints, and behavior. Instances
|
* including its name, aliases, descriptions, value constraints, and behavior. Instances
|
||||||
* of this class are used to define and manage the parameters that the `Parser` class
|
* of this class are used to define and manage the parameters that the `Parser` class
|
||||||
* recognizes and processes.
|
* recognizes and processes.
|
||||||
*
|
* <p>
|
||||||
* A parameter can be configured as a default parameter or as a help command. The default
|
* A parameter can be configured as a default parameter or as a help command. The default
|
||||||
* parameter serves as a catch-all for unmatched command-line inputs, while the help
|
* parameter serves as a catch-all for unmatched command-line inputs, while the help
|
||||||
* command provides usage information for users.
|
* command provides usage information for users.
|
||||||
@ -41,16 +168,16 @@ public class Parameter {
|
|||||||
/**
|
/**
|
||||||
* Constructs a new Parameter with the given attributes.
|
* Constructs a new Parameter with the given attributes.
|
||||||
*
|
*
|
||||||
* @param name The name of the parameter.
|
* @param name The name of the parameter.
|
||||||
* @param minNumberValues The minimum number of values allowed for this parameter.
|
* @param minNumberValues The minimum number of values allowed for this parameter.
|
||||||
* @param maxNumberValues The maximum number of values allowed for this parameter.
|
* @param maxNumberValues The maximum number of values allowed for this parameter.
|
||||||
* @param multipleEntries Indicates whether multiple entries are allowed for this parameter.
|
* @param multipleEntries Indicates whether multiple entries are allowed for this parameter.
|
||||||
* @param shortDescription A brief description of the parameter.
|
* @param shortDescription A brief description of the parameter.
|
||||||
* @param longDescription A detailed description of the parameter.
|
* @param longDescription A detailed description of the parameter.
|
||||||
* @param callback A consumer function that processes a list of values associated with this parameter.
|
* @param callback A consumer function that processes a list of values associated with this parameter.
|
||||||
* @param isHelpCommand Indicates whether this parameter represents a help command.
|
* @param isHelpCommand Indicates whether this parameter represents a help command.
|
||||||
* @param isDefaultParameter Indicates whether this parameter is the default parameter.
|
* @param isDefaultParameter Indicates whether this parameter is the default parameter.
|
||||||
* @param aliasList A list of aliases for this parameter.
|
* @param aliasList A list of aliases for this parameter.
|
||||||
*/
|
*/
|
||||||
public Parameter(String name, int minNumberValues, int maxNumberValues, boolean multipleEntries, String shortDescription, String longDescription, Consumer<List<String>> callback, boolean isHelpCommand, boolean isDefaultParameter, List<String> aliasList) {
|
public Parameter(String name, int minNumberValues, int maxNumberValues, boolean multipleEntries, String shortDescription, String longDescription, Consumer<List<String>> callback, boolean isHelpCommand, boolean isDefaultParameter, List<String> aliasList) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@ -64,131 +191,4 @@ public class Parameter {
|
|||||||
this.isDefaultParameter = isDefaultParameter;
|
this.isDefaultParameter = isDefaultParameter;
|
||||||
this.aliasList = aliasList;
|
this.aliasList = aliasList;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The `name` variable represents the primary identifier for a parameter.
|
|
||||||
* This string is used to uniquely identify a command-line parameter when parsing
|
|
||||||
* arguments. It serves as the key for registering and retrieving parameters in a
|
|
||||||
* command-line parser.
|
|
||||||
*
|
|
||||||
* The value of `name` is case-insensitive and can be used alongside aliases to
|
|
||||||
* recognize and process parameters provided in the command-line input.
|
|
||||||
*/
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies the minimum number of values required for this parameter.
|
|
||||||
*
|
|
||||||
* This variable enforces a constraint on the number of arguments that must
|
|
||||||
* be provided for the parameter during command-line parsing. If the number of
|
|
||||||
* provided arguments is less than this value, an exception will be thrown during parsing.
|
|
||||||
*
|
|
||||||
* It is primarily used in validation logic within the command-line parser
|
|
||||||
* to ensure the required input is received for proper processing of the parameter.
|
|
||||||
*/
|
|
||||||
private int minNumberValues;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies the maximum number of values that the corresponding parameter can accept.
|
|
||||||
*
|
|
||||||
* This field is used to validate and enforce constraints during command-line parsing.
|
|
||||||
* If more values than specified are provided for a parameter, the parser throws an exception
|
|
||||||
* to indicate a violation of this constraint.
|
|
||||||
*
|
|
||||||
* A value of 0 implies that the parameter does not support any values, while a positive
|
|
||||||
* value indicates the exact maximum limit of acceptable values.
|
|
||||||
*/
|
|
||||||
private int maxNumberValues;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether the parameter allows multiple entries.
|
|
||||||
*
|
|
||||||
* If set to {@code true}, the parameter can be specified multiple times
|
|
||||||
* on the command-line. This allows the association of multiple values
|
|
||||||
* or options with the same parameter name.
|
|
||||||
*
|
|
||||||
* If set to {@code false}, the parameter can be specified only once
|
|
||||||
* during command-line parsing.
|
|
||||||
*/
|
|
||||||
private boolean multipleEntries;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A concise, human-readable description of the parameter's purpose and functionality.
|
|
||||||
*
|
|
||||||
* This description provides a brief summary to help users understand the
|
|
||||||
* essential role of the parameter within the command-line parser. It is typically
|
|
||||||
* displayed in usage instructions, help messages, or command summaries.
|
|
||||||
*/
|
|
||||||
private String shortDescription;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A detailed, extended description of the parameter's purpose and usage.
|
|
||||||
*
|
|
||||||
* This description provides deeper context about what the parameter represents,
|
|
||||||
* how it fits into the overall command-line application, and any nuances
|
|
||||||
* associated with its use. It is displayed when help or additional documentation
|
|
||||||
* for a specific parameter is requested.
|
|
||||||
*/
|
|
||||||
private String longDescription;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A callback function that processes a list of string arguments for a specific parameter.
|
|
||||||
* The callback is invoked during command-line parsing when a parameter is recognized,
|
|
||||||
* passing the associated values of the parameter for further processing.
|
|
||||||
*
|
|
||||||
* The provided {@link Consumer} implementation defines the logic to
|
|
||||||
* handle or validate the values supplied for a command-line parameter. The values
|
|
||||||
* passed are determined based on the parameter's configuration (e.g., minimum and
|
|
||||||
* maximum number of associated values).
|
|
||||||
*
|
|
||||||
* This field is typically set for the `Parameter` class to enable custom handling
|
|
||||||
* of parameter-specific logic, such as invoking a help command or performing
|
|
||||||
* business logic with the values parsed from the command-line arguments.
|
|
||||||
*/
|
|
||||||
private Consumer<List<String>> callback;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether the parameter is designated as the help command.
|
|
||||||
*
|
|
||||||
* When set to {@code true}, this parameter is treated as the special help command
|
|
||||||
* within the command-line parser. A parameter marked as the help command typically
|
|
||||||
* provides users with information about available options or detailed descriptions
|
|
||||||
* of specific commands when invoked. If this flag is enabled, a custom callback
|
|
||||||
* method for displaying help context is automatically associated with the parameter.
|
|
||||||
*/
|
|
||||||
private boolean isHelpCommand;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates whether the parameter is designated as the default parameter.
|
|
||||||
*
|
|
||||||
* The default parameter is a special type of parameter that can accept arguments
|
|
||||||
* without an explicit prefix or identifier. It is used when a provided command-line
|
|
||||||
* argument does not match any defined parameter and does not start with a special
|
|
||||||
* character (e.g., '-') typically used to denote named parameters.
|
|
||||||
*
|
|
||||||
* If this field is set to {@code true}, the parameter is treated as the default
|
|
||||||
* parameter. Only one parameter can be assigned this role within the parser.
|
|
||||||
* Attempting to assign multiple default parameters or a default parameter without
|
|
||||||
* accepting values (both `minNumberValues` and `maxNumberValues` set to 0) will
|
|
||||||
* result in an exception during configuration.
|
|
||||||
*
|
|
||||||
* This property is utilized during the parsing process to determine whether an
|
|
||||||
* unmatched argument should be handled as a default parameter value, simplifying
|
|
||||||
* the handling of positional arguments or other unnamed input data.
|
|
||||||
*/
|
|
||||||
private boolean isDefaultParameter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of alias names associated with a parameter.
|
|
||||||
*
|
|
||||||
* This variable holds alternative names that can be used
|
|
||||||
* to reference the parameter in command-line input. Each alias
|
|
||||||
* functions as an equivalent to the primary parameter name.
|
|
||||||
* Aliases are case-insensitive when used within the command-line parser.
|
|
||||||
*
|
|
||||||
* The aliases associated with a parameter allow greater flexibility
|
|
||||||
* and user convenience when specifying parameters during command-line execution.
|
|
||||||
*/
|
|
||||||
@Singular("alias")
|
|
||||||
private List<String> aliasList;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,11 @@ package de.neitzel.core.commandline;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Parser class is responsible for parsing and handling command-line arguments.
|
* The Parser class is responsible for parsing and handling command-line arguments.
|
||||||
@ -15,32 +19,32 @@ public class Parser {
|
|||||||
/**
|
/**
|
||||||
* A map to store parameters where the key is a string representing
|
* A map to store parameters where the key is a string representing
|
||||||
* the parameter name and the value is the corresponding {@link Parameter} object.
|
* the parameter name and the value is the corresponding {@link Parameter} object.
|
||||||
*
|
* <p>
|
||||||
* This map serves as a registry for defining, organizing, and accessing
|
* This map serves as a registry for defining, organizing, and accessing
|
||||||
* command-line parameters. Each entry holds a parameter's metadata and
|
* command-line parameters. Each entry holds a parameter's metadata and
|
||||||
* its associated configuration to facilitate effective command-line parsing.
|
* its associated configuration to facilitate effective command-line parsing.
|
||||||
*
|
* <p>
|
||||||
* Key considerations:
|
* Key considerations:
|
||||||
* - The key represents the primary name of the parameter.
|
* - The key represents the primary name of the parameter.
|
||||||
* - The value, encapsulated as a {@link Parameter} object, includes
|
* - The value, encapsulated as a {@link Parameter} object, includes
|
||||||
* details such as descriptions, value constraints, aliases, and processing logic.
|
* details such as descriptions, value constraints, aliases, and processing logic.
|
||||||
*/
|
*/
|
||||||
private Map<String, Parameter> parameters = new HashMap<>();
|
private Map<String, Parameter> parameters = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `defaultParameter` variable represents the default command-line parameter for a parser.
|
* The `defaultParameter` variable represents the default command-line parameter for a parser.
|
||||||
*
|
* <p>
|
||||||
* This parameter is used to capture arguments that do not explicitly match any named parameter
|
* This parameter is used to capture arguments that do not explicitly match any named parameter
|
||||||
* or alias. It acts as a catch-all for unnamed command-line input, often simplifying the handling
|
* or alias. It acts as a catch-all for unnamed command-line input, often simplifying the handling
|
||||||
* of positional arguments or other free-form input provided by the user.
|
* of positional arguments or other free-form input provided by the user.
|
||||||
*
|
* <p>
|
||||||
* It is essential to note:
|
* It is essential to note:
|
||||||
* - Only one parameter can be designated as the default parameter in a command-line parser.
|
* - Only one parameter can be designated as the default parameter in a command-line parser.
|
||||||
* - The parameter must allow values to be processed (e.g., its `minNumberValues` or `maxNumberValues`
|
* - The parameter must allow values to be processed (e.g., its `minNumberValues` or `maxNumberValues`
|
||||||
* should not disallow input).
|
* should not disallow input).
|
||||||
* - This parameter is automatically invoked if an input does not match any explicitly named
|
* - This parameter is automatically invoked if an input does not match any explicitly named
|
||||||
* parameter or alias in the parser configuration.
|
* parameter or alias in the parser configuration.
|
||||||
*
|
* <p>
|
||||||
* When set to `null`, the parser does not handle unmatched arguments, and unrecognized inputs
|
* When set to `null`, the parser does not handle unmatched arguments, and unrecognized inputs
|
||||||
* may result in an error or exception, depending on the parser's implementation.
|
* may result in an error or exception, depending on the parser's implementation.
|
||||||
*/
|
*/
|
||||||
@ -48,12 +52,12 @@ public class Parser {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new instance of the `Parser` class.
|
* Constructs a new instance of the `Parser` class.
|
||||||
*
|
* <p>
|
||||||
* The `Parser` class is responsible for parsing and processing
|
* The `Parser` class is responsible for parsing and processing
|
||||||
* command-line arguments. It interprets input data based on defined
|
* command-line arguments. It interprets input data based on defined
|
||||||
* parameters and provides structured access to arguments, enabling
|
* parameters and provides structured access to arguments, enabling
|
||||||
* streamlined application configuration and execution.
|
* streamlined application configuration and execution.
|
||||||
*
|
* <p>
|
||||||
* This constructor initializes the parser without any predefined
|
* This constructor initializes the parser without any predefined
|
||||||
* configuration or parameters. Users can define parameters,
|
* configuration or parameters. Users can define parameters,
|
||||||
* add handlers, and parse command-line arguments using available
|
* add handlers, and parse command-line arguments using available
|
||||||
@ -71,7 +75,6 @@ public class Parser {
|
|||||||
* and other metadata such as whether it is a help command or default parameter.
|
* and other metadata such as whether it is a help command or default parameter.
|
||||||
* The parameter must fulfill specific constraints if defined as the default
|
* The parameter must fulfill specific constraints if defined as the default
|
||||||
* parameter (e.g., accepting values).
|
* parameter (e.g., accepting values).
|
||||||
*
|
|
||||||
* @throws IllegalArgumentException if the parameter is defined as a default parameter and
|
* @throws IllegalArgumentException if the parameter is defined as a default parameter and
|
||||||
* does not accept values (both minNumberValues and maxNumberValues
|
* does not accept values (both minNumberValues and maxNumberValues
|
||||||
* are set to 0).
|
* are set to 0).
|
||||||
@ -81,7 +84,7 @@ public class Parser {
|
|||||||
parameters.put(parameter.getName().toLowerCase(), parameter);
|
parameters.put(parameter.getName().toLowerCase(), parameter);
|
||||||
|
|
||||||
// Add for all aliases
|
// Add for all aliases
|
||||||
for (String alias: parameter.getAliasList()) {
|
for (String alias : parameter.getAliasList()) {
|
||||||
parameters.put(alias.toLowerCase(), parameter);
|
parameters.put(alias.toLowerCase(), parameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,22 +101,39 @@ public class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a given parameter string matches a valid parameter within the defined constraints.
|
* Handles the help command when invoked in the command-line interface, providing information about available parameters.
|
||||||
|
* If no arguments are provided, it lists all possible parameters and their short descriptions.
|
||||||
|
* If specific arguments are supplied, it provides the detailed description of the corresponding parameter.
|
||||||
|
* In case an unknown parameter is specified, it indicates so to the user.
|
||||||
*
|
*
|
||||||
* @param param the parameter string to be checked. This can either be a key defined in the `parameters` map
|
* @param arguments a list of strings representing the user-provided arguments. If empty or {@code null},
|
||||||
* or a potential default parameter if it does not start with a dash ("-").
|
* the method lists all available parameters with their short descriptions. If a parameter name
|
||||||
* @return {@code true} if the parameter is a valid entry in the `parameters` map or matches the default parameter;
|
* is provided in the list, its detailed information is displayed. If an unrecognized parameter
|
||||||
* {@code false} otherwise, including when the parameter starts with a dash ("-").
|
* is provided, a corresponding message is shown.
|
||||||
*/
|
*/
|
||||||
protected boolean isParameter(final String param) {
|
public void helpCommandCallback(final List<String> arguments) {
|
||||||
if (parameters.containsKey(param)) return true;
|
if (arguments == null || arguments.size() == 0) {
|
||||||
if (param.startsWith("-")) return false;
|
System.out.println("Moegliche Parameter der Applikation:");
|
||||||
return defaultParameter != null;
|
parameters.values().stream()
|
||||||
|
.distinct()
|
||||||
|
.sorted(Comparator.comparing(Parameter::getName))
|
||||||
|
.forEach(p -> System.out.println(p.getShortDescription()));
|
||||||
|
} else {
|
||||||
|
Parameter parameter = null;
|
||||||
|
if (parameters.containsKey(arguments.get(0))) parameter = parameters.get(arguments.get(0));
|
||||||
|
if (parameters.containsKey("-" + arguments.get(0))) parameter = parameters.get("-" + arguments.get(0));
|
||||||
|
|
||||||
|
if (parameter == null) {
|
||||||
|
System.out.println("Unbekannter Parameter: " + arguments.get(0));
|
||||||
|
} else {
|
||||||
|
System.out.println(parameter.getLongDescription());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses an array of command-line arguments and processes them as defined by the application's parameters.
|
* Parses an array of command-line arguments and processes them as defined by the application's parameters.
|
||||||
*
|
* <p>
|
||||||
* The method iteratively checks each argument in the provided array, validates it against
|
* The method iteratively checks each argument in the provided array, validates it against
|
||||||
* the registered parameters, and invokes the corresponding processing callback if applicable.
|
* the registered parameters, and invokes the corresponding processing callback if applicable.
|
||||||
* Unrecognized parameters or missing required values will result in an {@code IllegalArgumentException}.
|
* Unrecognized parameters or missing required values will result in an {@code IllegalArgumentException}.
|
||||||
@ -151,6 +171,20 @@ public class Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether a given parameter string matches a valid parameter within the defined constraints.
|
||||||
|
*
|
||||||
|
* @param param the parameter string to be checked. This can either be a key defined in the `parameters` map
|
||||||
|
* or a potential default parameter if it does not start with a dash ("-").
|
||||||
|
* @return {@code true} if the parameter is a valid entry in the `parameters` map or matches the default parameter;
|
||||||
|
* {@code false} otherwise, including when the parameter starts with a dash ("-").
|
||||||
|
*/
|
||||||
|
protected boolean isParameter(final String param) {
|
||||||
|
if (parameters.containsKey(param)) return true;
|
||||||
|
if (param.startsWith("-")) return false;
|
||||||
|
return defaultParameter != null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a list of options by consuming arguments from the provided {@link ArgumentProvider}
|
* Retrieves a list of options by consuming arguments from the provided {@link ArgumentProvider}
|
||||||
* within the range specified by the minimum and maximum values.
|
* within the range specified by the minimum and maximum values.
|
||||||
@ -163,8 +197,8 @@ public class Parser {
|
|||||||
* than `min`, it will be adjusted to match `min`. Only up to `max`
|
* than `min`, it will be adjusted to match `min`. Only up to `max`
|
||||||
* arguments will be added to the result.
|
* arguments will be added to the result.
|
||||||
* @return A list of argument strings, containing up to `max` elements and at least `min`
|
* @return A list of argument strings, containing up to `max` elements and at least `min`
|
||||||
* elements if sufficient arguments are available. The list will not include
|
* elements if sufficient arguments are available. The list will not include
|
||||||
* arguments already recognized in the `parameters` map.
|
* arguments already recognized in the `parameters` map.
|
||||||
*/
|
*/
|
||||||
private List<String> getOptions(ArgumentProvider provider, int min, int max) {
|
private List<String> getOptions(ArgumentProvider provider, int min, int max) {
|
||||||
if (max < min) max = min;
|
if (max < min) max = min;
|
||||||
@ -182,35 +216,4 @@ public class Parser {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the help command when invoked in the command-line interface, providing information about available parameters.
|
|
||||||
* If no arguments are provided, it lists all possible parameters and their short descriptions.
|
|
||||||
* If specific arguments are supplied, it provides the detailed description of the corresponding parameter.
|
|
||||||
* In case an unknown parameter is specified, it indicates so to the user.
|
|
||||||
*
|
|
||||||
* @param arguments a list of strings representing the user-provided arguments. If empty or {@code null},
|
|
||||||
* the method lists all available parameters with their short descriptions. If a parameter name
|
|
||||||
* is provided in the list, its detailed information is displayed. If an unrecognized parameter
|
|
||||||
* is provided, a corresponding message is shown.
|
|
||||||
*/
|
|
||||||
public void helpCommandCallback(final List<String> arguments) {
|
|
||||||
if (arguments == null || arguments.size() == 0) {
|
|
||||||
System.out.println("Moegliche Parameter der Applikation:");
|
|
||||||
parameters.values().stream()
|
|
||||||
.distinct()
|
|
||||||
.sorted(Comparator.comparing(Parameter::getName))
|
|
||||||
.forEach(p -> System.out.println(p.getShortDescription() ));
|
|
||||||
} else {
|
|
||||||
Parameter parameter = null;
|
|
||||||
if (parameters.containsKey(arguments.get(0))) parameter = parameters.get(arguments.get(0));
|
|
||||||
if (parameters.containsKey("-" + arguments.get(0))) parameter = parameters.get("-" + arguments.get(0));
|
|
||||||
|
|
||||||
if (parameter == null) {
|
|
||||||
System.out.println("Unbekannter Parameter: " + arguments.get(0));
|
|
||||||
} else {
|
|
||||||
System.out.println(parameter.getLongDescription());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,19 @@ import java.io.InputStream;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class ImageScaler {
|
public class ImageScaler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the target image format for scaling operations.
|
||||||
|
* <p>
|
||||||
|
* 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").
|
||||||
|
* <p>
|
||||||
|
* 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";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private constructor to prevent instantiation of this utility class.
|
* Private constructor to prevent instantiation of this utility class.
|
||||||
*/
|
*/
|
||||||
@ -24,59 +37,17 @@ public class ImageScaler {
|
|||||||
throw new UnsupportedOperationException("Utility class cannot be instantiated");
|
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.
|
* Scales an image provided as a byte array to the specified dimensions and returns an InputStream of the scaled image.
|
||||||
*
|
* <p>
|
||||||
* The scaling behavior is determined by the enforceSize parameter:
|
* 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 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.
|
* - 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 originalImageBytes The byte array representing the original image.
|
||||||
* @param width The (maximum) width of the target scaled image.
|
* @param width The (maximum) width of the target scaled image.
|
||||||
* @param height The (maximum) height 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.
|
* @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.
|
* @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) {
|
public static InputStream scaledImage(final byte[] originalImageBytes, final int width, final int height, final boolean enforceSize) {
|
||||||
@ -91,20 +62,49 @@ public class ImageScaler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a scaled version of the given {@link BufferedImage} and returns it as a byte array.
|
* Creates a scaled image from the given byte array representation and returns it as a byte array.
|
||||||
|
* <p>
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a scaled version of the given {@link BufferedImage} and returns it as a byte array.
|
||||||
|
* <p>
|
||||||
* The scaling behavior is determined by the enforceSize parameter:
|
* The scaling behavior is determined by the enforceSize parameter:
|
||||||
* - If enforceSize is false, the provided dimensions are treated as maximum values, and the image
|
* - 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
|
* will maintain its original aspect ratio. The resulting image may be smaller than the specified
|
||||||
* dimensions.
|
* dimensions.
|
||||||
* - If enforceSize is true, the resulting image will match the exact specified dimensions, potentially
|
* - If enforceSize is true, the resulting image will match the exact specified dimensions, potentially
|
||||||
* adding transparent padding to fit the aspect ratio.
|
* adding transparent padding to fit the aspect ratio.
|
||||||
*
|
*
|
||||||
* @param originalImage The original image to be scaled. Must not be null.
|
* @param originalImage The original image to be scaled. Must not be null.
|
||||||
* @param width The specified (maximum) width of the scaled image.
|
* @param width The specified (maximum) width of the scaled image.
|
||||||
* @param height The specified (maximum) height 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
|
* @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.
|
* 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.
|
* @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) {
|
protected static byte[] createScaledImage(final BufferedImage originalImage, final int width, final int height, final boolean enforceSize) {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import java.util.stream.Stream;
|
|||||||
/**
|
/**
|
||||||
* Represents the context of the application and serves as the foundation of a dependency
|
* Represents the context of the application and serves as the foundation of a dependency
|
||||||
* injection framework.
|
* injection framework.
|
||||||
*
|
* <p>
|
||||||
* The {@code ApplicationContext} is responsible for scanning, instantiating, and managing
|
* The {@code ApplicationContext} is responsible for scanning, instantiating, and managing
|
||||||
* the lifecycle of components. Components are identified through the specified base package
|
* the lifecycle of components. Components are identified through the specified base package
|
||||||
* (or derived from a configuration class) and instantiated based on their dependency
|
* (or derived from a configuration class) and instantiated based on their dependency
|
||||||
@ -52,17 +52,17 @@ public class ApplicationContext {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves an instance of the specified component type from the application context.
|
* Retrieves an instance of the specified component type from the application context.
|
||||||
*
|
* <p>
|
||||||
* This method uses dependency injection to construct the component if it is not already
|
* This method uses dependency injection to construct the component if it is not already
|
||||||
* a singleton instance. The component's type and scope are determined by the framework,
|
* a singleton instance. The component's type and scope are determined by the framework,
|
||||||
* and the appropriate initialization and lifecycle management are performed.
|
* and the appropriate initialization and lifecycle management are performed.
|
||||||
*
|
*
|
||||||
* @param <T> the type of the component to retrieve
|
* @param <T> the type of the component to retrieve
|
||||||
* @param type the {@code Class} object representing the type of the component
|
* @param type the {@code Class} object representing the type of the component
|
||||||
* @return an instance of the requested component type
|
* @return an instance of the requested component type
|
||||||
* @throws IllegalArgumentException if no component is found for the specified type
|
* @throws IllegalArgumentException if no component is found for the specified type
|
||||||
* @throws IllegalStateException if no suitable constructor is found for the component
|
* @throws IllegalStateException if no suitable constructor is found for the component
|
||||||
* @throws RuntimeException if any error occurs during instantiation
|
* @throws RuntimeException if any error occurs during instantiation
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public <T> T getComponent(Class<? extends T> type) {
|
public <T> T getComponent(Class<? extends T> type) {
|
||||||
@ -85,7 +85,7 @@ public class ApplicationContext {
|
|||||||
Class<?>[] paramTypes = constructor.getParameterTypes();
|
Class<?>[] paramTypes = constructor.getParameterTypes();
|
||||||
Object[] parameters = new Object[paramTypes.length];
|
Object[] parameters = new Object[paramTypes.length];
|
||||||
for (int i = 0; i < paramTypes.length; i++) {
|
for (int i = 0; i < paramTypes.length; i++) {
|
||||||
parameters[i] = getComponent(paramTypes[i]);
|
parameters[i] = getComponent(paramTypes[i]);
|
||||||
}
|
}
|
||||||
instance = constructor.newInstance(parameters);
|
instance = constructor.newInstance(parameters);
|
||||||
if (scope == Scope.SINGLETON) {
|
if (scope == Scope.SINGLETON) {
|
||||||
@ -103,12 +103,12 @@ public class ApplicationContext {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if a given constructor can be instantiated using the provided parameter map.
|
* Determines if a given constructor can be instantiated using the provided parameter map.
|
||||||
*
|
* <p>
|
||||||
* The method evaluates whether every parameter type required by the constructor
|
* The method evaluates whether every parameter type required by the constructor
|
||||||
* is available in the given parameter map. If all parameter types are present,
|
* is available in the given parameter map. If all parameter types are present,
|
||||||
* the constructor is considered instantiable.
|
* the constructor is considered instantiable.
|
||||||
*
|
*
|
||||||
* @param constructor the constructor to check for instantiation feasibility
|
* @param constructor the constructor to check for instantiation feasibility
|
||||||
* @param parameterMap a map containing available parameter types and their associated component data
|
* @param parameterMap a map containing available parameter types and their associated component data
|
||||||
* @return true if all parameter types required by the constructor are contained in the parameter map, false otherwise
|
* @return true if all parameter types required by the constructor are contained in the parameter map, false otherwise
|
||||||
*/
|
*/
|
||||||
@ -118,14 +118,14 @@ public class ApplicationContext {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a singleton instance of a specific type into the application context.
|
* Registers a singleton instance of a specific type into the application context.
|
||||||
*
|
* <p>
|
||||||
* This method allows manual addition of singleton components to the context by
|
* This method allows manual addition of singleton components to the context by
|
||||||
* providing a concrete instance of the required type. If the type is already mapped
|
* providing a concrete instance of the required type. If the type is already mapped
|
||||||
* to a different instance or type within the context, an {@link IllegalStateException}
|
* to a different instance or type within the context, an {@link IllegalStateException}
|
||||||
* is thrown to prevent replacement of an existing singleton.
|
* is thrown to prevent replacement of an existing singleton.
|
||||||
*
|
*
|
||||||
* @param <T> the type of the component to register
|
* @param <T> the type of the component to register
|
||||||
* @param type the class type of the component being added
|
* @param type the class type of the component being added
|
||||||
* @param instance the singleton instance of the component to register
|
* @param instance the singleton instance of the component to register
|
||||||
* @throws IllegalStateException if the type is already registered with a different instance
|
* @throws IllegalStateException if the type is already registered with a different instance
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import lombok.Getter;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a component in a dependency injection framework.
|
* Represents a component in a dependency injection framework.
|
||||||
*
|
* <p>
|
||||||
* A component is a unit of functionality that is managed by the framework, allowing
|
* A component is a unit of functionality that is managed by the framework, allowing
|
||||||
* for controlled instantiation and lifecycle management. The component is associated
|
* for controlled instantiation and lifecycle management. The component is associated
|
||||||
* with a specific type and a scope. The scope determines whether the component is
|
* with a specific type and a scope. The scope determines whether the component is
|
||||||
@ -15,7 +15,7 @@ public class ComponentData {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the type of the component being managed by the dependency injection framework.
|
* Represents the type of the component being managed by the dependency injection framework.
|
||||||
*
|
* <p>
|
||||||
* This variable holds the class object corresponding to the specific type of the component.
|
* This variable holds the class object corresponding to the specific type of the component.
|
||||||
* It is used to identify and instantiate the component during the dependency injection process.
|
* It is used to identify and instantiate the component during the dependency injection process.
|
||||||
* The type is immutable and is specified when the component is created.
|
* The type is immutable and is specified when the component is created.
|
||||||
@ -24,10 +24,10 @@ public class ComponentData {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the lifecycle and instantiation rules for the associated component.
|
* Defines the lifecycle and instantiation rules for the associated component.
|
||||||
*
|
* <p>
|
||||||
* The {@code Scope} determines whether the component is created as a singleton
|
* The {@code Scope} determines whether the component is created as a singleton
|
||||||
* (a single shared instance) or as a prototype (a new instance for each request).
|
* (a single shared instance) or as a prototype (a new instance for each request).
|
||||||
*
|
* <p>
|
||||||
* This variable is immutable and represents the specific {@code Scope} assigned
|
* This variable is immutable and represents the specific {@code Scope} assigned
|
||||||
* to the component, influencing its behavior within the dependency injection framework.
|
* to the component, influencing its behavior within the dependency injection framework.
|
||||||
*/
|
*/
|
||||||
@ -35,7 +35,7 @@ public class ComponentData {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the instantiated object associated with the component.
|
* Stores the instantiated object associated with the component.
|
||||||
*
|
* <p>
|
||||||
* This field holds the actual instance of the component's type within the dependency
|
* This field holds the actual instance of the component's type within the dependency
|
||||||
* injection framework. For components with a {@code SINGLETON} scope, this field is
|
* injection framework. For components with a {@code SINGLETON} scope, this field is
|
||||||
* set only once and shared across the entire application. For components with a
|
* set only once and shared across the entire application. For components with a
|
||||||
@ -47,7 +47,7 @@ public class ComponentData {
|
|||||||
/**
|
/**
|
||||||
* Constructs a new Component instance with the specified type and scope.
|
* Constructs a new Component instance with the specified type and scope.
|
||||||
*
|
*
|
||||||
* @param type the class type of the component
|
* @param type the class type of the component
|
||||||
* @param scope the scope of the component, which determines its lifecycle
|
* @param scope the scope of the component, which determines its lifecycle
|
||||||
*/
|
*/
|
||||||
public ComponentData(Class<?> type, Scope scope) {
|
public ComponentData(Class<?> type, Scope scope) {
|
||||||
@ -57,10 +57,10 @@ public class ComponentData {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new ComponentData instance with the specified type and an initial instance.
|
* Constructs a new ComponentData instance with the specified type and an initial instance.
|
||||||
*
|
* <p>
|
||||||
* This can be used to add Singletons manually without scanning for them.
|
* This can be used to add Singletons manually without scanning for them.
|
||||||
*
|
*
|
||||||
* @param type the class type of the component
|
* @param type the class type of the component
|
||||||
* @param instance the initial instance of the component
|
* @param instance the initial instance of the component
|
||||||
*/
|
*/
|
||||||
public ComponentData(Class<?> type, Object instance) {
|
public ComponentData(Class<?> type, Object instance) {
|
||||||
|
|||||||
@ -5,7 +5,13 @@ import de.neitzel.core.inject.annotation.Inject;
|
|||||||
import org.reflections.Reflections;
|
import org.reflections.Reflections;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -21,13 +27,13 @@ public class ComponentScanner {
|
|||||||
* FXML-compatible components detected during the scanning process.
|
* FXML-compatible components detected during the scanning process.
|
||||||
* These components are used as part of the dependency injection mechanism
|
* These components are used as part of the dependency injection mechanism
|
||||||
* within the {@code InjectableComponentScanner}.
|
* within the {@code InjectableComponentScanner}.
|
||||||
*
|
* <p>
|
||||||
* The {@code fxmlComponents} set is populated during the component scanning
|
* The {@code fxmlComponents} set is populated during the component scanning
|
||||||
* phase by identifying all classes within a specified package hierarchy
|
* phase by identifying all classes within a specified package hierarchy
|
||||||
* annotated with the {@code @Component} annotation. These classes serve
|
* annotated with the {@code @Component} annotation. These classes serve
|
||||||
* as the primary source for further analysis, resolution, and instantiation
|
* as the primary source for further analysis, resolution, and instantiation
|
||||||
* in the scanning workflow.
|
* in the scanning workflow.
|
||||||
*
|
* <p>
|
||||||
* This field is immutable, ensuring thread safety and consistent state
|
* This field is immutable, ensuring thread safety and consistent state
|
||||||
* throughout the lifetime of the {@code InjectableComponentScanner}.
|
* throughout the lifetime of the {@code InjectableComponentScanner}.
|
||||||
*/
|
*/
|
||||||
@ -35,12 +41,12 @@ public class ComponentScanner {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A set of component types that are not uniquely associated with a single implementation.
|
* A set of component types that are not uniquely associated with a single implementation.
|
||||||
*
|
* <p>
|
||||||
* This collection is populated during the analysis of discovered components to identify
|
* This collection is populated during the analysis of discovered components to identify
|
||||||
* types that have multiple implementations, preventing them from being uniquely resolved.
|
* types that have multiple implementations, preventing them from being uniquely resolved.
|
||||||
* For example, if multiple components implement the same interface, that interface will be
|
* For example, if multiple components implement the same interface, that interface will be
|
||||||
* added to this set.
|
* added to this set.
|
||||||
*
|
* <p>
|
||||||
* It is primarily used during the component registration process to avoid ambiguities
|
* It is primarily used during the component registration process to avoid ambiguities
|
||||||
* in the dependency injection system, ensuring that only resolvable, uniquely identifiable
|
* in the dependency injection system, ensuring that only resolvable, uniquely identifiable
|
||||||
* components can be instantiated and injected.
|
* components can be instantiated and injected.
|
||||||
@ -52,13 +58,13 @@ public class ComponentScanner {
|
|||||||
* The key represents a super type (interface or abstract class), and the value
|
* The key represents a super type (interface or abstract class), and the value
|
||||||
* is the specific implementation or subclass that is uniquely identified among
|
* is the specific implementation or subclass that is uniquely identified among
|
||||||
* the scanned components.
|
* the scanned components.
|
||||||
*
|
* <p>
|
||||||
* This map is populated during the component analysis process. For each super type
|
* 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
|
* in the scanned components, if exactly one implementation is found, it is
|
||||||
* considered unique and added to this mapping. In case multiple implementations
|
* 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
|
* exist for a given super type, it is excluded from this map and classified as
|
||||||
* a non-unique type.
|
* a non-unique type.
|
||||||
*
|
* <p>
|
||||||
* This mapping is utilized for resolving dependencies and determining which components
|
* This mapping is utilized for resolving dependencies and determining which components
|
||||||
* can be instantiated based on unique type information.
|
* can be instantiated based on unique type information.
|
||||||
*/
|
*/
|
||||||
@ -70,7 +76,7 @@ public class ComponentScanner {
|
|||||||
* that can be instantiated. The entries are resolved through the scanning and analysis
|
* that can be instantiated. The entries are resolved through the scanning and analysis
|
||||||
* of available component types, ensuring that only components with resolvable
|
* of available component types, ensuring that only components with resolvable
|
||||||
* dependencies are included.
|
* dependencies are included.
|
||||||
*
|
* <p>
|
||||||
* This field is part of the dependency injection process, where components annotated
|
* This field is part of the dependency injection process, where components annotated
|
||||||
* with {@code @Component} or similar annotations are scanned, analyzed, and registered.
|
* with {@code @Component} or similar annotations are scanned, analyzed, and registered.
|
||||||
* The resolution process checks if a component can be instantiated based on its
|
* The resolution process checks if a component can be instantiated based on its
|
||||||
@ -81,11 +87,11 @@ public class ComponentScanner {
|
|||||||
/**
|
/**
|
||||||
* A list of error messages encountered during the scanning and analysis
|
* A list of error messages encountered during the scanning and analysis
|
||||||
* of components in the dependency injection process.
|
* of components in the dependency injection process.
|
||||||
*
|
* <p>
|
||||||
* This list is populated when components cannot be resolved due to issues
|
* This list is populated when components cannot be resolved due to issues
|
||||||
* such as multiple implementations of a superclass or interface,
|
* such as multiple implementations of a superclass or interface,
|
||||||
* unmet construction requirements, or unresolved dependencies.
|
* unmet construction requirements, or unresolved dependencies.
|
||||||
*
|
* <p>
|
||||||
* Errors added to this list provide detailed descriptions of the specific
|
* Errors added to this list provide detailed descriptions of the specific
|
||||||
* issues that prevent certain components from being instantiated correctly.
|
* issues that prevent certain components from being instantiated correctly.
|
||||||
*/
|
*/
|
||||||
@ -117,19 +123,19 @@ public class ComponentScanner {
|
|||||||
/**
|
/**
|
||||||
* Analyzes component types within the injected components and classifies them based on their
|
* Analyzes component types within the injected components and classifies them based on their
|
||||||
* inheritance hierarchy and relationships.
|
* inheritance hierarchy and relationships.
|
||||||
*
|
* <p>
|
||||||
* This method performs the following operations:
|
* This method performs the following operations:
|
||||||
* 1. Maps each component to all of its superclasses and interfaces.
|
* 1. Maps each component to all of its superclasses and interfaces.
|
||||||
* 2. Identifies which superclasses or interfaces have multiple implementing components.
|
* 2. Identifies which superclasses or interfaces have multiple implementing components.
|
||||||
* 3. Populates:
|
* 3. Populates:
|
||||||
* - A list of superclasses or interfaces that are not uniquely linked to a single component.
|
* - 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.
|
* - A map where unique superclasses or interfaces are associated with a specific implementing component.
|
||||||
*
|
* <p>
|
||||||
* The mappings are built using the following data structures:
|
* 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 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 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.
|
* - A map of unique superclasses/interfaces to their corresponding component.
|
||||||
*
|
* <p>
|
||||||
* This method is a key part of the component scanning and resolution process, facilitating
|
* This method is a key part of the component scanning and resolution process, facilitating
|
||||||
* the identification of potential instantiation ambiguities or conflicts.
|
* the identification of potential instantiation ambiguities or conflicts.
|
||||||
*/
|
*/
|
||||||
@ -160,22 +166,22 @@ public class ComponentScanner {
|
|||||||
* Resolves and identifies instantiable components from a set of scanned components.
|
* Resolves and identifies instantiable components from a set of scanned components.
|
||||||
* This process determines which components can be instantiated based on their dependencies
|
* This process determines which components can be instantiated based on their dependencies
|
||||||
* and class relationships, while tracking unresolved types and potential conflicts.
|
* and class relationships, while tracking unresolved types and potential conflicts.
|
||||||
*
|
* <p>
|
||||||
* The resolution process involves:
|
* The resolution process involves:
|
||||||
* 1. Iteratively determining which components can be instantiated using the known types
|
* 1. Iteratively determining which components can be instantiated using the known types
|
||||||
* map. A component is resolvable if its dependencies can be satisfied by the current
|
* map. A component is resolvable if its dependencies can be satisfied by the current
|
||||||
* set of known types.
|
* set of known types.
|
||||||
* 2. Registering resolvable components and their superclasses/interfaces into the known
|
* 2. Registering resolvable components and their superclasses/interfaces into the known
|
||||||
* types map for future iterations.
|
* types map for future iterations.
|
||||||
* 3. Removing successfully resolved components from the unresolved set.
|
* 3. Removing successfully resolved components from the unresolved set.
|
||||||
* 4. Repeating the process until no further components can be resolved in a given iteration.
|
* 4. Repeating the process until no further components can be resolved in a given iteration.
|
||||||
*
|
* <p>
|
||||||
* At the end of the resolution process:
|
* At the end of the resolution process:
|
||||||
* - Resolvable components are added to the `instantiableComponents` map, which maps types
|
* - Resolvable components are added to the `instantiableComponents` map, which maps types
|
||||||
* to their corresponding instantiable implementations.
|
* to their corresponding instantiable implementations.
|
||||||
* - Unresolved components are identified, and error details are collected to highlight
|
* - Unresolved components are identified, and error details are collected to highlight
|
||||||
* dependencies or conflicts preventing their instantiation.
|
* dependencies or conflicts preventing their instantiation.
|
||||||
*
|
* <p>
|
||||||
* If errors are encountered due to unresolved components, they are logged for further analysis.
|
* If errors are encountered due to unresolved components, they are logged for further analysis.
|
||||||
*/
|
*/
|
||||||
private void resolveInstantiableComponents() {
|
private void resolveInstantiableComponents() {
|
||||||
@ -208,21 +214,22 @@ public class ComponentScanner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a component and its superclasses or interfaces in the provided map of known types.
|
* Retrieves all superclasses and interfaces of the specified class, excluding the {@code Object} class.
|
||||||
* This method ensures that the component and its inheritance hierarchy are associated with the
|
|
||||||
* component's data unless the supertype is marked as non-unique.
|
|
||||||
*
|
*
|
||||||
* @param component the {@code ComponentData} instance representing the component to be registered
|
* @param clazz the class for which to retrieve all superclasses and interfaces
|
||||||
* @param knownTypes the map where component types and their data are stored
|
* @return a set of all superclasses and interfaces implemented by the specified class
|
||||||
*/
|
*/
|
||||||
private void registerComponentWithSuperTypes(ComponentData component, Map<Class<?>, ComponentData> knownTypes) {
|
private Set<Class<?>> getAllSuperTypes(Class<?> clazz) {
|
||||||
knownTypes.put(component.getType(), component);
|
Set<Class<?>> result = new HashSet<>();
|
||||||
|
Class<?> superClass = clazz.getSuperclass();
|
||||||
|
|
||||||
for (Class<?> superType : getAllSuperTypes(component.getType())) {
|
while (superClass != null && superClass != Object.class) {
|
||||||
if (!notUniqueTypes.contains(superType)) {
|
result.add(superClass);
|
||||||
knownTypes.put(superType, component);
|
superClass = superClass.getSuperclass();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.addAll(Arrays.asList(clazz.getInterfaces()));
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -232,7 +239,7 @@ public class ComponentScanner {
|
|||||||
* Additionally, all fields annotated with {@code @Inject} must have their types present in the
|
* Additionally, all fields annotated with {@code @Inject} must have their types present in the
|
||||||
* known types set.
|
* known types set.
|
||||||
*
|
*
|
||||||
* @param component the class to check for instantiability
|
* @param component the class to check for instantiability
|
||||||
* @param knownTypes the set of currently known types that can be used to satisfy dependencies
|
* @param knownTypes the set of currently known types that can be used to satisfy dependencies
|
||||||
* @return {@code true} if the component can be instantiated; {@code false} otherwise
|
* @return {@code true} if the component can be instantiated; {@code false} otherwise
|
||||||
*/
|
*/
|
||||||
@ -260,6 +267,24 @@ public class ComponentScanner {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a component and its superclasses or interfaces in the provided map of known types.
|
||||||
|
* This method ensures that the component and its inheritance hierarchy are associated with the
|
||||||
|
* component's data unless the supertype is marked as non-unique.
|
||||||
|
*
|
||||||
|
* @param component the {@code ComponentData} instance representing the component to be registered
|
||||||
|
* @param knownTypes the map where component types and their data are stored
|
||||||
|
*/
|
||||||
|
private void registerComponentWithSuperTypes(ComponentData component, Map<Class<?>, ComponentData> knownTypes) {
|
||||||
|
knownTypes.put(component.getType(), component);
|
||||||
|
|
||||||
|
for (Class<?> superType : getAllSuperTypes(component.getType())) {
|
||||||
|
if (!notUniqueTypes.contains(superType)) {
|
||||||
|
knownTypes.put(superType, component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collects the instantiation errors for a set of unresolved classes and appends
|
* Collects the instantiation errors for a set of unresolved classes and appends
|
||||||
* detailed error messages to the internal error list. This method analyzes unresolved
|
* detailed error messages to the internal error list. This method analyzes unresolved
|
||||||
@ -304,7 +329,7 @@ public class ComponentScanner {
|
|||||||
*
|
*
|
||||||
* @param component the class for which conflicting types need to be identified.
|
* @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,
|
* @return a comma-separated string of fully qualified names of conflicting parameter types,
|
||||||
* or an empty string if no conflicts are found.
|
* 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())
|
||||||
@ -314,30 +339,11 @@ public class ComponentScanner {
|
|||||||
.collect(Collectors.joining(", "));
|
.collect(Collectors.joining(", "));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves all superclasses and interfaces of the specified class, excluding the {@code Object} 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<>();
|
|
||||||
Class<?> superClass = clazz.getSuperclass();
|
|
||||||
|
|
||||||
while (superClass != null && superClass != Object.class) {
|
|
||||||
result.add(superClass);
|
|
||||||
superClass = superClass.getSuperclass();
|
|
||||||
}
|
|
||||||
|
|
||||||
result.addAll(Arrays.asList(clazz.getInterfaces()));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a map of classes representing component types to their corresponding instantiable implementations.
|
* Retrieves a map of classes representing component types to their corresponding instantiable implementations.
|
||||||
*
|
*
|
||||||
* @return A map where the key is a class type and the value is the corresponding class implementation
|
* @return A map where the key is a class type and the value is the corresponding class implementation
|
||||||
* that can be instantiated.
|
* that can be instantiated.
|
||||||
*/
|
*/
|
||||||
public Map<Class<?>, ComponentData> getInstantiableComponents() {
|
public Map<Class<?>, ComponentData> getInstantiableComponents() {
|
||||||
return instantiableComponents;
|
return instantiableComponents;
|
||||||
@ -349,7 +355,7 @@ public class ComponentScanner {
|
|||||||
* component scanning and analysis process.
|
* component scanning and analysis process.
|
||||||
*
|
*
|
||||||
* @return a set of classes representing the types that have multiple
|
* @return a set of classes representing the types that have multiple
|
||||||
* implementations and cannot be resolved uniquely
|
* implementations and cannot be resolved uniquely
|
||||||
*/
|
*/
|
||||||
public Set<Class<?>> getNotUniqueTypes() {
|
public Set<Class<?>> getNotUniqueTypes() {
|
||||||
return notUniqueTypes;
|
return notUniqueTypes;
|
||||||
@ -361,7 +367,7 @@ public class ComponentScanner {
|
|||||||
* implementation, ensuring no ambiguity in resolution.
|
* implementation, ensuring no ambiguity in resolution.
|
||||||
*
|
*
|
||||||
* @return a map where the keys are types (e.g., interfaces or superclasses)
|
* @return a map where the keys are types (e.g., interfaces or superclasses)
|
||||||
* and the values are their unique component implementations.
|
* and the values are their unique component implementations.
|
||||||
*/
|
*/
|
||||||
public Map<Class<?>, Class<?>> getUniqueTypeToComponent() {
|
public Map<Class<?>, Class<?>> getUniqueTypeToComponent() {
|
||||||
return uniqueTypeToComponent;
|
return uniqueTypeToComponent;
|
||||||
|
|||||||
@ -11,14 +11,14 @@ import java.lang.annotation.Target;
|
|||||||
* Indicates that an annotated class is a "component" within a dependency injection framework.
|
* 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
|
* Classes annotated with {@code @Component} are recognized during the component scanning process
|
||||||
* as candidates for instantiation and management by the dependency injection framework.
|
* as candidates for instantiation and management by the dependency injection framework.
|
||||||
*
|
* <p>
|
||||||
* This annotation is typically used on classes that represent application-specific components,
|
* This annotation is typically used on classes that represent application-specific components,
|
||||||
* such as service implementations, controllers, or other objects intended to be instantiated
|
* such as service implementations, controllers, or other objects intended to be instantiated
|
||||||
* and injected into other components during runtime.
|
* and injected into other components during runtime.
|
||||||
*
|
* <p>
|
||||||
* The annotation must be placed at the type level, and it is retained at 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.
|
* to allow for reflection-based scanning of classes within specified packages.
|
||||||
*
|
* <p>
|
||||||
* Usage of this annotation assumes that there exists a component scanning mechanism
|
* Usage of this annotation assumes that there exists a component scanning mechanism
|
||||||
* that processes annotated classes and identifies their roles, dependencies, and hierarchy
|
* that processes annotated classes and identifies their roles, dependencies, and hierarchy
|
||||||
* within the application's structure.
|
* within the application's structure.
|
||||||
|
|||||||
@ -10,17 +10,17 @@ import java.lang.annotation.Target;
|
|||||||
* injection framework. Classes annotated with {@code @Config} are used as markers
|
* injection framework. Classes annotated with {@code @Config} are used as markers
|
||||||
* for defining settings and application-specific configurations required by the
|
* for defining settings and application-specific configurations required by the
|
||||||
* dependency injection mechanism.
|
* dependency injection mechanism.
|
||||||
*
|
* <p>
|
||||||
* Typically, configuration classes provide metadata required for setting up the
|
* Typically, configuration classes provide metadata required for setting up the
|
||||||
* framework, such as specifying the base package to scan for components.
|
* framework, such as specifying the base package to scan for components.
|
||||||
*
|
* <p>
|
||||||
* This annotation must be applied at the type level and is retained at runtime to
|
* This annotation must be applied at the type level and is retained at runtime to
|
||||||
* facilitate reflection-based processing. It is intended to serve as a declarative
|
* facilitate reflection-based processing. It is intended to serve as a declarative
|
||||||
* representation of configuration options for the dependency injection container.
|
* representation of configuration options for the dependency injection container.
|
||||||
*
|
* <p>
|
||||||
* Attributes:
|
* Attributes:
|
||||||
* - {@code basePackage}: Specifies the package name where the framework should scan
|
* - {@code basePackage}: Specifies the package name where the framework should scan
|
||||||
* for classes annotated with dependency injection annotations such as {@code @Component}.
|
* for classes annotated with dependency injection annotations such as {@code @Component}.
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
@ -31,7 +31,7 @@ public @interface Config {
|
|||||||
* can be scanned and identified as candidates for dependency injection.
|
* can be scanned and identified as candidates for dependency injection.
|
||||||
*
|
*
|
||||||
* @return the base package name as a string; returns an empty string by default
|
* @return the base package name as a string; returns an empty string by default
|
||||||
* if no specific package is defined.
|
* if no specific package is defined.
|
||||||
*/
|
*/
|
||||||
String basePackage() default "";
|
String basePackage() default "";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,10 +10,10 @@ import java.lang.annotation.Target;
|
|||||||
* a dependency injection framework. Fields annotated with {@code @Inject}
|
* a dependency injection framework. Fields annotated with {@code @Inject}
|
||||||
* are automatically populated with the required component instance during runtime,
|
* are automatically populated with the required component instance during runtime,
|
||||||
* typically by the dependency injection container.
|
* typically by the dependency injection container.
|
||||||
*
|
* <p>
|
||||||
* This annotation must be applied at the field level and is retained at runtime
|
* This annotation must be applied at the field level and is retained at runtime
|
||||||
* to enable reflection-based identification and assignment of dependencies.
|
* to enable reflection-based identification and assignment of dependencies.
|
||||||
*
|
* <p>
|
||||||
* The framework's dependency resolution mechanism identifies the appropriate
|
* The framework's dependency resolution mechanism identifies the appropriate
|
||||||
* instance to inject based on the field's type or custom configuration,
|
* instance to inject based on the field's type or custom configuration,
|
||||||
* ensuring loose coupling and easier testability.
|
* ensuring loose coupling and easier testability.
|
||||||
|
|||||||
@ -2,7 +2,11 @@ package de.neitzel.core.io;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.IllegalCharsetNameException;
|
import java.nio.charset.IllegalCharsetNameException;
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,15 @@ package de.neitzel.core.io;
|
|||||||
import de.neitzel.core.util.ArrayUtils;
|
import de.neitzel.core.util.ArrayUtils;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
|||||||
@ -13,7 +13,7 @@ public class StringEncoder {
|
|||||||
* static utility methods for array-related operations.
|
* static utility methods for array-related operations.
|
||||||
*
|
*
|
||||||
* @throws UnsupportedOperationException always, to indicate that this class
|
* @throws UnsupportedOperationException always, to indicate that this class
|
||||||
* should not be instantiated.
|
* should not be instantiated.
|
||||||
*/
|
*/
|
||||||
private StringEncoder() {
|
private StringEncoder() {
|
||||||
throw new UnsupportedOperationException("Utility class");
|
throw new UnsupportedOperationException("Utility class");
|
||||||
@ -27,7 +27,7 @@ public class StringEncoder {
|
|||||||
* @param data the string to decode; may contain encoded characters or regular text. If null, an empty string is returned.
|
* @param data the string to decode; may contain encoded characters or regular text. If null, an empty string is returned.
|
||||||
* @return the decoded string with all encoded characters replaced by their original representations.
|
* @return the decoded string with all encoded characters replaced by their original representations.
|
||||||
* @throws IllegalArgumentException if the input string has encoding markup
|
* @throws IllegalArgumentException if the input string has encoding markup
|
||||||
* that is improperly formatted or incomplete.
|
* that is improperly formatted or incomplete.
|
||||||
*/
|
*/
|
||||||
public static String decodeData(final String data) {
|
public static String decodeData(final String data) {
|
||||||
if (data == null) return "";
|
if (data == null) return "";
|
||||||
@ -38,17 +38,17 @@ public class StringEncoder {
|
|||||||
int indexAmp = remaining.indexOf("&");
|
int indexAmp = remaining.indexOf("&");
|
||||||
if (indexAmp == -1) {
|
if (indexAmp == -1) {
|
||||||
result.append(remaining);
|
result.append(remaining);
|
||||||
remaining="";
|
remaining = "";
|
||||||
} else {
|
} else {
|
||||||
// First get the elements till the &
|
// First get the elements till the &
|
||||||
if (indexAmp > 0) {
|
if (indexAmp > 0) {
|
||||||
result.append(remaining.substring(0, indexAmp));
|
result.append(remaining.substring(0, indexAmp));
|
||||||
remaining = remaining.substring(indexAmp);
|
remaining = remaining.substring(indexAmp);
|
||||||
}
|
}
|
||||||
int endSpecial=remaining.indexOf(";");
|
int endSpecial = remaining.indexOf(";");
|
||||||
if (endSpecial == -1) throw new IllegalArgumentException("String couldn't be decoded! (" + data + ")");
|
if (endSpecial == -1) throw new IllegalArgumentException("String couldn't be decoded! (" + data + ")");
|
||||||
String special = remaining.substring(0, endSpecial+1);
|
String special = remaining.substring(0, endSpecial + 1);
|
||||||
remaining = remaining.substring(endSpecial+1);
|
remaining = remaining.substring(endSpecial + 1);
|
||||||
result.append(decodeCharacter(special));
|
result.append(decodeCharacter(special));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,7 +67,7 @@ public class StringEncoder {
|
|||||||
public static char decodeCharacter(final String data) {
|
public static char decodeCharacter(final String data) {
|
||||||
if (!data.startsWith("&#")) throw new IllegalArgumentException("Data does not start with &# (" + data + ")");
|
if (!data.startsWith("&#")) throw new IllegalArgumentException("Data does not start with &# (" + data + ")");
|
||||||
if (!data.endsWith(";")) throw new IllegalArgumentException("Data does not end with ; (" + data + ")");
|
if (!data.endsWith(";")) throw new IllegalArgumentException("Data does not end with ; (" + data + ")");
|
||||||
return (char)Integer.parseInt(data.substring(2, data.length()-1));
|
return (char) Integer.parseInt(data.substring(2, data.length() - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,16 +77,16 @@ public class StringEncoder {
|
|||||||
*
|
*
|
||||||
* @param data the input string to encode; if null, an empty string is returned
|
* @param data the input string to encode; if null, an empty string is returned
|
||||||
* @return an encoded string where non-ASCII and ampersand characters
|
* @return an encoded string where non-ASCII and ampersand characters
|
||||||
* are replaced with numeric character references
|
* are replaced with numeric character references
|
||||||
*/
|
*/
|
||||||
public static String encodeData(final String data) {
|
public static String encodeData(final String data) {
|
||||||
if (data == null) return "";
|
if (data == null) return "";
|
||||||
|
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
for (int index=0; index < data.length(); index++) {
|
for (int index = 0; index < data.length(); index++) {
|
||||||
char character = data.charAt(index);
|
char character = data.charAt(index);
|
||||||
if (character < 32 || character > 127 || character == 38) {
|
if (character < 32 || character > 127 || character == 38) {
|
||||||
result.append("&#" + (int)character + ";");
|
result.append("&#" + (int) character + ";");
|
||||||
} else {
|
} else {
|
||||||
result.append(character);
|
result.append(character);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,16 +16,30 @@ public class ToneGenerator {
|
|||||||
* static utility methods for array-related operations.
|
* static utility methods for array-related operations.
|
||||||
*
|
*
|
||||||
* @throws UnsupportedOperationException always, to indicate that this class
|
* @throws UnsupportedOperationException always, to indicate that this class
|
||||||
* should not be instantiated.
|
* should not be instantiated.
|
||||||
*/
|
*/
|
||||||
private ToneGenerator() {
|
private ToneGenerator() {
|
||||||
throw new UnsupportedOperationException("Utility class");
|
throw new UnsupportedOperationException("Utility class");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays a tone based on a predefined tone name for a specified duration.
|
* 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 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.
|
* @param duration The duration of the tone in milliseconds.
|
||||||
* @throws LineUnavailableException If a line for audio playback cannot be opened.
|
* @throws LineUnavailableException If a line for audio playback cannot be opened.
|
||||||
*/
|
*/
|
||||||
@ -64,18 +78,4 @@ public class ToneGenerator {
|
|||||||
line.drain();
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,34 +7,23 @@ import java.util.HashMap;
|
|||||||
* with their corresponding frequencies in hertz (Hz). It allows users to
|
* with their corresponding frequencies in hertz (Hz). It allows users to
|
||||||
* retrieve the frequency of a tone based on its standard notation name,
|
* retrieve the frequency of a tone based on its standard notation name,
|
||||||
* such as "C3", "A4", or "G#5".
|
* such as "C3", "A4", or "G#5".
|
||||||
*
|
* <p>
|
||||||
* This class can be particularly useful in applications related to sound synthesis,
|
* This class can be particularly useful in applications related to sound synthesis,
|
||||||
* music theory, signal processing, and other audio-related domains that require precise
|
* music theory, signal processing, and other audio-related domains that require precise
|
||||||
* frequency information for specific tones.
|
* frequency information for specific tones.
|
||||||
*/
|
*/
|
||||||
public class ToneTable {
|
public class ToneTable {
|
||||||
/**
|
|
||||||
* Private constructor to prevent instantiation of the utility class.
|
|
||||||
* This utility class is not meant to be instantiated, as it only provides
|
|
||||||
* static utility methods for array-related operations.
|
|
||||||
*
|
|
||||||
* @throws UnsupportedOperationException always, to indicate that this class
|
|
||||||
* should not be instantiated.
|
|
||||||
*/
|
|
||||||
private ToneTable() {
|
|
||||||
throw new UnsupportedOperationException("Utility class");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A static map that associates written tone names with their corresponding frequencies in hertz (Hz).
|
* 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.
|
* 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,
|
* 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.
|
* which are used in applications such as tone generation or audio playback.
|
||||||
*
|
* <p>
|
||||||
* The map is a crucial component of the ToneTable class, providing quick lookup of frequencies
|
* The map is a crucial component of the ToneTable class, providing quick lookup of frequencies
|
||||||
* for predefined tone names.
|
* for predefined tone names.
|
||||||
*/
|
*/
|
||||||
private static final HashMap<String, Double> toneMap = new HashMap<>();
|
private static final HashMap<String, Double> toneMap = new HashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
toneMap.put("C0", 16.35);
|
toneMap.put("C0", 16.35);
|
||||||
toneMap.put("C#0", 17.32);
|
toneMap.put("C#0", 17.32);
|
||||||
@ -191,6 +180,18 @@ public class ToneTable {
|
|||||||
toneMap.put("B8", 7902.13);
|
toneMap.put("B8", 7902.13);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor to prevent instantiation of the utility class.
|
||||||
|
* This utility class is not meant to be instantiated, as it only provides
|
||||||
|
* static utility methods for array-related operations.
|
||||||
|
*
|
||||||
|
* @throws UnsupportedOperationException always, to indicate that this class
|
||||||
|
* should not be instantiated.
|
||||||
|
*/
|
||||||
|
private ToneTable() {
|
||||||
|
throw new UnsupportedOperationException("Utility class");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the frequency of the tone based on the given tone name.
|
* 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.
|
* If the tone name exists in the tone map, its corresponding frequency is returned.
|
||||||
|
|||||||
@ -8,7 +8,14 @@ import java.sql.Connection;
|
|||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.Spliterator;
|
||||||
|
import java.util.Spliterators;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
@ -23,311 +30,312 @@ import java.util.stream.StreamSupport;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class Query<T> {
|
public class Query<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A factory function that extracts an Integer from the first column of a given ResultSet.
|
* A factory function that extracts an Integer from the first column of a given ResultSet.
|
||||||
* <p>
|
* <p>
|
||||||
* This function is designed to facilitate the conversion of a ResultSet row into an Integer
|
* 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
|
* 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.
|
* 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 {
|
||||||
return rs.getInt(1);
|
return rs.getInt(1);
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new Query object with the given database connection.
|
|
||||||
*
|
|
||||||
* @param connection The Connection object used to interact with the database.
|
|
||||||
*/
|
|
||||||
public Query(Connection connection) {
|
|
||||||
this.connection = connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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)
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
try (Scanner scanner = new Scanner(Objects.requireNonNull(getClass().getResourceAsStream(resourcePath)),
|
|
||||||
StandardCharsets.UTF_8)) {
|
|
||||||
this.queryText = scanner.useDelimiter("\\A").next();
|
|
||||||
}
|
|
||||||
log.info("query text: {}", this.queryText);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
log.info("query text: {}", this.queryText);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a parameter to the query at the specified index with a custom setter function.
|
|
||||||
*
|
|
||||||
* @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));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a parameter to the query using the specified ParameterSetter.
|
|
||||||
*
|
|
||||||
* @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);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
TrimmingResultSet trimmingResultSet = new TrimmingResultSet(rs);
|
|
||||||
return streamFromResultSet(trimmingResultSet, factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates and returns a PreparedStatement object configured with the query text
|
|
||||||
* and parameters defined for the current instance.
|
|
||||||
*
|
|
||||||
* @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);
|
|
||||||
for (ParameterSetter setter : parameterSetters) {
|
|
||||||
setter.apply(ps);
|
|
||||||
}
|
|
||||||
return ps;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 if a database access error occurs or the SQL statement is invalid.
|
|
||||||
*/
|
|
||||||
public void execute() throws SQLException {
|
|
||||||
try (PreparedStatement ps = createPreparedStatement()) {
|
|
||||||
ps.execute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 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() {
|
|
||||||
if (!hasNextChecked) {
|
|
||||||
hasNext = rs.next();
|
|
||||||
hasNextChecked = true;
|
|
||||||
}
|
}
|
||||||
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()) {
|
|
||||||
throw new NoSuchElementException();
|
|
||||||
}
|
|
||||||
hasNextChecked = false;
|
|
||||||
return factory.apply(rs);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
Spliterator<T> spliterator = Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED);
|
|
||||||
return StreamSupport.stream(spliterator, false)
|
|
||||||
.onClose(() -> {
|
|
||||||
try {
|
|
||||||
rs.getStatement().close();
|
|
||||||
} catch (SQLException ignored) {
|
|
||||||
// Exception is ignored.
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
rs.close();
|
|
||||||
} catch (SQLException ignored) {
|
|
||||||
// Exception is ignored.
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a managed stream to the given handler and ensures proper resource closing after use.
|
* Represents a database connection instance.
|
||||||
* A {@code Stream} is opened and passed to the specified handler as an argument.
|
* This connection is used to interact with a database
|
||||||
* The stream will be automatically closed when the handler finishes execution or throws an exception.
|
* through executing queries and retrieving results.
|
||||||
*
|
* It is immutable and initialized once.
|
||||||
* @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.
|
private final Connection connection;
|
||||||
* @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.
|
/**
|
||||||
*/
|
* A list of ParameterSetter objects used to configure parameters.
|
||||||
public <R> R withStream(Function<Stream<T>, R> handler) throws SQLException {
|
* This collection serves as a storage for managing parameter-setting logic dynamically.
|
||||||
try (Stream<T> stream = stream()) {
|
* It is initialized as an empty ArrayList and is immutable since it is declared as final.
|
||||||
return handler.apply(stream);
|
*/
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new Query object with the given database connection.
|
||||||
|
*
|
||||||
|
* @param connection The Connection object used to interact with the database.
|
||||||
|
*/
|
||||||
|
public Query(Connection connection) {
|
||||||
|
this.connection = connection;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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> 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> {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes an operation on three input arguments and allows for the operation to throw
|
* Executes the given SQL query using the provided database connection.
|
||||||
* an {@link SQLException}. This method is intended to facilitate SQL-related processes
|
|
||||||
* where three parameters are involved.
|
|
||||||
*
|
*
|
||||||
* @param t the first input argument
|
* @param connection the database connection to be used for executing the query
|
||||||
* @param u the second input argument
|
* @param query the SQL query to be executed
|
||||||
* @param v the third input argument
|
* @throws SQLException if a database access error occurs or the query execution fails
|
||||||
* @throws SQLException if an SQL error occurs during execution
|
|
||||||
*/
|
*/
|
||||||
void apply(T t, U u, V v) throws SQLException;
|
public static void execute(final Connection connection, final String query) throws SQLException {
|
||||||
}
|
new Query<Object>(connection)
|
||||||
|
.setQueryText(query)
|
||||||
/**
|
.execute();
|
||||||
* 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 {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies an operation to the given PreparedStatement object.
|
* Executes the SQL statement represented by the PreparedStatement created in
|
||||||
* This method is intended to set parameters or perform other modifications on a PreparedStatement.
|
* 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.
|
||||||
*
|
*
|
||||||
* @param ps the PreparedStatement instance to be modified or configured
|
* @throws SQLException if a database access error occurs or the SQL statement is invalid.
|
||||||
* @throws SQLException if a database access error occurs while applying the operation
|
|
||||||
*/
|
*/
|
||||||
void apply(PreparedStatement ps) throws SQLException;
|
public void execute() throws SQLException {
|
||||||
}
|
try (PreparedStatement ps = createPreparedStatement()) {
|
||||||
|
ps.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns a PreparedStatement object configured with the query text
|
||||||
|
* and parameters defined for the current instance.
|
||||||
|
*
|
||||||
|
* @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);
|
||||||
|
for (ParameterSetter setter : parameterSetters) {
|
||||||
|
setter.apply(ps);
|
||||||
|
}
|
||||||
|
return ps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
try (Scanner scanner = new Scanner(Objects.requireNonNull(getClass().getResourceAsStream(resourcePath)),
|
||||||
|
StandardCharsets.UTF_8)) {
|
||||||
|
this.queryText = scanner.useDelimiter("\\A").next();
|
||||||
|
}
|
||||||
|
log.info("query text: {}", this.queryText);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
log.info("query text: {}", this.queryText);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a parameter to the query at the specified index with a custom setter function.
|
||||||
|
*
|
||||||
|
* @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));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a parameter to the query using the specified ParameterSetter.
|
||||||
|
*
|
||||||
|
* @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);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
TrimmingResultSet trimmingResultSet = new TrimmingResultSet(rs);
|
||||||
|
return streamFromResultSet(trimmingResultSet, factory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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() {
|
||||||
|
if (!hasNextChecked) {
|
||||||
|
hasNext = rs.next();
|
||||||
|
hasNextChecked = true;
|
||||||
|
}
|
||||||
|
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()) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
hasNextChecked = false;
|
||||||
|
return factory.apply(rs);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Spliterator<T> spliterator = Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED);
|
||||||
|
return StreamSupport.stream(spliterator, false)
|
||||||
|
.onClose(() -> {
|
||||||
|
try {
|
||||||
|
rs.getStatement().close();
|
||||||
|
} catch (SQLException ignored) {
|
||||||
|
// Exception is ignored.
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
rs.close();
|
||||||
|
} catch (SQLException ignored) {
|
||||||
|
// Exception is ignored.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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> 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> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies an operation to the given PreparedStatement object.
|
||||||
|
* This method is intended to set parameters or perform other modifications on a 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
@ -13,7 +13,7 @@ public class ArrayUtils {
|
|||||||
* static utility methods for array-related operations.
|
* static utility methods for array-related operations.
|
||||||
*
|
*
|
||||||
* @throws UnsupportedOperationException always, to indicate that this class
|
* @throws UnsupportedOperationException always, to indicate that this class
|
||||||
* should not be instantiated.
|
* should not be instantiated.
|
||||||
*/
|
*/
|
||||||
private ArrayUtils() {
|
private ArrayUtils() {
|
||||||
throw new UnsupportedOperationException("Utility class");
|
throw new UnsupportedOperationException("Utility class");
|
||||||
@ -23,11 +23,11 @@ public class ArrayUtils {
|
|||||||
* Checks if the given character array contains the specified character.
|
* Checks if the given character array contains the specified character.
|
||||||
*
|
*
|
||||||
* @param array the character array to be searched
|
* @param array the character array to be searched
|
||||||
* @param ch the character to search for in the array
|
* @param ch the character to search for in the array
|
||||||
* @return true if the character is found in the array, false otherwise
|
* @return true if the character is found in the array, false otherwise
|
||||||
*/
|
*/
|
||||||
public static boolean contains(char[] array, char ch) {
|
public static boolean contains(char[] array, char ch) {
|
||||||
for(int index=0; index < array.length; index++) {
|
for (int index = 0; index < array.length; index++) {
|
||||||
if (array[index] == ch) return true;
|
if (array[index] == ch) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -37,11 +37,11 @@ public class ArrayUtils {
|
|||||||
* Checks if the specified integer array contains the given integer value.
|
* Checks if the specified integer array contains the given integer value.
|
||||||
*
|
*
|
||||||
* @param array the array of integers to search
|
* @param array the array of integers to search
|
||||||
* @param ch the integer value to search for in the array
|
* @param ch the integer value to search for in the array
|
||||||
* @return true if the array contains the specified integer value, false otherwise
|
* @return true if the array contains the specified integer value, false otherwise
|
||||||
*/
|
*/
|
||||||
public static boolean contains(int[] array, int ch) {
|
public static boolean contains(int[] array, int ch) {
|
||||||
for(int index=0; index < array.length; index++) {
|
for (int index = 0; index < array.length; index++) {
|
||||||
if (array[index] == ch) return true;
|
if (array[index] == ch) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -51,11 +51,11 @@ public class ArrayUtils {
|
|||||||
* Checks if the specified long array contains the given long value.
|
* Checks if the specified long array contains the given long value.
|
||||||
*
|
*
|
||||||
* @param array the array of long values to search
|
* @param array the array of long values to search
|
||||||
* @param ch the long value to search for in the array
|
* @param ch the long value to search for in the array
|
||||||
* @return true if the array contains the specified long value, false otherwise
|
* @return true if the array contains the specified long value, false otherwise
|
||||||
*/
|
*/
|
||||||
public static boolean contains(long[] array, long ch) {
|
public static boolean contains(long[] array, long ch) {
|
||||||
for(int index=0; index < array.length; index++) {
|
for (int index = 0; index < array.length; index++) {
|
||||||
if (array[index] == ch) return true;
|
if (array[index] == ch) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -65,12 +65,12 @@ public class ArrayUtils {
|
|||||||
* Checks if the specified array contains the given element.
|
* Checks if the specified array contains the given element.
|
||||||
*
|
*
|
||||||
* @param array the array to be searched
|
* @param array the array to be searched
|
||||||
* @param ch the element to search for in the array
|
* @param ch the element to search for in the array
|
||||||
* @param <T> the type of the elements in the array
|
* @param <T> the type of the elements in the array
|
||||||
* @return true if the element is found in the array, false otherwise
|
* @return true if the element is found in the array, false otherwise
|
||||||
*/
|
*/
|
||||||
public static <T> boolean contains(T[] array, T ch) {
|
public static <T> boolean contains(T[] array, T ch) {
|
||||||
for(int index=0; index < array.length; index++) {
|
for (int index = 0; index < array.length; index++) {
|
||||||
if (Objects.equals(array[index], ch)) return true;
|
if (Objects.equals(array[index], ch)) return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -14,7 +14,7 @@ public class EnumUtil {
|
|||||||
* static utility methods for array-related operations.
|
* static utility methods for array-related operations.
|
||||||
*
|
*
|
||||||
* @throws UnsupportedOperationException always, to indicate that this class
|
* @throws UnsupportedOperationException always, to indicate that this class
|
||||||
* should not be instantiated.
|
* should not be instantiated.
|
||||||
*/
|
*/
|
||||||
private EnumUtil() {
|
private EnumUtil() {
|
||||||
throw new UnsupportedOperationException("Utility class");
|
throw new UnsupportedOperationException("Utility class");
|
||||||
@ -25,7 +25,7 @@ public class EnumUtil {
|
|||||||
* of the enum constants within the specified Enum class. The pattern also includes optional delimiters
|
* of the enum constants within the specified Enum class. The pattern also includes optional delimiters
|
||||||
* such as commas, spaces, and empty values, enabling the matching of lists or sequences of flag values.
|
* such as commas, spaces, and empty values, enabling the matching of lists or sequences of flag values.
|
||||||
*
|
*
|
||||||
* @param <T> The type of the Enum.
|
* @param <T> The type of the Enum.
|
||||||
* @param clazz The Enum class for which the regular expression is to be generated.
|
* @param clazz The Enum class for which the regular expression is to be generated.
|
||||||
* @return A String containing the regular expression pattern for matching the Enum's flag values.
|
* @return A String containing the regular expression pattern for matching the Enum's flag values.
|
||||||
*/
|
*/
|
||||||
@ -33,9 +33,9 @@ public class EnumUtil {
|
|||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
result.append("(|,|\\s");
|
result.append("(|,|\\s");
|
||||||
|
|
||||||
for (T flag: clazz.getEnumConstants()) {
|
for (T flag : clazz.getEnumConstants()) {
|
||||||
result.append("|");
|
result.append("|");
|
||||||
for(char ch: flag.toString().toUpperCase().toCharArray()) {
|
for (char ch : flag.toString().toUpperCase().toCharArray()) {
|
||||||
if (Character.isAlphabetic(ch)) {
|
if (Character.isAlphabetic(ch)) {
|
||||||
result.append("[");
|
result.append("[");
|
||||||
result.append(ch);
|
result.append(ch);
|
||||||
@ -52,25 +52,25 @@ public class EnumUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a delimited string containing Enum constant names into a list of Enum constants.
|
* Parses a delimited string containing Enum constant names into a list of Enum constants.
|
||||||
*
|
* <p>
|
||||||
* This method takes an Enum class and a string of flags, splits the string by commas
|
* This method takes an Enum class and a string of flags, splits the string by commas
|
||||||
* or whitespace, and converts each substring into an Enum constant of the given class.
|
* or whitespace, and converts each substring into an Enum constant of the given class.
|
||||||
* The flag names are case-insensitive and will be converted to uppercase before matching
|
* The flag names are case-insensitive and will be converted to uppercase before matching
|
||||||
* to the Enum constants.
|
* to the Enum constants.
|
||||||
*
|
*
|
||||||
* @param <T> The type of the Enum.
|
* @param <T> The type of the Enum.
|
||||||
* @param clazz The Enum class to which the flags should be parsed.
|
* @param clazz The Enum class to which the flags should be parsed.
|
||||||
* @param flags A string containing the delimited list of flag names to parse.
|
* @param flags A string containing the delimited list of flag names to parse.
|
||||||
* Individual names can be separated by commas or whitespace.
|
* Individual names can be separated by commas or whitespace.
|
||||||
* @return A list of Enum constants parsed from the input string. If the input string
|
* @return A list of Enum constants parsed from the input string. If the input string
|
||||||
* is null or empty, an empty list is returned.
|
* is null or empty, an empty list is returned.
|
||||||
* @throws IllegalArgumentException if any of the flag names do not match the constants in the given Enum class.
|
* @throws IllegalArgumentException if any of the flag names do not match the constants in the given Enum class.
|
||||||
* @throws NullPointerException if the clazz parameter is null.
|
* @throws NullPointerException if the clazz parameter is null.
|
||||||
*/
|
*/
|
||||||
public static <T extends Enum<T>> List<T> parseFlags(final Class<T> clazz, final String flags) {
|
public static <T extends Enum<T>> List<T> parseFlags(final Class<T> clazz, final String flags) {
|
||||||
List<T> result = new ArrayList<>();
|
List<T> result = new ArrayList<>();
|
||||||
if (flags != null) {
|
if (flags != null) {
|
||||||
for (String flag: flags.split("[,\\s]")) {
|
for (String flag : flags.split("[,\\s]")) {
|
||||||
if (!flag.isEmpty()) result.add(T.valueOf(clazz, flag.toUpperCase()));
|
if (!flag.isEmpty()) result.add(T.valueOf(clazz, flag.toUpperCase()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ public class RegionalizationMessage {
|
|||||||
* which is used to retrieve locale-specific objects and messages.
|
* which is used to retrieve locale-specific objects and messages.
|
||||||
* It facilitates the process of internationalization by loading resources
|
* It facilitates the process of internationalization by loading resources
|
||||||
* such as text and messages from specified bundles based on the provided locale.
|
* such as text and messages from specified bundles based on the provided locale.
|
||||||
*
|
* <p>
|
||||||
* This variable is initialized in constructors of the `RegionalizationMessage` class
|
* This variable is initialized in constructors of the `RegionalizationMessage` class
|
||||||
* and is used internally by various methods to fetch localized messages.
|
* and is used internally by various methods to fetch localized messages.
|
||||||
*/
|
*/
|
||||||
@ -50,21 +50,36 @@ public class RegionalizationMessage {
|
|||||||
*
|
*
|
||||||
* @param key The key for which the localized message needs to be retrieved.
|
* @param key The key for which the localized message needs to be retrieved.
|
||||||
* @return The localized message as a String if the key exists in the resource bundle;
|
* @return The localized message as a String if the key exists in the resource bundle;
|
||||||
* otherwise, null.
|
* otherwise, null.
|
||||||
*/
|
*/
|
||||||
public String getMessage(final String key) {
|
public String getMessage(final String key) {
|
||||||
if (!res.containsKey(key)) return null;
|
if (!res.containsKey(key)) return null;
|
||||||
return res.getString(key);
|
return res.getString(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a localized and formatted message from a resource bundle based on a key.
|
||||||
|
* If the key is not found in the resource bundle, a default message is used.
|
||||||
|
* The message supports parameter substitution using the supplied arguments.
|
||||||
|
*
|
||||||
|
* @param key The key used to retrieve the localized message from the resource bundle.
|
||||||
|
* @param defaultMessage The default message to be used if the key is not found in the resource bundle.
|
||||||
|
* @param params The parameters to substitute into the message placeholders.
|
||||||
|
* @return A formatted string containing the localized message with substituted parameters.
|
||||||
|
*/
|
||||||
|
public String getFormattedMessage(final String key, final String defaultMessage, final Object... params) {
|
||||||
|
MessageFormat format = new MessageFormat(getMessage(key, defaultMessage));
|
||||||
|
return format.format(params);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a localized message for the given key from the resource bundle.
|
* Retrieves a localized message for the given key from the resource bundle.
|
||||||
* If the key is not found, the specified default message is returned.
|
* If the key is not found, the specified default message is returned.
|
||||||
*
|
*
|
||||||
* @param key The key to look up in the resource bundle.
|
* @param key The key to look up in the resource bundle.
|
||||||
* @param defaultMessage The default message to return if the key is not found.
|
* @param defaultMessage The default message to return if the key is not found.
|
||||||
* @return The localized message corresponding to the key, or the default message
|
* @return The localized message corresponding to the key, or the default message
|
||||||
* if the key does not exist in the resource bundle.
|
* if the key does not exist in the resource bundle.
|
||||||
*/
|
*/
|
||||||
public String getMessage(final String key, final String defaultMessage) {
|
public String getMessage(final String key, final String defaultMessage) {
|
||||||
if (res.containsKey(key))
|
if (res.containsKey(key))
|
||||||
@ -72,19 +87,4 @@ public class RegionalizationMessage {
|
|||||||
|
|
||||||
return defaultMessage;
|
return defaultMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a localized and formatted message from a resource bundle based on a key.
|
|
||||||
* If the key is not found in the resource bundle, a default message is used.
|
|
||||||
* The message supports parameter substitution using the supplied arguments.
|
|
||||||
*
|
|
||||||
* @param key The key used to retrieve the localized message from the resource bundle.
|
|
||||||
* @param defaultMessage The default message to be used if the key is not found in the resource bundle.
|
|
||||||
* @param params The parameters to substitute into the message placeholders.
|
|
||||||
* @return A formatted string containing the localized message with substituted parameters.
|
|
||||||
*/
|
|
||||||
public String getFormattedMessage(final String key, final String defaultMessage, final Object... params) {
|
|
||||||
MessageFormat format = new MessageFormat(getMessage(key, defaultMessage));
|
|
||||||
return format.format(params);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,122 +7,140 @@ import java.util.Locale;
|
|||||||
/**
|
/**
|
||||||
* A utility class for measuring elapsed time. The StopWatch can be started, stopped, and queried for the elapsed time in various formats.
|
* A utility class for measuring elapsed time. The StopWatch can be started, stopped, and queried for the elapsed time in various formats.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class StopWatch {
|
public class StopWatch {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the starting time of the stopwatch. This variable is set when the stopwatch is started and is used to calculate the elapsed
|
* Represents the starting time of the stopwatch. This variable is set when the stopwatch is started and is used to calculate the elapsed
|
||||||
* duration between the start and the stop or the current time.
|
* duration between the start and the stop or the current time.
|
||||||
*/
|
*/
|
||||||
private Instant startTime;
|
private Instant startTime;
|
||||||
/**
|
|
||||||
* Represents the stopping time of the stopwatch. This variable is set when the stopwatch is stopped and is used to calculate the elapsed
|
|
||||||
* duration between the start and stop times.
|
|
||||||
*/
|
|
||||||
private Instant stopTime;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the stopwatch by recording the current time as the starting time. If the stopwatch was previously stopped or not yet started,
|
* Represents the stopping time of the stopwatch. This variable is set when the stopwatch is stopped and is used to calculate the elapsed
|
||||||
* this method resets the starting time to the current time and clears the stopping time. This method is typically called before measuring
|
* duration between the start and stop times.
|
||||||
* elapsed time.
|
*/
|
||||||
*/
|
private Instant stopTime;
|
||||||
public void start() {
|
|
||||||
this.startTime = Instant.now();
|
|
||||||
this.stopTime = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the stopwatch by recording the current time as the stopping time.
|
* Creates a new StopWatch instance in the reset state.
|
||||||
* <p>
|
*
|
||||||
* This method should only be called after the stopwatch has been started using the {@code start()} method. If the stopwatch has not been
|
* <p>The constructor does not start timing. Both {@link #startTime} and {@link #stopTime}
|
||||||
* started, an {@code IllegalStateException} will be thrown. Once this method is called, the elapsed time can be calculated as the
|
* are initialized to {@code null}. To begin measuring elapsed time, call {@link #start()}.
|
||||||
* duration between the starting and stopping times.
|
* After calling {@link #stop()}, retrieve the measured duration with {@link #getUsedTime()} or
|
||||||
*
|
* a human-readable string via {@link #getUsedTimeFormatted(boolean, boolean, boolean, boolean, boolean)}.
|
||||||
* @throws IllegalStateException if the stopwatch has not been started prior to calling this method.
|
* </p>
|
||||||
*/
|
*
|
||||||
public void stop() {
|
* <p>Note: This class is not synchronized; synchronize externally if the same instance
|
||||||
if (startTime == null) {
|
* is accessed from multiple threads.</p>
|
||||||
throw new IllegalStateException("StopWatch must be started before stopping.");
|
*/
|
||||||
}
|
public StopWatch() {
|
||||||
this.stopTime = Instant.now();
|
// default constructor only ...
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the amount of time elapsed between the stopwatch's starting time and either its stopping time or the current time if the
|
|
||||||
* stopwatch is still running.
|
|
||||||
*
|
|
||||||
* @return the duration representing the elapsed time between the start and end points of the stopwatch.
|
|
||||||
* @throws IllegalStateException if the stopwatch has not been started before calling this method.
|
|
||||||
*/
|
|
||||||
public Duration getUsedTime() {
|
|
||||||
if (startTime == null) {
|
|
||||||
throw new IllegalStateException("StopWatch has not been started.");
|
|
||||||
}
|
|
||||||
Instant end = (stopTime != null) ? stopTime : Instant.now();
|
|
||||||
return Duration.between(startTime, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Formats the duration of elapsed time measured by the stopwatch into a human-readable string. The formatted output may include days,
|
|
||||||
* hours, minutes, seconds, and milliseconds, depending on the parameters. The method retrieves the elapsed time via the
|
|
||||||
* {@code getUsedTime} method and formats it based on the specified flags.
|
|
||||||
*
|
|
||||||
* @param includeMillis whether to include milliseconds in the formatted output
|
|
||||||
* @param includeSeconds whether to include seconds in the formatted output
|
|
||||||
* @param includeMinutes whether to include minutes in the formatted output
|
|
||||||
* @param includeHours whether to include hours in the formatted output
|
|
||||||
* @param includeDays whether to include days in the formatted output
|
|
||||||
* @return a formatted string representing the elapsed time, including the components specified by the parameters
|
|
||||||
*/
|
|
||||||
public String getUsedTimeFormatted(boolean includeMillis, boolean includeSeconds, boolean includeMinutes, boolean includeHours,
|
|
||||||
boolean includeDays) {
|
|
||||||
Duration duration = getUsedTime();
|
|
||||||
|
|
||||||
long millis = duration.toMillis();
|
|
||||||
long days = millis / (24 * 60 * 60 * 1000);
|
|
||||||
millis %= (24 * 60 * 60 * 1000);
|
|
||||||
|
|
||||||
long hours = millis / (60 * 60 * 1000);
|
|
||||||
millis %= (60 * 60 * 1000);
|
|
||||||
long minutes = millis / (60 * 1000);
|
|
||||||
millis %= (60 * 1000);
|
|
||||||
long seconds = millis / 1000;
|
|
||||||
millis %= 1000;
|
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
|
|
||||||
if (includeDays && days > 0) {
|
|
||||||
sb.append(days).append("d ");
|
|
||||||
}
|
|
||||||
if (includeHours && (hours > 0 || sb.length() > 0)) {
|
|
||||||
sb.append(hours).append("h ");
|
|
||||||
}
|
|
||||||
if (includeMinutes && (minutes > 0 || sb.length() > 0)) {
|
|
||||||
sb.append(minutes).append("m ");
|
|
||||||
}
|
|
||||||
if (includeSeconds && (seconds > 0 || sb.length() > 0)) {
|
|
||||||
sb.append(seconds);
|
|
||||||
}
|
|
||||||
if (includeMillis) {
|
|
||||||
if (includeSeconds) {
|
|
||||||
sb.append(String.format(Locale.US, ".%03d", millis));
|
|
||||||
} else {
|
|
||||||
sb.append(millis).append("ms");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String result = sb.toString().trim();
|
/**
|
||||||
return result.isEmpty() ? "0ms" : result;
|
* Starts the stopwatch by recording the current time as the starting time. If the stopwatch was previously stopped or not yet started,
|
||||||
}
|
* this method resets the starting time to the current time and clears the stopping time. This method is typically called before measuring
|
||||||
|
* elapsed time.
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
this.startTime = Instant.now();
|
||||||
|
this.stopTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a compact and human-readable formatted string representing the elapsed time measured by the stopwatch. The method formats the
|
* Stops the stopwatch by recording the current time as the stopping time.
|
||||||
* duration using a combination of days, hours, minutes, seconds, and milliseconds. This compact format removes additional verbosity in
|
* <p>
|
||||||
* favor of a shorter, more concise representation.
|
* This method should only be called after the stopwatch has been started using the {@code start()} method. If the stopwatch has not been
|
||||||
*
|
* started, an {@code IllegalStateException} will be thrown. Once this method is called, the elapsed time can be calculated as the
|
||||||
* @return a compact formatted string representing the elapsed time, including days, hours, minutes, seconds, and milliseconds as
|
* duration between the starting and stopping times.
|
||||||
* applicable
|
*
|
||||||
*/
|
* @throws IllegalStateException if the stopwatch has not been started prior to calling this method.
|
||||||
public String getUsedTimeFormattedCompact() {
|
*/
|
||||||
return getUsedTimeFormatted(true, true, true, true, true);
|
public void stop() {
|
||||||
}
|
if (startTime == null) {
|
||||||
|
throw new IllegalStateException("StopWatch must be started before stopping.");
|
||||||
|
}
|
||||||
|
this.stopTime = Instant.now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a compact and human-readable formatted string representing the elapsed time measured by the stopwatch. The method formats the
|
||||||
|
* duration using a combination of days, hours, minutes, seconds, and milliseconds. This compact format removes additional verbosity in
|
||||||
|
* favor of a shorter, more concise representation.
|
||||||
|
*
|
||||||
|
* @return a compact formatted string representing the elapsed time, including days, hours, minutes, seconds, and milliseconds as
|
||||||
|
* applicable
|
||||||
|
*/
|
||||||
|
public String getUsedTimeFormattedCompact() {
|
||||||
|
return getUsedTimeFormatted(true, true, true, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the duration of elapsed time measured by the stopwatch into a human-readable string. The formatted output may include days,
|
||||||
|
* hours, minutes, seconds, and milliseconds, depending on the parameters. The method retrieves the elapsed time via the
|
||||||
|
* {@code getUsedTime} method and formats it based on the specified flags.
|
||||||
|
*
|
||||||
|
* @param includeMillis whether to include milliseconds in the formatted output
|
||||||
|
* @param includeSeconds whether to include seconds in the formatted output
|
||||||
|
* @param includeMinutes whether to include minutes in the formatted output
|
||||||
|
* @param includeHours whether to include hours in the formatted output
|
||||||
|
* @param includeDays whether to include days in the formatted output
|
||||||
|
* @return a formatted string representing the elapsed time, including the components specified by the parameters
|
||||||
|
*/
|
||||||
|
public String getUsedTimeFormatted(boolean includeMillis, boolean includeSeconds, boolean includeMinutes, boolean includeHours,
|
||||||
|
boolean includeDays) {
|
||||||
|
Duration duration = getUsedTime();
|
||||||
|
|
||||||
|
long millis = duration.toMillis();
|
||||||
|
long days = millis / (24 * 60 * 60 * 1000);
|
||||||
|
millis %= (24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
long hours = millis / (60 * 60 * 1000);
|
||||||
|
millis %= (60 * 60 * 1000);
|
||||||
|
long minutes = millis / (60 * 1000);
|
||||||
|
millis %= (60 * 1000);
|
||||||
|
long seconds = millis / 1000;
|
||||||
|
millis %= 1000;
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
if (includeDays && days > 0) {
|
||||||
|
sb.append(days).append("d ");
|
||||||
|
}
|
||||||
|
if (includeHours && (hours > 0 || !sb.isEmpty())) {
|
||||||
|
sb.append(hours).append("h ");
|
||||||
|
}
|
||||||
|
if (includeMinutes && (minutes > 0 || !sb.isEmpty())) {
|
||||||
|
sb.append(minutes).append("m ");
|
||||||
|
}
|
||||||
|
if (includeSeconds && (seconds > 0 || !sb.isEmpty())) {
|
||||||
|
sb.append(seconds);
|
||||||
|
}
|
||||||
|
if (includeMillis) {
|
||||||
|
if (includeSeconds) {
|
||||||
|
sb.append(String.format(Locale.US, ".%03d", millis));
|
||||||
|
} else {
|
||||||
|
sb.append(millis).append("ms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String result = sb.toString().trim();
|
||||||
|
return result.isEmpty() ? "0ms" : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the amount of time elapsed between the stopwatch's starting time and either its stopping time or the current time if the
|
||||||
|
* stopwatch is still running.
|
||||||
|
*
|
||||||
|
* @return the duration representing the elapsed time between the start and end points of the stopwatch.
|
||||||
|
* @throws IllegalStateException if the stopwatch has not been started before calling this method.
|
||||||
|
*/
|
||||||
|
public Duration getUsedTime() {
|
||||||
|
if (startTime == null) {
|
||||||
|
throw new IllegalStateException("StopWatch has not been started.");
|
||||||
|
}
|
||||||
|
Instant end = (stopTime != null) ? stopTime : Instant.now();
|
||||||
|
return Duration.between(startTime, end);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,33 +8,33 @@ import java.util.Map;
|
|||||||
* Utility class providing common string manipulation methods.
|
* Utility class providing common string manipulation methods.
|
||||||
*/
|
*/
|
||||||
public class Strings {
|
public class Strings {
|
||||||
|
/**
|
||||||
|
* A map that holds the system's environment variables. The keys represent
|
||||||
|
* environment variable names, and the values represent their corresponding
|
||||||
|
* values. It is initialized with the current system environment variables using
|
||||||
|
* {@link System#getenv()}.
|
||||||
|
* <p>
|
||||||
|
* This variable provides access to the underlying system environment, which can
|
||||||
|
* be used for various purposes such as configuration, path resolution, or
|
||||||
|
* dynamic value substitution.
|
||||||
|
* <p>
|
||||||
|
* Note: Modifications to this map will not affect the system environment, as it
|
||||||
|
* is a copy of the environment at the time of initialization.
|
||||||
|
*/
|
||||||
|
private static Map<String, String> environmentVariables = System.getenv();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private constructor to prevent instantiation of the utility class.
|
* Private constructor to prevent instantiation of the utility class.
|
||||||
* This utility class is not meant to be instantiated, as it only provides
|
* This utility class is not meant to be instantiated, as it only provides
|
||||||
* static utility methods for array-related operations.
|
* static utility methods for array-related operations.
|
||||||
*
|
*
|
||||||
* @throws UnsupportedOperationException always, to indicate that this class
|
* @throws UnsupportedOperationException always, to indicate that this class
|
||||||
* should not be instantiated.
|
* should not be instantiated.
|
||||||
*/
|
*/
|
||||||
private Strings() {
|
private Strings() {
|
||||||
throw new UnsupportedOperationException("Utility class");
|
throw new UnsupportedOperationException("Utility class");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A map that holds the system's environment variables. The keys represent
|
|
||||||
* environment variable names, and the values represent their corresponding
|
|
||||||
* values. It is initialized with the current system environment variables using
|
|
||||||
* {@link System#getenv()}.
|
|
||||||
*
|
|
||||||
* This variable provides access to the underlying system environment, which can
|
|
||||||
* be used for various purposes such as configuration, path resolution, or
|
|
||||||
* dynamic value substitution.
|
|
||||||
*
|
|
||||||
* Note: Modifications to this map will not affect the system environment, as it
|
|
||||||
* is a copy of the environment at the time of initialization.
|
|
||||||
*/
|
|
||||||
private static Map<String, String> environmentVariables = System.getenv();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expands environment variable placeholders within the provided text.
|
* Expands environment variable placeholders within the provided text.
|
||||||
* Placeholders in the format ${VARIABLE_NAME} are replaced with their
|
* Placeholders in the format ${VARIABLE_NAME} are replaced with their
|
||||||
@ -43,8 +43,8 @@ public class Strings {
|
|||||||
* @param text The input string containing environment variable placeholders.
|
* @param text The input string containing environment variable placeholders.
|
||||||
* If null, the method returns null.
|
* If null, the method returns null.
|
||||||
* @return A string with the placeholders replaced by their corresponding
|
* @return A string with the placeholders replaced by their corresponding
|
||||||
* environment variable values. If no placeholders are found,
|
* environment variable values. If no placeholders are found,
|
||||||
* the original string is returned.
|
* the original string is returned.
|
||||||
*/
|
*/
|
||||||
public static String expandEnvironmentVariables(String text) {
|
public static String expandEnvironmentVariables(String text) {
|
||||||
if (text == null) return null;
|
if (text == null) return null;
|
||||||
@ -63,7 +63,7 @@ public class Strings {
|
|||||||
* @return true if the string is null or has a length of 0; false otherwise.
|
* @return true if the string is null or has a length of 0; false otherwise.
|
||||||
*/
|
*/
|
||||||
public static boolean isNullOrEmpty(final String string) {
|
public static boolean isNullOrEmpty(final String string) {
|
||||||
return (string == null || string.length()==0);
|
return (string == null || string.length() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,12 +73,12 @@ public class Strings {
|
|||||||
* @param value The input string that might contain surrounding double quotes.
|
* @param value The input string that might contain surrounding double quotes.
|
||||||
* The input cannot be null, and leading/trailing whitespaces will be ignored.
|
* The input cannot be null, and leading/trailing whitespaces will be ignored.
|
||||||
* @return A string with leading and trailing double quotes removed if present.
|
* @return A string with leading and trailing double quotes removed if present.
|
||||||
* Returns the original string if no surrounding double quotes are detected.
|
* Returns the original string if no surrounding double quotes are detected.
|
||||||
*/
|
*/
|
||||||
public static String removeQuotes(final String value) {
|
public static String removeQuotes(final String value) {
|
||||||
String trimmedValue = value.trim();
|
String trimmedValue = value.trim();
|
||||||
if (trimmedValue.length() > 1 && trimmedValue.startsWith("\"") && trimmedValue.endsWith("\""))
|
if (trimmedValue.length() > 1 && trimmedValue.startsWith("\"") && trimmedValue.endsWith("\""))
|
||||||
return trimmedValue.substring(1, trimmedValue.length()-1);
|
return trimmedValue.substring(1, trimmedValue.length() - 1);
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@ -87,11 +87,11 @@ public class Strings {
|
|||||||
* Replaces illegal characters in a given string with a specified replacement string.
|
* Replaces illegal characters in a given string with a specified replacement string.
|
||||||
* Optionally, consecutive illegal characters can be combined into a single replacement.
|
* Optionally, consecutive illegal characters can be combined into a single replacement.
|
||||||
*
|
*
|
||||||
* @param value The input string in which illegal characters are to be replaced.
|
* @param value The input string in which illegal characters are to be replaced.
|
||||||
* @param illegalCharacters A string specifying the set of illegal characters to be matched.
|
* @param illegalCharacters A string specifying the set of illegal characters to be matched.
|
||||||
* @param replacement The string to replace each illegal character or group of characters.
|
* @param replacement The string to replace each illegal character or group of characters.
|
||||||
* @param combine A boolean flag indicating whether consecutive illegal characters should be replaced
|
* @param combine A boolean flag indicating whether consecutive illegal characters should be replaced
|
||||||
* with a single replacement string. If true, groups of illegal characters are combined.
|
* with a single replacement string. If true, groups of illegal characters are combined.
|
||||||
* @return A new string with the illegal characters replaced by the provided replacement string.
|
* @return A new string with the illegal characters replaced by the provided replacement string.
|
||||||
*/
|
*/
|
||||||
public static String replaceIllegalCharacters(final String value, final String illegalCharacters, final String replacement, final boolean combine) {
|
public static String replaceIllegalCharacters(final String value, final String illegalCharacters, final String replacement, final boolean combine) {
|
||||||
@ -104,12 +104,12 @@ public class Strings {
|
|||||||
* with a specified replacement string. Optionally combines consecutive non-allowed
|
* with a specified replacement string. Optionally combines consecutive non-allowed
|
||||||
* characters into a single replacement.
|
* characters into a single replacement.
|
||||||
*
|
*
|
||||||
* @param value The input string to be processed.
|
* @param value The input string to be processed.
|
||||||
* @param allowedCharacters A string containing the set of characters that are allowed.
|
* @param allowedCharacters A string containing the set of characters that are allowed.
|
||||||
* Any character not in this set will be replaced.
|
* Any character not in this set will be replaced.
|
||||||
* @param replacement The string to replace non-allowed characters with.
|
* @param replacement The string to replace non-allowed characters with.
|
||||||
* @param combine If true, consecutive non-allowed characters will be replaced with
|
* @param combine If true, consecutive non-allowed characters will be replaced with
|
||||||
* a single instance of the replacement string.
|
* a single instance of the replacement string.
|
||||||
* @return A new string with non-allowed characters replaced according to the specified rules.
|
* @return A new string with non-allowed characters replaced according to the specified rules.
|
||||||
*/
|
*/
|
||||||
public static String replaceNonAllowedCharacters(final String value, final String allowedCharacters, final String replacement, final boolean combine) {
|
public static String replaceNonAllowedCharacters(final String value, final String allowedCharacters, final String replacement, final boolean combine) {
|
||||||
@ -131,11 +131,11 @@ public class Strings {
|
|||||||
public static String increment(@NonNull final String element) {
|
public static String increment(@NonNull final String element) {
|
||||||
if (element.isEmpty()) return "1";
|
if (element.isEmpty()) return "1";
|
||||||
|
|
||||||
String firstPart = element.substring(0, element.length()-1);
|
String firstPart = element.substring(0, element.length() - 1);
|
||||||
char lastChar = element.charAt(element.length()-1);
|
char lastChar = element.charAt(element.length() - 1);
|
||||||
if (lastChar == '9') return firstPart + 'A';
|
if (lastChar == '9') return firstPart + 'A';
|
||||||
if (lastChar == 'Z') return increment(firstPart) + '0';
|
if (lastChar == 'Z') return increment(firstPart) + '0';
|
||||||
return firstPart + (char)(lastChar+1);
|
return firstPart + (char) (lastChar + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -144,10 +144,10 @@ public class Strings {
|
|||||||
* the string is returned unchanged. If the input string's length exceeds the max
|
* the string is returned unchanged. If the input string's length exceeds the max
|
||||||
* length, it is truncated to that length.
|
* length, it is truncated to that length.
|
||||||
*
|
*
|
||||||
* @param original The original input string to be processed. If null, the method returns null.
|
* @param original The original input string to be processed. If null, the method returns null.
|
||||||
* @param maxLength The maximum number of characters allowed in the resulting string.
|
* @param maxLength The maximum number of characters allowed in the resulting string.
|
||||||
* @return The original string if its length is less than or equal to maxLength,
|
* @return The original string if its length is less than or equal to maxLength,
|
||||||
* otherwise a truncated version of the string up to maxLength characters.
|
* otherwise a truncated version of the string up to maxLength characters.
|
||||||
*/
|
*/
|
||||||
public static String limitCharNumber(final String original, final int maxLength) {
|
public static String limitCharNumber(final String original, final int maxLength) {
|
||||||
if (original == null || original.length() <= maxLength) return original;
|
if (original == null || original.length() <= maxLength) return original;
|
||||||
|
|||||||
@ -8,7 +8,12 @@ import org.w3c.dom.Node;
|
|||||||
|
|
||||||
import javax.xml.parsers.DocumentBuilder;
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
import javax.xml.transform.*;
|
import javax.xml.transform.OutputKeys;
|
||||||
|
import javax.xml.transform.Result;
|
||||||
|
import javax.xml.transform.Source;
|
||||||
|
import javax.xml.transform.Transformer;
|
||||||
|
import javax.xml.transform.TransformerException;
|
||||||
|
import javax.xml.transform.TransformerFactory;
|
||||||
import javax.xml.transform.dom.DOMSource;
|
import javax.xml.transform.dom.DOMSource;
|
||||||
import javax.xml.transform.stream.StreamResult;
|
import javax.xml.transform.stream.StreamResult;
|
||||||
import javax.xml.transform.stream.StreamSource;
|
import javax.xml.transform.stream.StreamSource;
|
||||||
@ -25,214 +30,211 @@ import java.util.Date;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class XmlUtils {
|
public class XmlUtils {
|
||||||
/**
|
/**
|
||||||
* Private constructor to prevent instantiation of the utility class.
|
* A {@link DateTimeFormatter} instance used for formatting or parsing dates in the "yyyy-MM-dd" pattern.
|
||||||
* This utility class is not meant to be instantiated, as it only provides
|
* <p>
|
||||||
* static utility methods for array-related operations.
|
* This formatter adheres to the XML date format standard (ISO-8601). It can be used
|
||||||
*
|
* to ensure consistent date representations in XML or other similar text-based formats.
|
||||||
* @throws UnsupportedOperationException always, to indicate that this class
|
* <p>
|
||||||
* should not be instantiated.
|
* Thread-safe and immutable, this formatter can be shared across multiple threads.
|
||||||
*/
|
*/
|
||||||
private XmlUtils() {
|
public static final DateTimeFormatter XML_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||||
throw new UnsupportedOperationException("Utility class");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link DateTimeFormatter} instance used for formatting or parsing dates in the "yyyy-MM-dd" pattern.
|
* Private constructor to prevent instantiation of the utility class.
|
||||||
*
|
* This utility class is not meant to be instantiated, as it only provides
|
||||||
* This formatter adheres to the XML date format standard (ISO-8601). It can be used
|
* static utility methods for array-related operations.
|
||||||
* to ensure consistent date representations in XML or other similar text-based formats.
|
*
|
||||||
*
|
* @throws UnsupportedOperationException always, to indicate that this class
|
||||||
* Thread-safe and immutable, this formatter can be shared across multiple threads.
|
* should not be instantiated.
|
||||||
*/
|
*/
|
||||||
public static final DateTimeFormatter XML_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
private XmlUtils() {
|
||||||
|
throw new UnsupportedOperationException("Utility class");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new element in the provided XML document with the specified name.
|
* Creates an XML element with the specified name and value, formatted as a date string.
|
||||||
*
|
* The date value is converted to an XML-compatible string format using a predefined formatter.
|
||||||
* @param doc The XML document in which the new element is to be created. Must not be null.
|
*
|
||||||
* @param name The name of the element to be created. Must not be null or empty.
|
* @param doc the XML document to which the element belongs; must not be null
|
||||||
* @return The newly created element with the given name. Never returns null.
|
* @param name the name of the element to be created; must not be null or empty
|
||||||
*/
|
* @param value the date value to be assigned to the created element; must not be null
|
||||||
public static Element createElement(final Document doc, final String name) {
|
* @return the created XML element containing the specified name and formatted date value
|
||||||
log.debug("Creating a new element " + name);
|
*/
|
||||||
return doc.createElement(name);
|
public static Element createElement(final Document doc, final String name, final Date value) {
|
||||||
}
|
return createElement(doc, name, XML_DATE_FORMATTER.format(value.toInstant()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new XML element with the specified name and value, appends the value
|
* Creates a new XML element with the specified name and value, appends the value
|
||||||
* as a text node to the element, and returns the resulting element.
|
* as a text node to the element, and returns the resulting element.
|
||||||
*
|
*
|
||||||
* @param doc the XML document to which the element belongs. Must not be null.
|
* @param doc the XML document to which the element belongs. Must not be null.
|
||||||
* @param name the name of the element to create. Must not be null.
|
* @param name the name of the element to create. Must not be null.
|
||||||
* @param value the text value to be set as the content of the created element.
|
* @param value the text value to be set as the content of the created element.
|
||||||
* If null, an empty string will be used as the content.
|
* If null, an empty string will be used as the content.
|
||||||
* @return the newly created XML element containing the specified value as a text node.
|
* @return the newly created XML element containing the specified value as a text node.
|
||||||
*/
|
*/
|
||||||
public static Element createElement(final Document doc, final String name, final String value) {
|
public static Element createElement(final Document doc, final String name, final String value) {
|
||||||
log.debug("Creating a new element " + name + " with value: " + value);
|
log.debug("Creating a new element " + name + " with value: " + value);
|
||||||
Element element = doc.createElement(name);
|
Element element = doc.createElement(name);
|
||||||
|
|
||||||
Node content = doc.createTextNode(value != null ? value : "");
|
Node content = doc.createTextNode(value != null ? value : "");
|
||||||
element.appendChild(content);
|
element.appendChild(content);
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an XML element with the specified name and value, formatted as a date string.
|
* Creates an XML element with the specified name and optional value.
|
||||||
* The date value is converted to an XML-compatible string format using a predefined formatter.
|
*
|
||||||
*
|
* @param doc The Document object to which the element will belong. Must not be null.
|
||||||
* @param doc the XML document to which the element belongs; must not be null
|
* @param name The name of the element to be created. Must not be null or empty.
|
||||||
* @param name the name of the element to be created; must not be null or empty
|
* @param value The optional value to be set as the text content of the element. If null,
|
||||||
* @param value the date value to be assigned to the created element; must not be null
|
* the element will have an empty text content.
|
||||||
* @return the created XML element containing the specified name and formatted date value
|
* @return The newly created Element object with the specified name and value.
|
||||||
*/
|
*/
|
||||||
public static Element createElement(final Document doc, final String name, final Date value) {
|
public static Element createElement(final Document doc, final String name, final Integer value) {
|
||||||
return createElement(doc, name, XML_DATE_FORMATTER.format(value.toInstant()));
|
log.debug("Creating a new element " + name + " with value: " + value);
|
||||||
}
|
Element element = doc.createElement(name);
|
||||||
|
|
||||||
/**
|
Node content = doc.createTextNode(value != null ? "" + value : "");
|
||||||
* Creates an XML element with the specified name and optional value.
|
element.appendChild(content);
|
||||||
*
|
|
||||||
* @param doc The Document object to which the element will belong. Must not be null.
|
|
||||||
* @param name The name of the element to be created. Must not be null or empty.
|
|
||||||
* @param value The optional value to be set as the text content of the element. If null,
|
|
||||||
* the element will have an empty text content.
|
|
||||||
* @return The newly created Element object with the specified name and value.
|
|
||||||
*/
|
|
||||||
public static Element createElement(final Document doc, final String name, final Integer value) {
|
|
||||||
log.debug("Creating a new element " + name + " with value: " + value);
|
|
||||||
Element element = doc.createElement(name);
|
|
||||||
|
|
||||||
Node content = doc.createTextNode(value != null ? ""+value : "");
|
return element;
|
||||||
element.appendChild(content);
|
}
|
||||||
|
|
||||||
return element;
|
/**
|
||||||
}
|
* Creates a new element with the specified name, appends it to the provided parent element,
|
||||||
|
* and returns the newly created element.
|
||||||
|
*
|
||||||
|
* @param doc The Document to which the new element belongs. Must not be null.
|
||||||
|
* @param name The name of the new element to be created. Must not be null or empty.
|
||||||
|
* @param parent The parent element to which the new element will be appended. Must not be null.
|
||||||
|
* @return The newly created and appended Element object.
|
||||||
|
*/
|
||||||
|
public static Element createAndInsertElement(final Document doc, final String name, @NonNull final Element parent) {
|
||||||
|
log.debug("Creating a new element " + name + " and adding it to a parent.");
|
||||||
|
Element element = createElement(doc, name);
|
||||||
|
parent.appendChild(element);
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new element with the specified name, appends it to the provided parent element,
|
* Creates a new element in the provided XML document with the specified name.
|
||||||
* and returns the newly created element.
|
*
|
||||||
*
|
* @param doc The XML document in which the new element is to be created. Must not be null.
|
||||||
* @param doc The Document to which the new element belongs. Must not be null.
|
* @param name The name of the element to be created. Must not be null or empty.
|
||||||
* @param name The name of the new element to be created. Must not be null or empty.
|
* @return The newly created element with the given name. Never returns null.
|
||||||
* @param parent The parent element to which the new element will be appended. Must not be null.
|
*/
|
||||||
* @return The newly created and appended Element object.
|
public static Element createElement(final Document doc, final String name) {
|
||||||
*/
|
log.debug("Creating a new element " + name);
|
||||||
public static Element createAndInsertElement(final Document doc, final String name, @NonNull final Element parent) {
|
return doc.createElement(name);
|
||||||
log.debug("Creating a new element " + name + " and adding it to a parent.");
|
}
|
||||||
Element element = createElement(doc, name);
|
|
||||||
parent.appendChild(element);
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new XML element with a specified name and value, adds it to the specified parent element,
|
* Creates a new XML element with a specified name and value, adds it to the specified parent element,
|
||||||
* and returns the created element.
|
* and returns the created element.
|
||||||
*
|
*
|
||||||
* @param doc The XML document to which the new element belongs. Must not be null.
|
* @param doc The XML document to which the new element belongs. Must not be null.
|
||||||
* @param name The name of the element to create. Must not be null.
|
* @param name The name of the element to create. Must not be null.
|
||||||
* @param value The value to be set for the created element. Can be null if no value is required.
|
* @param value The value to be set for the created element. Can be null if no value is required.
|
||||||
* @param parent The parent element to which the new element will be appended. Must not be null.
|
* @param parent The parent element to which the new element will be appended. Must not be null.
|
||||||
* @return The newly created and inserted element.
|
* @return The newly created and inserted element.
|
||||||
*/
|
*/
|
||||||
public static Element createAndInsertElement(final Document doc, final String name, final String value, @NonNull final Element parent) {
|
public static Element createAndInsertElement(final Document doc, final String name, final String value, @NonNull final Element parent) {
|
||||||
log.debug("Creating a new element " + name + " with value: " + value + " and adding it to a parent.");
|
log.debug("Creating a new element " + name + " with value: " + value + " and adding it to a parent.");
|
||||||
Element element = createElement(doc, name, value);
|
Element element = createElement(doc, name, value);
|
||||||
parent.appendChild(element);
|
parent.appendChild(element);
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats a given XML string by applying proper indentation to enhance readability.
|
||||||
|
* The method uses a transformer to process the XML input, applying indentation
|
||||||
|
* with a four-space configuration. In case of an error, the original XML string
|
||||||
|
* is returned without any modifications.
|
||||||
|
*
|
||||||
|
* @param xmlStream The raw XML string to be formatted. Cannot be null.
|
||||||
|
* @return The formatted XML string with proper indentation. If an exception occurs
|
||||||
|
* during formatting, the original XML string is returned.
|
||||||
|
*/
|
||||||
|
public static String format(final String xmlStream) {
|
||||||
|
log.debug("formatXML");
|
||||||
|
|
||||||
/**
|
try {
|
||||||
* Formats a given XML string by applying proper indentation to enhance readability.
|
Source xmlInput = new StreamSource(new StringReader(xmlStream));
|
||||||
* The method uses a transformer to process the XML input, applying indentation
|
StringWriter stringWriter = new StringWriter();
|
||||||
* with a four-space configuration. In case of an error, the original XML string
|
StreamResult xmlOutput = new StreamResult(stringWriter);
|
||||||
* is returned without any modifications.
|
|
||||||
*
|
|
||||||
* @param xmlStream The raw XML string to be formatted. Cannot be null.
|
|
||||||
* @return The formatted XML string with proper indentation. If an exception occurs
|
|
||||||
* during formatting, the original XML string is returned.
|
|
||||||
*/
|
|
||||||
public static String format(final String xmlStream) {
|
|
||||||
log.debug("formatXML");
|
|
||||||
|
|
||||||
try {
|
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||||
Source xmlInput = new StreamSource(new StringReader(xmlStream));
|
transformerFactory.setAttribute("indent-number", "4");
|
||||||
StringWriter stringWriter = new StringWriter();
|
|
||||||
StreamResult xmlOutput = new StreamResult(stringWriter);
|
|
||||||
|
|
||||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
Transformer transformer = transformerFactory.newTransformer();
|
||||||
transformerFactory.setAttribute("indent-number", "4");
|
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||||
|
transformer.transform(xmlInput, xmlOutput);
|
||||||
|
|
||||||
Transformer transformer = transformerFactory.newTransformer();
|
return xmlOutput.getWriter().toString();
|
||||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
} catch (TransformerException e) {
|
||||||
transformer.transform(xmlInput, xmlOutput);
|
log.error("Error in XML: " + e.getMessage() + "\n" + xmlStream, e);
|
||||||
|
}
|
||||||
|
|
||||||
return xmlOutput.getWriter().toString();
|
return xmlStream;
|
||||||
}
|
}
|
||||||
catch(TransformerException e) {
|
|
||||||
log.error("Error in XML: " + e.getMessage() + "\n"+xmlStream, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return xmlStream;
|
/**
|
||||||
}
|
* Formats a given XML Document into a human-readable string representation.
|
||||||
|
* The method applies indentation and specific output properties to the XML content.
|
||||||
|
* If an exception occurs during the transformation process, it logs the error
|
||||||
|
* and returns null.
|
||||||
|
*
|
||||||
|
* @param doc The XML Document to be formatted. Must not be null.
|
||||||
|
* @return A formatted string representation of the XML document, or null if an error occurs during processing.
|
||||||
|
*/
|
||||||
|
public static String format(final Document doc) {
|
||||||
|
try {
|
||||||
|
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
||||||
|
transformerFactory.setAttribute("indent-number", "4");
|
||||||
|
Transformer transformer = transformerFactory.newTransformer();
|
||||||
|
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||||
|
transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
|
||||||
|
transformer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1");
|
||||||
|
StringWriter stringWriter = new StringWriter();
|
||||||
|
|
||||||
/**
|
Source source = new DOMSource(doc);
|
||||||
* Formats a given XML Document into a human-readable string representation.
|
Result result = new StreamResult(stringWriter);
|
||||||
* The method applies indentation and specific output properties to the XML content.
|
transformer.transform(source, result);
|
||||||
* If an exception occurs during the transformation process, it logs the error
|
|
||||||
* and returns null.
|
|
||||||
*
|
|
||||||
* @param doc The XML Document to be formatted. Must not be null.
|
|
||||||
* @return A formatted string representation of the XML document, or null if an error occurs during processing.
|
|
||||||
*/
|
|
||||||
public static String format(final Document doc) {
|
|
||||||
try {
|
|
||||||
TransformerFactory transformerFactory = TransformerFactory.newInstance();
|
|
||||||
transformerFactory.setAttribute("indent-number", "4");
|
|
||||||
Transformer transformer = transformerFactory.newTransformer();
|
|
||||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
|
||||||
transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
|
|
||||||
transformer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1");
|
|
||||||
StringWriter stringWriter = new StringWriter();
|
|
||||||
|
|
||||||
Source source = new DOMSource(doc);
|
String xmlString = stringWriter.toString();
|
||||||
Result result = new StreamResult(stringWriter);
|
log.info("MO Request: " + xmlString);
|
||||||
transformer.transform(source, result);
|
return xmlString;
|
||||||
|
} catch (TransformerException e) {
|
||||||
|
log.error("Error in XML Transformation: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
String xmlString = stringWriter.toString();
|
return null;
|
||||||
log.info("MO Request: " + xmlString);
|
}
|
||||||
return xmlString;
|
|
||||||
}
|
|
||||||
catch(TransformerException e) {
|
|
||||||
log.error("Error in XML Transformation: " + e.getMessage(), e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
/**
|
||||||
}
|
* Validates whether a given XML string is well-formed.
|
||||||
|
* This method attempts to parse the input XML string and checks
|
||||||
/**
|
* if it can successfully create a valid DOM object from it.
|
||||||
* Validates whether a given XML string is well-formed.
|
*
|
||||||
* This method attempts to parse the input XML string and checks
|
* @param xml The XML string to be validated. Must not be null.
|
||||||
* if it can successfully create a valid DOM object from it.
|
* If null or invalid, the method will return false.
|
||||||
*
|
* @return true if the XML string is well-formed and can be successfully parsed;
|
||||||
* @param xml The XML string to be validated. Must not be null.
|
* false otherwise, such as in the case of invalid content or parsing errors.
|
||||||
* If null or invalid, the method will return false.
|
*/
|
||||||
* @return true if the XML string is well-formed and can be successfully parsed;
|
public static boolean checkXml(final String xml) {
|
||||||
* false otherwise, such as in the case of invalid content or parsing errors.
|
try {
|
||||||
*/
|
InputStream stream = new ByteArrayInputStream(xml.getBytes());
|
||||||
public static boolean checkXml(final String xml) {
|
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||||
try {
|
DocumentBuilder docBuilder = dbf.newDocumentBuilder();
|
||||||
InputStream stream = new ByteArrayInputStream(xml.getBytes());
|
Document document = docBuilder.parse(stream);
|
||||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
return document != null;
|
||||||
DocumentBuilder docBuilder = dbf.newDocumentBuilder();
|
} catch (Exception ex) {
|
||||||
Document document = docBuilder.parse(stream);
|
log.warn("Exception when validating xml.", ex);
|
||||||
return document != null;
|
return false;
|
||||||
} catch (Exception ex) {
|
}
|
||||||
log.warn("Exception when validating xml.", ex);
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,11 @@ import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_2;
|
|||||||
import de.neitzel.core.inject.testcomponents.test1ok.sub.TestComponent1_2;
|
import de.neitzel.core.inject.testcomponents.test1ok.sub.TestComponent1_2;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
class InjectableComponentScannerTest {
|
class InjectableComponentScannerTest {
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@ -2,20 +2,26 @@ package de.neitzel.encryption;
|
|||||||
|
|
||||||
import com.idealista.fpe.config.Alphabet;
|
import com.idealista.fpe.config.Alphabet;
|
||||||
|
|
||||||
public abstract class BaseAlphabet implements Alphabet {
|
/**
|
||||||
|
* Base implementation of the {@link Alphabet} interface providing common helper methods
|
||||||
|
* for working with character sets and validating characters against the alphabet.
|
||||||
|
*/
|
||||||
|
public interface BaseAlphabet extends Alphabet {
|
||||||
/**
|
/**
|
||||||
* Replaces illegal characters of a string with a given replacement character
|
* Replaces illegal characters of a string with a given replacement character.
|
||||||
* @param text Text to check.
|
*
|
||||||
* @param replacement Replacement character.
|
* @param text Text to check; may not be null
|
||||||
* @return String in which all characters was replaced.
|
* @param replacement Replacement character to use for characters not present in the alphabet
|
||||||
|
* @return a new string in which every invalid character was replaced with the provided replacement
|
||||||
|
* @throws IllegalArgumentException if {@code replacement} is not a valid character in the alphabet
|
||||||
*/
|
*/
|
||||||
public String replaceIllegalCharacters(String text, char replacement) {
|
default String replaceIllegalCharacters(String text, char replacement) {
|
||||||
// Validate
|
// Validate
|
||||||
if (!isValidCharacter(replacement)) throw new IllegalArgumentException("replacement");
|
if (!isValidCharacter(replacement)) throw new IllegalArgumentException("replacement");
|
||||||
|
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
for (char ch: text.toCharArray()) {
|
for (char ch : text.toCharArray()) {
|
||||||
if (isValidCharacter(ch)){
|
if (isValidCharacter(ch)) {
|
||||||
result.append(ch);
|
result.append(ch);
|
||||||
} else {
|
} else {
|
||||||
result.append(replacement);
|
result.append(replacement);
|
||||||
@ -26,10 +32,11 @@ public abstract class BaseAlphabet implements Alphabet {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a given Character is part of the alphabet.
|
* Checks if a given Character is part of the alphabet.
|
||||||
|
*
|
||||||
* @param character Character to check.
|
* @param character Character to check.
|
||||||
* @return true if character is valid, else false.
|
* @return true if character is valid, else false.
|
||||||
*/
|
*/
|
||||||
public boolean isValidCharacter(char character) {
|
default boolean isValidCharacter(char character) {
|
||||||
// Compare with all characters
|
// Compare with all characters
|
||||||
for (char ch : availableCharacters()) {
|
for (char ch : availableCharacters()) {
|
||||||
if (ch == character) return true;
|
if (ch == character) return true;
|
||||||
|
|||||||
@ -7,49 +7,53 @@ import com.idealista.fpe.config.LengthRange;
|
|||||||
|
|
||||||
import javax.crypto.KeyGenerator;
|
import javax.crypto.KeyGenerator;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Random;
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class to deal with FF1Encryption using com.idealista:format-preserving-encryption:1.0.0.
|
* Helper class to deal with FF1Encryption using com.idealista:format-preserving-encryption:1.0.0.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class FF1Encryption {
|
public class FF1Encryption {
|
||||||
|
|
||||||
/**
|
private static final SecureRandom RANDOM = new SecureRandom();
|
||||||
* Key to use for all encryption / unencryption
|
|
||||||
*/
|
|
||||||
private byte[] key;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should to small strings be ignored?
|
* Key to use for all encryption / unencryption.
|
||||||
*
|
|
||||||
* If this is set to true, small strings (less than 2 characters) will not be encrypted!
|
|
||||||
*/
|
*/
|
||||||
private boolean ignoreToShortStrings;
|
private final byte[] key;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tweak to use for encryption
|
* Should too small strings be ignored?
|
||||||
|
* If this is set to true, small strings (less than {@code minLength} characters)
|
||||||
|
* will not be encrypted or decrypted and will be returned unchanged.
|
||||||
*/
|
*/
|
||||||
private byte[] tweak;
|
private final boolean ignoreToShortStrings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Domain used for encryption
|
* Tweak to use for encryption.
|
||||||
*/
|
*/
|
||||||
private Domain domain;
|
private final byte[] tweak;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format preserving encryption to use.
|
* Domain used for encryption.
|
||||||
*/
|
*/
|
||||||
private FormatPreservingEncryption encryption;
|
private final Domain domain;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimum length of a string.
|
* Format preserving encryption implementation to use.
|
||||||
*/
|
*/
|
||||||
private int minLength;
|
private final FormatPreservingEncryption encryption;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum length of a string for which encryption/decryption will be applied.
|
||||||
|
*/
|
||||||
|
private final int minLength;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of FF1Encryption
|
* Creates a new instance of FF1Encryption
|
||||||
* @param key AES key to use.
|
*
|
||||||
* @param tweak tweak to use for encryption / decryption
|
* @param key AES key to use.
|
||||||
|
* @param tweak tweak to use for encryption / decryption
|
||||||
* @param ignoreToShortStrings Ignore strings that are to short.
|
* @param ignoreToShortStrings Ignore strings that are to short.
|
||||||
*/
|
*/
|
||||||
public FF1Encryption(byte[] key, byte[] tweak, boolean ignoreToShortStrings) {
|
public FF1Encryption(byte[] key, byte[] tweak, boolean ignoreToShortStrings) {
|
||||||
@ -58,12 +62,13 @@ public class FF1Encryption {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of FF1Encryption
|
* Creates a new instance of FF1Encryption
|
||||||
* @param key AES key to use.
|
*
|
||||||
* @param tweak tweak to use for encryption / decryption
|
* @param key AES key to use.
|
||||||
|
* @param tweak tweak to use for encryption / decryption
|
||||||
* @param ignoreToShortStrings Ignore strings that are to short.
|
* @param ignoreToShortStrings Ignore strings that are to short.
|
||||||
* @param domain Domain to use for encryption
|
* @param domain Domain to use for encryption
|
||||||
* @param minLength Minimum length of string.
|
* @param minLength Minimum length of string.
|
||||||
* @param maxLength Maximum length of string.
|
* @param maxLength Maximum length of string.
|
||||||
*/
|
*/
|
||||||
public FF1Encryption(byte[] key, byte[] tweak, boolean ignoreToShortStrings, Domain domain, int minLength, int maxLength) {
|
public FF1Encryption(byte[] key, byte[] tweak, boolean ignoreToShortStrings, Domain domain, int minLength, int maxLength) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
@ -72,15 +77,45 @@ public class FF1Encryption {
|
|||||||
this.domain = domain;
|
this.domain = domain;
|
||||||
this.minLength = minLength;
|
this.minLength = minLength;
|
||||||
|
|
||||||
encryption = FormatPreservingEncryptionBuilder
|
this.encryption = FormatPreservingEncryptionBuilder
|
||||||
.ff1Implementation().withDomain(domain)
|
.ff1Implementation()
|
||||||
|
.withDomain(domain)
|
||||||
.withDefaultPseudoRandomFunction(key)
|
.withDefaultPseudoRandomFunction(key)
|
||||||
.withLengthRange(new LengthRange(minLength, maxLength))
|
.withLengthRange(new LengthRange(minLength, maxLength))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
byte[] key = new byte[length];
|
||||||
|
RANDOM.nextBytes(key);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypts a given text.
|
* Encrypts a given text.
|
||||||
|
*
|
||||||
* @param plainText Unencrypted text.
|
* @param plainText Unencrypted text.
|
||||||
* @return Encrypted text.
|
* @return Encrypted text.
|
||||||
*/
|
*/
|
||||||
@ -97,6 +132,7 @@ public class FF1Encryption {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt a given text.
|
* Decrypt a given text.
|
||||||
|
*
|
||||||
* @param cipherText Encrypted text.
|
* @param cipherText Encrypted text.
|
||||||
* @return Decrypted text.
|
* @return Decrypted text.
|
||||||
*/
|
*/
|
||||||
@ -110,31 +146,4 @@ public class FF1Encryption {
|
|||||||
// Return decrypted text.
|
// Return decrypted text.
|
||||||
return encryption.decrypt(cipherText, tweak);
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,12 +3,14 @@ package de.neitzel.encryption;
|
|||||||
/**
|
/**
|
||||||
* Main characters of the Alphabet.
|
* Main characters of the Alphabet.
|
||||||
*/
|
*/
|
||||||
public class MainAlphabet extends BaseAlphabet {
|
public class MainAlphabet implements BaseAlphabet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All characters we want to use.
|
* All characters that are part of the alphabet used by the MainDomain.
|
||||||
|
* This array includes lowercase letters, uppercase letters, digits, a set of
|
||||||
|
* special characters, and German-specific characters.
|
||||||
*/
|
*/
|
||||||
private static final char[] ALL_CHARS = new char[] {
|
private static final char[] ALL_CHARS = new char[]{
|
||||||
// lowercase characters
|
// lowercase characters
|
||||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
|
'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',
|
's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||||
@ -21,16 +23,38 @@ public class MainAlphabet extends BaseAlphabet {
|
|||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||||
|
|
||||||
// special characters
|
// special characters
|
||||||
'-', '_', '?', ' ', '#', '+', '/', '*', '!', '\"', '§', '$', '%', '&', '(', ')', '=', '@',
|
'-', '_', '?', ' ', '#', '+', '/', '*', '!', '"', '§', '$', '%', '&', '(', ')', '=', '@',
|
||||||
'€', ',', ';', '.', ':', '<', '>', '|', '\'', '\\', '{', '}', '[', ']',
|
'€', ',', ';', '.', ':', '<', '>', '|', '\'', '\\', '{', '}', '[', ']',
|
||||||
|
|
||||||
// german special characters
|
// german special characters
|
||||||
'ä', 'Ä', 'ö', 'Ö', 'ü', 'Ü', 'ß'
|
'ä', 'Ä', 'ö', 'Ö', 'ü', 'Ü', 'ß'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new MainAlphabet instance.
|
||||||
|
*
|
||||||
|
* <p>This constructor performs no runtime initialization because the
|
||||||
|
* alphabet is represented by the static {@code ALL_CHARS} array. Use
|
||||||
|
* {@link #availableCharacters()} to obtain the supported characters and
|
||||||
|
* {@link #radix()} to obtain the alphabet size.</p>
|
||||||
|
*
|
||||||
|
* <p> Important usage notes: </p>
|
||||||
|
* <ul>
|
||||||
|
* <li>The array returned by {@link #availableCharacters()} is the internal
|
||||||
|
* static array; callers must not modify it.</li>
|
||||||
|
* <li>The alphabet intentionally contains letters (lower and upper case),
|
||||||
|
* digits, common punctuation and a set of German-specific characters
|
||||||
|
* to support locale-sensitive operations.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public MainAlphabet() {
|
||||||
|
// default constructor only ...
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the available characters.
|
* Gets the available characters.
|
||||||
* @return
|
*
|
||||||
|
* @return an array containing all supported characters in the alphabet
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public char[] availableCharacters() {
|
public char[] availableCharacters() {
|
||||||
@ -39,7 +63,8 @@ public class MainAlphabet extends BaseAlphabet {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the radix of the alphabet.
|
* Gets the radix of the alphabet.
|
||||||
* @return
|
*
|
||||||
|
* @return the number of characters in the alphabet (radix)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Integer radix() {
|
public Integer radix() {
|
||||||
|
|||||||
@ -4,26 +4,60 @@ import com.idealista.fpe.config.Alphabet;
|
|||||||
import com.idealista.fpe.config.Domain;
|
import com.idealista.fpe.config.Domain;
|
||||||
import com.idealista.fpe.config.GenericTransformations;
|
import com.idealista.fpe.config.GenericTransformations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Domain implementation that provides the alphabet and transformation helpers
|
||||||
|
* used by the format-preserving encryption implementation in this project.
|
||||||
|
* <p>
|
||||||
|
* This class delegates character-to-index and index-to-character transformations
|
||||||
|
* to a {@link GenericTransformations} helper configured with the {@link MainAlphabet}.
|
||||||
|
*/
|
||||||
public class MainDomain implements Domain {
|
public class MainDomain implements Domain {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The alphabet used by the domain for encoding/decoding characters.
|
||||||
|
*/
|
||||||
private Alphabet _alphabet;
|
private Alphabet _alphabet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper instance performing actual transformations between characters and indices.
|
||||||
|
*/
|
||||||
private GenericTransformations transformations;
|
private GenericTransformations transformations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new {@code MainDomain} instance and initializes its alphabet and transformation helper.
|
||||||
|
*/
|
||||||
public MainDomain() {
|
public MainDomain() {
|
||||||
_alphabet = new MainAlphabet();
|
_alphabet = new MainAlphabet();
|
||||||
transformations = new GenericTransformations(_alphabet.availableCharacters());
|
transformations = new GenericTransformations(_alphabet.availableCharacters());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link Alphabet} implementation used by this domain.
|
||||||
|
*
|
||||||
|
* @return the alphabet used for transforming values in this domain
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Alphabet alphabet() {
|
public Alphabet alphabet() {
|
||||||
return _alphabet;
|
return _alphabet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms a string of characters into an array of integer indices according to the configured alphabet.
|
||||||
|
*
|
||||||
|
* @param data the input string to transform; may not be null
|
||||||
|
* @return an integer array representing the indices of the characters in the given string
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public int[] transform(String data) {
|
public int[] transform(String data) {
|
||||||
return transformations.transform(data);
|
return transformations.transform(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms an array of integer indices back into a string using the configured alphabet.
|
||||||
|
*
|
||||||
|
* @param data the array of indices to transform back into characters
|
||||||
|
* @return the resulting string produced from the given index array
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String transform(int[] data) {
|
public String transform(int[] data) {
|
||||||
return transformations.transform(data);
|
return transformations.transform(data);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@ -3,9 +3,27 @@ package de.neitzel.fx.component.example;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple address model used in the FX example component.
|
||||||
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class Address {
|
public class Address {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Street name and number.
|
||||||
|
*/
|
||||||
private String street;
|
private String street;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* City name.
|
||||||
|
*/
|
||||||
private String city;
|
private String city;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor only
|
||||||
|
*/
|
||||||
|
public Address() {
|
||||||
|
// default constructor only
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -6,8 +6,32 @@ import javafx.scene.Parent;
|
|||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example JavaFX application demonstrating the {@link de.neitzel.fx.component.ComponentLoader} usage.
|
||||||
|
*/
|
||||||
public class ExampleApp extends Application {
|
public class ExampleApp extends Application {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor only
|
||||||
|
*/
|
||||||
|
public ExampleApp() {
|
||||||
|
// default constructor only
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launches the JavaFX application.
|
||||||
|
*
|
||||||
|
* @param args command line arguments
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
launch(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes sample model data, loads the FX component and shows the primary stage.
|
||||||
|
*
|
||||||
|
* @param primaryStage the primary stage for this application
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage primaryStage) throws Exception {
|
public void start(Stage primaryStage) throws Exception {
|
||||||
// Beispielmodel initialisieren
|
// Beispielmodel initialisieren
|
||||||
@ -29,8 +53,4 @@ public class ExampleApp extends Application {
|
|||||||
primaryStage.setScene(scene);
|
primaryStage.setScene(scene);
|
||||||
primaryStage.show();
|
primaryStage.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
launch();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -5,8 +5,17 @@ package de.neitzel.fx.component.example;
|
|||||||
* taking care os Classloader Requirements of JavaFX. (Important when starting from inside NetBeans!)
|
* taking care os Classloader Requirements of JavaFX. (Important when starting from inside NetBeans!)
|
||||||
*/
|
*/
|
||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor only
|
||||||
|
*/
|
||||||
|
public Main() {
|
||||||
|
// default constructor only
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Additional main methode to start Application.
|
* Additional main methode to start Application.
|
||||||
|
*
|
||||||
* @param args Commandline Arguments.
|
* @param args Commandline Arguments.
|
||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@ -3,9 +3,27 @@ package de.neitzel.fx.component.example;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple person model used in the FX example component.
|
||||||
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class Person {
|
public class Person {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The person's full name.
|
||||||
|
*/
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The address of the person.
|
||||||
|
*/
|
||||||
private Address address;
|
private Address address;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor only
|
||||||
|
*/
|
||||||
|
public Person() {
|
||||||
|
// default constructor only
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,23 +6,44 @@ import javafx.scene.Parent;
|
|||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal JavaFX application launcher used by the examples.
|
||||||
|
*
|
||||||
|
* <p>This class initializes the JavaFX runtime, loads {@code MainWindow.fxml} and shows
|
||||||
|
* the primary stage. It is intended as a small bootstrap used by IDEs or test runs
|
||||||
|
* to start the JavaFX UI for demonstration purposes.
|
||||||
|
*/
|
||||||
public class JavaFXApp extends Application {
|
public class JavaFXApp extends Application {
|
||||||
|
|
||||||
@Override
|
private static final Logger LOGGER = Logger.getLogger(JavaFXApp.class.getName());
|
||||||
public void start(Stage primaryStage) {
|
|
||||||
try {
|
|
||||||
primaryStage.setTitle("Hello World!");
|
|
||||||
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("MainWindow.fxml"));
|
|
||||||
Parent root = fxmlLoader.load();
|
|
||||||
primaryStage.setScene(new Scene(root));
|
|
||||||
primaryStage.show();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
System.out.println("Exception: " + ex.getMessage());
|
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
/**
|
||||||
launch(args);
|
* Default constructor only.
|
||||||
}
|
*
|
||||||
|
* <p>The constructor does not perform UI initialization. UI setup happens in {@link #start(Stage)}
|
||||||
|
* after JavaFX has set up the application thread.
|
||||||
|
*/
|
||||||
|
public JavaFXApp() {
|
||||||
|
// default constructor only
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
launch(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(Stage primaryStage) {
|
||||||
|
try {
|
||||||
|
primaryStage.setTitle("Hello World!");
|
||||||
|
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("MainWindow.fxml"));
|
||||||
|
Parent root = fxmlLoader.load();
|
||||||
|
primaryStage.setScene(new Scene(root));
|
||||||
|
primaryStage.show();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Exception while starting JavaFXApp", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,8 +5,26 @@ package de.neitzel.fx.injectfx.example;
|
|||||||
* taking care os Classloader Requirements of JavaFX. (Important when starting from inside NetBeans!)
|
* taking care os Classloader Requirements of JavaFX. (Important when starting from inside NetBeans!)
|
||||||
*/
|
*/
|
||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new helper instance of {@code Main}.
|
||||||
|
*
|
||||||
|
* <p>This constructor is intentionally empty. The {@code Main} class serves as an alternative
|
||||||
|
* entry point to launch the JavaFX application via {@link JavaFXApp#main(String[])} in
|
||||||
|
* environments (IDEs, build tools) where directly starting a class that extends
|
||||||
|
* {@code javafx.application.Application} may fail due to classloader or JVM startup constraints.
|
||||||
|
*
|
||||||
|
* <p>Do not perform application initialization here; use the static {@code main} method to
|
||||||
|
* start the JavaFX runtime. This constructor exists to allow tooling and frameworks to
|
||||||
|
* instantiate the class reflectively if required.
|
||||||
|
*/
|
||||||
|
public Main() {
|
||||||
|
// default constructor only
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Additional main methode to start Application.
|
* Additional main methode to start Application.
|
||||||
|
*
|
||||||
* @param args Commandline Arguments.
|
* @param args Commandline Arguments.
|
||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@ -8,13 +8,49 @@ import javafx.scene.control.TextField;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for the MainWindow FXML view.
|
||||||
|
*
|
||||||
|
* <p>This class acts as the backing controller for the {@code MainWindow.fxml} UI. It demonstrates a
|
||||||
|
* minimal example of a JavaFX controller that:
|
||||||
|
* <ul>
|
||||||
|
* <li>receives UI element injection via {@link FXML} (the {@link #textField} field),</li>
|
||||||
|
* <li>initializes the view in {@link #initialize(URL, ResourceBundle)}, and</li>
|
||||||
|
* <li>handles user actions via {@link #onButtonClick(ActionEvent)}.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Usage notes:
|
||||||
|
* <ul>
|
||||||
|
* <li>Fields annotated with {@link FXML} are injected by the {@code FXMLLoader} before
|
||||||
|
* {@link #initialize(URL, ResourceBundle)} is called — do not expect them to be non-null
|
||||||
|
* in the constructor.</li>
|
||||||
|
* <li>All UI updates must run on the JavaFX Application Thread. Methods called by FXML
|
||||||
|
* (such as {@link #onButtonClick(ActionEvent)}) are executed on that thread by the
|
||||||
|
* JavaFX runtime.</li>
|
||||||
|
* <li>The controller keeps a simple click counter and updates {@link #textField} to reflect
|
||||||
|
* the current count using {@link #displayCounter()}.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
public class MainWindow implements Initializable {
|
public class MainWindow implements Initializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counter for button clicks shown in the UI.
|
||||||
|
*/
|
||||||
private int counter = 0;
|
private int counter = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text field UI control showing the click counter.
|
||||||
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
private TextField textField;
|
private TextField textField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor only
|
||||||
|
*/
|
||||||
|
public MainWindow() {
|
||||||
|
// default constructor only
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize(URL location, ResourceBundle resources) {
|
public void initialize(URL location, ResourceBundle resources) {
|
||||||
displayCounter();
|
displayCounter();
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
<?import javafx.scene.control.TextField?>
|
<?import javafx.scene.control.TextField?>
|
||||||
<?import javafx.scene.layout.AnchorPane?>
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
<AnchorPane xmlns="http://javafx.com/javafx"
|
<AnchorPane xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
|
||||||
xmlns:nfx="http://example.com/nfx"
|
xmlns:nfx="http://example.com/nfx"
|
||||||
|
xmlns="http://javafx.com/javafx"
|
||||||
fx:controller="de.neitzel.fx.component.ComponentController"
|
fx:controller="de.neitzel.fx.component.ComponentController"
|
||||||
prefWidth="300" prefHeight="100">
|
prefWidth="300" prefHeight="100">
|
||||||
<children>
|
<children>
|
||||||
|
|||||||
@ -3,9 +3,11 @@
|
|||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
<?import javafx.scene.control.TextField?>
|
<?import javafx.scene.control.TextField?>
|
||||||
<?import javafx.scene.layout.AnchorPane?>
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
<AnchorPane prefHeight="127.0" prefWidth="209.0" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.neitzel.fx.injectfx.example.MainWindow">
|
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" prefHeight="127.0" prefWidth="209.0"
|
||||||
<children>
|
xmlns="http://javafx.com/javafx/17.0.2-ea" fx:controller="de.neitzel.fx.injectfx.example.MainWindow">
|
||||||
<Button fx:id="button" layoutX="44.0" layoutY="70.0" mnemonicParsing="false" onAction="#onButtonClick" text="Click Me" />
|
<children>
|
||||||
<TextField fx:id="textField" layoutX="14.0" layoutY="24.0" />
|
<Button fx:id="button" layoutX="44.0" layoutY="70.0" mnemonicParsing="false" onAction="#onButtonClick"
|
||||||
</children>
|
text="Click Me"/>
|
||||||
|
<TextField fx:id="textField" layoutX="14.0" layoutY="24.0"/>
|
||||||
|
</children>
|
||||||
</AnchorPane>
|
</AnchorPane>
|
||||||
|
|||||||
@ -3,9 +3,9 @@
|
|||||||
<?import javafx.scene.control.TextField?>
|
<?import javafx.scene.control.TextField?>
|
||||||
<?import javafx.scene.layout.AnchorPane?>
|
<?import javafx.scene.layout.AnchorPane?>
|
||||||
<?import javafx.scene.layout.Pane?>
|
<?import javafx.scene.layout.Pane?>
|
||||||
<AnchorPane xmlns="http://javafx.com/javafx"
|
<AnchorPane xmlns:fx="http://javafx.com/fxml"
|
||||||
xmlns:fx="http://javafx.com/fxml"
|
|
||||||
xmlns:nfx="http://example.com/nfx"
|
xmlns:nfx="http://example.com/nfx"
|
||||||
|
xmlns="http://javafx.com/javafx"
|
||||||
fx:controller="de.neitzel.fx.component.ComponentController"
|
fx:controller="de.neitzel.fx.component.ComponentController"
|
||||||
prefWidth="300" prefHeight="180">
|
prefWidth="300" prefHeight="180">
|
||||||
<children>
|
<children>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,48 @@
|
|||||||
package de.neitzel.fx.component;
|
package de.neitzel.fx.component;
|
||||||
|
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.Property;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleDoubleProperty;
|
||||||
|
import javafx.beans.property.SimpleFloatProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleLongProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AutoViewModel automatically exposes JavaFX properties for all readable/writable fields
|
||||||
|
* of a given POJO model. It creates appropriate Property instances and keeps them in a map
|
||||||
|
* for lookup by field name.
|
||||||
|
*
|
||||||
|
* @param <T> the type of the underlying model
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class AutoViewModel<T> {
|
public class AutoViewModel<T> {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(AutoViewModel.class.getName());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The wrapped model instance.
|
||||||
|
*/
|
||||||
private final T model;
|
private final T model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of field name to JavaFX Property instance exposing that field's value.
|
||||||
|
*/
|
||||||
private final Map<String, Property<?>> properties = new HashMap<>();
|
private final Map<String, Property<?>> properties = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an AutoViewModel for the provided model and initializes properties via reflection.
|
||||||
|
*
|
||||||
|
* @param model the POJO model whose getters/setters are used to create JavaFX properties
|
||||||
|
*/
|
||||||
public AutoViewModel(T model) {
|
public AutoViewModel(T model) {
|
||||||
this.model = model;
|
this.model = model;
|
||||||
initProperties();
|
initProperties();
|
||||||
@ -27,13 +58,17 @@ public class AutoViewModel<T> {
|
|||||||
properties.put(fieldName, prop);
|
properties.put(fieldName, prop);
|
||||||
|
|
||||||
// Bind ViewModel → Model
|
// Bind ViewModel → Model
|
||||||
prop.addListener((obs, oldVal, newVal) -> {
|
prop.addListener((_obs, _oldVal, newVal) -> {
|
||||||
|
// use _obs and _oldVal to satisfy static analysis (they are intentionally retained)
|
||||||
|
if (_oldVal != null && _oldVal.equals(newVal)) {
|
||||||
|
// no-op: values unchanged
|
||||||
|
}
|
||||||
Method setter = findSetterFor(model.getClass(), fieldName, newVal != null ? newVal.getClass() : null);
|
Method setter = findSetterFor(model.getClass(), fieldName, newVal != null ? newVal.getClass() : null);
|
||||||
if (setter != null) {
|
if (setter != null) {
|
||||||
try {
|
try {
|
||||||
setter.invoke(model, newVal);
|
setter.invoke(model, newVal);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
LOGGER.log(Level.WARNING, "Failed to invoke setter for field " + fieldName, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -41,16 +76,6 @@ public class AutoViewModel<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Property<?> getProperty(String name) {
|
|
||||||
return properties.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public T getModel() {
|
|
||||||
return model;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== Hilfsmethoden ==========
|
|
||||||
|
|
||||||
private boolean isGetter(Method method) {
|
private boolean isGetter(Method method) {
|
||||||
return Modifier.isPublic(method.getModifiers())
|
return Modifier.isPublic(method.getModifiers())
|
||||||
&& method.getParameterCount() == 0
|
&& method.getParameterCount() == 0
|
||||||
@ -68,20 +93,27 @@ public class AutoViewModel<T> {
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String decapitalize(String str) {
|
// ========== Hilfsmethoden ==========
|
||||||
if (str == null || str.isEmpty()) return str;
|
|
||||||
return str.substring(0, 1).toLowerCase() + str.substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object invokeGetter(Method method) {
|
private Object invokeGetter(Method method) {
|
||||||
try {
|
try {
|
||||||
return method.invoke(model);
|
return method.invoke(model);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
LOGGER.log(Level.WARNING, "Failed to invoke getter: " + method.getName(), e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Property<?> toProperty(Object value) {
|
||||||
|
if (value instanceof String s) return new SimpleStringProperty(s);
|
||||||
|
if (value instanceof Integer i) return new SimpleIntegerProperty(i);
|
||||||
|
if (value instanceof Boolean b) return new SimpleBooleanProperty(b);
|
||||||
|
if (value instanceof Double d) return new SimpleDoubleProperty(d);
|
||||||
|
if (value instanceof Float f) return new SimpleFloatProperty(f);
|
||||||
|
if (value instanceof Long l) return new SimpleLongProperty(l);
|
||||||
|
return new SimpleObjectProperty<>(value);
|
||||||
|
}
|
||||||
|
|
||||||
private Method findSetterFor(Class<?> clazz, String fieldName, Class<?> valueType) {
|
private Method findSetterFor(Class<?> clazz, String fieldName, Class<?> valueType) {
|
||||||
String setterName = "set" + capitalize(fieldName);
|
String setterName = "set" + capitalize(fieldName);
|
||||||
for (Method m : clazz.getMethods()) {
|
for (Method m : clazz.getMethods()) {
|
||||||
@ -94,18 +126,21 @@ public class AutoViewModel<T> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String decapitalize(String str) {
|
||||||
|
if (str == null || str.isEmpty()) return str;
|
||||||
|
return str.substring(0, 1).toLowerCase() + str.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
private String capitalize(String str) {
|
private String capitalize(String str) {
|
||||||
if (str == null || str.isEmpty()) return str;
|
if (str == null || str.isEmpty()) return str;
|
||||||
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Property<?> toProperty(Object value) {
|
public Property<?> getProperty(String name) {
|
||||||
if (value instanceof String s) return new SimpleStringProperty(s);
|
return properties.get(name);
|
||||||
if (value instanceof Integer i) return new SimpleIntegerProperty(i);
|
}
|
||||||
if (value instanceof Boolean b) return new SimpleBooleanProperty(b);
|
|
||||||
if (value instanceof Double d) return new SimpleDoubleProperty(d);
|
public T getModel() {
|
||||||
if (value instanceof Float f) return new SimpleFloatProperty(f);
|
return model;
|
||||||
if (value instanceof Long l) return new SimpleLongProperty(l);
|
|
||||||
return new SimpleObjectProperty<>(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,11 +4,9 @@ import de.neitzel.fx.component.controls.Binding;
|
|||||||
import de.neitzel.fx.component.controls.FxmlComponent;
|
import de.neitzel.fx.component.controls.FxmlComponent;
|
||||||
import de.neitzel.fx.component.model.BindingData;
|
import de.neitzel.fx.component.model.BindingData;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.beans.value.ObservableValue;
|
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.Parent;
|
import javafx.scene.Parent;
|
||||||
import javafx.scene.layout.Pane;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@ -17,9 +15,9 @@ import java.io.IOException;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ComponentLoader is responsible for loading JavaFX FXML components and binding
|
* ComponentLoader is responsible for loading JavaFX FXML components and binding
|
||||||
@ -27,45 +25,18 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ComponentLoader {
|
public class ComponentLoader {
|
||||||
private Map<String, Map<String, String>> nfxBindingMap = new HashMap<>();
|
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private Object controller;
|
private Object controller;
|
||||||
|
|
||||||
public Parent load(URL fxmlPath) {
|
|
||||||
return load(null, fxmlPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Parent load(String fxmlPath) {
|
|
||||||
return load(null, fxmlPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Parent load(Object model, URL fxmlPath) {
|
|
||||||
try {
|
|
||||||
AutoViewModel<?> viewModel = new AutoViewModel<>(model);
|
|
||||||
FXMLLoader loader = new FXMLLoader(fxmlPath);
|
|
||||||
loader.setControllerFactory(type -> new ComponentController(viewModel));
|
|
||||||
Parent root = loader.load();
|
|
||||||
controller = loader.getController();
|
|
||||||
return root;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("unable to load fxml: " + fxmlPath, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads an FXML file and binds its elements to a generated ViewModel
|
* Default constructor only
|
||||||
* based on the given POJO model.
|
|
||||||
*
|
|
||||||
* @param model the data model (POJO) to bind to the UI
|
|
||||||
* @param fxmlPath the path to the FXML file
|
|
||||||
* @return the root JavaFX node loaded from FXML
|
|
||||||
*/
|
*/
|
||||||
public Parent load(Object model, String fxmlPath) {
|
public ComponentLoader() {
|
||||||
return load(model, getClass().getResource(fxmlPath));
|
// default constructor only
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T extends Parent> T load(URL fxmlUrl, Object controller, String nothing) throws IOException {
|
public static <T extends Parent> T load(URL fxmlUrl, Object controller, @SuppressWarnings("unused") String nothing) throws IOException {
|
||||||
FXMLLoader loader = new FXMLLoader(fxmlUrl);
|
FXMLLoader loader = new FXMLLoader(fxmlUrl);
|
||||||
loader.setController(controller);
|
loader.setController(controller);
|
||||||
T root = loader.load();
|
T root = loader.load();
|
||||||
@ -85,21 +56,23 @@ public class ComponentLoader {
|
|||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
private static <T> void bindBidirectionalSafe(@NotNull Property<T> source, Property<?> target) {
|
private static <T> void bindBidirectionalSafe(@NotNull Property<T> source, Property<?> target) {
|
||||||
try {
|
try {
|
||||||
Property<T> targetCasted = (Property<T>) target;
|
Property<T> targetCasted = (Property<T>) target;
|
||||||
source.bindBidirectional(targetCasted);
|
source.bindBidirectional(targetCasted);
|
||||||
} catch (ClassCastException e) {
|
} catch (ClassCastException e) {
|
||||||
log.error("⚠️ Typkonflikt beim Binding: %s ⇄ %s%n", source.getClass(), target.getClass(), e);
|
log.error("⚠️ Typkonflikt beim Binding: {} ⇄ {}", source.getClass(), target.getClass(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
private static <T> void bindSafe(@NotNull Property<T> source, Property<?> target) {
|
private static <T> void bindSafe(@NotNull Property<T> source, Property<?> target) {
|
||||||
try {
|
try {
|
||||||
Property<T> targetCasted = (Property<T>) target;
|
Property<T> targetCasted = (Property<T>) target;
|
||||||
source.bind(targetCasted);
|
source.bind(targetCasted);
|
||||||
} catch (ClassCastException e) {
|
} catch (ClassCastException e) {
|
||||||
log.error("⚠️ Typkonflikt beim Binding: %s ⇄ %s%n", source.getClass(), target.getClass(), e);
|
log.error("⚠️ Typkonflikt beim Binding: {} ⇄ {}", source.getClass(), target.getClass(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,9 +82,7 @@ public class ComponentLoader {
|
|||||||
Object source = resolveExpression(binding.getSource(), namespace);
|
Object source = resolveExpression(binding.getSource(), namespace);
|
||||||
Object target = resolveExpression(binding.getTarget(), namespace);
|
Object target = resolveExpression(binding.getTarget(), namespace);
|
||||||
|
|
||||||
if (source instanceof Property && target instanceof Property) {
|
if (source instanceof Property<?> sourceProp && target instanceof Property<?> targetProp) {
|
||||||
Property<?> sourceProp = (Property<?>) source;
|
|
||||||
Property<?> targetProp = (Property<?>) target;
|
|
||||||
|
|
||||||
Class<?> sourceType = getPropertyType(sourceProp);
|
Class<?> sourceType = getPropertyType(sourceProp);
|
||||||
Class<?> targetType = getPropertyType(targetProp);
|
Class<?> targetType = getPropertyType(targetProp);
|
||||||
@ -124,7 +95,7 @@ public class ComponentLoader {
|
|||||||
if (bindableForward && bindableBackward) {
|
if (bindableForward && bindableBackward) {
|
||||||
bindBidirectionalSafe(sourceProp, targetProp);
|
bindBidirectionalSafe(sourceProp, targetProp);
|
||||||
} else {
|
} else {
|
||||||
log.error("⚠️ Kann bidirektionales Binding nicht durchführen: Typen inkompatibel (%s ⇄ %s)%n", sourceType, targetType);
|
log.error("⚠️ Kann bidirektionales Binding nicht durchführen: Typen inkompatibel ({} ⇄ {})", sourceType, targetType);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "unidirectional":
|
case "unidirectional":
|
||||||
@ -132,13 +103,13 @@ public class ComponentLoader {
|
|||||||
if (bindableForward) {
|
if (bindableForward) {
|
||||||
bindSafe(sourceProp, targetProp);
|
bindSafe(sourceProp, targetProp);
|
||||||
} else {
|
} else {
|
||||||
log.error("⚠️ Kann unidirektionales Binding nicht durchführen: %s → %s nicht zuweisbar%n", sourceType, targetType);
|
log.error("⚠️ Kann unidirektionales Binding nicht durchführen: {} → {} nicht zuweisbar", sourceType, targetType);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Fehler beim Binding: " + binding.getSource() + " → " + binding.getTarget(), e);
|
log.error("Fehler beim Binding: {} → {}", binding.getSource(), binding.getTarget(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,6 +145,42 @@ public class ComponentLoader {
|
|||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Parent load(URL fxmlPath) {
|
||||||
|
return load(null, fxmlPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Parent load(Object model, URL fxmlPath) {
|
||||||
|
try {
|
||||||
|
AutoViewModel<?> viewModel = new AutoViewModel<>(model);
|
||||||
|
FXMLLoader loader = new FXMLLoader(fxmlPath);
|
||||||
|
loader.setControllerFactory(type -> {
|
||||||
|
Objects.requireNonNull(type);
|
||||||
|
return new ComponentController(viewModel);
|
||||||
|
});
|
||||||
|
Parent root = loader.load();
|
||||||
|
controller = loader.getController();
|
||||||
|
return root;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("unable to load fxml: " + fxmlPath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Parent load(String fxmlPath) {
|
||||||
|
return load(null, fxmlPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an FXML file and binds its elements to a generated ViewModel
|
||||||
|
* based on the given POJO model.
|
||||||
|
*
|
||||||
|
* @param model the data model (POJO) to bind to the UI
|
||||||
|
* @param fxmlPath the path to the FXML file
|
||||||
|
* @return the root JavaFX node loaded from FXML
|
||||||
|
*/
|
||||||
|
public Parent load(Object model, String fxmlPath) {
|
||||||
|
return load(model, getClass().getResource(fxmlPath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binds a JavaFX UI control to a ViewModel property according to the specified direction.
|
* Binds a JavaFX UI control to a ViewModel property according to the specified direction.
|
||||||
*
|
*
|
||||||
@ -181,6 +188,7 @@ public class ComponentLoader {
|
|||||||
* @param vmProp the ViewModel property to bind to
|
* @param vmProp the ViewModel property to bind to
|
||||||
* @param direction the direction of the binding (e.g., "bidirectional", "read")
|
* @param direction the direction of the binding (e.g., "bidirectional", "read")
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
private void bindNodeToProperty(javafx.scene.Node node, Property<?> vmProp, String direction) {
|
private void bindNodeToProperty(javafx.scene.Node node, Property<?> vmProp, String direction) {
|
||||||
if (node instanceof javafx.scene.control.TextField tf && vmProp instanceof javafx.beans.property.StringProperty sp) {
|
if (node instanceof javafx.scene.control.TextField tf && vmProp instanceof javafx.beans.property.StringProperty sp) {
|
||||||
if ("bidirectional".equalsIgnoreCase(direction)) {
|
if ("bidirectional".equalsIgnoreCase(direction)) {
|
||||||
|
|||||||
@ -11,19 +11,53 @@ import javafx.scene.layout.StackPane;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reusable JavaFX component that loads and embeds an FXML-defined UI fragment.
|
||||||
|
* <p>
|
||||||
|
* The component exposes three properties:
|
||||||
|
* <ul>
|
||||||
|
* <li>fxml: the resource path to the FXML file to load (relative to the component class)</li>
|
||||||
|
* <li>direction: a textual hint that describes binding directionality (default "unidirectional")</li>
|
||||||
|
* <li>data: an arbitrary object that will be injected into the loaded controller if a suitable setter exists</li>
|
||||||
|
* </ul>
|
||||||
|
* <p>
|
||||||
|
* When the {@code fxml} property changes the FXML is loaded via {@link ComponentLoader}. After loading the
|
||||||
|
* controller instance available from the loader, this component attempts to inject the object from the
|
||||||
|
* {@code data} property by calling a matching {@code setData(...)} method via reflection.
|
||||||
|
*/
|
||||||
public class FxmlComponent extends StackPane {
|
public class FxmlComponent extends StackPane {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The FXML resource path to be loaded by this component. A value of {@code null} or blank disables loading.
|
||||||
|
*/
|
||||||
private final StringProperty fxml = new SimpleStringProperty();
|
private final StringProperty fxml = new SimpleStringProperty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Human-readable binding direction hint used by outer frameworks; defaults to "unidirectional".
|
||||||
|
*/
|
||||||
private final StringProperty direction = new SimpleStringProperty("unidirectional");
|
private final StringProperty direction = new SimpleStringProperty("unidirectional");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional data object that is injected into the controller of the loaded FXML when available.
|
||||||
|
*/
|
||||||
private final ObjectProperty<Object> data = new SimpleObjectProperty<>();
|
private final ObjectProperty<Object> data = new SimpleObjectProperty<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance of {@code FxmlComponent}.
|
||||||
|
* <p>
|
||||||
|
* The constructor registers listeners on the {@code fxml} and {@code data} properties to
|
||||||
|
* perform lazy loading and data injection when values change.
|
||||||
|
*/
|
||||||
public FxmlComponent() {
|
public FxmlComponent() {
|
||||||
fxml.addListener((obs, oldVal, newVal) -> load());
|
fxml.addListener((obs, oldVal, newVal) -> load());
|
||||||
data.addListener((obs, oldVal, newVal) -> injectData());
|
data.addListener((obs, oldVal, newVal) -> injectData());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the FXML specified by the {@link #fxml} property and replaces the current children
|
||||||
|
* with the loaded root node. If no FXML is configured or the path is blank, the method returns
|
||||||
|
* without performing any action.
|
||||||
|
*/
|
||||||
private void load() {
|
private void load() {
|
||||||
if (getFxml() == null || getFxml().isBlank()) return;
|
if (getFxml() == null || getFxml().isBlank()) return;
|
||||||
ComponentLoader loader = new ComponentLoader();
|
ComponentLoader loader = new ComponentLoader();
|
||||||
@ -37,6 +71,10 @@ public class FxmlComponent extends StackPane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to inject the {@link #data} object into the controller of the currently loaded child node,
|
||||||
|
* if a controller is present and defines a single-argument method named {@code setData}.
|
||||||
|
*/
|
||||||
private void injectData() {
|
private void injectData() {
|
||||||
if (!getChildren().isEmpty() && getData() != null) {
|
if (!getChildren().isEmpty() && getData() != null) {
|
||||||
Object controller = getControllerFromChild(getChildren().get(0));
|
Object controller = getControllerFromChild(getChildren().get(0));
|
||||||
@ -46,22 +84,50 @@ public class FxmlComponent extends StackPane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the configured FXML resource path.
|
||||||
|
*
|
||||||
|
* @return the FXML path or {@code null} if not set
|
||||||
|
*/
|
||||||
public String getFxml() {
|
public String getFxml() {
|
||||||
return fxml.get();
|
return fxml.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the FXML resource path to load. Changing this value triggers a reload of the component content.
|
||||||
|
*
|
||||||
|
* @param fxml resource path relative to the component class
|
||||||
|
*/
|
||||||
public void setFxml(String fxml) {
|
public void setFxml(String fxml) {
|
||||||
this.fxml.set(fxml);
|
this.fxml.set(fxml);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data object that will be injected into the loaded controller when available.
|
||||||
|
*
|
||||||
|
* @return the data object or {@code null}
|
||||||
|
*/
|
||||||
public Object getData() {
|
public Object getData() {
|
||||||
return data.get();
|
return data.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the data object to be injected into the controller of the loaded FXML.
|
||||||
|
* Changing the value will attempt injection into the currently loaded controller.
|
||||||
|
*
|
||||||
|
* @param data the data object to inject
|
||||||
|
*/
|
||||||
public void setData(Object data) {
|
public void setData(Object data) {
|
||||||
this.data.set(data);
|
this.data.set(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses reflection to find a single-argument method named {@code setData} on the controller and
|
||||||
|
* invokes it with the provided {@code dataObject}.
|
||||||
|
*
|
||||||
|
* @param controller the controller instance to inject into
|
||||||
|
* @param dataObject the object to inject
|
||||||
|
*/
|
||||||
private void injectDataToController(Object controller, Object dataObject) {
|
private void injectDataToController(Object controller, Object dataObject) {
|
||||||
// Daten-Objekt per Reflection zuweisen
|
// Daten-Objekt per Reflection zuweisen
|
||||||
// Beispiel: Controller hat `setData(User data)`
|
// Beispiel: Controller hat `setData(User data)`
|
||||||
@ -79,6 +145,12 @@ public class FxmlComponent extends StackPane {
|
|||||||
// Optional: automatisch Binding ausführen (s. u.)
|
// Optional: automatisch Binding ausführen (s. u.)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the controller instance from a child node's properties map if present.
|
||||||
|
*
|
||||||
|
* @param node the child node to inspect
|
||||||
|
* @return the controller object stored under the property key {@code "fx:controller"} or {@code null}
|
||||||
|
*/
|
||||||
private Object getControllerFromChild(Node node) {
|
private Object getControllerFromChild(Node node) {
|
||||||
if (node.getProperties().containsKey("fx:controller")) {
|
if (node.getProperties().containsKey("fx:controller")) {
|
||||||
return node.getProperties().get("fx:controller");
|
return node.getProperties().get("fx:controller");
|
||||||
@ -86,24 +158,48 @@ public class FxmlComponent extends StackPane {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the JavaFX property representing the configured FXML path.
|
||||||
|
*
|
||||||
|
* @return the FXML property
|
||||||
|
*/
|
||||||
public StringProperty fxmlProperty() {
|
public StringProperty fxmlProperty() {
|
||||||
return fxml;
|
return fxml;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the JavaFX property representing the binding direction hint.
|
||||||
|
*
|
||||||
|
* @return the direction property
|
||||||
|
*/
|
||||||
public StringProperty directionProperty() {
|
public StringProperty directionProperty() {
|
||||||
return direction;
|
return direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the configured direction string.
|
||||||
|
*
|
||||||
|
* @return the direction hint (e.g., "unidirectional")
|
||||||
|
*/
|
||||||
public String getDirection() {
|
public String getDirection() {
|
||||||
return direction.get();
|
return direction.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the direction hint used by higher-level binding logic.
|
||||||
|
*
|
||||||
|
* @param direction textual direction hint
|
||||||
|
*/
|
||||||
public void setDirection(String direction) {
|
public void setDirection(String direction) {
|
||||||
this.direction.set(direction);
|
this.direction.set(direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the JavaFX property representing the data object used for injection.
|
||||||
|
*
|
||||||
|
* @return the data property
|
||||||
|
*/
|
||||||
public ObjectProperty<Object> dataProperty() {
|
public ObjectProperty<Object> dataProperty() {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,21 +7,22 @@ import javafx.beans.property.StringProperty;
|
|||||||
* The Binding class represents a connection between a source and a target,
|
* The Binding class represents a connection between a source and a target,
|
||||||
* with an associated direction. It is typically used to define a binding
|
* with an associated direction. It is typically used to define a binding
|
||||||
* relationship that determines how data flows between these two entities.
|
* relationship that determines how data flows between these two entities.
|
||||||
*
|
* <p>
|
||||||
* The class provides three properties:
|
* The class provides three properties:
|
||||||
* - direction: Represents the type of the binding. Defaults to "unidirectional".
|
* - direction: Represents the type of the binding. Defaults to "unidirectional".
|
||||||
* - source: Represents the source of the binding.
|
* - source: Represents the source of the binding.
|
||||||
* - target: Represents the target of the binding.
|
* - target: Represents the target of the binding.
|
||||||
*
|
* <p>
|
||||||
* It uses JavaFX property types, allowing these properties to be observed
|
* It uses JavaFX property types, allowing these properties to be observed
|
||||||
* for changes.
|
* for changes.
|
||||||
*/
|
*/
|
||||||
public class BindingData {
|
public class BindingData {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the direction of the binding in the {@code Binding} class.
|
* Represents the direction of the binding in the {@code Binding} class.
|
||||||
* It determines whether the binding is unidirectional or bidirectional.
|
* It determines whether the binding is unidirectional or bidirectional.
|
||||||
* The default value is "unidirectional".
|
* The default value is "unidirectional".
|
||||||
*
|
* <p>
|
||||||
* This property is observed for changes, enabling dynamic updates
|
* This property is observed for changes, enabling dynamic updates
|
||||||
* within the JavaFX property system.
|
* within the JavaFX property system.
|
||||||
*/
|
*/
|
||||||
@ -34,12 +35,20 @@ public class BindingData {
|
|||||||
* when the source value is modified.
|
* when the source value is modified.
|
||||||
*/
|
*/
|
||||||
private StringProperty source = new SimpleStringProperty();
|
private StringProperty source = new SimpleStringProperty();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the target of the binding in the Binding class.
|
* Represents the target of the binding in the Binding class.
|
||||||
* This property holds the target value, which can be observed for changes.
|
* This property holds the target value, which can be observed for changes.
|
||||||
*/
|
*/
|
||||||
private StringProperty target = new SimpleStringProperty();
|
private StringProperty target = new SimpleStringProperty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor only
|
||||||
|
*/
|
||||||
|
public BindingData() {
|
||||||
|
// default constructor only
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current direction of the binding.
|
* Gets the current direction of the binding.
|
||||||
* The direction determines how data flows between
|
* The direction determines how data flows between
|
||||||
@ -47,37 +56,52 @@ public class BindingData {
|
|||||||
*
|
*
|
||||||
* @return the current direction of the binding
|
* @return the current direction of the binding
|
||||||
*/
|
*/
|
||||||
public String getDirection() { return direction.get(); }
|
public String getDirection() {
|
||||||
|
return direction.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the direction of the binding.
|
* Sets the direction of the binding.
|
||||||
*
|
*
|
||||||
* @param dir the new direction to set for the binding
|
* @param dir the new direction to set for the binding
|
||||||
*/
|
*/
|
||||||
public void setDirection(String dir) { direction.set(dir); }
|
public void setDirection(String dir) {
|
||||||
|
direction.set(dir);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current source value of the binding.
|
* Gets the current source value of the binding.
|
||||||
*
|
*
|
||||||
* @return the source value as a String.
|
* @return the source value as a String.
|
||||||
*/
|
*/
|
||||||
public String getSource() { return source.get(); }
|
public String getSource() {
|
||||||
|
return source.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the value of the source property for this binding.
|
* Sets the value of the source property for this binding.
|
||||||
*
|
*
|
||||||
* @param source the new source value to be set
|
* @param source the new source value to be set
|
||||||
*/
|
*/
|
||||||
public void setSource(String source) { this.source.set(source); }
|
public void setSource(String source) {
|
||||||
|
this.source.set(source);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the current value of the target property.
|
* Retrieves the current value of the target property.
|
||||||
*
|
*
|
||||||
* @return the value of the target property as a String.
|
* @return the value of the target property as a String.
|
||||||
*/
|
*/
|
||||||
public String getTarget() { return target.get(); }
|
public String getTarget() {
|
||||||
|
return target.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the target property of the binding.
|
* Sets the target property of the binding.
|
||||||
*
|
*
|
||||||
* @param target the new value to set for the target property
|
* @param target the new value to set for the target property
|
||||||
*/
|
*/
|
||||||
public void setTarget(String target) { this.target.set(target); }
|
public void setTarget(String target) {
|
||||||
|
this.target.set(target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -19,7 +19,9 @@ public class FXMLComponentInstances {
|
|||||||
/** Map holding instances of all @FXMLComponent classes, indexed by class and its unique superclasses/interfaces. */
|
/** Map holding instances of all @FXMLComponent classes, indexed by class and its unique superclasses/interfaces. */
|
||||||
private final Map<Class<?>, Object> instanceMap = new HashMap<>();
|
private final Map<Class<?>, Object> instanceMap = new HashMap<>();
|
||||||
|
|
||||||
/** The InjectableComponents instance that provides information about instantiable components. */
|
/**
|
||||||
|
* The InjectableComponents instance that provides information about instantiable components.
|
||||||
|
*/
|
||||||
private final ComponentScanner injectableScanner;
|
private final ComponentScanner injectableScanner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -13,27 +13,29 @@ import java.util.stream.Stream;
|
|||||||
* support dependency injection. It uses a parameter map to resolve and supply dependencies to
|
* support dependency injection. It uses a parameter map to resolve and supply dependencies to
|
||||||
* controller constructors dynamically during instantiation.
|
* controller constructors dynamically during instantiation.
|
||||||
*
|
*
|
||||||
* This class simplifies the process of injecting dependencies into JavaFX controllers by analyzing
|
* <p>This class analyzes available constructors of a requested controller class at runtime and
|
||||||
* the constructors of the given controller classes at runtime. It selects the constructor that best
|
* selects the first constructor whose parameter types are all present in the internal parameter map.
|
||||||
* matches the available dependencies in the parameter map and creates an instance of the controller.
|
* The corresponding instances are retrieved and passed to the constructor to create the controller.
|
||||||
*
|
*
|
||||||
* It implements the Callback interface to provide compatibility with the JavaFX FXMLLoader, allowing
|
* <p>It implements the {@link javafx.util.Callback} interface to integrate with the JavaFX
|
||||||
* controllers with dependencies to be injected seamlessly during the FXML loading process.
|
* {@link javafx.fxml.FXMLLoader} controller factory mechanism.
|
||||||
*/
|
*/
|
||||||
public class InjectingControllerFactory implements Callback<Class<?>, Object> {
|
public class InjectingControllerFactory implements Callback<Class<?>, Object> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A map that stores class-to-object mappings used for dependency injection
|
* A map that stores class-to-object mappings used for dependency injection
|
||||||
* in controller instantiation. This map is utilized to resolve and supply
|
* in controller instantiation. Each key represents a parameter type and the
|
||||||
* the required dependencies for constructors during the creation of controller
|
* associated value is the instance to be injected for that type.
|
||||||
* instances.
|
|
||||||
*
|
|
||||||
* Each key in the map represents a class type, and the corresponding value
|
|
||||||
* is the instance of that type. This allows the {@link InjectingControllerFactory}
|
|
||||||
* to use the stored instances to dynamically match and inject dependencies
|
|
||||||
* into controllers at runtime.
|
|
||||||
*/
|
*/
|
||||||
private final Map<Class<?>, Object> parameterMap = new HashMap<>();
|
private final Map<Class<?>, Object> parameterMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor only
|
||||||
|
*/
|
||||||
|
public InjectingControllerFactory() {
|
||||||
|
// default constructor only
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a mapping between a class and its corresponding object instance
|
* Adds a mapping between a class and its corresponding object instance
|
||||||
* to the parameter map used for dependency injection.
|
* to the parameter map used for dependency injection.
|
||||||
@ -81,11 +83,11 @@ public class InjectingControllerFactory implements Callback<Class<?>, Object> {
|
|||||||
* This method checks if the parameter map contains entries for all parameter types required
|
* This method checks if the parameter map contains entries for all parameter types required
|
||||||
* by the specified constructor.
|
* by the specified constructor.
|
||||||
*
|
*
|
||||||
* @param constructor The constructor to be evaluated for instantiability.
|
* @param constructor The constructor to be evaluated for instantiability.
|
||||||
* @param parameterMap A map where keys are parameter types and values are the corresponding
|
* @param parameterMap A map where keys are parameter types and values are the corresponding
|
||||||
* instances available for injection.
|
* instances available for injection.
|
||||||
* @return {@code true} if the constructor can be instantiated with the given parameter map;
|
* @return {@code true} if the constructor can be instantiated with the given parameter map;
|
||||||
* {@code false} otherwise.
|
* {@code false} otherwise.
|
||||||
*/
|
*/
|
||||||
private boolean canBeInstantiated(Constructor<?> constructor, Map<Class<?>, Object> parameterMap) {
|
private boolean canBeInstantiated(Constructor<?> constructor, Map<Class<?>, Object> parameterMap) {
|
||||||
return Stream.of(constructor.getParameterTypes()).allMatch(parameterMap::containsKey);
|
return Stream.of(constructor.getParameterTypes()).allMatch(parameterMap::containsKey);
|
||||||
|
|||||||
@ -11,12 +11,12 @@ import java.net.URL;
|
|||||||
* The InjectingFXMLLoader class provides a custom implementation of JavaFX's FXMLLoader
|
* The InjectingFXMLLoader class provides a custom implementation of JavaFX's FXMLLoader
|
||||||
* that supports dependency injection. It facilitates the loading of FXML files while
|
* that supports dependency injection. It facilitates the loading of FXML files while
|
||||||
* dynamically injecting dependencies into controllers during the loading process.
|
* dynamically injecting dependencies into controllers during the loading process.
|
||||||
*
|
* <p>
|
||||||
* This class utilizes the InjectingControllerFactory to enable seamless integration
|
* This class utilizes the InjectingControllerFactory to enable seamless integration
|
||||||
* of dependency injection with JavaFX controllers. Dependencies can be added to the
|
* of dependency injection with JavaFX controllers. Dependencies can be added to the
|
||||||
* controller factory, and the loader will use this factory to instantiate controllers
|
* controller factory, and the loader will use this factory to instantiate controllers
|
||||||
* with the appropriate dependencies.
|
* with the appropriate dependencies.
|
||||||
*
|
* <p>
|
||||||
* Features of this loader include:
|
* Features of this loader include:
|
||||||
* - Support for dependency injection into JavaFX controllers by using a custom factory.
|
* - Support for dependency injection into JavaFX controllers by using a custom factory.
|
||||||
* - Adding custom dependency mappings at runtime.
|
* - Adding custom dependency mappings at runtime.
|
||||||
@ -28,20 +28,20 @@ public class InjectingFXMLLoader {
|
|||||||
/**
|
/**
|
||||||
* Represents an instance of the {@link InjectingControllerFactory}, which is used for creating
|
* Represents an instance of the {@link InjectingControllerFactory}, which is used for creating
|
||||||
* and managing controller instances with dependency injection capabilities in a JavaFX application.
|
* and managing controller instances with dependency injection capabilities in a JavaFX application.
|
||||||
*
|
* <p>
|
||||||
* This factory facilitates the injection of dependencies by dynamically resolving and supplying
|
* This factory facilitates the injection of dependencies by dynamically resolving and supplying
|
||||||
* required objects during controller instantiation. It plays a critical role in enabling seamless
|
* required objects during controller instantiation. It plays a critical role in enabling seamless
|
||||||
* integration of dependency injection with JavaFX's {@link FXMLLoader}.
|
* integration of dependency injection with JavaFX's {@link FXMLLoader}.
|
||||||
*
|
* <p>
|
||||||
* The `controllerFactory` is used internally by the {@link InjectingFXMLLoader} to provide a consistent
|
* The `controllerFactory` is used internally by the {@link InjectingFXMLLoader} to provide a consistent
|
||||||
* and extensible mechanism for controller creation while maintaining loose coupling and enhancing testability.
|
* and extensible mechanism for controller creation while maintaining loose coupling and enhancing testability.
|
||||||
*
|
* <p>
|
||||||
* Key responsibilities:
|
* Key responsibilities:
|
||||||
* - Manages a mapping of classes to their injectable instances required for controller instantiation.
|
* - Manages a mapping of classes to their injectable instances required for controller instantiation.
|
||||||
* - Dynamically analyzes and invokes appropriate constructors for controllers based on the availability
|
* - Dynamically analyzes and invokes appropriate constructors for controllers based on the availability
|
||||||
* of dependencies.
|
* of dependencies.
|
||||||
* - Ensures that controllers are created with required dependencies, preventing manual resolution of injections.
|
* - Ensures that controllers are created with required dependencies, preventing manual resolution of injections.
|
||||||
*
|
* <p>
|
||||||
* This variable is initialized in the {@link InjectingFXMLLoader} and can be extended with additional
|
* This variable is initialized in the {@link InjectingFXMLLoader} and can be extended with additional
|
||||||
* mappings at runtime using relevant methods.
|
* mappings at runtime using relevant methods.
|
||||||
*/
|
*/
|
||||||
@ -51,7 +51,7 @@ public class InjectingFXMLLoader {
|
|||||||
* Default constructor for the InjectingFXMLLoader class.
|
* Default constructor for the InjectingFXMLLoader class.
|
||||||
* This initializes the loader with a new instance of the {@link InjectingControllerFactory},
|
* This initializes the loader with a new instance of the {@link InjectingControllerFactory},
|
||||||
* which is used to provide dependency injection capabilities for JavaFX controllers.
|
* which is used to provide dependency injection capabilities for JavaFX controllers.
|
||||||
*
|
* <p>
|
||||||
* The {@link InjectingControllerFactory} allows for the registration and dynamic injection
|
* The {@link InjectingControllerFactory} allows for the registration and dynamic injection
|
||||||
* of dependencies into controllers when they are instantiated during the FXML loading process.
|
* of dependencies into controllers when they are instantiated during the FXML loading process.
|
||||||
*/
|
*/
|
||||||
@ -100,7 +100,7 @@ public class InjectingFXMLLoader {
|
|||||||
* available for dependency injection.
|
* available for dependency injection.
|
||||||
*/
|
*/
|
||||||
private void addInjectingData(FXMLComponentInstances instances) {
|
private void addInjectingData(FXMLComponentInstances instances) {
|
||||||
for (var clazz: instances.getInstanceMap().keySet()) {
|
for (var clazz : instances.getInstanceMap().keySet()) {
|
||||||
addInjectingData(clazz, instances.getInstance(clazz));
|
addInjectingData(clazz, instances.getInstance(clazz));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,7 +54,7 @@ public class BindingAwareFXMLLoader<T> {
|
|||||||
* Recursively traverses the scene graph and binds controls to properties
|
* Recursively traverses the scene graph and binds controls to properties
|
||||||
* in the ViewModel based on custom metadata in the node properties map.
|
* in the ViewModel based on custom metadata in the node properties map.
|
||||||
*
|
*
|
||||||
* @param node the current node to inspect
|
* @param node the current node to inspect
|
||||||
* @param viewModel the ViewModel holding the properties
|
* @param viewModel the ViewModel holding the properties
|
||||||
*/
|
*/
|
||||||
private void bindNodesRecursively(Node node, GenericViewModel<T> viewModel) {
|
private void bindNodesRecursively(Node node, GenericViewModel<T> viewModel) {
|
||||||
@ -92,9 +92,9 @@ public class BindingAwareFXMLLoader<T> {
|
|||||||
* Binds two JavaFX properties according to the specified binding direction.
|
* Binds two JavaFX properties according to the specified binding direction.
|
||||||
*
|
*
|
||||||
* @param controlProperty the property of the control (e.g. TextField.textProperty)
|
* @param controlProperty the property of the control (e.g. TextField.textProperty)
|
||||||
* @param modelProperty the property from the ViewModel
|
* @param modelProperty the property from the ViewModel
|
||||||
* @param direction the direction of the binding
|
* @param direction the direction of the binding
|
||||||
* @param <V> the value type of the property
|
* @param <V> the value type of the property
|
||||||
*/
|
*/
|
||||||
private <V> void bind(Property<V> controlProperty, Property<V> modelProperty, BindDirection direction) {
|
private <V> void bind(Property<V> controlProperty, Property<V> modelProperty, BindDirection direction) {
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
|
|||||||
@ -12,18 +12,21 @@ import java.util.ResourceBundle;
|
|||||||
* @param <T> the type of the model
|
* @param <T> the type of the model
|
||||||
*/
|
*/
|
||||||
public class GenericViewController<T> implements Initializable {
|
public class GenericViewController<T> implements Initializable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The GenericViewModel instance wrapping the underlying model.
|
* The GenericViewModel instance wrapping the underlying model.
|
||||||
*/
|
*/
|
||||||
private GenericViewModel<T> viewModel;
|
private GenericViewModel<T> viewModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the model for this controller by wrapping it in a GenericViewModel.
|
* Creates a new controller instance in an uninitialized state.
|
||||||
*
|
*
|
||||||
* @param model the model to be used by the ViewModel
|
* <p>The constructor does not set a model. Call {@link #setModel(Object)} to
|
||||||
|
* attach a model instance before performing view bindings or accessing the view model via
|
||||||
|
* {@link #getViewModel()}.</p>
|
||||||
*/
|
*/
|
||||||
public void setModel(T model) {
|
public GenericViewController() {
|
||||||
this.viewModel = new GenericViewModel<>(model);
|
// default constructor only ...
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,10 +38,19 @@ public class GenericViewController<T> implements Initializable {
|
|||||||
return viewModel.getModel();
|
return viewModel.getModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the model for this controller by wrapping it in a GenericViewModel.
|
||||||
|
*
|
||||||
|
* @param model the model to be used by the ViewModel
|
||||||
|
*/
|
||||||
|
public void setModel(T model) {
|
||||||
|
this.viewModel = new GenericViewModel<>(model);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standard JavaFX initialize method. No-op, as actual binding is done externally via FXMLLoader extension.
|
* Standard JavaFX initialize method. No-op, as actual binding is done externally via FXMLLoader extension.
|
||||||
*
|
*
|
||||||
* @param location the location used to resolve relative paths for the root object, or null if unknown
|
* @param location the location used to resolve relative paths for the root object, or null if unknown
|
||||||
* @param resources the resources used to localize the root object, or null if not localized
|
* @param resources the resources used to localize the root object, or null if not localized
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
package de.neitzel.fx.mvvm;
|
package de.neitzel.fx.mvvm;
|
||||||
|
|
||||||
import javafx.beans.property.*;
|
import javafx.beans.property.Property;
|
||||||
|
import javafx.beans.property.SimpleBooleanProperty;
|
||||||
|
import javafx.beans.property.SimpleDoubleProperty;
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty;
|
||||||
|
import javafx.beans.property.SimpleLongProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
|
||||||
import java.beans.BeanInfo;
|
import java.beans.BeanInfo;
|
||||||
import java.beans.IntrospectionException;
|
import java.beans.IntrospectionException;
|
||||||
@ -80,7 +86,7 @@ public class GenericViewModel<T> {
|
|||||||
/**
|
/**
|
||||||
* Creates an appropriate JavaFX Property instance for the given type and initial value.
|
* Creates an appropriate JavaFX Property instance for the given type and initial value.
|
||||||
*
|
*
|
||||||
* @param type the type of the property (e.g., String.class, int.class)
|
* @param type the type of the property (e.g., String.class, int.class)
|
||||||
* @param initialValue the initial value of the property
|
* @param initialValue the initial value of the property
|
||||||
* @return a new JavaFX Property instance
|
* @return a new JavaFX Property instance
|
||||||
*/
|
*/
|
||||||
@ -98,8 +104,8 @@ public class GenericViewModel<T> {
|
|||||||
* Retrieves the JavaFX property associated with the given name and type.
|
* Retrieves the JavaFX property associated with the given name and type.
|
||||||
*
|
*
|
||||||
* @param propertyClass the expected property type (e.g., StringProperty.class)
|
* @param propertyClass the expected property type (e.g., StringProperty.class)
|
||||||
* @param name the name of the model field
|
* @param name the name of the model field
|
||||||
* @param <P> the type of the Property
|
* @param <P> the type of the Property
|
||||||
* @return the corresponding JavaFX property
|
* @return the corresponding JavaFX property
|
||||||
* @throws IllegalArgumentException if the property doesn't exist or the type mismatches
|
* @throws IllegalArgumentException if the property doesn't exist or the type mismatches
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import java.util.Set;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom {@link TypeAdapter} implementation for selectively excluding specified fields during JSON serialization.
|
* A custom {@link TypeAdapter} implementation for selectively excluding specified fields during JSON serialization.
|
||||||
*
|
* <p>
|
||||||
* This adapter facilitates both serialization and deserialization of objects while providing functionality to remove specified
|
* This adapter facilitates both serialization and deserialization of objects while providing functionality to remove specified
|
||||||
* fields from the serialized output. The set of excluded field names is provided during the adapter's initialization.
|
* fields from the serialized output. The set of excluded field names is provided during the adapter's initialization.
|
||||||
*
|
*
|
||||||
@ -22,115 +22,116 @@ import java.util.Set;
|
|||||||
*/
|
*/
|
||||||
public class ExcludeFieldsAdapter<T> extends TypeAdapter<T> {
|
public class ExcludeFieldsAdapter<T> extends TypeAdapter<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of objects that this adapter handles for serialization and deserialization.
|
* The type of objects that this adapter handles for serialization and deserialization.
|
||||||
*
|
* <p>
|
||||||
* This is a generic class type parameter representing the class of objects that the
|
* This is a generic class type parameter representing the class of objects that the
|
||||||
* {@link ExcludeFieldsAdapter} instance is designed to process. It ensures type
|
* {@link ExcludeFieldsAdapter} instance is designed to process. It ensures type
|
||||||
* safety and guarantees the compatibility of the adapter with a specific object type.
|
* safety and guarantees the compatibility of the adapter with a specific object type.
|
||||||
*/
|
*/
|
||||||
private final Class<T> type;
|
private final Class<T> type;
|
||||||
/**
|
|
||||||
* A set containing the names of fields to be excluded during JSON serialization.
|
|
||||||
*
|
|
||||||
* This collection is used in the {@link ExcludeFieldsAdapter} to determine which fields should be removed from the
|
|
||||||
* serialized JSON representation of an object. The fields to be excluded are specified during the initialization
|
|
||||||
* of the adapter.
|
|
||||||
*/
|
|
||||||
private final Set<String> excludedFields;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An instance of the Gson library used for JSON serialization and deserialization.
|
* A set containing the names of fields to be excluded during JSON serialization.
|
||||||
*
|
* <p>
|
||||||
* This variable holds the Gson object that is utilized to handle the conversion processes within the
|
* This collection is used in the {@link ExcludeFieldsAdapter} to determine which fields should be removed from the
|
||||||
* custom serialization and deserialization logic. If not explicitly provided during initialization,
|
* serialized JSON representation of an object. The fields to be excluded are specified during the initialization
|
||||||
* it can be lazily initialized using a GsonBuilder.
|
* of the adapter.
|
||||||
*/
|
*/
|
||||||
private Gson gson;
|
private final Set<String> excludedFields;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link GsonBuilder} instance used for configuring and creating {@link Gson} objects within the adapter.
|
* An instance of the Gson library used for JSON serialization and deserialization.
|
||||||
*
|
* <p>
|
||||||
* This variable serves as a reference to a GsonBuilder that allows customization of the Gson instance,
|
* This variable holds the Gson object that is utilized to handle the conversion processes within the
|
||||||
* including registering custom type adapters, serializers, and deserializers, as well as adjusting serialization policies.
|
* custom serialization and deserialization logic. If not explicitly provided during initialization,
|
||||||
*
|
* it can be lazily initialized using a GsonBuilder.
|
||||||
* The {@link #gsonBuilder} is primarily utilized when an existing {@link Gson} instance is not directly provided.
|
*/
|
||||||
* It ensures that the adapter can defer the creation of a Gson object until it is explicitly required.
|
private Gson gson;
|
||||||
*/
|
|
||||||
private GsonBuilder gsonBuilder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of {@code ExcludeFieldsAdapter} for managing the serialization and deserialization
|
* A {@link GsonBuilder} instance used for configuring and creating {@link Gson} objects within the adapter.
|
||||||
* of objects of the specified {@code type}, while excluding certain fields during serialization.
|
* <p>
|
||||||
*
|
* This variable serves as a reference to a GsonBuilder that allows customization of the Gson instance,
|
||||||
* @param type the class type of the objects to be serialized and deserialized
|
* including registering custom type adapters, serializers, and deserializers, as well as adjusting serialization policies.
|
||||||
* @param excludedFields the set of field names to be excluded during serialization
|
* <p>
|
||||||
* @param gson the Gson instance to be used for serialization and deserialization
|
* The {@link #gsonBuilder} is primarily utilized when an existing {@link Gson} instance is not directly provided.
|
||||||
*/
|
* It ensures that the adapter can defer the creation of a Gson object until it is explicitly required.
|
||||||
public ExcludeFieldsAdapter(Class<T> type, Set<String> excludedFields, Gson gson) {
|
*/
|
||||||
this.type = type;
|
private GsonBuilder gsonBuilder;
|
||||||
this.excludedFields = new HashSet<>(excludedFields);
|
|
||||||
this.gsonBuilder = null;
|
|
||||||
this.gson = gson;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance of {@code ExcludeFieldsAdapter} that selectively excludes specified fields from
|
* Creates a new instance of {@code ExcludeFieldsAdapter} for managing the serialization and deserialization
|
||||||
* the JSON output based on the configuration provided during initialization.
|
* of objects of the specified {@code type}, while excluding certain fields during serialization.
|
||||||
*
|
*
|
||||||
* @param type the class of the type to be serialized and deserialized
|
* @param type the class type of the objects to be serialized and deserialized
|
||||||
* @param excludedFields a set of field names to be excluded from the serialized JSON output
|
* @param excludedFields the set of field names to be excluded during serialization
|
||||||
* @param gsonBuilder an instance of {@code GsonBuilder} used to create a custom {@code Gson} instance if needed
|
* @param gson the Gson instance to be used for serialization and deserialization
|
||||||
*/
|
*/
|
||||||
public ExcludeFieldsAdapter(Class<T> type, Set<String> excludedFields, GsonBuilder gsonBuilder) {
|
public ExcludeFieldsAdapter(Class<T> type, Set<String> excludedFields, Gson gson) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.excludedFields = new HashSet<>(excludedFields);
|
this.excludedFields = new HashSet<>(excludedFields);
|
||||||
this.gsonBuilder = gsonBuilder;
|
this.gsonBuilder = null;
|
||||||
this.gson = null;
|
this.gson = gson;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lazily initializes and retrieves the {@link Gson} instance. If the instance is null, it creates a new {@link Gson}
|
|
||||||
* object using the {@link GsonBuilder} provided during the adapter's initialization.
|
|
||||||
*
|
|
||||||
* @return the {@link Gson} instance used for serialization and deserialization
|
|
||||||
*/
|
|
||||||
private Gson getGson() {
|
|
||||||
if (gson == null) {
|
|
||||||
gson = gsonBuilder.create();
|
|
||||||
}
|
|
||||||
return gson;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serializes an object of type {@code T} into JSON format, excluding certain fields specified during the initialization of this adapter.
|
|
||||||
* The object is first converted into a {@link JsonObject}, then the fields listed in the excluded fields set are removed from the JSON
|
|
||||||
* representation before writing it to the provided {@link JsonWriter}.
|
|
||||||
*
|
|
||||||
* @param out the {@link JsonWriter} to write the serialized JSON data to
|
|
||||||
* @param value the object of type {@code T} to serialize into JSON
|
|
||||||
* @throws IOException if an I/O error occurs during writing
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void write(JsonWriter out, T value) throws IOException {
|
|
||||||
JsonObject obj = getGson().toJsonTree(value).getAsJsonObject();
|
|
||||||
|
|
||||||
for (String field : excludedFields) {
|
|
||||||
obj.remove(field);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Streams.write(obj, out);
|
/**
|
||||||
}
|
* Constructs an instance of {@code ExcludeFieldsAdapter} that selectively excludes specified fields from
|
||||||
|
* the JSON output based on the configuration provided during initialization.
|
||||||
|
*
|
||||||
|
* @param type the class of the type to be serialized and deserialized
|
||||||
|
* @param excludedFields a set of field names to be excluded from the serialized JSON output
|
||||||
|
* @param gsonBuilder an instance of {@code GsonBuilder} used to create a custom {@code Gson} instance if needed
|
||||||
|
*/
|
||||||
|
public ExcludeFieldsAdapter(Class<T> type, Set<String> excludedFields, GsonBuilder gsonBuilder) {
|
||||||
|
this.type = type;
|
||||||
|
this.excludedFields = new HashSet<>(excludedFields);
|
||||||
|
this.gsonBuilder = gsonBuilder;
|
||||||
|
this.gson = null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a JSON input stream and deserializes it into an object of the specified type.
|
* Serializes an object of type {@code T} into JSON format, excluding certain fields specified during the initialization of this adapter.
|
||||||
*
|
* The object is first converted into a {@link JsonObject}, then the fields listed in the excluded fields set are removed from the JSON
|
||||||
* @param in the {@link JsonReader} to read the JSON input from
|
* representation before writing it to the provided {@link JsonWriter}.
|
||||||
* @return an instance of type {@code T} deserialized from the JSON input
|
*
|
||||||
* @throws IOException if an error occurs during reading or deserialization
|
* @param out the {@link JsonWriter} to write the serialized JSON data to
|
||||||
*/
|
* @param value the object of type {@code T} to serialize into JSON
|
||||||
@Override
|
* @throws IOException if an I/O error occurs during writing
|
||||||
public T read(JsonReader in) throws IOException {
|
*/
|
||||||
return getGson().fromJson(in, type);
|
@Override
|
||||||
}
|
public void write(JsonWriter out, T value) throws IOException {
|
||||||
|
JsonObject obj = getGson().toJsonTree(value).getAsJsonObject();
|
||||||
|
|
||||||
|
for (String field : excludedFields) {
|
||||||
|
obj.remove(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
Streams.write(obj, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazily initializes and retrieves the {@link Gson} instance. If the instance is null, it creates a new {@link Gson}
|
||||||
|
* object using the {@link GsonBuilder} provided during the adapter's initialization.
|
||||||
|
*
|
||||||
|
* @return the {@link Gson} instance used for serialization and deserialization
|
||||||
|
*/
|
||||||
|
private Gson getGson() {
|
||||||
|
if (gson == null) {
|
||||||
|
gson = gsonBuilder.create();
|
||||||
|
}
|
||||||
|
return gson;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a JSON input stream and deserializes it into an object of the specified type.
|
||||||
|
*
|
||||||
|
* @param in the {@link JsonReader} to read the JSON input from
|
||||||
|
* @return an instance of type {@code T} deserialized from the JSON input
|
||||||
|
* @throws IOException if an error occurs during reading or deserialization
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public T read(JsonReader in) throws IOException {
|
||||||
|
return getGson().fromJson(in, type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
package de.neitzel.gson.adapter;
|
package de.neitzel.gson.adapter;
|
||||||
|
|
||||||
import com.google.gson.*;
|
import com.google.gson.JsonDeserializationContext;
|
||||||
|
import com.google.gson.JsonDeserializer;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
import com.google.gson.JsonSerializationContext;
|
||||||
|
import com.google.gson.JsonSerializer;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@ -18,35 +24,35 @@ import java.time.format.DateTimeParseException;
|
|||||||
*/
|
*/
|
||||||
public class InstantTypeAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant> {
|
public class InstantTypeAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes an {@link Instant} object into its ISO-8601 string representation as a {@link JsonElement}.
|
* Serializes an {@link Instant} object into its ISO-8601 string representation as a {@link JsonElement}.
|
||||||
*
|
*
|
||||||
* @param src the {@link Instant} object to be serialized
|
* @param src the {@link Instant} object to be serialized
|
||||||
* @param typeOfSrc the specific genericized type of {@code src} (typically ignored in this implementation)
|
* @param typeOfSrc the specific genericized type of {@code src} (typically ignored in this implementation)
|
||||||
* @param context the context of the serialization process to provide custom serialization logic
|
* @param context the context of the serialization process to provide custom serialization logic
|
||||||
* @return a {@link JsonPrimitive} containing the ISO-8601 formatted string representation of the {@link Instant}
|
* @return a {@link JsonPrimitive} containing the ISO-8601 formatted string representation of the {@link Instant}
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) {
|
public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
return new JsonPrimitive(src.toString()); // ISO-8601: "2025-04-02T10:15:30Z"
|
return new JsonPrimitive(src.toString()); // ISO-8601: "2025-04-02T10:15:30Z"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deserializes a JSON element into an {@link Instant} object.
|
* Deserializes a JSON element into an {@link Instant} object.
|
||||||
*
|
*
|
||||||
* @param json the JSON data being deserialized
|
* @param json the JSON data being deserialized
|
||||||
* @param typeOfT the specific genericized type of the object to deserialize
|
* @param typeOfT the specific genericized type of the object to deserialize
|
||||||
* @param context the deserialization context
|
* @param context the deserialization context
|
||||||
* @return the deserialized {@link Instant} object
|
* @return the deserialized {@link Instant} object
|
||||||
* @throws JsonParseException if the JSON element does not represent a valid ISO-8601 instant format
|
* @throws JsonParseException if the JSON element does not represent a valid ISO-8601 instant format
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Instant deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
|
public Instant deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
|
||||||
throws JsonParseException {
|
throws JsonParseException {
|
||||||
try {
|
try {
|
||||||
return Instant.parse(json.getAsString());
|
return Instant.parse(json.getAsString());
|
||||||
} catch (DateTimeParseException e) {
|
} catch (DateTimeParseException e) {
|
||||||
throw new JsonParseException("Invalid Instant format", e);
|
throw new JsonParseException("Invalid Instant format", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package de.neitzel.log4j;
|
package de.neitzel.log4j;
|
||||||
|
|
||||||
|
import org.apache.log4j.Logger;
|
||||||
import org.apache.log4j.PropertyConfigurator;
|
import org.apache.log4j.PropertyConfigurator;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -9,6 +10,7 @@ import java.net.URISyntaxException;
|
|||||||
* Utility class for managing Log4j configurations. Provides methods to set up
|
* Utility class for managing Log4j configurations. Provides methods to set up
|
||||||
* Log4j configurations from default files, resources, or command line arguments.
|
* Log4j configurations from default files, resources, or command line arguments.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class Log4jUtils {
|
public class Log4jUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,13 +26,13 @@ public class Log4jUtils {
|
|||||||
*/
|
*/
|
||||||
public static final String DEFAULT_LOG4J_RESOURCE = "/log4j.default.properties";
|
public static final String DEFAULT_LOG4J_RESOURCE = "/log4j.default.properties";
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(Log4jUtils.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the system property "log4j.configuration" is set.
|
* Default constructor only
|
||||||
*
|
|
||||||
* @return true if the "log4j.configuration" property is defined, false otherwise.
|
|
||||||
*/
|
*/
|
||||||
public static boolean isLog4jConfigFileSet() {
|
public Log4jUtils() {
|
||||||
return System.getProperty("log4j.configuration") != null;
|
// default constructor only
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,7 +40,7 @@ public class Log4jUtils {
|
|||||||
* This method leverages a default configuration file path and a default resource path
|
* This method leverages a default configuration file path and a default resource path
|
||||||
* to set up Log4j logging if a configuration file is not already specified via
|
* to set up Log4j logging if a configuration file is not already specified via
|
||||||
* a system property. If a valid configuration file or resource is found, it will be applied.
|
* a system property. If a valid configuration file or resource is found, it will be applied.
|
||||||
*
|
* <p>
|
||||||
* Delegates to the overloaded {@code setLog4jConfiguration(String log4jConfigFile, String defaultResource)}
|
* Delegates to the overloaded {@code setLog4jConfiguration(String log4jConfigFile, String defaultResource)}
|
||||||
* method using predefined defaults.
|
* method using predefined defaults.
|
||||||
*/
|
*/
|
||||||
@ -46,22 +48,6 @@ public class Log4jUtils {
|
|||||||
setLog4jConfiguration(DEFAULT_LOG4J_LOGFILE, DEFAULT_LOG4J_RESOURCE);
|
setLog4jConfiguration(DEFAULT_LOG4J_LOGFILE, DEFAULT_LOG4J_RESOURCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs the absolute path to the specified Log4j configuration file located
|
|
||||||
* in the same directory as the JAR file of the application.
|
|
||||||
*
|
|
||||||
* @param log4jConfigFile The name of the Log4j configuration file.
|
|
||||||
* @return The absolute path to the specified Log4j configuration file if the
|
|
||||||
* path is successfully constructed; otherwise, returns null in case of an error.
|
|
||||||
*/
|
|
||||||
public static String getLog4jLogfileAtJar(final String log4jConfigFile) {
|
|
||||||
try {
|
|
||||||
return new File(Log4jUtils.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent() + "/" + log4jConfigFile;
|
|
||||||
} catch (URISyntaxException ex) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the Log4j configuration using the specified configuration file or a default resource
|
* Sets the Log4j configuration using the specified configuration file or a default resource
|
||||||
* if the configuration file is not found. If a Log4j configuration is already set, the method
|
* if the configuration file is not found. If a Log4j configuration is already set, the method
|
||||||
@ -79,10 +65,35 @@ public class Log4jUtils {
|
|||||||
if (new File(log4jConfigFile).exists()) {
|
if (new File(log4jConfigFile).exists()) {
|
||||||
PropertyConfigurator.configure(log4jConfigFile);
|
PropertyConfigurator.configure(log4jConfigFile);
|
||||||
} else if (fileAtJar != null && new File(fileAtJar).exists()) {
|
} else if (fileAtJar != null && new File(fileAtJar).exists()) {
|
||||||
System.out.println("Nutze Log4J Konfiguration bei jar File: " + fileAtJar);
|
LOGGER.info("Using Log4J configuration from jar file: " + fileAtJar);
|
||||||
PropertyConfigurator.configure(fileAtJar);
|
PropertyConfigurator.configure(fileAtJar);
|
||||||
}else {
|
} else {
|
||||||
PropertyConfigurator.configure(Log4jUtils.class.getResource(defaultResource));
|
PropertyConfigurator.configure(Log4jUtils.class.getResource(defaultResource));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the system property "log4j.configuration" is set.
|
||||||
|
*
|
||||||
|
* @return true if the "log4j.configuration" property is defined, false otherwise.
|
||||||
|
*/
|
||||||
|
public static boolean isLog4jConfigFileSet() {
|
||||||
|
return System.getProperty("log4j.configuration") != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the absolute path to the specified Log4j configuration file located
|
||||||
|
* in the same directory as the JAR file of the application.
|
||||||
|
*
|
||||||
|
* @param log4jConfigFile The name of the Log4j configuration file.
|
||||||
|
* @return The absolute path to the specified Log4j configuration file if the
|
||||||
|
* path is successfully constructed; otherwise, returns null in case of an error.
|
||||||
|
*/
|
||||||
|
public static String getLog4jLogfileAtJar(final String log4jConfigFile) {
|
||||||
|
try {
|
||||||
|
return new File(Log4jUtils.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent() + "/" + log4jConfigFile;
|
||||||
|
} catch (URISyntaxException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import java.util.Objects;
|
|||||||
* This class provides methods to set or get the account details and overrides methods for string
|
* This class provides methods to set or get the account details and overrides methods for string
|
||||||
* representation, equality checks, and hashing.
|
* representation, equality checks, and hashing.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class ImapAccount {
|
public class ImapAccount {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -15,15 +16,6 @@ public class ImapAccount {
|
|||||||
* of the mail server that handles IMAP communication.
|
* of the mail server that handles IMAP communication.
|
||||||
*/
|
*/
|
||||||
protected String server;
|
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.
|
* Represents the username or identifier used to authenticate the IMAP account.
|
||||||
@ -31,66 +23,94 @@ public class ImapAccount {
|
|||||||
* to the IMAP server.
|
* to the IMAP server.
|
||||||
*/
|
*/
|
||||||
protected String user;
|
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.
|
* The password associated with the IMAP account.
|
||||||
* This is used to authenticate the user when connecting to the IMAP server.
|
* This is used to authenticate the user when connecting to the IMAP server.
|
||||||
*/
|
*/
|
||||||
protected String password;
|
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".
|
* Specifies the protocol used for the IMAP account, such as "IMAP" or "IMAPS".
|
||||||
* This field determines the communication protocol for interacting with the server.
|
* This field determines the communication protocol for interacting with the server.
|
||||||
*/
|
*/
|
||||||
protected String protocol;
|
protected String protocol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor only
|
||||||
|
*/
|
||||||
|
public ImapAccount() {
|
||||||
|
// default constructor only
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the protocol used by this IMAP account.
|
* Retrieves the protocol used by this IMAP account.
|
||||||
*
|
*
|
||||||
* @return the protocol as a String, which defines the communication method for the IMAP account.
|
* @return the protocol as a String, which defines the communication method for the IMAP account.
|
||||||
*/
|
*/
|
||||||
public String getProtocol() { return protocol; }
|
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,
|
* Sets the protocol used by the IMAP account.
|
||||||
* 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.
|
* @param protocol the protocol to be used, e.g., "IMAP" or "IMAPS".
|
||||||
*/
|
*/
|
||||||
@Override public String toString() {
|
public void setProtocol(String protocol) {
|
||||||
return String.format("ImapAccount(%s, %s, %s, %s",
|
this.protocol = protocol;
|
||||||
server,
|
|
||||||
user,
|
|
||||||
password.length()==0 ? "''" : "########" ,
|
|
||||||
protocol);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,7 +120,8 @@ public class ImapAccount {
|
|||||||
*
|
*
|
||||||
* @return An integer hash code representing this ImapAccount instance.
|
* @return An integer hash code representing this ImapAccount instance.
|
||||||
*/
|
*/
|
||||||
@Override public int hashCode() {
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
return Objects.hash(server, user, password, protocol);
|
return Objects.hash(server, user, password, protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +132,8 @@ public class ImapAccount {
|
|||||||
* @param obj the object to compare with this instance
|
* @param obj the object to compare with this instance
|
||||||
* @return true if the specified object is equal to this ImapAccount instance; false otherwise
|
* @return true if the specified object is equal to this ImapAccount instance; false otherwise
|
||||||
*/
|
*/
|
||||||
@Override public boolean equals(Object obj) {
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
// Check type of argument, includes check of null.
|
// Check type of argument, includes check of null.
|
||||||
if (!(obj instanceof ImapAccount)) return false;
|
if (!(obj instanceof ImapAccount)) return false;
|
||||||
|
|
||||||
@ -120,9 +142,24 @@ public class ImapAccount {
|
|||||||
|
|
||||||
// Check the comparison of all field
|
// Check the comparison of all field
|
||||||
ImapAccount other = (ImapAccount) obj;
|
ImapAccount other = (ImapAccount) obj;
|
||||||
return (server == other.server || (server != null && server.equals(other.server))) &&
|
return Objects.equals(server, other.server) &&
|
||||||
(user == other.user || (user != null && user.equals(other.user))) &&
|
Objects.equals(user, other.user) &&
|
||||||
(password == other.password || (password != null && password.equals(other.password))) &&
|
Objects.equals(password, other.password) &&
|
||||||
(protocol == other.protocol || (protocol != null && protocol.equals(other.protocol)));
|
Objects.equals(protocol, other.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 == null || password.isEmpty()) ? "''" : "########",
|
||||||
|
protocol);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,18 @@ package de.neitzel.net.imap;
|
|||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import javax.mail.*;
|
import javax.mail.Flags;
|
||||||
import java.util.*;
|
import javax.mail.Folder;
|
||||||
|
import javax.mail.Message;
|
||||||
|
import javax.mail.MessagingException;
|
||||||
|
import javax.mail.NoSuchProviderException;
|
||||||
|
import javax.mail.Session;
|
||||||
|
import javax.mail.Store;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ImapAccountMonitor class is responsible for monitoring an IMAP email account
|
* The ImapAccountMonitor class is responsible for monitoring an IMAP email account
|
||||||
@ -44,13 +54,20 @@ public class ImapAccountMonitor {
|
|||||||
*/
|
*/
|
||||||
protected Map<String, Folder> folders = new HashMap<String, Folder>();
|
protected Map<String, Folder> folders = new HashMap<String, Folder>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monitors an IMAP account by establishing a connection to the specified mail server
|
* Monitors an IMAP account by establishing a connection to the specified mail server
|
||||||
* using the provided account credentials and protocol.
|
* using the provided account credentials and protocol.
|
||||||
*
|
*
|
||||||
* @param account the IMAP account containing details such as server, user, password, 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 NoSuchProviderException if the specified mail store protocol is not available
|
||||||
* @throws MessagingException if the connection to the mail server fails
|
* @throws MessagingException if the connection to the mail server fails
|
||||||
*/
|
*/
|
||||||
public ImapAccountMonitor(ImapAccount account) throws NoSuchProviderException, MessagingException {
|
public ImapAccountMonitor(ImapAccount account) throws NoSuchProviderException, MessagingException {
|
||||||
log.trace("Constructor ({})", account.toString());
|
log.trace("Constructor ({})", account.toString());
|
||||||
@ -77,19 +94,19 @@ public class ImapAccountMonitor {
|
|||||||
* Closes the resources associated with the current instance, including
|
* Closes the resources associated with the current instance, including
|
||||||
* IMAP folders and the store. The method ensures that all folders are
|
* IMAP folders and the store. The method ensures that all folders are
|
||||||
* closed properly, followed by the closure of the store.
|
* closed properly, followed by the closure of the store.
|
||||||
*
|
* <p>
|
||||||
* Any exceptions encountered during the closure of folders or the store
|
* Any exceptions encountered during the closure of folders or the store
|
||||||
* are caught and logged without interrupting the overall closing process.
|
* are caught and logged without interrupting the overall closing process.
|
||||||
* This is to ensure that all resources are attempted to be closed even if
|
* This is to ensure that all resources are attempted to be closed even if
|
||||||
* an error occurs with one of them.
|
* an error occurs with one of them.
|
||||||
*
|
* <p>
|
||||||
* The method logs the start and end of the closing process for traceability.
|
* The method logs the start and end of the closing process for traceability.
|
||||||
*/
|
*/
|
||||||
public void close() {
|
public void close() {
|
||||||
log.trace("close() called.");
|
log.trace("close() called.");
|
||||||
|
|
||||||
// Close the folders
|
// Close the folders
|
||||||
for (Folder folder: folders.values()) {
|
for (Folder folder : folders.values()) {
|
||||||
try {
|
try {
|
||||||
folder.close(false);
|
folder.close(false);
|
||||||
} catch (MessagingException ex) {
|
} catch (MessagingException ex) {
|
||||||
@ -152,28 +169,28 @@ public class ImapAccountMonitor {
|
|||||||
* Checks all monitored folders for new email messages, processes unseen and undeleted messages,
|
* 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
|
* and raises a NewMailEvent for each new email discovered. The method ensures processed messages
|
||||||
* are marked as seen.
|
* are marked as seen.
|
||||||
*
|
* <p>
|
||||||
* The method iterates through all folders currently being monitored, fetching their messages and
|
* 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
|
* 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.
|
* as seen and a NewMailEvent is raised for it.
|
||||||
*
|
* <p>
|
||||||
* Proper error handling is implemented to log any MessagingException that occurs while processing
|
* 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.
|
* the messages of a folder without interrupting the processing of other folders.
|
||||||
*
|
* <p>
|
||||||
* Note: This method relies on external logging (via `log`) and appropriate event handling
|
* Note: This method relies on external logging (via `log`) and appropriate event handling
|
||||||
* via `raiseNewEmailEvent`. The `folders` collection holds the monitored folders required.
|
* via `raiseNewEmailEvent`. The `folders` collection holds the monitored folders required.
|
||||||
*
|
* <p>
|
||||||
* The folder processing stops only in case of irrecoverable exceptions outside this method's scope.
|
* The folder processing stops only in case of irrecoverable exceptions outside this method's scope.
|
||||||
*/
|
*/
|
||||||
public void check() {
|
public void check() {
|
||||||
log.trace("check() called.");
|
log.trace("check() called.");
|
||||||
|
|
||||||
// Loop through all folders that are monitored
|
// Loop through all folders that are monitored
|
||||||
for (Folder folder: folders.values()) {
|
for (Folder folder : folders.values()) {
|
||||||
try {
|
try {
|
||||||
// Loop through all messages.
|
// Loop through all messages.
|
||||||
Message[] messages = folder.getMessages();
|
Message[] messages = folder.getMessages();
|
||||||
for (Message message: messages) {
|
for (Message message : messages) {
|
||||||
// Check if message wasn't seen and wasn't deleted
|
// Check if message wasn't seen and wasn't deleted
|
||||||
if (!message.isSet(Flags.Flag.SEEN) && !message.isSet(Flags.Flag.DELETED)) {
|
if (!message.isSet(Flags.Flag.SEEN) && !message.isSet(Flags.Flag.DELETED)) {
|
||||||
// Mark message as seen.
|
// Mark message as seen.
|
||||||
@ -193,11 +210,35 @@ public class ImapAccountMonitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A collection of listeners that respond to IMAP-related events.
|
* Raises a new email event and notifies all registered listeners.
|
||||||
* Each listener in this collection can perform specific actions when triggered
|
* The method loops through the listeners and invokes their {@code NewEmailReceived}
|
||||||
* by events such as receiving new email notifications.
|
* 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.
|
||||||
*/
|
*/
|
||||||
private Collection<ImapListener> imapListeners = new ArrayList<ImapListener>();
|
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!");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an IMAP listener to receive notifications about IMAP-related events such as new email arrivals.
|
* Adds an IMAP listener to receive notifications about IMAP-related events such as new email arrivals.
|
||||||
@ -222,35 +263,4 @@ public class ImapAccountMonitor {
|
|||||||
imapListeners.remove(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!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,8 +37,8 @@ public class NewEmailEvent extends EventObject {
|
|||||||
* This event encapsulates information about a newly received email,
|
* This event encapsulates information about a newly received email,
|
||||||
* including its associated message and the source of the event.
|
* including its associated message and the source of the event.
|
||||||
*
|
*
|
||||||
* @param source the object on which the event initially occurred; typically represents
|
* @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.
|
* 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.
|
* @param message the Message object representing the email associated with this event.
|
||||||
*/
|
*/
|
||||||
public NewEmailEvent(Object source, Message message) {
|
public NewEmailEvent(Object source, Message message) {
|
||||||
|
|||||||
@ -8,15 +8,49 @@ import javax.mail.internet.MimeMultipart;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility functions to use with javax.mail.Message instances.
|
* Utility methods for extracting textual content from {@link javax.mail.Message} instances.
|
||||||
|
*
|
||||||
|
* <p>This class provides helpers to obtain a best-effort plain-text representation of an email's
|
||||||
|
* content. It supports the common MIME types:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@code text/plain} — returned as-is</li>
|
||||||
|
* <li>{@code text/html} — the HTML body is returned as a string (not converted to plain text)</li>
|
||||||
|
* <li>{@code multipart/*} — parts are inspected recursively; for {@code multipart/alternative}
|
||||||
|
* the last (most faithful) alternative is preferred; for other multipart types the textual
|
||||||
|
* content of all parts is concatenated in order.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>All methods return an empty string when no textual content can be found. The methods may throw
|
||||||
|
* {@link IOException} or {@link MessagingException} when the underlying message parts cannot be read.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class MessageUtils {
|
public class MessageUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the text of the email message.
|
* Private constructor for utility class.
|
||||||
* @param message Message to get the text from.
|
* Prevents instantiation of this utility class.
|
||||||
* @return Text of the email message.
|
*/
|
||||||
* @throws IOException
|
private MessageUtils() {
|
||||||
* @throws MessagingException
|
// Utility class - private constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts a best-effort textual representation from the provided {@link Message}.
|
||||||
|
*
|
||||||
|
* <p>Behavior:
|
||||||
|
* <ul>
|
||||||
|
* <li>If the message mime-type is {@code text/plain} the plain text content is returned.</li>
|
||||||
|
* <li>If the message mime-type is {@code text/html} the HTML content is returned as a string.
|
||||||
|
* (This method does not perform HTML-to-plain-text conversion.)</li>
|
||||||
|
* <li>If the message mime-type is {@code multipart/*} the method inspects all parts recursively.
|
||||||
|
* For {@code multipart/alternative} the most faithful alternative (the last part) is chosen.
|
||||||
|
* For other multipart types the textual contents of all parts are concatenated in order.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param message the email message from which to extract text; must not be {@code null}
|
||||||
|
* @return a <strong>non-null</strong> string containing the extracted text (possibly empty)
|
||||||
|
* @throws IOException if an I/O error occurs while reading message content
|
||||||
|
* @throws MessagingException if the message content cannot be processed due to MIME/format errors
|
||||||
*/
|
*/
|
||||||
public static String getTextFromMessage(Message message) throws IOException, MessagingException {
|
public static String getTextFromMessage(Message message) throws IOException, MessagingException {
|
||||||
String result = "";
|
String result = "";
|
||||||
@ -30,11 +64,23 @@ public class MessageUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the text of a MimeMultipart.
|
* Extracts the textual content from a {@link MimeMultipart} instance.
|
||||||
* @param mimeMultipart MimeMultipart to get the text from.
|
*
|
||||||
* @return Text of the MimeMultipart instance.
|
* <p>Algorithm:
|
||||||
* @throws IOException
|
* <ul>
|
||||||
* @throws MessagingException
|
* <li>If the multipart has zero parts a {@link MessagingException} is thrown.</li>
|
||||||
|
* <li>If the multipart's content type matches {@code multipart/alternative} the last part
|
||||||
|
* (the most faithful alternative) is returned.</li>
|
||||||
|
* <li>Otherwise the method concatenates the result of {@link #getTextFromBodyPart(BodyPart)}
|
||||||
|
* for each part in document order and returns the combined text.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>The method handles nested multiparts by recursive calls.
|
||||||
|
*
|
||||||
|
* @param mimeMultipart the multipart object to inspect
|
||||||
|
* @return a non-null string containing the concatenated textual content of the multipart
|
||||||
|
* @throws IOException if an I/O error occurs while reading parts
|
||||||
|
* @throws MessagingException if the multipart is malformed or cannot be processed
|
||||||
*/
|
*/
|
||||||
private static String getTextFromMimeMultipart(
|
private static String getTextFromMimeMultipart(
|
||||||
MimeMultipart mimeMultipart) throws IOException, MessagingException {
|
MimeMultipart mimeMultipart) throws IOException, MessagingException {
|
||||||
@ -47,20 +93,29 @@ public class MessageUtils {
|
|||||||
// alternatives appear in an order of increasing
|
// alternatives appear in an order of increasing
|
||||||
// faithfulness to the original content. Customize as req'd.
|
// faithfulness to the original content. Customize as req'd.
|
||||||
return getTextFromBodyPart(mimeMultipart.getBodyPart(count - 1));
|
return getTextFromBodyPart(mimeMultipart.getBodyPart(count - 1));
|
||||||
String result = "";
|
StringBuilder result = new StringBuilder();
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
BodyPart bodyPart = mimeMultipart.getBodyPart(i);
|
BodyPart bodyPart = mimeMultipart.getBodyPart(i);
|
||||||
result += getTextFromBodyPart(bodyPart);
|
result.append(getTextFromBodyPart(bodyPart));
|
||||||
}
|
}
|
||||||
return result;
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the text of a BodyPart
|
* Extracts textual content from a single {@link BodyPart}.
|
||||||
* @param bodyPart BodyPart to get text from.
|
*
|
||||||
* @return Text of body part or empty string if not a known type.
|
* <p>Behavior:
|
||||||
* @throws IOException
|
* <ul>
|
||||||
* @throws MessagingException
|
* <li>{@code text/plain}: returns the plain text content.</li>
|
||||||
|
* <li>{@code text/html}: returns the HTML content as a string (no conversion performed).</li>
|
||||||
|
* <li>If the body part itself contains a {@link MimeMultipart} this method recurses into it.</li>
|
||||||
|
* <li>For unknown or non-textual content types an empty string is returned.</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @param bodyPart the body part to extract text from
|
||||||
|
* @return the textual content of the body part, or an empty string if none is available
|
||||||
|
* @throws IOException if an I/O error occurs while reading the body part content
|
||||||
|
* @throws MessagingException if the body part cannot be processed
|
||||||
*/
|
*/
|
||||||
private static String getTextFromBodyPart(
|
private static String getTextFromBodyPart(
|
||||||
BodyPart bodyPart) throws IOException, MessagingException {
|
BodyPart bodyPart) throws IOException, MessagingException {
|
||||||
@ -73,8 +128,8 @@ public class MessageUtils {
|
|||||||
//String html = (String) bodyPart.getContent();
|
//String html = (String) bodyPart.getContent();
|
||||||
// Not parsing the html right now!
|
// Not parsing the html right now!
|
||||||
// result = org.jsoup.Jsoup.parse(html).text();
|
// result = org.jsoup.Jsoup.parse(html).text();
|
||||||
} else if (bodyPart.getContent() instanceof MimeMultipart){
|
} else if (bodyPart.getContent() instanceof MimeMultipart) {
|
||||||
result = getTextFromMimeMultipart((MimeMultipart)bodyPart.getContent());
|
result = getTextFromMimeMultipart((MimeMultipart) bodyPart.getContent());
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<ruleset name="Custom Java Ruleset"
|
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
name="Custom Java Ruleset"
|
||||||
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
|
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
|
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
|
||||||
|
|
||||||
<description>
|
<description>
|
||||||
@ -11,27 +11,27 @@
|
|||||||
|
|
||||||
<rule ref="category/java/bestpractices.xml">
|
<rule ref="category/java/bestpractices.xml">
|
||||||
<!-- System.println is ok in simple Console App -->
|
<!-- System.println is ok in simple Console App -->
|
||||||
<exclude name="SystemPrintln" />
|
<exclude name="SystemPrintln"/>
|
||||||
</rule>
|
</rule>
|
||||||
|
|
||||||
<rule ref="category/java/codestyle.xml" />
|
<rule ref="category/java/codestyle.xml"/>
|
||||||
|
|
||||||
<rule ref="category/java/design.xml" />
|
<rule ref="category/java/design.xml"/>
|
||||||
<rule ref="category/java/design.xml/LoosePackageCoupling">
|
<rule ref="category/java/design.xml/LoosePackageCoupling">
|
||||||
<properties>
|
<properties>
|
||||||
<property name="packages" value="de.kneitzel" />
|
<property name="packages" value="de.kneitzel"/>
|
||||||
<!-- <property name="classes" value="" /> -->
|
<!-- <property name="classes" value="" /> -->
|
||||||
</properties>
|
</properties>
|
||||||
</rule>
|
</rule>
|
||||||
|
|
||||||
<rule ref="category/java/documentation.xml" />
|
<rule ref="category/java/documentation.xml"/>
|
||||||
|
|
||||||
<rule ref="category/java/errorprone.xml" />
|
<rule ref="category/java/errorprone.xml"/>
|
||||||
|
|
||||||
<rule ref="category/java/multithreading.xml" />
|
<rule ref="category/java/multithreading.xml"/>
|
||||||
|
|
||||||
<rule ref="category/java/performance.xml" />
|
<rule ref="category/java/performance.xml"/>
|
||||||
|
|
||||||
<rule ref="category/java/security.xml" />
|
<rule ref="category/java/security.xml"/>
|
||||||
|
|
||||||
</ruleset>
|
</ruleset>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user