Merge pull request 'feature/github-copilot-consolidation' (#1) from feature/github-copilot-consolidation into main
Reviewed-on: #1
This commit is contained in:
commit
0c5b92bd76
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
|
||||
# specific language governing permissions and limitations
|
||||
# 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
|
||||
|
||||
29
README.md
29
README.md
@ -2,24 +2,32 @@
|
||||
|
||||
## 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.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
|
||||
@ -41,10 +49,19 @@ Some JavaFX additions.
|
||||
|
||||
Some example programs that use the fx component
|
||||
|
||||
### gson
|
||||
|
||||
TypeAdapter for gson.
|
||||
|
||||
### 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
|
||||
|
||||
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"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
<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>
|
||||
|
||||
|
||||
@ -6,10 +6,10 @@ import java.util.Iterator;
|
||||
* The ArgumentProvider class is a helper utility for iterating over an array
|
||||
* of command-line arguments. It provides methods to check for available arguments
|
||||
* and retrieve them in sequence.
|
||||
*
|
||||
* This class is designed to work seamlessly with a command-line parser
|
||||
* and handle tokenized inputs efficiently. It implements the Iterator interface
|
||||
* for ease of iteration.
|
||||
* <p>
|
||||
* This class is designed to work seamlessly with a command-line parser and handle
|
||||
* tokenized inputs efficiently. It implements the {@link Iterator} interface for ease
|
||||
* of iteration and provides convenience methods for peeking ahead.
|
||||
*/
|
||||
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}
|
||||
* class to iterate through and retrieve arguments systematically.
|
||||
*/
|
||||
private String[] arguments;
|
||||
private final String[] arguments;
|
||||
|
||||
/**
|
||||
* Tracks the current position within an array of arguments.
|
||||
*
|
||||
* This variable is used to index into the arguments array, enabling
|
||||
* sequential access to command-line tokens. It is incremented as arguments
|
||||
* are processed or retrieved using methods such as {@code next()} or {@code peek()}
|
||||
* in the {@link ArgumentProvider} class.
|
||||
*
|
||||
* Its value starts at 0 and increases until it reaches the length of
|
||||
* the provided arguments array, at which point iteration ends.
|
||||
* This variable is used to index into the arguments array, enabling sequential access
|
||||
* to command-line tokens. It is incremented as arguments are processed or retrieved
|
||||
* using methods such as {@code next()} or {@code peek()} in the {@link ArgumentProvider}
|
||||
* class. 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;
|
||||
|
||||
@ -43,21 +40,12 @@ public class ArgumentProvider implements Iterator<String> {
|
||||
*/
|
||||
public ArgumentProvider(final String[] arguments) {
|
||||
if (arguments == null) {
|
||||
this.arguments = new String[] {};
|
||||
this.arguments = new String[]{};
|
||||
} else {
|
||||
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.
|
||||
*
|
||||
@ -68,6 +56,26 @@ public class ArgumentProvider implements Iterator<String> {
|
||||
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.
|
||||
* If no more arguments are available, returns {@code null}.
|
||||
@ -81,15 +89,4 @@ public class ArgumentProvider implements Iterator<String> {
|
||||
current++;
|
||||
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,
|
||||
* such as constraints on the number of values it accepts, descriptions, and aliases.
|
||||
* 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
|
||||
* structured handling of arguments provided via the command line.
|
||||
*/
|
||||
@ -23,14 +23,141 @@ import java.util.function.Consumer;
|
||||
@Setter
|
||||
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.
|
||||
*
|
||||
* <p>
|
||||
* This class encapsulates the metadata and configuration of a command-line parameter,
|
||||
* 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
|
||||
* recognizes and processes.
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
* command provides usage information for users.
|
||||
@ -64,131 +191,4 @@ public class Parameter {
|
||||
this.isDefaultParameter = isDefaultParameter;
|
||||
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 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.
|
||||
@ -15,11 +19,11 @@ public class Parser {
|
||||
/**
|
||||
* A map to store parameters where the key is a string representing
|
||||
* the parameter name and the value is the corresponding {@link Parameter} object.
|
||||
*
|
||||
* <p>
|
||||
* This map serves as a registry for defining, organizing, and accessing
|
||||
* command-line parameters. Each entry holds a parameter's metadata and
|
||||
* its associated configuration to facilitate effective command-line parsing.
|
||||
*
|
||||
* <p>
|
||||
* Key considerations:
|
||||
* - The key represents the primary name of the parameter.
|
||||
* - 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.
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
* of positional arguments or other free-form input provided by the user.
|
||||
*
|
||||
* <p>
|
||||
* It is essential to note:
|
||||
* - 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`
|
||||
* should not disallow input).
|
||||
* - This parameter is automatically invoked if an input does not match any explicitly named
|
||||
* parameter or alias in the parser configuration.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*/
|
||||
@ -48,12 +52,12 @@ public class Parser {
|
||||
|
||||
/**
|
||||
* Constructs a new instance of the `Parser` class.
|
||||
*
|
||||
* <p>
|
||||
* The `Parser` class is responsible for parsing and processing
|
||||
* command-line arguments. It interprets input data based on defined
|
||||
* parameters and provides structured access to arguments, enabling
|
||||
* streamlined application configuration and execution.
|
||||
*
|
||||
* <p>
|
||||
* This constructor initializes the parser without any predefined
|
||||
* configuration or parameters. Users can define parameters,
|
||||
* 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.
|
||||
* The parameter must fulfill specific constraints if defined as the default
|
||||
* parameter (e.g., accepting values).
|
||||
*
|
||||
* @throws IllegalArgumentException if the parameter is defined as a default parameter and
|
||||
* does not accept values (both minNumberValues and maxNumberValues
|
||||
* are set to 0).
|
||||
@ -81,7 +84,7 @@ public class Parser {
|
||||
parameters.put(parameter.getName().toLowerCase(), parameter);
|
||||
|
||||
// Add for all aliases
|
||||
for (String alias: parameter.getAliasList()) {
|
||||
for (String alias : parameter.getAliasList()) {
|
||||
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
|
||||
* 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 ("-").
|
||||
* @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.
|
||||
*/
|
||||
protected boolean isParameter(final String param) {
|
||||
if (parameters.containsKey(param)) return true;
|
||||
if (param.startsWith("-")) return false;
|
||||
return defaultParameter != null;
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 registered parameters, and invokes the corresponding processing callback if applicable.
|
||||
* 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}
|
||||
* within the range specified by the minimum and maximum values.
|
||||
@ -182,35 +216,4 @@ public class Parser {
|
||||
|
||||
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;
|
||||
|
||||
import de.neitzel.core.util.FileUtils;
|
||||
import de.neitzel.core.io.FileUtils;
|
||||
import de.neitzel.core.util.Strings;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@ -28,7 +28,7 @@ public class Configuration {
|
||||
* A {@link Properties} object that stores a set of key-value pairs.
|
||||
* This variable can be used to manage configuration settings or other
|
||||
* collections of properties within the application.
|
||||
*
|
||||
* <p>
|
||||
* It provides methods to load, retrieve, and modify properties
|
||||
* as necessary for the application's requirements.
|
||||
*/
|
||||
@ -62,7 +62,7 @@ public class Configuration {
|
||||
*/
|
||||
protected boolean getBooleanProperty(final String key, final boolean 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
* removes surrounding quotes if present, and expands any environment variables found
|
||||
@ -118,6 +106,18 @@ public class Configuration {
|
||||
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
|
||||
* not exist in the properties or the value is null/empty, the provided default
|
||||
@ -146,7 +146,7 @@ public class Configuration {
|
||||
if (value == null) {
|
||||
properties.setProperty(key, "");
|
||||
} 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
|
||||
*/
|
||||
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());
|
||||
}
|
||||
}
|
||||
@ -261,7 +261,7 @@ public class Configuration {
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,19 @@ import java.io.InputStream;
|
||||
@Slf4j
|
||||
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.
|
||||
*/
|
||||
@ -24,51 +37,9 @@ public class ImageScaler {
|
||||
throw new UnsupportedOperationException("Utility class cannot be instantiated");
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the target image format for scaling operations.
|
||||
*
|
||||
* This variable defines the file format that will be used when writing
|
||||
* or encoding the scaled image. The format must be compatible with
|
||||
* Java's ImageIO framework (e.g., "png", "jpg", "bmp").
|
||||
*
|
||||
* In this case, the target format is set to "png", allowing images
|
||||
* to be scaled and saved or returned as PNG files. It ensures
|
||||
* consistent output format across the image scaling methods in the class.
|
||||
*/
|
||||
private static String TARGET_FORMAT="png";
|
||||
|
||||
/**
|
||||
* Creates a scaled image from the given byte array representation and returns it as a byte array.
|
||||
*
|
||||
* If enforceSize is set to false, the given dimensions are used as maximum values for scaling.
|
||||
* The resulting image may retain its aspect ratio and be smaller than the specified dimensions.
|
||||
* If enforceSize is true, the resulting image will match the exact dimensions, potentially adding
|
||||
* transparent areas on the top/bottom or right/left sides to maintain the original aspect ratio.
|
||||
*
|
||||
* @param originalImageBytes The byte array representing the original image.
|
||||
* @param width The (maximum) width of the target image.
|
||||
* @param height The (maximum) height of the target image.
|
||||
* @param enforceSize A flag indicating whether the resulting image should strictly adhere to specified dimensions.
|
||||
* If false, the image will retain its aspect ratio without empty borders.
|
||||
* @return A byte array representing the scaled image, or null if the operation failed or the input was invalid.
|
||||
*/
|
||||
public static byte[] createScaledImage(final byte[] originalImageBytes, final int width, final int height, final boolean enforceSize) {
|
||||
// Validation
|
||||
if (originalImageBytes == null) return null;
|
||||
if (originalImageBytes.length==0) return null;
|
||||
|
||||
try {
|
||||
// Create the image from a byte array.
|
||||
BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(originalImageBytes));
|
||||
return createScaledImage(originalImage, width, height, enforceSize);
|
||||
} catch (Exception ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales an image provided as a byte array to the specified dimensions and returns an InputStream of the scaled image.
|
||||
*
|
||||
* <p>
|
||||
* The scaling behavior is determined by the enforceSize parameter:
|
||||
* - If enforceSize is false, the provided dimensions are treated as maximum values, maintaining the original aspect ratio.
|
||||
* - If enforceSize is true, the resulting image will match the exact dimensions, potentially with transparent borders to keep the original aspect ratio.
|
||||
@ -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:
|
||||
* - 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
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
package de.neitzel.core.io;
|
||||
|
||||
import de.neitzel.core.util.FileUtils;
|
||||
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.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,
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* This class is useful for applications needing to process text files in specific encodings and ensures
|
||||
* encoding compatibility.
|
||||
*/
|
||||
@ -25,7 +28,7 @@ public class ConvertedEncodingFileReader extends InputStreamReader {
|
||||
* 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.
|
||||
* The default value is set to "ISO-8859-15".
|
||||
*
|
||||
* <p>
|
||||
* Modifying this variable requires careful consideration, as it affects
|
||||
* the behavior of methods that rely on encoding validation, particularly
|
||||
* 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";
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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 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.
|
||||
* @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
|
||||
*/
|
||||
private static void setCheckEncoding(final String encoding) {
|
||||
if (Charset.forName(encoding) != null) throw new IllegalCharsetNameException("Encoding " + encoding + " is not supported!");
|
||||
|
||||
checkEncoding = encoding;
|
||||
public ConvertedEncodingFileReader(final String filename, final String targetFormat) throws IOException {
|
||||
this(new File(filename), targetFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,19 +62,6 @@ public class ConvertedEncodingFileReader extends InputStreamReader {
|
||||
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.
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Utility class for handling file operations, such as encoding checks, content reading/writing,
|
||||
* path manipulations, and file conversions.
|
||||
* A utility class for file-related operations. This class provides functionalities
|
||||
* for handling files and directories in an efficient manner.
|
||||
*/
|
||||
@Slf4j
|
||||
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.
|
||||
* 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
|
||||
* 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)
|
||||
* Determines if the content of the given file is encoded in UTF-8.
|
||||
*
|
||||
* 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");
|
||||
|
||||
/**
|
||||
* 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;
|
||||
public static boolean isUTF8(final File file) throws IOException {
|
||||
return isUTF8(file, DEFAULT_CHECK_ENCODING);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*
|
||||
* <p>
|
||||
* 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).
|
||||
*
|
||||
@ -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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
@ -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.
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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);
|
||||
if (skipBOM) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
@ -294,4 +304,60 @@ public class FileUtils {
|
||||
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("&");
|
||||
if (indexAmp == -1) {
|
||||
result.append(remaining);
|
||||
remaining="";
|
||||
remaining = "";
|
||||
} else {
|
||||
// First get the elements till the &
|
||||
if (indexAmp > 0) {
|
||||
result.append(remaining.substring(0, 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 + ")");
|
||||
String special = remaining.substring(0, endSpecial+1);
|
||||
remaining = remaining.substring(endSpecial+1);
|
||||
String special = remaining.substring(0, endSpecial + 1);
|
||||
remaining = remaining.substring(endSpecial + 1);
|
||||
result.append(decodeCharacter(special));
|
||||
}
|
||||
}
|
||||
@ -67,7 +67,7 @@ public class StringEncoder {
|
||||
public static char decodeCharacter(final String 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 + ")");
|
||||
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 "";
|
||||
|
||||
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);
|
||||
if (character < 32 || character > 127 || character == 38) {
|
||||
result.append("&#" + (int)character + ";");
|
||||
result.append("&#" + (int) character + ";");
|
||||
} else {
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@ -64,18 +78,4 @@ public class ToneGenerator {
|
||||
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
|
||||
* retrieve the frequency of a tone based on its standard notation name,
|
||||
* such as "C3", "A4", or "G#5".
|
||||
*
|
||||
* <p>
|
||||
* This class can be particularly useful in applications related to sound synthesis,
|
||||
* music theory, signal processing, and other audio-related domains that require precise
|
||||
* frequency information for specific tones.
|
||||
*/
|
||||
public class ToneTable {
|
||||
/**
|
||||
* 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).
|
||||
* The keys represent tone names (e.g., "C4", "D#5"), and the values are their respective frequencies.
|
||||
* This map serves as a reference for converting tone names into their precise frequency values,
|
||||
* which are used in applications such as tone generation or audio playback.
|
||||
*
|
||||
* <p>
|
||||
* The map is a crucial component of the ToneTable class, providing quick lookup of frequencies
|
||||
* for predefined tone names.
|
||||
*/
|
||||
private static final HashMap<String, Double> toneMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
toneMap.put("C0", 16.35);
|
||||
toneMap.put("C#0", 17.32);
|
||||
@ -191,6 +180,18 @@ public class ToneTable {
|
||||
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.
|
||||
* 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.ResultSet;
|
||||
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.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
@ -89,6 +96,20 @@ public class Query<T> {
|
||||
.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.
|
||||
*
|
||||
@ -100,6 +121,21 @@ public class Query<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a PreparedStatement object configured with the query text
|
||||
* and parameters defined for the current instance.
|
||||
*
|
||||
* @return a PreparedStatement object initialized with the query and parameter values
|
||||
* @throws SQLException if a database access error occurs or the PreparedStatement cannot be created
|
||||
*/
|
||||
private PreparedStatement createPreparedStatement() throws SQLException {
|
||||
PreparedStatement ps = connection.prepareStatement(queryText);
|
||||
for (ParameterSetter setter : parameterSetters) {
|
||||
setter.apply(ps);
|
||||
}
|
||||
return ps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the query resource by loading its content from the given resource path.
|
||||
* The resource content is read and stored as the query text for the query object.
|
||||
@ -166,6 +202,22 @@ public class Query<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a managed stream to the given handler and ensures proper resource closing after use.
|
||||
* A {@code Stream} is opened and passed to the specified handler as an argument.
|
||||
* The stream will be automatically closed when the handler finishes execution or throws an exception.
|
||||
*
|
||||
* @param handler a function which receives a {@code Stream<T>} and processes it, returning a result of type {@code R}.
|
||||
* @param <R> the type of the result produced by the handler.
|
||||
* @return the result returned by the handler after processing the stream.
|
||||
* @throws SQLException if an SQL error occurs during the creation or handling of the stream.
|
||||
*/
|
||||
public <R> R withStream(Function<Stream<T>, R> handler) throws SQLException {
|
||||
try (Stream<T> stream = stream()) {
|
||||
return handler.apply(stream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Stream} from the result set obtained by executing a prepared statement.
|
||||
* The result set is wrapped in a {@link TrimmingResultSet} to handle data trimming.
|
||||
@ -179,35 +231,6 @@ public class Query<T> {
|
||||
return streamFromResultSet(trimmingResultSet, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a PreparedStatement object configured with the query text
|
||||
* and parameters defined for the current instance.
|
||||
*
|
||||
* @return a PreparedStatement object initialized with the query and parameter values
|
||||
* @throws SQLException if a database access error occurs or the PreparedStatement cannot be created
|
||||
*/
|
||||
private PreparedStatement createPreparedStatement() throws SQLException {
|
||||
PreparedStatement ps = connection.prepareStatement(queryText);
|
||||
for (ParameterSetter setter : parameterSetters) {
|
||||
setter.apply(ps);
|
||||
}
|
||||
return ps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the SQL statement represented by the PreparedStatement created in
|
||||
* the method {@code createPreparedStatement()}. This method is used to perform
|
||||
* database operations such as updates or other actions that do not produce a
|
||||
* result set.
|
||||
*
|
||||
* @throws SQLException if a database access error occurs or the SQL statement is invalid.
|
||||
*/
|
||||
public void execute() throws SQLException {
|
||||
try (PreparedStatement ps = createPreparedStatement()) {
|
||||
ps.execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a {@link ResultSet} into a {@link Stream} of objects created using the provided {@link Function}
|
||||
* factory. The stream ensures resources such as the {@code ResultSet} and its associated statement are
|
||||
@ -220,6 +243,7 @@ public class Query<T> {
|
||||
private Stream<T> streamFromResultSet(final ResultSet rs, final Function<ResultSet, T> factory) {
|
||||
Iterator<T> iterator = new Iterator<>() {
|
||||
private boolean hasNextChecked = false;
|
||||
|
||||
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
|
||||
* 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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
@ -41,7 +41,7 @@ public class ArrayUtils {
|
||||
* @return true if the array contains the specified integer value, false otherwise
|
||||
*/
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
@ -55,7 +55,7 @@ public class ArrayUtils {
|
||||
* @return true if the array contains the specified long value, false otherwise
|
||||
*/
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
@ -70,7 +70,7 @@ public class ArrayUtils {
|
||||
* @return true if the element is found in the array, false otherwise
|
||||
*/
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
|
||||
@ -33,9 +33,9 @@ public class EnumUtil {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append("(|,|\\s");
|
||||
|
||||
for (T flag: clazz.getEnumConstants()) {
|
||||
for (T flag : clazz.getEnumConstants()) {
|
||||
result.append("|");
|
||||
for(char ch: flag.toString().toUpperCase().toCharArray()) {
|
||||
for (char ch : flag.toString().toUpperCase().toCharArray()) {
|
||||
if (Character.isAlphabetic(ch)) {
|
||||
result.append("[");
|
||||
result.append(ch);
|
||||
@ -52,7 +52,7 @@ public class EnumUtil {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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
|
||||
@ -70,7 +70,7 @@ public class EnumUtil {
|
||||
public static <T extends Enum<T>> List<T> parseFlags(final Class<T> clazz, final String flags) {
|
||||
List<T> result = new ArrayList<>();
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ public class RegionalizationMessage {
|
||||
* which is used to retrieve locale-specific objects and messages.
|
||||
* It facilitates the process of internationalization by loading resources
|
||||
* such as text and messages from specified bundles based on the provided locale.
|
||||
*
|
||||
* <p>
|
||||
* This variable is initialized in constructors of the `RegionalizationMessage` class
|
||||
* and is used internally by various methods to fetch localized messages.
|
||||
*/
|
||||
@ -57,6 +57,21 @@ public class RegionalizationMessage {
|
||||
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.
|
||||
* If the key is not found, the specified default message is returned.
|
||||
@ -72,19 +87,4 @@ public class RegionalizationMessage {
|
||||
|
||||
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.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class StopWatch {
|
||||
|
||||
/**
|
||||
@ -14,12 +15,29 @@ public class StopWatch {
|
||||
* duration between the start and the stop or the current time.
|
||||
*/
|
||||
private Instant startTime;
|
||||
|
||||
/**
|
||||
* Represents the stopping time of the stopwatch. This variable is set when the stopwatch is stopped and is used to calculate the elapsed
|
||||
* duration between the start and stop times.
|
||||
*/
|
||||
private Instant stopTime;
|
||||
|
||||
/**
|
||||
* 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,
|
||||
* 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
|
||||
* stopwatch is still running.
|
||||
* Provides a compact and human-readable formatted string representing the elapsed time measured by the stopwatch. The method formats the
|
||||
* duration using a combination of days, hours, minutes, seconds, and milliseconds. This compact format removes additional verbosity in
|
||||
* favor of a shorter, more concise representation.
|
||||
*
|
||||
* @return the duration representing the elapsed time between the start and end points of the stopwatch.
|
||||
* @throws IllegalStateException if the stopwatch has not been started before calling this method.
|
||||
* @return a compact formatted string representing the elapsed time, including days, hours, minutes, seconds, and milliseconds as
|
||||
* applicable
|
||||
*/
|
||||
public Duration getUsedTime() {
|
||||
if (startTime == null) {
|
||||
throw new IllegalStateException("StopWatch has not been started.");
|
||||
}
|
||||
Instant end = (stopTime != null) ? stopTime : Instant.now();
|
||||
return Duration.between(startTime, end);
|
||||
public String getUsedTimeFormattedCompact() {
|
||||
return getUsedTimeFormatted(true, true, true, true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,13 +108,13 @@ public class StopWatch {
|
||||
if (includeDays && days > 0) {
|
||||
sb.append(days).append("d ");
|
||||
}
|
||||
if (includeHours && (hours > 0 || sb.length() > 0)) {
|
||||
if (includeHours && (hours > 0 || !sb.isEmpty())) {
|
||||
sb.append(hours).append("h ");
|
||||
}
|
||||
if (includeMinutes && (minutes > 0 || sb.length() > 0)) {
|
||||
if (includeMinutes && (minutes > 0 || !sb.isEmpty())) {
|
||||
sb.append(minutes).append("m ");
|
||||
}
|
||||
if (includeSeconds && (seconds > 0 || sb.length() > 0)) {
|
||||
if (includeSeconds && (seconds > 0 || !sb.isEmpty())) {
|
||||
sb.append(seconds);
|
||||
}
|
||||
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
|
||||
* 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.
|
||||
* Calculates the amount of time elapsed between the stopwatch's starting time and either its stopping time or the current time if the
|
||||
* stopwatch is still running.
|
||||
*
|
||||
* @return a compact formatted string representing the elapsed time, including days, hours, minutes, seconds, and milliseconds as
|
||||
* applicable
|
||||
* @return the duration representing the elapsed time between the start and end points of the stopwatch.
|
||||
* @throws IllegalStateException if the stopwatch has not been started before calling this method.
|
||||
*/
|
||||
public String getUsedTimeFormattedCompact() {
|
||||
return getUsedTimeFormatted(true, true, true, true, true);
|
||||
public Duration getUsedTime() {
|
||||
if (startTime == null) {
|
||||
throw new IllegalStateException("StopWatch has not been started.");
|
||||
}
|
||||
Instant end = (stopTime != null) ? stopTime : Instant.now();
|
||||
return Duration.between(startTime, end);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,21 @@ import java.util.Map;
|
||||
* Utility class providing common string manipulation methods.
|
||||
*/
|
||||
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.
|
||||
* 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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
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) {
|
||||
String trimmedValue = value.trim();
|
||||
if (trimmedValue.length() > 1 && trimmedValue.startsWith("\"") && trimmedValue.endsWith("\""))
|
||||
return trimmedValue.substring(1, trimmedValue.length()-1);
|
||||
return trimmedValue.substring(1, trimmedValue.length() - 1);
|
||||
|
||||
return value;
|
||||
}
|
||||
@ -131,11 +131,11 @@ public class Strings {
|
||||
public static String increment(@NonNull final String element) {
|
||||
if (element.isEmpty()) return "1";
|
||||
|
||||
String firstPart = element.substring(0, element.length()-1);
|
||||
char lastChar = element.charAt(element.length()-1);
|
||||
String firstPart = element.substring(0, element.length() - 1);
|
||||
char lastChar = element.charAt(element.length() - 1);
|
||||
if (lastChar == '9') return firstPart + 'A';
|
||||
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.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.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
@ -25,6 +30,16 @@ import java.util.Date;
|
||||
*/
|
||||
@Slf4j
|
||||
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.
|
||||
* 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
|
||||
* to ensure consistent date representations in XML or other similar text-based formats.
|
||||
*
|
||||
* Thread-safe and immutable, this formatter can be shared across multiple threads.
|
||||
* @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 final DateTimeFormatter XML_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
|
||||
/**
|
||||
* 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);
|
||||
public static Element createElement(final Document doc, final String name, final Date value) {
|
||||
return createElement(doc, name, XML_DATE_FORMATTER.format(value.toInstant()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,19 +85,6 @@ public class XmlUtils {
|
||||
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.
|
||||
*
|
||||
@ -105,7 +98,7 @@ public class XmlUtils {
|
||||
log.debug("Creating a new element " + name + " with value: " + value);
|
||||
Element element = doc.createElement(name);
|
||||
|
||||
Node content = doc.createTextNode(value != null ? ""+value : "");
|
||||
Node content = doc.createTextNode(value != null ? "" + value : "");
|
||||
element.appendChild(content);
|
||||
|
||||
return element;
|
||||
@ -127,6 +120,18 @@ public class XmlUtils {
|
||||
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,
|
||||
* and returns the created element.
|
||||
@ -144,7 +149,6 @@ public class XmlUtils {
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Formats a given XML string by applying proper indentation to enhance readability.
|
||||
* The method uses a transformer to process the XML input, applying indentation
|
||||
@ -171,9 +175,8 @@ public class XmlUtils {
|
||||
transformer.transform(xmlInput, xmlOutput);
|
||||
|
||||
return xmlOutput.getWriter().toString();
|
||||
}
|
||||
catch(TransformerException e) {
|
||||
log.error("Error in XML: " + e.getMessage() + "\n"+xmlStream, e);
|
||||
} catch (TransformerException e) {
|
||||
log.error("Error in XML: " + e.getMessage() + "\n" + xmlStream, e);
|
||||
}
|
||||
|
||||
return xmlStream;
|
||||
@ -205,8 +208,7 @@ public class XmlUtils {
|
||||
String xmlString = stringWriter.toString();
|
||||
log.info("MO Request: " + xmlString);
|
||||
return xmlString;
|
||||
}
|
||||
catch(TransformerException e) {
|
||||
} catch (TransformerException 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"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
<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>
|
||||
|
||||
|
||||
@ -2,20 +2,26 @@ package de.neitzel.encryption;
|
||||
|
||||
import com.idealista.fpe.config.Alphabet;
|
||||
|
||||
public abstract class BaseAlphabet implements Alphabet {
|
||||
/**
|
||||
* Replaces illegal characters of a string with a given replacement character
|
||||
* @param text Text to check.
|
||||
* @param replacement Replacement character.
|
||||
* @return String in which all characters was replaced.
|
||||
/**
|
||||
* Base implementation of the {@link Alphabet} interface providing common helper methods
|
||||
* for working with character sets and validating characters against the alphabet.
|
||||
*/
|
||||
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
|
||||
if (!isValidCharacter(replacement)) throw new IllegalArgumentException("replacement");
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (char ch: text.toCharArray()) {
|
||||
if (isValidCharacter(ch)){
|
||||
for (char ch : text.toCharArray()) {
|
||||
if (isValidCharacter(ch)) {
|
||||
result.append(ch);
|
||||
} else {
|
||||
result.append(replacement);
|
||||
@ -26,10 +32,11 @@ public abstract class BaseAlphabet implements Alphabet {
|
||||
|
||||
/**
|
||||
* Checks if a given Character is part of the alphabet.
|
||||
*
|
||||
* @param character Character to check.
|
||||
* @return true if character is valid, else false.
|
||||
*/
|
||||
public boolean isValidCharacter(char character) {
|
||||
default boolean isValidCharacter(char character) {
|
||||
// Compare with all characters
|
||||
for (char ch : availableCharacters()) {
|
||||
if (ch == character) return true;
|
||||
|
||||
@ -7,47 +7,51 @@ import com.idealista.fpe.config.LengthRange;
|
||||
|
||||
import javax.crypto.KeyGenerator;
|
||||
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.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class FF1Encryption {
|
||||
|
||||
/**
|
||||
* Key to use for all encryption / unencryption
|
||||
*/
|
||||
private byte[] key;
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
/**
|
||||
* Should to small strings be ignored?
|
||||
*
|
||||
* If this is set to true, small strings (less than 2 characters) will not be encrypted!
|
||||
* Key to use for all encryption / unencryption.
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @param key AES key to use.
|
||||
* @param tweak tweak to use for encryption / decryption
|
||||
* @param ignoreToShortStrings Ignore strings that are to short.
|
||||
@ -58,6 +62,7 @@ public class FF1Encryption {
|
||||
|
||||
/**
|
||||
* Creates a new instance of FF1Encryption
|
||||
*
|
||||
* @param key AES key to use.
|
||||
* @param tweak tweak to use for encryption / decryption
|
||||
* @param ignoreToShortStrings Ignore strings that are to short.
|
||||
@ -72,15 +77,45 @@ public class FF1Encryption {
|
||||
this.domain = domain;
|
||||
this.minLength = minLength;
|
||||
|
||||
encryption = FormatPreservingEncryptionBuilder
|
||||
.ff1Implementation().withDomain(domain)
|
||||
this.encryption = FormatPreservingEncryptionBuilder
|
||||
.ff1Implementation()
|
||||
.withDomain(domain)
|
||||
.withDefaultPseudoRandomFunction(key)
|
||||
.withLengthRange(new LengthRange(minLength, maxLength))
|
||||
.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.
|
||||
*
|
||||
* @param plainText Unencrypted text.
|
||||
* @return Encrypted text.
|
||||
*/
|
||||
@ -97,6 +132,7 @@ public class FF1Encryption {
|
||||
|
||||
/**
|
||||
* Decrypt a given text.
|
||||
*
|
||||
* @param cipherText Encrypted text.
|
||||
* @return Decrypted text.
|
||||
*/
|
||||
@ -110,31 +146,4 @@ public class FF1Encryption {
|
||||
// Return decrypted text.
|
||||
return encryption.decrypt(cipherText, tweak);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new AES key woth the given length.
|
||||
* @param length Length of the key in bits. Must be 128, 192 or 256 bits
|
||||
* @return Byte array of the new key.
|
||||
*/
|
||||
public static byte[] createNewKey(int length) {
|
||||
try {
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(length); // for example
|
||||
return keyGen.generateKey().getEncoded();
|
||||
} catch (NoSuchAlgorithmException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new tweak of the given length.
|
||||
* @param length Number of bytes the new teeak should have.
|
||||
* @return byte array with the new tweak.
|
||||
*/
|
||||
public static byte[] createNewTweak(int length) {
|
||||
Random random = new Random();
|
||||
byte[] key = new byte[length];
|
||||
random.nextBytes(key);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,12 +3,14 @@ package de.neitzel.encryption;
|
||||
/**
|
||||
* 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
|
||||
'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',
|
||||
@ -21,16 +23,38 @@ public class MainAlphabet extends BaseAlphabet {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
|
||||
// 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.
|
||||
* @return
|
||||
*
|
||||
* @return an array containing all supported characters in the alphabet
|
||||
*/
|
||||
@Override
|
||||
public char[] availableCharacters() {
|
||||
@ -39,7 +63,8 @@ public class MainAlphabet extends BaseAlphabet {
|
||||
|
||||
/**
|
||||
* Gets the radix of the alphabet.
|
||||
* @return
|
||||
*
|
||||
* @return the number of characters in the alphabet (radix)
|
||||
*/
|
||||
@Override
|
||||
public Integer radix() {
|
||||
|
||||
@ -4,26 +4,60 @@ import com.idealista.fpe.config.Alphabet;
|
||||
import com.idealista.fpe.config.Domain;
|
||||
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 {
|
||||
|
||||
/**
|
||||
* The alphabet used by the domain for encoding/decoding characters.
|
||||
*/
|
||||
private Alphabet _alphabet;
|
||||
|
||||
/**
|
||||
* Helper instance performing actual transformations between characters and indices.
|
||||
*/
|
||||
private GenericTransformations transformations;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code MainDomain} instance and initializes its alphabet and transformation helper.
|
||||
*/
|
||||
public MainDomain() {
|
||||
_alphabet = new MainAlphabet();
|
||||
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
|
||||
public Alphabet 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
|
||||
public int[] transform(String 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
|
||||
public String transform(int[] data) {
|
||||
return transformations.transform(data);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
<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>
|
||||
|
||||
|
||||
@ -3,9 +3,27 @@ package de.neitzel.fx.component.example;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Simple address model used in the FX example component.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class Address {
|
||||
|
||||
/**
|
||||
* Street name and number.
|
||||
*/
|
||||
private String street;
|
||||
|
||||
/**
|
||||
* City name.
|
||||
*/
|
||||
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.stage.Stage;
|
||||
|
||||
/**
|
||||
* Example JavaFX application demonstrating the {@link de.neitzel.fx.component.ComponentLoader} usage.
|
||||
*/
|
||||
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
|
||||
public void start(Stage primaryStage) throws Exception {
|
||||
// Beispielmodel initialisieren
|
||||
@ -29,8 +53,4 @@ public class ExampleApp extends Application {
|
||||
primaryStage.setScene(scene);
|
||||
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!)
|
||||
*/
|
||||
public class Main {
|
||||
|
||||
/**
|
||||
* Default constructor only
|
||||
*/
|
||||
public Main() {
|
||||
// default constructor only
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional main methode to start Application.
|
||||
*
|
||||
* @param args Commandline Arguments.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
@ -3,9 +3,27 @@ package de.neitzel.fx.component.example;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* Simple person model used in the FX example component.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class Person {
|
||||
|
||||
/**
|
||||
* The person's full name.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* The address of the person.
|
||||
*/
|
||||
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.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 {
|
||||
|
||||
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
|
||||
public void start(Stage primaryStage) {
|
||||
try {
|
||||
@ -17,12 +43,7 @@ public class JavaFXApp extends Application {
|
||||
primaryStage.setScene(new Scene(root));
|
||||
primaryStage.show();
|
||||
} catch (Exception ex) {
|
||||
System.out.println("Exception: " + ex.getMessage());
|
||||
ex.printStackTrace();
|
||||
LOGGER.log(Level.SEVERE, "Exception while starting JavaFXApp", ex);
|
||||
}
|
||||
}
|
||||
|
||||
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!)
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param args Commandline Arguments.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
@ -8,13 +8,49 @@ import javafx.scene.control.TextField;
|
||||
import java.net.URL;
|
||||
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 {
|
||||
|
||||
/**
|
||||
* Counter for button clicks shown in the UI.
|
||||
*/
|
||||
private int counter = 0;
|
||||
|
||||
/**
|
||||
* Text field UI control showing the click counter.
|
||||
*/
|
||||
@FXML
|
||||
private TextField textField;
|
||||
|
||||
/**
|
||||
* Default constructor only
|
||||
*/
|
||||
public MainWindow() {
|
||||
// default constructor only
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
displayCounter();
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<AnchorPane xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
<AnchorPane xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns:nfx="http://example.com/nfx"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="de.neitzel.fx.component.ComponentController"
|
||||
prefWidth="300" prefHeight="100">
|
||||
<children>
|
||||
|
||||
@ -3,9 +3,11 @@
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<AnchorPane prefHeight="127.0" prefWidth="209.0" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="de.neitzel.fx.injectfx.example.MainWindow">
|
||||
<AnchorPane 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>
|
||||
<Button fx:id="button" layoutX="44.0" layoutY="70.0" mnemonicParsing="false" onAction="#onButtonClick" text="Click Me" />
|
||||
<TextField fx:id="textField" layoutX="14.0" layoutY="24.0" />
|
||||
<Button fx:id="button" layoutX="44.0" layoutY="70.0" mnemonicParsing="false" onAction="#onButtonClick"
|
||||
text="Click Me"/>
|
||||
<TextField fx:id="textField" layoutX="14.0" layoutY="24.0"/>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.AnchorPane?>
|
||||
<?import javafx.scene.layout.Pane?>
|
||||
<AnchorPane xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
<AnchorPane xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns:nfx="http://example.com/nfx"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
fx:controller="de.neitzel.fx.component.ComponentController"
|
||||
prefWidth="300" prefHeight="180">
|
||||
<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"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
<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>
|
||||
|
||||
@ -49,6 +49,11 @@
|
||||
<artifactId>core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.neitzel.lib</groupId>
|
||||
<artifactId>injection</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -1,22 +1,70 @@
|
||||
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.Modifier;
|
||||
import java.util.HashMap;
|
||||
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> {
|
||||
|
||||
/**
|
||||
* The wrapped model instance.
|
||||
* -- GETTER --
|
||||
* Retrieves the model associated with this AutoViewModel.
|
||||
*/
|
||||
@Getter
|
||||
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<>();
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this.model = model;
|
||||
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() {
|
||||
for (Method getter : model.getClass().getMethods()) {
|
||||
if (isGetter(getter)) {
|
||||
@ -27,13 +75,17 @@ public class AutoViewModel<T> {
|
||||
properties.put(fieldName, prop);
|
||||
|
||||
// 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);
|
||||
if (setter != null) {
|
||||
try {
|
||||
setter.invoke(model, newVal);
|
||||
} 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);
|
||||
}
|
||||
|
||||
public T getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
// ========== Hilfsmethoden ==========
|
||||
|
||||
/**
|
||||
* 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".
|
||||
*
|
||||
* @param method the method to be assessed
|
||||
* @return true if the method adheres to the JavaBean getter convention, false otherwise
|
||||
*/
|
||||
private boolean isGetter(Method method) {
|
||||
return Modifier.isPublic(method.getModifiers())
|
||||
&& method.getParameterCount() == 0
|
||||
@ -58,6 +108,14 @@ public class AutoViewModel<T> {
|
||||
&& (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) {
|
||||
String name = method.getName();
|
||||
if (name.startsWith("get")) {
|
||||
@ -68,20 +126,57 @@ public class AutoViewModel<T> {
|
||||
return name;
|
||||
}
|
||||
|
||||
private String decapitalize(String str) {
|
||||
if (str == null || str.isEmpty()) return str;
|
||||
return str.substring(0, 1).toLowerCase() + str.substring(1);
|
||||
}
|
||||
// ========== Hilfsmethoden ==========
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
try {
|
||||
return method.invoke(model);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
log.warn("Failed to invoke getter: {}", method.getName(), e);
|
||||
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) {
|
||||
String setterName = "set" + capitalize(fieldName);
|
||||
for (Method m : clazz.getMethods()) {
|
||||
@ -94,18 +189,38 @@ public class AutoViewModel<T> {
|
||||
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) {
|
||||
if (str == null || str.isEmpty()) return str;
|
||||
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||
}
|
||||
|
||||
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);
|
||||
/**
|
||||
* Retrieves the JavaFX property associated with the given field name.
|
||||
*
|
||||
* @param name the name of the field whose associated JavaFX property is to be returned
|
||||
* @return the JavaFX {@link Property} associated with the specified field name,
|
||||
* or null if no property exists for the given name
|
||||
*/
|
||||
public Property<?> getProperty(String name) {
|
||||
return properties.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package de.neitzel.fx.component;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* which contains JavaFX properties derived from the model.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class ComponentController {
|
||||
|
||||
/**
|
||||
@ -20,4 +18,14 @@ public class ComponentController {
|
||||
*/
|
||||
@Getter
|
||||
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;
|
||||
|
||||
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.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
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.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* ComponentLoader is responsible for loading JavaFX FXML components and binding
|
||||
* 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 {
|
||||
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
|
||||
@ -32,83 +271,7 @@ public class ComponentLoader {
|
||||
* @return the root JavaFX node loaded from FXML
|
||||
*/
|
||||
public Parent load(Object model, String fxmlPath) {
|
||||
try {
|
||||
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;
|
||||
return load(model, getClass().getResource(fxmlPath));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -118,6 +281,7 @@ public class ComponentLoader {
|
||||
* @param vmProp the ViewModel property to bind to
|
||||
* @param direction the direction of the binding (e.g., "bidirectional", "read")
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
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 ("bidirectional".equalsIgnoreCase(direction)) {
|
||||
@ -128,61 +292,4 @@ public class ComponentLoader {
|
||||
}
|
||||
// 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;
|
||||
|
||||
import de.neitzel.core.inject.ComponentData;
|
||||
import de.neitzel.core.inject.ComponentScanner;
|
||||
import de.neitzel.injection.ComponentData;
|
||||
import de.neitzel.injection.ComponentScanner;
|
||||
import lombok.Getter;
|
||||
|
||||
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. */
|
||||
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;
|
||||
|
||||
/**
|
||||
|
||||
@ -13,27 +13,29 @@ import java.util.stream.Stream;
|
||||
* support dependency injection. It uses a parameter map to resolve and supply dependencies to
|
||||
* controller constructors dynamically during instantiation.
|
||||
*
|
||||
* This class simplifies the process of injecting dependencies into JavaFX controllers by analyzing
|
||||
* the constructors of the given controller classes at runtime. It selects the constructor that best
|
||||
* matches the available dependencies in the parameter map and creates an instance of the controller.
|
||||
* <p>This class analyzes available constructors of a requested controller class at runtime and
|
||||
* selects the first constructor whose parameter types are all present in the internal parameter map.
|
||||
* 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
|
||||
* controllers with dependencies to be injected seamlessly during the FXML loading process.
|
||||
* <p>It implements the {@link javafx.util.Callback} interface to integrate with the JavaFX
|
||||
* {@link javafx.fxml.FXMLLoader} controller factory mechanism.
|
||||
*/
|
||||
public class InjectingControllerFactory implements Callback<Class<?>, Object> {
|
||||
|
||||
/**
|
||||
* A map that stores class-to-object mappings used for dependency injection
|
||||
* in controller instantiation. This map is utilized to resolve and supply
|
||||
* the required dependencies for constructors during the creation of controller
|
||||
* instances.
|
||||
*
|
||||
* Each key in the map represents a class type, and the corresponding value
|
||||
* is the instance of that type. This allows the {@link InjectingControllerFactory}
|
||||
* to use the stored instances to dynamically match and inject dependencies
|
||||
* into controllers at runtime.
|
||||
* in controller instantiation. Each key represents a parameter type and the
|
||||
* associated value is the instance to be injected for that type.
|
||||
*/
|
||||
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
|
||||
* to the parameter map used for dependency injection.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
package de.neitzel.fx.injectfx;
|
||||
|
||||
import de.neitzel.core.inject.ComponentScanner;
|
||||
import de.neitzel.injection.ComponentScanner;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@ -11,12 +11,12 @@ import java.net.URL;
|
||||
* The InjectingFXMLLoader class provides a custom implementation of JavaFX's FXMLLoader
|
||||
* that supports dependency injection. It facilitates the loading of FXML files while
|
||||
* dynamically injecting dependencies into controllers during the loading process.
|
||||
*
|
||||
* <p>
|
||||
* This class utilizes the InjectingControllerFactory to enable seamless integration
|
||||
* of dependency injection with JavaFX controllers. Dependencies can be added to the
|
||||
* controller factory, and the loader will use this factory to instantiate controllers
|
||||
* with the appropriate dependencies.
|
||||
*
|
||||
* <p>
|
||||
* Features of this loader include:
|
||||
* - Support for dependency injection into JavaFX controllers by using a custom factory.
|
||||
* - 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
|
||||
* 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
|
||||
* required objects during controller instantiation. It plays a critical role in enabling seamless
|
||||
* integration of dependency injection with JavaFX's {@link FXMLLoader}.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* Key responsibilities:
|
||||
* - Manages a mapping of classes to their injectable instances required for controller instantiation.
|
||||
* - Dynamically analyzes and invokes appropriate constructors for controllers based on the availability
|
||||
* of dependencies.
|
||||
* - Ensures that controllers are created with required dependencies, preventing manual resolution of injections.
|
||||
*
|
||||
* <p>
|
||||
* This variable is initialized in the {@link InjectingFXMLLoader} and can be extended with additional
|
||||
* mappings at runtime using relevant methods.
|
||||
*/
|
||||
@ -51,7 +51,7 @@ public class InjectingFXMLLoader {
|
||||
* Default constructor for the InjectingFXMLLoader class.
|
||||
* This initializes the loader with a new instance of the {@link InjectingControllerFactory},
|
||||
* which is used to provide dependency injection capabilities for JavaFX controllers.
|
||||
*
|
||||
* <p>
|
||||
* The {@link InjectingControllerFactory} allows for the registration and dynamic injection
|
||||
* of dependencies into controllers when they are instantiated during the FXML loading process.
|
||||
*/
|
||||
@ -100,7 +100,7 @@ public class InjectingFXMLLoader {
|
||||
* available for dependency injection.
|
||||
*/
|
||||
private void addInjectingData(FXMLComponentInstances instances) {
|
||||
for (var clazz: instances.getInstanceMap().keySet()) {
|
||||
for (var clazz : instances.getInstanceMap().keySet()) {
|
||||
addInjectingData(clazz, instances.getInstance(clazz));
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,11 +6,9 @@ import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.TextInputControl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
/**
|
||||
* 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.
|
||||
@ -24,6 +22,17 @@ public class BindingAwareFXMLLoader<T> {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@ -68,23 +77,36 @@ public class BindingAwareFXMLLoader<T> {
|
||||
if (userData instanceof String propertyName) {
|
||||
BindDirection direction = getDirection(node);
|
||||
|
||||
if (node instanceof TextInputControl control) {
|
||||
switch (node) {
|
||||
case TextInputControl control -> {
|
||||
Property<String> prop = viewModel.property(StringProperty.class, propertyName);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
bind(slider.valueProperty(), prop, direction);
|
||||
} else if (node instanceof javafx.scene.control.DatePicker datePicker) {
|
||||
}
|
||||
case javafx.scene.control.DatePicker datePicker -> {
|
||||
@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);
|
||||
}
|
||||
default -> {
|
||||
// ignore unsupported nodes
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -12,18 +12,21 @@ import java.util.ResourceBundle;
|
||||
* @param <T> the type of the model
|
||||
*/
|
||||
public class GenericViewController<T> implements Initializable {
|
||||
|
||||
/**
|
||||
* The GenericViewModel instance wrapping the underlying model.
|
||||
*/
|
||||
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) {
|
||||
this.viewModel = new GenericViewModel<>(model);
|
||||
public GenericViewController() {
|
||||
// default constructor only ...
|
||||
}
|
||||
|
||||
/**
|
||||
@ -35,6 +38,15 @@ public class GenericViewController<T> implements Initializable {
|
||||
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.
|
||||
*
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
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.IntrospectionException;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
<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>
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ import java.util.Set;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* This is a generic class type parameter representing the class of objects that the
|
||||
* {@link ExcludeFieldsAdapter} instance is designed to process. It ensures type
|
||||
* safety and guarantees the compatibility of the adapter with a specific object type.
|
||||
*/
|
||||
private final Class<T> type;
|
||||
|
||||
/**
|
||||
* A set containing the names of fields to be excluded during JSON serialization.
|
||||
*
|
||||
* <p>
|
||||
* This collection is used in the {@link ExcludeFieldsAdapter} to determine which fields should be removed from the
|
||||
* serialized JSON representation of an object. The fields to be excluded are specified during the initialization
|
||||
* of the adapter.
|
||||
@ -41,7 +42,7 @@ public class ExcludeFieldsAdapter<T> extends TypeAdapter<T> {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* custom serialization and deserialization logic. If not explicitly provided during initialization,
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*/
|
||||
@ -89,19 +90,6 @@ public class ExcludeFieldsAdapter<T> extends TypeAdapter<T> {
|
||||
this.gson = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily initializes and retrieves the {@link Gson} instance. If the instance is null, it creates a new {@link Gson}
|
||||
* object using the {@link GsonBuilder} provided during the adapter's initialization.
|
||||
*
|
||||
* @return the {@link Gson} instance used for serialization and deserialization
|
||||
*/
|
||||
private Gson getGson() {
|
||||
if (gson == null) {
|
||||
gson = gsonBuilder.create();
|
||||
}
|
||||
return gson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes an object of type {@code T} into JSON format, excluding certain fields specified during the initialization of this adapter.
|
||||
* The object is first converted into a {@link JsonObject}, then the fields listed in the excluded fields set are removed from the JSON
|
||||
@ -122,6 +110,19 @@ public class ExcludeFieldsAdapter<T> extends TypeAdapter<T> {
|
||||
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.
|
||||
*
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
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.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.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
|
||||
* injection framework.
|
||||
*
|
||||
* <p>
|
||||
* The {@code ApplicationContext} is responsible for scanning, instantiating, and managing
|
||||
* the lifecycle of components. Components are identified through the specified base package
|
||||
* (or derived from a configuration class) and instantiated based on their dependency
|
||||
@ -52,7 +52,7 @@ public class ApplicationContext {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* a singleton instance. The component's type and scope are determined by the framework,
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* The method evaluates whether every parameter type required by the constructor
|
||||
* is available in the given parameter map. If all parameter types are present,
|
||||
* the constructor is considered instantiable.
|
||||
@ -118,7 +118,7 @@ public class ApplicationContext {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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}
|
||||
@ -1,10 +1,10 @@
|
||||
package de.neitzel.core.inject;
|
||||
package de.neitzel.injection;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* Represents a component in a dependency injection framework.
|
||||
*
|
||||
* <p>
|
||||
* A component is a unit of functionality that is managed by the framework, allowing
|
||||
* for controlled instantiation and lifecycle management. The component is associated
|
||||
* with a specific type and a scope. The scope determines whether the component is
|
||||
@ -15,7 +15,7 @@ public class ComponentData {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
@ -24,10 +24,10 @@ public class ComponentData {
|
||||
|
||||
/**
|
||||
* Defines the lifecycle and instantiation rules for the associated component.
|
||||
*
|
||||
* <p>
|
||||
* 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).
|
||||
*
|
||||
* <p>
|
||||
* This variable is immutable and represents the specific {@code Scope} assigned
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* This field holds the actual instance of the component's type within the dependency
|
||||
* injection framework. For components with a {@code SINGLETON} scope, this field is
|
||||
* set only once and shared across the entire application. For components with a
|
||||
@ -57,7 +57,7 @@ public class ComponentData {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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.core.inject.annotation.Inject;
|
||||
import de.neitzel.injection.annotation.Component;
|
||||
import de.neitzel.injection.annotation.Inject;
|
||||
import org.reflections.Reflections;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -21,13 +27,13 @@ public class ComponentScanner {
|
||||
* FXML-compatible components detected during the scanning process.
|
||||
* These components are used as part of the dependency injection mechanism
|
||||
* within the {@code InjectableComponentScanner}.
|
||||
*
|
||||
* <p>
|
||||
* The {@code fxmlComponents} set is populated during the component scanning
|
||||
* phase by identifying all classes within a specified package hierarchy
|
||||
* annotated with the {@code @Component} annotation. These classes serve
|
||||
* as the primary source for further analysis, resolution, and instantiation
|
||||
* in the scanning workflow.
|
||||
*
|
||||
* <p>
|
||||
* This field is immutable, ensuring thread safety and consistent state
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* This collection is populated during the analysis of discovered components to identify
|
||||
* types that have multiple implementations, preventing them from being uniquely resolved.
|
||||
* For example, if multiple components implement the same interface, that interface will be
|
||||
* added to this set.
|
||||
*
|
||||
* <p>
|
||||
* It is primarily used during the component registration process to avoid ambiguities
|
||||
* in the dependency injection system, ensuring that only resolvable, uniquely identifiable
|
||||
* components can be instantiated and injected.
|
||||
@ -52,13 +58,13 @@ public class ComponentScanner {
|
||||
* The key represents a super type (interface or abstract class), and the value
|
||||
* is the specific implementation or subclass that is uniquely identified among
|
||||
* the scanned components.
|
||||
*
|
||||
* <p>
|
||||
* This map is populated during the component analysis process. For each super type
|
||||
* in the scanned components, if exactly one implementation is found, it is
|
||||
* considered unique and added to this mapping. In case multiple implementations
|
||||
* exist for a given super type, it is excluded from this map and classified as
|
||||
* a non-unique type.
|
||||
*
|
||||
* <p>
|
||||
* This mapping is utilized for resolving dependencies and determining which components
|
||||
* 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
|
||||
* of available component types, ensuring that only components with resolvable
|
||||
* dependencies are included.
|
||||
*
|
||||
* <p>
|
||||
* This field is part of the dependency injection process, where components annotated
|
||||
* with {@code @Component} or similar annotations are scanned, analyzed, and registered.
|
||||
* The resolution process checks if a component can be instantiated based on its
|
||||
@ -81,11 +87,11 @@ public class ComponentScanner {
|
||||
/**
|
||||
* A list of error messages encountered during the scanning and analysis
|
||||
* of components in the dependency injection process.
|
||||
*
|
||||
* <p>
|
||||
* This list is populated when components cannot be resolved due to issues
|
||||
* such as multiple implementations of a superclass or interface,
|
||||
* unmet construction requirements, or unresolved dependencies.
|
||||
*
|
||||
* <p>
|
||||
* Errors added to this list provide detailed descriptions of the specific
|
||||
* 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
|
||||
* inheritance hierarchy and relationships.
|
||||
*
|
||||
* <p>
|
||||
* This method performs the following operations:
|
||||
* 1. Maps each component to all of its superclasses and interfaces.
|
||||
* 2. Identifies which superclasses or interfaces have multiple implementing components.
|
||||
* 3. Populates:
|
||||
* - A list of superclasses or interfaces that are not uniquely linked to a single component.
|
||||
* - A map where unique superclasses or interfaces are associated with a specific implementing component.
|
||||
*
|
||||
* <p>
|
||||
* The mappings are built using the following data structures:
|
||||
* - A map from superclasses/interfaces to the list of components that implement or extend them.
|
||||
* - A list of non-unique types where multiple components exist for the same superclass or interface.
|
||||
* - A map of unique superclasses/interfaces to their corresponding component.
|
||||
*
|
||||
* <p>
|
||||
* This method is a key part of the component scanning and resolution process, facilitating
|
||||
* 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.
|
||||
* This process determines which components can be instantiated based on their dependencies
|
||||
* and class relationships, while tracking unresolved types and potential conflicts.
|
||||
*
|
||||
* <p>
|
||||
* The resolution process involves:
|
||||
* 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
|
||||
@ -169,13 +175,13 @@ public class ComponentScanner {
|
||||
* types map for future iterations.
|
||||
* 3. Removing successfully resolved components from the unresolved set.
|
||||
* 4. Repeating the process until no further components can be resolved in a given iteration.
|
||||
*
|
||||
* <p>
|
||||
* At the end of the resolution process:
|
||||
* - Resolvable components are added to the `instantiableComponents` map, which maps types
|
||||
* to their corresponding instantiable implementations.
|
||||
* - Unresolved components are identified, and error details are collected to highlight
|
||||
* dependencies or conflicts preventing their instantiation.
|
||||
*
|
||||
* <p>
|
||||
* If errors are encountered due to unresolved components, they are logged for further analysis.
|
||||
*/
|
||||
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.
|
||||
* 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.
|
||||
* Retrieves all superclasses and interfaces of the specified class, excluding the {@code Object} class.
|
||||
*
|
||||
* @param component the {@code ComponentData} instance representing the component to be registered
|
||||
* @param knownTypes the map where component types and their data are stored
|
||||
* @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 void registerComponentWithSuperTypes(ComponentData component, Map<Class<?>, ComponentData> knownTypes) {
|
||||
knownTypes.put(component.getType(), component);
|
||||
private Set<Class<?>> getAllSuperTypes(Class<?> clazz) {
|
||||
Set<Class<?>> result = new HashSet<>();
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
|
||||
for (Class<?> superType : getAllSuperTypes(component.getType())) {
|
||||
if (!notUniqueTypes.contains(superType)) {
|
||||
knownTypes.put(superType, component);
|
||||
}
|
||||
while (superClass != null && superClass != Object.class) {
|
||||
result.add(superClass);
|
||||
superClass = superClass.getSuperclass();
|
||||
}
|
||||
|
||||
result.addAll(Arrays.asList(clazz.getInterfaces()));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -260,6 +267,24 @@ public class ComponentScanner {
|
||||
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
|
||||
* detailed error messages to the internal error list. This method analyzes unresolved
|
||||
@ -314,25 +339,6 @@ public class ComponentScanner {
|
||||
.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.
|
||||
*
|
||||
@ -1,4 +1,4 @@
|
||||
package de.neitzel.core.inject;
|
||||
package de.neitzel.injection;
|
||||
|
||||
/**
|
||||
* 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.Retention;
|
||||
@ -11,14 +11,14 @@ import java.lang.annotation.Target;
|
||||
* Indicates that an annotated class is a "component" within a dependency injection framework.
|
||||
* Classes annotated with {@code @Component} are recognized during the component scanning process
|
||||
* as candidates for instantiation and management by the dependency injection framework.
|
||||
*
|
||||
* <p>
|
||||
* This annotation is typically used on classes that represent application-specific components,
|
||||
* such as service implementations, controllers, or other objects intended to be instantiated
|
||||
* and injected into other components during runtime.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* Usage of this annotation assumes that there exists a component scanning mechanism
|
||||
* that processes annotated classes and identifies their roles, dependencies, and hierarchy
|
||||
* within the application's structure.
|
||||
@ -1,4 +1,4 @@
|
||||
package de.neitzel.core.inject.annotation;
|
||||
package de.neitzel.injection.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
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
|
||||
* for defining settings and application-specific configurations required by the
|
||||
* dependency injection mechanism.
|
||||
*
|
||||
* <p>
|
||||
* Typically, configuration classes provide metadata required for setting up the
|
||||
* 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
|
||||
* facilitate reflection-based processing. It is intended to serve as a declarative
|
||||
* representation of configuration options for the dependency injection container.
|
||||
*
|
||||
* <p>
|
||||
* Attributes:
|
||||
* - {@code basePackage}: Specifies the package name where the framework should scan
|
||||
* 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.Retention;
|
||||
@ -10,10 +10,10 @@ import java.lang.annotation.Target;
|
||||
* a dependency injection framework. Fields annotated with {@code @Inject}
|
||||
* are automatically populated with the required component instance during runtime,
|
||||
* typically by the dependency injection container.
|
||||
*
|
||||
* <p>
|
||||
* This annotation must be applied at the field level and is retained at runtime
|
||||
* to enable reflection-based identification and assignment of dependencies.
|
||||
*
|
||||
* <p>
|
||||
* The framework's dependency resolution mechanism identifies the appropriate
|
||||
* instance to inject based on the field's type or custom configuration,
|
||||
* ensuring loose coupling and easier testability.
|
||||
@ -1,13 +1,17 @@
|
||||
package de.neitzel.core.inject;
|
||||
package de.neitzel.injection;
|
||||
|
||||
import de.neitzel.core.inject.testcomponents.test1ok.SuperClass;
|
||||
import de.neitzel.core.inject.testcomponents.test1ok.TestComponent1_1;
|
||||
import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_1;
|
||||
import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_2;
|
||||
import de.neitzel.core.inject.testcomponents.test1ok.sub.TestComponent1_2;
|
||||
import de.neitzel.injection.testcomponents.test1ok.SuperClass;
|
||||
import de.neitzel.injection.testcomponents.test1ok.TestComponent1_1;
|
||||
import de.neitzel.injection.testcomponents.test1ok.TestInterface1_1;
|
||||
import de.neitzel.injection.testcomponents.test1ok.TestInterface1_2;
|
||||
import de.neitzel.injection.testcomponents.test1ok.sub.TestComponent1_2;
|
||||
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 {
|
||||
|
||||
@ -16,7 +20,7 @@ class InjectableComponentScannerTest {
|
||||
*/
|
||||
@Test
|
||||
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 nonUniqueTypes = scanner.getNotUniqueTypes();
|
||||
|
||||
@ -37,7 +41,7 @@ class InjectableComponentScannerTest {
|
||||
*/
|
||||
@Test
|
||||
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 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
|
||||
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.
|
||||
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
<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>
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package de.neitzel.log4j;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
import org.apache.log4j.PropertyConfigurator;
|
||||
|
||||
import java.io.File;
|
||||
@ -9,6 +10,7 @@ import java.net.URISyntaxException;
|
||||
* Utility class for managing Log4j configurations. Provides methods to set up
|
||||
* Log4j configurations from default files, resources, or command line arguments.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class Log4jUtils {
|
||||
|
||||
/**
|
||||
@ -24,13 +26,13 @@ public class Log4jUtils {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return true if the "log4j.configuration" property is defined, false otherwise.
|
||||
* Default constructor only
|
||||
*/
|
||||
public static boolean isLog4jConfigFileSet() {
|
||||
return System.getProperty("log4j.configuration") != null;
|
||||
public Log4jUtils() {
|
||||
// default constructor only
|
||||
}
|
||||
|
||||
/**
|
||||
@ -38,7 +40,7 @@ public class Log4jUtils {
|
||||
* 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
|
||||
* 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)}
|
||||
* method using predefined defaults.
|
||||
*/
|
||||
@ -46,22 +48,6 @@ public class Log4jUtils {
|
||||
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
|
||||
* 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()) {
|
||||
PropertyConfigurator.configure(log4jConfigFile);
|
||||
} 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);
|
||||
}else {
|
||||
} else {
|
||||
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"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
<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>
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import java.util.Objects;
|
||||
* This class provides methods to set or get the account details and overrides methods for string
|
||||
* representation, equality checks, and hashing.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class ImapAccount {
|
||||
|
||||
/**
|
||||
@ -15,15 +16,6 @@ public class ImapAccount {
|
||||
* of the mail server that handles IMAP communication.
|
||||
*/
|
||||
protected String server;
|
||||
/**
|
||||
* Retrieves the server address associated with this IMAP*/
|
||||
public String getServer() { return server; }
|
||||
/**
|
||||
* Sets the server address for the IMAP account.
|
||||
*
|
||||
* @param server the server address to be configured for the IMAP account
|
||||
*/
|
||||
public void setServer(String server) { this.server = server; }
|
||||
|
||||
/**
|
||||
* Represents the username or identifier used to authenticate the IMAP account.
|
||||
@ -31,66 +23,94 @@ public class ImapAccount {
|
||||
* to the IMAP server.
|
||||
*/
|
||||
protected String user;
|
||||
/**
|
||||
* Retrieves the username associated with this IMAP account.
|
||||
*
|
||||
* @return the username of the IMAP account as a String.
|
||||
*/
|
||||
public String getUser() { return user; }
|
||||
/**
|
||||
* Sets the username associated with the IMAP account.
|
||||
*
|
||||
* @param user The username to be set for this IMAP account.
|
||||
*/
|
||||
public void setUser(String user) { this.user = user; }
|
||||
|
||||
/**
|
||||
* The password associated with the IMAP account.
|
||||
* This is used to authenticate the user when connecting to the IMAP server.
|
||||
*/
|
||||
protected String password;
|
||||
/**
|
||||
* Retrieves the password associated with this IMAP account.
|
||||
*
|
||||
* @return the password of the IMAP account.
|
||||
*/
|
||||
public String getPassword() { return password; }
|
||||
/**
|
||||
* Sets the password for the IMAP account.
|
||||
*
|
||||
* @param password The password to be set for the account.
|
||||
*/
|
||||
public void setPassword(String password) { this.password = password; }
|
||||
|
||||
/**
|
||||
* Specifies the protocol used for the IMAP account, such as "IMAP" or "IMAPS".
|
||||
* This field determines the communication protocol for interacting with the server.
|
||||
*/
|
||||
protected String protocol;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return the protocol as a String, which defines the communication method for the IMAP account.
|
||||
*/
|
||||
public String getProtocol() { return protocol; }
|
||||
/**
|
||||
* Sets the protocol used by the IMAP account.
|
||||
* @param protocol the protocol to be used, e.g., "IMAP" or "IMAPS".
|
||||
*/
|
||||
public void setProtocol(String protocol) { this.protocol = protocol; }
|
||||
public String getProtocol() {
|
||||
return 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.
|
||||
* Sets the protocol used by the IMAP account.
|
||||
*
|
||||
* @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() {
|
||||
return String.format("ImapAccount(%s, %s, %s, %s",
|
||||
server,
|
||||
user,
|
||||
password.length()==0 ? "''" : "########" ,
|
||||
protocol);
|
||||
public void setProtocol(String protocol) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,7 +120,8 @@ public class ImapAccount {
|
||||
*
|
||||
* @return An integer hash code representing this ImapAccount instance.
|
||||
*/
|
||||
@Override public int hashCode() {
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(server, user, password, protocol);
|
||||
}
|
||||
|
||||
@ -111,7 +132,8 @@ public class ImapAccount {
|
||||
* @param obj the object to compare with this instance
|
||||
* @return true if the specified object is equal to this ImapAccount instance; false otherwise
|
||||
*/
|
||||
@Override public boolean equals(Object obj) {
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
// Check type of argument, includes check of null.
|
||||
if (!(obj instanceof ImapAccount)) return false;
|
||||
|
||||
@ -120,9 +142,24 @@ public class ImapAccount {
|
||||
|
||||
// Check the comparison of all field
|
||||
ImapAccount other = (ImapAccount) obj;
|
||||
return (server == other.server || (server != null && server.equals(other.server))) &&
|
||||
(user == other.user || (user != null && user.equals(other.user))) &&
|
||||
(password == other.password || (password != null && password.equals(other.password))) &&
|
||||
(protocol == other.protocol || (protocol != null && protocol.equals(other.protocol)));
|
||||
return Objects.equals(server, other.server) &&
|
||||
Objects.equals(user, other.user) &&
|
||||
Objects.equals(password, other.password) &&
|
||||
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 javax.mail.*;
|
||||
import java.util.*;
|
||||
import javax.mail.Flags;
|
||||
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
|
||||
@ -44,6 +54,13 @@ public class ImapAccountMonitor {
|
||||
*/
|
||||
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
|
||||
* using the provided account credentials and protocol.
|
||||
@ -77,19 +94,19 @@ public class ImapAccountMonitor {
|
||||
* Closes the resources associated with the current instance, including
|
||||
* IMAP folders and the store. The method ensures that all folders are
|
||||
* closed properly, followed by the closure of the store.
|
||||
*
|
||||
* <p>
|
||||
* Any exceptions encountered during the closure of folders or the store
|
||||
* are caught and logged without interrupting the overall closing process.
|
||||
* This is to ensure that all resources are attempted to be closed even if
|
||||
* an error occurs with one of them.
|
||||
*
|
||||
* <p>
|
||||
* The method logs the start and end of the closing process for traceability.
|
||||
*/
|
||||
public void close() {
|
||||
log.trace("close() called.");
|
||||
|
||||
// Close the folders
|
||||
for (Folder folder: folders.values()) {
|
||||
for (Folder folder : folders.values()) {
|
||||
try {
|
||||
folder.close(false);
|
||||
} catch (MessagingException ex) {
|
||||
@ -152,28 +169,28 @@ public class ImapAccountMonitor {
|
||||
* Checks all monitored folders for new email messages, processes unseen and undeleted messages,
|
||||
* and raises a NewMailEvent for each new email discovered. The method ensures processed messages
|
||||
* are marked as seen.
|
||||
*
|
||||
* <p>
|
||||
* The method iterates through all folders currently being monitored, fetching their messages and
|
||||
* evaluating the state of each. If a message is neither marked as seen nor deleted, it is marked
|
||||
* as seen and a NewMailEvent is raised for it.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* Note: This method relies on external logging (via `log`) and appropriate event handling
|
||||
* 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.
|
||||
*/
|
||||
public void check() {
|
||||
log.trace("check() called.");
|
||||
|
||||
// Loop through all folders that are monitored
|
||||
for (Folder folder: folders.values()) {
|
||||
for (Folder folder : folders.values()) {
|
||||
try {
|
||||
// Loop through all messages.
|
||||
Message[] messages = folder.getMessages();
|
||||
for (Message message: messages) {
|
||||
for (Message message : messages) {
|
||||
// Check if message wasn't seen and wasn't deleted
|
||||
if (!message.isSet(Flags.Flag.SEEN) && !message.isSet(Flags.Flag.DELETED)) {
|
||||
// Mark message as seen.
|
||||
@ -193,11 +210,35 @@ public class ImapAccountMonitor {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
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.
|
||||
@ -222,35 +263,4 @@ public class ImapAccountMonitor {
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* Gets the text of the email message.
|
||||
* @param message Message to get the text from.
|
||||
* @return Text of the email message.
|
||||
* @throws IOException
|
||||
* @throws MessagingException
|
||||
* Private constructor for utility class.
|
||||
* Prevents instantiation of this utility class.
|
||||
*/
|
||||
private MessageUtils() {
|
||||
// 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 {
|
||||
String result = "";
|
||||
@ -30,11 +64,23 @@ public class MessageUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the text of a MimeMultipart.
|
||||
* @param mimeMultipart MimeMultipart to get the text from.
|
||||
* @return Text of the MimeMultipart instance.
|
||||
* @throws IOException
|
||||
* @throws MessagingException
|
||||
* Extracts the textual content from a {@link MimeMultipart} instance.
|
||||
*
|
||||
* <p>Algorithm:
|
||||
* <ul>
|
||||
* <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(
|
||||
MimeMultipart mimeMultipart) throws IOException, MessagingException {
|
||||
@ -47,20 +93,29 @@ public class MessageUtils {
|
||||
// alternatives appear in an order of increasing
|
||||
// faithfulness to the original content. Customize as req'd.
|
||||
return getTextFromBodyPart(mimeMultipart.getBodyPart(count - 1));
|
||||
String result = "";
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < count; i++) {
|
||||
BodyPart bodyPart = mimeMultipart.getBodyPart(i);
|
||||
result += getTextFromBodyPart(bodyPart);
|
||||
result.append(getTextFromBodyPart(bodyPart));
|
||||
}
|
||||
return result;
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the text of a BodyPart
|
||||
* @param bodyPart BodyPart to get text from.
|
||||
* @return Text of body part or empty string if not a known type.
|
||||
* @throws IOException
|
||||
* @throws MessagingException
|
||||
* Extracts textual content from a single {@link BodyPart}.
|
||||
*
|
||||
* <p>Behavior:
|
||||
* <ul>
|
||||
* <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(
|
||||
BodyPart bodyPart) throws IOException, MessagingException {
|
||||
@ -73,8 +128,8 @@ public class MessageUtils {
|
||||
//String html = (String) bodyPart.getContent();
|
||||
// Not parsing the html right now!
|
||||
// result = org.jsoup.Jsoup.parse(html).text();
|
||||
} else if (bodyPart.getContent() instanceof MimeMultipart){
|
||||
result = getTextFromMimeMultipart((MimeMultipart)bodyPart.getContent());
|
||||
} else if (bodyPart.getContent() instanceof MimeMultipart) {
|
||||
result = getTextFromMimeMultipart((MimeMultipart) bodyPart.getContent());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -1,8 +1,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: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">
|
||||
|
||||
<description>
|
||||
@ -11,27 +11,27 @@
|
||||
|
||||
<rule ref="category/java/bestpractices.xml">
|
||||
<!-- System.println is ok in simple Console App -->
|
||||
<exclude name="SystemPrintln" />
|
||||
<exclude name="SystemPrintln"/>
|
||||
</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">
|
||||
<properties>
|
||||
<property name="packages" value="de.kneitzel" />
|
||||
<property name="packages" value="de.kneitzel"/>
|
||||
<!-- <property name="classes" value="" /> -->
|
||||
</properties>
|
||||
</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>
|
||||
|
||||
43
pom.xml
43
pom.xml
@ -13,6 +13,7 @@
|
||||
<modules>
|
||||
<module>core</module>
|
||||
<module>encryption</module>
|
||||
<module>injection</module>
|
||||
<module>fx</module>
|
||||
<module>gson</module>
|
||||
<module>net</module>
|
||||
@ -22,38 +23,39 @@
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<java.version>21</java.version>
|
||||
<java.version>25</java.version>
|
||||
<required.maven.version>3.8.6</required.maven.version>
|
||||
|
||||
<!-- 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>
|
||||
<junit.version>5.12.1</junit.version>
|
||||
<lombok.version>1.18.38</lombok.version>
|
||||
<mockito.version>5.16.1</mockito.version>
|
||||
<junit.version>6.0.1</junit.version>
|
||||
<lombok.version>1.18.42</lombok.version>
|
||||
<mockito.version>5.20.0</mockito.version>
|
||||
<reflections.version>0.10.2</reflections.version>
|
||||
<slf4j.version>2.0.17</slf4j.version>
|
||||
|
||||
<!-- 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>
|
||||
<jpackage.maven.plugin>0.1.5</jpackage.maven.plugin>
|
||||
<maven.clean.plugin>3.3.2</maven.clean.plugin>
|
||||
<maven.compiler.plugin>3.13.0</maven.compiler.plugin>
|
||||
<maven.clean.plugin>3.5.0</maven.clean.plugin>
|
||||
<maven.compiler.plugin>3.14.1</maven.compiler.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.install.plugin>3.1.2</maven.install.plugin>
|
||||
<maven.jar.plugin>3.4.1</maven.jar.plugin>
|
||||
<maven.javadoc.plugin>3.6.3</maven.javadoc.plugin>
|
||||
<maven.project.info.reports.plugin>3.5.0</maven.project.info.reports.plugin>
|
||||
<maven.resources.plugin>3.3.1</maven.resources.plugin>
|
||||
<maven.jar.plugin>3.5.0</maven.jar.plugin>
|
||||
<maven.javadoc.plugin>3.12.0</maven.javadoc.plugin>
|
||||
<maven.project.info.reports.plugin>3.9.0</maven.project.info.reports.plugin>
|
||||
<maven.resources.plugin>3.4.0</maven.resources.plugin>
|
||||
<maven.shade.plugin>3.5.3</maven.shade.plugin>
|
||||
<maven.site.plugin>4.0.0-M14</maven.site.plugin>
|
||||
<maven.surfire.plugin>3.2.5</maven.surfire.plugin>
|
||||
<maven.site.plugin>4.0.0-M16</maven.site.plugin>
|
||||
<maven.surfire.plugin>3.5.4</maven.surfire.plugin>
|
||||
<moditect.maven.plugin>1.0.0.RC2</moditect.maven.plugin>
|
||||
<maven.pmd.plugin>3.22.0</maven.pmd.plugin>
|
||||
<spotbugs.maven.plugin>4.8.5.0</spotbugs.maven.plugin>
|
||||
<maven.pmd.plugin>3.28.0</maven.pmd.plugin>
|
||||
<spotbugs.maven.plugin>4.9.8.2</spotbugs.maven.plugin>
|
||||
|
||||
<!-- other properties -->
|
||||
<pmd.target>pmd</pmd.target>
|
||||
@ -274,6 +276,9 @@
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sourcepath>${project.build.sourceDirectory}</sourcepath>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
@ -347,15 +352,19 @@
|
||||
<report>javadoc</report>
|
||||
</reports>
|
||||
</reportSet>
|
||||
<!--
|
||||
<reportSet>
|
||||
<id>tests</id>
|
||||
<configuration>
|
||||
<show>private</show>
|
||||
<failOnError>false</failOnError>
|
||||
<quiet>true</quiet>
|
||||
</configuration>
|
||||
<reports>
|
||||
<report>test-javadoc</report>
|
||||
</reports>
|
||||
</reportSet>
|
||||
-->
|
||||
</reportSets>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user