diff --git a/server/src/main/java/org/elasticsearch/common/Strings.java b/server/src/main/java/org/elasticsearch/common/Strings.java index 8fcc3ab92d11b..5dc461de2ed97 100644 --- a/server/src/main/java/org/elasticsearch/common/Strings.java +++ b/server/src/main/java/org/elasticsearch/common/Strings.java @@ -29,6 +29,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.StringTokenizer; import java.util.TreeSet; @@ -40,72 +41,16 @@ public class Strings { public static final String[] EMPTY_ARRAY = new String[0]; - public static void spaceify(int spaces, String from, StringBuilder to) throws Exception { + public static void spaceify(int spaces, String from, StringBuilder to) throws IOException { + char[] spaceChars = new char[spaces]; + Arrays.fill(spaceChars, ' '); + try (BufferedReader reader = new BufferedReader(new StringReader(from))) { String line; while ((line = reader.readLine()) != null) { - for (int i = 0; i < spaces; i++) { - to.append(' '); - } - to.append(line).append('\n'); - } - } - } - - /** - * Splits a backslash escaped string on the separator. - *

- * Current backslash escaping supported: - *
\n \t \r \b \f are escaped the same as a Java String - *
Other characters following a backslash are produced verbatim (\c => c) - * - * @param s the string to split - * @param separator the separator to split on - * @param decode decode backslash escaping - */ - public static List splitSmart(String s, String separator, boolean decode) { - ArrayList lst = new ArrayList<>(2); - StringBuilder sb = new StringBuilder(); - int pos = 0, end = s.length(); - while (pos < end) { - if (s.startsWith(separator, pos)) { - if (sb.length() > 0) { - lst.add(sb.toString()); - sb = new StringBuilder(); - } - pos += separator.length(); - continue; - } - - char ch = s.charAt(pos++); - if (ch == '\\') { - if (decode == false) { - sb.append(ch); - } - if (pos >= end) { - break; // ERROR, or let it go? - } - ch = s.charAt(pos++); - if (decode) { - ch = switch (ch) { - case 'n' -> '\n'; - case 't' -> '\t'; - case 'r' -> '\r'; - case 'b' -> '\b'; - case 'f' -> '\f'; - default -> ch; - }; - } + to.append(spaceChars).append(line).append('\n'); } - - sb.append(ch); } - - if (sb.length() > 0) { - lst.add(sb.toString()); - } - - return lst; } // --------------------------------------------------------------------- @@ -127,7 +72,7 @@ public static List splitSmart(String s, String separator, boolean decode * @see #hasText(String) */ public static boolean hasLength(CharSequence str) { - return (str != null && str.length() > 0); + return (str != null && str.isEmpty() == false); } /** @@ -192,13 +137,7 @@ public static boolean hasText(CharSequence str) { if (hasLength(str) == false) { return false; } - int strLen = str.length(); - for (int i = 0; i < strLen; i++) { - if (Character.isWhitespace(str.charAt(i)) == false) { - return true; - } - } - return false; + return str.chars().anyMatch(c -> Character.isWhitespace(c) == false); } /** @@ -226,11 +165,11 @@ public static String trimLeadingCharacter(String str, char leadingCharacter) { if (hasLength(str) == false) { return str; } - StringBuilder sb = new StringBuilder(str); - while (sb.length() > 0 && sb.charAt(0) == leadingCharacter) { - sb.deleteCharAt(0); + int i = 0; + while (i < str.length() && str.charAt(i) == leadingCharacter) { + i++; } - return sb.toString(); + return str.substring(i); } /** @@ -270,7 +209,7 @@ public static String replace(String inString, String oldPattern, String newPatte // the index of an occurrence we've found, or -1 int patLen = oldPattern.length(); while (index >= 0) { - sb.append(inString.substring(pos, index)); + sb.append(inString, pos, index); sb.append(newPattern); pos = index + patLen; index = inString.indexOf(oldPattern, pos); @@ -289,17 +228,29 @@ public static String replace(String inString, String oldPattern, String newPatte * @return the resulting String */ public static String deleteAny(String inString, String charsToDelete) { + return inString != null ? deleteAny((CharSequence) inString, charsToDelete).toString() : null; + } + + /** + * Delete any character in a given CharSequence. + * + * @param inString the original CharSequence + * @param charsToDelete a set of characters to delete. + * E.g. "az\n" will delete 'a's, 'z's and new lines. + * @return the resulting CharSequence + */ + public static CharSequence deleteAny(CharSequence inString, String charsToDelete) { if (hasLength(inString) == false || hasLength(charsToDelete) == false) { return inString; } - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(inString.length()); for (int i = 0; i < inString.length(); i++) { char c = inString.charAt(i); if (charsToDelete.indexOf(c) == -1) { sb.append(c); } } - return sb.toString(); + return sb; } // --------------------------------------------------------------------- @@ -322,37 +273,24 @@ private static String changeFirstCharacterCase(String str, boolean capitalize) { if (str == null || str.length() == 0) { return str; } - StringBuilder sb = new StringBuilder(str.length()); - if (capitalize) { - sb.append(Character.toUpperCase(str.charAt(0))); - } else { - sb.append(Character.toLowerCase(str.charAt(0))); + char newChar = capitalize ? Character.toUpperCase(str.charAt(0)) : Character.toLowerCase(str.charAt(0)); + if (newChar == str.charAt(0)) { + return str; // nothing changed } - sb.append(str.substring(1)); - return sb.toString(); + + return newChar + str.substring(1); } - public static final String INVALID_FILENAME_CHARS = "[" - + Stream.of('\\', '/', '*', '?', '"', '<', '>', '|', ' ', ',').map(c -> "'" + c + "'").collect(Collectors.joining(",")) - + "]"; + public static final String INVALID_FILENAME_CHARS = Stream.of('\\', '/', '*', '?', '"', '<', '>', '|', ' ', ',') + .map(c -> "'" + c + "'") + .collect(Collectors.joining(",", "[", "]")); public static boolean validFileName(String fileName) { - for (int i = 0; i < fileName.length(); i++) { - if (isInvalidFileNameCharacter(fileName.charAt(i))) { - return false; - } - } - return true; + return fileName.chars().noneMatch(c -> isInvalidFileNameCharacter((char) c)); } public static boolean validFileNameExcludingAstrix(String fileName) { - for (int i = 0; i < fileName.length(); i++) { - char c = fileName.charAt(i); - if (c != '*' && isInvalidFileNameCharacter(c)) { - return false; - } - } - return true; + return fileName.chars().noneMatch(c -> c != '*' && isInvalidFileNameCharacter((char) c)); } private static boolean isInvalidFileNameCharacter(char c) { @@ -374,7 +312,7 @@ public static String[] toStringArray(Collection collection) { if (collection == null) { return null; } - return collection.toArray(new String[collection.size()]); + return collection.toArray(String[]::new); } /** @@ -532,21 +470,31 @@ public static String[] delimitedListToStringArray(String str, String delimiter, if (delimiter == null) { return new String[] { str }; } - List result = new ArrayList<>(); - if ("".equals(delimiter)) { + List result; + if (delimiter.isEmpty()) { + // split on every character + result = new ArrayList<>(str.length()); + if (charsToDelete == null) { + charsToDelete = ""; + } for (int i = 0; i < str.length(); i++) { - result.add(deleteAny(str.substring(i, i + 1), charsToDelete)); + if (charsToDelete.indexOf(str.charAt(i)) == -1) { + result.add(Character.toString(str.charAt(i))); + } else { + result.add(""); + } } } else { + result = new ArrayList<>(); int pos = 0; int delPos; while ((delPos = str.indexOf(delimiter, pos)) != -1) { - result.add(deleteAny(str.substring(pos, delPos), charsToDelete)); + result.add(deleteAny(str.subSequence(pos, delPos), charsToDelete).toString()); pos = delPos + delimiter.length(); } if (str.length() > 0 && pos <= str.length()) { // Add rest of String, but not in case of empty input. - result.add(deleteAny(str.substring(pos), charsToDelete)); + result.add(deleteAny(str.subSequence(pos, str.length()), charsToDelete).toString()); } } return toStringArray(result); @@ -570,10 +518,8 @@ public static String[] commaDelimitedListToStringArray(String str) { * @return a Set of String entries in the list */ public static Set commaDelimitedListToSet(String str) { - Set set = new TreeSet<>(); String[] tokens = commaDelimitedListToStringArray(str); - set.addAll(Arrays.asList(tokens)); - return set; + return new TreeSet<>(Arrays.asList(tokens)); } /** @@ -939,38 +885,19 @@ public static boolean isNullOrBlank(@Nullable String s) { return s == null || s.isBlank(); } - public static String coalesceToEmpty(@Nullable String s) { - return s == null ? "" : s; - } - public static String padStart(String s, int minimumLength, char c) { - if (s == null) { - throw new NullPointerException("s"); - } + Objects.requireNonNull(s, "s"); if (s.length() >= minimumLength) { return s; } else { - StringBuilder sb = new StringBuilder(minimumLength); - for (int i = s.length(); i < minimumLength; i++) { - sb.append(c); - } - - sb.append(s); - return sb.toString(); + return Character.toString(c).repeat(minimumLength - s.length()) + s; } } public static String toLowercaseAscii(String in) { - StringBuilder out = new StringBuilder(); - Iterator iter = in.codePoints().iterator(); - while (iter.hasNext()) { - int codepoint = iter.next(); - if (codepoint > 128) { - out.appendCodePoint(codepoint); - } else { - out.appendCodePoint(Character.toLowerCase(codepoint)); - } - } - return out.toString(); + return in.codePoints() + .map(cp -> cp <= 128 ? Character.toLowerCase(cp) : cp) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString(); } } diff --git a/server/src/test/java/org/elasticsearch/common/StringsTests.java b/server/src/test/java/org/elasticsearch/common/StringsTests.java index ef005df4eaac7..8e25ee55652c5 100644 --- a/server/src/test/java/org/elasticsearch/common/StringsTests.java +++ b/server/src/test/java/org/elasticsearch/common/StringsTests.java @@ -14,43 +14,111 @@ import org.elasticsearch.xcontent.ToXContentObject; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import static org.elasticsearch.common.Strings.cleanTruncate; +import static org.elasticsearch.common.Strings.collectionToDelimitedString; +import static org.elasticsearch.common.Strings.collectionToDelimitedStringWithLimit; +import static org.elasticsearch.common.Strings.deleteAny; +import static org.elasticsearch.common.Strings.delimitedListToStringArray; +import static org.elasticsearch.common.Strings.hasLength; +import static org.elasticsearch.common.Strings.hasText; +import static org.elasticsearch.common.Strings.isAllOrWildcard; +import static org.elasticsearch.common.Strings.isEmpty; +import static org.elasticsearch.common.Strings.padStart; +import static org.elasticsearch.common.Strings.spaceify; +import static org.elasticsearch.common.Strings.substring; +import static org.elasticsearch.common.Strings.toLowercaseAscii; +import static org.elasticsearch.common.Strings.tokenizeByCommaToSet; +import static org.elasticsearch.common.Strings.trimLeadingCharacter; import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.emptyArray; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasToString; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.lessThanOrEqualTo; public class StringsTests extends ESTestCase { + public void testSpaceify() throws Exception { + String[] lines = new String[] { randomAlphaOfLength(5), randomAlphaOfLength(5), randomAlphaOfLength(5) }; + + // spaceify always finishes with \n regardless of input + StringBuilder sb = new StringBuilder(); + spaceify(4, String.join("\n", lines), sb); + assertThat(sb.toString(), equalTo(Arrays.stream(lines).map(s -> " ".repeat(4) + s).collect(Collectors.joining("\n", "", "\n")))); + + sb = new StringBuilder(); + spaceify(0, String.join("\n", lines), sb); + assertThat(sb.toString(), equalTo(Arrays.stream(lines).collect(Collectors.joining("\n", "", "\n")))); + } + + public void testHasLength() { + assertFalse(hasLength((String) null)); + assertFalse(hasLength("")); + assertTrue(hasLength(" ")); + assertTrue(hasLength("Hello")); + + assertTrue(hasLength("\0")); + } + + public void testIsEmpty() { + assertTrue(isEmpty(null)); + assertTrue(isEmpty("")); + assertFalse(isEmpty(" ")); + assertFalse(isEmpty("Hello")); + + assertFalse(isEmpty("\0")); + } + + public void testHasText() { + assertFalse(hasText(null)); + assertFalse(hasText("")); + assertFalse(hasText(" ")); + assertTrue(hasText("12345")); + assertTrue(hasText(" 12345 ")); + + String asciiWhitespace = IntStream.rangeClosed(0, 32) + .filter(Character::isWhitespace) + .mapToObj(Character::toString) + .collect(Collectors.joining()); + assertFalse(hasText(asciiWhitespace)); + assertTrue(hasText("\ud855\udddd")); + } + public void testIsAllOrWildCardString() { - assertThat(Strings.isAllOrWildcard("_all"), is(true)); - assertThat(Strings.isAllOrWildcard("*"), is(true)); - assertThat(Strings.isAllOrWildcard("foo"), is(false)); - assertThat(Strings.isAllOrWildcard(""), is(false)); - assertThat(Strings.isAllOrWildcard((String) null), is(false)); + assertThat(isAllOrWildcard("_all"), is(true)); + assertThat(isAllOrWildcard("*"), is(true)); + assertThat(isAllOrWildcard("foo"), is(false)); + assertThat(isAllOrWildcard(""), is(false)); + assertThat(isAllOrWildcard((String) null), is(false)); } public void testSubstring() { - assertEquals(null, Strings.substring(null, 0, 1000)); - assertEquals("foo", Strings.substring("foo", 0, 1000)); - assertEquals("foo", Strings.substring("foo", 0, 3)); - assertEquals("oo", Strings.substring("foo", 1, 3)); - assertEquals("oo", Strings.substring("foo", 1, 100)); - assertEquals("f", Strings.substring("foo", 0, 1)); + assertNull(substring(null, 0, 1000)); + assertEquals("foo", substring("foo", 0, 1000)); + assertEquals("foo", substring("foo", 0, 3)); + assertEquals("oo", substring("foo", 1, 3)); + assertEquals("oo", substring("foo", 1, 100)); + assertEquals("f", substring("foo", 0, 1)); } public void testCleanTruncate() { - assertEquals(null, Strings.cleanTruncate(null, 10)); - assertEquals("foo", Strings.cleanTruncate("foo", 10)); - assertEquals("foo", Strings.cleanTruncate("foo", 3)); + assertNull(cleanTruncate(null, 10)); + assertEquals("foo", cleanTruncate("foo", 10)); + assertEquals("foo", cleanTruncate("foo", 3)); // Throws out high surrogates - assertEquals("foo", Strings.cleanTruncate("foo\uD83D\uDEAB", 4)); + assertEquals("foo", cleanTruncate("foo\uD83D\uDEAB", 4)); // But will keep the whole character - assertEquals("foo\uD83D\uDEAB", Strings.cleanTruncate("foo\uD83D\uDEAB", 5)); + assertEquals("foo\uD83D\uDEAB", cleanTruncate("foo\uD83D\uDEAB", 5)); /* * Doesn't take care around combining marks. This example has its * meaning changed because that last codepoint is supposed to combine @@ -58,8 +126,13 @@ public void testCleanTruncate() { * circle around it with a slash through it. As in "no 'o's allowed * here. */ - assertEquals("o", Strings.cleanTruncate("o\uD83D\uDEAB", 1)); - assertEquals("", Strings.cleanTruncate("foo", 0)); + assertEquals("o", cleanTruncate("o\uD83D\uDEAB", 1)); + assertEquals("", cleanTruncate("foo", 0)); + } + + public void testTrimLeadingCharacter() { + assertThat(trimLeadingCharacter("abcdef", 'g'), equalTo("abcdef")); + assertThat(trimLeadingCharacter("aaabcdef", 'a'), equalTo("bcdef")); } public void testToStringToXContent() { @@ -107,17 +180,59 @@ public void testToStringToXContentWithOrWithoutParams() { ); } + public void testDeleteAny() { + assertNull(deleteAny((CharSequence) null, "abc")); + assertNull(deleteAny((String) null, "abc")); + assertThat(deleteAny(new StringBuilder("foo"), null), hasToString("foo")); + assertThat(deleteAny("foo", null), equalTo("foo")); + + assertThat(deleteAny("abc\ndef\t", "az\n"), equalTo("bcdef\t")); + + String testStr = randomUnicodeOfLength(10); + String delete = testStr.substring(testStr.length() - 1) + testStr.substring(0, 1); + assertThat(deleteAny(testStr, delete), equalTo(testStr.substring(1, testStr.length() - 1))); + assertThat(deleteAny(new StringBuilder(testStr), delete), hasToString(testStr.substring(1, testStr.length() - 1))); + + // this method doesn't really work with surrogates + } + public void testSplitStringToSet() { - assertEquals(Strings.tokenizeByCommaToSet(null), Sets.newHashSet()); - assertEquals(Strings.tokenizeByCommaToSet(""), Sets.newHashSet()); - assertEquals(Strings.tokenizeByCommaToSet("a,b,c"), Sets.newHashSet("a", "b", "c")); - assertEquals(Strings.tokenizeByCommaToSet("a, b, c"), Sets.newHashSet("a", "b", "c")); - assertEquals(Strings.tokenizeByCommaToSet(" a , b, c "), Sets.newHashSet("a", "b", "c")); - assertEquals(Strings.tokenizeByCommaToSet("aa, bb, cc"), Sets.newHashSet("aa", "bb", "cc")); - assertEquals(Strings.tokenizeByCommaToSet(" a "), Sets.newHashSet("a")); - assertEquals(Strings.tokenizeByCommaToSet(" a "), Sets.newHashSet("a")); - assertEquals(Strings.tokenizeByCommaToSet(" aa "), Sets.newHashSet("aa")); - assertEquals(Strings.tokenizeByCommaToSet(" "), Sets.newHashSet()); + assertEquals(tokenizeByCommaToSet(null), Sets.newHashSet()); + assertEquals(tokenizeByCommaToSet(""), Sets.newHashSet()); + assertEquals(tokenizeByCommaToSet("a,b,c"), Sets.newHashSet("a", "b", "c")); + assertEquals(tokenizeByCommaToSet("a, b, c"), Sets.newHashSet("a", "b", "c")); + assertEquals(tokenizeByCommaToSet(" a , b, c "), Sets.newHashSet("a", "b", "c")); + assertEquals(tokenizeByCommaToSet("aa, bb, cc"), Sets.newHashSet("aa", "bb", "cc")); + assertEquals(tokenizeByCommaToSet(" a "), Sets.newHashSet("a")); + assertEquals(tokenizeByCommaToSet(" a "), Sets.newHashSet("a")); + assertEquals(tokenizeByCommaToSet(" aa "), Sets.newHashSet("aa")); + assertEquals(tokenizeByCommaToSet(" "), Sets.newHashSet()); + } + + public void testDelimitedListToStringArray() { + String testStr; + assertThat(delimitedListToStringArray(null, " ", "a"), emptyArray()); + // NOTE: current behaviour is to not delete anything if the delimiter is null + assertThat(delimitedListToStringArray(testStr = randomAlphaOfLength(10), null, "a"), arrayContaining(testStr)); + assertThat( + delimitedListToStringArray(testStr = randomAlphaOfLength(10), "", null), + arrayContaining(testStr.chars().mapToObj(Character::toString).toArray()) + ); + assertThat( + delimitedListToStringArray("bcdabceabcdf", "", "a"), + arrayContaining("b", "c", "d", "", "b", "c", "e", "", "b", "c", "d", "f") + ); + assertThat( + delimitedListToStringArray("bcdabceabcdf", "", "da"), + arrayContaining("b", "c", "", "", "b", "c", "e", "", "b", "c", "", "f") + ); + assertThat( + delimitedListToStringArray("abcdabceabcdf", "", "da"), + arrayContaining("", "b", "c", "", "", "b", "c", "e", "", "b", "c", "", "f") + ); + assertThat(delimitedListToStringArray("abcd,abce,abcdf", ",", "da"), arrayContaining("bc", "bce", "bcf")); + assertThat(delimitedListToStringArray("abcd,abce,abcdf,", ",", "da"), arrayContaining("bc", "bce", "bcf", "")); + assertThat(delimitedListToStringArray("abcd,abce,abcdf,bcad,a", ",a", "d"), arrayContaining("abc", "bce", "bcf,bca", "")); } public void testCollectionToDelimitedStringWithLimitZero() { @@ -134,7 +249,7 @@ public void testCollectionToDelimitedStringWithLimitZero() { } final StringBuilder stringBuilder = new StringBuilder(); - Strings.collectionToDelimitedStringWithLimit(strings, delimiter, prefix, suffix, 0, stringBuilder); + collectionToDelimitedStringWithLimit(strings, delimiter, prefix, suffix, 0, stringBuilder); final String completelyTruncatedDescription = stringBuilder.toString(); if (count == 0) { @@ -162,11 +277,11 @@ public void testCollectionToDelimitedStringWithLimitTruncation() { strings.add(randomAlphaOfLength(between(minLength, 10))); } - final int fullDescriptionLength = Strings.collectionToDelimitedString(strings, delimiter, prefix, suffix).length(); + final int fullDescriptionLength = collectionToDelimitedString(strings, delimiter, prefix, suffix).length(); final int lastItemSize = prefix.length() + strings.get(count - 1).length() + suffix.length(); final int truncatedLength = between(0, fullDescriptionLength - lastItemSize - 1); final StringBuilder stringBuilder = new StringBuilder(); - Strings.collectionToDelimitedStringWithLimit(strings, delimiter, prefix, suffix, truncatedLength, stringBuilder); + collectionToDelimitedStringWithLimit(strings, delimiter, prefix, suffix, truncatedLength, stringBuilder); final String truncatedDescription = stringBuilder.toString(); assertThat(truncatedDescription, allOf(containsString("... (" + count + " in total,"), endsWith(" omitted)"))); @@ -189,7 +304,7 @@ public void testCollectionToDelimitedStringWithLimitNoTruncation() { strings.add(randomAlphaOfLength(between(0, 10))); } - final String fullDescription = Strings.collectionToDelimitedString(strings, delimiter, prefix, suffix); + final String fullDescription = collectionToDelimitedString(strings, delimiter, prefix, suffix); for (String string : strings) { assertThat(fullDescription, containsString(prefix + string + suffix)); } @@ -199,7 +314,40 @@ public void testCollectionToDelimitedStringWithLimitNoTruncation() { final int limit = randomFrom(between(minLimit, fullDescription.length()), between(minLimit, Integer.MAX_VALUE), Integer.MAX_VALUE); final StringBuilder stringBuilder = new StringBuilder(); - Strings.collectionToDelimitedStringWithLimit(strings, delimiter, prefix, suffix, limit, stringBuilder); + collectionToDelimitedStringWithLimit(strings, delimiter, prefix, suffix, limit, stringBuilder); assertThat(stringBuilder.toString(), equalTo(fullDescription)); } + + public void testPadStart() { + String testStr; + assertThat(padStart("", 5, 'a'), equalTo("aaaaa")); + assertThat(padStart(testStr = randomAlphaOfLength(6), 10, ' '), equalTo(" ".repeat(4) + testStr)); + assertThat(padStart(testStr = randomAlphaOfLength(6), 5, ' '), equalTo(testStr)); + assertThat(padStart(testStr = randomAlphaOfLength(6), 10, 'f'), equalTo("f".repeat(4) + testStr)); + } + + public void testToLowercaseAscii() { + String testStr; + assertThat(toLowercaseAscii(""), equalTo("")); + assertThat(toLowercaseAscii(testStr = randomAlphaOfLength(5)), equalTo(testStr.toLowerCase(Locale.ROOT))); + + // all ascii characters + testStr = IntStream.rangeClosed(0, 255).mapToObj(i -> Character.toString((char) i)).collect(Collectors.joining()); + assertThat(toLowercaseAscii(testStr), equalTo(lowercaseAsciiOnly(testStr))); + + // sling in some unicode too + assertThat(toLowercaseAscii(testStr = randomUnicodeOfCodepointLength(20)), equalTo(lowercaseAsciiOnly(testStr))); + } + + private static String lowercaseAsciiOnly(String s) { + // explicitly lowercase just ascii characters + StringBuilder sb = new StringBuilder(s); + for (int i = 0; i < sb.length(); i++) { + char c = sb.charAt(i); + if (c >= 'A' && c <= 'Z') { + sb.setCharAt(i, (char) (sb.charAt(i) + 32)); + } + } + return sb.toString(); + } } diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/process/normalizer/Normalizer.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/process/normalizer/Normalizer.java index 7d61cbdb1c98d..916b59b0558fa 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/process/normalizer/Normalizer.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/process/normalizer/Normalizer.java @@ -9,12 +9,12 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.common.Strings; import org.elasticsearch.xpack.ml.job.process.normalizer.output.NormalizerResultHandler; import java.io.IOException; import java.util.Iterator; import java.util.List; +import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; @@ -104,12 +104,12 @@ private static void writeNormalizableAndChildrenRecursively(Normalizable normali process.writeRecord( new String[] { normalizable.getLevel().asString(), - Strings.coalesceToEmpty(normalizable.getPartitionFieldName()), - Strings.coalesceToEmpty(normalizable.getPartitionFieldValue()), - Strings.coalesceToEmpty(normalizable.getPersonFieldName()), - Strings.coalesceToEmpty(normalizable.getPersonFieldValue()), - Strings.coalesceToEmpty(normalizable.getFunctionName()), - Strings.coalesceToEmpty(normalizable.getValueFieldName()), + Objects.requireNonNullElse(normalizable.getPartitionFieldName(), ""), + Objects.requireNonNullElse(normalizable.getPartitionFieldValue(), ""), + Objects.requireNonNullElse(normalizable.getPersonFieldName(), ""), + Objects.requireNonNullElse(normalizable.getPersonFieldValue(), ""), + Objects.requireNonNullElse(normalizable.getFunctionName(), ""), + Objects.requireNonNullElse(normalizable.getValueFieldName(), ""), Double.toString(normalizable.getProbability()), Double.toString(normalizable.getNormalizedScore()) } ); diff --git a/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/cli/EmbeddedCli.java b/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/cli/EmbeddedCli.java index c1e9df46af066..6a1477cf05d3c 100644 --- a/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/cli/EmbeddedCli.java +++ b/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/cli/EmbeddedCli.java @@ -11,7 +11,6 @@ import org.elasticsearch.cli.MockTerminal; import org.elasticsearch.cli.ProcessInfo; import org.elasticsearch.cli.Terminal; -import org.elasticsearch.common.Strings; import org.elasticsearch.core.IOUtils; import org.elasticsearch.core.Nullable; import org.elasticsearch.xpack.sql.cli.Cli; @@ -29,6 +28,7 @@ import java.io.PipedOutputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -274,7 +274,7 @@ public String command(String command) throws IOException { * while we're typing a command. */ private List expectedCommandEchos(String command) { - List commandLines = Strings.splitSmart(command, "\n", false); + List commandLines = Arrays.stream(command.split("\n")).filter(s -> s.isEmpty() == false).toList(); List result = new ArrayList<>(commandLines.size() * 2); result.add("[?1h=[?2004h[33msql> [0m" + commandLines.get(0)); // Every line gets an extra new line because, I dunno, but it looks right in the CLI diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/string/StringFunctionUtils.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/string/StringFunctionUtils.java deleted file mode 100644 index 406520787f3dd..0000000000000 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/string/StringFunctionUtils.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -package org.elasticsearch.xpack.sql.expression.function.scalar.string; - -import static org.elasticsearch.common.Strings.hasLength; - -final class StringFunctionUtils { - - private StringFunctionUtils() {} - - /** - * Extract a substring from the given string, using start index and length of the extracted substring. - * - * @param s the original String - * @param start starting position for the substring within the original string. 0-based index position - * @param length length in characters of the subtracted substring - * @return the resulting String - */ - static String substring(String s, int start, int length) { - if (hasLength(s) == false) { - return s; - } - - if (start < 0) { - start = 0; - } - - if (start + 1 > s.length() || length < 0) { - return ""; - } - - return (start + length > s.length()) ? s.substring(start) : s.substring(start, start + length); - } -} diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/string/SubstringFunctionProcessor.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/string/SubstringFunctionProcessor.java index f31264350b069..8fe0bca193a3d 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/string/SubstringFunctionProcessor.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/string/SubstringFunctionProcessor.java @@ -6,6 +6,7 @@ */ package org.elasticsearch.xpack.sql.expression.function.scalar.string; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.xpack.ql.expression.gen.processor.Processor; @@ -59,11 +60,11 @@ public static Object doProcess(Object input, Object start, Object length) { Check.isFixedNumberAndInRange(start, "start", (long) Integer.MIN_VALUE + 1, (long) Integer.MAX_VALUE); Check.isFixedNumberAndInRange(length, "length", 0L, (long) Integer.MAX_VALUE); - return StringFunctionUtils.substring( - input instanceof Character ? input.toString() : (String) input, - ((Number) start).intValue() - 1, // SQL is 1-based when it comes to string manipulation - ((Number) length).intValue() - ); + String s = input instanceof Character ? input.toString() : (String) input; + int strStart = ((Number) start).intValue() - 1; // SQL is 1-based when it comes to string manipulation + strStart = Math.min(Math.max(0, strStart), s.length()); // sanitise string start index + + return Strings.substring(s, strStart, strStart + ((Number) length).intValue()); } protected Processor input() {