diff --git a/README.md b/README.md index 65fafaf..c070acd 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,16 @@ # NeitzelLib Maven Project -Small Library with classes that I found helpfully or interesting. +## A Java Utilities Library -The idea is not to diretly use anything from this library. It is only meant to provide some code which could be a quick start when required inside a project. So simply copy the classes that you need. +This repository is **not** a production-ready library, but rather a loose collection of small helpers, utilities, and ideas that might come in handy for Java developers. -## Components +The code in this project is intended as a **starting point** or **inspiration** for your own solutions. It is often easier to copy and adapt individual classes as needed instead of using the whole library as a dependency. -### core +Feel free to explore, copy, modify, and improve whatever you find useful. -This is the core library that does not have special dependencies like JavaFX. +> ⚠️ Use at your own discretion — no guarantees of stability, backwards compatibility, or completeness. -It contains: -- **inject** Some small, basic Injection Library (Just a quick start) -- **sql** Helper classes to work with SQL in Java +--- -### fx -Library that extends JavaFX or helps with it. - -It contains: -- **component** Just a quick start where I experiment with the idea to have JavaFX components which means that we have a View to display a specific Model. -- **injectfx** Injection inside JavaFX, main idea is to use constructor injection on FXML controller to include required Elements. -- **mvvm** The mvvmFX project seems to get no more updates / is no longer maintained. In this area I am simply playing around with some helper classes to make the use of the mvvm pattern easier through generation of ViewModels. **Currently not really useable** \ No newline at end of file +## License +This is free and unencumbered software released into the public domain. Please see the [License](LICENSE.md) for details. \ No newline at end of file diff --git a/core/src/main/java/de/neitzel/core/commandline/ArgumentProvider.java b/core/src/main/java/de/neitzel/core/commandline/ArgumentProvider.java new file mode 100644 index 0000000..c4906b4 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/commandline/ArgumentProvider.java @@ -0,0 +1,95 @@ +package de.neitzel.core.commandline; + +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. + */ +public class ArgumentProvider implements Iterator { + + /** + * A String array representing the command-line arguments provided to the application. + * This array holds each tokenized element of the command-line input for further parsing + * and processing by the application logic. It is used within the {@code ArgumentProvider} + * class to iterate through and retrieve arguments systematically. + */ + private 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. + */ + private int current = 0; + + /** + * Creates an instance of the ArgumentProvider class to iterate over the given array of arguments. + * If the provided array is null, it initializes the arguments with an empty array. + * + * @param arguments The array of command-line arguments to be iterated over. + * If null, it defaults to an empty array. + */ + public ArgumentProvider(final String[] arguments) { + if (arguments == null) { + 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. + * + * @param count the number of additional arguments to check for availability + * @return true if there are at least the specified number of arguments available, otherwise false + */ + public boolean hasNext(final int count) { + return current + count <= arguments.length; + } + + /** + * Retrieves the next argument in the sequence. + * If no more arguments are available, returns {@code null}. + * + * @return the next argument as a {@code String}, or {@code null} if no further arguments are available + */ + public String next() { + if (!hasNext()) return null; + + String result = arguments[current]; + 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]; + } +} diff --git a/core/src/main/java/de/neitzel/core/commandline/Parameter.java b/core/src/main/java/de/neitzel/core/commandline/Parameter.java new file mode 100644 index 0000000..f47ee86 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/commandline/Parameter.java @@ -0,0 +1,192 @@ +package de.neitzel.core.commandline; + +import lombok.*; +import lombok.extern.log4j.Log4j; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +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. + * + * This class is intended to be used as part of a command-line parser, enabling + * structured handling of arguments provided via the command line. + */ +@Slf4j +@Builder +@Getter +@Setter +public class Parameter { + + /** + * Represents a command-line parameter definition within the application. + * + * 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. + * + * 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. + */ + public 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 isDefaultParameter Indicates whether this parameter is the default 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> callback, boolean isHelpCommand, boolean isDefaultParameter, List aliasList) { + this.name = name; + this.minNumberValues = minNumberValues; + this.maxNumberValues = maxNumberValues; + this.multipleEntries = multipleEntries; + this.shortDescription = shortDescription; + this.longDescription = longDescription; + this.callback = callback; + this.isHelpCommand = isHelpCommand; + 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> 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 aliasList; +} diff --git a/core/src/main/java/de/neitzel/core/commandline/Parser.java b/core/src/main/java/de/neitzel/core/commandline/Parser.java new file mode 100644 index 0000000..f6e7728 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/commandline/Parser.java @@ -0,0 +1,216 @@ +package de.neitzel.core.commandline; + +import lombok.extern.slf4j.Slf4j; + +import java.util.*; + +/** + * The Parser class is responsible for parsing and handling command-line arguments. + * It allows for the registration of known parameters and provides mechanisms + * to validate and process command-line input. + */ +@Slf4j +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. + * + * 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. + * + * 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. + */ + private Map parameters = new HashMap<>(); + + /** + * The `defaultParameter` variable represents the default command-line parameter for a parser. + * + * 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. + * + * It is essential to note: + * - Only one parameter can be designated as the default parameter in a command-line parser. + * - The parameter must allow values to be processed (e.g., its `minNumberValues` or `maxNumberValues` + * should not disallow input). + * - This parameter is automatically invoked if an input does not match any explicitly named + * parameter or alias in the parser configuration. + * + * 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. + */ + private Parameter defaultParameter = null; + + /** + * Constructs a new instance of the `Parser` class. + * + * 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. + * + * This constructor initializes the parser without any predefined + * configuration or parameters. Users can define parameters, + * add handlers, and parse command-line arguments using available + * methods in the class. + */ + public Parser() { + } + + /** + * Adds a new parameter to the internal parameter map. The method registers the parameter + * using its primary name and all specified aliases (case-insensitive). It also sets up + * specific behaviors for help and default parameters based on the parameter's configuration. + * + * @param parameter the Parameter object to be added. It contains the primary name, aliases, + * 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). + */ + public void addParameter(final Parameter parameter) { + // Add by name + parameters.put(parameter.getName().toLowerCase(), parameter); + + // Add for all aliases + for (String alias: parameter.getAliasList()) { + parameters.put(alias.toLowerCase(), parameter); + } + + // Set help Callback for help command. + if (parameter.isHelpCommand()) + parameter.setCallback(this::helpCommandCallback); + + if (parameter.isDefaultParameter()) { + if (parameter.getMinNumberValues() == 0 && parameter.getMaxNumberValues() == 0) + throw new IllegalArgumentException("Default Parameter must accept values!"); + + defaultParameter = parameter; + } + } + + /** + * 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; + } + + /** + * Parses an array of command-line arguments and processes them as defined by the application's parameters. + * + * 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}. + * + * @param args the array of command-line arguments to be parsed and processed. Each element in this + * array represents a single input argument provided to the application. The format and + * content of the arguments are expected to conform to the application's parameter definitions. + */ + public void parse(final String[] args) { + ArgumentProvider provider = new ArgumentProvider(args); + while (provider.hasNext()) { + log.debug("Parsing argument: " + provider.peek()); + + // Peek to see the next parameter. + String next = provider.peek().toLowerCase(); + if (!isParameter(next)) { + log.error("Unknown Parameter: " + next); + throw new IllegalArgumentException("Unknown argument: " + next); + } + + Parameter parameter = parameters.get(next); + if (parameter == null) { + parameter = defaultParameter; + } else { + provider.next(); + } + + if (!provider.hasNext(parameter.getMinNumberValues())) { + String message = "Parameter " + next + " requires " + parameter.getMinNumberValues() + " more elements!"; + log.error(message); + throw new IllegalArgumentException(message); + } + + parameter.getCallback().accept(getOptions(provider, parameter.getMinNumberValues(), parameter.getMaxNumberValues())); + } + } + + /** + * Retrieves a list of options by consuming arguments from the provided {@link ArgumentProvider} + * within the range specified by the minimum and maximum values. + * + * @param provider The source providing the command-line arguments. This object allows + * retrieving and examining argument strings in sequence. + * @param min The minimum number of arguments to be retrieved. If fewer arguments + * are available, no arguments will be added to the result. + * @param max The maximum number of arguments to be retrieved. If `max` is smaller + * 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. + */ + private List getOptions(ArgumentProvider provider, int min, int max) { + if (max < min) max = min; + List result = new ArrayList<>(); + + int current = 0; + while (current < max && provider.hasNext()) { + if (current < min || !parameters.containsKey(provider.peek())) { + result.add(provider.next()); + current++; + } else { + break; + } + } + + 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 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()); + } + } + } +} diff --git a/core/src/main/java/de/neitzel/core/config/Configuration.java b/core/src/main/java/de/neitzel/core/config/Configuration.java new file mode 100644 index 0000000..df2f7d9 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/config/Configuration.java @@ -0,0 +1,268 @@ +package de.neitzel.core.config; + +import de.neitzel.core.util.FileUtils; +import de.neitzel.core.util.Strings; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URISyntaxException; +import java.nio.charset.Charset; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Map; +import java.util.Properties; + +/** + * Provides a utility class for managing configuration properties in an + * application. This class allows loading properties from files, accessing + * values with various types including environment variable substitution, + * and updating properties. + */ +@Slf4j +public class Configuration { + + /** + * A {@link Properties} object that stores a set of key-value pairs. + * This variable can be used to manage configuration settings or other + * collections of properties within the application. + * + * It provides methods to load, retrieve, and modify properties + * as necessary for the application's requirements. + */ + private final Properties properties; + + /** + * Constructs a new Configuration instance with default properties. + * This constructor initializes the Configuration object using an + * empty set of {@code Properties}. + */ + public Configuration() { + this(new Properties()); + } + + /** + * Constructs a new Configuration instance using the provided properties. + * + * @param properties the Properties object containing configuration key-value pairs + */ + public Configuration(Properties properties) { + this.properties = properties; + } + + /** + * Retrieves a boolean property value associated with the specified key. If the key does not exist in the properties, + * the provided default value is returned. The method also supports interpreting specific string values as true. + * + * @param key the key used to retrieve the boolean property + * @param defaultValue the default value returned if the key is not found in the properties + * @return the boolean value associated with the key, or the defaultValue if the key does not exist + */ + protected boolean getBooleanProperty(final String key, final boolean defaultValue) { + if (!properties.containsKey(key)) return defaultValue; + return getStringProperty(key, defaultValue ? "ja": "nein").equalsIgnoreCase("ja") || properties.getProperty(key).equalsIgnoreCase("true"); + } + + /** + * Retrieves the value of the specified property as a trimmed string. + * If the property is not found, the default value is returned. + * + * @param key the key of the property to retrieve + * @param defaultValue the default value to return if the property is not found + * @return the trimmed string value of the property, or the default value if the property is not found + */ + protected String getStringProperty(final String key, final String defaultValue) { + if (!properties.containsKey(key)) return defaultValue; + return properties.getProperty(key).trim(); + } + + /** + * Retrieves a string property value associated with the specified key, applies + * environment variable expansion on the value, and returns the processed result. + * + * @param key the key identifying the property to retrieve. + * @param defaultValue the default value to use if the property is not found. + * @return the processed property value with expanded environment variables, or + * the defaultValue if the property is not found. + */ + protected String getStringPropertyWithEnv(final String key, final String defaultValue) { + String result = getStringProperty(key, defaultValue); + return Strings.expandEnvironmentVariables(result); + } + + /** + * Retrieves the value of a string property associated with the specified key, + * removes any surrounding quotes from the value, and returns the resultant string. + * + * @param key the key associated with the desired property + * @param defaultValue the default value to return if the property is not found or is null + * @return the string property without surrounding quotes, or the defaultValue if the property is not found + */ + protected String getStringPropertyWithoutQuotes(final String key, final String defaultValue) { + return Strings.removeQuotes(getStringProperty(key, defaultValue)); + } + + /** + * Retrieves the string value of a configuration property identified by the given key, + * removes surrounding quotes if present, and expands any environment variables found + * within the string. If the property is not found, a default value is used. + * + * @param key the key identifying the configuration property + * @param defaultValue the default value to use if the property is not found + * @return the processed string property with quotes removed and environment variables expanded + */ + protected String getStringPropertyWithoutQuotesWithEnv(final String key, final String defaultValue) { + String result = getStringPropertyWithoutQuotes(key, defaultValue); + return Strings.expandEnvironmentVariables(result); + } + + /** + * Retrieves the integer value for the specified property key. If the key does + * not exist in the properties or the value is null/empty, the provided default + * value is returned. + * + * @param key the property key to retrieve the value for + * @param defaultValue the default value to return if the key is not present + * or the value is null/empty + * @return the integer value associated with the key, or the defaultValue if + * the key does not exist or its value is null/empty + */ + protected Integer getIntegerProperty(final String key, final Integer defaultValue) { + if (!properties.containsKey(key)) return defaultValue; + String value = properties.getProperty(key); + return Strings.isNullOrEmpty(value) ? null : Integer.parseInt(value); + } + + /** + * Sets an integer property in the properties object. If the provided value is null, + * an empty string will be stored as the property's value. + * + * @param key the key under which the property will be stored + * @param value the integer value to be stored; if null, an empty string will be used + */ + protected void setIntegerProperty(final String key, final Integer value) { + if (value == null) { + properties.setProperty(key, ""); + } else { + properties.setProperty(key, ""+value); + } + } + + /** + * Retrieves a LocalDate value from the properties based on the provided key. + * If the key does not exist or the value is invalid, a default value is returned. + * + * @param key the key to look up the property in the properties map + * @param defaultValue the default LocalDate value to return if the key is not found + * @param formatString the format string to parse the LocalDate value + * @return the LocalDate value from the properties if available and valid, + * otherwise the defaultValue + */ + protected LocalDate getLocalDateProperty(final String key, final LocalDate defaultValue, final String formatString) { + if (!properties.containsKey(key)) return defaultValue; + + String value = properties.getProperty(key); + if (value == null || value.isEmpty()) return null; + return LocalDate.parse(value, DateTimeFormatter.ofPattern(formatString)); + } + + /** + * Sets a property with the given key and a formatted LocalDate value. + * If the provided value is null, the property will be set to an empty string. + * + * @param key the key of the property to set + * @param value the LocalDate value to format and set as the property value + * @param formatString the pattern string used to format the LocalDate value + */ + protected void setLocalDateProperty(final String key, final LocalDate value, final String formatString) { + if (value == null) { + setProperty(key, ""); + } else { + setProperty(key, value.format(DateTimeFormatter.ofPattern(formatString))); + } + } + + /** + * Sets a property with the specified key to the given value. If the value is null, + * it defaults to an empty string. Logs the operation and updates the property. + * + * @param key the key of the property to be set + * @param value the value to be associated with the specified key; defaults to an empty string if null + */ + public void setProperty(final String key, final String value) { + String newValue = value == null ? "" : value; + log.info("Setting a new value for '" + key + "': '" + newValue + "'"); + properties.setProperty(key, newValue); + } + + /** + * Loads the content of the specified file using the provided file name. + * + * @param fileName the name of the file to be loaded + */ + public void load(final String fileName) { + load(fileName, Charset.defaultCharset().name(), true); + } + + /** + * Loads the configuration from a specified file. If the file does not exist in the + * specified location, it attempts to find the file alongside the JAR file of the application. + * Reads the configuration with the provided encoding and an option to accept UTF-8 encoding. + * + * @param fileName the name of the configuration file to be loaded + * @param encoding the encoding format to be used while reading the configuration file + * @param acceptUTF8 a boolean flag indicating whether to accept UTF-8 encoding + */ + public void load(final String fileName, final String encoding, final boolean acceptUTF8) { + log.info("Reading Config: " + fileName + " with encoding: " + encoding + "accepting UTF-8: " + acceptUTF8); + File configFile = new File(fileName); + + // Try to get the file next to the jar file if the configFile does not exist. + if (!configFile.exists()) { + try { + String fileAtJar = new File(Configuration.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent() + "/" + fileName; + configFile = new File(fileAtJar); + } catch (URISyntaxException ex) { + log.error("Unable to get path of jar file / class.", ex); + } + } + + // Read the configuration file if it exists. + if (configFile.exists()) { + log.info("Reading Configuration file. " + configFile.getAbsolutePath()); + try (InputStreamReader reader = FileUtils.createUniversalFileReader(configFile, encoding, acceptUTF8)) { + log.info("Reading the configuration with encoding: " + reader.getEncoding()); + properties.load(reader); + } catch (FileNotFoundException fnfe) { + log.error("Configuration file: " + fileName + " not found!", fnfe); + } catch (IOException ioe) { + log.error("Cannot read config file.", ioe); + } + } else { + log.error("Unable to load config file! Last try: " + configFile.getAbsolutePath()); + } + } + + /** + * Merges the properties from the provided configuration into the current configuration. + * + * @param config the Configuration object whose properties will be merged into this instance + */ + public void merge(final Configuration config) { + for(Map.Entry entry: config.properties.entrySet()) { + properties.put(entry.getKey(), entry.getValue()); + } + } + + /** + * Removes the specified key-value pair from the properties map if the key exists. + * + * @param key the key to be removed from the properties map + */ + public void remove(final String key){ + if (properties.containsKey(key)) properties.remove(key); + } +} + diff --git a/core/src/main/java/de/neitzel/core/io/ConvertedEncodingFileReader.java b/core/src/main/java/de/neitzel/core/io/ConvertedEncodingFileReader.java new file mode 100644 index 0000000..aa37570 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/io/ConvertedEncodingFileReader.java @@ -0,0 +1,103 @@ +package de.neitzel.core.io; + +import de.neitzel.core.util.FileUtils; +import lombok.extern.slf4j.Slf4j; + +import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; + +/** + * The ConvertedEncodingFileReader class extends {@link InputStreamReader} to provide functionality + * for handling file encoding conversion transparently. If a file is detected to be in UTF-8 encoding, + * this class converts it to the specified target encoding using a temporary file, then opens the reader + * with the converted encoding. If the file is already in the target encoding, it opens the reader directly. + * + * This class is useful for applications needing to process text files in specific encodings and ensures + * encoding compatibility. + */ +@Slf4j +public class ConvertedEncodingFileReader extends InputStreamReader { + + /** + * The `checkEncoding` variable specifies the default encoding to be used + * when verifying file encodings within the `ConvertedEncodingFileReader` class. + * This encoding is primarily used to determine whether a file needs conversion + * to the target format or can be read directly in its existing format. + * The default value is set to "ISO-8859-15". + * + * Modifying this variable requires careful consideration, as it affects + * the behavior of methods that rely on encoding validation, particularly + * in the process of detecting UTF-8 files or converting them during file reading. + */ + private static String checkEncoding = "ISO-8859-15"; + + /** + * Sets the encoding that will be used to check the file encoding for compatibility. + * Throws an exception if the specified encoding is not valid or supported. + * + * @param encoding the name of the character encoding to set as the check encoding; + * it must be a valid and supported Charset. + * @throws IllegalCharsetNameException if the specified encoding is not valid or supported. + */ + private static void setCheckEncoding(final String encoding) { + if (Charset.forName(encoding) != null) throw new IllegalCharsetNameException("Encoding " + encoding + " is not supported!"); + + checkEncoding = encoding; + } + + /** + * Constructs a ConvertedEncodingFileReader for a specified file and target encoding format. + * The class reads the provided file and ensures that its content is handled in the target encoding. + * If the file is not already in the target encoding, it converts the file's encoding + * transparently using a temporary file before reading it. + * + * @param file The file to be read. Must exist and be accessible. + * @param targetFormat The target character encoding format to which the file content should be converted. + * @throws IOException If the file does not exist, is inaccessible, or an error occurs during the encoding conversion process. + */ + public ConvertedEncodingFileReader(final File file, final String targetFormat) throws IOException { + super(createTargetFormatInputFileStream(file, targetFormat), targetFormat); + } + + /** + * Constructs a ConvertedEncodingFileReader for reading a file with encoding conversion support. + * This constructor takes the file path as a string and ensures the file's encoding is either + * converted to the specified target format or read directly if it matches the target format. + * + * @param filename the path to the file to be read + * @param targetFormat the target encoding format to use for reading the file + * @throws IOException if an I/O error occurs while accessing or reading the specified file + */ + public ConvertedEncodingFileReader(final String filename, final String targetFormat) throws IOException { + this(new File(filename), targetFormat); + } + + /** + * Creates an input file stream for a given file, converting its encoding if necessary. + * If the file is not in UTF-8 encoding, a direct {@link FileInputStream} is returned for the file. + * If the file is in UTF-8 encoding, it is converted to the specified target format using a temporary file, + * and then an input stream for the temporary file is returned. + * + * @param file the file for which the input stream is to be created + * @param targetFormat the desired target encoding format + * @return a {@link FileInputStream} for the file or a temporary file with converted encoding + * @throws IOException if the file does not exist or an error occurs during file operations + */ + private static FileInputStream createTargetFormatInputFileStream(final File file, final String targetFormat) throws IOException { + if (!file.exists()) { + String errorMessage = "File " + file.toString() + " does not exist!"; + log.error(errorMessage); + throw new FileNotFoundException(errorMessage); + } + + if (!FileUtils.isUTF8(file, checkEncoding)) { + return new FileInputStream(file); + } else { + File tempFile = File.createTempFile(file.getName(), "tmp"); + FileUtils.convertTextFile(file, "UTF-8", tempFile, targetFormat); + tempFile.deleteOnExit(); + return new FileInputStream(tempFile); + } + } +} diff --git a/core/src/main/java/de/neitzel/core/io/ISO8859EncodingFileReader.java b/core/src/main/java/de/neitzel/core/io/ISO8859EncodingFileReader.java new file mode 100644 index 0000000..05186c2 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/io/ISO8859EncodingFileReader.java @@ -0,0 +1,46 @@ +package de.neitzel.core.io; + +import java.io.File; +import java.io.IOException; + +/** + * The ISO8859EncodingFileReader class extends the ConvertedEncodingFileReader to provide + * file reading functionality with specific encoding support for ISO-8859-15. + * This class ensures that the file content is either read directly in the ISO-8859-15 encoding + * or converted transparently to this encoding if needed. + */ +public class ISO8859EncodingFileReader extends ConvertedEncodingFileReader { + + /** + * Represents the target encoding format used by the ISO8859EncodingFileReader class. + * The TARGET_FORMAT is set to "ISO-8859-15", which specifies an encoding standard + * that is a variant of ISO-8859-1, adding the Euro symbol and other additional characters. + * This constant is used to indicate the desired character encoding for reading file content. + */ + private static final String TARGET_FORMAT = "ISO-8859-15"; + + /** + * Constructs an instance of ISO8859EncodingFileReader to read the provided file + * while ensuring the encoding is set to ISO-8859-15. If the file is not in the + * target encoding, this method transparently converts the file content to match + * the encoding before reading. + * + * @param file The file to be read. Must exist and be accessible. + * @throws IOException If the file does not exist, is inaccessible, or an error + * occurs during the encoding conversion process. + */ + public ISO8859EncodingFileReader(File file) throws IOException { + super(file, TARGET_FORMAT); + } + + /** + * Constructs an ISO8859EncodingFileReader for reading a file with encoding conversion + * to the ISO-8859-15 format. This constructor accepts the file path as a string. + * + * @param filename the path to the file to be read + * @throws IOException if an I/O error occurs while accessing or reading the specified file + */ + public ISO8859EncodingFileReader(String filename) throws IOException { + super(filename, TARGET_FORMAT); + } +} diff --git a/core/src/main/java/de/neitzel/core/io/StringEncoder.java b/core/src/main/java/de/neitzel/core/io/StringEncoder.java new file mode 100644 index 0000000..723d27f --- /dev/null +++ b/core/src/main/java/de/neitzel/core/io/StringEncoder.java @@ -0,0 +1,96 @@ +package de.neitzel.core.io; + +/** + * Utility class for encoding and decoding strings. + * This class provides methods for transforming strings into encoded representations + * and decoding them back to the original representation. + */ +public class StringEncoder { + + /** + * 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 StringEncoder() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Decodes a string containing encoded characters back to its original representation. + * Encoded characters are expected to be in the format {@code "&#;"}, where + * {@code } is a numeric representation of the character to decode. + * + * @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. + */ + public static String decodeData(final String data) { + if (data == null) return ""; + + String remaining = data; + StringBuilder result = new StringBuilder(); + while (!remaining.isEmpty()) { + int indexAmp = remaining.indexOf("&"); + if (indexAmp == -1) { + result.append(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(";"); + if (endSpecial == -1) throw new IllegalArgumentException("String couldn't be decoded! (" + data + ")"); + String special = remaining.substring(0, endSpecial+1); + remaining = remaining.substring(endSpecial+1); + result.append(decodeCharacter(special)); + } + } + return result.toString(); + } + + /** + * Decodes a character from its numeric character reference representation. + * The input string must represent an encoded character in the format {@code "&#;"}. + * + * @param data the string containing the numeric character reference to decode. + * It must start with {@code "&#"} and end with {@code ";"}. + * @return the decoded character represented by the numeric character reference. + * @throws IllegalArgumentException if the input string does not follow the expected format. + */ + 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)); + } + + /** + * Encodes the provided string by converting characters outside the ASCII range (32-127) + * and the ampersand {@code "&"} character into their corresponding numeric character reference + * representation (e.g., {@code "&"}). + * + * @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 + */ + public static String encodeData(final String data) { + if (data == null) return ""; + + StringBuilder result = new StringBuilder(); + for (int index=0; index < data.length(); index++) { + char character = data.charAt(index); + if (character < 32 || character > 127 || character == 38) { + result.append("&#" + (int)character + ";"); + } else { + result.append(character); + } + } + return result.toString(); + } +} diff --git a/core/src/main/java/de/neitzel/core/sound/ToneGenerator.java b/core/src/main/java/de/neitzel/core/sound/ToneGenerator.java index cab8df8..9e92c17 100644 --- a/core/src/main/java/de/neitzel/core/sound/ToneGenerator.java +++ b/core/src/main/java/de/neitzel/core/sound/ToneGenerator.java @@ -10,6 +10,18 @@ import javax.sound.sampled.SourceDataLine; * It allows playing tones based on frequency or predefined tone names. */ public class ToneGenerator { + /** + * 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 ToneGenerator() { + throw new UnsupportedOperationException("Utility class"); + } + /** * Plays a tone based on a predefined tone name for a specified duration. * diff --git a/core/src/main/java/de/neitzel/core/sound/ToneTable.java b/core/src/main/java/de/neitzel/core/sound/ToneTable.java index a195658..e592ae0 100644 --- a/core/src/main/java/de/neitzel/core/sound/ToneTable.java +++ b/core/src/main/java/de/neitzel/core/sound/ToneTable.java @@ -13,6 +13,18 @@ import java.util.HashMap; * 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. diff --git a/core/src/main/java/de/neitzel/core/sql/Query.java b/core/src/main/java/de/neitzel/core/sql/Query.java index ac86b59..f92d184 100644 --- a/core/src/main/java/de/neitzel/core/sql/Query.java +++ b/core/src/main/java/de/neitzel/core/sql/Query.java @@ -22,7 +22,6 @@ import java.util.stream.StreamSupport; * @param The type of the objects returned by the query. */ @Slf4j -@RequiredArgsConstructor public class Query { /** @@ -69,6 +68,15 @@ public class Query { */ 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. * diff --git a/core/src/main/java/de/neitzel/core/sql/TrimmingResultSet.java b/core/src/main/java/de/neitzel/core/sql/TrimmingResultSet.java index 7a0b55a..1297e67 100644 --- a/core/src/main/java/de/neitzel/core/sql/TrimmingResultSet.java +++ b/core/src/main/java/de/neitzel/core/sql/TrimmingResultSet.java @@ -18,7 +18,6 @@ import java.util.Map; * If any additional behaviors or transformations are applied, they are documented * in the respective overridden method descriptions. */ -@RequiredArgsConstructor public class TrimmingResultSet implements ResultSet { /** @@ -31,7 +30,16 @@ public class TrimmingResultSet implements ResultSet { * SQL query execution lifecycle. */ private final ResultSet resultSet; - + + /** + * Initializes a new instance of the TrimmingResultSet class, wrapping the provided ResultSet. + * + * @param resultSet the ResultSet to be wrapped and processed by this TrimmingResultSet instance + */ + public TrimmingResultSet(ResultSet resultSet) { + this.resultSet = resultSet; + } + /** * Retrieves the string value from the specified column index of the current row in the ResultSet. * The value is trimmed to remove leading and trailing whitespace. If the value is null, it returns null. diff --git a/core/src/main/java/de/neitzel/core/util/ArrayUtils.java b/core/src/main/java/de/neitzel/core/util/ArrayUtils.java new file mode 100644 index 0000000..0abb26d --- /dev/null +++ b/core/src/main/java/de/neitzel/core/util/ArrayUtils.java @@ -0,0 +1,78 @@ +package de.neitzel.core.util; + +import java.util.Objects; + +/** + * Utility class providing helper methods for working with arrays. + */ +public class ArrayUtils { + + /** + * 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 ArrayUtils() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * 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 + * @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++) { + if (array[index] == ch) return true; + } + return false; + } + + /** + * 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 + * @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++) { + if (array[index] == ch) return true; + } + return false; + } + + /** + * 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 + * @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++) { + if (array[index] == ch) return true; + } + return false; + } + + /** + * 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 the type of the elements in the array + * @return true if the element is found in the array, false otherwise + */ + public static boolean contains(T[] array, T ch) { + for(int index=0; index < array.length; index++) { + if (Objects.equals(array[index], ch)) return true; + } + return false; + } +} diff --git a/core/src/main/java/de/neitzel/core/util/EnumUtil.java b/core/src/main/java/de/neitzel/core/util/EnumUtil.java new file mode 100644 index 0000000..5b029d5 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/util/EnumUtil.java @@ -0,0 +1,79 @@ +package de.neitzel.core.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility class for working with Enum types, providing methods to parse and generate + * patterns for Enum flags. + */ +public class EnumUtil { + /** + * 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 EnumUtil() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Generates a regular expression pattern that matches all possible case-insensitive representations + * 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 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. + */ + public static > String getFlagRegEx(Class clazz) { + StringBuilder result = new StringBuilder(); + result.append("(|,|\\s"); + + for (T flag: clazz.getEnumConstants()) { + result.append("|"); + for(char ch: flag.toString().toUpperCase().toCharArray()) { + if (Character.isAlphabetic(ch)) { + result.append("["); + result.append(ch); + result.append(Character.toLowerCase(ch)); + result.append("]"); + } else { + result.append(ch); + } + } + } + result.append(")*"); + return result.toString(); + } + + /** + * Parses a delimited string containing Enum constant names into a list of Enum constants. + * + * 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 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. + * @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. + */ + public static > List parseFlags(final Class clazz, final String flags) { + List result = new ArrayList<>(); + if (flags != null) { + for (String flag: flags.split("[,\\s]")) { + if (!flag.isEmpty()) result.add(T.valueOf(clazz, flag.toUpperCase())); + } + } + return result; + } +} diff --git a/core/src/main/java/de/neitzel/core/util/FileUtils.java b/core/src/main/java/de/neitzel/core/util/FileUtils.java new file mode 100644 index 0000000..8606083 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/util/FileUtils.java @@ -0,0 +1,298 @@ +package de.neitzel.core.util; + +import lombok.extern.log4j.Log4j; +import lombok.extern.slf4j.Slf4j; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.text.SimpleDateFormat; +import java.util.stream.Collectors; + +/** + * Utility class for handling file operations, such as encoding checks, content reading/writing, + * path manipulations, and file conversions. + */ +@Slf4j +public class FileUtils { + /** + * 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 FileUtils() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Defines a standardized timestamp format for debugging purposes, specifically used for naming + * or identifying files with precise timestamps. The format is "yyyy-MM-dd_HH_mm_ss_SSS", which + * includes: + * - Year in four digits (yyyy) + * - Month in two digits (MM) + * - Day of the month in two digits (dd) + * - Hour in 24-hour format with two digits (HH) + * - Minutes in two digits (mm) + * - Seconds in two digits (ss) + * - Milliseconds in three digits (SSS) + * + * This ensures timestamps are sortable and easily identifiable. + */ + public static final SimpleDateFormat DEBUG_FILE_TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH_mm_ss_SSS"); + + /** + * Default encoding used for string checks and validations in the application. + * + * This constant represents the `ISO-8859-15` encoding, which is a standardized + * character set encoding, commonly used in contexts where backward compatibility + * with `ISO-8859-1` is required, but with support for certain additional characters, + * such as the euro currency symbol (€). + */ + public static final String DEFAULT_CHECK_ENCODING = "ISO-8859-15"; + + /** + * Specifies the default buffer size used for data processing operations. + * + * This constant represents the size of the buffer in bytes, set to 1024, + * and is typically utilized in input/output operations to optimize performance + * by reducing the number of read/write calls. + */ + public static final int BUFFER_SIZE = 1024; + + /** + * Determines whether the given file is encoded in UTF-8. + * + * @param file The file to be checked for UTF-8 encoding. + * @param checkEncoding The character encoding to use while checking the file content. + * @return true if the file is determined to be encoded in UTF-8; false otherwise. + * @throws IOException If an I/O error occurs while reading the file. + */ + public static boolean isUTF8(final File file, final String checkEncoding) throws IOException { + + if (hasUTF8BOM(file)) return true; + + int BUFFER_SIZE = 1024; + char[] buffer = new char[BUFFER_SIZE]; + + try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file), checkEncoding)) { + + while (reader.read(buffer, 0, BUFFER_SIZE) > 0) { + + if ( + (ArrayUtils.contains(buffer, (char) 0x00C2)) // Part of UTF-8 Characters 0xC2 0xZZ + || (ArrayUtils.contains(buffer, (char) 0x00C3))) { // Part of UTF-8 Characters 0xC3 0xZZ + return true; + } + + } + + return false; + } catch (IOException ex) { + log.error("Exception while reading file.", ex); + throw ex; + } + } + + /** + * Checks if the provided file starts with a UTF-8 Byte Order Mark (BOM). + * + * This method reads the first character of the file using a reader that assumes + * UTF-8 encoding and checks if it matches the Unicode Byte Order Mark (U+FEFF). + * + * @param file The file to check for a UTF-8 BOM. Must not be null. + * @return true if the file starts with a UTF-8 BOM, false otherwise. + * @throws IOException If an input or output exception occurs while reading the file. + */ + public static boolean hasUTF8BOM(final File file) throws IOException { + try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) { + return reader.read() == 0xFEFF; + } catch (IOException ex) { + log.error("Exception while reading file.", ex); + throw ex; + } + } + + /** + * Determines if the content of the given file is encoded in UTF-8. + * + * @param file The file to check for UTF-8 encoding. Must not be null. + * @return true if the file content is in UTF-8 encoding; false otherwise. + * @throws IOException If an I/O error occurs while reading the file. + */ + public static boolean isUTF8(final File file) throws IOException { + return isUTF8(file, DEFAULT_CHECK_ENCODING); + } + + /** + * Converts the content of a text file from one character encoding format to another. + * + * This method reads the input text file using the specified source encoding and writes + * the content to the output text file in the specified target encoding. + * + * @param inFile The input text file to be converted. Must not be null. + * @param sourceFormat The character encoding of the input file. Must not be null or empty. + * @param outFile The output text file to write the converted content to. Must not be null. + * @param targetFormat The character encoding to be used for the output file. Must not be null or empty. + * @throws IOException If an I/O error occurs during reading or writing. + */ + public static void convertTextFile(final File inFile, final String sourceFormat, final File outFile, final String targetFormat) throws IOException { + char[] buffer = new char[BUFFER_SIZE]; + + int charsRead, startIndex; + boolean first = true; + try (InputStreamReader reader = new InputStreamReader(new FileInputStream(inFile), sourceFormat); + OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(outFile), targetFormat)) { + + while ((charsRead = reader.read(buffer, 0, BUFFER_SIZE)) > 0) { + startIndex = 0; + + if (first) { + // Check UTF-8 BOM + if (buffer[0] == 0xFEFF) { + log.info("BOM found!"); + startIndex = 1; + } + + first = false; + } + + writer.write(buffer, startIndex, charsRead - startIndex); + } + + } catch (IOException ex) { + log.error("Exception when converting Textfile.", ex); + throw ex; + } + } + + /** + * Creates a universal file reader for the specified file name. + * This method initializes and returns an InputStreamReader to read + * the content of the given file. + * + * @param filename The name or path of the file to be read. + * @return An InputStreamReader for reading the specified file. + * @throws IOException If an I/O error occurs while creating the file reader. + */ + public static InputStreamReader createUniversalFileReader(final String filename) throws IOException { + return createUniversalFileReader(new File(filename)); + } + + /** + * Creates a universal file reader for the specified file and format. + * The method resolves the file using its name and the expected format, + * returning an InputStreamReader for reading the file contents. + * + * @param filename the name of the file to be read. + * @param expectedFormat the format expected for the file content. + * @return an InputStreamReader for the specified file and format. + * @throws IOException if an I/O error occurs while opening or reading the file. + */ + public static InputStreamReader createUniversalFileReader(final String filename, final String expectedFormat) throws IOException { + return createUniversalFileReader(new File(filename), expectedFormat); + } + + /** + * Creates a universal file reader for the specified file using the default encoding and configuration. + * + * @param file The file to be read. Must not be null. + * @return An InputStreamReader configured to read the specified file. + * @throws IOException If an I/O error occurs while creating the reader. + */ + public static InputStreamReader createUniversalFileReader(final File file) throws IOException { + return createUniversalFileReader(file, DEFAULT_CHECK_ENCODING, true); + } + + /** + * Creates a universal file reader for the specified file with an expected format. + * This method wraps the given file in an InputStreamReader for consistent character stream manipulation. + * + * @param file The file to be read. Must not be null. + * @param expectedFormat The expected format of the file (e.g., encoding). Must not be null. + * @return An InputStreamReader for the specified file, allowing the caller to read the file + * with the desired format applied. + * @throws IOException If an I/O error occurs during the creation of the reader. + */ + public static InputStreamReader createUniversalFileReader(final File file, final String expectedFormat) throws IOException { + return createUniversalFileReader(file, expectedFormat, true); + } + + /** + * Creates an InputStreamReader for reading a file, considering the specified encoding format + * and whether UTF-8 should be accepted. Handles potential BOM for UTF-8 encoded files. + * + * @param file The file to be read. + * @param expectedFormat The expected encoding format of the file. + * @param acceptUTF8 Indicates whether UTF-8 encoding should be accepted if detected. + * @return An InputStreamReader for the specified file and encoding. + * @throws IOException If there is an error accessing the file or reading its content. + */ + public static InputStreamReader createUniversalFileReader(final File file, final String expectedFormat, final boolean acceptUTF8) throws IOException { + String encoding = acceptUTF8 && isUTF8(file, expectedFormat) + ? "UTF-8" + : expectedFormat; + + boolean skipBOM = encoding.equals("UTF-8") && hasUTF8BOM(file); + + InputStreamReader result = new InputStreamReader(new FileInputStream(file), encoding); + if (skipBOM) { + int BOM = result.read(); + if (BOM != 0xFEFF) log.error ("Skipping BOM but value not 0xFEFF!"); + } + return result; + } + + /** + * Retrieves the parent directory of the given file or directory path. + * + * If the given path does not have a parent directory, it defaults to returning the + * current directory represented by ".". + * + * @param filename The file or directory path for which the parent directory is to be retrieved. + * @return The parent directory of the given path, or "." if no parent directory exists. + */ + public static String getParentDirectory(final String filename) { + File file = new File(filename); + return file.getParent() != null ? file.getParent() : "."; + } + + /** + * Extracts the name of a file from the given file path. + * + * @param filename The full path or name of the file. Must not be null. + * @return The name of the file without any directory path. + */ + public static String getFilename(final String filename) { + File file = new File(filename); + return file.getName(); + } + + /** + * Reads the content of a file and returns it as a string, joining all lines with the system line separator. + * + * @param filename The name of the file to be read. + * @return A string containing the content of the file with all lines joined by the system line separator. + * @throws IOException If an I/O error occurs while reading the file. + */ + public static String readFileContent(final String filename) throws IOException { + try (BufferedReader reader = new BufferedReader(createUniversalFileReader(filename))) { + return reader.lines().collect(Collectors.joining(System.lineSeparator())); + } + } + + /** + * Writes the given content to the specified file path. If the file already exists, it will be overwritten. + * + * @param path The path of the file to write to. Must not be null and must be writable. + * @param content The content to be written to the file. Must not be null. + * @throws IOException If an I/O error occurs during writing to the file. + */ + public static void writeFile(final Path path, final String content) throws IOException { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(path.toFile()))) { + writer.write(content); + } + } +} diff --git a/core/src/main/java/de/neitzel/core/util/RegionalizationMessage.java b/core/src/main/java/de/neitzel/core/util/RegionalizationMessage.java new file mode 100644 index 0000000..345ee10 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/util/RegionalizationMessage.java @@ -0,0 +1,90 @@ +package de.neitzel.core.util; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * The RegionalizationMessage class provides functionality to retrieve + * localized messages from a resource bundle and format messages with parameters. + * It supports multiple constructors for initializing with specific locales if needed. + */ +public class RegionalizationMessage { + + /** + * The `res` variable holds a `ResourceBundle` instance, + * 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. + * + * This variable is initialized in constructors of the `RegionalizationMessage` class + * and is used internally by various methods to fetch localized messages. + */ + private ResourceBundle res; + + /** + * Constructs a new RegionalizationMessage instance using the specified resource bundle source. + * This constructor initializes the resource bundle with the given source name. + * + * @param source The base name of the resource bundle to load. + * This is typically a fully qualified class name or package name for the resource. + */ + public RegionalizationMessage(final String source) { + res = ResourceBundle.getBundle(source); + } + + /** + * Constructs a RegionalizationMessage object to retrieve localized messages + * from a specified resource bundle using the given source and locale. + * + * @param source The base name of the resource bundle, a fully qualified class name. + * @param locale The Locale object specifying the desired language and region for localization. + */ + public RegionalizationMessage(final String source, final Locale locale) { + res = ResourceBundle.getBundle(source, locale); + } + + /** + * Retrieves the localized message corresponding to the specified key from the resource bundle. + * If the key does not exist in the resource bundle, this method returns null. + * + * @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. + */ + public String getMessage(final String key) { + if (!res.containsKey(key)) return null; + return res.getString(key); + } + + /** + * 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 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. + */ + public String getMessage(final String key, final String defaultMessage) { + if (res.containsKey(key)) + return res.getString(key); + + 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); + } +} diff --git a/core/src/main/java/de/neitzel/core/util/Strings.java b/core/src/main/java/de/neitzel/core/util/Strings.java new file mode 100644 index 0000000..6034981 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/util/Strings.java @@ -0,0 +1,157 @@ +package de.neitzel.core.util; + +import lombok.NonNull; + +import java.util.Map; + +/** + * Utility class providing common string manipulation methods. + */ +public class Strings { + /** + * 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 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 environmentVariables = System.getenv(); + + /** + * Expands environment variable placeholders within the provided text. + * Placeholders in the format ${VARIABLE_NAME} are replaced with their + * corresponding values from the environment variables. + * + * @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. + */ + public static String expandEnvironmentVariables(String text) { + if (text == null) return null; + for (Map.Entry entry : environmentVariables.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue().replace('\\', '/'); + text = text.replaceAll("\\$\\{" + key + "\\}", value); + } + return text; + } + + /** + * Checks whether a given string is either null or empty. + * + * @param string the string to be checked for nullity or emptiness. + * @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); + } + + /** + * Removes surrounding double quotes from the input string if they are present. + * Trims leading and trailing whitespaces from the input before processing. + * + * @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. + */ + 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 value; + } + + /** + * 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 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. + * @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) { + String replacementRegex = "[" + illegalCharacters + "]" + (combine ? "+" : ""); + return value.replaceAll(replacementRegex, replacement); + } + + /** + * Replaces characters in the input string that are not in the allowed characters set + * with a specified replacement string. Optionally combines consecutive non-allowed + * characters into a single replacement. + * + * @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. + * @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) { + String replacementRegex = "[^" + allowedCharacters + "]" + (combine ? "+" : ""); + return value.replaceAll(replacementRegex, replacement); + } + + /** + * Increments a string value by modifying its last character, or leading characters if necessary. + * The increment follows these rules: + * - If the last character is a digit '9', it wraps to 'A'. + * - If the last character is 'Z', the preceding part of the string is recursively incremented, and '0' is appended. + * - If the string is empty, it returns "1". + * - For all other characters, increments the last character by one. + * + * @param element The string whose value is to be incremented. Must not be null. + * @return The incremented string value. If the input string is empty, returns "1". + */ + 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); + if (lastChar == '9') return firstPart + 'A'; + if (lastChar == 'Z') return increment(firstPart) + '0'; + return firstPart + (char)(lastChar+1); + } + + /** + * Trims the input string to a specified maximum length, if necessary. + * If the input string's length is less than or equal to the specified max length, + * 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 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. + */ + public static String limitCharNumber(final String original, final int maxLength) { + if (original == null || original.length() <= maxLength) return original; + + return original.substring(0, maxLength); + } +} diff --git a/core/src/main/java/de/neitzel/core/util/XmlUtils.java b/core/src/main/java/de/neitzel/core/util/XmlUtils.java new file mode 100644 index 0000000..5c35142 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/util/XmlUtils.java @@ -0,0 +1,239 @@ +package de.neitzel.core.util; + +import lombok.NonNull; +import lombok.extern.log4j.Log4j; +import lombok.extern.slf4j.Slf4j; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.*; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +/** + * Utility class for handling common XML operations such as creating XML elements, + * formatting XML strings, and validating XML content. + */ +@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. + * + * 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"); + + /** + * 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 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); + + 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); + + Node content = doc.createTextNode(value != null ? ""+value : ""); + element.appendChild(content); + + return element; + } + + /** + * Creates a new element with the specified name, appends it to the provided parent element, + * and returns the newly created element. + * + * @param doc The Document to which the new element belongs. Must not be null. + * @param name The name of the new element to be created. Must not be null or empty. + * @param parent The parent element to which the new element will be appended. Must not be null. + * @return The newly created and appended Element object. + */ + public static Element createAndInsertElement(final Document doc, final String name, @NonNull final Element parent) { + log.debug("Creating a new element " + name + " and adding it to a parent."); + Element element = createElement(doc, name); + parent.appendChild(element); + return element; + } + + /** + * Creates a new 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"); + + 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"); + + 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 xmlStream; + } + + /** + * Formats a given XML Document into a human-readable string representation. + * The method applies indentation and specific output properties to the XML content. + * If an exception occurs during the transformation process, it logs the error + * and returns null. + * + * @param doc The XML Document to be formatted. Must not be null. + * @return A formatted string representation of the XML document, or null if an error occurs during processing. + */ + public static String format(final Document doc) { + try { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformerFactory.setAttribute("indent-number", "4"); + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.STANDALONE, "yes"); + transformer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1"); + StringWriter stringWriter = new StringWriter(); + + Source source = new DOMSource(doc); + 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); + } + + 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; + } + } +} diff --git a/fx-example/pom.xml b/fx-example/pom.xml index f5b0b13..562daf0 100644 --- a/fx-example/pom.xml +++ b/fx-example/pom.xml @@ -43,160 +43,4 @@ - - - - - - image - - - image - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - ${maven.dependency.plugin} - - - - copy-dependencies - package - - copy-dependencies - - - ${project.build.directory}/modules - runtime - false - false - true - - - - - - copy - install - - copy - - - ${project.build.directory}/modules - - - ${project.groupId} - ${project.artifactId} - ${project.version} - ${project.packaging} - ${project.build.finalName}.jar - - - true - - - - - - - com.github.akman - jpackage-maven-plugin - ${jpackage.maven.plugin} - - ${appName} - IMAGE - - - - - javafx\..* - - - - - - javafx.controls - javafx.graphics - javafx.fxml - javafx.web - - ${main.class} - ${project.build.directory}/modules - ${jar.filename}.jar - - - - install - - jpackage - - - - - - - - - - - fatjar - - - fatjar - - - - - - org.apache.maven.plugins - maven-shade-plugin - ${maven.shade.plugin} - - - package - - shade - - - - - true - full - - - - - ${main.class} - 1.0 - - - - - - *:* - - META-INF/MANIFEST.MF - **/module-info.class - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - - - - - - - - - diff --git a/log4j/pom.xml b/log4j/pom.xml new file mode 100644 index 0000000..015db45 --- /dev/null +++ b/log4j/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + + de.neitzel.lib + neitzellib + 1.0-SNAPSHOT + + + log4j + + + + ${project.artifactId} + ${project.artifactId} + ${project.artifactId} + ${project.artifactId}-${project.version} + + + 1.2.17 + + + + + log4j + log4j + ${log4j.version} + + + log4j + apache-log4j-extras + ${log4j.version} + + + + + ${jar.filename} + + + + com.github.spotbugs + spotbugs-maven-plugin + + + + org.apache.maven.plugins + maven-pmd-plugin + + + + diff --git a/log4j/src/main/java/de/neitzel/log4j/Log4jUtils.java b/log4j/src/main/java/de/neitzel/log4j/Log4jUtils.java new file mode 100644 index 0000000..9b79868 --- /dev/null +++ b/log4j/src/main/java/de/neitzel/log4j/Log4jUtils.java @@ -0,0 +1,88 @@ +package de.neitzel.log4j; + +import org.apache.log4j.PropertyConfigurator; + +import java.io.File; +import java.net.URISyntaxException; + +/** + * Utility class for managing Log4j configurations. Provides methods to set up + * Log4j configurations from default files, resources, or command line arguments. + */ +public class Log4jUtils { + + /** + * The default path to the Log4j configuration file. + * This variable is used to specify the local file path for the Log4j configuration + * when no custom configuration is provided. + */ + public static final String DEFAULT_LOG4J_LOGFILE = "./log4j.properties"; + + /** + * The default resource path to the Log4j configuration file included in the classpath. + * This path is used as a fallback when no other Log4j configuration is explicitly set. + */ + public static final String DEFAULT_LOG4J_RESOURCE = "/log4j.default.properties"; + + /** + * Checks if the system property "log4j.configuration" is set. + * + * @return true if the "log4j.configuration" property is defined, false otherwise. + */ + public static boolean isLog4jConfigFileSet() { + return System.getProperty("log4j.configuration") != null; + } + + /** + * Configures Log4j using default configuration settings. + * This method leverages a default configuration file path and a default resource path + * to set up Log4j logging if a configuration file is not already specified via + * a system property. If a valid configuration file or resource is found, it will be applied. + * + * Delegates to the overloaded {@code setLog4jConfiguration(String log4jConfigFile, String defaultResource)} + * method using predefined defaults. + */ + public static void setLog4jConfiguration() { + setLog4jConfiguration(DEFAULT_LOG4J_LOGFILE, DEFAULT_LOG4J_RESOURCE); + } + + /** + * Constructs the absolute path to the specified Log4j configuration file located + * in the same directory as the JAR file of the application. + * + * @param log4jConfigFile The name of the Log4j configuration file. + * @return The absolute path to the specified Log4j configuration file if the + * path is successfully constructed; otherwise, returns null in case of an error. + */ + public static String getLog4jLogfileAtJar(final String log4jConfigFile) { + try { + return new File(Log4jUtils.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent() + "/" + log4jConfigFile; + } catch (URISyntaxException ex) { + return null; + } + } + + /** + * Sets the Log4j configuration using the specified configuration file or a default resource + * if the configuration file is not found. If a Log4j configuration is already set, the method + * does nothing. + * + * @param log4jConfigFile the path to the Log4j configuration file to be used. + * @param defaultResource the fallback resource to be used as the configuration if the file + * is not found or accessible. + */ + public static void setLog4jConfiguration(final String log4jConfigFile, final String defaultResource) { + if (isLog4jConfigFileSet()) return; + + String fileAtJar = getLog4jLogfileAtJar(log4jConfigFile); + + if (new File(log4jConfigFile).exists()) { + PropertyConfigurator.configure(log4jConfigFile); + } else if (fileAtJar != null && new File(fileAtJar).exists()) { + System.out.println("Nutze Log4J Konfiguration bei jar File: " + fileAtJar); + PropertyConfigurator.configure(fileAtJar); + }else { + PropertyConfigurator.configure(Log4jUtils.class.getResource(defaultResource)); + } + } +} diff --git a/pom.xml b/pom.xml index 35ab1ba..770d184 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,7 @@ encryption fx net + log4j fx-example @@ -24,10 +25,10 @@ 21.0.3 - 24.1.0 - 5.10.2 - 1.18.32 - 5.12.0 + 26.0.2 + 5.12.1 + 1.18.38 + 5.16.1 0.10.2 2.0.17 @@ -99,6 +100,7 @@ + @@ -143,6 +145,13 @@ test + + + org.slf4j + slf4j-simple + test + + org.jetbrains