This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new d560956ed9 Add @PathRemainder annotation
d560956ed9 is described below

commit d560956ed9d496f26d88b8e9559ec32b0f91f6b7
Author: James Bognar <[email protected]>
AuthorDate: Wed Oct 15 09:28:23 2025 -0400

    Add @PathRemainder annotation
---
 TODO.txt                                           |  33 +--
 .../juneau/http/annotation/PathRemainder.java      | 179 +++++++++++++
 .../http/annotation/PathRemainderAnnotation.java   | 288 +++++++++++++++++++++
 .../org/apache/juneau/httppart/HttpPartSchema.java |  18 ++
 juneau-docs/docs/release-notes/9.2.0.md            |  41 +++
 juneau-docs/docs/topics/09.03.04.PathVariables.md  |  30 +++
 juneau-docs/docs/topics/11.10.08.Path.md           |  21 ++
 .../rest/client/remote/RemoteOperationArg.java     |  13 +
 .../java/org/apache/juneau/rest/RestContext.java   |   1 +
 .../apache/juneau/rest/arg/PathRemainderArg.java   | 102 ++++++++
 .../juneau/rest/annotation/PathRemainder_Test.java | 154 +++++++++++
 scripts/README-check-fluent-setter-overrides.md    | 164 ++++++++++++
 scripts/check-fluent-setter-overrides.py           | 273 +++++++++++++++++++
 13 files changed, 1286 insertions(+), 31 deletions(-)

diff --git a/TODO.txt b/TODO.txt
index bcff129e03..b45961b3d6 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -1,41 +1,12 @@
-ClassInfo improvements to getMethod (e.g. getMethodExact vs getMethod).Re-add 
@PathRemainder annotation.
+ClassInfo improvements to getMethod (e.g. getMethodExact vs getMethod).
+Re-add @PathRemainder annotation.
 Thrown NotFound causes - javax.servlet.ServletException: Invalid method 
response: 200
 
-Replace @Bean(findFluentSetters) with @FluentSetters.
 HttpResponse should use list of Headers and have a headers(Header...) method.
 HttpResponse should allow you to set code.
 HttpException subclasses can set status, but does it use code?
 HttpException should use list of Headers and have a headers(Header...) method.
 
-JsonSchema should have fluent getters and setters.
 
 @ResponseBody and @ResponseHeaders shouldn't be required on HttpResponse 
objects.
 
-This has to be easier:
-       @Enumerated(STRING)
-       @Schema(description="Routing types that this directive applies to.")
-       @NotEmpty(message="At least one copy type is required")
-       @Fetched(primary=true, fetcher=CopyTypes.class)
-       @SortNatural
-       @Beanp(type=TreeSet.class, params=CopyType.class)
-       protected Set<CopyType> copyTypes;
-
-       public Set<CopyType> getCopyTypes() {
-               return copyTypes;
-       }
-
-       public Directive setCopyTypes(Set<CopyType> value) {
-               this.copyTypes = value;
-               return this;
-       }
-       
-assertBodyMatches should tell you at which position it differs and make it 
obvious in the error.
-
-
-            .extracting(Directive::getLabel, Directive::getStatus, 
Directive::getStart, Directive::getEnd)
-            .containsOnly(d.getLabel(), DirectiveStatus.ACTIVE, d.getStart(), 
d.getEnd());
-            
-        assertThat(releaseService.sortReleases(Arrays.asList(sb0_224, sb0_226, 
sb0_222))).containsExactly(sb0_222, sb0_224, sb0_226);
-            
-
-Better support for SortedSet properties.
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/PathRemainder.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/PathRemainder.java
new file mode 100644
index 0000000000..e89a840a0e
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/PathRemainder.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.http.annotation;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.*;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.httppart.*;
+import org.apache.juneau.oapi.*;
+
+/**
+ * REST request path remainder annotation.
+ *
+ * <p>
+ * Identifies a POJO to be used as the path remainder (the part after the path 
match) on an HTTP request.
+ *
+ * <p>
+ * This is a specialized shortcut for <c><ja>@Path</ja>(<js>"/*"</js>)</c>.
+ *
+ * <p>
+ * Can be used in the following locations:
+ * <ul>
+ *     <li>Arguments and argument-types of server-side 
<ja>@RestOp</ja>-annotated methods.
+ *     <li>Arguments and argument-types of client-side 
<ja>@RemoteResource</ja>-annotated interfaces.
+ *     <li>Methods and return types of server-side and client-side 
<ja>@Request</ja>-annotated interfaces.
+ * </ul>
+ *
+ * <h5 class='topic'>Arguments and argument-types of server-side 
@RestOp-annotated methods</h5>
+ * <p>
+ * Annotation that can be applied to a parameter of a 
<ja>@RestOp</ja>-annotated method to identify it as the 
+ * path remainder after the path pattern match.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *     <ja>@RestGet</ja>(<js>"/myurl/{foo}/{bar}/*"</js>)
+ *     <jk>public void</jk> doGet(
+ *                     <ja>@Path</ja>(<js>"foo"</js>) String <jv>foo</jv>,
+ *                     <ja>@Path</ja>(<js>"bar"</js>) <jk>int</jk> 
<jv>bar</jv>,
+ *                     <ja>@PathRemainder</ja> String <jv>remainder</jv>,  
<jc>// Equivalent to @Path("/*")</jc>
+ *             ) {...}
+ * </p>
+ *
+ * <p>
+ * This is functionally equivalent to using 
<c><ja>@Path</ja>(<js>"/*"</js>)</c>, but provides a more intuitive name.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/Path";>@Path</a>
+ * </ul>
+ *
+ * <h5 class='topic'>Arguments and argument-types of client-side 
@RemoteResource-annotated interfaces</h5>
+ * <p>
+ * Annotation applied to Java method arguments of interface proxies to denote 
that they represent the path remainder on the request.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/PathRemainder";>@PathRemainder</a>
+ * </ul>
+ *
+ * <h5 class='topic'>Methods and return types of server-side and client-side 
@Request-annotated interfaces</h5>
+ * <p>
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/Request";>@Request</a>
+ * </ul>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='ja'>{@link Path}
+ * </ul>
+ *
+ * @since 9.2.0
+ */
+@Documented
+@Target({PARAMETER,METHOD,TYPE,FIELD})
+@Retention(RUNTIME)
+@Inherited
+@Repeatable(PathRemainderAnnotation.Array.class)
+@ContextApply(PathRemainderAnnotation.Applier.class)
+public @interface PathRemainder {
+
+       /**
+        * Default value for this parameter.
+        *
+        * @return The annotation value.
+        */
+       String def() default "";
+
+       /**
+        * Optional description for the exposed API.
+        *
+        * @return The annotation value.
+        */
+       String[] description() default {};
+
+       /**
+        * Dynamically apply this annotation to the specified classes.
+        *
+        * <h5 class='section'>See Also:</h5><ul>
+        *      <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/DynamicallyAppliedAnnotations";>Dynamically
 Applied Annotations</a>
+        * </ul>
+        *
+        * @return The annotation value.
+        */
+       String[] on() default {};
+
+       /**
+        * Dynamically apply this annotation to the specified classes.
+        *
+        * <p>
+        * Identical to {@link #on()} except allows you to specify class 
objects instead of a strings.
+        *
+        * <h5 class='section'>See Also:</h5><ul>
+        *      <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/DynamicallyAppliedAnnotations";>Dynamically
 Applied Annotations</a>
+        * </ul>
+        *
+        * @return The annotation value.
+        */
+       Class<?>[] onClass() default {};
+
+       /**
+        * Specifies the {@link HttpPartParser} class used for parsing strings 
to values.
+        *
+        * <p>
+        * Overrides for this part the part parser defined on the REST resource 
which by default is {@link OpenApiParser}.
+        *
+        * @return The annotation value.
+        */
+       Class<? extends HttpPartParser> parser() default 
HttpPartParser.Void.class;
+
+       /**
+        * <mk>schema</mk> field of the <a class='doclink' 
href='https://swagger.io/specification/v2#parameterObject'>Swagger Parameter 
Object</a>.
+        *
+        * <p>
+        * The schema defining the type used for parameter.
+        *
+        * <p>
+        * The {@link Schema @Schema} annotation can also be used standalone on 
the parameter or type.
+        * Values specified on this field override values specified on the 
type, and values specified on child types override values
+        * specified on parent types.
+        *
+        * <h5 class='section'>Used for:</h5>
+        * <ul class='spaced-list'>
+        *      <li>
+        *              Server-side schema-based parsing and parsing validation.
+        *      <li>
+        *              Server-side generated Swagger documentation.
+        *      <li>
+        *              Client-side schema-based serializing and serializing 
validation.
+        * </ul>
+        *
+        * @return The annotation value.
+        */
+       Schema schema() default @Schema;
+
+       /**
+        * Specifies the {@link HttpPartSerializer} class used for serializing 
values to strings.
+        *
+        * <p>
+        * Overrides for this part the part serializer defined on the REST 
client which by default is {@link OpenApiSerializer}.
+        *
+        * @return The annotation value.
+        */
+       Class<? extends HttpPartSerializer> serializer() default 
HttpPartSerializer.Void.class;
+}
+
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/PathRemainderAnnotation.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/PathRemainderAnnotation.java
new file mode 100644
index 0000000000..c8d790650f
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/PathRemainderAnnotation.java
@@ -0,0 +1,288 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.http.annotation;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+import static org.apache.juneau.common.utils.Utils.*;
+import static org.apache.juneau.internal.ArrayUtils.*;
+
+import java.lang.annotation.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.httppart.*;
+import org.apache.juneau.reflect.*;
+import org.apache.juneau.svl.*;
+
+/**
+ * Utility classes and methods for the {@link PathRemainder @PathRemainder} 
annotation.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='ja'>{@link PathRemainder}
+ *     <li class='ja'>{@link Path}
+ * </ul>
+ *
+ * @since 9.2.0
+ */
+public class PathRemainderAnnotation {
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Static
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /** Default value */
+       public static final PathRemainder DEFAULT = create().build();
+
+       /**
+        * Instantiates a new builder for this class.
+        *
+        * @return A new builder object.
+        */
+       public static Builder create() {
+               return new Builder();
+       }
+
+       /**
+        * Instantiates a new builder for this class.
+        *
+        * @param on The targets this annotation applies to.
+        * @return A new builder object.
+        */
+       public static Builder create(Class<?>...on) {
+               return create().on(on);
+       }
+
+       /**
+        * Instantiates a new builder for this class.
+        *
+        * @param on The targets this annotation applies to.
+        * @return A new builder object.
+        */
+       public static Builder create(String...on) {
+               return create().on(on);
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified annotation contains all 
default values.
+        *
+        * @param a The annotation to check.
+        * @return <jk>true</jk> if the specified annotation contains all 
default values.
+        */
+       public static boolean empty(PathRemainder a) {
+               return a == null || DEFAULT.equals(a);
+       }
+
+       /**
+        * Finds the default value from the specified list of annotations.
+        *
+        * @param pi The parameter.
+        * @return The last matching default value, or {@link Value#empty()} if 
not found.
+        */
+       public static Value<String> findDef(ParamInfo pi) {
+               Value<String> n = Value.empty();
+               pi.forEachAnnotation(PathRemainder.class, x -> 
isNotEmpty(x.def()), x -> n.set(x.def()));
+               return n;
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Builder
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Builder class.
+        *
+        * <h5 class='section'>See Also:</h5><ul>
+        *      <li class='jm'>{@link 
org.apache.juneau.BeanContext.Builder#annotations(Annotation...)}
+        * </ul>
+        */
+       public static class Builder extends 
TargetedAnnotationTMFBuilder<Builder> {
+
+               Class<? extends HttpPartParser> parser = 
HttpPartParser.Void.class;
+               Class<? extends HttpPartSerializer> serializer = 
HttpPartSerializer.Void.class;
+               Schema schema = SchemaAnnotation.DEFAULT;
+               String def="";
+               String[] description = {};
+
+               /**
+                * Constructor.
+                */
+               protected Builder() {
+                       super(PathRemainder.class);
+               }
+
+               /**
+                * Instantiates a new {@link PathRemainder @PathRemainder} 
object initialized with this builder.
+                *
+                * @return A new {@link PathRemainder @PathRemainder} object.
+                */
+               public PathRemainder build() {
+                       return new Impl(this);
+               }
+
+               /**
+                * Sets the {@link PathRemainder#def} property on this 
annotation.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder def(String value) {
+                       this.def = value;
+                       return this;
+               }
+
+               /**
+                * Sets the {@link PathRemainder#description} property on this 
annotation.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder description(String...value) {
+                       this.description = value;
+                       return this;
+               }
+
+               /**
+                * Sets the {@link PathRemainder#parser} property on this 
annotation.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder parser(Class<? extends HttpPartParser> value) {
+                       this.parser = value;
+                       return this;
+               }
+
+               /**
+                * Sets the {@link PathRemainder#schema} property on this 
annotation.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder schema(Schema value) {
+                       this.schema = value;
+                       return this;
+               }
+
+               /**
+                * Sets the {@link PathRemainder#serializer} property on this 
annotation.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder serializer(Class<? extends HttpPartSerializer> 
value) {
+                       this.serializer = value;
+                       return this;
+               }
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Implementation
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       private static class Impl extends TargetedAnnotationTImpl implements 
PathRemainder {
+
+               private final Class<? extends HttpPartParser> parser;
+               private final Class<? extends HttpPartSerializer> serializer;
+               private final String def;
+               private final String[] description;
+               private final Schema schema;
+
+               Impl(Builder b) {
+                       super(b);
+                       this.def = b.def;
+                       this.description = copyOf(b.description);
+                       this.parser = b.parser;
+                       this.schema = b.schema;
+                       this.serializer = b.serializer;
+                       postConstruct();
+               }
+
+               @Override /* PathRemainder */
+               public String def() {
+                       return def;
+               }
+
+               @Override /* PathRemainder */
+               public String[] description() {
+                       return description;
+               }
+
+               @Override /* PathRemainder */
+               public Class<? extends HttpPartParser> parser() {
+                       return parser;
+               }
+
+               @Override /* PathRemainder */
+               public Schema schema() {
+                       return schema;
+               }
+
+               @Override /* PathRemainder */
+               public Class<? extends HttpPartSerializer> serializer() {
+                       return serializer;
+               }
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Appliers
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Applies targeted {@link PathRemainder} annotations to a {@link 
org.apache.juneau.BeanContext.Builder}.
+        */
+       public static class Applier extends 
AnnotationApplier<PathRemainder,BeanContext.Builder> {
+
+               /**
+                * Constructor.
+                *
+                * @param vr The resolver for resolving values in annotations.
+                */
+               public Applier(VarResolverSession vr) {
+                       super(PathRemainder.class, BeanContext.Builder.class, 
vr);
+               }
+
+               @Override
+               public void apply(AnnotationInfo<PathRemainder> ai, 
BeanContext.Builder b) {
+                       PathRemainder a = ai.inner();
+                       if (isEmptyArray(a.on(), a.onClass()))
+                               return;
+                       b.annotations(a);
+               }
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Other
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * A collection of {@link PathRemainder @PathRemainder annotations}.
+        */
+       @Documented
+       @Target({METHOD,TYPE})
+       @Retention(RUNTIME)
+       @Inherited
+       public static @interface Array {
+
+               /**
+                * The child annotations.
+                *
+                * @return The annotation value.
+                */
+               PathRemainder[] value();
+       }
+}
\ No newline at end of file
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
index 75ee3979cf..1801041b0a 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
@@ -746,6 +746,8 @@ public class HttpPartSchema {
                                apply((Query)a);
                        else if (a instanceof Path)
                                apply((Path)a);
+                       else if (a instanceof PathRemainder)
+                               apply((PathRemainder)a);
                        else if (a instanceof Response)
                                apply((Response)a);
                        else if (a instanceof StatusCode)
@@ -819,6 +821,22 @@ public class HttpPartSchema {
                        return this;
                }
 
+               Builder apply(PathRemainder a) {
+                       if (! SchemaAnnotation.empty(a.schema()))
+                               apply(a.schema());
+                       // PathRemainder is always "/*"
+                       name("/*");
+                       _default(a.def());
+                       parser(a.parser());
+                       serializer(a.serializer());
+
+                       // Path remainder always allows empty value.
+                       allowEmptyValue();
+                       required(false);
+
+                       return this;
+               }
+
                Builder apply(Response a) {
                        allowEmptyValue(true);
                        apply(a.schema());
diff --git a/juneau-docs/docs/release-notes/9.2.0.md 
b/juneau-docs/docs/release-notes/9.2.0.md
index 734b6ba71d..7c34125fd8 100644
--- a/juneau-docs/docs/release-notes/9.2.0.md
+++ b/juneau-docs/docs/release-notes/9.2.0.md
@@ -344,10 +344,51 @@ Major changes include:
 
 - **Enhanced OpenAPI Compatibility**: Jakarta Validation constraints are now 
seamlessly translated to OpenAPI schema properties, enabling automatic 
documentation generation and client-side validation in tools like Swagger UI.
 
+#### @PathRemainder Annotation
+
+- **New Annotation**: Added `@PathRemainder` as an intuitive shortcut for 
`@Path("/*")` to capture the path remainder in REST endpoints.
+
+  **Features**:
+  - More explicit and self-documenting than `@Path("/*")`
+  - Works seamlessly in both server-side REST methods and client-side remote 
proxies
+  - Supports all `@Path` features: default values, custom parsers/serializers, 
schema validation, OpenAPI documentation
+  - Automatically configured with `required=false` and `allowEmptyValue=true` 
(path remainder is optional)
+
+  **Usage Example - Server Side**:
+  ```java
+  @RestGet("/files/*")
+  public File getFile(@PathRemainder String path) {
+      return new File(path);
+  }
+  ```
+
+  **Usage Example - Client Side**:
+  ```java
+  @Remote
+  public interface FileService {
+      @RemoteGet("/files/{+remainder}")
+      File getFile(@PathRemainder String remainder);
+  }
+  ```
+
+  **Implementation Details**:
+  - Created `PathRemainderArg` resolver for server-side parameter resolution
+  - Added support in `RemoteOperationArg` for client-side remote proxies
+  - Extended `HttpPartSchema.Builder` with `apply(PathRemainder)` method
+  - Comprehensive unit tests for server-side, including default values and 
mixed parameters
+
+  **Documentation**:
+  - Updated `/docs/topics/09.03.04.PathVariables.md` with path remainder 
examples
+  - Updated `/docs/topics/11.10.08.Path.md` for remote proxy usage
+
 #### CSV Serializer - Object Swap Support
 
 - **Full Object Swap Support**: The `CsvSerializer` now supports object swaps, 
bringing it to feature parity with other Juneau serializers like JSON, XML, and 
UON.
 
+  :::note
+  The `CsvParser` is not yet implemented. CSV parsing support will be added in 
a future release, at which point swap support will be included.
+  :::
+
   **Key Features**:
   - **Bean Property Swaps**: Automatically applies swaps registered via 
`.swaps()` to bean property values
   - **Map Value Swaps**: Transforms map values using registered swaps before 
CSV serialization
diff --git a/juneau-docs/docs/topics/09.03.04.PathVariables.md 
b/juneau-docs/docs/topics/09.03.04.PathVariables.md
index 41db1736fb..0825cc47a0 100644
--- a/juneau-docs/docs/topics/09.03.04.PathVariables.md
+++ b/juneau-docs/docs/topics/09.03.04.PathVariables.md
@@ -28,3 +28,33 @@ Path variables resolved in parent resource paths are also 
available to the child
 :::note
 All variables in the path must be specified or else the target will not 
resolve and a `404` will result.
 :::
+
+## Path Remainder
+
+The path remainder (the part matched by `/*`) can be captured using either <a 
href="/site/apidocs/org/apache/juneau/http/annotation/Path.html" 
target="_blank">@Path("/*")</a> or the more intuitive <a 
href="/site/apidocs/org/apache/juneau/http/annotation/PathRemainder.html" 
target="_blank">@PathRemainder</a> annotation.
+
+:::tip Example
+```java
+@Rest
+public class MyResource extends BasicRestServlet {
+
+    // Using @PathRemainder (preferred)
+    @RestGet("/files/*")
+    public File getFile(@PathRemainder String path) {
+        return new File(path);
+    }
+
+    // Equivalent using @Path("/*")
+    @RestPost("/upload/*")
+    public void uploadFile(@Path("/*") String path, @Content File file) {
+        ...
+    }
+}
+```
+:::
+
+The <a 
href="/site/apidocs/org/apache/juneau/http/annotation/PathRemainder.html" 
target="_blank">@PathRemainder</a> annotation supports the same features as <a 
href="/site/apidocs/org/apache/juneau/http/annotation/Path.html" 
target="_blank">@Path</a>, including:
+- Default values via `def()` property
+- Custom parsers and serializers
+- Schema validation
+- OpenAPI/Swagger documentation generation
diff --git a/juneau-docs/docs/topics/11.10.08.Path.md 
b/juneau-docs/docs/topics/11.10.08.Path.md
index 7ddb48efb2..218dc338e0 100644
--- a/juneau-docs/docs/topics/11.10.08.Path.md
+++ b/juneau-docs/docs/topics/11.10.08.Path.md
@@ -52,8 +52,29 @@ Path arguments can be any of the following types:
 
 See the link below for information about supported data types in OpenAPI 
serialization.
 
+## Path Remainder
+
+For capturing the path remainder (the part matched by `/*`), you can use the 
<a href="/site/apidocs/org/apache/juneau/http/annotation/PathRemainder.html" 
target="_blank">@PathRemainder</a> annotation as a more intuitive alternative 
to `@Path("/*")`.
+
+:::tip Example
+```java
+@Remote(path="/myproxy")
+public interface MyProxy {
+
+    // Using @PathRemainder (preferred)
+    @RemoteGet("/files/{+remainder}")
+    String getFile(@PathRemainder String remainder);
+
+    // Equivalent using @Path("/*")
+    @RemotePost("/upload/{+remainder}")
+    void uploadFile(@Path("/*") String remainder, @Content File file);
+}
+```
+:::
+
 :::info See Also
 
 - [OpenAPI Serializers](/docs/topics/OpenApiSerializers)
+- <a href="/site/apidocs/org/apache/juneau/http/annotation/PathRemainder.html" 
target="_blank">@PathRemainder</a>
 
 :::
\ No newline at end of file
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationArg.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationArg.java
index ecf8789b8b..2614a416cc 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationArg.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationArg.java
@@ -47,6 +47,14 @@ public class RemoteOperationArg {
                this.schema = schema;
        }
 
+       RemoteOperationArg(int index, HttpPartType partType, HttpPartSchema 
schema, String overrideName) {
+               this.index = index;
+               this.partType = partType;
+               this.serializer = 
BeanCreator.of(HttpPartSerializer.class).type(schema.getSerializer()).execute();
+               // Create a new schema with the overridden name
+               this.schema = 
HttpPartSchema.create().name(overrideName).build();
+       }
+
        /**
         * Returns the name of the HTTP part.
         *
@@ -109,6 +117,11 @@ public class RemoteOperationArg {
                        return new RemoteOperationArg(i, QUERY, 
HttpPartSchema.create(Query.class, mpi));
                } else if (mpi.hasAnnotation(FormData.class)) {
                        return new RemoteOperationArg(i, FORMDATA, 
HttpPartSchema.create(FormData.class, mpi));
+               } else if (mpi.hasAnnotation(PathRemainder.class)) {
+                       // PathRemainder is equivalent to @Path("/*")
+                       // Create with schema properties but override name to 
"/*"
+                       HttpPartSchema schema = 
HttpPartSchema.create(PathRemainder.class, mpi);
+                       return new RemoteOperationArg(i, PATH, schema, "/*");
                } else if (mpi.hasAnnotation(Path.class)) {
                        return new RemoteOperationArg(i, PATH, 
HttpPartSchema.create(Path.class, mpi));
                } else if (mpi.hasAnnotation(Content.class)) {
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index 0399f1aac4..a734502f8c 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -3198,6 +3198,7 @@ public class RestContext extends Context {
                                                MethodArg.class,
                                                ParserArg.class,
                                                PathArg.class,
+                                               PathRemainderArg.class,
                                                QueryArg.class,
                                                ReaderParserArg.class,
                                                RequestBeanArg.class,
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/PathRemainderArg.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/PathRemainderArg.java
new file mode 100644
index 0000000000..b3085a7110
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/PathRemainderArg.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.arg;
+
+import static org.apache.juneau.http.annotation.PathRemainderAnnotation.*;
+
+import java.lang.reflect.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.httppart.*;
+import org.apache.juneau.reflect.*;
+import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.httppart.*;
+import org.apache.juneau.rest.util.*;
+
+/**
+ * Resolves method parameters annotated with {@link PathRemainder} on {@link 
RestOp}-annotated Java methods.
+ *
+ * <p>
+ * This is a specialized version of {@link PathArg} for the path remainder 
(the part matched by {@code /*}).
+ * It's functionally equivalent to using {@code @Path("/*")}, but provides a 
more intuitive annotation name.
+ *
+ * <p>
+ * The parameter value is resolved using:
+ * <p class='bjava'>
+ *     <jv>opSession</jv>
+ *             .{@link RestOpSession#getRequest() getRequest}()
+ *             .{@link RestRequest#getPathParams() getPathParams}()
+ *             .{@link RequestPathParams#get(String) get}(<js>"/*"</js>)
+ *             .{@link RequestPathParam#as(Class) as}(<jv>type</jv>);
+ * </p>
+ *
+ * <p>
+ * {@link HttpPartSchema schema} is derived from the {@link PathRemainder} 
annotation.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/JavaMethodParameters";>Java Method 
Parameters</a>
+ *     <li class='ja'>{@link PathRemainder}
+ *     <li class='jc'>{@link PathArg}
+ * </ul>
+ *
+ * @since 9.2.0
+ */
+public class PathRemainderArg implements RestOpArg {
+       private final HttpPartParser partParser;
+       private final HttpPartSchema schema;
+       private final String def;
+       private final Type type;
+
+       /**
+        * Static creator.
+        *
+        * @param paramInfo The Java method parameter being resolved.
+        * @param annotations The annotations to apply to any new part parsers.
+        * @param pathMatcher Path matcher for the specified method (not used, 
but included for BeanStore compatibility).
+        * @return A new {@link PathRemainderArg}, or <jk>null</jk> if the 
parameter is not annotated with {@link PathRemainder}.
+        */
+       public static PathRemainderArg create(ParamInfo paramInfo, 
AnnotationWorkList annotations, UrlPathMatcher pathMatcher) {
+               if (paramInfo.hasAnnotation(PathRemainder.class) || 
paramInfo.getParameterType().hasAnnotation(PathRemainder.class))
+                       return new PathRemainderArg(paramInfo, annotations);
+               return null;
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param paramInfo The Java method parameter being resolved.
+        * @param annotations The annotations to apply to any new part parsers.
+        */
+       protected PathRemainderArg(ParamInfo paramInfo, AnnotationWorkList 
annotations) {
+               this.def = findDef(paramInfo).orElse(null);
+               this.type = paramInfo.getParameterType().innerType();
+               this.schema = HttpPartSchema.create(PathRemainder.class, 
paramInfo);
+               Class<? extends HttpPartParser> pp = schema.getParser();
+               this.partParser = pp != null ? 
HttpPartParser.creator().type(pp).apply(annotations).create() : null;
+       }
+
+       @Override /* RestOpArg */
+       public Object resolve(RestOpSession opSession) throws Exception {
+               RestRequest req = opSession.getRequest();
+               HttpPartParserSession ps = partParser == null ? 
req.getPartParserSession() : partParser.getPartSession();
+               // The path remainder is stored under the name "/*"
+               return 
req.getPathParams().get("/*").parser(ps).schema(schema).def(def).as(type).orElse(null);
+       }
+}
+
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/PathRemainder_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/PathRemainder_Test.java
index 0eb82e9e4c..df18f4d706 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/PathRemainder_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/PathRemainder_Test.java
@@ -132,4 +132,158 @@ class PathRemainder_Test extends TestBase {
                        .assertStatus(200)
                        .assertContent("[{a:1,b:'foo'}]");
        }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // @PathRemainder annotation tests
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Rest
+       public static class C {
+               @RestOp(path="/a/*")
+               public String a(@PathRemainder String remainder) {
+                       return ""+remainder;
+               }
+               @RestGet(path="/b/*")
+               public String b(@PathRemainder String remainder) {
+                       return ""+remainder;
+               }
+               @RestPut(path="/c/*")
+               public String c(@PathRemainder String remainder) {
+                       return ""+remainder;
+               }
+               @RestPost(path="/d/*")
+               public String d(@PathRemainder String remainder) {
+                       return ""+remainder;
+               }
+               @RestDelete(path="/e/*")
+               public String e(@PathRemainder String remainder) {
+                       return ""+remainder;
+               }
+       }
+
+       @Test void c01_pathRemainderAnnotation() throws Exception {
+               var c = MockRestClient.build(C.class);
+
+               // Test that @PathRemainder works identically to @Path("/*")
+               c.get("/a").run().assertContent("null");
+               c.get("/a/").run().assertContent("");
+               c.get("/a/foo").run().assertContent("foo");
+               c.get("/a/foo/bar").run().assertContent("foo/bar");
+
+               c.get("/b").run().assertContent("null");
+               c.get("/b/").run().assertContent("");
+               c.get("/b/foo").run().assertContent("foo");
+               c.get("/b/foo/bar").run().assertContent("foo/bar");
+
+               c.put("/c").run().assertContent("null");
+               c.put("/c/").run().assertContent("");
+               c.put("/c/foo").run().assertContent("foo");
+               c.put("/c/foo/bar").run().assertContent("foo/bar");
+
+               c.post("/d").run().assertContent("null");
+               c.post("/d/").run().assertContent("");
+               c.post("/d/foo").run().assertContent("foo");
+               c.post("/d/foo/bar").run().assertContent("foo/bar");
+
+               c.delete("/e").run().assertContent("null");
+               c.delete("/e/").run().assertContent("");
+               c.delete("/e/foo").run().assertContent("foo");
+               c.delete("/e/foo/bar").run().assertContent("foo/bar");
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // @PathRemainder with Optional and complex types
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Rest(serializers=Json5Serializer.class)
+       public static class D {
+               @RestGet(path="/a/*")
+               public Object a(@PathRemainder Optional<Integer> f1) {
+                       assertNotNull(f1);
+                       return f1;
+               }
+               @RestPut(path="/b/*")
+               public Object b(@PathRemainder Optional<ABean> f1) {
+                       assertNotNull(f1);
+                       return f1;
+               }
+               @RestPost(path="/c/*")
+               public Object c(@PathRemainder Optional<List<ABean>> f1) {
+                       assertNotNull(f1);
+                       return f1;
+               }
+               @RestDelete(path="/d/*")
+               public Object d(@PathRemainder List<Optional<ABean>> f1) {
+                       return f1;
+               }
+       }
+
+       @Test void d01_pathRemainderWithOptional() throws Exception {
+               var d = MockRestClient.buildJson(D.class);
+               d.get("/a/123")
+                       .run()
+                       .assertStatus(200)
+                       .assertContent("123");
+               d.put("/b/a=1,b=foo")
+                       .run()
+                       .assertStatus(200)
+                       .assertContent("{a:1,b:'foo'}");
+               d.post("/c/@((a=1,b=foo))")
+                       .run()
+                       .assertStatus(200)
+                       .assertContent("[{a:1,b:'foo'}]");
+               d.delete("/d/@((a=1,b=foo))")
+                       .run()
+                       .assertStatus(200)
+                       .assertContent("[{a:1,b:'foo'}]");
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // @PathRemainder with mixed path parameters
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Rest
+       public static class E {
+               @RestGet(path="/a/{foo}/{bar}/*")
+               public String a(@Path("foo") String foo, @Path("bar") int bar, 
@PathRemainder String remainder) {
+                       return "foo="+foo+",bar="+bar+",remainder="+remainder;
+               }
+               @RestPost(path="/b/{id}/*")
+               public String b(@Path("id") String id, @PathRemainder String 
remainder) {
+                       return "id="+id+",remainder="+remainder;
+               }
+       }
+
+       @Test void e01_pathRemainderWithOtherPathParams() throws Exception {
+               var e = MockRestClient.build(E.class);
+               e.get("/a/x/123/extra/path")
+                       .run()
+                       .assertContent("foo=x,bar=123,remainder=extra/path");
+               e.get("/a/hello/456")
+                       .run()
+                       .assertContent("foo=hello,bar=456,remainder=null");
+               e.post("/b/myId/more/stuff")
+                       .run()
+                       .assertContent("id=myId,remainder=more/stuff");
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // @PathRemainder with default values
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Rest
+       public static class F {
+               @RestGet(path="/a/*")
+               public String a(@PathRemainder(def="defaultValue") String 
remainder) {
+                       return ""+remainder;
+               }
+       }
+
+       @Test void f01_pathRemainderWithDefault() throws Exception {
+               var f = MockRestClient.build(F.class);
+               f.get("/a").run().assertContent("defaultValue");
+               f.get("/a/").run().assertContent("");
+               f.get("/a/custom").run().assertContent("custom");
+       }
+
 }
\ No newline at end of file
diff --git a/scripts/README-check-fluent-setter-overrides.md 
b/scripts/README-check-fluent-setter-overrides.md
new file mode 100644
index 0000000000..04dd6857a9
--- /dev/null
+++ b/scripts/README-check-fluent-setter-overrides.md
@@ -0,0 +1,164 @@
+# Check Fluent Setter Overrides Script
+
+## Purpose
+
+This script scans the Juneau codebase to find missing fluent setter overrides 
in subclasses.
+
+## Problem
+
+Juneau uses fluent-style setters extensively for method chaining:
+
+```java
+public class X {
+    public X setY(Y y) {
+        this.y = y;
+        return this;
+    }
+}
+```
+
+When a class extends another class with fluent setters, it should override 
those setters to return the correct subclass type:
+
+```java
+public class X2 extends X {
+    @Override 
+    public X2 setY(Y y) {
+        super.setY(y);
+        return this;
+    }
+}
+```
+
+Without these overrides, method chaining breaks:
+
+```java
+// Without override - compiler error!
+X2 obj = new X2()
+    .setY(y)    // Returns X, not X2
+    .setX2SpecificMethod();  // Error: X doesn't have this method
+
+// With proper override - works!
+X2 obj = new X2()
+    .setY(y)    // Returns X2
+    .setX2SpecificMethod();  // OK
+```
+
+## Usage
+
+### Basic Usage
+
+Run from the Juneau root directory:
+
+```bash
+python3 scripts/check-fluent-setter-overrides.py
+```
+
+Or run directly (script is executable):
+
+```bash
+./scripts/check-fluent-setter-overrides.py
+```
+
+### Output
+
+The script will:
+1. Scan all Java files in the source tree
+2. Identify classes and their inheritance relationships
+3. Find fluent setter methods (methods that return `this`)
+4. Check if subclasses properly override these setters
+5. Report any missing overrides grouped by class
+
+Example output:
+
+```
+Juneau Fluent Setter Override Checker
+==================================================
+
+Scanning for Java files...
+Found 2847 Java files
+
+Extracting class information...
+Found 1523 classes
+Found 3421 fluent setter methods
+
+Building class hierarchy...
+
+Checking for missing fluent setter overrides...
+
+MISSING OVERRIDES (23 found):
+==================================================
+
+Class: RestClientBuilder
+  File: 
juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
+  Missing 5 override(s):
+    - debug(Enablement)
+      From parent: BeanContextBuilder
+    - locale(Locale)
+      From parent: BeanContextBuilder
+    - mediaType(MediaType)
+      From parent: BeanContextBuilder
+    - timeZone(TimeZone)
+      From parent: BeanContextBuilder
+    - beansRequireDefaultConstructor()
+      From parent: BeanContextBuilder
+
+Class: HtmlDocSerializerBuilder
+  File: 
juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlDocSerializerBuilder.java
+  Missing 3 override(s):
+    - addBeanTypes()
+      From parent: HtmlSerializerBuilder
+    - detectRecursions()
+      From parent: SerializerBuilder
+    - ignoreRecursions()
+      From parent: SerializerBuilder
+
+==================================================
+Total missing overrides: 23
+
+Note: These are informational and do not fail the build.
+Consider adding these overrides to maintain fluent API consistency.
+```
+
+## What the Script Checks
+
+The script identifies fluent setters by looking for:
+- Public methods that return the class type (e.g., `public X setFoo(...)`)
+- Methods that contain `return this;` in their body
+- Methods in parent classes that should be overridden in subclasses
+
+## What to Do with Results
+
+When the script reports missing overrides:
+
+1. **Review the findings** - Not all reported methods may need overrides
+2. **Add missing overrides** - For methods that should be overridden:
+
+```java
+@Override
+public SubClass methodName(ParamType param) {
+    super.methodName(param);
+    return this;
+}
+```
+
+3. **Consider fluent API design** - Ensure method chaining works correctly 
across the inheritance hierarchy
+
+## Limitations
+
+- The script uses regex-based parsing (not a full Java parser)
+- May not catch all edge cases (inner classes, complex generics, etc.)
+- Reports are informational only - manual review is recommended
+- Does not validate that overrides are implemented correctly
+
+## Integration
+
+This script can be run:
+- Manually during development
+- As part of code review process
+- In CI/CD pipelines (informational only)
+- Before releases to ensure API consistency
+
+## Exit Code
+
+The script always exits with code 0 (success) to avoid failing builds. Results 
are informational only.
+
diff --git a/scripts/check-fluent-setter-overrides.py 
b/scripts/check-fluent-setter-overrides.py
new file mode 100755
index 0000000000..a5260f27b7
--- /dev/null
+++ b/scripts/check-fluent-setter-overrides.py
@@ -0,0 +1,273 @@
+#!/usr/bin/env python3
+# 
***************************************************************************************************************************
+# * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
+# * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
+# * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance            *
+# * with the License.  You may obtain a copy of the License at                 
                                             *
+# *                                                                            
                                             *
+# *  http://www.apache.org/licenses/LICENSE-2.0                                
                                             *
+# *                                                                            
                                             *
+# * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an  *
+# * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied.  See the License for the        *
+# * specific language governing permissions and limitations under the License. 
                                             *
+# 
***************************************************************************************************************************
+"""
+Script to check for missing fluent setter overrides in the Juneau codebase.
+
+This script:
+1. Scans all Java files in the source tree
+2. Identifies classes and their parent classes
+3. Finds fluent setter methods (methods that return 'this')
+4. Checks if subclasses override these setters with the correct return type
+5. Reports any missing overrides
+
+A fluent setter is a method that:
+- Starts with 'set' or is a builder-style method
+- Returns the class type (for method chaining)
+- Is public
+"""
+
+import os
+import re
+import sys
+from pathlib import Path
+from collections import defaultdict
+
+class JavaClass:
+    """Represents a Java class with its metadata."""
+    def __init__(self, name, file_path, extends=None, package=None):
+        self.name = name
+        self.file_path = file_path
+        self.extends = extends
+        self.package = package
+        self.fluent_setters = []  # List of (method_name, params, return_type)
+        self.overridden_methods = set()  # Set of method signatures that are 
overridden
+        
+    def add_fluent_setter(self, method_name, params, return_type):
+        """Add a fluent setter method."""
+        self.fluent_setters.append({
+            'name': method_name,
+            'params': params,
+            'return_type': return_type
+        })
+    
+    def add_overridden_method(self, method_name, params):
+        """Add an overridden method."""
+        signature = f"{method_name}({params})"
+        self.overridden_methods.add(signature)
+    
+    def get_full_name(self):
+        """Get the fully qualified class name."""
+        if self.package:
+            return f"{self.package}.{self.name}"
+        return self.name
+
+def extract_package(content):
+    """Extract package name from Java file content."""
+    match = re.search(r'^\s*package\s+([\w.]+)\s*;', content, re.MULTILINE)
+    return match.group(1) if match else None
+
+def extract_class_info(file_path):
+    """Extract class information from a Java file."""
+    try:
+        with open(file_path, 'r', encoding='utf-8') as f:
+            content = f.read()
+        
+        package = extract_package(content)
+        
+        # Find class declarations (public class X extends Y)
+        class_pattern = re.compile(
+            
r'^\s*public\s+(?:static\s+)?(?:abstract\s+)?class\s+(\w+)(?:\s+extends\s+([\w.<>,
 ]+?))?(?:\s+implements\s+[\w.<>, ]+?)?\s*\{',
+            re.MULTILINE
+        )
+        
+        classes = []
+        for match in class_pattern.finditer(content):
+            class_name = match.group(1)
+            extends = match.group(2).strip() if match.group(2) else None
+            
+            # Clean up extends (remove generics for simplicity)
+            if extends:
+                extends = re.sub(r'<.*?>', '', extends).strip()
+            
+            java_class = JavaClass(class_name, file_path, extends, package)
+            
+            # Find fluent setters in this class
+            # Pattern: public ClassName methodName(...) { ... return this; }
+            # We look for methods that return the class type
+            method_pattern = re.compile(
+                
rf'^\s*(?:@Override\s+)?public\s+{re.escape(class_name)}\s+(\w+)\s*\((.*?)\)\s*\{{',
+                re.MULTILINE
+            )
+            
+            for method_match in method_pattern.finditer(content):
+                method_name = method_match.group(1)
+                params = method_match.group(2).strip()
+                
+                # Check if this method returns 'this'
+                # Look ahead to see if there's a 'return this;' in the method 
body
+                method_start = method_match.end()
+                # Find the matching closing brace (simplified - just look for 
return this)
+                method_body_sample = content[method_start:method_start + 500]
+                if 'return this;' in method_body_sample or 'return this' in 
method_body_sample:
+                    java_class.add_fluent_setter(method_name, params, 
class_name)
+            
+            # Find overridden methods
+            override_pattern = re.compile(
+                r'@Override[^\n]*\n\s*public\s+\w+\s+(\w+)\s*\((.*?)\)',
+                re.MULTILINE
+            )
+            
+            for override_match in override_pattern.finditer(content):
+                method_name = override_match.group(1)
+                params = override_match.group(2).strip()
+                java_class.add_overridden_method(method_name, params)
+            
+            classes.append(java_class)
+        
+        return classes
+    
+    except Exception as e:
+        print(f"ERROR: Failed to process {file_path}: {e}", file=sys.stderr)
+        return []
+
+def find_java_files(source_dir):
+    """Find all Java files in the source tree."""
+    java_files = []
+    
+    for root, dirs, files in os.walk(source_dir):
+        # Skip certain directories
+        dirs[:] = [d for d in dirs if not d.startswith('.') and d not in 
{'target', 'node_modules', 'build', 'dist'}]
+        
+        for file in files:
+            if file.endswith('.java'):
+                java_files.append(Path(root) / file)
+    
+    return java_files
+
+def build_class_hierarchy(classes):
+    """Build a hierarchy of classes by name."""
+    class_map = {}  # Maps class name to JavaClass objects (may have 
duplicates)
+    
+    for java_class in classes:
+        class_name = java_class.name
+        if class_name not in class_map:
+            class_map[class_name] = []
+        class_map[class_name].append(java_class)
+    
+    return class_map
+
+def check_missing_overrides(classes, class_map):
+    """Check for missing fluent setter overrides."""
+    missing_overrides = []
+    
+    for java_class in classes:
+        if not java_class.extends:
+            continue
+        
+        parent_name = java_class.extends
+        
+        # Find parent class
+        if parent_name not in class_map:
+            continue
+        
+        # Get all parent classes with this name (there may be multiple in 
different packages)
+        parent_classes = class_map[parent_name]
+        
+        # Check each parent class
+        for parent_class in parent_classes:
+            # Check each fluent setter in the parent
+            for setter in parent_class.fluent_setters:
+                method_name = setter['name']
+                params = setter['params']
+                signature = f"{method_name}({params})"
+                
+                # Check if this method is overridden in the child class
+                if signature not in java_class.overridden_methods:
+                    # Also check if the child has this as a fluent setter
+                    # (it might define it without @Override annotation)
+                    has_fluent = False
+                    for child_setter in java_class.fluent_setters:
+                        if child_setter['name'] == method_name and 
child_setter['params'] == params:
+                            has_fluent = True
+                            break
+                    
+                    if not has_fluent:
+                        missing_overrides.append({
+                            'child_class': java_class.name,
+                            'child_file': str(java_class.file_path),
+                            'parent_class': parent_class.name,
+                            'parent_file': str(parent_class.file_path),
+                            'method_name': method_name,
+                            'method_params': params,
+                            'method_signature': signature
+                        })
+    
+    return missing_overrides
+
+def main():
+    """Main entry point."""
+    # Get the script directory (should be /juneau/scripts)
+    script_dir = Path(__file__).parent
+    juneau_root = script_dir.parent
+    
+    print("Juneau Fluent Setter Override Checker")
+    print("=" * 50)
+    
+    # Find all Java files
+    print("\nScanning for Java files...")
+    java_files = find_java_files(juneau_root)
+    print(f"Found {len(java_files)} Java files")
+    
+    # Extract class information
+    print("\nExtracting class information...")
+    all_classes = []
+    for java_file in java_files:
+        classes = extract_class_info(java_file)
+        all_classes.extend(classes)
+    
+    print(f"Found {len(all_classes)} classes")
+    
+    # Count fluent setters
+    total_fluent_setters = sum(len(c.fluent_setters) for c in all_classes)
+    print(f"Found {total_fluent_setters} fluent setter methods")
+    
+    # Build class hierarchy
+    print("\nBuilding class hierarchy...")
+    class_map = build_class_hierarchy(all_classes)
+    
+    # Check for missing overrides
+    print("\nChecking for missing fluent setter overrides...")
+    missing = check_missing_overrides(all_classes, class_map)
+    
+    # Report results
+    if missing:
+        print(f"\nMISSING OVERRIDES ({len(missing)} found):")
+        print("=" * 50)
+        
+        # Group by child class for better readability
+        by_class = defaultdict(list)
+        for item in missing:
+            by_class[item['child_class']].append(item)
+        
+        for child_class, items in sorted(by_class.items()):
+            print(f"\nClass: {child_class}")
+            print(f"  File: {items[0]['child_file']}")
+            print(f"  Missing {len(items)} override(s):")
+            
+            for item in items:
+                print(f"    - {item['method_name']}({item['method_params']})")
+                print(f"      From parent: {item['parent_class']}")
+        
+        print(f"\n{'=' * 50}")
+        print(f"Total missing overrides: {len(missing)}")
+        print("\nNote: These are informational and do not fail the build.")
+        print("Consider adding these overrides to maintain fluent API 
consistency.")
+    else:
+        print("\n✓ All fluent setters are properly overridden!")
+    
+    sys.exit(0)
+
+if __name__ == "__main__":
+    main()
+

Reply via email to