diff --git a/core/src/main/java/de/neitzel/core/config/Configuration.java b/core/src/main/java/de/neitzel/core/config/Configuration.java
index 94d9f3f..010b8e3 100644
--- a/core/src/main/java/de/neitzel/core/config/Configuration.java
+++ b/core/src/main/java/de/neitzel/core/config/Configuration.java
@@ -1,6 +1,6 @@
package de.neitzel.core.config;
-import de.neitzel.core.util.FileUtils;
+import de.neitzel.core.io.FileUtils;
import de.neitzel.core.util.Strings;
import lombok.extern.slf4j.Slf4j;
@@ -28,7 +28,7 @@ public class Configuration {
* A {@link Properties} object that stores a set of key-value pairs.
* This variable can be used to manage configuration settings or other
* collections of properties within the application.
- *
+ *
* It provides methods to load, retrieve, and modify properties
* 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,
* 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
* @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");
+ 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 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
*/
@@ -82,34 +82,22 @@ public class Configuration {
* 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 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.
+ * 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 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
*/
@@ -118,16 +106,28 @@ public class Configuration {
return Strings.expandEnvironmentVariables(result);
}
+ /**
+ * Retrieves the value of a string property associated with the specified key,
+ * removes any surrounding quotes from the value, and returns the resultant string.
+ *
+ * @param key the key associated with the desired property
+ * @param defaultValue the default value to return if the property is not found or is null
+ * @return the string property without surrounding quotes, or the defaultValue if the property is not found
+ */
+ protected String getStringPropertyWithoutQuotes(final String key, final String defaultValue) {
+ return Strings.removeQuotes(getStringProperty(key, defaultValue));
+ }
+
/**
* Retrieves the integer value for the specified property key. If the key does
* not exist in the properties or the value is null/empty, the provided default
* 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
* 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
+ * 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;
@@ -139,14 +139,14 @@ public class Configuration {
* 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 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);
+ properties.setProperty(key, "" + value);
}
}
@@ -154,11 +154,11 @@ public class Configuration {
* 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 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
+ * otherwise the defaultValue
*/
protected LocalDate getLocalDateProperty(final String key, final LocalDate defaultValue, final String formatString) {
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.
* 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 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) {
@@ -188,7 +188,7 @@ public class Configuration {
* 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 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) {
@@ -211,9 +211,9 @@ public class Configuration {
* 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
+ * @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);
@@ -251,7 +251,7 @@ public class Configuration {
* @param config the Configuration object whose properties will be merged into this instance
*/
public void merge(final Configuration config) {
- for(Map.Entry entry: config.properties.entrySet()) {
+ for (Map.Entry entry : config.properties.entrySet()) {
properties.put(entry.getKey(), entry.getValue());
}
}
@@ -261,7 +261,7 @@ public class Configuration {
*
* @param key the key to be removed from the properties map
*/
- public void remove(final String key){
+ public void remove(final String key) {
if (properties.containsKey(key)) properties.remove(key);
}
}
diff --git a/core/src/main/java/de/neitzel/core/io/ConvertedEncodingFileReader.java b/core/src/main/java/de/neitzel/core/io/ConvertedEncodingFileReader.java
index aa37570..ce6205c 100644
--- a/core/src/main/java/de/neitzel/core/io/ConvertedEncodingFileReader.java
+++ b/core/src/main/java/de/neitzel/core/io/ConvertedEncodingFileReader.java
@@ -1,6 +1,5 @@
package de.neitzel.core.io;
-import de.neitzel.core.util.FileUtils;
import lombok.extern.slf4j.Slf4j;
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,
* 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.
*/
@@ -25,47 +24,19 @@ public class ConvertedEncodingFileReader extends InputStreamReader {
* This encoding is primarily used to determine whether a file needs conversion
* to the target format or can be read directly in its existing format.
* The default value is set to "ISO-8859-15".
- *
+ *
* 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 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
*/
@@ -73,13 +44,27 @@ public class ConvertedEncodingFileReader extends InputStreamReader {
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.
* 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 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
@@ -100,4 +85,19 @@ public class ConvertedEncodingFileReader extends InputStreamReader {
return new FileInputStream(tempFile);
}
}
+
+ /**
+ * Sets the encoding that will be used to check the file encoding for compatibility.
+ * Throws an exception if the specified encoding is not valid or supported.
+ *
+ * @param encoding the name of the character encoding to set as the check encoding;
+ * it must be a valid and supported Charset.
+ * @throws IllegalCharsetNameException if the specified encoding is not valid or supported.
+ */
+ private static void setCheckEncoding(final String encoding) {
+ if (Charset.forName(encoding) != null)
+ throw new IllegalCharsetNameException("Encoding " + encoding + " is not supported!");
+
+ checkEncoding = encoding;
+ }
}
diff --git a/core/src/main/java/de/neitzel/core/util/FileUtils.java b/core/src/main/java/de/neitzel/core/io/FileUtils.java
similarity index 78%
rename from core/src/main/java/de/neitzel/core/util/FileUtils.java
rename to core/src/main/java/de/neitzel/core/io/FileUtils.java
index 6bb3219..5d99475 100644
--- a/core/src/main/java/de/neitzel/core/util/FileUtils.java
+++ b/core/src/main/java/de/neitzel/core/io/FileUtils.java
@@ -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 java.io.*;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.stream.Collectors;
/**
- * Utility class for handling file operations, such as encoding checks, content reading/writing,
- * path manipulations, and file conversions.
+ * A utility class for file-related operations. This class provides functionalities
+ * for handling files and directories in an efficient manner.
*/
@Slf4j
public class FileUtils {
- /**
- * 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
@@ -37,14 +27,14 @@ public class FileUtils {
* - 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,
@@ -54,17 +44,40 @@ public class FileUtils {
/**
* 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;
+ /**
+ * 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.
*
- * @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.
* @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.
@@ -82,7 +95,7 @@ public class FileUtils {
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
+ || (ArrayUtils.contains(buffer, (char) 0x00C3))) { // Part of UTF-8 Characters 0xC3 0xZZ
return true;
}
@@ -97,7 +110,7 @@ public class FileUtils {
/**
* 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).
*
@@ -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.
- *
+ *
* 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 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 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.
*/
@@ -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.
* 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 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.
@@ -194,25 +183,14 @@ public class FileUtils {
return createUniversalFileReader(new File(filename), expectedFormat);
}
- /**
- * Creates a universal file reader for the specified file using the default encoding and configuration.
- *
- * @param file The file to be read. Must not be null.
- * @return An InputStreamReader configured to read the specified file.
- * @throws IOException If an I/O error occurs while creating the reader.
- */
- public static InputStreamReader createUniversalFileReader(final File file) throws IOException {
- return createUniversalFileReader(file, DEFAULT_CHECK_ENCODING, true);
- }
-
/**
* Creates a universal file reader for the specified file with an expected format.
* This method wraps the given file in an InputStreamReader for consistent character stream manipulation.
*
- * @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.
* @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.
*/
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
* 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 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.
* @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);
if (skipBOM) {
int BOM = result.read();
- if (BOM != 0xFEFF) log.error ("Skipping BOM but value not 0xFEFF!");
+ if (BOM != 0xFEFF) log.error("Skipping BOM but value not 0xFEFF!");
}
return result;
}
/**
* Retrieves the parent directory of the given file or directory path.
- *
+ *
* If the given path does not have a parent directory, it defaults to returning the
* 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.
*
- * @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.
* @throws IOException If an I/O error occurs during writing to the file.
*/
@@ -294,4 +296,60 @@ public class FileUtils {
writer.write(content);
}
}
+
+ /**
+ * Deletes the specified file or directory. If the target is a directory, all its contents,
+ * including subdirectories and files, will be deleted recursively.
+ * If the target file or directory does not exist, the method immediately returns {@code true}.
+ *
+ * @param targetFile the {@code Path} of the file or directory to be deleted
+ * @return {@code true} if the target file or directory was successfully deleted,
+ * or if it does not exist; {@code false} if an error occurred during deletion
+ */
+ public static boolean remove(Path targetFile) {
+ if (!Files.exists(targetFile)) {
+ return true;
+ }
+
+ if (Files.isDirectory(targetFile)) {
+ return removeDirectory(targetFile);
+ }
+
+ return targetFile.toFile().delete();
+ }
+
+ /**
+ * Deletes the specified directory and all its contents, including subdirectories and files.
+ * The method performs a recursive deletion, starting with the deepest entries in the directory tree.
+ * If the directory does not exist, the method immediately returns true.
+ *
+ * @param targetDir the {@code Path} of the directory to be deleted
+ * @return {@code true} if the directory and all its contents were successfully deleted
+ * or if the directory does not exist; {@code false} if an error occurred during deletion
+ */
+ public static boolean removeDirectory(Path targetDir) {
+ if (!Files.exists(targetDir)) {
+ return true;
+ }
+
+ if (!Files.isDirectory(targetDir)) {
+ return false;
+ }
+
+ try {
+ Files.walk(targetDir)
+ .sorted((a, b) -> b.compareTo(a))
+ .forEach(path -> {
+ try {
+ Files.deleteIfExists(path);
+ } catch (Exception ignored) {
+ }
+ });
+ } catch (IOException ignored) {
+ return false;
+ }
+
+ return true;
+ }
+
}
diff --git a/core/src/main/java/de/neitzel/core/io/TempDirectory.java b/core/src/main/java/de/neitzel/core/io/TempDirectory.java
new file mode 100644
index 0000000..f7ea1ed
--- /dev/null
+++ b/core/src/main/java/de/neitzel/core/io/TempDirectory.java
@@ -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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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 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);
+ }
+ });
+ }
+ }
+ }
+}
diff --git a/fx/ideas.md b/fx/ideas.md
index 8c180d5..8fa6cf0 100644
--- a/fx/ideas.md
+++ b/fx/ideas.md
@@ -1,15 +1,99 @@
+# TODO
+
+- SimpleListProperty auch nutzen bei Collections!
+
# Ideen
# Bindings
+
Bindngs kommen über ein spezielles Binding Control, das dann notwendige Bindings beinhaltet.
-Da kann dann auch eine eigene Logik zur Erkennung des Bindings erfolgen oder zusätziche Informationen bezüglich notwendiger Elemente in dem ViewModel
+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 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, Property>, Format)
+bindBidirectional(Property, Property, StringConverter)
+bindBidirectional(Property, Property)
+
+## Unidirektional mit Bindings.createStringBinding()
+
+```Java
+DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault());
+
+textField.
+
+textProperty().
+
+bind(Bindings.createStringBinding(
+ () ->{
+
+Instant instant = instantProperty.get();
+ return instant ==null?"":formatter.
+
+format(instant);
+ },
+instantProperty
+));
+```
+
+Bindings:
+
+- BooleanBinding
+- DoubleBinding
+- FloatBinding
+- IntegerBinding
+- LongBinding
+- StringBinding
+- ObjectBinding
+- NumberBinding
+
+Bindings haben Methoden, die dann weitere Bindings erzeugen.
+==> Parameter der Methode: Property, Observable und einer Methode, die ein Binding erstellt und das ViewModel>, das
+dann genutzt werden kann, um weitere Properties zu holen ==> Erzeugung von neuen Properties.
+
+Diese BindingCreators haben Namen, PropertyTyp und ein BindingType.
# FXMLComponent
+
Dient dem Laden einer Komponente und bekommt dazu das fxml, die Daten und ggf. auch eine ModelView.
# ModelView generieren
+
Damit eine ModelView nicht ständig manuell generiert werden muss, ist hier ggf. etwas zu generieren?
Ggf. eine eigenes Beschreibungssprache?
+# Aufbau einer Validierung
+- gehört in das ViewModel
+-
\ No newline at end of file
diff --git a/fx/src/main/java/de/neitzel/fx/component/ComponentLoader.java b/fx/src/main/java/de/neitzel/fx/component/ComponentLoader.java
index 84f8bc2..9859156 100644
--- a/fx/src/main/java/de/neitzel/fx/component/ComponentLoader.java
+++ b/fx/src/main/java/de/neitzel/fx/component/ComponentLoader.java
@@ -1,12 +1,20 @@
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.value.ObservableValue;
import javafx.fxml.FXMLLoader;
+import javafx.scene.Node;
import javafx.scene.Parent;
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.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
@@ -16,13 +24,35 @@ import java.util.Map;
/**
* ComponentLoader is responsible for loading JavaFX FXML components and binding
* them to automatically generated ViewModels based on simple POJO models.
- *
- * It parses custom NFX attributes in FXML to bind UI elements to properties in the ViewModel,
- * and supports recursive loading of subcomponents.
*/
+@Slf4j
public class ComponentLoader {
private Map> 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
* based on the given POJO model.
@@ -32,83 +62,116 @@ public class ComponentLoader {
* @return the root JavaFX node loaded from FXML
*/
public Parent load(Object model, String fxmlPath) {
+ return load(model, getClass().getResource(fxmlPath));
+ }
+
+ public static T load(URL fxmlUrl, Object controller, String nothing) throws IOException {
+ FXMLLoader loader = new FXMLLoader(fxmlUrl);
+ loader.setController(controller);
+ T root = loader.load();
+
+ Map namespace = loader.getNamespace();
+
+ // Nach allen BindingControls suchen:
+ List 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 void bindBidirectionalSafe(@NotNull Property source, Property> target) {
try {
- AutoViewModel> viewModel = new AutoViewModel<>(model);
- String cleanedUri = preprocessNfxAttributes(fxmlPath);
- FXMLLoader loader = new FXMLLoader(new URL(cleanedUri));
- loader.setControllerFactory(type -> new ComponentController(viewModel));
- Parent root = loader.load();
- processNfxBindings(root, viewModel, loader);
- return root;
- } catch (IOException e) {
- throw new RuntimeException("unable to load fxml: " + fxmlPath, e);
+ Property targetCasted = (Property) target;
+ source.bindBidirectional(targetCasted);
+ } catch (ClassCastException e) {
+ log.error("⚠️ Typkonflikt beim Binding: %s ⇄ %s%n", source.getClass(), target.getClass(), e);
}
}
- /**
- * Processes all UI elements for NFX binding attributes and applies
- * the appropriate bindings to the ViewModel.
- *
- * @param root the root node of the loaded FXML hierarchy
- * @param viewModel the generated ViewModel to bind against
- */
- private void processNfxBindings(Parent root, AutoViewModel> viewModel, FXMLLoader loader) {
- walkNodes(root, node -> {
- var nfx = lookupNfxAttributes(node, loader);
- String target = nfx.get("nfx:target");
- String direction = nfx.get("nfx:direction");
- String source = nfx.get("nfx:source");
-
- if (target != null && direction != null) {
- Property> vmProp = viewModel.getProperty(target);
- bindNodeToProperty(node, vmProp, direction);
- }
-
- if (source != null) {
- Object subModel = ((Property>) viewModel.getProperty(target)).getValue();
- Parent subComponent = load(subModel, source);
- if (node instanceof Pane pane) {
- pane.getChildren().setAll(subComponent);
- }
- }
- });
+ private static void bindSafe(@NotNull Property source, Property> target) {
+ try {
+ Property targetCasted = (Property) target;
+ source.bind(targetCasted);
+ } catch (ClassCastException e) {
+ log.error("⚠️ Typkonflikt beim Binding: %s ⇄ %s%n", source.getClass(), target.getClass(), e);
+ }
}
- /**
- * Recursively walks all nodes in the scene graph starting from the root,
- * applying the given consumer to each node.
- *
- * @param root the starting node
- * @param consumer the consumer to apply to each node
- */
- private void walkNodes(Parent root, java.util.function.Consumer consumer) {
- consumer.accept(root);
- if (root instanceof Pane pane) {
- for (javafx.scene.Node child : pane.getChildren()) {
- if (child instanceof Parent p) {
- walkNodes(p, consumer);
- } else {
- consumer.accept(child);
+ private static void evaluateBindings(List bindings, Map namespace) {
+ for (var binding : bindings) {
+ try {
+ Object source = resolveExpression(binding.getSource(), namespace);
+ Object target = resolveExpression(binding.getTarget(), namespace);
+
+ if (source instanceof Property && target instanceof Property) {
+ Property> sourceProp = (Property>) source;
+ Property> targetProp = (Property>) target;
+
+ Class> sourceType = getPropertyType(sourceProp);
+ Class> targetType = getPropertyType(targetProp);
+
+ boolean bindableForward = targetType.isAssignableFrom(sourceType);
+ boolean bindableBackward = sourceType.isAssignableFrom(targetType);
+
+ switch (binding.getDirection().toLowerCase()) {
+ case "bidirectional":
+ if (bindableForward && bindableBackward) {
+ bindBidirectionalSafe(sourceProp, targetProp);
+ } else {
+ log.error("⚠️ Kann bidirektionales Binding nicht durchführen: Typen inkompatibel (%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);
}
}
}
- /**
- * Extracts custom NFX attributes from a node's properties map.
- * These attributes are expected to be in the format "nfx:..." and hold string values.
- *
- * @param node the node to inspect
- * @return a map of NFX attribute names to values
- */
- private Map extractNfxAttributes(javafx.scene.Node node) {
- Map result = new HashMap<>();
- node.getProperties().forEach((k, v) -> {
- if (k instanceof String key && key.startsWith("nfx:") && v instanceof String value) {
- result.put(key, value);
+ private static Class> getPropertyType(Property> prop) {
+ try {
+ Method getter = prop.getClass().getMethod("get");
+ return getter.getReturnType();
+ } catch (Exception e) {
+ return Object.class; // Fallback
+ }
+ }
+
+ private static Object resolveExpression(@NotNull String expr, @NotNull Map namespace) throws Exception {
+ // z.B. "viewModel.username"
+ String[] parts = expr.split("\\.");
+ Object current = namespace.get(parts[0]);
+ for (int i = 1; i < parts.length; i++) {
+ String getter = "get" + Character.toUpperCase(parts[i].charAt(0)) + parts[i].substring(1);
+ current = current.getClass().getMethod(getter).invoke(current);
+ }
+ return current;
+ }
+
+ private static @NotNull List collectAllNodes(Node root) {
+ List 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
}
-
- 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 nfxAttrs = new HashMap<>();
- var attrs = el.getAttributes();
-
- List 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 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());
- }
}
diff --git a/fx/src/main/java/de/neitzel/fx/component/controls/Binding.java b/fx/src/main/java/de/neitzel/fx/component/controls/Binding.java
index f4dc831..ffafadd 100644
--- a/fx/src/main/java/de/neitzel/fx/component/controls/Binding.java
+++ b/fx/src/main/java/de/neitzel/fx/component/controls/Binding.java
@@ -10,15 +10,15 @@ import javafx.scene.layout.Region;
* of {@link BindingData} objects. It extends the {@link Region} class and
* provides functionality to bind and monitor connections between source
* and target properties.
- *
+ *
* The primary purpose of this control is to maintain an observable list
* of bindings, allowing developers to track or adjust the linked properties
* dynamically.
- *
+ *
* The internal list of bindings is implemented as an {@link ObservableList},
* allowing property change notifications to be easily monitored for UI
* updates or other reactive behaviors.
- *
+ *
* This class serves as an organizational component and does not provide
* 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
* {@link Binding} instance. This list is utilized to manage and monitor
* the bindings between source and target properties dynamically.
- *
+ *
* The list is implemented as an {@link ObservableList}, allowing changes in the
* collection to be observed and reacted to, such as triggering UI updates or
* responding to binding modifications.
- *
+ *
* This field is initialized as an empty list using {@link FXCollections#observableArrayList()}.
* It is declared as final to ensure its reference cannot be changed, while the
* contents of the list remain mutable.
*/
private final ObservableList bindings = FXCollections.observableArrayList();
+ /**
+ * 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() {
+ // Empty, the ComponentLoader is used to work on the bindings.
+ }
+
/**
* Retrieves the observable list of {@code Binding} objects associated with this control.
* The returned list allows monitoring and management of the bindings maintained
@@ -49,19 +64,4 @@ public class Binding extends Region {
public ObservableList getBindings() {
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"
- }
}
\ No newline at end of file
diff --git a/fx/src/main/java/de/neitzel/fx/component/controls/FxmlComponent.java b/fx/src/main/java/de/neitzel/fx/component/controls/FxmlComponent.java
index a871db4..635910f 100644
--- a/fx/src/main/java/de/neitzel/fx/component/controls/FxmlComponent.java
+++ b/fx/src/main/java/de/neitzel/fx/component/controls/FxmlComponent.java
@@ -9,27 +9,16 @@ import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.layout.StackPane;
-import java.io.IOException;
import java.util.Arrays;
public class FxmlComponent extends StackPane {
private final StringProperty fxml = new SimpleStringProperty();
+
private final StringProperty direction = new SimpleStringProperty("unidirectional");
+
private final ObjectProperty 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 dataProperty() { return data; }
- public Object getData() { return data.get(); }
- public void setData(Object data) { this.data.set(data); }
-
public FxmlComponent() {
fxml.addListener((obs, oldVal, newVal) -> load());
data.addListener((obs, oldVal, newVal) -> injectData());
@@ -37,20 +26,14 @@ public class FxmlComponent extends StackPane {
private void load() {
if (getFxml() == null || getFxml().isBlank()) return;
- try {
- ComponentLoader loader = new ComponentLoader();
- // Option: ControllerFactory verwenden, wenn nötig
- Parent content = loader.load(getClass().getResource(getFxml()));
+ ComponentLoader loader = new ComponentLoader();
+ Parent content = loader.load(getClass().getResource(getFxml()));
- getChildren().setAll(content);
+ getChildren().setAll(content);
- Object controller = loader.getController();
- if (controller != null && getData() != null) {
- injectDataToController(controller, getData());
- }
-
- } catch (IOException e) {
- e.printStackTrace();
+ Object controller = loader.getController();
+ if (controller != null && getData() != null) {
+ injectDataToController(controller, getData());
}
}
@@ -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) {
// Daten-Objekt per Reflection zuweisen
// Beispiel: Controller hat `setData(User data)`
@@ -86,5 +85,25 @@ public class FxmlComponent extends StackPane {
}
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 dataProperty() {
+ return data;
+ }
}
diff --git a/fx/src/main/java/de/neitzel/fx/component/model/BindingData.java b/fx/src/main/java/de/neitzel/fx/component/model/BindingData.java
index 6c7ba64..8b65618 100644
--- a/fx/src/main/java/de/neitzel/fx/component/model/BindingData.java
+++ b/fx/src/main/java/de/neitzel/fx/component/model/BindingData.java
@@ -26,6 +26,7 @@ public class BindingData {
* within the JavaFX property system.
*/
private StringProperty direction = new SimpleStringProperty("unidirectional");
+
/**
* Represents the source of the binding. This property holds a string value
* that specifies the originating object or identifier in the binding connection.