package org.apache.beehive.controls.runtime.bean;
/*
* Copyright 2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $Header:$
*/
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyVetoException;
import java.beans.beancontext.BeanContext;
import java.beans.beancontext.BeanContextServiceRevokedEvent;
import java.beans.beancontext.BeanContextServiceRevokedListener;
import java.beans.beancontext.BeanContextServiceProvider;
import java.beans.beancontext.BeanContextServices;
import java.beans.beancontext.BeanContextServicesSupport;
import java.util.*;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;
import org.apache.beehive.controls.api.ControlException;
import org.apache.beehive.controls.api.bean.ControlInterface;
import org.apache.beehive.controls.api.context.ControlHandle;
import org.apache.beehive.controls.api.properties.AnnotatedElementMap;
import org.apache.beehive.controls.api.properties.BeanPropertyMap;
import org.apache.beehive.controls.api.properties.PropertyMap;
import org.apache.beehive.controls.api.properties.PropertySet;
import org.apache.beehive.controls.api.properties.PropertySetProxy;
/**
* The ControlBeanContext implements the basic BeanContextServices implementation
* for ControlBeans.
*
* It provides several basic functions:
* - it defines the generic services that are available for all control containers
* - it acts as the base class for other container service implementations
* - it acts as the BeanContextServicesRevokedListener when an associated control
* bean has lost access to services.
*/
public class ControlBeanContext
extends BeanContextServicesSupport
implements BeanContextServiceRevokedListener,
org.apache.beehive.controls.api.context.ControlBeanContext,
java.beans.PropertyChangeListener,
java.beans.VetoableChangeListener
{
/**
* Creates a new ControlBeanContext instance associated with a specific
* control bean.
*
* @param bean The control bean that contains/scopes the ControlBeanContext. If null,
it means the ControlBeanContext is for a top-level container.
*/
protected ControlBeanContext(ControlBean bean)
{
_bean = bean;
}
/**
* The ControlBeanContextProvider inner class acts as a single BeanContext service
* provider for the ControlBeanContext service class. The implementation is simple,
* because the runtime ControlBeanContext implementation class directly implements
* this interface.
*/
private static class ControlBeanContextProvider implements BeanContextServiceProvider
{
//
// BeanContextServiceProvider.getService()
//
public Object getService(BeanContextServices bcs, Object requestor, Class serviceClass,
Object serviceSelector)
{
//
// Contextual services for a ControlBean is provided by the peer context
// instance.
//
if (requestor instanceof ControlBean)
return ((ControlBean)requestor).getControlBeanContext();
return null;
}
//
// BeanContextServiceProvider.releaseService()
//
public void releaseService(BeanContextServices bcs, Object requestor, Object service)
{
// noop, because context exists whether referenced or not
}
//
// BeanContextServiceProvider.getContextServiceSelectors()
//
public Iterator getCurrentServiceSelectors(BeanContextServices bcs, Class serviceClass)
{
return null; // no selectors
}
}
/**
* A singleton instance of the ControlBeanContextProvider class is that will be registered
* on all ControlBeanContext instances. The provider can be a singleton because it is
* completely stateless and thread-safe.
*/
static private ControlBeanContextProvider theProvider = new ControlBeanContextProvider();
/**
* Called by BeanContextSupport superclass during construction and deserialization to
* initialize subclass transient state
*/
public void initialize()
{
super.initialize();
//
// Register the ControlBeanContext provider on all new context instances.
//
addService(org.apache.beehive.controls.api.context.ControlBeanContext.class, theProvider);
}
/**
* Implements the BeanContextServicesRevokedListener.servicesRevoked method. This is
* called if the associated ControlBean has requested a service that is being subsequently
* revoked.
*/
public void serviceRevoked(BeanContextServiceRevokedEvent bcsre)
{
//
// This can happen, if the control is disassociated from a parent context that is
// providing services.
//
}
/**
* Overrides the BeanContextChild.setBeanContext() method. This hook is used to perform
* additional processing that needs to occur when the control is associated with a new
* nesting context.
*/
public synchronized void setBeanContext(BeanContext beanContext) throws PropertyVetoException
{
ControlBeanContext cbcs = null;
if (beanContext != null)
{
//
// ControlBeans can only be nested in context service instances that derive
// from ControlBeanContext.
//
if (!(beanContext instanceof ControlBeanContext))
{
PropertyChangeEvent pce = new PropertyChangeEvent(_bean, "beanContext",
getBeanContext(), beanContext);
throw new PropertyVetoException("Context does not support nesting controls: " +
beanContext.getClass(), pce);
}
cbcs = (ControlBeanContext)beanContext;
}
super.setBeanContext(beanContext);
resetControlID();
_hasSingleThreadedParent = cbcs != null ? cbcs.isSingleThreadedContainer() : false;
//
// Notify the bean that its context (container) has been set.
//
if (_bean != null)
_bean.setBeanContext(beanContext);
}
/**
* The NameGenerator class is a simple helper class that creates new unique names based
* upon a base prefix and an incrementing counter
*/
private static class NameGenerator implements java.io.Serializable
{
NameGenerator(String namePrefix)
{
_namePrefix = namePrefix;
}
/**
* Get the next unique name
*/
public synchronized String next()
{
return _namePrefix + _nextIndex++;
}
int _nextIndex = 0;
String _namePrefix;
}
/**
* Returns a new NameGenerator instance based upon a particular naming
* prefix.
*/
private NameGenerator getNameGenerator(String namePrefix)
{
synchronized(this)
{
if (_nameGenerators == null)
_nameGenerators = new HashMap<String,NameGenerator>();
NameGenerator nameGenerator = (NameGenerator)_nameGenerators.get(namePrefix);
if (nameGenerator == null)
{
nameGenerator = new NameGenerator(namePrefix);
_nameGenerators.put(namePrefix, nameGenerator);
}
return nameGenerator;
}
}
/**
* Generates a new unique control ID for an instance of the target class
*/
public String generateUniqueID(Class clazz)
{
String namePrefix = clazz.getName();
int dotIndex = namePrefix.lastIndexOf('.');
if (dotIndex > 0)
namePrefix = namePrefix.substring(dotIndex+1);
NameGenerator nameGenerator = getNameGenerator(namePrefix);
return nameGenerator.next();
}
/**
* Overrides the BeanContextSupport.add() method to perform additional validation
* that is unique for ControlBeans containers.
*/
public boolean add(Object targetChild)
{
//
// The context can contain ControlBeans and other types of objects, such as a control
// factory.
//
String beanID = null;
if (targetChild instanceof ControlBean)
{
//
//
//
ControlBean bean = (ControlBean)targetChild;
beanID = bean.getLocalID();
//
// The bean is anonymous, so we must generate a new unique name within this context.
//
if (beanID == null)
{
beanID = generateUniqueID(bean.getClass());
bean.setLocalID(beanID);
}
ControlBean existingBean = (ControlBean)_childMap.get(beanID);
if (existingBean != null && existingBean != targetChild)
{
throw new IllegalArgumentException("Attempting to add control with duplicate ID: " +
beanID);
}
}
boolean added = super.add(targetChild);
if (added && beanID != null)
_childMap.put(beanID, targetChild);
return added;
}
/**
* Overrides the BeanContextSupport.remove() method to perform additional post-processing
* on child removal.
*/
public boolean remove(Object targetChild)
{
assert targetChild instanceof ControlBean; // should be guaranteed above
boolean removed = super.remove(targetChild);
if (removed)
{
//
// Remove from the locally maintained child map
//
String localID = ((ControlBean)targetChild).getLocalID();
Object removedChild = _childMap.remove(localID);
assert removedChild == targetChild; // just being safe
}
return removed;
}
/**
* Returns a ControlBean instance nested the current BeanContext.
* @param id the identifier for the target control, relative to the current
* context.
*/
public ControlBean getBean(String id)
{
// If no control id separator found, the bean is a direct child of this context
int delim = id.indexOf(org.apache.beehive.controls.api.bean.ControlBean.IDSeparator);
if (delim < 0) // child is a direct descendent
return (ControlBean)_childMap.get(id);
// Find the child referenced by the first element in the path
ControlBean bean = (ControlBean)_childMap.get(id.substring(0, delim));
if (bean == null)
return bean;
// Get the BeanContext associated with the found child, and then ask it
// to resolve the rest of the path
return ((ControlBeanContext)bean.getBeanContextProxy()).getBean(id.substring(delim+1));
}
/**
* Returns the ControlBean associated directly with the ControlBeanContext. If the
* context represents a top-level container, will return null.
*/
public ControlBean getControlBean()
{
return _bean;
}
public synchronized boolean hasSingleThreadedParent()
{
return _hasSingleThreadedParent;
}
/**
* Returns true if this container associated with this context service enforces
* single-threaded invocation, false otherwise.
*
* This MUST be overridden by container-specific subclasses. If they guarantee
* single-threaded behavior (such as the EJB container), they should return true.
*/
public synchronized boolean isSingleThreadedContainer()
{
return ( hasSingleThreadedParent() || ( _bean != null && _bean.hasSingleThreadedImpl() ));
}
/**
* The initializeControl method is invoked when the implementation instance associated
* with the context has been instantiated and initialized.
*/
public void initializeControl()
{
//
// Deliver the onCreate event to any register lifecycle listeners
//
if (_lifeCycleListeners != null)
{
Iterator iter = _lifeCycleListeners.iterator();
while (iter.hasNext())
{
LifeCycle listener = (LifeCycle)iter.next();
listener.onCreate();
}
}
}
/**
* Returns the PropertyMap containing default properties for an AnnotatedElement
* in the current context. The initialization of PropertyMap binding is always
* done by delegating to a ControlContainerContext, enabling containers to implement
* property binding mechanisms (such as external config) that may override the values
* contained within the element annotations.
*/
public PropertyMap getAnnotationMap(AnnotatedElement annotElem)
{
ControlBeanContext beanContext = this;
while (beanContext != null)
{
// REVIEW: should ControlContainerContext-derived classes override getBeanAnnotationMap? Not sure
// that name makes sense, and perhaps it shouldn't take a ControlBean.
if (beanContext instanceof ControlContainerContext)
return beanContext.getBeanAnnotationMap(_bean, annotElem);
beanContext = (ControlBeanContext)beanContext.getBeanContext();
}
// No ControlContainerContext was found, so just use the default implementation
return getBeanAnnotationMap(_bean, annotElem);
}
/**
* The default implementation of getBeanAnnotationMap. This returns a map based purely
* upon annotation reflection
*/
protected PropertyMap getBeanAnnotationMap(ControlBean bean, AnnotatedElement annotElem)
{
PropertyMap map = new AnnotatedElementMap(annotElem);
// REVIEW: is this the right place to handle the general control client case?
if ( bean != null )
setDelegateMap( map, bean, annotElem );
return map;
}
static protected void setDelegateMap( PropertyMap map, ControlBean bean, AnnotatedElement annotElem )
{
//
// If building an annotation map for a method or field, we want to delegate back
// to the base control type.
//
Class annotClass = null;
if (annotElem instanceof Field)
{
annotClass = ((Field)annotElem).getType();
}
else if (annotElem instanceof Method)
{
annotClass = bean.getControlInterface();
}
if (annotClass != null)
{
PropertyMap delegateMap = bean.getAnnotationMap(annotClass);
map.setDelegateMap(delegateMap);
}
}
/**
* Returns the default binding based entirely upon annotations or naming conventions.
*/
static public String getDefaultControlBinding(Class controlIntf)
{
controlIntf = ControlBean.getMostDerivedInterface(controlIntf);
ControlInterface intfAnnot =
(ControlInterface)controlIntf.getAnnotation(ControlInterface.class);
String implBinding = intfAnnot.defaultBinding();
implBinding = resolveDefaultBinding( implBinding, controlIntf.getName() );
return implBinding;
}
/**
* Implements the default control implementation binding algorithm ( <InterfaceName> + "Impl" ). See
* documentation for the org.apache.beehive.controls.api.bean.ControlInterface annotation.
*
* @param implBinding the value of the defaultBinding attribute returned from a ControlInterface annotation
* @param controlClass the actual name of the interface decorated by the ControlInterface annotation
* @return the resolved defaultBinding value
*/
public static String resolveDefaultBinding( String implBinding, String controlClass )
{
int intfIndex = implBinding.indexOf(ControlInterface.INTERFACE_NAME);
if (intfIndex >= 0)
{
implBinding = implBinding.substring(0,intfIndex) + controlClass +
implBinding.substring(intfIndex +
ControlInterface.INTERFACE_NAME.length());
}
return implBinding;
}
//
// ControlBeanContext.getControlInterface
//
public Class getControlInterface()
{
return _bean.getControlInterface();
}
//
// ControlBeanContext.getControlPropertySet
//
public <T extends Annotation> T getControlPropertySet(Class<T> propertySet)
{
PropertyMap map = _bean.getPropertyMap();
//
// Optional properties are not exposed to clients using traditional JavaBean
// setters/getters (because there is way to represent an 'unset' value); for
// these properties, the impl can tell if the PropertySet is unset because
// this method will return null.
//
if (!map.containsPropertySet(propertySet))
{
PropertySet psAnnot = propertySet.getAnnotation(PropertySet.class);
if (psAnnot.optional())
return null;
}
//
// Construct a new PropertySet proxy instance that derives its values from
// the bean property map.
//
return PropertySetProxy.getProxy(propertySet, map);
}
//
// ControlBeanContext.getMethodPropertySet
//
public <T extends Annotation> T getMethodPropertySet(Method m, Class<T> propertySet)
{
PropertyMap map = _bean.getAnnotationMap(m);
//
// Optional properties are not exposed to clients using traditional JavaBean
// setters/getters (because there is way to represent an 'unset' value); for
// these properties, the impl can tell if the PropertySet is unset because
// this method will return null.
//
if (!map.containsPropertySet(propertySet))
{
PropertySet psAnnot = propertySet.getAnnotation(PropertySet.class);
if (psAnnot.optional())
return null;
}
//
// Construct a new PropertySet proxy instance that derives its values from
// the method property map.
//
return PropertySetProxy.getProxy(propertySet, _bean.getAnnotationMap(m));
}
//
// ControlBeanContext.getParameterPropertySet
//
public <T extends Annotation> T getParameterPropertySet(Method m, int i, Class<T> propertySet)
throws IllegalArgumentException, IndexOutOfBoundsException
{
if (i >= m.getParameterTypes().length)
throw new IndexOutOfBoundsException("Invalid parameter index for method:" + m);
//
// BUGBUG: Currently, there is no external override mechanism for method parameters
//
Annotation [] paramAnnots = m.getParameterAnnotations()[i];
for (int j = 0; j < paramAnnots.length; j++)
if (propertySet.isAssignableFrom(paramAnnots[j].getClass()))
return (T)paramAnnots[j];
return null;
}
//
// ControlBeanContext.getParameterNames
//
public String [] getParameterNames(Method m) throws IllegalArgumentException
{
return _bean.getParameterNames(m);
}
//
// ControlBeanContext.getNamedParameterValue
//
public Object getParameterValue(Method m, String parameterName, Object [] parameters)
throws IllegalArgumentException
{
String [] names = getParameterNames(m);
// Validate the input parameter array
if (parameters.length != names.length)
throw new IllegalArgumentException("Expected " + names.length + " parameters," +
"Found " + parameters.length);
// Finding the index of the matching parameter name
int i = 0;
while (i < names.length)
{
if (names[i].equals(parameterName))
break;
i++;
}
if (i == names.length)
throw new IllegalArgumentException("No method parameter with name: " + parameterName);
// Return the parameter value at the matched index
return parameters[i];
}
//
// ControlBeanContext.getPropertyMap
//
public PropertyMap getControlPropertyMap()
{
//
// Return a wrapped copy of the original bean property map, so any edits
// don't impact the bean properties.
//
return new BeanPropertyMap(_bean.getPropertyMap());
}
//
// ControlBeanContext.getService
//
public <T> T getService(Class<T> serviceClass, Object selector)
{
//
// If the requested service is a ControlBeanContext instance, the current instance
// can be returned.
//
if (serviceClass.equals(org.apache.beehive.controls.api.context.ControlBeanContext.class))
return (T)this;
//
// The parent BeanContext is responsible for providing requested services. If
// no parent context is available or it is does not manage services, then no service.
//
BeanContext bc = getBeanContext();
if (bc == null || !(bc instanceof BeanContextServices))
return null;
//
// Call getService on the parent context, using this bean as the requestor and the
// this context as the child context and ServicesRevoked event listener parameters.
//
try
{
return (T)((BeanContextServices)bc).getService(this, _bean, serviceClass, selector,
this);
}
catch (TooManyListenersException tmle)
{
// This would be highly unusual... it implies that the registration for service
// revocation notifications failed for some reason.
throw new ControlException("Unable to register for service events", tmle);
}
}
//
// ControlBeanContext.getControlHandle
//
public ControlHandle getControlHandle()
{
//
// Find the associated ControlContainerContext, which provides a container-specific
// implementation of ControlHandle
//
ControlBeanContext beanContext = this;
while (beanContext != null && !(beanContext instanceof ControlContainerContext))
beanContext = (ControlBeanContext)beanContext.getBeanContext();
if (beanContext == null)
return null;
//
// Ask the container for a ControlHandle instance referencing the target bean
//
return ((ControlContainerContext)beanContext).getControlHandle(_bean);
}
//
// ControlBeanContext.getClassLoader
//
public java.lang.ClassLoader getClassLoader()
{
return getControlInterface().getClassLoader();
}
/**
* Filename that contains ordering priority for controls interceptor services.
* Each line in the file is a fully qualified interface name. 1st line in the file
* is highest priority.
*/
public static final String INTERCEPTOR_CONFIG_FILE = "controls-interceptors.config";
/**
* Applies externally defined (via INTERCEPTOR_CONFIG_FILE) ordering priority for
* controls interceptor services.
*
* @param interceptors
* @return String[]
*/
public static String[] prioritizeInterceptors( String [] interceptors )
{
if ( interceptors == null )
return null;
// Read external configuration to obtain desired prioritization.
if ( _interceptorPriorities == null )
{
// Only ever attempt to read the external configuration once; bounce the VM if you want to try again.
_interceptorPriorities = new ArrayList<String>();
BufferedReader in = null;
try
{
InputStream configFileStream =
ControlBeanContext.class.getClassLoader().getResourceAsStream( INTERCEPTOR_CONFIG_FILE );
if ( configFileStream != null )
{
in = new BufferedReader(new InputStreamReader(configFileStream));
String str;
while ((str = in.readLine()) != null)
_interceptorPriorities.add(str);
}
}
catch (IOException e)
{
}
finally
{
try
{
if (in != null)
in.close();
}
catch ( IOException ie ) { }
}
}
// Put input list of interceptors into a Set for easy lookup
Set<String> input = new HashSet<String>();
for ( String ii : interceptors )
input.add( ii );
// Scan through priorities list, building a prioritized list
ArrayList<String> prioritized = new ArrayList<String>(interceptors.length);
for ( String p : _interceptorPriorities )
{
if ( input.contains(p) )
{
input.remove(p);
prioritized.add(p);
}
}
// Anything still left in the input set did not have a priority associated with it,
// so they just go at the end in arbitrary order.
for ( String p : input )
prioritized.add(p);
return prioritized.toArray(new String[prioritized.size()]);
}
//
// ControlBeanContext.addLifeCycleListener
//
synchronized public void addLifeCycleListener(LifeCycle listener)
{
if (_lifeCycleListeners == null)
{
_lifeCycleListeners = new Vector<LifeCycle>();
//
// Since bound/constrained property changes are exposed as lifecycle events, we
// need to register ourselves as a listener for these events the first time a
// lifecycle listener is added.
//
_bean.getPropertyChangeSupport().addPropertyChangeListener(this);
_bean.getVetoableChangeSupport().addVetoableChangeListener(this);
}
_lifeCycleListeners.addElement(listener);
}
//
// ControlBeanContext.removeLifeCycleListener
//
synchronized public void removeLifeCycleListener(LifeCycle listener)
{
if (_lifeCycleListeners != null)
_lifeCycleListeners.removeElement(listener);
}
//
// PropertyChangeListener.propertyChange
//
public void propertyChange(PropertyChangeEvent pce)
{
if (_lifeCycleListeners != null)
{
Iterator iter = _lifeCycleListeners.iterator();
while (iter.hasNext())
{
LifeCycle listener = (LifeCycle)iter.next();
listener.onPropertyChange(pce);
}
}
}
//
// VetoableChangeListener.vetoableChange
//
public void vetoableChange(PropertyChangeEvent pce) throws PropertyVetoException
{
if (_lifeCycleListeners != null)
{
Iterator iter = _lifeCycleListeners.iterator();
while (iter.hasNext())
{
LifeCycle listener = (LifeCycle)iter.next();
listener.onVetoableChange(pce);
}
}
}
/* package */ String getControlID()
{
if (_controlID != null || _bean == null)
return _controlID;
// Initially set to the local beans relative ID
String id = _bean.getLocalID();
// If there is a parent context, prepend its ID and the ID separator
BeanContext bc = getBeanContext();
if (bc != null && bc instanceof ControlBeanContext)
{
String parentID = ((ControlBeanContext)bc).getControlID();
if (parentID != null)
{
id = parentID +
org.apache.beehive.controls.api.bean.ControlBean.IDSeparator +
id;
}
}
// Cache the computed value
_controlID = id;
return id;
}
/**
* Resets the composite control ID for this context and all children beneath it. This
* can be used to invalidate cached values when necessary (for example, when a context
* is reparented).
*/
private void resetControlID()
{
_controlID = null;
Iterator childIter = this.iterator();
while (childIter.hasNext())
{
Object child = childIter.next();
if (child instanceof ControlBeanContext)
((ControlBeanContext)child).resetControlID();
}
}
//
// BeanContextServices.getCurrentServiceClasses
// Override the default implementatino of getCurrentServiceClasses inherited from
// java.beans.beancontext.BeanContextServicesSuppport. The reason for this is a bug/
// flaw in its underlying implementation. It does not include any services exposed
// by any nesting contexts. This is contradictory to the implementation of hasService()
// and getService() which do delegate up to a parent context to find services if not
// available on the local context. This means hasService() could return 'true' for a
// service that isn't returned by getCurrentServiceClasses(), which seems like a bug.
//
synchronized public Iterator getCurrentServiceClasses()
{
Set classSet = new HashSet();
BeanContextServices bcs = this;
while (bcs != null)
{
// Optimize the case where we can do a direct copy based upon impl knowledge.
if (bcs instanceof ControlBeanContext)
{
classSet.addAll(((ControlBeanContext)bcs).services.keySet());
}
else
{
Iterator iter = bcs.getCurrentServiceClasses();
while (iter.hasNext())
classSet.add(iter.next());
}
// Go up to the parent, if it is a service provider as well
BeanContext bc = getBeanContext();
if (bc instanceof BeanContextServices && bcs != bc)
bcs = (BeanContextServices)bc;
else
bcs = null;
}
return classSet.iterator();
}
//
// BeanContextServices.getCurrentServiceSelectors
// Override getCurrentServiceSelectors for the same reason as above
//
public Iterator getCurrentServiceSelectors(Class serviceClass)
{
if (hasService(serviceClass))
return super.getCurrentServiceSelectors(serviceClass);
BeanContext bc = getBeanContext();
if (bc instanceof BeanContextServices)
return ((BeanContextServices)bc).getCurrentServiceSelectors(serviceClass);
return null;
}
/**
* The ControlBean instance that this context is providing services for. This value can
* be null, if the context instance is associated with top-level (non-control) context.
*/
private ControlBean _bean;
/**
* Indicates whether this context's parent guarantees single-threaded behaviour.
*/
private boolean _hasSingleThreadedParent = false;
/**
* Maps children by the local (relative) ID of the child to the actual bean instance.
* This needs to be synchronized, because adds/removes/gets are not necessarily guaranteed
* to happen within the scope of the global hierarchy lock. It would be relatively easy
* to synchronize add/remove, since setBeanContext on the child is inside this lock scope,
* but gets on the map are another story.
*/
private Map<String,Object> _childMap =
Collections.synchronizedMap(new HashMap<String,Object>());
/**
* Maintains a set of NameGenerators (for control ID generation) keyed by a
* base prefix. The map itself is lazily constructed, so there is minimal
* overhead of no id generation is needed in this context.
*/
private Map<String,NameGenerator> _nameGenerators;
/**
* Maintains the list of lifecycle event listeners (if any) for this context.
*/
transient private Vector<LifeCycle> _lifeCycleListeners;
/**
* Caches the full composite control ID, that includes the entire path from the root
* ContainerContext to the associated bean. This value can be transient, since it
* can be easily recomputed when needed.
*/
transient private String _controlID;
private static ArrayList<String> _interceptorPriorities;
}