001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.internal;
014
015import static org.apache.juneau.internal.ThrowableUtils.*;
016
017import java.io.*;
018import java.lang.reflect.*;
019import java.math.*;
020import java.net.*;
021import java.nio.*;
022import java.nio.charset.*;
023import java.text.*;
024import java.util.*;
025import java.util.concurrent.*;
026import java.util.concurrent.atomic.*;
027import java.util.regex.*;
028
029import javax.xml.bind.*;
030
031import org.apache.juneau.*;
032import org.apache.juneau.json.*;
033import org.apache.juneau.parser.*;
034import org.apache.juneau.parser.ParseException;
035
036/**
037 * Reusable string utility methods.
038 */
039public final class StringUtils {
040
041   private static final AsciiSet numberChars = AsciiSet.create("-xX.+-#pP0123456789abcdefABCDEF");
042   private static final AsciiSet firstNumberChars =AsciiSet.create("+-.#0123456789");
043   private static final AsciiSet octChars = AsciiSet.create("01234567");
044   private static final AsciiSet decChars = AsciiSet.create("0123456789");
045   private static final AsciiSet hexChars = AsciiSet.create("0123456789abcdefABCDEF");
046
047   // Maps 6-bit nibbles to BASE64 characters.
048   private static final char[] base64m1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
049
050   // Characters that do not need to be URL-encoded
051   private static final AsciiSet unencodedChars = AsciiSet.create().ranges("a-z","A-Z","0-9").chars("-_.!~*'()\\").build();
052
053   // Characters that really do not need to be URL-encoded
054   private static final AsciiSet unencodedCharsLax = unencodedChars.copy()
055      .chars(":@$,")  // reserved, but can't be confused in a query parameter.
056      .chars("{}|\\^[]`")  // unwise characters.
057      .build();
058
059   // Maps BASE64 characters to 6-bit nibbles.
060   private static final byte[] base64m2 = new byte[128];
061   static {
062      for (int i = 0; i < 64; i++)
063         base64m2[base64m1[i]] = (byte)i;
064   }
065
066   /**
067    * Parses a number from the specified reader stream.
068    *
069    * @param r The reader to parse the string from.
070    * @param type
071    *    The number type to created.
072    *    Can be any of the following:
073    *    <ul>
074    *       <li> Integer
075    *       <li> Double
076    *       <li> Float
077    *       <li> Long
078    *       <li> Short
079    *       <li> Byte
080    *       <li> BigInteger
081    *       <li> BigDecimal
082    *    </ul>
083    *    If <jk>null</jk>, uses the best guess.
084    * @throws IOException If a problem occurred trying to read from the reader.
085    * @return The parsed number.
086    * @throws Exception
087    */
088   public static Number parseNumber(ParserReader r, Class<? extends Number> type) throws Exception {
089      return parseNumber(parseNumberString(r), type);
090   }
091
092   /**
093    * Reads a numeric string from the specified reader.
094    *
095    * @param r The reader to read form.
096    * @return The parsed number string.
097    * @throws Exception
098    */
099   public static String parseNumberString(ParserReader r) throws Exception {
100      r.mark();
101      int c = 0;
102      while (true) {
103         c = r.read();
104         if (c == -1)
105            break;
106         if (! numberChars.contains((char)c)) {
107            r.unread();
108            break;
109         }
110      }
111      return r.getMarked();
112   }
113
114   /**
115    * Parses a number from the specified string.
116    *
117    * @param s The string to parse the number from.
118    * @param type
119    *    The number type to created.
120    *    Can be any of the following:
121    *    <ul>
122    *       <li> Integer
123    *       <li> Double
124    *       <li> Float
125    *       <li> Long
126    *       <li> Short
127    *       <li> Byte
128    *       <li> BigInteger
129    *       <li> BigDecimal
130    *    </ul>
131    *    If <jk>null</jk> or <code>Number</code>, uses the best guess.
132    * @return The parsed number, or <jk>null</jk> if the string was null.
133    * @throws ParseException
134    */
135   public static Number parseNumber(String s, Class<? extends Number> type) throws ParseException {
136      if (s == null)
137         return null;
138      if (s.isEmpty())
139         s = "0";
140      if (type == null)
141         type = Number.class;
142
143      try {
144         // Determine the data type if it wasn't specified.
145         boolean isAutoDetect = (type == Number.class);
146         boolean isDecimal = false;
147         if (isAutoDetect) {
148            // If we're auto-detecting, then we use either an Integer, Long, or Double depending on how
149            // long the string is.
150            // An integer range is -2,147,483,648 to 2,147,483,647
151            // An long range is -9,223,372,036,854,775,808 to +9,223,372,036,854,775,807
152            isDecimal = isDecimal(s);
153            if (isDecimal) {
154               if (s.length() > 20)
155                  type = Double.class;
156               else if (s.length() >= 10)
157                  type = Long.class;
158               else
159                  type = Integer.class;
160            }
161            else if (isFloat(s))
162               type = Double.class;
163            else
164               throw new NumberFormatException(s);
165         }
166
167         if (type == Double.class || type == Double.TYPE) {
168            Double d = Double.valueOf(s);
169            Float f = Float.valueOf(s);
170            if (isAutoDetect && (!isDecimal) && d.toString().equals(f.toString()))
171               return f;
172            return d;
173         }
174         if (type == Float.class || type == Float.TYPE)
175            return Float.valueOf(s);
176         if (type == BigDecimal.class)
177            return new BigDecimal(s);
178         if (type == Long.class || type == Long.TYPE || type == AtomicLong.class) {
179            try {
180               Long l = Long.decode(s);
181               if (type == AtomicLong.class)
182                  return new AtomicLong(l);
183               if (isAutoDetect && l >= Integer.MIN_VALUE && l <= Integer.MAX_VALUE) {
184                  // This occurs if the string is 10 characters long but is still a valid integer value.
185                  return l.intValue();
186               }
187               return l;
188            } catch (NumberFormatException e) {
189               if (isAutoDetect) {
190                  // This occurs if the string is 20 characters long but still falls outside the range of a valid long.
191                  return Double.valueOf(s);
192               }
193               throw e;
194            }
195         }
196         if (type == Integer.class || type == Integer.TYPE)
197            return Integer.decode(s);
198         if (type == Short.class || type == Short.TYPE)
199            return Short.decode(s);
200         if (type == Byte.class || type == Byte.TYPE)
201            return Byte.decode(s);
202         if (type == BigInteger.class)
203            return new BigInteger(s);
204         if (type == AtomicInteger.class)
205            return new AtomicInteger(Integer.decode(s));
206         throw new ParseException("Unsupported Number type: {0}", type.getName());
207      } catch (NumberFormatException e) {
208         throw new ParseException(e, "Invalid number: ''{0}'', class=''{1}''", s, type.getSimpleName());
209      }
210   }
211
212   private static final Pattern fpRegex = Pattern.compile(
213      "[+-]?(NaN|Infinity|((((\\p{Digit}+)(\\.)?((\\p{Digit}+)?)([eE][+-]?(\\p{Digit}+))?)|(\\.((\\p{Digit}+))([eE][+-]?(\\p{Digit}+))?)|(((0[xX](\\p{XDigit}+)(\\.)?)|(0[xX](\\p{XDigit}+)?(\\.)(\\p{XDigit}+)))[pP][+-]?(\\p{Digit}+)))[fFdD]?))[\\x00-\\x20]*"
214   );
215
216   /**
217    * Converts a <code>String</code> to a <code>Character</code>
218    *
219    * @param o The string to convert.
220    * @return The first character of the string if the string is of length 0, or <jk>null</jk> if the string is <jk>null</jk> or empty.
221    * @throws ParseException If string has a length greater than 1.
222    */
223   public static Character parseCharacter(Object o) throws ParseException {
224      if (o == null)
225         return null;
226      String s = o.toString();
227      if (s.length() == 0)
228         return null;
229      if (s.length() == 1)
230         return s.charAt(0);
231      throw new ParseException("Invalid character: ''{0}''", s);
232   }
233
234   /**
235    * Returns <jk>true</jk> if this string can be parsed by {@link #parseNumber(String, Class)}.
236    *
237    * @param s The string to check.
238    * @return <jk>true</jk> if this string can be parsed without causing an exception.
239    */
240   public static boolean isNumeric(String s) {
241      if (s == null || s.isEmpty())
242         return false;
243      if (! isFirstNumberChar(s.charAt(0)))
244         return false;
245      return isDecimal(s) || isFloat(s);
246   }
247
248   /**
249    * Returns <jk>true</jk> if the specified character is a valid first character for a number.
250    *
251    * @param c The character to test.
252    * @return <jk>true</jk> if the specified character is a valid first character for a number.
253    */
254   public static boolean isFirstNumberChar(char c) {
255      return firstNumberChars.contains(c);
256   }
257
258   /**
259    * Returns <jk>true</jk> if the specified string is a floating point number.
260    *
261    * @param s The string to check.
262    * @return <jk>true</jk> if the specified string is a floating point number.
263    */
264   public static boolean isFloat(String s) {
265      if (s == null || s.isEmpty())
266         return false;
267      if (! firstNumberChars.contains(s.charAt(0)))
268         return (s.equals("NaN") || s.equals("Infinity"));
269      int i = 0;
270      int length = s.length();
271      char c = s.charAt(0);
272      if (c == '+' || c == '-')
273         i++;
274      if (i == length)
275         return false;
276      c = s.charAt(i++);
277      if (c == '.' || decChars.contains(c)) {
278         return fpRegex.matcher(s).matches();
279      }
280      return false;
281   }
282
283   /**
284    * Returns <jk>true</jk> if the specified string is numeric.
285    *
286    * @param s The string to check.
287    * @return <jk>true</jk> if the specified string is numeric.
288    */
289   public static boolean isDecimal(String s) {
290      if (s == null || s.isEmpty())
291         return false;
292      if (! firstNumberChars.contains(s.charAt(0)))
293         return false;
294      int i = 0;
295      int length = s.length();
296      char c = s.charAt(0);
297      boolean isPrefixed = false;
298      if (c == '+' || c == '-') {
299         isPrefixed = true;
300         i++;
301      }
302      if (i == length)
303         return false;
304      c = s.charAt(i++);
305      if (c == '0' && length > (isPrefixed ? 2 : 1)) {
306         c = s.charAt(i++);
307         if (c == 'x' || c == 'X') {
308            for (int j = i; j < length; j++) {
309               if (! hexChars.contains(s.charAt(j)))
310                  return false;
311            }
312         } else if (octChars.contains(c)) {
313            for (int j = i; j < length; j++)
314               if (! octChars.contains(s.charAt(j)))
315                  return false;
316         } else {
317            return false;
318         }
319      } else if (c == '#') {
320         for (int j = i; j < length; j++) {
321            if (! hexChars.contains(s.charAt(j)))
322               return false;
323         }
324      } else if (decChars.contains(c)) {
325         for (int j = i; j < length; j++)
326            if (! decChars.contains(s.charAt(j)))
327               return false;
328      } else {
329         return false;
330      }
331      return true;
332   }
333
334   /**
335    * Convenience method for getting a stack trace as a string.
336    *
337    * @param t The throwable to get the stack trace from.
338    * @return The same content that would normally be rendered via <code>t.printStackTrace()</code>
339    */
340   public static String getStackTrace(Throwable t) {
341      StringWriter sw = new StringWriter();
342      try (PrintWriter pw = new PrintWriter(sw)) {
343         t.printStackTrace(pw);
344      }
345      return sw.toString();
346   }
347
348   /**
349    * Join the specified tokens into a delimited string.
350    *
351    * @param tokens The tokens to join.
352    * @param separator The delimiter.
353    * @return The delimited string.  If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>.
354    */
355   public static String join(Object[] tokens, String separator) {
356      if (tokens == null)
357         return null;
358      StringBuilder sb = new StringBuilder();
359      for (int i = 0; i < tokens.length; i++) {
360         if (i > 0)
361            sb.append(separator);
362         sb.append(tokens[i]);
363      }
364      return sb.toString();
365   }
366
367   /**
368    * Join the specified tokens into a delimited string.
369    *
370    * @param tokens The tokens to join.
371    * @param d The delimiter.
372    * @return The delimited string.  If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>.
373    */
374   public static String join(int[] tokens, String d) {
375      if (tokens == null)
376         return null;
377      StringBuilder sb = new StringBuilder();
378      for (int i = 0; i < tokens.length; i++) {
379         if (i > 0)
380            sb.append(d);
381         sb.append(tokens[i]);
382      }
383      return sb.toString();
384   }
385
386   /**
387    * Join the specified tokens into a delimited string.
388    *
389    * @param tokens The tokens to join.
390    * @param d The delimiter.
391    * @return The delimited string.  If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>.
392    */
393   public static String join(Collection<?> tokens, String d) {
394      if (tokens == null)
395         return null;
396      return join(tokens, d, new StringBuilder()).toString();
397   }
398
399   /**
400    * Joins the specified tokens into a delimited string and writes the output to the specified string builder.
401    *
402    * @param tokens The tokens to join.
403    * @param d The delimiter.
404    * @param sb The string builder to append the response to.
405    * @return The same string builder passed in as <code>sb</code>.
406    */
407   public static StringBuilder join(Collection<?> tokens, String d, StringBuilder sb) {
408      if (tokens == null)
409         return sb;
410      for (Iterator<?> iter = tokens.iterator(); iter.hasNext();) {
411         sb.append(iter.next());
412         if (iter.hasNext())
413            sb.append(d);
414      }
415      return sb;
416   }
417
418   /**
419    * Joins the specified tokens into a delimited string.
420    *
421    * @param tokens The tokens to join.
422    * @param d The delimiter.
423    * @return The delimited string.  If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>.
424    */
425   public static String join(Object[] tokens, char d) {
426      if (tokens == null)
427         return null;
428      return join(tokens, d, new StringBuilder()).toString();
429   }
430
431   /**
432    * Same as {@link #join(Object[], char)} except escapes the delimiter character if found in the tokens.
433    *
434    * @param tokens The tokens to join.
435    * @param d The delimiter.
436    * @return The delimited string.  If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>.
437    */
438   public static String joine(Object[] tokens, char d) {
439      if (tokens == null)
440         return null;
441      return joine(tokens, d, new StringBuilder()).toString();
442   }
443
444   private static AsciiSet getEscapeSet(char c) {
445      AsciiSet s = ESCAPE_SETS.get(c);
446      if (s == null) {
447         s = AsciiSet.create().chars(c, '\\').build();
448         ESCAPE_SETS.put(c, s);
449      }
450      return s;
451   }
452   static Map<Character,AsciiSet> ESCAPE_SETS = new ConcurrentHashMap<>();
453
454   /**
455    * Join the specified tokens into a delimited string and writes the output to the specified string builder.
456    *
457    * @param tokens The tokens to join.
458    * @param d The delimiter.
459    * @param sb The string builder to append the response to.
460    * @return The same string builder passed in as <code>sb</code>.
461    */
462   public static StringBuilder join(Object[] tokens, char d, StringBuilder sb) {
463      if (tokens == null)
464         return sb;
465      for (int i = 0; i < tokens.length; i++) {
466         if (i > 0)
467            sb.append(d);
468         sb.append(tokens[i]);
469      }
470      return sb;
471   }
472
473   /**
474    * Same as {@link #join(Object[], char, StringBuilder)} but escapes the delimiter character if found in the tokens.
475    *
476    * @param tokens The tokens to join.
477    * @param d The delimiter.
478    * @param sb The string builder to append the response to.
479    * @return The same string builder passed in as <code>sb</code>.
480    */
481   public static StringBuilder joine(Object[] tokens, char d, StringBuilder sb) {
482      if (tokens == null)
483         return sb;
484      AsciiSet as = getEscapeSet(d);
485      for (int i = 0; i < tokens.length; i++) {
486         if (i > 0)
487            sb.append(d);
488         sb.append(escapeChars(asString(tokens[i]), as));
489      }
490      return sb;
491   }
492
493   /**
494    * Join the specified tokens into a delimited string.
495    *
496    * @param tokens The tokens to join.
497    * @param d The delimiter.
498    * @return The delimited string.  If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>.
499    */
500   public static String join(int[] tokens, char d) {
501      if (tokens == null)
502         return null;
503      StringBuilder sb = new StringBuilder();
504      for (int i = 0; i < tokens.length; i++) {
505         if (i > 0)
506            sb.append(d);
507         sb.append(tokens[i]);
508      }
509      return sb.toString();
510   }
511
512   /**
513    * Join the specified tokens into a delimited string.
514    *
515    * @param tokens The tokens to join.
516    * @param d The delimiter.
517    * @return The delimited string.  If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>.
518    */
519   public static String join(Collection<?> tokens, char d) {
520      if (tokens == null)
521         return null;
522      StringBuilder sb = new StringBuilder();
523      for (Iterator<?> iter = tokens.iterator(); iter.hasNext();) {
524         sb.append(iter.next());
525         if (iter.hasNext())
526            sb.append(d);
527      }
528      return sb.toString();
529   }
530
531   /**
532    * Same as {@link #join(Collection, char)} but escapes the delimiter if found in the tokens.
533    *
534    * @param tokens The tokens to join.
535    * @param d The delimiter.
536    * @return The delimited string.  If <code>tokens</code> is <jk>null</jk>, returns <jk>null</jk>.
537    */
538   public static String joine(Collection<?> tokens, char d) {
539      if (tokens == null)
540         return null;
541      AsciiSet as = getEscapeSet(d);
542      StringBuilder sb = new StringBuilder();
543      for (Iterator<?> iter = tokens.iterator(); iter.hasNext();) {
544         sb.append(escapeChars(asString(iter.next()), as));
545         if (iter.hasNext())
546            sb.append(d);
547      }
548      return sb.toString();
549   }
550
551   /**
552    * Joins tokens with newlines.
553    *
554    * @param tokens The tokens to concatenate.
555    * @return A string with the specified tokens contatenated with newlines.
556    */
557   public static String joinnl(Object[] tokens) {
558      return join(tokens, '\n');
559   }
560
561   /**
562    * Shortcut for calling <code>split(s, <js>','</js>)</code>
563    *
564    * @param s The string to split.  Can be <jk>null</jk>.
565    * @return The tokens, or <jk>null</jk> if the string was null.
566    */
567   public static String[] split(String s) {
568      return split(s, ',');
569   }
570
571   /**
572    * Splits a character-delimited string into a string array.
573    *
574    * <p>
575    * Does not split on escaped-delimiters (e.g. "\,");
576    * Resulting tokens are trimmed of whitespace.
577    *
578    * <p>
579    * <b>NOTE:</b>  This behavior is different than the Jakarta equivalent.
580    * split("a,b,c",',') -> {"a","b","c"}
581    * split("a, b ,c ",',') -> {"a","b","c"}
582    * split("a,,c",',') -> {"a","","c"}
583    * split(",,",',') -> {"","",""}
584    * split("",',') -> {}
585    * split(null,',') -> null
586    * split("a,b\,c,d", ',', false) -> {"a","b\,c","d"}
587    * split("a,b\\,c,d", ',', false) -> {"a","b\","c","d"}
588    * split("a,b\,c,d", ',', true) -> {"a","b,c","d"}
589    *
590    * @param s The string to split.  Can be <jk>null</jk>.
591    * @param c The character to split on.
592    * @return The tokens, or <jk>null</jk> if the string was null.
593    */
594   public static String[] split(String s, char c) {
595      return split(s, c, Integer.MAX_VALUE);
596   }
597
598   /**
599    * Same as {@link #split(String, char)} but limits the number of tokens returned.
600    *
601    * @param s The string to split.  Can be <jk>null</jk>.
602    * @param c The character to split on.
603    * @param limit The maximum number of tokens to return.
604    * @return The tokens, or <jk>null</jk> if the string was null.
605    */
606   public static String[] split(String s, char c, int limit) {
607
608      AsciiSet escapeChars = getEscapeSet(c);
609
610      if (s == null)
611         return null;
612      if (isEmpty(s))
613         return new String[0];
614      if (s.indexOf(c) == -1)
615         return new String[]{s};
616
617      List<String> l = new LinkedList<>();
618      char[] sArray = s.toCharArray();
619      int x1 = 0, escapeCount = 0;
620      limit--;
621      for (int i = 0; i < sArray.length && limit > 0; i++) {
622         if (sArray[i] == '\\') escapeCount++;
623         else if (sArray[i]==c && escapeCount % 2 == 0) {
624            String s2 = new String(sArray, x1, i-x1);
625            String s3 = unEscapeChars(s2, escapeChars);
626            l.add(s3.trim());
627            limit--;
628            x1 = i+1;
629         }
630         if (sArray[i] != '\\') escapeCount = 0;
631      }
632      String s2 = new String(sArray, x1, sArray.length-x1);
633      String s3 = unEscapeChars(s2, escapeChars);
634      l.add(s3.trim());
635
636      return l.toArray(new String[l.size()]);
637   }
638
639   /**
640    * Same as {@link #split(String, char)} except splits all strings in the input and returns a single result.
641    *
642    * @param s The string to split.  Can be <jk>null</jk>.
643    * @param c The character to split on.
644    * @return The tokens.
645    */
646   public static String[] split(String[] s, char c) {
647      if (s == null)
648         return null;
649      List<String> l = new LinkedList<>();
650      for (String ss : s) {
651         if (ss == null || ss.indexOf(c) == -1)
652            l.add(ss);
653         else
654            l.addAll(Arrays.asList(split(ss, c)));
655      }
656      return l.toArray(new String[l.size()]);
657   }
658
659   /**
660    * Splits a list of key-value pairs into an ordered map.
661    *
662    * <p>
663    * Example:
664    * <p class='bcode w800'>
665    *    String in = <js>"foo=1;bar=2"</js>;
666    *    Map m = StringUtils.<jsm>splitMap</jsm>(in, <js>';'</js>, <js>'='</js>, <jk>true</jk>);
667    * </p>
668    *
669    * @param s The string to split.
670    * @param trim Trim strings after parsing.
671    * @return The parsed map.  Never <jk>null</jk>.
672    */
673   public static Map<String,String> splitMap(String s, boolean trim) {
674
675      if (s == null)
676         return null;
677      if (isEmpty(s))
678         return Collections.EMPTY_MAP;
679
680      Map<String,String> m = new LinkedHashMap<>();
681
682      int
683         S1 = 1,  // Found start of key, looking for equals.
684         S2 = 2;  // Found equals, looking for delimiter (or end).
685
686      int state = S1;
687
688      char[] sArray = s.toCharArray();
689      int x1 = 0, escapeCount = 0;
690      String key = null;
691      for (int i = 0; i < sArray.length + 1; i++) {
692         char c = i == sArray.length ? ',' : sArray[i];
693         if (c == '\\')
694            escapeCount++;
695         if (escapeCount % 2 == 0) {
696            if (state == S1) {
697               if (c == '=') {
698                  key = s.substring(x1, i);
699                  if (trim)
700                     key = trim(key);
701                  key = unEscapeChars(key, MAP_ESCAPE_SET);
702                  state = S2;
703                  x1 = i+1;
704               } else if (c == ',') {
705                  key = s.substring(x1, i);
706                  if (trim)
707                     key = trim(key);
708                  key = unEscapeChars(key, MAP_ESCAPE_SET);
709                  m.put(key, "");
710                  state = S1;
711                  x1 = i+1;
712               }
713            } else if (state == S2) {
714               if (c == ',') {
715                  String val = s.substring(x1, i);
716                  if (trim)
717                     val = trim(val);
718                  val = unEscapeChars(val, MAP_ESCAPE_SET);
719                  m.put(key, val);
720                  key = null;
721                  x1 = i+1;
722                  state = S1;
723               }
724            }
725         }
726         if (c != '\\') escapeCount = 0;
727      }
728
729      return m;
730   }
731
732   private static final AsciiSet MAP_ESCAPE_SET = AsciiSet.create(",=\\");
733
734   /**
735    * Returns <jk>true</jk> if the specified string contains any of the specified characters.
736    *
737    * @param s The string to test.
738    * @param chars The characters to look for.
739    * @return
740    *    <jk>true</jk> if the specified string contains any of the specified characters.
741    *    <br><jk>false</jk> if the string is <jk>null</jk>.
742    */
743   public static boolean containsAny(String s, char...chars) {
744      if (s == null)
745         return false;
746      for (int i = 0; i < s.length(); i++) {
747         char c = s.charAt(i);
748         for (char c2 : chars)
749            if (c == c2)
750               return true;
751      }
752      return false;
753   }
754
755   /**
756    * Splits a space-delimited string with optionally quoted arguments.
757    *
758    * <p>
759    * Examples:
760    * <ul>
761    *    <li><js>"foo"</js> =&gt; <code>["foo"]</code>
762    *    <li><js>" foo "</js> =&gt; <code>["foo"]</code>
763    *    <li><js>"foo bar baz"</js> =&gt; <code>["foo","bar","baz"]</code>
764    *    <li><js>"foo 'bar baz'"</js> =&gt; <code>["foo","bar baz"]</code>
765    *    <li><js>"foo \"bar baz\""</js> =&gt; <code>["foo","bar baz"]</code>
766    *    <li><js>"foo 'bar\'baz'"</js> =&gt; <code>["foo","bar'baz"]</code>
767    * </ul>
768    *
769    * @param s The input string.
770    * @return
771    *    The results, or <jk>null</jk> if the input was <jk>null</jk>.
772    *    <br>An empty string results in an empty array.
773    */
774   public static String[] splitQuoted(String s) {
775
776      if (s == null)
777         return null;
778
779      s = s.trim();
780
781      if (isEmpty(s))
782         return new String[0];
783
784      if (! containsAny(s, ' ', '\t', '\'', '"'))
785         return new String[]{s};
786
787      int
788         S1 = 1,  // Looking for start of token.
789         S2 = 2,  // Found ', looking for end '
790         S3 = 3,  // Found ", looking for end "
791         S4 = 4;  // Found non-whitespace, looking for end whitespace.
792
793      int state = S1;
794
795      boolean isInEscape = false, needsUnescape = false;
796      int mark = 0;
797
798      List<String> l = new ArrayList<>();
799      for (int i = 0; i < s.length(); i++) {
800         char c = s.charAt(i);
801
802         if (state == S1) {
803            if (c == '\'') {
804               state = S2;
805               mark = i+1;
806            } else if (c == '"') {
807               state = S3;
808               mark = i+1;
809            } else if (c != ' ' && c != '\t') {
810               state = S4;
811               mark = i;
812            }
813         } else if (state == S2 || state == S3) {
814            if (c == '\\') {
815               isInEscape = ! isInEscape;
816               needsUnescape = true;
817            } else if (! isInEscape) {
818               if (c == (state == S2 ? '\'' : '"')) {
819                  String s2 = s.substring(mark, i);
820                  if (needsUnescape)
821                     s2 = unEscapeChars(s2, QUOTE_ESCAPE_SET);
822                  l.add(s2);
823                  state = S1;
824                  isInEscape = needsUnescape = false;
825               }
826            } else {
827               isInEscape = false;
828            }
829         } else if (state == S4) {
830            if (c == ' ' || c == '\t') {
831               l.add(s.substring(mark, i));
832               state = S1;
833            }
834         }
835      }
836      if (state == S4)
837         l.add(s.substring(mark));
838      else if (state == S2 || state == S3)
839         throw new RuntimeException("Unmatched string quotes: " + s);
840      return l.toArray(new String[l.size()]);
841   }
842
843   private static final AsciiSet QUOTE_ESCAPE_SET = AsciiSet.create("\"'\\");
844
845   /**
846    * Returns <jk>true</jk> if specified string is <jk>null</jk> or empty.
847    *
848    * @param s The string to check.
849    * @return <jk>true</jk> if specified string is <jk>null</jk> or empty.
850    */
851   public static boolean isEmpty(String s) {
852      return s == null || s.isEmpty();
853   }
854
855   /**
856    * Returns <jk>true</jk> if specified string is <jk>null</jk> or it's {@link #toString()} method returns an empty
857    * string.
858    *
859    * @param s The string to check.
860    * @return
861    *    <jk>true</jk> if specified string is <jk>null</jk> or it's {@link #toString()} method returns an empty string.
862    */
863   public static boolean isEmpty(Object s) {
864      return s == null || s.toString().isEmpty();
865   }
866
867   /**
868    * Returns <jk>true</jk> if specified string is not <jk>null</jk> or empty.
869    *
870    * @param s The string to check.
871    * @return <jk>true</jk> if specified string is not <jk>null</jk> or empty.
872    */
873   public static boolean isNotEmpty(String s) {
874      return ! isEmpty(s);
875   }
876
877   /**
878    * Returns <jk>true</jk> if specified string is not <jk>null</jk> or it's {@link #toString()} method doesn't return an empty
879    * string.
880    *
881    * @param s The string to check.
882    * @return
883    *    <jk>true</jk> if specified string is not <jk>null</jk> or it's {@link #toString()} method doesn't return an empty string.
884    */
885   public static boolean isNotEmpty(Object s) {
886      return ! isEmpty(s);
887   }
888
889   /**
890    * Returns <jk>null</jk> if the specified string is <jk>null</jk> or empty.
891    *
892    * @param s The string to check.
893    * @return <jk>null</jk> if the specified string is <jk>null</jk> or empty, or the same string if not.
894    */
895   public static String nullIfEmpty(String s) {
896      if (s == null || s.isEmpty())
897         return null;
898      return s;
899   }
900
901   /**
902    * Returns an empty string if the specified string is <jk>null</jk>.
903    *
904    * @param s The string to check.
905    * @return An empty string if the specified string is <jk>null</jk>, or the same string otherwise.
906    */
907   public static String emptyIfNull(String s) {
908      if (s == null)
909         return "";
910      return s;
911   }
912
913   /**
914    * Returns an empty string if the specified object is <jk>null</jk>.
915    *
916    * @param o The object to check.
917    * @return An empty string if the specified object is <jk>null</jk>, or the object converted to a string using {@link String#toString()}.
918    */
919   public static String emptyIfNull(Object o) {
920      if (o == null)
921         return "";
922      return o.toString();
923   }
924
925
926   /**
927    * Removes escape characters from the specified characters.
928    *
929    * @param s The string to remove escape characters from.
930    * @param escaped The characters escaped.
931    * @return A new string if characters were removed, or the same string if not or if the input was <jk>null</jk>.
932    */
933   public static String unEscapeChars(String s, AsciiSet escaped) {
934      if (s == null || s.length() == 0)
935         return s;
936      int count = 0;
937      for (int i = 0; i < s.length(); i++)
938         if (escaped.contains(s.charAt(i)))
939            count++;
940      if (count == 0)
941         return s;
942      StringBuffer sb = new StringBuffer(s.length()-count);
943      for (int i = 0; i < s.length(); i++) {
944         char c = s.charAt(i);
945
946         if (c == '\\') {
947            if (i+1 != s.length()) {
948               char c2 = s.charAt(i+1);
949               if (escaped.contains(c2)) {
950                  i++;
951               } else if (c2 == '\\') {
952                  sb.append('\\');
953                  i++;
954               }
955            }
956         }
957         sb.append(s.charAt(i));
958      }
959      return sb.toString();
960   }
961
962   /**
963    * Escapes the specified characters in the string.
964    *
965    * @param s The string with characters to escape.
966    * @param escaped The characters to escape.
967    * @return The string with characters escaped, or the same string if no escapable characters were found.
968    */
969   public static String escapeChars(String s, AsciiSet escaped) {
970      if (s == null || s.length() == 0)
971         return s;
972
973      int count = 0;
974      for (int i = 0; i < s.length(); i++)
975         if (escaped.contains(s.charAt(i)))
976            count++;
977      if (count == 0)
978         return s;
979
980      StringBuffer sb = new StringBuffer(s.length() + count);
981      for (int i = 0; i < s.length(); i++) {
982         char c = s.charAt(i);
983         if (escaped.contains(c))
984            sb.append('\\');
985         sb.append(c);
986      }
987      return sb.toString();
988   }
989
990   /**
991    * Debug method for rendering non-ASCII character sequences.
992    *
993    * @param s The string to decode.
994    * @return A string with non-ASCII characters converted to <js>"[hex]"</js> sequences.
995    */
996   public static String decodeHex(String s) {
997      if (s == null)
998         return null;
999      StringBuilder sb = new StringBuilder();
1000      for (char c : s.toCharArray()) {
1001         if (c < ' ' || c > '~')
1002            sb.append("["+Integer.toHexString(c)+"]");
1003         else
1004            sb.append(c);
1005      }
1006      return sb.toString();
1007   }
1008
1009   /**
1010    * An efficient method for checking if a string starts with a character.
1011    *
1012    * @param s The string to check.  Can be <jk>null</jk>.
1013    * @param c The character to check for.
1014    * @return <jk>true</jk> if the specified string is not <jk>null</jk> and starts with the specified character.
1015    */
1016   public static boolean startsWith(String s, char c) {
1017      if (s != null) {
1018         int i = s.length();
1019         if (i > 0)
1020            return s.charAt(0) == c;
1021      }
1022      return false;
1023   }
1024
1025   /**
1026    * An efficient method for checking if a string ends with a character.
1027    *
1028    * @param s The string to check.  Can be <jk>null</jk>.
1029    * @param c The character to check for.
1030    * @return <jk>true</jk> if the specified string is not <jk>null</jk> and ends with the specified character.
1031    */
1032   public static boolean endsWith(String s, char c) {
1033      if (s != null) {
1034         int i = s.length();
1035         if (i > 0)
1036            return s.charAt(i-1) == c;
1037      }
1038      return false;
1039   }
1040
1041   /**
1042    * Same as {@link #endsWith(String, char)} except check for multiple characters.
1043    *
1044    * @param s The string to check.  Can be <jk>null</jk>.
1045    * @param c The characters to check for.
1046    * @return <jk>true</jk> if the specified string is not <jk>null</jk> and ends with the specified character.
1047    */
1048   public static boolean endsWith(String s, char...c) {
1049      if (s != null) {
1050         int i = s.length();
1051         if (i > 0) {
1052            char c2 = s.charAt(i-1);
1053            for (char cc : c)
1054               if (c2 == cc)
1055                  return true;
1056         }
1057      }
1058      return false;
1059   }
1060
1061   /**
1062    * Converts the specified number into a 2 hexadecimal characters.
1063    *
1064    * @param num The number to convert to hex.
1065    * @return A <code><jk>char</jk>[2]</code> containing the specified characters.
1066    */
1067   public static final char[] toHex2(int num) {
1068      if (num < 0 || num > 255)
1069         throw new NumberFormatException("toHex2 can only be used on numbers between 0 and 255");
1070      char[] n = new char[2];
1071      int a = num%16;
1072      n[1] = (char)(a > 9 ? 'A'+a-10 : '0'+a);
1073      a = (num/16)%16;
1074      n[0] = (char)(a > 9 ? 'A'+a-10 : '0'+a);
1075      return n;
1076   }
1077
1078   /**
1079    * Converts the specified number into a 4 hexadecimal characters.
1080    *
1081    * @param num The number to convert to hex.
1082    * @return A <code><jk>char</jk>[4]</code> containing the specified characters.
1083    */
1084   public static final char[] toHex4(int num) {
1085      char[] n = new char[4];
1086      int a = num%16;
1087      n[3] = (char)(a > 9 ? 'A'+a-10 : '0'+a);
1088      int base = 16;
1089      for (int i = 1; i < 4; i++) {
1090         a = (num/base)%16;
1091         base <<= 4;
1092         n[3-i] = (char)(a > 9 ? 'A'+a-10 : '0'+a);
1093      }
1094      return n;
1095   }
1096
1097   /**
1098    * Converts the specified number into a 8 hexadecimal characters.
1099    *
1100    * @param num The number to convert to hex.
1101    * @return A <code><jk>char</jk>[8]</code> containing the specified characters.
1102    */
1103   public static final char[] toHex8(long num) {
1104      char[] n = new char[8];
1105      long a = num%16;
1106      n[7] = (char)(a > 9 ? 'A'+a-10 : '0'+a);
1107      int base = 16;
1108      for (int i = 1; i < 8; i++) {
1109         a = (num/base)%16;
1110         base <<= 4;
1111         n[7-i] = (char)(a > 9 ? 'A'+a-10 : '0'+a);
1112      }
1113      return n;
1114   }
1115
1116   /**
1117    * Tests two strings for equality, but gracefully handles nulls.
1118    *
1119    * @param s1 String 1.
1120    * @param s2 String 2.
1121    * @return <jk>true</jk> if the strings are equal.
1122    */
1123   public static boolean isEquals(String s1, String s2) {
1124      if (s1 == null)
1125         return s2 == null;
1126      if (s2 == null)
1127         return false;
1128      return s1.equals(s2);
1129   }
1130
1131   /**
1132    * Shortcut for calling <code>base64Encode(in.getBytes(<js>"UTF-8"</js>))</code>
1133    *
1134    * @param in The input string to convert.
1135    * @return The string converted to BASE-64 encoding.
1136    */
1137   public static String base64EncodeToString(String in) {
1138      if (in == null)
1139         return null;
1140      return base64Encode(in.getBytes(IOUtils.UTF8));
1141   }
1142
1143   /**
1144    * BASE64-encodes the specified byte array.
1145    *
1146    * @param in The input byte array to convert.
1147    * @return The byte array converted to a BASE-64 encoded string.
1148    */
1149   public static String base64Encode(byte[] in) {
1150      int outLength = (in.length * 4 + 2) / 3;   // Output length without padding
1151      char[] out = new char[((in.length + 2) / 3) * 4];  // Length includes padding.
1152      int iIn = 0;
1153      int iOut = 0;
1154      while (iIn < in.length) {
1155         int i0 = in[iIn++] & 0xff;
1156         int i1 = iIn < in.length ? in[iIn++] & 0xff : 0;
1157         int i2 = iIn < in.length ? in[iIn++] & 0xff : 0;
1158         int o0 = i0 >>> 2;
1159         int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
1160         int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
1161         int o3 = i2 & 0x3F;
1162         out[iOut++] = base64m1[o0];
1163         out[iOut++] = base64m1[o1];
1164         out[iOut] = iOut < outLength ? base64m1[o2] : '=';
1165         iOut++;
1166         out[iOut] = iOut < outLength ? base64m1[o3] : '=';
1167         iOut++;
1168      }
1169      return new String(out);
1170   }
1171
1172   /**
1173    * Shortcut for calling <code>base64Decode(String)</code> and converting the result to a UTF-8 encoded string.
1174    *
1175    * @param in The BASE-64 encoded string to decode.
1176    * @return The decoded string.
1177    */
1178   public static String base64DecodeToString(String in) {
1179      byte[] b = base64Decode(in);
1180      if (b == null)
1181         return null;
1182      return new String(b, IOUtils.UTF8);
1183   }
1184
1185   /**
1186    * BASE64-decodes the specified string.
1187    *
1188    * @param in The BASE-64 encoded string.
1189    * @return The decoded byte array.
1190    */
1191   public static byte[] base64Decode(String in) {
1192      if (in == null)
1193         return null;
1194
1195      byte bIn[] = in.getBytes(IOUtils.UTF8);
1196
1197      if (bIn.length % 4 != 0)
1198         illegalArg("Invalid BASE64 string length.  Must be multiple of 4.");
1199
1200      // Strip out any trailing '=' filler characters.
1201      int inLength = bIn.length;
1202      while (inLength > 0 && bIn[inLength - 1] == '=')
1203         inLength--;
1204
1205      int outLength = (inLength * 3) / 4;
1206      byte[] out = new byte[outLength];
1207      int iIn = 0;
1208      int iOut = 0;
1209      while (iIn < inLength) {
1210         int i0 = bIn[iIn++];
1211         int i1 = bIn[iIn++];
1212         int i2 = iIn < inLength ? bIn[iIn++] : 'A';
1213         int i3 = iIn < inLength ? bIn[iIn++] : 'A';
1214         int b0 = base64m2[i0];
1215         int b1 = base64m2[i1];
1216         int b2 = base64m2[i2];
1217         int b3 = base64m2[i3];
1218         int o0 = (b0 << 2) | (b1 >>> 4);
1219         int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2);
1220         int o2 = ((b2 & 3) << 6) | b3;
1221         out[iOut++] = (byte)o0;
1222         if (iOut < outLength)
1223            out[iOut++] = (byte)o1;
1224         if (iOut < outLength)
1225            out[iOut++] = (byte)o2;
1226      }
1227      return out;
1228   }
1229
1230   /**
1231    * Generated a random UUID with the specified number of characters.
1232    *
1233    * <p>
1234    * Characters are composed of lower-case ASCII letters and numbers only.
1235    *
1236    * <p>
1237    * This method conforms to the restrictions for hostnames as specified in {@doc https://tools.ietf.org/html/rfc952 RFC 952}
1238    * Since each character has 36 possible values, the square approximation formula for the number of generated IDs
1239    * that would produce a 50% chance of collision is:
1240    * <code>sqrt(36^N)</code>.
1241    * Dividing this number by 10 gives you an approximation of the number of generated IDs needed to produce a
1242    * &lt;1% chance of collision.
1243    *
1244    * <p>
1245    * For example, given 5 characters, the number of generated IDs need to produce a &lt;1% chance of collision would
1246    * be:
1247    * <code>sqrt(36^5)/10=777</code>
1248    *
1249    * @param numchars The number of characters in the generated UUID.
1250    * @return A new random UUID.
1251    */
1252   public static String generateUUID(int numchars) {
1253      Random r = new Random();
1254      StringBuilder sb = new StringBuilder(numchars);
1255      for (int i = 0; i < numchars; i++) {
1256         int c = r.nextInt(36) + 97;
1257         if (c > 'z')
1258            c -= ('z'-'0'+1);
1259         sb.append((char)c);
1260      }
1261      return sb.toString();
1262   }
1263
1264   /**
1265    * Same as {@link String#trim()} but prevents <code>NullPointerExceptions</code>.
1266    *
1267    * @param s The string to trim.
1268    * @return The trimmed string, or <jk>null</jk> if the string was <jk>null</jk>.
1269    */
1270   public static String trim(String s) {
1271      if (s == null)
1272         return null;
1273      return s.trim();
1274   }
1275
1276   /**
1277    * Parses an ISO8601 string into a date.
1278    *
1279    * <p>
1280    * Supports any of the following formats:
1281    * <br><code>yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddThh, yyyy-MM-ddThh:mm, yyyy-MM-ddThh:mm:ss, yyyy-MM-ddThh:mm:ss.SSS</code>
1282    *
1283    * @param date The date string.
1284    * @return The parsed date.
1285    * @throws IllegalArgumentException
1286    */
1287   public static Date parseIsoDate(String date) throws IllegalArgumentException {
1288      if (isEmpty(date))
1289         return null;
1290      return parseIsoCalendar(date).getTime();
1291   }
1292
1293   /**
1294    * Parses an ISO8601 string into a calendar.
1295    *
1296    * <p>
1297    * Supports any of the following formats:
1298    * <br><code>yyyy, yyyy-MM, yyyy-MM-dd, yyyy-MM-ddThh, yyyy-MM-ddThh:mm, yyyy-MM-ddThh:mm:ss, yyyy-MM-ddThh:mm:ss.SSS</code>
1299    *
1300    * @param date The date string.
1301    * @return The parsed calendar.
1302    * @throws IllegalArgumentException
1303    */
1304   public static Calendar parseIsoCalendar(String date) throws IllegalArgumentException {
1305      if (isEmpty(date))
1306         return null;
1307      date = date.trim().replace(' ', 'T');  // Convert to 'standard' ISO8601
1308      if (date.indexOf(',') != -1)  // Trim milliseconds
1309         date = date.substring(0, date.indexOf(','));
1310      if (date.matches("\\d{4}"))
1311         date += "-01-01T00:00:00";
1312      else if (date.matches("\\d{4}\\-\\d{2}"))
1313         date += "-01T00:00:00";
1314      else if (date.matches("\\d{4}\\-\\d{2}\\-\\d{2}"))
1315         date += "T00:00:00";
1316      else if (date.matches("\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}"))
1317         date += ":00:00";
1318      else if (date.matches("\\d{4}\\-\\d{2}\\-\\d{2}T\\d{2}\\:\\d{2}"))
1319         date += ":00";
1320      return DatatypeConverter.parseDateTime(date);
1321   }
1322
1323   /**
1324    * Converts the specified object to an ISO8601 date string.
1325    *
1326    * @param c The object to convert.
1327    * @return The converted object.
1328    */
1329   public static String toIsoDate(Calendar c) {
1330      return DatatypeConverter.printDate(c);
1331   }
1332
1333   /**
1334    * Converts the specified object to an ISO8601 date-time string.
1335    *
1336    * @param c The object to convert.
1337    * @return The converted object.
1338    */
1339   public static String toIsoDateTime(Calendar c) {
1340      return DatatypeConverter.printDateTime(c);
1341   }
1342
1343   /**
1344    * Simple utility for replacing variables of the form <js>"{key}"</js> with values in the specified map.
1345    *
1346    * <p>
1347    * Nested variables are supported in both the input string and map values.
1348    *
1349    * <p>
1350    * If the map does not contain the specified value, the variable is not replaced.
1351    *
1352    * <p>
1353    * <jk>null</jk> values in the map are treated as blank strings.
1354    *
1355    * @param s The string containing variables to replace.
1356    * @param m The map containing the variable values.
1357    * @return The new string with variables replaced, or the original string if it didn't have variables in it.
1358    */
1359   public static String replaceVars(String s, Map<String,Object> m) {
1360
1361      if (s == null)
1362         return null;
1363
1364      if (s.indexOf('{') == -1)
1365         return s;
1366
1367      int S1 = 1;    // Not in variable, looking for {
1368      int S2 = 2;    // Found {, Looking for }
1369
1370      int state = S1;
1371      boolean hasInternalVar = false;
1372      int x = 0;
1373      int depth = 0;
1374      int length = s.length();
1375      StringBuilder out = new StringBuilder();
1376      for (int i = 0; i < length; i++) {
1377         char c = s.charAt(i);
1378         if (state == S1) {
1379            if (c == '{') {
1380               state = S2;
1381               x = i;
1382            } else {
1383               out.append(c);
1384            }
1385         } else /* state == S2 */ {
1386            if (c == '{') {
1387               depth++;
1388               hasInternalVar = true;
1389            } else if (c == '}') {
1390               if (depth > 0) {
1391                  depth--;
1392               } else {
1393                  String key = s.substring(x+1, i);
1394                  key = (hasInternalVar ? replaceVars(key, m) : key);
1395                  hasInternalVar = false;
1396                  if (! m.containsKey(key))
1397                     out.append('{').append(key).append('}');
1398                  else {
1399                     Object val = m.get(key);
1400                     if (val == null)
1401                        val = "";
1402                     String v = val.toString();
1403                     // If the replacement also contains variables, replace them now.
1404                     if (v.indexOf('{') != -1)
1405                        v = replaceVars(v, m);
1406                     out.append(v);
1407                  }
1408                  state = 1;
1409               }
1410            }
1411         }
1412      }
1413      return out.toString();
1414   }
1415
1416   /**
1417    * Returns <jk>true</jk> if the specified path string is prefixed with the specified prefix.
1418    *
1419    * <h5 class='section'>Example:</h5>
1420    * <p class='bcode w800'>
1421    *    pathStartsWith(<js>"foo"</js>, <js>"foo"</js>);  <jc>// true</jc>
1422    *    pathStartsWith(<js>"foo/bar"</js>, <js>"foo"</js>);  <jc>// true</jc>
1423    *    pathStartsWith(<js>"foo2"</js>, <js>"foo"</js>);  <jc>// false</jc>
1424    *    pathStartsWith(<js>"foo2"</js>, <js>""</js>);  <jc>// false</jc>
1425    * </p>
1426    *
1427    * @param path The path to check.
1428    * @param pathPrefix The prefix.
1429    * @return <jk>true</jk> if the specified path string is prefixed with the specified prefix.
1430    */
1431   public static boolean pathStartsWith(String path, String pathPrefix) {
1432      if (path == null || pathPrefix == null)
1433         return false;
1434      if (path.startsWith(pathPrefix))
1435         return path.length() == pathPrefix.length() || path.charAt(pathPrefix.length()) == '/';
1436      return false;
1437   }
1438
1439   /**
1440    * Same as {@link #pathStartsWith(String, String)} but returns <jk>true</jk> if at least one prefix matches.
1441    *
1442    * @param path The path to check.
1443    * @param pathPrefixes The prefixes.
1444    * @return <jk>true</jk> if the specified path string is prefixed with any of the specified prefixes.
1445    */
1446   public static boolean pathStartsWith(String path, String[] pathPrefixes) {
1447      for (String p : pathPrefixes)
1448         if (pathStartsWith(path, p))
1449            return true;
1450      return false;
1451   }
1452
1453   /**
1454    * Replaces <js>"\\uXXXX"</js> character sequences with their unicode characters.
1455    *
1456    * @param s The string to replace unicode sequences in.
1457    * @return A string with unicode sequences replaced.
1458    */
1459   public static String replaceUnicodeSequences(String s) {
1460      if (s.indexOf('\\') == -1)
1461         return s;
1462      Pattern p = Pattern.compile("\\\\u(\\p{XDigit}{4})");
1463      Matcher m = p.matcher(s);
1464      StringBuffer sb = new StringBuffer(s.length());
1465      while (m.find()) {
1466         String ch = String.valueOf((char) Integer.parseInt(m.group(1), 16));
1467         m.appendReplacement(sb, Matcher.quoteReplacement(ch));
1468      }
1469      m.appendTail(sb);
1470      return sb.toString();
1471   }
1472
1473   /**
1474    * Creates an escaped-unicode sequence (e.g. <js>"\\u1234"</js>) for the specified character.
1475    *
1476    * @param c The character to create a sequence for.
1477    * @return An escaped-unicode sequence.
1478    */
1479   public static String unicodeSequence(char c) {
1480      StringBuilder sb = new StringBuilder(6);
1481      sb.append('\\').append('u');
1482      for (char cc : toHex4(c))
1483         sb.append(cc);
1484      return sb.toString();
1485   }
1486
1487   /**
1488    * Returns the specified field in a delimited string without splitting the string.
1489    *
1490    * <p>
1491    * Equivalent to the following:
1492    * <p class='bcode w800'>
1493    *    String in = <js>"0,1,2"</js>;
1494    *    String[] parts = in.split(<js>","</js>);
1495    *    String p1 = (parts.<jk>length</jk> > 1 ? parts[1] : <js>""</js>);
1496    * </p>
1497    *
1498    * @param fieldNum The field number.  Zero-indexed.
1499    * @param s The input string.
1500    * @param delim The delimiter character.
1501    * @return The field entry in the string, or a blank string if it doesn't exist or the string is null.
1502    */
1503   public static String getField(int fieldNum, String s, char delim) {
1504      return getField(fieldNum, s, delim, "");
1505   }
1506
1507   /**
1508    * Same as {@link #getField(int, String, char)} except allows you to specify the default value.
1509    *
1510    * @param fieldNum The field number.  Zero-indexed.
1511    * @param s The input string.
1512    * @param delim The delimiter character.
1513    * @param def The default value if the field does not exist.
1514    * @return The field entry in the string, or the default value if it doesn't exist or the string is null.
1515    */
1516   public static String getField(int fieldNum, String s, char delim, String def) {
1517      if (s == null || fieldNum < 0)
1518         return def;
1519      int start = 0;
1520      for (int i = 0; i < s.length(); i++) {
1521         char c = s.charAt(i);
1522         if (c == delim) {
1523            fieldNum--;
1524            if (fieldNum == 0)
1525               start = i+1;
1526         }
1527         if (fieldNum < 0)
1528            return s.substring(start, i);
1529      }
1530      if (start == 0)
1531         return def;
1532      return s.substring(start);
1533   }
1534
1535   /**
1536    * Calls {@link #toString()} on the specified object if it's not null.
1537    *
1538    * @param o The object to convert to a string.
1539    * @return The object converted to a string, or <jk>null</jk> if the object was null.
1540    */
1541   public static String asString(Object o) {
1542      return (o == null ? null : o.toString());
1543   }
1544
1545   /**
1546    * Converts an array of objects to an array of strings.
1547    *
1548    * @param o The array of objects to convert to strings.
1549    * @return A new array of objects converted to strings.
1550    */
1551   public static String[] asStrings(Object...o) {
1552      if (o == null)
1553         return null;
1554      if (o instanceof String[])
1555         return (String[])o;
1556      String[] s = new String[o.length];
1557      for (int i = 0; i < o.length; i++)
1558         s[i] = asString(o[i]);
1559      return s;
1560   }
1561
1562   /**
1563    * Converts a hexadecimal byte stream (e.g. "34A5BC") into a UTF-8 encoded string.
1564    *
1565    * @param hex The hexadecimal string.
1566    * @return The UTF-8 string.
1567    */
1568   public static String fromHexToUTF8(String hex) {
1569      ByteBuffer buff = ByteBuffer.allocate(hex.length()/2);
1570      for (int i = 0; i < hex.length(); i+=2)
1571         buff.put((byte)Integer.parseInt(hex.substring(i, i+2), 16));
1572      buff.rewind();
1573      Charset cs = Charset.forName("UTF-8");
1574      return cs.decode(buff).toString();
1575   }
1576
1577   /**
1578    * Converts a space-deliminted hexadecimal byte stream (e.g. "34 A5 BC") into a UTF-8 encoded string.
1579    *
1580    * @param hex The hexadecimal string.
1581    * @return The UTF-8 string.
1582    */
1583   public static String fromSpacedHexToUTF8(String hex) {
1584      ByteBuffer buff = ByteBuffer.allocate((hex.length()+1)/3);
1585      for (int i = 0; i < hex.length(); i+=3)
1586         buff.put((byte)Integer.parseInt(hex.substring(i, i+2), 16));
1587      buff.rewind();
1588      Charset cs = Charset.forName("UTF-8");
1589      return cs.decode(buff).toString();
1590   }
1591
1592   private static final char[] HEX = "0123456789ABCDEF".toCharArray();
1593
1594   /**
1595    * Converts a byte array into a simple hexadecimal character string.
1596    *
1597    * @param bytes The bytes to convert to hexadecimal.
1598    * @return A new string consisting of hexadecimal characters.
1599    */
1600   public static String toHex(byte[] bytes) {
1601      StringBuilder sb = new StringBuilder(bytes.length * 2);
1602      for (int j = 0; j < bytes.length; j++) {
1603         int v = bytes[j] & 0xFF;
1604         sb.append(HEX[v >>> 4]).append(HEX[v & 0x0F]);
1605      }
1606      return sb.toString();
1607   }
1608
1609   /**
1610    * Same as {@link #toHex(byte[])} but puts spaces between the byte strings.
1611    *
1612    * @param bytes The bytes to convert to hexadecimal.
1613    * @return A new string consisting of hexadecimal characters.
1614    */
1615   public static String toSpacedHex(byte[] bytes) {
1616      StringBuilder sb = new StringBuilder(bytes.length * 3);
1617      for (int j = 0; j < bytes.length; j++) {
1618         if (j > 0)
1619            sb.append(' ');
1620         int v = bytes[j] & 0xFF;
1621         sb.append(HEX[v >>> 4]).append(HEX[v & 0x0F]);
1622      }
1623      return sb.toString();
1624   }
1625
1626   /**
1627    * Converts a hexadecimal character string to a byte array.
1628    *
1629    * @param hex The string to convert to a byte array.
1630    * @return A new byte array.
1631    */
1632   public static byte[] fromHex(String hex) {
1633      ByteBuffer buff = ByteBuffer.allocate(hex.length()/2);
1634      for (int i = 0; i < hex.length(); i+=2)
1635         buff.put((byte)Integer.parseInt(hex.substring(i, i+2), 16));
1636      buff.rewind();
1637      return buff.array();
1638   }
1639
1640   /**
1641    * Same as {@link #fromHex(String)} except expects spaces between the byte strings.
1642    *
1643    * @param hex The string to convert to a byte array.
1644    * @return A new byte array.
1645    */
1646   public static byte[] fromSpacedHex(String hex) {
1647      ByteBuffer buff = ByteBuffer.allocate((hex.length()+1)/3);
1648      for (int i = 0; i < hex.length(); i+=3)
1649         buff.put((byte)Integer.parseInt(hex.substring(i, i+2), 16));
1650      buff.rewind();
1651      return buff.array();
1652   }
1653
1654   /**
1655    * Creates a repeated pattern.
1656    *
1657    * @param count The number of times to repeat the pattern.
1658    * @param pattern The pattern to repeat.
1659    * @return A new string consisting of the repeated pattern.
1660    */
1661   public static String repeat(int count, String pattern) {
1662      StringBuilder sb = new StringBuilder(pattern.length() * count);
1663      for (int i = 0; i < count; i++)
1664         sb.append(pattern);
1665      return sb.toString();
1666   }
1667
1668   /**
1669    * Trims whitespace characters from the beginning of the specified string.
1670    *
1671    * @param s The string to trim.
1672    * @return The trimmed string, or <jk>null</jk> if the string was <jk>null</jk>.
1673    */
1674   public static String trimStart(String s) {
1675      if (s != null)
1676         while (s.length() > 0 && Character.isWhitespace(s.charAt(0)))
1677            s = s.substring(1);
1678      return s;
1679   }
1680
1681   /**
1682    * Trims whitespace characters from the end of the specified string.
1683    *
1684    * @param s The string to trim.
1685    * @return The trimmed string, or <jk>null</jk> if the string was <jk>null</jk>.
1686    */
1687   public static String trimEnd(String s) {
1688      if (s != null)
1689         while (s.length() > 0 && Character.isWhitespace(s.charAt(s.length()-1)))
1690            s = s.substring(0, s.length()-1);
1691      return s;
1692   }
1693
1694   /**
1695    * Returns <jk>true</jk> if the specified string is one of the specified values.
1696    *
1697    * @param s
1698    *    The string to test.
1699    *    Can be <jk>null</jk>.
1700    * @param values
1701    *    The values to test.
1702    *    Can contain <jk>null</jk>.
1703    * @return <jk>true</jk> if the specified string is one of the specified values.
1704    */
1705   public static boolean isOneOf(String s, String...values) {
1706      for (int i = 0; i < values.length; i++)
1707         if (StringUtils.isEquals(s, values[i]))
1708            return true;
1709      return false;
1710   }
1711
1712   /**
1713    * Trims <js>'/'</js> characters from both the start and end of the specified string.
1714    *
1715    * @param s The string to trim.
1716    * @return A new trimmed string, or the same string if no trimming was necessary.
1717    */
1718   public static String trimSlashes(String s) {
1719      if (s == null)
1720         return null;
1721      while (endsWith(s, '/'))
1722         s = s.substring(0, s.length()-1);
1723      while (s.length() > 0 && s.charAt(0) == '/')
1724         s = s.substring(1);
1725      return s;
1726   }
1727
1728   /**
1729    * Trims <js>'/'</js> characters from the end of the specified string.
1730    *
1731    * @param s The string to trim.
1732    * @return A new trimmed string, or the same string if no trimming was necessary.
1733    */
1734   public static String trimTrailingSlashes(String s) {
1735      if (s == null)
1736         return null;
1737      while (endsWith(s, '/'))
1738         s = s.substring(0, s.length()-1);
1739      return s;
1740   }
1741
1742   /**
1743    * Trims <js>'/'</js> characters from the end of the specified string.
1744    *
1745    * @param s The string to trim.
1746    * @return The same string buffer.
1747    */
1748   public static StringBuffer trimTrailingSlashes(StringBuffer s) {
1749      if (s == null)
1750         return null;
1751      while (s.length() > 0 && s.charAt(s.length()-1) == '/')
1752         s.setLength(s.length()-1);
1753      return s;
1754   }
1755
1756   /**
1757    * Shortcut for calling <code>URLEncoder.<jsm>encode</jsm>(o.toString(), <js>"UTF-8"</js>)</code>.
1758    *
1759    * @param o The object to encode.
1760    * @return The URL encoded string, or <jk>null</jk> if the object was null.
1761    */
1762   public static String urlEncode(Object o) {
1763      try {
1764         if (o != null)
1765            return URLEncoder.encode(o.toString(), "UTF-8");
1766      } catch (UnsupportedEncodingException e) {}
1767      return null;
1768   }
1769
1770   private static final AsciiSet URL_ENCODE_PATHINFO_VALIDCHARS =
1771      AsciiSet.create().ranges("a-z","A-Z","0-9").chars("-_.*/()").build();
1772
1773   /**
1774    * Similar to {@link #urlEncode(Object)} but doesn't encode <js>"/"</js> characters.
1775    *
1776    * @param o The object to encode.
1777    * @return The URL encoded string, or <jk>null</jk> if the object was null.
1778    */
1779   public static String urlEncodePath(Object o) {
1780      if (o == null)
1781         return null;
1782      String s = asString(o);
1783
1784      boolean needsEncode = false;
1785      for (int i = 0; i < s.length() && ! needsEncode; i++)
1786         needsEncode = URL_ENCODE_PATHINFO_VALIDCHARS.contains(s.charAt(i));
1787      if (! needsEncode)
1788         return s;
1789
1790      StringBuilder sb = new StringBuilder();
1791      CharArrayWriter caw = new CharArrayWriter();
1792      int caseDiff = ('a' - 'A');
1793
1794      for (int i = 0; i < s.length();) {
1795         char c = s.charAt(i);
1796         if (URL_ENCODE_PATHINFO_VALIDCHARS.contains(c)) {
1797            sb.append(c);
1798            i++;
1799         } else {
1800            if (c == ' ') {
1801               sb.append('+');
1802               i++;
1803            } else {
1804               do {
1805                  caw.write(c);
1806                  if (c >= 0xD800 && c <= 0xDBFF) {
1807                     if ( (i+1) < s.length()) {
1808                        int d = s.charAt(i+1);
1809                        if (d >= 0xDC00 && d <= 0xDFFF) {
1810                           caw.write(d);
1811                           i++;
1812                        }
1813                     }
1814                  }
1815                  i++;
1816               } while (i < s.length() && !URL_ENCODE_PATHINFO_VALIDCHARS.contains((c = s.charAt(i))));
1817
1818               caw.flush();
1819               String s2 = new String(caw.toCharArray());
1820               byte[] ba = s2.getBytes(IOUtils.UTF8);
1821               for (int j = 0; j < ba.length; j++) {
1822                  sb.append('%');
1823                  char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16);
1824                  if (Character.isLetter(ch)) {
1825                     ch -= caseDiff;
1826                  }
1827                  sb.append(ch);
1828                  ch = Character.forDigit(ba[j] & 0xF, 16);
1829                  if (Character.isLetter(ch)) {
1830                     ch -= caseDiff;
1831                  }
1832                  sb.append(ch);
1833               }
1834               caw.reset();
1835            }
1836         }
1837      }
1838      return sb.toString();
1839   }
1840
1841   /**
1842    * Decodes a <code>application/x-www-form-urlencoded</code> string using <code>UTF-8</code> encoding scheme.
1843    *
1844    * @param s The string to decode.
1845    * @return The decoded string, or <jk>null</jk> if input is <jk>null</jk>.
1846    */
1847   public static String urlDecode(String s) {
1848      if (s == null)
1849         return s;
1850      boolean needsDecode = false;
1851      for (int i = 0; i < s.length() && ! needsDecode; i++) {
1852         char c = s.charAt(i);
1853         if (c == '+' || c == '%')
1854            needsDecode = true;
1855      }
1856      if (needsDecode) {
1857         try {
1858            return URLDecoder.decode(s, "UTF-8");
1859         } catch (UnsupportedEncodingException e) {/* Won't happen */}
1860      }
1861      return s;
1862   }
1863
1864   /**
1865    * Encodes a <code>application/x-www-form-urlencoded</code> string using <code>UTF-8</code> encoding scheme.
1866    *
1867    * @param s The string to encode.
1868    * @return The encoded string, or <jk>null</jk> if input is <jk>null</jk>.
1869    */
1870   public static String urlEncode(String s) {
1871      if (s == null)
1872         return null;
1873      boolean needsEncode = false;
1874      for (int i = 0; i < s.length() && ! needsEncode; i++)
1875         needsEncode |= (! unencodedChars.contains(s.charAt(i)));
1876      if (needsEncode) {
1877         try {
1878            return URLEncoder.encode(s, "UTF-8");
1879         } catch (UnsupportedEncodingException e) {/* Won't happen */}
1880      }
1881      return s;
1882   }
1883
1884   /**
1885    * Same as {@link #urlEncode(String)} except only excapes characters that absolutely need to be escaped.
1886    *
1887    * @param s The string to escape.
1888    * @return The encoded string, or <jk>null</jk> if input is <jk>null</jk>.
1889    */
1890   public static String urlEncodeLax(String s) {
1891      if (s == null)
1892         return null;
1893      boolean needsEncode = false;
1894      for (int i = 0; i < s.length() && ! needsEncode; i++)
1895         needsEncode |= (! unencodedCharsLax.contains(s.charAt(i)));
1896      if (needsEncode) {
1897         StringBuilder sb = new StringBuilder(s.length()*2);
1898         for (int i = 0; i < s.length(); i++) {
1899            char c = s.charAt(i);
1900            if (unencodedCharsLax.contains(c))
1901               sb.append(c);
1902            else if (c == ' ')
1903               sb.append("+");
1904            else if (c <= 127)
1905               sb.append('%').append(toHex2(c));
1906            else
1907               try {
1908                  sb.append(URLEncoder.encode(""+c, "UTF-8"));  // Yuck.
1909               } catch (UnsupportedEncodingException e) {
1910                  // Not possible.
1911               }
1912         }
1913         s = sb.toString();
1914      }
1915      return s;
1916   }
1917
1918   /**
1919    * Splits a string into equally-sized parts.
1920    *
1921    * @param s The string to split.
1922    * @param size The token sizes.
1923    * @return The tokens, or <jk>null</jk> if the input was <jk>null</jk>.
1924    */
1925   public static List<String> splitEqually(String s, int size) {
1926      if (s == null)
1927         return null;
1928      if (size <= 0)
1929         return Collections.singletonList(s);
1930
1931      List<String> l = new ArrayList<>((s.length() + size - 1) / size);
1932
1933      for (int i = 0; i < s.length(); i += size)
1934         l.add(s.substring(i, Math.min(s.length(), i + size)));
1935
1936      return l;
1937   }
1938
1939   /**
1940    * Returns the first non-whitespace character in the string.
1941    *
1942    * @param s The string to check.
1943    * @return
1944    *    The first non-whitespace character, or <code>0</code> if the string is <jk>null</jk>, empty, or composed
1945    *    of only whitespace.
1946    */
1947   public static char firstNonWhitespaceChar(String s) {
1948      if (s != null)
1949         for (int i = 0; i < s.length(); i++)
1950            if (! Character.isWhitespace(s.charAt(i)))
1951               return s.charAt(i);
1952      return 0;
1953   }
1954
1955   /**
1956    * Returns the last non-whitespace character in the string.
1957    *
1958    * @param s The string to check.
1959    * @return
1960    *    The last non-whitespace character, or <code>0</code> if the string is <jk>null</jk>, empty, or composed
1961    *    of only whitespace.
1962    */
1963   public static char lastNonWhitespaceChar(String s) {
1964      if (s != null)
1965         for (int i = s.length()-1; i >= 0; i--)
1966            if (! Character.isWhitespace(s.charAt(i)))
1967               return s.charAt(i);
1968      return 0;
1969   }
1970
1971   /**
1972    * Returns the character at the specified index in the string without throwing exceptions.
1973    *
1974    * @param s The string.
1975    * @param i The index position.
1976    * @return
1977    *    The character at the specified index, or <code>0</code> if the index is out-of-range or the string
1978    *    is <jk>null</jk>.
1979    */
1980   public static char charAt(String s, int i) {
1981      if (s == null)
1982         return 0;
1983      if (i < 0 || i >= s.length())
1984         return 0;
1985      return s.charAt(i);
1986   }
1987
1988   /**
1989    * Efficiently determines whether a URL is of the pattern "xxx://xxx"
1990    *
1991    * @param s The string to test.
1992    * @return <jk>true</jk> if it's an absolute path.
1993    */
1994   public static boolean isAbsoluteUri(String s) {
1995
1996      if (isEmpty(s))
1997         return false;
1998
1999      // Use a state machine for maximum performance.
2000
2001      int S1 = 1;  // Looking for http
2002      int S2 = 2;  // Found http, looking for :
2003      int S3 = 3;  // Found :, looking for /
2004      int S4 = 4;  // Found /, looking for /
2005      int S5 = 5;  // Found /, looking for x
2006
2007      int state = S1;
2008      for (int i = 0; i < s.length(); i++) {
2009         char c = s.charAt(i);
2010         if (state == S1) {
2011            if (c >= 'a' && c <= 'z')
2012               state = S2;
2013            else
2014               return false;
2015         } else if (state == S2) {
2016            if (c == ':')
2017               state = S3;
2018            else if (c < 'a' || c > 'z')
2019               return false;
2020         } else if (state == S3) {
2021            if (c == '/')
2022               state = S4;
2023            else
2024               return false;
2025         } else if (state == S4) {
2026            if (c == '/')
2027               state = S5;
2028            else
2029               return false;
2030         } else if (state == S5) {
2031            return true;
2032         }
2033      }
2034      return false;
2035   }
2036
2037   /**
2038    * Efficiently determines whether a URL is of the pattern "xxx:/xxx".
2039    *
2040    * <p>
2041    * The pattern matched is: <code>[a-z]{2,}\:\/.*</code>
2042    *
2043    * <p>
2044    * Note that this excludes filesystem paths such as <js>"C:/temp"</js>.
2045    *
2046    * @param s The string to test.
2047    * @return <jk>true</jk> if it's an absolute path.
2048    */
2049   public static boolean isUri(String s) {
2050
2051      if (isEmpty(s))
2052         return false;
2053
2054      // Use a state machine for maximum performance.
2055
2056      int S1 = 1;  // Looking for protocol char 1
2057      int S2 = 2;  // Found protocol char 1, looking for protocol char 2
2058      int S3 = 3;  // Found protocol char 2, looking for :
2059      int S4 = 4;  // Found :, looking for /
2060
2061
2062      int state = S1;
2063      for (int i = 0; i < s.length(); i++) {
2064         char c = s.charAt(i);
2065         if (state == S1) {
2066            if (c >= 'a' && c <= 'z')
2067               state = S2;
2068            else
2069               return false;
2070         } else if (state == S2) {
2071            if (c >= 'a' && c <= 'z')
2072               state = S3;
2073            else
2074               return false;
2075         } else if (state == S3) {
2076            if (c == ':')
2077               state = S4;
2078            else if (c < 'a' || c > 'z')
2079               return false;
2080         } else if (state == S4) {
2081            if (c == '/')
2082               return true;
2083            return false;
2084         }
2085      }
2086      return false;
2087   }
2088
2089   /**
2090    * Given an absolute URI, returns just the authority portion (e.g. <js>"http://hostname:port"</js>)
2091    *
2092    * @param s The URI string.
2093    * @return Just the authority portion of the URI.
2094    */
2095   public static String getAuthorityUri(String s) {
2096
2097      // Use a state machine for maximum performance.
2098
2099      int S1 = 1;  // Looking for http
2100      int S2 = 2;  // Found http, looking for :
2101      int S3 = 3;  // Found :, looking for /
2102      int S4 = 4;  // Found /, looking for /
2103      int S5 = 5;  // Found /, looking for x
2104      int S6 = 6;  // Found x, looking for /
2105
2106      int state = S1;
2107      for (int i = 0; i < s.length(); i++) {
2108         char c = s.charAt(i);
2109         if (state == S1) {
2110            if (c >= 'a' && c <= 'z')
2111               state = S2;
2112            else
2113               return s;
2114         } else if (state == S2) {
2115            if (c == ':')
2116               state = S3;
2117            else if (c < 'a' || c > 'z')
2118               return s;
2119         } else if (state == S3) {
2120            if (c == '/')
2121               state = S4;
2122            else
2123               return s;
2124         } else if (state == S4) {
2125            if (c == '/')
2126               state = S5;
2127            else
2128               return s;
2129         } else if (state == S5) {
2130            if (c != '/')
2131               state = S6;
2132            else
2133               return s;
2134         } else if (state == S6) {
2135            if (c == '/')
2136               return s.substring(0, i);
2137         }
2138      }
2139      return s;
2140   }
2141
2142   /**
2143    * Converts the specified object to a URI.
2144    *
2145    * @param o The object to convert to a URI.
2146    * @return A new URI, or the same object if the object was already a URI, or
2147    */
2148   public static URI toURI(Object o) {
2149      if (o == null || o instanceof URI)
2150         return (URI)o;
2151      try {
2152         return new URI(o.toString());
2153      } catch (URISyntaxException e) {
2154         throw new RuntimeException(e);
2155      }
2156   }
2157
2158   /**
2159    * Returns the first non-null, non-empty string in the list.
2160    *
2161    * @param s The strings to test.
2162    * @return The first non-empty string in the list, or <jk>null</jk> if they were all <jk>null</jk> or empty.
2163    */
2164   public static String firstNonEmpty(String...s) {
2165      for (String ss : s)
2166         if (isNotEmpty(ss))
2167            return ss;
2168      return null;
2169   }
2170
2171   /**
2172    * Same as {@link String#indexOf(int)} except allows you to check for multiple characters.
2173    *
2174    * @param s The string to check.
2175    * @param c The characters to check for.
2176    * @return The index into the string that is one of the specified characters.
2177    */
2178   public static int indexOf(String s, char...c) {
2179      if (s == null)
2180         return -1;
2181      for (int i = 0; i < s.length(); i++) {
2182         char c2 = s.charAt(i);
2183         for (char cc : c)
2184            if (c2 == cc)
2185               return i;
2186      }
2187      return -1;
2188   }
2189
2190   /**
2191    * Similar to {@link MessageFormat#format(String, Object...)} except allows you to specify POJO arguments.
2192    *
2193    * @param pattern The string pattern.
2194    * @param args The arguments.
2195    * @return The formatted string.
2196    */
2197   public static String format(String pattern, Object...args) {
2198      if (args == null || args.length == 0)
2199         return pattern;
2200      for (int i = 0; i < args.length; i++)
2201         args[i] = convertToReadable(args[i]);
2202      return MessageFormat.format(pattern, args);
2203   }
2204
2205   private static Object convertToReadable(Object o) {
2206      if (o == null)
2207         return null;
2208      if (o instanceof ClassMeta)
2209         return ((ClassMeta<?>)o).getReadableName();
2210      if (BeanContext.DEFAULT == null)
2211         return o.toString();
2212      ClassMeta<?> cm = BeanContext.DEFAULT.getClassMetaForObject(o);
2213      if (cm.isMapOrBean() || cm.isCollectionOrArray())
2214         return SimpleJsonSerializer.DEFAULT.toString(o);
2215      if (cm.isClass())
2216         return ((Class<?>)o).getName();
2217      if (cm.isMethod())
2218         return ClassUtils.toString((Method)o);
2219      return o.toString();
2220   }
2221
2222   /**
2223    * Converts a string containing a possible multiplier suffix to an integer.
2224    *
2225    * <p>
2226    * The string can contain any of the following multiplier suffixes:
2227    * <ul>
2228    *    <li><js>"K"</js> - x 1024
2229    *    <li><js>"M"</js> - x 1024*1024
2230    *    <li><js>"G"</js> - x 1024*1024*1024
2231    * </ul>
2232    *
2233    * @param s The string to parse.
2234    * @return The parsed value.
2235    */
2236   public static int parseIntWithSuffix(String s) {
2237      assertFieldNotNull(s, "s");
2238      int m = 1;
2239      if (s.endsWith("G")) {
2240         m = 1024*1024*1024;
2241         s = s.substring(0, s.length()-1).trim();
2242      } else if (s.endsWith("M")) {
2243         m = 1024*1024;
2244         s = s.substring(0, s.length()-1).trim();
2245      } else if (s.endsWith("K")) {
2246         m = 1024;
2247         s = s.substring(0, s.length()-1).trim();
2248      }
2249      return Integer.decode(s) * m;
2250   }
2251
2252   /**
2253    * Converts a string containing a possible multiplier suffix to a long.
2254    *
2255    * <p>
2256    * The string can contain any of the following multiplier suffixes:
2257    * <ul>
2258    *    <li><js>"K"</js> - x 1024
2259    *    <li><js>"M"</js> - x 1024*1024
2260    *    <li><js>"G"</js> - x 1024*1024*1024
2261    * </ul>
2262    *
2263    * @param s The string to parse.
2264    * @return The parsed value.
2265    */
2266   public static long parseLongWithSuffix(String s) {
2267      assertFieldNotNull(s, "s");
2268      int m = 1;
2269      if (s.endsWith("G")) {
2270         m = 1024*1024*1024;
2271         s = s.substring(0, s.length()-1).trim();
2272      } else if (s.endsWith("M")) {
2273         m = 1024*1024;
2274         s = s.substring(0, s.length()-1).trim();
2275      } else if (s.endsWith("K")) {
2276         m = 1024;
2277         s = s.substring(0, s.length()-1).trim();
2278      }
2279      return Long.decode(s) * m;
2280   }
2281
2282   /**
2283    * Same as {@link String#contains(CharSequence)} except returns <jk>null</jk> if the value is null.
2284    *
2285    * @param value The string to check.
2286    * @param substring The value to check for.
2287    * @return <jk>true</jk> if the value contains the specified substring.
2288    */
2289   public static boolean contains(String value, CharSequence substring) {
2290      return value == null ? false : value.contains(substring);
2291   }
2292
2293   /**
2294    * Returns <jk>true</jk> if the specified string appears to be an JSON array.
2295    *
2296    * @param o The object to test.
2297    * @param ignoreWhitespaceAndComments If <jk>true</jk>, leading and trailing whitespace and comments will be ignored.
2298    * @return <jk>true</jk> if the specified string appears to be a JSON array.
2299    */
2300   public static boolean isObjectList(Object o, boolean ignoreWhitespaceAndComments) {
2301      if (o instanceof CharSequence) {
2302         String s = o.toString();
2303         if (! ignoreWhitespaceAndComments)
2304            return (s.startsWith("[") && s.endsWith("]"));
2305         if (firstRealCharacter(s) != '[')
2306            return false;
2307         int i = s.lastIndexOf(']');
2308         if (i == -1)
2309            return false;
2310         s = s.substring(i+1);
2311         if (firstRealCharacter(s) != -1)
2312            return false;
2313         return true;
2314      }
2315      return false;
2316   }
2317
2318   /**
2319    * Parses a string that can consist of either a JSON array or comma-delimited list.
2320    *
2321    * <p>
2322    * The type of string is auto-detected.
2323    *
2324    * @param s The string to parse.
2325    * @return The parsed string.
2326    * @throws ParseException
2327    */
2328   public static ObjectList parseListOrCdl(String s) throws ParseException {
2329      if (isEmpty(s))
2330         return null;
2331      if (! isObjectList(s, true))
2332         return new ObjectList(Arrays.asList(StringUtils.split(s.trim(), ',')));
2333      return new ObjectList(s);
2334   }
2335
2336   /**
2337    * Returns <jk>true</jk> if the specified string is valid JSON.
2338    *
2339    * <p>
2340    * Leading and trailing spaces are ignored.
2341    * <br>Leading and trailing comments are not allowed.
2342    *
2343    * @param s The string to test.
2344    * @return <jk>true</jk> if the specified string is valid JSON.
2345    */
2346   public static boolean isJson(String s) {
2347      if (s == null)
2348         return false;
2349      char c1 = StringUtils.firstNonWhitespaceChar(s), c2 = StringUtils.lastNonWhitespaceChar(s);
2350      if (c1 == '{' && c2 == '}' || c1 == '[' && c2 == ']' || c1 == '\'' && c2 == '\'')
2351         return true;
2352      if (StringUtils.isOneOf(s, "true","false","null"))
2353         return true;
2354      if (StringUtils.isNumeric(s))
2355         return true;
2356      return false;
2357   }
2358
2359   /**
2360    * Returns <jk>true</jk> if the specified string appears to be a JSON object.
2361    *
2362    * @param o The object to test.
2363    * @param ignoreWhitespaceAndComments If <jk>true</jk>, leading and trailing whitespace and comments will be ignored.
2364    * @return <jk>true</jk> if the specified string appears to be a JSON object.
2365    */
2366   public static boolean isObjectMap(Object o, boolean ignoreWhitespaceAndComments) {
2367      if (o instanceof CharSequence) {
2368         String s = o.toString();
2369         if (! ignoreWhitespaceAndComments)
2370            return (s.startsWith("{") && s.endsWith("}"));
2371         if (firstRealCharacter(s) != '{')
2372            return false;
2373         int i = s.lastIndexOf('}');
2374         if (i == -1)
2375            return false;
2376         s = s.substring(i+1);
2377         if (firstRealCharacter(s) != -1)
2378            return false;
2379         return true;
2380      }
2381      return false;
2382   }
2383
2384   private static int firstRealCharacter(String s) {
2385      try (StringReader r = new StringReader(s)) {
2386         int c = 0;
2387         while ((c = r.read()) != -1) {
2388            if (! Character.isWhitespace(c)) {
2389               if (c == '/') {
2390                  skipComments(r);
2391               } else {
2392                  return c;
2393               }
2394            }
2395         }
2396         return -1;
2397      } catch (Exception e) {
2398         throw new RuntimeException(e);
2399      }
2400   }
2401   private static void skipComments(StringReader r) throws IOException {
2402      int c = r.read();
2403      //  "/* */" style comments
2404      if (c == '*') {
2405         while (c != -1)
2406            if ((c = r.read()) == '*')
2407               if ((c = r.read()) == '/')
2408                  return;
2409      //  "//" style comments
2410      } else if (c == '/') {
2411         while (c != -1) {
2412            c = r.read();
2413            if (c == -1 || c == '\n')
2414               return;
2415         }
2416      }
2417   }
2418
2419   /**
2420    * Takes in a string, splits it by lines, and then prepends each line with line numbers.
2421    *
2422    * @param s The string.
2423    * @return The string with line numbers added.
2424    */
2425   public static String getNumberedLines(String s) {
2426      return getNumberedLines(s, 1, Integer.MAX_VALUE);
2427   }
2428
2429   /**
2430    * Same as {@link #getNumberedLines(String)} except only returns the specified lines.
2431    *
2432    * <p>
2433    * Out-of-bounds values are allowed and fixed.
2434    *
2435    * @param s The string.
2436    * @param start The starting line (1-indexed).
2437    * @param end The ending line (1-indexed).
2438    * @return The string with line numbers added.
2439    */
2440   public static String getNumberedLines(String s, int start, int end) {
2441      if (s == null)
2442         return null;
2443      String[] lines = s.split("[\r\n]+");
2444      final int digits = String.valueOf(lines.length).length();
2445      if (start < 1)
2446         start = 1;
2447      if (end > lines.length)
2448         end = lines.length;
2449      StringBuilder sb = new StringBuilder();
2450      for (String l :  Arrays.asList(lines).subList(start-1, end))
2451         sb.append(String.format("%0"+digits+"d", start++)).append(": ").append(l).append("\n");
2452      return sb.toString();
2453   }
2454
2455   /**
2456    * Compares two strings, but gracefully handles <jk>nulls</jk>.
2457    *
2458    * @param s1 The first string.
2459    * @param s2 The second string.
2460    * @return The same as {@link String#compareTo(String)}.
2461    */
2462   public static int compare(String s1, String s2) {
2463      if (s1 == null && s2 == null)
2464         return 0;
2465      if (s1 == null)
2466         return Integer.MIN_VALUE;
2467      if (s2 == null)
2468         return Integer.MAX_VALUE;
2469      return s1.compareTo(s2);
2470   }
2471
2472   /**
2473    * Returns the first character in the specified string.
2474    *
2475    * @param s The string to check.
2476    * @return The first character in the string, or <code>0</code> if the string is <jk>null</jk> or empty.
2477    */
2478   public static char firstChar(String s) {
2479      if (s == null || s.length() == 0)
2480         return 0;
2481      return s.charAt(0);
2482   }
2483
2484   /**
2485    * Converts a string containing <js>"*"</js> meta characters with a regular expression pattern.
2486    *
2487    * @param s The string to create a pattern from.
2488    * @return A regular expression pattern.
2489    */
2490   public static Pattern getMatchPattern(String s) {
2491      if (s == null)
2492         return null;
2493      StringBuilder sb = new StringBuilder();
2494      sb.append("\\Q");
2495      for (int i = 0; i < s.length(); i++) {
2496         char c = s.charAt(i);
2497         if (c == '*')
2498            sb.append("\\E").append(".*").append("\\Q");
2499         else
2500            sb.append(c);
2501      }
2502      sb.append("\\E");
2503      return Pattern.compile(sb.toString());
2504   }
2505
2506   /**
2507    * Null-safe {@link String#toLowerCase()}.
2508    *
2509    * @param s The string to convert to lower case.
2510    * @return The string converted to lower case, or <jk>null</jk> if the string was null.
2511    */
2512   public static String toLowerCase(String s) {
2513      return s == null ? null : s.toLowerCase();
2514   }
2515}