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 a3c24376ac StringUtils improvements
a3c24376ac is described below
commit a3c24376ac639a425d4195de29f88f544a2ff6b3
Author: James Bognar <[email protected]>
AuthorDate: Sun Nov 30 10:49:31 2025 -0500
StringUtils improvements
---
.../juneau/junit/bct/BasicBeanConverter.java | 24 +-
.../org/apache/juneau/junit/bct/BctAssertions.java | 2 +-
.../juneau/junit/bct/PropertyExtractors.java | 4 +-
.../apache/juneau/common/utils/StringUtils.java | 366 ++++++++++++++-------
.../java/org/apache/juneau/common/utils/Utils.java | 252 ++------------
.../java/org/apache/juneau/rest/RestRequest.java | 3 +-
.../juneau/common/utils/StringUtils_Test.java | 48 ++-
7 files changed, 349 insertions(+), 350 deletions(-)
diff --git
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BasicBeanConverter.java
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BasicBeanConverter.java
index 0f2585df6c..fd41d95eca 100644
---
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BasicBeanConverter.java
+++
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BasicBeanConverter.java
@@ -899,11 +899,31 @@ public class BasicBeanConverter implements BeanConverter {
var c = o.getClass();
var stringifier = stringifierMap.computeIfAbsent(c,
this::findStringifier);
if (stringifier.isEmpty()) {
- stringifier = of(canListify(o) ? (bc, o2) ->
bc.stringify(bc.listify(o2)) : (bc, o2) -> safeToString(o2));
+ stringifier = of(canListify(o) ? (bc, o2) ->
bc.stringify(bc.listify(o2)) : (bc, o2) -> this.safeToString(o2));
stringifierMap.putIfAbsent(c, stringifier);
}
var o2 = o;
- return stringifier.map(x -> (Stringifier)x).map(x ->
x.apply(this, o2)).map(Utils::safeToString).orElse(null);
+ return stringifier.map(x -> (Stringifier)x).map(x ->
x.apply(this, o2)).map(this::safeToString).orElse(null);
+ }
+
+ /**
+ * Safely converts an object to a string, catching any exceptions
thrown by toString().
+ *
+ * <p>
+ * This helper method ensures that exceptions thrown by problematic
{@code toString()} implementations
+ * don't propagate up the call stack. Instead, it returns a descriptive
error message containing
+ * the exception type and message.
+ *
+ * @param o The object to convert to a string. May be any object
including <jk>null</jk>.
+ * @return The string representation of the object, or a formatted
error message if toString() throws an exception.
+ * Returns <js>"null"</js> if the object is <jk>null</jk>.
+ */
+ private String safeToString(Object o) {
+ try {
+ return o.toString();
+ } catch (Throwable t) { // NOSONAR
+ return cns(t) + ": " + t.getMessage();
+ }
}
/**
diff --git
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctAssertions.java
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctAssertions.java
index b0b87c52be..bb3d14b7e7 100644
---
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctAssertions.java
+++
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctAssertions.java
@@ -912,7 +912,7 @@ public class BctAssertions {
assertNotNull(value, "Value was null.");
var v =
args.getBeanConverter().orElse(DEFAULT_CONVERTER).stringify(value);
- var m = getGlobMatchPattern(pattern).matcher(v);
+ var m = StringUtils.getGlobMatchPattern(pattern).matcher(v);
assertTrue(m.matches(), args.getMessage("Pattern didn''t match.
==> pattern: <{0}> but was: <{1}>", pattern, v));
}
diff --git
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/PropertyExtractors.java
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/PropertyExtractors.java
index 828ceea51c..0ba796948a 100644
---
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/PropertyExtractors.java
+++
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/PropertyExtractors.java
@@ -21,6 +21,8 @@ import static org.apache.juneau.common.utils.Utils.*;
import java.lang.reflect.*;
import java.util.*;
+import org.apache.juneau.common.utils.*;
+
/**
* Collection of standard property extractor implementations for the
Bean-Centric Testing framework.
*
@@ -105,7 +107,7 @@ public class PropertyExtractors {
public Object extract(BeanConverter converter, Object o, String
name) {
var l = converter.listify(o);
if (name.matches("-?\\d+")) {
- var index = parseInt(name);
+ var index = StringUtils.parseInt(name);
if (index < 0) {
index = l.size() + index; // Convert
negative index to positive
}
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 f59b9fbda8..1116d03605 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
@@ -2044,12 +2044,12 @@ public class StringUtils {
public static boolean isValidMacAddress(String mac) {
if (isEmpty(mac))
return false;
-
+
// Remove separators and check if it's 12 hex digits
var cleaned = mac.replaceAll("[:-]", "").toUpperCase();
if (cleaned.length() != 12)
return false;
-
+
// Check if all characters are valid hex digits
return cleaned.matches("^[0-9A-F]{12}$");
}
@@ -2081,41 +2081,41 @@ public class StringUtils {
public static boolean isValidHostname(String hostname) {
if (isEmpty(hostname))
return false;
-
+
// Cannot start or end with a dot
if (hostname.startsWith(".") || hostname.endsWith("."))
return false;
-
+
// Total length cannot exceed 253 characters
if (hostname.length() > 253)
return false;
-
+
// Split by dots (use -1 to preserve trailing empty strings)
var labels = hostname.split("\\.", -1);
-
+
// Must have at least one label
if (labels.length == 0)
return false;
-
+
// Check each label
for (var label : labels) {
// Label cannot be empty
if (label.isEmpty())
return false;
-
+
// Label cannot exceed 63 characters
if (label.length() > 63)
return false;
-
+
// Label cannot start or end with hyphen
if (label.startsWith("-") || label.endsWith("-"))
return false;
-
+
// Label can only contain letters, digits, and hyphens
if (! label.matches("^[a-zA-Z0-9-]+$"))
return false;
}
-
+
return true;
}
@@ -2139,10 +2139,10 @@ public class StringUtils {
public static int wordCount(String str) {
if (isEmpty(str))
return 0;
-
+
var count = 0;
var inWord = false;
-
+
for (var i = 0; i < str.length(); i++) {
var c = str.charAt(i);
if (Character.isLetterOrDigit(c) || c == '_') {
@@ -2154,7 +2154,7 @@ public class StringUtils {
inWord = false;
}
}
-
+
return count;
}
@@ -2177,7 +2177,7 @@ public class StringUtils {
public static int lineCount(String str) {
if (isEmpty(str))
return 0;
-
+
var count = 1; // At least one line
for (var i = 0; i < str.length(); i++) {
var c = str.charAt(i);
@@ -2191,7 +2191,7 @@ public class StringUtils {
count++;
}
}
-
+
return count;
}
@@ -2214,11 +2214,11 @@ public class StringUtils {
public static char mostFrequentChar(String str) {
if (isEmpty(str))
return '\0';
-
+
var charCounts = new int[Character.MAX_VALUE + 1];
var maxCount = 0;
var maxChar = '\0';
-
+
// Count occurrences of each character
for (var i = 0; i < str.length(); i++) {
var c = str.charAt(i);
@@ -2228,7 +2228,7 @@ public class StringUtils {
maxChar = c;
}
}
-
+
return maxChar;
}
@@ -2254,17 +2254,17 @@ public class StringUtils {
public static double entropy(String str) {
if (isEmpty(str))
return 0.0;
-
+
var length = str.length();
if (length == 0)
return 0.0;
-
+
// Count character frequencies
var charCounts = new int[Character.MAX_VALUE + 1];
for (var i = 0; i < length; i++) {
charCounts[str.charAt(i)]++;
}
-
+
// Calculate entropy
var entropy = 0.0;
for (var count : charCounts) {
@@ -2273,7 +2273,7 @@ public class StringUtils {
entropy -= probability * (Math.log(probability)
/ Math.log(2.0));
}
}
-
+
return entropy;
}
@@ -2300,11 +2300,11 @@ public class StringUtils {
public static double readabilityScore(String str) {
if (isEmpty(str))
return 0.0;
-
+
var words = extractWords(str);
if (words.isEmpty())
return 0.0;
-
+
// Count sentences (ending with . ! ?)
var sentenceCount = 0;
for (var i = 0; i < str.length(); i++) {
@@ -2315,22 +2315,22 @@ public class StringUtils {
}
if (sentenceCount == 0)
sentenceCount = 1; // At least one sentence
-
+
// Calculate average words per sentence
var avgWordsPerSentence = (double)words.size() / sentenceCount;
-
+
// Estimate average syllables per word (simplified: count vowel
groups)
var totalSyllables = 0;
for (var word : words) {
totalSyllables += estimateSyllables(word);
}
var avgSyllablesPerWord = (double)totalSyllables / words.size();
-
+
// Simplified Flesch Reading Ease formula
// Score = 206.835 - (1.015 * ASL) - (84.6 * ASW)
// Where ASL = average sentence length (words), ASW = average
syllables per word
var score = 206.835 - (1.015 * avgWordsPerSentence) - (84.6 *
avgSyllablesPerWord);
-
+
// Clamp to 0-100 range
return Math.max(0.0, Math.min(100.0, score));
}
@@ -2341,26 +2341,26 @@ public class StringUtils {
private static int estimateSyllables(String word) {
if (word == null || word.isEmpty())
return 1;
-
+
var lower = word.toLowerCase();
var count = 0;
var prevWasVowel = false;
-
+
for (var i = 0; i < lower.length(); i++) {
var c = lower.charAt(i);
var isVowel = (c == 'a' || c == 'e' || c == 'i' || c ==
'o' || c == 'u' || c == 'y');
-
+
if (isVowel && ! prevWasVowel) {
count++;
}
prevWasVowel = isVowel;
}
-
+
// Handle silent 'e' at the end
if (lower.endsWith("e") && count > 1) {
count--;
}
-
+
// At least one syllable
return Math.max(1, count);
}
@@ -2575,11 +2575,11 @@ public class StringUtils {
return null;
if (variables == null || variables.isEmpty())
return template;
-
+
var result = new StringBuilder();
var i = 0;
var length = template.length();
-
+
while (i < length) {
var dollarIndex = template.indexOf("${", i);
if (dollarIndex == -1) {
@@ -2587,10 +2587,10 @@ public class StringUtils {
result.append(template.substring(i));
break;
}
-
+
// Append text before the variable
result.append(template.substring(i, dollarIndex));
-
+
// Find the closing brace
var braceIndex = template.indexOf('}', dollarIndex + 2);
if (braceIndex == -1) {
@@ -2598,11 +2598,11 @@ public class StringUtils {
result.append(template.substring(dollarIndex));
break;
}
-
+
// Extract variable name
var varName = template.substring(dollarIndex + 2,
braceIndex);
var value = variables.get(varName);
-
+
if (variables.containsKey(varName)) {
// Variable exists in map (even if null)
result.append(value != null ? value.toString()
: "null");
@@ -2610,10 +2610,10 @@ public class StringUtils {
// Variable not found, keep the original
placeholder
result.append("${").append(varName).append("}");
}
-
+
i = braceIndex + 1;
}
-
+
return result.toString();
}
@@ -2641,15 +2641,15 @@ public class StringUtils {
return word;
if (count == 1)
return word;
-
+
var lower = word.toLowerCase();
var length = word.length();
-
+
// Words ending in s, x, z, ch, sh -> add "es"
if (lower.endsWith("s") || lower.endsWith("x") ||
lower.endsWith("z") || lower.endsWith("ch") || lower.endsWith("sh")) {
return word + "es";
}
-
+
// Words ending in "y" preceded by a consonant -> replace "y"
with "ies"
if (length > 1 && lower.endsWith("y")) {
var secondLast = lower.charAt(length - 2);
@@ -2657,7 +2657,7 @@ public class StringUtils {
return word.substring(0, length - 1) + "ies";
}
}
-
+
// Words ending in "f" or "fe" -> replace with "ves" (basic
rule)
if (lower.endsWith("f")) {
return word.substring(0, length - 1) + "ves";
@@ -2665,7 +2665,7 @@ public class StringUtils {
if (lower.endsWith("fe")) {
return word.substring(0, length - 2) + "ves";
}
-
+
// Default: add "s"
return word + "s";
}
@@ -2689,7 +2689,7 @@ public class StringUtils {
public static String ordinal(int number) {
var abs = Math.abs(number);
var suffix = "th";
-
+
// Special cases for 11, 12, 13 (all use "th")
if (abs % 100 != 11 && abs % 100 != 12 && abs % 100 != 13) {
var lastDigit = abs % 10;
@@ -2700,7 +2700,7 @@ public class StringUtils {
else if (lastDigit == 3)
suffix = "rd";
}
-
+
return number + suffix;
}
@@ -2982,16 +2982,16 @@ public class StringUtils {
return -1;
if (str2 == null)
return 1;
-
+
var len1 = str1.length();
var len2 = str2.length();
var i1 = 0;
var i2 = 0;
-
+
while (i1 < len1 && i2 < len2) {
var c1 = str1.charAt(i1);
var c2 = str2.charAt(i2);
-
+
// If both are digits, compare numerically
if (Character.isDigit(c1) && Character.isDigit(c2)) {
// Skip leading zeros
@@ -2999,7 +2999,7 @@ public class StringUtils {
i1++;
while (i2 < len2 && str2.charAt(i2) == '0')
i2++;
-
+
// Find end of number sequences
var end1 = i1;
var end2 = i2;
@@ -3007,13 +3007,13 @@ public class StringUtils {
end1++;
while (end2 < len2 &&
Character.isDigit(str2.charAt(end2)))
end2++;
-
+
// Compare lengths first (longer number is
larger)
var lenNum1 = end1 - i1;
var lenNum2 = end2 - i2;
if (lenNum1 != lenNum2)
return lenNum1 - lenNum2;
-
+
// Same length, compare digit by digit
for (var j = 0; j < lenNum1; j++) {
var d1 = str1.charAt(i1 + j);
@@ -3021,7 +3021,7 @@ public class StringUtils {
if (d1 != d2)
return d1 - d2;
}
-
+
i1 = end1;
i2 = end2;
} else {
@@ -3033,7 +3033,7 @@ public class StringUtils {
i2++;
}
}
-
+
return len1 - len2;
}
@@ -3059,18 +3059,18 @@ public class StringUtils {
str1 = "";
if (str2 == null)
str2 = "";
-
+
var len1 = str1.length();
var len2 = str2.length();
-
+
// Use dynamic programming with optimized space (only need
previous row)
var prev = new int[len2 + 1];
var curr = new int[len2 + 1];
-
+
// Initialize first row
for (var j = 0; j <= len2; j++)
prev[j] = j;
-
+
for (var i = 1; i <= len1; i++) {
curr[0] = i;
for (var j = 1; j <= len2; j++) {
@@ -3085,7 +3085,7 @@ public class StringUtils {
prev = curr;
curr = temp;
}
-
+
return prev[len2];
}
@@ -3111,14 +3111,14 @@ public class StringUtils {
str1 = "";
if (str2 == null)
str2 = "";
-
+
if (str1.equals(str2))
return 1.0;
-
+
var maxLen = Math.max(str1.length(), str2.length());
if (maxLen == 0)
return 1.0;
-
+
var distance = levenshteinDistance(str1, str2);
return 1.0 - ((double)distance / maxLen);
}
@@ -3659,7 +3659,7 @@ public class StringUtils {
*/
public static int parseIntWithSuffix(String s) {
assertArgNotNull("s", s);
- var m = multiplier(s);
+ var m = multiplierInt(s);
if (m == 1)
return Integer.decode(s);
return Integer.decode(s.substring(0, s.length() - 1).trim()) *
m; // NOSONAR - NPE not possible here.
@@ -3741,7 +3741,7 @@ public class StringUtils {
*/
public static long parseLongWithSuffix(String s) {
assertArgNotNull("s", s);
- var m = multiplier2(s);
+ var m = multiplierLong(s);
if (m == 1)
return Long.decode(s);
return Long.decode(s.substring(0, s.length() - 1).trim()) * m;
// NOSONAR - NPE not possible here.
@@ -4249,14 +4249,14 @@ public class StringUtils {
// Split into words first, then combine words that fit
var words = line.split(" +"); // Split on one or more
spaces
var currentLine = new StringBuilder();
-
+
for (var word : words) {
if (word.isEmpty())
continue;
-
+
var wordLength = word.length();
var currentLength = currentLine.length();
-
+
if (currentLength == 0) {
// First word on line
// Only break single words if there are
multiple words in the input
@@ -4314,7 +4314,7 @@ public class StringUtils {
}
}
}
-
+
// Append any remaining line
if (currentLine.length() > 0) {
if (result.length() > 0)
@@ -4362,6 +4362,10 @@ public class StringUtils {
* For un-numbered sequential placeholders <js>"{}"</js>, use {@link
#format(String, Object...)} instead.
*
* <p>
+ * Variable values are converted to strings using {@link
#readable(Object)} to ensure consistent,
+ * readable formatting (e.g., byte arrays are converted to hex,
collections are formatted without spaces).
+ *
+ * <p>
* Nested variables are supported in both the input string and map
values.
*
* <p>
@@ -4424,7 +4428,7 @@ public class StringUtils {
else {
if (val == null)
val = "";
- var v = val.toString();
+ var v = r(val);
// If the replacement
also contains variables, replace them now.
if (v.indexOf('{') !=
-1)
v =
formatNamed(v, m);
@@ -5182,7 +5186,7 @@ public class StringUtils {
var result = new LinkedHashMap<String,String>();
if (isEmpty(str))
return result;
-
+
var entries = split(str, entryDelimiter);
for (var entry : entries) {
if (isEmpty(entry))
@@ -5225,7 +5229,7 @@ public class StringUtils {
public static List<String> extractNumbers(String str) {
if (isEmpty(str))
return Collections.emptyList();
-
+
var result = new ArrayList<String>();
var pattern = Pattern.compile("\\d+(?:\\.\\d+)?");
var matcher = pattern.matcher(str);
@@ -5253,7 +5257,7 @@ public class StringUtils {
public static List<String> extractEmails(String str) {
if (isEmpty(str))
return Collections.emptyList();
-
+
var result = new ArrayList<String>();
// Email regex pattern (same as isEmail but without ^ and $
anchors)
var pattern =
Pattern.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}");
@@ -5282,7 +5286,7 @@ public class StringUtils {
public static List<String> extractUrls(String str) {
if (isEmpty(str))
return Collections.emptyList();
-
+
var result = new ArrayList<String>();
// Basic URL pattern: protocol://domain/path
var pattern =
Pattern.compile("(?:https?|ftp)://[\\w\\-._~:/?#\\[\\]@!$&'()*+,;=%]+",
Pattern.CASE_INSENSITIVE);
@@ -5311,7 +5315,7 @@ public class StringUtils {
public static List<String> extractWords(String str) {
if (isEmpty(str))
return Collections.emptyList();
-
+
var result = new ArrayList<String>();
// Word pattern: sequence of word characters (letters, digits,
underscore)
var pattern = Pattern.compile("\\w+");
@@ -5344,7 +5348,7 @@ public class StringUtils {
public static List<String> extractBetween(String str, String start,
String end) {
if (isEmpty(str) || isEmpty(start) || isEmpty(end))
return Collections.emptyList();
-
+
var result = new ArrayList<String>();
var startIndex = 0;
while (true) {
@@ -5389,7 +5393,7 @@ public class StringUtils {
return str;
if (fromChars.length() != toChars.length())
throw new IllegalArgumentException("fromChars and
toChars must have the same length");
-
+
var sb = new StringBuilder(str.length());
for (var i = 0; i < str.length(); i++) {
var c = str.charAt(i);
@@ -5422,16 +5426,16 @@ public class StringUtils {
public static String soundex(String str) {
if (isEmpty(str))
return null;
-
+
var upper = str.toUpperCase();
var result = new StringBuilder(4);
result.append(upper.charAt(0));
-
+
// Soundex mapping: 0 = AEIOUHWY, 1 = BFPV, 2 = CGJKQSXZ, 3 =
DT, 4 = L, 5 = MN, 6 = R
// H/W/Y don't get codes but don't break sequences either
// Initialize lastCode to a value that won't match any real code
var lastCode = '\0';
-
+
for (var i = 1; i < upper.length() && result.length() < 4; i++)
{
var c = upper.charAt(i);
var code = getSoundexCode(c);
@@ -5446,12 +5450,12 @@ public class StringUtils {
}
// If code == lastCode, skip it (consecutive same codes)
}
-
+
// Pad with zeros if needed
while (result.length() < 4) {
result.append('0');
}
-
+
return result.toString();
}
@@ -5496,15 +5500,15 @@ public class StringUtils {
public static String metaphone(String str) {
if (isEmpty(str))
return null;
-
+
var upper = str.toUpperCase().replaceAll("[^A-Z]", "");
if (upper.isEmpty())
return "";
-
+
var result = new StringBuilder();
var i = 0;
var len = upper.length();
-
+
// Handle initial characters
if (upper.startsWith("KN") || upper.startsWith("GN") ||
upper.startsWith("PN") || upper.startsWith("AE") || upper.startsWith("WR")) {
i = 1;
@@ -5515,20 +5519,20 @@ public class StringUtils {
result.append('W');
i = 2;
}
-
+
// Process remaining characters
while (i < len && result.length() < 4) {
var c = upper.charAt(i);
var prev = i > 0 ? upper.charAt(i - 1) : '\0';
var next = i < len - 1 ? upper.charAt(i + 1) : '\0';
var next2 = i < len - 2 ? upper.charAt(i + 2) : '\0';
-
+
// Skip duplicates (except C)
if (c == prev && c != 'C') {
i++;
continue;
}
-
+
switch (c) {
case 'B':
if (prev != 'M' || next != '\0')
@@ -5636,7 +5640,7 @@ public class StringUtils {
}
i++;
}
-
+
return result.length() > 0 ? result.toString() :
upper.substring(0, Math.min(1, upper.length()));
}
@@ -5666,16 +5670,16 @@ public class StringUtils {
public static String[] doubleMetaphone(String str) {
if (isEmpty(str))
return null;
-
+
// For simplicity, return the same code for both primary and
alternate
// A full Double Metaphone implementation would be much more
complex
var primary = metaphone(str);
if (primary == null)
return null;
-
+
// Generate alternate code (simplified - full implementation
would have different rules)
var alternate = primary;
-
+
return new String[] { primary, alternate };
}
@@ -5719,10 +5723,10 @@ public class StringUtils {
public static String removeAccents(String str) {
if (str == null)
return null;
-
+
// Normalize to NFD (decomposed form)
var normalized = Normalizer.normalize(str, Normalizer.Form.NFD);
-
+
// Remove combining diacritical marks (Unicode category Mn)
var sb = new StringBuilder(normalized.length());
for (var i = 0; i < normalized.length(); i++) {
@@ -5733,7 +5737,7 @@ public class StringUtils {
sb.append(c);
}
}
-
+
return sb.toString();
}
@@ -6640,7 +6644,7 @@ public class StringUtils {
* @param in The string to append.
* @return The StringBuilder with the string appended.
*/
- private static StringBuilder append(StringBuilder sb, String in) {
+ public static StringBuilder append(StringBuilder sb, String in) {
if (sb == null)
return new StringBuilder(in);
sb.append(in);
@@ -6677,7 +6681,7 @@ public class StringUtils {
* @param s The string to analyze for multiplier suffix.
* @return The multiplier value (1 if no valid suffix found).
*/
- private static int multiplier(String s) {
+ private static int multiplierInt(String s) {
var c = isEmpty(s) ? 'z' : s.charAt(s.length() - 1);
if (c == 'G')
return 1024 * 1024 * 1024;
@@ -6700,7 +6704,7 @@ public class StringUtils {
* @param s The string to analyze for multiplier suffix.
* @return The multiplier value (1 if no valid suffix found).
*/
- private static long multiplier2(String s) {
+ private static long multiplierLong(String s) {
var c = isEmpty(s) ? 'z' : s.charAt(s.length() - 1);
if (c == 'P')
return 1024 * 1024 * 1024 * 1024 * 1024l;
@@ -6769,7 +6773,7 @@ public class StringUtils {
* @param s The string to convert.
* @return The lowercase string, or <jk>null</jk> if the input was
<jk>null</jk>.
*/
- public static String lc(String s) {
+ public static String lowerCase(String s) {
return s == null ? null : s.toLowerCase();
}
@@ -6779,7 +6783,7 @@ public class StringUtils {
* @param s The string to convert.
* @return The uppercase string, or <jk>null</jk> if the input was
<jk>null</jk>.
*/
- public static String uc(String s) {
+ public static String upperCase(String s) {
return s == null ? null : s.toUpperCase();
}
@@ -7169,6 +7173,7 @@ public class StringUtils {
* <p>
* Returns the same StringBuilder instance for method chaining.
* If the string is <jk>null</jk> or empty, nothing is appended.
+ * If <c>sb</c> is <jk>null</jk> and an append is going to occur, a new
StringBuilder is automatically created.
*
* <h5 class='section'>Examples:</h5>
* <p class='bjava'>
@@ -7178,17 +7183,22 @@ public class StringUtils {
* appendIfNotEmpty(<jv>sb</jv>, <jk>null</jk>); <jc>// Does
nothing</jc>
* appendIfNotEmpty(<jv>sb</jv>, <js>"world"</js>); <jc>//
Appends "world"</jc>
* <jc>// Result: "helloworld"</jc>
+ *
+ * <jc>// Auto-create StringBuilder if null and append occurs</jc>
+ * StringBuilder <jv>sb2</jv> = appendIfNotEmpty(<jk>null</jk>,
<js>"test"</js>); <jc>// Creates new StringBuilder with "test"</jc>
+ * StringBuilder <jv>sb3</jv> = appendIfNotEmpty(<jk>null</jk>,
<jk>null</jk>); <jc>// Returns null (no append occurred)</jc>
* </p>
*
- * @param sb The StringBuilder to append to. Must not be <jk>null</jk>.
+ * @param sb The StringBuilder to append to. Can be <jk>null</jk>.
* @param str The string to append if not empty. Can be <jk>null</jk>.
- * @return The same StringBuilder instance for method chaining.
- * @throws IllegalArgumentException If <c>sb</c> is <jk>null</jk>.
+ * @return The same StringBuilder instance for method chaining, or a
new StringBuilder if <c>sb</c> was <jk>null</jk> and an append occurred, or
<jk>null</jk> if <c>sb</c> was <jk>null</jk> and no append occurred.
*/
public static StringBuilder appendIfNotEmpty(StringBuilder sb, String
str) {
- assertArgNotNull("sb", sb);
- if (isNotEmpty(str))
+ if (isNotEmpty(str)) {
+ if (sb == null)
+ sb = new StringBuilder();
sb.append(str);
+ }
return sb;
}
@@ -7198,6 +7208,7 @@ public class StringUtils {
* <p>
* Returns the same StringBuilder instance for method chaining.
* If the string is <jk>null</jk>, empty, or contains only whitespace,
nothing is appended.
+ * If <c>sb</c> is <jk>null</jk> and an append is going to occur, a new
StringBuilder is automatically created.
*
* <h5 class='section'>Examples:</h5>
* <p class='bjava'>
@@ -7207,17 +7218,22 @@ public class StringUtils {
* appendIfNotBlank(<jv>sb</jv>, <jk>null</jk>); <jc>// Does
nothing</jc>
* appendIfNotBlank(<jv>sb</jv>, <js>"world"</js>); <jc>//
Appends "world"</jc>
* <jc>// Result: "helloworld"</jc>
+ *
+ * <jc>// Auto-create StringBuilder if null and append occurs</jc>
+ * StringBuilder <jv>sb2</jv> = appendIfNotBlank(<jk>null</jk>,
<js>"test"</js>); <jc>// Creates new StringBuilder with "test"</jc>
+ * StringBuilder <jv>sb3</jv> = appendIfNotBlank(<jk>null</jk>,
<js>" "</js>); <jc>// Returns null (no append occurred)</jc>
* </p>
*
- * @param sb The StringBuilder to append to. Must not be <jk>null</jk>.
+ * @param sb The StringBuilder to append to. Can be <jk>null</jk>.
* @param str The string to append if not blank. Can be <jk>null</jk>.
- * @return The same StringBuilder instance for method chaining.
- * @throws IllegalArgumentException If <c>sb</c> is <jk>null</jk>.
+ * @return The same StringBuilder instance for method chaining, or a
new StringBuilder if <c>sb</c> was <jk>null</jk> and an append occurred, or
<jk>null</jk> if <c>sb</c> was <jk>null</jk> and no append occurred.
*/
public static StringBuilder appendIfNotBlank(StringBuilder sb, String
str) {
- assertArgNotNull("sb", sb);
- if (isNotBlank(str))
+ if (isNotBlank(str)) {
+ if (sb == null)
+ sb = new StringBuilder();
sb.append(str);
+ }
return sb;
}
@@ -7228,6 +7244,7 @@ public class StringUtils {
* Returns the same StringBuilder instance for method chaining.
* If the StringBuilder is empty, only the string is appended (no
separator).
* If the StringBuilder is not empty, the separator is appended first,
then the string.
+ * If <c>sb</c> is <jk>null</jk> and an append is going to occur, a new
StringBuilder is automatically created.
*
* <h5 class='section'>Examples:</h5>
* <p class='bjava'>
@@ -7236,18 +7253,22 @@ public class StringUtils {
* appendWithSeparator(<jv>sb</jv>, <js>"second"</js>, <js>",
"</js>); <jc>// Appends ", second"</jc>
* appendWithSeparator(<jv>sb</jv>, <js>"third"</js>, <js>",
"</js>); <jc>// Appends ", third"</jc>
* <jc>// Result: "first, second, third"</jc>
+ *
+ * <jc>// Auto-create StringBuilder if null and append occurs</jc>
+ * StringBuilder <jv>sb2</jv> = appendWithSeparator(<jk>null</jk>,
<js>"test"</js>, <js>", "</js>); <jc>// Creates new StringBuilder with
"test"</jc>
+ * StringBuilder <jv>sb3</jv> = appendWithSeparator(<jk>null</jk>,
<jk>null</jk>, <js>", "</js>); <jc>// Returns null (no append occurred)</jc>
* </p>
*
- * @param sb The StringBuilder to append to. Must not be <jk>null</jk>.
+ * @param sb The StringBuilder to append to. Can be <jk>null</jk>.
* @param str The string to append. Can be <jk>null</jk>.
* @param separator The separator to add before the string if the
StringBuilder is not empty. Can be <jk>null</jk>.
- * @return The same StringBuilder instance for method chaining.
- * @throws IllegalArgumentException If <c>sb</c> is <jk>null</jk>.
+ * @return The same StringBuilder instance for method chaining, or a
new StringBuilder if <c>sb</c> was <jk>null</jk> and an append occurred, or
<jk>null</jk> if <c>sb</c> was <jk>null</jk> and no append occurred.
*/
public static StringBuilder appendWithSeparator(StringBuilder sb,
String str, String separator) {
- assertArgNotNull("sb", sb);
if (str != null) {
- if (sb.length() > 0 && separator != null)
+ if (sb == null)
+ sb = new StringBuilder();
+ else if (sb.length() > 0 && separator != null)
sb.append(separator);
sb.append(str);
}
@@ -7445,4 +7466,115 @@ public class StringUtils {
* Constructor.
*/
protected StringUtils() {}
+
+ /**
+ * Same as {@link Float#parseFloat(String)} but removes any underscore
characters first.
+ *
+ * <p>Allows for better readability of numeric literals (e.g.,
<js>"1_000.5"</js>).
+ *
+ * @param value The string to parse.
+ * @return The parsed float value.
+ * @throws NumberFormatException If the string cannot be parsed.
+ * @throws NullPointerException If the string is <jk>null</jk>.
+ */
+ public static float parseFloat(String value) {
+ return Float.parseFloat(StringUtils.removeUnderscores(value));
+ }
+
+ /**
+ * Same as {@link Integer#parseInt(String)} but removes any underscore
characters first.
+ *
+ * <p>Allows for better readability of numeric literals (e.g.,
<js>"1_000_000"</js>).
+ *
+ * @param value The string to parse.
+ * @return The parsed integer value.
+ * @throws NumberFormatException If the string cannot be parsed.
+ * @throws NullPointerException If the string is <jk>null</jk>.
+ */
+ public static int parseInt(String value) {
+ return Integer.parseInt(StringUtils.removeUnderscores(value));
+ }
+
+ /**
+ * Same as {@link Long#parseLong(String)} but removes any underscore
characters first.
+ *
+ * <p>Allows for better readability of numeric literals (e.g.,
<js>"1_000_000"</js>).
+ *
+ * @param value The string to parse.
+ * @return The parsed long value.
+ * @throws NumberFormatException If the string cannot be parsed.
+ * @throws NullPointerException If the string is <jk>null</jk>.
+ */
+ public static long parseLong(String value) {
+ return Long.parseLong(StringUtils.removeUnderscores(value));
+ }
+
+ /**
+ * Converts a string containing glob-style wildcard characters to a
regular expression {@link java.util.regex.Pattern}.
+ *
+ * <p>This method converts glob-style patterns to regular expressions
with the following mappings:
+ * <ul>
+ * <li>{@code *} matches any sequence of characters (including
none)</li>
+ * <li>{@code ?} matches exactly one character</li>
+ * <li>All other characters are treated literally</li>
+ * </ul>
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jk>var</jk> <jv>pattern</jv> =
<jsm>getGlobMatchPattern</jsm>(<js>"user_*_temp"</js>);
+ * <jk>boolean</jk> <jv>matches</jv> =
<jv>pattern</jv>.matcher(<js>"user_alice_temp"</js>).matches(); <jc>//
true</jc>
+ * <jv>matches</jv> =
<jv>pattern</jv>.matcher(<js>"user_bob_temp"</js>).matches(); <jc>//
true</jc>
+ * <jv>matches</jv> =
<jv>pattern</jv>.matcher(<js>"admin_alice_temp"</js>).matches(); <jc>//
false</jc>
+ * </p>
+ *
+ * @param s The glob-style wildcard pattern string.
+ * @return A compiled {@link java.util.regex.Pattern} object, or
<jk>null</jk> if the input string is <jk>null</jk>.
+ */
+ public static java.util.regex.Pattern getGlobMatchPattern(String s) {
+ return getGlobMatchPattern(s, 0);
+ }
+
+ /**
+ * Converts a string containing glob-style wildcard characters to a
regular expression {@link java.util.regex.Pattern} with flags.
+ *
+ * <p>This method converts glob-style patterns to regular expressions
with the following mappings:
+ * <ul>
+ * <li>{@code *} matches any sequence of characters (including
none)</li>
+ * <li>{@code ?} matches exactly one character</li>
+ * <li>All other characters are treated literally</li>
+ * </ul>
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * <jc>// Case-insensitive matching</jc>
+ * <jk>var</jk> <jv>pattern</jv> =
<jsm>getGlobMatchPattern</jsm>(<js>"USER_*"</js>,
Pattern.<jsf>CASE_INSENSITIVE</jsf>);
+ * <jk>boolean</jk> <jv>matches</jv> =
<jv>pattern</jv>.matcher(<js>"user_alice"</js>).matches(); <jc>// true</jc>
+ * </p>
+ *
+ * @param s The glob-style wildcard pattern string.
+ * @param flags Regular expression flags (see {@link
java.util.regex.Pattern} constants).
+ * @return A compiled {@link java.util.regex.Pattern} object, or
<jk>null</jk> if the input string is <jk>null</jk>.
+ */
+ public static java.util.regex.Pattern getGlobMatchPattern(String s, int
flags) {
+ if (s == null)
+ return null;
+ var sb = new StringBuilder();
+ sb.append("\\Q");
+ for (var i = 0; i < s.length(); i++) {
+ var c = s.charAt(i);
+ if (c == '*')
+ sb.append("\\E").append(".*").append("\\Q");
+ else if (c == '?')
+ sb.append("\\E").append(".").append("\\Q");
+ else
+ sb.append(c);
+ }
+ sb.append("\\E");
+ return java.util.regex.Pattern.compile(sb.toString(), flags);
+ }
+
+ public static String removeUnderscores(String value) {
+ assertArgNotNull("value", value);
+ return notContains(value, '_') ? value : value.replace("_", "");
+ }
}
\ No newline at end of file
diff --git
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/Utils.java
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/Utils.java
index 3a10862138..9fa7ecc373 100644
---
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/Utils.java
+++
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/Utils.java
@@ -190,12 +190,21 @@ public class Utils {
/**
* Tests two objects for equality, gracefully handling nulls and arrays.
*
+ * <p>
+ * This method handles annotations specially by delegating to {@link
AnnotationUtils#equals(Annotation, Annotation)}
+ * to ensure proper annotation comparison according to the annotation
equality contract.
+ *
* @param <T> The value types.
* @param o1 Object 1.
* @param o2 Object 2.
* @return <jk>true</jk> if both objects are equal based on the {@link
Object#equals(Object)} method.
+ * @see AnnotationUtils#equals(Annotation, Annotation)
*/
public static <T> boolean eq(T o1, T o2) {
+ // Handle annotations specially
+ if (o1 instanceof java.lang.annotation.Annotation o1a && o2
instanceof java.lang.annotation.Annotation o2a)
+ return AnnotationUtils.equals(o1a, o2a);
+
if (isArray(o1) && isArray(o2)) {
int l1 = Array.getLength(o1), l2 = Array.getLength(o2);
if (l1 != l2)
@@ -240,39 +249,6 @@ public class Utils {
return test.test(o1, o2);
}
- /**
- * Tests two annotations for equality using the criteria presented in
the {@link java.lang.annotation.Annotation#equals(Object)} API docs.
- *
- * <p>
- * This method delegates to {@link
AnnotationUtils#equals(java.lang.annotation.Annotation,
java.lang.annotation.Annotation)}
- * to ensure proper annotation comparison according to the annotation
equality contract.
- *
- * @param a1 Annotation 1.
- * @param a2 Annotation 2.
- * @return <jk>true</jk> if the two annotations are equal or both are
<jk>null</jk>.
- * @see AnnotationUtils#equals(java.lang.annotation.Annotation,
java.lang.annotation.Annotation)
- */
- public static boolean eq(java.lang.annotation.Annotation a1,
java.lang.annotation.Annotation a2) {
- return AnnotationUtils.equals(a1, a2);
- }
-
- /**
- * Tests two annotations for inequality using the criteria presented in
the {@link java.lang.annotation.Annotation#equals(Object)} API docs.
- *
- * <p>
- * This method is the negation of {@link
#eq(java.lang.annotation.Annotation, java.lang.annotation.Annotation)},
- * which delegates to {@link
AnnotationUtils#equals(java.lang.annotation.Annotation,
java.lang.annotation.Annotation)}.
- *
- * @param a1 Annotation 1.
- * @param a2 Annotation 2.
- * @return <jk>true</jk> if the two annotations are not equal.
- * @see #eq(java.lang.annotation.Annotation,
java.lang.annotation.Annotation)
- * @see AnnotationUtils#equals(java.lang.annotation.Annotation,
java.lang.annotation.Annotation)
- */
- public static boolean ne(java.lang.annotation.Annotation a1,
java.lang.annotation.Annotation a2) {
- return ! eq(a1, a2);
- }
-
/**
* Tests two strings for case-insensitive equality, but gracefully
handles nulls.
*
@@ -304,70 +280,6 @@ public class Utils {
return null;
}
- /**
- * Converts a string containing glob-style wildcard characters to a
regular expression {@link java.util.regex.Pattern}.
- *
- * <p>This method converts glob-style patterns to regular expressions
with the following mappings:
- * <ul>
- * <li>{@code *} matches any sequence of characters (including
none)</li>
- * <li>{@code ?} matches exactly one character</li>
- * <li>All other characters are treated literally</li>
- * </ul>
- *
- * <h5 class='section'>Example:</h5>
- * <p class='bjava'>
- * <jk>var</jk> <jv>pattern</jv> =
<jsm>getGlobMatchPattern</jsm>(<js>"user_*_temp"</js>);
- * <jk>boolean</jk> <jv>matches</jv> =
<jv>pattern</jv>.matcher(<js>"user_alice_temp"</js>).matches(); <jc>//
true</jc>
- * <jv>matches</jv> =
<jv>pattern</jv>.matcher(<js>"user_bob_temp"</js>).matches(); <jc>//
true</jc>
- * <jv>matches</jv> =
<jv>pattern</jv>.matcher(<js>"admin_alice_temp"</js>).matches(); <jc>//
false</jc>
- * </p>
- *
- * @param s The glob-style wildcard pattern string.
- * @return A compiled {@link java.util.regex.Pattern} object, or
<jk>null</jk> if the input string is <jk>null</jk>.
- */
- public static java.util.regex.Pattern getGlobMatchPattern(String s) {
- return getGlobMatchPattern(s, 0);
- }
-
- /**
- * Converts a string containing glob-style wildcard characters to a
regular expression {@link java.util.regex.Pattern} with flags.
- *
- * <p>This method converts glob-style patterns to regular expressions
with the following mappings:
- * <ul>
- * <li>{@code *} matches any sequence of characters (including
none)</li>
- * <li>{@code ?} matches exactly one character</li>
- * <li>All other characters are treated literally</li>
- * </ul>
- *
- * <h5 class='section'>Example:</h5>
- * <p class='bjava'>
- * <jc>// Case-insensitive matching</jc>
- * <jk>var</jk> <jv>pattern</jv> =
<jsm>getGlobMatchPattern</jsm>(<js>"USER_*"</js>,
Pattern.<jsf>CASE_INSENSITIVE</jsf>);
- * <jk>boolean</jk> <jv>matches</jv> =
<jv>pattern</jv>.matcher(<js>"user_alice"</js>).matches(); <jc>// true</jc>
- * </p>
- *
- * @param s The glob-style wildcard pattern string.
- * @param flags Regular expression flags (see {@link
java.util.regex.Pattern} constants).
- * @return A compiled {@link java.util.regex.Pattern} object, or
<jk>null</jk> if the input string is <jk>null</jk>.
- */
- public static java.util.regex.Pattern getGlobMatchPattern(String s, int
flags) {
- if (s == null)
- return null;
- var sb = new StringBuilder();
- sb.append("\\Q");
- for (var i = 0; i < s.length(); i++) {
- var c = s.charAt(i);
- if (c == '*')
- sb.append("\\E").append(".*").append("\\Q");
- else if (c == '?')
- sb.append("\\E").append(".").append("\\Q");
- else
- sb.append(c);
- }
- sb.append("\\E");
- return java.util.regex.Pattern.compile(sb.toString(), flags);
- }
-
/**
* Shortcut for calling {@link StringUtils#format(String, Object...)}.
*
@@ -488,30 +400,6 @@ public class Utils {
return nn(o) && o.getClass().isArray();
}
- /**
- * Returns <jk>true</jk> if the specified object can be converted to a
list.
- *
- * <p>
- * The following types are considered convertible:
- * <ul>
- * <li>Collections
- * <li>Iterables
- * <li>Iterators
- * <li>Enumerations
- * <li>Streams
- * <li>Maps
- * <li>Optional
- * <li>Arrays
- * </ul>
- *
- * @param o The object to check.
- * @return <jk>true</jk> if the object can be converted to a list.
- */
- public static boolean isConvertibleToList(Object o) {
- return nn(o) && (o instanceof Collection || o instanceof
Iterable || o instanceof Iterator || o instanceof Enumeration || o instanceof
Stream || o instanceof Map || o instanceof Optional
- || isArray(o));
- }
-
/**
* Returns <jk>true</jk> if the specified object is empty.
*
@@ -850,54 +738,6 @@ public class Utils {
}
}
- /**
- * Safely converts an object to its string representation, handling any
exceptions that may occur.
- *
- * <p>This method provides a fail-safe way to call {@code toString()}
on any object, ensuring that
- * exceptions thrown by problematic {@code toString()} implementations
don't propagate up the call stack.
- * Instead, it returns a descriptive error message containing the
exception type and message.</p>
- *
- * <h5 class='section'>Exception Handling:</h5>
- * <p>If the object's {@code toString()} method throws any {@code
Throwable}, this method catches it
- * and returns a formatted string in the form: {@code "<ExceptionType>:
<exception message>"}.</p>
- *
- * <h5 class='section'>Examples:</h5>
- * <p class='bjava'>
- * <jc>// Normal case - returns object's toString() result</jc>
- * String <jv>result</jv> =
<jsm>safeToString</jsm>(<js>"Hello"</js>);
- * <jc>// result = "Hello"</jc>
- *
- * <jc>// Exception case - returns formatted error message</jc>
- * Object <jv>problematic</jv> = <jk>new</jk> Object() {
- * <ja>@Override</ja>
- * <jk>public</jk> String toString() {
- * <jk>throw new</jk> RuntimeException(<js>"Cannot
convert"</js>);
- * }
- * };
- * String <jv>result</jv> =
<jsm>safeToString</jsm>(<jv>problematic</jv>);
- * <jc>// result = "RuntimeException: Cannot convert"</jc>
- * </p>
- *
- * <h5 class='section'>Use Cases:</h5>
- * <ul>
- * <li><b>Object stringification in converters:</b> Safe conversion
of arbitrary objects to strings</li>
- * <li><b>Debugging and logging:</b> Ensures log statements never
fail due to toString() exceptions</li>
- * <li><b>Error handling:</b> Graceful degradation when objects have
problematic string representations</li>
- * <li><b>Third-party object integration:</b> Safe handling of
objects from external libraries</li>
- * </ul>
- *
- * @param o The object to convert to a string. May be any object
including <jk>null</jk>.
- * @return The string representation of the object, or a formatted
error message if toString() throws an exception.
- * Returns <js>"null"</js> if the object is <jk>null</jk>.
- */
- public static String safeToString(Object o) {
- try {
- return o.toString();
- } catch (Throwable t) { // NOSONAR
- return cns(t) + ": " + t.getMessage();
- }
- }
-
/**
* Helper method for creating StringBuilder objects.
*
@@ -1104,54 +944,6 @@ public class Utils {
return n >= lower && n <= higher;
}
- /**
- * Same as {@link Integer#parseInt(String)} but removes any underscore
characters first.
- *
- * <p>Allows for better readability of numeric literals (e.g.,
<js>"1_000_000"</js>).
- *
- * @param value The string to parse.
- * @return The parsed integer value.
- * @throws NumberFormatException If the string cannot be parsed.
- * @throws NullPointerException If the string is <jk>null</jk>.
- */
- public static int parseInt(String value) {
- return Integer.parseInt(removeUnderscores(value));
- }
-
- /**
- * Same as {@link Long#parseLong(String)} but removes any underscore
characters first.
- *
- * <p>Allows for better readability of numeric literals (e.g.,
<js>"1_000_000"</js>).
- *
- * @param value The string to parse.
- * @return The parsed long value.
- * @throws NumberFormatException If the string cannot be parsed.
- * @throws NullPointerException If the string is <jk>null</jk>.
- */
- public static long parseLong(String value) {
- return Long.parseLong(removeUnderscores(value));
- }
-
- /**
- * Same as {@link Float#parseFloat(String)} but removes any underscore
characters first.
- *
- * <p>Allows for better readability of numeric literals (e.g.,
<js>"1_000.5"</js>).
- *
- * @param value The string to parse.
- * @return The parsed float value.
- * @throws NumberFormatException If the string cannot be parsed.
- * @throws NullPointerException If the string is <jk>null</jk>.
- */
- public static float parseFloat(String value) {
- return Float.parseFloat(removeUnderscores(value));
- }
-
- private static String removeUnderscores(String value) {
- if (value == null)
- throw new NullPointerException("Trying to parse null
string.");
- return notContains(value, '_') ? value : value.replace("_", "");
- }
-
/**
* Shortcut for calling {@link StringUtils#stringSupplier(Supplier)}.
*
@@ -1277,4 +1069,30 @@ public class Utils {
return null;
return cnsq(o) + "@" + System.identityHashCode(o);
}
+
+ /**
+ * Convenience method for calling {@link StringUtils#upperCase(String)}.
+ *
+ * <p>
+ * Converts the string to uppercase if not null.
+ *
+ * @param value The string to convert.
+ * @return The uppercase string, or <jk>null</jk> if the input was
<jk>null</jk>.
+ */
+ public static String uc(String value) {
+ return StringUtils.upperCase(value);
+ }
+
+ /**
+ * Convenience method for calling {@link StringUtils#lowerCase(String)}.
+ *
+ * <p>
+ * Converts the string to lowercase if not null.
+ *
+ * @param value The string to convert.
+ * @return The lowercase string, or <jk>null</jk> if the input was
<jk>null</jk>.
+ */
+ public static String lc(String value) {
+ return StringUtils.lowerCase(value);
+ }
}
\ No newline at end of file
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
index 7f421ce086..3b59f9d5e9 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -38,6 +38,7 @@ import org.apache.juneau.*;
import org.apache.juneau.assertions.*;
import org.apache.juneau.bean.swagger.*;
import org.apache.juneau.bean.swagger.Swagger;
+import org.apache.juneau.common.utils.*;
import org.apache.juneau.config.*;
import org.apache.juneau.cp.*;
import org.apache.juneau.http.annotation.*;
@@ -1195,7 +1196,7 @@ public class RestRequest extends
HttpServletRequestWrapper {
var x = inner.getProtocol();
var i = x.indexOf('/');
var j = x.indexOf('.', i);
- var pv = new ProtocolVersion(x.substring(0, i),
parseInt(x.substring(i + 1, j)), parseInt(x.substring(j + 1)));
+ var pv = new ProtocolVersion(x.substring(0, i),
StringUtils.parseInt(x.substring(i + 1, j)), StringUtils.parseInt(x.substring(j
+ 1)));
return new BasicRequestLine(inner.getMethod(),
inner.getRequestURI(), pv);
}
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/common/utils/StringUtils_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/common/utils/StringUtils_Test.java
index 0422f64e62..e0c62dd1b6 100755
---
a/juneau-utest/src/test/java/org/apache/juneau/common/utils/StringUtils_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/common/utils/StringUtils_Test.java
@@ -3100,18 +3100,18 @@ class StringUtils_Test extends TestBase {
//====================================================================================================
@Test
void a77_lc() {
- assertNull(lc(null));
- assertEquals("", lc(""));
- assertEquals("hello", lc("HELLO"));
- assertEquals("hello world", lc("Hello World"));
+ assertNull(lowerCase(null));
+ assertEquals("", lowerCase(""));
+ assertEquals("hello", lowerCase("HELLO"));
+ assertEquals("hello world", lowerCase("Hello World"));
}
@Test
void a78_uc() {
- assertNull(uc(null));
- assertEquals("", uc(""));
- assertEquals("HELLO", uc("hello"));
- assertEquals("HELLO WORLD", uc("Hello World"));
+ assertNull(upperCase(null));
+ assertEquals("", upperCase(""));
+ assertEquals("HELLO", upperCase("hello"));
+ assertEquals("HELLO WORLD", upperCase("Hello World"));
}
//====================================================================================================
@@ -3574,7 +3574,14 @@ class StringUtils_Test extends TestBase {
appendIfNotEmpty(sb2, "suffix");
assertEquals("prefixsuffix", sb2.toString());
- assertThrows(IllegalArgumentException.class, () ->
appendIfNotEmpty(null, "test"));
+ // Auto-create StringBuilder if null and append occurs
+ var sb3 = appendIfNotEmpty(null, "test");
+ assertNotNull(sb3);
+ assertEquals("test", sb3.toString());
+
+ // Return null if null and no append occurs
+ assertNull(appendIfNotEmpty(null, null));
+ assertNull(appendIfNotEmpty(null, ""));
}
//====================================================================================================
@@ -3605,7 +3612,16 @@ class StringUtils_Test extends TestBase {
appendIfNotBlank(sb2, "suffix");
assertEquals("prefixsuffix", sb2.toString());
- assertThrows(IllegalArgumentException.class, () ->
appendIfNotBlank(null, "test"));
+ // Auto-create StringBuilder if null and append occurs
+ var sb3 = appendIfNotBlank(null, "test");
+ assertNotNull(sb3);
+ assertEquals("test", sb3.toString());
+
+ // Return null if null and no append occurs
+ assertNull(appendIfNotBlank(null, null));
+ assertNull(appendIfNotBlank(null, ""));
+ assertNull(appendIfNotBlank(null, " "));
+ assertNull(appendIfNotBlank(null, "\t\n"));
}
//====================================================================================================
@@ -3641,7 +3657,17 @@ class StringUtils_Test extends TestBase {
appendWithSeparator(sb4, "test", ", ");
assertEquals("test", sb4.toString());
- assertThrows(IllegalArgumentException.class, () ->
appendWithSeparator(null, "test", ", "));
+ // Auto-create StringBuilder if null and append occurs
+ var sb5 = appendWithSeparator(null, "test", ", ");
+ assertNotNull(sb5);
+ assertEquals("test", sb5.toString());
+
+ var sb6 = appendWithSeparator(null, "first", ", ");
+ appendWithSeparator(sb6, "second", ", ");
+ assertEquals("first, second", sb6.toString());
+
+ // Return null if null and no append occurs
+ assertNull(appendWithSeparator(null, null, ", "));
}
//====================================================================================================