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