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