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 <pa...@asert.com.au>
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"

Reply via email to