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