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 * https://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 * Operations on {@link CharSequence} that are 021 * {@code null} safe. 022 * 023 * @see CharSequence 024 * @since 3.0 025 */ 026public class CharSequenceUtils { 027 028 private static final int NOT_FOUND = -1; 029 030 /** 031 * Whether the running JDK folds a supplementary code point split across a surrogate pair when comparing case insensitively in 032 * {@link String#regionMatches(boolean, int, String, int, int)}. JDKs up to and including Java 11 compare surrogate by surrogate and never match such a 033 * pair; later JDKs fold the whole code point. Probing what {@link String} actually does (rather than gating on a version constant) keeps every 034 * {@link CharSequence} type in step with {@link String} on whatever JDK is running. DESERET CAPITAL LETTER LONG I (U+10400) folds to its small form 035 * (U+10428). 036 */ 037 private static final boolean STRING_FOLDS_SUPPLEMENTARY_CASE = new String(Character.toChars(0x10400)).regionMatches(true, 0, 038 new String(Character.toChars(0x10428)), 0, 2); 039 040 static final int TO_STRING_LIMIT = 16; 041 042 private static boolean checkLaterThan1(final CharSequence cs, final CharSequence searchChar, final int len2, final int start1) { 043 for (int i = 1, j = len2 - 1; i <= j; i++, j--) { 044 if (cs.charAt(start1 + i) != searchChar.charAt(i) || cs.charAt(start1 + j) != searchChar.charAt(j)) { 045 return false; 046 } 047 } 048 return true; 049 } 050 051 /** 052 * Tests whether two code points are equal ignoring case, matching the folding used by {@link String#regionMatches(boolean, int, String, int, int)}. 053 * 054 * @param cp1 The first code point. 055 * @param cp2 The second code point. 056 * @return whether the code points are equal ignoring case. 057 */ 058 private static boolean equalsIgnoreCase(final int cp1, final int cp2) { 059 final int u1 = Character.toUpperCase(cp1); 060 final int u2 = Character.toUpperCase(cp2); 061 return u1 == u2 || Character.toLowerCase(u1) == Character.toLowerCase(u2); 062 } 063 064 /** 065 * Used by the indexOf(CharSequence methods) as a green implementation of indexOf. 066 * <p> 067 * {@link CharSequence} types without a dedicated branch are scanned in place rather than materialized with {@code toString()}: for builder 068 * types (for example {@code org.apache.commons.lang3.text.StrBuilder}), {@code toString()} copies the whole buffer, and callers that invoke 069 * this method once per occurrence (such as {@code deleteAll}/{@code replaceAll}) would multiply that copy into allocation-quadratic churn. 070 * </p> 071 * 072 * @param cs The {@link CharSequence} to be processed. 073 * @param searchChar The {@link CharSequence} to be searched for. 074 * @param start The start index. 075 * @return The index where the search sequence was found, or {@code -1} if there is no such occurrence. 076 */ 077 static int indexOf(final CharSequence cs, final CharSequence searchChar, final int start) { 078 if (cs == null || searchChar == null) { 079 return StringUtils.INDEX_NOT_FOUND; 080 } 081 if (cs instanceof String) { 082 return ((String) cs).indexOf(searchChar.toString(), start); 083 } 084 if (cs instanceof StringBuilder) { 085 return ((StringBuilder) cs).indexOf(searchChar.toString(), start); 086 } 087 if (cs instanceof StringBuffer) { 088 return ((StringBuffer) cs).indexOf(searchChar.toString(), start); 089 } 090 // Direct scan without copying cs; matches the semantics of String.indexOf(String, int). 091 final int len1 = cs.length(); 092 final int len2 = searchChar.length(); 093 final int from = Math.max(start, 0); 094 if (len2 == 0) { 095 return Math.min(from, len1); 096 } 097 if (len2 > len1 - from) { 098 return StringUtils.INDEX_NOT_FOUND; 099 } 100 final char char0 = searchChar.charAt(0); 101 final int max = len1 - len2; 102 for (int i = from; i <= max; i++) { 103 if (cs.charAt(i) == char0 && checkLaterThan1(cs, searchChar, len2, i)) { 104 return i; 105 } 106 } 107 return StringUtils.INDEX_NOT_FOUND; 108 } 109 110 /** 111 * Returns the index within {@code cs} of the first occurrence of the specified character, starting the search at the specified index. 112 * <p> 113 * If a character with value {@code searchChar} occurs in the character sequence represented by the {@code cs} object at an index no smaller than 114 * {@code start}, then the index of the first such occurrence is returned. For values of {@code searchChar} in the range from 0 to 0xFFFF (inclusive), this 115 * is the smallest value <em>k</em> such that: 116 * </p> 117 * 118 * <pre> 119 * (this.charAt(<em>k</em>) == searchChar) && (<em>k</em> >= start) 120 * </pre> 121 * <p> 122 * is true. For other values of {@code searchChar}, it is the smallest value <em>k</em> such that: 123 * </p> 124 * 125 * <pre> 126 * (this.codePointAt(<em>k</em>) == searchChar) && (<em>k</em> >= start) 127 * </pre> 128 * <p> 129 * is true. In either case, if no such character occurs inm {@code cs} at or after position {@code start}, then {@code -1} is returned. 130 * </p> 131 * <p> 132 * There is no restriction on the value of {@code start}. If it is negative, it has the same effect as if it were zero: the entire {@link CharSequence} may 133 * be searched. If it is greater than the length of {@code cs}, it has the same effect as if it were equal to the length of {@code cs}: {@code -1} is 134 * returned. 135 * </p> 136 * <p> 137 * All indices are specified in {@code char} values (Unicode code units). 138 * </p> 139 * 140 * @param cs The {@link CharSequence} to be processed, not null. 141 * @param searchChar The char to be searched for. 142 * @param start The start index, negative starts at the string start. 143 * @return The index where the search char was found, -1 if not found. 144 * @since 3.6 updated to behave more like {@link String}. 145 */ 146 static int indexOf(final CharSequence cs, final int searchChar, int start) { 147 if (cs instanceof String) { 148 return ((String) cs).indexOf(searchChar, start); 149 } 150 final int sz = cs.length(); 151 if (start < 0) { 152 start = 0; 153 } 154 if (searchChar < Character.MIN_SUPPLEMENTARY_CODE_POINT) { 155 for (int i = start; i < sz; i++) { 156 if (cs.charAt(i) == searchChar) { 157 return i; 158 } 159 } 160 return NOT_FOUND; 161 } 162 //supplementary characters (LANG1300) 163 if (searchChar <= Character.MAX_CODE_POINT) { 164 final char[] chars = Character.toChars(searchChar); 165 for (int i = start; i < sz - 1; i++) { 166 final char high = cs.charAt(i); 167 final char low = cs.charAt(i + 1); 168 if (high == chars[0] && low == chars[1]) { 169 return i; 170 } 171 } 172 } 173 return NOT_FOUND; 174 } 175 176 /** 177 * Used by the lastIndexOf(CharSequence methods) as a green implementation of lastIndexOf 178 * 179 * @param cs The {@link CharSequence} to be processed. 180 * @param searchChar The {@link CharSequence} to find. 181 * @param start The start index. 182 * @return The index where the search sequence was found. 183 */ 184 static int lastIndexOf(final CharSequence cs, final CharSequence searchChar, int start) { 185 if (searchChar == null || cs == null) { 186 return NOT_FOUND; 187 } 188 if (searchChar instanceof String) { 189 if (cs instanceof String) { 190 return ((String) cs).lastIndexOf((String) searchChar, start); 191 } 192 if (cs instanceof StringBuilder) { 193 return ((StringBuilder) cs).lastIndexOf((String) searchChar, start); 194 } 195 if (cs instanceof StringBuffer) { 196 return ((StringBuffer) cs).lastIndexOf((String) searchChar, start); 197 } 198 } 199 200 final int len1 = cs.length(); 201 final int len2 = searchChar.length(); 202 203 if (start > len1) { 204 start = len1; 205 } 206 207 if (start < 0 || len2 > len1) { 208 return NOT_FOUND; 209 } 210 211 if (len2 == 0) { 212 return start; 213 } 214 215 if (len2 <= TO_STRING_LIMIT) { 216 if (cs instanceof String) { 217 return ((String) cs).lastIndexOf(searchChar.toString(), start); 218 } 219 if (cs instanceof StringBuilder) { 220 return ((StringBuilder) cs).lastIndexOf(searchChar.toString(), start); 221 } 222 if (cs instanceof StringBuffer) { 223 return ((StringBuffer) cs).lastIndexOf(searchChar.toString(), start); 224 } 225 } 226 227 if (start + len2 > len1) { 228 start = len1 - len2; 229 } 230 231 final char char0 = searchChar.charAt(0); 232 233 int i = start; 234 while (true) { 235 while (cs.charAt(i) != char0) { 236 i--; 237 if (i < 0) { 238 return NOT_FOUND; 239 } 240 } 241 if (checkLaterThan1(cs, searchChar, len2, i)) { 242 return i; 243 } 244 i--; 245 if (i < 0) { 246 return NOT_FOUND; 247 } 248 } 249 } 250 251 /** 252 * Returns the index within {@code cs} of the last occurrence of the specified character, searching backward starting at the specified index. For values of 253 * {@code searchChar} in the range from 0 to 0xFFFF (inclusive), the index returned is the largest value <em>k</em> such that: 254 * 255 * <pre> 256 * (this.charAt(<em>k</em>) == searchChar) && (<em>k</em> <= start) 257 * </pre> 258 * <p> 259 * is true. For other values of {@code searchChar}, it is the largest value <em>k</em> such that: 260 * <p> 261 * 262 * <pre> 263 * (this.codePointAt(<em>k</em>) == searchChar) && (<em>k</em> <= start) 264 * </pre> 265 * <p> 266 * is true. In either case, if no such character occurs in {@code cs} at or before position {@code start}, then {@code -1} is returned. 267 * </p> 268 * <p> 269 * All indices are specified in {@code char} values (Unicode code units). 270 * </p> 271 * 272 * @param cs The {@link CharSequence} to be processed. 273 * @param searchChar The char to be searched for. 274 * @param start The start index, negative returns -1, beyond length starts at end. 275 * @return The index where the search char was found, -1 if not found. 276 * @since 3.6 updated to behave more like {@link String}. 277 */ 278 static int lastIndexOf(final CharSequence cs, final int searchChar, int start) { 279 if (cs instanceof String) { 280 return ((String) cs).lastIndexOf(searchChar, start); 281 } 282 final int sz = cs.length(); 283 if (start < 0) { 284 return NOT_FOUND; 285 } 286 if (start >= sz) { 287 start = sz - 1; 288 } 289 if (searchChar < Character.MIN_SUPPLEMENTARY_CODE_POINT) { 290 for (int i = start; i >= 0; --i) { 291 if (cs.charAt(i) == searchChar) { 292 return i; 293 } 294 } 295 return NOT_FOUND; 296 } 297 //supplementary characters (LANG1300) 298 //NOTE - we must do a forward traversal for this to avoid duplicating code points 299 if (searchChar <= Character.MAX_CODE_POINT) { 300 final char[] chars = Character.toChars(searchChar); 301 // A supplementary code point spans two chars, so its high surrogate can start no later 302 // than sz - 2; clamp the search origin instead of bailing out when start is the last index. 303 for (int i = Math.min(start, sz - 2); i >= 0; i--) { 304 final char high = cs.charAt(i); 305 final char low = cs.charAt(i + 1); 306 if (chars[0] == high && chars[1] == low) { 307 return i; 308 } 309 } 310 } 311 return NOT_FOUND; 312 } 313 314 /** 315 * Tests if two string regions are equal. 316 * 317 * @param cs The {@link CharSequence} to be processed. 318 * @param ignoreCase whether or not to be case-insensitive. 319 * @param thisStart The index to start on the {@code cs} CharSequence. 320 * @param substring The {@link CharSequence} to be looked for. 321 * @param start The index to start on the {@code substring} CharSequence. 322 * @param length character length of the region. 323 * @return whether the region matched. 324 * @see String#regionMatches(boolean, int, String, int, int) 325 */ 326 static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart, final CharSequence substring, final int start, 327 final int length) { 328 // Green implementation of regionMatches. 329 if (cs instanceof String && substring instanceof String) { 330 return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length); 331 } 332 // Extract these first so we detect NPEs the same as the java.lang.String version 333 final int srcLen = cs.length() - thisStart; 334 final int otherLen = substring.length() - start; 335 // Check for invalid parameters 336 if (thisStart < 0 || start < 0 || length < 0) { 337 return false; 338 } 339 // Check that the regions are long enough 340 if (srcLen < length || otherLen < length) { 341 return false; 342 } 343 final int end1 = thisStart + length; 344 final int end2 = start + length; 345 int index1 = thisStart; 346 int index2 = start; 347 while (index1 < end1 && index2 < end2) { 348 final char c1 = cs.charAt(index1); 349 final char c2 = substring.charAt(index2); 350 if (c1 == c2) { 351 index1++; 352 index2++; 353 continue; 354 } 355 if (!ignoreCase) { 356 return false; 357 } 358 // The same case-insensitive check as String#regionMatches(boolean, int, String, int, int). 359 if (!equalsIgnoreCase(c1, c2)) { 360 // Only fold a supplementary code point split across a surrogate pair where String itself does, so 361 // every CharSequence type gives the same result that String does on the running JDK (see field). 362 if (!STRING_FOLDS_SUPPLEMENTARY_CASE) { 363 return false; 364 } 365 int cp1 = c1; 366 if (Character.isHighSurrogate(c1)) { 367 if (index1 + 1 < end1 && Character.isLowSurrogate(cs.charAt(index1 + 1))) { 368 cp1 = Character.toCodePoint(c1, cs.charAt(index1 + 1)); 369 index1++; 370 } 371 } else if (Character.isLowSurrogate(c1) && index1 > thisStart && Character.isHighSurrogate(cs.charAt(index1 - 1))) { 372 cp1 = Character.toCodePoint(cs.charAt(index1 - 1), c1); 373 } 374 int cp2 = c2; 375 if (Character.isHighSurrogate(c2)) { 376 if (index2 + 1 < end2 && Character.isLowSurrogate(substring.charAt(index2 + 1))) { 377 cp2 = Character.toCodePoint(c2, substring.charAt(index2 + 1)); 378 index2++; 379 } 380 } else if (Character.isLowSurrogate(c2) && index2 > start && Character.isHighSurrogate(substring.charAt(index2 - 1))) { 381 cp2 = Character.toCodePoint(substring.charAt(index2 - 1), c2); 382 } 383 if (!equalsIgnoreCase(cp1, cp2)) { 384 return false; 385 } 386 } 387 index1++; 388 index2++; 389 } 390 return true; 391 } 392 393 /** 394 * Returns a new {@link CharSequence} that is a subsequence of this 395 * sequence starting with the {@code char} value at the specified index. 396 * 397 * <p> 398 * This provides the {@link CharSequence} equivalent to {@link String#substring(int)}. 399 * The length (in {@code char}) of the returned sequence is {@code length() - start}, 400 * so if {@code start == end} then an empty sequence is returned. 401 * </p> 402 * 403 * @param cs The specified subsequence, null returns null. 404 * @param start The start index, inclusive, valid. 405 * @return A new subsequence, may be null. 406 * @throws IndexOutOfBoundsException Thrown if {@code start} is negative or if 407 * {@code start} is greater than {@code length()}. 408 */ 409 public static CharSequence subSequence(final CharSequence cs, final int start) { 410 return cs == null ? null : cs.subSequence(start, cs.length()); 411 } 412 413 /** 414 * Converts the given CharSequence to a char[]. 415 * 416 * @param source The {@link CharSequence} to be processed. 417 * @return The resulting char array, never null. 418 * @since 3.11 419 */ 420 public static char[] toCharArray(final CharSequence source) { 421 // See CharSequenceUtilsBenchmark 422 final int len = StringUtils.length(source); 423 if (len == 0) { 424 return ArrayUtils.EMPTY_CHAR_ARRAY; 425 } 426 if (source instanceof String) { 427 return ((String) source).toCharArray(); 428 } 429 if (source instanceof StringBuilder) { 430 final char[] array = new char[len]; 431 ((StringBuilder) source).getChars(0, len, array, 0); 432 return array; 433 } 434 if (source instanceof StringBuffer) { 435 final char[] array = new char[len]; 436 ((StringBuffer) source).getChars(0, len, array, 0); 437 return array; 438 } 439 final char[] array = new char[len]; 440 for (int i = 0; i < len; i++) { 441 array[i] = source.charAt(i); 442 } 443 return array; 444 } 445 446 /** 447 * {@link CharSequenceUtils} instances should NOT be constructed in 448 * standard programming. 449 * 450 * <p> 451 * This constructor is public to permit tools that require a JavaBean 452 * instance to operate. 453 * </p> 454 * 455 * @deprecated TODO Make private in 4.0. 456 */ 457 @Deprecated 458 public CharSequenceUtils() { 459 // empty 460 } 461}