Added FileUtils / TempDirectory from EmailTool.

Moved FileUtils to io package.
This commit is contained in:
Konrad Neitzel 2025-07-01 15:38:07 +02:00
parent 2b60038774
commit 5aa75be6ba
9 changed files with 550 additions and 306 deletions

View File

@ -1,6 +1,6 @@
package de.neitzel.core.config; package de.neitzel.core.config;
import de.neitzel.core.util.FileUtils; import de.neitzel.core.io.FileUtils;
import de.neitzel.core.util.Strings; import de.neitzel.core.util.Strings;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -28,7 +28,7 @@ public class Configuration {
* A {@link Properties} object that stores a set of key-value pairs. * A {@link Properties} object that stores a set of key-value pairs.
* This variable can be used to manage configuration settings or other * This variable can be used to manage configuration settings or other
* collections of properties within the application. * collections of properties within the application.
* * <p>
* It provides methods to load, retrieve, and modify properties * It provides methods to load, retrieve, and modify properties
* as necessary for the application's requirements. * as necessary for the application's requirements.
*/ */
@ -56,20 +56,20 @@ public class Configuration {
* Retrieves a boolean property value associated with the specified key. If the key does not exist in the 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. * 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 key the key used to retrieve the boolean property
* @param defaultValue the default value returned if the key is not found in the properties * @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 * @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) { protected boolean getBooleanProperty(final String key, final boolean defaultValue) {
if (!properties.containsKey(key)) return defaultValue; if (!properties.containsKey(key)) return defaultValue;
return getStringProperty(key, defaultValue ? "ja": "nein").equalsIgnoreCase("ja") || properties.getProperty(key).equalsIgnoreCase("true"); return getStringProperty(key, defaultValue ? "ja" : "nein").equalsIgnoreCase("ja") || properties.getProperty(key).equalsIgnoreCase("true");
} }
/** /**
* Retrieves the value of the specified property as a trimmed string. * Retrieves the value of the specified property as a trimmed string.
* If the property is not found, the default value is returned. * If the property is not found, the default value is returned.
* *
* @param key the key of the property to retrieve * @param key the key of the property to retrieve
* @param defaultValue the default value to return if the property is not found * @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 * @return the trimmed string value of the property, or the default value if the property is not found
*/ */
@ -82,34 +82,22 @@ public class Configuration {
* Retrieves a string property value associated with the specified key, applies * Retrieves a string property value associated with the specified key, applies
* environment variable expansion on the value, and returns the processed result. * environment variable expansion on the value, and returns the processed result.
* *
* @param key the key identifying the property to retrieve. * @param key the key identifying the property to retrieve.
* @param defaultValue the default value to use if the property is not found. * @param defaultValue the default value to use if the property is not found.
* @return the processed property value with expanded environment variables, or * @return the processed property value with expanded environment variables, or
* the defaultValue if the property is not found. * the defaultValue if the property is not found.
*/ */
protected String getStringPropertyWithEnv(final String key, final String defaultValue) { protected String getStringPropertyWithEnv(final String key, final String defaultValue) {
String result = getStringProperty(key, defaultValue); String result = getStringProperty(key, defaultValue);
return Strings.expandEnvironmentVariables(result); return Strings.expandEnvironmentVariables(result);
} }
/**
* Retrieves the value of a string property associated with the specified key,
* removes any surrounding quotes from the value, and returns the resultant string.
*
* @param key the key associated with the desired property
* @param defaultValue the default value to return if the property is not found or is null
* @return the string property without surrounding quotes, or the defaultValue if the property is not found
*/
protected String getStringPropertyWithoutQuotes(final String key, final String defaultValue) {
return Strings.removeQuotes(getStringProperty(key, defaultValue));
}
/** /**
* Retrieves the string value of a configuration property identified by the given key, * Retrieves the string value of a configuration property identified by the given key,
* removes surrounding quotes if present, and expands any environment variables found * removes surrounding quotes if present, and expands any environment variables found
* within the string. If the property is not found, a default value is used. * within the string. If the property is not found, a default value is used.
* *
* @param key the key identifying the configuration property * @param key the key identifying the configuration property
* @param defaultValue the default value to use if the property is not found * @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 * @return the processed string property with quotes removed and environment variables expanded
*/ */
@ -118,16 +106,28 @@ public class Configuration {
return Strings.expandEnvironmentVariables(result); return Strings.expandEnvironmentVariables(result);
} }
/**
* Retrieves the value of a string property associated with the specified key,
* removes any surrounding quotes from the value, and returns the resultant string.
*
* @param key the key associated with the desired property
* @param defaultValue the default value to return if the property is not found or is null
* @return the string property without surrounding quotes, or the defaultValue if the property is not found
*/
protected String getStringPropertyWithoutQuotes(final String key, final String defaultValue) {
return Strings.removeQuotes(getStringProperty(key, defaultValue));
}
/** /**
* Retrieves the integer value for the specified property key. If the key does * Retrieves the integer value for the specified property key. If the key does
* not exist in the properties or the value is null/empty, the provided default * not exist in the properties or the value is null/empty, the provided default
* value is returned. * value is returned.
* *
* @param key the property key to retrieve the value for * @param key the property key to retrieve the value for
* @param defaultValue the default value to return if the key is not present * @param defaultValue the default value to return if the key is not present
* or the value is null/empty * or the value is null/empty
* @return the integer value associated with the key, or the defaultValue if * @return the integer value associated with the key, or the defaultValue if
* the key does not exist or its value is null/empty * the key does not exist or its value is null/empty
*/ */
protected Integer getIntegerProperty(final String key, final Integer defaultValue) { protected Integer getIntegerProperty(final String key, final Integer defaultValue) {
if (!properties.containsKey(key)) return defaultValue; if (!properties.containsKey(key)) return defaultValue;
@ -139,14 +139,14 @@ public class Configuration {
* Sets an integer property in the properties object. If the provided value is null, * 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. * an empty string will be stored as the property's value.
* *
* @param key the key under which the property will be stored * @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 * @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) { protected void setIntegerProperty(final String key, final Integer value) {
if (value == null) { if (value == null) {
properties.setProperty(key, ""); properties.setProperty(key, "");
} else { } else {
properties.setProperty(key, ""+value); properties.setProperty(key, "" + value);
} }
} }
@ -154,11 +154,11 @@ public class Configuration {
* Retrieves a LocalDate value from the properties based on the provided key. * 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. * 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 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 defaultValue the default LocalDate value to return if the key is not found
* @param formatString the format string to parse the LocalDate value * @param formatString the format string to parse the LocalDate value
* @return the LocalDate value from the properties if available and valid, * @return the LocalDate value from the properties if available and valid,
* otherwise the defaultValue * otherwise the defaultValue
*/ */
protected LocalDate getLocalDateProperty(final String key, final LocalDate defaultValue, final String formatString) { protected LocalDate getLocalDateProperty(final String key, final LocalDate defaultValue, final String formatString) {
if (!properties.containsKey(key)) return defaultValue; if (!properties.containsKey(key)) return defaultValue;
@ -172,8 +172,8 @@ public class Configuration {
* Sets a property with the given key and a formatted LocalDate value. * 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. * 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 key the key of the property to set
* @param value the LocalDate value to format and set as the property value * @param value the LocalDate value to format and set as the property value
* @param formatString the pattern string used to format the LocalDate value * @param formatString the pattern string used to format the LocalDate value
*/ */
protected void setLocalDateProperty(final String key, final LocalDate value, final String formatString) { protected void setLocalDateProperty(final String key, final LocalDate value, final String formatString) {
@ -188,7 +188,7 @@ public class Configuration {
* Sets a property with the specified key to the given value. If the value is null, * 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. * it defaults to an empty string. Logs the operation and updates the property.
* *
* @param key the key of the property to be set * @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 * @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) { public void setProperty(final String key, final String value) {
@ -211,9 +211,9 @@ public class Configuration {
* specified location, it attempts to find the file alongside the JAR file of the application. * 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. * 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 fileName the name of the configuration file to be loaded
* @param encoding the encoding format to be used while reading the configuration file * @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 * @param acceptUTF8 a boolean flag indicating whether to accept UTF-8 encoding
*/ */
public void load(final String fileName, final String encoding, final boolean acceptUTF8) { public void load(final String fileName, final String encoding, final boolean acceptUTF8) {
log.info("Reading Config: " + fileName + " with encoding: " + encoding + "accepting UTF-8: " + acceptUTF8); log.info("Reading Config: " + fileName + " with encoding: " + encoding + "accepting UTF-8: " + acceptUTF8);
@ -251,7 +251,7 @@ public class Configuration {
* @param config the Configuration object whose properties will be merged into this instance * @param config the Configuration object whose properties will be merged into this instance
*/ */
public void merge(final Configuration config) { public void merge(final Configuration config) {
for(Map.Entry<Object, Object> entry: config.properties.entrySet()) { for (Map.Entry<Object, Object> entry : config.properties.entrySet()) {
properties.put(entry.getKey(), entry.getValue()); properties.put(entry.getKey(), entry.getValue());
} }
} }
@ -261,7 +261,7 @@ public class Configuration {
* *
* @param key the key to be removed from the properties map * @param key the key to be removed from the properties map
*/ */
public void remove(final String key){ public void remove(final String key) {
if (properties.containsKey(key)) properties.remove(key); if (properties.containsKey(key)) properties.remove(key);
} }
} }

View File

@ -1,6 +1,5 @@
package de.neitzel.core.io; package de.neitzel.core.io;
import de.neitzel.core.util.FileUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.io.*; import java.io.*;
@ -12,7 +11,7 @@ import java.nio.charset.IllegalCharsetNameException;
* for handling file encoding conversion transparently. If a file is detected to be in UTF-8 encoding, * for handling file encoding conversion transparently. If a file is detected to be in UTF-8 encoding,
* this class converts it to the specified target encoding using a temporary file, then opens the reader * this class converts it to the specified target encoding using a temporary file, then opens the reader
* with the converted encoding. If the file is already in the target encoding, it opens the reader directly. * with the converted encoding. If the file is already in the target encoding, it opens the reader directly.
* * <p>
* This class is useful for applications needing to process text files in specific encodings and ensures * This class is useful for applications needing to process text files in specific encodings and ensures
* encoding compatibility. * encoding compatibility.
*/ */
@ -25,47 +24,19 @@ public class ConvertedEncodingFileReader extends InputStreamReader {
* This encoding is primarily used to determine whether a file needs conversion * This encoding is primarily used to determine whether a file needs conversion
* to the target format or can be read directly in its existing format. * to the target format or can be read directly in its existing format.
* The default value is set to "ISO-8859-15". * The default value is set to "ISO-8859-15".
* * <p>
* Modifying this variable requires careful consideration, as it affects * Modifying this variable requires careful consideration, as it affects
* the behavior of methods that rely on encoding validation, particularly * the behavior of methods that rely on encoding validation, particularly
* in the process of detecting UTF-8 files or converting them during file reading. * in the process of detecting UTF-8 files or converting them during file reading.
*/ */
private static String checkEncoding = "ISO-8859-15"; private static String checkEncoding = "ISO-8859-15";
/**
* Sets the encoding that will be used to check the file encoding for compatibility.
* 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. * 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 * 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. * 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 filename the path to the file to be read
* @param targetFormat the target encoding format to use for reading the file * @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 * @throws IOException if an I/O error occurs while accessing or reading the specified file
*/ */
@ -73,13 +44,27 @@ public class ConvertedEncodingFileReader extends InputStreamReader {
this(new File(filename), targetFormat); this(new File(filename), targetFormat);
} }
/**
* 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);
}
/** /**
* Creates an input file stream for a given file, converting its encoding if necessary. * Creates an input file stream for a given file, converting its encoding if necessary.
* If the file is not in UTF-8 encoding, a direct {@link FileInputStream} is returned for the file. * If the file is not in UTF-8 encoding, a direct {@link FileInputStream} is returned for the file.
* If the file is in UTF-8 encoding, it is converted to the specified target format using a temporary 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. * 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 file the file for which the input stream is to be created
* @param targetFormat the desired target encoding format * @param targetFormat the desired target encoding format
* @return a {@link FileInputStream} for the file or a temporary file with converted encoding * @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 * @throws IOException if the file does not exist or an error occurs during file operations
@ -100,4 +85,19 @@ public class ConvertedEncodingFileReader extends InputStreamReader {
return new FileInputStream(tempFile); return new FileInputStream(tempFile);
} }
} }
/**
* Sets the encoding that will be used to check the file encoding for compatibility.
* Throws an exception if the specified encoding is not valid or supported.
*
* @param encoding the name of the character encoding to set as the check encoding;
* it must be a valid and supported Charset.
* @throws IllegalCharsetNameException if the specified encoding is not valid or supported.
*/
private static void setCheckEncoding(final String encoding) {
if (Charset.forName(encoding) != null)
throw new IllegalCharsetNameException("Encoding " + encoding + " is not supported!");
checkEncoding = encoding;
}
} }

View File

@ -1,31 +1,21 @@
package de.neitzel.core.util; package de.neitzel.core.io;
import de.neitzel.core.util.ArrayUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.io.*; import java.io.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
* Utility class for handling file operations, such as encoding checks, content reading/writing, * A utility class for file-related operations. This class provides functionalities
* path manipulations, and file conversions. * for handling files and directories in an efficient manner.
*/ */
@Slf4j @Slf4j
public class FileUtils { public class FileUtils {
/**
* 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 * 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 * or identifying files with precise timestamps. The format is "yyyy-MM-dd_HH_mm_ss_SSS", which
@ -37,14 +27,14 @@ public class FileUtils {
* - Minutes in two digits (mm) * - Minutes in two digits (mm)
* - Seconds in two digits (ss) * - Seconds in two digits (ss)
* - Milliseconds in three digits (SSS) * - Milliseconds in three digits (SSS)
* * <p>
* This ensures timestamps are sortable and easily identifiable. * 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"); 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. * Default encoding used for string checks and validations in the application.
* * <p>
* This constant represents the `ISO-8859-15` encoding, which is a standardized * This constant represents the `ISO-8859-15` encoding, which is a standardized
* character set encoding, commonly used in contexts where backward compatibility * character set encoding, commonly used in contexts where backward compatibility
* with `ISO-8859-1` is required, but with support for certain additional characters, * with `ISO-8859-1` is required, but with support for certain additional characters,
@ -54,17 +44,40 @@ public class FileUtils {
/** /**
* Specifies the default buffer size used for data processing operations. * Specifies the default buffer size used for data processing operations.
* * <p>
* This constant represents the size of the buffer in bytes, set to 1024, * This constant represents the size of the buffer in bytes, set to 1024,
* and is typically utilized in input/output operations to optimize performance * and is typically utilized in input/output operations to optimize performance
* by reducing the number of read/write calls. * by reducing the number of read/write calls.
*/ */
public static final int BUFFER_SIZE = 1024; public static final int BUFFER_SIZE = 1024;
/**
* Private constructor to prevent instantiation of the utility class.
* This utility class is not meant to be instantiated, as it only provides
* 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");
}
/**
* 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);
}
/** /**
* Determines whether the given file is encoded in UTF-8. * Determines whether the given file is encoded in UTF-8.
* *
* @param file The file to be checked for UTF-8 encoding. * @param file The file to be checked for UTF-8 encoding.
* @param checkEncoding The character encoding to use while checking the file content. * @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. * @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. * @throws IOException If an I/O error occurs while reading the file.
@ -82,7 +95,7 @@ public class FileUtils {
if ( if (
(ArrayUtils.contains(buffer, (char) 0x00C2)) // Part of UTF-8 Characters 0xC2 0xZZ (ArrayUtils.contains(buffer, (char) 0x00C2)) // Part of UTF-8 Characters 0xC2 0xZZ
|| (ArrayUtils.contains(buffer, (char) 0x00C3))) { // Part of UTF-8 Characters 0xC3 0xZZ || (ArrayUtils.contains(buffer, (char) 0x00C3))) { // Part of UTF-8 Characters 0xC3 0xZZ
return true; return true;
} }
@ -97,7 +110,7 @@ public class FileUtils {
/** /**
* Checks if the provided file starts with a UTF-8 Byte Order Mark (BOM). * Checks if the provided file starts with a UTF-8 Byte Order Mark (BOM).
* * <p>
* This method reads the first character of the file using a reader that assumes * This method reads the first character of the file using a reader that assumes
* UTF-8 encoding and checks if it matches the Unicode Byte Order Mark (U+FEFF). * UTF-8 encoding and checks if it matches the Unicode Byte Order Mark (U+FEFF).
* *
@ -114,26 +127,15 @@ public class FileUtils {
} }
} }
/**
* Determines if the content of the given file is encoded in UTF-8.
*
* @param file The file to check for UTF-8 encoding. Must not be null.
* @return true if the file content is in UTF-8 encoding; false otherwise.
* @throws IOException If an I/O error occurs while reading the file.
*/
public static boolean isUTF8(final File file) throws IOException {
return isUTF8(file, DEFAULT_CHECK_ENCODING);
}
/** /**
* Converts the content of a text file from one character encoding format to another. * Converts the content of a text file from one character encoding format to another.
* * <p>
* This method reads the input text file using the specified source encoding and writes * This method reads the input text file using the specified source encoding and writes
* the content to the output text file in the specified target encoding. * the content to the output text file in the specified target encoding.
* *
* @param inFile The input text file to be converted. Must not be null. * @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 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 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. * @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. * @throws IOException If an I/O error occurs during reading or writing.
*/ */
@ -167,25 +169,12 @@ public class FileUtils {
} }
} }
/**
* Creates a universal file reader for the specified file name.
* This method initializes and returns an InputStreamReader to read
* the content of the given file.
*
* @param filename The name or path of the file to be read.
* @return An InputStreamReader for reading the specified file.
* @throws IOException If an I/O error occurs while creating the file reader.
*/
public static InputStreamReader createUniversalFileReader(final String filename) throws IOException {
return createUniversalFileReader(new File(filename));
}
/** /**
* Creates a universal file reader for the specified file and format. * Creates a universal file reader for the specified file and format.
* The method resolves the file using its name and the expected format, * The method resolves the file using its name and the expected format,
* returning an InputStreamReader for reading the file contents. * returning an InputStreamReader for reading the file contents.
* *
* @param filename the name of the file to be read. * @param filename the name of the file to be read.
* @param expectedFormat the format expected for the file content. * @param expectedFormat the format expected for the file content.
* @return an InputStreamReader for the specified file and format. * @return an InputStreamReader for the specified file and format.
* @throws IOException if an I/O error occurs while opening or reading the file. * @throws IOException if an I/O error occurs while opening or reading the file.
@ -194,25 +183,14 @@ public class FileUtils {
return createUniversalFileReader(new File(filename), expectedFormat); return createUniversalFileReader(new File(filename), expectedFormat);
} }
/**
* Creates a universal file reader for the specified file using the default encoding and configuration.
*
* @param file The file to be read. Must not be null.
* @return An InputStreamReader configured to read the specified file.
* @throws IOException If an I/O error occurs while creating the reader.
*/
public static InputStreamReader createUniversalFileReader(final File file) throws IOException {
return createUniversalFileReader(file, DEFAULT_CHECK_ENCODING, true);
}
/** /**
* Creates a universal file reader for the specified file with an expected format. * Creates a universal file reader for the specified file with an expected format.
* This method wraps the given file in an InputStreamReader for consistent character stream manipulation. * This method wraps the given file in an InputStreamReader for consistent character stream manipulation.
* *
* @param file The file to be read. Must not be null. * @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. * @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 * @return An InputStreamReader for the specified file, allowing the caller to read the file
* with the desired format applied. * with the desired format applied.
* @throws IOException If an I/O error occurs during the creation of the reader. * @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 { public static InputStreamReader createUniversalFileReader(final File file, final String expectedFormat) throws IOException {
@ -223,9 +201,9 @@ public class FileUtils {
* Creates an InputStreamReader for reading a file, considering the specified encoding format * 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. * and whether UTF-8 should be accepted. Handles potential BOM for UTF-8 encoded files.
* *
* @param file The file to be read. * @param file The file to be read.
* @param expectedFormat The expected encoding format of the file. * @param expectedFormat The expected encoding format of the file.
* @param acceptUTF8 Indicates whether UTF-8 encoding should be accepted if detected. * @param acceptUTF8 Indicates whether UTF-8 encoding should be accepted if detected.
* @return An InputStreamReader for the specified file and encoding. * @return An InputStreamReader for the specified file and encoding.
* @throws IOException If there is an error accessing the file or reading its content. * @throws IOException If there is an error accessing the file or reading its content.
*/ */
@ -239,14 +217,14 @@ public class FileUtils {
InputStreamReader result = new InputStreamReader(new FileInputStream(file), encoding); InputStreamReader result = new InputStreamReader(new FileInputStream(file), encoding);
if (skipBOM) { if (skipBOM) {
int BOM = result.read(); int BOM = result.read();
if (BOM != 0xFEFF) log.error ("Skipping BOM but value not 0xFEFF!"); if (BOM != 0xFEFF) log.error("Skipping BOM but value not 0xFEFF!");
} }
return result; return result;
} }
/** /**
* Retrieves the parent directory of the given file or directory path. * Retrieves the parent directory of the given file or directory path.
* * <p>
* If the given path does not have a parent directory, it defaults to returning the * If the given path does not have a parent directory, it defaults to returning the
* current directory represented by ".". * current directory represented by ".".
* *
@ -282,10 +260,34 @@ public class FileUtils {
} }
} }
/**
* Creates a universal file reader for the specified file name.
* This method initializes and returns an InputStreamReader to read
* the content of the given file.
*
* @param filename The name or path of the file to be read.
* @return An InputStreamReader for reading the specified file.
* @throws IOException If an I/O error occurs while creating the file reader.
*/
public static InputStreamReader createUniversalFileReader(final String filename) throws IOException {
return createUniversalFileReader(new File(filename));
}
/**
* Creates a universal file reader for the specified file using the default encoding and configuration.
*
* @param file The file to be read. Must not be null.
* @return An InputStreamReader configured to read the specified file.
* @throws IOException If an I/O error occurs while creating the reader.
*/
public static InputStreamReader createUniversalFileReader(final File file) throws IOException {
return createUniversalFileReader(file, DEFAULT_CHECK_ENCODING, true);
}
/** /**
* Writes the given content to the specified file path. If the file already exists, it will be overwritten. * Writes the given content to the specified file path. If the file already exists, it will be overwritten.
* *
* @param path The path of the file to write to. Must not be null and must be writable. * @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. * @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. * @throws IOException If an I/O error occurs during writing to the file.
*/ */
@ -294,4 +296,60 @@ public class FileUtils {
writer.write(content); writer.write(content);
} }
} }
/**
* Deletes the specified file or directory. If the target is a directory, all its contents,
* including subdirectories and files, will be deleted recursively.
* If the target file or directory does not exist, the method immediately returns {@code true}.
*
* @param targetFile the {@code Path} of the file or directory to be deleted
* @return {@code true} if the target file or directory was successfully deleted,
* or if it does not exist; {@code false} if an error occurred during deletion
*/
public static boolean remove(Path targetFile) {
if (!Files.exists(targetFile)) {
return true;
}
if (Files.isDirectory(targetFile)) {
return removeDirectory(targetFile);
}
return targetFile.toFile().delete();
}
/**
* Deletes the specified directory and all its contents, including subdirectories and files.
* The method performs a recursive deletion, starting with the deepest entries in the directory tree.
* If the directory does not exist, the method immediately returns true.
*
* @param targetDir the {@code Path} of the directory to be deleted
* @return {@code true} if the directory and all its contents were successfully deleted
* or if the directory does not exist; {@code false} if an error occurred during deletion
*/
public static boolean removeDirectory(Path targetDir) {
if (!Files.exists(targetDir)) {
return true;
}
if (!Files.isDirectory(targetDir)) {
return false;
}
try {
Files.walk(targetDir)
.sorted((a, b) -> b.compareTo(a))
.forEach(path -> {
try {
Files.deleteIfExists(path);
} catch (Exception ignored) {
}
});
} catch (IOException ignored) {
return false;
}
return true;
}
} }

View File

@ -0,0 +1,76 @@
package de.neitzel.core.io;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.stream.Stream;
/**
* A utility class for creating and managing a temporary directory.
* Instances of this class create a unique temporary directory on the filesystem
* that can safely be used during the runtime of a program.
* <p>
* The directory is automatically cleaned up when the instance is closed.
*/
public class TempDirectory implements AutoCloseable {
/**
* The filesystem path representing the temporary directory managed by this instance.
* This path is initialized when the TempDirectory object is created and points
* to a unique, newly created directory.
* <p>
* The directory can be used to safely store temporary runtime files. It is automatically
* deleted along with its content when the associated TempDirectory object is closed.
*/
private final Path directory;
/**
* Creates a temporary directory with a unique name on the filesystem.
* The directory will have a prefix specified by the user and is intended
* to serve as a temporary workspace during the runtime of the program.
*
* @param prefix the prefix to be used for the name of the temporary directory
* @throws IOException if an I/O error occurs when creating the directory
*/
public TempDirectory(String prefix) throws IOException {
this.directory = Files.createTempDirectory(prefix);
}
/**
* Retrieves the path of the temporary directory associated with this instance.
*
* @return the {@code Path} of the temporary directory
*/
public Path getDirectory() {
return directory;
}
/**
* Closes the temporary directory by cleaning up its contents and deleting the directory itself.
* <p>
* This method ensures that all files and subdirectories within the temporary directory are
* deleted in a reverse order, starting from the deepest leaf in the directory tree. If
* the directory does not exist, the method will not perform any actions.
* <p>
* If an error occurs while deleting any file or directory, a RuntimeException is thrown
* with the details of the failure.
*
* @throws IOException if an I/O error occurs while accessing the directory or its contents.
*/
@Override
public void close() throws IOException {
if (Files.exists(directory)) {
try (Stream<Path> walk = Files.walk(directory)) {
walk.sorted(Comparator.reverseOrder())
.forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
throw new RuntimeException("Failed to delete: " + path, e);
}
});
}
}
}
}

View File

@ -1,15 +1,99 @@
# TODO
- SimpleListProperty auch nutzen bei Collections!
# Ideen # Ideen
# Bindings # Bindings
Bindngs kommen über ein spezielles Binding Control, das dann notwendige Bindings beinhaltet. Bindngs kommen über ein spezielles Binding Control, das dann notwendige Bindings beinhaltet.
Da kann dann auch eine eigene Logik zur Erkennung des Bindings erfolgen oder zusätziche Informationen bezüglich notwendiger Elemente in dem ViewModel Da kann dann auch eine eigene Logik zur Erkennung des Bindings erfolgen oder zusätziche Informationen bezüglich
notwendiger Elemente in dem ViewModel
## Bidirektional Converter für Bindings
```Java
StringConverter<Instant> converter = new StringConverter<>() {
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault());
@Override
public String toString(Instant instant) {
return instant == null ? "" : formatter.format(instant);
}
@Override
public Instant fromString(String string) {
if (string == null || string.isBlank()) return null;
try {
LocalDateTime dateTime = LocalDateTime.parse(string, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return dateTime.atZone(ZoneId.systemDefault()).toInstant();
} catch (DateTimeParseException e) {
// Optional: Fehlermeldung anzeigen
return null;
}
}
};
// Binding einrichten:
Bindings.
bindBidirectional(textField.textProperty(),instantProperty,converter);
```
Overloads von bindBidirectional
bindBidirectional(Property<String>, Property<?>, Format)
bindBidirectional(Property<String>, Property<T>, StringConverter<T>)
bindBidirectional(Property<T>, Property<T>)
## Unidirektional mit Bindings.createStringBinding()
```Java
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault());
textField.
textProperty().
bind(Bindings.createStringBinding(
() ->{
Instant instant = instantProperty.get();
return instant ==null?"":formatter.
format(instant);
},
instantProperty
));
```
Bindings:
- BooleanBinding
- DoubleBinding
- FloatBinding
- IntegerBinding
- LongBinding
- StringBinding
- ObjectBinding
- NumberBinding
Bindings haben Methoden, die dann weitere Bindings erzeugen.
==> Parameter der Methode: Property, Observable und einer Methode, die ein Binding erstellt und das ViewModel<?>, das
dann genutzt werden kann, um weitere Properties zu holen ==> Erzeugung von neuen Properties.
Diese BindingCreators haben Namen, PropertyTyp und ein BindingType.
# FXMLComponent # FXMLComponent
Dient dem Laden einer Komponente und bekommt dazu das fxml, die Daten und ggf. auch eine ModelView. Dient dem Laden einer Komponente und bekommt dazu das fxml, die Daten und ggf. auch eine ModelView.
# ModelView generieren # ModelView generieren
Damit eine ModelView nicht ständig manuell generiert werden muss, ist hier ggf. etwas zu generieren? Damit eine ModelView nicht ständig manuell generiert werden muss, ist hier ggf. etwas zu generieren?
Ggf. eine eigenes Beschreibungssprache? Ggf. eine eigenes Beschreibungssprache?
# Aufbau einer Validierung
- gehört in das ViewModel
-

View File

@ -1,12 +1,20 @@
package de.neitzel.fx.component; package de.neitzel.fx.component;
import de.neitzel.fx.component.controls.Binding;
import de.neitzel.fx.component.controls.FxmlComponent;
import de.neitzel.fx.component.model.BindingData;
import javafx.beans.property.Property; import javafx.beans.property.Property;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.layout.Pane; import javafx.scene.layout.Pane;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -16,13 +24,35 @@ import java.util.Map;
/** /**
* ComponentLoader is responsible for loading JavaFX FXML components and binding * ComponentLoader is responsible for loading JavaFX FXML components and binding
* them to automatically generated ViewModels based on simple POJO models. * them to automatically generated ViewModels based on simple POJO models.
* <p>
* It parses custom NFX attributes in FXML to bind UI elements to properties in the ViewModel,
* and supports recursive loading of subcomponents.
*/ */
@Slf4j
public class ComponentLoader { public class ComponentLoader {
private Map<String, Map<String, String>> nfxBindingMap = new HashMap<>(); private Map<String, Map<String, String>> nfxBindingMap = new HashMap<>();
@Getter
private Object controller;
public Parent load(URL fxmlPath) {
return load(null, fxmlPath);
}
public Parent load(String fxmlPath) {
return load(null, fxmlPath);
}
public Parent load(Object model, URL fxmlPath) {
try {
AutoViewModel<?> viewModel = new AutoViewModel<>(model);
FXMLLoader loader = new FXMLLoader(fxmlPath);
loader.setControllerFactory(type -> new ComponentController(viewModel));
Parent root = loader.load();
controller = loader.getController();
return root;
} catch (IOException e) {
throw new RuntimeException("unable to load fxml: " + fxmlPath, e);
}
}
/** /**
* Loads an FXML file and binds its elements to a generated ViewModel * Loads an FXML file and binds its elements to a generated ViewModel
* based on the given POJO model. * based on the given POJO model.
@ -32,83 +62,116 @@ public class ComponentLoader {
* @return the root JavaFX node loaded from FXML * @return the root JavaFX node loaded from FXML
*/ */
public Parent load(Object model, String fxmlPath) { public Parent load(Object model, String fxmlPath) {
return load(model, getClass().getResource(fxmlPath));
}
public static <T extends Parent> T load(URL fxmlUrl, Object controller, String nothing) throws IOException {
FXMLLoader loader = new FXMLLoader(fxmlUrl);
loader.setController(controller);
T root = loader.load();
Map<String, Object> namespace = loader.getNamespace();
// Nach allen BindingControls suchen:
List<Binding> bindingControls = collectAllNodes(root).stream()
.filter(n -> n instanceof Binding)
.map(n -> (Binding) n)
.toList();
for (Binding bc : bindingControls) {
evaluateBindings(bc.getBindings(), namespace);
}
return root;
}
private static <T> void bindBidirectionalSafe(@NotNull Property<T> source, Property<?> target) {
try { try {
AutoViewModel<?> viewModel = new AutoViewModel<>(model); Property<T> targetCasted = (Property<T>) target;
String cleanedUri = preprocessNfxAttributes(fxmlPath); source.bindBidirectional(targetCasted);
FXMLLoader loader = new FXMLLoader(new URL(cleanedUri)); } catch (ClassCastException e) {
loader.setControllerFactory(type -> new ComponentController(viewModel)); log.error("⚠️ Typkonflikt beim Binding: %s ⇄ %s%n", source.getClass(), target.getClass(), e);
Parent root = loader.load();
processNfxBindings(root, viewModel, loader);
return root;
} catch (IOException e) {
throw new RuntimeException("unable to load fxml: " + fxmlPath, e);
} }
} }
/** private static <T> void bindSafe(@NotNull Property<T> source, Property<?> target) {
* Processes all UI elements for NFX binding attributes and applies try {
* the appropriate bindings to the ViewModel. Property<T> targetCasted = (Property<T>) target;
* source.bind(targetCasted);
* @param root the root node of the loaded FXML hierarchy } catch (ClassCastException e) {
* @param viewModel the generated ViewModel to bind against log.error("⚠️ Typkonflikt beim Binding: %s ⇄ %s%n", source.getClass(), target.getClass(), e);
*/ }
private void processNfxBindings(Parent root, AutoViewModel<?> viewModel, FXMLLoader loader) {
walkNodes(root, node -> {
var nfx = lookupNfxAttributes(node, loader);
String target = nfx.get("nfx:target");
String direction = nfx.get("nfx:direction");
String source = nfx.get("nfx:source");
if (target != null && direction != null) {
Property<?> vmProp = viewModel.getProperty(target);
bindNodeToProperty(node, vmProp, direction);
}
if (source != null) {
Object subModel = ((Property<?>) viewModel.getProperty(target)).getValue();
Parent subComponent = load(subModel, source);
if (node instanceof Pane pane) {
pane.getChildren().setAll(subComponent);
}
}
});
} }
/** private static void evaluateBindings(List<BindingData> bindings, Map<String, Object> namespace) {
* Recursively walks all nodes in the scene graph starting from the root, for (var binding : bindings) {
* applying the given consumer to each node. try {
* Object source = resolveExpression(binding.getSource(), namespace);
* @param root the starting node Object target = resolveExpression(binding.getTarget(), namespace);
* @param consumer the consumer to apply to each node
*/ if (source instanceof Property && target instanceof Property) {
private void walkNodes(Parent root, java.util.function.Consumer<javafx.scene.Node> consumer) { Property<?> sourceProp = (Property<?>) source;
consumer.accept(root); Property<?> targetProp = (Property<?>) target;
if (root instanceof Pane pane) {
for (javafx.scene.Node child : pane.getChildren()) { Class<?> sourceType = getPropertyType(sourceProp);
if (child instanceof Parent p) { Class<?> targetType = getPropertyType(targetProp);
walkNodes(p, consumer);
} else { boolean bindableForward = targetType.isAssignableFrom(sourceType);
consumer.accept(child); boolean bindableBackward = sourceType.isAssignableFrom(targetType);
switch (binding.getDirection().toLowerCase()) {
case "bidirectional":
if (bindableForward && bindableBackward) {
bindBidirectionalSafe(sourceProp, targetProp);
} else {
log.error("⚠️ Kann bidirektionales Binding nicht durchführen: Typen inkompatibel (%s ⇄ %s)%n", sourceType, targetType);
}
break;
case "unidirectional":
default:
if (bindableForward) {
bindSafe(sourceProp, targetProp);
} else {
log.error("⚠️ Kann unidirektionales Binding nicht durchführen: %s → %s nicht zuweisbar%n", sourceType, targetType);
}
break;
}
} }
} catch (Exception e) {
log.error("Fehler beim Binding: " + binding.getSource() + "" + binding.getTarget(), e);
} }
} }
} }
/** private static Class<?> getPropertyType(Property<?> prop) {
* Extracts custom NFX attributes from a node's properties map. try {
* These attributes are expected to be in the format "nfx:..." and hold string values. Method getter = prop.getClass().getMethod("get");
* return getter.getReturnType();
* @param node the node to inspect } catch (Exception e) {
* @return a map of NFX attribute names to values return Object.class; // Fallback
*/ }
private Map<String, String> extractNfxAttributes(javafx.scene.Node node) { }
Map<String, String> result = new HashMap<>();
node.getProperties().forEach((k, v) -> { private static Object resolveExpression(@NotNull String expr, @NotNull Map<String, Object> namespace) throws Exception {
if (k instanceof String key && key.startsWith("nfx:") && v instanceof String value) { // z.B. "viewModel.username"
result.put(key, value); String[] parts = expr.split("\\.");
Object current = namespace.get(parts[0]);
for (int i = 1; i < parts.length; i++) {
String getter = "get" + Character.toUpperCase(parts[i].charAt(0)) + parts[i].substring(1);
current = current.getClass().getMethod(getter).invoke(current);
}
return current;
}
private static @NotNull List<Node> collectAllNodes(Node root) {
List<Node> nodes = new ArrayList<>();
nodes.add(root);
if (root instanceof Parent parent && !(root instanceof FxmlComponent)) {
for (Node child : parent.getChildrenUnmodifiable()) {
nodes.addAll(collectAllNodes(child));
} }
}); }
return result; return nodes;
} }
/** /**
@ -128,61 +191,4 @@ public class ComponentLoader {
} }
// Additional control types (e.g., CheckBox, ComboBox) can be added here // Additional control types (e.g., CheckBox, ComboBox) can be added here
} }
private String preprocessNfxAttributes(String fxmlPath) {
try {
nfxBindingMap.clear();
var factory = javax.xml.parsers.DocumentBuilderFactory.newInstance();
var builder = factory.newDocumentBuilder();
var doc = builder.parse(getClass().getResourceAsStream(fxmlPath));
var all = doc.getElementsByTagName("*");
int autoId = 0;
for (int i = 0; i < all.getLength(); i++) {
var el = (org.w3c.dom.Element) all.item(i);
Map<String, String> nfxAttrs = new HashMap<>();
var attrs = el.getAttributes();
List<String> toRemove = new ArrayList<>();
for (int j = 0; j < attrs.getLength(); j++) {
var attr = (org.w3c.dom.Attr) attrs.item(j);
if (attr.getName().startsWith("nfx:")) {
nfxAttrs.put(attr.getName(), attr.getValue());
toRemove.add(attr.getName());
}
}
if (!nfxAttrs.isEmpty()) {
String fxid = el.getAttribute("fx:id");
if (fxid == null || fxid.isBlank()) {
fxid = "auto_id_" + (++autoId);
el.setAttribute("fx:id", fxid);
}
nfxBindingMap.put(fxid, nfxAttrs);
toRemove.forEach(el::removeAttribute);
}
}
// Speichere das bereinigte Dokument als temporäre Datei
File tempFile = File.createTempFile("cleaned_fxml", ".fxml");
tempFile.deleteOnExit();
var transformer = javax.xml.transform.TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");
transformer.transform(new javax.xml.transform.dom.DOMSource(doc),
new javax.xml.transform.stream.StreamResult(tempFile));
return tempFile.toURI().toString();
} catch (Exception e) {
throw new RuntimeException("Preprocessing failed for: " + fxmlPath, e);
}
}
private Map<String, String> lookupNfxAttributes(javafx.scene.Node node, FXMLLoader loader) {
String fxid = loader.getNamespace().entrySet().stream()
.filter(e -> e.getValue() == node)
.map(Map.Entry::getKey)
.findFirst().orElse(null);
if (fxid == null) return Map.of();
return nfxBindingMap.getOrDefault(fxid, Map.of());
}
} }

View File

@ -10,15 +10,15 @@ import javafx.scene.layout.Region;
* of {@link BindingData} objects. It extends the {@link Region} class and * of {@link BindingData} objects. It extends the {@link Region} class and
* provides functionality to bind and monitor connections between source * provides functionality to bind and monitor connections between source
* and target properties. * and target properties.
* * <p>
* The primary purpose of this control is to maintain an observable list * The primary purpose of this control is to maintain an observable list
* of bindings, allowing developers to track or adjust the linked properties * of bindings, allowing developers to track or adjust the linked properties
* dynamically. * dynamically.
* * <p>
* The internal list of bindings is implemented as an {@link ObservableList}, * The internal list of bindings is implemented as an {@link ObservableList},
* allowing property change notifications to be easily monitored for UI * allowing property change notifications to be easily monitored for UI
* updates or other reactive behaviors. * updates or other reactive behaviors.
* * <p>
* This class serves as an organizational component and does not provide * This class serves as an organizational component and does not provide
* any user interaction by default. * any user interaction by default.
*/ */
@ -28,17 +28,32 @@ public class Binding extends Region {
* Represents an observable list of {@link BindingData} objects contained within the * Represents an observable list of {@link BindingData} objects contained within the
* {@link Binding} instance. This list is utilized to manage and monitor * {@link Binding} instance. This list is utilized to manage and monitor
* the bindings between source and target properties dynamically. * the bindings between source and target properties dynamically.
* * <p>
* The list is implemented as an {@link ObservableList}, allowing changes in the * The list is implemented as an {@link ObservableList}, allowing changes in the
* collection to be observed and reacted to, such as triggering UI updates or * collection to be observed and reacted to, such as triggering UI updates or
* responding to binding modifications. * responding to binding modifications.
* * <p>
* This field is initialized as an empty list using {@link FXCollections#observableArrayList()}. * This field is initialized as an empty list using {@link FXCollections#observableArrayList()}.
* It is declared as final to ensure its reference cannot be changed, while the * It is declared as final to ensure its reference cannot be changed, while the
* contents of the list remain mutable. * contents of the list remain mutable.
*/ */
private final ObservableList<BindingData> bindings = FXCollections.observableArrayList(); private final ObservableList<BindingData> bindings = FXCollections.observableArrayList();
/**
* Constructs a new instance of the BindingControl class.
* <p>
* This default constructor initializes the BindingControl without
* any pre-configured bindings. The instance will contain an empty
* {@link ObservableList} of {@link BindingData} objects, which can later
* be populated as needed.
* <p>
* The constructor does not perform additional setup or initialization,
* allowing the class to be extended or customized as necessary.
*/
public Binding() {
// Empty, the ComponentLoader is used to work on the bindings.
}
/** /**
* Retrieves the observable list of {@code Binding} objects associated with this control. * Retrieves the observable list of {@code Binding} objects associated with this control.
* The returned list allows monitoring and management of the bindings maintained * The returned list allows monitoring and management of the bindings maintained
@ -49,19 +64,4 @@ public class Binding extends Region {
public ObservableList<BindingData> getBindings() { public ObservableList<BindingData> getBindings() {
return bindings; return bindings;
} }
/**
* Constructs a new instance of the BindingControl class.
*
* This default constructor initializes the BindingControl without
* any pre-configured bindings. The instance will contain an empty
* {@link ObservableList} of {@link BindingData} objects, which can later
* be populated as needed.
*
* The constructor does not perform additional setup or initialization,
* allowing the class to be extended or customized as necessary.
*/
public Binding() {
// Absichtlich leer wird später "ausgewertet"
}
} }

View File

@ -9,27 +9,16 @@ import javafx.scene.Node;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
public class FxmlComponent extends StackPane { public class FxmlComponent extends StackPane {
private final StringProperty fxml = new SimpleStringProperty(); private final StringProperty fxml = new SimpleStringProperty();
private final StringProperty direction = new SimpleStringProperty("unidirectional"); private final StringProperty direction = new SimpleStringProperty("unidirectional");
private final ObjectProperty<Object> data = new SimpleObjectProperty<>(); private final ObjectProperty<Object> data = new SimpleObjectProperty<>();
public StringProperty fxmlProperty() { return fxml; }
public String getFxml() { return fxml.get(); }
public void setFxml(String fxml) { this.fxml.set(fxml); }
public StringProperty directionProperty() { return direction; }
public String getDirection() { return direction.get(); }
public void setDirection(String direction) { this.direction.set(direction); }
public ObjectProperty<Object> dataProperty() { return data; }
public Object getData() { return data.get(); }
public void setData(Object data) { this.data.set(data); }
public FxmlComponent() { public FxmlComponent() {
fxml.addListener((obs, oldVal, newVal) -> load()); fxml.addListener((obs, oldVal, newVal) -> load());
data.addListener((obs, oldVal, newVal) -> injectData()); data.addListener((obs, oldVal, newVal) -> injectData());
@ -37,20 +26,14 @@ public class FxmlComponent extends StackPane {
private void load() { private void load() {
if (getFxml() == null || getFxml().isBlank()) return; if (getFxml() == null || getFxml().isBlank()) return;
try { ComponentLoader loader = new ComponentLoader();
ComponentLoader loader = new ComponentLoader(); Parent content = loader.load(getClass().getResource(getFxml()));
// Option: ControllerFactory verwenden, wenn nötig
Parent content = loader.load(getClass().getResource(getFxml()));
getChildren().setAll(content); getChildren().setAll(content);
Object controller = loader.getController(); Object controller = loader.getController();
if (controller != null && getData() != null) { if (controller != null && getData() != null) {
injectDataToController(controller, getData()); injectDataToController(controller, getData());
}
} catch (IOException e) {
e.printStackTrace();
} }
} }
@ -63,6 +46,22 @@ public class FxmlComponent extends StackPane {
} }
} }
public String getFxml() {
return fxml.get();
}
public void setFxml(String fxml) {
this.fxml.set(fxml);
}
public Object getData() {
return data.get();
}
public void setData(Object data) {
this.data.set(data);
}
private void injectDataToController(Object controller, Object dataObject) { private void injectDataToController(Object controller, Object dataObject) {
// Daten-Objekt per Reflection zuweisen // Daten-Objekt per Reflection zuweisen
// Beispiel: Controller hat `setData(User data)` // Beispiel: Controller hat `setData(User data)`
@ -86,5 +85,25 @@ public class FxmlComponent extends StackPane {
} }
return null; return null;
} }
public StringProperty fxmlProperty() {
return fxml;
}
public StringProperty directionProperty() {
return direction;
}
public String getDirection() {
return direction.get();
}
public void setDirection(String direction) {
this.direction.set(direction);
}
public ObjectProperty<Object> dataProperty() {
return data;
}
} }

View File

@ -26,6 +26,7 @@ public class BindingData {
* within the JavaFX property system. * within the JavaFX property system.
*/ */
private StringProperty direction = new SimpleStringProperty("unidirectional"); private StringProperty direction = new SimpleStringProperty("unidirectional");
/** /**
* Represents the source of the binding. This property holds a string value * Represents the source of the binding. This property holds a string value
* that specifies the originating object or identifier in the binding connection. * that specifies the originating object or identifier in the binding connection.