View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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.util.Random;
32  import java.util.stream.IntStream;
33  import java.util.stream.Stream;
34  
35  import org.junit.jupiter.api.Test;
36  import org.junit.jupiter.params.ParameterizedTest;
37  import org.junit.jupiter.params.provider.Arguments;
38  import org.junit.jupiter.params.provider.MethodSource;
39  
40  /**
41   * Tests CharSequenceUtils
42   */
43  class CharSequenceUtilsTest extends AbstractLangTest {
44  
45      private abstract static class RunTest {
46  
47          abstract boolean invoke();
48  
49          void run(final TestData data, final String id) {
50              if (data.throwable != null) {
51                  assertThrows(data.throwable, this::invoke, id + " Expected " + data.throwable);
52              } else {
53                  final boolean stringCheck = invoke();
54                  assertEquals(data.expected, stringCheck, id + " Failed test " + data);
55              }
56          }
57  
58      }
59  
60      static class TestData {
61          final String source;
62          final boolean ignoreCase;
63          final int toffset;
64          final String other;
65          final int ooffset;
66          final int len;
67          final boolean expected;
68          final Class<? extends Throwable> throwable;
69  
70          TestData(final String source, final boolean ignoreCase, final int toffset, final String other, final int ooffset, final int len,
71                  final boolean expected) {
72              this.source = source;
73              this.ignoreCase = ignoreCase;
74              this.toffset = toffset;
75              this.other = other;
76              this.ooffset = ooffset;
77              this.len = len;
78              this.expected = expected;
79              this.throwable = null;
80          }
81  
82          TestData(final String source, final boolean ignoreCase, final int toffset, final String other, final int ooffset, final int len,
83                  final Class<? extends Throwable> throwable) {
84              this.source = source;
85              this.ignoreCase = ignoreCase;
86              this.toffset = toffset;
87              this.other = other;
88              this.ooffset = ooffset;
89              this.len = len;
90              this.expected = false;
91              this.throwable = throwable;
92          }
93  
94          @Override
95          public String toString() {
96              final StringBuilder sb = new StringBuilder();
97              sb.append(source).append("[").append(toffset).append("]");
98              sb.append(ignoreCase ? " caseblind " : " samecase ");
99              sb.append(other).append("[").append(ooffset).append("]");
100             sb.append(" ").append(len).append(" => ");
101             if (throwable != null) {
102                 sb.append(throwable);
103             } else {
104                 sb.append(expected);
105             }
106             return sb.toString();
107         }
108     }
109 
110     static class WrapperString implements CharSequence {
111         private final CharSequence inner;
112 
113         WrapperString(final CharSequence inner) {
114             this.inner = inner;
115         }
116 
117         @Override
118         public char charAt(final int index) {
119             return inner.charAt(index);
120         }
121 
122         @Override
123         public IntStream chars() {
124             return inner.chars();
125         }
126 
127         @Override
128         public IntStream codePoints() {
129             return inner.codePoints();
130         }
131 
132         @Override
133         public int length() {
134             return inner.length();
135         }
136 
137         @Override
138         public CharSequence subSequence(final int start, final int end) {
139             return inner.subSequence(start, end);
140         }
141 
142         @Override
143         public String toString() {
144             return inner.toString();
145         }
146     }
147 
148     private static final TestData[] TEST_DATA = {
149             // @formatter:off
150             //           Source  IgnoreCase Offset Other  Offset Length Result
151             new TestData("",     true,      -1,     "",    -1,    -1,    false),
152             new TestData("",     true,       0,     "",     0,     1,    false),
153             new TestData("a",    true,       0,     "abc",  0,     0,    true),
154             new TestData("a",    true,       0,     "abc",  0,     1,    true),
155             new TestData("a",    true,       0,     null,   0,     0,    NullPointerException.class),
156             new TestData(null,   true,       0,     null,   0,     0,    NullPointerException.class),
157             new TestData(null,   true,       0,     "",     0,     0,    NullPointerException.class),
158             new TestData("Abc",  true,       0,     "abc",  0,     3,    true),
159             new TestData("Abc",  false,      0,     "abc",  0,     3,    false),
160             new TestData("Abc",  true,       1,     "abc",  1,     2,    true),
161             new TestData("Abc",  false,      1,     "abc",  1,     2,    true),
162             new TestData("Abcd", true,       1,     "abcD", 1,     2,    true),
163             new TestData("Abcd", false,      1,     "abcD", 1,     2,    true),
164             // @formatter:on
165     };
166 
167     static Stream<Arguments> lastIndexWithStandardCharSequence() {
168         // @formatter:off
169         return Stream.of(
170             arguments("abc", "b", 2, 1),
171             arguments(new StringBuilder("abc"), "b", 2, 1),
172             arguments(new StringBuffer("abc"), "b", 2, 1),
173             arguments("abc", new StringBuilder("b"), 2, 1),
174             arguments(new StringBuilder("abc"), new StringBuilder("b"), 2, 1),
175             arguments(new StringBuffer("abc"), new StringBuffer("b"), 2, 1),
176             arguments(new StringBuilder("abc"), new StringBuffer("b"), 2, 1)
177         );
178         // @formatter:on
179     }
180 
181     @Test
182     void testConstructor() {
183         assertNotNull(new CharSequenceUtils());
184         final Constructor<?>[] cons = CharSequenceUtils.class.getDeclaredConstructors();
185         assertEquals(1, cons.length);
186         assertTrue(Modifier.isPublic(cons[0].getModifiers()));
187         assertTrue(Modifier.isPublic(CharSequenceUtils.class.getModifiers()));
188         assertFalse(Modifier.isFinal(CharSequenceUtils.class.getModifiers()));
189     }
190 
191     @ParameterizedTest
192     @MethodSource("lastIndexWithStandardCharSequence")
193     void testLastIndexOfWithDifferentCharSequences(final CharSequence cs, final CharSequence search, final int start, final int expected) {
194         assertEquals(expected, CharSequenceUtils.lastIndexOf(cs, search, start));
195     }
196 
197     @Test
198     void testNewLastIndexOf() {
199         testNewLastIndexOfSingle("808087847-1321060740-635567660180086727-925755305", "-1321060740-635567660", 21);
200         testNewLastIndexOfSingle("", "");
201         testNewLastIndexOfSingle("1", "");
202         testNewLastIndexOfSingle("", "1");
203         testNewLastIndexOfSingle("1", "1");
204         testNewLastIndexOfSingle("11", "1");
205         testNewLastIndexOfSingle("1", "11");
206 
207         testNewLastIndexOfSingle("apache", "a");
208         testNewLastIndexOfSingle("apache", "p");
209         testNewLastIndexOfSingle("apache", "e");
210         testNewLastIndexOfSingle("apache", "x");
211         testNewLastIndexOfSingle("oraoraoraora", "r");
212         testNewLastIndexOfSingle("mudamudamudamuda", "d");
213         // There is a route through checkLaterThan1#checkLaterThan1
214         // which only gets touched if there is a two letter (or more) partial match
215         // (in this case "st") earlier in the searched string.
216         testNewLastIndexOfSingle("junk-ststarting", "starting");
217 
218         final Random random = new Random();
219         final StringBuilder seg = new StringBuilder();
220         while (seg.length() <= CharSequenceUtils.TO_STRING_LIMIT) {
221             seg.append(random.nextInt());
222         }
223         StringBuilder original = new StringBuilder(seg);
224         testNewLastIndexOfSingle(original, seg);
225         for (int i = 0; i < 100; i++) {
226             if (random.nextDouble() < 0.5) {
227                 original.append(random.nextInt() % 10);
228             } else {
229                 original = new StringBuilder().append(random.nextInt() % 100).append(original);
230             }
231             testNewLastIndexOfSingle(original, seg);
232         }
233     }
234 
235     private void testNewLastIndexOfSingle(final CharSequence a, final CharSequence b) {
236         final int maxa = Math.max(a.length(), b.length());
237         for (int i = -maxa - 10; i <= maxa + 10; i++) {
238             testNewLastIndexOfSingle(a, b, i);
239         }
240         testNewLastIndexOfSingle(a, b, Integer.MIN_VALUE);
241         testNewLastIndexOfSingle(a, b, Integer.MAX_VALUE);
242     }
243 
244     private void testNewLastIndexOfSingle(final CharSequence a, final CharSequence b, final int start) {
245         testNewLastIndexOfSingleSingle(a, b, start);
246         testNewLastIndexOfSingleSingle(b, a, start);
247     }
248 
249     private void testNewLastIndexOfSingleSingle(final CharSequence a, final CharSequence b, final int start) {
250         assertEquals(a.toString().lastIndexOf(b.toString(), start),
251                 CharSequenceUtils.lastIndexOf(new WrapperString(a.toString()), new WrapperString(b.toString()), start),
252                 "testNewLastIndexOf fails! original : " + a + " seg : " + b + " start : " + start);
253     }
254 
255     @Test
256     void testRegionMatches() {
257         for (final TestData data : TEST_DATA) {
258             new RunTest() {
259                 @Override
260                 boolean invoke() {
261                     return data.source.regionMatches(data.ignoreCase, data.toffset, data.other, data.ooffset, data.len);
262                 }
263             }.run(data, "String");
264             new RunTest() {
265                 @Override
266                 boolean invoke() {
267                     return CharSequenceUtils.regionMatches(data.source, data.ignoreCase, data.toffset, data.other, data.ooffset, data.len);
268                 }
269             }.run(data, "CSString");
270             new RunTest() {
271                 @Override
272                 boolean invoke() {
273                     return CharSequenceUtils.regionMatches(new StringBuilder(data.source), data.ignoreCase, data.toffset, data.other, data.ooffset, data.len);
274                 }
275             }.run(data, "CSNonString");
276         }
277     }
278 
279     @Test
280     void testSubSequence() {
281         //
282         // null input
283         //
284         assertNull(CharSequenceUtils.subSequence(null, -1));
285         assertNull(CharSequenceUtils.subSequence(null, 0));
286         assertNull(CharSequenceUtils.subSequence(null, 1));
287         //
288         // non-null input
289         //
290         assertEquals(StringUtils.EMPTY, CharSequenceUtils.subSequence(StringUtils.EMPTY, 0));
291         assertEquals("012", CharSequenceUtils.subSequence("012", 0));
292         assertEquals("12", CharSequenceUtils.subSequence("012", 1));
293         assertEquals("2", CharSequenceUtils.subSequence("012", 2));
294         assertEquals(StringUtils.EMPTY, CharSequenceUtils.subSequence("012", 3));
295     }
296 
297     @Test
298     void testSubSequenceNegativeStart() {
299         assertIndexOutOfBoundsException(() -> CharSequenceUtils.subSequence(StringUtils.EMPTY, -1));
300     }
301 
302     @Test
303     void testSubSequenceTooLong() {
304         assertIndexOutOfBoundsException(() -> CharSequenceUtils.subSequence(StringUtils.EMPTY, 1));
305     }
306 
307     @Test
308     void testToCharArray() {
309         final StringBuilder builder = new StringBuilder("abcdefg");
310         final char[] expected = builder.toString().toCharArray();
311         assertArrayEquals(expected, CharSequenceUtils.toCharArray(builder));
312         assertArrayEquals(expected, CharSequenceUtils.toCharArray(builder.toString()));
313         assertArrayEquals(ArrayUtils.EMPTY_CHAR_ARRAY, CharSequenceUtils.toCharArray(null));
314     }
315 }