This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 6a7bd4ac0c StringUtils improvements
6a7bd4ac0c is described below
commit 6a7bd4ac0ccd175d8fee1d81fc2151b244a14c49
Author: James Bognar <[email protected]>
AuthorDate: Sun Nov 30 11:24:54 2025 -0500
StringUtils improvements
---
.../apache/juneau/common/utils/StringUtils.java | 238 ++++++++++++++++++---
1 file changed, 205 insertions(+), 33 deletions(-)
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/StringUtils.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/StringUtils.java
index e3f5dd903b..344815190a 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/StringUtils.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/StringUtils.java
@@ -294,11 +294,28 @@ public class StringUtils {
/**
* Returns the character at the specified index in the string without
throwing exceptions.
*
+ * <p>
+ * This is a null-safe and bounds-safe version of {@link
String#charAt(int)}.
+ * Returns <c>0</c> (null character) if:
+ * <ul>
+ * <li>The string is <jk>null</jk></li>
+ * <li>The index is negative</li>
+ * <li>The index is greater than or equal to the string length</li>
+ * </ul>
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * charAt(<js>"Hello"</js>, 0); <jc>// 'H'</jc>
+ * charAt(<js>"Hello"</js>, 4); <jc>// 'o'</jc>
+ * charAt(<js>"Hello"</js>, 5); <jc>// 0 (out of bounds)</jc>
+ * charAt(<js>"Hello"</js>, -1); <jc>// 0 (out of bounds)</jc>
+ * charAt(<jk>null</jk>, 0); <jc>// 0 (null string)</jc>
+ * </p>
+ *
* @param s The string.
* @param i The index position.
- * @return
- * The character at the specified index, or <c>0</c> if the index
is out-of-range or the string
- * is <jk>null</jk>.
+ * @return The character at the specified index, or <c>0</c> if the
index is out-of-range or the string is <jk>null</jk>.
+ * @see String#charAt(int)
*/
public static char charAt(String s, int i) {
if (s == null || i < 0 || i >= s.length())
@@ -326,11 +343,31 @@ public class StringUtils {
}
/**
- * Compares two strings, but gracefully handles <jk>nulls</jk>.
+ * Compares two strings lexicographically, but gracefully handles
<jk>null</jk> values.
+ *
+ * <p>
+ * Null handling:
+ * <ul>
+ * <li>Both <jk>null</jk> → returns <c>0</c> (equal)</li>
+ * <li>First <jk>null</jk> → returns {@link Integer#MIN_VALUE}</li>
+ * <li>Second <jk>null</jk> → returns {@link Integer#MAX_VALUE}</li>
+ * <li>Neither <jk>null</jk> → returns the same as {@link
String#compareTo(String)}</li>
+ * </ul>
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * compare(<js>"apple"</js>, <js>"banana"</js>); <jc>// negative
(apple < banana)</jc>
+ * compare(<js>"banana"</js>, <js>"apple"</js>); <jc>// positive
(banana > apple)</jc>
+ * compare(<js>"apple"</js>, <js>"apple"</js>); <jc>// 0
(equal)</jc>
+ * compare(<jk>null</jk>, <jk>null</jk>); <jc>// 0
(equal)</jc>
+ * compare(<jk>null</jk>, <js>"apple"</js>); <jc>//
Integer.MIN_VALUE</jc>
+ * compare(<js>"apple"</js>, <jk>null</jk>); <jc>//
Integer.MAX_VALUE</jc>
+ * </p>
*
* @param s1 The first string.
* @param s2 The second string.
- * @return The same as {@link String#compareTo(String)}.
+ * @return A negative integer, zero, or a positive integer as the first
string is less than, equal to, or greater than the second.
+ * @see String#compareTo(String)
*/
public static int compare(String s1, String s2) {
if (s1 == null && s2 == null)
@@ -343,11 +380,26 @@ public class StringUtils {
}
/**
- * Converts string into a GZipped input stream.
+ * Compresses a UTF-8 string into a GZIP-compressed byte array.
+ *
+ * <p>
+ * This method compresses the input string using GZIP compression. The
string is first converted to
+ * UTF-8 bytes, then compressed. Use {@link #decompress(byte[])} to
decompress the result.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Compress a string</jc>
+ * byte[] <jv>compressed</jv> = compress(<js>"Hello World"</js>);
+ *
+ * <jc>// Decompress it back</jc>
+ * String <jv>decompressed</jv> = decompress(<jv>compressed</jv>);
+ * <jc>// Returns: "Hello World"</jc>
+ * </p>
*
- * @param contents The contents to compress.
- * @return The input stream converted to GZip.
- * @throws Exception Exception occurred.
+ * @param contents The UTF-8 string to compress.
+ * @return The GZIP-compressed byte array.
+ * @throws Exception If compression fails.
+ * @see #decompress(byte[])
*/
public static byte[] compress(String contents) throws Exception {
var baos = new ByteArrayOutputStream(contents.length() >> 1);
@@ -360,11 +412,28 @@ public class StringUtils {
}
/**
- * Null-safe {@link String#contains(CharSequence)} operation.
+ * Checks if a string contains any of the specified characters.
+ *
+ * <p>
+ * This is a null-safe operation that returns <jk>false</jk> if:
+ * <ul>
+ * <li>The string is <jk>null</jk></li>
+ * <li>The values array is <jk>null</jk> or empty</li>
+ * <li>None of the specified characters are found in the string</li>
+ * </ul>
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * contains(<js>"Hello World"</js>, <js>'o'</js>, <js>'x'</js>);
<jc>// true (contains 'o')</jc>
+ * contains(<js>"Hello World"</js>, <js>'x'</js>, <js>'y'</js>);
<jc>// false</jc>
+ * contains(<jk>null</jk>, <js>'a'</js>);
<jc>// false</jc>
+ * </p>
*
* @param s The string to check.
* @param values The characters to check for.
* @return <jk>true</jk> if the string contains any of the specified
characters.
+ * @see #contains(String, CharSequence)
+ * @see #contains(String, String...)
*/
public static boolean contains(String s, char...values) {
if (s == null || values == null || values.length == 0)
@@ -377,11 +446,24 @@ public class StringUtils {
}
/**
- * Same as {@link String#contains(CharSequence)} except returns
<jk>null</jk> if the value is null.
+ * Null-safe {@link String#contains(CharSequence)} operation.
+ *
+ * <p>
+ * Returns <jk>false</jk> if the string is <jk>null</jk>, otherwise
behaves the same as
+ * {@link String#contains(CharSequence)}.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * contains(<js>"Hello World"</js>, <js>"World"</js>); <jc>//
true</jc>
+ * contains(<js>"Hello World"</js>, <js>"Foo"</js>); <jc>//
false</jc>
+ * contains(<jk>null</jk>, <js>"Hello"</js>); <jc>//
false</jc>
+ * </p>
*
* @param value The string to check.
- * @param substring The value to check for.
- * @return <jk>true</jk> if the value contains the specified substring.
+ * @param substring The substring to check for.
+ * @return <jk>true</jk> if the value contains the specified substring,
<jk>false</jk> if the string is <jk>null</jk>.
+ * @see #contains(String, char...)
+ * @see #contains(String, String...)
*/
public static boolean contains(String value, CharSequence substring) {
return nn(value) && value.contains(substring);
@@ -400,10 +482,10 @@ public class StringUtils {
*
* <h5 class='section'>Example:</h5>
* <p class='bjava'>
- * containsAny(<js>"Hello World"</js>, <js>"Hello"</js>,
<js>"Foo"</js>); <jc>// true (contains "Hello")</jc>
- * containsAny(<js>"Hello World"</js>, <js>"Foo"</js>,
<js>"Bar"</js>); <jc>// false</jc>
- * containsAny(<jk>null</jk>, <js>"Hello"</js>);
<jc>// false</jc>
- * containsAny(<js>"Hello"</js>);
<jc>// false (no values to check)</jc>
+ * contains(<js>"Hello World"</js>, <js>"Hello"</js>,
<js>"Foo"</js>); <jc>// true (contains "Hello")</jc>
+ * contains(<js>"Hello World"</js>, <js>"Foo"</js>,
<js>"Bar"</js>); <jc>// false</jc>
+ * contains(<jk>null</jk>, <js>"Hello"</js>);
<jc>// false</jc>
+ * contains(<js>"Hello"</js>);
<jc>// false (no values to check)</jc>
* </p>
*
* @param s The string to check.
@@ -424,11 +506,21 @@ public class StringUtils {
}
/**
- * Counts the number of the specified character in the specified string.
+ * Counts the number of occurrences of the specified character in the
specified string.
+ *
+ * <p>
+ * Returns <c>0</c> if the string is <jk>null</jk>.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * countChars(<js>"Hello World"</js>, <js>'o'</js>); <jc>//
2</jc>
+ * countChars(<js>"Hello World"</js>, <js>'x'</js>); <jc>//
0</jc>
+ * countChars(<jk>null</jk>, <js>'a'</js>); <jc>//
0</jc>
+ * </p>
*
* @param s The string to check.
- * @param c The character to check for.
- * @return The number of those characters or zero if the string was
<jk>null</jk>.
+ * @param c The character to count.
+ * @return The number of occurrences of the character, or <c>0</c> if
the string was <jk>null</jk>.
*/
public static int countChars(String s, char c) {
var count = 0;
@@ -443,8 +535,20 @@ public class StringUtils {
/**
* Debug method for rendering non-ASCII character sequences.
*
+ * <p>
+ * Converts non-printable and non-ASCII characters (outside the range
<c>0x20-0x7E</c>) to hexadecimal
+ * sequences in the format <js>"[hex]"</js>. Printable ASCII characters
are left unchanged.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * decodeHex(<js>"Hello"</js>); <jc>// "Hello"</jc>
+ * decodeHex(<js>"Hello\u0000World"</js>); <jc>//
"Hello[0]World"</jc>
+ * decodeHex(<js>"Hello\u00A9"</js>); <jc>//
"Hello[a9]"</jc>
+ * decodeHex(<jk>null</jk>); <jc>// null</jc>
+ * </p>
+ *
* @param s The string to decode.
- * @return A string with non-ASCII characters converted to
<js>"[hex]"</js> sequences.
+ * @return A string with non-ASCII characters converted to
<js>"[hex]"</js> sequences, or <jk>null</jk> if input is <jk>null</jk>.
*/
public static String decodeHex(String s) {
if (s == null)
@@ -460,22 +564,51 @@ public class StringUtils {
}
/**
- * Converts a GZipped input stream into a string.
+ * Decompresses a GZIP-compressed byte array into a UTF-8 string.
*
- * @param is The contents to decompress.
- * @return The string.
- * @throws Exception Exception occurred.
+ * <p>
+ * This method is the inverse of {@link #compress(String)}. It takes a
byte array that was compressed
+ * using GZIP compression and decompresses it into a UTF-8 encoded
string.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Compress a string</jc>
+ * byte[] <jv>compressed</jv> = compress(<js>"Hello World"</js>);
+ *
+ * <jc>// Decompress it back</jc>
+ * String <jv>decompressed</jv> = decompress(<jv>compressed</jv>);
+ * <jc>// Returns: "Hello World"</jc>
+ * </p>
+ *
+ * @param is The GZIP-compressed byte array to decompress.
+ * @return The decompressed UTF-8 string.
+ * @throws Exception If decompression fails or the input is not valid
GZIP data.
+ * @see #compress(String)
*/
public static String decompress(byte[] is) throws Exception {
return read(new GZIPInputStream(new ByteArrayInputStream(is)));
}
/**
- * Finds the position where the two strings differ.
+ * Finds the position where the two strings first differ.
+ *
+ * <p>
+ * This method compares strings character by character and returns the
index of the first position
+ * where they differ. If the strings are equal, returns <c>-1</c>. If
one string is a prefix of the other,
+ * returns the length of the shorter string.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * diffPosition(<js>"apple"</js>, <js>"apple"</js>); <jc>//
-1 (equal)</jc>
+ * diffPosition(<js>"apple"</js>, <js>"apricot"</js>); <jc>// 2
(differs at 'p' vs 'r')</jc>
+ * diffPosition(<js>"apple"</js>, <js>"app"</js>); <jc>// 3
(shorter string ends here)</jc>
+ * diffPosition(<js>"app"</js>, <js>"apple"</js>); <jc>// 3
(shorter string ends here)</jc>
+ * </p>
*
* @param s1 The first string.
* @param s2 The second string.
* @return The position where the two strings differ, or <c>-1</c> if
they're equal.
+ * @see #diffPositionIc(String, String)
*/
public static int diffPosition(String s1, String s2) {
s1 = emptyIfNull(s1);
@@ -494,11 +627,24 @@ public class StringUtils {
}
/**
- * Finds the position where the two strings differ ignoring case.
+ * Finds the position where the two strings first differ, ignoring case.
+ *
+ * <p>
+ * This method compares strings character by character
(case-insensitive) and returns the index of the first position
+ * where they differ. If the strings are equal (ignoring case), returns
<c>-1</c>. If one string is a prefix of the other,
+ * returns the length of the shorter string.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * diffPositionIc(<js>"Apple"</js>, <js>"apple"</js>); <jc>//
-1 (equal ignoring case)</jc>
+ * diffPositionIc(<js>"Apple"</js>, <js>"Apricot"</js>); <jc>//
2 (differs at 'p' vs 'r')</jc>
+ * diffPositionIc(<js>"APPLE"</js>, <js>"app"</js>); <jc>//
3 (shorter string ends here)</jc>
+ * </p>
*
* @param s1 The first string.
* @param s2 The second string.
- * @return The position where the two strings differ, or <c>-1</c> if
they're equal.
+ * @return The position where the two strings differ, or <c>-1</c> if
they're equal (ignoring case).
+ * @see #diffPosition(String, String)
*/
public static int diffPositionIc(String s1, String s2) {
s1 = emptyIfNull(s1);
@@ -517,11 +663,24 @@ public class StringUtils {
}
/**
- * An efficient method for checking if a string ends with a character.
+ * Checks if a string ends with the specified character.
*
- * @param s The string to check. Can be <jk>null</jk>.
+ * <p>
+ * This is a null-safe operation. Returns <jk>false</jk> if the string
is <jk>null</jk> or empty.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * endsWith(<js>"Hello"</js>, <js>'o'</js>); <jc>// true</jc>
+ * endsWith(<js>"Hello"</js>, <js>'H'</js>); <jc>// false</jc>
+ * endsWith(<jk>null</jk>, <js>'o'</js>); <jc>// false</jc>
+ * endsWith(<js>""</js>, <js>'o'</js>); <jc>// false</jc>
+ * </p>
+ *
+ * @param s The string to check. Can be <jk>null</jk>.
* @param c The character to check for.
* @return <jk>true</jk> if the specified string is not <jk>null</jk>
and ends with the specified character.
+ * @see #endsWith(String, char...)
+ * @see String#endsWith(String)
*/
public static boolean endsWith(String s, char c) {
if (nn(s)) {
@@ -533,11 +692,24 @@ public class StringUtils {
}
/**
- * Same as {@link #endsWith(String, char)} except check for multiple
characters.
+ * Checks if a string ends with any of the specified characters.
*
- * @param s The string to check. Can be <jk>null</jk>.
+ * <p>
+ * This is a null-safe operation. Returns <jk>false</jk> if the string
is <jk>null</jk>, empty,
+ * or the characters array is <jk>null</jk> or empty.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * endsWith(<js>"Hello"</js>, <js>'o'</js>, <js>'x'</js>);
<jc>// true (ends with 'o')</jc>
+ * endsWith(<js>"Hello"</js>, <js>'x'</js>, <js>'y'</js>);
<jc>// false</jc>
+ * endsWith(<jk>null</jk>, <js>'o'</js>);
<jc>// false</jc>
+ * </p>
+ *
+ * @param s The string to check. Can be <jk>null</jk>.
* @param c The characters to check for.
- * @return <jk>true</jk> if the specified string is not <jk>null</jk>
and ends with the specified character.
+ * @return <jk>true</jk> if the specified string is not <jk>null</jk>
and ends with any of the specified characters.
+ * @see #endsWith(String, char)
+ * @see String#endsWith(String)
*/
public static boolean endsWith(String s, char...c) {
if (nn(s)) {