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 java.lang.CharSequence} that are
021 * {@code null} safe.</p>
022 *
023 * @see java.lang.CharSequence
024 * @since 3.0
025 * @version $Id: CharSequenceUtils.java 1469220 2013-04-18 08:15:47Z bayard $
026 */
027public class CharSequenceUtils {
028
029    /**
030     * <p>{@code CharSequenceUtils} instances should NOT be constructed in
031     * standard programming. </p>
032     *
033     * <p>This constructor is public to permit tools that require a JavaBean
034     * instance to operate.</p>
035     */
036    public CharSequenceUtils() {
037        super();
038    }
039
040    //-----------------------------------------------------------------------
041    /**
042     * <p>Returns a new {@code CharSequence} that is a subsequence of this
043     * sequence starting with the {@code char} value at the specified index.</p>
044     *
045     * <p>This provides the {@code CharSequence} equivalent to {@link String#substring(int)}.
046     * The length (in {@code char}) of the returned sequence is {@code length() - start},
047     * so if {@code start == end} then an empty sequence is returned.</p>
048     *
049     * @param cs  the specified subsequence, null returns null
050     * @param start  the start index, inclusive, valid
051     * @return a new subsequence, may be null
052     * @throws IndexOutOfBoundsException if {@code start} is negative or if 
053     *  {@code start} is greater than {@code length()}
054     */
055    public static CharSequence subSequence(final CharSequence cs, final int start) {
056        return cs == null ? null : cs.subSequence(start, cs.length());
057    }
058
059    //-----------------------------------------------------------------------
060    /**
061     * <p>Finds the first index in the {@code CharSequence} that matches the
062     * specified character.</p>
063     *
064     * @param cs  the {@code CharSequence} to be processed, not null
065     * @param searchChar  the char to be searched for
066     * @param start  the start index, negative starts at the string start
067     * @return the index where the search char was found, -1 if not found
068     */
069    static int indexOf(final CharSequence cs, final int searchChar, int start) {
070        if (cs instanceof String) {
071            return ((String) cs).indexOf(searchChar, start);
072        } else {
073            final int sz = cs.length();
074            if (start < 0) {
075                start = 0;
076            }
077            for (int i = start; i < sz; i++) {
078                if (cs.charAt(i) == searchChar) {
079                    return i;
080                }
081            }
082            return -1;
083        }
084    }
085
086    /**
087     * Used by the indexOf(CharSequence methods) as a green implementation of indexOf.
088     *
089     * @param cs the {@code CharSequence} to be processed
090     * @param searchChar the {@code CharSequence} to be searched for
091     * @param start the start index
092     * @return the index where the search sequence was found
093     */
094    static int indexOf(final CharSequence cs, final CharSequence searchChar, final int start) {
095        return cs.toString().indexOf(searchChar.toString(), start);
096//        if (cs instanceof String && searchChar instanceof String) {
097//            // TODO: Do we assume searchChar is usually relatively small;
098//            //       If so then calling toString() on it is better than reverting to
099//            //       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        } else {
120            final int sz = cs.length();
121            if (start < 0) {
122                return -1;
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 -1;
133        }
134    }
135
136    /**
137     * Used by the lastIndexOf(CharSequence methods) as a green implementation of lastIndexOf
138     *
139     * @param cs the {@code CharSequence} to be processed
140     * @param searchChar the {@code CharSequence} to be searched for
141     * @param start the start index
142     * @return the index where the search sequence was found
143     */
144    static int lastIndexOf(final CharSequence cs, final CharSequence searchChar, final int start) {
145        return cs.toString().lastIndexOf(searchChar.toString(), start);
146//        if (cs instanceof String && searchChar instanceof String) {
147//            // TODO: Do we assume searchChar is usually relatively small;
148//            //       If so then calling toString() on it is better than reverting to
149//            //       the green implementation in the else block
150//            return ((String) cs).lastIndexOf((String) searchChar, start);
151//        } else {
152//            // TODO: Implement rather than convert to String
153//            return cs.toString().lastIndexOf(searchChar.toString(), start);
154//        }
155    }
156
157    /**
158     * Green implementation of toCharArray.
159     *
160     * @param cs the {@code CharSequence} to be processed
161     * @return the resulting char array
162     */
163    static char[] toCharArray(final CharSequence cs) {
164        if (cs instanceof String) {
165            return ((String) cs).toCharArray();
166        } else {
167            final int sz = cs.length();
168            final char[] array = new char[cs.length()];
169            for (int i = 0; i < sz; i++) {
170                array[i] = cs.charAt(i);
171            }
172            return array;
173        }
174    }
175
176    /**
177     * Green implementation of regionMatches.
178     *
179     * @param cs the {@code CharSequence} to be processed
180     * @param ignoreCase whether or not to be case insensitive
181     * @param thisStart the index to start on the {@code cs} CharSequence
182     * @param substring the {@code CharSequence} to be looked for
183     * @param start the index to start on the {@code substring} CharSequence
184     * @param length character length of the region
185     * @return whether the region matched
186     */
187    static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart,
188            final CharSequence substring, final int start, final int length)    {
189        if (cs instanceof String && substring instanceof String) {
190            return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length);
191        } else {
192            int index1 = thisStart;
193            int index2 = start;
194            int tmpLen = length;
195
196            while (tmpLen-- > 0) {
197                char c1 = cs.charAt(index1++);
198                char c2 = substring.charAt(index2++);
199
200                if (c1 == c2) {
201                    continue;
202                }
203
204                if (!ignoreCase) {
205                    return false;
206                }
207
208                // The same check as in String.regionMatches():
209                if (Character.toUpperCase(c1) != Character.toUpperCase(c2)
210                        && Character.toLowerCase(c1) != Character.toLowerCase(c2)) {
211                    return false;
212                }
213            }
214
215            return true;
216        }
217    }
218}