JDK-8065554 : MatchResult should provide values of named-capturing groups
  • Type: Enhancement
  • Component: core-libs
  • Sub-Component: java.util.regex
  • Affected Version: 19
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2014-11-14
  • Updated: 2023-11-25
  • Resolved: 2022-09-29
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 20
20 b18Fixed
Related Reports
CSR :  
Duplicate :  
Duplicate :  
Duplicate :  
Duplicate :  
Relates :  
Relates :  
Description
A DESCRIPTION OF THE REQUEST :
Method  String group(String name) has been added in class java.util.regex.Matcher since 1.7 to deal with named-capturing groups.
Unfortunately, interface  java.util.regex.MatchResult has not been extended with the same method, even though now it would be possible with a default method without breaking backwards compatibility.
 

JUSTIFICATION :
Expressions like matcher.toMatchResult().group("MY GROUP") cannot be typechecked when matcher has static type  java.util.regex.Matcher, even though class java.util.regex.Matcher defines method  String group(String name)

Note that toMatchResult() is very useful  if one wants the result to be unaffected by subsequent operations performed upon the matcher; for instance, this is necessary if one has to synchronize the matcher with a character stream with methods Matcher region(int start, int end) or  Matcher reset(CharSequence input).
But toMatchResult()  is useless if one has to  deal with named-capturing groups, because method  String group(String name) cannot be invoked on the result of  toMatchResult()

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RequestTest {

   public static void main(String[] args) {
      Matcher matcher = Pattern.compile("(?<NUMBER>[0-9]+)|(?<BLANKS>\\s+)").matcher("42  0");
      matcher.lookingAt();
      MatchResult res = matcher.toMatchResult();
      matcher.region(res.end(), matcher.regionEnd());
      // assert res.group("NUMBER").equals("42"); // this line should compile, and assert should succeed
    }
}
     
ACTUAL -
The commented line in the code snippet above  cannot be compiled, as expected, since method group(String name)  cannot be found in interface  java.util.regex.MatchResult

---------- BEGIN SOURCE ----------
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RequestTest {

   public static void main(String[] args) {
      Matcher matcher = Pattern.compile("(?<NUMBER>[0-9]+)|(?<BLANKS>\\s+)").matcher("42  0");
      matcher.lookingAt();
      assert matcher.group("NUMBER").equals("42");
      MatchResult res = matcher.toMatchResult();
      matcher.region(res.end(), matcher.regionEnd());
      // assert matcher.group("NUMBER").equals("42"); // throws java.lang.IllegalStateException
      // assert res.group("NUMBER").equals("42"); // this line should compile, and assert should succeed
    }
}
    
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
One can either use method String group(int group), but this implies giving up using  named-capturing groups, or has to resort to reflection (!).

import java.lang.reflect.InvocationTargetException;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RequestReport {

	public static void main(String[] args) throws NoSuchMethodException,
			IllegalAccessException, IllegalArgumentException,
			InvocationTargetException, SecurityException {
		Matcher matcher = Pattern.compile("(?<NUMBER>[0-9]+)|(?<BLANKS>\\s+)").matcher("42  0");
		matcher.lookingAt();
		assert matcher.group("NUMBER").equals("42");
		MatchResult res = matcher.toMatchResult();
		matcher.region(res.end(), matcher.regionEnd());
		assert res.group(1).equals("42");
		assert res.getClass().getDeclaredMethod("group", String.class)
				.invoke(res, "NUMBER").equals("42");
		matcher.lookingAt();
		assert matcher.group("BLANKS") != null;
	}
}



Comments
Changeset: ce85cac9 Author: Raffaello Giulietti <rgiulietti@openjdk.org> Date: 2022-09-29 09:16:21 +0000 URL: https://git.openjdk.org/jdk/commit/ce85cac947158b4e1f554c55f726c923a49b1a41
29-09-2022

After continued discussion we've decided not to include hitEnd and requireEnd in MatchResult. The reason is that these are there primarily for Scanner (and potentially other code) which uses Matcher directly. The information returned by these methods is used to determine whether to call a matching method again, possibly after advancing the start position or adding additional input to the text being matched. Thus the information provided by these methods isn't useful to have on MatchResult.
26-08-2022

A pull request was submitted for review. URL: https://git.openjdk.org/jdk/pull/10000 Date: 2022-08-24 15:48:38 +0000
24-08-2022

On further reflection, it seems like the point of MatchResult is to provide a value that contains all of the results-related state of a Matcher. This obviously includes the stuff it currently has (strings and indexes of numbered groups). Named groups are an obvious addition. Previously I had mentioned the state of whether there is actually a match at all; this is returned as a boolean by Matcher's matching methods (find, matches, lookingAt), and it's kept in an internal field, but there otherwise doesn't seem to be a way to query the state from Matcher. If this were added to MatchResult, Matcher would also get it (because Matcher implements MatchResult). In addition, there are a couple other bits of result state in Matcher: hitEnd and requireEnd. Propagating these to MatchResult should also be considered. A careful inspection of Matcher's fields, even private ones, might reveal important bits of match state that should be preserved into a MatchResult and potentially exposed through APIs.
04-12-2020

Cay Horstmann points out that there is additional pressure to get named groups support on MatchResult -- specifically MatchResult.group(String) -- since there are now methods Matcher.results() and Scanner.findAll() that return Stream<MatchResult>. http://mail.openjdk.java.net/pipermail/core-libs-dev/2020-December/071930.html
04-12-2020

This is fairly simple to do by adding some default methods. An additional default method that would be useful is to add a query to determine whether the MatchResult actually has a match. A MatchResult without a match can be obtained thus: var matcher = pattern.matcher(string); var mr = matcher.toMatchResult(); The resulting MatchResult throws IllegalStateException from all methods (except groupCount()) which makes it unpleasant to use in certain circumstances.
04-10-2019

See JDK-8072984 for some possibilities about implementing this RFE.
12-02-2015