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