diff --git a/bundles/org.openhab.core/src/main/java/org/openhab/core/util/StringUtils.java b/bundles/org.openhab.core/src/main/java/org/openhab/core/util/StringUtils.java new file mode 100644 index 00000000000..98e4a692867 --- /dev/null +++ b/bundles/org.openhab.core/src/main/java/org/openhab/core/util/StringUtils.java @@ -0,0 +1,322 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.core.util; + +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * Static utility methods that are helpful when dealing with strings. + * + * @author Leo Siepel - Initial contribution + */ + +@NonNullByDefault +public class StringUtils { + + /** + * If a newline char exists at the end of the line, it is removed + * + *
+     * Util.chomp(null)          = null
+     * Util.chomp("")            = ""
+     * Util.chomp("abc \r")      = "abc "
+     * Util.chomp("abc\n")       = "abc"
+     * Util.chomp("abc\r\n")     = "abc"
+     * Util.chomp("abc\r\n\r\n") = "abc\r\n"
+     * Util.chomp("abc\n\r")     = "abc\n"
+     * Util.chomp("abc\n\rabc")  = "abc\n\rabc"
+     * Util.chomp("\r")          = ""
+     * Util.chomp("\n")          = ""
+     * Util.chomp("\r\n")        = ""
+     * 
+ * + * @param str the input String to escape, may be null + * @return the chomped string, may be null + */ + public static @Nullable String chomp(final @Nullable String str) { + if (str == null || str.isEmpty()) { + return str; + } + + if (str.endsWith("\r\n")) { + return str.substring(0, str.length() - 2); + } else if (str.endsWith("\r") || str.endsWith("\n")) { + return str.substring(0, str.length() - 1); + } else { + return str; + } + } + + /** + * Simple method to escape XML special characters in String. + * There are five XML special characters which needs to be escaped: + * + *
+     * & - &
+     * < - <
+     * > - >
+     * " - "
+     * ' - '
+     * 
+ * + * @param str the input xml as String to escape, may be null + * @return the escaped xml as String, may be null + */ + public static @Nullable String escapeXml(@Nullable String str) { + if (str == null || str.isEmpty()) { + return str; + } + + str = str.replace("&", "&"); + str = str.replace("<", "<"); + str = str.replace(">", ">"); + str = str.replace("\"", """); + str = str.replace("'", "'"); + return str; + } + + /** + * Capitalizes a String changing the first character to title case. + * No other characters are changed. + * + *
+     * ""       => ""
+     * "cat"    => "Cat"
+     * "cAt"    => "CAt"
+     * "'cat'"  => "'cat'"
+     * 
+ * + * @param val the String to capitalize, may be null + * @return the capitalized String, may be null + */ + public static @Nullable String capitalize(@Nullable String str) { + if (str == null || str.isEmpty()) { + return str; + } + + StringBuilder sb = new StringBuilder(str); + sb.setCharAt(0, Character.toUpperCase(str.charAt(0))); + return sb.toString(); + } + + /** + * Capitalizes words in the string. Only the first char of every word is capitalized, other are set to lowercase. + * Words are recognized by an underscore delimiter. + * + *
+      * "openHAB_is_cool"   => "Openhab_Is_Cool"
+      * "foobar_Example" => "Foobar_Example"
+     * 
+ * + * @param str the String to capitalize, may be null + * @return the capitalized String, may be null + */ + public static @Nullable String capitalizeByUnderscore(@Nullable String str) { + if (str == null || str.isEmpty()) { + return str; + } + + final String delimiter = "_"; + StringBuilder capitalizedFully = new StringBuilder(); + for (String splitStr : str.split(delimiter)) { + if (splitStr.length() > 0) { + capitalizedFully.append(splitStr.substring(0, 1).toUpperCase()); + } + if (splitStr.length() > 1) { + capitalizedFully.append(splitStr.substring(1).toLowerCase()); + } + capitalizedFully.append(delimiter); + } + + capitalizedFully.setLength(Math.max(capitalizedFully.length() - 1, 0)); + return capitalizedFully.toString(); + } + + /** + * Capitalizes words in the string. Only the first char of every word is capitalized, the rest is left as is. + * Words are recognized by any whitespace. + * + *
+      * "openHAB is cool"   => "OpenHAB Is Cool"
+      * "foobar Example" => "Foobar Example"
+     * 
+ * + * @param str the String to capitalize, may be null + * @return the capitalized String, may be null + */ + public static @Nullable String capitalizeByWhitespace(@Nullable String str) { + if (str == null || str.isEmpty()) { + return str; + } + + StringBuilder processed = new StringBuilder(); + for (String splitted : str.split("\\s+")) { + if (splitted.length() > 1) { + processed.append(splitted.substring(0, 1).toUpperCase()); + processed.append(splitted.substring(1)); + } else { + processed.append(splitted.toUpperCase()); + } + processed.append(" "); + } + processed.setLength(Math.max(processed.length() - 1, 0)); + return processed.toString(); + } + + /** + * Pads the string from the left + * + *
+      * padLeft("9", 4, "0")        => "0009"
+      * padLeft("3112", 12, "*")    => "********3112"
+      * padLeft("openHAB", 4, "*")  => "openHAB"
+     * 
+ * + * @param str the String to pad, may be null + * @param minSize the minimum String size to return + * @param padString the String to add when padding + * @return the padded String + */ + public static String padLeft(@Nullable String str, int minSize, String padString) { + if (str == null) { + str = ""; + } + + return String.format("%" + minSize + "s", str).replace(" ", padString); + } + + /** + * Creates a random string + * + * @param length the length of the String to return + * @param charset the characters to use to create the String + * @return the random String + */ + public static String getRandomString(int length, String charset) { + StringBuilder sb = new StringBuilder(length); + SecureRandom secureRandom = new SecureRandom(); + for (int i = 0; i < length; i++) { + final int index = secureRandom.nextInt(charset.length()); + sb.append(charset.charAt(index)); + } + + return sb.toString(); + } + + /** + * Creates a random string with [A-Zaz] characters + * + * @param length the length of the String to return + * @return the random String + */ + public static String getRandomAlphabetic(int length) { + return StringUtils.getRandomString(length, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvxyz"); + } + + /** + * Creates a random string with only hex characters + * + * @param length the length of the String to return + * @return the random String + */ + public static String getRandomHex(int length) { + return StringUtils.getRandomString(length, "0123456789ABCDEF"); + } + + /** + * Creates a random string with [A-Za-z0-9] characters + * + * @param length the length of the String to return + * @return the random String + */ + public static String getRandomAlphanumeric(int length) { + return StringUtils.getRandomString(length, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvxyz"); + } + + /** + * Splits the string by character type into an array + * + *
+     * "ab de fg"   => "ab", " ", "de", " ", "fg";
+     * "ab   de fg" => "ab", "   ", "de", " ", "fg";
+     * "ab:cd:ef"   => "ab", ":", "cd", ":", "ef";
+     * "number5"    =>  "number", "5"
+     * "fooBar"     =>  "foo", "Bar"
+     * "OHRules"    =>  "OH", "Rules"
+     * 
+ * + * @param str the input String to split, may be null + * @return the splitted String + */ + public static String[] splitByCharacterType(@Nullable String str) { + if (str == null || str.isBlank()) { + return new String[0]; + } + + List cache = new ArrayList<>(); + char[] inputAsCharArray = str.toCharArray(); + int prevType = Character.getType(inputAsCharArray[0]); + int prevTypeStart = 0; + for (int i = prevTypeStart + 1; i < inputAsCharArray.length; i++) { + int curType = Character.getType(inputAsCharArray[i]); + if (prevType == curType) { + continue; + } + if (curType == Character.LOWERCASE_LETTER && prevType == Character.UPPERCASE_LETTER) { + int tmpStart = i - 1; + if (tmpStart != prevTypeStart) { + cache.add(new String(inputAsCharArray, prevTypeStart, tmpStart - prevTypeStart)); + prevTypeStart = tmpStart; + } + } else { + cache.add(new String(inputAsCharArray, prevTypeStart, i - prevTypeStart)); + prevTypeStart = i; + } + prevType = curType; + } + cache.add(new String(inputAsCharArray, prevTypeStart, inputAsCharArray.length - prevTypeStart)); + return cache.toArray(String[]::new); + } + + /** + * Simple method to un escape XML special characters in String. + * There are five XML Special characters which needs to be escaped: + * + *
+     * & => &
+     * < => <
+     * > => >
+     * " => "
+     * ' => '
+     * 
+ * + * @param input the input xml as String to unescape, may be null + * @return the unescaped xml as String, may be null + */ + public static @Nullable String unEscapeXml(@Nullable String str) { + if (str == null || str.isEmpty()) { + return str; + } + str = str.replace("&", "&"); + str = str.replace("<", "<"); + str = str.replace(">", ">"); + str = str.replace(""", "\""); + str = str.replace("'", "'"); + return str; + } +} diff --git a/bundles/org.openhab.core/src/test/java/org/openhab/core/util/StringUtilsTest.java b/bundles/org.openhab.core/src/test/java/org/openhab/core/util/StringUtilsTest.java new file mode 100644 index 00000000000..ae9bda01b5f --- /dev/null +++ b/bundles/org.openhab.core/src/test/java/org/openhab/core/util/StringUtilsTest.java @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2010-2023 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package org.openhab.core.util; + +import static org.junit.jupiter.api.Assertions.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; + +/** + * The {@link StringUtils} class defines some static string utility methods + * + * @author Leo Siepel - Initial contribution + */ +@NonNullByDefault +public class StringUtilsTest { + + @Test + public void chompTest() { + assertEquals("", StringUtils.chomp("")); + assertEquals(null, StringUtils.chomp(null)); + assertEquals("abc ", StringUtils.chomp("abc \r")); + assertEquals("abc", StringUtils.chomp("abc\n")); + assertEquals("abc", StringUtils.chomp("abc\r\n")); + assertEquals("abc\r\n", StringUtils.chomp("abc\r\n\r\n")); + assertEquals("abc\n", StringUtils.chomp("abc\n\r")); + assertEquals("abc\n\rabc", StringUtils.chomp("abc\n\rabc")); + assertEquals("", StringUtils.chomp("\r")); + assertEquals("", StringUtils.chomp("\n")); + assertEquals("", StringUtils.chomp("\r\n")); + } + + @Test + public void escapeXmlTest() { + assertEquals(null, StringUtils.escapeXml(null)); + assertEquals(" ", StringUtils.escapeXml(" ")); + assertEquals("invalidxml", StringUtils.escapeXml("invalidxml")); + assertEquals("<xmlExample>&</xmlExample>", StringUtils.escapeXml("&")); + assertEquals("<xmlExample>"</xmlExample>", + StringUtils.escapeXml("\"")); + assertEquals("<xmlExample>'</xmlExample>", + StringUtils.escapeXml("\'")); + } + + @Test + public void capitalizeTest() { + assertEquals(null, StringUtils.capitalize(null)); + assertEquals(" ", StringUtils.capitalize(" ")); + assertEquals("Cat", StringUtils.capitalize("cat")); + assertEquals("CAt", StringUtils.capitalize("cAt")); + assertEquals("'cat'", StringUtils.capitalize("'cat'")); + } + + @Test + public void capitalizeAllWordsTest() { + assertEquals(null, StringUtils.capitalizeByUnderscore(null)); + assertEquals("Openhab_Is_Cool", StringUtils.capitalizeByUnderscore("openHAB_is_cool")); + assertEquals("Foobar_Example", StringUtils.capitalizeByUnderscore("foobar_Example")); + assertEquals("'another_Test'", StringUtils.capitalizeByUnderscore("'another_test'")); + } + + @Test + public void capitalizeWordsTest() { + assertEquals("OpenHAB Is Cool", StringUtils.capitalizeByWhitespace("openHAB is cool")); + assertEquals("Foobar Example", StringUtils.capitalizeByWhitespace("foobar Example")); + assertEquals("'another Test'", StringUtils.capitalizeByWhitespace("'another test'")); + } + + @Test + public void getRandomString() { + String randomstring = StringUtils.getRandomString(10000, "123"); + assertEquals(6666, randomstring.replace("1", "").length(), 333, + "randomString does not equaly (<5% delta) use all characters in set"); + } + + @Test + public void padLeft() { + assertEquals("000000", StringUtils.padLeft("", 6, "0")); + assertEquals("000000", StringUtils.padLeft(null, 6, "0")); + assertEquals("000teststr", StringUtils.padLeft("teststr", 10, "0")); + assertEquals("AAAAAAp3RF@CT", StringUtils.padLeft("p3RF@CT", 13, "A")); + assertEquals("nopaddingshouldhappen", StringUtils.padLeft("nopaddingshouldhappen", 21, "x")); + assertEquals("LongerStringThenMinSize", StringUtils.padLeft("LongerStringThenMinSize", 10, "x")); + } + + @Test + public void splitByCharacterType() { + assertArrayEquals(new String[0], StringUtils.splitByCharacterType(null)); + assertArrayEquals(new String[0], StringUtils.splitByCharacterType("")); + assertArrayEquals(new String[] { "ab", " ", "de", " ", "fg" }, StringUtils.splitByCharacterType("ab de fg")); + assertArrayEquals(new String[] { "ab", " ", "de", " ", "fg" }, + StringUtils.splitByCharacterType("ab de fg")); + assertArrayEquals(new String[] { "ab", ":", "cd", ":", "ef" }, StringUtils.splitByCharacterType("ab:cd:ef")); + assertArrayEquals(new String[] { "number", "5" }, StringUtils.splitByCharacterType("number5")); + assertArrayEquals(new String[] { "foo", "Bar" }, StringUtils.splitByCharacterType("fooBar")); + assertArrayEquals(new String[] { "foo", "200", "Bar" }, StringUtils.splitByCharacterType("foo200Bar")); + assertArrayEquals(new String[] { "ASF", "Rules" }, StringUtils.splitByCharacterType("ASFRules")); + } +}