This is an automated email from the ASF dual-hosted git repository.
sunlan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new 751059f0bb GROOVY-11701: Improved named-capturing group support for
Groovy matchers (#2254)
751059f0bb is described below
commit 751059f0bb16bbee489755349439634859333624
Author: Paul King <[email protected]>
AuthorDate: Thu Jun 19 09:06:03 2025 +1000
GROOVY-11701: Improved named-capturing group support for Groovy matchers
(#2254)
---
.../groovy/runtime/StringGroovyMethods.java | 144 ++++++++++++++++++++-
.../groovy/groovy/RegularExpressionsTest.groovy | 22 +++-
2 files changed, 159 insertions(+), 7 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/runtime/StringGroovyMethods.java
b/src/main/java/org/codehaus/groovy/runtime/StringGroovyMethods.java
index 1df73c29ad..7cdadcdf08 100644
--- a/src/main/java/org/codehaus/groovy/runtime/StringGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/StringGroovyMethods.java
@@ -47,6 +47,7 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
@@ -1547,6 +1548,146 @@ public class StringGroovyMethods extends
DefaultGroovyMethodsSupport {
return list;
}
+ // stateful matcher API is not very extensible; care should be taken if
using the matcher directly as it will potentially be mutated
+ // this mostly provides an efficient list delegate but will less
efficiently recalculates matches for named groups
+ static class GroupList implements List<String> {
+ private final Matcher matcher;
+ private final int count;
+ private final List<String> delegate;
+
+ public GroupList(Matcher matcher, int count) {
+ this.matcher = matcher;
+ this.count = count;
+ this.delegate = getGroups(matcher);
+ }
+
+ @Override
+ public int size() {
+ return delegate.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return delegate.isEmpty();
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ return delegate.contains(o);
+ }
+
+ @Override
+ public Iterator<String> iterator() {
+ return delegate.iterator();
+ }
+
+ @Override
+ public Object[] toArray() {
+ return delegate.toArray();
+ }
+
+ @Override
+ public boolean add(String s) {
+ throw new UnsupportedOperationException("Can't add to a Matcher
result");
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ throw new UnsupportedOperationException("Can't remove a Matcher
result");
+ }
+
+ @Override
+ public boolean addAll(Collection c) {
+ throw new UnsupportedOperationException("Can't addAll to a Matcher
result");
+ }
+
+ @Override
+ public boolean addAll(int index, Collection c) {
+ throw new UnsupportedOperationException("Can't addAll to a Matcher
result");
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException("Can't clear a Matcher
result");
+ }
+
+ @Override
+ public String get(int index) {
+ return delegate.get(index);
+ }
+
+ public String get(String name) {
+ matcher.reset();
+ for (int i = 0; i <= count; i++) {
+ matcher.find();
+ }
+ return matcher.group(name);
+ }
+
+ public String getAt(String name) {
+ return matcher.group(name);
+ }
+
+ @Override
+ public String set(int index, String element) {
+ throw new UnsupportedOperationException("Can't set a Matcher
result");
+ }
+
+ @Override
+ public void add(int index, String element) {
+ throw new UnsupportedOperationException("Can't add to a Matcher
result");
+ }
+
+ @Override
+ public String remove(int index) {
+ throw new UnsupportedOperationException("Can't remove a Matcher
result");
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ return delegate.indexOf(o);
+ }
+
+ @Override
+ public int lastIndexOf(Object o) {
+ return delegate.lastIndexOf(o);
+ }
+
+ @Override
+ public ListIterator<String> listIterator() {
+ return delegate.listIterator();
+ }
+
+ @Override
+ public ListIterator<String> listIterator(int index) {
+ return delegate.listIterator(index);
+ }
+
+ @Override
+ public List<String> subList(int fromIndex, int toIndex) {
+ return delegate.subList(fromIndex, toIndex);
+ }
+
+ @Override
+ public boolean retainAll(Collection<?> c) {
+ return delegate.retainAll(c);
+ }
+
+ @Override
+ public boolean removeAll(Collection<?> c) {
+ throw new UnsupportedOperationException("Can't remove from a
Matcher result");
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ return delegate.containsAll(c);
+ }
+
+ @Override
+ public Object[] toArray(Object[] a) {
+ return delegate.toArray(a);
+ }
+ }
/**
* Checks whether a Matcher contains a group or not.
*
@@ -1778,6 +1919,7 @@ public class StringGroovyMethods extends
DefaultGroovyMethodsSupport {
self.reset();
return new Iterator() {
private boolean done, found;
+ private int count;
@Override
public boolean hasNext() {
@@ -1804,7 +1946,7 @@ public class StringGroovyMethods extends
DefaultGroovyMethodsSupport {
if (hasGroup(self)) {
// yes, so return the specified group list
- return getGroups(self);
+ return new GroupList(self, count++);
} else {
// not using groups, so return the nth
// occurrence of the pattern
diff --git a/src/test/groovy/groovy/RegularExpressionsTest.groovy
b/src/test/groovy/groovy/RegularExpressionsTest.groovy
index d256f1ca81..b26d9658e0 100644
--- a/src/test/groovy/groovy/RegularExpressionsTest.groovy
+++ b/src/test/groovy/groovy/RegularExpressionsTest.groovy
@@ -254,20 +254,30 @@ final class RegularExpressionsTest {
switch ("cheesefoo") {
case ~"cheesecheese":
- assert false;
- break;
+ assert false
+ break
case ~"(cheese)(foo)":
- def m = Matcher.getLastMatcher();
+ def m = Matcher.getLastMatcher()
assert m.group(0) == "cheesefoo"
assert m.group(1) == "cheese"
assert m.group(2) == "foo"
assert m.groupCount() == 2
- break;
+ break
default:
assert false
}
}
+ @Test
+ void testNamedCategoryGroups() {
+ def issueRegex = /(?<project>\w*)-(?<number>\d+)/
+ def m = 'GROOVY-11701' =~ issueRegex
+ assert m[0][1] == 'GROOVY'
+ assert m[0]['project'] == 'GROOVY'
+ assert m[0].number == '11701'
+
+ }
+
@Test
void testRyhmeMatchGina() {
def myFairStringy = 'The rain in Spain stays mainly in the plain!'
@@ -388,7 +398,7 @@ final class RegularExpressionsTest {
@Test
void testFifth() {
def matcher = "\$abc." =~ "\\\$(.*)\\."
- matcher.matches(); // must be invoked
+ matcher.matches() // must be invoked
assert matcher.group(1) == "abc" // is one, not zero
assert matcher[0] == ["\$abc.", "abc"]
assert matcher[0][1] == "abc"
@@ -398,7 +408,7 @@ final class RegularExpressionsTest {
void testSixth() {
def matcher = "\$abc." =~ /\$(.*)\./ // no need to double-escape!
assert "\\\$(.*)\\." == /\$(.*)\./
- matcher.matches(); // must be invoked
+ matcher.matches() // must be invoked
assert matcher.group(1) == "abc" // is one, not zero
assert matcher[0] == ["\$abc.", "abc"]
assert matcher[0][1] == "abc"