diff --git a/core/src/main/java/de/neitzel/core/commandline/ArgumentProvider.java b/core/src/main/java/de/neitzel/core/commandline/ArgumentProvider.java new file mode 100644 index 0000000..2c2efd5 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/commandline/ArgumentProvider.java @@ -0,0 +1,69 @@ +package de.neitzel.core.commandline; + +import java.util.Iterator; + +/** + * Provides one argument after the other. + */ +public class ArgumentProvider implements Iterator { + + /** + * List of Arguments (from command line) + */ + private String[] arguments; + + /** + * Current element we work on. + */ + private int current = 0; + + /** + * Creates a new instance of ArgumentProvider. + * @param arguments List of Arguments. + */ + public ArgumentProvider(final String[] arguments) { + if (arguments == null) { + this.arguments = new String[] {}; + } else { + this.arguments = arguments; + } + } + + /** + * Checks if more arguments are available. + * @return True if more arguments are available else false. + */ + public boolean hasNext() { + return current < arguments.length; + } + + /** + * Checks if count more arguments are available. + * @param count Number of arguments we want to get. + * @return True if count more arguments are available else false. + */ + public boolean hasNext(final int count) { + return current + count <= arguments.length; + } + + /** + * Get the next token. + * @return The next token or null if no more available. + */ + public String next() { + if (!hasNext()) return null; + + String result = arguments[current]; + current++; + return result; + } + + /** + * Get the next token without removing it. + * @return The next token or null if no more available. + */ + public String peek() { + if (!hasNext()) return null; + return arguments[current]; + } +} diff --git a/core/src/main/java/de/neitzel/core/commandline/Parameter.java b/core/src/main/java/de/neitzel/core/commandline/Parameter.java new file mode 100644 index 0000000..14a7b54 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/commandline/Parameter.java @@ -0,0 +1,75 @@ +package de.neitzel.core.commandline; + +import lombok.*; +import lombok.extern.log4j.Log4j; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.function.Consumer; + +/** + * A parameter on the command line. + *
+ * Each parameter has to start with either - or /! + */ +@Slf4j +@Builder +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class Parameter { + + /** + * Name of the parameter. + */ + private String name; + + /** + * Min number of values given. + */ + private int minNumberValues; + + /** + * Max number of values given. + */ + private int maxNumberValues; + + /** + * Defines if the parameter can be given multiple times. + */ + private boolean multipleEntries; + + /** + * Short description of the parameter. + */ + private String shortDescription; + + /** + * Long description of the parameter. + */ + private String longDescription; + + /** + * Callback wenn der Parameter gefunden wurde. + */ + private Consumer> callback; + + /** + * Determines if this Element is the parameter for help. + */ + private boolean isHelpCommand; + + /** + * Determines if this Parameter is the default parameter. + *
+ * A default parameter must take at least one additional value. + */ + private boolean isDefaultParameter; + + /** + * List of aliases. + */ + @Singular("alias") + private List aliasList; +} diff --git a/core/src/main/java/de/neitzel/core/commandline/Parser.java b/core/src/main/java/de/neitzel/core/commandline/Parser.java new file mode 100644 index 0000000..c6074f2 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/commandline/Parser.java @@ -0,0 +1,145 @@ +package de.neitzel.core.commandline; + +import lombok.extern.slf4j.Slf4j; + +import java.util.*; + +/** + * A parser of the CommandLine. + *
+ * Parameter are defined. Each parameter will start with either - or /. + */ +@Slf4j +public class Parser { + + /** + * Parameters known by the CommandLineParser. + */ + private Map parameters = new HashMap<>(); + + /** + * The default parameter. + */ + private Parameter defaultParameter = null; + + /** + * Adds a parameter to the list of known parameters. + * @param parameter + */ + public void addParameter(final Parameter parameter) { + // Add by name + parameters.put(parameter.getName().toLowerCase(), parameter); + + // Add for all aliases + for (String alias: parameter.getAliasList()) { + parameters.put(alias.toLowerCase(), parameter); + } + + // Set help Callback for help command. + if (parameter.isHelpCommand()) + parameter.setCallback(this::helpCommandCallback); + + if (parameter.isDefaultParameter()) { + if (parameter.getMinNumberValues() == 0 && parameter.getMaxNumberValues() == 0) + throw new IllegalArgumentException("Default Parameter must accept values!"); + + defaultParameter = parameter; + } + } + + /** + * Checks if param is a parameter. + *
+ * - When the parameter is known as a parameter, then it is true. + * - Everything that starts with a - must be a known parameter -> false + * - Parameter is not known and does not start with a -? -> Default parameter if available. + * @param param Parameter in lower case to check. + * @return true if it is either a known parameter or an argument for the default parameter. + */ + protected boolean isParameter(final String param) { + if (parameters.containsKey(param)) return true; + if (param.startsWith("-")) return false; + return defaultParameter != null; + } + + /** + * Parse the given commandline arguments. + * @param args Commandline Arguments to parse. + */ + public void parse(final String[] args) { + ArgumentProvider provider = new ArgumentProvider(args); + while (provider.hasNext()) { + log.debug("Parsing argument: " + provider.peek()); + + // Peek to see the next parameter. + String next = provider.peek().toLowerCase(); + if (!isParameter(next)) { + log.error("Unknown Parameter: " + next); + throw new IllegalArgumentException("Unknown argument: " + next); + } + + Parameter parameter = parameters.get(next); + if (parameter == null) { + parameter = defaultParameter; + } else { + provider.next(); + } + + if (!provider.hasNext(parameter.getMinNumberValues())) { + String message = "Parameter " + next + " requires " + parameter.getMinNumberValues() + " more elements!"; + log.error(message); + throw new IllegalArgumentException(message); + } + + parameter.getCallback().accept(getOptions(provider, parameter.getMinNumberValues(), parameter.getMaxNumberValues())); + } + } + + /** + * Get the given optional data of a parameter- + * @param provider Provider of token. + * @param min Minimum number of elements to get. + * @param max Maximum number of elements to get. + * @return List of token of the Parameter. + */ + private List getOptions(ArgumentProvider provider, int min, int max) { + if (max < min) max = min; + List result = new ArrayList<>(); + + int current = 0; + while (current < max && provider.hasNext()) { + if (current < min || !parameters.containsKey(provider.peek())) { + result.add(provider.next()); + current++; + } else { + break; + } + } + + return result; + } + + /** + * Callback for help command. + * @param arguments null for general help or name of a command. + */ + public void helpCommandCallback(final List arguments) { + if (arguments == null || arguments.size() == 0) { + System.out.println("Moegliche Parameter der Applikation:"); + parameters.values().stream() + .distinct() + .sorted(Comparator.comparing(Parameter::getName)) + .forEach(p -> System.out.println(p.getShortDescription() )); + } else { + Parameter parameter = null; + if (parameters.containsKey(arguments.get(0))) parameter = parameters.get(arguments.get(0)); + if (parameters.containsKey("-" + arguments.get(0))) parameter = parameters.get("-" + arguments.get(0)); + + if (parameter == null) { + System.out.println("Unbekannter Parameter: " + arguments.get(0)); + } else { + System.out.println(parameter.getLongDescription()); + } + } + } +} diff --git a/core/src/main/java/de/neitzel/core/config/Configuration.java b/core/src/main/java/de/neitzel/core/config/Configuration.java new file mode 100644 index 0000000..8cf7da9 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/config/Configuration.java @@ -0,0 +1,221 @@ +package de.neitzel.core.config; + +import de.neitzel.core.util.FileUtils; +import de.neitzel.core.util.Strings; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URISyntaxException; +import java.nio.charset.Charset; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Map; +import java.util.Properties; + +/** + * Base class for a simple configuration. + */ +@Slf4j +public class Configuration { + + /** + * Properties with configuration data. + */ + private Properties properties = new Properties(); + + /** + * Gets a boolean property + * @param defaultValue Default value to return if key is not present. + * @param key Key of the property to get. + * @return The value of the property if it exists or the default value. + */ + 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"); + } + + /** + * Gets a String property + * @param key Key of the property to query. + * @param defaultValue Default value to return if property not available. + * @return The property if it exists, else the default value. + */ + protected String getStringProperty(final String key, final String defaultValue) { + if (!properties.containsKey(key)) return defaultValue; + return properties.getProperty(key).trim(); + } + + /** + * Gets a String property with all environment variables replaced + *

+ * Environment variable can be included with ${variable name}. + *

+ * @param key Key of the property to query. + * @param defaultValue Default value to return if property not available. + * @return The property if it exists, else the default value. + */ + protected String getStringPropertyWithEnv(final String key, final String defaultValue) { + String result = getStringProperty(key, defaultValue); + return Strings.expandEnvironmentVariables(result); + } + + /** + * Gets a String property without quotes. + *

+ * If the value is put in quotes, then the quote signs are replaced. + *

+ * @param key Key of the property to query. + * @param defaultValue Default value to return if property not available. + * @return The property if it exists, else the default value (without leading/ending quote signs). + */ + protected String getStringPropertyWithoutQuotes(final String key, final String defaultValue) { + return Strings.removeQuotes(getStringProperty(key, defaultValue)); + } + + /** + * Gets a String property without quotes. + *

+ * If the value is put in quotes, then the quote signs are replaced. + *

+ * @param key Key of the property to query. + * @param defaultValue Default value to return if property not available. + * @return The property if it exists, else the default value (without leading/ending quote signs). + */ + protected String getStringPropertyWithoutQuotesWithEnv(final String key, final String defaultValue) { + String result = getStringPropertyWithoutQuotes(key, defaultValue); + return Strings.expandEnvironmentVariables(result); + } + + /** + * Gets an Integer property. + *
+ * Supports null as value. + * @param defaultValue Default value to return if key is not present. + * @param key Key of the property to get. + * @return The value of the property if it exists or the default value. + */ + protected Integer getIntegerProperty(final String key, final Integer defaultValue) { + if (!properties.containsKey(key)) return defaultValue; + String value = properties.getProperty(key); + return Strings.isNullOrEmpty(value) ? null : Integer.parseInt(value); + } + + /** + * Sets an Integer property. + * @param key Key of the property to set. + * @param value Value to set. + */ + protected void setIntegerProperty(final String key, final Integer value) { + if (value == null) { + properties.setProperty(key, ""); + } else { + properties.setProperty(key, ""+value); + } + } + + /** + * Gets the property as LocalDate. + *
+ * An null value or an empty String is given as null. + * @param key Key of the property. + * @param defaultValue default Value. + * @param formatString Format String to use. + * @return The LocalDate stored the property or the default value if property unknown or couldn't be parsed. + */ + protected LocalDate getLocalDateProperty(final String key, final LocalDate defaultValue, final String formatString) { + if (!properties.containsKey(key)) return defaultValue; + + String value = properties.getProperty(key); + if (value == null || value.isEmpty()) return null; + return LocalDate.parse(value, DateTimeFormatter.ofPattern(formatString)); + } + + /** + * Sets the LocalDate using the provided format String. + *
+ * Null is allowed and is stored as empty string. + * @param key Key of the property to set. + * @param value Value to set. + * @param formatString Format string to use. + */ + protected void setLocalDateProperty(final String key, final LocalDate value, final String formatString) { + if (value == null) { + setProperty(key, ""); + } else { + setProperty(key, value.format(DateTimeFormatter.ofPattern(formatString))); + } + } + + /** + * Sets a property to a new value. + * @param key Key of property to set. + * @param value New value of property. + */ + public void setProperty(final String key, final String value) { + String newValue = value == null ? "" : value; + log.info("Setting a new value for '" + key + "': '" + newValue + "'"); + properties.setProperty(key, newValue); + } + + /** + * Loads the configuration of the file. + */ + public void load(final String fileName) { + load(fileName, Charset.defaultCharset().name(), true); + } + + /** + * Loads the configuration of the file. + */ + public void load(final String fileName, final String encoding, final boolean acceptUTF8) { + log.info("Reading Config: " + fileName + " with encoding: " + encoding + "accepting UTF-8: " + acceptUTF8); + File configFile = new File(fileName); + + // Try to get the file next to the jar file if the configFile does not exist. + if (!configFile.exists()) { + try { + String fileAtJar = new File(Configuration.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent() + "/" + fileName; + configFile = new File(fileAtJar); + } catch (URISyntaxException ex) { + log.error("Unable to get path of jar file / class.", ex); + } + } + + // Read the configuration file if it exists. + if (configFile.exists()) { + log.info("Reading Configuration file. " + configFile.getAbsolutePath()); + try (InputStreamReader reader = FileUtils.createUniversalFileReader(configFile, encoding, acceptUTF8)) { + log.info("Reading the configuration with encoding: " + reader.getEncoding()); + properties.load(reader); + } catch (FileNotFoundException fnfe) { + log.error("Configuration file: " + fileName + " not found!", fnfe); + } catch (IOException ioe) { + log.error("Cannot read config file.", ioe); + } + } else { + log.error("Unable to load config file! Last try: " + configFile.getAbsolutePath()); + } + } + + /** + * Insert the configuration settings of the given config. + * @param config Configuration to merge into this instance. + */ + public void merge(final Configuration config) { + for(Map.Entry entry: config.properties.entrySet()) { + properties.put(entry.getKey(), entry.getValue()); + } + } + + /** + * Removes a key from the configuration. + * @param 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 new file mode 100644 index 0000000..f2b5ccf --- /dev/null +++ b/core/src/main/java/de/neitzel/core/io/ConvertedEncodingFileReader.java @@ -0,0 +1,76 @@ +package de.neitzel.core.io; + +import de.neitzel.core.util.FileUtils; +import lombok.extern.slf4j.Slf4j; + +import java.io.*; +import java.nio.charset.Charset; +import java.nio.charset.IllegalCharsetNameException; + +/** + * FileReader that converts an UTF-8 file to ISO-8859-15 if required. + *

+ * This FileReader checks, if the file to be read is an UTF-8 file. + * If an UTF-8 encoding is found, a temporary file will be created with the content + * of the original File - just encoded in the new format. + *

+ *

+ * This Reader is mainly tested ith ISO-8859-15 and UTF-8. Other formats are not really supported. + *

+ */ +@Slf4j +public class ConvertedEncodingFileReader extends InputStreamReader { + + private static String checkEncoding = "ISO-8859-15"; + + private static void setCheckEncoding(final String encoding) { + if (Charset.forName(encoding) != null) throw new IllegalCharsetNameException("Encoding " + encoding + " is not supported!"); + + checkEncoding = encoding; + } + + /** + * Creates a new ConvertedEncodingFileReader from a given File. + * @param file File to convert if required and open reader. + * @param targetFormat Target Format to use. + * @throws IOException + */ + public ConvertedEncodingFileReader(final File file, final String targetFormat) throws IOException { + super(createTargetFormatInputFileStream(file, targetFormat), targetFormat); + } + + /** + * Creates a new ISO8859ConvertedFileReader from a given File. + * @param filename File to convert if required and open reader. + * @throws IOException + */ + public ConvertedEncodingFileReader(final String filename, final String targetFormat) throws IOException { + this(new File(filename), targetFormat); + } + + /** + * Creates an ISO8859-15 encoded InputFileStream on an file. + *

+ * If the file is UTF-8 encoded, then it is converted to a temp file and the temp file will be opened. + *

+ * @param file + * @return The InputFileStream on the original file or the converted file in case of an UTF-8 file. + * @throws IOException + */ + private static FileInputStream createTargetFormatInputFileStream(final File file, final String targetFormat) throws IOException { + if (!file.exists()) { + String errorMessage = "File " + file.toString() + " does not exist!"; + log.error(errorMessage); + throw new FileNotFoundException(errorMessage); + } + + if (!FileUtils.isUTF8(file, checkEncoding)) { + return new FileInputStream(file); + } else { + File tempFile = File.createTempFile(file.getName(), "tmp"); + FileUtils.convertTextFile(file, "UTF-8", tempFile, targetFormat); + tempFile.deleteOnExit(); + return new FileInputStream(tempFile); + } + } +} diff --git a/core/src/main/java/de/neitzel/core/io/ISO8859EncodingFileReader.java b/core/src/main/java/de/neitzel/core/io/ISO8859EncodingFileReader.java new file mode 100644 index 0000000..e7bcdf0 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/io/ISO8859EncodingFileReader.java @@ -0,0 +1,17 @@ +package de.neitzel.core.io; + +import java.io.File; +import java.io.IOException; + +public class ISO8859EncodingFileReader extends ConvertedEncodingFileReader { + + private static final String TARGET_FORMAT = "ISO-8859-15"; + + public ISO8859EncodingFileReader(File file) throws IOException { + super(file, TARGET_FORMAT); + } + + public ISO8859EncodingFileReader(String filename) throws IOException { + super(filename, TARGET_FORMAT); + } +} diff --git a/core/src/main/java/de/neitzel/core/io/StringEncoder.java b/core/src/main/java/de/neitzel/core/io/StringEncoder.java new file mode 100644 index 0000000..4193bdc --- /dev/null +++ b/core/src/main/java/de/neitzel/core/io/StringEncoder.java @@ -0,0 +1,75 @@ +package de.neitzel.core.io; + +/** + * An encoder for Strings. + *

+ * All characters with unicode number less than 32 or greater than 127 or 38 (Ampersend) + * will be encoded with &#number; with number as the decimal unicode number. + */ +public class StringEncoder { + + /** + * Decodes the encoded String. + * @param data Encoded string. + * @return Decoded String. + */ + public static String decodeData(final String data) { + if (data == null) return ""; + + String remaining = data; + StringBuilder result = new StringBuilder(); + while (!remaining.isEmpty()) { + int indexAmp = remaining.indexOf("&"); + if (indexAmp == -1) { + result.append(remaining); + remaining=""; + } else { + // First get the elements till the & + if (indexAmp > 0) { + result.append(remaining.substring(0, indexAmp)); + remaining = remaining.substring(indexAmp); + } + int endSpecial=remaining.indexOf(";"); + if (endSpecial == -1) throw new IllegalArgumentException("String couldn't be decoded! (" + data + ")"); + String special = remaining.substring(0, endSpecial+1); + remaining = remaining.substring(endSpecial+1); + result.append(decodeCharacter(special)); + } + } + return result.toString(); + } + + /** + * Decode a single character. + * @param data String in the form &#xxx; with xxx a decimal number. + * @return The decoded character. + */ + public static char decodeCharacter(final String data) { + if (!data.startsWith("&#")) throw new IllegalArgumentException("Data does not start with &# (" + data + ")"); + if (!data.endsWith(";")) throw new IllegalArgumentException("Data does not end with ; (" + data + ")"); + return (char)Integer.parseInt(data.substring(2, data.length()-1)); + } + + /** + * Encode data to a better String representation. + *

+ * All Characters between from ascii 32 to 127 (except 38 / &) + * are replaced with a &#code; where code is the number of the character. + * @param data String that should be encoded. + * @return Encoded String. + */ + public static String encodeData(final String data) { + if (data == null) return ""; + + StringBuilder result = new StringBuilder(); + for (int index=0; index < data.length(); index++) { + char character = data.charAt(index); + if (character < 32 || character > 127 || character == 38) { + result.append("&#" + (int)character + ";"); + } else { + result.append(character); + } + } + return result.toString(); + } +} diff --git a/core/src/main/java/de/neitzel/core/util/ArrayUtils.java b/core/src/main/java/de/neitzel/core/util/ArrayUtils.java new file mode 100644 index 0000000..bbc0131 --- /dev/null +++ b/core/src/main/java/de/neitzel/core/util/ArrayUtils.java @@ -0,0 +1,20 @@ +package de.neitzel.core.util; + +/** + * Utility Class with static methods about arrays. + */ +public class ArrayUtils { + + /** + * Checks if a given char is inside an array of chars. + * @param array Array of chars to seach in. + * @param ch Character to search. + * @return true if character was found, else false. + */ + public static boolean contains(char[] array, char ch) { + for(int index=0; index < array.length; index++) { + if (array[index] == ch) return true; + } + return false; + } +} diff --git a/core/src/main/java/de/neitzel/core/util/EnumUtil.java b/core/src/main/java/de/neitzel/core/util/EnumUtil.java new file mode 100644 index 0000000..7cb9bbf --- /dev/null +++ b/core/src/main/java/de/neitzel/core/util/EnumUtil.java @@ -0,0 +1,49 @@ +package de.neitzel.core.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * Enumeration Utilities + */ +public class EnumUtil { + /** + * Creates a regular expression to match a comma separated list of Flags. + * @return Regular expression to match flags. + */ + public static > String getFlagRegEx(Class clazz) { + StringBuilder result = new StringBuilder(); + result.append("(|,|\\s"); + + for (T flag: clazz.getEnumConstants()) { + result.append("|"); + for(char ch: flag.toString().toUpperCase().toCharArray()) { + if (Character.isAlphabetic(ch)) { + result.append("["); + result.append(ch); + result.append(Character.toLowerCase(ch)); + result.append("]"); + } else { + result.append(ch); + } + } + } + result.append(")*"); + return result.toString(); + } + + /** + * Parse a list of comma separated flags into a List of RequestFlags. + * @param flags String with comma separated flags. + * @return List of RequestFlags. + */ + public static > List parseFlags(final Class clazz, final String flags) { + List result = new ArrayList<>(); + if (flags != null) { + for (String flag: flags.split("[,\\s]")) { + if (!flag.isEmpty()) result.add(T.valueOf(clazz, flag.toUpperCase())); + } + } + return result; + } +} diff --git a/core/src/main/java/de/neitzel/core/util/FileUtils.java b/core/src/main/java/de/neitzel/core/util/FileUtils.java new file mode 100644 index 0000000..63e6d3e --- /dev/null +++ b/core/src/main/java/de/neitzel/core/util/FileUtils.java @@ -0,0 +1,246 @@ +package de.neitzel.core.util; + +import lombok.extern.log4j.Log4j; +import lombok.extern.slf4j.Slf4j; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.text.SimpleDateFormat; +import java.util.stream.Collectors; + +/** + * Utility Methods regarding files. + */ +@Slf4j +public class FileUtils { + + /** + * Date/Time format to add to request files. + */ + public static final SimpleDateFormat DEBUG_FILE_TIMESTAMP_FORMAT = new SimpleDateFormat("yyyy-MM-dd_HH_mm_ss_SSS"); + + /** + * Default encoding used to check the File + */ + public static final String DEFAULT_CHECK_ENCODING = "ISO-8859-15"; + + /** + * Default Buffer size. + */ + public static final int BUFFER_SIZE = 1024; + + /** + * Checks if a File is UTF-8 encoded. + *

+ * This method checks a file for + * - first 3 bytes for UTF-8 BOM (0xEF 0xBB 0xBF) + * - Existence of 0xC2 / 0xC3 character. + *

+ * @param file File to check. + * @param checkEncoding Encoding to use for the check, e.g. ISO-8859-15. + * @return true if an UTF-8 file is found, else false. + * @throws IOException IOException is thrown if the file could not be read. + */ + public static boolean isUTF8(final File file, final String checkEncoding) throws IOException { + + if (hasUTF8BOM(file)) return true; + + int BUFFER_SIZE = 1024; + char[] buffer = new char[BUFFER_SIZE]; + + try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file), checkEncoding)) { + + while (reader.read(buffer, 0, BUFFER_SIZE) > 0) { + + if ( + (ArrayUtils.contains(buffer, (char) 0x00C2)) // Part of UTF-8 Characters 0xC2 0xZZ + || (ArrayUtils.contains(buffer, (char) 0x00C3))) { // Part of UTF-8 Characters 0xC3 0xZZ + return true; + } + + } + + return false; + } catch (IOException ex) { + log.error("Exception while reading file.", ex); + throw ex; + } + } + + /** + * Checks if the File has a UTF-8 BOM. + *

+ * This method checks a file for + * - first 3 bytes for UTF-8 BOM (0xEF 0xBB 0xBF) + * - Existence of 0xC2 / 0xC3 character. + *

+ * @param file File to check. + * @return true if an UTF-8 BOM Header was found. + * @throws IOException IOException is thrown if the file could not be read. + */ + public static boolean hasUTF8BOM(final File file) throws IOException { + try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) { + return reader.read() == 0xFEFF; + } catch (IOException ex) { + log.error("Exception while reading file.", ex); + throw ex; + } + } + + /** + * Checks if a File is UTF-8 encoded. + *

+ * This method checks a file for + * - first 3 bytes for UTF-8 BOM (0xEF 0xBB 0xBF) + * - Existence of 0xC2 / 0xC3 character. + *

+ * @param file File to check. + * @return true if an UTF-8 file is found, else false. + * @throws IOException IOException is thrown if the file could not be read. + */ + public static boolean isUTF8(final File file) throws IOException { + return isUTF8(file, DEFAULT_CHECK_ENCODING); + } + + /** + * Converts the given Textfile inFile to the outFile using the given encodings. + * @param inFile File to read as UTF-8. + * @param sourceFormat Format of the source file. + * @param outFile File to write with ISO-8859-15 format. + * @param targetFormat Format of the target file. + * @throws IOException Thrown when files couldn't be read / written. + */ + public static void convertTextFile(final File inFile, final String sourceFormat, final File outFile, final String targetFormat) throws IOException { + char[] buffer = new char[BUFFER_SIZE]; + + int charsRead, startIndex; + boolean first = true; + try (InputStreamReader reader = new InputStreamReader(new FileInputStream(inFile), sourceFormat); + OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(outFile), targetFormat)) { + + while ((charsRead = reader.read(buffer, 0, BUFFER_SIZE)) > 0) { + startIndex = 0; + + if (first) { + // Check UTF-8 BOM + if (buffer[0] == 0xFEFF) { + log.info("BOM found!"); + startIndex = 1; + } + + first = false; + } + + writer.write(buffer, startIndex, charsRead - startIndex); + } + + } catch (IOException ex) { + log.error("Exception when converting Textfile.", ex); + throw ex; + } + } + + /** + * Creates a new InputStreamReader with the given format or UTF-8 if an UTF-8 file was recognized. + * @param filename Name of file to read. + * @return An InputStreamReader + */ + public static InputStreamReader createUniversalFileReader(final String filename) throws IOException { + return createUniversalFileReader(new File(filename)); + } + + /** + * Creates a new InputStreamReader with the given format or UTF-8 if an UTF-8 file was recognized. + * @param filename Name of file to read. + * @param expectedFormat Expected format e.g. ISO-8859-15 + * @return An InputStreamReader + */ + public static InputStreamReader createUniversalFileReader(final String filename, final String expectedFormat) throws IOException { + return createUniversalFileReader(new File(filename), expectedFormat); + } + + /** + * Creates a new InputStreamReader with the given format or UTF-8 if an UTF-8 file was recognized. + * @param file File to read. + * @return An InputStreamReader + */ + public static InputStreamReader createUniversalFileReader(final File file) throws IOException { + return createUniversalFileReader(file, DEFAULT_CHECK_ENCODING, true); + } + + /** + * Creates a new InputStreamReader with the given format or UTF-8 if an UTF-8 file was recognized. + * @param file File to read. + * @param expectedFormat Expected format e.g. ISO-8859-15 + * @return An InputStreamReader + */ + public static InputStreamReader createUniversalFileReader(final File file, final String expectedFormat) throws IOException { + return createUniversalFileReader(file, expectedFormat, true); + } + + /** + * Creates a new InputStreamReader with the given format or UTF-8 if an UTF-8 file was recognized. + * @param file File to read. + * @param expectedFormat Expected format e.g. ISO-8859-15 + * @return An InputStreamReader + */ + public static InputStreamReader createUniversalFileReader(final File file, final String expectedFormat, final boolean acceptUTF8) throws IOException { + String encoding = acceptUTF8 && isUTF8(file, expectedFormat) + ? "UTF-8" + : expectedFormat; + + boolean skipBOM = encoding.equals("UTF-8") && hasUTF8BOM(file); + + InputStreamReader result = new InputStreamReader(new FileInputStream(file), encoding); + if (skipBOM) { + int BOM = result.read(); + if (BOM != 0xFEFF) log.error ("Skipping BOM but value not 0xFEFF!"); + } + return result; + } + + /** + * Gets the parent directory of a file name. + * @param filename Name of file. + * @return Parent folder. + */ + public static String getParentDirectory(final String filename) { + File file = new File(filename); + return file.getParent() != null ? file.getParent() : "."; + } + + /** + * Gets the core name of the file without path. + * @param filename Filename with path. + * @return Filename without path. + */ + public static String getFilename(final String filename) { + File file = new File(filename); + return file.getName(); + } + + /** + * Reads the content of a file (With the correct encoding!). + * @param filename Name of file to read. + * @return Content of the file or null in case of an error. + * @throws IOException Throes an IOException if the file cannot be read. + */ + public static String readFileContent(final String filename) throws IOException { + try (BufferedReader reader = new BufferedReader(createUniversalFileReader(filename))) { + return reader.lines().collect(Collectors.joining(System.lineSeparator())); + } + } + + /** + * Writes the content given to a file. + * @param path Path of the file to write. + * @param content Content to write. + * @throws IOException Thrown if the file could not be written. + */ + public static void writeFile(final Path path, final String content) throws IOException { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(path.toFile()))) { + writer.write(content); + } + } +} diff --git a/core/src/main/java/de/neitzel/core/util/RegionalizationMessage.java b/core/src/main/java/de/neitzel/core/util/RegionalizationMessage.java new file mode 100644 index 0000000..5d2e5be --- /dev/null +++ b/core/src/main/java/de/neitzel/core/util/RegionalizationMessage.java @@ -0,0 +1,65 @@ +package de.neitzel.core.util; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.ResourceBundle; + +public class RegionalizationMessage { + + /** + * Resource Bundle to use for operations. + */ + private ResourceBundle res; + + /** + * Creates a new instance of RegionalizationMessage. + * @param source Source of messages. + */ + public RegionalizationMessage(final String source) { + res = ResourceBundle.getBundle(source); + } + + /** + * Creates a new instance of RegionalizationMessage. + * @param source Source of messages. + * @param locale Locale to use. + */ + public RegionalizationMessage(final String source, final Locale locale) { + res = ResourceBundle.getBundle(source, locale); + } + + /** + * Gets the Message behind a key. + * @param key Key to look up. + * @return Message or null. + */ + public String getMessage(final String key) { + if (!res.containsKey(key)) return null; + return res.getString(key); + } + + /** + * Gets the Message behind a key. + * @param key Key to look up. + * @param defaultMessage Default message to use if message is not available. + * @return Message from resource or default Message. + */ + public String getMessage(final String key, final String defaultMessage) { + if (res.containsKey(key)) + return res.getString(key); + + return defaultMessage; + } + + /** + * Gets a formatted message. + * @param key key to look up message. + * @param defaultMessage Default message to use if message couldn't be loaded. + * @param params parameter to format the message. + * @return Formatted message. + */ + public String getFormattedMessage(final String key, final String defaultMessage, final Object... params) { + MessageFormat format = new MessageFormat(getMessage(key, defaultMessage)); + return format.format(params); + } +} diff --git a/core/src/main/java/de/neitzel/core/util/Strings.java b/core/src/main/java/de/neitzel/core/util/Strings.java new file mode 100644 index 0000000..a1d2eff --- /dev/null +++ b/core/src/main/java/de/neitzel/core/util/Strings.java @@ -0,0 +1,109 @@ +package de.neitzel.core.util; + +import lombok.NonNull; + +import java.util.Map; + +/** + * Utility Methods for Strings + */ +public class Strings { + /** + * Environment variables of the system. + */ + private static Map environmentVariables = System.getenv(); + + /** + * Expands environment Variables inside a string. + *

+ * The environment variables should be given as ${variable}. + *

+ *

+ * Backslash inside variable values are replaced with slashes to avoid that they are seen as escape character. + * This makes the use for paths on windows possible but reduces the use with non paths in which you need a backslash. + *

+ * @param text Test in which environment variables should be expanded. + * @return String with all environment variables expanded. + */ + public static String expandEnvironmentVariables(String text) { + if (text == null) return null; + for (Map.Entry entry : environmentVariables.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue().replace('\\', '/'); + text = text.replaceAll("\\$\\{" + key + "\\}", value); + } + return text; + } + + /** + * Checks if the given String is null or empty. + * @param string String to check. + * @return true if given String is null or empty. + */ + public static boolean isNullOrEmpty(final String string) { + return (string == null || string.length()==0); + } + + /** + * Removes a leading / ending quote if there. + * @param value String to remove the quotes. + * @return Parameter string if it does not start / end with a quote, else string without leading/ending quote. + */ + public static String removeQuotes(final String value) { + String trimmedValue = value.trim(); + if (trimmedValue.length() > 1 && trimmedValue.startsWith("\"") && trimmedValue.endsWith("\"")) + return trimmedValue.substring(1, trimmedValue.length()-1); + + return value; + } + + /** + * Filters a given String and removes all illegal characters. + * @param value String value to filter all illegal characters from. + * @param illegalCharacters String with all Illegal Characters to filter out. + * @param replacement String to replace illegal characters with. + * @param combine Should multiple illegal characters that are together be replaced with one replacement string only? + * @return A new string where all illegal characters are replaced with the replacement string. + */ + public static String replaceIllegalCharacters(final String value, final String illegalCharacters, final String replacement, final boolean combine) { + String replacementRegex = "[" + illegalCharacters + "]" + (combine ? "+" : ""); + return value.replaceAll(replacementRegex, replacement); + } + + /** + * Filters a given String and removes all illegal characters. + *

+ * All characters, that are not inside allowedCharacters, are illegal characters. + *

+ * @param value String value to filter all illegal characters from. + * @param allowedCharacters String with all allowed Characters to filter out. + * @param replacement String to replace illegal characters with. + * @param combine Should multiple illegal characters that are together be replaced with one replacement string only? + * @return A new string where all illegal characters are replaced with the replacement string. + */ + public static String replaceNonAllowedCharacters(final String value, final String allowedCharacters, final String replacement, final boolean combine) { + String replacementRegex = "[^" + allowedCharacters + "]" + (combine ? "+" : ""); + return value.replaceAll(replacementRegex, replacement); + } + + /** + * Gets the next Element which follows to the current element. + * @param element to get the precessor. + * @return The next element after this one. + */ + public static String increment(@NonNull final String element) { + if (element.isEmpty()) return "1"; + + String firstPart = element.substring(0, element.length()-1); + char lastChar = element.charAt(element.length()-1); + if (lastChar == '9') return firstPart + 'A'; + if (lastChar == 'Z') return increment(firstPart) + '0'; + return firstPart + (char)(lastChar+1); + } + + public static String limitCharNumber(final String original, final int maxLength) { + if (original == null || original.length() <= maxLength) return original; + + return original.substring(0, maxLength); + } +} diff --git a/core/src/main/java/de/neitzel/core/util/XmlUtils.java b/core/src/main/java/de/neitzel/core/util/XmlUtils.java new file mode 100644 index 0000000..973c4bb --- /dev/null +++ b/core/src/main/java/de/neitzel/core/util/XmlUtils.java @@ -0,0 +1,203 @@ +package de.neitzel.core.util; + +import lombok.NonNull; +import lombok.extern.log4j.Log4j; +import lombok.extern.slf4j.Slf4j; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.*; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +/** + * Converter with helper functions to format XML. + */ +@Slf4j +public class XmlUtils { + + /** + * DateTimeFormatter instance to format a date for XML use (xs:date type). + */ + public static final DateTimeFormatter XML_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + /** + * Creates a new XML element to be used inside a document. + * + * @param doc XML Document to use. + * @param name Name of the Element. + * @return Created Element. + */ + public static Element createElement(final Document doc, final String name) { + log.debug("Creating a new element " + name); + return doc.createElement(name); + } + + /** + * Creates a new XML element with text value to be used inside a document. + * + * @param doc XML Document to use. + * @param name Name of the Element. + * @param value Value of the Text Node. + * @return Created Element. + */ + public static Element createElement(final Document doc, final String name, final String value) { + log.debug("Creating a new element " + name + " with value: " + value); + Element element = doc.createElement(name); + + Node content = doc.createTextNode(value != null ? value : ""); + element.appendChild(content); + + return element; + } + + /** + * Creates a new XML element with date value to be used inside a document. + * + * @param doc XML Document to use. + * @param name Name of the Element. + * @param value Date to insert into node. + * @return Created Element. + */ + public static Element createElement(final Document doc, final String name, final Date value) { + return createElement(doc, name, XML_DATE_FORMATTER.format(value.toInstant())); + } + + /** + * Creates a new XML element with integer value to be used inside a document. + * + * @param doc XML Document to use. + * @param name Name of the Element. + * @param value Value of the Integer Node. + * @return Created Element. + */ + public static Element createElement(final Document doc, final String name, final Integer value) { + log.debug("Creating a new element " + name + " with value: " + value); + Element element = doc.createElement(name); + + Node content = doc.createTextNode(value != null ? ""+value : ""); + element.appendChild(content); + + return element; + } + + /** + * Creates a new XML element to be used inside a document and adds it to the parent. + * + * @param doc XML Document to use. + * @param name Name of the Element. + * @param parent Parent for the Element. + * @return Created Element. + */ + public static Element createAndInsertElement(final Document doc, final String name, @NonNull final Element parent) { + log.debug("Creating a new element " + name + " and adding it to a parent."); + Element element = createElement(doc, name); + parent.appendChild(element); + return element; + } + + /** + * Creates a new XML element with text value And adds it to the parent. + * + * @param name Name of the Element. + * @param value Value of the Text Node. + * @param parent Parent for the Element. + * @return Created Element. + */ + public static Element createAndInsertElement(final Document doc, final String name, final String value, @NonNull final Element parent) { + log.debug("Creating a new element " + name + " with value: " + value + " and adding it to a parent."); + Element element = createElement(doc, name, value); + parent.appendChild(element); + return element; + } + + + /** + * Formats the XML from a given String with XML content. + * + * @param xmlStream String with XML content. + * @return Formated XML content or original content in case of an error. + */ + public static String format(final String xmlStream) { + log.debug("formatXML"); + + try { + Source xmlInput = new StreamSource(new StringReader(xmlStream)); + StringWriter stringWriter = new StringWriter(); + StreamResult xmlOutput = new StreamResult(stringWriter); + + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformerFactory.setAttribute("indent-number", "4"); + + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.transform(xmlInput, xmlOutput); + + return xmlOutput.getWriter().toString(); + } + catch(TransformerException e) { + log.error("Error in XML: " + e.getMessage() + "\n"+xmlStream, e); + } + + return xmlStream; + } + + /** + * Formats the XML from a given XML Document. + * + * @param doc XML Document to format. + * @return Formated XML content. + */ + public static String format(final Document doc) { + try { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformerFactory.setAttribute("indent-number", "4"); + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.STANDALONE, "yes"); + transformer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1"); + StringWriter stringWriter = new StringWriter(); + + Source source = new DOMSource(doc); + Result result = new StreamResult(stringWriter); + transformer.transform(source, result); + + String xmlString = stringWriter.toString(); + log.info("MO Request: " + xmlString); + return xmlString; + } + catch(TransformerException e) { + log.error("Error in XML Transformation: " + e.getMessage(), e); + } + + return null; + } + + /** + * Checks if the given xml string is valid XML + * @param xml XML String. + * @return true if xml is valid. + */ + public static boolean checkXml(final String xml) { + try { + InputStream stream = new ByteArrayInputStream(xml.getBytes()); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = dbf.newDocumentBuilder(); + Document document = docBuilder.parse(stream); + return document != null; + } catch (Exception ex) { + log.warn("Exception when validating xml.", ex); + return false; + } + } +} diff --git a/log4j/pom.xml b/log4j/pom.xml new file mode 100644 index 0000000..015db45 --- /dev/null +++ b/log4j/pom.xml @@ -0,0 +1,54 @@ + + + 4.0.0 + + + de.neitzel.lib + neitzellib + 1.0-SNAPSHOT + + + log4j + + + + ${project.artifactId} + ${project.artifactId} + ${project.artifactId} + ${project.artifactId}-${project.version} + + + 1.2.17 + + + + + log4j + log4j + ${log4j.version} + + + log4j + apache-log4j-extras + ${log4j.version} + + + + + ${jar.filename} + + + + com.github.spotbugs + spotbugs-maven-plugin + + + + org.apache.maven.plugins + maven-pmd-plugin + + + + diff --git a/log4j/src/main/java/de/neitzel/log4j/Log4jUtils.java b/log4j/src/main/java/de/neitzel/log4j/Log4jUtils.java new file mode 100644 index 0000000..4ab25e7 --- /dev/null +++ b/log4j/src/main/java/de/neitzel/log4j/Log4jUtils.java @@ -0,0 +1,75 @@ +package de.neitzel.log4j; + +import org.apache.log4j.PropertyConfigurator; + +import java.io.File; +import java.net.URISyntaxException; + +/** + * Adds Utility Methods for log4j. + */ +public class Log4jUtils { + + /** + * Default Logfile that should be read. + * Must be a relative path that is checked from the current directory and also from the location of the jar file. + */ + public static final String DEFAULT_LOG4J_LOGFILE = "./log4j.properties"; + + /** + * Resource to use when no file is found. + */ + public static final String DEFAULT_LOG4J_RESOURCE = "/log4j.default.properties"; + + /** + * Checks if a log4j config file was set on command line with -Dlog4j.configuration + * @return true if log4j.configuration was set. + */ + public static boolean isLog4jConfigFileSet() { + return System.getProperty("log4j.configuration") != null; + } + + /** + * Uses the default configurations if no config file was set. + */ + public static void setLog4jConfiguration() { + setLog4jConfiguration(DEFAULT_LOG4J_LOGFILE, DEFAULT_LOG4J_RESOURCE); + } + + /** + * Gets the file with path relative to the jar file. + * @param log4jConfigFile log4j config file. + * @return Path if possible or null. + */ + public static String getLog4jLogfileAtJar(final String log4jConfigFile) { + try { + return new File(Log4jUtils.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent() + "/" + log4jConfigFile; + } catch (URISyntaxException ex) { + return null; + } + } + + /** + * Uses the default configurations if no config file was set. + *

+ * A log4j configuration can be set using -Dlog4j.configuration. If no configuration was set, + * we look for the log4jConfigFile. If it does not exist, we read the defaultResource. + *

+ * @param log4jConfigFile Default file to look for. + * @param defaultResource Resource path to use if config file wasn't found. + */ + public static void setLog4jConfiguration(final String log4jConfigFile, final String defaultResource) { + if (isLog4jConfigFileSet()) return; + + String fileAtJar = getLog4jLogfileAtJar(log4jConfigFile); + + if (new File(log4jConfigFile).exists()) { + PropertyConfigurator.configure(log4jConfigFile); + } else if (fileAtJar != null && new File(fileAtJar).exists()) { + System.out.println("Nutze Log4J Konfiguration bei jar File: " + fileAtJar); + PropertyConfigurator.configure(fileAtJar); + }else { + PropertyConfigurator.configure(Log4jUtils.class.getResource(defaultResource)); + } + } +} diff --git a/pom.xml b/pom.xml index 35ab1ba..9a30a16 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,7 @@ encryption fx net + log4j fx-example @@ -99,6 +100,7 @@ +