View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang3;
18  
19  /**
20   * <p>Operations on {@link CharSequence} that are
21   * {@code null} safe.</p>
22   *
23   * @see CharSequence
24   * @since 3.0
25   */
26  public class CharSequenceUtils {
27  
28      private static final int NOT_FOUND = -1;
29  
30      /**
31       * <p>{@code CharSequenceUtils} instances should NOT be constructed in
32       * standard programming. </p>
33       *
34       * <p>This constructor is public to permit tools that require a JavaBean
35       * instance to operate.</p>
36       */
37      public CharSequenceUtils() {
38          super();
39      }
40  
41      //-----------------------------------------------------------------------
42      /**
43       * <p>Returns a new {@code CharSequence} that is a subsequence of this
44       * sequence starting with the {@code char} value at the specified index.</p>
45       *
46       * <p>This provides the {@code CharSequence} equivalent to {@link String#substring(int)}.
47       * The length (in {@code char}) of the returned sequence is {@code length() - start},
48       * so if {@code start == end} then an empty sequence is returned.</p>
49       *
50       * @param cs  the specified subsequence, null returns null
51       * @param start  the start index, inclusive, valid
52       * @return a new subsequence, may be null
53       * @throws IndexOutOfBoundsException if {@code start} is negative or if 
54       *  {@code start} is greater than {@code length()}
55       */
56      public static CharSequence subSequence(final CharSequence cs, final int start) {
57          return cs == null ? null : cs.subSequence(start, cs.length());
58      }
59  
60      //-----------------------------------------------------------------------
61      /**
62       * <p>Finds the first index in the {@code CharSequence} that matches the
63       * specified character.</p>
64       *
65       * @param cs  the {@code CharSequence} to be processed, not null
66       * @param searchChar  the char to be searched for
67       * @param start  the start index, negative starts at the string start
68       * @return the index where the search char was found, -1 if not found
69       */
70      static int indexOf(final CharSequence cs, final int searchChar, int start) {
71          if (cs instanceof String) {
72              return ((String) cs).indexOf(searchChar, start);
73          }
74          final int sz = cs.length();
75          if (start < 0) {
76              start = 0;
77          }
78          for (int i = start; i < sz; i++) {
79              if (cs.charAt(i) == searchChar) {
80                  return i;
81              }
82          }
83          return NOT_FOUND;
84      }
85  
86      /**
87       * Used by the indexOf(CharSequence methods) as a green implementation of indexOf.
88       *
89       * @param cs the {@code CharSequence} to be processed
90       * @param searchChar the {@code CharSequence} to be searched for
91       * @param start the start index
92       * @return the index where the search sequence was found
93       */
94      static int indexOf(final CharSequence cs, final CharSequence searchChar, final int start) {
95          return cs.toString().indexOf(searchChar.toString(), start);
96  //        if (cs instanceof String && searchChar instanceof String) {
97  //            // TODO: Do we assume searchChar is usually relatively small;
98  //            //       If so then calling toString() on it is better than reverting to
99  //            //       the green implementation in the else block
100 //            return ((String) cs).indexOf((String) searchChar, start);
101 //        } else {
102 //            // TODO: Implement rather than convert to String
103 //            return cs.toString().indexOf(searchChar.toString(), start);
104 //        }
105     }
106 
107     /**
108      * <p>Finds the last index in the {@code CharSequence} that matches the
109      * specified character.</p>
110      *
111      * @param cs  the {@code CharSequence} to be processed
112      * @param searchChar  the char to be searched for
113      * @param start  the start index, negative returns -1, beyond length starts at end
114      * @return the index where the search char was found, -1 if not found
115      */
116     static int lastIndexOf(final CharSequence cs, final int searchChar, int start) {
117         if (cs instanceof String) {
118             return ((String) cs).lastIndexOf(searchChar, start);
119         }
120         final int sz = cs.length();
121         if (start < 0) {
122             return NOT_FOUND;
123         }
124         if (start >= sz) {
125             start = sz - 1;
126         }
127         for (int i = start; i >= 0; --i) {
128             if (cs.charAt(i) == searchChar) {
129                 return i;
130             }
131         }
132         return NOT_FOUND;
133     }
134 
135     /**
136      * Used by the lastIndexOf(CharSequence methods) as a green implementation of lastIndexOf
137      *
138      * @param cs the {@code CharSequence} to be processed
139      * @param searchChar the {@code CharSequence} to be searched for
140      * @param start the start index
141      * @return the index where the search sequence was found
142      */
143     static int lastIndexOf(final CharSequence cs, final CharSequence searchChar, final int start) {
144         return cs.toString().lastIndexOf(searchChar.toString(), start);
145 //        if (cs instanceof String && searchChar instanceof String) {
146 //            // TODO: Do we assume searchChar is usually relatively small;
147 //            //       If so then calling toString() on it is better than reverting to
148 //            //       the green implementation in the else block
149 //            return ((String) cs).lastIndexOf((String) searchChar, start);
150 //        } else {
151 //            // TODO: Implement rather than convert to String
152 //            return cs.toString().lastIndexOf(searchChar.toString(), start);
153 //        }
154     }
155 
156     /**
157      * Green implementation of toCharArray.
158      *
159      * @param cs the {@code CharSequence} to be processed
160      * @return the resulting char array
161      */
162     static char[] toCharArray(final CharSequence cs) {
163         if (cs instanceof String) {
164             return ((String) cs).toCharArray();
165         }
166         final int sz = cs.length();
167         final char[] array = new char[cs.length()];
168         for (int i = 0; i < sz; i++) {
169             array[i] = cs.charAt(i);
170         }
171         return array;
172     }
173 
174     /**
175      * Green implementation of regionMatches.
176      *
177      * @param cs the {@code CharSequence} to be processed
178      * @param ignoreCase whether or not to be case insensitive
179      * @param thisStart the index to start on the {@code cs} CharSequence
180      * @param substring the {@code CharSequence} to be looked for
181      * @param start the index to start on the {@code substring} CharSequence
182      * @param length character length of the region
183      * @return whether the region matched
184      */
185     static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart,
186             final CharSequence substring, final int start, final int length)    {
187         if (cs instanceof String && substring instanceof String) {
188             return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length);
189         }
190         int index1 = thisStart;
191         int index2 = start;
192         int tmpLen = length;
193 
194         while (tmpLen-- > 0) {
195             final char c1 = cs.charAt(index1++);
196             final char c2 = substring.charAt(index2++);
197 
198             if (c1 == c2) {
199                 continue;
200             }
201 
202             if (!ignoreCase) {
203                 return false;
204             }
205 
206             // The same check as in String.regionMatches():
207             if (Character.toUpperCase(c1) != Character.toUpperCase(c2)
208                     && Character.toLowerCase(c1) != Character.toLowerCase(c2)) {
209                 return false;
210             }
211         }
212 
213         return true;
214     }
215 }