Work in progress ...

This commit is contained in:
Konrad Neitzel
2025-11-30 18:58:05 +01:00
parent 9bbddaaae9
commit 5503dd192e
64 changed files with 4855 additions and 4252 deletions
+2 -2
View File
@@ -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.
@@ -41,16 +168,16 @@ public class Parameter {
/**
* Constructs a new Parameter with the given attributes.
*
* @param name The name of the parameter.
* @param minNumberValues The minimum number of values allowed for this parameter.
* @param maxNumberValues The maximum number of values allowed for this parameter.
* @param multipleEntries Indicates whether multiple entries are allowed for this parameter.
* @param shortDescription A brief description of the parameter.
* @param longDescription A detailed description of the parameter.
* @param callback A consumer function that processes a list of values associated with this parameter.
* @param isHelpCommand Indicates whether this parameter represents a help command.
* @param name The name of the parameter.
* @param minNumberValues The minimum number of values allowed for this parameter.
* @param maxNumberValues The maximum number of values allowed for this parameter.
* @param multipleEntries Indicates whether multiple entries are allowed for this parameter.
* @param shortDescription A brief description of the parameter.
* @param longDescription A detailed description of the parameter.
* @param callback A consumer function that processes a list of values associated with this parameter.
* @param isHelpCommand Indicates whether this parameter represents a help command.
* @param isDefaultParameter Indicates whether this parameter is the default parameter.
* @param aliasList A list of aliases for this parameter.
* @param aliasList A list of aliases for this parameter.
*/
public Parameter(String name, int minNumberValues, int maxNumberValues, boolean multipleEntries, String shortDescription, String longDescription, Consumer<List<String>> callback, boolean isHelpCommand, boolean isDefaultParameter, List<String> aliasList) {
this.name = name;
@@ -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,32 +19,32 @@ 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
* details such as descriptions, value constraints, aliases, and processing logic.
* details such as descriptions, value constraints, aliases, and processing logic.
*/
private Map<String, Parameter> parameters = new HashMap<>();
/**
* 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).
* 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.
*
* 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.
@@ -163,8 +197,8 @@ public class Parser {
* than `min`, it will be adjusted to match `min`. Only up to `max`
* arguments will be added to the result.
* @return A list of argument strings, containing up to `max` elements and at least `min`
* elements if sufficient arguments are available. The list will not include
* arguments already recognized in the `parameters` map.
* elements if sufficient arguments are available. The list will not include
* arguments already recognized in the `parameters` map.
*/
private List<String> getOptions(ArgumentProvider provider, int min, int max) {
if (max < min) max = min;
@@ -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());
}
}
}
}
@@ -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,59 +37,17 @@ 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.
*
* @param originalImageBytes The byte array representing the original image.
* @param width The (maximum) width of the target scaled image.
* @param height The (maximum) height of the target scaled image.
* @param enforceSize A flag indicating whether to enforce the exact scaled dimensions. If false, the resulting image retains its aspect ratio.
* @param width The (maximum) width of the target scaled image.
* @param height The (maximum) height of the target scaled image.
* @param enforceSize A flag indicating whether to enforce the exact scaled dimensions. If false, the resulting image retains its aspect ratio.
* @return An InputStream representing the scaled image, or null if the operation fails or the input image is invalid.
*/
public static InputStream scaledImage(final byte[] originalImageBytes, final int width, final int height, final boolean enforceSize) {
@@ -91,20 +62,49 @@ public class ImageScaler {
}
/**
* Creates a scaled version of the given {@link BufferedImage} and returns it as a byte array.
* Creates a scaled image from the given byte array representation and returns it as a byte array.
* <p>
* If enforceSize is set to false, the given dimensions are used as maximum values for scaling.
* The resulting image may retain its aspect ratio and be smaller than the specified dimensions.
* If enforceSize is true, the resulting image will match the exact dimensions, potentially adding
* transparent areas on the top/bottom or right/left sides to maintain the original aspect ratio.
*
* @param originalImageBytes The byte array representing the original image.
* @param width The (maximum) width of the target image.
* @param height The (maximum) height of the target image.
* @param enforceSize A flag indicating whether the resulting image should strictly adhere to specified dimensions.
* If false, the image will retain its aspect ratio without empty borders.
* @return A byte array representing the scaled image, or null if the operation failed or the input was invalid.
*/
public static byte[] createScaledImage(final byte[] originalImageBytes, final int width, final int height, final boolean enforceSize) {
// Validation
if (originalImageBytes == null) return null;
if (originalImageBytes.length == 0) return null;
try {
// Create the image from a byte array.
BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(originalImageBytes));
return createScaledImage(originalImage, width, height, enforceSize);
} catch (Exception ex) {
return null;
}
}
/**
* Creates a scaled version of the given {@link BufferedImage} and returns it as a byte array.
* <p>
* The scaling behavior is determined by the enforceSize parameter:
* - If enforceSize is false, the provided dimensions are treated as maximum values, and the image
* will maintain its original aspect ratio. The resulting image may be smaller than the specified
* dimensions.
* will maintain its original aspect ratio. The resulting image may be smaller than the specified
* dimensions.
* - If enforceSize is true, the resulting image will match the exact specified dimensions, potentially
* adding transparent padding to fit the aspect ratio.
* adding transparent padding to fit the aspect ratio.
*
* @param originalImage The original image to be scaled. Must not be null.
* @param width The specified (maximum) width of the scaled image.
* @param height The specified (maximum) height of the scaled image.
* @param enforceSize A flag indicating whether the scaled image should strictly conform to the specified
* dimensions. If true, the image may include transparent areas to maintain the aspect ratio.
* @param width The specified (maximum) width of the scaled image.
* @param height The specified (maximum) height of the scaled image.
* @param enforceSize A flag indicating whether the scaled image should strictly conform to the specified
* dimensions. If true, the image may include transparent areas to maintain the aspect ratio.
* @return A byte array representing the scaled image, or null if the scaling operation fails or the input image is invalid.
*/
protected static byte[] createScaledImage(final BufferedImage originalImage, final int width, final int height, final boolean enforceSize) {
@@ -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,17 +52,17 @@ 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.
*
* @param <T> the type of the component to retrieve
* @param <T> the type of the component to retrieve
* @param type the {@code Class} object representing the type of the component
* @return an instance of the requested component type
* @throws IllegalArgumentException if no component is found for the specified type
* @throws IllegalStateException if no suitable constructor is found for the component
* @throws RuntimeException if any error occurs during instantiation
* @throws IllegalStateException if no suitable constructor is found for the component
* @throws RuntimeException if any error occurs during instantiation
*/
@SuppressWarnings("unchecked")
public <T> T getComponent(Class<? extends T> type) {
@@ -85,7 +85,7 @@ public class ApplicationContext {
Class<?>[] paramTypes = constructor.getParameterTypes();
Object[] parameters = new Object[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++) {
parameters[i] = getComponent(paramTypes[i]);
parameters[i] = getComponent(paramTypes[i]);
}
instance = constructor.newInstance(parameters);
if (scope == Scope.SINGLETON) {
@@ -103,12 +103,12 @@ 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.
*
* @param constructor the constructor to check for instantiation feasibility
* @param constructor the constructor to check for instantiation feasibility
* @param parameterMap a map containing available parameter types and their associated component data
* @return true if all parameter types required by the constructor are contained in the parameter map, false otherwise
*/
@@ -118,14 +118,14 @@ public class ApplicationContext {
/**
* Registers a singleton instance of a specific type into the application context.
*
* <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}
* is thrown to prevent replacement of an existing singleton.
*
* @param <T> the type of the component to register
* @param type the class type of the component being added
* @param <T> the type of the component to register
* @param type the class type of the component being added
* @param instance the singleton instance of the component to register
* @throws IllegalStateException if the type is already registered with a different instance
*/
@@ -4,7 +4,7 @@ import lombok.Getter;
/**
* Represents a component in a dependency injection framework.
*
* <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
@@ -47,7 +47,7 @@ public class ComponentData {
/**
* Constructs a new Component instance with the specified type and scope.
*
* @param type the class type of the component
* @param type the class type of the component
* @param scope the scope of the component, which determines its lifecycle
*/
public ComponentData(Class<?> type, Scope scope) {
@@ -57,10 +57,10 @@ public class ComponentData {
/**
* Constructs a new ComponentData instance with the specified type and an initial instance.
*
* <p>
* This can be used to add Singletons manually without scanning for them.
*
* @param type the class type of the component
* @param type the class type of the component
* @param instance the initial instance of the component
*/
public ComponentData(Class<?> type, Object instance) {
@@ -5,7 +5,13 @@ import de.neitzel.core.inject.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.
*
* - 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,22 +166,22 @@ 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
* set of known types.
* map. A component is resolvable if its dependencies can be satisfied by the current
* set of known types.
* 2. Registering resolvable components and their superclasses/interfaces into the known
* types map for future iterations.
* types map for future iterations.
* 3. Removing successfully resolved components from the unresolved set.
* 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.
* to their corresponding instantiable implementations.
* - Unresolved components are identified, and error details are collected to highlight
* dependencies or conflicts preventing their instantiation.
*
* dependencies or conflicts preventing their instantiation.
* <p>
* If errors are encountered due to unresolved components, they are logged for further analysis.
*/
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;
}
/**
@@ -232,7 +239,7 @@ public class ComponentScanner {
* Additionally, all fields annotated with {@code @Inject} must have their types present in the
* known types set.
*
* @param component the class to check for instantiability
* @param component the class to check for instantiability
* @param knownTypes the set of currently known types that can be used to satisfy dependencies
* @return {@code true} if the component can be instantiated; {@code false} otherwise
*/
@@ -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
@@ -304,7 +329,7 @@ public class ComponentScanner {
*
* @param component the class for which conflicting types need to be identified.
* @return a comma-separated string of fully qualified names of conflicting parameter types,
* or an empty string if no conflicts are found.
* or an empty string if no conflicts are found.
*/
private String getConflictingTypes(Class<?> component) {
return Arrays.stream(component.getConstructors())
@@ -314,30 +339,11 @@ 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.
*
* @return A map where the key is a class type and the value is the corresponding class implementation
* that can be instantiated.
* that can be instantiated.
*/
public Map<Class<?>, ComponentData> getInstantiableComponents() {
return instantiableComponents;
@@ -349,7 +355,7 @@ public class ComponentScanner {
* component scanning and analysis process.
*
* @return a set of classes representing the types that have multiple
* implementations and cannot be resolved uniquely
* implementations and cannot be resolved uniquely
*/
public Set<Class<?>> getNotUniqueTypes() {
return notUniqueTypes;
@@ -361,7 +367,7 @@ public class ComponentScanner {
* implementation, ensuring no ambiguity in resolution.
*
* @return a map where the keys are types (e.g., interfaces or superclasses)
* and the values are their unique component implementations.
* and the values are their unique component implementations.
*/
public Map<Class<?>, Class<?>> getUniqueTypeToComponent() {
return uniqueTypeToComponent;
@@ -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.
@@ -10,17 +10,17 @@ 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}.
* for classes annotated with dependency injection annotations such as {@code @Component}.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@@ -31,7 +31,7 @@ public @interface Config {
* can be scanned and identified as candidates for dependency injection.
*
* @return the base package name as a string; returns an empty string by default
* if no specific package is defined.
* if no specific package is defined.
*/
String basePackage() default "";
}
@@ -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.
@@ -2,7 +2,11 @@ package de.neitzel.core.io;
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;
@@ -3,7 +3,15 @@ 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;
@@ -13,7 +13,7 @@ public class StringEncoder {
* static utility methods for array-related operations.
*
* @throws UnsupportedOperationException always, to indicate that this class
* should not be instantiated.
* should not be instantiated.
*/
private StringEncoder() {
throw new UnsupportedOperationException("Utility class");
@@ -27,7 +27,7 @@ public class StringEncoder {
* @param data the string to decode; may contain encoded characters or regular text. If null, an empty string is returned.
* @return the decoded string with all encoded characters replaced by their original representations.
* @throws IllegalArgumentException if the input string has encoding markup
* that is improperly formatted or incomplete.
* that is improperly formatted or incomplete.
*/
public static String decodeData(final String data) {
if (data == null) return "";
@@ -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));
}
/**
@@ -77,16 +77,16 @@ public class StringEncoder {
*
* @param data the input string to encode; if null, an empty string is returned
* @return an encoded string where non-ASCII and ampersand characters
* are replaced with numeric character references
* are replaced with numeric character references
*/
public static String encodeData(final String data) {
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);
}
@@ -16,16 +16,30 @@ public class ToneGenerator {
* static utility methods for array-related operations.
*
* @throws UnsupportedOperationException always, to indicate that this class
* should not be instantiated.
* should not be instantiated.
*/
private 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.
*
* @param tone The name of the tone to play. Must correspond to a predefined tone in the tone table.
* @param tone The name of the tone to play. Must correspond to a predefined tone in the tone table.
* @param duration The duration of the tone in milliseconds.
* @throws LineUnavailableException If a line for audio playback cannot be opened.
*/
@@ -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.
+304 -296
View File
@@ -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;
@@ -23,311 +30,312 @@ import java.util.stream.StreamSupport;
@Slf4j
public class Query<T> {
/**
* A factory function that extracts an Integer from the first column of a given ResultSet.
* <p>
* This function is designed to facilitate the conversion of a ResultSet row into an Integer
* by accessing the value in the first column of the result set. If an SQL exception is encountered
* during the retrieval of the integer value, it is wrapped and re-thrown as a runtime exception.
*/
public static final Function<ResultSet, Integer> INTEGER_FACTORY = rs -> {
try {
return rs.getInt(1);
} catch (SQLException e) {
throw new RuntimeException(e);
}
};
/**
* Represents a database connection instance.
* This connection is used to interact with a database
* through executing queries and retrieving results.
* It is immutable and initialized once.
*/
private final Connection connection;
/**
* A list of ParameterSetter objects used to configure parameters.
* This collection serves as a storage for managing parameter-setting logic dynamically.
* It is initialized as an empty ArrayList and is immutable since it is declared as final.
*/
private final List<ParameterSetter> parameterSetters = new ArrayList<>();
/**
* A functional interface representing a factory method used to produce instances of type T.
* This factory is typically implemented to map rows from a {@link ResultSet} to objects of type T.
* The input parameter is a {@link ResultSet}, which provides access to database query results.
* The resulting output is an instance of type T created based on the data in the provided ResultSet.
*/
private Function<ResultSet, T> factory;
/**
* Represents the text of a query.
* This variable stores the query string used for processing or execution purposes within the application.
*/
private String queryText;
/**
* Constructs a new Query object with the given database connection.
*
* @param connection The Connection object used to interact with the database.
*/
public Query(Connection connection) {
this.connection = connection;
}
/**
* Executes the given SQL query using the provided database connection.
*
* @param connection the database connection to be used for executing the query
* @param query the SQL query to be executed
* @throws SQLException if a database access error occurs or the query execution fails
*/
public static void execute(final Connection connection, final String query) throws SQLException {
new Query<Object>(connection)
.setQueryText(query)
.execute();
}
/**
* Sets the query text for the current query instance.
*
* @param queryText the text of the query to be set
* @return the current instance of the query
*/
public Query<T> setQueryText(final String queryText) {
this.queryText = queryText;
return this;
}
/**
* Sets the query resource by loading its content from the given resource path.
* The resource content is read and stored as the query text for the query object.
*
* @param resourcePath the path to the resource file containing the query text
* @return the current Query object with the loaded query text
*/
public Query<T> setQueryResource(final String resourcePath) {
log.info("loading resource: {}", resourcePath);
try (Scanner scanner = new Scanner(Objects.requireNonNull(getClass().getResourceAsStream(resourcePath)),
StandardCharsets.UTF_8)) {
this.queryText = scanner.useDelimiter("\\A").next();
}
log.info("query text: {}", this.queryText);
return this;
}
/**
* Replaces all occurrences of the specified placeholder in the query text with the provided value.
*
* @param placeholder the placeholder string to be replaced in the query text
* @param value the value to replace the placeholder with
* @return the updated Query instance with the modified query text
*/
public Query<T> replace(final String placeholder, final String value) {
this.queryText = this.queryText.replace(placeholder, value);
log.info("query text: {}", this.queryText);
return this;
}
/**
* Adds a parameter to the query at the specified index with a custom setter function.
*
* @param index The position of the parameter in the query starting from 1.
* @param value The value of the parameter to be set.
* @param setter A TriSqlFunction that defines how to set the parameter in the PreparedStatement.
* @param <X> The type of the parameter value.
* @return The current query instance for method chaining.
*/
public <X> Query<T> addParameter(final int index, final X value, final TriSqlFunction<PreparedStatement, Integer, X> setter) {
parameterSetters.add(ps -> setter.apply(ps, index, value));
return this;
}
/**
* Adds a parameter to the query using the specified ParameterSetter.
*
* @param setter the ParameterSetter used to configure the parameter
* @return the current Query instance for method chaining
*/
public Query<T> addParameter(ParameterSetter setter) {
parameterSetters.add(setter);
return this;
}
/**
* Sets the factory function used to convert a {@link ResultSet} into an instance of type {@code T}.
*
* @param factory the function responsible for mapping a {@code ResultSet} to an object of type {@code T}
* @return the current {@code Query} instance with the specified factory function set
*/
public Query<T> factory(final Function<ResultSet, T> factory) {
this.factory = factory;
return this;
}
/**
* Creates a {@link Stream} from the result set obtained by executing a prepared statement.
* The result set is wrapped in a {@link TrimmingResultSet} to handle data trimming.
*
* @return a {@link Stream} of type T populated with the query results processed using the specified factory
* @throws SQLException if an SQL error occurs while executing the prepared statement or creating the stream
*/
private Stream<T> stream() throws SQLException {
ResultSet rs = createPreparedStatement().executeQuery();
TrimmingResultSet trimmingResultSet = new TrimmingResultSet(rs);
return streamFromResultSet(trimmingResultSet, factory);
}
/**
* Creates and returns a PreparedStatement object configured with the query text
* and parameters defined for the current instance.
*
* @return a PreparedStatement object initialized with the query and parameter values
* @throws SQLException if a database access error occurs or the PreparedStatement cannot be created
*/
private PreparedStatement createPreparedStatement() throws SQLException {
PreparedStatement ps = connection.prepareStatement(queryText);
for (ParameterSetter setter : parameterSetters) {
setter.apply(ps);
}
return ps;
}
/**
* Executes the SQL statement represented by the PreparedStatement created in
* the method {@code createPreparedStatement()}. This method is used to perform
* database operations such as updates or other actions that do not produce a
* result set.
*
* @throws SQLException if a database access error occurs or the SQL statement is invalid.
*/
public void execute() throws SQLException {
try (PreparedStatement ps = createPreparedStatement()) {
ps.execute();
}
}
/**
* Converts a {@link ResultSet} into a {@link Stream} of objects created using the provided {@link Function}
* factory. The stream ensures resources such as the {@code ResultSet} and its associated statement are
* closed when the stream is closed.
*
* @param rs the {@link ResultSet} to be transformed into a stream
* @param factory a {@link Function} that maps rows of the {@link ResultSet} to objects of type T
* @return a {@link Stream} of objects of type T created from the {@code ResultSet} rows
*/
private Stream<T> streamFromResultSet(final ResultSet rs, final Function<ResultSet, T> factory) {
Iterator<T> iterator = new Iterator<>() {
private boolean hasNextChecked = false;
private boolean hasNext;
/**
* Checks if the iteration has more elements. This method verifies if the
* underlying {@link ResultSet} contains another row that has not yet been processed.
*
* @return {@code true} if there are more elements in the {@link ResultSet},
* otherwise {@code false}.
*/
@Override
@SneakyThrows
public boolean hasNext() {
if (!hasNextChecked) {
hasNext = rs.next();
hasNextChecked = true;
/**
* A factory function that extracts an Integer from the first column of a given ResultSet.
* <p>
* This function is designed to facilitate the conversion of a ResultSet row into an Integer
* by accessing the value in the first column of the result set. If an SQL exception is encountered
* during the retrieval of the integer value, it is wrapped and re-thrown as a runtime exception.
*/
public static final Function<ResultSet, Integer> INTEGER_FACTORY = rs -> {
try {
return rs.getInt(1);
} catch (SQLException e) {
throw new RuntimeException(e);
}
return hasNext;
}
/**
* Returns the next element in the iteration. This method applies the provided
* factory function to the current row of the {@link ResultSet} to create an object of type T.
* It throws {@link NoSuchElementException} if there are no more elements to iterate.
*
* @return the next element of type T as created by the factory function from the current row of the {@link ResultSet}
* @throws NoSuchElementException if the iteration has no more elements
*/
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
hasNextChecked = false;
return factory.apply(rs);
}
};
Spliterator<T> spliterator = Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED);
return StreamSupport.stream(spliterator, false)
.onClose(() -> {
try {
rs.getStatement().close();
} catch (SQLException ignored) {
// Exception is ignored.
}
try {
rs.close();
} catch (SQLException ignored) {
// Exception is ignored.
}
});
}
/**
* Provides a managed stream to the given handler and ensures proper resource closing after use.
* 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 database connection instance.
* This connection is used to interact with a database
* through executing queries and retrieving results.
* It is immutable and initialized once.
*/
private final Connection connection;
/**
* A list of ParameterSetter objects used to configure parameters.
* This collection serves as a storage for managing parameter-setting logic dynamically.
* It is initialized as an empty ArrayList and is immutable since it is declared as final.
*/
private final List<ParameterSetter> parameterSetters = new ArrayList<>();
/**
* A functional interface representing a factory method used to produce instances of type T.
* This factory is typically implemented to map rows from a {@link ResultSet} to objects of type T.
* The input parameter is a {@link ResultSet}, which provides access to database query results.
* The resulting output is an instance of type T created based on the data in the provided ResultSet.
*/
private Function<ResultSet, T> factory;
/**
* Represents the text of a query.
* This variable stores the query string used for processing or execution purposes within the application.
*/
private String queryText;
/**
* Constructs a new Query object with the given database connection.
*
* @param connection The Connection object used to interact with the database.
*/
public Query(Connection connection) {
this.connection = connection;
}
}
/**
* Represents a functional interface that accepts three input arguments and allows for operations
* that can throw an {@link SQLException}. This can be useful in scenarios where SQL-related
* logic requires processing with three parameters.
*
* @param <T> the type of the first input to the operation
* @param <U> the type of the second input to the operation
* @param <V> the type of the third input to the operation
*/
@FunctionalInterface
public interface TriSqlFunction<T, U, V> {
/**
* Executes an operation on three input arguments and allows for the operation to throw
* an {@link SQLException}. This method is intended to facilitate SQL-related processes
* where three parameters are involved.
* Executes the given SQL query using the provided database connection.
*
* @param t the first input argument
* @param u the second input argument
* @param v the third input argument
* @throws SQLException if an SQL error occurs during execution
* @param connection the database connection to be used for executing the query
* @param query the SQL query to be executed
* @throws SQLException if a database access error occurs or the query execution fails
*/
void apply(T t, U u, V v) throws SQLException;
}
/**
* Functional interface designed to handle operations on a PreparedStatement.
* Represents a single abstract method that performs specific work on the
* provided PreparedStatement instance.
*/
@FunctionalInterface
public interface ParameterSetter {
public static void execute(final Connection connection, final String query) throws SQLException {
new Query<Object>(connection)
.setQueryText(query)
.execute();
}
/**
* Applies an operation to the given PreparedStatement object.
* This method is intended to set parameters or perform other modifications on a PreparedStatement.
* 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.
*
* @param ps the PreparedStatement instance to be modified or configured
* @throws SQLException if a database access error occurs while applying the operation
* @throws SQLException if a database access error occurs or the SQL statement is invalid.
*/
void apply(PreparedStatement ps) throws SQLException;
}
public void execute() throws SQLException {
try (PreparedStatement ps = createPreparedStatement()) {
ps.execute();
}
}
/**
* Sets the query text for the current query instance.
*
* @param queryText the text of the query to be set
* @return the current instance of the query
*/
public Query<T> setQueryText(final String queryText) {
this.queryText = queryText;
return this;
}
/**
* Creates and returns a PreparedStatement object configured with the query text
* and parameters defined for the current instance.
*
* @return a PreparedStatement object initialized with the query and parameter values
* @throws SQLException if a database access error occurs or the PreparedStatement cannot be created
*/
private PreparedStatement createPreparedStatement() throws SQLException {
PreparedStatement ps = connection.prepareStatement(queryText);
for (ParameterSetter setter : parameterSetters) {
setter.apply(ps);
}
return ps;
}
/**
* Sets the query resource by loading its content from the given resource path.
* The resource content is read and stored as the query text for the query object.
*
* @param resourcePath the path to the resource file containing the query text
* @return the current Query object with the loaded query text
*/
public Query<T> setQueryResource(final String resourcePath) {
log.info("loading resource: {}", resourcePath);
try (Scanner scanner = new Scanner(Objects.requireNonNull(getClass().getResourceAsStream(resourcePath)),
StandardCharsets.UTF_8)) {
this.queryText = scanner.useDelimiter("\\A").next();
}
log.info("query text: {}", this.queryText);
return this;
}
/**
* Replaces all occurrences of the specified placeholder in the query text with the provided value.
*
* @param placeholder the placeholder string to be replaced in the query text
* @param value the value to replace the placeholder with
* @return the updated Query instance with the modified query text
*/
public Query<T> replace(final String placeholder, final String value) {
this.queryText = this.queryText.replace(placeholder, value);
log.info("query text: {}", this.queryText);
return this;
}
/**
* Adds a parameter to the query at the specified index with a custom setter function.
*
* @param index The position of the parameter in the query starting from 1.
* @param value The value of the parameter to be set.
* @param setter A TriSqlFunction that defines how to set the parameter in the PreparedStatement.
* @param <X> The type of the parameter value.
* @return The current query instance for method chaining.
*/
public <X> Query<T> addParameter(final int index, final X value, final TriSqlFunction<PreparedStatement, Integer, X> setter) {
parameterSetters.add(ps -> setter.apply(ps, index, value));
return this;
}
/**
* Adds a parameter to the query using the specified ParameterSetter.
*
* @param setter the ParameterSetter used to configure the parameter
* @return the current Query instance for method chaining
*/
public Query<T> addParameter(ParameterSetter setter) {
parameterSetters.add(setter);
return this;
}
/**
* Sets the factory function used to convert a {@link ResultSet} into an instance of type {@code T}.
*
* @param factory the function responsible for mapping a {@code ResultSet} to an object of type {@code T}
* @return the current {@code Query} instance with the specified factory function set
*/
public Query<T> factory(final Function<ResultSet, T> factory) {
this.factory = factory;
return this;
}
/**
* Provides a managed stream to the given handler and ensures proper resource closing after use.
* A {@code Stream} is opened and passed to the specified handler as an argument.
* The stream will be automatically closed when the handler finishes execution or throws an exception.
*
* @param handler a function which receives a {@code Stream<T>} and processes it, returning a result of type {@code R}.
* @param <R> the type of the result produced by the handler.
* @return the result returned by the handler after processing the stream.
* @throws SQLException if an SQL error occurs during the creation or handling of the stream.
*/
public <R> R withStream(Function<Stream<T>, R> handler) throws SQLException {
try (Stream<T> stream = stream()) {
return handler.apply(stream);
}
}
/**
* Creates a {@link Stream} from the result set obtained by executing a prepared statement.
* The result set is wrapped in a {@link TrimmingResultSet} to handle data trimming.
*
* @return a {@link Stream} of type T populated with the query results processed using the specified factory
* @throws SQLException if an SQL error occurs while executing the prepared statement or creating the stream
*/
private Stream<T> stream() throws SQLException {
ResultSet rs = createPreparedStatement().executeQuery();
TrimmingResultSet trimmingResultSet = new TrimmingResultSet(rs);
return streamFromResultSet(trimmingResultSet, factory);
}
/**
* Converts a {@link ResultSet} into a {@link Stream} of objects created using the provided {@link Function}
* factory. The stream ensures resources such as the {@code ResultSet} and its associated statement are
* closed when the stream is closed.
*
* @param rs the {@link ResultSet} to be transformed into a stream
* @param factory a {@link Function} that maps rows of the {@link ResultSet} to objects of type T
* @return a {@link Stream} of objects of type T created from the {@code ResultSet} rows
*/
private Stream<T> streamFromResultSet(final ResultSet rs, final Function<ResultSet, T> factory) {
Iterator<T> iterator = new Iterator<>() {
private boolean hasNextChecked = false;
private boolean hasNext;
/**
* Checks if the iteration has more elements. This method verifies if the
* underlying {@link ResultSet} contains another row that has not yet been processed.
*
* @return {@code true} if there are more elements in the {@link ResultSet},
* otherwise {@code false}.
*/
@Override
@SneakyThrows
public boolean hasNext() {
if (!hasNextChecked) {
hasNext = rs.next();
hasNextChecked = true;
}
return hasNext;
}
/**
* Returns the next element in the iteration. This method applies the provided
* factory function to the current row of the {@link ResultSet} to create an object of type T.
* It throws {@link NoSuchElementException} if there are no more elements to iterate.
*
* @return the next element of type T as created by the factory function from the current row of the {@link ResultSet}
* @throws NoSuchElementException if the iteration has no more elements
*/
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
hasNextChecked = false;
return factory.apply(rs);
}
};
Spliterator<T> spliterator = Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED);
return StreamSupport.stream(spliterator, false)
.onClose(() -> {
try {
rs.getStatement().close();
} catch (SQLException ignored) {
// Exception is ignored.
}
try {
rs.close();
} catch (SQLException ignored) {
// Exception is ignored.
}
});
}
/**
* Represents a functional interface that accepts three input arguments and allows for operations
* that can throw an {@link SQLException}. This can be useful in scenarios where SQL-related
* logic requires processing with three parameters.
*
* @param <T> the type of the first input to the operation
* @param <U> the type of the second input to the operation
* @param <V> the type of the third input to the operation
*/
@FunctionalInterface
public interface TriSqlFunction<T, U, V> {
/**
* Executes an operation on three input arguments and allows for the operation to throw
* an {@link SQLException}. This method is intended to facilitate SQL-related processes
* where three parameters are involved.
*
* @param t the first input argument
* @param u the second input argument
* @param v the third input argument
* @throws SQLException if an SQL error occurs during execution
*/
void apply(T t, U u, V v) throws SQLException;
}
/**
* Functional interface designed to handle operations on a PreparedStatement.
* Represents a single abstract method that performs specific work on the
* provided PreparedStatement instance.
*/
@FunctionalInterface
public interface ParameterSetter {
/**
* Applies an operation to the given PreparedStatement object.
* This method is intended to set parameters or perform other modifications on a PreparedStatement.
*
* @param ps the PreparedStatement instance to be modified or configured
* @throws SQLException if a database access error occurs while applying the operation
*/
void apply(PreparedStatement ps) throws SQLException;
}
}
File diff suppressed because it is too large Load Diff
@@ -13,7 +13,7 @@ public class ArrayUtils {
* static utility methods for array-related operations.
*
* @throws UnsupportedOperationException always, to indicate that this class
* should not be instantiated.
* should not be instantiated.
*/
private ArrayUtils() {
throw new UnsupportedOperationException("Utility class");
@@ -23,11 +23,11 @@ public class ArrayUtils {
* Checks if the given character array contains the specified character.
*
* @param array the character array to be searched
* @param ch the character to search for in the array
* @param ch the character to search for in the array
* @return true if the character is found in the array, false otherwise
*/
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;
@@ -37,11 +37,11 @@ public class ArrayUtils {
* Checks if the specified integer array contains the given integer value.
*
* @param array the array of integers to search
* @param ch the integer value to search for in the array
* @param ch the integer value to search for in the array
* @return true if the array contains the specified integer value, false otherwise
*/
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;
@@ -51,11 +51,11 @@ public class ArrayUtils {
* Checks if the specified long array contains the given long value.
*
* @param array the array of long values to search
* @param ch the long value to search for in the array
* @param ch the long value to search for in the array
* @return true if the array contains the specified long value, false otherwise
*/
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;
@@ -65,12 +65,12 @@ public class ArrayUtils {
* Checks if the specified array contains the given element.
*
* @param array the array to be searched
* @param ch the element to search for in the array
* @param <T> the type of the elements in the array
* @param ch the element to search for in the array
* @param <T> the type of the elements in the array
* @return true if the element is found in the array, false otherwise
*/
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;
@@ -14,7 +14,7 @@ public class EnumUtil {
* static utility methods for array-related operations.
*
* @throws UnsupportedOperationException always, to indicate that this class
* should not be instantiated.
* should not be instantiated.
*/
private EnumUtil() {
throw new UnsupportedOperationException("Utility class");
@@ -25,7 +25,7 @@ public class EnumUtil {
* of the enum constants within the specified Enum class. The pattern also includes optional delimiters
* such as commas, spaces, and empty values, enabling the matching of lists or sequences of flag values.
*
* @param <T> The type of the Enum.
* @param <T> The type of the Enum.
* @param clazz The Enum class for which the regular expression is to be generated.
* @return A String containing the regular expression pattern for matching the Enum's flag values.
*/
@@ -33,9 +33,9 @@ public class EnumUtil {
StringBuilder result = new StringBuilder();
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,25 +52,25 @@ 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
* to the Enum constants.
*
* @param <T> The type of the Enum.
* @param <T> The type of the Enum.
* @param clazz The Enum class to which the flags should be parsed.
* @param flags A string containing the delimited list of flag names to parse.
* Individual names can be separated by commas or whitespace.
* @return A list of Enum constants parsed from the input string. If the input string
* is null or empty, an empty list is returned.
* is null or empty, an empty list is returned.
* @throws IllegalArgumentException if any of the flag names do not match the constants in the given Enum class.
* @throws NullPointerException if the clazz parameter is null.
* @throws NullPointerException if the clazz parameter is null.
*/
public static <T extends Enum<T>> List<T> parseFlags(final Class<T> clazz, final String flags) {
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.
*/
@@ -50,21 +50,36 @@ public class RegionalizationMessage {
*
* @param key The key for which the localized message needs to be retrieved.
* @return The localized message as a String if the key exists in the resource bundle;
* otherwise, null.
* otherwise, null.
*/
public String getMessage(final String key) {
if (!res.containsKey(key)) return null;
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.
*
* @param key The key to look up in the resource bundle.
* @param key The key to look up in the resource bundle.
* @param defaultMessage The default message to return if the key is not found.
* @return The localized message corresponding to the key, or the default message
* if the key does not exist in the resource bundle.
* if the key does not exist in the resource bundle.
*/
public String getMessage(final String key, final String defaultMessage) {
if (res.containsKey(key))
@@ -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,122 +7,140 @@ import java.util.Locale;
/**
* A utility class for measuring elapsed time. The StopWatch can be started, stopped, and queried for the elapsed time in various formats.
*/
@SuppressWarnings("unused")
public class StopWatch {
/**
* Represents the starting time of the stopwatch. This variable is set when the stopwatch is started and is used to calculate the elapsed
* 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;
/**
* Represents the starting time of the stopwatch. This variable is set when the stopwatch is started and is used to calculate the elapsed
* duration between the start and the stop or the current time.
*/
private Instant startTime;
/**
* Starts the stopwatch by recording the current time as the starting time. If the stopwatch was previously stopped or not yet started,
* this method resets the starting time to the current time and clears the stopping time. This method is typically called before measuring
* elapsed time.
*/
public void start() {
this.startTime = Instant.now();
this.stopTime = null;
}
/**
* 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;
/**
* Stops the stopwatch by recording the current time as the stopping time.
* <p>
* This method should only be called after the stopwatch has been started using the {@code start()} method. If the stopwatch has not been
* started, an {@code IllegalStateException} will be thrown. Once this method is called, the elapsed time can be calculated as the
* duration between the starting and stopping times.
*
* @throws IllegalStateException if the stopwatch has not been started prior to calling this method.
*/
public void stop() {
if (startTime == null) {
throw new IllegalStateException("StopWatch must be started before stopping.");
}
this.stopTime = Instant.now();
}
/**
* Calculates the amount of time elapsed between the stopwatch's starting time and either its stopping time or the current time if the
* stopwatch is still running.
*
* @return the duration representing the elapsed time between the start and end points of the stopwatch.
* @throws IllegalStateException if the stopwatch has not been started before calling this method.
*/
public Duration getUsedTime() {
if (startTime == null) {
throw new IllegalStateException("StopWatch has not been started.");
}
Instant end = (stopTime != null) ? stopTime : Instant.now();
return Duration.between(startTime, end);
}
/**
* Formats the duration of elapsed time measured by the stopwatch into a human-readable string. The formatted output may include days,
* hours, minutes, seconds, and milliseconds, depending on the parameters. The method retrieves the elapsed time via the
* {@code getUsedTime} method and formats it based on the specified flags.
*
* @param includeMillis whether to include milliseconds in the formatted output
* @param includeSeconds whether to include seconds in the formatted output
* @param includeMinutes whether to include minutes in the formatted output
* @param includeHours whether to include hours in the formatted output
* @param includeDays whether to include days in the formatted output
* @return a formatted string representing the elapsed time, including the components specified by the parameters
*/
public String getUsedTimeFormatted(boolean includeMillis, boolean includeSeconds, boolean includeMinutes, boolean includeHours,
boolean includeDays) {
Duration duration = getUsedTime();
long millis = duration.toMillis();
long days = millis / (24 * 60 * 60 * 1000);
millis %= (24 * 60 * 60 * 1000);
long hours = millis / (60 * 60 * 1000);
millis %= (60 * 60 * 1000);
long minutes = millis / (60 * 1000);
millis %= (60 * 1000);
long seconds = millis / 1000;
millis %= 1000;
StringBuilder sb = new StringBuilder();
if (includeDays && days > 0) {
sb.append(days).append("d ");
}
if (includeHours && (hours > 0 || sb.length() > 0)) {
sb.append(hours).append("h ");
}
if (includeMinutes && (minutes > 0 || sb.length() > 0)) {
sb.append(minutes).append("m ");
}
if (includeSeconds && (seconds > 0 || sb.length() > 0)) {
sb.append(seconds);
}
if (includeMillis) {
if (includeSeconds) {
sb.append(String.format(Locale.US, ".%03d", millis));
} else {
sb.append(millis).append("ms");
}
/**
* 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 ...
}
String result = sb.toString().trim();
return result.isEmpty() ? "0ms" : result;
}
/**
* Starts the stopwatch by recording the current time as the starting time. If the stopwatch was previously stopped or not yet started,
* this method resets the starting time to the current time and clears the stopping time. This method is typically called before measuring
* elapsed time.
*/
public void start() {
this.startTime = Instant.now();
this.stopTime = null;
}
/**
* Provides a compact and human-readable formatted string representing the elapsed time measured by the stopwatch. The method formats the
* duration using a combination of days, hours, minutes, seconds, and milliseconds. This compact format removes additional verbosity in
* favor of a shorter, more concise representation.
*
* @return a compact formatted string representing the elapsed time, including days, hours, minutes, seconds, and milliseconds as
* applicable
*/
public String getUsedTimeFormattedCompact() {
return getUsedTimeFormatted(true, true, true, true, true);
}
/**
* Stops the stopwatch by recording the current time as the stopping time.
* <p>
* This method should only be called after the stopwatch has been started using the {@code start()} method. If the stopwatch has not been
* started, an {@code IllegalStateException} will be thrown. Once this method is called, the elapsed time can be calculated as the
* duration between the starting and stopping times.
*
* @throws IllegalStateException if the stopwatch has not been started prior to calling this method.
*/
public void stop() {
if (startTime == null) {
throw new IllegalStateException("StopWatch must be started before stopping.");
}
this.stopTime = Instant.now();
}
/**
* Provides a compact and human-readable formatted string representing the elapsed time measured by the stopwatch. The method formats the
* duration using a combination of days, hours, minutes, seconds, and milliseconds. This compact format removes additional verbosity in
* favor of a shorter, more concise representation.
*
* @return a compact formatted string representing the elapsed time, including days, hours, minutes, seconds, and milliseconds as
* applicable
*/
public String getUsedTimeFormattedCompact() {
return getUsedTimeFormatted(true, true, true, true, true);
}
/**
* Formats the duration of elapsed time measured by the stopwatch into a human-readable string. The formatted output may include days,
* hours, minutes, seconds, and milliseconds, depending on the parameters. The method retrieves the elapsed time via the
* {@code getUsedTime} method and formats it based on the specified flags.
*
* @param includeMillis whether to include milliseconds in the formatted output
* @param includeSeconds whether to include seconds in the formatted output
* @param includeMinutes whether to include minutes in the formatted output
* @param includeHours whether to include hours in the formatted output
* @param includeDays whether to include days in the formatted output
* @return a formatted string representing the elapsed time, including the components specified by the parameters
*/
public String getUsedTimeFormatted(boolean includeMillis, boolean includeSeconds, boolean includeMinutes, boolean includeHours,
boolean includeDays) {
Duration duration = getUsedTime();
long millis = duration.toMillis();
long days = millis / (24 * 60 * 60 * 1000);
millis %= (24 * 60 * 60 * 1000);
long hours = millis / (60 * 60 * 1000);
millis %= (60 * 60 * 1000);
long minutes = millis / (60 * 1000);
millis %= (60 * 1000);
long seconds = millis / 1000;
millis %= 1000;
StringBuilder sb = new StringBuilder();
if (includeDays && days > 0) {
sb.append(days).append("d ");
}
if (includeHours && (hours > 0 || !sb.isEmpty())) {
sb.append(hours).append("h ");
}
if (includeMinutes && (minutes > 0 || !sb.isEmpty())) {
sb.append(minutes).append("m ");
}
if (includeSeconds && (seconds > 0 || !sb.isEmpty())) {
sb.append(seconds);
}
if (includeMillis) {
if (includeSeconds) {
sb.append(String.format(Locale.US, ".%03d", millis));
} else {
sb.append(millis).append("ms");
}
}
String result = sb.toString().trim();
return result.isEmpty() ? "0ms" : result;
}
/**
* Calculates the amount of time elapsed between the stopwatch's starting time and either its stopping time or the current time if the
* stopwatch is still running.
*
* @return the duration representing the elapsed time between the start and end points of the stopwatch.
* @throws IllegalStateException if the stopwatch has not been started before calling this method.
*/
public Duration getUsedTime() {
if (startTime == null) {
throw new IllegalStateException("StopWatch has not been started.");
}
Instant end = (stopTime != null) ? stopTime : Instant.now();
return Duration.between(startTime, end);
}
}
@@ -8,33 +8,33 @@ import java.util.Map;
* Utility class providing common string manipulation methods.
*/
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
* static utility methods for array-related operations.
*
* @throws UnsupportedOperationException always, to indicate that this class
* should not be instantiated.
* should not be instantiated.
*/
private 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
@@ -43,8 +43,8 @@ public class Strings {
* @param text The input string containing environment variable placeholders.
* If null, the method returns null.
* @return A string with the placeholders replaced by their corresponding
* environment variable values. If no placeholders are found,
* the original string is returned.
* environment variable values. If no placeholders are found,
* the original string is returned.
*/
public static String expandEnvironmentVariables(String text) {
if (text == null) return null;
@@ -63,7 +63,7 @@ public class Strings {
* @return true if the string is null or has a length of 0; false otherwise.
*/
public static boolean isNullOrEmpty(final String string) {
return (string == null || string.length()==0);
return (string == null || string.length() == 0);
}
/**
@@ -73,12 +73,12 @@ public class Strings {
* @param value The input string that might contain surrounding double quotes.
* The input cannot be null, and leading/trailing whitespaces will be ignored.
* @return A string with leading and trailing double quotes removed if present.
* Returns the original string if no surrounding double quotes are detected.
* Returns the original string if no surrounding double quotes are detected.
*/
public static String removeQuotes(final String value) {
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;
}
@@ -87,11 +87,11 @@ public class Strings {
* Replaces illegal characters in a given string with a specified replacement string.
* Optionally, consecutive illegal characters can be combined into a single replacement.
*
* @param value The input string in which illegal characters are to be replaced.
* @param value The input string in which illegal characters are to be replaced.
* @param illegalCharacters A string specifying the set of illegal characters to be matched.
* @param replacement The string to replace each illegal character or group of characters.
* @param combine A boolean flag indicating whether consecutive illegal characters should be replaced
* with a single replacement string. If true, groups of illegal characters are combined.
* @param replacement The string to replace each illegal character or group of characters.
* @param combine A boolean flag indicating whether consecutive illegal characters should be replaced
* with a single replacement string. If true, groups of illegal characters are combined.
* @return A new string with the illegal characters replaced by the provided replacement string.
*/
public static String replaceIllegalCharacters(final String value, final String illegalCharacters, final String replacement, final boolean combine) {
@@ -104,12 +104,12 @@ public class Strings {
* with a specified replacement string. Optionally combines consecutive non-allowed
* characters into a single replacement.
*
* @param value The input string to be processed.
* @param value The input string to be processed.
* @param allowedCharacters A string containing the set of characters that are allowed.
* Any character not in this set will be replaced.
* @param replacement The string to replace non-allowed characters with.
* @param combine If true, consecutive non-allowed characters will be replaced with
* a single instance of the replacement string.
* Any character not in this set will be replaced.
* @param replacement The string to replace non-allowed characters with.
* @param combine If true, consecutive non-allowed characters will be replaced with
* a single instance of the replacement string.
* @return A new string with non-allowed characters replaced according to the specified rules.
*/
public static String replaceNonAllowedCharacters(final String value, final String allowedCharacters, final String replacement, final boolean combine) {
@@ -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);
}
/**
@@ -144,10 +144,10 @@ public class Strings {
* the string is returned unchanged. If the input string's length exceeds the max
* length, it is truncated to that length.
*
* @param original The original input string to be processed. If null, the method returns null.
* @param original The original input string to be processed. If null, the method returns null.
* @param maxLength The maximum number of characters allowed in the resulting string.
* @return The original string if its length is less than or equal to maxLength,
* otherwise a truncated version of the string up to maxLength characters.
* otherwise a truncated version of the string up to maxLength characters.
*/
public static String limitCharNumber(final String original, final int maxLength) {
if (original == null || original.length() <= maxLength) return original;
@@ -8,7 +8,12 @@ import org.w3c.dom.Node;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.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,214 +30,211 @@ import java.util.Date;
*/
@Slf4j
public class XmlUtils {
/**
* 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 XmlUtils() {
throw new UnsupportedOperationException("Utility class");
}
/**
* 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");
/**
* A {@link DateTimeFormatter} instance used for formatting or parsing dates in the "yyyy-MM-dd" pattern.
*
* 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.
*/
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
* static utility methods for array-related operations.
*
* @throws UnsupportedOperationException always, to indicate that this class
* should not be instantiated.
*/
private XmlUtils() {
throw new UnsupportedOperationException("Utility class");
}
/**
* 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 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 a new XML element with the specified name and value, appends the value
* as a text node to the element, and returns the resulting element.
*
* @param doc the XML document to which the element belongs. Must not be null.
* @param name the name of the element to create. Must not be null.
* @param value the text value to be set as the content of the created element.
* If null, an empty string will be used as the content.
* @return the newly created XML element containing the specified value as a text node.
*/
public static Element createElement(final Document doc, final String name, final String value) {
log.debug("Creating a new element " + name + " with value: " + value);
Element element = doc.createElement(name);
/**
* Creates a new XML element with the specified name and value, appends the value
* as a text node to the element, and returns the resulting element.
*
* @param doc the XML document to which the element belongs. Must not be null.
* @param name the name of the element to create. Must not be null.
* @param value the text value to be set as the content of the created element.
* If null, an empty string will be used as the content.
* @return the newly created XML element containing the specified value as a text node.
*/
public static Element createElement(final Document doc, final String name, final String value) {
log.debug("Creating a new element " + name + " with value: " + value);
Element element = doc.createElement(name);
Node content = doc.createTextNode(value != null ? value : "");
element.appendChild(content);
Node content = doc.createTextNode(value != null ? value : "");
element.appendChild(content);
return element;
}
return element;
}
/**
* Creates an XML element with the specified name and value, formatted as a date string.
* The date value is converted to an XML-compatible string format using a predefined formatter.
*
* @param doc the XML document to which the element belongs; must not be null
* @param name the name of the element to be created; must not be null or empty
* @param value the date value to be assigned to the created element; must not be null
* @return the created XML element containing the specified name and formatted date value
*/
public static Element createElement(final Document doc, final String name, final Date value) {
return createElement(doc, name, XML_DATE_FORMATTER.format(value.toInstant()));
}
/**
* Creates an XML element with the specified name and optional value.
*
* @param doc The Document object to which the element will belong. Must not be null.
* @param name The name of the element to be created. Must not be null or empty.
* @param value The optional value to be set as the text content of the element. If null,
* the element will have an empty text content.
* @return The newly created Element object with the specified name and value.
*/
public static Element createElement(final Document doc, final String name, final Integer value) {
log.debug("Creating a new element " + name + " with value: " + value);
Element element = doc.createElement(name);
/**
* Creates an XML element with the specified name and optional value.
*
* @param doc The Document object to which the element will belong. Must not be null.
* @param name The name of the element to be created. Must not be null or empty.
* @param value The optional value to be set as the text content of the element. If null,
* the element will have an empty text content.
* @return The newly created Element object with the specified name and value.
*/
public static Element createElement(final Document doc, final String name, final Integer value) {
log.debug("Creating a new element " + name + " with value: " + value);
Element element = doc.createElement(name);
Node content = doc.createTextNode(value != null ? "" + value : "");
element.appendChild(content);
Node content = doc.createTextNode(value != null ? ""+value : "");
element.appendChild(content);
return element;
}
return element;
}
/**
* Creates a new element with the specified name, appends it to the provided parent element,
* and returns the newly created element.
*
* @param doc The Document to which the new element belongs. Must not be null.
* @param name The name of the new element to be created. Must not be null or empty.
* @param parent The parent element to which the new element will be appended. Must not be null.
* @return The newly created and appended Element object.
*/
public static Element createAndInsertElement(final Document doc, final String name, @NonNull final Element parent) {
log.debug("Creating a new element " + name + " and adding it to a parent.");
Element element = createElement(doc, name);
parent.appendChild(element);
return element;
}
/**
* Creates a new element with the specified name, appends it to the provided parent element,
* and returns the newly created element.
*
* @param doc The Document to which the new element belongs. Must not be null.
* @param name The name of the new element to be created. Must not be null or empty.
* @param parent The parent element to which the new element will be appended. Must not be null.
* @return The newly created and appended Element object.
*/
public static Element createAndInsertElement(final Document doc, final String name, @NonNull final Element parent) {
log.debug("Creating a new element " + name + " and adding it to a parent.");
Element element = createElement(doc, name);
parent.appendChild(element);
return element;
}
/**
* Creates a new element 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.
*
* @param doc The XML document to which the new element belongs. Must not be null.
* @param name The name of the element to create. Must not be null.
* @param value The value to be set for the created element. Can be null if no value is required.
* @param parent The parent element to which the new element will be appended. Must not be null.
* @return The newly created and inserted element.
*/
public static Element createAndInsertElement(final Document doc, final String name, final String value, @NonNull final Element parent) {
log.debug("Creating a new element " + name + " with value: " + value + " and adding it to a parent.");
Element element = createElement(doc, name, value);
parent.appendChild(element);
return element;
}
/**
* Creates a new XML element with a specified name and value, adds it to the specified parent element,
* and returns the created element.
*
* @param doc The XML document to which the new element belongs. Must not be null.
* @param name The name of the element to create. Must not be null.
* @param value The value to be set for the created element. Can be null if no value is required.
* @param parent The parent element to which the new element will be appended. Must not be null.
* @return The newly created and inserted element.
*/
public static Element createAndInsertElement(final Document doc, final String name, final String value, @NonNull final Element parent) {
log.debug("Creating a new element " + name + " with value: " + value + " and adding it to a parent.");
Element element = createElement(doc, name, value);
parent.appendChild(element);
return element;
}
/**
* Formats a given XML string by applying proper indentation to enhance readability.
* The method uses a transformer to process the XML input, applying indentation
* with a four-space configuration. In case of an error, the original XML string
* is returned without any modifications.
*
* @param xmlStream The raw XML string to be formatted. Cannot be null.
* @return The formatted XML string with proper indentation. If an exception occurs
* during formatting, the original XML string is returned.
*/
public static String format(final String xmlStream) {
log.debug("formatXML");
/**
* Formats a given XML string by applying proper indentation to enhance readability.
* The method uses a transformer to process the XML input, applying indentation
* with a four-space configuration. In case of an error, the original XML string
* is returned without any modifications.
*
* @param xmlStream The raw XML string to be formatted. Cannot be null.
* @return The formatted XML string with proper indentation. If an exception occurs
* during formatting, the original XML string is returned.
*/
public static String format(final String xmlStream) {
log.debug("formatXML");
try {
Source xmlInput = new StreamSource(new StringReader(xmlStream));
StringWriter stringWriter = new StringWriter();
StreamResult xmlOutput = new StreamResult(stringWriter);
try {
Source xmlInput = new StreamSource(new StringReader(xmlStream));
StringWriter stringWriter = new StringWriter();
StreamResult xmlOutput = new StreamResult(stringWriter);
TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformerFactory.setAttribute("indent-number", "4");
TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformerFactory.setAttribute("indent-number", "4");
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.transform(xmlInput, xmlOutput);
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.transform(xmlInput, xmlOutput);
return xmlOutput.getWriter().toString();
} catch (TransformerException e) {
log.error("Error in XML: " + e.getMessage() + "\n" + xmlStream, e);
}
return xmlOutput.getWriter().toString();
}
catch(TransformerException e) {
log.error("Error in XML: " + e.getMessage() + "\n"+xmlStream, e);
}
return xmlStream;
}
return xmlStream;
}
/**
* Formats a given XML Document into a human-readable string representation.
* The method applies indentation and specific output properties to the XML content.
* If an exception occurs during the transformation process, it logs the error
* and returns null.
*
* @param doc The XML Document to be formatted. Must not be null.
* @return A formatted string representation of the XML document, or null if an error occurs during processing.
*/
public static String format(final Document doc) {
try {
TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformerFactory.setAttribute("indent-number", "4");
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
transformer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1");
StringWriter stringWriter = new StringWriter();
/**
* Formats a given XML Document into a human-readable string representation.
* The method applies indentation and specific output properties to the XML content.
* If an exception occurs during the transformation process, it logs the error
* and returns null.
*
* @param doc The XML Document to be formatted. Must not be null.
* @return A formatted string representation of the XML document, or null if an error occurs during processing.
*/
public static String format(final Document doc) {
try {
TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformerFactory.setAttribute("indent-number", "4");
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.STANDALONE, "yes");
transformer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1");
StringWriter stringWriter = new StringWriter();
Source source = new DOMSource(doc);
Result result = new StreamResult(stringWriter);
transformer.transform(source, result);
Source source = new DOMSource(doc);
Result result = new StreamResult(stringWriter);
transformer.transform(source, result);
String xmlString = stringWriter.toString();
log.info("MO Request: " + xmlString);
return xmlString;
} catch (TransformerException e) {
log.error("Error in XML Transformation: " + e.getMessage(), e);
}
String xmlString = stringWriter.toString();
log.info("MO Request: " + xmlString);
return xmlString;
}
catch(TransformerException e) {
log.error("Error in XML Transformation: " + e.getMessage(), e);
}
return null;
}
return null;
}
/**
* Validates whether a given XML string is well-formed.
* This method attempts to parse the input XML string and checks
* if it can successfully create a valid DOM object from it.
*
* @param xml The XML string to be validated. Must not be null.
* If null or invalid, the method will return false.
* @return true if the XML string is well-formed and can be successfully parsed;
* false otherwise, such as in the case of invalid content or parsing errors.
*/
public static boolean checkXml(final String xml) {
try {
InputStream stream = new ByteArrayInputStream(xml.getBytes());
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = dbf.newDocumentBuilder();
Document document = docBuilder.parse(stream);
return document != null;
} catch (Exception ex) {
log.warn("Exception when validating xml.", ex);
return false;
}
}
/**
* Validates whether a given XML string is well-formed.
* This method attempts to parse the input XML string and checks
* if it can successfully create a valid DOM object from it.
*
* @param xml The XML string to be validated. Must not be null.
* If null or invalid, the method will return false.
* @return true if the XML string is well-formed and can be successfully parsed;
* false otherwise, such as in the case of invalid content or parsing errors.
*/
public static boolean checkXml(final String xml) {
try {
InputStream stream = new ByteArrayInputStream(xml.getBytes());
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = dbf.newDocumentBuilder();
Document document = docBuilder.parse(stream);
return document != null;
} catch (Exception ex) {
log.warn("Exception when validating xml.", ex);
return false;
}
}
}
@@ -7,7 +7,11 @@ import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_2;
import de.neitzel.core.inject.testcomponents.test1ok.sub.TestComponent1_2;
import 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 {