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 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 1469220 2013-04-18 08:15:47Z bayard $ 026 */ 027public 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(final CharSequence cs, final 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(final CharSequence cs, final int searchChar, int start) { 070 if (cs instanceof String) { 071 return ((String) cs).indexOf(searchChar, start); 072 } else { 073 final 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(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 } else { 120 final 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(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 } else { 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 /** 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(final CharSequence cs, final boolean ignoreCase, final int thisStart, 188 final CharSequence substring, final int start, final int length) { 189 if (cs instanceof String && substring instanceof String) { 190 return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length); 191 } else { 192 int index1 = thisStart; 193 int index2 = start; 194 int tmpLen = length; 195 196 while (tmpLen-- > 0) { 197 char c1 = cs.charAt(index1++); 198 char c2 = substring.charAt(index2++); 199 200 if (c1 == c2) { 201 continue; 202 } 203 204 if (!ignoreCase) { 205 return false; 206 } 207 208 // The same check as in String.regionMatches(): 209 if (Character.toUpperCase(c1) != Character.toUpperCase(c2) 210 && Character.toLowerCase(c1) != Character.toLowerCase(c2)) { 211 return false; 212 } 213 } 214 215 return true; 216 } 217 } 218}