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 * @version $Id: CharSequenceUtils.java 1606051 2014-06-27 12:22:17Z ggregory $
026 */
027public class CharSequenceUtils {
028
029    private static final int NOT_FOUND = -1;
030
031    /**
032     * <p>{@code CharSequenceUtils} instances should NOT be constructed in
033     * standard programming. </p>
034     *
035     * <p>This constructor is public to permit tools that require a JavaBean
036     * instance to operate.</p>
037     */
038    public CharSequenceUtils() {
039        super();
040    }
041
042    //-----------------------------------------------------------------------
043    /**
044     * <p>Returns a new {@code CharSequence} that is a subsequence of this
045     * sequence starting with the {@code char} value at the specified index.</p>
046     *
047     * <p>This provides the {@code CharSequence} equivalent to {@link String#substring(int)}.
048     * The length (in {@code char}) of the returned sequence is {@code length() - start},
049     * so if {@code start == end} then an empty sequence is returned.</p>
050     *
051     * @param cs  the specified subsequence, null returns null
052     * @param start  the start index, inclusive, valid
053     * @return a new subsequence, may be null
054     * @throws IndexOutOfBoundsException if {@code start} is negative or if 
055     *  {@code start} is greater than {@code length()}
056     */
057    public static CharSequence subSequence(final CharSequence cs, final int start) {
058        return cs == null ? null : cs.subSequence(start, cs.length());
059    }
060
061    //-----------------------------------------------------------------------
062    /**
063     * <p>Finds the first index in the {@code CharSequence} that matches the
064     * specified character.</p>
065     *
066     * @param cs  the {@code CharSequence} to be processed, not null
067     * @param searchChar  the char to be searched for
068     * @param start  the start index, negative starts at the string start
069     * @return the index where the search char was found, -1 if not found
070     */
071    static int indexOf(final CharSequence cs, final int searchChar, int start) {
072        if (cs instanceof String) {
073            return ((String) cs).indexOf(searchChar, start);
074        }
075        final int sz = cs.length();
076        if (start < 0) {
077            start = 0;
078        }
079        for (int i = start; i < sz; i++) {
080            if (cs.charAt(i) == searchChar) {
081                return i;
082            }
083        }
084        return NOT_FOUND;
085    }
086
087    /**
088     * Used by the indexOf(CharSequence methods) as a green implementation of indexOf.
089     *
090     * @param cs the {@code CharSequence} to be processed
091     * @param searchChar the {@code CharSequence} to be searched for
092     * @param start the start index
093     * @return the index where the search sequence was found
094     */
095    static int indexOf(final CharSequence cs, final CharSequence searchChar, final int start) {
096        return cs.toString().indexOf(searchChar.toString(), start);
097//        if (cs instanceof String && searchChar instanceof String) {
098//            // TODO: Do we assume searchChar is usually relatively small;
099//            //       If so then calling toString() on it is better than reverting to
100//            //       the green implementation in the else block
101//            return ((String) cs).indexOf((String) searchChar, start);
102//        } else {
103//            // TODO: Implement rather than convert to String
104//            return cs.toString().indexOf(searchChar.toString(), start);
105//        }
106    }
107
108    /**
109     * <p>Finds the last index in the {@code CharSequence} that matches the
110     * specified character.</p>
111     *
112     * @param cs  the {@code CharSequence} to be processed
113     * @param searchChar  the char to be searched for
114     * @param start  the start index, negative returns -1, beyond length starts at end
115     * @return the index where the search char was found, -1 if not found
116     */
117    static int lastIndexOf(final CharSequence cs, final int searchChar, int start) {
118        if (cs instanceof String) {
119            return ((String) cs).lastIndexOf(searchChar, start);
120        }
121        final int sz = cs.length();
122        if (start < 0) {
123            return NOT_FOUND;
124        }
125        if (start >= sz) {
126            start = sz - 1;
127        }
128        for (int i = start; i >= 0; --i) {
129            if (cs.charAt(i) == searchChar) {
130                return i;
131            }
132        }
133        return NOT_FOUND;
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        }
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     * Green implementation of regionMatches.
177     *
178     * @param cs the {@code CharSequence} to be processed
179     * @param ignoreCase whether or not to be case insensitive
180     * @param thisStart the index to start on the {@code cs} CharSequence
181     * @param substring the {@code CharSequence} to be looked for
182     * @param start the index to start on the {@code substring} CharSequence
183     * @param length character length of the region
184     * @return whether the region matched
185     */
186    static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart,
187            final CharSequence substring, final int start, final int length)    {
188        if (cs instanceof String && substring instanceof String) {
189            return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length);
190        }
191        int index1 = thisStart;
192        int index2 = start;
193        int tmpLen = length;
194
195        while (tmpLen-- > 0) {
196            final char c1 = cs.charAt(index1++);
197            final char c2 = substring.charAt(index2++);
198
199            if (c1 == c2) {
200                continue;
201            }
202
203            if (!ignoreCase) {
204                return false;
205            }
206
207            // The same check as in String.regionMatches():
208            if (Character.toUpperCase(c1) != Character.toUpperCase(c2)
209                    && Character.toLowerCase(c1) != Character.toLowerCase(c2)) {
210                return false;
211            }
212        }
213
214        return true;
215    }
216}