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     */
017    package 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 1091471 2011-04-12 15:34:43Z scolebourne $
026     */
027    public 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(CharSequence cs, 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(CharSequence cs, int searchChar, int start) {
070            if (cs instanceof String) {
071                return ((String) cs).indexOf(searchChar, start);
072            } else {
073                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(CharSequence cs, CharSequence searchChar, 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(CharSequence cs, int searchChar, int start) {
117            if (cs instanceof String) {
118                return ((String) cs).lastIndexOf(searchChar, start);
119            } else {
120                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(CharSequence cs, CharSequence searchChar, 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(CharSequence cs) {
164            if (cs instanceof String) {
165                return ((String) cs).toCharArray();
166            } else {
167                int sz = cs.length();
168                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(CharSequence cs, boolean ignoreCase, int thisStart,
188                CharSequence substring, int start, int length)    {
189            if (cs instanceof String && substring instanceof String) {
190                return ((String) cs).regionMatches(ignoreCase, thisStart, ((String) substring), start, length);
191            } else {
192                // TODO: Implement rather than convert to String
193                return cs.toString().regionMatches(ignoreCase, thisStart, substring.toString(), start, length);
194            }
195        }
196    
197    }