8071479: Stream and lambdafication improvements to j.u.regex.Matcher
Reviewed-by: smarks, briangoetz, sherman
This commit is contained in:
parent
68b3fe02ad
commit
0405de7fca
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1999, 2015, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -25,7 +25,16 @@
|
|||||||
|
|
||||||
package java.util.regex;
|
package java.util.regex;
|
||||||
|
|
||||||
|
import java.util.ConcurrentModificationException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Spliterator;
|
||||||
|
import java.util.Spliterators;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An engine that performs match operations on a {@linkplain java.lang.CharSequence
|
* An engine that performs match operations on a {@linkplain java.lang.CharSequence
|
||||||
@ -208,6 +217,11 @@ public final class Matcher implements MatchResult {
|
|||||||
*/
|
*/
|
||||||
boolean anchoringBounds = true;
|
boolean anchoringBounds = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of times this matcher's state has been modified
|
||||||
|
*/
|
||||||
|
int modCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No default constructor.
|
* No default constructor.
|
||||||
*/
|
*/
|
||||||
@ -248,11 +262,76 @@ public final class Matcher implements MatchResult {
|
|||||||
* @since 1.5
|
* @since 1.5
|
||||||
*/
|
*/
|
||||||
public MatchResult toMatchResult() {
|
public MatchResult toMatchResult() {
|
||||||
Matcher result = new Matcher(this.parentPattern, text.toString());
|
return toMatchResult(text.toString());
|
||||||
result.first = this.first;
|
}
|
||||||
result.last = this.last;
|
|
||||||
result.groups = this.groups.clone();
|
private MatchResult toMatchResult(String text) {
|
||||||
return result;
|
return new ImmutableMatchResult(this.first,
|
||||||
|
this.last,
|
||||||
|
groupCount(),
|
||||||
|
this.groups.clone(),
|
||||||
|
text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ImmutableMatchResult implements MatchResult {
|
||||||
|
private final int first;
|
||||||
|
private final int last;
|
||||||
|
private final int[] groups;
|
||||||
|
private final int groupCount;
|
||||||
|
private final String text;
|
||||||
|
|
||||||
|
ImmutableMatchResult(int first, int last, int groupCount,
|
||||||
|
int groups[], String text)
|
||||||
|
{
|
||||||
|
this.first = first;
|
||||||
|
this.last = last;
|
||||||
|
this.groupCount = groupCount;
|
||||||
|
this.groups = groups;
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int start() {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int start(int group) {
|
||||||
|
if (group < 0 || group > groupCount)
|
||||||
|
throw new IndexOutOfBoundsException("No group " + group);
|
||||||
|
return groups[group * 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int end() {
|
||||||
|
return last;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int end(int group) {
|
||||||
|
if (group < 0 || group > groupCount)
|
||||||
|
throw new IndexOutOfBoundsException("No group " + group);
|
||||||
|
return groups[group * 2 + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int groupCount() {
|
||||||
|
return groupCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String group() {
|
||||||
|
return group(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String group(int group) {
|
||||||
|
if (group < 0 || group > groupCount)
|
||||||
|
throw new IndexOutOfBoundsException("No group " + group);
|
||||||
|
if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
|
||||||
|
return null;
|
||||||
|
return text.subSequence(groups[group * 2], groups[group * 2 + 1]).toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -284,6 +363,7 @@ public final class Matcher implements MatchResult {
|
|||||||
groups[i] = -1;
|
groups[i] = -1;
|
||||||
for (int i = 0; i < locals.length; i++)
|
for (int i = 0; i < locals.length; i++)
|
||||||
locals[i] = -1;
|
locals[i] = -1;
|
||||||
|
modCount++;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,6 +388,7 @@ public final class Matcher implements MatchResult {
|
|||||||
lastAppendPosition = 0;
|
lastAppendPosition = 0;
|
||||||
from = 0;
|
from = 0;
|
||||||
to = getTextLength();
|
to = getTextLength();
|
||||||
|
modCount++;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -803,6 +884,7 @@ public final class Matcher implements MatchResult {
|
|||||||
// Append the match substitution
|
// Append the match substitution
|
||||||
sb.append(result);
|
sb.append(result);
|
||||||
lastAppendPosition = last;
|
lastAppendPosition = last;
|
||||||
|
modCount++;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -892,6 +974,7 @@ public final class Matcher implements MatchResult {
|
|||||||
// Append the match substitution
|
// Append the match substitution
|
||||||
sb.append(result);
|
sb.append(result);
|
||||||
lastAppendPosition = last;
|
lastAppendPosition = last;
|
||||||
|
modCount++;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1077,6 +1160,183 @@ public final class Matcher implements MatchResult {
|
|||||||
return text.toString();
|
return text.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces every subsequence of the input sequence that matches the
|
||||||
|
* pattern with the result of applying the given replacer function to the
|
||||||
|
* match result of this matcher corresponding to that subsequence.
|
||||||
|
* Exceptions thrown by the function are relayed to the caller.
|
||||||
|
*
|
||||||
|
* <p> This method first resets this matcher. It then scans the input
|
||||||
|
* sequence looking for matches of the pattern. Characters that are not
|
||||||
|
* part of any match are appended directly to the result string; each match
|
||||||
|
* is replaced in the result by the applying the replacer function that
|
||||||
|
* returns a replacement string. Each replacement string may contain
|
||||||
|
* references to captured subsequences as in the {@link #appendReplacement
|
||||||
|
* appendReplacement} method.
|
||||||
|
*
|
||||||
|
* <p> Note that backslashes (<tt>\</tt>) and dollar signs (<tt>$</tt>) in
|
||||||
|
* a replacement string may cause the results to be different than if it
|
||||||
|
* were being treated as a literal replacement string. Dollar signs may be
|
||||||
|
* treated as references to captured subsequences as described above, and
|
||||||
|
* backslashes are used to escape literal characters in the replacement
|
||||||
|
* string.
|
||||||
|
*
|
||||||
|
* <p> Given the regular expression <tt>dog</tt>, the input
|
||||||
|
* <tt>"zzzdogzzzdogzzz"</tt>, and the function
|
||||||
|
* <tt>mr -> mr.group().toUpperCase()</tt>, an invocation of this method on
|
||||||
|
* a matcher for that expression would yield the string
|
||||||
|
* <tt>"zzzDOGzzzDOGzzz"</tt>.
|
||||||
|
*
|
||||||
|
* <p> Invoking this method changes this matcher's state. If the matcher
|
||||||
|
* is to be used in further matching operations then it should first be
|
||||||
|
* reset. </p>
|
||||||
|
*
|
||||||
|
* <p> The replacer function should not modify this matcher's state during
|
||||||
|
* replacement. This method will, on a best-effort basis, throw a
|
||||||
|
* {@link java.util.ConcurrentModificationException} if such modification is
|
||||||
|
* detected.
|
||||||
|
*
|
||||||
|
* <p> The state of each match result passed to the replacer function is
|
||||||
|
* guaranteed to be constant only for the duration of the replacer function
|
||||||
|
* call and only if the replacer function does not modify this matcher's
|
||||||
|
* state.
|
||||||
|
*
|
||||||
|
* @implNote
|
||||||
|
* This implementation applies the replacer function to this matcher, which
|
||||||
|
* is an instance of {@code MatchResult}.
|
||||||
|
*
|
||||||
|
* @param replacer
|
||||||
|
* The function to be applied to the match result of this matcher
|
||||||
|
* that returns a replacement string.
|
||||||
|
* @return The string constructed by replacing each matching subsequence
|
||||||
|
* with the result of applying the replacer function to that
|
||||||
|
* matched subsequence, substituting captured subsequences as
|
||||||
|
* needed.
|
||||||
|
* @throws NullPointerException if the replacer function is null
|
||||||
|
* @throws ConcurrentModificationException if it is detected, on a
|
||||||
|
* best-effort basis, that the replacer function modified this
|
||||||
|
* matcher's state
|
||||||
|
* @since 1.9
|
||||||
|
*/
|
||||||
|
public String replaceAll(Function<MatchResult, String> replacer) {
|
||||||
|
Objects.requireNonNull(replacer);
|
||||||
|
reset();
|
||||||
|
boolean result = find();
|
||||||
|
if (result) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
do {
|
||||||
|
int ec = modCount;
|
||||||
|
String replacement = replacer.apply(this);
|
||||||
|
if (ec != modCount)
|
||||||
|
throw new ConcurrentModificationException();
|
||||||
|
appendReplacement(sb, replacement);
|
||||||
|
result = find();
|
||||||
|
} while (result);
|
||||||
|
appendTail(sb);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
return text.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a stream of match results for each subsequence of the input
|
||||||
|
* sequence that matches the pattern. The match results occur in the
|
||||||
|
* same order as the matching subsequences in the input sequence.
|
||||||
|
*
|
||||||
|
* <p> Each match result is produced as if by {@link #toMatchResult()}.
|
||||||
|
*
|
||||||
|
* <p> This method does not reset this matcher. Matching starts on
|
||||||
|
* initiation of the terminal stream operation either at the beginning of
|
||||||
|
* this matcher's region, or, if the matcher has not since been reset, at
|
||||||
|
* the first character not matched by a previous match.
|
||||||
|
*
|
||||||
|
* <p> If the matcher is to be used for further matching operations after
|
||||||
|
* the terminal stream operation completes then it should be first reset.
|
||||||
|
*
|
||||||
|
* <p> This matcher's state should not be modified during execution of the
|
||||||
|
* returned stream's pipeline. The returned stream's source
|
||||||
|
* {@code Spliterator} is <em>fail-fast</em> and will, on a best-effort
|
||||||
|
* basis, throw a {@link java.util.ConcurrentModificationException} if such
|
||||||
|
* modification is detected.
|
||||||
|
*
|
||||||
|
* @return a sequential stream of match results.
|
||||||
|
* @since 1.9
|
||||||
|
*/
|
||||||
|
public Stream<MatchResult> results() {
|
||||||
|
class MatchResultIterator implements Iterator<MatchResult> {
|
||||||
|
// -ve for call to find, 0 for not found, 1 for found
|
||||||
|
int state = -1;
|
||||||
|
// State for concurrent modification checking
|
||||||
|
// -1 for uninitialized
|
||||||
|
int expectedCount = -1;
|
||||||
|
// The input sequence as a string, set once only after first find
|
||||||
|
// Avoids repeated conversion from CharSequence for each match
|
||||||
|
String textAsString;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MatchResult next() {
|
||||||
|
if (expectedCount >= 0 && expectedCount != modCount)
|
||||||
|
throw new ConcurrentModificationException();
|
||||||
|
|
||||||
|
if (!hasNext())
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
|
||||||
|
state = -1;
|
||||||
|
return toMatchResult(textAsString);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
if (state >= 0)
|
||||||
|
return state == 1;
|
||||||
|
|
||||||
|
// Defer throwing ConcurrentModificationException to when next
|
||||||
|
// or forEachRemaining is called. The is consistent with other
|
||||||
|
// fail-fast implementations.
|
||||||
|
if (expectedCount >= 0 && expectedCount != modCount)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
boolean found = find();
|
||||||
|
// Capture the input sequence as a string on first find
|
||||||
|
if (found && state < 0)
|
||||||
|
textAsString = text.toString();
|
||||||
|
state = found ? 1 : 0;
|
||||||
|
expectedCount = modCount;
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forEachRemaining(Consumer<? super MatchResult> action) {
|
||||||
|
if (expectedCount >= 0 && expectedCount != modCount)
|
||||||
|
throw new ConcurrentModificationException();
|
||||||
|
|
||||||
|
int s = state;
|
||||||
|
if (s == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Set state to report no more elements on further operations
|
||||||
|
state = 0;
|
||||||
|
expectedCount = -1;
|
||||||
|
|
||||||
|
// Perform a first find if required
|
||||||
|
if (s < 0 && !find())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Capture the input sequence as a string on first find
|
||||||
|
textAsString = text.toString();
|
||||||
|
|
||||||
|
do {
|
||||||
|
int ec = modCount;
|
||||||
|
action.accept(toMatchResult(textAsString));
|
||||||
|
if (ec != modCount)
|
||||||
|
throw new ConcurrentModificationException();
|
||||||
|
} while (find());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
|
||||||
|
new MatchResultIterator(), Spliterator.ORDERED | Spliterator.NONNULL), false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the first subsequence of the input sequence that matches the
|
* Replaces the first subsequence of the input sequence that matches the
|
||||||
* pattern with the given replacement string.
|
* pattern with the given replacement string.
|
||||||
@ -1122,6 +1382,79 @@ public final class Matcher implements MatchResult {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the first subsequence of the input sequence that matches the
|
||||||
|
* pattern with the result of applying the given replacer function to the
|
||||||
|
* match result of this matcher corresponding to that subsequence.
|
||||||
|
* Exceptions thrown by the replace function are relayed to the caller.
|
||||||
|
*
|
||||||
|
* <p> This method first resets this matcher. It then scans the input
|
||||||
|
* sequence looking for a match of the pattern. Characters that are not
|
||||||
|
* part of the match are appended directly to the result string; the match
|
||||||
|
* is replaced in the result by the applying the replacer function that
|
||||||
|
* returns a replacement string. The replacement string may contain
|
||||||
|
* references to captured subsequences as in the {@link #appendReplacement
|
||||||
|
* appendReplacement} method.
|
||||||
|
*
|
||||||
|
* <p>Note that backslashes (<tt>\</tt>) and dollar signs (<tt>$</tt>) in
|
||||||
|
* the replacement string may cause the results to be different than if it
|
||||||
|
* were being treated as a literal replacement string. Dollar signs may be
|
||||||
|
* treated as references to captured subsequences as described above, and
|
||||||
|
* backslashes are used to escape literal characters in the replacement
|
||||||
|
* string.
|
||||||
|
*
|
||||||
|
* <p> Given the regular expression <tt>dog</tt>, the input
|
||||||
|
* <tt>"zzzdogzzzdogzzz"</tt>, and the function
|
||||||
|
* <tt>mr -> mr.group().toUpperCase()</tt>, an invocation of this method on
|
||||||
|
* a matcher for that expression would yield the string
|
||||||
|
* <tt>"zzzDOGzzzdogzzz"</tt>.
|
||||||
|
*
|
||||||
|
* <p> Invoking this method changes this matcher's state. If the matcher
|
||||||
|
* is to be used in further matching operations then it should first be
|
||||||
|
* reset.
|
||||||
|
*
|
||||||
|
* <p> The replacer function should not modify this matcher's state during
|
||||||
|
* replacement. This method will, on a best-effort basis, throw a
|
||||||
|
* {@link java.util.ConcurrentModificationException} if such modification is
|
||||||
|
* detected.
|
||||||
|
*
|
||||||
|
* <p> The state of the match result passed to the replacer function is
|
||||||
|
* guaranteed to be constant only for the duration of the replacer function
|
||||||
|
* call and only if the replacer function does not modify this matcher's
|
||||||
|
* state.
|
||||||
|
*
|
||||||
|
* @implNote
|
||||||
|
* This implementation applies the replacer function to this matcher, which
|
||||||
|
* is an instance of {@code MatchResult}.
|
||||||
|
*
|
||||||
|
* @param replacer
|
||||||
|
* The function to be applied to the match result of this matcher
|
||||||
|
* that returns a replacement string.
|
||||||
|
* @return The string constructed by replacing the first matching
|
||||||
|
* subsequence with the result of applying the replacer function to
|
||||||
|
* the matched subsequence, substituting captured subsequences as
|
||||||
|
* needed.
|
||||||
|
* @throws NullPointerException if the replacer function is null
|
||||||
|
* @throws ConcurrentModificationException if it is detected, on a
|
||||||
|
* best-effort basis, that the replacer function modified this
|
||||||
|
* matcher's state
|
||||||
|
* @since 1.9
|
||||||
|
*/
|
||||||
|
public String replaceFirst(Function<MatchResult, String> replacer) {
|
||||||
|
Objects.requireNonNull(replacer);
|
||||||
|
reset();
|
||||||
|
if (!find())
|
||||||
|
return text.toString();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
int ec = modCount;
|
||||||
|
String replacement = replacer.apply(this);
|
||||||
|
if (ec != modCount)
|
||||||
|
throw new ConcurrentModificationException();
|
||||||
|
appendReplacement(sb, replacement);
|
||||||
|
appendTail(sb);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the limits of this matcher's region. The region is the part of the
|
* Sets the limits of this matcher's region. The region is the part of the
|
||||||
* input sequence that will be searched to find a match. Invoking this
|
* input sequence that will be searched to find a match. Invoking this
|
||||||
@ -1365,6 +1698,7 @@ public final class Matcher implements MatchResult {
|
|||||||
if (!result)
|
if (!result)
|
||||||
this.first = -1;
|
this.first = -1;
|
||||||
this.oldLast = this.last;
|
this.oldLast = this.last;
|
||||||
|
this.modCount++;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1387,6 +1721,7 @@ public final class Matcher implements MatchResult {
|
|||||||
if (!result)
|
if (!result)
|
||||||
this.first = -1;
|
this.first = -1;
|
||||||
this.oldLast = this.last;
|
this.oldLast = this.last;
|
||||||
|
this.modCount++;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2013, 2015 Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -23,8 +23,8 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @test
|
* @test
|
||||||
* @bug 8016846 8024341
|
* @bug 8016846 8024341 8071479
|
||||||
* @summary Unit tests for wrapping classes should delegate to default methods
|
* @summary Unit tests stream and lambda-based methods on Pattern and Matcher
|
||||||
* @library ../stream/bootlib
|
* @library ../stream/bootlib
|
||||||
* @build java.util.stream.OpTestCase
|
* @build java.util.stream.OpTestCase
|
||||||
* @run testng/othervm PatternStreamTest
|
* @run testng/othervm PatternStreamTest
|
||||||
@ -34,158 +34,283 @@ import org.testng.annotations.DataProvider;
|
|||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.ConcurrentModificationException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.regex.MatchResult;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.LambdaTestHelpers;
|
import java.util.stream.LambdaTestHelpers;
|
||||||
import java.util.stream.OpTestCase;
|
import java.util.stream.OpTestCase;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.stream.TestData;
|
import java.util.stream.TestData;
|
||||||
|
|
||||||
|
import static org.testng.Assert.*;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public class PatternStreamTest extends OpTestCase {
|
public class PatternStreamTest extends OpTestCase {
|
||||||
|
|
||||||
@DataProvider(name = "Stream<String>")
|
@DataProvider(name = "Patterns")
|
||||||
public static Object[][] makeStreamTestData() {
|
public static Object[][] makeStreamTestData() {
|
||||||
|
// Each item must match the type signature of the consumer of this data
|
||||||
|
// String, String, Pattern
|
||||||
List<Object[]> data = new ArrayList<>();
|
List<Object[]> data = new ArrayList<>();
|
||||||
|
|
||||||
String description = "";
|
String description = "All matches";
|
||||||
String input = "awgqwefg1fefw4vssv1vvv1";
|
String input = "XXXXXX";
|
||||||
Pattern pattern = Pattern.compile("4");
|
Pattern pattern = Pattern.compile("X");
|
||||||
List<String> expected = new ArrayList<>();
|
data.add(new Object[]{description, input, pattern});
|
||||||
expected.add("awgqwefg1fefw");
|
|
||||||
expected.add("vssv1vvv1");
|
|
||||||
|
|
||||||
// Must match the type signature of the consumer of this data, testStrings
|
description = "Bounded every other match";
|
||||||
// String, String, Pattern, List<String>
|
input = "XYXYXYYXYX";
|
||||||
data.add(new Object[]{description, input, pattern, expected});
|
pattern = Pattern.compile("X");
|
||||||
|
data.add(new Object[]{description, input, pattern});
|
||||||
|
|
||||||
|
description = "Every other match";
|
||||||
|
input = "YXYXYXYYXYXY";
|
||||||
|
pattern = Pattern.compile("X");
|
||||||
|
data.add(new Object[]{description, input, pattern});
|
||||||
|
|
||||||
|
description = "";
|
||||||
|
input = "awgqwefg1fefw4vssv1vvv1";
|
||||||
|
pattern = Pattern.compile("4");
|
||||||
|
data.add(new Object[]{description, input, pattern});
|
||||||
|
|
||||||
input = "afbfq\u00a3abgwgb\u00a3awngnwggw\u00a3a\u00a3ahjrnhneerh";
|
input = "afbfq\u00a3abgwgb\u00a3awngnwggw\u00a3a\u00a3ahjrnhneerh";
|
||||||
pattern = Pattern.compile("\u00a3a");
|
pattern = Pattern.compile("\u00a3a");
|
||||||
expected = new ArrayList<>();
|
data.add(new Object[]{description, input, pattern});
|
||||||
expected.add("afbfq");
|
|
||||||
expected.add("bgwgb");
|
|
||||||
expected.add("wngnwggw");
|
|
||||||
expected.add("");
|
|
||||||
expected.add("hjrnhneerh");
|
|
||||||
|
|
||||||
data.add(new Object[]{description, input, pattern, expected});
|
|
||||||
|
|
||||||
|
|
||||||
input = "awgqwefg1fefw4vssv1vvv1";
|
input = "awgqwefg1fefw4vssv1vvv1";
|
||||||
pattern = Pattern.compile("1");
|
pattern = Pattern.compile("1");
|
||||||
expected = new ArrayList<>();
|
data.add(new Object[]{description, input, pattern});
|
||||||
expected.add("awgqwefg");
|
|
||||||
expected.add("fefw4vssv");
|
|
||||||
expected.add("vvv");
|
|
||||||
|
|
||||||
data.add(new Object[]{description, input, pattern, expected});
|
|
||||||
|
|
||||||
|
|
||||||
input = "a\u4ebafg1fefw\u4eba4\u9f9cvssv\u9f9c1v\u672c\u672cvv";
|
input = "a\u4ebafg1fefw\u4eba4\u9f9cvssv\u9f9c1v\u672c\u672cvv";
|
||||||
pattern = Pattern.compile("1");
|
pattern = Pattern.compile("1");
|
||||||
expected = new ArrayList<>();
|
data.add(new Object[]{description, input, pattern});
|
||||||
expected.add("a\u4ebafg");
|
|
||||||
expected.add("fefw\u4eba4\u9f9cvssv\u9f9c");
|
|
||||||
expected.add("v\u672c\u672cvv");
|
|
||||||
|
|
||||||
data.add(new Object[]{description, input, pattern, expected});
|
|
||||||
|
|
||||||
|
|
||||||
input = "1\u56da23\u56da456\u56da7890";
|
input = "1\u56da23\u56da456\u56da7890";
|
||||||
pattern = Pattern.compile("\u56da");
|
pattern = Pattern.compile("\u56da");
|
||||||
expected = new ArrayList<>();
|
data.add(new Object[]{description, input, pattern});
|
||||||
expected.add("1");
|
|
||||||
expected.add("23");
|
|
||||||
expected.add("456");
|
|
||||||
expected.add("7890");
|
|
||||||
|
|
||||||
data.add(new Object[]{description, input, pattern, expected});
|
|
||||||
|
|
||||||
|
|
||||||
input = "1\u56da23\u9f9c\u672c\u672c\u56da456\u56da\u9f9c\u672c7890";
|
input = "1\u56da23\u9f9c\u672c\u672c\u56da456\u56da\u9f9c\u672c7890";
|
||||||
pattern = Pattern.compile("\u56da");
|
pattern = Pattern.compile("\u56da");
|
||||||
expected = new ArrayList<>();
|
data.add(new Object[]{description, input, pattern});
|
||||||
expected.add("1");
|
|
||||||
expected.add("23\u9f9c\u672c\u672c");
|
|
||||||
expected.add("456");
|
|
||||||
expected.add("\u9f9c\u672c7890");
|
|
||||||
|
|
||||||
data.add(new Object[]{description, input, pattern, expected});
|
|
||||||
|
|
||||||
|
|
||||||
description = "Empty input";
|
description = "Empty input";
|
||||||
input = "";
|
input = "";
|
||||||
pattern = Pattern.compile("\u56da");
|
pattern = Pattern.compile("\u56da");
|
||||||
expected = new ArrayList<>();
|
data.add(new Object[]{description, input, pattern});
|
||||||
expected.add("");
|
|
||||||
|
|
||||||
data.add(new Object[]{description, input, pattern, expected});
|
|
||||||
|
|
||||||
|
|
||||||
description = "Empty input with empty pattern";
|
description = "Empty input with empty pattern";
|
||||||
input = "";
|
input = "";
|
||||||
pattern = Pattern.compile("");
|
pattern = Pattern.compile("");
|
||||||
expected = new ArrayList<>();
|
data.add(new Object[]{description, input, pattern});
|
||||||
expected.add("");
|
|
||||||
|
|
||||||
data.add(new Object[]{description, input, pattern, expected});
|
|
||||||
|
|
||||||
|
|
||||||
description = "Multiple separators";
|
description = "Multiple separators";
|
||||||
input = "This is,testing: with\tdifferent separators.";
|
input = "This is,testing: with\tdifferent separators.";
|
||||||
pattern = Pattern.compile("[ \t,:.]");
|
pattern = Pattern.compile("[ \t,:.]");
|
||||||
expected = new ArrayList<>();
|
data.add(new Object[]{description, input, pattern});
|
||||||
expected.add("This");
|
|
||||||
expected.add("is");
|
|
||||||
expected.add("testing");
|
|
||||||
expected.add("");
|
|
||||||
expected.add("with");
|
|
||||||
expected.add("different");
|
|
||||||
expected.add("separators");
|
|
||||||
|
|
||||||
|
|
||||||
description = "Repeated separators within and at end";
|
description = "Repeated separators within and at end";
|
||||||
input = "boo:and:foo";
|
input = "boo:and:foo";
|
||||||
pattern = Pattern.compile("o");
|
pattern = Pattern.compile("o");
|
||||||
expected = new ArrayList<>();
|
data.add(new Object[]{description, input, pattern});
|
||||||
expected.add("b");
|
|
||||||
expected.add("");
|
|
||||||
expected.add(":and:f");
|
|
||||||
|
|
||||||
|
|
||||||
description = "Many repeated separators within and at end";
|
description = "Many repeated separators within and at end";
|
||||||
input = "booooo:and:fooooo";
|
input = "booooo:and:fooooo";
|
||||||
pattern = Pattern.compile("o");
|
pattern = Pattern.compile("o");
|
||||||
expected = new ArrayList<>();
|
data.add(new Object[]{description, input, pattern});
|
||||||
expected.add("b");
|
|
||||||
expected.add("");
|
|
||||||
expected.add("");
|
|
||||||
expected.add("");
|
|
||||||
expected.add("");
|
|
||||||
expected.add(":and:f");
|
|
||||||
|
|
||||||
description = "Many repeated separators before last match";
|
description = "Many repeated separators before last match";
|
||||||
input = "fooooo:";
|
input = "fooooo:";
|
||||||
pattern = Pattern.compile("o");
|
pattern = Pattern.compile("o");
|
||||||
expected = new ArrayList<>();
|
data.add(new Object[] {description, input, pattern});
|
||||||
expected.add("f");
|
|
||||||
expected.add("");
|
|
||||||
expected.add("");
|
|
||||||
expected.add("");
|
|
||||||
expected.add("");
|
|
||||||
expected.add(":");
|
|
||||||
|
|
||||||
data.add(new Object[] {description, input, pattern, expected});
|
|
||||||
return data.toArray(new Object[0][]);
|
return data.toArray(new Object[0][]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(dataProvider = "Stream<String>")
|
@Test(dataProvider = "Patterns")
|
||||||
public void testStrings(String description, String input, Pattern pattern, List<String> expected) {
|
public void testPatternSplitAsStream(String description, String input, Pattern pattern) {
|
||||||
|
// Derive expected result from pattern.split
|
||||||
|
List<String> expected = Arrays.asList(pattern.split(input));
|
||||||
|
|
||||||
Supplier<Stream<String>> ss = () -> pattern.splitAsStream(input);
|
Supplier<Stream<String>> ss = () -> pattern.splitAsStream(input);
|
||||||
withData(TestData.Factory.ofSupplier(description, ss))
|
withData(TestData.Factory.ofSupplier(description, ss))
|
||||||
.stream(LambdaTestHelpers.identity())
|
.stream(LambdaTestHelpers.identity())
|
||||||
.expectedResult(expected)
|
.expectedResult(expected)
|
||||||
.exercise();
|
.exercise();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "Patterns")
|
||||||
|
public void testReplaceFirst(String description, String input, Pattern pattern) {
|
||||||
|
// Derive expected result from Matcher.replaceFirst(String )
|
||||||
|
String expected = pattern.matcher(input).replaceFirst("R");
|
||||||
|
String actual = pattern.matcher(input).replaceFirst(r -> "R");
|
||||||
|
assertEquals(actual, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "Patterns")
|
||||||
|
public void testReplaceAll(String description, String input, Pattern pattern) {
|
||||||
|
// Derive expected result from Matcher.replaceAll(String )
|
||||||
|
String expected = pattern.matcher(input).replaceAll("R");
|
||||||
|
String actual = pattern.matcher(input).replaceAll(r -> "R");
|
||||||
|
assertEquals(actual, expected);
|
||||||
|
|
||||||
|
// Derive expected result from Matcher.find
|
||||||
|
Matcher m = pattern.matcher(input);
|
||||||
|
int expectedMatches = 0;
|
||||||
|
while (m.find()) {
|
||||||
|
expectedMatches++;
|
||||||
|
}
|
||||||
|
AtomicInteger actualMatches = new AtomicInteger();
|
||||||
|
pattern.matcher(input).replaceAll(r -> "R" + actualMatches.incrementAndGet());
|
||||||
|
assertEquals(expectedMatches, actualMatches.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(dataProvider = "Patterns")
|
||||||
|
public void testMatchResults(String description, String input, Pattern pattern) {
|
||||||
|
// Derive expected result from Matcher.find
|
||||||
|
Matcher m = pattern.matcher(input);
|
||||||
|
List<MatchResultHolder> expected = new ArrayList<>();
|
||||||
|
while (m.find()) {
|
||||||
|
expected.add(new MatchResultHolder(m));
|
||||||
|
}
|
||||||
|
|
||||||
|
Supplier<Stream<MatchResult>> ss = () -> pattern.matcher(input).results();
|
||||||
|
withData(TestData.Factory.ofSupplier(description, ss))
|
||||||
|
.stream(s -> s.map(MatchResultHolder::new))
|
||||||
|
.expectedResult(expected)
|
||||||
|
.exercise();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFailfastMatchResults() {
|
||||||
|
Pattern p = Pattern.compile("X");
|
||||||
|
Matcher m = p.matcher("XX");
|
||||||
|
|
||||||
|
Stream<MatchResult> s = m.results();
|
||||||
|
m.find();
|
||||||
|
// Should start on the second match
|
||||||
|
assertEquals(s.count(), 1);
|
||||||
|
|
||||||
|
// Fail fast without short-circuit
|
||||||
|
// Exercises Iterator.forEachRemaining
|
||||||
|
m.reset();
|
||||||
|
try {
|
||||||
|
m.results().peek(mr -> m.reset()).count();
|
||||||
|
fail();
|
||||||
|
} catch (ConcurrentModificationException e) {
|
||||||
|
// Should reach here
|
||||||
|
}
|
||||||
|
|
||||||
|
m.reset();
|
||||||
|
try {
|
||||||
|
m.results().peek(mr -> m.find()).count();
|
||||||
|
fail();
|
||||||
|
} catch (ConcurrentModificationException e) {
|
||||||
|
// Should reach here
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail fast with short-circuit
|
||||||
|
// Exercises Iterator.hasNext/next
|
||||||
|
m.reset();
|
||||||
|
try {
|
||||||
|
m.results().peek(mr -> m.reset()).limit(2).count();
|
||||||
|
fail();
|
||||||
|
} catch (ConcurrentModificationException e) {
|
||||||
|
// Should reach here
|
||||||
|
}
|
||||||
|
|
||||||
|
m.reset();
|
||||||
|
try {
|
||||||
|
m.results().peek(mr -> m.find()).limit(2).count();
|
||||||
|
fail();
|
||||||
|
} catch (ConcurrentModificationException e) {
|
||||||
|
// Should reach here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFailfastReplace() {
|
||||||
|
Pattern p = Pattern.compile("X");
|
||||||
|
Matcher m = p.matcher("XX");
|
||||||
|
|
||||||
|
// Fail fast without short-circuit
|
||||||
|
// Exercises Iterator.forEachRemaining
|
||||||
|
m.reset();
|
||||||
|
try {
|
||||||
|
m.replaceFirst(mr -> { m.reset(); return "Y"; });
|
||||||
|
fail();
|
||||||
|
} catch (ConcurrentModificationException e) {
|
||||||
|
// Should reach here
|
||||||
|
}
|
||||||
|
|
||||||
|
m.reset();
|
||||||
|
try {
|
||||||
|
m.replaceAll(mr -> { m.reset(); return "Y"; });
|
||||||
|
fail();
|
||||||
|
} catch (ConcurrentModificationException e) {
|
||||||
|
// Should reach here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A holder of MatchResult that can compare
|
||||||
|
static class MatchResultHolder implements Comparable<MatchResultHolder> {
|
||||||
|
final MatchResult mr;
|
||||||
|
|
||||||
|
MatchResultHolder(Matcher m) {
|
||||||
|
this(m.toMatchResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
MatchResultHolder(MatchResult mr) {
|
||||||
|
this.mr = mr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(MatchResultHolder that) {
|
||||||
|
int c = that.mr.group().compareTo(this.mr.group());
|
||||||
|
if (c != 0)
|
||||||
|
return c;
|
||||||
|
|
||||||
|
c = Integer.compare(that.mr.start(), this.mr.start());
|
||||||
|
if (c != 0)
|
||||||
|
return c;
|
||||||
|
|
||||||
|
c = Integer.compare(that.mr.end(), this.mr.end());
|
||||||
|
if (c != 0)
|
||||||
|
return c;
|
||||||
|
|
||||||
|
c = Integer.compare(that.mr.groupCount(), this.mr.groupCount());
|
||||||
|
if (c != 0)
|
||||||
|
return c;
|
||||||
|
|
||||||
|
for (int g = 0; g < this.mr.groupCount(); g++) {
|
||||||
|
c = that.mr.group(g).compareTo(this.mr.group(g));
|
||||||
|
if (c != 0)
|
||||||
|
return c;
|
||||||
|
|
||||||
|
c = Integer.compare(that.mr.start(g), this.mr.start(g));
|
||||||
|
if (c != 0)
|
||||||
|
return c;
|
||||||
|
|
||||||
|
c = Integer.compare(that.mr.end(g), this.mr.end(g));
|
||||||
|
if (c != 0)
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object that) {
|
||||||
|
if (this == that) return true;
|
||||||
|
if (that == null || getClass() != that.getClass()) return false;
|
||||||
|
|
||||||
|
return this.compareTo((MatchResultHolder) that) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return mr.group().hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
* 8027645 8035076 8039124 8035975
|
* 8027645 8035076 8039124 8035975
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.regex.*;
|
import java.util.regex.*;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
@ -291,24 +292,26 @@ public class RegExTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void nullArgumentTest() {
|
private static void nullArgumentTest() {
|
||||||
check(new Runnable() { public void run() { Pattern.compile(null); }});
|
check(() -> Pattern.compile(null));
|
||||||
check(new Runnable() { public void run() { Pattern.matches(null, null); }});
|
check(() -> Pattern.matches(null, null));
|
||||||
check(new Runnable() { public void run() { Pattern.matches("xyz", null);}});
|
check(() -> Pattern.matches("xyz", null));
|
||||||
check(new Runnable() { public void run() { Pattern.quote(null);}});
|
check(() -> Pattern.quote(null));
|
||||||
check(new Runnable() { public void run() { Pattern.compile("xyz").split(null);}});
|
check(() -> Pattern.compile("xyz").split(null));
|
||||||
check(new Runnable() { public void run() { Pattern.compile("xyz").matcher(null);}});
|
check(() -> Pattern.compile("xyz").matcher(null));
|
||||||
|
|
||||||
final Matcher m = Pattern.compile("xyz").matcher("xyz");
|
final Matcher m = Pattern.compile("xyz").matcher("xyz");
|
||||||
m.matches();
|
m.matches();
|
||||||
check(new Runnable() { public void run() { m.appendTail((StringBuffer)null);}});
|
check(() -> m.appendTail((StringBuffer) null));
|
||||||
check(new Runnable() { public void run() { m.appendTail((StringBuilder)null);}});
|
check(() -> m.appendTail((StringBuilder)null));
|
||||||
check(new Runnable() { public void run() { m.replaceAll(null);}});
|
check(() -> m.replaceAll((String) null));
|
||||||
check(new Runnable() { public void run() { m.replaceFirst(null);}});
|
check(() -> m.replaceAll((Function<MatchResult, String>)null));
|
||||||
check(new Runnable() { public void run() { m.appendReplacement((StringBuffer)null, null);}});
|
check(() -> m.replaceFirst((String)null));
|
||||||
check(new Runnable() { public void run() { m.appendReplacement((StringBuilder)null, null);}});
|
check(() -> m.replaceFirst((Function<MatchResult, String>) null));
|
||||||
check(new Runnable() { public void run() { m.reset(null);}});
|
check(() -> m.appendReplacement((StringBuffer)null, null));
|
||||||
check(new Runnable() { public void run() { Matcher.quoteReplacement(null);}});
|
check(() -> m.appendReplacement((StringBuilder)null, null));
|
||||||
//check(new Runnable() { public void run() { m.usePattern(null);}});
|
check(() -> m.reset(null));
|
||||||
|
check(() -> Matcher.quoteReplacement(null));
|
||||||
|
//check(() -> m.usePattern(null));
|
||||||
|
|
||||||
report("Null Argument");
|
report("Null Argument");
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user