Ben Weidig created TAP5-2833:
--------------------------------

             Summary: SelectModel should adopt upper-bounded wildcards for 
option collections
                 Key: TAP5-2833
                 URL: https://issues.apache.org/jira/browse/TAP5-2833
             Project: Tapestry 5
          Issue Type: Improvement
          Components: tapestry-core
            Reporter: Ben Weidig


h2. Description

{{SelectModel}} is used to implement domain-specific custom classes that 
implement {{OptionModel}} or {{OptionGroupModel}}.
However, due to Java's generic type invariance, passing collections of these 
custom implementations (e.g., {{List<MyCustomOptionModel>}}) directly to 
standard {{SelectModel}} constructors, factories, or builders is blocked by the 
compiler.

This forces developers to write tedious boilerplate code (such as 
stream-mapping or copying arrays) to upcast their collections to 
{{List<OptionModel>}} purely to satisfy the compiler type-checks.

We can significantly improve developer ergonomics and framework usability by 
modifying input parameter signatures across the selection APIs to adopt 
upper-bounded wildcards (e.g., {{List<? extends OptionModel>}}).

Furthermore, {{SelectModel.getOptions()}} should become a {{default}} method 
returning {{null}}, as flat selects are by far the most common use case on the 
web.

h3. Functional Improvement

* Eliminate redundant collection-copying boilerplate at call sites when working 
with custom implementation lists.
* Align Tapestry's internal model instantiation APIs with established Java 
Generics design principles ("PECS", Producer Extends, Consumer Super).
* Simplify {{SelectModel}} implementations by removing repetive {{null}} value 
for groups.

h2. Technical Tasks / Proposed Solution

# *Relax Constructor Signatures*: Update concrete constructors (such as 
{{SelectModelImpl}}) to accept wildcards:
** Change {{List<OptionGroupModel>}} to {{List<? extends OptionGroupModel>}}
** Change {{List<OptionModel>}} to {{List<? extends OptionModel>}}
# *Relax Utility/Factory Signatures*: Apply the same change to any framework 
helper/factory methods (such as {{SelectModelFactory}}) that consume 
collections to build selection models.
# *Add default method*: Return {{null}}

h2. Why the Change?

* *Framework Ergonomics*: Developers should be able to supply a collection of 
any type implementing {{OptionModel}} directly to a select model without manual 
type conversion. Not needing to implement the same "no groups" method 
repeatedly simplifies the required code.
* *PECS Best Practice*: Because these constructors and factories only read 
(produce) elements from the provided lists to populate UI components, the 
collections can safely be parameterized with {{? extends}}, which relaxes 
caller constraints without compromising safety.

h2. Risks / Breaking Changes (Downstream Perspective)

h3. Method Overrides in Subclasses (Source-Breaking Change)

*Risk Level*: Low-to-Medium

If downstream developers have extended concrete classes (such as 
{{SelectModelImpl}} or custom helper components) and overridden setter methods, 
changing those parameter signatures in the parent class will break compilation 
in the subclass.
In Java, parameter types in overridden methods must match exactly.

*Mitigation*: Ensure the change is documented in the release notes and 
communicated on the mailing lists so developers can update overridden method 
signatures in their custom subclasses to match the wildcards.

h3. Modifying Interface Return Types (High Risk - Strongly Discouraged)

*Risk Level*: High (if return types are changed)

If the return types of core interface methods (such as 
{{SelectModel.getOptions()}} or {{OptionGroupModel.getOptions()}}) are changed 
from {{List<OptionModel>}} to {{List<? extends OptionModel>}}, it will cause 
immediate compilation errors for any downstream consumer assigning the result 
to a strict {{List<OptionModel>}} variable.

*Mitigation*: Keep the wildcard relaxation restricted strictly to *input 
parameters* (constructors, setters, and factory inputs). Do not change the 
return types of the getters on public interfaces, preserving backward 
compatibility for existing consumers.

h3. Binary Compatibility

*Risk Level*: None

Due to Java's type erasure, {{List<OptionModel>}} and {{List<? extends 
OptionModel>}} both erase to raw {{java.util.List}} at runtime.

Existing pre-compiled binaries and compiled third-party modules running against 
this updated version will link and execute successfully without throwing 
{{NoSuchMethodError}}.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to