Package groovy.util.regex

# Class BalancedGroup

java.lang.Object
groovy.util.regex.BalancedGroup

public final class BalancedGroup extends Object
A node in the tree of balanced groups extracted by `find(java.lang.CharSequence, java.lang.String, java.lang.String)`, also exposed on `CharSequence` via `StringGroovyMethods.findBalancedGroups(CharSequence, String, String)`.

Java's `Pattern` has no .NET-style balancing groups (`(?<name1-name2>…)`). This type is both the structured result and the entry point for Groovy's stack-based equivalent: each node is one successfully closed span, with immediately nested spans as children (richer than .NET's flat `CaptureCollection` on a single group).

Offsets mirror .NET `Capture.Index` / `Length`: `getStart()` and `getEnd()` describe the range of `getMatchedString()` in the original input (half-open `[start, end)`). `getFullStart()` / `getFullEnd()` always cover the complete pair including delimiters, even when `BalancedGroup.MatchOptions.includeEdges()` is `false` (comparable to knowing both the balancing capture and the open/close match positions).

Parent links and nesting depth are wired when a node is attached as a child of another node during construction; root nodes have a `null` parent and depth `0`. Prefer `find(java.lang.CharSequence, java.lang.String, java.lang.String)` (or the GDK methods on `CharSequence`) as the public entry point — constructors are package-private because they perform one-shot parent wiring.

Since:
  6.0.0

- ## Nested Class Summary

  Nested Classes
  Modifier and Type
  Class
  Description
  `static final record`
  `BalancedGroup.MatchOptions`
  Options controlling balanced-group matching.

- ## Method Summary

  Modifier and Type
  Method
  Description
  `static List<BalancedGroup>`
  `find(CharSequence text, String openRegex, String closeRegex)`
  Finds balanced groups using `BalancedGroup.MatchOptions.defaults()`.
  `static List<BalancedGroup>`
  `find(CharSequence text, String openRegex, String closeRegex, BalancedGroup.MatchOptions options)`
  Finds balanced (nested) groups in `text` and returns them as a forest of `BalancedGroup` nodes.
  `List<BalancedGroup>`
  `getChildren()`
  Returns the immediately nested balanced groups.
  `int`
  `getDepth()`
  Nesting depth of this node (0 for a root).
  `int`
  `getEnd()`
  End index of `getMatchedString()` in the original input (exclusive).
  `int`
  `getFullEnd()`
  End index of the full balanced pair, always including the closing delimiter (exclusive).
  `int`
  `getFullStart()`
  Start index of the full balanced pair, always including the opening delimiter.
  `int`
  `getLength()`
  Length of `getMatchedString()` (`end - start`).
  `String`
  `getMatchedString()`
  Returns the text captured for this group.
  `BalancedGroup`
  `getParent()`
  Returns the enclosing group, if any.
  `int`
  `getStart()`
  Start index of `getMatchedString()` in the original input (inclusive).
  `String`
  `toString()`
  Returns the matched text of this group.

  ### Methods inherited from class java.lang.Object

  `clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait`

- ## Method Details

  - ### find

    public static List<BalancedGroup> find(CharSequence text, String openRegex, String closeRegex)
    Finds balanced groups using `BalancedGroup.MatchOptions.defaults()`.
    Parameters:
      `text` - text to scan
      `openRegex` - regex for an opening delimiter (must not be empty)
      `closeRegex` - regex for a closing delimiter (must not be empty)
    Returns:
      unmodifiable list of outermost groups (empty if none)
    Since:
      6.0.0
    See Also:
      - `find(CharSequence, String, String, MatchOptions)`

  - ### find

    public static List<BalancedGroup> find(CharSequence text, String openRegex, String closeRegex, BalancedGroup.MatchOptions options)
    Finds balanced (nested) groups in `text` and returns them as a forest of `BalancedGroup` nodes.

    Relation to .NET balancing groups: .NET keeps a capture stack per named group, pushes with `(?&lt;Open&gt;…)`, pops with `(?&lt;-Open&gt;…)` or `(?&lt;Between-Open&gt;…)`, and can require a fully empty stack via `(?(Open)(?!))`. This method uses the same push/pop idea on an explicit stack, but returns a hierarchical tree (children + parent) rather than a flat `CaptureCollection`, and always extracts every completed span in one left-to-right scan.

    Algorithm: matches of a combined tokenizer (`IGNORE`? / `OPEN` / `CLOSE`) are consumed in order. Nesting is resolved by the stack, not by recursive regex, so balancing itself does not introduce ReDoS; cost is proportional to the number of tokenizer hits times the cost of the user-supplied patterns. Compiled tokenizers are cached (bounded LRU).

    Fault tolerance (differs from a strict .NET `(?(Open)(?!))` pattern): unmatched closers are ignored; unclosed openers are dropped at end-of-input, but groups already completed inside them are promoted outward ("orphan rescue").

    Named capturing groups `OPEN`, `CLOSE`, and `IGNORE` are reserved for the tokenizer; do not use those names inside `openRegex`, `closeRegex`, or `BalancedGroup.MatchOptions.ignoreRegex()`.

    Parameters:
      `text` - text to scan
      `openRegex` - regex for an opening delimiter (must not be empty)
      `closeRegex` - regex for a closing delimiter (must not be empty)
      `options` - match options; `null` means `BalancedGroup.MatchOptions.defaults()`
    Returns:
      unmodifiable list of outermost groups (empty if none)
    Throws:
      `NullPointerException` - if `text`, `openRegex`, or `closeRegex` is `null`
      `IllegalArgumentException` - if `openRegex` or `closeRegex` is empty
      `PatternSyntaxException` - if a supplied pattern is not a valid Java regex
    Since:
      6.0.0

  - ### getMatchedString

    public String getMatchedString()
    Returns the text captured for this group.
    Returns:
      the matched text (never `null`); may include or exclude boundary delimiters depending on `BalancedGroup.MatchOptions.includeEdges()`

  - ### getStart

    public int getStart()
    Start index of `getMatchedString()` in the original input (inclusive). Comparable to .NET `Capture.Index`.
    Returns:
      the start offset

  - ### getEnd

    public int getEnd()
    End index of `getMatchedString()` in the original input (exclusive). Length is `getEnd() - getStart()`, like .NET `Capture.Length`.
    Returns:
      the end offset

  - ### getLength

    public int getLength()
    Length of `getMatchedString()` (`end - start`).
    Returns:
      the length

  - ### getFullStart

    public int getFullStart()
    Start index of the full balanced pair, always including the opening delimiter.
    Returns:
      the full-span start offset

  - ### getFullEnd

    public int getFullEnd()
    End index of the full balanced pair, always including the closing delimiter (exclusive).
    Returns:
      the full-span end offset

  - ### getChildren

    public List<BalancedGroup> getChildren()
    Returns the immediately nested balanced groups.
    Returns:
      an unmodifiable list of children; empty (never `null`) for a leaf

  - ### getParent

    public BalancedGroup getParent()
    Returns the enclosing group, if any.
    Returns:
      the parent node, or `null` if this is a root

  - ### getDepth

    public int getDepth()
    Nesting depth of this node (0 for a root).

    Resolved from the parent chain on first request and cached, along with every ancestor walked on the way, so resolving the depth of every node in a tree costs O(n) overall and any single node is O(1) once resolved. Assigning eagerly during construction would instead re-walk each subtree every time it is attached to a new parent, which the bottom-up assembly in `find(java.lang.CharSequence, java.lang.String, java.lang.String)` does once per closing delimiter — quadratic in the nesting. The walk is iterative because a deeply nested document must not consume a stack frame per level.

    Returns:
      the number of ancestors

  - ### toString

    public String toString()
    Returns the matched text of this group.
    Overrides:
      `toString` in class `Object`
    Returns:
      the same value as `getMatchedString()`