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