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)