We have a hierarchy of generic "parameter" types, with non-generic concrete leaf classes, a vertical slice of it given here:
public interface Parameter<T>
public T getValue();
public void setValue(T value);
…
public abstract class AbstractParameter<T> implements Parameter<T>
public final T getValue()
public final void setValue(T value)
…
public abstract class AbstractNumberParameter<T extends Number> extends AbstractParameter<T> implements NumberParameter<T>
…
public class IntegerParameter extends AbstractNumberParameter<Integer>
…
getValue
and setValue
are not overridden in AbstractNumberParameter
or IntegerParameter
.
In one case, an instance of IntegerParameter is used in an iteration of facelet code that iterates through a parameter list and conditionally renders a parameter's corresponding view depending on the value of an enum field in the parameter (which is more coarse than the generic type parameter).
<p:dataTable id="inputPanel" styleClass="parameter-input-table"
value="#{metricModuleManager.metricModule.parameters}"
var="parameter" rowIndexVar="rowIndex">
...
<p:column>
<h:panelGroup id="inputArea">
...
<p:spinner id="numberInput"
rendered="#{parameter.type=='NUMBER'}"
stepFactor="#{parameter.resolution}"
value="#{parameter.value}"
parameter="#{parameter.name}"
validator="#{metricModuleManager.validateParameter}">
<p:ajax update="numberMessage" />
<p:message id="numberMessage" for="numberInput" />
</p:spinner>
...
</h:panelGroup>
</p:column>
...
</p:dataTable>
(PrimeFaces elements are used.)
It seems to vary between development or runtime environments whether or not the value of the spinner is correctly converted to the setter parameter's type. To avoid a later ClassCastException, the value being passed to setValue
would need to be an Integer
instance. In my case, I am observing a String
being passed to the setter (String
apparently being the default type for attributes, if I understand correctly). However, the same code seems to work perfectly on my coworker's computer, such that the integer value is used in the correct spot later on in the workflow. It also seems to work correctly on a separate server.
I and my coworker are both using JSF 2 with Tomcat 7 integrated with Eclipse.
If this code consistently didn't work, then I would just assume it was related to type erasure. But, the inconsistent behavior makes me think that perhaps at least some implementations of JSF are able to draw from limited type metadata in this case to determine the correct converter to use. It does seem like the actual Integer
type argument is stored in IntegerParameter
's Class
metadata:
p.getClass().getGenericSuperclass().toString()
yielded AbstractNumberParameter<Integer>
in debug code where p
was the IntegerParameter
instance.
ClassCastException when calling TreeSet<Long>.contains( Long.valueOf( someLongValue ) ) and https://stackoverflow.com/a/13866179/516433 establish that through type erasure, a direct instance of a class with a generic type parameter won't carry with it the actual type argument, so in such a case, the JSF implementation wouldn't have the needed type information to choose the correct converter. But, is this case different in that the type argument is specified as part of the class definition? (https://stackoverflow.com/a/16886880/1867423 suggests this is the case.) Would the JSF implementation then be able to use introspection to determine the proper converter to use?
What might explain the difference in behavior between environments? We are somewhat stumped.