jakarta.faces.component.UIComponent Maven / Gradle / Ivy
/*
* Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.faces.component;
import static com.sun.faces.util.Util.isAnyNull;
import static com.sun.faces.util.Util.isOneOf;
import static jakarta.faces.application.Resource.COMPONENT_RESOURCE_KEY;
import static jakarta.faces.component.visit.VisitHint.SKIP_TRANSIENT;
import static jakarta.faces.component.visit.VisitHint.SKIP_UNRENDERED;
import static jakarta.faces.component.visit.VisitResult.ACCEPT;
import static jakarta.faces.component.visit.VisitResult.COMPLETE;
import static java.util.Collections.emptyMap;
import static java.util.logging.Level.SEVERE;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import jakarta.el.ELContext;
import jakarta.el.ELException;
import jakarta.el.ValueExpression;
import jakarta.faces.FacesException;
import jakarta.faces.FacesWrapper;
import jakarta.faces.application.Resource;
import jakarta.faces.component.visit.VisitCallback;
import jakarta.faces.component.visit.VisitContext;
import jakarta.faces.component.visit.VisitHint;
import jakarta.faces.component.visit.VisitResult;
import jakarta.faces.context.FacesContext;
import jakarta.faces.event.AbortProcessingException;
import jakarta.faces.event.ComponentSystemEvent;
import jakarta.faces.event.ComponentSystemEventListener;
import jakarta.faces.event.FacesEvent;
import jakarta.faces.event.FacesListener;
import jakarta.faces.event.PostRestoreStateEvent;
import jakarta.faces.event.SystemEvent;
import jakarta.faces.event.SystemEventListener;
import jakarta.faces.event.SystemEventListenerHolder;
import jakarta.faces.render.Renderer;
/**
*
* UIComponent is the base class for all user interface components in Jakarta Server
* Faces. The set of {@link UIComponent} instances associated with a particular request and response are organized into
* a component tree under a {@link UIViewRoot} that represents the entire content of the request or response.
*
*
*
* For the convenience of component developers, {@link UIComponentBase} provides the default behavior that is specified
* for a {@link UIComponent}, and is the base class for all of the concrete {@link UIComponent} "base" implementations.
* Component writers are encouraged to subclass {@link UIComponentBase}, instead of directly implementing this abstract
* class, to reduce the impact of any future changes to the method signatures.
*
*
*
* If the {@link jakarta.faces.event.ListenerFor} annotation is attached to the class definition of a
* Component
, that class must also implement {@link jakarta.faces.event.ComponentSystemEventListener}.
*
*
*
* Dynamically modifying the component tree can happen at any time, during and after restoring the view, but not during
* state saving and needs to function properly with respect to rendering and state saving
*
*/
public abstract class UIComponent implements PartialStateHolder, TransientStateHolder, SystemEventListenerHolder, ComponentSystemEventListener {
private static Logger LOGGER = Logger.getLogger("jakarta.faces.component", "jakarta.faces.LogStrings");
/**
*
* The value of this constant is used as the key in the component attribute map, the value for which is a
* java.beans.BeanInfo
implementation describing the composite component. This BeanInfo
is
* known as the composite component BeanInfo.
*
*
* @since 2.0
*/
public static final String BEANINFO_KEY = "jakarta.faces.component.BEANINFO_KEY";
/**
*
* The value of this constant is used as the key in the composite component BeanDescriptor for the
* Map<PropertyDescriptor>
that contains meta-information for the declared facets for this composite
* component. This map must contain an entry under the key {@link #COMPOSITE_FACET_NAME}, even if no facets were
* explicitly declared. See {@link #COMPOSITE_FACET_NAME}.
*
*
* @since 2.0
*/
public static final String FACETS_KEY = "jakarta.faces.component.FACETS_KEY";
/**
*
* The value of this constant is used as the key in the component attributes Map
for the
* {@link jakarta.faces.view.Location} in the view at which this component instance resides.
*
*
* @since 2.0
*/
public static final String VIEW_LOCATION_KEY = "jakarta.faces.component.VIEW_LOCATION_KEY";
/**
*
* The value of this constant is used as the key in the composite component BeanDescriptor for a
* ValueExpression
that evaluates to the component-type
of the composite component
* root UIComponent
for this composite component, if one was declared by the composite component
* author.
*
*
* @since 2.0
*/
public static final String COMPOSITE_COMPONENT_TYPE_KEY = "jakarta.faces.component.COMPOSITE_COMPONENT_TYPE";
/**
*
* The value of this constant is used as the key in the Map
returned as described in {@link #FACETS_KEY}
* for the PropertyDescriptor
describing the composite component facet. The value of this constant is also
* used as the key in the Map
returned from {@link #getFacets}. In this case, it refers to the actual facet
* that is the {@link jakarta.faces.component.UIPanel} that is the parent of the all of the components in the
* <composite:implementation>
section of the composite component VDL file.
*
*
* @since 2.0
*/
public static final String COMPOSITE_FACET_NAME = "jakarta.faces.component.COMPOSITE_FACET_NAME";
/**
*
* This constant enables one to quickly discover the names of the declared composite component attributes that have been
* given default values by the composite component author. The information is exposed as a
* Collection<String>
returned from the getValue()
method on the composite component
* BeanDescriptor, when this constant is passed as the argument.
*
*
* @since 2.1
*/
public static final String ATTRS_WITH_DECLARED_DEFAULT_VALUES = "jakarta.faces.component.ATTR_NAMES_WITH_DEFAULT_VALUES";
/**
* key used to look up current component stack if FacesContext attributes
*/
private static final String _CURRENT_COMPONENT_STACK_KEY = "jakarta.faces.component.CURRENT_COMPONENT_STACK";
/**
* key used to look up current composite component stack if FacesContext attributes
*/
private static final String _CURRENT_COMPOSITE_COMPONENT_STACK_KEY = "jakarta.faces.component.CURRENT_COMPOSITE_COMPONENT_STACK";
enum PropertyKeysPrivate {
attributesThatAreSet
}
/**
* Properties that are tracked by state saving.
*/
enum PropertyKeys {
rendered, attributes, bindings, rendererType, systemEventListeners, behaviors, passThroughAttributes
}
/**
* List of attributes that have been set on the component (this may be from setValueExpression, the attributes map, or
* setters from the concrete HTML components. This allows for faster rendering of attributes as this list is
* authoritative on what has been set.
*/
List attributesThatAreSet;
ComponentStateHelper stateHelper;
UIComponent compositeParent;
private boolean isInView;
private Map resourceBundleMap;
// It is safe to cache this because components never go from being
// composite to non-composite.
private transient Boolean isCompositeComponent;
/**
* Track whether we have been pushed as current in order to handle mismatched pushes and pops of Jakarta Expression
* Language context stack. We use a counter to handle cases where the same component is pushed on multiple times
*/
private int _isPushedAsCurrentRefCount = 0;
// -------------------------------------------------------------- Attributes
/**
*
* Return a mutable Map
representing the attributes (and properties, see below) associated wth this
* {@link UIComponent}, keyed by attribute name (which must be a String). The returned implementation must support all
* of the standard and optional Map
methods, plus support the following additional requirements:
*
*
* - The
Map
implementation must implement the java.io.Serializable
interface.
* - Any attempt to add a
null
key or value must throw a NullPointerException
.
* - Any attempt to add a key that is not a String must throw a
ClassCastException
.
* - If the attribute name specified as a key matches a property of this {@link UIComponent}'s implementation class,
* the following methods will have special behavior:
*
* containsKey
- Return false
.
* get()
- If the property is readable, call the getter method and return the returned value (wrapping
* primitive values in their corresponding wrapper classes); otherwise throw IllegalArgumentException
.
* put()
- If the property is writeable, call the setter method to set the corresponding value
* (unwrapping primitive values in their corresponding wrapper classes). If the property is not writeable, or an attempt
* is made to set a property of primitive type to null
, throw IllegalArgumentException
.
* remove
- Throw IllegalArgumentException
.
*
*
*
*
* @return the component attribute map.
*/
public abstract Map getAttributes();
/**
*
* This is a convenience method that simply calls {@link #getPassThroughAttributes(boolean)}, passing {@code true} as
* the argument. This method must never return {@code null}.
*
*
* @return the pass-through attribute map.
* @since 2.2
*/
public Map getPassThroughAttributes() {
return getPassThroughAttributes(true);
}
/**
*
* This method has the same specification as {@link #getPassThroughAttributes() } except that it is allowed to return
* {@code null} if and only if the argument {@code create} is {@code false} and no pass through attribute data structure
* exists for this instance. The returned {@code Map} implementation must support all of the standard and optional
* {@code Map} methods, plus support the following additional requirements. The map must be stored in using
* {@link #getStateHelper}.
*
*
*
*
*
* The {@code Map} implementation must implement {@code java.io.Serializable}.
*
*
*
* Any attempt to add a {@code null} key or value must throw a {@code NullPointerException}.
*
*
*
* Any attempt to add a key that is not a {@code String} must throw an {@code IllegalArgumentException}.
*
*
*
* For backward compatibility with components that extend directly from this class, a default implementation is provided
* that returns the empty map.
*
*
*
*
* @param create if true
, a new {@code Map} instance will be created if it does not exist already. If
* false
, and there is no existing Map
instance, one will not be created and null
* will be returned.
* @return A {@code Map} instance, or {@code null}.
*
* @since 2.2
*/
public Map getPassThroughAttributes(boolean create) {
return emptyMap();
}
// ---------------------------------------------------------------- Bindings
/**
*
* Return the {@link ValueExpression} used to calculate the value for the specified attribute or property name, if any.
*
*
*
* This method must be overridden and implemented for components that comply with Jakarta Faces 1.2 and later.
*
*
* @param name Name of the attribute or property for which to retrieve a {@link ValueExpression}
* @return the value expression, or null
.
* @since 1.2
* @throws NullPointerException if name
is null
*
*/
public ValueExpression getValueExpression(String name) {
if (name == null) {
throw new NullPointerException();
}
@SuppressWarnings("unchecked")
Map map = (Map) getStateHelper().get(UIComponentBase.PropertyKeys.bindings);
return map != null ? map.get(name) : null;
}
/**
*
* Set the {@link ValueExpression} used to calculate the value for the specified attribute or property name, if any.
*
*
*
* The implementation must call {@link ValueExpression#isLiteralText} on the argument expression
. If
* isLiteralText()
returns true
, invoke {@link ValueExpression#getValue} on the argument
* expression and pass the result as the value
parameter in a call to this.{@link
* #getAttributes()}.put(name, value)
where name
is the argument name
. If an exception
* is thrown as a result of calling {@link ValueExpression#getValue}, wrap it in a {@link jakarta.faces.FacesException}
* and re-throw it. If isLiteralText()
returns false
, simply store the un-evaluated
* expression
argument in the collection of ValueExpression
s under the key given by the
* argument name
.
*
*
*
* This method must be overridden and implemented for components that comply with Jakarta Faces 1.2 and later.
*
*
* @since 1.2
*
* @param name Name of the attribute or property for which to set a {@link ValueExpression}
* @param binding The {@link ValueExpression} to set, or null
to remove any currently set
* {@link ValueExpression}
*
* @throws IllegalArgumentException if name
is one of id
or parent
* @throws NullPointerException if name
is null
*
*/
public void setValueExpression(String name, ValueExpression binding) {
if (name == null) {
throw new NullPointerException();
}
if (isOneOf(name, "id", "parent")) {
throw new IllegalArgumentException();
}
if (binding != null) {
if (!binding.isLiteralText()) {
@SuppressWarnings("unchecked")
List sProperties = (List) getStateHelper().get(PropertyKeysPrivate.attributesThatAreSet);
if (sProperties == null) {
getStateHelper().add(PropertyKeysPrivate.attributesThatAreSet, name);
} else if (!sProperties.contains(name)) {
getStateHelper().add(PropertyKeysPrivate.attributesThatAreSet, name);
}
getStateHelper().put(UIComponentBase.PropertyKeys.bindings, name, binding);
} else {
ELContext context = FacesContext.getCurrentInstance().getELContext();
try {
getAttributes().put(name, binding.getValue(context));
} catch (ELException ele) {
throw new FacesException(ele);
}
}
} else {
getStateHelper().remove(PropertyKeysPrivate.attributesThatAreSet, name);
getStateHelper().remove(UIComponentBase.PropertyKeys.bindings, name);
}
}
// -------------------------------------------------------------- Properties
boolean initialState;
/**
*
* An implementation of {@link PartialStateHolder#markInitialState}, this method is called by the runtime to indicate
* that the instance should start tracking changes to its state.
*
*
* @since 2.0
*/
@Override
public void markInitialState() {
initialState = true;
}
/**
*
* An implementation of {@link PartialStateHolder#initialStateMarked}, this method is called by the runtime to test if
* the {@link PartialStateHolder#markInitialState} method was called.
*
*
* @since 2.0
*/
@Override
public boolean initialStateMarked() {
return initialState;
}
/**
*
* An implementation of {@link PartialStateHolder#clearInitialState}, this method is called by the runtime to tell the
* instance to stop tracking state changes.
*
*
* @since 2.0
*/
@Override
public void clearInitialState() {
initialState = false;
}
/**
*
* Return the {@link StateHelper} instance used to help this component implement {@link PartialStateHolder}.
*
*
* @return the state helper.
* @since 2.0
*/
protected StateHelper getStateHelper() {
return getStateHelper(true);
}
/**
*
* Like {@link #getStateHelper()}, but only create a state helper instance if the argument creat
is
* true
.
*
*
* @param create if true
, a new {@link StateHelper} instance will be created if it does not exist already.
* If false
, and there is no existing StateHelper
instance, one will not be created and
* null
will be returned.
*
* @return the state helper.
* @since 2.0
*/
protected StateHelper getStateHelper(boolean create) {
if (create && stateHelper == null) {
stateHelper = new ComponentStateHelper(this);
}
return stateHelper;
}
/**
*
* Return the {@link TransientStateHelper} instance for this UIComponent
instance. The default
* implementation simply calls through to {@link #getTransientStateHelper(boolean)} passing true
as the
* argument.
*
*
* @return the transient state helper.
* @since 2.1
*/
public TransientStateHelper getTransientStateHelper() {
return getTransientStateHelper(true);
}
/**
*
* Return the {@link TransientStateHelper} instance for this UIComponent
instance.
*
*
* @param create if true
create, if necessary, any internal data structures. If false
, do not
* create any instances. In this case, it is possible for this method to return null
.
* @return the transient state helper.
* @since 2.1
*/
public TransientStateHelper getTransientStateHelper(boolean create) {
if (create && stateHelper == null) {
stateHelper = new ComponentStateHelper(this);
}
return stateHelper;
}
/**
*
* For components that need to support the concept of transient state, this method will restore any state saved on a
* prior call to {@link #saveTransientState}.
*
*
* @since 2.1
*/
@Override
public void restoreTransientState(FacesContext context, Object state) {
boolean forceCreate = state != null;
TransientStateHelper helper = getTransientStateHelper(forceCreate);
if (helper != null) {
helper.restoreTransientState(context, state);
}
}
/**
*
* For components that need to support the concept of transient state, this method will save any state that is known to
* be transient in nature.
*
*
* @since 2.1
*/
@Override
public Object saveTransientState(FacesContext context) {
TransientStateHelper helper = getTransientStateHelper(false);
return helper == null ? null : helper.saveTransientState(context);
}
/**
*
* Return true
if this component is within the view hierarchy otherwise false
*
* @return true
if within a view hierarchy, false
otherwise.
* @since 2.0
*/
public boolean isInView() {
return isInView;
}
/**
*
* Updates the status as to whether or not this component is currently within the view hierarchy. This method
* must never be called by developers; a {@link UIComponent}'s internal implementation will call it as components are
* added to or removed from a parent's child List
or facet Map
.
*
*
* @param isInView flag indicating whether or not this component is within the view hierachy
*
* @since 2.0
*/
public void setInView(boolean isInView) {
this.isInView = isInView;
}
/**
*
* Enable Jakarta Expression Language to access the clientId
of a component. This is particularly useful in
* combination with the component
and cc
implicit objects. A default implementation is
* provided that simply calls {@link FacesContext#getCurrentInstance} and then calls through to
* {@link #getClientId(FacesContext)}.
*
*
* @return the client id.
* @since 2.0
*/
public String getClientId() {
return getClientId(FacesContext.getCurrentInstance());
}
/**
*
* Return a client-side identifier for this component, generating one if necessary. The associated {@link Renderer}, if
* any, will be asked to convert the clientId to a form suitable for transmission to the client.
*
*
*
* The return from this method must be the same value throughout the lifetime of the instance, unless the
* id
property of the component is changed, or the component is placed in a {@link NamingContainer} whose
* client ID changes (for example, {@link UIData}). However, even in these cases, consecutive calls to this method must
* always return the same value. The implementation must follow these steps in determining the clientId:
*
*
*
* Find the closest ancestor to this component in the view hierarchy that implements
* NamingContainer
. Call getContainerClientId()
on it and save the result as the
* parentId
local variable. Call {@link #getId} on this component and save the result as the
* myId
local variable. If myId
is null
, call
* context.getViewRoot().createUniqueId()
and assign the result to myId. If parentId
is
* non-null
, let myId
equal parentId
* + {@link UINamingContainer#getSeparatorChar} + myId
. Call {@link Renderer#convertClientId}, passing
* myId
, and return the result.
*
*
* @param context The {@link FacesContext} for the current request
* @return the client id.
*
* @throws NullPointerException if context
is null
*/
public abstract String getClientId(FacesContext context);
/**
*
* Allow components that implement {@link NamingContainer} to selectively disable prepending their clientId to their
* descendent's clientIds by breaking the prepending logic into a separately callable method. See {@link #getClientId}
* for usage.
*
*
*
* By default, this method will call through to {@link #getClientId} and return the result.
*
* @param context the Faces context.
* @return the container client id.
* @since 1.2
*
* @throws NullPointerException if context
is null
*/
public String getContainerClientId(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
return getClientId(context);
}
/**
*
* Return the identifier of the component family to which this component belongs. This identifier, in conjunction with
* the value of the rendererType
property, may be used to select the appropriate {@link Renderer} for this
* component instance. Note this method should NOT return null
*
*
* @return the component family (not null).
*/
public abstract String getFamily();
/**
*
* Return the component identifier of this {@link UIComponent}.
*
*
* @return the component identifier.
*/
public abstract String getId();
/**
*
* Set the component identifier of this {@link UIComponent} (if any). Component identifiers must obey the following
* syntax restrictions:
*
*
* - Must not be a zero-length String.
* - First character must be a letter or an underscore ('_').
* - Subsequent characters must be a letter, a digit, an underscore ('_'), or a dash ('-').
* -
*
*
*
* Component identifiers must also obey the following semantic restrictions (note that this restriction is
* NOT enforced by the setId()
implementation):
*
*
* - The specified identifier must be unique among all the components (including facets) that are descendents of the
* nearest ancestor {@link UIComponent} that is a {@link NamingContainer}, or within the scope of the entire component
* tree if there is no such ancestor that is a {@link NamingContainer}.
*
*
* @param id The new component identifier, or null
to indicate that this {@link UIComponent} does not have
* a component identifier
*
* @throws IllegalArgumentException if id
is not syntactically valid
*/
public abstract void setId(String id);
/**
*
* Return the parent {@link UIComponent} of this UIComponent
, if any. A component must allow child
* components to be added to and removed from the list of children of this component, even though the child component
* returns null from getParent( )
.
*
*
* @return the parent component.
*/
public abstract UIComponent getParent();
/**
*
* Set the parent UIComponent
of this
* UIComponent
. If
* parent.isInView()
returns true
, calling this method will first cause a
* {@link jakarta.faces.event.PreRemoveFromViewEvent} to be published, for this node, and then the children of this
* node. Then, once the re-parenting has occurred, a {@link jakarta.faces.event.PostAddToViewEvent} will be published as
* well, first for this node, and then for the node's children, but only if any
* of the following conditions are true.
*
*
*
*
*
* -
*
* {@link jakarta.faces.context.FacesContext#getCurrentPhaseId} returns {@link jakarta.faces.event.PhaseId#RESTORE_VIEW}
* and partial state saving is enabled.
*
*
*
* -
*
* {@link jakarta.faces.context.FacesContext#isPostback} returns false
and
* {@link jakarta.faces.context.FacesContext#getCurrentPhaseId} returns something other than
* {@link jakarta.faces.event.PhaseId#RESTORE_VIEW}
*
*
*
*
*
*
*
*
* This method must never be called by developers; a {@link UIComponent}'s internal implementation will call it
* as components are added to or removed from a parent's child List
or facet Map
.
*
*
* @param parent The new parent, or null
for the root node of a component tree
*/
public abstract void setParent(UIComponent parent);
/**
*
* Return true
if this component (and its children) should be rendered during the Render Response
* phase of the request processing lifecycle.
*
*
* @return true
if the component should be rendered, false
otherwise.
*/
public abstract boolean isRendered();
/**
*
* Set the rendered
property of this {@link UIComponent}.
*
*
* @param rendered If true
render this component; otherwise, do not render this component
*/
public abstract void setRendered(boolean rendered);
/**
*
* Return the {@link Renderer} type for this {@link UIComponent} (if any).
*
*
* @return the renderer type.
*/
public abstract String getRendererType();
/**
*
* Set the {@link Renderer} type for this {@link UIComponent}, or null
for components that render
* themselves.
*
*
* @param rendererType Logical identifier of the type of {@link Renderer} to use, or null
for components
* that render themselves
*/
public abstract void setRendererType(String rendererType);
/**
*
* Return a flag indicating whether this component is responsible for rendering its child components. The default
* implementation in {@link UIComponentBase#getRendersChildren} tries to find the renderer for this component. If it
* does, it calls {@link Renderer#getRendersChildren} and returns the result. If it doesn't, it returns false. As of
* version 1.2 of the Jakarta Faces Specification, component authors are encouraged to return true
* from this method and rely on {@link UIComponentBase#encodeChildren}.
*
*
* @return true
if the component renders its children, false
otherwise.
*/
public abstract boolean getRendersChildren();
/**
*
* Return a Map<String,String>
of the ResourceBundle
for this component. A component may
* have a ResourceBundle
associated with it. This bundle may contain localized properties relating to
* instances of this component. The default implementation first looks for a ResourceBundle
with a base
* name equal to the fully qualified class name of the current UIComponent this
and Locale
* equal to the Locale
of the current UIViewRoot
. If no such bundle is found, and the
* component is a composite component, let resourceName be the resourceName of the {@link Resource}
* for this composite component, replacing the file extension with ".properties". Let libraryName be the
* libraryName of the the {@link Resource} for this composite component. Call
* {@link jakarta.faces.application.ResourceHandler#createResource(java.lang.String,java.lang.String)}, passing the
* derived resourceName and libraryName. Note that this will automatically allow for the localization
* of the ResourceBundle
due to the localization facility implemented in createResource
, which
* is specified in section 2.6.1.3 "Resource Identifiers" of the Jakarta Faces Specification Document.
* If the resultant {@link Resource} exists and can be
* found, the InputStream
for the resource is used to create a ResourceBundle
. If either of
* the two previous steps for obtaining the ResourceBundle
for this component is successful, the
* ResourceBundle
is wrapped in a Map<String,String>
and returned. Otherwise
* Collections.EMPTY_MAP
is returned.
*
*
* @return the resource bundle map.
* @since 2.0
*/
public Map getResourceBundleMap() {
if (resourceBundleMap == null) {
FacesContext context = FacesContext.getCurrentInstance();
// Step 1: look for a ResourceBundle under the FQCN of this instance
ResourceBundle resourceBundle = findResourceBundleUnderFQCNofThis(context);
// Step 2: if this is a composite component, look for a
// ResourceBundle as a Resource
if (resourceBundle == null) {
resourceBundle = findResourceBundleAsResource(context);
}
// Step 3: if the previous steps yielded a ResourceBundle, wrap it
// with a Map
if (resourceBundle != null) {
resourceBundleMap = wrapBundleAsMap(resourceBundle);
}
if (resourceBundleMap == null) {
resourceBundleMap = emptyMap();
}
}
return resourceBundleMap;
}
// This is necessary for Jakarta Faces components that extend from UIComponent
// directly rather than extending from UIComponentBase. Such components
// may need to have implementations provided for methods that originated
// from a spec version more recent than the version with which the component
// complies. Currently this private property is only consulted in the
// getValueExpression() method.
// private boolean isUIComponentBase;
// private boolean isUIComponentBaseIsSet = false;
//
// private boolean isUIComponentBase() {
// if (!isUIComponentBaseIsSet) {
// isUIComponentBase = (this instanceof UIComponentBase);
// }
//
// return isUIComponentBase;
// }
// ------------------------------------------------- Tree Management Methods
/**
*
* Return a mutable List
representing the child
* {@link UIComponent}s associated with this component. The returned implementation must support all of the standard and
* optional List
methods, plus support the following additional requirements:
*
*
* - The
List
implementation must implement the java.io.Serializable
interface.
* - Any attempt to add a
null
must throw a NullPointerException
* - Any attempt to add an object that does not implement {@link UIComponent} must throw a ClassCastException.
* - Whenever a new child component is added, the
parent
property of the child must be set to this
* component instance. If the parent
property of the child was already non-null, the child must first be
* removed from its previous parent (where it may have been either a child or a facet).
* - Whenever an existing child component is removed, the
parent
property of the child must be set to
* null
.
*
* -
*
* After the child component has been added to the view, {@link jakarta.faces.application.Application#publishEvent} must
* be called, passing {@link jakarta.faces.event.PostAddToViewEvent}.class
as the first argument and the
* newly added component as the second argument if any the following cases are true.
*
*
*
*
* -
*
* {@link jakarta.faces.context.FacesContext#getCurrentPhaseId} returns {@link jakarta.faces.event.PhaseId#RESTORE_VIEW}
* and partial state saving is enabled.
*
*
*
* -
*
* {@link jakarta.faces.context.FacesContext#isPostback} returns false
and
* {@link jakarta.faces.context.FacesContext#getCurrentPhaseId} returns something other than
* {@link jakarta.faces.event.PhaseId#RESTORE_VIEW}
*
*
*
*
*
*
*
*
*
* @return the list of children.
*/
public abstract List getChildren();
/**
*
* Return the number of child {@link UIComponent}s that are associated with this {@link UIComponent}. If there are no
* children, this method must return 0. The method must not cause the creation of a child component list.
*
*
* @return the number of child components.
*/
public abstract int getChildCount();
/**
*
* Search for and return the {@link UIComponent} with an id
that
* matches the specified search expression (if any), according to the algorithm described below.
*
*
*
* WARNING: The found UIComponent
instance, if any, is returned without regard for its
* tree traversal context. Retrieving an Jakarta Expression Language-bound attribute from the component is not safe.
* Jakarta Expression Language expressions can contain implicit objects, such as #{component}
, which assume
* they are being evaluated within the scope of a tree traversal context. Evaluating expressions with these kinds of
* implicit objects outside of a tree traversal context produces undefined results. See {@link #invokeOnComponent} for a
* method that does correctly account for the tree traversal context when operating on the found
* UIComponent
instance. {@link #invokeOnComponent} is also useful to find components given a simple
* clientId
.
*
*
* Component identifiers are required to be unique within the scope of the closest ancestor {@link NamingContainer} that
* encloses this component (which might be this component itself). If there are no {@link NamingContainer} components in
* the ancestry of this component, the root component in the tree is treated as if it were a {@link NamingContainer},
* whether or not its class actually implements the {@link NamingContainer} interface.
*
*
*
* A search expression consists of either an identifier (which is matched exactly against the id
* property of a {@link UIComponent}, or a series of such identifiers linked by the
* {@link UINamingContainer#getSeparatorChar} character value. The search algorithm should operates as follows, though
* alternate alogrithms may be used as long as the end result is the same:
*
*
*
* - Identify the {@link UIComponent} that will be the base for searching, by stopping as soon as one of the following
* conditions is met:
*
* - If the search expression begins with the the separator character (called an "absolute" search expression), the
* base will be the root {@link UIComponent} of the component tree. The leading separator character will be stripped
* off, and the remainder of the search expression will be treated as a "relative" search expression as described
* below.
* - Otherwise, if this {@link UIComponent} is a {@link NamingContainer} it will serve as the basis.
* - Otherwise, search up the parents of this component. If a {@link NamingContainer} is encountered, it will be the
* base.
* - Otherwise (if no {@link NamingContainer} is encountered) the root {@link UIComponent} will be the base.
*
*
* - The search expression (possibly modified in the previous step) is now a "relative" search expression that will be
* used to locate the component (if any) that has an
id
that matches, within the scope of the base
* component. The match is performed as follows:
*
* - If the search expression is a simple identifier, this value is compared to the
id
property, and then
* recursively through the facets and children of the base {@link UIComponent} (except that if a descendant
* {@link NamingContainer} is found, its own facets and children are not searched).
* - If the search expression includes more than one identifier separated by the separator character, the first
* identifier is used to locate a {@link NamingContainer} by the rules in the previous bullet point. Then, the
*
findComponent()
method of this {@link NamingContainer} will be called, passing the remainder of the
* search expression.
*
*
*
*
* @param expr Search expression identifying the {@link UIComponent} to be returned
*
* @return the found {@link UIComponent}, or null
if the component was not found.
*
* @throws IllegalArgumentException if an intermediate identifier in a search expression identifies a
* {@link UIComponent} that is not a {@link NamingContainer}
* @throws NullPointerException if expr
is null
*/
public abstract UIComponent findComponent(String expr);
/**
*
* Starting at this component in the View hierarchy, search for a component
* with a clientId
equal to the argument clientId
and, if found, call the
* {@link ContextCallback#invokeContextCallback} method on the argument callback
, passing the current
* {@link FacesContext} and the found component as arguments. This method is similar to {@link #findComponent} but it
* does not support the leading {@link UINamingContainer#getSeparatorChar} syntax for searching from the root of the
* View.
*
*
*
* The default implementation will first check if this.getClientId()
is equal to the argument
* clientId
. If so, first call {@link #pushComponentToEL}, then call
* the {@link ContextCallback#invokeContextCallback} method on the argument callback, passing through the
* FacesContext
argument and passing this as the component argument. Then
* call {@link #popComponentFromEL}. If an Exception
is thrown by the callback, wrap it in a
* {@link FacesException} and re-throw it. Otherwise, return true
.
*
*
*
* Otherwise, for each component returned by {@link #getFacetsAndChildren}, call invokeOnComponent()
* passing the arguments to this method, in order. The first time invokeOnComponent()
returns true, abort
* traversing the rest of the Iterator
and return true
.
*
*
*
* When calling {@link ContextCallback#invokeContextCallback} the implementation of this method must guarantee that the
* state of the component passed to the callback correctly reflects the component's position in the View hierarchy with
* respect to any state found in the argument clientId
. For example, an iterating component such as
* {@link UIData} will need to set its row index to correctly reflect the argument clientId
before finding
* the appropriate child component backed by the correct row. When the callback returns, either normally or by throwing
* an Exception
the implementation of this method must restore the state of the view to the way it was
* before invoking the callback.
*
*
*
* If none of the elements from {@link #getFacetsAndChildren} returned true
from
* invokeOnComponent()
, return false
.
*
*
*
* Simple usage example to find a component by clientId
.
*
*
*
*
private UIComponent found = null;
private void doFind(FacesContext context, String clientId) {
context.getViewRoot().invokeOnComponent(context, clientId,
new ContextCallback() {
public void invokeContextCallback(FacesContext context,
UIComponent component) {
found = component;
}
});
}
*
*
*
*
*
* @since 1.2
*
* @param context the {@link FacesContext} for the current request
*
* @param clientId the client identifier of the component to be passed to the argument callback.
*
* @param callback an implementation of the Callback interface.
*
* @throws NullPointerException if any of the arguments are null
*
* @throws FacesException if the argument Callback throws an Exception, it is wrapped in a FacesException
* and re-thrown.
*
* @return true
if the a component with the given clientId
is found, the callback method was
* successfully invoked passing that component as an argument, and no Exception was thrown. Returns false
* if no component with the given clientId
is found.
*
*/
public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback) throws FacesException {
if (isAnyNull(context, clientId, callback)) {
throw new NullPointerException();
}
boolean found = false;
if (clientId.equals(getClientId(context))) {
try {
pushComponentToEL(context, this);
callback.invokeContextCallback(context, this);
return true;
} catch (Exception e) {
throw new FacesException(e);
} finally {
popComponentFromEL(context);
}
} else {
Iterator facetsAndChildrenIterator = getFacetsAndChildren();
while (facetsAndChildrenIterator.hasNext() && !found) {
found = facetsAndChildrenIterator.next().invokeOnComponent(context, clientId, callback);
}
}
return found;
}
// ------------------------------------------------ Facet Management Methods
/**
*
* Return a mutable Map
representing the facet {@link UIComponent}s associated with this
* {@link UIComponent}, keyed by facet name (which must be a String). The returned implementation must support all of
* the standard and optional Map
methods, plus support the following additional requirements:
*
*
*
* - The
Map
implementation must implement the java.io.Serializable
interface.
* - Any attempt to add a
null
key or value must throw a NullPointerException.
* - Any attempt to add a key that is not a String must throw a ClassCastException.
* - Any attempt to add a value that is not a {@link UIComponent} must throw a ClassCastException.
* - Whenever a new facet {@link UIComponent} is added:
*
* - The
parent
property of the component must be set to this component instance.
* - If the
parent
property of the component was already non-null, the component must first be removed
* from its previous parent (where it may have been either a child or a facet).
*
*
*
* - Whenever an existing facet {@link UIComponent} is removed:
*
* - The
parent
property of the facet must be set to null
.
*
*
*
*
* @return the map of facets.
*/
public abstract Map getFacets();
/**
*
* Return the number of facet {@link UIComponent}s that are associated with this {@link UIComponent}. If there are no
* facets, this method must return 0. The method must not cause the creation of a facet component map.
*
*
*
* For backwards compatability with classes that extend UIComponent directly, a default implementation is provided that
* simply calls {@link #getFacets} and then calls the size()
method on the returned Map
. A
* more optimized version of this method is provided in {@link UIComponentBase#getFacetCount}.
*
* @return the number of facets.
* @since 1.2
*/
public int getFacetCount() {
return getFacets().size();
}
/**
*
* Convenience method to return the named facet, if it exists, or null
otherwise. If the requested facet
* does not exist, the facets Map must not be created.
*
*
* @param name Name of the desired facet
* @return the component, or null
.
*/
public abstract UIComponent getFacet(String name);
/**
*
* Return an Iterator
over the facet followed by child {@link UIComponent}s of this {@link UIComponent}.
* Facets are returned in an undefined order, followed by all the children in the order they are stored in the child
* list. If this component has no facets or children, an empty Iterator
is returned.
*
*
*
* The returned Iterator
must not support the remove()
operation.
*
*
* @return the facets and children iterator.
*/
public abstract Iterator getFacetsAndChildren();
// -------------------------------------------- Lifecycle Processing Methods
/**
*
* Broadcast the specified {@link FacesEvent} to all registered event listeners who have expressed an interest in events
* of this type. Listeners are called in the order in which they were added.
*
*
* If the event
is an instance of {@link jakarta.faces.event.BehaviorEvent} and the current
* component
is the source of the event
call
* {@link jakarta.faces.event.BehaviorEvent#getBehavior} to get the {@link jakarta.faces.component.behavior.Behavior}
* for the event.
*
* Call
* {@link jakarta.faces.component.behavior.Behavior#broadcast(jakarta.faces.event.BehaviorEvent)} on the
* Behavior
instance.
*
*
* @param event The {@link FacesEvent} to be broadcast
*
* @throws AbortProcessingException Signal the Jakarta Faces implementation that no further processing on the
* current event should be performed
* @throws IllegalArgumentException if the implementation class of this {@link FacesEvent} is not supported by this
* component
* @throws NullPointerException if event
is null
*/
public abstract void broadcast(FacesEvent event) throws AbortProcessingException;
/**
*
* Decode any new state of this {@link UIComponent} from the request contained in the specified {@link FacesContext},
* and store this state as needed.
*
*
* During decoding, events may be enqueued for later processing (by event listeners who have registered an interest), by
* calling queueEvent()
.
*
*
* @param context {@link FacesContext} for the request we are processing
*
* @throws NullPointerException if context
is null
*/
public abstract void decode(FacesContext context);
/**
*
* Perform a tree visit starting at this node in the tree.
*
*
*
*
*
* UIComponent.visitTree() implementations do not invoke the {@link VisitCallback} directly, but instead call
* {@link VisitContext#invokeVisitCallback} to invoke the callback. This allows {@code VisitContext} implementations to
* provide optimized tree traversals, for example by only calling the {@code
* VisitCallback} for a subset of components.
*
*
*
* UIComponent.visitTree() implementations must call UIComponent.pushComponentToEL() before performing the visit and
* UIComponent.popComponentFromEL() after the visit.
*
*
*
* @param visitContext the VisitContext
for this visit
* @param callback the VisitCallback
instance whose visit
method will be called for each node
* visited.
* @return component implementations may return true
to indicate that the tree visit is complete (eg. all
* components that need to be visited have been visited). This results in the tree visit being short-circuited such that
* no more components are visited.
*
* @see VisitContext#invokeVisitCallback VisitContext.invokeVisitCallback()
*
* @since 2.0
*/
public boolean visitTree(VisitContext visitContext, VisitCallback callback) {
// First check to see whether we are visitable. If not
// short-circuit out of this subtree, though allow the
// visit to proceed through to other subtrees.
if (!isVisitable(visitContext)) {
return false;
}
// Push ourselves to Jakarta Expression Language before visiting
FacesContext facesContext = visitContext.getFacesContext();
pushComponentToEL(facesContext, null);
try {
// Visit ourselves. Note that we delegate to the
// VisitContext to actually perform the visit.
VisitResult result = visitContext.invokeVisitCallback(this, callback);
// If the visit is complete, short-circuit out and end the visit
if (result == COMPLETE) {
return true;
}
// Visit children if necessary
if (result == ACCEPT) {
Iterator kids = getFacetsAndChildren();
while (kids.hasNext()) {
boolean done = kids.next().visitTree(visitContext, callback);
// If any kid visit returns true, we are done.
if (done) {
return true;
}
}
}
} finally {
// Pop ourselves off the Jakarta Expression Language stack
popComponentFromEL(facesContext);
}
// Return false to allow the visit to continue
return false;
}
/**
*
* Return true
if this component should be visited, false
otherwise. Called by
* {@link UIComponent#visitTree UIComponent.visitTree()} to determine whether this component satisfies the hints
* returned by {@link jakarta.faces.component.visit.VisitContext#getHints}.
*
*
*
*
*
* If this method returns false, the tree visited is short-circuited such that neither the component nor any of its
* descendents will be visited
*
*
*
* Custom {@code visitTree()} implementations may call this method to determine whether the component is visitable
* before performing any visit-related processing.
*
*
*
*
* @param context the Visit context.
* @return true
if visitable, false
otherwise.
* @since 2.0
*/
protected boolean isVisitable(VisitContext context) {
// VisitHints currently defines two hints that affect visitability:
// VIIST_RENDERED and VISIT_TRANSIENT.
// Check for both of these and if set, verify that we comply.
Set hints = context.getHints();
if (hints.contains(SKIP_UNRENDERED) && !isRendered() || hints.contains(SKIP_TRANSIENT) && isTransient()) {
return false;
}
return true;
}
/**
*
* If our rendered
property is true
, render the
* beginning of the current state of this {@link UIComponent} to the response contained in the specified
* {@link FacesContext}. Call
* {@link #pushComponentToEL(jakarta.faces.context.FacesContext,jakarta.faces.component.UIComponent)}. Call
* {@link jakarta.faces.application.Application#publishEvent}, passing
* {@link jakarta.faces.event.PreRenderComponentEvent}.class
as the first argument and the component
* instance to be rendered as the second argument.
*
*
*
* If a {@link Renderer} is associated with this {@link UIComponent}, the actual encoding will be delegated to
* {@link Renderer#encodeBegin(FacesContext, UIComponent)}.
*
*
*
* If our rendered
property is false
, call
* {@link #pushComponentToEL(jakarta.faces.context.FacesContext,jakarta.faces.component.UIComponent)} and return
* immediately.
*
*
* @param context {@link FacesContext} for the response we are creating
*
* @throws IOException if an input/output error occurs while rendering
* @throws NullPointerException if context
is null
*/
public abstract void encodeBegin(FacesContext context) throws IOException;
/**
*
* If our rendered
property is true
, render the child {@link UIComponent}s of this
* {@link UIComponent}. This method will only be called if the rendersChildren
property is
* true
.
*
*
*
* If a {@link Renderer} is associated with this {@link UIComponent}, the actual encoding will be delegated to
* {@link Renderer#encodeChildren(FacesContext, UIComponent)}. If no {@link Renderer}
* is associated with this {@link UIComponent}, iterate over each of the children of this component and call
* {@link #encodeAll(jakarta.faces.context.FacesContext)}.
*
*
* @param context {@link FacesContext} for the response we are creating
*
* @throws IOException if an input/output error occurs while rendering
* @throws NullPointerException if context
is null
*/
public abstract void encodeChildren(FacesContext context) throws IOException;
/**
*
* If our rendered
property is true
, render the
* ending of the current state of this {@link UIComponent}.
*
*
*
* If a {@link Renderer} is associated with this {@link UIComponent}, the actual encoding will be delegated to
* {@link Renderer#encodeEnd(FacesContext, UIComponent)}.
*
*
*
* Call {@link UIComponent#popComponentFromEL}. before returning regardless of the value of the rendered
* property.
*
*
* @param context {@link FacesContext} for the response we are creating
*
* @throws IOException if an input/output error occurs while rendering
* @throws NullPointerException if context
is null
*/
public abstract void encodeEnd(FacesContext context) throws IOException;
/**
*
* If this component returns true
from {@link #isRendered}, take the following action.
*
*
*
* Render this component and all its children that return true
from isRendered()
, regardless
* of the value of the {@link #getRendersChildren} flag.
*
*
* @param context the Faces context.
* @since 1.2
* @throws IOException if an input/output error occurs while rendering
* @throws NullPointerException if context
is null
*/
public void encodeAll(FacesContext context) throws IOException {
if (context == null) {
throw new NullPointerException();
}
if (!isRendered()) {
return;
}
encodeBegin(context);
if (getRendersChildren()) {
encodeChildren(context);
} else if (getChildCount() > 0) {
for (UIComponent kid : getChildren()) {
kid.encodeAll(context);
}
}
encodeEnd(context);
}
@SuppressWarnings("unchecked")
private static ArrayDeque _getComponentELStack(String keyName, Map