Compare commits
10 Commits
7d16122e9e
...
edf02e9816
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edf02e9816 | ||
|
|
3178dc2723 | ||
|
|
5503dd192e | ||
|
|
9bbddaaae9 | ||
|
|
9c55794be8 | ||
|
|
93de125e67 | ||
|
|
6c1338d171 | ||
|
|
5aa75be6ba | ||
|
|
2b60038774 | ||
|
|
be6a8c853e |
15
.github/JavaDoc-rules.md
vendored
Normal file
15
.github/JavaDoc-rules.md
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
JavaDoc should be included for:
|
||||||
|
[]: # - All public, protected and private classes and interfaces
|
||||||
|
[]: # - All public, protected and private methods and constructors
|
||||||
|
[]: # - Any method that overrides a superclass method
|
||||||
|
[]: # 3. If JavaDoc is missing, please add it following these guidelines:
|
||||||
|
[]: # - Use clear and concise language to describe the purpose and functionality.
|
||||||
|
[]: # - Include descriptions for all parameters using @param tags.
|
||||||
|
[]: # - Include a description of the return value using @return tags.
|
||||||
|
[]: # - Mention any exceptions that the method might throw using @throws tags.
|
||||||
|
[]: # - Follow standard JavaDoc formatting conventions.
|
||||||
|
[]: # 4. After adding JavaDoc, review it for clarity and completeness.
|
||||||
|
[]: # 5. Commit your changes with a message indicating that JavaDoc has been added or updated.
|
||||||
|
[]: #
|
||||||
|
[]: # By following these instructions, we can ensure that our codebase remains well-documented and easy to understand
|
||||||
|
for all contributors.
|
||||||
9
.github/copilot-instructions.md
vendored
Normal file
9
.github/copilot-instructions.md
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
When working on a prompt, please follow these instructions:
|
||||||
|
|
||||||
|
1. When creating Code then just create the code required to complete the task. Do not include any explanations,
|
||||||
|
comments, or other text.
|
||||||
|
2. After code changes, check if any JavaDoc is missing. Please see JavaDoc-rules.md for guidelines on adding JavaDoc.
|
||||||
|
3. Create Unit Tests for the code you created or modified. Ensure that the tests cover various scenarios and edge cases.
|
||||||
|
4. When writing commit messages, please follow the guidelines in git-commit-instructions.md to ensure clarity and
|
||||||
|
consistency.
|
||||||
|
5. Ensure that your code adheres to the project's coding standards and best practices.
|
||||||
9
.github/git-commit-instructions.md
vendored
Normal file
9
.github/git-commit-instructions.md
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Git Commit Message Guidelines
|
||||||
|
|
||||||
|
When creating a commit message, please follow these guidelines to ensure clarity and consistency across the project:
|
||||||
|
|
||||||
|
1. **Structure of Commit Message**:
|
||||||
|
- The commit message should consist of a header, a body (optional), and a footer (optional).
|
||||||
|
- The header should be a single line that summarizes the changes made.
|
||||||
|
- If necessary, the body can provide additional context or details about the changes.
|
||||||
|
- The footer can include references to issues or breaking changes.
|
||||||
1
.github/prompts/TEST.prompt.md
vendored
Normal file
1
.github/prompts/TEST.prompt.md
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
Please just respond with "Hello, World!"
|
||||||
2
.mvn/wrapper/maven-wrapper.properties
vendored
2
.mvn/wrapper/maven-wrapper.properties
vendored
@ -14,5 +14,5 @@
|
|||||||
# KIND, either express or implied. See the License for the
|
# KIND, either express or implied. See the License for the
|
||||||
# specific language governing permissions and limitations
|
# specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip
|
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip
|
||||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
|
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
|
||||||
|
|||||||
29
README.md
29
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
|
||||||
|
|
||||||
@ -41,10 +49,19 @@ Some JavaFX additions.
|
|||||||
|
|
||||||
Some example programs that use the fx component
|
Some example programs that use the fx component
|
||||||
|
|
||||||
|
### gson
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
Some network stuff using javax.mail as dependency.
|
Some network stuff using javax.mail as dependency.
|
||||||
|
|
||||||
|
### quarkus
|
||||||
|
|
||||||
|
Some additions for quarkus that might be useful.
|
||||||
@ -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.
|
||||||
@ -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,11 +19,11 @@ 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
|
||||||
@ -29,18 +33,18 @@ public class Parser {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
package de.neitzel.core.config;
|
package de.neitzel.core.config;
|
||||||
|
|
||||||
import de.neitzel.core.util.FileUtils;
|
import de.neitzel.core.io.FileUtils;
|
||||||
import de.neitzel.core.util.Strings;
|
import de.neitzel.core.util.Strings;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ public class Configuration {
|
|||||||
* A {@link Properties} object that stores a set of key-value pairs.
|
* A {@link Properties} object that stores a set of key-value pairs.
|
||||||
* This variable can be used to manage configuration settings or other
|
* This variable can be used to manage configuration settings or other
|
||||||
* collections of properties within the application.
|
* collections of properties within the application.
|
||||||
*
|
* <p>
|
||||||
* It provides methods to load, retrieve, and modify properties
|
* It provides methods to load, retrieve, and modify properties
|
||||||
* as necessary for the application's requirements.
|
* as necessary for the application's requirements.
|
||||||
*/
|
*/
|
||||||
@ -62,7 +62,7 @@ public class Configuration {
|
|||||||
*/
|
*/
|
||||||
protected boolean getBooleanProperty(final String key, final boolean defaultValue) {
|
protected boolean getBooleanProperty(final String key, final boolean defaultValue) {
|
||||||
if (!properties.containsKey(key)) return defaultValue;
|
if (!properties.containsKey(key)) return defaultValue;
|
||||||
return getStringProperty(key, defaultValue ? "ja": "nein").equalsIgnoreCase("ja") || properties.getProperty(key).equalsIgnoreCase("true");
|
return getStringProperty(key, defaultValue ? "ja" : "nein").equalsIgnoreCase("ja") || properties.getProperty(key).equalsIgnoreCase("true");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,18 +92,6 @@ public class Configuration {
|
|||||||
return Strings.expandEnvironmentVariables(result);
|
return Strings.expandEnvironmentVariables(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the value of a string property associated with the specified key,
|
|
||||||
* removes any surrounding quotes from the value, and returns the resultant string.
|
|
||||||
*
|
|
||||||
* @param key the key associated with the desired property
|
|
||||||
* @param defaultValue the default value to return if the property is not found or is null
|
|
||||||
* @return the string property without surrounding quotes, or the defaultValue if the property is not found
|
|
||||||
*/
|
|
||||||
protected String getStringPropertyWithoutQuotes(final String key, final String defaultValue) {
|
|
||||||
return Strings.removeQuotes(getStringProperty(key, defaultValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the string value of a configuration property identified by the given key,
|
* Retrieves the string value of a configuration property identified by the given key,
|
||||||
* removes surrounding quotes if present, and expands any environment variables found
|
* removes surrounding quotes if present, and expands any environment variables found
|
||||||
@ -118,6 +106,18 @@ public class Configuration {
|
|||||||
return Strings.expandEnvironmentVariables(result);
|
return Strings.expandEnvironmentVariables(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the value of a string property associated with the specified key,
|
||||||
|
* removes any surrounding quotes from the value, and returns the resultant string.
|
||||||
|
*
|
||||||
|
* @param key the key associated with the desired property
|
||||||
|
* @param defaultValue the default value to return if the property is not found or is null
|
||||||
|
* @return the string property without surrounding quotes, or the defaultValue if the property is not found
|
||||||
|
*/
|
||||||
|
protected String getStringPropertyWithoutQuotes(final String key, final String defaultValue) {
|
||||||
|
return Strings.removeQuotes(getStringProperty(key, defaultValue));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the integer value for the specified property key. If the key does
|
* Retrieves the integer value for the specified property key. If the key does
|
||||||
* not exist in the properties or the value is null/empty, the provided default
|
* not exist in the properties or the value is null/empty, the provided default
|
||||||
@ -146,7 +146,7 @@ public class Configuration {
|
|||||||
if (value == null) {
|
if (value == null) {
|
||||||
properties.setProperty(key, "");
|
properties.setProperty(key, "");
|
||||||
} else {
|
} else {
|
||||||
properties.setProperty(key, ""+value);
|
properties.setProperty(key, "" + value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,7 +251,7 @@ public class Configuration {
|
|||||||
* @param config the Configuration object whose properties will be merged into this instance
|
* @param config the Configuration object whose properties will be merged into this instance
|
||||||
*/
|
*/
|
||||||
public void merge(final Configuration config) {
|
public void merge(final Configuration config) {
|
||||||
for(Map.Entry<Object, Object> entry: config.properties.entrySet()) {
|
for (Map.Entry<Object, Object> entry : config.properties.entrySet()) {
|
||||||
properties.put(entry.getKey(), entry.getValue());
|
properties.put(entry.getKey(), entry.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -261,7 +261,7 @@ public class Configuration {
|
|||||||
*
|
*
|
||||||
* @param key the key to be removed from the properties map
|
* @param key the key to be removed from the properties map
|
||||||
*/
|
*/
|
||||||
public void remove(final String key){
|
public void remove(final String key) {
|
||||||
if (properties.containsKey(key)) properties.remove(key);
|
if (properties.containsKey(key)) properties.remove(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,51 +37,9 @@ 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.
|
||||||
@ -91,8 +62,37 @@ 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
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
package de.neitzel.core.io;
|
package de.neitzel.core.io;
|
||||||
|
|
||||||
import de.neitzel.core.util.FileUtils;
|
|
||||||
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;
|
||||||
|
|
||||||
@ -12,7 +15,7 @@ import java.nio.charset.IllegalCharsetNameException;
|
|||||||
* for handling file encoding conversion transparently. If a file is detected to be in UTF-8 encoding,
|
* for handling file encoding conversion transparently. If a file is detected to be in UTF-8 encoding,
|
||||||
* this class converts it to the specified target encoding using a temporary file, then opens the reader
|
* this class converts it to the specified target encoding using a temporary file, then opens the reader
|
||||||
* with the converted encoding. If the file is already in the target encoding, it opens the reader directly.
|
* with the converted encoding. If the file is already in the target encoding, it opens the reader directly.
|
||||||
*
|
* <p>
|
||||||
* This class is useful for applications needing to process text files in specific encodings and ensures
|
* This class is useful for applications needing to process text files in specific encodings and ensures
|
||||||
* encoding compatibility.
|
* encoding compatibility.
|
||||||
*/
|
*/
|
||||||
@ -25,7 +28,7 @@ public class ConvertedEncodingFileReader extends InputStreamReader {
|
|||||||
* This encoding is primarily used to determine whether a file needs conversion
|
* This encoding is primarily used to determine whether a file needs conversion
|
||||||
* to the target format or can be read directly in its existing format.
|
* to the target format or can be read directly in its existing format.
|
||||||
* The default value is set to "ISO-8859-15".
|
* The default value is set to "ISO-8859-15".
|
||||||
*
|
* <p>
|
||||||
* Modifying this variable requires careful consideration, as it affects
|
* Modifying this variable requires careful consideration, as it affects
|
||||||
* the behavior of methods that rely on encoding validation, particularly
|
* the behavior of methods that rely on encoding validation, particularly
|
||||||
* in the process of detecting UTF-8 files or converting them during file reading.
|
* in the process of detecting UTF-8 files or converting them during file reading.
|
||||||
@ -33,17 +36,16 @@ public class ConvertedEncodingFileReader extends InputStreamReader {
|
|||||||
private static String checkEncoding = "ISO-8859-15";
|
private static String checkEncoding = "ISO-8859-15";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the encoding that will be used to check the file encoding for compatibility.
|
* Constructs a ConvertedEncodingFileReader for reading a file with encoding conversion support.
|
||||||
* Throws an exception if the specified encoding is not valid or supported.
|
* This constructor takes the file path as a string and ensures the file's encoding is either
|
||||||
|
* converted to the specified target format or read directly if it matches the target format.
|
||||||
*
|
*
|
||||||
* @param encoding the name of the character encoding to set as the check encoding;
|
* @param filename the path to the file to be read
|
||||||
* it must be a valid and supported Charset.
|
* @param targetFormat the target encoding format to use for reading the file
|
||||||
* @throws IllegalCharsetNameException if the specified encoding is not valid or supported.
|
* @throws IOException if an I/O error occurs while accessing or reading the specified file
|
||||||
*/
|
*/
|
||||||
private static void setCheckEncoding(final String encoding) {
|
public ConvertedEncodingFileReader(final String filename, final String targetFormat) throws IOException {
|
||||||
if (Charset.forName(encoding) != null) throw new IllegalCharsetNameException("Encoding " + encoding + " is not supported!");
|
this(new File(filename), targetFormat);
|
||||||
|
|
||||||
checkEncoding = encoding;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,19 +62,6 @@ public class ConvertedEncodingFileReader extends InputStreamReader {
|
|||||||
super(createTargetFormatInputFileStream(file, targetFormat), targetFormat);
|
super(createTargetFormatInputFileStream(file, targetFormat), targetFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a ConvertedEncodingFileReader for reading a file with encoding conversion support.
|
|
||||||
* This constructor takes the file path as a string and ensures the file's encoding is either
|
|
||||||
* converted to the specified target format or read directly if it matches the target format.
|
|
||||||
*
|
|
||||||
* @param filename the path to the file to be read
|
|
||||||
* @param targetFormat the target encoding format to use for reading the file
|
|
||||||
* @throws IOException if an I/O error occurs while accessing or reading the specified file
|
|
||||||
*/
|
|
||||||
public ConvertedEncodingFileReader(final String filename, final String targetFormat) throws IOException {
|
|
||||||
this(new File(filename), targetFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an input file stream for a given file, converting its encoding if necessary.
|
* Creates an input file stream for a given file, converting its encoding if necessary.
|
||||||
* If the file is not in UTF-8 encoding, a direct {@link FileInputStream} is returned for the file.
|
* If the file is not in UTF-8 encoding, a direct {@link FileInputStream} is returned for the file.
|
||||||
@ -100,4 +89,19 @@ public class ConvertedEncodingFileReader extends InputStreamReader {
|
|||||||
return new FileInputStream(tempFile);
|
return new FileInputStream(tempFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the encoding that will be used to check the file encoding for compatibility.
|
||||||
|
* Throws an exception if the specified encoding is not valid or supported.
|
||||||
|
*
|
||||||
|
* @param encoding the name of the character encoding to set as the check encoding;
|
||||||
|
* it must be a valid and supported Charset.
|
||||||
|
* @throws IllegalCharsetNameException if the specified encoding is not valid or supported.
|
||||||
|
*/
|
||||||
|
private static void setCheckEncoding(final String encoding) {
|
||||||
|
if (Charset.forName(encoding) != null)
|
||||||
|
throw new IllegalCharsetNameException("Encoding " + encoding + " is not supported!");
|
||||||
|
|
||||||
|
checkEncoding = encoding;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,64 @@
|
|||||||
package de.neitzel.core.util;
|
package de.neitzel.core.io;
|
||||||
|
|
||||||
|
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.Path;
|
import java.nio.file.Path;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for handling file operations, such as encoding checks, content reading/writing,
|
* A utility class for file-related operations. This class provides functionalities
|
||||||
* path manipulations, and file conversions.
|
* for handling files and directories in an efficient manner.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class FileUtils {
|
public class FileUtils {
|
||||||
|
/**
|
||||||
|
* Defines a standardized timestamp format for debugging purposes, specifically used for naming
|
||||||
|
* or identifying files with precise timestamps. The format is "yyyy-MM-dd_HH_mm_ss_SSS", which
|
||||||
|
* includes:
|
||||||
|
* - Year in four digits (yyyy)
|
||||||
|
* - Month in two digits (MM)
|
||||||
|
* - Day of the month in two digits (dd)
|
||||||
|
* - Hour in 24-hour format with two digits (HH)
|
||||||
|
* - Minutes in two digits (mm)
|
||||||
|
* - Seconds in two digits (ss)
|
||||||
|
* - Milliseconds in three digits (SSS)
|
||||||
|
* <p>
|
||||||
|
* This ensures timestamps are sortable and easily identifiable.
|
||||||
|
*/
|
||||||
|
public static final SimpleDateFormat DEBUG_FILE_TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH_mm_ss_SSS");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default encoding used for string checks and validations in the application.
|
||||||
|
* <p>
|
||||||
|
* This constant represents the `ISO-8859-15` encoding, which is a standardized
|
||||||
|
* character set encoding, commonly used in contexts where backward compatibility
|
||||||
|
* with `ISO-8859-1` is required, but with support for certain additional characters,
|
||||||
|
* such as the euro currency symbol (€).
|
||||||
|
*/
|
||||||
|
public static final String DEFAULT_CHECK_ENCODING = "ISO-8859-15";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the default buffer size used for data processing operations.
|
||||||
|
* <p>
|
||||||
|
* This constant represents the size of the buffer in bytes, set to 1024,
|
||||||
|
* and is typically utilized in input/output operations to optimize performance
|
||||||
|
* by reducing the number of read/write calls.
|
||||||
|
*/
|
||||||
|
public static final int BUFFER_SIZE = 1024;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@ -27,39 +72,15 @@ public class FileUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a standardized timestamp format for debugging purposes, specifically used for naming
|
* Determines if the content of the given file is encoded in UTF-8.
|
||||||
* or identifying files with precise timestamps. The format is "yyyy-MM-dd_HH_mm_ss_SSS", which
|
|
||||||
* includes:
|
|
||||||
* - Year in four digits (yyyy)
|
|
||||||
* - Month in two digits (MM)
|
|
||||||
* - Day of the month in two digits (dd)
|
|
||||||
* - Hour in 24-hour format with two digits (HH)
|
|
||||||
* - Minutes in two digits (mm)
|
|
||||||
* - Seconds in two digits (ss)
|
|
||||||
* - Milliseconds in three digits (SSS)
|
|
||||||
*
|
*
|
||||||
* This ensures timestamps are sortable and easily identifiable.
|
* @param file The file to check for UTF-8 encoding. Must not be null.
|
||||||
|
* @return true if the file content is in UTF-8 encoding; false otherwise.
|
||||||
|
* @throws IOException If an I/O error occurs while reading the file.
|
||||||
*/
|
*/
|
||||||
public static final SimpleDateFormat DEBUG_FILE_TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH_mm_ss_SSS");
|
public static boolean isUTF8(final File file) throws IOException {
|
||||||
|
return isUTF8(file, DEFAULT_CHECK_ENCODING);
|
||||||
/**
|
}
|
||||||
* Default encoding used for string checks and validations in the application.
|
|
||||||
*
|
|
||||||
* This constant represents the `ISO-8859-15` encoding, which is a standardized
|
|
||||||
* character set encoding, commonly used in contexts where backward compatibility
|
|
||||||
* with `ISO-8859-1` is required, but with support for certain additional characters,
|
|
||||||
* such as the euro currency symbol (€).
|
|
||||||
*/
|
|
||||||
public static final String DEFAULT_CHECK_ENCODING = "ISO-8859-15";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies the default buffer size used for data processing operations.
|
|
||||||
*
|
|
||||||
* This constant represents the size of the buffer in bytes, set to 1024,
|
|
||||||
* and is typically utilized in input/output operations to optimize performance
|
|
||||||
* by reducing the number of read/write calls.
|
|
||||||
*/
|
|
||||||
public static final int BUFFER_SIZE = 1024;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether the given file is encoded in UTF-8.
|
* Determines whether the given file is encoded in UTF-8.
|
||||||
@ -97,7 +118,7 @@ public class FileUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the provided file starts with a UTF-8 Byte Order Mark (BOM).
|
* Checks if the provided file starts with a UTF-8 Byte Order Mark (BOM).
|
||||||
*
|
* <p>
|
||||||
* This method reads the first character of the file using a reader that assumes
|
* This method reads the first character of the file using a reader that assumes
|
||||||
* UTF-8 encoding and checks if it matches the Unicode Byte Order Mark (U+FEFF).
|
* UTF-8 encoding and checks if it matches the Unicode Byte Order Mark (U+FEFF).
|
||||||
*
|
*
|
||||||
@ -114,20 +135,9 @@ public class FileUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if the content of the given file is encoded in UTF-8.
|
|
||||||
*
|
|
||||||
* @param file The file to check for UTF-8 encoding. Must not be null.
|
|
||||||
* @return true if the file content is in UTF-8 encoding; false otherwise.
|
|
||||||
* @throws IOException If an I/O error occurs while reading the file.
|
|
||||||
*/
|
|
||||||
public static boolean isUTF8(final File file) throws IOException {
|
|
||||||
return isUTF8(file, DEFAULT_CHECK_ENCODING);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the content of a text file from one character encoding format to another.
|
* Converts the content of a text file from one character encoding format to another.
|
||||||
*
|
* <p>
|
||||||
* This method reads the input text file using the specified source encoding and writes
|
* This method reads the input text file using the specified source encoding and writes
|
||||||
* the content to the output text file in the specified target encoding.
|
* the content to the output text file in the specified target encoding.
|
||||||
*
|
*
|
||||||
@ -167,19 +177,6 @@ public class FileUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a universal file reader for the specified file name.
|
|
||||||
* This method initializes and returns an InputStreamReader to read
|
|
||||||
* the content of the given file.
|
|
||||||
*
|
|
||||||
* @param filename The name or path of the file to be read.
|
|
||||||
* @return An InputStreamReader for reading the specified file.
|
|
||||||
* @throws IOException If an I/O error occurs while creating the file reader.
|
|
||||||
*/
|
|
||||||
public static InputStreamReader createUniversalFileReader(final String filename) throws IOException {
|
|
||||||
return createUniversalFileReader(new File(filename));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a universal file reader for the specified file and format.
|
* Creates a universal file reader for the specified file and format.
|
||||||
* The method resolves the file using its name and the expected format,
|
* The method resolves the file using its name and the expected format,
|
||||||
@ -194,17 +191,6 @@ public class FileUtils {
|
|||||||
return createUniversalFileReader(new File(filename), expectedFormat);
|
return createUniversalFileReader(new File(filename), expectedFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a universal file reader for the specified file using the default encoding and configuration.
|
|
||||||
*
|
|
||||||
* @param file The file to be read. Must not be null.
|
|
||||||
* @return An InputStreamReader configured to read the specified file.
|
|
||||||
* @throws IOException If an I/O error occurs while creating the reader.
|
|
||||||
*/
|
|
||||||
public static InputStreamReader createUniversalFileReader(final File file) throws IOException {
|
|
||||||
return createUniversalFileReader(file, DEFAULT_CHECK_ENCODING, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a universal file reader for the specified file with an expected format.
|
* Creates a universal file reader for the specified file with an expected format.
|
||||||
* This method wraps the given file in an InputStreamReader for consistent character stream manipulation.
|
* This method wraps the given file in an InputStreamReader for consistent character stream manipulation.
|
||||||
@ -239,14 +225,14 @@ public class FileUtils {
|
|||||||
InputStreamReader result = new InputStreamReader(new FileInputStream(file), encoding);
|
InputStreamReader result = new InputStreamReader(new FileInputStream(file), encoding);
|
||||||
if (skipBOM) {
|
if (skipBOM) {
|
||||||
int BOM = result.read();
|
int BOM = result.read();
|
||||||
if (BOM != 0xFEFF) log.error ("Skipping BOM but value not 0xFEFF!");
|
if (BOM != 0xFEFF) log.error("Skipping BOM but value not 0xFEFF!");
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the parent directory of the given file or directory path.
|
* Retrieves the parent directory of the given file or directory path.
|
||||||
*
|
* <p>
|
||||||
* If the given path does not have a parent directory, it defaults to returning the
|
* If the given path does not have a parent directory, it defaults to returning the
|
||||||
* current directory represented by ".".
|
* current directory represented by ".".
|
||||||
*
|
*
|
||||||
@ -282,6 +268,30 @@ public class FileUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a universal file reader for the specified file name.
|
||||||
|
* This method initializes and returns an InputStreamReader to read
|
||||||
|
* the content of the given file.
|
||||||
|
*
|
||||||
|
* @param filename The name or path of the file to be read.
|
||||||
|
* @return An InputStreamReader for reading the specified file.
|
||||||
|
* @throws IOException If an I/O error occurs while creating the file reader.
|
||||||
|
*/
|
||||||
|
public static InputStreamReader createUniversalFileReader(final String filename) throws IOException {
|
||||||
|
return createUniversalFileReader(new File(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a universal file reader for the specified file using the default encoding and configuration.
|
||||||
|
*
|
||||||
|
* @param file The file to be read. Must not be null.
|
||||||
|
* @return An InputStreamReader configured to read the specified file.
|
||||||
|
* @throws IOException If an I/O error occurs while creating the reader.
|
||||||
|
*/
|
||||||
|
public static InputStreamReader createUniversalFileReader(final File file) throws IOException {
|
||||||
|
return createUniversalFileReader(file, DEFAULT_CHECK_ENCODING, true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the given content to the specified file path. If the file already exists, it will be overwritten.
|
* Writes the given content to the specified file path. If the file already exists, it will be overwritten.
|
||||||
*
|
*
|
||||||
@ -294,4 +304,60 @@ public class FileUtils {
|
|||||||
writer.write(content);
|
writer.write(content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the specified file or directory. If the target is a directory, all its contents,
|
||||||
|
* including subdirectories and files, will be deleted recursively.
|
||||||
|
* If the target file or directory does not exist, the method immediately returns {@code true}.
|
||||||
|
*
|
||||||
|
* @param targetFile the {@code Path} of the file or directory to be deleted
|
||||||
|
* @return {@code true} if the target file or directory was successfully deleted,
|
||||||
|
* or if it does not exist; {@code false} if an error occurred during deletion
|
||||||
|
*/
|
||||||
|
public static boolean remove(Path targetFile) {
|
||||||
|
if (!Files.exists(targetFile)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Files.isDirectory(targetFile)) {
|
||||||
|
return removeDirectory(targetFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetFile.toFile().delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the specified directory and all its contents, including subdirectories and files.
|
||||||
|
* The method performs a recursive deletion, starting with the deepest entries in the directory tree.
|
||||||
|
* If the directory does not exist, the method immediately returns true.
|
||||||
|
*
|
||||||
|
* @param targetDir the {@code Path} of the directory to be deleted
|
||||||
|
* @return {@code true} if the directory and all its contents were successfully deleted
|
||||||
|
* or if the directory does not exist; {@code false} if an error occurred during deletion
|
||||||
|
*/
|
||||||
|
public static boolean removeDirectory(Path targetDir) {
|
||||||
|
if (!Files.exists(targetDir)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Files.isDirectory(targetDir)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Files.walk(targetDir)
|
||||||
|
.sorted((a, b) -> b.compareTo(a))
|
||||||
|
.forEach(path -> {
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(path);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -83,10 +83,10 @@ public class StringEncoder {
|
|||||||
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);
|
||||||
}
|
}
|
||||||
|
|||||||
76
core/src/main/java/de/neitzel/core/io/TempDirectory.java
Normal file
76
core/src/main/java/de/neitzel/core/io/TempDirectory.java
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package de.neitzel.core.io;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility class for creating and managing a temporary directory.
|
||||||
|
* Instances of this class create a unique temporary directory on the filesystem
|
||||||
|
* that can safely be used during the runtime of a program.
|
||||||
|
* <p>
|
||||||
|
* The directory is automatically cleaned up when the instance is closed.
|
||||||
|
*/
|
||||||
|
public class TempDirectory implements AutoCloseable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The filesystem path representing the temporary directory managed by this instance.
|
||||||
|
* This path is initialized when the TempDirectory object is created and points
|
||||||
|
* to a unique, newly created directory.
|
||||||
|
* <p>
|
||||||
|
* The directory can be used to safely store temporary runtime files. It is automatically
|
||||||
|
* deleted along with its content when the associated TempDirectory object is closed.
|
||||||
|
*/
|
||||||
|
private final Path directory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a temporary directory with a unique name on the filesystem.
|
||||||
|
* The directory will have a prefix specified by the user and is intended
|
||||||
|
* to serve as a temporary workspace during the runtime of the program.
|
||||||
|
*
|
||||||
|
* @param prefix the prefix to be used for the name of the temporary directory
|
||||||
|
* @throws IOException if an I/O error occurs when creating the directory
|
||||||
|
*/
|
||||||
|
public TempDirectory(String prefix) throws IOException {
|
||||||
|
this.directory = Files.createTempDirectory(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the path of the temporary directory associated with this instance.
|
||||||
|
*
|
||||||
|
* @return the {@code Path} of the temporary directory
|
||||||
|
*/
|
||||||
|
public Path getDirectory() {
|
||||||
|
return directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the temporary directory by cleaning up its contents and deleting the directory itself.
|
||||||
|
* <p>
|
||||||
|
* This method ensures that all files and subdirectories within the temporary directory are
|
||||||
|
* deleted in a reverse order, starting from the deepest leaf in the directory tree. If
|
||||||
|
* the directory does not exist, the method will not perform any actions.
|
||||||
|
* <p>
|
||||||
|
* If an error occurs while deleting any file or directory, a RuntimeException is thrown
|
||||||
|
* with the details of the failure.
|
||||||
|
*
|
||||||
|
* @throws IOException if an I/O error occurs while accessing the directory or its contents.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (Files.exists(directory)) {
|
||||||
|
try (Stream<Path> walk = Files.walk(directory)) {
|
||||||
|
walk.sorted(Comparator.reverseOrder())
|
||||||
|
.forEach(path -> {
|
||||||
|
try {
|
||||||
|
Files.delete(path);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to delete: " + path, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -22,6 +22,20 @@ public class 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.
|
||||||
*
|
*
|
||||||
@ -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;
|
||||||
@ -89,6 +96,20 @@ public class Query<T> {
|
|||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the query text for the current query instance.
|
* Sets the query text for the current query instance.
|
||||||
*
|
*
|
||||||
@ -100,6 +121,21 @@ public class Query<T> {
|
|||||||
return this;
|
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.
|
* 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.
|
* The resource content is read and stored as the query text for the query object.
|
||||||
@ -166,6 +202,22 @@ public class Query<T> {
|
|||||||
return this;
|
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.
|
* 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.
|
* The result set is wrapped in a {@link TrimmingResultSet} to handle data trimming.
|
||||||
@ -179,35 +231,6 @@ public class Query<T> {
|
|||||||
return streamFromResultSet(trimmingResultSet, factory);
|
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}
|
* 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
|
* factory. The stream ensures resources such as the {@code ResultSet} and its associated statement are
|
||||||
@ -220,6 +243,7 @@ public class Query<T> {
|
|||||||
private Stream<T> streamFromResultSet(final ResultSet rs, final Function<ResultSet, T> factory) {
|
private Stream<T> streamFromResultSet(final ResultSet rs, final Function<ResultSet, T> factory) {
|
||||||
Iterator<T> iterator = new Iterator<>() {
|
Iterator<T> iterator = new Iterator<>() {
|
||||||
private boolean hasNextChecked = false;
|
private boolean hasNextChecked = false;
|
||||||
|
|
||||||
private boolean hasNext;
|
private boolean hasNext;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -272,22 +296,6 @@ public class Query<T> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a managed stream to the given handler and ensures proper resource closing after use.
|
|
||||||
* A {@code Stream} is opened and passed to the specified handler as an argument.
|
|
||||||
* The stream will be automatically closed when the handler finishes execution or throws an exception.
|
|
||||||
*
|
|
||||||
* @param handler a function which receives a {@code Stream<T>} and processes it, returning a result of type {@code R}.
|
|
||||||
* @param <R> the type of the result produced by the handler.
|
|
||||||
* @return the result returned by the handler after processing the stream.
|
|
||||||
* @throws SQLException if an SQL error occurs during the creation or handling of the stream.
|
|
||||||
*/
|
|
||||||
public <R> R withStream(Function<Stream<T>, R> handler) throws SQLException {
|
|
||||||
try (Stream<T> stream = stream()) {
|
|
||||||
return handler.apply(stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a functional interface that accepts three input arguments and allows for operations
|
* 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
|
* that can throw an {@link SQLException}. This can be useful in scenarios where SQL-related
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -27,7 +27,7 @@ public class ArrayUtils {
|
|||||||
* @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;
|
||||||
@ -41,7 +41,7 @@ public class ArrayUtils {
|
|||||||
* @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;
|
||||||
@ -55,7 +55,7 @@ public class ArrayUtils {
|
|||||||
* @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;
|
||||||
@ -70,7 +70,7 @@ public class ArrayUtils {
|
|||||||
* @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;
|
||||||
|
|||||||
@ -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,7 +52,7 @@ 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
|
||||||
@ -70,7 +70,7 @@ public class EnumUtil {
|
|||||||
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.
|
||||||
*/
|
*/
|
||||||
@ -57,6 +57,21 @@ public class RegionalizationMessage {
|
|||||||
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.
|
||||||
@ -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,6 +7,7 @@ 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 {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -14,12 +15,29 @@ public class StopWatch {
|
|||||||
* 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
|
* 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.
|
* duration between the start and stop times.
|
||||||
*/
|
*/
|
||||||
private Instant stopTime;
|
private Instant stopTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new StopWatch instance in the reset state.
|
||||||
|
*
|
||||||
|
* <p>The constructor does not start timing. Both {@link #startTime} and {@link #stopTime}
|
||||||
|
* are initialized to {@code null}. To begin measuring elapsed time, call {@link #start()}.
|
||||||
|
* After calling {@link #stop()}, retrieve the measured duration with {@link #getUsedTime()} or
|
||||||
|
* a human-readable string via {@link #getUsedTimeFormatted(boolean, boolean, boolean, boolean, boolean)}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>Note: This class is not synchronized; synchronize externally if the same instance
|
||||||
|
* is accessed from multiple threads.</p>
|
||||||
|
*/
|
||||||
|
public StopWatch() {
|
||||||
|
// default constructor only ...
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the stopwatch by recording the current time as the starting time. If the stopwatch was previously stopped or not yet started,
|
* 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
|
* this method resets the starting time to the current time and clears the stopping time. This method is typically called before measuring
|
||||||
@ -47,18 +65,15 @@ public class StopWatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the amount of time elapsed between the stopwatch's starting time and either its stopping time or the current time if the
|
* Provides a compact and human-readable formatted string representing the elapsed time measured by the stopwatch. The method formats the
|
||||||
* stopwatch is still running.
|
* 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 the duration representing the elapsed time between the start and end points of the stopwatch.
|
* @return a compact formatted string representing the elapsed time, including days, hours, minutes, seconds, and milliseconds as
|
||||||
* @throws IllegalStateException if the stopwatch has not been started before calling this method.
|
* applicable
|
||||||
*/
|
*/
|
||||||
public Duration getUsedTime() {
|
public String getUsedTimeFormattedCompact() {
|
||||||
if (startTime == null) {
|
return getUsedTimeFormatted(true, true, true, true, true);
|
||||||
throw new IllegalStateException("StopWatch has not been started.");
|
|
||||||
}
|
|
||||||
Instant end = (stopTime != null) ? stopTime : Instant.now();
|
|
||||||
return Duration.between(startTime, end);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,13 +108,13 @@ public class StopWatch {
|
|||||||
if (includeDays && days > 0) {
|
if (includeDays && days > 0) {
|
||||||
sb.append(days).append("d ");
|
sb.append(days).append("d ");
|
||||||
}
|
}
|
||||||
if (includeHours && (hours > 0 || sb.length() > 0)) {
|
if (includeHours && (hours > 0 || !sb.isEmpty())) {
|
||||||
sb.append(hours).append("h ");
|
sb.append(hours).append("h ");
|
||||||
}
|
}
|
||||||
if (includeMinutes && (minutes > 0 || sb.length() > 0)) {
|
if (includeMinutes && (minutes > 0 || !sb.isEmpty())) {
|
||||||
sb.append(minutes).append("m ");
|
sb.append(minutes).append("m ");
|
||||||
}
|
}
|
||||||
if (includeSeconds && (seconds > 0 || sb.length() > 0)) {
|
if (includeSeconds && (seconds > 0 || !sb.isEmpty())) {
|
||||||
sb.append(seconds);
|
sb.append(seconds);
|
||||||
}
|
}
|
||||||
if (includeMillis) {
|
if (includeMillis) {
|
||||||
@ -115,14 +130,17 @@ public class StopWatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a compact and human-readable formatted string representing the elapsed time measured by the stopwatch. The method formats the
|
* Calculates the amount of time elapsed between the stopwatch's starting time and either its stopping time or the current time if the
|
||||||
* duration using a combination of days, hours, minutes, seconds, and milliseconds. This compact format removes additional verbosity in
|
* stopwatch is still running.
|
||||||
* favor of a shorter, more concise representation.
|
|
||||||
*
|
*
|
||||||
* @return a compact formatted string representing the elapsed time, including days, hours, minutes, seconds, and milliseconds as
|
* @return the duration representing the elapsed time between the start and end points of the stopwatch.
|
||||||
* applicable
|
* @throws IllegalStateException if the stopwatch has not been started before calling this method.
|
||||||
*/
|
*/
|
||||||
public String getUsedTimeFormattedCompact() {
|
public Duration getUsedTime() {
|
||||||
return getUsedTimeFormatted(true, true, true, true, true);
|
if (startTime == null) {
|
||||||
|
throw new IllegalStateException("StopWatch has not been started.");
|
||||||
|
}
|
||||||
|
Instant end = (stopTime != null) ? stopTime : Instant.now();
|
||||||
|
return Duration.between(startTime, end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,21 @@ 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
|
||||||
@ -20,21 +35,6 @@ public class 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
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -78,7 +78,7 @@ public class Strings {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -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,6 +30,16 @@ import java.util.Date;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class XmlUtils {
|
public class XmlUtils {
|
||||||
|
/**
|
||||||
|
* A {@link DateTimeFormatter} instance used for formatting or parsing dates in the "yyyy-MM-dd" pattern.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* Thread-safe and immutable, this formatter can be shared across multiple threads.
|
||||||
|
*/
|
||||||
|
public static final DateTimeFormatter XML_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@ -38,25 +53,16 @@ public class XmlUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link DateTimeFormatter} instance used for formatting or parsing dates in the "yyyy-MM-dd" pattern.
|
* 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.
|
||||||
*
|
*
|
||||||
* This formatter adheres to the XML date format standard (ISO-8601). It can be used
|
* @param doc the XML document to which the element belongs; must not be null
|
||||||
* to ensure consistent date representations in XML or other similar text-based formats.
|
* @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
|
||||||
* Thread-safe and immutable, this formatter can be shared across multiple threads.
|
* @return the created XML element containing the specified name and formatted date value
|
||||||
*/
|
*/
|
||||||
public static final DateTimeFormatter XML_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
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 element in the provided XML document with the specified name.
|
|
||||||
*
|
|
||||||
* @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.
|
|
||||||
* @return The newly created element with the given name. Never returns null.
|
|
||||||
*/
|
|
||||||
public static Element createElement(final Document doc, final String name) {
|
|
||||||
log.debug("Creating a new element " + name);
|
|
||||||
return doc.createElement(name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,19 +85,6 @@ public class XmlUtils {
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 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 value the date value to be assigned to the created element; must not be null
|
|
||||||
* @return the created XML element containing the specified name and formatted date value
|
|
||||||
*/
|
|
||||||
public static Element createElement(final Document doc, final String name, final Date value) {
|
|
||||||
return createElement(doc, name, XML_DATE_FORMATTER.format(value.toInstant()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an XML element with the specified name and optional value.
|
* Creates an XML element with the specified name and optional value.
|
||||||
*
|
*
|
||||||
@ -105,7 +98,7 @@ public class XmlUtils {
|
|||||||
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;
|
||||||
@ -127,6 +120,18 @@ public class XmlUtils {
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new element in the provided XML document with the specified name.
|
||||||
|
*
|
||||||
|
* @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.
|
||||||
|
* @return The newly created element with the given name. Never returns null.
|
||||||
|
*/
|
||||||
|
public static Element createElement(final Document doc, final String name) {
|
||||||
|
log.debug("Creating a new element " + name);
|
||||||
|
return doc.createElement(name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@ -144,7 +149,6 @@ public class XmlUtils {
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats a given XML string by applying proper indentation to enhance readability.
|
* Formats a given XML string by applying proper indentation to enhance readability.
|
||||||
* The method uses a transformer to process the XML input, applying indentation
|
* The method uses a transformer to process the XML input, applying indentation
|
||||||
@ -171,9 +175,8 @@ public class XmlUtils {
|
|||||||
transformer.transform(xmlInput, xmlOutput);
|
transformer.transform(xmlInput, xmlOutput);
|
||||||
|
|
||||||
return xmlOutput.getWriter().toString();
|
return xmlOutput.getWriter().toString();
|
||||||
}
|
} catch (TransformerException e) {
|
||||||
catch(TransformerException e) {
|
log.error("Error in XML: " + e.getMessage() + "\n" + xmlStream, e);
|
||||||
log.error("Error in XML: " + e.getMessage() + "\n"+xmlStream, e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return xmlStream;
|
return xmlStream;
|
||||||
@ -205,8 +208,7 @@ public class XmlUtils {
|
|||||||
String xmlString = stringWriter.toString();
|
String xmlString = stringWriter.toString();
|
||||||
log.info("MO Request: " + xmlString);
|
log.info("MO Request: " + xmlString);
|
||||||
return xmlString;
|
return xmlString;
|
||||||
}
|
} catch (TransformerException e) {
|
||||||
catch(TransformerException e) {
|
|
||||||
log.error("Error in XML Transformation: " + e.getMessage(), e);
|
log.error("Error in XML Transformation: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
package de.neitzel.core.inject.testcomponents.test1ok;
|
|
||||||
|
|
||||||
public class SuperClass {
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
package de.neitzel.core.inject.testcomponents.test1ok;
|
|
||||||
|
|
||||||
public interface TestInterface1_1 {
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
package de.neitzel.core.inject.testcomponents.test1ok;
|
|
||||||
|
|
||||||
public interface TestInterface1_2 {
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
package de.neitzel.core.inject.testcomponents.test1ok.sub;
|
|
||||||
|
|
||||||
import de.neitzel.core.inject.annotation.Component;
|
|
||||||
import de.neitzel.core.inject.testcomponents.test1ok.SuperClass;
|
|
||||||
import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_1;
|
|
||||||
import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_2;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class TestComponent1_2 extends SuperClass implements TestInterface1_1, TestInterface1_2 {
|
|
||||||
public TestComponent1_2() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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
|
||||||
* Replaces illegal characters of a string with a given replacement character
|
* for working with character sets and validating characters against the alphabet.
|
||||||
* @param text Text to check.
|
|
||||||
* @param replacement Replacement character.
|
|
||||||
* @return String in which all characters was replaced.
|
|
||||||
*/
|
*/
|
||||||
public String replaceIllegalCharacters(String text, char replacement) {
|
public interface BaseAlphabet extends Alphabet {
|
||||||
|
/**
|
||||||
|
* Replaces illegal characters of a string with a given replacement character.
|
||||||
|
*
|
||||||
|
* @param text Text to check; may not be null
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
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,47 +7,51 @@ 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 key AES key to use.
|
||||||
* @param tweak tweak to use for encryption / decryption
|
* @param tweak tweak to use for encryption / decryption
|
||||||
* @param ignoreToShortStrings Ignore strings that are to short.
|
* @param ignoreToShortStrings Ignore strings that are to short.
|
||||||
@ -58,6 +62,7 @@ public class FF1Encryption {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of FF1Encryption
|
* Creates a new instance of FF1Encryption
|
||||||
|
*
|
||||||
* @param key AES key to use.
|
* @param key AES key to use.
|
||||||
* @param tweak tweak to use for encryption / decryption
|
* @param tweak tweak to use for encryption / decryption
|
||||||
* @param ignoreToShortStrings Ignore strings that are to short.
|
* @param ignoreToShortStrings Ignore strings that are to short.
|
||||||
@ -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,8 +6,34 @@ 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 {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(JavaFXApp.class.getName());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
@Override
|
||||||
public void start(Stage primaryStage) {
|
public void start(Stage primaryStage) {
|
||||||
try {
|
try {
|
||||||
@ -17,12 +43,7 @@ public class JavaFXApp extends Application {
|
|||||||
primaryStage.setScene(new Scene(root));
|
primaryStage.setScene(new Scene(root));
|
||||||
primaryStage.show();
|
primaryStage.show();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
System.out.println("Exception: " + ex.getMessage());
|
LOGGER.log(Level.SEVERE, "Exception while starting JavaFXApp", ex);
|
||||||
ex.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
launch(args);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
||||||
|
xmlns="http://javafx.com/javafx/17.0.2-ea" fx:controller="de.neitzel.fx.injectfx.example.MainWindow">
|
||||||
<children>
|
<children>
|
||||||
<Button fx:id="button" layoutX="44.0" layoutY="70.0" mnemonicParsing="false" onAction="#onButtonClick" text="Click Me" />
|
<Button fx:id="button" layoutX="44.0" layoutY="70.0" mnemonicParsing="false" onAction="#onButtonClick"
|
||||||
<TextField fx:id="textField" layoutX="14.0" layoutY="24.0" />
|
text="Click Me"/>
|
||||||
|
<TextField fx:id="textField" layoutX="14.0" layoutY="24.0"/>
|
||||||
</children>
|
</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>
|
||||||
|
|||||||
99
fx/ideas.md
Normal file
99
fx/ideas.md
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# TODO
|
||||||
|
|
||||||
|
- SimpleListProperty auch nutzen bei Collections!
|
||||||
|
|
||||||
|
# Ideen
|
||||||
|
|
||||||
|
# Bindings
|
||||||
|
|
||||||
|
Bindngs kommen über ein spezielles Binding Control, das dann notwendige Bindings beinhaltet.
|
||||||
|
|
||||||
|
Da kann dann auch eine eigene Logik zur Erkennung des Bindings erfolgen oder zusätziche Informationen bezüglich
|
||||||
|
notwendiger Elemente in dem ViewModel
|
||||||
|
|
||||||
|
## Bidirektional Converter für Bindings
|
||||||
|
|
||||||
|
```Java
|
||||||
|
StringConverter<Instant> converter = new StringConverter<>() {
|
||||||
|
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString(Instant instant) {
|
||||||
|
return instant == null ? "" : formatter.format(instant);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Instant fromString(String string) {
|
||||||
|
if (string == null || string.isBlank()) return null;
|
||||||
|
try {
|
||||||
|
LocalDateTime dateTime = LocalDateTime.parse(string, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||||
|
return dateTime.atZone(ZoneId.systemDefault()).toInstant();
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
|
// Optional: Fehlermeldung anzeigen
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Binding einrichten:
|
||||||
|
Bindings.
|
||||||
|
|
||||||
|
bindBidirectional(textField.textProperty(),instantProperty,converter);
|
||||||
|
```
|
||||||
|
|
||||||
|
Overloads von bindBidirectional
|
||||||
|
bindBidirectional(Property<String>, Property<?>, Format)
|
||||||
|
bindBidirectional(Property<String>, Property<T>, StringConverter<T>)
|
||||||
|
bindBidirectional(Property<T>, Property<T>)
|
||||||
|
|
||||||
|
## Unidirektional mit Bindings.createStringBinding()
|
||||||
|
|
||||||
|
```Java
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault());
|
||||||
|
|
||||||
|
textField.
|
||||||
|
|
||||||
|
textProperty().
|
||||||
|
|
||||||
|
bind(Bindings.createStringBinding(
|
||||||
|
() ->{
|
||||||
|
|
||||||
|
Instant instant = instantProperty.get();
|
||||||
|
return instant ==null?"":formatter.
|
||||||
|
|
||||||
|
format(instant);
|
||||||
|
},
|
||||||
|
instantProperty
|
||||||
|
));
|
||||||
|
```
|
||||||
|
|
||||||
|
Bindings:
|
||||||
|
|
||||||
|
- BooleanBinding
|
||||||
|
- DoubleBinding
|
||||||
|
- FloatBinding
|
||||||
|
- IntegerBinding
|
||||||
|
- LongBinding
|
||||||
|
- StringBinding
|
||||||
|
- ObjectBinding
|
||||||
|
- NumberBinding
|
||||||
|
|
||||||
|
Bindings haben Methoden, die dann weitere Bindings erzeugen.
|
||||||
|
==> Parameter der Methode: Property, Observable und einer Methode, die ein Binding erstellt und das ViewModel<?>, das
|
||||||
|
dann genutzt werden kann, um weitere Properties zu holen ==> Erzeugung von neuen Properties.
|
||||||
|
|
||||||
|
Diese BindingCreators haben Namen, PropertyTyp und ein BindingType.
|
||||||
|
|
||||||
|
# FXMLComponent
|
||||||
|
|
||||||
|
Dient dem Laden einer Komponente und bekommt dazu das fxml, die Daten und ggf. auch eine ModelView.
|
||||||
|
|
||||||
|
# ModelView generieren
|
||||||
|
|
||||||
|
Damit eine ModelView nicht ständig manuell generiert werden muss, ist hier ggf. etwas zu generieren?
|
||||||
|
Ggf. eine eigenes Beschreibungssprache?
|
||||||
|
|
||||||
|
# Aufbau einer Validierung
|
||||||
|
|
||||||
|
- gehört in das ViewModel
|
||||||
|
-
|
||||||
@ -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>
|
||||||
|
|
||||||
@ -49,6 +49,11 @@
|
|||||||
<artifactId>core</artifactId>
|
<artifactId>core</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>de.neitzel.lib</groupId>
|
||||||
|
<artifactId>injection</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@ -1,22 +1,70 @@
|
|||||||
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 lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
public class AutoViewModel<T> {
|
public class AutoViewModel<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The wrapped model instance.
|
||||||
|
* -- GETTER --
|
||||||
|
* Retrieves the model associated with this AutoViewModel.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a mapping of field names to JavaFX properties for the associated model object.
|
||||||
|
* <p>
|
||||||
|
* This method utilizes reflection to iterate through the public no-argument getters
|
||||||
|
* of the model's class to create corresponding JavaFX properties. It performs the following:
|
||||||
|
* <p>
|
||||||
|
* 1. Identifies methods that adhere to the getter naming conventions (e.g., `getFieldName` or `isFieldName`).
|
||||||
|
* 2. Maps the field name derived from the getter method to a JavaFX property, creating an appropriate
|
||||||
|
* property type based on the getter's return type.
|
||||||
|
* 3. Adds a listener to each property to bind updates from the property back to the model object
|
||||||
|
* using the corresponding setter method, if available. This ensures bi-directional synchronization
|
||||||
|
* between the ViewModel and the underlying model.
|
||||||
|
* <p>
|
||||||
|
* Exceptions that may occur during reflective operations (e.g., invoking methods) are caught
|
||||||
|
* and logged to avoid runtime failures.
|
||||||
|
*/
|
||||||
private void initProperties() {
|
private void initProperties() {
|
||||||
for (Method getter : model.getClass().getMethods()) {
|
for (Method getter : model.getClass().getMethods()) {
|
||||||
if (isGetter(getter)) {
|
if (isGetter(getter)) {
|
||||||
@ -27,13 +75,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();
|
log.warn("Failed to invoke setter for field {}", fieldName, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -41,16 +93,14 @@ public class AutoViewModel<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Property<?> getProperty(String name) {
|
/**
|
||||||
return properties.get(name);
|
* Determines if a given method follows the JavaBean getter convention.
|
||||||
}
|
* The method must be public, take no parameters, have a non-void return type,
|
||||||
|
* and its name should start with either "get" or "is".
|
||||||
public T getModel() {
|
*
|
||||||
return model;
|
* @param method the method to be assessed
|
||||||
}
|
* @return true if the method adheres to the JavaBean getter convention, false otherwise
|
||||||
|
*/
|
||||||
// ========== 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
|
||||||
@ -58,6 +108,14 @@ public class AutoViewModel<T> {
|
|||||||
&& (method.getName().startsWith("get") || method.getName().startsWith("is"));
|
&& (method.getName().startsWith("get") || method.getName().startsWith("is"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derives the field name from a given Java method by following JavaBean naming conventions.
|
||||||
|
* The method's name is analyzed to strip the "get" or "is" prefix (if present),
|
||||||
|
* and the result is converted into a decapitalized field name.
|
||||||
|
*
|
||||||
|
* @param method the method whose name is to be processed to derive the field name
|
||||||
|
* @return the derived field name, or the original method name if no "get" or "is" prefix is found
|
||||||
|
*/
|
||||||
private String getFieldName(Method method) {
|
private String getFieldName(Method method) {
|
||||||
String name = method.getName();
|
String name = method.getName();
|
||||||
if (name.startsWith("get")) {
|
if (name.startsWith("get")) {
|
||||||
@ -68,20 +126,57 @@ 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes the specified method on the model object and returns the result.
|
||||||
|
* If the method invocation fails, logs a warning and returns null.
|
||||||
|
*
|
||||||
|
* @param method the method to be invoked, typically a JavaBean getter
|
||||||
|
* @return the result of invoking the method, or null if an error occurs
|
||||||
|
*/
|
||||||
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();
|
log.warn("Failed to invoke getter: {}", method.getName(), e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an object into an appropriate JavaFX {@link Property} based on its type.
|
||||||
|
*
|
||||||
|
* @param value the object to be converted into a JavaFX property; can be of type String, Integer,
|
||||||
|
* Boolean, Double, Float, Long, or any other object
|
||||||
|
* @return a JavaFX {@link Property} corresponding to the type of the input object, such as
|
||||||
|
* {@link SimpleStringProperty} for String, {@link SimpleIntegerProperty} for Integer,
|
||||||
|
* {@link SimpleBooleanProperty} for Boolean, and so on. If the type is not explicitly
|
||||||
|
* handled, a {@link SimpleObjectProperty} is returned wrapping the object
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds a setter method in a specified class that corresponds to the given field name and value type.
|
||||||
|
* <p>
|
||||||
|
* The method searches for a public method in the class whose name matches the JavaBean-style setter
|
||||||
|
* naming convention (e.g., "setFieldName"). The method must accept exactly one parameter, and if a
|
||||||
|
* value type is provided, the parameter type must be assignable from it.
|
||||||
|
*
|
||||||
|
* @param clazz the class to search for the method
|
||||||
|
* @param fieldName the name of the field for which the setter method is being sought
|
||||||
|
* @param valueType the expected type of the parameter for the setter method, or null if no specific
|
||||||
|
* type constraint is required
|
||||||
|
* @return the setter method if found, or null if no matching method is identified
|
||||||
|
*/
|
||||||
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 +189,38 @@ public class AutoViewModel<T> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the first character of the given string to lowercase, leaving the rest of the string unchanged.
|
||||||
|
* If the input string is null or empty, it is returned as-is.
|
||||||
|
*
|
||||||
|
* @param str the string to be decapitalized
|
||||||
|
* @return the decapitalized string, or the original string if it is null or empty
|
||||||
|
*/
|
||||||
|
private String decapitalize(String str) {
|
||||||
|
if (str == null || str.isEmpty()) return str;
|
||||||
|
return str.substring(0, 1).toLowerCase() + str.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Capitalizes the first letter of the given string and leaves the rest of the string unchanged.
|
||||||
|
* If the input string is null or empty, it is returned as is.
|
||||||
|
*
|
||||||
|
* @param str the string to be capitalized
|
||||||
|
* @return the input string with its first character converted to uppercase, or the original string if it is null or empty
|
||||||
|
*/
|
||||||
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) {
|
/**
|
||||||
if (value instanceof String s) return new SimpleStringProperty(s);
|
* Retrieves the JavaFX property associated with the given field name.
|
||||||
if (value instanceof Integer i) return new SimpleIntegerProperty(i);
|
*
|
||||||
if (value instanceof Boolean b) return new SimpleBooleanProperty(b);
|
* @param name the name of the field whose associated JavaFX property is to be returned
|
||||||
if (value instanceof Double d) return new SimpleDoubleProperty(d);
|
* @return the JavaFX {@link Property} associated with the specified field name,
|
||||||
if (value instanceof Float f) return new SimpleFloatProperty(f);
|
* or null if no property exists for the given name
|
||||||
if (value instanceof Long l) return new SimpleLongProperty(l);
|
*/
|
||||||
return new SimpleObjectProperty<>(value);
|
public Property<?> getProperty(String name) {
|
||||||
|
return properties.get(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package de.neitzel.fx.component;
|
package de.neitzel.fx.component;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generic controller used by the ComponentLoader to bind FXML views
|
* Generic controller used by the ComponentLoader to bind FXML views
|
||||||
@ -10,7 +9,6 @@ import lombok.RequiredArgsConstructor;
|
|||||||
* This controller provides access to the {@link AutoViewModel}
|
* This controller provides access to the {@link AutoViewModel}
|
||||||
* which contains JavaFX properties derived from the model.
|
* which contains JavaFX properties derived from the model.
|
||||||
*/
|
*/
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class ComponentController {
|
public class ComponentController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,4 +18,14 @@ public class ComponentController {
|
|||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
private final AutoViewModel<?> viewModel;
|
private final AutoViewModel<?> viewModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new ComponentController instance with the specified AutoViewModel.
|
||||||
|
*
|
||||||
|
* @param viewModel the AutoViewModel containing JavaFX properties derived from the model,
|
||||||
|
* used for binding the view to the model.
|
||||||
|
*/
|
||||||
|
public ComponentController(AutoViewModel<?> viewModel) {
|
||||||
|
this.viewModel = viewModel;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,27 +1,266 @@
|
|||||||
package de.neitzel.fx.component;
|
package de.neitzel.fx.component;
|
||||||
|
|
||||||
|
import de.neitzel.fx.component.controls.Binding;
|
||||||
|
import de.neitzel.fx.component.controls.FxmlComponent;
|
||||||
|
import de.neitzel.fx.component.model.BindingData;
|
||||||
import javafx.beans.property.Property;
|
import javafx.beans.property.Property;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Node;
|
||||||
import javafx.scene.Parent;
|
import javafx.scene.Parent;
|
||||||
import javafx.scene.layout.Pane;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
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
|
||||||
* them to automatically generated ViewModels based on simple POJO models.
|
* them to automatically generated ViewModels based on simple POJO models.
|
||||||
* <p>
|
|
||||||
* It parses custom NFX attributes in FXML to bind UI elements to properties in the ViewModel,
|
|
||||||
* and supports recursive loading of subcomponents.
|
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class ComponentLoader {
|
public class ComponentLoader {
|
||||||
private Map<String, Map<String, String>> nfxBindingMap = new HashMap<>();
|
|
||||||
|
/**
|
||||||
|
* The controller object associated with the ComponentLoader.
|
||||||
|
* It is typically used to manage the behavior and interactions of the UI components
|
||||||
|
* defined in an FXML file.
|
||||||
|
*/
|
||||||
|
private Object controller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor only
|
||||||
|
*/
|
||||||
|
public ComponentLoader() {
|
||||||
|
// default constructor only
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an FXML file from the specified URL, sets the provided controller, and processes bindings.
|
||||||
|
* This method parses the FXML file, initializes the specified controller, and evaluates binding controls
|
||||||
|
* defined within the FXML namespace.
|
||||||
|
*
|
||||||
|
* @param <T> the type of the root node, which must extend {@code Parent}
|
||||||
|
* @param fxmlUrl the URL of the FXML file to be loaded
|
||||||
|
* @param controller the controller to associate with the FXML file
|
||||||
|
* @param nothing an unused parameter, included for compatibility
|
||||||
|
* @return the root node loaded from the FXML file
|
||||||
|
* @throws IOException if there is an error reading the FXML file
|
||||||
|
*/
|
||||||
|
public static <T extends Parent> T load(URL fxmlUrl, Object controller, @SuppressWarnings("unused") String nothing) throws IOException {
|
||||||
|
FXMLLoader loader = new FXMLLoader(fxmlUrl);
|
||||||
|
loader.setController(controller);
|
||||||
|
T root = loader.load();
|
||||||
|
|
||||||
|
Map<String, Object> namespace = loader.getNamespace();
|
||||||
|
|
||||||
|
// Nach allen BindingControls suchen:
|
||||||
|
List<Binding> bindingControls = collectAllNodes(root).stream()
|
||||||
|
.filter(n -> n instanceof Binding)
|
||||||
|
.map(n -> (Binding) n)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
for (Binding bc : bindingControls) {
|
||||||
|
evaluateBindings(bc.getBindings(), namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely binds two properties bidirectionally while ensuring type compatibility.
|
||||||
|
* If the target property's type is not compatible with the source property's type, the binding will
|
||||||
|
* not be performed, and an error will be logged.
|
||||||
|
*
|
||||||
|
* @param source the source property to bind, must not be null
|
||||||
|
* @param target the target property to bind to; the type will be checked for compatibility at runtime
|
||||||
|
* @param <T> the type of the source property's value
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <T> void bindBidirectionalSafe(@NotNull Property<T> source, Property<?> target) {
|
||||||
|
try {
|
||||||
|
Property<T> targetCasted = (Property<T>) target;
|
||||||
|
source.bindBidirectional(targetCasted);
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
log.error("⚠️ Typkonflikt beim Binding: {} ⇄ {}", source.getClass(), target.getClass(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to bind the source property to the target property in a type-safe manner.
|
||||||
|
* If the target property's type is incompatible with the source property's type, the binding operation
|
||||||
|
* will be skipped, and an error message will be logged.
|
||||||
|
*
|
||||||
|
* @param source the source property to bind, must not be null
|
||||||
|
* @param target the target property to which the source will be bound
|
||||||
|
* @param <T> the type of the source property's value
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <T> void bindSafe(@NotNull Property<T> source, Property<?> target) {
|
||||||
|
try {
|
||||||
|
Property<T> targetCasted = (Property<T>) target;
|
||||||
|
source.bind(targetCasted);
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
log.error("⚠️ Typkonflikt beim Binding: {} ⇄ {}", source.getClass(), target.getClass(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates and establishes bindings between source and target properties based on the provided
|
||||||
|
* bindings list and namespace. Supports unidirectional and bidirectional bindings, ensuring type
|
||||||
|
* compatibility before binding. Logs errors for unsuccessful binding attempts or exceptions.
|
||||||
|
*
|
||||||
|
* @param bindings a list of BindingData objects that define the source, target, and direction for the bindings
|
||||||
|
* @param namespace a map representing the namespace from which expressions in the bindings are resolved
|
||||||
|
*/
|
||||||
|
private static void evaluateBindings(List<BindingData> bindings, Map<String, Object> namespace) {
|
||||||
|
for (var binding : bindings) {
|
||||||
|
try {
|
||||||
|
Object source = resolveExpression(binding.getSource(), namespace);
|
||||||
|
Object target = resolveExpression(binding.getTarget(), namespace);
|
||||||
|
|
||||||
|
if (source instanceof Property<?> sourceProp && target instanceof Property<?> targetProp) {
|
||||||
|
|
||||||
|
Class<?> sourceType = getPropertyType(sourceProp);
|
||||||
|
Class<?> targetType = getPropertyType(targetProp);
|
||||||
|
|
||||||
|
boolean bindableForward = targetType.isAssignableFrom(sourceType);
|
||||||
|
boolean bindableBackward = sourceType.isAssignableFrom(targetType);
|
||||||
|
|
||||||
|
switch (binding.getDirection().toLowerCase()) {
|
||||||
|
case "bidirectional":
|
||||||
|
if (bindableForward && bindableBackward) {
|
||||||
|
bindBidirectionalSafe(sourceProp, targetProp);
|
||||||
|
} else {
|
||||||
|
log.error("⚠️ Kann bidirektionales Binding nicht durchführen: Typen inkompatibel ({} ⇄ {})", sourceType, targetType);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "unidirectional":
|
||||||
|
default:
|
||||||
|
if (bindableForward) {
|
||||||
|
bindSafe(sourceProp, targetProp);
|
||||||
|
} else {
|
||||||
|
log.error("⚠️ Kann unidirektionales Binding nicht durchführen: {} → {} nicht zuweisbar", sourceType, targetType);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Fehler beim Binding: {} → {}", binding.getSource(), binding.getTarget(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines the type of the value held by the specified property.
|
||||||
|
*
|
||||||
|
* @param prop the property whose value type is to be determined, must not be null
|
||||||
|
* @return the class of the property's value type; returns Object.class if the type cannot be determined
|
||||||
|
*/
|
||||||
|
private static Class<?> getPropertyType(Property<?> prop) {
|
||||||
|
try {
|
||||||
|
Method getter = prop.getClass().getMethod("get");
|
||||||
|
return getter.getReturnType();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return Object.class; // Fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a dot-separated expression (e.g., "viewModel.username") by navigating through the provided namespace
|
||||||
|
* map and invoking the corresponding getter methods to access nested properties.
|
||||||
|
*
|
||||||
|
* @param expr the dot-separated expression to resolve, must not be null
|
||||||
|
* @param namespace a map containing the initial objects to resolve the expression from, must not be null
|
||||||
|
* @return the value resolved from the specified expression
|
||||||
|
* @throws Exception if an error occurs while invoking getter methods or resolving the expression
|
||||||
|
*/
|
||||||
|
private static Object resolveExpression(@NotNull String expr, @NotNull Map<String, Object> namespace) throws Exception {
|
||||||
|
// z.B. "viewModel.username"
|
||||||
|
String[] parts = expr.split("\\.");
|
||||||
|
Object current = namespace.get(parts[0]);
|
||||||
|
for (int i = 1; i < parts.length; i++) {
|
||||||
|
String getter = "get" + Character.toUpperCase(parts[i].charAt(0)) + parts[i].substring(1);
|
||||||
|
current = current.getClass().getMethod(getter).invoke(current);
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively collects all nodes in a scene graph starting from the given root node.
|
||||||
|
*
|
||||||
|
* @param root the starting node from which all descendant nodes will be collected
|
||||||
|
* @return a list containing all nodes, including the root node and its descendants
|
||||||
|
*/
|
||||||
|
private static @NotNull List<Node> collectAllNodes(Node root) {
|
||||||
|
List<Node> nodes = new ArrayList<>();
|
||||||
|
nodes.add(root);
|
||||||
|
if (root instanceof Parent parent && !(root instanceof FxmlComponent)) {
|
||||||
|
for (Node child : parent.getChildrenUnmodifiable()) {
|
||||||
|
nodes.addAll(collectAllNodes(child));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the controller associated with the ComponentLoader.
|
||||||
|
*
|
||||||
|
* @return the controller object
|
||||||
|
*/
|
||||||
|
public Object getController() {
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an FXML file and returns the root JavaFX node without binding to a data model.
|
||||||
|
* This method uses the given {@code fxmlPath} to locate and load the FXML file.
|
||||||
|
*
|
||||||
|
* @param fxmlPath the URL of the FXML file to be loaded
|
||||||
|
* @return the root JavaFX node loaded from the specified FXML file
|
||||||
|
*/
|
||||||
|
public Parent load(URL fxmlPath) {
|
||||||
|
return load(null, fxmlPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an FXML file and binds its elements to a generated ViewModel
|
||||||
|
* based on the given POJO model and specified FXML path.
|
||||||
|
*
|
||||||
|
* @param model the data model (POJO) to bind to the UI
|
||||||
|
* @param fxmlPath the URL path to the FXML file
|
||||||
|
* @return the root JavaFX node loaded from FXML
|
||||||
|
* @throws RuntimeException if the FXML could not be loaded
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an FXML file and returns its root node.
|
||||||
|
*
|
||||||
|
* @param fxmlPath the relative path to the FXML file
|
||||||
|
* @return the root JavaFX node loaded from the FXML file
|
||||||
|
*/
|
||||||
|
public Parent load(String fxmlPath) {
|
||||||
|
return load(null, fxmlPath);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads an FXML file and binds its elements to a generated ViewModel
|
* Loads an FXML file and binds its elements to a generated ViewModel
|
||||||
@ -32,83 +271,7 @@ public class ComponentLoader {
|
|||||||
* @return the root JavaFX node loaded from FXML
|
* @return the root JavaFX node loaded from FXML
|
||||||
*/
|
*/
|
||||||
public Parent load(Object model, String fxmlPath) {
|
public Parent load(Object model, String fxmlPath) {
|
||||||
try {
|
return load(model, getClass().getResource(fxmlPath));
|
||||||
AutoViewModel<?> viewModel = new AutoViewModel<>(model);
|
|
||||||
String cleanedUri = preprocessNfxAttributes(fxmlPath);
|
|
||||||
FXMLLoader loader = new FXMLLoader(new URL(cleanedUri));
|
|
||||||
loader.setControllerFactory(type -> new ComponentController(viewModel));
|
|
||||||
Parent root = loader.load();
|
|
||||||
processNfxBindings(root, viewModel, loader);
|
|
||||||
return root;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("unable to load fxml: " + fxmlPath, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes all UI elements for NFX binding attributes and applies
|
|
||||||
* the appropriate bindings to the ViewModel.
|
|
||||||
*
|
|
||||||
* @param root the root node of the loaded FXML hierarchy
|
|
||||||
* @param viewModel the generated ViewModel to bind against
|
|
||||||
*/
|
|
||||||
private void processNfxBindings(Parent root, AutoViewModel<?> viewModel, FXMLLoader loader) {
|
|
||||||
walkNodes(root, node -> {
|
|
||||||
var nfx = lookupNfxAttributes(node, loader);
|
|
||||||
String target = nfx.get("nfx:target");
|
|
||||||
String direction = nfx.get("nfx:direction");
|
|
||||||
String source = nfx.get("nfx:source");
|
|
||||||
|
|
||||||
if (target != null && direction != null) {
|
|
||||||
Property<?> vmProp = viewModel.getProperty(target);
|
|
||||||
bindNodeToProperty(node, vmProp, direction);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source != null) {
|
|
||||||
Object subModel = ((Property<?>) viewModel.getProperty(target)).getValue();
|
|
||||||
Parent subComponent = load(subModel, source);
|
|
||||||
if (node instanceof Pane pane) {
|
|
||||||
pane.getChildren().setAll(subComponent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively walks all nodes in the scene graph starting from the root,
|
|
||||||
* applying the given consumer to each node.
|
|
||||||
*
|
|
||||||
* @param root the starting node
|
|
||||||
* @param consumer the consumer to apply to each node
|
|
||||||
*/
|
|
||||||
private void walkNodes(Parent root, java.util.function.Consumer<javafx.scene.Node> consumer) {
|
|
||||||
consumer.accept(root);
|
|
||||||
if (root instanceof Pane pane) {
|
|
||||||
for (javafx.scene.Node child : pane.getChildren()) {
|
|
||||||
if (child instanceof Parent p) {
|
|
||||||
walkNodes(p, consumer);
|
|
||||||
} else {
|
|
||||||
consumer.accept(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts custom NFX attributes from a node's properties map.
|
|
||||||
* These attributes are expected to be in the format "nfx:..." and hold string values.
|
|
||||||
*
|
|
||||||
* @param node the node to inspect
|
|
||||||
* @return a map of NFX attribute names to values
|
|
||||||
*/
|
|
||||||
private Map<String, String> extractNfxAttributes(javafx.scene.Node node) {
|
|
||||||
Map<String, String> result = new HashMap<>();
|
|
||||||
node.getProperties().forEach((k, v) -> {
|
|
||||||
if (k instanceof String key && key.startsWith("nfx:") && v instanceof String value) {
|
|
||||||
result.put(key, value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -118,6 +281,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)) {
|
||||||
@ -128,61 +292,4 @@ public class ComponentLoader {
|
|||||||
}
|
}
|
||||||
// Additional control types (e.g., CheckBox, ComboBox) can be added here
|
// Additional control types (e.g., CheckBox, ComboBox) can be added here
|
||||||
}
|
}
|
||||||
|
|
||||||
private String preprocessNfxAttributes(String fxmlPath) {
|
|
||||||
try {
|
|
||||||
nfxBindingMap.clear();
|
|
||||||
var factory = javax.xml.parsers.DocumentBuilderFactory.newInstance();
|
|
||||||
var builder = factory.newDocumentBuilder();
|
|
||||||
var doc = builder.parse(getClass().getResourceAsStream(fxmlPath));
|
|
||||||
var all = doc.getElementsByTagName("*");
|
|
||||||
|
|
||||||
int autoId = 0;
|
|
||||||
for (int i = 0; i < all.getLength(); i++) {
|
|
||||||
var el = (org.w3c.dom.Element) all.item(i);
|
|
||||||
Map<String, String> nfxAttrs = new HashMap<>();
|
|
||||||
var attrs = el.getAttributes();
|
|
||||||
|
|
||||||
List<String> toRemove = new ArrayList<>();
|
|
||||||
for (int j = 0; j < attrs.getLength(); j++) {
|
|
||||||
var attr = (org.w3c.dom.Attr) attrs.item(j);
|
|
||||||
if (attr.getName().startsWith("nfx:")) {
|
|
||||||
nfxAttrs.put(attr.getName(), attr.getValue());
|
|
||||||
toRemove.add(attr.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!nfxAttrs.isEmpty()) {
|
|
||||||
String fxid = el.getAttribute("fx:id");
|
|
||||||
if (fxid == null || fxid.isBlank()) {
|
|
||||||
fxid = "auto_id_" + (++autoId);
|
|
||||||
el.setAttribute("fx:id", fxid);
|
|
||||||
}
|
|
||||||
nfxBindingMap.put(fxid, nfxAttrs);
|
|
||||||
toRemove.forEach(el::removeAttribute);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Speichere das bereinigte Dokument als temporäre Datei
|
|
||||||
File tempFile = File.createTempFile("cleaned_fxml", ".fxml");
|
|
||||||
tempFile.deleteOnExit();
|
|
||||||
var transformer = javax.xml.transform.TransformerFactory.newInstance().newTransformer();
|
|
||||||
transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");
|
|
||||||
transformer.transform(new javax.xml.transform.dom.DOMSource(doc),
|
|
||||||
new javax.xml.transform.stream.StreamResult(tempFile));
|
|
||||||
|
|
||||||
return tempFile.toURI().toString();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Preprocessing failed for: " + fxmlPath, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, String> lookupNfxAttributes(javafx.scene.Node node, FXMLLoader loader) {
|
|
||||||
String fxid = loader.getNamespace().entrySet().stream()
|
|
||||||
.filter(e -> e.getValue() == node)
|
|
||||||
.map(Map.Entry::getKey)
|
|
||||||
.findFirst().orElse(null);
|
|
||||||
if (fxid == null) return Map.of();
|
|
||||||
return nfxBindingMap.getOrDefault(fxid, Map.of());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,67 @@
|
|||||||
|
package de.neitzel.fx.component.controls;
|
||||||
|
|
||||||
|
import de.neitzel.fx.component.model.BindingData;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The BindingControl class represents a UI control that manages a list
|
||||||
|
* of {@link BindingData} objects. It extends the {@link Region} class and
|
||||||
|
* provides functionality to bind and monitor connections between source
|
||||||
|
* and target properties.
|
||||||
|
* <p>
|
||||||
|
* The primary purpose of this control is to maintain an observable list
|
||||||
|
* of bindings, allowing developers to track or adjust the linked properties
|
||||||
|
* dynamically.
|
||||||
|
* <p>
|
||||||
|
* The internal list of bindings is implemented as an {@link ObservableList},
|
||||||
|
* allowing property change notifications to be easily monitored for UI
|
||||||
|
* updates or other reactive behaviors.
|
||||||
|
* <p>
|
||||||
|
* This class serves as an organizational component and does not provide
|
||||||
|
* any user interaction by default.
|
||||||
|
*/
|
||||||
|
public class Binding extends Region {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an observable list of {@link BindingData} objects contained within the
|
||||||
|
* {@link Binding} instance. This list is utilized to manage and monitor
|
||||||
|
* the bindings between source and target properties dynamically.
|
||||||
|
* <p>
|
||||||
|
* The list is implemented as an {@link ObservableList}, allowing changes in the
|
||||||
|
* collection to be observed and reacted to, such as triggering UI updates or
|
||||||
|
* responding to binding modifications.
|
||||||
|
* <p>
|
||||||
|
* This field is initialized as an empty list using {@link FXCollections#observableArrayList()}.
|
||||||
|
* It is declared as final to ensure its reference cannot be changed, while the
|
||||||
|
* contents of the list remain mutable.
|
||||||
|
*/
|
||||||
|
private final ObservableList<BindingData> bindings = FXCollections.observableArrayList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new instance of the BindingControl class.
|
||||||
|
* <p>
|
||||||
|
* This default constructor initializes the BindingControl without
|
||||||
|
* any pre-configured bindings. The instance will contain an empty
|
||||||
|
* {@link ObservableList} of {@link BindingData} objects, which can later
|
||||||
|
* be populated as needed.
|
||||||
|
* <p>
|
||||||
|
* The constructor does not perform additional setup or initialization,
|
||||||
|
* allowing the class to be extended or customized as necessary.
|
||||||
|
*/
|
||||||
|
public Binding() {
|
||||||
|
// Empty, the ComponentLoader is used to work on the bindings.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the observable list of {@code Binding} objects associated with this control.
|
||||||
|
* The returned list allows monitoring and management of the bindings maintained
|
||||||
|
* by the {@code BindingControl}.
|
||||||
|
*
|
||||||
|
* @return an {@code ObservableList<Binding>} containing the bindings managed by this control
|
||||||
|
*/
|
||||||
|
public ObservableList<BindingData> getBindings() {
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,196 @@
|
|||||||
|
package de.neitzel.fx.component.controls;
|
||||||
|
|
||||||
|
import de.neitzel.fx.component.ComponentLoader;
|
||||||
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.Parent;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
|
||||||
|
private final StringProperty fxml = new SimpleStringProperty();
|
||||||
|
|
||||||
|
private final StringProperty direction = new SimpleStringProperty("unidirectional");
|
||||||
|
|
||||||
|
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() {
|
||||||
|
fxml.addListener((obs, oldVal, newVal) -> load());
|
||||||
|
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() {
|
||||||
|
if (getFxml() == null || getFxml().isBlank()) return;
|
||||||
|
ComponentLoader loader = new ComponentLoader();
|
||||||
|
Parent content = loader.load(getClass().getResource(getFxml()));
|
||||||
|
|
||||||
|
getChildren().setAll(content);
|
||||||
|
|
||||||
|
Object controller = loader.getController();
|
||||||
|
if (controller != null && getData() != null) {
|
||||||
|
injectDataToController(controller, getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
||||||
|
if (!getChildren().isEmpty() && getData() != null) {
|
||||||
|
Object controller = getControllerFromChild(getChildren().get(0));
|
||||||
|
if (controller != null) {
|
||||||
|
injectDataToController(controller, getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the configured FXML resource path.
|
||||||
|
*
|
||||||
|
* @return the FXML path or {@code null} if not set
|
||||||
|
*/
|
||||||
|
public String getFxml() {
|
||||||
|
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) {
|
||||||
|
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() {
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
// Daten-Objekt per Reflection zuweisen
|
||||||
|
// Beispiel: Controller hat `setData(User data)`
|
||||||
|
Arrays.stream(controller.getClass().getMethods())
|
||||||
|
.filter(m -> m.getName().equals("setData") && m.getParameterCount() == 1)
|
||||||
|
.findFirst()
|
||||||
|
.ifPresent(method -> {
|
||||||
|
try {
|
||||||
|
method.invoke(controller, dataObject);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
if (node.getProperties().containsKey("fx:controller")) {
|
||||||
|
return node.getProperties().get("fx:controller");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the JavaFX property representing the configured FXML path.
|
||||||
|
*
|
||||||
|
* @return the FXML property
|
||||||
|
*/
|
||||||
|
public StringProperty fxmlProperty() {
|
||||||
|
return fxml;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the JavaFX property representing the binding direction hint.
|
||||||
|
*
|
||||||
|
* @return the direction property
|
||||||
|
*/
|
||||||
|
public StringProperty directionProperty() {
|
||||||
|
return direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the configured direction string.
|
||||||
|
*
|
||||||
|
* @return the direction hint (e.g., "unidirectional")
|
||||||
|
*/
|
||||||
|
public String getDirection() {
|
||||||
|
return direction.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the direction hint used by higher-level binding logic.
|
||||||
|
*
|
||||||
|
* @param direction textual direction hint
|
||||||
|
*/
|
||||||
|
public void setDirection(String direction) {
|
||||||
|
this.direction.set(direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the JavaFX property representing the data object used for injection.
|
||||||
|
*
|
||||||
|
* @return the data property
|
||||||
|
*/
|
||||||
|
public ObjectProperty<Object> dataProperty() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
107
fx/src/main/java/de/neitzel/fx/component/model/BindingData.java
Normal file
107
fx/src/main/java/de/neitzel/fx/component/model/BindingData.java
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package de.neitzel.fx.component.model;
|
||||||
|
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Binding class represents a connection between a source and a target,
|
||||||
|
* with an associated direction. It is typically used to define a binding
|
||||||
|
* relationship that determines how data flows between these two entities.
|
||||||
|
* <p>
|
||||||
|
* The class provides three properties:
|
||||||
|
* - direction: Represents the type of the binding. Defaults to "unidirectional".
|
||||||
|
* - source: Represents the source of the binding.
|
||||||
|
* - target: Represents the target of the binding.
|
||||||
|
* <p>
|
||||||
|
* It uses JavaFX property types, allowing these properties to be observed
|
||||||
|
* for changes.
|
||||||
|
*/
|
||||||
|
public class BindingData {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the direction of the binding in the {@code Binding} class.
|
||||||
|
* It determines whether the binding is unidirectional or bidirectional.
|
||||||
|
* The default value is "unidirectional".
|
||||||
|
* <p>
|
||||||
|
* This property is observed for changes, enabling dynamic updates
|
||||||
|
* within the JavaFX property system.
|
||||||
|
*/
|
||||||
|
private StringProperty direction = new SimpleStringProperty("unidirectional");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the source of the binding. This property holds a string value
|
||||||
|
* that specifies the originating object or identifier in the binding connection.
|
||||||
|
* It can be observed for changes, allowing updates to the binding relationship
|
||||||
|
* when the source value is modified.
|
||||||
|
*/
|
||||||
|
private StringProperty source = new SimpleStringProperty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the target of the binding in the Binding class.
|
||||||
|
* This property holds the target value, which can be observed for changes.
|
||||||
|
*/
|
||||||
|
private StringProperty target = new SimpleStringProperty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor only
|
||||||
|
*/
|
||||||
|
public BindingData() {
|
||||||
|
// default constructor only
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current direction of the binding.
|
||||||
|
* The direction determines how data flows between
|
||||||
|
* the source and the target, and typically defaults to "unidirectional".
|
||||||
|
*
|
||||||
|
* @return the current direction of the binding
|
||||||
|
*/
|
||||||
|
public String getDirection() {
|
||||||
|
return direction.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the direction of the binding.
|
||||||
|
*
|
||||||
|
* @param dir the new direction to set for the binding
|
||||||
|
*/
|
||||||
|
public void setDirection(String dir) {
|
||||||
|
direction.set(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current source value of the binding.
|
||||||
|
*
|
||||||
|
* @return the source value as a String.
|
||||||
|
*/
|
||||||
|
public String getSource() {
|
||||||
|
return source.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of the source property for this binding.
|
||||||
|
*
|
||||||
|
* @param source the new source value to be set
|
||||||
|
*/
|
||||||
|
public void setSource(String source) {
|
||||||
|
this.source.set(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the current value of the target property.
|
||||||
|
*
|
||||||
|
* @return the value of the target property as a String.
|
||||||
|
*/
|
||||||
|
public String getTarget() {
|
||||||
|
return target.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the target property of the binding.
|
||||||
|
*
|
||||||
|
* @param target the new value to set for the target property
|
||||||
|
*/
|
||||||
|
public void setTarget(String target) {
|
||||||
|
this.target.set(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
package de.neitzel.fx.injectfx;
|
package de.neitzel.fx.injectfx;
|
||||||
|
|
||||||
import de.neitzel.core.inject.ComponentData;
|
import de.neitzel.injection.ComponentData;
|
||||||
import de.neitzel.core.inject.ComponentScanner;
|
import de.neitzel.injection.ComponentScanner;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
@ -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.
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
package de.neitzel.fx.injectfx;
|
package de.neitzel.fx.injectfx;
|
||||||
|
|
||||||
import de.neitzel.core.inject.ComponentScanner;
|
import de.neitzel.injection.ComponentScanner;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,11 +6,9 @@ import javafx.fxml.FXMLLoader;
|
|||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
import javafx.scene.Parent;
|
import javafx.scene.Parent;
|
||||||
import javafx.scene.control.TextInputControl;
|
import javafx.scene.control.TextInputControl;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
/**
|
/**
|
||||||
* Custom FXMLLoader that binds JavaFX controls to a GenericViewModel using metadata from FXML.
|
* Custom FXMLLoader that binds JavaFX controls to a GenericViewModel using metadata from FXML.
|
||||||
* It supports automatic binding setup based on properties defined in the FXML's node properties.
|
* It supports automatic binding setup based on properties defined in the FXML's node properties.
|
||||||
@ -24,6 +22,17 @@ public class BindingAwareFXMLLoader<T> {
|
|||||||
*/
|
*/
|
||||||
private final T model;
|
private final T model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new instance of the BindingAwareFXMLLoader class.
|
||||||
|
* This loader is designed to load FXML files and automatically bind UI elements
|
||||||
|
* to the properties of the provided model using a binding-aware approach.
|
||||||
|
*
|
||||||
|
* @param model the model object to be used for binding to the UI components
|
||||||
|
*/
|
||||||
|
public BindingAwareFXMLLoader(T model) {
|
||||||
|
this.model = model;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads an FXML file and performs automatic binding setup using the GenericViewModel.
|
* Loads an FXML file and performs automatic binding setup using the GenericViewModel.
|
||||||
*
|
*
|
||||||
@ -68,23 +77,36 @@ public class BindingAwareFXMLLoader<T> {
|
|||||||
if (userData instanceof String propertyName) {
|
if (userData instanceof String propertyName) {
|
||||||
BindDirection direction = getDirection(node);
|
BindDirection direction = getDirection(node);
|
||||||
|
|
||||||
if (node instanceof TextInputControl control) {
|
switch (node) {
|
||||||
|
case TextInputControl control -> {
|
||||||
Property<String> prop = viewModel.property(StringProperty.class, propertyName);
|
Property<String> prop = viewModel.property(StringProperty.class, propertyName);
|
||||||
bind(control.textProperty(), prop, direction);
|
bind(control.textProperty(), prop, direction);
|
||||||
} else if (node instanceof javafx.scene.control.Label label) {
|
}
|
||||||
|
case javafx.scene.control.Label label -> {
|
||||||
Property<String> prop = viewModel.property(StringProperty.class, propertyName);
|
Property<String> prop = viewModel.property(StringProperty.class, propertyName);
|
||||||
bind(label.textProperty(), prop, direction);
|
bind(label.textProperty(), prop, direction);
|
||||||
} else if (node instanceof javafx.scene.control.CheckBox checkBox) {
|
}
|
||||||
|
case javafx.scene.control.CheckBox checkBox -> {
|
||||||
Property<Boolean> prop = viewModel.property(javafx.beans.property.BooleanProperty.class, propertyName);
|
Property<Boolean> prop = viewModel.property(javafx.beans.property.BooleanProperty.class, propertyName);
|
||||||
bind(checkBox.selectedProperty(), prop, direction);
|
bind(checkBox.selectedProperty(), prop, direction);
|
||||||
} else if (node instanceof javafx.scene.control.Slider slider) {
|
}
|
||||||
|
case javafx.scene.control.Slider slider -> {
|
||||||
Property<Number> prop = viewModel.property(javafx.beans.property.DoubleProperty.class, propertyName);
|
Property<Number> prop = viewModel.property(javafx.beans.property.DoubleProperty.class, propertyName);
|
||||||
bind(slider.valueProperty(), prop, direction);
|
bind(slider.valueProperty(), prop, direction);
|
||||||
} else if (node instanceof javafx.scene.control.DatePicker datePicker) {
|
}
|
||||||
|
case javafx.scene.control.DatePicker datePicker -> {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Property<java.time.LocalDate> prop = (Property<java.time.LocalDate>) viewModel.property(javafx.beans.property.ObjectProperty.class, propertyName);
|
Property<java.time.LocalDate> prop =
|
||||||
|
(Property<java.time.LocalDate>) viewModel.property(
|
||||||
|
javafx.beans.property.ObjectProperty.class,
|
||||||
|
propertyName
|
||||||
|
);
|
||||||
bind(datePicker.valueProperty(), prop, direction);
|
bind(datePicker.valueProperty(), prop, direction);
|
||||||
}
|
}
|
||||||
|
default -> {
|
||||||
|
// ignore unsupported nodes
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,6 +38,15 @@ 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.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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.
|
||||||
*
|
*
|
||||||
@ -24,15 +24,16 @@ 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.
|
* A set containing the names of fields to be excluded during JSON serialization.
|
||||||
*
|
* <p>
|
||||||
* This collection is used in the {@link ExcludeFieldsAdapter} to determine which fields should be removed from the
|
* 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
|
* serialized JSON representation of an object. The fields to be excluded are specified during the initialization
|
||||||
* of the adapter.
|
* of the adapter.
|
||||||
@ -41,7 +42,7 @@ public class ExcludeFieldsAdapter<T> extends TypeAdapter<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* An instance of the Gson library used for JSON serialization and deserialization.
|
* An instance of the Gson library used for JSON serialization and deserialization.
|
||||||
*
|
* <p>
|
||||||
* This variable holds the Gson object that is utilized to handle the conversion processes within the
|
* This variable holds the Gson object that is utilized to handle the conversion processes within the
|
||||||
* custom serialization and deserialization logic. If not explicitly provided during initialization,
|
* custom serialization and deserialization logic. If not explicitly provided during initialization,
|
||||||
* it can be lazily initialized using a GsonBuilder.
|
* it can be lazily initialized using a GsonBuilder.
|
||||||
@ -50,10 +51,10 @@ public class ExcludeFieldsAdapter<T> extends TypeAdapter<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link GsonBuilder} instance used for configuring and creating {@link Gson} objects within the adapter.
|
* A {@link GsonBuilder} instance used for configuring and creating {@link Gson} objects within the adapter.
|
||||||
*
|
* <p>
|
||||||
* This variable serves as a reference to a GsonBuilder that allows customization of the Gson instance,
|
* This variable serves as a reference to a GsonBuilder that allows customization of the Gson instance,
|
||||||
* including registering custom type adapters, serializers, and deserializers, as well as adjusting serialization policies.
|
* including registering custom type adapters, serializers, and deserializers, as well as adjusting serialization policies.
|
||||||
*
|
* <p>
|
||||||
* The {@link #gsonBuilder} is primarily utilized when an existing {@link Gson} instance is not directly provided.
|
* 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.
|
* It ensures that the adapter can defer the creation of a Gson object until it is explicitly required.
|
||||||
*/
|
*/
|
||||||
@ -89,19 +90,6 @@ public class ExcludeFieldsAdapter<T> extends TypeAdapter<T> {
|
|||||||
this.gson = null;
|
this.gson = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Lazily initializes and retrieves the {@link Gson} instance. If the instance is null, it creates a new {@link Gson}
|
|
||||||
* object using the {@link GsonBuilder} provided during the adapter's initialization.
|
|
||||||
*
|
|
||||||
* @return the {@link Gson} instance used for serialization and deserialization
|
|
||||||
*/
|
|
||||||
private Gson getGson() {
|
|
||||||
if (gson == null) {
|
|
||||||
gson = gsonBuilder.create();
|
|
||||||
}
|
|
||||||
return gson;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serializes an object of type {@code T} into JSON format, excluding certain fields specified during the initialization of this adapter.
|
* 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
|
* The object is first converted into a {@link JsonObject}, then the fields listed in the excluded fields set are removed from the JSON
|
||||||
@ -122,6 +110,19 @@ public class ExcludeFieldsAdapter<T> extends TypeAdapter<T> {
|
|||||||
Streams.write(obj, out);
|
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.
|
* Reads a JSON input stream and deserializes it into an object of the specified 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;
|
||||||
|
|||||||
270
gson/src/main/java/de/neitzel/gson/config/JsonConfiguration.java
Normal file
270
gson/src/main/java/de/neitzel/gson/config/JsonConfiguration.java
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
package de.neitzel.gson.config;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import lombok.Builder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a configuration utility class that manages application settings and allows
|
||||||
|
* loading and saving the configuration in JSON format. This class is built using Gson for
|
||||||
|
* JSON serialization and deserialization.
|
||||||
|
* <p>
|
||||||
|
* The class maintains application settings in memory and provides methods to store
|
||||||
|
* the configuration on the filesystem and retrieve it as needed.
|
||||||
|
*/
|
||||||
|
@Builder(builderClassName = "ConfigurationBuilder")
|
||||||
|
public class JsonConfiguration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the application.
|
||||||
|
* This variable is used to identify the application and is incorporated into various configurations,
|
||||||
|
* such as the naming of directories or files associated with the application's settings.
|
||||||
|
* It is finalized and set during the construction of the {@code JsonConfiguration} instance and cannot be changed afterward.
|
||||||
|
*/
|
||||||
|
private final String appName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The filesystem path representing the home directory utilized by the application.
|
||||||
|
* This directory serves as the location where application-specific configuration files
|
||||||
|
* and data are stored. The path is initialized when the {@code JsonConfiguration} instance
|
||||||
|
* is constructed, based on the settings provided through the {@code JsonConfigurationBuilder}.
|
||||||
|
* It can be customized to a user-specified value or defaults to the user's home directory.
|
||||||
|
* <p>
|
||||||
|
* The {@code homeDir} variable is integral in determining the location of the main configuration
|
||||||
|
* file and other related resources used by the application.
|
||||||
|
*/
|
||||||
|
private final String homeDir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A map that stores configuration settings as key-value pairs,
|
||||||
|
* where both the keys and values are represented as strings.
|
||||||
|
* <p>
|
||||||
|
* This map is used to manage dynamic or runtime settings for the configuration,
|
||||||
|
* allowing for flexible assignment and retrieval of values associated with specific
|
||||||
|
* configuration keys. It is initialized as a final instance, ensuring it cannot be
|
||||||
|
* reassigned after creation.
|
||||||
|
*/
|
||||||
|
private final Map<String, String> settings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Gson instance used for handling JSON serialization and deserialization within
|
||||||
|
* the JsonConfiguration class. This instance is immutable and customized with
|
||||||
|
* registered adapters during the initialization of the JsonConfiguration.
|
||||||
|
* <p>
|
||||||
|
* The Gson object provides functionality for converting Java objects to JSON
|
||||||
|
* and vice versa. It supports complex serialization and deserialization
|
||||||
|
* workflows by leveraging adapters specified during the configuration phase.
|
||||||
|
* <p>
|
||||||
|
* Adapters can be registered to customize the behavior of (de)serialization
|
||||||
|
* for specific types.
|
||||||
|
*/
|
||||||
|
private final Gson gson;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a key-value pair into the configuration settings.
|
||||||
|
* The value is serialized into a JSON string before being stored.
|
||||||
|
*
|
||||||
|
* @param key the key under which the value will be stored
|
||||||
|
* @param value the object to be associated with the specified key
|
||||||
|
*/
|
||||||
|
public void set(String key, Object value) {
|
||||||
|
settings.put(key, gson.toJson(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a value associated with the specified key from the configuration,
|
||||||
|
* deserializing it into the specified class type using Gson.
|
||||||
|
* If the key does not exist in the settings, the method returns {@code null}.
|
||||||
|
*
|
||||||
|
* @param <T> the type of the object to be deserialized
|
||||||
|
* @param key the key corresponding to the value in the configuration
|
||||||
|
* @param clazz the {@code Class} object representing the expected type of the value
|
||||||
|
* @return the deserialized object of type {@code T}, or {@code null}
|
||||||
|
* if the key does not exist
|
||||||
|
*/
|
||||||
|
public <T> T get(String key, Class<T> clazz) {
|
||||||
|
String json = settings.get(key);
|
||||||
|
return json != null ? gson.fromJson(json, clazz) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a value associated with the given key from the configuration as an object of the specified type.
|
||||||
|
* If the key does not exist in the configuration, the provided default value is returned.
|
||||||
|
*
|
||||||
|
* @param <T> the type of the object to be returned
|
||||||
|
* @param key the key whose associated value is to be retrieved
|
||||||
|
* @param clazz the class of the object to deserialize the value into
|
||||||
|
* @param defaultValue the default value to return if the key does not exist or the value is null
|
||||||
|
* @return the value associated with the specified key, deserialized into the specified type,
|
||||||
|
* or the default value if the key does not exist or the value is null
|
||||||
|
*/
|
||||||
|
public <T> T get(String key, Class<T> clazz, T defaultValue) {
|
||||||
|
String json = settings.get(key);
|
||||||
|
return json != null ? gson.fromJson(json, clazz) : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the configuration data from the JSON configuration file located at the path
|
||||||
|
* determined by the {@code getConfigFilePath()} method.
|
||||||
|
* If the configuration file exists, its content is read and deserialized into a map
|
||||||
|
* of key-value pairs using Gson. The method clears the current settings and populates
|
||||||
|
* them with the loaded values.
|
||||||
|
*
|
||||||
|
* @throws IOException if an I/O error occurs while reading the configuration file
|
||||||
|
*/
|
||||||
|
public void load() throws IOException {
|
||||||
|
Path configPath = getConfigFilePath();
|
||||||
|
if (Files.exists(configPath)) {
|
||||||
|
try (var reader = Files.newBufferedReader(configPath)) {
|
||||||
|
Map<String, String> loaded = gson.fromJson(reader, new TypeToken<Map<String, String>>() {
|
||||||
|
}.getType());
|
||||||
|
settings.clear();
|
||||||
|
settings.putAll(loaded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the path to the configuration file for the application.
|
||||||
|
* The configuration file is located in the user's home directory, within a hidden
|
||||||
|
* folder named after the application, and is named "config.json".
|
||||||
|
*
|
||||||
|
* @return the {@code Path} to the application's configuration file
|
||||||
|
*/
|
||||||
|
private Path getConfigFilePath() {
|
||||||
|
return Path.of(homeDir, "." + appName, "config.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the current configuration to a JSON file.
|
||||||
|
* <p>
|
||||||
|
* This method serializes the current settings into a JSON format and writes
|
||||||
|
* them to a file located at the configuration path. If the parent directory
|
||||||
|
* of the configuration file does not exist, it is created automatically.
|
||||||
|
* <p>
|
||||||
|
* The configuration file path is determined based on the application name
|
||||||
|
* and home directory. Any existing content in the file will be overwritten
|
||||||
|
* during this operation.
|
||||||
|
*
|
||||||
|
* @throws IOException if an I/O error occurs while creating the directory,
|
||||||
|
* opening the file, or writing to the file
|
||||||
|
*/
|
||||||
|
public void save() throws IOException {
|
||||||
|
Path configPath = getConfigFilePath();
|
||||||
|
Files.createDirectories(configPath.getParent());
|
||||||
|
try (var writer = Files.newBufferedWriter(configPath)) {
|
||||||
|
gson.toJson(settings, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builder class for creating instances of {@code JsonConfiguration}.
|
||||||
|
* The {@code JsonConfigurationBuilder} allows the configuration of application
|
||||||
|
* name, home directory, and custom Gson adapters before building a {@code JsonConfiguration} object.
|
||||||
|
*/
|
||||||
|
public static class JsonConfigurationBuilder {
|
||||||
|
/**
|
||||||
|
* A map that holds custom Gson adapters to register with a GsonBuilder.
|
||||||
|
* The keys represent the classes for which the adapters are applicable,
|
||||||
|
* and the values are the adapter instances associated with those classes.
|
||||||
|
* <p>
|
||||||
|
* This variable is used to store user-defined type adapters, allowing
|
||||||
|
* for customized serialization and deserialization behavior for specific
|
||||||
|
* classes when constructing a {@code JsonConfiguration}.
|
||||||
|
* <p>
|
||||||
|
* It is populated using the {@code addGsonAdapter} method in the
|
||||||
|
* {@code JsonConfigurationBuilder} class and is later passed to a
|
||||||
|
* {@code GsonBuilder} for registration.
|
||||||
|
*/
|
||||||
|
private final Map<Class<?>, Object> gsonAdapters = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the application being configured.
|
||||||
|
* This variable holds a string representation of the application's name and is used
|
||||||
|
* to identify the application within the context of the {@code JsonConfiguration}.
|
||||||
|
* It is set during the construction of a {@code JsonConfigurationBuilder} instance.
|
||||||
|
*/
|
||||||
|
private String appName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the home directory path of the current user.
|
||||||
|
* By default, this variable is initialized to the value of the "user.home" system property,
|
||||||
|
* which typically points to the user's home directory on the filesystem.
|
||||||
|
* <p>
|
||||||
|
* This field can be customized to point to a different directory via the builder class
|
||||||
|
* methods when building an instance of {@code JsonConfiguration}.
|
||||||
|
* <p>
|
||||||
|
* Example system values for "user.home" may include paths such as "~/Users/username" for macOS,
|
||||||
|
* "C:/Users/username" for Windows, or "/home/username" for Unix/Linux systems.
|
||||||
|
*/
|
||||||
|
private String homeDir = System.getProperty("user.home");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the application name to be used in the configuration.
|
||||||
|
*
|
||||||
|
* @param appName the name of the application to be set
|
||||||
|
* @return the current instance of {@code JsonConfigurationBuilder} for chaining
|
||||||
|
*/
|
||||||
|
public JsonConfigurationBuilder appName(String appName) {
|
||||||
|
this.appName = appName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the home directory for the configuration being built.
|
||||||
|
* This method specifies the directory path where the application's configuration
|
||||||
|
* files or settings should be stored.
|
||||||
|
*
|
||||||
|
* @param homeDir the path to the desired home directory
|
||||||
|
* @return the current instance of {@code JsonConfigurationBuilder} to enable method chaining
|
||||||
|
*/
|
||||||
|
public JsonConfigurationBuilder homeDir(String homeDir) {
|
||||||
|
this.homeDir = homeDir;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a custom Gson adapter to the builder for the specified type.
|
||||||
|
* This method allows registration of a type-to-adapter mapping that will be applied
|
||||||
|
* when building the Gson instance within the {@code JsonConfiguration}.
|
||||||
|
*
|
||||||
|
* @param type the {@code Class} of the type for which the adapter is being registered
|
||||||
|
* @param adapter the adapter object to be used for the specified type
|
||||||
|
* @return the current instance of {@code JsonConfigurationBuilder}, allowing method chaining
|
||||||
|
*/
|
||||||
|
public JsonConfigurationBuilder addGsonAdapter(Class<?> type, Object adapter) {
|
||||||
|
gsonAdapters.put(type, adapter);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds and returns an instance of {@code JsonConfiguration} with the specified
|
||||||
|
* settings, including the application name, home directory, and any registered
|
||||||
|
* custom Gson adapters.
|
||||||
|
*
|
||||||
|
* @return a {@code JsonConfiguration} instance configured with the builder's parameters.
|
||||||
|
*/
|
||||||
|
public JsonConfiguration build() {
|
||||||
|
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||||
|
for (var entry : gsonAdapters.entrySet()) {
|
||||||
|
gsonBuilder.registerTypeAdapter(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
Gson gson = gsonBuilder.create();
|
||||||
|
|
||||||
|
return new JsonConfiguration(
|
||||||
|
appName,
|
||||||
|
homeDir,
|
||||||
|
new HashMap<>(),
|
||||||
|
gson
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,128 @@
|
|||||||
|
package de.neitzel.gson.config;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
class JsonConfigurationTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test class for JsonConfiguration containing unit tests for the `get` method.
|
||||||
|
* The `get` method fetches the value associated with a given key and converts
|
||||||
|
* it from JSON representation to the specified class type.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetExistingKeyWithValidValue() {
|
||||||
|
// Arrange
|
||||||
|
Gson gson = new Gson();
|
||||||
|
HashMap<String, String> settings = new HashMap<>();
|
||||||
|
settings.put("testKey", gson.toJson(42));
|
||||||
|
JsonConfiguration jsonConfiguration = JsonConfiguration.builder()
|
||||||
|
.settings(settings)
|
||||||
|
.gson(gson)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
Integer value = jsonConfiguration.get("testKey", Integer.class);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(42, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetNonExistingKey() {
|
||||||
|
// Arrange
|
||||||
|
Gson gson = new Gson();
|
||||||
|
HashMap<String, String> settings = new HashMap<>();
|
||||||
|
JsonConfiguration jsonConfiguration = JsonConfiguration.builder()
|
||||||
|
.settings(settings)
|
||||||
|
.gson(gson)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
String value = jsonConfiguration.get("nonExistentKey", String.class);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertNull(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetWithDefaultValueWhenKeyExists() {
|
||||||
|
// Arrange
|
||||||
|
Gson gson = new Gson();
|
||||||
|
HashMap<String, String> settings = new HashMap<>();
|
||||||
|
settings.put("testKey", gson.toJson("Hello World"));
|
||||||
|
JsonConfiguration jsonConfiguration = JsonConfiguration.builder()
|
||||||
|
.settings(settings)
|
||||||
|
.gson(gson)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
String value = jsonConfiguration.get("testKey", String.class, "Default Value");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals("Hello World", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetWithDefaultValueWhenKeyDoesNotExist() {
|
||||||
|
// Arrange
|
||||||
|
Gson gson = new Gson();
|
||||||
|
HashMap<String, String> settings = new HashMap<>();
|
||||||
|
JsonConfiguration jsonConfiguration = JsonConfiguration.builder()
|
||||||
|
.settings(settings)
|
||||||
|
.gson(gson)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
String value = jsonConfiguration.get("nonExistentKey", String.class, "Default Value");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals("Default Value", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetWithComplexObject() {
|
||||||
|
// Arrange
|
||||||
|
Gson gson = new Gson();
|
||||||
|
HashMap<String, String> settings = new HashMap<>();
|
||||||
|
TestObject testObject = new TestObject("John Doe", 30);
|
||||||
|
settings.put("complexKey", gson.toJson(testObject));
|
||||||
|
JsonConfiguration jsonConfiguration = JsonConfiguration.builder()
|
||||||
|
.settings(settings)
|
||||||
|
.gson(gson)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
TestObject result = jsonConfiguration.get("complexKey", TestObject.class);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals("John Doe", result.name());
|
||||||
|
assertEquals(30, result.age());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper class for complex object tests
|
||||||
|
static class TestObject {
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
private final int age;
|
||||||
|
|
||||||
|
TestObject(String name, int age) {
|
||||||
|
this.name = name;
|
||||||
|
this.age = age;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String name() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int age() {
|
||||||
|
return age;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
injection/pom.xml
Normal file
51
injection/pom.xml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project 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">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>de.neitzel.lib</groupId>
|
||||||
|
<artifactId>neitzellib</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>injection</artifactId>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<!-- Application Properties -->
|
||||||
|
<link.name>${project.artifactId}</link.name>
|
||||||
|
<launcher>${project.artifactId}</launcher>
|
||||||
|
<appName>${project.artifactId}</appName>
|
||||||
|
<jar.filename>${project.artifactId}-${project.version}</jar.filename>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.reflections</groupId>
|
||||||
|
<artifactId>reflections</artifactId>
|
||||||
|
<version>${reflections.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.inject</groupId>
|
||||||
|
<artifactId>jakarta.inject-api</artifactId>
|
||||||
|
<version>${jakarta-inject-api}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<finalName>${jar.filename}</finalName>
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.github.spotbugs</groupId>
|
||||||
|
<artifactId>spotbugs-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-pmd-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
package de.neitzel.core.inject;
|
package de.neitzel.injection;
|
||||||
|
|
||||||
import de.neitzel.core.inject.annotation.Config;
|
import de.neitzel.injection.annotation.Config;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -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,7 +52,7 @@ 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.
|
||||||
@ -103,7 +103,7 @@ 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.
|
||||||
@ -118,7 +118,7 @@ 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}
|
||||||
@ -1,10 +1,10 @@
|
|||||||
package de.neitzel.core.inject;
|
package de.neitzel.injection;
|
||||||
|
|
||||||
import lombok.Getter;
|
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
|
||||||
@ -57,7 +57,7 @@ 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
|
||||||
@ -1,11 +1,17 @@
|
|||||||
package de.neitzel.core.inject;
|
package de.neitzel.injection;
|
||||||
|
|
||||||
import de.neitzel.core.inject.annotation.Component;
|
import de.neitzel.injection.annotation.Component;
|
||||||
import de.neitzel.core.inject.annotation.Inject;
|
import de.neitzel.injection.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,7 +166,7 @@ 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
|
||||||
@ -169,13 +175,13 @@ public class ComponentScanner {
|
|||||||
* 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -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
|
||||||
@ -314,25 +339,6 @@ 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.
|
||||||
*
|
*
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package de.neitzel.core.inject;
|
package de.neitzel.injection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the scope of a component in a dependency injection framework.
|
* Represents the scope of a component in a dependency injection framework.
|
||||||
@ -1,6 +1,6 @@
|
|||||||
package de.neitzel.core.inject.annotation;
|
package de.neitzel.injection.annotation;
|
||||||
|
|
||||||
import de.neitzel.core.inject.Scope;
|
import de.neitzel.injection.Scope;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
@ -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.
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package de.neitzel.core.inject.annotation;
|
package de.neitzel.injection.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
@ -10,14 +10,14 @@ 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}.
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package de.neitzel.core.inject.annotation;
|
package de.neitzel.injection.annotation;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
@ -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.
|
||||||
@ -1,13 +1,17 @@
|
|||||||
package de.neitzel.core.inject;
|
package de.neitzel.injection;
|
||||||
|
|
||||||
import de.neitzel.core.inject.testcomponents.test1ok.SuperClass;
|
import de.neitzel.injection.testcomponents.test1ok.SuperClass;
|
||||||
import de.neitzel.core.inject.testcomponents.test1ok.TestComponent1_1;
|
import de.neitzel.injection.testcomponents.test1ok.TestComponent1_1;
|
||||||
import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_1;
|
import de.neitzel.injection.testcomponents.test1ok.TestInterface1_1;
|
||||||
import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_2;
|
import de.neitzel.injection.testcomponents.test1ok.TestInterface1_2;
|
||||||
import de.neitzel.core.inject.testcomponents.test1ok.sub.TestComponent1_2;
|
import de.neitzel.injection.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 {
|
||||||
|
|
||||||
@ -16,7 +20,7 @@ class InjectableComponentScannerTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testLoadComponents() {
|
void testLoadComponents() {
|
||||||
ComponentScanner scanner = new ComponentScanner("de.neitzel.core.inject.testcomponents.test1ok");
|
ComponentScanner scanner = new ComponentScanner("de.neitzel.injection.testcomponents.test1ok");
|
||||||
var instantiableComponents = scanner.getInstantiableComponents();
|
var instantiableComponents = scanner.getInstantiableComponents();
|
||||||
var nonUniqueTypes = scanner.getNotUniqueTypes();
|
var nonUniqueTypes = scanner.getNotUniqueTypes();
|
||||||
|
|
||||||
@ -37,7 +41,7 @@ class InjectableComponentScannerTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testComponentsFailWithUnknownParameters() {
|
void testComponentsFailWithUnknownParameters() {
|
||||||
ComponentScanner scanner = new ComponentScanner("de.neitzel.core.inject.testcomponents.test2fail");
|
ComponentScanner scanner = new ComponentScanner("de.neitzel.injection.testcomponents.test2fail");
|
||||||
var instantiableComponents = scanner.getInstantiableComponents();
|
var instantiableComponents = scanner.getInstantiableComponents();
|
||||||
var nonUniqueTypes = scanner.getNotUniqueTypes();
|
var nonUniqueTypes = scanner.getNotUniqueTypes();
|
||||||
|
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
package de.neitzel.injection.testcomponents.test1ok;
|
||||||
|
|
||||||
|
public class SuperClass {
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
package de.neitzel.core.inject.testcomponents.test1ok;
|
package de.neitzel.injection.testcomponents.test1ok;
|
||||||
|
|
||||||
import de.neitzel.core.inject.annotation.Component;
|
import de.neitzel.injection.annotation.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class TestComponent1_1 extends SuperClass implements TestInterface1_2 {
|
public class TestComponent1_1 extends SuperClass implements TestInterface1_2 {
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
package de.neitzel.injection.testcomponents.test1ok;
|
||||||
|
|
||||||
|
public interface TestInterface1_1 {
|
||||||
|
}
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
package de.neitzel.injection.testcomponents.test1ok;
|
||||||
|
|
||||||
|
public interface TestInterface1_2 {
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package de.neitzel.injection.testcomponents.test1ok.sub;
|
||||||
|
|
||||||
|
import de.neitzel.injection.annotation.Component;
|
||||||
|
import de.neitzel.injection.testcomponents.test1ok.SuperClass;
|
||||||
|
import de.neitzel.injection.testcomponents.test1ok.TestInterface1_1;
|
||||||
|
import de.neitzel.injection.testcomponents.test1ok.TestInterface1_2;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class TestComponent1_2 extends SuperClass implements TestInterface1_1, TestInterface1_2 {
|
||||||
|
public TestComponent1_2() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
package de.neitzel.core.inject.testcomponents.test2fail;
|
package de.neitzel.injection.testcomponents.test2fail;
|
||||||
|
|
||||||
import de.neitzel.core.inject.annotation.Component;
|
import de.neitzel.injection.annotation.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TestComponent1 that should fail.
|
* TestComponent1 that should fail.
|
||||||
@ -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,6 +54,13 @@ 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.
|
||||||
@ -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!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
43
pom.xml
43
pom.xml
@ -13,6 +13,7 @@
|
|||||||
<modules>
|
<modules>
|
||||||
<module>core</module>
|
<module>core</module>
|
||||||
<module>encryption</module>
|
<module>encryption</module>
|
||||||
|
<module>injection</module>
|
||||||
<module>fx</module>
|
<module>fx</module>
|
||||||
<module>gson</module>
|
<module>gson</module>
|
||||||
<module>net</module>
|
<module>net</module>
|
||||||
@ -22,38 +23,39 @@
|
|||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>21</java.version>
|
<java.version>25</java.version>
|
||||||
<required.maven.version>3.8.6</required.maven.version>
|
<required.maven.version>3.8.6</required.maven.version>
|
||||||
|
|
||||||
<!-- Dependency versions -->
|
<!-- Dependency versions -->
|
||||||
<javafx.version>21.0.3</javafx.version>
|
<jakarta-inject-api>2.0.1</jakarta-inject-api>
|
||||||
|
<javafx.version>25.0.1</javafx.version>
|
||||||
<jetbrains.annotations.version>26.0.2</jetbrains.annotations.version>
|
<jetbrains.annotations.version>26.0.2</jetbrains.annotations.version>
|
||||||
<junit.version>5.12.1</junit.version>
|
<junit.version>6.0.1</junit.version>
|
||||||
<lombok.version>1.18.38</lombok.version>
|
<lombok.version>1.18.42</lombok.version>
|
||||||
<mockito.version>5.16.1</mockito.version>
|
<mockito.version>5.20.0</mockito.version>
|
||||||
<reflections.version>0.10.2</reflections.version>
|
<reflections.version>0.10.2</reflections.version>
|
||||||
<slf4j.version>2.0.17</slf4j.version>
|
<slf4j.version>2.0.17</slf4j.version>
|
||||||
|
|
||||||
<!-- Plugin dependencies -->
|
<!-- Plugin dependencies -->
|
||||||
<codehaus.version.plugin>2.16.2</codehaus.version.plugin>
|
<codehaus.version.plugin>2.20.1</codehaus.version.plugin>
|
||||||
<javafx.maven.plugin>0.0.8</javafx.maven.plugin>
|
<javafx.maven.plugin>0.0.8</javafx.maven.plugin>
|
||||||
<jpackage.maven.plugin>0.1.5</jpackage.maven.plugin>
|
<jpackage.maven.plugin>0.1.5</jpackage.maven.plugin>
|
||||||
<maven.clean.plugin>3.3.2</maven.clean.plugin>
|
<maven.clean.plugin>3.5.0</maven.clean.plugin>
|
||||||
<maven.compiler.plugin>3.13.0</maven.compiler.plugin>
|
<maven.compiler.plugin>3.14.1</maven.compiler.plugin>
|
||||||
<maven.dependency.plugin>3.6.1</maven.dependency.plugin>
|
<maven.dependency.plugin>3.6.1</maven.dependency.plugin>
|
||||||
<maven.deploy.plugin>3.1.2</maven.deploy.plugin>
|
<maven.deploy.plugin>3.1.4</maven.deploy.plugin>
|
||||||
<maven.enforcer.plugin>3.4.1</maven.enforcer.plugin>
|
<maven.enforcer.plugin>3.4.1</maven.enforcer.plugin>
|
||||||
<maven.install.plugin>3.1.2</maven.install.plugin>
|
<maven.install.plugin>3.1.2</maven.install.plugin>
|
||||||
<maven.jar.plugin>3.4.1</maven.jar.plugin>
|
<maven.jar.plugin>3.5.0</maven.jar.plugin>
|
||||||
<maven.javadoc.plugin>3.6.3</maven.javadoc.plugin>
|
<maven.javadoc.plugin>3.12.0</maven.javadoc.plugin>
|
||||||
<maven.project.info.reports.plugin>3.5.0</maven.project.info.reports.plugin>
|
<maven.project.info.reports.plugin>3.9.0</maven.project.info.reports.plugin>
|
||||||
<maven.resources.plugin>3.3.1</maven.resources.plugin>
|
<maven.resources.plugin>3.4.0</maven.resources.plugin>
|
||||||
<maven.shade.plugin>3.5.3</maven.shade.plugin>
|
<maven.shade.plugin>3.5.3</maven.shade.plugin>
|
||||||
<maven.site.plugin>4.0.0-M14</maven.site.plugin>
|
<maven.site.plugin>4.0.0-M16</maven.site.plugin>
|
||||||
<maven.surfire.plugin>3.2.5</maven.surfire.plugin>
|
<maven.surfire.plugin>3.5.4</maven.surfire.plugin>
|
||||||
<moditect.maven.plugin>1.0.0.RC2</moditect.maven.plugin>
|
<moditect.maven.plugin>1.0.0.RC2</moditect.maven.plugin>
|
||||||
<maven.pmd.plugin>3.22.0</maven.pmd.plugin>
|
<maven.pmd.plugin>3.28.0</maven.pmd.plugin>
|
||||||
<spotbugs.maven.plugin>4.8.5.0</spotbugs.maven.plugin>
|
<spotbugs.maven.plugin>4.9.8.2</spotbugs.maven.plugin>
|
||||||
|
|
||||||
<!-- other properties -->
|
<!-- other properties -->
|
||||||
<pmd.target>pmd</pmd.target>
|
<pmd.target>pmd</pmd.target>
|
||||||
@ -274,6 +276,9 @@
|
|||||||
<goals>
|
<goals>
|
||||||
<goal>jar</goal>
|
<goal>jar</goal>
|
||||||
</goals>
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<sourcepath>${project.build.sourceDirectory}</sourcepath>
|
||||||
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
@ -347,15 +352,19 @@
|
|||||||
<report>javadoc</report>
|
<report>javadoc</report>
|
||||||
</reports>
|
</reports>
|
||||||
</reportSet>
|
</reportSet>
|
||||||
|
<!--
|
||||||
<reportSet>
|
<reportSet>
|
||||||
<id>tests</id>
|
<id>tests</id>
|
||||||
<configuration>
|
<configuration>
|
||||||
<show>private</show>
|
<show>private</show>
|
||||||
|
<failOnError>false</failOnError>
|
||||||
|
<quiet>true</quiet>
|
||||||
</configuration>
|
</configuration>
|
||||||
<reports>
|
<reports>
|
||||||
<report>test-javadoc</report>
|
<report>test-javadoc</report>
|
||||||
</reports>
|
</reports>
|
||||||
</reportSet>
|
</reportSet>
|
||||||
|
-->
|
||||||
</reportSets>
|
</reportSets>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user