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}