1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.lang3;
18
19 import static org.apache.commons.lang3.LangAssertions.assertIndexOutOfBoundsException;
20 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21 import static org.junit.jupiter.api.Assertions.assertEquals;
22 import static org.junit.jupiter.api.Assertions.assertFalse;
23 import static org.junit.jupiter.api.Assertions.assertNotNull;
24 import static org.junit.jupiter.api.Assertions.assertNull;
25 import static org.junit.jupiter.api.Assertions.assertThrows;
26 import static org.junit.jupiter.api.Assertions.assertTrue;
27 import static org.junit.jupiter.params.provider.Arguments.arguments;
28
29 import java.lang.reflect.Constructor;
30 import java.lang.reflect.Modifier;
31 import java.nio.CharBuffer;
32 import java.util.Random;
33 import java.util.stream.IntStream;
34 import java.util.stream.Stream;
35
36 import org.junit.jupiter.api.Test;
37 import org.junit.jupiter.params.ParameterizedTest;
38 import org.junit.jupiter.params.provider.Arguments;
39 import org.junit.jupiter.params.provider.MethodSource;
40
41
42
43
44 class CharSequenceUtilsTest extends AbstractLangTest {
45
46 private abstract static class RunTest {
47
48 abstract boolean invoke();
49
50 void run(final TestData data, final String id) {
51 if (data.throwable != null) {
52 assertThrows(data.throwable, this::invoke, id + " Expected " + data.throwable);
53 } else {
54 final boolean stringCheck = invoke();
55 assertEquals(data.expected, stringCheck, id + " Failed test " + data);
56 }
57 }
58
59 }
60
61 static class TestData {
62 final String source;
63 final boolean ignoreCase;
64 final int toffset;
65 final String other;
66 final int ooffset;
67 final int len;
68 final boolean expected;
69 final Class<? extends Throwable> throwable;
70
71 TestData(final String source, final boolean ignoreCase, final int toffset, final String other, final int ooffset, final int len,
72 final boolean expected) {
73 this.source = source;
74 this.ignoreCase = ignoreCase;
75 this.toffset = toffset;
76 this.other = other;
77 this.ooffset = ooffset;
78 this.len = len;
79 this.expected = expected;
80 this.throwable = null;
81 }
82
83 TestData(final String source, final boolean ignoreCase, final int toffset, final String other, final int ooffset, final int len,
84 final Class<? extends Throwable> throwable) {
85 this.source = source;
86 this.ignoreCase = ignoreCase;
87 this.toffset = toffset;
88 this.other = other;
89 this.ooffset = ooffset;
90 this.len = len;
91 this.expected = false;
92 this.throwable = throwable;
93 }
94
95 @Override
96 public String toString() {
97 final StringBuilder sb = new StringBuilder();
98 sb.append(source).append("[").append(toffset).append("]");
99 sb.append(ignoreCase ? " caseblind " : " samecase ");
100 sb.append(other).append("[").append(ooffset).append("]");
101 sb.append(" ").append(len).append(" => ");
102 if (throwable != null) {
103 sb.append(throwable);
104 } else {
105 sb.append(expected);
106 }
107 return sb.toString();
108 }
109 }
110
111 static class WrapperString implements CharSequence {
112 private final CharSequence inner;
113
114 WrapperString(final CharSequence inner) {
115 this.inner = inner;
116 }
117
118 @Override
119 public char charAt(final int index) {
120 return inner.charAt(index);
121 }
122
123 @Override
124 public IntStream chars() {
125 return inner.chars();
126 }
127
128 @Override
129 public IntStream codePoints() {
130 return inner.codePoints();
131 }
132
133 @Override
134 public int length() {
135 return inner.length();
136 }
137
138 @Override
139 public CharSequence subSequence(final int start, final int end) {
140 return inner.subSequence(start, end);
141 }
142
143 @Override
144 public String toString() {
145 return inner.toString();
146 }
147 }
148
149 private static final TestData[] TEST_DATA = {
150
151
152 new TestData("", true, -1, "", -1, -1, false),
153 new TestData("", true, 0, "", 0, 1, false),
154 new TestData("a", true, 0, "abc", 0, 0, true),
155 new TestData("a", true, 0, "abc", 0, 1, true),
156 new TestData("a", true, 0, null, 0, 0, NullPointerException.class),
157 new TestData(null, true, 0, null, 0, 0, NullPointerException.class),
158 new TestData(null, true, 0, "", 0, 0, NullPointerException.class),
159 new TestData("Abc", true, 0, "abc", 0, 3, true),
160 new TestData("Abc", false, 0, "abc", 0, 3, false),
161 new TestData("Abc", true, 1, "abc", 1, 2, true),
162 new TestData("Abc", false, 1, "abc", 1, 2, true),
163 new TestData("Abcd", true, 1, "abcD", 1, 2, true),
164 new TestData("Abcd", false, 1, "abcD", 1, 2, true),
165
166 };
167
168 private static void assertRegionMatchesParity(final String source, final boolean ignoreCase, final int toffset, final String other,
169 final int ooffset, final int len) {
170
171 final boolean expected = source.regionMatches(ignoreCase, toffset, other, ooffset, len);
172 final CharSequence[] sources = {source, new StringBuilder(source), new StringBuffer(source), CharBuffer.wrap(source)};
173 for (final CharSequence cs : sources) {
174 assertEquals(expected, CharSequenceUtils.regionMatches(cs, ignoreCase, toffset, other, ooffset, len),
175 cs.getClass().getSimpleName() + " differs from String for " + source + " vs " + other);
176 }
177 }
178
179 static Stream<Arguments> lastIndexWithStandardCharSequence() {
180
181 return Stream.of(
182 arguments("abc", "b", 2, 1),
183 arguments(new StringBuilder("abc"), "b", 2, 1),
184 arguments(new StringBuffer("abc"), "b", 2, 1),
185 arguments("abc", new StringBuilder("b"), 2, 1),
186 arguments(new StringBuilder("abc"), new StringBuilder("b"), 2, 1),
187 arguments(new StringBuffer("abc"), new StringBuffer("b"), 2, 1),
188 arguments(new StringBuilder("abc"), new StringBuffer("b"), 2, 1)
189 );
190
191 }
192
193 @Test
194 void testConstructor() {
195 assertNotNull(new CharSequenceUtils());
196 final Constructor<?>[] cons = CharSequenceUtils.class.getDeclaredConstructors();
197 assertEquals(1, cons.length);
198 assertTrue(Modifier.isPublic(cons[0].getModifiers()));
199 assertTrue(Modifier.isPublic(CharSequenceUtils.class.getModifiers()));
200 assertFalse(Modifier.isFinal(CharSequenceUtils.class.getModifiers()));
201 }
202
203 @ParameterizedTest
204 @MethodSource("lastIndexWithStandardCharSequence")
205 void testLastIndexOfWithDifferentCharSequences(final CharSequence cs, final CharSequence search, final int start, final int expected) {
206 assertEquals(expected, CharSequenceUtils.lastIndexOf(cs, search, start));
207 }
208
209
210
211
212
213
214 @Test
215 void testNewIndexOf() {
216 testNewIndexOfSingle("808087847-1321060740-635567660180086727-925755305", "-1321060740-635567660");
217 testNewIndexOfSingle("", "");
218 testNewIndexOfSingle("1", "");
219 testNewIndexOfSingle("", "1");
220 testNewIndexOfSingle("1", "1");
221 testNewIndexOfSingle("11", "1");
222 testNewIndexOfSingle("1", "11");
223 testNewIndexOfSingle("apache", "a");
224 testNewIndexOfSingle("apache", "p");
225 testNewIndexOfSingle("apache", "e");
226 testNewIndexOfSingle("apache", "x");
227 testNewIndexOfSingle("oraoraoraora", "r");
228 testNewIndexOfSingle("mudamudamudamuda", "d");
229 testNewIndexOfSingle("junk-ststarting", "starting");
230
231 final CharSequence noToString = new WrapperString("hello world") {
232 @Override
233 public String toString() {
234 throw new AssertionError("cs.toString() must not be called by indexOf");
235 }
236 };
237 assertEquals(6, CharSequenceUtils.indexOf(noToString, "world", 0));
238 assertEquals(-1, CharSequenceUtils.indexOf(noToString, "worlds", 0));
239 }
240
241 private void testNewIndexOfSingle(final CharSequence a, final CharSequence b) {
242 final int maxa = Math.max(a.length(), b.length());
243 for (int i = -maxa - 10; i <= maxa + 10; i++) {
244 testNewIndexOfSingle(a, b, i);
245 }
246 testNewIndexOfSingle(a, b, Integer.MIN_VALUE);
247 testNewIndexOfSingle(a, b, Integer.MAX_VALUE);
248 }
249
250 private void testNewIndexOfSingle(final CharSequence a, final CharSequence b, final int start) {
251 testNewIndexOfSingleSingle(a, b, start);
252 testNewIndexOfSingleSingle(b, a, start);
253 }
254
255 private void testNewIndexOfSingleSingle(final CharSequence a, final CharSequence b, final int start) {
256 assertEquals(a.toString().indexOf(b.toString(), start),
257 CharSequenceUtils.indexOf(new WrapperString(a.toString()), new WrapperString(b.toString()), start),
258 "testNewIndexOf fails! original : " + a + " seg : " + b + " start : " + start);
259 }
260
261 @Test
262 void testNewLastIndexOf() {
263 testNewLastIndexOfSingle("808087847-1321060740-635567660180086727-925755305", "-1321060740-635567660", 21);
264 testNewLastIndexOfSingle("", "");
265 testNewLastIndexOfSingle("1", "");
266 testNewLastIndexOfSingle("", "1");
267 testNewLastIndexOfSingle("1", "1");
268 testNewLastIndexOfSingle("11", "1");
269 testNewLastIndexOfSingle("1", "11");
270
271 testNewLastIndexOfSingle("apache", "a");
272 testNewLastIndexOfSingle("apache", "p");
273 testNewLastIndexOfSingle("apache", "e");
274 testNewLastIndexOfSingle("apache", "x");
275 testNewLastIndexOfSingle("oraoraoraora", "r");
276 testNewLastIndexOfSingle("mudamudamudamuda", "d");
277
278
279
280 testNewLastIndexOfSingle("junk-ststarting", "starting");
281
282 final Random random = new Random();
283 final StringBuilder seg = new StringBuilder();
284 while (seg.length() <= CharSequenceUtils.TO_STRING_LIMIT) {
285 seg.append(random.nextInt());
286 }
287 StringBuilder original = new StringBuilder(seg);
288 testNewLastIndexOfSingle(original, seg);
289 for (int i = 0; i < 100; i++) {
290 if (random.nextDouble() < 0.5) {
291 original.append(random.nextInt() % 10);
292 } else {
293 original = new StringBuilder().append(random.nextInt() % 100).append(original);
294 }
295 testNewLastIndexOfSingle(original, seg);
296 }
297 }
298
299 private void testNewLastIndexOfSingle(final CharSequence a, final CharSequence b) {
300 final int maxa = Math.max(a.length(), b.length());
301 for (int i = -maxa - 10; i <= maxa + 10; i++) {
302 testNewLastIndexOfSingle(a, b, i);
303 }
304 testNewLastIndexOfSingle(a, b, Integer.MIN_VALUE);
305 testNewLastIndexOfSingle(a, b, Integer.MAX_VALUE);
306 }
307
308 private void testNewLastIndexOfSingle(final CharSequence a, final CharSequence b, final int start) {
309 testNewLastIndexOfSingleSingle(a, b, start);
310 testNewLastIndexOfSingleSingle(b, a, start);
311 }
312
313 private void testNewLastIndexOfSingleSingle(final CharSequence a, final CharSequence b, final int start) {
314 assertEquals(a.toString().lastIndexOf(b.toString(), start),
315 CharSequenceUtils.lastIndexOf(new WrapperString(a.toString()), new WrapperString(b.toString()), start),
316 "testNewLastIndexOf fails! original : " + a + " seg : " + b + " start : " + start);
317 }
318
319 @Test
320 void testRegionMatches() {
321 for (final TestData data : TEST_DATA) {
322 new RunTest() {
323 @Override
324 boolean invoke() {
325 return data.source.regionMatches(data.ignoreCase, data.toffset, data.other, data.ooffset, data.len);
326 }
327 }.run(data, "String");
328 new RunTest() {
329 @Override
330 boolean invoke() {
331 return CharSequenceUtils.regionMatches(data.source, data.ignoreCase, data.toffset, data.other, data.ooffset, data.len);
332 }
333 }.run(data, "CSString");
334 new RunTest() {
335 @Override
336 boolean invoke() {
337 return CharSequenceUtils.regionMatches(new StringBuilder(data.source), data.ignoreCase, data.toffset, data.other, data.ooffset, data.len);
338 }
339 }.run(data, "CSNonString");
340 }
341 }
342
343
344
345
346
347
348
349
350 @Test
351 void testRegionMatchesSupplementaryCaseFold() {
352 assertRegionMatchesParity("\uD801\uDC00", true, 0, "\uD801\uDC28", 0, 2);
353 assertRegionMatchesParity("\uD801\uDC00", false, 0, "\uD801\uDC28", 0, 2);
354 assertRegionMatchesParity("\uD801\uDC28", true, 0, "\uD801\uDC00", 0, 2);
355 assertRegionMatchesParity("x\uD801\uDC00", true, 1, "\uD801\uDC28", 0, 2);
356 assertRegionMatchesParity("\uD801\uDC00", true, 0, "\uD801\uDC29", 0, 2);
357 }
358
359 @Test
360 void testSubSequence() {
361
362
363
364 assertNull(CharSequenceUtils.subSequence(null, -1));
365 assertNull(CharSequenceUtils.subSequence(null, 0));
366 assertNull(CharSequenceUtils.subSequence(null, 1));
367
368
369
370 assertEquals(StringUtils.EMPTY, CharSequenceUtils.subSequence(StringUtils.EMPTY, 0));
371 assertEquals("012", CharSequenceUtils.subSequence("012", 0));
372 assertEquals("12", CharSequenceUtils.subSequence("012", 1));
373 assertEquals("2", CharSequenceUtils.subSequence("012", 2));
374 assertEquals(StringUtils.EMPTY, CharSequenceUtils.subSequence("012", 3));
375 }
376
377 @Test
378 void testSubSequenceNegativeStart() {
379 assertIndexOutOfBoundsException(() -> CharSequenceUtils.subSequence(StringUtils.EMPTY, -1));
380 }
381
382 @Test
383 void testSubSequenceTooLong() {
384 assertIndexOutOfBoundsException(() -> CharSequenceUtils.subSequence(StringUtils.EMPTY, 1));
385 }
386
387 @Test
388 void testToCharArray() {
389 final StringBuilder builder = new StringBuilder("abcdefg");
390 final char[] expected = builder.toString().toCharArray();
391 assertArrayEquals(expected, CharSequenceUtils.toCharArray(builder));
392 assertArrayEquals(expected, CharSequenceUtils.toCharArray(builder.toString()));
393 assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, CharSequenceUtils.toCharArray(null));
394 }
395 }