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