001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.common.utils;
018
019import static java.util.stream.Collectors.*;
020import static org.apache.juneau.common.utils.StringUtils.*;
021
022import java.io.*;
023import java.lang.reflect.*;
024import java.nio.charset.*;
025import java.text.*;
026import java.time.format.*;
027import java.util.*;
028import java.util.concurrent.*;
029import java.util.function.*;
030import java.util.regex.*;
031import java.util.stream.*;
032
033/**
034 * Common utility methods.
035 *
036 * <p>This class contains various static utility methods for working with collections, strings, objects, and other common operations.
037 */
038public class Utils {
039
040   private static final Map<Class<?>,Function<String,?>> ENV_FUNCTIONS = new IdentityHashMap<>();
041
042   static {
043      ENV_FUNCTIONS.put(Boolean.class, Boolean::valueOf);
044      ENV_FUNCTIONS.put(Charset.class, Charset::forName);
045   }
046
047   private static final ConcurrentHashMap<String,String> PROPERTY_TO_ENV = new ConcurrentHashMap<>();
048
049   /**
050    * Creates an array of objects.
051    *
052    * @param <T> The component type of the array.
053    * @param x The objects to place in the array.
054    * @return A new array containing the specified objects.
055    */
056   @SafeVarargs
057   public static <T> T[] a(T...x) {
058      return x;
059   }
060
061   /**
062    * Traverses all elements in the specified object and accumulates them into a list.
063    *
064    * @param <T> The element type.
065    * @param o The object to traverse.
066    * @return A list containing all accumulated elements.
067    */
068   public static <T> List<T> accumulate(Object o) {
069      var l = list();
070      traverse(o, l::add);
071      return (List<T>) l;
072   }
073
074   /**
075    * Shortcut for creating an unmodifiable list out of an array of values.
076    *
077    * @param <T> The element type.
078    * @param values The values to add to the list.
079    * @return An unmodifiable list containing the specified values, or <jk>null</jk> if the input is <jk>null</jk>.
080    */
081   @SafeVarargs
082   public static <T> List<T> alist(T...values) {  // NOSONAR
083      return values == null ? null : Arrays.asList(values);
084   }
085
086   /**
087    * Converts the specified collection to an array.
088    *
089    * @param <E> The element type.
090    * @param value The collection to convert.
091    * @param componentType The component type of the array.
092    * @return A new array.
093    */
094   public static <E> E[] array(Collection<E> value, Class<E> componentType) {
095      if (value == null)
096         return null;
097      E[] array = (E[])Array.newInstance(componentType, value.size());
098      return value.toArray(array);
099   }
100
101   /**
102    * Converts any array (including primitive arrays) to a List.
103    *
104    * @param array The array to convert. Can be any array type including primitives.
105    * @return A List containing the array elements. Primitive values are auto-boxed.
106    *         Returns null if the input is null.
107    * @throws IllegalArgumentException if the input is not an array.
108    */
109   public static List<Object> arrayToList(Object array) {
110      if (array == null) {
111         return null;  // NOSONAR
112      }
113
114      assertArg(isArray(array), "Input must be an array but was {0}", array.getClass().getName());
115
116      var componentType = array.getClass().getComponentType();
117      var length = Array.getLength(array);
118      var result = new ArrayList<>(length);
119
120      // Handle primitive arrays specifically for better performance
121      if (componentType.isPrimitive()) {
122         if (componentType == int.class) {
123            var arr = (int[]) array;
124            for (int value : arr) {
125               result.add(value);
126            }
127         } else if (componentType == long.class) {
128            var arr = (long[]) array;
129            for (long value : arr) {
130               result.add(value);
131            }
132         } else if (componentType == double.class) {
133            var arr = (double[]) array;
134            for (double value : arr) {
135               result.add(value);
136            }
137         } else if (componentType == float.class) {
138            var arr = (float[]) array;
139            for (float value : arr) {
140               result.add(value);
141            }
142         } else if (componentType == boolean.class) {
143            var arr = (boolean[]) array;
144            for (boolean value : arr) {
145               result.add(value);
146            }
147         } else if (componentType == byte.class) {
148            var arr = (byte[]) array;
149            for (byte value : arr) {
150               result.add(value);
151            }
152         } else if (componentType == char.class) {
153            var arr = (char[]) array;
154            for (char value : arr) {
155               result.add(value);
156            }
157         } else if (componentType == short.class) {
158            var arr = (short[]) array;
159            for (short value : arr) {
160               result.add(value);
161            }
162         }
163      } else {
164         // Handle Object arrays
165         for (var i = 0; i < length; i++) {
166            result.add(Array.get(array, i));
167         }
168      }
169
170      return result;
171   }
172
173   /**
174    * Throws an {@link IllegalArgumentException} if the specified expression is <jk>false</jk>.
175    *
176    * <h5 class='section'>Example:</h5>
177    * <p class='bjava'>
178    *    <jk>import static</jk> org.apache.juneau.internal.ArgUtils.*;
179    *
180    * <jk>public</jk> String setFoo(List&lt;String&gt; <jv>foo</jv>) {
181    *    <jsm>assertArg</jsm>(<jv>foo</jv> != <jk>null</jk> &amp;&amp; ! <jv>foo</jv>.isEmpty(), <js>"'foo' cannot be null or empty."</js>);
182    *    ...
183    * }
184    * </p>
185    *
186    * @param expression The boolean expression to check.
187    * @param msg The exception message.
188    * @param args The exception message args.
189    * @throws IllegalArgumentException Constructed exception.
190    */
191   public static final void assertArg(boolean expression, String msg, Object...args) throws IllegalArgumentException {
192      if (! expression)
193         throw new IllegalArgumentException(MessageFormat.format(msg, args));
194   }
195
196   /**
197    * Throws an {@link IllegalArgumentException} if the specified argument is <jk>null</jk>.
198    *
199    * <h5 class='section'>Example:</h5>
200    * <p class='bjava'>
201    *    <jk>import static</jk> org.apache.juneau.internal.ArgUtils.*;
202    *
203    * <jk>public</jk> String setFoo(String <jv>foo</jv>) {
204    *    <jsm>assertArgNotNull</jsm>(<js>"foo"</js>, <jv>foo</jv>);
205    *    ...
206    * }
207    * </p>
208    *
209    * @param <T> The argument data type.
210    * @param name The argument name.
211    * @param o The object to check.
212    * @return The same argument.
213    * @throws IllegalArgumentException Constructed exception.
214    */
215   public static final <T> T assertArgNotNull(String name, T o) throws IllegalArgumentException {
216      assertArg(o != null, "Argument ''{0}'' cannot be null.", name);
217      return o;
218   }
219
220   /**
221    * Throws an {@link IllegalArgumentException} if the specified string is <jk>null</jk> or blank.
222    *
223    * @param name The argument name.
224    * @param o The object to check.
225    * @return The same object.
226    * @throws IllegalArgumentException Thrown if the specified string is <jk>null</jk> or blank.
227    */
228   public static final String assertArgNotNullOrBlank(String name, String o) throws IllegalArgumentException {
229      assertArg(o != null, "Argument ''{0}'' cannot be null.", name);
230      assertArg(! o.isBlank(), "Argument ''{0}'' cannot be blank.", name);
231      return o;
232   }
233
234   /**
235    * Throws an {@link IllegalArgumentException} if the specified varargs array or any of its elements are <jk>null</jk>.
236    *
237    * @param <T> The element type.
238    * @param name The argument name.
239    * @param o The object to check.
240    * @return The same object.
241    * @throws IllegalArgumentException Thrown if the specified varargs array or any of its elements are <jk>null</jk>.
242    */
243   public static final <T> T[] assertVarargsNotNull(String name, T[] o) throws IllegalArgumentException {
244      assertArg(o != null, "Argument ''{0}'' cannot be null.", name);
245      for (int i = 0; i < o.length; i++)
246         assertArg(o[i] != null, "Argument ''{0}'' parameter {1} cannot be null.", name, i);
247      return o;
248   }
249
250   /**
251    * Throws an {@link IllegalArgumentException} if the specified value doesn't have all subclasses of the specified type.
252    *
253    * @param <E> The element type.
254    * @param name The argument name.
255    * @param type The expected parent class.
256    * @param value The array value being checked.
257    * @return The value cast to the specified array type.
258    * @throws IllegalArgumentException Constructed exception.
259    */
260   public static final <E> Class<E>[] assertClassArrayArgIsType(String name, Class<E> type, Class<?>[] value) throws IllegalArgumentException {
261      for (var i = 0; i < value.length; i++)
262         if (! type.isAssignableFrom(value[i]))
263            throw new IllegalArgumentException("Arg "+name+" did not have arg of type "+type.getName()+" at index "+i+": "+value[i].getName());
264      return (Class<E>[])value;
265   }
266
267   /**
268    * Casts an object to a specific type if it's an instance of that type.
269    *
270    * @param <T> The type to cast to.
271    * @param c The type to cast to.
272    * @param o The object to cast to.
273    * @return The cast object, or <jk>null</jk> if the object wasn't the specified type.
274    */
275   public static <T> T cast(Class<T> c, Object o) {
276      return o != null && c.isInstance(o) ? c.cast(o) : null;
277   }
278
279   /**
280    * If the specified object is an instance of the specified class, casts it to that type.
281    *
282    * @param <T> The class to cast to.
283    * @param o The object to cast.
284    * @param c The class to cast to.
285    * @return The cast object, or <jk>null</jk> if the object wasn't an instance of the specified class.
286    */
287   public static <T> T castOrNull(Object o, Class<T> c) {
288      if (c.isInstance(o))
289         return c.cast(o);
290      return null;
291   }
292
293   /**
294    * Compares two objects for equality.
295    *
296    * <p>
297    * Nulls are always considered less-than unless both are null.
298    *
299    * @param o1 Object 1.
300    * @param o2 Object 2.
301    * @return
302    *    <c>-1</c>, <c>0</c>, or <c>1</c> if <c>o1</c> is less-than, equal, or greater-than <c>o2</c>.
303    * <br><c>0</c> if objects are not of the same type or do not implement the {@link Comparable} interface.
304    */
305   @SuppressWarnings({ "rawtypes", "unchecked" })
306   public static int compare(Object o1, Object o2) {
307      if (o1 == null) {
308         if (o2 == null)
309            return 0;
310         return -1;
311      } else if (o2 == null) {
312         return 1;
313      }
314
315      if (o1.getClass() == o2.getClass() && o1 instanceof Comparable)
316         return ((Comparable)o1).compareTo(o2);
317
318      return 0;
319   }
320
321   /**
322    * Null-safe {@link String#contains(CharSequence)} operation.
323    *
324    * @param s The string to check.
325    * @param values The characters to check for.
326    * @return <jk>true</jk> if the string contains any of the specified characters.
327    */
328   public static boolean contains(String s, char...values) {
329      if (s == null || values == null || values.length == 0)
330         return false;
331      for (var v : values) {
332         if (s.indexOf(v) >= 0)
333            return true;
334      }
335      return false;
336   }
337
338   /**
339    * Null-safe {@link String#contains(CharSequence)} operation.
340    *
341    * @param s The string to check.
342    * @param values The substrings to check for.
343    * @return <jk>true</jk> if the string contains any of the specified substrings.
344    */
345   public static boolean contains(String s, String...values) {
346      if (s == null || values == null || values.length == 0)
347         return false;
348      for (var v : values) {
349         if (s.contains(v))
350            return true;
351      }
352      return false;
353   }
354
355   /**
356    * Creates an empty array of the specified type.
357    *
358    * @param <T> The component type of the array.
359    * @param type The component type class.
360    * @return An empty array of the specified type.
361    */
362   public static <T> T[] ea(Class<T> type) {
363      return (T[])Array.newInstance(type, 0);
364   }
365
366   /**
367    * Shortcut for creating an empty list of the specified type.
368    *
369    * @param <T> The element type.
370    * @param type The element type class.
371    * @return An empty list.
372    */
373   public static <T> List<T> elist(Class<T> type) {
374      return Collections.emptyList();
375   }
376
377   /**
378    * Shortcut for creating an empty map of the specified types.
379    *
380    * @param <K> The key type.
381    * @param <V> The value type.
382    * @param keyType The key type class.
383    * @param valueType The value type class.
384    * @return An empty unmodifiable map.
385    */
386   public static <K,V> Map<K,V> emap(Class<K> keyType, Class<V> valueType) {
387      return Collections.emptyMap();
388   }
389
390   /**
391    * Returns an empty {@link Optional}.
392    *
393    * @param <T> The component type.
394    * @return An empty {@link Optional}.
395    */
396   public static <T> Optional<T> empty() {
397      return Optional.empty();
398   }
399
400   /**
401    * Returns the specified string, or blank if that string is null.
402    *
403    * @param value The value to convert to a string.
404    * @return The string representation of the value, or an empty string if <jk>null</jk>.
405    */
406   public static String emptyIfNull(Object value) {
407      return value == null ? "" : value.toString();
408   }
409
410   /**
411    * Looks up a system property or environment variable.
412    *
413    * <p>
414    * First looks in system properties.  Then converts the name to env-safe and looks in the system environment.
415    * Then returns the default if it can't be found.
416    *
417    * @param name The property name.
418    * @return The value if found.
419    */
420   public static Optional<String> env(String name) {
421      var s = System.getProperty(name);
422      if (s == null)
423         s = System.getenv(envName(name));
424      return opt(s);
425   }
426
427   /**
428    * Looks up a system property or environment variable.
429    *
430    * <p>
431    * First looks in system properties.  Then converts the name to env-safe and looks in the system environment.
432    * Then returns the default if it can't be found.
433    *
434    * @param <T> The type to convert the value to.
435    * @param name The property name.
436    * @param def The default value if not found.
437    * @return The default value.
438    */
439   public static <T> T env(String name, T def) {
440      return env(name).map(x -> toType(x, def)).orElse(def);
441   }
442
443   /**
444    * Converts a property name to an environment variable name.
445    *
446    * @param name The property name to convert.
447    * @return The environment variable name (uppercase with dots replaced by underscores).
448    */
449   private static String envName(String name) {
450      return PROPERTY_TO_ENV.computeIfAbsent(name, x->x.toUpperCase().replace(".", "_"));
451   }
452
453   /**
454    * Tests two strings for equality, but gracefully handles nulls.
455    *
456    * @param caseInsensitive Use case-insensitive matching.
457    * @param s1 String 1.
458    * @param s2 String 2.
459    * @return <jk>true</jk> if the strings are equal.
460    */
461   public static boolean eq(boolean caseInsensitive, String s1, String s2) {
462      return caseInsensitive ? eqic(s1, s2) : eq(s1, s2);
463   }
464
465   /**
466    * Tests two objects for equality, gracefully handling nulls and arrays.
467    *
468    * @param <T> The value types.
469    * @param o1 Object 1.
470    * @param o2 Object 2.
471    * @return <jk>true</jk> if both objects are equal based on the {@link Object#equals(Object)} method.
472    */
473   public static <T> boolean eq(T o1, T o2) {
474      if (isArray(o1) && isArray(o2)) {
475         int l1 = Array.getLength(o1), l2 = Array.getLength(o2);
476         if (l1 != l2)
477            return false;
478         for (int i = 0; i < l1; i++)
479            if (! eq(Array.get(o1, i), Array.get(o2, i)))
480               return false;
481         return true;
482      }
483      return Objects.equals(o1, o2);
484   }
485
486   /**
487    * Tests two objects for equality, gracefully handling nulls.
488    *
489    * Allows you to simplify object comparison without sacrificing efficiency.
490    *
491    * Example:
492    * <code>
493    *    public boolean equals(Object o)
494    *       return eq(this, (Role)o, (x,y)-&gt;eq(x.id,y.id) &amp;&amp; eq(x.name,y.name) &amp;&amp; eq(x.created,y.created) &amp;&amp; eq(x.createdBy,y.createdBy));
495    *    }
496    * </code>
497    *
498    * @param <T> Object 1 type.
499    * @param <U> Object 2 type.
500    * @param o1 Object 1.
501    * @param o2 Object 2.
502    * @param test The test to use for equality.
503    * @return <jk>true</jk> if both objects are equal based on the test.
504    */
505   public static <T,U> boolean eq(T o1, U o2, BiPredicate<T,U> test) {
506      if (o1 == null) { return o2 == null; }
507      if (o2 == null) { return false; }
508      if (o1 == o2) { return true; }
509      return test.test(o1, o2);
510   }
511
512   /**
513    * Tests two strings for case-insensitive equality, but gracefully handles nulls.
514    *
515    * @param s1 String 1.
516    * @param s2 String 2.
517    * @return <jk>true</jk> if the strings are equal.
518    */
519   public static boolean eqic(String s1, String s2) {
520      if (s1 == null)
521         return s2 == null;
522      if (s2 == null)
523         return false;
524      return s1.equalsIgnoreCase(s2);
525   }
526
527   /**
528    * Same as MessageFormat.format().
529    *
530    * @param pattern The message pattern.
531    * @param args The arguments to substitute into the pattern.
532    * @return The formatted string.
533    */
534   public static String f(String pattern, Object...args) {
535      if (args.length == 0)
536         return pattern;
537      return MessageFormat.format(pattern, args);
538   }
539
540   /**
541    * Returns the first non-null value in the specified array
542    *
543    * @param <T> The value types.
544    * @param t The values to check.
545    * @return The first non-null value, or <jk>null</jk> if the array is null or empty or contains only <jk>null</jk> values.
546    */
547   @SafeVarargs
548   public static <T> T firstNonNull(T... t) {
549      if (t != null)
550         for (T tt : t)
551            if (tt != null)
552               return tt;
553      return null;
554   }
555
556   /**
557    * Creates a formatted string supplier with message arguments for lazy evaluation.
558    *
559    * <p>This method returns a {@link Supplier} that formats the string pattern with the provided arguments
560    * only when the supplier's {@code get()} method is called. This is useful for expensive string formatting
561    * operations that may not always be needed, such as error messages in assertions.</p>
562    *
563    * <h5 class='section'>Usage Examples:</h5>
564    * <p class='bjava'>
565    *    <jc>// Lazy evaluation - string is only formatted if assertion fails</jc>
566    *    assertTrue(condition, fms(<js>"Expected {0} but got {1}"</js>, expected, actual));
567    *
568    *    <jc>// Can be used anywhere a Supplier&lt;String&gt; is expected</jc>
569    *    Supplier&lt;String&gt; <jv>messageSupplier</jv> = fms(<js>"Processing item {0} of {1}"</js>, i, total);
570    * </p>
571    *
572    * @param pattern The message pattern using <js>{0}</js>, <js>{1}</js>, etc. placeholders.
573    * @param args The arguments to substitute into the pattern placeholders.
574    * @return A {@link Supplier} that will format the string when {@code get()} is called.
575    * @see StringUtils#format(String, Object...)
576    */
577   public static Supplier<String> fms(String pattern, Object...args) {
578      return ()->StringUtils.format(pattern, args);
579   }
580
581   /**
582    * Converts a string containing <js>"*"</js> meta characters with a regular expression pattern.
583    *
584    * @param s The string to create a pattern from.
585    * @return A regular expression pattern.
586    */
587   public static Pattern getMatchPattern3(String s) {
588      return getMatchPattern3(s, 0);
589   }
590
591   /**
592    * Converts a string containing <js>"*"</js> meta characters with a regular expression pattern.
593    *
594    * @param s The string to create a pattern from.
595    * @param flags Regular expression flags.
596    * @return A regular expression pattern.
597    */
598   public static Pattern getMatchPattern3(String s, int flags) {
599      if (s == null)
600         return null;
601      var sb = new StringBuilder();
602      sb.append("\\Q");
603      for (var i = 0; i < s.length(); i++) {
604         var c = s.charAt(i);
605         if (c == '*')
606            sb.append("\\E").append(".*").append("\\Q");
607         else if (c == '?')
608            sb.append("\\E").append(".").append("\\Q");
609         else
610            sb.append(c);
611      }
612      sb.append("\\E");
613      return Pattern.compile(sb.toString(), flags);
614   }
615
616   /**
617    * Shortcut for calling {@link Objects#hash(Object...)}.
618    *
619    * @param values The values to hash.
620    * @return A hash code value for the given values.
621    */
622   public static final int hash(Object...values) {
623      return Objects.hash(values);
624   }
625
626   /**
627    * Creates an {@link IllegalArgumentException}.
628    *
629    * @param msg The exception message.
630    * @param args The arguments to substitute into the message.
631    * @return A new IllegalArgumentException with the formatted message.
632    */
633   public static IllegalArgumentException illegalArg(String msg, Object...args) {
634      return new IllegalArgumentException(args.length == 0 ? msg : f(msg, args));
635   }
636
637   /**
638    * Checks if the specified object is an array.
639    *
640    * @param o The object to check.
641    * @return <jk>true</jk> if the object is not <jk>null</jk> and is an array.
642    */
643   public static boolean isArray(Object o) {
644      return o != null && o.getClass().isArray();
645   }
646
647   /**
648    * Returns <jk>true</jk> if the specified object is empty.
649    *
650    * <p>
651    * Return <jk>true</jk> if the value is any of the following:
652    * <ul>
653    *    <li><jk>null</jk>
654    *    <li>An empty Collection
655    *    <li>An empty Map
656    *    <li>An empty array
657    *    <li>An empty CharSequence
658    *    <li>An empty String when serialized to a string using {@link Object#toString()}.
659    * </ul>
660    *
661    * @param o The object to test.
662    * @return <jk>true</jk> if the specified object is empty.
663    */
664   public static boolean isEmpty(Object o) {
665      if (o == null)
666         return true;
667      if (o instanceof Collection<?> o2)
668         return  o2.isEmpty();
669      if (o instanceof Map<?,?> o2)
670         return o2.isEmpty();
671      if (isArray(o))
672         return (Array.getLength(o) == 0);
673      return o.toString().isEmpty();
674   }
675
676   /**
677    * Returns <jk>true</jk> if string is <jk>null</jk> or empty.
678    *
679    * @param o The string to check.
680    * @return <jk>true</jk> if string is <jk>null</jk> or empty.
681    */
682   public static boolean isEmpty(String o) {
683      return o == null || o.isEmpty();
684   }
685
686   /**
687    * Returns <jk>true</jk> if specified string is <jk>null</jk> or empty or consists of only blanks.
688    *
689    * @param s The string to check.
690    * @return <jk>true</jk> if specified string is <jk>null</jk> or empty or consists of only blanks.
691    */
692   public static boolean isEmptyOrBlank(String s) {
693      return s == null || s.trim().isEmpty();
694   }
695
696   /**
697    * Returns <jk>true</jk> if the specified object is not <jk>null</jk> and not empty.
698    *
699    * Works on any of the following data types:  String, CharSequence, Collection, Map, array.
700    * All other types are stringified and then checked as a String.
701    *
702    * @param value The value being checked.
703    * @return <jk>true</jk> if the specified object is not <jk>null</jk> and not empty.
704    */
705   public static boolean isNotEmpty(Object value) {
706      if (value == null) return false;
707      if (value instanceof CharSequence x) return ! x.isEmpty();
708      if (value instanceof Collection<?> x) return ! x.isEmpty();
709      if (value instanceof Map<?,?> x) return ! x.isEmpty();
710      if (isArray(value)) return Array.getLength(value) > 0;
711      return isNotEmpty(s(value));
712   }
713
714   /**
715    * Returns <jk>true</jk> if string is not <jk>null</jk> and not empty.
716    *
717    * @param o The string to check.
718    * @return <jk>true</jk> if string is not <jk>null</jk> and not empty.
719    */
720   public static boolean isNotEmpty(String o) {
721      return ! isEmpty(o);
722   }
723
724   /**
725    * Returns <jk>true</jk> if the specified number is not <jk>null</jk> and not <c>-1</c>.
726    *
727    * @param <T> The value types.
728    * @param value The value being checked.
729    * @return <jk>true</jk> if the specified number is not <jk>null</jk> and not <c>-1</c>.
730    */
731   public static <T extends Number> boolean isNotMinusOne(T value) {
732      return value != null && value.intValue() != -1;
733   }
734
735   /**
736    * Returns <jk>true</jk> if the specified object is not <jk>null</jk>.
737    *
738    * @param <T> The value type.
739    * @param value The value being checked.
740    * @return <jk>true</jk> if the specified object is not <jk>null</jk>.
741    */
742   public static <T> boolean isNotNull(T value) {
743      return value != null;
744   }
745
746   /**
747    * Returns <jk>true</jk> if the specified boolean is not <jk>null</jk> and is <jk>true</jk>.
748    *
749    * @param value The value being checked.
750    * @return <jk>true</jk> if the specified boolean is not <jk>null</jk> and is <jk>true</jk>.
751    */
752   public static boolean isTrue(Boolean value) {
753      return value != null && value;
754   }
755
756   /**
757    * Join the specified tokens into a delimited string.
758    *
759    * @param tokens The tokens to join.
760    * @param d The delimiter.
761    * @return The delimited string.  If <c>tokens</c> is <jk>null</jk>, returns <jk>null</jk>.
762    */
763   public static String join(Collection<?> tokens, char d) {
764      if (tokens == null)
765         return null;
766      var sb = new StringBuilder();
767      for (var iter = tokens.iterator(); iter.hasNext();) {
768         sb.append(iter.next());
769         if (iter.hasNext())
770            sb.append(d);
771      }
772      return sb.toString();
773   }
774
775   /**
776    * Join the specified tokens into a delimited string.
777    *
778    * @param tokens The tokens to join.
779    * @param d The delimiter.
780    * @return The delimited string.  If <c>tokens</c> is <jk>null</jk>, returns <jk>null</jk>.
781    */
782   public static String join(Collection<?> tokens, String d) {
783      if (tokens == null)
784         return null;
785      return join(tokens, d, new StringBuilder()).toString();
786   }
787
788   /**
789    * Joins the specified tokens into a delimited string and writes the output to the specified string builder.
790    *
791    * @param tokens The tokens to join.
792    * @param d The delimiter.
793    * @param sb The string builder to append the response to.
794    * @return The same string builder passed in as <c>sb</c>.
795    */
796   public static StringBuilder join(Collection<?> tokens, String d, StringBuilder sb) {
797      if (tokens == null)
798         return sb;
799      for (var iter = tokens.iterator(); iter.hasNext();) {
800         sb.append(iter.next());
801         if (iter.hasNext())
802            sb.append(d);
803      }
804      return sb;
805   }
806
807   /**
808    * Join the specified tokens into a delimited string.
809    *
810    * @param tokens The tokens to join.
811    * @param d The delimiter.
812    * @return The delimited string.  If <c>tokens</c> is <jk>null</jk>, returns <jk>null</jk>.
813    */
814   public static String join(int[] tokens, char d) {
815      if (tokens == null)
816         return null;
817      var sb = new StringBuilder();
818      for (var i = 0; i < tokens.length; i++) {
819         if (i > 0)
820            sb.append(d);
821         sb.append(tokens[i]);
822      }
823      return sb.toString();
824   }
825
826   /**
827    * Join the specified tokens into a delimited string.
828    *
829    * @param tokens The tokens to join.
830    * @param d The delimiter.
831    * @return The delimited string.  If <c>tokens</c> is <jk>null</jk>, returns <jk>null</jk>.
832    */
833   public static String join(List<?> tokens, char d) {
834      if (tokens == null)
835         return null;
836      var sb = new StringBuilder();
837      for (int i = 0, j = tokens.size(); i < j; i++) {
838         if (i > 0)
839            sb.append(d);
840         sb.append(tokens.get(i));
841      }
842      return sb.toString();
843   }
844
845   /**
846    * Join the specified tokens into a delimited string.
847    *
848    * @param tokens The tokens to join.
849    * @param d The delimiter.
850    * @return The delimited string.  If <c>tokens</c> is <jk>null</jk>, returns <jk>null</jk>.
851    */
852   public static String join(List<?> tokens, String d) {
853      if (tokens == null)
854         return null;
855      return join(tokens, d, new StringBuilder()).toString();
856   }
857
858   /**
859    * Joins the specified tokens into a delimited string and writes the output to the specified string builder.
860    *
861    * @param tokens The tokens to join.
862    * @param d The delimiter.
863    * @param sb The string builder to append the response to.
864    * @return The same string builder passed in as <c>sb</c>.
865    */
866   public static StringBuilder join(List<?> tokens, String d, StringBuilder sb) {
867      if (tokens == null)
868         return sb;
869      for (int i = 0, j = tokens.size(); i < j; i++) {
870         if (i > 0)
871            sb.append(d);
872         sb.append(tokens.get(i));
873      }
874      return sb;
875   }
876
877   /**
878    * Joins the specified tokens into a delimited string.
879    *
880    * @param tokens The tokens to join.
881    * @param d The delimiter.
882    * @return The delimited string.  If <c>tokens</c> is <jk>null</jk>, returns <jk>null</jk>.
883    */
884   public static String join(Object[] tokens, char d) {
885      if (tokens == null)
886         return null;
887      if (tokens.length == 1)
888         return emptyIfNull(s(tokens[0]));
889      return join(tokens, d, new StringBuilder()).toString();
890   }
891
892   /**
893    * Join the specified tokens into a delimited string and writes the output to the specified string builder.
894    *
895    * @param tokens The tokens to join.
896    * @param d The delimiter.
897    * @param sb The string builder to append the response to.
898    * @return The same string builder passed in as <c>sb</c>.
899    */
900   public static StringBuilder join(Object[] tokens, char d, StringBuilder sb) {
901      if (tokens == null)
902         return sb;
903      for (var i = 0; i < tokens.length; i++) {
904         if (i > 0)
905            sb.append(d);
906         sb.append(tokens[i]);
907      }
908      return sb;
909   }
910
911   /**
912    * Join the specified tokens into a delimited string.
913    *
914    * @param tokens The tokens to join.
915    * @param separator The delimiter.
916    * @return The delimited string.  If <c>tokens</c> is <jk>null</jk>, returns <jk>null</jk>.
917    */
918   public static String join(Object[] tokens, String separator) {
919      if (tokens == null)
920         return null;
921      var sb = new StringBuilder();
922      for (var i = 0; i < tokens.length; i++) {
923         if (i > 0)
924            sb.append(separator);
925         sb.append(tokens[i]);
926      }
927      return sb.toString();
928   }
929
930   /**
931    * Joins tokens with newlines.
932    *
933    * @param tokens The tokens to concatenate.
934    * @return A string with the specified tokens contatenated with newlines.
935    */
936   public static String joinnl(Object[] tokens) {
937      return join(tokens, '\n');
938   }
939
940   /**
941    * Shortcut for creating a modifiable list out of an array of values.
942    *
943    * @param <T> The element type.
944    * @param values The values to add to the list.
945    * @return A modifiable list containing the specified values.
946    */
947   @SafeVarargs
948   public static <T> List<T> list(T...values) {  // NOSONAR
949      return new ArrayList<>(Arrays.asList(values));
950   }
951
952   /**
953    * Convenience method for creating an {@link ArrayList} of the specified size.
954    *
955    * @param <E> The element type.
956    * @param size The initial size of the list.
957    * @return A new modifiable list.
958    */
959   public static <E> ArrayList<E> listOfSize(int size) {
960      return new ArrayList<>(size);
961   }
962
963   /**
964    * Shortcut for creating a modifiable map out of an array of key-value pairs.
965    *
966    * @param <K> The key type.
967    * @param <V> The value type.
968    * @param values The key-value pairs (alternating keys and values).
969    * @return A modifiable LinkedHashMap containing the specified key-value pairs.
970    */
971   @SafeVarargs
972   public static <K,V> LinkedHashMap<K,V> map(Object...values) {  // NOSONAR
973      var m = new LinkedHashMap<K,V>();
974      for (var i = 0; i < values.length; i+=2) {
975         m.put((K)values[i], (V)values[i+1]);
976      }
977      return m;
978   }
979
980   /**
981    * Returns <jk>null</jk> for the specified type.
982    *
983    * @param <T> The type.
984    * @param type The type class.
985    * @return <jk>null</jk>.
986    */
987   public static <T> T n(Class<T> type) {
988      return null;
989   }
990
991   /**
992    * Returns <jk>null</jk> for the specified array type.
993    *
994    * @param <T> The component type.
995    * @param type The component type class.
996    * @return <jk>null</jk>.
997    */
998   public static <T> T[] na(Class<T> type) {
999      return null;
1000   }
1001
1002   /**
1003    * Null-safe not-equals check.
1004    *
1005    * @param <T> The object type.
1006    * @param s1 Object 1.
1007    * @param s2 Object 2.
1008    * @return <jk>true</jk> if the objects are not equal.
1009    */
1010   public static <T> boolean ne(T s1, T s2) {
1011      return ! eq(s1, s2);
1012   }
1013
1014   /**
1015    * Tests two objects for inequality, gracefully handling nulls.
1016    *
1017    * @param <T> Object 1 type.
1018    * @param <U> Object 2 type.
1019    * @param o1 Object 1.
1020    * @param o2 Object 2.
1021    * @param test The test to use for equality.
1022    * @return <jk>false</jk> if both objects are equal based on the test.
1023    */
1024   public static <T,U> boolean ne(T o1, U o2, BiPredicate<T,U> test) {
1025      if (o1 == null)
1026         return o2 != null;
1027      if (o2 == null)
1028         return true;
1029      if (o1 == o2)
1030         return false;
1031      return ! test.test(o1, o2);
1032   }
1033
1034   /**
1035    * Tests two strings for non-equality ignoring case, but gracefully handles nulls.
1036    *
1037    * @param s1 String 1.
1038    * @param s2 String 2.
1039    * @return <jk>true</jk> if the strings are not equal ignoring case.
1040    */
1041   public static boolean neic(String s1, String s2) {
1042      return ! eqic(s1, s2);
1043   }
1044
1045   /**
1046    * Returns a null list.
1047    *
1048    * @param <T> The element type.
1049    * @param type The element type class.
1050    * @return <jk>null</jk>.
1051    */
1052   public static <T> List<T> nlist(Class<T> type) {
1053      return null;
1054   }
1055
1056   /**
1057    * Returns a null map.
1058    *
1059    * @param <K> The key type.
1060    * @param <V> The value type.
1061    * @param keyType The key type class.
1062    * @param valueType The value type class.
1063    * @return <jk>null</jk>.
1064    */
1065   public static <K,V> Map<K,V> nmap(Class<K> keyType, Class<V> valueType) {
1066      return null;
1067   }
1068
1069   /**
1070    * Null-safe string not-contains operation.
1071    *
1072    * @param s The string to check.
1073    * @param values The characters to check for.
1074    * @return <jk>true</jk> if the string does not contain any of the specified characters.
1075    */
1076   public static boolean notContains(String s, char...values) {
1077      return ! contains(s, values);
1078   }
1079
1080   /**
1081    * Returns the specified string, or <jk>null</jk> if that string is <jk>null</jk> or empty.
1082    *
1083    * @param value The string value to check.
1084    * @return The string value, or <jk>null</jk> if the string is <jk>null</jk> or empty.
1085    */
1086   public static String nullIfEmpty(String value) {
1087      return isEmpty(value) ? null : value;
1088   }
1089
1090   /**
1091    * Returns <jk>null</jk> if the specified string is <jk>null</jk> or empty.
1092    *
1093    * @param s The string to check.
1094    * @return <jk>null</jk> if the specified string is <jk>null</jk> or empty, or the same string if not.
1095    */
1096   public static String nullIfEmpty3(String s) {
1097      if (s == null || s.isEmpty())
1098         return null;
1099      return s;
1100   }
1101
1102   /**
1103    * Returns an obfuscated version of the specified string.
1104    *
1105    * @param s The string to obfuscate.
1106    * @return The obfuscated string with most characters replaced by asterisks.
1107    */
1108   public static String obfuscate(String s) {
1109      if (s == null || s.length() < 2)
1110         return "*";
1111      return s.substring(0, 1) + s.substring(1).replaceAll(".", "*");  // NOSONAR
1112   }
1113
1114   /**
1115    * Shortcut for calling {@link Optional#ofNullable(Object)}.
1116    *
1117    * @param <T> The object type.
1118    * @param t The object to wrap in an Optional.
1119    * @return An Optional containing the specified object, or empty if <jk>null</jk>.
1120    */
1121   public static final <T> Optional<T> opt(T t) {
1122      return Optional.ofNullable(t);
1123   }
1124
1125   /**
1126    * Returns an empty Optional.
1127    *
1128    * @param <T> The object type.
1129    * @return An empty Optional.
1130    */
1131   public static final <T> Optional<T> opte() {
1132      return Optional.empty();
1133   }
1134
1135   /**
1136    * Prints all the specified lines to System.out.
1137    *
1138    * @param lines The lines to print.
1139    */
1140   public static final void printLines(String[] lines) {
1141      for (var i = 0; i < lines.length; i++)
1142         System.out.println(String.format("%4s:" + lines[i], i+1)); // NOSONAR - NOT DEBUG
1143   }
1144
1145   /**
1146    * Converts an arbitrary object to a readable string format suitable for debugging and testing.
1147    *
1148    * <p>This method provides intelligent formatting for various Java types, recursively processing
1149    * nested structures to create human-readable representations. It's extensively used throughout
1150    * the Juneau framework for test assertions and debugging output.</p>
1151    *
1152     * <h5 class='section'>Type-Specific Formatting:</h5>
1153    * <ul>
1154    *    <li><b>null:</b> Returns <js>null</js></li>
1155    *    <li><b>Optional:</b> Recursively formats the contained value (or <js>null</js> if empty)</li>
1156    *    <li><b>Collections:</b> Formats as <js>"[item1,item2,item3]"</js> with comma-separated elements</li>
1157    *    <li><b>Maps:</b> Formats as <js>"{key1=value1,key2=value2}"</js> with comma-separated entries</li>
1158    *    <li><b>Map.Entry:</b> Formats as <js>"key=value"</js></li>
1159    *    <li><b>Arrays:</b> Converts to list format <js>"[item1,item2,item3]"</js></li>
1160    *    <li><b>Iterables/Iterators/Enumerations:</b> Converts to list and formats recursively</li>
1161    *    <li><b>GregorianCalendar:</b> Formats as ISO instant timestamp</li>
1162    *    <li><b>Date:</b> Formats as ISO instant string (e.g., <js>"2023-12-25T10:30:00Z"</js>)</li>
1163    *    <li><b>InputStream:</b> Converts to hexadecimal representation</li>
1164    *    <li><b>Reader:</b> Reads content and returns as string</li>
1165    *    <li><b>File:</b> Reads file content and returns as string</li>
1166    *    <li><b>byte[]:</b> Converts to hexadecimal representation</li>
1167    *    <li><b>Enum:</b> Returns the enum name via {@link Enum#name()}</li>
1168    *    <li><b>All other types:</b> Uses {@link Object#toString()}</li>
1169    * </ul>
1170    *
1171    * <h5 class='section'>Examples:</h5>
1172    * <p class='bjava'>
1173    *    <jc>// Collections</jc>
1174    *    r(List.of("a", "b", "c")) <jc>// Returns: "[a,b,c]"</jc>
1175    *    r(Set.of(1, 2, 3)) <jc>// Returns: "[1,2,3]" (order may vary)</jc>
1176    *
1177    *    <jc>// Maps</jc>
1178    *    r(Map.of("foo", "bar", "baz", 123)) <jc>// Returns: "{foo=bar,baz=123}"</jc>
1179    *
1180    *    <jc>// Arrays</jc>
1181    *    r(new int[]{1, 2, 3}) <jc>// Returns: "[1,2,3]"</jc>
1182    *    r(new String[]{"a", "b"}) <jc>// Returns: "[a,b]"</jc>
1183    *
1184    *    <jc>// Nested structures</jc>
1185    *    r(List.of(Map.of("x", 1), Set.of("a", "b"))) <jc>// Returns: "[{x=1},[a,b]]"</jc>
1186    *
1187     *   <jc>// Special types</jc>
1188    *    r(Optional.of("test")) <jc>// Returns: "test"</jc>
1189    *    r(Optional.empty()) <jc>// Returns: null</jc>
1190    *    r(new Date(1640995200000L)) <jc>// Returns: "2022-01-01T00:00:00Z"</jc>
1191    *    r(MyEnum.FOO) <jc>// Returns: "FOO"</jc>
1192    * </p>
1193    *
1194    * <h5 class='section'>Recursive Processing:</h5>
1195    * <p>The method recursively processes nested structures, so complex objects containing
1196    * collections, maps, and arrays are fully flattened into readable format. This makes it
1197    * ideal for test assertions where you need to compare complex object structures.</p>
1198    *
1199    * <h5 class='section'>Error Handling:</h5>
1200    * <p>IO operations (reading files, streams) are wrapped in safe() calls, converting
1201    * any exceptions to RuntimeExceptions. Binary data (InputStreams, byte arrays) is
1202    * converted to hexadecimal representation for readability.</p>
1203    *
1204    * @param o The object to convert to readable format. Can be <jk>null</jk>.
1205    * @return A readable string representation of the object, or <jk>null</jk> if the input was <jk>null</jk>.
1206    * @see #safe(ThrowingSupplier)
1207    */
1208   public static String r(Object o) {
1209      if (o == null)
1210         return null;
1211      if (o instanceof Optional<?> o2)
1212         return r(o2.orElse(null));
1213      if (o instanceof Collection<?> o2)
1214         return o2.stream().map(Utils::r).collect(joining(",","[","]"));
1215      if (o instanceof Map<?,?> o2)
1216         return o2.entrySet().stream().map(Utils::r).collect(joining(",","{","}"));
1217      if (o instanceof Map.Entry<?,?> o2)
1218         return r(o2.getKey()) + '=' + r(o2.getValue());
1219      if (o instanceof Iterable<?> o2)
1220         return r(toList(o2));
1221      if (o instanceof Iterator<?> o2)
1222         return r(toList(o2));
1223      if (o instanceof Enumeration<?> o2)
1224         return r(toList(o2));
1225      if (o instanceof GregorianCalendar o2)
1226         return o2.toZonedDateTime().format(DateTimeFormatter.ISO_INSTANT);
1227      if (o instanceof Date o2)
1228         return o2.toInstant().toString();
1229      if (o instanceof InputStream o2)
1230         return toHex(o2);
1231      if (o instanceof Reader o2)
1232         return safe(()->IOUtils.read(o2));
1233      if (o instanceof File o2)
1234         return safe(()->IOUtils.read(o2));
1235      if (o instanceof byte[] o2)
1236         return toHex(o2);
1237      if (o instanceof Enum o2)
1238         return o2.name();
1239      if (o instanceof Class o2)
1240         return o2.getSimpleName();
1241      if (o instanceof Executable o2) {
1242         var sb = new StringBuilder(64);
1243         sb.append(o2 instanceof Constructor ? o2.getDeclaringClass().getSimpleName() : o2.getName()).append('(');
1244         Class<?>[] pt = o2.getParameterTypes();
1245         for (int i = 0; i < pt.length; i++) {
1246            if (i > 0)
1247               sb.append(',');
1248            sb.append(pt[i].getSimpleName());
1249         }
1250         sb.append(')');
1251         return sb.toString();
1252      }
1253      if (isArray(o)) {
1254         var l = list();
1255         for (var i = 0; i < Array.getLength(o); i++) {
1256            l.add(Array.get(o, i));
1257         }
1258         return r(l);
1259      }
1260      return o.toString();
1261   }
1262
1263   /**
1264    * Creates a {@link RuntimeException}.
1265    *
1266    * @param msg The exception message.
1267    * @param args The arguments to substitute into the message.
1268    * @return A new RuntimeException with the formatted message.
1269    */
1270   public static RuntimeException runtimeException(String msg, Object...args) {
1271      return new RuntimeException(args.length == 0 ? msg : f(msg, args));
1272   }
1273
1274   /**
1275    * Shortcut for converting an object to a string.
1276    *
1277    * @param val The object to convert.
1278    * @return The string representation of the object, or <jk>null</jk> if the object is <jk>null</jk>.
1279    */
1280   public static String s(Object val) {
1281      return val == null ? null : val.toString();
1282   }
1283
1284   /**
1285    * Runs a snippet of code and encapsulates any throwable inside a {@link RuntimeException}.
1286    *
1287    * @param snippet The snippet of code to run.
1288    */
1289   public static void safe(Snippet snippet) {
1290      try {
1291         snippet.run();
1292      } catch (RuntimeException t) {
1293         throw t;
1294      } catch (Throwable t) {
1295         throw ThrowableUtils.asRuntimeException(t);
1296      }
1297   }
1298
1299   /**
1300    * Used to wrap code that returns a value but throws an exception.
1301    * Useful in cases where you're trying to execute code in a fluent method call
1302    * or are trying to eliminate untestable catch blocks in code.
1303    *
1304    * @param <T> The return type.
1305    * @param s The supplier that may throw an exception.
1306    * @return The result of the supplier execution.
1307    */
1308   public static <T> T safe(ThrowingSupplier<T> s) {
1309      try {
1310         return s.get();
1311      } catch (RuntimeException e) {
1312         throw e;
1313      } catch (Exception e) {
1314         throw new RuntimeException(e);
1315      }
1316   }
1317
1318   /**
1319    * Allows you to wrap a supplier that throws an exception so that it can be used in a fluent interface.
1320    *
1321    * @param <T> The supplier type.
1322    * @param supplier The supplier throwing an exception.
1323    * @return The supplied result.
1324    * @throws RuntimeException if supplier threw an exception.
1325    */
1326   public static <T> T safeSupplier(ThrowableUtils.SupplierWithThrowable<T> supplier) {
1327      try {
1328         return supplier.get();
1329      } catch (RuntimeException t) {
1330         throw t;
1331      } catch (Throwable t) {
1332         throw ThrowableUtils.asRuntimeException(t);
1333      }
1334   }
1335
1336   /**
1337    * Shortcut for creating a modifiable set out of an array of values.
1338    *
1339    * @param <T> The element type.
1340    * @param values The values to add to the set.
1341    * @return A modifiable LinkedHashSet containing the specified values.
1342    */
1343   @SafeVarargs
1344   public static <T> LinkedHashSet<T> set(T...values) {  // NOSONAR
1345      return new LinkedHashSet<>(Arrays.asList(values));
1346   }
1347
1348   /**
1349    * Splits a comma-delimited list into a list of strings.
1350    *
1351    * @param s The string to split.
1352    * @return A list of split strings, or an empty list if the input is <jk>null</jk>.
1353    */
1354   public static List<String> split(String s) {
1355      return s == null ? Collections.emptyList() : split(s, ',');
1356   }
1357
1358   /**
1359    * Splits a character-delimited string into a string array.
1360    *
1361    * <p>
1362    * Does not split on escaped-delimiters (e.g. "\,");
1363    * Resulting tokens are trimmed of whitespace.
1364    *
1365    * <p>
1366    * <b>NOTE:</b>  This behavior is different than the Jakarta equivalent.
1367    * split("a,b,c",',') -&gt; {"a","b","c"}
1368    * split("a, b ,c ",',') -&gt; {"a","b","c"}
1369    * split("a,,c",',') -&gt; {"a","","c"}
1370    * split(",,",',') -&gt; {"","",""}
1371    * split("",',') -&gt; {}
1372    * split(null,',') -&gt; null
1373    * split("a,b\,c,d", ',', false) -&gt; {"a","b\,c","d"}
1374    * split("a,b\\,c,d", ',', false) -&gt; {"a","b\","c","d"}
1375    * split("a,b\,c,d", ',', true) -&gt; {"a","b,c","d"}
1376    *
1377    * @param s The string to split.  Can be <jk>null</jk>.
1378    * @param c The character to split on.
1379    * @return The tokens, or <jk>null</jk> if the string was null.
1380    */
1381   public static List<String> split(String s, char c) {
1382      return split(s, c, Integer.MAX_VALUE);
1383   }
1384
1385   /**
1386    * Same as {@link #splita(String,char)} but consumes the tokens instead of creating an array.
1387    *
1388    * @param s The string to split.
1389    * @param c The character to split on.
1390    * @param consumer The consumer of the tokens.
1391    */
1392   public static void split(String s, char c, Consumer<String> consumer) {
1393      var escapeChars = StringUtils.getEscapeSet(c);
1394
1395      if (isEmpty(s))
1396         return;
1397      if (s.indexOf(c) == -1) {
1398         consumer.accept(s);
1399         return;
1400      }
1401
1402      var x1 = 0;
1403      var escapeCount = 0;
1404
1405      for (var i = 0; i < s.length(); i++) {
1406         if (s.charAt(i) == '\\')
1407            escapeCount++;
1408         else if (s.charAt(i)==c && escapeCount % 2 == 0) {
1409            var s2 = s.substring(x1, i);
1410            var s3 = StringUtils.unEscapeChars(s2, escapeChars);
1411            consumer.accept(s3.trim());  // NOSONAR - NPE not possible.
1412            x1 = i+1;
1413         }
1414         if (s.charAt(i) != '\\')
1415            escapeCount = 0;
1416      }
1417      var s2 = s.substring(x1);
1418      var s3 = StringUtils.unEscapeChars(s2, escapeChars);
1419      consumer.accept(s3.trim());  // NOSONAR - NPE not possible.
1420   }
1421
1422   /**
1423    * Same as {@link #splita(String, char)} but limits the number of tokens returned.
1424    *
1425    * @param s The string to split.  Can be <jk>null</jk>.
1426    * @param c The character to split on.
1427    * @param limit The maximum number of tokens to return.
1428    * @return The tokens, or <jk>null</jk> if the string was null.
1429    */
1430   public static List<String> split(String s, char c, int limit) {
1431
1432      var escapeChars = StringUtils.getEscapeSet(c);
1433
1434      if (s == null)
1435         return null;  // NOSONAR - Intentional.
1436      if (isEmpty(s))
1437         return Collections.emptyList();
1438      if (s.indexOf(c) == -1)
1439         return Collections.singletonList(s);
1440
1441      var l = new LinkedList<String>();
1442      var sArray = s.toCharArray();
1443      var x1 = 0;
1444      var escapeCount = 0;
1445      limit--;
1446      for (var i = 0; i < sArray.length && limit > 0; i++) {
1447         if (sArray[i] == '\\')
1448            escapeCount++;
1449         else if (sArray[i]==c && escapeCount % 2 == 0) {
1450            var s2 = new String(sArray, x1, i-x1);
1451            var s3 = StringUtils.unEscapeChars(s2, escapeChars);
1452            l.add(s3.trim());
1453            limit--;
1454            x1 = i+1;
1455         }
1456         if (sArray[i] != '\\')
1457            escapeCount = 0;
1458      }
1459      var s2 = new String(sArray, x1, sArray.length-x1);
1460      var s3 = StringUtils.unEscapeChars(s2, escapeChars);
1461      l.add(s3.trim());
1462
1463      return l;
1464   }
1465
1466   /**
1467    * Same as {@link #splita(String)} but consumes the tokens instead of creating an array.
1468    *
1469    * @param s The string to split.
1470    * @param consumer The consumer of the tokens.
1471    */
1472   public static void split(String s, Consumer<String> consumer) {
1473      split(s, ',', consumer);
1474   }
1475
1476   /**
1477    * Splits a comma-delimited list into an array of strings.
1478    *
1479    * @param s The string to split.
1480    * @return An array of split strings.
1481    */
1482   public static String[] splita(String s) {
1483      return splita(s, ',');
1484   }
1485
1486   /**
1487    * Splits a character-delimited string into a string array.
1488    *
1489    * <p>
1490    * Does not split on escaped-delimiters (e.g. "\,");
1491    * Resulting tokens are trimmed of whitespace.
1492    *
1493    * <p>
1494    * <b>NOTE:</b>  This behavior is different than the Jakarta equivalent.
1495    * split("a,b,c",',') -&gt; {"a","b","c"}
1496    * split("a, b ,c ",',') -&gt; {"a","b","c"}
1497    * split("a,,c",',') -&gt; {"a","","c"}
1498    * split(",,",',') -&gt; {"","",""}
1499    * split("",',') -&gt; {}
1500    * split(null,',') -&gt; null
1501    * split("a,b\,c,d", ',', false) -&gt; {"a","b\,c","d"}
1502    * split("a,b\\,c,d", ',', false) -&gt; {"a","b\","c","d"}
1503    * split("a,b\,c,d", ',', true) -&gt; {"a","b,c","d"}
1504    *
1505    * @param s The string to split.  Can be <jk>null</jk>.
1506    * @param c The character to split on.
1507    * @return The tokens, or <jk>null</jk> if the string was null.
1508    */
1509   public static String[] splita(String s, char c) {
1510      return splita(s, c, Integer.MAX_VALUE);
1511   }
1512
1513   /**
1514    * Same as {@link #splita(String, char)} but limits the number of tokens returned.
1515    *
1516    * @param s The string to split.  Can be <jk>null</jk>.
1517    * @param c The character to split on.
1518    * @param limit The maximum number of tokens to return.
1519    * @return The tokens, or <jk>null</jk> if the string was null.
1520    */
1521   public static String[] splita(String s, char c, int limit) {
1522      var l = split(s, c, limit);
1523      return l == null ? null : l.toArray(new String[l.size()]);
1524   }
1525
1526   /**
1527    * Same as {@link #splita(String, char)} except splits all strings in the input and returns a single result.
1528    *
1529    * @param s The string to split.  Can be <jk>null</jk>.
1530    * @param c The character to split on.
1531    * @return The tokens, or null if the input array was null
1532    */
1533   public static String[] splita(String[] s, char c) {
1534      if (s == null)
1535         return null;  // NOSONAR - Intentional.
1536      var l = new LinkedList<String>();
1537      for (var ss : s) {
1538         if (ss == null || ss.indexOf(c) == -1)
1539            l.add(ss);
1540         else
1541            Collections.addAll(l, splita(ss, c));
1542      }
1543      return l.toArray(new String[l.size()]);
1544   }
1545
1546   /**
1547    * Splits a list of key-value pairs into an ordered map.
1548    *
1549    * <p>
1550    * Example:
1551    * <p class='bjava'>
1552    *    String <jv>in</jv> = <js>"foo=1;bar=2"</js>;
1553    *    Map <jv>map</jv> = StringUtils.<jsm>splitMap</jsm>(in, <js>';'</js>, <js>'='</js>, <jk>true</jk>);
1554    * </p>
1555    *
1556    * @param s The string to split.
1557    * @param trim Trim strings after parsing.
1558    * @return The parsed map, or null if the string was null.
1559    */
1560   public static Map<String,String> splitMap(String s, boolean trim) {
1561
1562      if (s == null)
1563         return null;  // NOSONAR - Intentional.
1564      if (isEmpty(s))
1565         return Collections.emptyMap();
1566
1567      var m = new LinkedHashMap<String,String>();
1568
1569      final int
1570         S1 = 1,  // Found start of key, looking for equals.
1571         S2 = 2;  // Found equals, looking for delimiter (or end).
1572
1573      var state = S1;
1574
1575      var sArray = s.toCharArray();
1576      var x1 = 0;
1577      var escapeCount = 0;
1578      String key = null;
1579      for (var i = 0; i < sArray.length + 1; i++) {
1580         var c = i == sArray.length ? ',' : sArray[i];
1581         if (c == '\\')
1582            escapeCount++;
1583         if (escapeCount % 2 == 0) {
1584            if (state == S1) {
1585               if (c == '=') {
1586                  key = s.substring(x1, i);
1587                  if (trim)
1588                     key = StringUtils.trim(key);
1589                  key = StringUtils.unEscapeChars(key, StringUtils.MAP_ESCAPE_SET);
1590                  state = S2;
1591                  x1 = i+1;
1592               } else if (c == ',') {
1593                  key = s.substring(x1, i);
1594                  if (trim)
1595                     key = StringUtils.trim(key);
1596                  key = StringUtils.unEscapeChars(key, StringUtils.MAP_ESCAPE_SET);
1597                  m.put(key, "");
1598                  state = S1;
1599                  x1 = i+1;
1600               }
1601            } else if (state == S2) {
1602               if (c == ',') {  // NOSONAR - Intentional.
1603                  var val = s.substring(x1, i);
1604                  if (trim)
1605                     val = StringUtils.trim(val);
1606                  val = StringUtils.unEscapeChars(val, StringUtils.MAP_ESCAPE_SET);
1607                  m.put(key, val);
1608                  key = null;
1609                  x1 = i+1;
1610                  state = S1;
1611               }
1612            }
1613         }
1614         if (c != '\\')
1615            escapeCount = 0;
1616      }
1617
1618      return m;
1619   }
1620
1621   /**
1622    * Splits the method arguments in the signature of a method.
1623    *
1624    * @param s The arguments to split.
1625    * @return The split arguments, or null if the input string is null.
1626    */
1627   public static String[] splitMethodArgs(String s) {
1628
1629      if (s == null)
1630         return null;  // NOSONAR - Intentional.
1631      if (isEmpty(s))
1632         return new String[0];
1633      if (s.indexOf(',') == -1)
1634         return new String[]{s};
1635
1636      var l = new LinkedList<String>();
1637      var sArray = s.toCharArray();
1638      var x1 = 0;
1639      var paramDepth = 0;
1640
1641      for (var i = 0; i < sArray.length; i++) {
1642         var c = s.charAt(i);
1643         if (c == '>')
1644            paramDepth++;
1645         else if (c == '<')
1646            paramDepth--;
1647         else if (c == ',' && paramDepth == 0) {
1648            var s2 = new String(sArray, x1, i-x1);
1649            l.add(s2.trim());
1650            x1 = i+1;
1651         }
1652      }
1653
1654      var s2 = new String(sArray, x1, sArray.length-x1);
1655      l.add(s2.trim());
1656
1657      return l.toArray(new String[l.size()]);
1658   }
1659
1660   /**
1661    * Splits a comma-delimited list containing "nesting constructs".
1662    *
1663    * Nesting constructs are simple embedded "{...}" comma-delimted lists.
1664    *
1665    * Example:
1666    *    "a{b,c},d" -> ["a{b,c}","d"]
1667    *
1668    * Handles escapes and trims whitespace from tokens.
1669    *
1670    * @param s The input string.
1671    *    The results, or <jk>null</jk> if the input was <jk>null</jk>.
1672    *    <br>An empty string results in an empty array.
1673    */
1674   public static List<String> splitNested(String s) {
1675      var escapeChars = StringUtils.getEscapeSet(',');
1676
1677      if (s == null) return null;  // NOSONAR - Intentional.
1678      if (isEmpty(s)) return Collections.emptyList();
1679      if (s.indexOf(',') == -1) return Collections.singletonList(StringUtils.trim(s));
1680
1681      var l = new LinkedList<String>();
1682
1683      var x1 = 0;
1684      var inEscape = false;
1685      var depthCount = 0;
1686
1687      for (var i = 0; i < s.length(); i++) {
1688         var c = s.charAt(i);
1689         if (inEscape) {
1690            if (c == '\\') {
1691               inEscape = false;
1692            }
1693         } else {
1694            if (c == '\\') {
1695               inEscape = true;
1696            } else if (c == '{') {
1697               depthCount++;
1698            } else if (c == '}') {
1699               depthCount--;
1700            } else if (c == ',' && depthCount == 0) {
1701               l.add(StringUtils.trim(StringUtils.unEscapeChars(s.substring(x1, i), escapeChars)));
1702               x1 = i+1;
1703            }
1704         }
1705      }
1706      l.add(StringUtils.trim(StringUtils.unEscapeChars(s.substring(x1, s.length()), escapeChars)));
1707
1708      return l;
1709   }
1710
1711   /**
1712    * Splits a nested comma-delimited list.
1713    *
1714    * Nesting constructs are simple embedded "{...}" comma-delimted lists.
1715    *
1716    * Example:
1717    *    "a{b,c{d,e}}" -> ["b","c{d,e}"]
1718    *
1719    * Handles escapes and trims whitespace from tokens.
1720    *
1721    * @param s The input string.
1722    *    The results, or <jk>null</jk> if the input was <jk>null</jk>.
1723    *    <br>An empty string results in an empty array.
1724    */
1725   public static List<String> splitNestedInner(String s) {
1726      if (s == null) throw illegalArg("String was null.");
1727      if (isEmpty(s)) throw illegalArg("String was empty.");
1728
1729      final int
1730         S1 = 1,  // Looking for '{'
1731         S2 = 2;  // Found '{', looking for '}'
1732
1733      var start = -1;
1734      var end = -1;
1735      var state = S1;
1736      var depth = 0;
1737      var inEscape = false;
1738
1739      for (var i = 0; i < s.length(); i++) {
1740         var c = s.charAt(i);
1741         if (inEscape) {
1742            if (c == '\\') {
1743               inEscape = false;
1744            }
1745         } else {
1746            if (c == '\\') {
1747               inEscape = true;
1748            } else if (state == S1) {
1749               if (c == '{') {
1750                  start = i+1;
1751                  state = S2;
1752               }
1753            } else /* state == S2 */ {
1754               if (c == '{') {
1755                  depth++;
1756               } else if (depth > 0 && c == '}') {
1757                  depth--;
1758               } else if (c == '}') {
1759                  end = i;
1760                  break;
1761               }
1762            }
1763         }
1764      }
1765
1766      if (start == -1) throw illegalArg("Start character '{' not found in string.", s);
1767      if (end == -1) throw illegalArg("End character '}' not found in string.", s);
1768      return splitNested(s.substring(start, end));
1769   }
1770
1771   /**
1772    * Splits a space-delimited string with optionally quoted arguments.
1773    *
1774    * <p>
1775    * Examples:
1776    * <ul>
1777    *    <li><js>"foo"</js> =&gt; <c>["foo"]</c>
1778    *    <li><js>" foo "</js> =&gt; <c>["foo"]</c>
1779    *    <li><js>"foo bar baz"</js> =&gt; <c>["foo","bar","baz"]</c>
1780    *    <li><js>"foo 'bar baz'"</js> =&gt; <c>["foo","bar baz"]</c>
1781    *    <li><js>"foo \"bar baz\""</js> =&gt; <c>["foo","bar baz"]</c>
1782    *    <li><js>"foo 'bar\'baz'"</js> =&gt; <c>["foo","bar'baz"]</c>
1783    * </ul>
1784    *
1785    * @param s The input string.
1786    * @return
1787    *    The results, or <jk>null</jk> if the input was <jk>null</jk>.
1788    *    <br>An empty string results in an empty array.
1789    */
1790   public static String[] splitQuoted(String s) {
1791      return splitQuoted(s, false);
1792   }
1793
1794   /**
1795    * Same as {@link #splitQuoted(String)} but allows you to optionally keep the quote characters.
1796    *
1797    * @param s The input string.
1798    * @param keepQuotes If <jk>true</jk>, quote characters are kept on the tokens.
1799    * @return
1800    *    The results, or <jk>null</jk> if the input was <jk>null</jk>.
1801    *    <br>An empty string results in an empty array.
1802    */
1803   public static String[] splitQuoted(String s, boolean keepQuotes) {
1804
1805      if (s == null)
1806         return null;  // NOSONAR - Intentional.
1807
1808      s = s.trim();
1809
1810      if (isEmpty(s))
1811         return new String[0];
1812
1813      if (! StringUtils.containsAny(s, ' ', '\t', '\'', '"'))
1814         return new String[]{s};
1815
1816      final int
1817         S1 = 1,  // Looking for start of token.
1818         S2 = 2,  // Found ', looking for end '
1819         S3 = 3,  // Found ", looking for end "
1820         S4 = 4;  // Found non-whitespace, looking for end whitespace.
1821
1822      var state = S1;
1823
1824      var isInEscape = false;
1825      var needsUnescape = false;
1826      var mark = 0;
1827
1828      var l = new ArrayList<String>();
1829      for (var i = 0; i < s.length(); i++) {
1830         var c = s.charAt(i);
1831
1832         if (state == S1) {
1833            if (c == '\'') {
1834               state = S2;
1835               mark = keepQuotes ? i : i+1;
1836            } else if (c == '"') {
1837               state = S3;
1838               mark = keepQuotes ? i : i+1;
1839            } else if (c != ' ' && c != '\t') {
1840               state = S4;
1841               mark = i;
1842            }
1843         } else if (state == S2 || state == S3) {
1844            if (c == '\\') {
1845               isInEscape = ! isInEscape;
1846               needsUnescape = ! keepQuotes;
1847            } else if (! isInEscape) {
1848               if (c == (state == S2 ? '\'' : '"')) {
1849                  var s2 = s.substring(mark, keepQuotes ? i+1 : i);
1850                  if (needsUnescape)  // NOSONAR - False positive check.
1851                     s2 = StringUtils.unEscapeChars(s2, StringUtils.QUOTE_ESCAPE_SET);
1852                  l.add(s2);
1853                  state = S1;
1854                  isInEscape = needsUnescape = false;
1855               }
1856            } else {
1857               isInEscape = false;
1858            }
1859         } else /* state == S4 */ {
1860            if (c == ' ' || c == '\t') {
1861               l.add(s.substring(mark, i));
1862               state = S1;
1863            }
1864         }
1865      }
1866      if (state == S4)
1867         l.add(s.substring(mark));
1868      else if (state == S2 || state == S3)
1869         throw new IllegalArgumentException("Unmatched string quotes: " + s);
1870      return l.toArray(new String[l.size()]);
1871   }
1872
1873   /**
1874    * Converts various collection-like objects to a {@link List}.
1875    *
1876    * <p>This utility method enables testing of any collection-like object by converting it to a List that can be
1877    * passed to methods such as TestUtils.assertList().</p>
1878    *
1879    * <h5 class='section'>Supported Input Types:</h5>
1880    * <ul>
1881    *    <li><b>List:</b> Returns the input unchanged</li>
1882    *    <li><b>Iterable:</b> Any collection, set, queue, etc. (converted to List preserving order)</li>
1883    *    <li><b>Iterator:</b> Converts iterator contents to List</li>
1884    *    <li><b>Enumeration:</b> Converts enumeration contents to List</li>
1885    *    <li><b>Stream:</b> Converts stream contents to List (stream is consumed)</li>
1886    *    <li><b>Map:</b> Converts map entries to List of Map.Entry objects</li>
1887    *    <li><b>Array:</b> Converts any array type (including primitive arrays) to List</li>
1888    * </ul>
1889    *
1890    * <h5 class='section'>Usage Examples:</h5>
1891    * <p class='bjava'>
1892    *    <jc>// Test a Set</jc>
1893    *    Set&lt;String&gt; <jv>mySet</jv> = Set.of(<js>"a"</js>, <js>"b"</js>, <js>"c"</js>);
1894    *    assertList(toList(<jv>mySet</jv>), <js>"a"</js>, <js>"b"</js>, <js>"c"</js>);
1895    *
1896    *    <jc>// Test an array</jc>
1897    *    String[] <jv>myArray</jv> = {<js>"x"</js>, <js>"y"</js>, <js>"z"</js>};
1898    *    assertList(toList(<jv>myArray</jv>), <js>"x"</js>, <js>"y"</js>, <js>"z"</js>);
1899    *
1900    *    <jc>// Test a primitive array</jc>
1901    *    <jk>int</jk>[] <jv>numbers</jv> = {1, 2, 3};
1902    *    assertList(toList(<jv>numbers</jv>), <js>"1"</js>, <js>"2"</js>, <js>"3"</js>);
1903    *
1904    *    <jc>// Test a Stream</jc>
1905    *    Stream&lt;String&gt; <jv>myStream</jv> = Stream.of(<js>"foo"</js>, <js>"bar"</js>);
1906    *    assertList(toList(<jv>myStream</jv>), <js>"foo"</js>, <js>"bar"</js>);
1907    *
1908    *    <jc>// Test a Map (converted to entries)</jc>
1909    *    Map&lt;String,Integer&gt; <jv>myMap</jv> = Map.of(<js>"a"</js>, 1, <js>"b"</js>, 2);
1910    *    assertList(toList(<jv>myMap</jv>), <js>"a=1"</js>, <js>"b=2"</js>);
1911    *
1912    *    <jc>// Test any Iterable collection</jc>
1913    *    Queue&lt;String&gt; <jv>myQueue</jv> = new LinkedList&lt;&gt;(List.of(<js>"first"</js>, <js>"second"</js>));
1914    *    assertList(toList(<jv>myQueue</jv>), <js>"first"</js>, <js>"second"</js>);
1915    * </p>
1916    *
1917    * <h5 class='section'>Integration with Testing:</h5>
1918    * <p>This method is specifically designed to work with testing frameworks to provide
1919    * a unified testing approach for all collection-like types. Instead of having separate assertion methods
1920    * for arrays, sets, and other collections, you can convert them all to Lists and use standard
1921    * list assertion methods.</p>
1922    *
1923    * @param o The object to convert to a List. Must not be null and must be a supported collection-like type.
1924    * @return A {@link List} containing the elements from the input object.
1925    * @throws IllegalArgumentException if the input object cannot be converted to a List.
1926    * @see #arrayToList(Object)
1927    */
1928   public static final List<?> toList(Object o) {  // NOSONAR
1929      assertArgNotNull("o", o);
1930      if (o instanceof List<?> o2) return o2;
1931      if (o instanceof Iterable<?> o2) return StreamSupport.stream(o2.spliterator(), false).toList();
1932      if (o instanceof Iterator<?> o2) return StreamSupport.stream(Spliterators.spliteratorUnknownSize(o2, 0), false).toList();
1933      if (o instanceof Enumeration<?> o2) return Collections.list(o2);
1934      if (o instanceof Stream<?> o2) return o2.toList();
1935      if (o instanceof Map<?,?> o2) return toList(o2.entrySet());
1936      if (o instanceof Optional<?> o2) return o2.isEmpty() ? Collections.emptyList() : Collections.singletonList(o2.get());
1937      if (isArray(o)) return arrayToList(o);
1938      throw runtimeException("Could not convert object of type {0} to a list", classNameOf(o));
1939   }
1940
1941   public static boolean isConvertibleToList(Object o) {
1942      return o != null && (o instanceof Collection || o instanceof Iterable || o instanceof Iterator || o instanceof Enumeration || o instanceof Stream || o instanceof Map || o instanceof Optional || isArray(o));
1943   }
1944
1945   /**
1946    * Converts an array to a stream of objects.
1947    * @param array The array to convert.
1948    * @return A new stream.
1949    */
1950   public static Stream<Object> toStream(Object array) {
1951      assertArg(isArray(array), "Arg was not an array.  Type: {0}", array.getClass().getName());
1952      var length = Array.getLength(array);
1953      return IntStream.range(0, length).mapToObj(i -> Array.get(array, i));
1954   }
1955
1956   /**
1957    * Converts a string to the specified type using registered conversion functions.
1958    *
1959    * @param <T> The target type.
1960    * @param s The string to convert.
1961    * @param def The default value (used to determine the target type).
1962    * @return The converted value, or <jk>null</jk> if the string or default is <jk>null</jk>.
1963    * @throws RuntimeException If the type is not supported for conversion.
1964    */
1965   @SuppressWarnings("rawtypes")
1966   private static <T> T toType(String s, T def) {
1967      if (s == null || def == null)
1968         return null;
1969      var c = (Class<T>)def.getClass();
1970      if (c == String.class)
1971         return (T)s;
1972      if (c.isEnum())
1973         return (T)Enum.valueOf((Class<? extends Enum>) c, s);
1974      var f = (Function<String,T>)ENV_FUNCTIONS.get(c);
1975      if (f == null)
1976         throw runtimeException("Invalid env type: {0}", c);
1977      return f.apply(s);
1978   }
1979
1980   /**
1981    * Traverses all elements in the specified object and executes a consumer for it.
1982    *
1983    * @param <T> The element type.
1984    * @param o The object to traverse.
1985    * @param c The consumer of the objects.
1986    */
1987   public static <T> void traverse(Object o, Consumer<T> c) {
1988      if (o == null)
1989         return;
1990      if (o instanceof Iterable<?> o2)
1991         o2.forEach(x -> traverse(x, c));
1992      else if (o instanceof Stream<?> o2)
1993         o2.forEach(x -> traverse(x, c));
1994      else if (isArray(o))
1995         toStream(o).forEach(x -> traverse(x, c));
1996      else
1997         c.accept((T)o);
1998   }
1999
2000   /**
2001    * Creates an unmodifiable view of the specified list.
2002    *
2003    * <p>This is a null-safe wrapper around {@link Collections#unmodifiableList(List)}.</p>
2004    *
2005    * @param <T> The element type.
2006    * @param value The list to make unmodifiable. Can be null.
2007    * @return An unmodifiable view of the list, or null if the input was null.
2008    */
2009   public static <T> List<T> u(List<? extends T> value) {
2010      return value == null ? null : Collections.unmodifiableList(value);
2011   }
2012
2013   /**
2014    * Creates an unmodifiable view of the specified map.
2015    *
2016    * <p>This is a null-safe wrapper around {@link Collections#unmodifiableMap(Map)}.</p>
2017    *
2018    * @param <K> The key type.
2019    * @param <V> The value type.
2020    * @param value The map to make unmodifiable. Can be null.
2021    * @return An unmodifiable view of the map, or null if the input was null.
2022    */
2023   public static <K,V> Map<K,V> u(Map<? extends K, ? extends V> value) {
2024      return value == null ? null : Collections.unmodifiableMap(value);
2025   }
2026
2027   /**
2028    * Creates an unmodifiable view of the specified set.
2029    *
2030    * <p>This is a null-safe wrapper around {@link Collections#unmodifiableSet(Set)}.</p>
2031    *
2032    * @param <T> The element type.
2033    * @param value The set to make unmodifiable. Can be null.
2034    * @return An unmodifiable view of the set, or null if the input was null.
2035    */
2036   public static <T> Set<T> u(Set<? extends T> value) {
2037      return value == null ? null : Collections.unmodifiableSet(value);
2038   }
2039
2040   /** Constructor - This class is meant to be subclasses. */
2041   protected Utils() {}
2042
2043   /**
2044    * Helper method for creating StringBuilder objects.
2045    *
2046    * @param value The string value to wrap in a StringBuilder.
2047    * @return A new StringBuilder containing the specified value.
2048    */
2049   public static StringBuilder sb(String value) {
2050      return new StringBuilder(value);
2051   }
2052
2053   /**
2054    * Gets the fully qualified class name of the specified object.
2055    *
2056    * @param o The object to get the class name for.
2057    * @return The fully qualified class name, or <jk>null</jk> if the object is <jk>null</jk>.
2058    */
2059   public static String classNameOf(Object o) {
2060      return o == null ? null : o.getClass().getName();
2061   }
2062
2063   /**
2064    * Gets the simple class name of the specified object.
2065    *
2066    * @param o The object to get the simple class name for.
2067    * @return The simple class name, or <jk>null</jk> if the object is <jk>null</jk>.
2068    */
2069   public static String simpleClassNameOf(Object o) {
2070      return o == null ? null : o.getClass().getSimpleName();
2071   }
2072}