001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.lang3;
018
019/**
020 * <p>Operations on {@link CharSequence} that are
021 * {@code null} safe.</p>
022 *
023 * @see CharSequence
024 * @since 3.0
025 */
026public class CharSequenceUtils {
027
028    private static final int NOT_FOUND = -1;
029
030    /**
031     * <p>{@code CharSequenceUtils} instances should NOT be constructed in
032     * standard programming. </p>
033     *
034     * <p>This constructor is public to permit tools that require a JavaBean
035     * instance to operate.</p>
036     */
037    public CharSequenceUtils() {
038        super();
039    }
040
041    //-----------------------------------------------------------------------
042    /**
043     * <p>Returns a new {@code CharSequence} that is a subsequence of this
044     * sequence starting with the {@code char} value at the specified index.</p>
045     *
046     * <p>This provides the {@code CharSequence} equivalent to {@link String#substring(int)}.
047     * The length (in {@code char}) of the returned sequence is {@code length() - start},
048     * so if {@code start == end} then an empty sequence is returned.</p>
049     *
050     * @param cs  the specified subsequence, null returns null
051     * @param start  the start index, inclusive, valid
052     * @return a new subsequence, may be null
053     * @throws IndexOutOfBoundsException if {@code start} is negative or if
054     *  {@code start} is greater than {@code length()}
055     */
056    public static CharSequence subSequence(final CharSequence cs, final int start) {
057        return cs == null ? null : cs.subSequence(start, cs.length());
058    }
059
060    //-----------------------------------------------------------------------
061    /**
062     * Returns the index within {@code cs} of the first occurrence of the
063     * specified character, starting the search at the specified index.
064     * <p>
065     * If a character with value {@code searchChar} occurs in the
066     * character sequence represented by the {@code cs}
067     * object at an index no smaller than {@code start}, then
068     * the index of the first such occurrence is returned. For values
069     * of {@code searchChar} in the range from 0 to 0xFFFF (inclusive),
070     * this is the smallest value <i>k</i> such that:
071     * <blockquote><pre>
072     * (this.charAt(<i>k</i>) == searchChar) &amp;&amp; (<i>k</i> &gt;= start)
073     * </pre></blockquote>
074     * is true. For other values of {@code searchChar}, it is the
075     * smallest value <i>k</i> such that:
076     * <blockquote><pre>
077     * (this.codePointAt(<i>k</i>) == searchChar) &amp;&amp; (<i>k</i> &gt;= start)
078     * </pre></blockquote>
079     * is true. In either case, if no such character occurs inm {@code cs}
080     * at or after position {@code start}, then
081     * {@code -1} is returned.
082     *
083     * <p>
084     * There is no restriction on the value of {@code start}. If it
085     * is negative, it has the same effect as if it were zero: the entire
086     * {@code CharSequence} may be searched. If it is greater than
087     * the length of {@code cs}, it has the same effect as if it were
088     * equal to the length of {@code cs}: {@code -1} is returned.
089     *
090     * <p>All indices are specified in {@code char} values
091     * (Unicode code units).
092     *
093     * @param cs  the {@code CharSequence} to be processed, not null
094     * @param searchChar  the char to be searched for
095     * @param start  the start index, negative starts at the string start
096     * @return the index where the search char was found, -1 if not found
097     * @since 3.6 updated to behave more like {@code String}
098     */
099    static int indexOf(final CharSequence cs, final int searchChar, int start) {
100        if (cs instanceof String) {
101            return ((String) cs).indexOf(searchChar, start);
102        }
103        final int sz = cs.length();
104        if (start < 0) {
105            start = 0;
106        }
107        if (searchChar < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
108            for (int i = start; i < sz; i++) {
109                if (cs.charAt(i) == searchChar) {
110                    return i;
111                }
112            }
113        }
114        //supplementary characters (LANG1300)
115        if (searchChar <= Character.MAX_CODE_POINT) {
116            final char[] chars = Character.toChars(searchChar);
117            for (int i = start; i < sz - 1; i++) {
118                final char high = cs.charAt(i);
119                final char low = cs.charAt(i + 1);
120                if (high == chars[0] && low == chars[1]) {
121                    return i;
122                }
123            }
124        }
125        return NOT_FOUND;
126    }
127
128    /**
129     * Used by the indexOf(CharSequence methods) as a green implementation of indexOf.
130     *
131     * @param cs the {@code CharSequence} to be processed
132     * @param searchChar the {@code CharSequence} to be searched for
133     * @param start the start index
134     * @return the index where the search sequence was found
135     */
136    static int indexOf(final CharSequence cs, final CharSequence searchChar, final int start) {
137        return cs.toString().indexOf(searchChar.toString(), start);
138//        if (cs instanceof String && searchChar instanceof String) {
139//            // TODO: Do we assume searchChar is usually relatively small;
140//            //       If so then calling toString() on it is better than reverting to
141//            //       the green implementation in the else block
142//            return ((String) cs).indexOf((String) searchChar, start);
143//        } else {
144//            // TODO: Implement rather than convert to String
145//            return cs.toString().indexOf(searchChar.toString(), start);
146//        }
147    }
148
149    /**
150     * Returns the index within {@code cs} of the last occurrence of
151     * the specified character, searching backward starting at the
152     * specified index. For values of {@code searchChar} in the range
153     * from 0 to 0xFFFF (inclusive), the index returned is the largest
154     * value <i>k</i> such that:
155     * <blockquote><pre>
156     * (this.charAt(<i>k</i>) == searchChar) &amp;&amp; (<i>k</i> &lt;= start)
157     * </pre></blockquote>
158     * is true. For other values of {@code searchChar}, it is the
159     * largest value <i>k</i> such that:
160     * <blockquote><pre>
161     * (this.codePointAt(<i>k</i>) == searchChar) &amp;&amp; (<i>k</i> &lt;= start)
162     * </pre></blockquote>
163     * is true. In either case, if no such character occurs in {@code cs}
164     * at or before position {@code start}, then {@code -1} is returned.
165     *
166     * <p>All indices are specified in {@code char} values
167     * (Unicode code units).
168     *
169     * @param cs  the {@code CharSequence} to be processed
170     * @param searchChar  the char to be searched for
171     * @param start  the start index, negative returns -1, beyond length starts at end
172     * @return the index where the search char was found, -1 if not found
173     * @since 3.6 updated to behave more like {@code String}
174     */
175    static int lastIndexOf(final CharSequence cs, final int searchChar, int start) {
176        if (cs instanceof String) {
177            return ((String) cs).lastIndexOf(searchChar, start);
178        }
179        final int sz = cs.length();
180        if (start < 0) {
181            return NOT_FOUND;
182        }
183        if (start >= sz) {
184            start = sz - 1;
185        }
186        if (searchChar < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
187            for (int i = start; i >= 0; --i) {
188                if (cs.charAt(i) == searchChar) {
189                    return i;
190                }
191            }
192        }
193        //supplementary characters (LANG1300)
194        //NOTE - we must do a forward traversal for this to avoid duplicating code points
195        if (searchChar <= Character.MAX_CODE_POINT) {
196            final char[] chars = Character.toChars(searchChar);
197            //make sure it's not the last index
198            if (start == sz - 1) {
199                return NOT_FOUND;
200            }
201            for (int i = start; i >= 0; i--) {
202                final char high = cs.charAt(i);
203                final char low = cs.charAt(i + 1);
204                if (chars[0] == high && chars[1] == low) {
205                    return i;
206                }
207            }
208        }
209        return NOT_FOUND;
210    }
211
212    /**
213     * Used by the lastIndexOf(CharSequence methods) as a green implementation of lastIndexOf
214     *
215     * @param cs the {@code CharSequence} to be processed
216     * @param searchChar the {@code CharSequence} to be searched for
217     * @param start the start index
218     * @return the index where the search sequence was found
219     */
220    static int lastIndexOf(final CharSequence cs, final CharSequence searchChar, final int start) {
221        return cs.toString().lastIndexOf(searchChar.toString(), start);
222//        if (cs instanceof String && searchChar instanceof String) {
223//            // TODO: Do we assume searchChar is usually relatively small;
224//            //       If so then calling toString() on it is better than reverting to
225//            //       the green implementation in the else block
226//            return ((String) cs).lastIndexOf((String) searchChar, start);
227//        } else {
228//            // TODO: Implement rather than convert to String
229//            return cs.toString().lastIndexOf(searchChar.toString(), start);
230//        }
231    }
232
233    /**
234     * Green implementation of toCharArray.
235     *
236     * @param cs the {@code CharSequence} to be processed
237     * @return the resulting char array
238     */
239    static char[] toCharArray(final CharSequence cs) {
240        if (cs instanceof String) {
241            return ((String) cs).toCharArray();
242        }
243        final int sz = cs.length();
244        final char[] array = new char[cs.length()];
245        for (int i = 0; i < sz; i++) {
246            array[i] = cs.charAt(i);
247        }
248        return array;
249    }
250
251    /**
252     * Green implementation of regionMatches.
253     *
254     * @param cs the {@code CharSequence} to be processed
255     * @param ignoreCase whether or not to be case insensitive
256     * @param thisStart the index to start on the {@code cs} CharSequence
257     * @param substring the {@code CharSequence} to be looked for
258     * @param start the index to start on the {@code substring} CharSequence
259     * @param length character length of the region
260     * @return whether the region matched
261     */
262    static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart,
263            final CharSequence substring, final int start, final int length)    {
264        if (cs instanceof String && substring instanceof String) {
265            return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length);
266        }
267        int index1 = thisStart;
268        int index2 = start;
269        int tmpLen = length;
270
271        // Extract these first so we detect NPEs the same as the java.lang.String version
272        final int srcLen = cs.length() - thisStart;
273        final int otherLen = substring.length() - start;
274
275        // Check for invalid parameters
276        if (thisStart < 0 || start < 0 || length < 0) {
277            return false;
278        }
279
280        // Check that the regions are long enough
281        if (srcLen < length || otherLen < length) {
282            return false;
283        }
284
285        while (tmpLen-- > 0) {
286            final char c1 = cs.charAt(index1++);
287            final char c2 = substring.charAt(index2++);
288
289            if (c1 == c2) {
290                continue;
291            }
292
293            if (!ignoreCase) {
294                return false;
295            }
296
297            // The same check as in String.regionMatches():
298            if (Character.toUpperCase(c1) != Character.toUpperCase(c2)
299                    && Character.toLowerCase(c1) != Character.toLowerCase(c2)) {
300                return false;
301            }
302        }
303
304        return true;
305    }
306}