/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2010 Oracle and/or its affiliates. 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_1_1.html
* or packager/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 packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [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.application.Application;
import javax.faces.component.behavior.Behavior;
import javax.faces.component.behavior.ClientBehavior;
import javax.faces.component.behavior.ClientBehaviorHolder;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.event.*;
import javax.faces.render.Renderer;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
import java.util.AbstractCollection;
import java.util.AbstractMap;
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.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* <p><strong class="changed_modified_2_0 changed_modified_2_0_rev_a changed_added_2_1">UIComponentBase</strong> is a
* convenience base class that implements the default concrete behavior
* of all methods defined by {@link UIComponent}.</p>
* <p/>
* <p>By default, this class defines <code>getRendersChildren()</code>
* to find the renderer for this component and call its
* <code>getRendersChildren()</code> method. The default implementation
* on the <code>Renderer</code> returns <code>false</code>. As of
* version 1.2 of the JavaServer Faces Specification, component authors
* are encouraged to return <code>true</code> 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 <code>true</code> instead.</p>
*/
public abstract class UIComponentBase extends UIComponent {
// -------------------------------------------------------------- Attributes
private static Logger LOGGER = Logger.getLogger("javax.faces.component",
"javax.faces.LogStrings");
private static final String ADDED = UIComponentBase.class.getName() + ".ADDED";
/**
* <p>Each entry is an map of <code>PropertyDescriptor</code>s describing
* the properties of a concrete {@link UIComponent} implementation, keyed
* by the corresponding <code>java.lang.Class</code>.</p>
* <p/>
* <p><strong>IMPLEMENTATION NOTE</strong> - This is implemented as a
* <code>WeakHashMap</code> 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.</p>
*/
@SuppressWarnings({"CollectionWithoutInitialCapacity"})
private static Map<Class<?>, Map<String, PropertyDescriptor>>
descriptors =
new WeakHashMap<Class<?>, Map<String, PropertyDescriptor>>();
/**
* Reference to the map of <code>PropertyDescriptor</code>s for this class
* in the <code>descriptors<code> <code>Map<code>.
*/
private Map<String, PropertyDescriptor> pdMap = null;
private Map<Class<? extends SystemEvent>, List<SystemEventListener>> listenersByEventClass;
/**
* <p>An EMPTY_OBJECT_ARRAY argument list to be passed to reflection methods.</p>
*/
private static final Object EMPTY_OBJECT_ARRAY[] = new Object[0];
public UIComponentBase() {
populateDescriptorsMapIfNecessary();
}
private void populateDescriptorsMapIfNecessary() {
Class<?> clazz = this.getClass();
synchronized(descriptors) {
pdMap = descriptors.get(clazz);
}
if (null != pdMap) {
return;
}
// load the property descriptors for this class.
PropertyDescriptor pd[] = getPropertyDescriptors();
if (pd != null) {
pdMap = new HashMap<String, PropertyDescriptor>(pd.length, 1.0f);
for (PropertyDescriptor aPd : pd) {
pdMap.put(aPd.getName(), aPd);
}
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "fine.component.populating_descriptor_map",
new Object[]{clazz,
Thread.currentThread().getName()});
}
// Check again
Map<String, PropertyDescriptor> reCheckMap = null;
synchronized(descriptors) {
reCheckMap = descriptors.get(clazz);
}
if (null != reCheckMap) {
return;
}
synchronized(descriptors) {
descriptors.put(clazz, pdMap);
}
}
}
/**
* <p>Return an array of <code>PropertyDescriptors</code> for this
* {@link UIComponent}'s implementation class. If no descriptors
* can be identified, a zero-length array will be returned.</p>
*
* @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);
}
/**
* <p>The <code>Map</code> containing our attributes, keyed by
* attribute name.</p>
*/
private AttributesMap attributes = null;
public Map<String, Object> getAttributes() {
if (attributes == null) {
attributes = new AttributesMap(this);
}
return (attributes);
}
@Override
public Map<String, Object> getPassThroughAttributes(boolean create) {
Map<String, Object> result = (Map<String, Object>)
this.getStateHelper().get(PropertyKeys.passThroughAttributes);
if (null == result) {
if (create) {
result = new PassThroughAttributesMap<String, Object>();
this.getStateHelper().put(PropertyKeys.passThroughAttributes,
result);
}
}
return result;
}
private static class PassThroughAttributesMap<K, V> extends ConcurrentHashMap<String, Object> implements Serializable {
@Override
public Object put(String key, Object value) {
if (null == key || null == value) {
throw new NullPointerException();
}
validateKey(key);
return super.put(key, value);
}
@Override
public Object putIfAbsent(String key, Object value) {
if (null == key || null == value) {
throw new NullPointerException();
}
validateKey(key);
return super.putIfAbsent(key, value);
}
private void validateKey(Object key) {
if (!(key instanceof String) || (key instanceof ValueExpression) || !(key instanceof Serializable)) {
throw new IllegalArgumentException();
}
}
}
// ---------------------------------------------------------------- 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
/**
* <p>The assigned client identifier for this component.</p>
*/
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 namingContainerAncestor =
this.getNamingContainerAncestor();
UIComponent parent = namingContainerAncestor;
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) {
String generatedId;
if (null != namingContainerAncestor &&
namingContainerAncestor instanceof UniqueIdVendor) {
generatedId = ((UniqueIdVendor)namingContainerAncestor).createUniqueId(context, null);
}
else {
generatedId = context.getViewRoot().createUniqueId();
}
setId(generatedId);
this.clientId = getId();
}
if (parentId != null) {
StringBuilder idBuilder =
new StringBuilder(parentId.length()
+ 1
+ this.clientId.length());
this.clientId = idBuilder.append(parentId)
.append(UINamingContainer.getSeparatorChar(context))
.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;
}
/**
* <p>The component identifier for this component.</p>
*/
private String id = null;
public String getId() {
return (id);
}
private UIComponent getNamingContainerAncestor() {
UIComponent namingContainer = this.getParent();
while (namingContainer != null) {
if (namingContainer instanceof NamingContainer) {
return namingContainer;
}
namingContainer = namingContainer.getParent();
}
return null;
}
/**
* @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
}
/**
* <p>The parent component for this component.</p>
*/
private UIComponent parent = null;
public UIComponent getParent() {
return (this.parent);
}
public void setParent(UIComponent parent) {
if (parent == null) {
if (this.parent != null) {
doPreRemoveProcessing(FacesContext.getCurrentInstance(), this);
this.parent = parent;
}
compositeParent = null;
} else {
this.parent = parent;
if (this.getAttributes().get(ADDED) == null) {
// add an attribute to this component here to indiciate that
// it's being processed. If we don't do this, and the component
// is re-parented, the events could fire again in certain cases
// and cause a stack overflow.
this.getAttributes().put(ADDED, Boolean.TRUE);
doPostAddProcessing(FacesContext.getCurrentInstance(), this);
// remove the attribute once we've returned from the event
// processing.
this.getAttributes().remove(ADDED);
}
}
}
public boolean isRendered() {
return Boolean.valueOf(getStateHelper().eval(PropertyKeys.rendered, Boolean.TRUE).toString());
}
public void setRendered(boolean rendered) {
getStateHelper().put(PropertyKeys.rendered, rendered);
}
public String getRendererType() {
return (String) getStateHelper().eval(PropertyKeys.rendererType);
}
public void setRendererType(String rendererType) {
getStateHelper().put(PropertyKeys.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
/*
* <p>The <code>List</code> containing our child components.</p>
*/
private List<UIComponent> children = null;
public List<UIComponent> 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);
}
}
/**
* <p>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 <code>parent</code> property will always be
* <code>null</code> when this method returns.</p>
*
* @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");
}
/**
* <p>Throw <code>IllegalArgumentException</code> if the specified
* component identifier is non-<code>null</code> and not
* syntactically valid. </p>
*
* @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("Empty id attribute is not allowed");
}
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);
}
}
}
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public UIComponent findComponent(String expr) {
if (expr == null) {
throw new NullPointerException();
}
FacesContext ctx = FacesContext.getCurrentInstance();
final char sepChar = UINamingContainer.getSeparatorChar(ctx);
final String SEPARATOR_STRING = String.valueOf(sepChar);
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) == sepChar) {
// 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);
}
/**
* <p>Return the {@link UIComponent} (if any) with the specified
* <code>id</code>, searching recursively starting at the specified
* <code>base</code>, 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.</p>
*
* @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}
*
* @throws NullPointerException {@inheritDoc}
* @throws FacesException {@inheritDoc}
* @since 1.2
*/
public boolean invokeOnComponent(FacesContext context, String clientId,
ContextCallback callback)
throws FacesException {
return super.invokeOnComponent(context, clientId, callback);
}
// ------------------------------------------------ Facet Management Methods
/*
* <p>The <code>Map</code> containing our related facet components.</p>
*/
private Map<String, UIComponent> facets = null;
public Map<String, UIComponent> 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<UIComponent> getFacetsAndChildren() {
Iterator<UIComponent> 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<UIComponent> unmodifiable =
Collections.unmodifiableCollection(getFacets().values());
result = unmodifiable.iterator();
}
// If there are only children and no facets
else if (0 == facetCount) {
List<UIComponent> 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 (event instanceof BehaviorEvent) {
BehaviorEvent behaviorEvent = (BehaviorEvent) event;
Behavior behavior = behaviorEvent.getBehavior();
behavior.broadcast(behaviorEvent);
}
if (listeners == null) {
return;
}
for (FacesListener listener : listeners.asArray(FacesListener.class)) {
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 {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Can't get Renderer for type " + rendererType);
}
}
}
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public void encodeBegin(FacesContext context) throws IOException {
if (context == null) {
throw new NullPointerException();
}
pushComponentToEL(context, null);
if (!isRendered()) {
return;
}
context.getApplication().publishEvent(context,
PreRenderComponentEvent.class,
this);
String rendererType = getRendererType();
if (rendererType != null) {
Renderer renderer = this.getRenderer(context);
if (renderer != null) {
renderer.encodeBegin(context, this);
} else {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.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);
}
// We've already logged for this component
} else {
if (getChildCount() > 0) {
for (UIComponent child : getChildren()) {
child.encodeAll(context);
}
}
}
}
/**
* @throws IOException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public void encodeEnd(FacesContext context) throws IOException {
if (context == null) {
throw new NullPointerException();
}
if (!isRendered()) {
popComponentFromEL(context);
return;
}
String rendererType = getRendererType();
if (rendererType != null) {
Renderer renderer = this.getRenderer(context);
if (renderer != null) {
renderer.encodeEnd(context, this);
}
}
popComponentFromEL(context);
}
// -------------------------------------------------- Event Listener Methods
private AttachedObjectListHolder<FacesListener> listeners;
/**
* <p>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:</p>
* <pre>
* public class FooEvent extends FacesEvent {
* ...
* protected boolean isAppropriateListener(FacesListener listener) {
* return (listener instanceof FooListener);
* }
* protected void processListener(FacesListener listener) {
* ((FooListener) listener).processFoo(this);
* }
* ...
* }
* <p/>
* public interface FooListener extends FacesListener {
* public void processFoo(FooEvent event);
* }
* <p/>
* public class FooComponent extends UIComponentBase {
* ...
* public void addFooListener(FooListener listener) {
* addFacesListener(listener);
* }
* public void removeFooListener(FooListener listener) {
* removeFacesListener(listener);
* }
* ...
* }
* </pre>
*
* @param listener The {@link FacesListener} to be registered
* @throws NullPointerException if <code>listener</code>
* is <code>null</code>
*/
protected void addFacesListener(FacesListener listener) {
if (listener == null) {
throw new NullPointerException();
}
if (listeners == null) {
listeners = new AttachedObjectListHolder<FacesListener>();
}
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 (this.listeners == null) {
return (FacesListener[]) Array.newInstance(clazz, 0);
}
FacesListener[] listeners = this.listeners.asArray(FacesListener.class);
if (listeners.length == 0) {
return (FacesListener[]) Array.newInstance(clazz, 0);
}
List<FacesListener> results = new ArrayList<FacesListener>(listeners.length);
for (FacesListener listener : listeners) {
if (((Class<?>) clazz).isAssignableFrom(listener.getClass())) {
results.add(listener);
}
}
return (results.toArray
((FacesListener[]) Array.newInstance(clazz,
results.size())));
}
/**
* <p>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 <code>listener</code>
* is <code>null</code>
*/
protected void removeFacesListener(FacesListener listener) {
if (listener == null) {
throw new NullPointerException();
}
if (listeners != null) {
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);
}
}
/**
* <p class="changed_added_2_1">Install the listener instance
* referenced by argument <code>componentListener</code> as a
* listener for events of type <code>eventClass</code> originating
* from this specific instance of <code>UIComponent</code>. The
* default implementation creates an inner {@link
* SystemEventListener} instance that wraps argument
* <code>componentListener</code> as the <code>listener</code>
* argument. This inner class must call through to the argument
* <code>componentListener</code> in its implementation of {@link
* SystemEventListener#processEvent} and its implementation of
* {@link SystemEventListener#isListenerForSource} must return
* true if the instance class of this <code>UIComponent</code> is
* assignable from the argument to
* <code>isListenerForSource</code>.</p>
*
* @param eventClass the <code>Class</code> of event for which
* <code>listener</code> must be fired.
* @param componentListener the implementation of {@link
* javax.faces.event.ComponentSystemEventListener} whose {@link
* javax.faces.event.ComponentSystemEventListener#processEvent} method must be called
* when events of type <code>facesEventClass</code> are fired.
*
* @throws <code>NullPointerException</code> if any of the
* arguments are <code>null</code>.
*
* @since 2.1
*/
public void subscribeToEvent(Class<? extends SystemEvent> eventClass,
ComponentSystemEventListener componentListener) {
if (eventClass == null) {
throw new NullPointerException();
}
if (componentListener == null) {
throw new NullPointerException();
}
if (initialStateMarked()) {
initialState = false;
}
if (null == listenersByEventClass) {
listenersByEventClass = new HashMap<Class<? extends SystemEvent>,
List<SystemEventListener>>(3, 1.0f);
}
SystemEventListener facesLifecycleListener =
new ComponentSystemEventListenerAdapter(componentListener, this);
List<SystemEventListener> listenersForEventClass =
listenersByEventClass.get(eventClass);
if (listenersForEventClass == null) {
listenersForEventClass = new ArrayList<SystemEventListener>(3);
listenersByEventClass.put(eventClass, listenersForEventClass);
}
if (!listenersForEventClass.contains(facesLifecycleListener)) {
listenersForEventClass.add(facesLifecycleListener);
}
}
/**
* <p class="changed_added_2_1">Remove the listener instance
* referenced by argument <code>componentListener</code> as a
* listener for events of type <code>eventClass</code>
* originating from this specific instance of
* <code>UIComponent</code>. When doing the comparison to
* determine if an existing listener is equal to the argument
* <code>componentListener</code> (and thus must be removed),
* the <code>equals()</code> method on the <em>existing
* listener</em> must be invoked, passing the argument
* <code>componentListener</code>, rather than the other way
* around.</p>
*
* @param eventClass the <code>Class</code> of event for which
* <code>listener</code> must be removed.
* @param componentListener the implementation of {@link
* ComponentSystemEventListener} whose {@link
* ComponentSystemEventListener#processEvent} method must no longer be called
* when events of type <code>eventClass</code> are fired.
*
* @throws <code>NullPointerException</code> if any of the
* arguments are <code>null</code>.
*
* @since 2.1
*/
public void unsubscribeFromEvent(Class<? extends SystemEvent> eventClass,
ComponentSystemEventListener componentListener) {
if (eventClass == null) {
throw new NullPointerException();
}
if (componentListener == null) {
throw new NullPointerException();
}
List<SystemEventListener> listeners =
getListenersForEventClass(eventClass);
if (listeners != null && !listeners.isEmpty()) {
for (Iterator<SystemEventListener> i = listeners.iterator(); i.hasNext();) {
SystemEventListener item = i.next();
ComponentSystemEventListenerAdapter csla =
(ComponentSystemEventListenerAdapter) item;
ComponentSystemEventListener l = csla.getWrapped();
if (l.equals(componentListener)) {
i.remove();
break;
}
}
}
}
/**
* <p class="changed_added_2_1">Return the
* <code>SystemEventListener</code> instances registered on this
* <code>UIComponent</code> instance that are interested in events
* of type <code>eventClass</code>.</p>
*
* @param eventClass the <code>Class</code> of event for which the
* listeners must be returned.
* @throws NullPointerException if argument <code>eventClass</code> is <code>null</code>.
*
* @since 2.1
*/
public List<SystemEventListener> getListenersForEventClass(Class<? extends SystemEvent> eventClass) {
if (eventClass == null) {
throw new NullPointerException();
}
List<SystemEventListener> result = null;
if (listenersByEventClass != null) {
result = listenersByEventClass.get(eventClass);
}
return result;
}
// ------------------------------------------------ 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;
}
pushComponentToEL(context, null);
// 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;
} finally {
popComponentFromEL(context);
}
}
/**
* @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;
}
pushComponentToEL(context, null);
Application app = context.getApplication();
app.publishEvent(context, PreValidateEvent.class, this);
// Process all the facets and children of this component
Iterator kids = getFacetsAndChildren();
while (kids.hasNext()) {
UIComponent kid = (UIComponent) kids.next();
kid.processValidators(context);
}
app.publishEvent(context, PostValidateEvent.class, this);
popComponentFromEL(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;
}
pushComponentToEL(context, null);
// Process all facets and children of this component
Iterator kids = getFacetsAndChildren();
while (kids.hasNext()) {
UIComponent kid = (UIComponent) kids.next();
kid.processUpdates(context);
}
popComponentFromEL(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;
pushComponentToEL(context, null);
// 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<Object> stateList = new ArrayList<Object>(count);
// if we have children, add them to the stateList
if (this.getChildCount() > 0) {
Iterator kids = getChildren().iterator();
UIComponent kid;
while (kids.hasNext()) {
kid = (UIComponent) kids.next();
if (!kid.isTransient()) {
stateList.add(kid.processSaveState(context));
popComponentFromEL(context);
}
}
}
pushComponentToEL(context, null);
// if we have facets, add them to the stateList
if (this.getFacetCount() > 0) {
Iterator myFacets = getFacets().entrySet().iterator();
UIComponent facet;
Object facetState;
Object[] facetSaveState;
Map.Entry entry;
while (myFacets.hasNext()) {
entry = (Map.Entry) myFacets.next();
facet = (UIComponent) entry.getValue();
if (!facet.isTransient()) {
facetState = facet.processSaveState(context);
popComponentFromEL(context);
facetSaveState = new Object[2];
facetSaveState[0] = entry.getKey();
facetSaveState[1] = facetState;
stateList.add(facetSaveState);
}
}
}
// finally, capture the stateList and replace the original,
// EMPTY_OBJECT_ARRAY Object array
childState = stateList.toArray();
}
stateStruct[CHILD_STATE] = childState;
return stateStruct;
}
/**
* @throws NullPointerException {@inheritDoc}
*/
public void processRestoreState(FacesContext context,
Object state) {
if (context == null) {
throw new NullPointerException();
}
Object[] stateStruct = (Object[]) state;
Object[] childState = (Object[]) stateStruct[CHILD_STATE];
// Process this component itself
restoreState(context, stateStruct[MY_STATE]);
int i = 0;
// Process all the children of this component
if (this.getChildCount() > 0) {
for (UIComponent kid : getChildren()) {
if (kid.isTransient()) {
continue;
}
Object currentState = childState[i++];
if (currentState == null) {
continue;
}
pushComponentToEL(context, null);
kid.processRestoreState(context, currentState);
popComponentFromEL(context);
}
}
// process all of the facets of this component
if (this.getFacetCount() > 0) {
int facetsSize = getFacets().size();
int j = 0;
Object[] facetSaveState;
String facetName;
UIComponent facet;
Object facetState;
while (j < facetsSize) {
if (null != (facetSaveState = (Object[]) childState[i++])) {
facetName = (String) facetSaveState[0];
facetState = facetSaveState[1];
facet = getFacets().get(facetName);
pushComponentToEL(context, null);
facet.processRestoreState(context, facetState);
popComponentFromEL(context);
}
++j;
}
}
}
// ------------------------------------------------------- Protected Methods
protected FacesContext getFacesContext() {
// PENDING(edburns): we can't use the cache ivar because we
// don't always know when to clear it. For example, in the
// "save state in server" case, the UIComponent instances stick
// around between requests, yielding stale facesContext
// references. If there was some way to clear the facesContext
// cache ivar for each node in the tree *after* the
// render-response phase, then we could keep a cache ivar. As
// it is now, we must always use the Thread Local Storage
// solution.
return FacesContext.getCurrentInstance();
}
protected Renderer getRenderer(FacesContext context) {
String rendererType = getRendererType();
Renderer result = null;
if (rendererType != null) {
result = context.getRenderKit().getRenderer(getFamily(),
rendererType);
if (null == result) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Can't get Renderer for type " + rendererType);
}
}
} else {
if (LOGGER.isLoggable(Level.FINE)) {
String id = this.getId();
id = (null != id) ? id : this.getClass().getName();
LOGGER.fine("No renderer-type for component " + id);
}
}
return result;
}
// ---------------------------------------------- PartialStateHolder Methods
/**
* <p class="changed_added_2_0">For each of the attached objects on
* this instance that implement {@link PartialStateHolder}, call
* {@link PartialStateHolder#markInitialState} on the attached object.</p>
* @since 2.0
*/
@Override
public void markInitialState() {
super.markInitialState();
if (listeners != null) {
listeners.markInitialState();
}
if (listenersByEventClass != null) {
for (List<SystemEventListener> listener : listenersByEventClass.values()) {
if (listener instanceof PartialStateHolder) {
((PartialStateHolder) listener).markInitialState();
}
}
}
if (behaviors != null) {
for (Entry<String, List<ClientBehavior>> entry : behaviors.entrySet()) {
for (ClientBehavior behavior : entry.getValue()) {
if (behavior instanceof PartialStateHolder) {
((PartialStateHolder) behavior).markInitialState();
}
}
}
}
}
/**
* <p class="changed_added_2_0">For each of the attached objects on
* this instance that implement {@link PartialStateHolder}, call
* {@link PartialStateHolder#clearInitialState} on the attached object.</p>
* @since 2.0
*/
@Override
public void clearInitialState() {
super.clearInitialState();
if (listeners != null) {
listeners.clearInitialState();
}
if (listenersByEventClass != null) {
for (List<SystemEventListener> listener : listenersByEventClass.values()) {
if (listener instanceof PartialStateHolder) {
((PartialStateHolder) listener).clearInitialState();
}
}
}
if (behaviors != null) {
for (Entry<String, List<ClientBehavior>> entry : behaviors.entrySet()) {
for (ClientBehavior behavior : entry.getValue()) {
if (behavior instanceof PartialStateHolder) {
((PartialStateHolder) behavior).clearInitialState();
}
}
}
}
}
public Object saveState(FacesContext context) {
Object[] values = null;
if (context == null) {
throw new NullPointerException();
}
assert (!transientFlag);
if (initialStateMarked()) {
Object savedFacesListeners = ((listeners != null) ? listeners.saveState(context) : null);
Object savedSysEventListeners = saveSystemEventListeners(context);
Object savedBehaviors = saveBehaviorsState(context);
Object savedBindings = null;
if (bindings != null) {
savedBindings = saveBindingsState(context);
}
Object savedHelper = null;
if (stateHelper != null) {
savedHelper = stateHelper.saveState(context);
}
if (savedFacesListeners == null
&& savedSysEventListeners == null
&& savedBehaviors == null
&& savedBindings == null
&& savedHelper == null) {
return null;
} else {
if (values == null || values.length != 5) {
values = new Object[5];
}
// since we're saving partial state, skip id and clientId
// as this will be reconstructed from the template execution
// when the view is restored
values[0] = savedFacesListeners;
values[1] = savedSysEventListeners;
values[2] = savedBehaviors;
values[3] = savedBindings;
values[4] = savedHelper;
return values;
}
} else {
if (values == null || values.length != 6) {
values = new Object[6];
}
values[0] = ((listeners != null) ? listeners.saveState(context) : null);
values[1] = saveSystemEventListeners(context);
values[2] = saveBehaviorsState(context);
if (bindings != null) {
values[3] = saveBindingsState(context);
}
if (stateHelper != null) {
values[4] = stateHelper.saveState(context);
}
values[5] = id;
return (values);
}
}
public void restoreState(FacesContext context, Object state) {
if (context == null) {
throw new NullPointerException();
}
if (state == null) {
return;
}
Object[] values = (Object[]) state;
if (values[0] != null) {
if (listeners == null) {
listeners = new AttachedObjectListHolder<FacesListener>();
}
listeners.restoreState(context, values[0]);
}
if (values[1] != null) {
Map m = restoreSystemEventListeners(context, values[1]);
if (listenersByEventClass != null) {
listenersByEventClass.putAll(m);
} else {
listenersByEventClass = m;
}
}
if (values[2] != null) {
behaviors = restoreBehaviorsState(context, values[2]);
}
if (values[3] != null) {
bindings = restoreBindingsState(context, values[3]);
}
if(values[4] != null) {
getStateHelper().restoreState(context, values[4]);
}
if (values.length == 6) {
// this means we've saved full state and need to do a little more
// work to finish the job
if (values[5] != null) {
id = (String) values[5];
}
}
}
/**
* <p>Flag indicating a desire to now participate in state saving.</p>
*/
private boolean transientFlag = false;
public boolean isTransient() {
return (this.transientFlag);
}
public void setTransient(boolean transientFlag) {
this.transientFlag = transientFlag;
}
// -------------------------------------- Helper methods for state saving
// --------- methods used by UIComponents to save their attached Objects.
/**
* <p class="changed_modified_2_0">This method is called by {@link
* UIComponent} subclasses that want to save one or more attached
* objects. It is a convenience method that does the work of saving
* attached objects that may or may not implement the {@link
* StateHolder} interface. Using this method implies the use of
* {@link #restoreAttachedState} to restore the attached
* objects.</p>
* <p/>
* <p>This method supports saving attached objects of the following
* type: <code>Object</code>s, <code>null</code> values, and <code
* class="changed_modified_2_0">Collection</code>s of these objects.
* If any contained objects are not <code
* class="changed_modified_2_0">Collection</code>s and do not
* implement {@link StateHolder}, they must have zero-argument
* public constructors. The exact structure of the returned object
* is undefined and opaque, but will be serializable. </p>
*
* @param context the {@link FacesContext} for this request.
* @param attachedObject the object, which may be a
* <code>List</code> instance, or an Object. The
* <code>attachedObject</code> (or the elements that comprise
* <code>attachedObject</code> may implement {@link StateHolder}.
* @throws NullPointerException if the context argument is null.
*/
public static Object saveAttachedState(FacesContext context,
Object attachedObject) {
if (null == context) {
throw new NullPointerException();
}
if (null == attachedObject) {
return null;
}
Object result;
Class mapOrCollectionClass = attachedObject.getClass();
boolean newWillSucceed = true;
// first, test for newability of the class.
try {
int modifiers = mapOrCollectionClass.getModifiers();
newWillSucceed = Modifier.isPublic(modifiers);
if (newWillSucceed) {
newWillSucceed = null != mapOrCollectionClass.getConstructor();
}
} catch (Exception e) {
newWillSucceed = false;
}
if (newWillSucceed && attachedObject instanceof Collection) {
Collection attachedCollection = (Collection) attachedObject;
List<StateHolderSaver> resultList = null;
for (Object item : attachedCollection) {
if (item != null) {
if (item instanceof StateHolder && ((StateHolder) item).isTransient()) {
continue;
}
if (resultList == null) {
resultList = new ArrayList<StateHolderSaver>(attachedCollection.size() + 1);
resultList.add(new StateHolderSaver(context, mapOrCollectionClass));
}
resultList.add(new StateHolderSaver(context, item));
}
}
result = resultList;
} else if (newWillSucceed && attachedObject instanceof Map) {
Map<Object, Object> attachedMap = (Map<Object, Object>) attachedObject;
List<StateHolderSaver> resultList = null;
Object key, value;
for (Map.Entry<Object, Object> entry : attachedMap.entrySet()) {
key = entry.getKey();
if (key instanceof StateHolder && ((StateHolder)key).isTransient()) {
continue;
}
value = entry.getValue();
if (value instanceof StateHolder && ((StateHolder)value).isTransient()) {
continue;
}
if (resultList == null) {
resultList = new ArrayList<StateHolderSaver>(attachedMap.size()*2 + 1);
resultList.add(new StateHolderSaver(context, mapOrCollectionClass));
}
resultList.add(new StateHolderSaver(context, key));
resultList.add(new StateHolderSaver(context, value));
}
result = resultList;
} else {
result = new StateHolderSaver(context, attachedObject);
}
return result;
}
/**
* <p>This method is called by {@link UIComponent} subclasses that
* need to restore the objects they saved using {@link
* #saveAttachedState}. This method is tightly coupled with {@link
* #saveAttachedState}.</p>
* <p/>
* <p>This method supports restoring all attached objects types
* supported by {@link #saveAttachedState}.</p>
*
* @param context the {@link FacesContext} for this request
* @param stateObj the opaque object returned from {@link
* #saveAttachedState}
* @throws NullPointerException if context is null.
* @throws IllegalStateException if the object is not
* previously returned by {@link #saveAttachedState}.
*/
public static Object restoreAttachedState(FacesContext context,
Object stateObj)
throws IllegalStateException {
if (null == context) {
throw new NullPointerException();
}
if (null == stateObj) {
return null;
}
Object result;
if (stateObj instanceof List) {
List<StateHolderSaver> stateList = (List<StateHolderSaver>) stateObj;
StateHolderSaver collectionSaver = stateList.get(0);
Class mapOrCollection = (Class) collectionSaver.restore(context);
if (Collection.class.isAssignableFrom(mapOrCollection)) {
Collection<Object> retCollection = null;
try {
retCollection = (Collection<Object>) mapOrCollection.newInstance();
} catch (Exception e) {
if (LOGGER.isLoggable(Level.SEVERE)) {
LOGGER.log(Level.SEVERE, e.toString(), e);
}
throw new IllegalStateException("Unknown object type");
}
for (int i = 1, len = stateList.size(); i < len; i++) {
try {
retCollection.add(stateList.get(i).restore(context));
} catch (ClassCastException cce) {
if (LOGGER.isLoggable(Level.SEVERE)) {
LOGGER.log(Level.SEVERE, cce.toString(), cce);
}
throw new IllegalStateException("Unknown object type");
}
}
result = retCollection;
} else {
// If we were doing assertions: assert(mapOrList.isAssignableFrom(Map.class));
Map<Object, Object> retMap = null;
try {
retMap = (Map<Object,Object>) mapOrCollection.newInstance();
} catch (Exception e) {
if (LOGGER.isLoggable(Level.SEVERE)) {
LOGGER.log(Level.SEVERE, e.toString(), e);
}
throw new IllegalStateException("Unknown object type");
}
for (int i = 1, len = stateList.size(); i < len; i+=2) {
try {
retMap.put(stateList.get(i).restore(context),
stateList.get(i+1).restore(context));
} catch (ClassCastException cce) {
if (LOGGER.isLoggable(Level.SEVERE)) {
LOGGER.log(Level.SEVERE, cce.toString(), cce);
}
throw new IllegalStateException("Unknown object type");
}
}
result = retMap;
}
} else if (stateObj instanceof StateHolderSaver) {
StateHolderSaver saver = (StateHolderSaver) stateObj;
result = saver.restore(context);
} else {
throw new IllegalStateException("Unknown object type");
}
return result;
}
private static Map<String, ValueExpression> restoreBindingsState(FacesContext context, Object state) {
if (state == null) {
return (null);
}
Object values[] = (Object[]) state;
String names[] = (String[]) values[0];
Object states[] = (Object[]) values[1];
Map<String, ValueExpression> bindings = new HashMap<String, ValueExpression>(names.length);
for (int i = 0; i < names.length; i++) {
bindings.put(names[i],
(ValueExpression) restoreAttachedState(context, states[i]));
}
return (bindings);
}
private Object saveBindingsState(FacesContext context) {
if (bindings == null) {
return (null);
}
Object values[] = new Object[2];
values[0] = bindings.keySet().toArray(new String[bindings.size()]);
Object[] bindingValues = bindings.values().toArray();
for (int i = 0; i < bindingValues.length; i++) {
bindingValues[i] = saveAttachedState(context, bindingValues[i]);
}
values[1] = bindingValues;
return (values);
}
private Object saveSystemEventListeners(FacesContext ctx) {
if (listenersByEventClass == null) {
return null;
}
int size = listenersByEventClass.size();
Object listeners[][] = new Object[size][2];
int idx = 0;
boolean savedState = false;
for (Entry<Class<? extends SystemEvent>, List<SystemEventListener>> e : listenersByEventClass.entrySet()) {
Object[] target = listeners[idx++];
target[0] = e.getKey();
target[1] = saveAttachedState(ctx, e.getValue());
if (target[1] == null) {
target[0] = null;
} else {
savedState = true;
}
}
return ((savedState) ? listeners : null);
}
private Map<Class<? extends SystemEvent>, List<SystemEventListener>> restoreSystemEventListeners(FacesContext ctx, Object state) {
if (state == null) {
return null;
}
Object[][] listeners = (Object[][]) state;
Map<Class<? extends SystemEvent>, List<SystemEventListener>> m =
new HashMap<Class<? extends SystemEvent>, List<SystemEventListener>>(listeners.length, 1.0f);
for (int i = 0, len = listeners.length; i < len; i++) {
Object[] source = listeners[i];
m.put((Class<? extends SystemEvent>) source[0],
(List<SystemEventListener>) restoreAttachedState(ctx, source[1]));
}
return m;
}
Map<String, PropertyDescriptor> getDescriptorMap() {
return pdMap;
}
private void doPostAddProcessing(FacesContext context, UIComponent added) {
if (parent.isInView()) {
publishAfterViewEvents(context, context.getApplication(), added);
}
}
private void doPreRemoveProcessing(FacesContext context,
UIComponent toRemove) {
if (parent.isInView()) {
disconnectFromView(context, context.getApplication(), toRemove);
}
}
//------------------------------------------------------------- BehaviorHolder stub methods.
/**
* behaviors associated with this component.
*/
private BehaviorsMap behaviors;
/**
* <p class="changed_added_2_0">This is a default implementation of
* {@link javax.faces.component.behavior.ClientBehaviorHolder#addClientBehavior}.
* <code>UIComponent</code> does not implement the
* {@link javax.faces.component.behavior.ClientBehaviorHolder} interface,
* but provides default implementations for the methods defined by
* {@link javax.faces.component.behavior.ClientBehaviorHolder} to simplify
* subclass implementations. Subclasses that wish to support the
* {@link javax.faces.component.behavior.ClientBehaviorHolder} contract must
* declare that the subclass implements
* {@link javax.faces.component.behavior.ClientBehaviorHolder}, and must provide
* an implementation of
* {@link javax.faces.component.behavior.ClientBehaviorHolder#getEventNames}.</p>
*
* @param eventName the logical name of the client-side event to attach
* the behavior to.
* @param behavior the {@link javax.faces.component.behavior.Behavior}
* instance to attach for the specified event name.
*
* @since 2.0
*/
public void addClientBehavior(String eventName, ClientBehavior behavior) {
assertClientBehaviorHolder();
// First, make sure that the event is supported. We don't want
// to bother attaching behaviors for unsupported events.
Collection<String> eventNames = getEventNames();
// getClientEventNames() is spec'ed to require a non-null Set.
// If getClientEventNames() returns null, throw an exception
// to indicate that the API in not being used properly.
if (eventNames == null) {
throw new IllegalStateException(
"Attempting to add a Behavior to a component " +
"that does not support any event types. " +
"getEventTypes() must return a non-null Set.");
}
if (eventNames.contains(eventName)) {
if (initialStateMarked()) {
// a Behavior has been added dynamically. Update existing
// Behaviors, if any, to save their full state.
if (behaviors != null) {
for (Entry<String, List<ClientBehavior>> entry : behaviors
.entrySet()) {
for (ClientBehavior b : entry.getValue()) {
if (b instanceof PartialStateHolder) {
((PartialStateHolder) behavior).clearInitialState();
}
}
}
}
}
// We've got an event that we support, create our Map
// if necessary
if (null == behaviors) {
// Typically we only have a small number of behaviors for
// any component - in most cases only 1. Using a very small
// initial capacity so that we keep the footprint to a minimum.
Map<String, List<ClientBehavior>> modifiableMap =
new HashMap<String, List<ClientBehavior>>(5,1.0f);
behaviors = new BehaviorsMap(modifiableMap);
}
List<ClientBehavior> eventBehaviours = behaviors.get(eventName);
if (null == eventBehaviours) {
// Again using small initial capacity - we typically
// only have 1 Behavior per event type.
eventBehaviours = new ArrayList<ClientBehavior>(3);
behaviors.getModifiableMap().put(eventName, eventBehaviours);
}
eventBehaviours.add(behavior);
}
}
/**
* <p class="changed_added_2_0">This is a default implementation of
* {@link javax.faces.component.behavior.ClientBehaviorHolder#getEventNames}.
* <code>UIComponent</code> does not implement the
* {@link javax.faces.component.behavior.ClientBehaviorHolder} interface,
* but provides default implementations for the methods defined by
* {@link javax.faces.component.behavior.ClientBehaviorHolder} to simplify
* subclass implementations. Subclasses that wish to support the
* {@link javax.faces.component.behavior.ClientBehaviorHolder} contract
* must declare that the subclass implements
* {@link javax.faces.component.behavior.ClientBehaviorHolder}, and must
* override this method to return a non-Empty <code>Collection</code>
* of the client event names that the component supports.</p>
*
* @since 2.0
*/
public Collection<String> getEventNames() {
assertClientBehaviorHolder();
// Note: we intentionally return null here even though this
// is not a valid value. The result is that addClientBehavior()
// will fail with an IllegalStateException if getEventNames()
// is not overridden. This should make it obvious to the
// component author that something is wrong.
return null;
}
/**
* <p class="changed_added_2_0">This is a default implementation of
* {@link javax.faces.component.behavior.ClientBehaviorHolder#getClientBehaviors}.
* <code>UIComponent</code> does not implement the
* {@link javax.faces.component.behavior.ClientBehaviorHolder} interface,
* but provides default implementations for the methods defined by
* {@link javax.faces.component.behavior.ClientBehaviorHolder} to simplify
* subclass implementations. Subclasses that wish to support the
* {@link javax.faces.component.behavior.ClientBehaviorHolder} contract
* must declare that the subclass implements
* {@link javax.faces.component.behavior.ClientBehaviorHolder}, and must add
* an implementation of
* {@link javax.faces.component.behavior.ClientBehaviorHolder#getEventNames}.</p>
*
* @since 2.0
*/
public Map<String, List<ClientBehavior>> getClientBehaviors() {
if (null == behaviors) {
return Collections.emptyMap();
}
return behaviors;
}
/**
* <p class="changed_added_2_0">This is a default implementation of
* {@link javax.faces.component.behavior.ClientBehaviorHolder#getDefaultEventName}.
* <code>UIComponent</code> does not implement the
* {@link javax.faces.component.behavior.ClientBehaviorHolder} interface,
* but provides default implementations for the methods defined by
* {@link javax.faces.component.behavior.ClientBehaviorHolder} to simplify
* subclass implementations. Subclasses that wish to support the
* {@link javax.faces.component.behavior.ClientBehaviorHolder} contract
* must declare that the subclass implements
* {@link javax.faces.component.behavior.ClientBehaviorHolder}, and must
* provide an implementation of
* {@link javax.faces.component.behavior.ClientBehaviorHolder#getEventNames}.</p>
*/
public String getDefaultEventName() {
assertClientBehaviorHolder();
// Our default implementation just returns null - no default
// event name;
return null;
}
/**
* {@link UIComponentBase} has stub methods from the {@link ClientBehaviorHolder} interface,
* but these method should be used only with componets that really implement holder interface.
* For an any other classes this method throws {@link IllegalStateException}
* @throws IllegalStateException
*/
private void assertClientBehaviorHolder() {
if (!isClientBehaviorHolder()) {
throw new IllegalStateException(
"Attempting to use a Behavior feature with a component " +
"that does not support any event types. " +
"Component must implement BehaviourHolder interface.");
}
}
/**
* @return true if component implements {@link ClientBehaviorHolder} interface.
*/
private boolean isClientBehaviorHolder() {
return ClientBehaviorHolder.class.isInstance(this);
}
/**
* Save state of the behaviors map.
* @param context the {@link FacesContext} for this request.
* @return map converted to the array of <code>Object</code> or null if no behaviors have been set.
*/
private Object saveBehaviorsState(FacesContext context){
Object state = null;
if (null != behaviors && behaviors.size() >0){
boolean stateWritten = false;
Object[] attachedBehaviors = new Object[behaviors.size()];
int i = 0;
for (List<ClientBehavior> eventBehaviors : behaviors.values()) {
// we need to take different action depending on whether
// or not markInitialState() was called. If it's not called,
// assume JSF 1.2 style state saving and call through to
// saveAttachedState(), otherwise, call saveState() on the
// behaviors directly.
Object[] attachedEventBehaviors = new Object[eventBehaviors.size()];
for (int j = 0; j < attachedEventBehaviors.length; j++) {
attachedEventBehaviors[j] = ((initialStateMarked())
? saveBehavior(context, eventBehaviors.get(j))
: saveAttachedState(context, eventBehaviors.get(j)));
if (!stateWritten) {
stateWritten = (attachedEventBehaviors[j] != null);
}
}
attachedBehaviors[i++] = attachedEventBehaviors;
}
if (stateWritten) {
state = new Object[]{behaviors.keySet().toArray(new String[behaviors.size()]),attachedBehaviors};
}
}
return state;
}
/**
* @param context the {@link FacesContext} for this request.
* @param state saved state of the {@link Behavior}'s attached to the component.
* @return restored <code>Map</code> of the behaviors.
*/
private BehaviorsMap restoreBehaviorsState(FacesContext context, Object state) {
if (null != state) {
Object[] values = (Object[]) state;
String[] names = (String[])values[0];
Object[] attachedBehaviors = (Object[]) values[1];
// we need to take different action depending on whether
// or not markInitialState() was called. If it's not called,
// assume JSF 1.2 style state saving and call through to
// restoreAttachedState(), otherwise, call restoreState() on the
// behaviors directly.
if (!initialStateMarked()) {
Map<String, List<ClientBehavior>> modifiableMap = new HashMap<String, List<ClientBehavior>>(
names.length,
1.0f);
for (int i = 0; i < attachedBehaviors.length; i++) {
Object[] attachedEventBehaviors = (Object[]) attachedBehaviors[i];
ArrayList<ClientBehavior> eventBehaviors =
new ArrayList<ClientBehavior>(attachedBehaviors.length);
for (int j = 0; j < attachedEventBehaviors.length; j++) {
eventBehaviors.add((ClientBehavior) restoreAttachedState(context,
attachedEventBehaviors[j]));
}
modifiableMap.put(names[i], eventBehaviors);
}
return new BehaviorsMap(modifiableMap);
} else {
for (int i = 0, len = names.length; i < len; i++) {
// assume the behaviors have already been populated by
// execution of the template. Process the state in the
// same order that the names were saved.
List<ClientBehavior> existingBehaviors =
behaviors.get(names[i]);
restoreBehaviors(context, existingBehaviors, (Object[]) attachedBehaviors[i]);
}
return behaviors;
}
}
return null;
}
private Object saveBehavior(FacesContext ctx, ClientBehavior behavior) {
// if the Behavior isn't a StateHolder, do nothing as it will be
// added to the BehaviorMap when the template is re-executed.
return ((behavior instanceof StateHolder)
? ((StateHolder) behavior).saveState(ctx)
: null);
}
private void restoreBehaviors(FacesContext ctx, List<ClientBehavior> existingBehaviors, Object[] state) {
// this method assumes a one to one correspondence in both length and
// order.
for (int i = 0, len = state.length; i < len; i++) {
ClientBehavior behavior = existingBehaviors.get(i);
if (state[i] == null) {
// nothing to do...move on
continue;
}
// if the Behavior is a StateHolder, invoke restoreState
// passing in the current state. If it's not, just ignore
// it and move along.
if (behavior instanceof StateHolder) {
if (state[i] instanceof StateHolderSaver) {
((StateHolderSaver)state[i]).restore(ctx);
} else {
((StateHolder) behavior).restoreState(ctx, state[i]);
}
}
}
}
private static void publishAfterViewEvents(FacesContext context,
Application application,
UIComponent component) {
component.setInView(true);
try {
component.pushComponentToEL(context, component);
application.publishEvent(context, PostAddToViewEvent.class, component);
if (component.getChildCount() > 0) {
Collection<UIComponent> clist =
new ArrayList<UIComponent>(component.getChildren());
for (UIComponent c : clist) {
publishAfterViewEvents(context, application, c);
}
}
if (component.getFacetCount() > 0) {
Collection<UIComponent> clist =
new ArrayList<UIComponent>(component.getFacets().values());
for (UIComponent c : clist) {
publishAfterViewEvents(context, application, c);
}
}
} finally {
component.popComponentFromEL(context);
}
}
private static void disconnectFromView(FacesContext context,
Application application,
UIComponent component) {
application.publishEvent(context,
PreRemoveFromViewEvent.class,
component);
component.setInView(false);
component.compositeParent = null;
if (component.getChildCount() > 0) {
List<UIComponent> children = component.getChildren();
for (UIComponent c : children) {
disconnectFromView(context, application, c);
}
}
if (component.getFacetCount() > 0) {
Map<String, UIComponent> facets = component.getFacets();
for (UIComponent c : facets.values()) {
disconnectFromView(context, application, c);
}
}
}
// --------------------------------------------------------- Private Classes
// For state saving
private final static Object[] EMPTY_ARRAY = new Object[0];
// Empty iterator for short circuiting operations
private final static Iterator<UIComponent> EMPTY_ITERATOR = new Iterator<UIComponent>() {
public void remove() {
throw new UnsupportedOperationException();
}
public UIComponent next() {
throw new NoSuchElementException("Empty Iterator");
}
public boolean hasNext() {
return false;
}
};
// Private implementation of Map that supports the functionality
// required by UIComponent.getFacets()
// HISTORY:
// Versions 1.333 and older used inheritence to provide the
// basic map functionality. This was wasteful since a
// component could be completely configured via ValueExpressions
// or (Bindings) which means an EMPTY_OBJECT_ARRAY Map would always be
// present when it wasn't needed. By using composition,
// we control if and when the Map is instantiated thereby
// reducing uneeded object allocation. This change also
// has a nice side effect in state saving since we no
// longer need to duplicate the map, we just provide the
// private 'attributes' map directly to the state saving process.
private static class AttributesMap implements Map<String, Object>, Serializable {
// this KEY is special to the AttributesMap - this allows the implementation
// to access the the List containing the attributes that have been set
private static final String ATTRIBUTES_THAT_ARE_SET_KEY =
UIComponentBase.class.getName() + ".attributesThatAreSet";
//private Map<String, Object> attributes;
private transient Map<String, PropertyDescriptor> pdMap;
private transient UIComponent component;
private static final long serialVersionUID = -6773035086539772945L;
// -------------------------------------------------------- Constructors
private AttributesMap(UIComponent component) {
this.component = component;
this.pdMap = ((UIComponentBase) component).getDescriptorMap();
}
public boolean containsKey(Object keyObj) {
if (ATTRIBUTES_THAT_ARE_SET_KEY.equals(keyObj)) {
return true;
}
String key = (String) keyObj;
PropertyDescriptor pd =
getPropertyDescriptor(key);
if (pd == null) {
Map<String,Object> attributes = (Map<String,Object>)
component.getStateHelper().get(PropertyKeys.attributes);
if (attributes != null) {
return attributes.containsKey(key);
} else {
return (false);
}
} else {
return (false);
}
}
public Object get(Object keyObj) {
String key = (String) keyObj;
Object result = null;
if (key == null) {
throw new NullPointerException();
}
if (ATTRIBUTES_THAT_ARE_SET_KEY.equals(key)) {
result = component.getStateHelper().get(UIComponent.PropertyKeysPrivate.attributesThatAreSet);
}
Map<String,Object> attributes = (Map<String,Object>)
component.getStateHelper().get(PropertyKeys.attributes);
if (null == result) {
PropertyDescriptor pd =
getPropertyDescriptor(key);
if (pd != null) {
try {
Method readMethod = pd.getReadMethod();
if (readMethod != null) {
result = (readMethod.invoke(component,
EMPTY_OBJECT_ARRAY));
} else {
throw new IllegalArgumentException(key);
}
} catch (IllegalAccessException e) {
throw new FacesException(e);
} catch (InvocationTargetException e) {
throw new FacesException(e.getTargetException());
}
} else if (attributes != null) {
if (attributes.containsKey(key)) {
result = attributes.get(key);
}
}
}
if (null == result) {
ValueExpression ve = component.getValueExpression(key);
if (ve != null) {
try {
result = ve.getValue(component.getFacesContext().getELContext());
} catch (ELException e) {
throw new FacesException(e);
}
}
}
return result;
}
public Object put(String keyValue, Object value) {
if (keyValue == null) {
throw new NullPointerException();
}
if (ATTRIBUTES_THAT_ARE_SET_KEY.equals(keyValue)) {
if (component.attributesThatAreSet == null) {
if (value instanceof List) {
component.getStateHelper().put(UIComponent.PropertyKeysPrivate.attributesThatAreSet,
value);
}
}
return null;
}
PropertyDescriptor pd =
getPropertyDescriptor(keyValue);
if (pd != null) {
try {
Object result = null;
Method readMethod = pd.getReadMethod();
if (readMethod != null) {
result = readMethod.invoke
(component, EMPTY_OBJECT_ARRAY);
}
Method writeMethod = pd.getWriteMethod();
if (writeMethod != null) {
writeMethod.invoke
(component, value);
} else {
// TODO: i18n
throw new IllegalArgumentException("Setter not found for property " + keyValue);
}
return (result);
} catch (IllegalAccessException e) {
throw new FacesException(e);
} catch (InvocationTargetException e) {
throw new FacesException
(e.getTargetException());
}
} else {
if (value == null) {
throw new NullPointerException();
}
List<String> sProperties =
(List<String>) component.getStateHelper().get(PropertyKeysPrivate.attributesThatAreSet);
if (sProperties == null) {
component.getStateHelper().add(PropertyKeysPrivate.attributesThatAreSet, keyValue);
} else if (!sProperties.contains(keyValue)) {
component.getStateHelper().add(PropertyKeysPrivate.attributesThatAreSet, keyValue);
}
return putAttribute(keyValue, value);
}
}
public void putAll(Map<? extends String, ?> map) {
if (map == null) {
throw new NullPointerException();
}
for (Map.Entry<? extends String, ?> entry : map.entrySet()) {
this.put(entry.getKey(), entry.getValue());
}
}
public Object remove(Object keyObj) {
String key = (String) keyObj;
if (key == null) {
throw new NullPointerException();
}
if (ATTRIBUTES_THAT_ARE_SET_KEY.equals(key)) {
return null;
}
PropertyDescriptor pd =
getPropertyDescriptor(key);
if (pd != null) {
throw new IllegalArgumentException(key);
} else {
Map<String,Object> attributes = getAttributes();
if (attributes != null) {
component.getStateHelper().remove(UIComponent.PropertyKeysPrivate.attributesThatAreSet,
key);
return (component.getStateHelper().remove(PropertyKeys.attributes,
key));
} else {
return null;
}
}
}
public int size() {
Map attributes = getAttributes();
return (attributes != null ? attributes.size() : 0);
}
public boolean isEmpty() {
Map attributes = getAttributes();
return (attributes == null || attributes.isEmpty());
}
public boolean containsValue(java.lang.Object value) {
Map attributes= getAttributes();
return (attributes != null && attributes.containsValue(value));
}
public void clear() {
component.getStateHelper().remove(PropertyKeys.attributes);
component.getStateHelper().remove(PropertyKeysPrivate.attributesThatAreSet);
}
public Set<String> keySet() {
Map<String,Object> attributes = getAttributes();
if (attributes != null)
return Collections.unmodifiableSet(attributes.keySet());
return Collections.emptySet();
}
public Collection<Object> values() {
Map<String,Object> attributes = getAttributes();
if (attributes != null)
return Collections.unmodifiableCollection(attributes.values());
return Collections.emptyList();
}
public Set<Entry<String, Object>> entrySet() {
Map<String,Object> attributes = getAttributes();
if (attributes != null)
return Collections.unmodifiableSet(attributes.entrySet());
return Collections.emptySet();
}
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Map)) {
return false;
}
Map t = (Map) o;
if (t.size() != size()) {
return false;
}
try {
for (Object e : entrySet()) {
Entry entry = (Entry) e;
Object key = entry.getKey();
Object value = entry.getValue();
if (value == null) {
if (!(t.get(key) == null && t.containsKey(key))) {
return false;
}
} else {
if (!value.equals(t.get(key))) {
return false;
}
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
public int hashCode() {
int h = 0;
for (Object o : entrySet()) {
h += o.hashCode();
}
return h;
}
private Map<String,Object> getAttributes() {
return (Map<String,Object>) component.getStateHelper().get(
PropertyKeys.attributes);
}
private Object putAttribute(String key, Object value) {
return component.getStateHelper().put(PropertyKeys.attributes,
key,
value);
}
/**
* <p>Return the <code>PropertyDescriptor</code> for the specified
* property name for this {@link UIComponent}'s implementation class,
* if any; otherwise, return <code>null</code>.</p>
*
* @param name Name of the property to return a descriptor for
* @throws FacesException if an introspection exception occurs
*/
PropertyDescriptor getPropertyDescriptor(String name) {
if (pdMap != null) {
return (pdMap.get(name));
}
return (null);
}
// ----------------------------------------------- Serialization Methods
// This is dependent on serialization occuring with in a
// a Faces request, however, since UIComponentBase.{save,restore}State()
// doesn't actually serialize the AttributesMap, these methods are here
// purely to be good citizens.
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeObject(component.getClass());
//noinspection NonSerializableObjectPassedToObjectStream
out.writeObject(component.saveState(FacesContext.getCurrentInstance()));
}
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
//noinspection unchecked
Class clazz = (Class) in.readObject();
try {
component = (UIComponent) clazz.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
component.restoreState(FacesContext.getCurrentInstance(), in.readObject());
}
}
// Private implementation of List that supports the functionality
// required by UIComponent.getChildren()
private static class ChildrenList extends ArrayList<UIComponent> {
private UIComponent component;
public ChildrenList(UIComponent component) {
super(6);
this.component = component;
}
public void add(int index, UIComponent element) {
if (element == null) {
throw new NullPointerException();
} else if ((index < 0) || (index > size())) {
throw new IndexOutOfBoundsException();
} else {
eraseParent(element);
super.add(index, element);
element.setParent(component);
}
}
public boolean add(UIComponent element) {
if (element == null) {
throw new NullPointerException();
} else {
eraseParent(element);
boolean result = super.add(element);
element.setParent(component);
return result;
}
}
public boolean addAll(Collection<? extends UIComponent> collection) {
Iterator<UIComponent> elements =
(new ArrayList<UIComponent>(collection)).iterator();
boolean changed = false;
while (elements.hasNext()) {
UIComponent element = elements.next();
if (element == null) {
throw new NullPointerException();
} else {
add(element);
changed = true;
}
}
return (changed);
}
public boolean addAll(int index, Collection<? extends UIComponent> collection) {
Iterator<UIComponent> elements =
(new ArrayList<UIComponent>(collection)).iterator();
boolean changed = false;
while (elements.hasNext()) {
UIComponent element = elements.next();
if (element == null) {
throw new NullPointerException();
} else {
add(index++, element);
changed = true;
}
}
return (changed);
}
public void clear() {
int n = size();
if (n < 1) {
return;
}
for (int i = 0; i < n; i++) {
UIComponent child = get(i);
child.setParent(null);
}
super.clear();
}
public Iterator<UIComponent> iterator() {
return (new ChildrenListIterator(this));
}
public ListIterator<UIComponent> listIterator() {
return (new ChildrenListIterator(this));
}
public ListIterator<UIComponent> listIterator(int index) {
return (new ChildrenListIterator(this, index));
}
public UIComponent remove(int index) {
UIComponent child = get(index);
super.remove(index);
child.setParent(null);
return (child);
}
public boolean remove(Object elementObj) {
UIComponent element = (UIComponent) elementObj;
if (element == null) {
throw new NullPointerException();
}
if (super.remove(element)) {
element.setParent(null);
return (true);
} else {
return (false);
}
}
public boolean removeAll(Collection<?> collection) {
boolean result = false;
for (Object elements : collection) {
if (remove(elements)) {
result = true;
}
}
return (result);
}
public boolean retainAll(Collection<?> collection) {
boolean modified = false;
Iterator<?> items = iterator();
while (items.hasNext()) {
if (!collection.contains(items.next())) {
items.remove();
modified = true;
}
}
return (modified);
}
public UIComponent set(int index, UIComponent element) {
if (element == null) {
throw new NullPointerException();
} else if ((index < 0) || (index >= size())) {
throw new IndexOutOfBoundsException();
} else {
eraseParent(element);
UIComponent previous = get(index);
super.set(index, element);
previous.setParent(null);
element.setParent(component);
return (previous);
}
}
}
// Private implementation of ListIterator for ChildrenList
private static class ChildrenListIterator implements ListIterator<UIComponent> {
public ChildrenListIterator(ChildrenList list) {
this.list = list;
this.index = 0;
}
public ChildrenListIterator(ChildrenList list, int index) {
this.list = list;
if ((index < 0) || (index > list.size())) {
throw new IndexOutOfBoundsException(String.valueOf(index));
} else {
this.index = index;
}
}
private ChildrenList list;
private int index;
private int last = -1; // Index last returned by next() or previous()
// Iterator methods
public boolean hasNext() {
return (index < list.size());
}
public UIComponent next() {
try {
UIComponent o = list.get(index);
last = index++;
return (o);
} catch (IndexOutOfBoundsException e) {
throw new NoSuchElementException(String.valueOf(index));
}
}
public void remove() {
if (last == -1) {
throw new IllegalStateException();
}
list.remove(last);
if (last < index) {
index--;
}
last = -1;
}
// ListIterator methods
public void add(UIComponent o) {
last = -1;
list.add(index++, o);
}
public boolean hasPrevious() {
return (index > 1);
}
public int nextIndex() {
return (index);
}
public UIComponent previous() {
try {
int current = index - 1;
UIComponent o = list.get(current);
last = current;
index = current;
return (o);
} catch (IndexOutOfBoundsException e) {
throw new NoSuchElementException();
}
}
public int previousIndex() {
return (index - 1);
}
public void set(UIComponent o) {
if (last == -1) {
throw new IllegalStateException();
}
list.set(last, o);
}
}
// Private implementation of Iterator for getFacetsAndChildren()
private final static class FacetsAndChildrenIterator implements Iterator<UIComponent> {
private Iterator<UIComponent> iterator;
private boolean childMode;
private UIComponent c;
public FacetsAndChildrenIterator(UIComponent c) {
this.c = c;
this.childMode = false;
}
private void update() {
if (this.iterator == null) {
// we must guarantee that 'iterator' is never null
if (this.c.getFacetCount() != 0) {
this.iterator = this.c.getFacets().values().iterator();
this.childMode = false;
} else if (this.c.getChildCount() != 0) {
this.iterator = this.c.getChildren().iterator();
this.childMode = true;
} else {
this.iterator = EMPTY_ITERATOR;
this.childMode = true;
}
} else if (!this.childMode
&& !this.iterator.hasNext()
&& this.c.getChildCount() != 0) {
this.iterator = this.c.getChildren().iterator();
this.childMode = true;
}
}
public boolean hasNext() {
this.update();
return this.iterator.hasNext();
}
public UIComponent next() {
this.update();
return this.iterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
}
// Private implementation of Map that supports the functionality
// required by UIComponent.getFacets()
private static class FacetsMap extends HashMap<String, UIComponent> {
private UIComponent component;
public FacetsMap(UIComponent component) {
super(3, 1.0f);
this.component = component;
}
public void clear() {
Iterator<String> keys = keySet().iterator();
while (keys.hasNext()) {
keys.next();
keys.remove();
}
super.clear();
}
public Set<Map.Entry<String, UIComponent>> entrySet() {
return (new FacetsMapEntrySet(this));
}
public Set<String> keySet() {
return (new FacetsMapKeySet(this));
}
public UIComponent put(String key, UIComponent value) {
if ((key == null) || (value == null)) {
throw new NullPointerException();
} else //noinspection ConstantConditions
if (!(key instanceof String) ||
!(value instanceof UIComponent)) {
throw new ClassCastException();
}
UIComponent previous = super.get(key);
if (previous != null) {
previous.setParent(null);
}
eraseParent(value);
UIComponent result = super.put(key, value);
value.setParent(component);
return (result);
}
public void putAll(Map<? extends String, ? extends UIComponent> map) {
if (map == null) {
throw new NullPointerException();
}
for (Map.Entry<? extends String, ? extends UIComponent> entry : map.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
public UIComponent remove(Object key) {
UIComponent previous = get(key);
if (previous != null) {
previous.setParent(null);
}
super.remove(key);
return (previous);
}
public Collection<UIComponent> values() {
return (new FacetsMapValues(this));
}
Iterator<String> keySetIterator() {
return ((new ArrayList<String>(super.keySet())).iterator());
}
}
// Private implementation of Set for FacetsMap.getEntrySet()
private static class FacetsMapEntrySet extends AbstractSet<Map.Entry<String, UIComponent>> {
public FacetsMapEntrySet(FacetsMap map) {
this.map = map;
}
private FacetsMap map = null;
public boolean add(Map.Entry<String, UIComponent> o) {
throw new UnsupportedOperationException();
}
public boolean addAll(Collection<? extends Map.Entry<String, UIComponent>> c) {
throw new UnsupportedOperationException();
}
public void clear() {
map.clear();
}
public boolean contains(Object o) {
if (o == null) {
throw new NullPointerException();
}
if (!(o instanceof Map.Entry)) {
return (false);
}
Map.Entry e = (Map.Entry) o;
Object k = e.getKey();
Object v = e.getValue();
if (!map.containsKey(k)) {
return (false);
}
if (v == null) {
return (map.get(k) == null);
} else {
return (v.equals(map.get(k)));
}
}
public boolean isEmpty() {
return (map.isEmpty());
}
public Iterator<Map.Entry<String, UIComponent>> iterator() {
return (new FacetsMapEntrySetIterator(map));
}
public boolean remove(Object o) {
if (o == null) {
throw new NullPointerException();
}
if (!(o instanceof Map.Entry)) {
return (false);
}
Object k = ((Map.Entry) o).getKey();
if (map.containsKey(k)) {
map.remove(k);
return (true);
} else {
return (false);
}
}
public boolean removeAll(Collection c) {
boolean result = false;
for (Object element : c) {
if (remove(element)) {
result = true;
}
}
return (result);
}
public boolean retainAll(Collection c) {
boolean result = false;
Iterator v = iterator();
while (v.hasNext()) {
if (!c.contains(v.next())) {
v.remove();
result = true;
}
}
return (result);
}
public int size() {
return (map.size());
}
}
// Private implementation of Map.Entry for FacetsMapEntrySet
private static class FacetsMapEntrySetEntry implements Map.Entry<String, UIComponent> {
public FacetsMapEntrySetEntry(FacetsMap map, String key) {
this.map = map;
this.key = key;
}
private FacetsMap map;
private String key;
public boolean equals(Object o) {
if (o == null) {
return (false);
}
if (!(o instanceof Map.Entry)) {
return (false);
}
Map.Entry e = (Map.Entry) o;
if (key == null) {
if (e.getKey() != null) {
return (false);
}
} else {
if (!key.equals(e.getKey())) {
return (false);
}
}
UIComponent v = map.get(key);
if (v == null) {
if (e.getValue() != null) {
return (false);
}
} else {
if (!v.equals(e.getValue())) {
return (false);
}
}
return (true);
}
public String getKey() {
return (key);
}
public UIComponent getValue() {
return (map.get(key));
}
public int hashCode() {
Object value = map.get(key);
return (((key == null) ? 0 : key.hashCode()) ^
((value == null) ? 0 : value.hashCode()));
}
public UIComponent setValue(UIComponent value) {
UIComponent previous = map.get(key);
map.put(key, value);
return (previous);
}
}
// Private implementation of Set for FacetsMap.getEntrySet().iterator()
private static class FacetsMapEntrySetIterator implements Iterator<Map.Entry<String, UIComponent>> {
public FacetsMapEntrySetIterator(FacetsMap map) {
this.map = map;
this.iterator = map.keySetIterator();
}
private FacetsMap map = null;
private Iterator<String> iterator = null;
private Map.Entry<String, UIComponent> last = null;
public boolean hasNext() {
return (iterator.hasNext());
}
public Map.Entry<String, UIComponent> next() {
last = new FacetsMapEntrySetEntry(map, iterator.next());
return (last);
}
public void remove() {
if (last == null) {
throw new IllegalStateException();
}
map.remove(((Map.Entry) last).getKey());
last = null;
}
}
// Private implementation of Set for FacetsMap.getKeySet()
private static class FacetsMapKeySet extends AbstractSet<String> {
public FacetsMapKeySet(FacetsMap map) {
this.map = map;
}
private FacetsMap map = null;
public boolean add(String o) {
throw new UnsupportedOperationException();
}
public boolean addAll(Collection<? extends String> c) {
throw new UnsupportedOperationException();
}
public void clear() {
map.clear();
}
public boolean contains(Object o) {
return (map.containsKey(o));
}
public boolean containsAll(Collection c) {
for (Object item : c) {
if (!map.containsKey(item)) {
return (false);
}
}
return (true);
}
public boolean isEmpty() {
return (map.isEmpty());
}
public Iterator<String> iterator() {
return (new FacetsMapKeySetIterator(map));
}
public boolean remove(Object o) {
if (map.containsKey(o)) {
map.remove(o);
return (true);
} else {
return (false);
}
}
public boolean removeAll(Collection c) {
boolean result = false;
for (Object item : c) {
if (map.containsKey(item)) {
map.remove(item);
result = true;
}
}
return (result);
}
public boolean retainAll(Collection c) {
boolean result = false;
Iterator v = iterator();
while (v.hasNext()) {
if (!c.contains(v.next())) {
v.remove();
result = true;
}
}
return (result);
}
public int size() {
return (map.size());
}
}
// Private implementation of Set for FacetsMap.getKeySet().iterator()
private static class FacetsMapKeySetIterator implements Iterator<String> {
public FacetsMapKeySetIterator(FacetsMap map) {
this.map = map;
this.iterator = map.keySetIterator();
}
private FacetsMap map = null;
private Iterator<String> iterator = null;
private String last = null;
public boolean hasNext() {
return (iterator.hasNext());
}
public String next() {
last = iterator.next();
return (last);
}
public void remove() {
if (last == null) {
throw new IllegalStateException();
}
map.remove(last);
last = null;
}
}
// Private implementation of Collection for FacetsMap.values()
private static class FacetsMapValues extends AbstractCollection<UIComponent> {
public FacetsMapValues(FacetsMap map) {
this.map = map;
}
private FacetsMap map;
public boolean add(UIComponent o) {
throw new UnsupportedOperationException();
}
public boolean addAll(Collection c) {
throw new UnsupportedOperationException();
}
public void clear() {
map.clear();
}
public boolean isEmpty() {
return (map.isEmpty());
}
public Iterator<UIComponent> iterator() {
return (new FacetsMapValuesIterator(map));
}
public int size() {
return (map.size());
}
}
// Private implementation of Iterator for FacetsMap.values().iterator()
private static class FacetsMapValuesIterator implements Iterator<UIComponent> {
public FacetsMapValuesIterator(FacetsMap map) {
this.map = map;
this.iterator = map.keySetIterator();
}
private FacetsMap map = null;
private Iterator<String> iterator = null;
private Object last = null;
public boolean hasNext() {
return (iterator.hasNext());
}
public UIComponent next() {
last = iterator.next();
return (map.get(last));
}
public void remove() {
if (last == null) {
throw new IllegalStateException();
}
map.remove(last);
last = null;
}
}
// Private static member class that provide access to Behaviors.
// Note that this Map must be unmodifiable to the external world,
// but UIComponentBase itself needs to be able to write to the Map.
// We solve these requirements wrapping the underlying modifiable
// Map inside of a unmodifiable map and providing private access to
// the underlying (modifable) Map
private static class BehaviorsMap extends AbstractMap<String, List<ClientBehavior>>{
private Map<String, List<ClientBehavior>> unmodifiableMap;
private Map<String, List<ClientBehavior>> modifiableMap;
private BehaviorsMap(Map<String, List<ClientBehavior>> modifiableMap) {
this.modifiableMap = modifiableMap;
this.unmodifiableMap = Collections.unmodifiableMap(modifiableMap);
}
public Set<Entry<String, List<ClientBehavior>>> entrySet() {
return unmodifiableMap.entrySet();
}
private Map<String, List<ClientBehavior>> getModifiableMap() {
return modifiableMap;
}
}
}