1 /* 2 * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /** 25 * @test 26 * @bug 8016846 8024341 8071479 8145006 27 * @summary Unit tests stream and lambda-based methods on Pattern and Matcher 28 * @library /lib/testlibrary/bootlib 29 * @build java.base/java.util.stream.OpTestCase 30 * @run testng/othervm PatternStreamTest 31 */ 32 33 package test.java.util.regex; 34 35 import static org.testng.Assert.*; 36 37 import android.compat.Compatibility; 38 import dalvik.annotation.compat.VersionCodes; 39 import dalvik.system.VMRuntime; 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.Collections; 43 import java.util.ConcurrentModificationException; 44 import java.util.List; 45 import java.util.concurrent.atomic.AtomicInteger; 46 import java.util.function.Supplier; 47 import java.util.regex.MatchResult; 48 import java.util.regex.Matcher; 49 import java.util.regex.Pattern; 50 import java.util.stream.Collectors; 51 import java.util.stream.Stream; 52 import org.openjdk.testlib.java.util.stream.LambdaTestHelpers; 53 import org.openjdk.testlib.java.util.stream.OpTestCase; 54 import org.openjdk.testlib.java.util.stream.TestData; 55 import org.testng.annotations.DataProvider; 56 import org.testng.annotations.Test; 57 58 @Test 59 public class PatternStreamTest extends OpTestCase { 60 61 @DataProvider(name = "Patterns") makeStreamTestData()62 public static Object[][] makeStreamTestData() { 63 // Each item must match the type signature of the consumer of this data 64 // String, String, Pattern 65 List<Object[]> data = new ArrayList<>(); 66 67 String description = "All matches"; 68 String input = "XXXXXX"; 69 Pattern pattern = Pattern.compile("X"); 70 data.add(new Object[]{description, input, pattern}); 71 72 description = "Bounded every other match"; 73 input = "XYXYXYYXYX"; 74 pattern = Pattern.compile("X"); 75 data.add(new Object[]{description, input, pattern}); 76 77 description = "Every other match"; 78 input = "YXYXYXYYXYXY"; 79 pattern = Pattern.compile("X"); 80 data.add(new Object[]{description, input, pattern}); 81 82 description = ""; 83 input = "awgqwefg1fefw4vssv1vvv1"; 84 pattern = Pattern.compile("4"); 85 data.add(new Object[]{description, input, pattern}); 86 87 input = "afbfq\u00a3abgwgb\u00a3awngnwggw\u00a3a\u00a3ahjrnhneerh"; 88 pattern = Pattern.compile("\u00a3a"); 89 data.add(new Object[]{description, input, pattern}); 90 91 input = "awgqwefg1fefw4vssv1vvv1"; 92 pattern = Pattern.compile("1"); 93 data.add(new Object[]{description, input, pattern}); 94 95 input = "a\u4ebafg1fefw\u4eba4\u9f9cvssv\u9f9c1v\u672c\u672cvv"; 96 pattern = Pattern.compile("1"); 97 data.add(new Object[]{description, input, pattern}); 98 99 input = "1\u56da23\u56da456\u56da7890"; 100 pattern = Pattern.compile("\u56da"); 101 data.add(new Object[]{description, input, pattern}); 102 103 input = "1\u56da23\u9f9c\u672c\u672c\u56da456\u56da\u9f9c\u672c7890"; 104 pattern = Pattern.compile("\u56da"); 105 data.add(new Object[]{description, input, pattern}); 106 107 description = "Empty input"; 108 input = ""; 109 pattern = Pattern.compile("\u56da"); 110 data.add(new Object[]{description, input, pattern}); 111 112 description = "Empty input with empty pattern"; 113 input = ""; 114 pattern = Pattern.compile(""); 115 data.add(new Object[]{description, input, pattern}); 116 117 description = "Multiple separators"; 118 input = "This is,testing: with\tdifferent separators."; 119 pattern = Pattern.compile("[ \t,:.]"); 120 data.add(new Object[]{description, input, pattern}); 121 122 description = "Repeated separators within and at end"; 123 input = "boo:and:foo"; 124 pattern = Pattern.compile("o"); 125 data.add(new Object[]{description, input, pattern}); 126 127 description = "Many repeated separators within and at end"; 128 input = "booooo:and:fooooo"; 129 pattern = Pattern.compile("o"); 130 data.add(new Object[]{description, input, pattern}); 131 132 description = "Many repeated separators before last match"; 133 input = "fooooo:"; 134 pattern = Pattern.compile("o"); 135 data.add(new Object[] {description, input, pattern}); 136 137 return data.toArray(new Object[0][]); 138 } 139 140 @Test(dataProvider = "Patterns") testPatternSplitAsStream(String description, String input, Pattern pattern)141 public void testPatternSplitAsStream(String description, String input, Pattern pattern) { 142 // Derive expected result from pattern.split 143 List<String> expected = Arrays.asList(pattern.split(input)); 144 145 // Android-added: Keep old behavior on Android 13 or below. http://b/286499139 146 if (input.isEmpty() 147 && !(VMRuntime.getSdkVersion() > VersionCodes.TIRAMISU 148 && Compatibility.isChangeEnabled( 149 Pattern.SPLIT_AS_STREAM_RETURNS_SINGLE_EMPTY_STRING))) { 150 expected = Collections.emptyList(); 151 } 152 153 Supplier<Stream<String>> ss = () -> pattern.splitAsStream(input); 154 withData(TestData.Factory.ofSupplier(description, ss)) 155 .stream(LambdaTestHelpers.identity()) 156 .expectedResult(expected) 157 .exercise(); 158 } 159 160 @Test(dataProvider = "Patterns") testReplaceFirst(String description, String input, Pattern pattern)161 public void testReplaceFirst(String description, String input, Pattern pattern) { 162 // Derive expected result from Matcher.replaceFirst(String ) 163 String expected = pattern.matcher(input).replaceFirst("R"); 164 String actual = pattern.matcher(input).replaceFirst(r -> "R"); 165 assertEquals(actual, expected); 166 } 167 168 @Test(dataProvider = "Patterns") testReplaceAll(String description, String input, Pattern pattern)169 public void testReplaceAll(String description, String input, Pattern pattern) { 170 // Derive expected result from Matcher.replaceAll(String ) 171 String expected = pattern.matcher(input).replaceAll("R"); 172 String actual = pattern.matcher(input).replaceAll(r -> "R"); 173 assertEquals(actual, expected); 174 175 // Derive expected result from Matcher.find 176 Matcher m = pattern.matcher(input); 177 int expectedMatches = 0; 178 while (m.find()) { 179 expectedMatches++; 180 } 181 AtomicInteger actualMatches = new AtomicInteger(); 182 pattern.matcher(input).replaceAll(r -> "R" + actualMatches.incrementAndGet()); 183 assertEquals(expectedMatches, actualMatches.get()); 184 } 185 186 @Test(dataProvider = "Patterns") testMatchResults(String description, String input, Pattern pattern)187 public void testMatchResults(String description, String input, Pattern pattern) { 188 // Derive expected result from Matcher.find 189 Matcher m = pattern.matcher(input); 190 List<MatchResultHolder> expected = new ArrayList<>(); 191 while (m.find()) { 192 expected.add(new MatchResultHolder(m)); 193 } 194 195 Supplier<Stream<MatchResult>> ss = () -> pattern.matcher(input).results(); 196 withData(TestData.Factory.ofSupplier(description, ss)) 197 .stream(s -> s.map(MatchResultHolder::new)) 198 .expectedResult(expected) 199 .exercise(); 200 } 201 202 @Test testLateBinding()203 public void testLateBinding() { 204 Pattern pattern = Pattern.compile(","); 205 206 StringBuilder sb = new StringBuilder("a,b,c,d,e"); 207 Stream<String> stream = pattern.splitAsStream(sb); 208 sb.setLength(3); 209 assertEquals(Arrays.asList("a", "b"), stream.collect(Collectors.toList())); 210 211 stream = pattern.splitAsStream(sb); 212 sb.append(",f,g"); 213 assertEquals(Arrays.asList("a", "b", "f", "g"), stream.collect(Collectors.toList())); 214 } 215 testFailfastMatchResults()216 public void testFailfastMatchResults() { 217 Pattern p = Pattern.compile("X"); 218 Matcher m = p.matcher("XX"); 219 220 Stream<MatchResult> s = m.results(); 221 m.find(); 222 // Should start on the second match 223 assertEquals(s.count(), 1); 224 225 // Fail fast without short-circuit 226 // Exercises Iterator.forEachRemaining 227 m.reset(); 228 try { 229 m.results().peek(mr -> m.reset()).count(); 230 fail(); 231 } catch (ConcurrentModificationException e) { 232 // Should reach here 233 } 234 235 m.reset(); 236 try { 237 m.results().peek(mr -> m.find()).count(); 238 fail(); 239 } catch (ConcurrentModificationException e) { 240 // Should reach here 241 } 242 243 // Fail fast with short-circuit 244 // Exercises Iterator.hasNext/next 245 m.reset(); 246 try { 247 m.results().peek(mr -> m.reset()).limit(2).count(); 248 fail(); 249 } catch (ConcurrentModificationException e) { 250 // Should reach here 251 } 252 253 m.reset(); 254 try { 255 m.results().peek(mr -> m.find()).limit(2).count(); 256 fail(); 257 } catch (ConcurrentModificationException e) { 258 // Should reach here 259 } 260 } 261 testFailfastReplace()262 public void testFailfastReplace() { 263 Pattern p = Pattern.compile("X"); 264 Matcher m = p.matcher("XX"); 265 266 // Fail fast without short-circuit 267 // Exercises Iterator.forEachRemaining 268 m.reset(); 269 try { 270 m.replaceFirst(mr -> { m.reset(); return "Y"; }); 271 fail(); 272 } catch (ConcurrentModificationException e) { 273 // Should reach here 274 } 275 276 m.reset(); 277 try { 278 m.replaceAll(mr -> { m.reset(); return "Y"; }); 279 fail(); 280 } catch (ConcurrentModificationException e) { 281 // Should reach here 282 } 283 } 284 285 // A holder of MatchResult that can compare 286 static class MatchResultHolder implements Comparable<MatchResultHolder> { 287 final MatchResult mr; 288 MatchResultHolder(Matcher m)289 MatchResultHolder(Matcher m) { 290 this(m.toMatchResult()); 291 } 292 MatchResultHolder(MatchResult mr)293 MatchResultHolder(MatchResult mr) { 294 this.mr = mr; 295 } 296 297 @Override compareTo(MatchResultHolder that)298 public int compareTo(MatchResultHolder that) { 299 int c = that.mr.group().compareTo(this.mr.group()); 300 if (c != 0) 301 return c; 302 303 c = Integer.compare(that.mr.start(), this.mr.start()); 304 if (c != 0) 305 return c; 306 307 c = Integer.compare(that.mr.end(), this.mr.end()); 308 if (c != 0) 309 return c; 310 311 c = Integer.compare(that.mr.groupCount(), this.mr.groupCount()); 312 if (c != 0) 313 return c; 314 315 for (int g = 0; g < this.mr.groupCount(); g++) { 316 c = that.mr.group(g).compareTo(this.mr.group(g)); 317 if (c != 0) 318 return c; 319 320 c = Integer.compare(that.mr.start(g), this.mr.start(g)); 321 if (c != 0) 322 return c; 323 324 c = Integer.compare(that.mr.end(g), this.mr.end(g)); 325 if (c != 0) 326 return c; 327 } 328 return 0; 329 } 330 331 @Override equals(Object that)332 public boolean equals(Object that) { 333 if (this == that) return true; 334 if (that == null || getClass() != that.getClass()) return false; 335 336 return this.compareTo((MatchResultHolder) that) == 0; 337 } 338 339 @Override hashCode()340 public int hashCode() { 341 return mr.group().hashCode(); 342 } 343 } 344 } 345