Class McpUriTemplateMatcher

java.lang.Object
org.apache.juneau.rest.server.mcp.McpUriTemplateMatcher

public final class McpUriTemplateMatcher extends Object
Hand-rolled, dependency-free compiler and reverse matcher for the narrow, reliably-invertible subset of RFC 6570 URI templates used by MCP resource-template reads.

A template is compiled once into an immutable matcher that exposes:

  • isReverseMatchable() - whether the template is one of the settled reversible forms (simple, reserved, fragment, path, label, or query scalar expressions, with no explode/prefix modifier and no ambiguous adjacency to another expression);
  • literalOctetCount() - the number of normalized literal octets outside {...} expressions, for registry specificity ranking;
  • variableCount() and variableNames() - the declared template variables, in declaration order; and
  • match(String) - reverse-matches a concrete URI against a reverse-matchable template, returning an immutable, insertion-ordered, percent-decoded variable map, or null if the URI does not match.
Reverse-matchable forms:

Non-explode, non-prefix simple scalar {var}, reserved scalar {+var}, fragment scalar {#var}, path segments {/var} / {/x,y}, label segments {.var} / {.x,y}, query start {?x} / {?x,y}, and query continuation {&x} / {&x,y}. All other legal RFC 6570 forms - exploded (*), prefixed (:n), matrix (;), multi-variable simple/reserved/ fragment expressions, and expressions with ambiguous adjacency to a neighboring expression - parse successfully (so they remain listable and completable) but are never selected for a template-backed read; match(String) always returns null for them.

Percent-encoding normalization:

Matching and literal-specificity counting follow RFC 3986 percent-normalization implemented fresh by this class: valid %HH triplets are hex-uppercased; triplets that decode to an unreserved octet (ALPHA / DIGIT / - / . / _ / ~) normalize to their literal character for comparison; triplets that decode to a reserved octet stay encoded while match boundaries are computed, so an encoded "%2F" is data inside a {var} segment rather than a path separator; captured values are then UTF-8 percent-decoded before being placed in the result map; and + is always a literal plus (this class never applies application/x-www-form-urlencoded plus-as-space semantics). A concrete URI with a malformed percent escape, or a capture whose decoded bytes are not valid UTF-8, never matches. This class deliberately does not reuse org.apache.juneau.commons.utils.StringUtils urlEncode/urlDecode/fixUrl/ urlEncodePath or the SVL EncodingFunctions: those implement form-urlencoded semantics (+ as space) and lack hex-case normalization, reserved-octet preservation, and malformed-escape rejection, all of which contradict the rules above.

Ambiguous-capture resolution:

A reversible template compiles to an alternating sequence of literal and capture pieces; because an expression with ambiguous adjacency to another expression is rejected as non-reverse-matchable (see above), a capture piece in a reverse-matchable template is always immediately followed by either a literal piece or the end of the template, never by another capture. When a reserved/fragment (unbounded) or path/label/simple (bounded-by-/) capture is immediately followed by a literal, match(String) resolves the capture's length via a single longest-to-shortest search for the position at which that immediately-following literal text occurs, using String.lastIndexOf(String, int). This deterministically implements "the longest capture that still permits the (immediately following) literal to match" without whole-suffix backtracking across multiple capture/literal boundaries, which keeps matching a bounded, linear-in-input scan per capture with no regex-style catastrophic backtracking risk, at the cost of not re-trying an earlier capture's length choice if a later, unrelated piece of the template fails to match. That scope narrowing is intentional and consistent with this matcher covering reliable inversion of a narrow subset, not a complete RFC 6570 engine.

Scope:

This matcher is intentionally MCP-local. It is not registered as a general-purpose RFC 3986/6570 utility; if a second real consumer outside MCP resource templates needs equivalent percent-normalization or URI-template parsing, that would justify extracting a shared helper into juneau-commons at that time, not before.

  • Method Details

    • compile

      public static McpUriTemplateMatcher compile(String uriTemplate)
      Parses and compiles a single RFC 6570 URI template.

      Registration syntax is validated independently of reverse-matchability: a syntactically legal template outside the reversible subset compiles successfully with isReverseMatchable() returning false, so it remains listable and completable even though match(String) always returns null for it.

      Parameters:
      uriTemplate - The URI template to compile. Must not be null or blank.
      Returns:
      A new compiled matcher.
      Throws:
      IllegalArgumentException - If the template is null/blank, has malformed braces, has a malformed percent escape, has invalid operator/varspec grammar, has a literal segment containing an invalid UTF-8 percent-escape sequence, or declares the same variable name more than once.
    • template

      public String template()
      The original source template this matcher was compiled from.
      Returns:
      The original template string. Never null.
    • isReverseMatchable

      public boolean isReverseMatchable()
      Whether this template is one of C4's reversible forms and therefore eligible for template-backed reads.
      Returns:
      true if match(String) can ever return a non-null result.
    • literalOctetCount

      public int literalOctetCount()
      The count of normalized literal URI octets outside {...} expressions.

      A normalized percent triplet counts as one literal octet, not three source characters. This is exposed purely as ranking metadata; this class applies no ranking itself. Registry code performing final ranking must not invent additional tie-breakers (operator type, capture width, or value length) beyond this count, variableCount(), and registration order.

      Returns:
      The literal octet count.
    • variableCount

      public int variableCount()
      The number of distinct variables declared by this template.
      Returns:
      The declared variable count.
    • variableNames

      The variables declared by this template, in declaration order.

      Populated regardless of isReverseMatchable(), so variables of a valid-but-not-reverse-matchable template remain available for exact-template-string completion lookups.

      Returns:
      An immutable, insertion-ordered list of declared variable names. Never null.
    • match

      public Map<String,String> match(String uri)
      Reverse-matches a concrete URI against this template.
      Parameters:
      uri - The concrete URI to match. Can be null, in which case this method returns null.
      Returns:
      An immutable, insertion-ordered, UTF-8-decoded map of variable name to decoded value, in declaration order, or null if this template is not reverse-matchable, the URI does not match, the URI contains a malformed percent escape, or a captured value's decoded bytes are not valid UTF-8.