javax.faces.component.UIComponentBase Maven / Gradle / Ivy
Show all versions of jsf-api Show documentation
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package javax.faces.component;
import javax.el.ELException;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.context.FacesContext;
import javax.faces.context.ExternalContext;
import javax.faces.el.ValueBinding;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import javax.faces.event.FacesListener;
import javax.faces.render.Renderer;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.Serializable;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* UIComponentBase is a convenience base class that
* implements the default concrete behavior of all methods defined by
* {@link UIComponent}.
*
* By default, this class defines getRendersChildren()
* to find the renderer for this component and call its
* getRendersChildren()
method. The default implementation
* on the Renderer
returns false
. As of
* version 1.2 of the JavaServer Faces Specification, component authors
* are encouraged to return true
from this method and rely
* on the implementation of {@link #encodeChildren} in this class and in
* the Renderer ({@link Renderer#encodeChildren}). Subclasses that wish
* to manage the rendering of their children should override this method
* to return true
instead.
*/
public abstract class UIComponentBase extends UIComponent {
// -------------------------------------------------------------- Attributes
private static Logger log = Logger.getLogger("javax.faces.component",
"javax.faces.LogStrings");
/**
* Each entry is an map of PropertyDescriptor
s describing
* the properties of a concrete {@link UIComponent} implementation, keyed
* by the corresponding java.lang.Class
.
*
* IMPLEMENTATION NOTE - This is implemented as a
* WeakHashMap
so that, even if this class is embedded in a
* container's class loader that is a parent to webapp class loaders,
* references to the classes will eventually expire.
*/
@SuppressWarnings({"CollectionWithoutInitialCapacity"})
private static Map, Map>
descriptors =
new WeakHashMap, Map>();
/**
* Reference to the map of PropertyDescriptor
s for this class
* in the descriptors Map.
*/
private Map pdMap = null;
/**
* An EMPTY_OBJECT_ARRAY argument list to be passed to reflection methods.
*/
private static final Object EMPTY_OBJECT_ARRAY[] = new Object[0];
private static final String IS_SAVE_BINDINGS_STATE_PARAM_NAME = "com.sun.faces.IS_SAVE_BINDINGS_STATE";
public UIComponentBase() {
populateDescriptorsMapIfNecessary();
}
private void populateDescriptorsMapIfNecessary() {
Class> clazz = this.getClass();
pdMap = descriptors.get(clazz);
if (null != pdMap) {
return;
}
// load the property descriptors for this class.
PropertyDescriptor pd[] = getPropertyDescriptors();
if (pd != null) {
pdMap = new HashMap(pd.length, 1.0f);
for (PropertyDescriptor aPd : pd) {
pdMap.put(aPd.getName(), aPd);
}
if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "fine.component.populating_descriptor_map",
new Object[]{clazz,
Thread.currentThread().getName()});
}
// Check again
Map reCheckMap =
descriptors.get(clazz);
if (null != reCheckMap) {
return;
}
descriptors.put(clazz, pdMap);
}
}
/**
* Return an array of PropertyDescriptors
for this
* {@link UIComponent}'s implementation class. If no descriptors
* can be identified, a zero-length array will be returned.
*
* @throws FacesException if an introspection exception occurs
*/
private PropertyDescriptor[] getPropertyDescriptors() {
PropertyDescriptor[] pd;
try {
pd = Introspector.getBeanInfo(this.getClass()).
getPropertyDescriptors();
} catch (IntrospectionException e) {
throw new FacesException(e);
}
return (pd);
}
/**
* The Map
containing our attributes, keyed by
* attribute name.
*/
private AttributesMap attributes = null;
public Map getAttributes() {
if (attributes == null) {
attributes = new AttributesMap(this);
}
return (attributes);
}
// ---------------------------------------------------------------- Bindings
/**
* {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @deprecated This has been replaced by {@link #getValueExpression}.
*/
public ValueBinding getValueBinding(String name) {
if (name == null) {
throw new NullPointerException();
}
ValueBinding result = null;
ValueExpression ve;
if (null != (ve = getValueExpression(name))) {
// if the ValueExpression is an instance of our private
// wrapper class.
if (ve.getClass().equals(ValueExpressionValueBindingAdapter.class)) {
result = ((ValueExpressionValueBindingAdapter)ve).getWrapped();
}
else {
// otherwise, this is a real ValueExpression. Wrap it
// in a ValueBinding.
result = new ValueBindingValueExpressionAdapter(ve);
}
}
return result;
}
/**
* {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @deprecated This has been replaced by {@link #setValueExpression}.
*/
public void setValueBinding(String name, ValueBinding binding) {
if (name == null) {
throw new NullPointerException();
}
if (binding != null) {
ValueExpressionValueBindingAdapter adapter =
new ValueExpressionValueBindingAdapter(binding);
setValueExpression(name, adapter);
} else {
setValueExpression(name, null);
}
}
// -------------------------------------------------------------- Properties
/**
* The assigned client identifier for this component.
*/
private String clientId = null;
/**
* @throws NullPointerException {@inheritDoc}
*/
public String getClientId(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
// if the clientId is not yet set
if (this.clientId == null) {
UIComponent parent = this.getNamingContainer();
String parentId = null;
// give the parent the opportunity to first
// grab a unique clientId
if (parent != null) {
parentId = parent.getContainerClientId(context);
}
// now resolve our own client id
this.clientId = getId();
if (this.clientId == null) {
setId(context.getViewRoot().createUniqueId());
this.clientId = getId();
}
if (parentId != null) {
StringBuilder idBuilder =
new StringBuilder(parentId.length()
+ 1
+ this.clientId.length());
this.clientId = idBuilder.append(parentId)
.append(NamingContainer.SEPARATOR_CHAR)
.append(this.clientId).toString();
}
// allow the renderer to convert the clientId
Renderer renderer = this.getRenderer(context);
if (renderer != null) {
this.clientId = renderer.convertClientId(context, this.clientId);
}
}
return this.clientId;
}
/**
* Private utilitity method for finding this
* UIComponent
's parent NamingContainer
.
* This method may return null
if there is not a
* parent NamingContainer
*
* @return the parent NamingContainer
*/
private UIComponent getNamingContainer() {
UIComponent namingContainer = this.getParent();
while (namingContainer != null) {
if (namingContainer instanceof NamingContainer) {
return namingContainer;
}
namingContainer = namingContainer.getParent();
}
return null;
}
/**
* The component identifier for this component.
*/
private String id = null;
public String getId() {
return (id);
}
/**
* @throws IllegalArgumentException {@inheritDoc}
* @throws IllegalStateException {@inheritDoc}
*/
public void setId(String id) {
// if the current ID is not null, and the passed
// argument is the same, no need to validate it
// as it has already been validated.
if (this.id == null || !(this.id.equals(id))) {
validateId(id);
this.id = id;
}
this.clientId = null; // Erase any cached value
}
/**
* The parent component for this component.
*/
private UIComponent parent = null;
public UIComponent getParent() {
return (this.parent);
}
public void setParent(UIComponent parent) {
this.parent = parent;
}
/**
* The "should this component be rendered" flag.
*/
private boolean rendered = true;
private boolean renderedSet = false;
public boolean isRendered() {
if (renderedSet) {
return (rendered);
}
ValueExpression ve = getValueExpression("rendered");
if (ve != null) {
try {
return (!Boolean.FALSE.equals(ve.getValue(getFacesContext().getELContext())));
}
catch (ELException e) {
throw new FacesException(e);
}
} else {
return (this.rendered);
}
}
public void setRendered(boolean rendered) {
this.rendered = rendered;
this.renderedSet = true;
}
/**
* The renderer type for this component.
*/
private String rendererType = null;
public String getRendererType() {
if (this.rendererType != null) {
return (this.rendererType);
}
ValueExpression ve = getValueExpression("rendererType");
if (ve != null) {
try {
return ((String) ve.getValue(getFacesContext().getELContext()));
}
catch (ELException e) {
throw new FacesException(e);
}
} else {
return (null);
}
}
public void setRendererType(String rendererType) {
this.rendererType = rendererType;
}
public boolean getRendersChildren() {
boolean result = false;
Renderer renderer;
if (getRendererType() != null) {
if (null !=
(renderer = getRenderer(getFacesContext()))) {
result = renderer.getRendersChildren();
}
}
return result;
}
// ------------------------------------------------- Tree Management Methods
/*
* The List
containing our child components.
*/
private List children = null;
public List getChildren() {
if (children == null) {
children = new ChildrenList(this);
}
return (children);
}
// Do not allocate the children List to answer this question
public int getChildCount() {
if (children != null) {
return (children.size());
} else {
return (0);
}
}
/**
* If the specified {@link UIComponent} has a non-null parent,
* remove it as a child or facet (as appropriate) of that parent.
* As a result, the parent
property will always be
* null
when this method returns.
*
* @param component {@link UIComponent} to have any parent erased
*/
private static void eraseParent(UIComponent component) {
UIComponent parent = component.getParent();
if (parent == null) {
return;
}
if (parent.getChildCount() > 0) {
List children = parent.getChildren();
int index = children.indexOf(component);
if (index >= 0) {
children.remove(index);
return;
}
}
if (parent.getFacetCount() > 0) {
Map facets = parent.getFacets();
Iterator entries = facets.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry entry = (Map.Entry) entries.next();
//noinspection ObjectEquality
if (entry.getValue() == component) {
entries.remove();
return;
}
}
}
// Throw an exception for the "cannot happen" case
throw new IllegalStateException("Parent was not null, " +
"but this component not related");
}
/**
* Throw IllegalArgumentException
if the specified
* component identifier is non-null
and not
* syntactically valid.
*
* @param id The component identifier to test
*/
private static void validateId(String id) {
if (id == null) {
return;
}
int n = id.length();
if (n < 1) {
throw new IllegalArgumentException("The id attribute may not be empty");
}
for (int i = 0; i < n; i++) {
char c = id.charAt(i);
if (i == 0) {
if (!Character.isLetter(c) && (c != '_')) {
throw new IllegalArgumentException(id);
}
} else {
if (!Character.isLetter(c) &&
!Character.isDigit(c) &&
(c != '-') && (c != '_')) {
throw new IllegalArgumentException(id);
}
}
}
}
private static final String SEPARATOR_STRING =
String.valueOf(NamingContainer.SEPARATOR_CHAR);
/**
* @throws NullPointerException {@inheritDoc}
*/
public UIComponent findComponent(String expr) {
if (expr == null) {
throw new NullPointerException();
}
if (expr.length() == 0) {
// if an empty value is provided, fail fast.
throw new IllegalArgumentException("\"\"");
}
// Identify the base component from which we will perform our search
UIComponent base = this;
if (expr.charAt(0) == NamingContainer.SEPARATOR_CHAR) {
// Absolute searches start at the root of the tree
while (base.getParent() != null) {
base = base.getParent();
}
// Treat remainder of the expression as relative
expr = expr.substring(1);
} else if (!(base instanceof NamingContainer)) {
// Relative expressions start at the closest NamingContainer or root
while (base.getParent() != null) {
if (base instanceof NamingContainer) {
break;
}
base = base.getParent();
}
}
// Evaluate the search expression (now guaranteed to be relative)
UIComponent result = null;
String[] segments = expr.split(SEPARATOR_STRING);
for (int i = 0, length = (segments.length - 1);
i < segments.length;
i++, length--) {
result = findComponent(base, segments[i], (i == 0));
// the first element of the expression may match base.id
// (vs. a child if of base)
if (i == 0 && result == null &&
segments[i].equals(base.getId())) {
result = base;
}
if (result != null && (!(result instanceof NamingContainer)) && length > 0) {
throw new IllegalArgumentException(segments[i]);
}
if (result == null) {
break;
}
base = result;
}
// Return the final result of our search
return (result);
}
/**
* Return the {@link UIComponent} (if any) with the specified
* id
, searching recursively starting at the specified
* base
, and examining the base component itself, followed
* by examining all the base component's facets and children (unless
* the base component is a {@link NamingContainer}, in which case the
* recursive scan is skipped.
*
* @param base Base {@link UIComponent} from which to search
* @param id Component identifier to be matched
*/
private static UIComponent findComponent(UIComponent base,
String id,
boolean checkId) {
if (checkId && id.equals(base.getId())) {
return base;
}
// Search through our facets and children
UIComponent result = null;
for (Iterator i = base.getFacetsAndChildren(); i.hasNext(); ) {
UIComponent kid = (UIComponent) i.next();
if (!(kid instanceof NamingContainer)) {
if (checkId && id.equals(kid.getId())) {
result = kid;
break;
}
result = findComponent(kid, id, true);
if (result != null) {
break;
}
} else if (id.equals(kid.getId())) {
result = kid;
break;
}
}
return (result);
}
/**
* {@inheritDoc}
* @since 1.2
* @throws NullPointerException {@inheritDoc}
* @throws FacesException {@inheritDoc}
*
*/
public boolean invokeOnComponent(FacesContext context, String clientId,
ContextCallback callback)
throws FacesException {
return super.invokeOnComponent(context, clientId, callback);
}
// ------------------------------------------------ Facet Management Methods
/*
* The Map
containing our related facet components.
*/
private Map facets = null;
public Map getFacets() {
if (facets == null) {
facets = new FacetsMap(this);
}
return (facets);
}
// Do not allocate the children List to answer this question
public int getFacetCount() {
if (facets != null) {
return (facets.size());
} else {
return (0);
}
}
// Do not allocate the facets Map to answer this question
public UIComponent getFacet(String name) {
if (facets != null) {
return (facets.get(name));
} else {
return (null);
}
}
public Iterator getFacetsAndChildren() {
Iterator result;
int childCount = this.getChildCount(),
facetCount = this.getFacetCount();
// If there are neither facets nor children
if (0 == childCount && 0 == facetCount) {
result = EMPTY_ITERATOR;
}
// If there are only facets and no children
else if (0 == childCount) {
Collection unmodifiable =
Collections.unmodifiableCollection(getFacets().values());
result = unmodifiable.iterator();
}
// If there are only children and no facets
else if (0 == facetCount) {
List unmodifiable =
Collections.unmodifiableList(getChildren());
result = unmodifiable.iterator();
}
// If there are both children and facets
else {
result = new FacetsAndChildrenIterator(this);
}
return result;
}
// -------------------------------------------- Lifecycle Processing Methods
/**
* @throws AbortProcessingException {@inheritDoc}
* @throws IllegalStateException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public void broadcast(FacesEvent event)
throws AbortProcessingException {
if (event == null) {
throw new NullPointerException();
}
if (listeners == null) {
return;
}
Iterator iter = listeners.iterator();
while (iter.hasNext()) {
FacesListener listener = iter.next();
if (event.isAppropriateListener(listener)) {
event.processListener(listener);
}
}
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public void decode(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
String rendererType = getRendererType();
if (rendererType != null) {
Renderer renderer = this.getRenderer(context);
if (renderer != null) {
renderer.decode(context, this);
}else {
// TODO: i18n
log.fine("Can't get Renderer for type " + rendererType);
}
}
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public void encodeBegin(FacesContext context) throws IOException {
if (context == null) {
throw new NullPointerException();
}
if (!isRendered()) {
return;
}
String rendererType = getRendererType();
if (rendererType != null) {
Renderer renderer = this.getRenderer(context);
if (renderer != null) {
renderer.encodeBegin(context, this);
} else {
// TODO: i18n
log.fine("Can't get Renderer for type " + rendererType);
}
}
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public void encodeChildren(FacesContext context) throws IOException {
if (context == null) {
throw new NullPointerException();
}
if (!isRendered()) {
return;
}
String rendererType = getRendererType();
if (rendererType != null) {
Renderer renderer = this.getRenderer(context);
if (renderer != null) {
renderer.encodeChildren(context, this);
} else {
// We've already logged for this component
}
}
}
/**
* @throws IOException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public void encodeEnd(FacesContext context) throws IOException {
if (context == null) {
throw new NullPointerException();
}
if (!isRendered()) {
return;
}
String rendererType = getRendererType();
if (rendererType != null) {
Renderer renderer = this.getRenderer(context);
if (renderer != null) {
renderer.encodeEnd(context, this);
} else {
// We've already logged for this component
}
}
}
// -------------------------------------------------- Event Listener Methods
/**
* Our {@link javax.faces.event.FacesListener}s. This data
* structure is lazily instantiated as necessary.
*/
private List listeners;
/**
* Add the specified {@link FacesListener} to the set of listeners
* registered to receive event notifications from this {@link UIComponent}.
* It is expected that {@link UIComponent} classes acting as event sources
* will have corresponding typesafe APIs for registering listeners of the
* required type, and the implementation of those registration methods
* will delegate to this method. For example:
*
* public class FooEvent extends FacesEvent {
* ...
* protected boolean isAppropriateListener(FacesListener listener) {
* return (listener instanceof FooListener);
* }
* protected void processListener(FacesListener listener) {
* ((FooListener) listener).processFoo(this);
* }
* ...
* }
*
* public interface FooListener extends FacesListener {
* public void processFoo(FooEvent event);
* }
*
* public class FooComponent extends UIComponentBase {
* ...
* public void addFooListener(FooListener listener) {
* addFacesListener(listener);
* }
* public void removeFooListener(FooListener listener) {
* removeFacesListener(listener);
* }
* ...
* }
*
*
* @param listener The {@link FacesListener} to be registered
*
* @throws NullPointerException if listener
* is null
*/
protected void addFacesListener(FacesListener listener) {
if (listener == null) {
throw new NullPointerException();
}
if (listeners == null) {
//noinspection CollectionWithoutInitialCapacity
listeners = new ArrayList();
}
listeners.add(listener);
}
/**
* @throws IllegalArgumentException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
protected FacesListener[] getFacesListeners(Class clazz) {
if (clazz == null) {
throw new NullPointerException();
}
if (!FacesListener.class.isAssignableFrom(clazz)) {
throw new IllegalArgumentException();
}
if (listeners == null) {
return ((FacesListener[])
java.lang.reflect.Array.newInstance(clazz, 0));
}
//noinspection CollectionWithoutInitialCapacity
List results = new ArrayList();
Iterator items = listeners.iterator();
while (items.hasNext()) {
FacesListener item = items.next();
if (((Class>)clazz).isAssignableFrom(item.getClass())) {
results.add(item);
}
}
return (results.toArray
((FacesListener []) java.lang.reflect.Array.newInstance(clazz,
results.size())));
}
/**
* Remove the specified {@link FacesListener} from the set of listeners
* registered to receive event notifications from this {@link UIComponent}.
*
* @param listener The {@link FacesListener} to be deregistered
*
* @throws NullPointerException if listener
* is null
*/
protected void removeFacesListener(FacesListener listener) {
if (listener == null) {
throw new NullPointerException();
}
if (listeners == null) {
return;
}
listeners.remove(listener);
}
/**
* @throws IllegalStateException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public void queueEvent(FacesEvent event) {
if (event == null) {
throw new NullPointerException();
}
UIComponent parent = getParent();
if (parent == null) {
throw new IllegalStateException();
} else {
parent.queueEvent(event);
}
}
// ------------------------------------------------ Lifecycle Phase Handlers
/**
* @throws NullPointerException {@inheritDoc}
*/
public void processDecodes(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
// Skip processing if our rendered flag is false
if (!isRendered()) {
return;
}
// Process all facets and children of this component
Iterator kids = getFacetsAndChildren();
while (kids.hasNext()) {
UIComponent kid = (UIComponent) kids.next();
kid.processDecodes(context);
}
// Process this component itself
try {
decode(context);
} catch (RuntimeException e) {
context.renderResponse();
throw e;
}
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public void processValidators(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
// Skip processing if our rendered flag is false
if (!isRendered()) {
return;
}
// Process all the facets and children of this component
Iterator kids = getFacetsAndChildren();
while (kids.hasNext()) {
UIComponent kid = (UIComponent) kids.next();
kid.processValidators(context);
}
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public void processUpdates(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
// Skip processing if our rendered flag is false
if (!isRendered()) {
return;
}
// Process all facets and children of this component
Iterator kids = getFacetsAndChildren();
while (kids.hasNext()) {
UIComponent kid = (UIComponent) kids.next();
kid.processUpdates(context);
}
}
private static final int MY_STATE = 0;
private static final int CHILD_STATE = 1;
/**
* @throws NullPointerException {@inheritDoc}
*/
public Object processSaveState(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
if (this.isTransient()) {
return null;
}
Object [] stateStruct = new Object[2];
Object [] childState = EMPTY_ARRAY;
// Process this component itself
stateStruct[MY_STATE] = saveState(context);
// determine if we have any children to store
int count = this.getChildCount() + this.getFacetCount();
if (count > 0) {
// this arraylist will store state
List