{
return new ComponentValueProvider<ParameterConduit>()
{
public ParameterConduit get(ComponentResources resources)
{
final InternalComponentResources icr = (InternalComponentResources) resources;
final Class fieldType = classCache.forName(fieldTypeName);
final PerThreadValue<ParameterState> stateValue = perThreadManager.createValue();
// Rely on some code generation in the component to set the default binding from
// the field, or from a default method.
return new ParameterConduit()
{
// Default value for parameter, computed *once* at
// page load time.
private Object defaultValue = classCache.defaultValueForType(fieldTypeName);
private Binding parameterBinding;
boolean loaded = false;
private boolean invariant = false;
{
// Inform the ComponentResources about the parameter conduit, so it can be
// shared with mixins.
icr.setParameterConduit(parameterName, this);
}
private ParameterState getState()
{
ParameterState state = stateValue.get();
if (state == null)
{
state = new ParameterState();
state.value = defaultValue;
stateValue.set(state);
}
return state;
}
private boolean isLoaded()
{
return loaded;
}
public void set(Object newValue)
{
ParameterState state = getState();
// Assignments before the page is loaded ultimately exist to set the
// default value for the field. Often this is from the (original)
// constructor method, which is converted to a real method as part of the transformation.
if (!loaded)
{
state.value = newValue;
defaultValue = newValue;
return;
}
// This will catch read-only or unbound parameters.
writeToBinding(newValue);
state.value = newValue;
// If caching is enabled for the parameter (the typical case) and the
// component is currently rendering, then the result
// can be cached in this ParameterConduit (until the component finishes
// rendering).
state.cached = annotation.cache() && icr.isRendering();
}
private Object readFromBinding()
{
Object result = null;
try
{
Object boundValue = parameterBinding.get();
result = typeCoercer.coerce(boundValue, fieldType);
}
catch (RuntimeException ex)
{
throw new TapestryException(String.format(
"Failure reading parameter '%s' of component %s: %s", parameterName,
icr.getCompleteId(), InternalUtils.toMessage(ex)), parameterBinding, ex);
}
if (result != null || annotation.allowNull())
return result;
throw new TapestryException(
String.format(
"Parameter '%s' of component %s is bound to null. This parameter is not allowed to be null.",
parameterName, icr.getCompleteId()), parameterBinding, null);
}
private void writeToBinding(Object newValue)
{
// An unbound parameter acts like a simple field
// with no side effects.
if (parameterBinding == null)
return;
try
{
Object coerced = typeCoercer.coerce(newValue, parameterBinding.getBindingType());
parameterBinding.set(coerced);
}
catch (RuntimeException ex)
{
throw new TapestryException(String.format(
"Failure writing parameter '%s' of component %s: %s", parameterName,
icr.getCompleteId(), InternalUtils.toMessage(ex)), icr, ex);
}
}
public void reset()
{
if (!invariant)
{
getState().reset(defaultValue);
}
}
public void load()
{
logger.debug(String.format("%s loading parameter %s", icr.getCompleteId(), parameterName));
// If it's bound at this point, that's because of an explicit binding
// in the template or @Component annotation.
if (!icr.isBound(parameterName))
{
logger.debug(String.format("%s parameter %s not yet bound", icr.getCompleteId(),
parameterName));
// Otherwise, construct a default binding, or use one provided from
// the component.
Binding binding = getDefaultBindingForParameter();
logger.debug(String.format("%s parameter %s bound to default %s", icr.getCompleteId(),
parameterName, binding));
if (binding != null)
icr.bindParameter(parameterName, binding);
}
parameterBinding = icr.getBinding(parameterName);
loaded = true;
invariant = parameterBinding != null && parameterBinding.isInvariant();
getState().value = defaultValue;
}
public boolean isBound()
{
return parameterBinding != null;
}
public Object get()
{
if (!isLoaded()) { return defaultValue; }
ParameterState state = getState();
if (state.cached || !isBound()) { return state.value; }
// Read the parameter's binding and cast it to the
// field's type.
Object result = readFromBinding();
// If the value is invariant, we can cache it until at least the end of the request (before
// 5.2, it would be cached forever in the pooled instance).
// Otherwise, we
// we may want to cache it for the remainder of the component render (if the
// component is currently rendering).
if (invariant || (annotation.cache() && icr.isRendering()))
{
state.value = result;
state.cached = true;
}