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
+ * & => & + * < => < + * > => > + * " => " + * ' => ' + *+ * + * @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("