8071479: Stream and lambdafication improvements to j.u.regex.Matcher
Reviewed-by: smarks, briangoetz, sherman
--- a/jdk/src/java.base/share/classes/java/util/regex/Matcher.java Tue Mar 03 10:30:44 2015 +0300
+++ b/jdk/src/java.base/share/classes/java/util/regex/Matcher.java Tue Mar 03 12:30:48 2015 +0100
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,7 +25,16 @@
package java.util.regex;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
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
@@ -209,6 +218,11 @@
boolean anchoringBounds = true;
/**
+ * Number of times this matcher's state has been modified
+ */
+ int modCount;
+
+ /**
* No default constructor.
*/
Matcher() {
@@ -248,11 +262,76 @@
* @since 1.5
*/
public MatchResult toMatchResult() {
- Matcher result = new Matcher(this.parentPattern, text.toString());
- result.first = this.first;
- result.last = this.last;
- result.groups = this.groups.clone();
- return result;
+ return toMatchResult(text.toString());
+ }
+
+ private MatchResult toMatchResult(String text) {
+ 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 @@
groups[i] = -1;
for (int i = 0; i < locals.length; i++)
locals[i] = -1;
+ modCount++;
return this;
}
@@ -308,6 +388,7 @@
lastAppendPosition = 0;
from = 0;
to = getTextLength();
+ modCount++;
return this;
}
@@ -803,6 +884,7 @@
// Append the match substitution
sb.append(result);
lastAppendPosition = last;
+ modCount++;
return this;
}
@@ -892,6 +974,7 @@
// Append the match substitution
sb.append(result);
lastAppendPosition = last;
+ modCount++;
return this;
}
@@ -1078,6 +1161,183 @@
}
/**
+ * 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
* pattern with the given replacement string.
*
@@ -1123,6 +1383,79 @@
}
/**
+ * 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
* input sequence that will be searched to find a match. Invoking this
* method resets the matcher, and then sets the region to start at the
@@ -1365,6 +1698,7 @@
if (!result)
this.first = -1;
this.oldLast = this.last;
+ this.modCount++;
return result;
}
@@ -1387,6 +1721,7 @@
if (!result)
this.first = -1;
this.oldLast = this.last;
+ this.modCount++;
return result;
}
--- a/jdk/test/java/util/regex/PatternStreamTest.java Tue Mar 03 10:30:44 2015 +0300
+++ b/jdk/test/java/util/regex/PatternStreamTest.java Tue Mar 03 12:30:48 2015 +0100
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -23,8 +23,8 @@
/**
* @test
- * @bug 8016846 8024341
- * @summary Unit tests for wrapping classes should delegate to default methods
+ * @bug 8016846 8024341 8071479
+ * @summary Unit tests stream and lambda-based methods on Pattern and Matcher
* @library ../stream/bootlib
* @build java.util.stream.OpTestCase
* @run testng/othervm PatternStreamTest
@@ -34,158 +34,283 @@
import org.testng.annotations.Test;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.ConcurrentModificationException;
import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
+import java.util.regex.MatchResult;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.LambdaTestHelpers;
import java.util.stream.OpTestCase;
import java.util.stream.Stream;
import java.util.stream.TestData;
+import static org.testng.Assert.*;
+
@Test
public class PatternStreamTest extends OpTestCase {
- @DataProvider(name = "Stream<String>")
+ @DataProvider(name = "Patterns")
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<>();
- String description = "";
- String input = "awgqwefg1fefw4vssv1vvv1";
- Pattern pattern = Pattern.compile("4");
- List<String> expected = new ArrayList<>();
- expected.add("awgqwefg1fefw");
- expected.add("vssv1vvv1");
+ String description = "All matches";
+ String input = "XXXXXX";
+ Pattern pattern = Pattern.compile("X");
+ data.add(new Object[]{description, input, pattern});
+
+ description = "Bounded every other match";
+ input = "XYXYXYYXYX";
+ pattern = Pattern.compile("X");
+ data.add(new Object[]{description, input, pattern});
- // Must match the type signature of the consumer of this data, testStrings
- // String, String, Pattern, List<String>
- data.add(new Object[]{description, input, pattern, expected});
+ 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";
pattern = Pattern.compile("\u00a3a");
- expected = new ArrayList<>();
- expected.add("afbfq");
- expected.add("bgwgb");
- expected.add("wngnwggw");
- expected.add("");
- expected.add("hjrnhneerh");
-
- data.add(new Object[]{description, input, pattern, expected});
-
+ data.add(new Object[]{description, input, pattern});
input = "awgqwefg1fefw4vssv1vvv1";
pattern = Pattern.compile("1");
- expected = new ArrayList<>();
- expected.add("awgqwefg");
- expected.add("fefw4vssv");
- expected.add("vvv");
-
- data.add(new Object[]{description, input, pattern, expected});
-
+ data.add(new Object[]{description, input, pattern});
input = "a\u4ebafg1fefw\u4eba4\u9f9cvssv\u9f9c1v\u672c\u672cvv";
pattern = Pattern.compile("1");
- expected = new ArrayList<>();
- expected.add("a\u4ebafg");
- expected.add("fefw\u4eba4\u9f9cvssv\u9f9c");
- expected.add("v\u672c\u672cvv");
-
- data.add(new Object[]{description, input, pattern, expected});
-
+ data.add(new Object[]{description, input, pattern});
input = "1\u56da23\u56da456\u56da7890";
pattern = Pattern.compile("\u56da");
- expected = new ArrayList<>();
- expected.add("1");
- expected.add("23");
- expected.add("456");
- expected.add("7890");
-
- data.add(new Object[]{description, input, pattern, expected});
-
+ data.add(new Object[]{description, input, pattern});
input = "1\u56da23\u9f9c\u672c\u672c\u56da456\u56da\u9f9c\u672c7890";
pattern = Pattern.compile("\u56da");
- expected = new ArrayList<>();
- expected.add("1");
- expected.add("23\u9f9c\u672c\u672c");
- expected.add("456");
- expected.add("\u9f9c\u672c7890");
-
- data.add(new Object[]{description, input, pattern, expected});
-
+ data.add(new Object[]{description, input, pattern});
description = "Empty input";
input = "";
pattern = Pattern.compile("\u56da");
- expected = new ArrayList<>();
- expected.add("");
-
- data.add(new Object[]{description, input, pattern, expected});
-
+ data.add(new Object[]{description, input, pattern});
description = "Empty input with empty pattern";
input = "";
pattern = Pattern.compile("");
- expected = new ArrayList<>();
- expected.add("");
-
- data.add(new Object[]{description, input, pattern, expected});
-
+ data.add(new Object[]{description, input, pattern});
description = "Multiple separators";
input = "This is,testing: with\tdifferent separators.";
pattern = Pattern.compile("[ \t,:.]");
- expected = new ArrayList<>();
- expected.add("This");
- expected.add("is");
- expected.add("testing");
- expected.add("");
- expected.add("with");
- expected.add("different");
- expected.add("separators");
-
+ data.add(new Object[]{description, input, pattern});
description = "Repeated separators within and at end";
input = "boo:and:foo";
pattern = Pattern.compile("o");
- expected = new ArrayList<>();
- expected.add("b");
- expected.add("");
- expected.add(":and:f");
-
+ data.add(new Object[]{description, input, pattern});
description = "Many repeated separators within and at end";
input = "booooo:and:fooooo";
pattern = Pattern.compile("o");
- expected = new ArrayList<>();
- expected.add("b");
- expected.add("");
- expected.add("");
- expected.add("");
- expected.add("");
- expected.add(":and:f");
+ data.add(new Object[]{description, input, pattern});
description = "Many repeated separators before last match";
input = "fooooo:";
pattern = Pattern.compile("o");
- expected = new ArrayList<>();
- expected.add("f");
- expected.add("");
- expected.add("");
- expected.add("");
- expected.add("");
- expected.add(":");
+ data.add(new Object[] {description, input, pattern});
- data.add(new Object[] {description, input, pattern, expected});
return data.toArray(new Object[0][]);
}
- @Test(dataProvider = "Stream<String>")
- public void testStrings(String description, String input, Pattern pattern, List<String> expected) {
+ @Test(dataProvider = "Patterns")
+ 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);
withData(TestData.Factory.ofSupplier(description, ss))
.stream(LambdaTestHelpers.identity())
.expectedResult(expected)
.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();
+ }
+ }
}
--- a/jdk/test/java/util/regex/RegExTest.java Tue Mar 03 10:30:44 2015 +0300
+++ b/jdk/test/java/util/regex/RegExTest.java Tue Mar 03 12:30:48 2015 +0100
@@ -35,6 +35,7 @@
* 8027645 8035076 8039124 8035975
*/
+import java.util.function.Function;
import java.util.regex.*;
import java.util.Random;
import java.io.*;
@@ -291,24 +292,26 @@
}
private static void nullArgumentTest() {
- check(new Runnable() { public void run() { Pattern.compile(null); }});
- check(new Runnable() { public void run() { Pattern.matches(null, null); }});
- check(new Runnable() { public void run() { Pattern.matches("xyz", null);}});
- check(new Runnable() { public void run() { Pattern.quote(null);}});
- check(new Runnable() { public void run() { Pattern.compile("xyz").split(null);}});
- check(new Runnable() { public void run() { Pattern.compile("xyz").matcher(null);}});
+ check(() -> Pattern.compile(null));
+ check(() -> Pattern.matches(null, null));
+ check(() -> Pattern.matches("xyz", null));
+ check(() -> Pattern.quote(null));
+ check(() -> Pattern.compile("xyz").split(null));
+ check(() -> Pattern.compile("xyz").matcher(null));
final Matcher m = Pattern.compile("xyz").matcher("xyz");
m.matches();
- check(new Runnable() { public void run() { m.appendTail((StringBuffer)null);}});
- check(new Runnable() { public void run() { m.appendTail((StringBuilder)null);}});
- check(new Runnable() { public void run() { m.replaceAll(null);}});
- check(new Runnable() { public void run() { m.replaceFirst(null);}});
- check(new Runnable() { public void run() { m.appendReplacement((StringBuffer)null, null);}});
- check(new Runnable() { public void run() { m.appendReplacement((StringBuilder)null, null);}});
- check(new Runnable() { public void run() { m.reset(null);}});
- check(new Runnable() { public void run() { Matcher.quoteReplacement(null);}});
- //check(new Runnable() { public void run() { m.usePattern(null);}});
+ check(() -> m.appendTail((StringBuffer) null));
+ check(() -> m.appendTail((StringBuilder)null));
+ check(() -> m.replaceAll((String) null));
+ check(() -> m.replaceAll((Function<MatchResult, String>)null));
+ check(() -> m.replaceFirst((String)null));
+ check(() -> m.replaceFirst((Function<MatchResult, String>) null));
+ check(() -> m.appendReplacement((StringBuffer)null, null));
+ check(() -> m.appendReplacement((StringBuilder)null, null));
+ check(() -> m.reset(null));
+ check(() -> Matcher.quoteReplacement(null));
+ //check(() -> m.usePattern(null));
report("Null Argument");
}