Added FileUtils / TempDirectory from EmailTool.
Moved FileUtils to io package.
This commit is contained in:
parent
2b60038774
commit
5aa75be6ba
@ -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.
|
||||||
*/
|
*/
|
||||||
@ -92,18 +92,6 @@ 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 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
|
||||||
@ -118,6 +106,18 @@ 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
|
||||||
|
|||||||
@ -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,7 +24,7 @@ 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.
|
||||||
@ -33,17 +32,16 @@ public class ConvertedEncodingFileReader extends InputStreamReader {
|
|||||||
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.
|
* Constructs a ConvertedEncodingFileReader for reading a file with encoding conversion support.
|
||||||
* Throws an exception if the specified encoding is not valid or supported.
|
* This constructor takes the file path as a string and ensures the file's encoding is either
|
||||||
|
* converted to the specified target format or read directly if it matches the target format.
|
||||||
*
|
*
|
||||||
* @param encoding the name of the character encoding to set as the check encoding;
|
* @param filename the path to the file to be read
|
||||||
* it must be a valid and supported Charset.
|
* @param targetFormat the target encoding format to use for reading the file
|
||||||
* @throws IllegalCharsetNameException if the specified encoding is not valid or supported.
|
* @throws IOException if an I/O error occurs while accessing or reading the specified file
|
||||||
*/
|
*/
|
||||||
private static void setCheckEncoding(final String encoding) {
|
public ConvertedEncodingFileReader(final String filename, final String targetFormat) throws IOException {
|
||||||
if (Charset.forName(encoding) != null) throw new IllegalCharsetNameException("Encoding " + encoding + " is not supported!");
|
this(new File(filename), targetFormat);
|
||||||
|
|
||||||
checkEncoding = encoding;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,19 +58,6 @@ public class ConvertedEncodingFileReader extends InputStreamReader {
|
|||||||
super(createTargetFormatInputFileStream(file, targetFormat), targetFormat);
|
super(createTargetFormatInputFileStream(file, targetFormat), targetFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a ConvertedEncodingFileReader for reading a file with encoding conversion support.
|
|
||||||
* This constructor takes the file path as a string and ensures the file's encoding is either
|
|
||||||
* converted to the specified target format or read directly if it matches the target format.
|
|
||||||
*
|
|
||||||
* @param filename the path to the file to be read
|
|
||||||
* @param targetFormat the target encoding format to use for reading the file
|
|
||||||
* @throws IOException if an I/O error occurs while accessing or reading the specified file
|
|
||||||
*/
|
|
||||||
public ConvertedEncodingFileReader(final String filename, final String targetFormat) throws IOException {
|
|
||||||
this(new File(filename), targetFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an input file stream for a given file, converting its encoding if necessary.
|
* 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.
|
||||||
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,56 @@
|
|||||||
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 {
|
||||||
|
/**
|
||||||
|
* Defines a standardized timestamp format for debugging purposes, specifically used for naming
|
||||||
|
* or identifying files with precise timestamps. The format is "yyyy-MM-dd_HH_mm_ss_SSS", which
|
||||||
|
* includes:
|
||||||
|
* - Year in four digits (yyyy)
|
||||||
|
* - Month in two digits (MM)
|
||||||
|
* - Day of the month in two digits (dd)
|
||||||
|
* - Hour in 24-hour format with two digits (HH)
|
||||||
|
* - Minutes in two digits (mm)
|
||||||
|
* - Seconds in two digits (ss)
|
||||||
|
* - Milliseconds in three digits (SSS)
|
||||||
|
* <p>
|
||||||
|
* This ensures timestamps are sortable and easily identifiable.
|
||||||
|
*/
|
||||||
|
public static final SimpleDateFormat DEBUG_FILE_TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH_mm_ss_SSS");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default encoding used for string checks and validations in the application.
|
||||||
|
* <p>
|
||||||
|
* This constant represents the `ISO-8859-15` encoding, which is a standardized
|
||||||
|
* character set encoding, commonly used in contexts where backward compatibility
|
||||||
|
* with `ISO-8859-1` is required, but with support for certain additional characters,
|
||||||
|
* such as the euro currency symbol (€).
|
||||||
|
*/
|
||||||
|
public static final String DEFAULT_CHECK_ENCODING = "ISO-8859-15";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the default buffer size used for data processing operations.
|
||||||
|
* <p>
|
||||||
|
* This constant represents the size of the buffer in bytes, set to 1024,
|
||||||
|
* and is typically utilized in input/output operations to optimize performance
|
||||||
|
* by reducing the number of read/write calls.
|
||||||
|
*/
|
||||||
|
public static final int BUFFER_SIZE = 1024;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private constructor to prevent instantiation of the utility class.
|
* Private constructor to prevent instantiation of the utility class.
|
||||||
* This utility class is not meant to be instantiated, as it only provides
|
* This utility class is not meant to be instantiated, as it only provides
|
||||||
@ -27,39 +64,15 @@ public class FileUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a standardized timestamp format for debugging purposes, specifically used for naming
|
* Determines if the content of the given file is encoded in UTF-8.
|
||||||
* or identifying files with precise timestamps. The format is "yyyy-MM-dd_HH_mm_ss_SSS", which
|
|
||||||
* includes:
|
|
||||||
* - Year in four digits (yyyy)
|
|
||||||
* - Month in two digits (MM)
|
|
||||||
* - Day of the month in two digits (dd)
|
|
||||||
* - Hour in 24-hour format with two digits (HH)
|
|
||||||
* - Minutes in two digits (mm)
|
|
||||||
* - Seconds in two digits (ss)
|
|
||||||
* - Milliseconds in three digits (SSS)
|
|
||||||
*
|
*
|
||||||
* This ensures timestamps are sortable and easily identifiable.
|
* @param file The file to check for UTF-8 encoding. Must not be null.
|
||||||
|
* @return true if the file content is in UTF-8 encoding; false otherwise.
|
||||||
|
* @throws IOException If an I/O error occurs while reading the file.
|
||||||
*/
|
*/
|
||||||
public static final SimpleDateFormat DEBUG_FILE_TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH_mm_ss_SSS");
|
public static boolean isUTF8(final File file) throws IOException {
|
||||||
|
return isUTF8(file, DEFAULT_CHECK_ENCODING);
|
||||||
/**
|
}
|
||||||
* Default encoding used for string checks and validations in the application.
|
|
||||||
*
|
|
||||||
* This constant represents the `ISO-8859-15` encoding, which is a standardized
|
|
||||||
* character set encoding, commonly used in contexts where backward compatibility
|
|
||||||
* with `ISO-8859-1` is required, but with support for certain additional characters,
|
|
||||||
* such as the euro currency symbol (€).
|
|
||||||
*/
|
|
||||||
public static final String DEFAULT_CHECK_ENCODING = "ISO-8859-15";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies the default buffer size used for data processing operations.
|
|
||||||
*
|
|
||||||
* This constant represents the size of the buffer in bytes, set to 1024,
|
|
||||||
* and is typically utilized in input/output operations to optimize performance
|
|
||||||
* by reducing the number of read/write calls.
|
|
||||||
*/
|
|
||||||
public static final int BUFFER_SIZE = 1024;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether the given file is encoded in UTF-8.
|
* Determines whether the given file is encoded in UTF-8.
|
||||||
@ -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,20 +127,9 @@ public class FileUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines if the content of the given file is encoded in UTF-8.
|
|
||||||
*
|
|
||||||
* @param file The file to check for UTF-8 encoding. Must not be null.
|
|
||||||
* @return true if the file content is in UTF-8 encoding; false otherwise.
|
|
||||||
* @throws IOException If an I/O error occurs while reading the file.
|
|
||||||
*/
|
|
||||||
public static boolean isUTF8(final File file) throws IOException {
|
|
||||||
return isUTF8(file, DEFAULT_CHECK_ENCODING);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the content of a text file from one character encoding format to another.
|
* 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.
|
||||||
*
|
*
|
||||||
@ -167,19 +169,6 @@ public class FileUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a universal file reader for the specified file name.
|
|
||||||
* This method initializes and returns an InputStreamReader to read
|
|
||||||
* the content of the given file.
|
|
||||||
*
|
|
||||||
* @param filename The name or path of the file to be read.
|
|
||||||
* @return An InputStreamReader for reading the specified file.
|
|
||||||
* @throws IOException If an I/O error occurs while creating the file reader.
|
|
||||||
*/
|
|
||||||
public static InputStreamReader createUniversalFileReader(final String filename) throws IOException {
|
|
||||||
return createUniversalFileReader(new File(filename));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a universal file reader for the specified file and format.
|
* 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,
|
||||||
@ -194,17 +183,6 @@ 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.
|
||||||
@ -246,7 +224,7 @@ public class FileUtils {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 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,6 +260,30 @@ public class FileUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a universal file reader for the specified file name.
|
||||||
|
* This method initializes and returns an InputStreamReader to read
|
||||||
|
* the content of the given file.
|
||||||
|
*
|
||||||
|
* @param filename The name or path of the file to be read.
|
||||||
|
* @return An InputStreamReader for reading the specified file.
|
||||||
|
* @throws IOException If an I/O error occurs while creating the file reader.
|
||||||
|
*/
|
||||||
|
public static InputStreamReader createUniversalFileReader(final String filename) throws IOException {
|
||||||
|
return createUniversalFileReader(new File(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a universal file reader for the specified file using the default encoding and configuration.
|
||||||
|
*
|
||||||
|
* @param file The file to be read. Must not be null.
|
||||||
|
* @return An InputStreamReader configured to read the specified file.
|
||||||
|
* @throws IOException If an I/O error occurs while creating the reader.
|
||||||
|
*/
|
||||||
|
public static InputStreamReader createUniversalFileReader(final File file) throws IOException {
|
||||||
|
return createUniversalFileReader(file, DEFAULT_CHECK_ENCODING, true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the given content to the specified file path. If the file already exists, it will be overwritten.
|
* Writes the given content to the specified file path. If the file already exists, it will be overwritten.
|
||||||
*
|
*
|
||||||
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
76
core/src/main/java/de/neitzel/core/io/TempDirectory.java
Normal file
76
core/src/main/java/de/neitzel/core/io/TempDirectory.java
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package de.neitzel.core.io;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility class for creating and managing a temporary directory.
|
||||||
|
* Instances of this class create a unique temporary directory on the filesystem
|
||||||
|
* that can safely be used during the runtime of a program.
|
||||||
|
* <p>
|
||||||
|
* The directory is automatically cleaned up when the instance is closed.
|
||||||
|
*/
|
||||||
|
public class TempDirectory implements AutoCloseable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The filesystem path representing the temporary directory managed by this instance.
|
||||||
|
* This path is initialized when the TempDirectory object is created and points
|
||||||
|
* to a unique, newly created directory.
|
||||||
|
* <p>
|
||||||
|
* The directory can be used to safely store temporary runtime files. It is automatically
|
||||||
|
* deleted along with its content when the associated TempDirectory object is closed.
|
||||||
|
*/
|
||||||
|
private final Path directory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a temporary directory with a unique name on the filesystem.
|
||||||
|
* The directory will have a prefix specified by the user and is intended
|
||||||
|
* to serve as a temporary workspace during the runtime of the program.
|
||||||
|
*
|
||||||
|
* @param prefix the prefix to be used for the name of the temporary directory
|
||||||
|
* @throws IOException if an I/O error occurs when creating the directory
|
||||||
|
*/
|
||||||
|
public TempDirectory(String prefix) throws IOException {
|
||||||
|
this.directory = Files.createTempDirectory(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the path of the temporary directory associated with this instance.
|
||||||
|
*
|
||||||
|
* @return the {@code Path} of the temporary directory
|
||||||
|
*/
|
||||||
|
public Path getDirectory() {
|
||||||
|
return directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the temporary directory by cleaning up its contents and deleting the directory itself.
|
||||||
|
* <p>
|
||||||
|
* This method ensures that all files and subdirectories within the temporary directory are
|
||||||
|
* deleted in a reverse order, starting from the deepest leaf in the directory tree. If
|
||||||
|
* the directory does not exist, the method will not perform any actions.
|
||||||
|
* <p>
|
||||||
|
* If an error occurs while deleting any file or directory, a RuntimeException is thrown
|
||||||
|
* with the details of the failure.
|
||||||
|
*
|
||||||
|
* @throws IOException if an I/O error occurs while accessing the directory or its contents.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (Files.exists(directory)) {
|
||||||
|
try (Stream<Path> walk = Files.walk(directory)) {
|
||||||
|
walk.sorted(Comparator.reverseOrder())
|
||||||
|
.forEach(path -> {
|
||||||
|
try {
|
||||||
|
Files.delete(path);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to delete: " + path, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
86
fx/ideas.md
86
fx/ideas.md
@ -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
|
||||||
|
-
|
||||||
@ -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) {
|
||||||
try {
|
return load(model, getClass().getResource(fxmlPath));
|
||||||
AutoViewModel<?> viewModel = new AutoViewModel<>(model);
|
}
|
||||||
String cleanedUri = preprocessNfxAttributes(fxmlPath);
|
|
||||||
FXMLLoader loader = new FXMLLoader(new URL(cleanedUri));
|
public static <T extends Parent> T load(URL fxmlUrl, Object controller, String nothing) throws IOException {
|
||||||
loader.setControllerFactory(type -> new ComponentController(viewModel));
|
FXMLLoader loader = new FXMLLoader(fxmlUrl);
|
||||||
Parent root = loader.load();
|
loader.setController(controller);
|
||||||
processNfxBindings(root, viewModel, loader);
|
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;
|
return root;
|
||||||
} catch (IOException e) {
|
}
|
||||||
throw new RuntimeException("unable to load fxml: " + fxmlPath, e);
|
|
||||||
|
private static <T> void bindBidirectionalSafe(@NotNull Property<T> source, Property<?> target) {
|
||||||
|
try {
|
||||||
|
Property<T> targetCasted = (Property<T>) target;
|
||||||
|
source.bindBidirectional(targetCasted);
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
log.error("⚠️ Typkonflikt beim Binding: %s ⇄ %s%n", source.getClass(), target.getClass(), 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) {
|
private static void evaluateBindings(List<BindingData> bindings, Map<String, Object> namespace) {
|
||||||
Object subModel = ((Property<?>) viewModel.getProperty(target)).getValue();
|
for (var binding : bindings) {
|
||||||
Parent subComponent = load(subModel, source);
|
try {
|
||||||
if (node instanceof Pane pane) {
|
Object source = resolveExpression(binding.getSource(), namespace);
|
||||||
pane.getChildren().setAll(subComponent);
|
Object target = resolveExpression(binding.getTarget(), namespace);
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (source instanceof Property && target instanceof Property) {
|
||||||
* Recursively walks all nodes in the scene graph starting from the root,
|
Property<?> sourceProp = (Property<?>) source;
|
||||||
* applying the given consumer to each node.
|
Property<?> targetProp = (Property<?>) target;
|
||||||
*
|
|
||||||
* @param root the starting node
|
Class<?> sourceType = getPropertyType(sourceProp);
|
||||||
* @param consumer the consumer to apply to each node
|
Class<?> targetType = getPropertyType(targetProp);
|
||||||
*/
|
|
||||||
private void walkNodes(Parent root, java.util.function.Consumer<javafx.scene.Node> consumer) {
|
boolean bindableForward = targetType.isAssignableFrom(sourceType);
|
||||||
consumer.accept(root);
|
boolean bindableBackward = sourceType.isAssignableFrom(targetType);
|
||||||
if (root instanceof Pane pane) {
|
|
||||||
for (javafx.scene.Node child : pane.getChildren()) {
|
switch (binding.getDirection().toLowerCase()) {
|
||||||
if (child instanceof Parent p) {
|
case "bidirectional":
|
||||||
walkNodes(p, consumer);
|
if (bindableForward && bindableBackward) {
|
||||||
|
bindBidirectionalSafe(sourceProp, targetProp);
|
||||||
} else {
|
} else {
|
||||||
consumer.accept(child);
|
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) -> {
|
|
||||||
if (k instanceof String key && key.startsWith("nfx:") && v instanceof String value) {
|
|
||||||
result.put(key, value);
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
return result;
|
|
||||||
|
private static Object resolveExpression(@NotNull String expr, @NotNull Map<String, Object> namespace) throws Exception {
|
||||||
|
// z.B. "viewModel.username"
|
||||||
|
String[] parts = expr.split("\\.");
|
||||||
|
Object current = namespace.get(parts[0]);
|
||||||
|
for (int i = 1; i < parts.length; i++) {
|
||||||
|
String getter = "get" + Character.toUpperCase(parts[i].charAt(0)) + parts[i].substring(1);
|
||||||
|
current = current.getClass().getMethod(getter).invoke(current);
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull List<Node> collectAllNodes(Node root) {
|
||||||
|
List<Node> nodes = new ArrayList<>();
|
||||||
|
nodes.add(root);
|
||||||
|
if (root instanceof Parent parent && !(root instanceof FxmlComponent)) {
|
||||||
|
for (Node child : parent.getChildrenUnmodifiable()) {
|
||||||
|
nodes.addAll(collectAllNodes(child));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -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,9 +26,7 @@ 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();
|
||||||
// Option: ControllerFactory verwenden, wenn nötig
|
|
||||||
Parent content = loader.load(getClass().getResource(getFxml()));
|
Parent content = loader.load(getClass().getResource(getFxml()));
|
||||||
|
|
||||||
getChildren().setAll(content);
|
getChildren().setAll(content);
|
||||||
@ -48,10 +35,6 @@ public class FxmlComponent extends StackPane {
|
|||||||
if (controller != null && getData() != null) {
|
if (controller != null && getData() != null) {
|
||||||
injectDataToController(controller, getData());
|
injectDataToController(controller, getData());
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void injectData() {
|
private void injectData() {
|
||||||
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user