1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 or later
5    * as published by the Free Software Foundation. This program is distributed
6    * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
7    * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *      http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * © CrossWire Bible Society, 2005 - 2016
18   *
19   */
20  package org.crosswire.common.util;
21  
22  import java.io.BufferedReader;
23  import java.io.IOException;
24  import java.io.Reader;
25  
26  /**
27   * A generic class of String utilities.
28   * 
29   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
30   * @author Joe Walker
31   */
32  public final class StringUtil {
33      /**
34       * Prevent instantiation
35       */
36      private StringUtil() {
37      }
38  
39      /**
40       * The newline character
41       */
42      public static final String NEWLINE = System.getProperty("line.separator", "\r\n");
43  
44      /**
45       * Compare two strings for equality such that both can be null.
46       * 
47       * @param string1 the first string
48       * @param string2 the second string
49       * @return true when both are null or both have the same string value
50       */
51      public static boolean equals(String string1, String string2) {
52          if (string1 == null) {
53              return string2 == null;
54          }
55          return string1.equals(string2);
56      }
57  
58      /**
59       * This method reads an InputStream <b>In its entirety</b>, and passes The
60       * text back as a string. If you are reading from a source that can block
61       * then be prepared for a long wait for this to return.
62       * 
63       * @param in
64       *            The Stream to read from.
65       * @return A string containing all the text from the Stream.
66       * @throws IOException when an I/O error occurred
67       */
68      public static String read(Reader in) throws IOException {
69          StringBuilder retcode = new StringBuilder();
70          // Quiet Android from complaining about using the default BufferReader buffer size.
71          // The actual buffer size is undocumented. So this is a good idea any way.
72          BufferedReader din = new BufferedReader(in, 8192);
73  
74          while (true) {
75              String line = din.readLine();
76  
77              if (line == null) {
78                  break;
79              }
80  
81              retcode.append(line);
82              retcode.append(NEWLINE);
83          }
84  
85          return retcode.toString();
86      }
87  
88      /**
89       * This function creates a readable title from a variable name type input.
90       * For example calling: StringUtil.createTitle("one_two") = "One Two"
91       * StringUtil.createTitle("oneTwo") = "One Two"
92       * 
93       * @param variable the name of a variable
94       * @return the generated title
95       */
96      public static String createTitle(String variable) {
97          StringBuilder retcode = new StringBuilder();
98          boolean lastlower = false;
99          boolean lastspace = true;
100 
101         for (int i = 0; i < variable.length(); i++) {
102             char c = variable.charAt(i);
103 
104             if (lastlower && Character.isUpperCase(c) && !lastspace) {
105                 retcode.append(' ');
106             }
107 
108             lastlower = !Character.isUpperCase(c);
109 
110             if (lastspace) {
111                 c = Character.toUpperCase(c);
112             }
113 
114             if (c == '_') {
115                 c = ' ';
116             }
117 
118             if (!lastspace || c != ' ') {
119                 retcode.append(c);
120             }
121 
122             lastspace = c == ' ';
123         }
124 
125         return retcode.toString();
126     }
127 
128     /**
129      * For example getInitials("Java DataBase Connectivity") = "JDC" and
130      * getInitials("Church of England") = "CoE".
131      * 
132      * @param sentence
133      *            The phrase from which to get the initial letters.
134      * @return The initial letters in the given words.
135      */
136     public static String getInitials(String sentence) {
137         String[] words = StringUtil.split(sentence);
138 
139         StringBuilder retcode = new StringBuilder();
140         for (int i = 0; i < words.length; i++) {
141             String word = words[i];
142 
143             char first = 0;
144             for (int j = 0; first == 0 && j < word.length(); j++) {
145                 char c = word.charAt(j);
146                 if (Character.isLetter(c)) {
147                     first = c;
148                 }
149             }
150 
151             if (first != 0) {
152                 retcode.append(first);
153             }
154         }
155 
156         return retcode.toString();
157     }
158 
159     /**
160      * Splits the provided text into an array, using whitespace as the
161      * separator. Whitespace is defined by {@link Character#isWhitespace(char)}.
162      * 
163      * <p>
164      * The separator is not included in the returned String array. Adjacent
165      * separators are treated as one separator.
166      * </p>
167      * 
168      * <pre>
169      * StringUtil.split(null)       = []
170      * StringUtil.split("")         = []
171      * StringUtil.split("abc def")  = ["abc", "def"]
172      * StringUtil.split("abc  def") = ["abc", "def"]
173      * StringUtil.split(" abc ")    = ["abc"]
174      * </pre>
175      * 
176      * @param str
177      *            the String to parse, may be null
178      * @return an array of parsed Strings, <code>null</code> if null String
179      *         input
180      */
181     public static String[] split(String str) {
182         if (str == null) {
183             return EMPTY_STRING_ARRAY.clone();
184         }
185 
186         int len = str.length();
187         if (len == 0) {
188             return EMPTY_STRING_ARRAY.clone();
189         }
190 
191         char[] cstr = str.toCharArray();
192 
193         int count = 0;
194         int start = 0;
195         int i = 0;
196         while ((i = indexOfWhitespace(cstr, start)) != -1) {
197             // Don't count separator at beginning,
198             // after another or at the end
199             if (i > start) {
200                 ++count;
201             }
202             start = i + 1;
203         }
204 
205         // If it didn't end with a separator then add in the last part
206         if (start < len) {
207             ++count;
208         }
209 
210         // Create the array
211         String[] list = new String[count];
212 
213         // If there were no separators
214         // then we have one big part
215         if (start == 0) {
216             list[0] = str;
217             return list;
218         }
219 
220         start = 0;
221         i = 0;
222         int x = 0;
223         while ((i = indexOfWhitespace(cstr, start)) != -1) {
224             // Don't count separator at beginning,
225             // after another or at the end
226             if (i > start) {
227                 list[x++] = str.substring(start, i);
228             }
229             start = i + 1;
230         }
231         // If it didn't end with a separator then add in the last part
232         if (start < len) {
233             list[x++] = str.substring(start);
234         }
235 
236         return list;
237     }
238 
239     /**
240      * Splits the provided text into an array, using whitespace as the
241      * separator. Whitespace is defined by {@link Character#isWhitespace(char)}.
242      * 
243      * <p>
244      * The separator is not included in the returned String array. Adjacent
245      * separators are treated as one separator.
246      * </p>
247      * 
248      * <pre>
249      * StringUtil.split(null)       = []
250      * StringUtil.split("")         = []
251      * StringUtil.split("abc def")  = ["abc", "def"]
252      * StringUtil.split("abc  def") = ["abc", "def"]
253      * StringUtil.split(" abc ")    = ["abc"]
254      * </pre>
255      * 
256      * @param str
257      *            the String to parse, may be null
258      * @param max the maximum number of elements to return
259      * @return an array of parsed Strings, <code>null</code> if null String
260      *         input
261      */
262     public static String[] split(String str, int max) {
263         if (str == null) {
264             return EMPTY_STRING_ARRAY.clone();
265         }
266 
267         int len = str.length();
268         if (len == 0) {
269             return EMPTY_STRING_ARRAY.clone();
270         }
271 
272         char[] cstr = str.toCharArray();
273 
274         int count = 0;
275         int start = 0;
276         int i = 0;
277         while ((i = indexOfWhitespace(cstr, start)) != -1) {
278             // Don't count separator at beginning,
279             // after another or at the end
280             if (i > start) {
281                 ++count;
282             }
283             start = i + 1;
284         }
285 
286         // If it didn't end with a separator then add in the last part
287         if (start < len) {
288             ++count;
289         }
290 
291         // If there were no separators
292         // then we have one big part
293         if (start == 0) {
294             String[] list = new String[count];
295             list[0] = str;
296             return list;
297         }
298 
299         // Limit the result
300         if (max > 0 && count > max) {
301             count = max;
302         }
303 
304         // Create the array
305         String[] list = new String[count];
306 
307         start = 0;
308         i = 0;
309         int x = 0;
310         while ((i = indexOfWhitespace(cstr, start)) != -1) {
311             // Don't count separator at beginning,
312             // after another or at the end
313             if (i > start && x < count) {
314                 list[x++] = str.substring(start, i);
315             }
316             start = i + 1;
317         }
318         // If it didn't end with a separator then add in the last part
319         if (start < len && x < count) {
320             list[x++] = str.substring(start);
321         }
322 
323         return list;
324     }
325 
326     /**
327      * Splits the provided text into an array, separator specified. This is an
328      * alternative to using StringTokenizer.
329      * 
330      * <p>
331      * The separator is not included in the returned String array. Adjacent
332      * separators are treated as one separator.
333      * </p>
334      * 
335      * <p>
336      * A <code>null</code> input String returns <code>null</code>.
337      * </p>
338      * 
339      * <pre>
340      * StringUtil.split(null, *)         = []
341      * StringUtil.split("", *)           = []
342      * StringUtil.split("a.b.c", '.')    = ["a", "b", "c"]
343      * StringUtil.split("a..b.c", '.')   = ["a", "b", "c"]
344      * StringUtil.split("a:b:c", '.')    = ["a:b:c"]
345      * StringUtil.split("a b c", ' ')    = ["a", "b", "c"]
346      * </pre>
347      * 
348      * @param str
349      *            the String to parse, may be null
350      * @param separatorChar
351      *            the character used as the delimiter
352      * @return an array of parsed Strings
353      */
354     public static String[] split(String str, char separatorChar) {
355         if (str == null) {
356             return EMPTY_STRING_ARRAY.clone();
357         }
358 
359         int len = str.length();
360         if (len == 0) {
361             return EMPTY_STRING_ARRAY.clone();
362         }
363 
364         // Determine the size of the array
365         int count = 0;
366         int start = 0;
367         int i = 0;
368         while ((i = str.indexOf(separatorChar, start)) != -1) {
369             // Don't count separator at beginning,
370             // after another or at the end
371             if (i > start && i < len) {
372                 ++count;
373             }
374             start = i + 1;
375         }
376         // If it didn't end with a separator then add in the last part
377         if (start < len) {
378             ++count;
379         }
380 
381         // Create the array
382         String[] list = new String[count];
383 
384         // If there were no separators
385         // then we have one big part
386         if (count == 1) {
387             list[0] = str;
388             return list;
389         }
390 
391         start = 0;
392         i = 0;
393         int x = 0;
394         while ((i = str.indexOf(separatorChar, start)) != -1) {
395             // Don't count separator at beginning,
396             // after another or at the end
397             if (i > start) {
398                 list[x++] = str.substring(start, i);
399             }
400             start = i + 1;
401         }
402         // If it didn't end with a separator then add in the last part
403         if (start < len) {
404             list[x++] = str.substring(start, len);
405         }
406 
407         return list;
408     }
409 
410     /**
411      * <p>
412      * Splits the provided text into an array, separator specified. This is an
413      * alternative to using StringTokenizer.
414      * </p>
415      * 
416      * <p>
417      * The separator is not included in the returned String array. Adjacent
418      * separators are treated as one separator.
419      * </p>
420      * 
421      * <p>
422      * A <code>null</code> input String returns <code>null</code>.
423      * </p>
424      * 
425      * <pre>
426      * StringUtil.split(null, *, 2)         = []
427      * StringUtil.split("", *, 2)           = []
428      * StringUtil.split("a.b.c", '.', 2)    = ["a", "b"]
429      * StringUtil.split("a..b.c", '.', 2)   = ["a", "b"]
430      * StringUtil.split("a:b:c", '.', 2)    = ["a:b:c"]
431      * StringUtil.split("a b c", ' ', 2)    = ["a", "b"]
432      * </pre>
433      * 
434      * @param str
435      *            the String to parse, may be null
436      * @param separatorChar
437      *            the character used as the delimiter
438      * @param max
439      *            the maximum number of elements to include in the array.
440      *            A zero or negative value implies no limit
441      * @return an array of parsed Strings
442      */
443     public static String[] split(String str, char separatorChar, int max) {
444         if (str == null) {
445             return EMPTY_STRING_ARRAY.clone();
446         }
447 
448         int len = str.length();
449         if (len == 0) {
450             return EMPTY_STRING_ARRAY.clone();
451         }
452 
453         // Determine the size of the array
454         int count = 0;
455         int start = 0;
456         int i = 0;
457         while ((i = str.indexOf(separatorChar, start)) != -1) {
458             // Don't count separator at beginning,
459             // after another or at the end
460             if (i > start) {
461                 ++count;
462             }
463             start = i + 1;
464         }
465 
466         // If it didn't end with a separator then add in the last part
467         if (start < len) {
468             ++count;
469         }
470 
471         // If there were no separators
472         // then we have one big part
473         if (count == 1) {
474             String[] list = new String[count];
475             list[0] = str;
476             return list;
477         }
478 
479         // Limit the result
480         if (max > 0 && count > max) {
481             count = max;
482         }
483 
484         // Create the array
485         String[] list = new String[count];
486 
487         start = 0;
488         i = 0;
489         int x = 0;
490         while ((i = str.indexOf(separatorChar, start)) != -1) {
491             // Don't count separator at beginning,
492             // after another or at the end
493             if (i > start && x < count) {
494                 list[x++] = str.substring(start, i);
495             }
496             start = i + 1;
497         }
498         // If it didn't end with a separator then add in the last part
499         if (start < len && x < count) {
500             list[x++] = str.substring(start);
501         }
502 
503         return list;
504     }
505 
506     /**
507      * <p>
508      * Splits the provided text into an array, separators specified. This is an
509      * alternative to using StringTokenizer.
510      * </p>
511      * 
512      * <p>
513      * The separator is not included in the returned String array. Adjacent
514      * separators are treated as one separator.
515      * </p>
516      * 
517      * <p>
518      * A <code>null</code> input String returns <code>null</code>. A
519      * <code>null</code> separatorChars splits on whitespace.
520      * </p>
521      * 
522      * <pre>
523      * StringUtil.split(null, *)         = []
524      * StringUtil.split("", *)           = []
525      * StringUtil.split("abc def", null) = ["abc", "def"]
526      * StringUtil.split("abc def", " ")  = ["abc", "def"]
527      * StringUtil.split("abc  def", " ") = ["abc", "def"]
528      * StringUtil.split("ab:cd:ef", ":") = ["ab", "cd", "ef"]
529      * </pre>
530      * 
531      * @param str
532      *            the String to parse, may be null
533      * @param separatorChars
534      *            the characters used as the delimiters, <code>null</code>
535      *            splits on whitespace
536      * @return an array of parsed Strings, <code>null</code> if null String
537      *         input
538      */
539     public static String[] split(String str, String separatorChars) {
540         return split(str, separatorChars, -1);
541     }
542 
543     /**
544      * <p>
545      * Splits the provided text into an array, separators specified. This is an
546      * alternative to using StringTokenizer.
547      * </p>
548      * 
549      * <p>
550      * The separator is not included in the returned String array. Adjacent
551      * separators are treated as one separator.
552      * </p>
553      * 
554      * <p>
555      * A <code>null</code> input String returns <code>null</code>. A
556      * <code>null</code> separatorChars splits on whitespace.
557      * </p>
558      * 
559      * <pre>
560      * StringUtil.split(null, *, *)            = []
561      * StringUtil.split("", *, *)              = []
562      * StringUtil.split("ab de fg", null, 0)   = ["ab", "cd", "ef"]
563      * StringUtil.split("ab   de fg", null, 0) = ["ab", "cd", "ef"]
564      * StringUtil.split("ab:cd:ef", ":", 0)    = ["ab", "cd", "ef"]
565      * StringUtil.split("ab:cd:ef", ":", 2)    = ["ab", "cd:ef"]
566      * </pre>
567      * 
568      * @param str
569      *            the String to parse, may be null
570      * @param separatorStr
571      *            the characters used as the delimiters, <code>null</code>
572      *            splits on whitespace
573      * @param max
574      *            the maximum number of elements to include in the array. A zero
575      *            or negative value implies no limit
576      * @return an array of parsed Strings
577      */
578     public static String[] split(String str, String separatorStr, int max) {
579         // Performance tuned for 2.0 (JDK1.4)
580         // Direct code is quicker than StringTokenizer.
581         // Also, StringTokenizer uses isSpace() not isWhitespace()
582 
583        if (separatorStr == null) {
584             return split(str, max);
585         }
586 
587         if (separatorStr.length() == 1) {
588             return split(str, separatorStr.charAt(0), max);
589         }
590 
591         if (str == null) {
592             return EMPTY_STRING_ARRAY.clone();
593         }
594 
595         int len = str.length();
596         if (len == 0) {
597             return EMPTY_STRING_ARRAY.clone();
598         }
599 
600         char[] cstr = str.toCharArray();
601         char[] separatorChars = separatorStr.toCharArray();
602 
603         int count = 0;
604         int start = 0;
605         int i = 0;
606         while ((i = indexOfAny(cstr, separatorChars, start)) != -1) {
607             // Don't count separator at beginning,
608             // after another or at the end
609             if (i > start) {
610                 ++count;
611             }
612             start = i + 1;
613         }
614 
615         // If it didn't end with a separator then add in the last part
616         if (start < len) {
617             ++count;
618         }
619 
620         // If there were no separators
621         // then we have one big part
622         if (count == 1) {
623             String[] list = new String[count];
624             list[0] = str;
625             return list;
626         }
627 
628         // Limit the result
629         if (max > 0 && count > max) {
630             count = max;
631         }
632 
633         // Create the array
634         String[] list = new String[count];
635 
636         start = 0;
637         i = 0;
638         int x = 0;
639         while ((i = indexOfAny(cstr, separatorChars, start)) != -1) {
640             // Don't count separator at beginning,
641             // after another or at the end
642             if (i > start && x < count) {
643                 list[x++] = str.substring(start, i);
644             }
645             start = i + 1;
646         }
647         // If it didn't end with a separator then add in the last part
648         if (start < len && x < count) {
649             list[x++] = str.substring(start);
650         }
651 
652         return list;
653     }
654 
655     /**
656      * <p>
657      * Splits the provided text into an array, separator specified. This is an
658      * alternative to using StringTokenizer.
659      * </p>
660      * 
661      * <p>
662      * The separator is not included in the returned String array. Adjacent
663      * separators are treated individually.
664      * </p>
665      * 
666      * <p>
667      * A <code>null</code> input String returns <code>null</code>.
668      * </p>
669      * 
670      * <pre>
671      * StringUtil.splitAll(null, *)         = []
672      * StringUtil.splitAll("", *)           = []
673      * StringUtil.splitAll("a.b.c", '.')    = ["a", "b", "c"]
674      * StringUtil.splitAll("a..b.c", '.')   = ["a", "", "b", "c"]
675      * StringUtil.splitAll("a:b:c", '.')    = ["a:b:c"]
676      * </pre>
677      * 
678      * @param str
679      *            the String to parse, may be null
680      * @param separatorChar
681      *            the character used as the delimiter
682      * @return an array of parsed Strings
683      */
684     public static String[] splitAll(String str, char separatorChar) {
685         if (str == null) {
686             return EMPTY_STRING_ARRAY.clone();
687         }
688 
689         int len = str.length();
690         if (len == 0) {
691             return EMPTY_STRING_ARRAY.clone();
692         }
693 
694         // Determine the size of the array
695         int count = 1;
696         int start = 0;
697         int i = 0;
698         while ((i = str.indexOf(separatorChar, start)) != -1) {
699             ++count;
700             start = i + 1;
701         }
702 
703         // Create the array
704         String[] list = new String[count];
705 
706         // If there were no separators
707         // then we have one big part
708         if (count == 1) {
709             list[0] = str;
710             return list;
711         }
712 
713         start = 0;
714         i = 0;
715         for (int x = 0; x < count; x++) {
716             i = str.indexOf(separatorChar, start);
717             if (i != -1) {
718                 list[x] = str.substring(start, i);
719             } else {
720                 list[x] = str.substring(start);
721             }
722             start = i + 1;
723         }
724 
725         return list;
726     }
727 
728     /**
729      * <p>
730      * Splits the provided text into an array, separator specified. This is an
731      * alternative to using StringTokenizer.
732      * </p>
733      * 
734      * <p>
735      * The separator is not included in the returned String array. Adjacent
736      * separators are treated individually.
737      * </p>
738      * 
739      * <p>
740      * A <code>null</code> input String returns <code>null</code>.
741      * </p>
742      * 
743      * <pre>
744      * StringUtil.splitAll(null, *, 2)         = []
745      * StringUtil.splitAll("", *, 2)           = []
746      * StringUtil.splitAll("a.b.c", '.', 2)    = ["a", "b"]
747      * StringUtil.splitAll("a..b.c", '.', 2)   = ["a", ""]
748      * StringUtil.splitAll("a:b:c", '.', 2)    = ["a:b:c"]
749      * StringUtil.splitAll("a b c", ' ', 2)    = ["a", "b"]
750      * </pre>
751      * 
752      * @param str
753      *            the String to parse, may be null
754      * @param separatorChar
755      *            the character used as the delimiter
756      * @param max
757      *            the maximum number of elements to include in the array.
758      *             A zero or negative value implies no limit
759      * @return an array of parsed Strings
760      */
761     public static String[] splitAll(String str, char separatorChar, int max) {
762         if (str == null) {
763             return EMPTY_STRING_ARRAY.clone();
764         }
765 
766         int len = str.length();
767         if (len == 0) {
768             return EMPTY_STRING_ARRAY.clone();
769         }
770 
771         // Determine the size of the array
772         int count = 1;
773         int start = 0;
774         int i = 0;
775         while ((i = str.indexOf(separatorChar, start)) != -1) {
776             ++count;
777             start = i + 1;
778         }
779 
780         // If there were no separators
781         // then we have one big part
782         if (count == 1) {
783             String[] list = new String[count];
784             list[0] = str;
785             return list;
786         }
787 
788         // Limit the result
789         if (max > 0 && count > max) {
790             count = max;
791         }
792 
793         // Create the array
794         String[] list = new String[count];
795 
796         start = 0;
797         i = 0;
798         for (int x = 0; x < count; x++) {
799             i = str.indexOf(separatorChar, start);
800             if (i != -1) {
801                 list[x] = str.substring(start, i);
802             } else {
803                 list[x] = str.substring(start, len);
804             }
805             start = i + 1;
806         }
807 
808         return list;
809     }
810 
811     /**
812      * <p>
813      * Joins the elements of the provided array into a single String containing
814      * the provided list of elements.
815      * </p>
816      * 
817      * <p>
818      * No delimiter is added before or after the list. A <code>null</code>
819      * separator is the same as an empty String (""). Null objects or empty
820      * strings within the array are represented by empty strings.
821      * </p>
822      * 
823      * <pre>
824      * StringUtil.join(null, *)                = null
825      * StringUtil.join([], *)                  = ""
826      * StringUtil.join([null], *)              = ""
827      * StringUtil.join(["a", "b", "c"], "--")  = "a--b--c"
828      * StringUtil.join(["a", "b", "c"], null)  = "abc"
829      * StringUtil.join(["a", "b", "c"], "")    = "abc"
830      * StringUtil.join([null, "", "a"], ',')   = ",,a"
831      * </pre>
832      * 
833      * @param array
834      *            the array of values to join together, may be null
835      * @param aSeparator
836      *            the separator character to use, null treated as ""
837      * @return the joined String, <code>null</code> if null array input
838      */
839     public static String join(Object[] array, String aSeparator) {
840         String separator = aSeparator;
841         if (array == null) {
842             return null;
843         }
844         if (separator == null) {
845             separator = "";
846         }
847         int arraySize = array.length;
848 
849         // ArraySize == 0: Len = 0
850         // ArraySize > 0: Len = NofStrings *(len(firstString) + len(separator))
851         // (Assuming that all Strings are roughly equally long)
852         int bufSize = arraySize == 0 ? 0 : arraySize * ((array[0] == null ? 16 : array[0].toString().length()) + separator.length());
853 
854         StringBuilder buf = new StringBuilder(bufSize);
855 
856         for (int i = 0; i < arraySize; i++) {
857             if (i > 0) {
858                 buf.append(separator);
859             }
860             if (array[i] != null) {
861                 buf.append(array[i]);
862             }
863         }
864         return buf.toString();
865     }
866 
867     /**
868      * Find the first occurrence of a separator in the character buffer beginning
869      * at the given offset.
870      * 
871      * @param str
872      *            the String to parse, may be null
873      * @param separatorChars
874      *            the characters used as the delimiters, <code>null</code>
875      *            splits on whitespace
876      * @param offset
877      *            the index of the first character to consider
878      * @return the index of a separator char in the string or -1
879      */
880     public static int indexOfAny(char[] str, char[] separatorChars, int offset) {
881         int strlen = str.length;
882         int seplen = separatorChars.length;
883         for (int i = offset; i < strlen; i++) {
884             char ch = str[i];
885             for (int j = 0; j < seplen; j++) {
886                 if (separatorChars[j] == ch) {
887                     return i;
888                 }
889             }
890         }
891         return -1;
892     }
893 
894     /**
895      * Find the first occurrence of a whitespace in the character buffer beginning
896      * at the given offset. 
897      * Whitespace is defined by {@link Character#isWhitespace(char)}.
898      * 
899      * @param str
900      *            the String to parse, may be null
901      * @param offset
902      *            the index of the first character to consider
903      * @return the index of a separator char in the string or -1
904      */
905     public static int indexOfWhitespace(char[] str, int offset) {
906         int strlen = str.length;
907         for (int i = offset; i < strlen; i++) {
908             if (Character.isWhitespace(str[i])) {
909                 return i;
910             }
911         }
912         return -1;
913     }
914 
915     /**
916      * An empty immutable <code>String</code> array.
917      */
918     public static final String[] EMPTY_STRING_ARRAY = new String[0];
919 
920 }
921