return new ComputedValue<FieldConduit<Object>>()
{
public ParameterConduit get(InstanceContext context)
{
final InternalComponentResources icr = context.get(InternalComponentResources.class);
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);
icr.getPageLifecycleCallbackHub().addPageLoadedCallback(new Runnable()
{
@Override
public void run()
{
load();
}
});
}
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 instance, InstanceContext context, 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;
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 && !allowNull)
{
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);
}
return result;
}
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()
{
if (logger.isDebugEnabled())
{
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))
{
if (logger.isDebugEnabled())
{
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();
if (logger.isDebugEnabled())
{
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(Object instance, InstanceContext context)
{
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;
}
return result;
}
private Binding getDefaultBindingForParameter()
{
if (InternalUtils.isNonBlank(annotation.value()))
{
return bindingSource.newBinding("default " + parameterName, icr,
annotation.defaultPrefix(), annotation.value());
}
if (annotation.autoconnect())
{
return defaultProvider.defaultBinding(parameterName, icr);
}
// Invoke the default method and install any value or Binding returned there.
invokeDefaultMethod();
return parameterBinding;
}
private void invokeDefaultMethod()
{
if (defaultMethodHandle == null)
{
return;
}
if (logger.isDebugEnabled())
{
logger.debug(String.format("%s invoking method %s to obtain default for parameter %s",
icr.getCompleteId(), defaultMethodHandle, parameterName));
}
MethodInvocationResult result = defaultMethodHandle.invoke(icr.getComponent());
result.rethrow();
Object defaultValue = result.getReturnValue();