View Javadoc
1   /*
2    * Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved.
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Lesser General Public
6    * License as published by the Free Software Foundation; either
7    * version 2.1 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU Lesser General Public
15   * License along with this program; if not, write to the Free Software
16   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
17   *
18   * Additionally licensed with:
19   *
20   * Licensed under the Apache License, Version 2.0 (the "License");
21   * you may not use this file except in compliance with the License.
22   * You may obtain a copy of the License at
23   *
24   *      http://www.apache.org/licenses/LICENSE-2.0
25   *
26   * Unless required by applicable law or agreed to in writing, software
27   * distributed under the License is distributed on an "AS IS" BASIS,
28   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29   * See the License for the specific language governing permissions and
30   * limitations under the License.
31   */
32  package org.spf4j.base;
33  
34  import com.google.common.annotations.GwtCompatible;
35  import com.google.common.annotations.GwtIncompatible;
36  import com.google.common.io.CharSource;
37  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
38  import java.io.IOException;
39  import java.io.Reader;
40  import java.io.UncheckedIOException;
41  import static java.lang.Math.min;
42  import java.util.function.IntPredicate;
43  import javax.annotation.Nonnull;
44  import javax.annotation.ParametersAreNonnullByDefault;
45  
46  /**
47   * Special methods to use for character sequences...
48   *
49   * @author zoly
50   */
51  @GwtCompatible
52  @ParametersAreNonnullByDefault
53  public final class CharSequences {
54  
55    private CharSequences() {
56    }
57  
58    /**
59     * function that calculates the number of operations that are needed to transform s1 into s2. operations are: char
60     * add, char delete, char modify See https://en.wikipedia.org/wiki/Levenshtein_distance for more info.
61     *
62     * @param s1
63     * @param s2
64     * @return the number of operations required to transform s1 into s2
65     */
66    @SuppressFBWarnings("CLI_CONSTANT_LIST_INDEX")
67    public static int distance(final CharSequence s1, final CharSequence s2) {
68      int l1 = s1.length();
69      int l2 = s2.length();
70      if (l1 == 0) {
71        return l2;
72      }
73      if (l2  == 0) {
74        return l1;
75      }
76      int[] prev = new int[l2];
77      char c1 = s1.charAt(0);
78      char cs20 = s2.charAt(0);
79      prev[0] = distance(c1, cs20);
80      for (int j = 1; j < l2; j++) {
81        int pd  = prev[j - 1];
82        prev[j] = pd == j ? pd + distance(c1, s2.charAt(j)) : pd + 1;
83      }
84      int[] dist = new int[l2];
85      for (int i = 1; i < l1; i++) {
86        c1 = s1.charAt(i);
87        int pd = prev[0];
88        dist[0] = pd == i ? pd + distance(c1, cs20) : pd + 1;
89        for (int j = 1; j < l2; j++) {
90          dist[j] = min(prev[j - 1] + distance(c1, s2.charAt(j)),
91                  min(prev[j] + 1, dist[j - 1] + 1));
92        }
93        int[] tmp = prev;
94        prev = dist;
95        dist = tmp;
96      }
97      return prev[l2 - 1];
98    }
99  
100 
101   public static int distance(final char c1, final char c2) {
102     return (c1 == c2) ? 0 : 1;
103   }
104 
105   /**
106    * compare s to t.
107    *
108    * @param s
109    * @param t
110    * @return
111    * @deprecated use compare.
112    */
113   @Deprecated
114   public static int compareTo(final CharSequence s, final CharSequence t) {
115     return compare(s, t);
116   }
117 
118   public static int compare(final CharSequence s, final CharSequence t) {
119     return compare(s, 0, s.length(), t, 0, t.length());
120   }
121 
122   public static int compare(final CharSequence s, final int sLength,
123           final CharSequence t, final int tLength) {
124     return compare(s, 0, sLength, t, 0, tLength);
125   }
126 
127   /**
128    * compare 2 CharSequence fragments.
129    *
130    * @param s the charsequence to compare
131    * @param sFrom the index for the first chars to compare.
132    * @param sLength the number of characters to compare.
133    * @param t the charsequence to compare to
134    * @param tFrom the index for the first character to compare to.
135    * @param tLength the number of characters to compare to.
136    * @return
137    */
138   public static int compare(final CharSequence s, final int sFrom, final int sLength,
139           final CharSequence t, final int tFrom, final int tLength) {
140 
141     int lim = min(sLength, tLength);
142     int i = sFrom;
143     int j = tFrom;
144     int sTo = sFrom + lim;
145     while (i < sTo) {
146       char c1 = s.charAt(i);
147       char c2 = t.charAt(j);
148       if (c1 != c2) {
149         return c1 - c2;
150       }
151       i++;
152       j++;
153     }
154     return sLength - tLength;
155   }
156 
157   public static boolean equalsNullables(final CharSequence s, final CharSequence t) {
158     if (s == null) {
159       return null == t;
160     } else if (t == null) {
161       return true;
162     } else {
163       return equals(s, t);
164     }
165   }
166 
167   public static boolean equals(final CharSequence s, final CharSequence t) {
168     final int sl = s.length();
169     final int tl = t.length();
170     if (sl != tl) {
171       return false;
172     } else {
173       for (int i = 0; i < sl; i++) {
174         if (s.charAt(i) != t.charAt(i)) {
175           return false;
176         }
177       }
178       return true;
179     }
180   }
181 
182   public static int hashcode(final CharSequence cs) {
183     if (cs instanceof String) {
184       return ((String) cs).hashCode();
185     }
186     int h = 0;
187     int len = cs.length();
188     if (len > 0) {
189       int off = 0;
190       for (int i = 0; i < len; i++) {
191         h = 31 * h + cs.charAt(off++);
192       }
193     }
194     return h;
195   }
196 
197   public static CharSequence subSequence(final CharSequence seq, final int startIdx, final int endIdx) {
198     if (startIdx == 0 && endIdx == seq.length()) {
199       return seq;
200     } else if (startIdx >= endIdx) {
201       return "";
202     } else {
203       return new SubSequence(seq, endIdx - startIdx, startIdx);
204     }
205   }
206 
207   private static final class SubSequence implements CharSequence {
208 
209     private final CharSequence underlyingSequence;
210     private final int length;
211     private final int startIdx;
212 
213     SubSequence(final CharSequence underlyingSequence, final int length, final int startIdx) {
214       this.underlyingSequence = underlyingSequence;
215       this.length = length;
216       this.startIdx = startIdx;
217     }
218 
219     @Override
220     public int length() {
221       return length;
222     }
223 
224     @Override
225     public char charAt(final int index) {
226       return underlyingSequence.charAt(startIdx + index);
227     }
228 
229     @Override
230     public CharSequence subSequence(final int start, final int end) {
231       return CharSequences.subSequence(underlyingSequence, startIdx + start, startIdx + end);
232     }
233 
234     @Override
235     @SuppressFBWarnings("STT_STRING_PARSING_A_FIELD")
236     public String toString() {
237       if (underlyingSequence instanceof String) {
238         return ((String) underlyingSequence).substring(startIdx, startIdx + length);
239       } else if (underlyingSequence instanceof StringBuilder) {
240         return ((StringBuilder) underlyingSequence).substring(startIdx, startIdx + length);
241       } else {
242         char[] chars = new char[length];
243         int idx = startIdx;
244         for (int i = 0; i < length; i++, idx++) {
245           chars[i] = underlyingSequence.charAt(idx);
246         }
247         return new String(chars);
248       }
249     }
250 
251   }
252 
253   public static boolean startsWith(final CharSequence sequence, final CharSequence prefix, final int toffset) {
254     int to = toffset;
255     int po = 0;
256     int pc = prefix.length();
257     int sl = sequence.length();
258     if ((toffset < 0) || (toffset > sl - pc)) {
259       return false;
260     }
261     while (--pc >= 0) {
262       if (sequence.charAt(to++) != prefix.charAt(po++)) {
263         return false;
264       }
265     }
266     return true;
267   }
268 
269   public static boolean endsWith(final CharSequence qc, final CharSequence with) {
270     int l = qc.length();
271     int start = l - with.length();
272     if (start >= 0) {
273       for (int i = start, j = 0; i < l; i++, j++) {
274         if (qc.charAt(i) != with.charAt(j)) {
275           return false;
276         }
277       }
278       return true;
279     } else {
280       return false;
281     }
282   }
283 
284   public static Appendable lineNumbered(final int startLineNr, final Appendable appendable)
285           throws IOException {
286     return lineNumbered(startLineNr, appendable, IntAppender.CommentNumberAppender.INSTANCE);
287   }
288 
289   public static Appendable lineNumbered(final int startLineNr, final Appendable appendable, final IntAppender ia)
290           throws IOException {
291     ia.append(startLineNr, appendable);
292     return new Appendable() {
293       private int lineNr = startLineNr + 1;
294 
295       @Override
296       public Appendable append(final CharSequence csq) throws IOException {
297         return append(csq, 0, csq.length());
298       }
299 
300       @Override
301       public Appendable append(final CharSequence csq, final int start, final int end) throws IOException {
302         int lastIdx = start;
303         for (int i = start; i < end; i++) {
304           if (csq.charAt(i) == '\n') {
305             int next = i + 1;
306             appendable.append(csq, lastIdx, next);
307             ia.append(lineNr++, appendable);
308             lastIdx = next;
309           }
310         }
311         if (lastIdx < end) {
312           appendable.append(csq, lastIdx, end);
313         }
314         return this;
315       }
316 
317       @Override
318       public Appendable append(final char c) throws IOException {
319         appendable.append(c);
320         if (c == '\n') {
321           ia.append(lineNr++, appendable);
322         }
323         return this;
324       }
325     };
326   }
327 
328   public static CharSequence toLineNumbered(final int startLineNr, final CharSequence source) {
329     return toLineNumbered(startLineNr, source, IntAppender.CommentNumberAppender.INSTANCE);
330   }
331 
332   public static CharSequence toLineNumbered(final int startLineNr, final CharSequence source, final IntAppender ia) {
333     int length = source.length();
334     StringBuilder destination = new StringBuilder(length + 6 * length / 80);
335     try {
336       lineNumbered(startLineNr, destination, ia).append(source);
337     } catch (IOException ex) {
338       throw new UncheckedIOException(ex);
339     }
340     return destination;
341   }
342 
343   /**
344    * A more flexible version of Integer.parseInt.
345    *
346    * @see java.lang.Integer.parseInt
347    */
348   public static int parseInt(@Nonnull final CharSequence s) {
349     return parseInt(s, 10);
350   }
351 
352   /**
353    * A more flexible version of Integer.parseInt.
354    *
355    * @see java.lang.Integer.parseInt
356    */
357   public static int parseInt(@Nonnull final CharSequence cs, final int radix) {
358 
359     if (radix < Character.MIN_RADIX) {
360       throw new NumberFormatException("radix " + radix
361               + " less than Character.MIN_RADIX");
362     }
363 
364     if (radix > Character.MAX_RADIX) {
365       throw new NumberFormatException("radix " + radix
366               + " greater than Character.MAX_RADIX");
367     }
368 
369     int result = 0;
370     boolean negative = false;
371     int len = cs.length();
372 
373     if (len > 0) {
374       int i = 0;
375       int limit = -Integer.MAX_VALUE;
376       int multmin;
377       int digit;
378       char firstChar = cs.charAt(0);
379       if (firstChar < '0') { // Possible leading "+" or "-"
380         if (firstChar == '-') {
381           negative = true;
382           limit = Integer.MIN_VALUE;
383         } else if (firstChar != '+') {
384           throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
385         }
386 
387         if (len == 1) { // Cannot have lone "+" or "-"
388           throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
389         }
390         i++;
391       }
392       multmin = limit / radix;
393       while (i < len) {
394         // Accumulating negatively avoids surprises near MAX_VALUE
395         digit = Character.digit(cs.charAt(i++), radix);
396         if (digit < 0) {
397           throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
398         }
399         if (result < multmin) {
400           throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
401         }
402         result *= radix;
403         if (result < limit + digit) {
404           throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
405         }
406         result -= digit;
407       }
408     } else {
409       throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
410     }
411     return negative ? result : -result;
412   }
413 
414 
415   /**
416    * will parse a unsigned integer from a char sequence from idxFrom.
417    * @param cs
418    * @param radix
419    * @param idxFrom
420    * @return
421    */
422   @SuppressWarnings("checkstyle:InnerAssignment")
423   public static int parseUnsignedInt(@Nonnull final CharSequence cs, final int radix, final int idxFrom) {
424     return parseUnsignedInt(cs, radix, idxFrom, cs.length());
425   }
426 
427 
428   /**
429    * will parse a unsigned integer from a char sequence from idxFrom.
430    * @param cs
431    * @param radix
432    * @param idxFrom
433    * @param idxTo
434    * @return
435    */
436   @SuppressWarnings("checkstyle:InnerAssignment")
437   public static int parseUnsignedInt(@Nonnull final CharSequence cs, final int radix,
438           final int idxFrom, final int idxTo) {
439     if (radix < Character.MIN_RADIX) {
440       throw new NumberFormatException("radix " + radix
441               + " less than Character.MIN_RADIX");
442     }
443     if (radix > Character.MAX_RADIX) {
444       throw new NumberFormatException("radix " + radix
445               + " greater than Character.MAX_RADIX");
446     }
447 
448     int result = 0;
449     int i = idxFrom;
450     int limit = -Integer.MAX_VALUE;
451     int multmin = limit / radix;
452     int digit;
453     while (i < idxTo && (digit = Character.digit(cs.charAt(i), radix)) >= 0) {
454       if (result < multmin) {
455         throw new NumberFormatException("For input char sequence: \"" + cs + "\" at " + i);
456       }
457       result *= radix;
458       if (result < limit + digit) {
459         throw new NumberFormatException("For input char sequence: \"" + cs + "\" at " + i);
460       }
461       result -= digit;
462       i++;
463     }
464     if (i == idxFrom) {
465       throw new NumberFormatException("No number in \"" + cs + "\" at " + idxFrom);
466     }
467     return -result;
468   }
469 
470 
471  @SuppressWarnings("checkstyle:InnerAssignment")
472   public static long parseUnsignedLong(@Nonnull final CharSequence cs, final int radix, final int idxFrom) {
473     return parseUnsignedLong(cs, radix, idxFrom, cs.length(), false);
474   }
475 
476   @SuppressWarnings("checkstyle:InnerAssignment")
477   public static long parseUnsignedLong(@Nonnull final CharSequence cs, final int radix,
478           final int idxFrom, final int idxTo, final boolean strict) {
479     if (radix < Character.MIN_RADIX) {
480       throw new NumberFormatException("radix " + radix
481               + " less than Character.MIN_RADIX");
482     }
483     if (radix > Character.MAX_RADIX) {
484       throw new NumberFormatException("radix " + radix
485               + " greater than Character.MAX_RADIX");
486     }
487 
488     long result = 0;
489     int i = idxFrom;
490     long limit = -Long.MAX_VALUE;
491     long multmin = limit / radix;
492     int digit;
493     while (i < idxTo && (digit = Character.digit(cs.charAt(i), radix)) >= 0) {
494       if (result < multmin) {
495         throw new NumberFormatException("For input char sequence: \"" + cs + "\" at " + i);
496       }
497       result *= radix;
498       if (result < limit + digit) {
499         throw new NumberFormatException("For input char sequence: \"" + cs + "\" at " + i);
500       }
501       result -= digit;
502       i++;
503     }
504     if (i == idxFrom) {
505       throw new NumberFormatException("No number in " + cs + " at " + idxFrom);
506     }
507     if (strict && i < idxTo) {
508       throw new NumberFormatException("No valid number in " + cs + " at " + idxFrom + " to " + idxTo);
509     }
510     return -result;
511   }
512 
513   /**
514    * A more flexible version of Long.parseLong.
515    *
516    * @see java.lang.Long.parseLong
517    */
518   public static long parseLong(@Nonnull final CharSequence cs) {
519     return parseLong(cs, 10);
520   }
521 
522   /**
523    * A more flexible version of Long.parseLong.
524    *
525    * @see java.lang.Long.parseLong
526    */
527   public static long parseLong(@Nonnull final CharSequence cs, final int radix) {
528 
529     if (radix < Character.MIN_RADIX) {
530       throw new NumberFormatException("radix " + radix
531               + " less than Character.MIN_RADIX");
532     }
533     if (radix > Character.MAX_RADIX) {
534       throw new NumberFormatException("radix " + radix
535               + " greater than Character.MAX_RADIX");
536     }
537 
538     long result = 0;
539     boolean negative = false;
540     int len = cs.length();
541 
542     if (len > 0) {
543       int i = 0;
544       long limit = -Long.MAX_VALUE;
545       long multmin;
546       int digit;
547       char firstChar = cs.charAt(0);
548       if (firstChar < '0') { // Possible leading "+" or "-"
549         if (firstChar == '-') {
550           negative = true;
551           limit = Long.MIN_VALUE;
552         } else if (firstChar != '+') {
553           throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
554         }
555 
556         if (len == 1) { // Cannot have lone "+" or "-"
557           throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
558         }
559         i++;
560       }
561       multmin = limit / radix;
562       while (i < len) {
563         // Accumulating negatively avoids surprises near MAX_VALUE
564         digit = Character.digit(cs.charAt(i++), radix);
565         if (digit < 0) {
566           throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
567         }
568         if (result < multmin) {
569           throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
570         }
571         result *= radix;
572         if (result < limit + digit) {
573           throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
574         }
575         result -= digit;
576       }
577     } else {
578       throw new NumberFormatException("For input char sequence: \"" + cs + '\"');
579     }
580     return negative ? result : -result;
581   }
582 
583   public static boolean containsAnyChar(final CharSequence string, final char... chars) {
584     return containsAnyChar(string, 0, string.length(), chars);
585   }
586 
587   public static boolean containsAnyChar(final CharSequence string,
588           final int start, final int end,
589           final char... chars) {
590     for (int i = start; i < end; i++) {
591       char c = string.charAt(i);
592       if (Arrays.search(chars, c) >= 0) {
593         return true;
594       }
595     }
596     return false;
597   }
598 
599   public static boolean isValidJavaId(final CharSequence name) {
600     int length = name.length();
601     if (length == 0) {
602       return false;
603     }
604     char first = name.charAt(0);
605     if (Character.isLetter(first) || first == '_') {
606       for (int i = 1; i < length; i++) {
607         char c = name.charAt(i);
608         if (c != '_' && !Character.isLetterOrDigit(c)) {
609           return false;
610         }
611       }
612       return true;
613     } else {
614       return false;
615     }
616   }
617 
618 
619   public static boolean isValidFileName(@Nonnull final CharSequence fileName) {
620     return !containsAnyChar(fileName, '/', '\\');
621   }
622 
623   public static <T extends CharSequence> T validatedFileName(@Nonnull final T fileName) {
624     if (!isValidFileName(fileName)) {
625       throw new IllegalArgumentException("Invalid file name: " + fileName);
626     }
627     return fileName;
628   }
629 
630   /**
631    * Equivalent to String.regionMatches.
632    */
633   public static boolean regionMatches(final CharSequence t, final int toffset,
634           final CharSequence other, final int ooffset, final int plen) {
635     int to = toffset;
636     int po = ooffset;
637     // Note: toffset, ooffset, or len might be near -1>>>1.
638     if ((ooffset < 0) || (toffset < 0) || (toffset > (long) t.length() - plen)
639             || (ooffset > (long) other.length() - plen)) {
640       return false;
641     }
642     int len = plen;
643     while (len-- > 0) {
644       if (t.charAt(to++) != other.charAt(po++)) {
645         return false;
646       }
647     }
648     return true;
649   }
650 
651   /**
652    * Equivalent/based on to String.regionMatches.
653    */
654   public static boolean regionMatchesIgnoreCase(final CharSequence ta, final int toffset,
655           final CharSequence pa, final int ooffset, final int plen) {
656     int to = toffset;
657     int po = ooffset;
658     // Note: toffset, ooffset, or len might be near -1>>>1.
659     if ((ooffset < 0) || (toffset < 0)
660             || (toffset > (long) ta.length() - plen)
661             || (ooffset > (long) pa.length() - plen)) {
662       return false;
663     }
664     int len = plen;
665     while (len-- > 0) {
666       char c1 = ta.charAt(to++);
667       char c2 = pa.charAt(po++);
668       if (c1 == c2) {
669         continue;
670       }
671       // If characters don't match but case may be ignored,
672       // try converting both characters to uppercase.
673       // If the results match, then the comparison scan should
674       // continue.
675       char u1 = Character.toUpperCase(c1);
676       char u2 = Character.toUpperCase(c2);
677       if (u1 == u2) {
678         continue;
679       }
680       // Unfortunately, conversion to uppercase does not work properly
681       // for the Georgian alphabet, which has strange rules about case
682       // conversion.  So we need to make one last check before
683       // exiting.
684       if (Character.toLowerCase(u1) != Character.toLowerCase(u2)) {
685         return false;
686       }
687     }
688     return true;
689   }
690 
691   /**
692    * regular wildcard matcher. * matches any number of consecutive characters. ? matches any single character.
693    *
694    * @param wildcard
695    * @param cs2Match
696    * @return
697    */
698   public static boolean match(final CharSequence wildcard, final CharSequence cs2Match) {
699     int i = 0;
700     int j = 0;
701     final int length = wildcard.length();
702     for (; i < length; i++, j++) {
703       final char some2 = wildcard.charAt(i);
704       if (some2 != cs2Match.charAt(j)) {
705         if (some2 == '*') {
706           i++;
707           if (i == length) {
708             return true;
709           }
710           final char some = wildcard.charAt(i);
711           while (some != cs2Match.charAt(j)) {
712             ++j;
713           }
714           j--;
715         } else if (some2 != '?') {
716           return false;
717         }
718       }
719     }
720     return j == cs2Match.length();
721   }
722 
723   /**
724    * Transform a wildcard expression 2 a java regular expression. * matches any number of consecutive characters. ?
725    * matches any single character.
726    *
727    * @param wildcard
728    * @return
729    */
730   public CharSequence getJavaRegexpStr(final CharSequence wildcard) {
731     final int length = wildcard.length();
732     final StringBuilder buff = new StringBuilder(length + 4);
733     for (int i = 0; i < length; i++) {
734       final char c = wildcard.charAt(i);
735       switch (c) {
736         case '*':
737           buff.append(".*");
738           break;
739         case '?':
740           buff.append('.');
741           break;
742         case '[':
743         case ']':
744         case '(':
745         case ')':
746         case '{':
747         case '}':
748         case '.':
749           buff.append('\\').append(c);
750           break;
751         default:
752           buff.append(c);
753       }
754     }
755     return buff;
756   }
757 
758   public static int indexOf(final CharSequence cs, final int from, final int to, final char c) {
759     for (int i = from; i < to; i++) {
760       if (c == cs.charAt(i)) {
761         return i;
762       }
763     }
764     return -1;
765   }
766 
767   public static int indexOf(final CharSequence cs, final int from, final int to, final IntPredicate cp) {
768     for (int i = from; i < to; i++) {
769       if (cp.test(cs.charAt(i))) {
770         return i;
771       }
772     }
773     return -1;
774   }
775 
776   public static int indexOf(final CharSequence cs, final int from, final int to, final char... chars) {
777     for (int i = from; i < to; i++) {
778       char charAt = cs.charAt(i);
779       for (char c : chars) {
780         if (c == charAt) {
781           return i;
782         }
783       }
784     }
785     return -1;
786   }
787 
788   public static boolean containsIgnoreCase(final CharSequence str, final CharSequence searchStr) {
789     return lastIndexOfIgnoreCase(str, searchStr) >= 0;
790   }
791 
792   public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) {
793     final int length = searchStr.length();
794     if (length == 0) {
795       return 0;
796     }
797     for (int i = str.length() - length; i >= 0; i--) {
798       if (regionMatchesIgnoreCase(str, i, searchStr, 0, length)) {
799         return i;
800       }
801     }
802     return -1;
803   }
804 
805   public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, final int idxStart) {
806     final int sLen = searchStr.length();
807     if (sLen == 0) {
808       return 0;
809     }
810     for (int i = idxStart, l = str.length() - sLen; i <= l; i++) {
811       if (regionMatchesIgnoreCase(str, i, searchStr, 0, sLen)) {
812         return i;
813       }
814     }
815     return -1;
816   }
817 
818 
819   public static int countIgnoreCase(final CharSequence str, final CharSequence searchStr) {
820     int result = 0;
821     int sLen = searchStr.length();
822     if (sLen == 0) {
823       return 0;
824     }
825     int from = 0;
826     int idx;
827     while ((idx = indexOfIgnoreCase(str, searchStr, from)) >= 0) {
828       result++;
829       from = idx + sLen;
830     }
831     return result;
832   }
833 
834 
835   @GwtIncompatible
836   public static Reader reader(final CharSequence cs) {
837     try {
838       return CharSource.wrap(cs).openStream();
839     } catch (IOException ex) {
840       throw new UncheckedIOException(ex);
841     }
842   }
843 
844 
845 }