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() {