• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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