8065554: MatchResult should provide values of named-capturing groups
Reviewed-by: smarks
This commit is contained in:
parent
1decdcee71
commit
ce85cac947
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2003, 2022, 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,6 +25,9 @@
|
|||||||
|
|
||||||
package java.util.regex;
|
package java.util.regex;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The result of a match operation.
|
* The result of a match operation.
|
||||||
*
|
*
|
||||||
@ -33,6 +36,15 @@ package java.util.regex;
|
|||||||
* groups and group boundaries can be seen but not modified through
|
* groups and group boundaries can be seen but not modified through
|
||||||
* a {@code MatchResult}.
|
* a {@code MatchResult}.
|
||||||
*
|
*
|
||||||
|
* @implNote
|
||||||
|
* Support for named groups is implemented by the default methods
|
||||||
|
* {@link #start(String)}, {@link #end(String)} and {@link #group(String)}.
|
||||||
|
* They all make use of the map returned by {@link #namedGroups()}, whose
|
||||||
|
* default implementation simply throws {@link UnsupportedOperationException}.
|
||||||
|
* It is thus sufficient to override {@link #namedGroups()} for these methods
|
||||||
|
* to work. However, overriding them directly might be preferable for
|
||||||
|
* performance or other reasons.
|
||||||
|
*
|
||||||
* @author Michael McCloskey
|
* @author Michael McCloskey
|
||||||
* @see Matcher
|
* @see Matcher
|
||||||
* @since 1.5
|
* @since 1.5
|
||||||
@ -48,7 +60,7 @@ public interface MatchResult {
|
|||||||
* If no match has yet been attempted,
|
* If no match has yet been attempted,
|
||||||
* or if the previous match operation failed
|
* or if the previous match operation failed
|
||||||
*/
|
*/
|
||||||
public int start();
|
int start();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the start index of the subsequence captured by the given group
|
* Returns the start index of the subsequence captured by the given group
|
||||||
@ -74,7 +86,38 @@ public interface MatchResult {
|
|||||||
* If there is no capturing group in the pattern
|
* If there is no capturing group in the pattern
|
||||||
* with the given index
|
* with the given index
|
||||||
*/
|
*/
|
||||||
public int start(int group);
|
int start(int group);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the start index of the subsequence captured by the given
|
||||||
|
* <a href="Pattern.html#groupname">named-capturing group</a> during the
|
||||||
|
* previous match operation.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* The name of a named-capturing group in this matcher's pattern
|
||||||
|
*
|
||||||
|
* @return The index of the first character captured by the group,
|
||||||
|
* or {@code -1} if the match was successful but the group
|
||||||
|
* itself did not match anything
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException
|
||||||
|
* If no match has yet been attempted,
|
||||||
|
* or if the previous match operation failed
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException
|
||||||
|
* If there is no capturing group in the pattern
|
||||||
|
* with the given name
|
||||||
|
*
|
||||||
|
* @implSpec
|
||||||
|
* The default implementation of this method invokes {@link #namedGroups()}
|
||||||
|
* to obtain the group number from the {@code name} argument, and uses it
|
||||||
|
* as argument to an invocation of {@link #start(int)}.
|
||||||
|
*
|
||||||
|
* @since 20
|
||||||
|
*/
|
||||||
|
default int start(String name) {
|
||||||
|
return start(groupNumber(name));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the offset after the last character matched.
|
* Returns the offset after the last character matched.
|
||||||
@ -85,7 +128,7 @@ public interface MatchResult {
|
|||||||
* If no match has yet been attempted,
|
* If no match has yet been attempted,
|
||||||
* or if the previous match operation failed
|
* or if the previous match operation failed
|
||||||
*/
|
*/
|
||||||
public int end();
|
int end();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the offset after the last character of the subsequence
|
* Returns the offset after the last character of the subsequence
|
||||||
@ -111,7 +154,38 @@ public interface MatchResult {
|
|||||||
* If there is no capturing group in the pattern
|
* If there is no capturing group in the pattern
|
||||||
* with the given index
|
* with the given index
|
||||||
*/
|
*/
|
||||||
public int end(int group);
|
int end(int group);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the offset after the last character of the subsequence
|
||||||
|
* captured by the given <a href="Pattern.html#groupname">named-capturing
|
||||||
|
* group</a> during the previous match operation.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* The name of a named-capturing group in this matcher's pattern
|
||||||
|
*
|
||||||
|
* @return The offset after the last character captured by the group,
|
||||||
|
* or {@code -1} if the match was successful
|
||||||
|
* but the group itself did not match anything
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException
|
||||||
|
* If no match has yet been attempted,
|
||||||
|
* or if the previous match operation failed
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException
|
||||||
|
* If there is no capturing group in the pattern
|
||||||
|
* with the given name
|
||||||
|
*
|
||||||
|
* @implSpec
|
||||||
|
* The default implementation of this method invokes {@link #namedGroups()}
|
||||||
|
* to obtain the group number from the {@code name} argument, and uses it
|
||||||
|
* as argument to an invocation of {@link #end(int)}.
|
||||||
|
*
|
||||||
|
* @since 20
|
||||||
|
*/
|
||||||
|
default int end(String name) {
|
||||||
|
return end(groupNumber(name));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the input subsequence matched by the previous match.
|
* Returns the input subsequence matched by the previous match.
|
||||||
@ -132,7 +206,7 @@ public interface MatchResult {
|
|||||||
* If no match has yet been attempted,
|
* If no match has yet been attempted,
|
||||||
* or if the previous match operation failed
|
* or if the previous match operation failed
|
||||||
*/
|
*/
|
||||||
public String group();
|
String group();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the input subsequence captured by the given group during the
|
* Returns the input subsequence captured by the given group during the
|
||||||
@ -170,7 +244,44 @@ public interface MatchResult {
|
|||||||
* If there is no capturing group in the pattern
|
* If there is no capturing group in the pattern
|
||||||
* with the given index
|
* with the given index
|
||||||
*/
|
*/
|
||||||
public String group(int group);
|
String group(int group);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the input subsequence captured by the given
|
||||||
|
* <a href="Pattern.html#groupname">named-capturing group</a> during the
|
||||||
|
* previous match operation.
|
||||||
|
*
|
||||||
|
* <p> If the match was successful but the group specified failed to match
|
||||||
|
* any part of the input sequence, then {@code null} is returned. Note
|
||||||
|
* that some groups, for example {@code (a*)}, match the empty string.
|
||||||
|
* This method will return the empty string when such a group successfully
|
||||||
|
* matches the empty string in the input. </p>
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* The name of a named-capturing group in this matcher's pattern
|
||||||
|
*
|
||||||
|
* @return The (possibly empty) subsequence captured by the named group
|
||||||
|
* during the previous match, or {@code null} if the group
|
||||||
|
* failed to match part of the input
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException
|
||||||
|
* If no match has yet been attempted,
|
||||||
|
* or if the previous match operation failed
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException
|
||||||
|
* If there is no capturing group in the pattern
|
||||||
|
* with the given name
|
||||||
|
*
|
||||||
|
* @implSpec
|
||||||
|
* The default implementation of this method invokes {@link #namedGroups()}
|
||||||
|
* to obtain the group number from the {@code name} argument, and uses it
|
||||||
|
* as argument to an invocation of {@link #group(int)}.
|
||||||
|
*
|
||||||
|
* @since 20
|
||||||
|
*/
|
||||||
|
default String group(String name) {
|
||||||
|
return group(groupNumber(name));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of capturing groups in this match result's pattern.
|
* Returns the number of capturing groups in this match result's pattern.
|
||||||
@ -184,6 +295,55 @@ public interface MatchResult {
|
|||||||
*
|
*
|
||||||
* @return The number of capturing groups in this matcher's pattern
|
* @return The number of capturing groups in this matcher's pattern
|
||||||
*/
|
*/
|
||||||
public int groupCount();
|
int groupCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an unmodifiable map from capturing group names to group numbers.
|
||||||
|
* If there are no named groups, returns an empty map.
|
||||||
|
*
|
||||||
|
* @return an unmodifiable map from capturing group names to group numbers
|
||||||
|
*
|
||||||
|
* @throws UnsupportedOperationException if the implementation does not
|
||||||
|
* support named groups.
|
||||||
|
*
|
||||||
|
* @implSpec The default implementation of this method always throws
|
||||||
|
* {@link UnsupportedOperationException}
|
||||||
|
*
|
||||||
|
* @apiNote
|
||||||
|
* This method must be overridden by an implementation that supports
|
||||||
|
* named groups.
|
||||||
|
*
|
||||||
|
* @since 20
|
||||||
|
*/
|
||||||
|
default Map<String,Integer> namedGroups() {
|
||||||
|
throw new UnsupportedOperationException("namedGroups()");
|
||||||
|
}
|
||||||
|
|
||||||
|
private int groupNumber(String name) {
|
||||||
|
Objects.requireNonNull(name, "Group name");
|
||||||
|
Integer number = namedGroups().get(name);
|
||||||
|
if (number != null) {
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("No group with name <" + name + ">");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether {@code this} contains a valid match from
|
||||||
|
* a previous match or find operation.
|
||||||
|
*
|
||||||
|
* @return whether {@code this} contains a valid match
|
||||||
|
*
|
||||||
|
* @throws UnsupportedOperationException if the implementation cannot report
|
||||||
|
* whether it has a match
|
||||||
|
*
|
||||||
|
* @implSpec The default implementation of this method always throws
|
||||||
|
* {@link UnsupportedOperationException}
|
||||||
|
*
|
||||||
|
* @since 20
|
||||||
|
*/
|
||||||
|
default boolean hasMatch() {
|
||||||
|
throw new UnsupportedOperationException("hasMatch()");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1999, 2022, 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
|
||||||
@ -27,6 +27,7 @@ package java.util.regex;
|
|||||||
|
|
||||||
import java.util.ConcurrentModificationException;
|
import java.util.ConcurrentModificationException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Spliterator;
|
import java.util.Spliterator;
|
||||||
@ -229,6 +230,8 @@ public final class Matcher implements MatchResult {
|
|||||||
*/
|
*/
|
||||||
int modCount;
|
int modCount;
|
||||||
|
|
||||||
|
private Map<String, Integer> namedGroups;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No default constructor.
|
* No default constructor.
|
||||||
*/
|
*/
|
||||||
@ -278,7 +281,8 @@ public final class Matcher implements MatchResult {
|
|||||||
this.last,
|
this.last,
|
||||||
groupCount(),
|
groupCount(),
|
||||||
this.groups.clone(),
|
this.groups.clone(),
|
||||||
text);
|
text,
|
||||||
|
namedGroups());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ImmutableMatchResult implements MatchResult {
|
private static class ImmutableMatchResult implements MatchResult {
|
||||||
@ -287,15 +291,18 @@ public final class Matcher implements MatchResult {
|
|||||||
private final int[] groups;
|
private final int[] groups;
|
||||||
private final int groupCount;
|
private final int groupCount;
|
||||||
private final String text;
|
private final String text;
|
||||||
|
private final Map<String, Integer> namedGroups;
|
||||||
|
|
||||||
ImmutableMatchResult(int first, int last, int groupCount,
|
ImmutableMatchResult(int first, int last, int groupCount,
|
||||||
int[] groups, String text)
|
int[] groups, String text,
|
||||||
|
Map<String, Integer> namedGroups)
|
||||||
{
|
{
|
||||||
this.first = first;
|
this.first = first;
|
||||||
this.last = last;
|
this.last = last;
|
||||||
this.groupCount = groupCount;
|
this.groupCount = groupCount;
|
||||||
this.groups = groups;
|
this.groups = groups;
|
||||||
this.text = text;
|
this.text = text;
|
||||||
|
this.namedGroups = namedGroups;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -307,8 +314,7 @@ public final class Matcher implements MatchResult {
|
|||||||
@Override
|
@Override
|
||||||
public int start(int group) {
|
public int start(int group) {
|
||||||
checkMatch();
|
checkMatch();
|
||||||
if (group < 0 || group > groupCount)
|
checkGroup(group);
|
||||||
throw new IndexOutOfBoundsException("No group " + group);
|
|
||||||
return groups[group * 2];
|
return groups[group * 2];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,8 +327,7 @@ public final class Matcher implements MatchResult {
|
|||||||
@Override
|
@Override
|
||||||
public int end(int group) {
|
public int end(int group) {
|
||||||
checkMatch();
|
checkMatch();
|
||||||
if (group < 0 || group > groupCount)
|
checkGroup(group);
|
||||||
throw new IndexOutOfBoundsException("No group " + group);
|
|
||||||
return groups[group * 2 + 1];
|
return groups[group * 2 + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,18 +345,33 @@ public final class Matcher implements MatchResult {
|
|||||||
@Override
|
@Override
|
||||||
public String group(int group) {
|
public String group(int group) {
|
||||||
checkMatch();
|
checkMatch();
|
||||||
if (group < 0 || group > groupCount)
|
checkGroup(group);
|
||||||
throw new IndexOutOfBoundsException("No group " + group);
|
if ((groups[group * 2] == -1) || (groups[group * 2 + 1] == -1))
|
||||||
if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
|
|
||||||
return null;
|
return null;
|
||||||
return text.subSequence(groups[group * 2], groups[group * 2 + 1]).toString();
|
return text.subSequence(groups[group * 2], groups[group * 2 + 1]).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Integer> namedGroups() {
|
||||||
|
return namedGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasMatch() {
|
||||||
|
return first >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkGroup(int group) {
|
||||||
|
if (group < 0 || group > groupCount)
|
||||||
|
throw new IndexOutOfBoundsException("No group " + group);
|
||||||
|
}
|
||||||
|
|
||||||
private void checkMatch() {
|
private void checkMatch() {
|
||||||
if (first < 0)
|
if (!hasMatch())
|
||||||
throw new IllegalStateException("No match found");
|
throw new IllegalStateException("No match found");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -446,8 +466,7 @@ public final class Matcher implements MatchResult {
|
|||||||
* or if the previous match operation failed
|
* or if the previous match operation failed
|
||||||
*/
|
*/
|
||||||
public int start() {
|
public int start() {
|
||||||
if (first < 0)
|
checkMatch();
|
||||||
throw new IllegalStateException("No match available");
|
|
||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -476,10 +495,8 @@ public final class Matcher implements MatchResult {
|
|||||||
* with the given index
|
* with the given index
|
||||||
*/
|
*/
|
||||||
public int start(int group) {
|
public int start(int group) {
|
||||||
if (first < 0)
|
checkMatch();
|
||||||
throw new IllegalStateException("No match available");
|
checkGroup(group);
|
||||||
if (group < 0 || group > groupCount())
|
|
||||||
throw new IndexOutOfBoundsException("No group " + group);
|
|
||||||
return groups[group * 2];
|
return groups[group * 2];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -518,8 +535,7 @@ public final class Matcher implements MatchResult {
|
|||||||
* or if the previous match operation failed
|
* or if the previous match operation failed
|
||||||
*/
|
*/
|
||||||
public int end() {
|
public int end() {
|
||||||
if (first < 0)
|
checkMatch();
|
||||||
throw new IllegalStateException("No match available");
|
|
||||||
return last;
|
return last;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -548,10 +564,8 @@ public final class Matcher implements MatchResult {
|
|||||||
* with the given index
|
* with the given index
|
||||||
*/
|
*/
|
||||||
public int end(int group) {
|
public int end(int group) {
|
||||||
if (first < 0)
|
checkMatch();
|
||||||
throw new IllegalStateException("No match available");
|
checkGroup(group);
|
||||||
if (group < 0 || group > groupCount())
|
|
||||||
throw new IndexOutOfBoundsException("No group " + group);
|
|
||||||
return groups[group * 2 + 1];
|
return groups[group * 2 + 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -640,10 +654,8 @@ public final class Matcher implements MatchResult {
|
|||||||
* with the given index
|
* with the given index
|
||||||
*/
|
*/
|
||||||
public String group(int group) {
|
public String group(int group) {
|
||||||
if (first < 0)
|
checkMatch();
|
||||||
throw new IllegalStateException("No match found");
|
checkGroup(group);
|
||||||
if (group < 0 || group > groupCount())
|
|
||||||
throw new IndexOutOfBoundsException("No group " + group);
|
|
||||||
if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
|
if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
|
||||||
return null;
|
return null;
|
||||||
return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
|
return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
|
||||||
@ -900,9 +912,7 @@ public final class Matcher implements MatchResult {
|
|||||||
* that does not exist in the pattern
|
* that does not exist in the pattern
|
||||||
*/
|
*/
|
||||||
public Matcher appendReplacement(StringBuffer sb, String replacement) {
|
public Matcher appendReplacement(StringBuffer sb, String replacement) {
|
||||||
// If no match, return error
|
checkMatch();
|
||||||
if (first < 0)
|
|
||||||
throw new IllegalStateException("No match available");
|
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
appendExpandedReplacement(replacement, result);
|
appendExpandedReplacement(replacement, result);
|
||||||
// Append the intervening text
|
// Append the intervening text
|
||||||
@ -991,8 +1001,7 @@ public final class Matcher implements MatchResult {
|
|||||||
*/
|
*/
|
||||||
public Matcher appendReplacement(StringBuilder sb, String replacement) {
|
public Matcher appendReplacement(StringBuilder sb, String replacement) {
|
||||||
// If no match, return error
|
// If no match, return error
|
||||||
if (first < 0)
|
checkMatch();
|
||||||
throw new IllegalStateException("No match available");
|
|
||||||
StringBuilder result = new StringBuilder();
|
StringBuilder result = new StringBuilder();
|
||||||
appendExpandedReplacement(replacement, result);
|
appendExpandedReplacement(replacement, result);
|
||||||
// Append the intervening text
|
// Append the intervening text
|
||||||
@ -1055,10 +1064,10 @@ public final class Matcher implements MatchResult {
|
|||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"capturing group name {" + gname +
|
"capturing group name {" + gname +
|
||||||
"} starts with digit character");
|
"} starts with digit character");
|
||||||
if (!parentPattern.namedGroups().containsKey(gname))
|
if (!namedGroups().containsKey(gname))
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"No group with name {" + gname + "}");
|
"No group with name {" + gname + "}");
|
||||||
refNum = parentPattern.namedGroups().get(gname);
|
refNum = namedGroups().get(gname);
|
||||||
cursor++;
|
cursor++;
|
||||||
} else {
|
} else {
|
||||||
// The first number is always a group
|
// The first number is always a group
|
||||||
@ -1796,10 +1805,47 @@ public final class Matcher implements MatchResult {
|
|||||||
*/
|
*/
|
||||||
int getMatchedGroupIndex(String name) {
|
int getMatchedGroupIndex(String name) {
|
||||||
Objects.requireNonNull(name, "Group name");
|
Objects.requireNonNull(name, "Group name");
|
||||||
if (first < 0)
|
checkMatch();
|
||||||
throw new IllegalStateException("No match found");
|
if (!namedGroups().containsKey(name))
|
||||||
if (!parentPattern.namedGroups().containsKey(name))
|
|
||||||
throw new IllegalArgumentException("No group with name <" + name + ">");
|
throw new IllegalArgumentException("No group with name <" + name + ">");
|
||||||
return parentPattern.namedGroups().get(name);
|
return namedGroups().get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkGroup(int group) {
|
||||||
|
if (group < 0 || group > groupCount())
|
||||||
|
throw new IndexOutOfBoundsException("No group " + group);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkMatch() {
|
||||||
|
if (!hasMatch())
|
||||||
|
throw new IllegalStateException("No match found");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* @return {@inheritDoc}
|
||||||
|
*
|
||||||
|
* @since {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, Integer> namedGroups() {
|
||||||
|
if (namedGroups == null) {
|
||||||
|
return namedGroups = parentPattern.namedGroups();
|
||||||
|
}
|
||||||
|
return namedGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* @return {@inheritDoc}
|
||||||
|
*
|
||||||
|
* @since {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean hasMatch() {
|
||||||
|
return first >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1843,7 +1843,7 @@ loop: for(int x=0, offset=0; x<nCodePoints; x++, offset+=len) {
|
|||||||
topClosureNodes = null;
|
topClosureNodes = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Integer> namedGroups() {
|
private Map<String, Integer> namedGroupsMap() {
|
||||||
Map<String, Integer> groups = namedGroups;
|
Map<String, Integer> groups = namedGroups;
|
||||||
if (groups == null) {
|
if (groups == null) {
|
||||||
namedGroups = groups = new HashMap<>(2);
|
namedGroups = groups = new HashMap<>(2);
|
||||||
@ -1851,6 +1851,18 @@ loop: for(int x=0, offset=0; x<nCodePoints; x++, offset+=len) {
|
|||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an unmodifiable map from capturing group names to group numbers.
|
||||||
|
* If there are no named groups, returns an empty map.
|
||||||
|
*
|
||||||
|
* @return an unmodifiable map from capturing group names to group numbers
|
||||||
|
*
|
||||||
|
* @since 20
|
||||||
|
*/
|
||||||
|
public Map<String, Integer> namedGroups() {
|
||||||
|
return Map.copyOf(namedGroupsMap());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to accumulate information about a subtree of the object graph
|
* Used to accumulate information about a subtree of the object graph
|
||||||
* so that optimizations can be applied to the subtree.
|
* so that optimizations can be applied to the subtree.
|
||||||
@ -2554,14 +2566,14 @@ loop: for(int x=0, offset=0; x<nCodePoints; x++, offset+=len) {
|
|||||||
if (read() != '<')
|
if (read() != '<')
|
||||||
throw error("\\k is not followed by '<' for named capturing group");
|
throw error("\\k is not followed by '<' for named capturing group");
|
||||||
String name = groupname(read());
|
String name = groupname(read());
|
||||||
if (!namedGroups().containsKey(name))
|
if (!namedGroupsMap().containsKey(name))
|
||||||
throw error("named capturing group <" + name + "> does not exist");
|
throw error("named capturing group <" + name + "> does not exist");
|
||||||
if (create) {
|
if (create) {
|
||||||
hasGroupRef = true;
|
hasGroupRef = true;
|
||||||
if (has(CASE_INSENSITIVE))
|
if (has(CASE_INSENSITIVE))
|
||||||
root = new CIBackRef(namedGroups().get(name), has(UNICODE_CASE));
|
root = new CIBackRef(namedGroupsMap().get(name), has(UNICODE_CASE));
|
||||||
else
|
else
|
||||||
root = new BackRef(namedGroups().get(name));
|
root = new BackRef(namedGroupsMap().get(name));
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
case 'l':
|
case 'l':
|
||||||
@ -3008,13 +3020,13 @@ loop: for(int x=0, offset=0; x<nCodePoints; x++, offset+=len) {
|
|||||||
if (ch != '=' && ch != '!') {
|
if (ch != '=' && ch != '!') {
|
||||||
// named captured group
|
// named captured group
|
||||||
String name = groupname(ch);
|
String name = groupname(ch);
|
||||||
if (namedGroups().containsKey(name))
|
if (namedGroupsMap().containsKey(name))
|
||||||
throw error("Named capturing group <" + name
|
throw error("Named capturing group <" + name
|
||||||
+ "> is already defined");
|
+ "> is already defined");
|
||||||
capturingGroup = true;
|
capturingGroup = true;
|
||||||
head = createGroup(false);
|
head = createGroup(false);
|
||||||
tail = root;
|
tail = root;
|
||||||
namedGroups().put(name, capturingGroupCount - 1);
|
namedGroupsMap().put(name, capturingGroupCount - 1);
|
||||||
head.next = expr(tail);
|
head.next = expr(tail);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
349
test/jdk/java/util/regex/NamedGroupsTests.java
Normal file
349
test/jdk/java/util/regex/NamedGroupsTests.java
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022, 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
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 8065554
|
||||||
|
* @run main NamedGroupsTests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.MatchResult;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class NamedGroupsTests {
|
||||||
|
|
||||||
|
/* An implementation purposely not overriding any default method */
|
||||||
|
private static class TestMatcherNoNamedGroups implements MatchResult {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int start() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int start(int group) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int end() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int end(int group) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String group() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String group(int group) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int groupCount() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
testMatchResultNoDefault();
|
||||||
|
|
||||||
|
testPatternNamedGroups();
|
||||||
|
testMatcherNamedGroups();
|
||||||
|
testMatchResultNamedGroups();
|
||||||
|
|
||||||
|
testMatcherHasMatch();
|
||||||
|
testMatchResultHasMatch();
|
||||||
|
|
||||||
|
testMatchResultStartEndGroupBeforeMatchOp();
|
||||||
|
testMatchResultStartEndGroupAfterMatchOp();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatchResultNoDefault() {
|
||||||
|
TestMatcherNoNamedGroups m = new TestMatcherNoNamedGroups();
|
||||||
|
try {
|
||||||
|
m.hasMatch();
|
||||||
|
} catch (UnsupportedOperationException e) { // swallowing intended
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
m.namedGroups();
|
||||||
|
} catch (UnsupportedOperationException e) { // swallowing intended
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
m.start("anyName");
|
||||||
|
} catch (UnsupportedOperationException e) { // swallowing intended
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
m.end("anyName");
|
||||||
|
} catch (UnsupportedOperationException e) { // swallowing intended
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
m.group("anyName");
|
||||||
|
} catch (UnsupportedOperationException e) { // swallowing intended
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatchResultStartEndGroupBeforeMatchOp() {
|
||||||
|
Matcher m = Pattern.compile("(?<some>.+?)(?<rest>.*)").matcher("abc");
|
||||||
|
try {
|
||||||
|
m.start("anyName");
|
||||||
|
} catch (IllegalStateException e) { // swallowing intended
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
m.end("anyName");
|
||||||
|
} catch (IllegalStateException e) { // swallowing intended
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
m.group("anyName");
|
||||||
|
} catch (IllegalStateException e) { // swallowing intended
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatchResultStartEndGroupAfterMatchOp() {
|
||||||
|
testMatchResultStartEndGroupNoMatch();
|
||||||
|
testMatchResultStartEndGroupWithMatch();
|
||||||
|
testMatchResultStartEndGroupNoMatchNoSuchGroup();
|
||||||
|
testMatchResultStartEndGroupWithMatchNoSuchGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatchResultStartEndGroupNoMatch() {
|
||||||
|
Pattern.compile("(?<some>.+?)(?<rest>.*)").matcher("")
|
||||||
|
.results()
|
||||||
|
.forEach(r -> {
|
||||||
|
if (r.start("some") >= 0) {
|
||||||
|
throw new RuntimeException("start(\"some\")");
|
||||||
|
}
|
||||||
|
if (r.start("rest") >= 0) {
|
||||||
|
throw new RuntimeException("start(\"rest\")");
|
||||||
|
}
|
||||||
|
if (r.end("some") >= 0) {
|
||||||
|
throw new RuntimeException("end(\"some\")");
|
||||||
|
}
|
||||||
|
if (r.end("rest") >= 0) {
|
||||||
|
throw new RuntimeException("end(\"rest\")");
|
||||||
|
}
|
||||||
|
if (r.group("some") != null) {
|
||||||
|
throw new RuntimeException("group(\"some\")");
|
||||||
|
}
|
||||||
|
if (r.group("rest") != null) {
|
||||||
|
throw new RuntimeException("group(\"rest\")");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatchResultStartEndGroupWithMatch() {
|
||||||
|
Pattern.compile("(?<some>.+?)(?<rest>.*)").matcher("abc")
|
||||||
|
.results()
|
||||||
|
.forEach(r -> {
|
||||||
|
if (r.start("some") < 0) {
|
||||||
|
throw new RuntimeException("start(\"some\")");
|
||||||
|
}
|
||||||
|
if (r.start("rest") < 0) {
|
||||||
|
throw new RuntimeException("start(\"rest\")");
|
||||||
|
}
|
||||||
|
if (r.end("some") < 0) {
|
||||||
|
throw new RuntimeException("end(\"some\")");
|
||||||
|
}
|
||||||
|
if (r.end("rest") < 0) {
|
||||||
|
throw new RuntimeException("end(\"rest\")");
|
||||||
|
}
|
||||||
|
if (r.group("some") == null) {
|
||||||
|
throw new RuntimeException("group(\"some\")");
|
||||||
|
}
|
||||||
|
if (r.group("rest") == null) {
|
||||||
|
throw new RuntimeException("group(\"rest\")");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatchResultStartEndGroupNoMatchNoSuchGroup() {
|
||||||
|
Pattern.compile("(?<some>.+?)(?<rest>.*)").matcher("")
|
||||||
|
.results()
|
||||||
|
.forEach(r -> {
|
||||||
|
try {
|
||||||
|
r.start("noSuchGroup");
|
||||||
|
} catch (IllegalArgumentException e) { // swallowing intended
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
r.end("noSuchGroup");
|
||||||
|
} catch (IllegalArgumentException e) { // swallowing intended
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
r.group("noSuchGroup");
|
||||||
|
} catch (IllegalArgumentException e) { // swallowing intended
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatchResultStartEndGroupWithMatchNoSuchGroup() {
|
||||||
|
Pattern.compile("(?<some>.+?)(?<rest>.*)").matcher("abc")
|
||||||
|
.results()
|
||||||
|
.forEach(r -> {
|
||||||
|
try {
|
||||||
|
r.start("noSuchGroup");
|
||||||
|
} catch (IllegalArgumentException e) { // swallowing intended
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
r.end("noSuchGroup");
|
||||||
|
} catch (IllegalArgumentException e) { // swallowing intended
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
r.group("noSuchGroup");
|
||||||
|
} catch (IllegalArgumentException e) { // swallowing intended
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatchResultHasMatch() {
|
||||||
|
testMatchResultHasMatchNoMatch();
|
||||||
|
testMatchResultHasMatchWithMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatchResultHasMatchNoMatch() {
|
||||||
|
Matcher m = Pattern.compile(".+").matcher("");
|
||||||
|
m.find();
|
||||||
|
if (m.toMatchResult().hasMatch()) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatchResultHasMatchWithMatch() {
|
||||||
|
Matcher m = Pattern.compile(".+").matcher("abc");
|
||||||
|
m.find();
|
||||||
|
if (!m.toMatchResult().hasMatch()) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatcherHasMatch() {
|
||||||
|
testMatcherHasMatchNoMatch();
|
||||||
|
testMatcherHasMatchWithMatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatcherHasMatchNoMatch() {
|
||||||
|
Matcher m = Pattern.compile(".+").matcher("");
|
||||||
|
m.find();
|
||||||
|
if (m.hasMatch()) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatcherHasMatchWithMatch() {
|
||||||
|
Matcher m = Pattern.compile(".+").matcher("abc");
|
||||||
|
m.find();
|
||||||
|
if (!m.hasMatch()) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatchResultNamedGroups() {
|
||||||
|
testMatchResultNamedGroupsNoNamedGroups();
|
||||||
|
testMatchResultNamedGroupsOneNamedGroup();
|
||||||
|
testMatchResultNamedGroupsTwoNamedGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatchResultNamedGroupsNoNamedGroups() {
|
||||||
|
if (!Pattern.compile(".*").matcher("")
|
||||||
|
.toMatchResult().namedGroups().isEmpty()) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatchResultNamedGroupsOneNamedGroup() {
|
||||||
|
if (!Pattern.compile("(?<all>.*)").matcher("")
|
||||||
|
.toMatchResult().namedGroups()
|
||||||
|
.equals(Map.of("all", 1))) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatchResultNamedGroupsTwoNamedGroups() {
|
||||||
|
if (!Pattern.compile("(?<some>.+?)(?<rest>.*)").matcher("")
|
||||||
|
.toMatchResult().namedGroups()
|
||||||
|
.equals(Map.of("some", 1, "rest", 2))) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatcherNamedGroups() {
|
||||||
|
testMatcherNamedGroupsNoNamedGroups();
|
||||||
|
testMatcherNamedGroupsOneNamedGroup();
|
||||||
|
testMatcherNamedGroupsTwoNamedGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatcherNamedGroupsNoNamedGroups() {
|
||||||
|
if (!Pattern.compile(".*").matcher("").namedGroups().isEmpty()) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatcherNamedGroupsOneNamedGroup() {
|
||||||
|
if (!Pattern.compile("(?<all>.*)").matcher("").namedGroups()
|
||||||
|
.equals(Map.of("all", 1))) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testMatcherNamedGroupsTwoNamedGroups() {
|
||||||
|
if (!Pattern.compile("(?<some>.+?)(?<rest>.*)").matcher("").namedGroups()
|
||||||
|
.equals(Map.of("some", 1, "rest", 2))) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testPatternNamedGroups() {
|
||||||
|
testPatternNamedGroupsNoNamedGroups();
|
||||||
|
testPatternNamedGroupsOneNamedGroup();
|
||||||
|
testPatternNamedGroupsTwoNamedGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testPatternNamedGroupsNoNamedGroups() {
|
||||||
|
if (!Pattern.compile(".*").namedGroups().isEmpty()) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testPatternNamedGroupsOneNamedGroup() {
|
||||||
|
if (!Pattern.compile("(?<all>.*)").namedGroups()
|
||||||
|
.equals(Map.of("all", 1))) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testPatternNamedGroupsTwoNamedGroups() {
|
||||||
|
if (!Pattern.compile("(?<some>.+?)(?<rest>.*)").namedGroups()
|
||||||
|
.equals(Map.of("some", 1, "rest", 2))) {
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user