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     * <p>Finds the first index in the {@code CharSequence} that matches the
063     * specified character.</p>
064     *
065     * @param cs  the {@code CharSequence} to be processed, not null
066     * @param searchChar  the char to be searched for
067     * @param start  the start index, negative starts at the string start
068     * @return the index where the search char was found, -1 if not found
069     */
070    static int indexOf(final CharSequence cs, final int searchChar, int start) {
071        if (cs instanceof String) {
072            return ((String) cs).indexOf(searchChar, start);
073        }
074        final int sz = cs.length();
075        if (start < 0) {
076            start = 0;
077        }
078        for (int i = start; i < sz; i++) {
079            if (cs.charAt(i) == searchChar) {
080                return i;
081            }
082        }
083        return NOT_FOUND;
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        }
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        // Extract these first so we detect NPEs the same as the java.lang.String version
195        final int srcLen = cs.length() - thisStart;
196        final int otherLen = substring.length() - start;
197
198        // Check for invalid parameters
199        if (thisStart < 0 || start < 0 || length < 0) {
200            return false;
201        }
202
203        // Check that the regions are long enough
204        if (srcLen < length || otherLen < length) {
205            return false;
206        }
207
208        while (tmpLen-- > 0) {
209            final char c1 = cs.charAt(index1++);
210            final char c2 = substring.charAt(index2++);
211
212            if (c1 == c2) {
213                continue;
214            }
215
216            if (!ignoreCase) {
217                return false;
218            }
219
220            // The same check as in String.regionMatches():
221            if (Character.toUpperCase(c1) != Character.toUpperCase(c2)
222                    && Character.toLowerCase(c1) != Character.toLowerCase(c2)) {
223                return false;
224            }
225        }
226
227        return true;
228    }
229}