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}