All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javax.faces.component.UIComponentBase Maven / Gradle / Ivy

Go to download

This is the master POM file for Sun's Implementation of the JSF 2.1 Specification.

There is a newer version: 2.2.20
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2013 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.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
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 LOGGER = Logger.getLogger("javax.faces.component", "javax.faces.LogStrings"); private static final String ADDED = UIComponentBase.class.getName() + ".ADDED"; /** *

Each entry is an map of PropertyDescriptors describing * the properties of a concrete {@link UIComponent} implementation, keyed * by the corresponding java.lang.Class.

*

*/ private Map, Map> descriptors; /** * Reference to the map of PropertyDescriptors for this class * in the descriptors Map. */ private Map pdMap = null; private Map, List> listenersByEventClass; /** *

An EMPTY_OBJECT_ARRAY argument list to be passed to reflection methods.

*/ private static final Object EMPTY_OBJECT_ARRAY[] = new Object[0]; public UIComponentBase() { populateDescriptorsMapIfNecessary(); } private void populateDescriptorsMapIfNecessary() { FacesContext facesContext = FacesContext.getCurrentInstance(); Class clazz = getClass(); /* * If we can find a valid FacesContext we are going to use it to get * access to the property descriptor map. */ if (facesContext != null && facesContext.getExternalContext() != null && facesContext.getExternalContext().getApplicationMap() != null) { Map applicationMap = facesContext.getExternalContext().getApplicationMap(); if (!applicationMap.containsKey("com.sun.faces.compnent.COMPONENT_DESCRIPTORS_MAP")) { applicationMap.put("com.sun.faces.compnent.COMPONENT_DESCRIPTORS_MAP", new ConcurrentHashMap, Map>()); } descriptors = (Map, Map>) applicationMap.get("com.sun.faces.compnent.COMPONENT_DESCRIPTORS_MAP"); pdMap = descriptors.get(clazz); } if (pdMap == null) { /* * We did not find the property descriptor map so we are now * going to load it. */ PropertyDescriptor pd[] = getPropertyDescriptors(); if (pd != null) { pdMap = new HashMap(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()}); } if (descriptors != null && !descriptors.containsKey(clazz)) { 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); } @Override public Map getPassThroughAttributes(boolean create) { Map result = (Map) this.getStateHelper().get(PropertyKeys.passThroughAttributes); if (null == result) { if (create) { result = new PassThroughAttributesMap(); this.getStateHelper().put(PropertyKeys.passThroughAttributes, result); } } return result; } private static class PassThroughAttributesMap extends ConcurrentHashMap 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 /** *

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 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; } /** *

The component identifier for this component.

*/ 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 } /** *

The parent component for this component.

*/ 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 /* *

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("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); } /** *

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} * * @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 /* *

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 (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); } else { // We've already logged for this component } } popComponentFromEL(context); } // -------------------------------------------------- Event Listener Methods private AttachedObjectListHolder 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) { listeners = new AttachedObjectListHolder(); } 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 results = new ArrayList(listeners.length); for (FacesListener listener : listeners) { if (((Class) clazz).isAssignableFrom(listener.getClass())) { results.add(listener); } } return (results.toArray ((FacesListener[]) 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) { 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); } } /** *

Install the listener instance * referenced by argument componentListener as a * listener for events of type eventClass originating * from this specific instance of UIComponent. The * default implementation creates an inner {@link * SystemEventListener} instance that wraps argument * componentListener as the listener * argument. This inner class must call through to the argument * componentListener in its implementation of {@link * SystemEventListener#processEvent} and its implementation of * {@link SystemEventListener#isListenerForSource} must return * true if the instance class of this UIComponent is * assignable from the argument to * isListenerForSource.

* * @param eventClass the Class of event for which * listener 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 facesEventClass are fired. * * @throws NullPointerException if any of the * arguments are null. * * @since 2.1 */ public void subscribeToEvent(Class 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, List>(3, 1.0f); } SystemEventListener facesLifecycleListener = new ComponentSystemEventListenerAdapter(componentListener, this); List listenersForEventClass = listenersByEventClass.get(eventClass); if (listenersForEventClass == null) { listenersForEventClass = new ArrayList(3); listenersByEventClass.put(eventClass, listenersForEventClass); } if (!listenersForEventClass.contains(facesLifecycleListener)) { listenersForEventClass.add(facesLifecycleListener); } } /** *

Remove the listener instance * referenced by argument componentListener as a * listener for events of type eventClass * originating from this specific instance of * UIComponent. When doing the comparison to * determine if an existing listener is equal to the argument * componentListener (and thus must be removed), * the equals() method on the existing * listener must be invoked, passing the argument * componentListener, rather than the other way * around.

* * @param eventClass the Class of event for which * listener must be removed. * @param componentListener the implementation of {@link * ComponentSystemEventListener} whose {@link * ComponentSystemEventListener#processEvent} method must no longer be called * when events of type eventClass are fired. * * @throws NullPointerException if any of the * arguments are null. * * @since 2.1 */ public void unsubscribeFromEvent(Class eventClass, ComponentSystemEventListener componentListener) { if (eventClass == null) { throw new NullPointerException(); } if (componentListener == null) { throw new NullPointerException(); } List listeners = getListenersForEventClass(eventClass); if (listeners != null && !listeners.isEmpty()) { for (Iterator i = listeners.iterator(); i.hasNext();) { SystemEventListener item = i.next(); ComponentSystemEventListenerAdapter csla = (ComponentSystemEventListenerAdapter) item; ComponentSystemEventListener l = csla.getWrapped(); if (l.equals(componentListener)) { i.remove(); break; } } } } /** *

Return the * SystemEventListener instances registered on this * UIComponent instance that are interested in events * of type eventClass.

* * @param eventClass the Class of event for which the * listeners must be returned. * @throws NullPointerException if argument eventClass is null. * * @since 2.1 */ public List getListenersForEventClass(Class eventClass) { if (eventClass == null) { throw new NullPointerException(); } List 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); try { // 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); try { 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); } finally { 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); try { // Process all facets and children of this component Iterator kids = getFacetsAndChildren(); while (kids.hasNext()) { UIComponent kid = (UIComponent) kids.next(); kid.processUpdates(context); } } finally { 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); try { // 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 stateList = new ArrayList(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)); } } } // 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); 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(); } } finally { popComponentFromEL(context); } stateStruct[CHILD_STATE] = childState; return stateStruct; } /** * @throws NullPointerException {@inheritDoc} */ public void processRestoreState(FacesContext context, Object state) { if (context == null) { throw new NullPointerException(); } pushComponentToEL(context, null); try { 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; } kid.processRestoreState(context, currentState); } } // 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); facet.processRestoreState(context, facetState); } ++j; } } } finally { popComponentFromEL(context); } } // ------------------------------------------------------- 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 /** *

For each of the attached objects on * this instance that implement {@link PartialStateHolder}, call * {@link PartialStateHolder#markInitialState} on the attached object.

* @since 2.0 */ @Override public void markInitialState() { super.markInitialState(); if (listeners != null) { listeners.markInitialState(); } if (listenersByEventClass != null) { for (List listener : listenersByEventClass.values()) { if (listener instanceof PartialStateHolder) { ((PartialStateHolder) listener).markInitialState(); } } } if (behaviors != null) { for (Entry> entry : behaviors.entrySet()) { for (ClientBehavior behavior : entry.getValue()) { if (behavior instanceof PartialStateHolder) { ((PartialStateHolder) behavior).markInitialState(); } } } } } /** *

For each of the attached objects on * this instance that implement {@link PartialStateHolder}, call * {@link PartialStateHolder#clearInitialState} on the attached object.

* @since 2.0 */ @Override public void clearInitialState() { super.clearInitialState(); if (listeners != null) { listeners.clearInitialState(); } if (listenersByEventClass != null) { for (List listener : listenersByEventClass.values()) { if (listener instanceof PartialStateHolder) { ((PartialStateHolder) listener).clearInitialState(); } } } if (behaviors != null) { for (Entry> 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(); } 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]; } } } /** *

Flag indicating a desire to now participate in state saving.

*/ 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. /** *

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.

*

*

This method supports saving attached objects of the following * type: Objects, null values, and Collections of these objects. * If any contained objects are not Collections 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.

* * @param context the {@link FacesContext} for this request. * @param attachedObject the object, which may be a * List instance, or an Object. The * attachedObject (or the elements that comprise * attachedObject 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 resultList = null; for (Object item : attachedCollection) { if (item != null) { if (item instanceof StateHolder && ((StateHolder) item).isTransient()) { continue; } if (resultList == null) { resultList = new ArrayList(attachedCollection.size() + 1); resultList.add(new StateHolderSaver(context, mapOrCollectionClass)); } resultList.add(new StateHolderSaver(context, item)); } } result = resultList; } else if (newWillSucceed && attachedObject instanceof Map) { Map attachedMap = (Map) attachedObject; List resultList = null; Object key, value; for (Map.Entry 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(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; } /** *

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}.

*

*

This method supports restoring all attached objects types * supported by {@link #saveAttachedState}.

* * @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 stateList = (List) stateObj; StateHolderSaver collectionSaver = stateList.get(0); Class mapOrCollection = (Class) collectionSaver.restore(context); if (Collection.class.isAssignableFrom(mapOrCollection)) { Collection retCollection = null; try { retCollection = (Collection) 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 retMap = null; try { retMap = (Map) 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 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 bindings = new HashMap(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, List> 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, List> restoreSystemEventListeners(FacesContext ctx, Object state) { if (state == null) { return null; } Object[][] listeners = (Object[][]) state; Map, List> m = new HashMap, List>(listeners.length, 1.0f); for (int i = 0, len = listeners.length; i < len; i++) { Object[] source = listeners[i]; m.put((Class) source[0], (List) restoreAttachedState(ctx, source[1])); } return m; } Map 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; /** *

This is a default implementation of * {@link javax.faces.component.behavior.ClientBehaviorHolder#addClientBehavior}. * UIComponent 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}.

* * @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 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> 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> modifiableMap = new HashMap>(5,1.0f); behaviors = new BehaviorsMap(modifiableMap); } List eventBehaviours = behaviors.get(eventName); if (null == eventBehaviours) { // Again using small initial capacity - we typically // only have 1 Behavior per event type. eventBehaviours = new ArrayList(3); behaviors.getModifiableMap().put(eventName, eventBehaviours); } eventBehaviours.add(behavior); } } /** *

This is a default implementation of * {@link javax.faces.component.behavior.ClientBehaviorHolder#getEventNames}. * UIComponent 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 Collection * of the client event names that the component supports.

* * @since 2.0 */ public Collection 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; } /** *

This is a default implementation of * {@link javax.faces.component.behavior.ClientBehaviorHolder#getClientBehaviors}. * UIComponent 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}.

* * @since 2.0 */ public Map> getClientBehaviors() { if (null == behaviors) { return Collections.emptyMap(); } return behaviors; } /** *

This is a default implementation of * {@link javax.faces.component.behavior.ClientBehaviorHolder#getDefaultEventName}. * UIComponent 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}.

*/ 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 Object 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 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 Map 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> modifiableMap = new HashMap>( names.length, 1.0f); for (int i = 0; i < attachedBehaviors.length; i++) { Object[] attachedEventBehaviors = (Object[]) attachedBehaviors[i]; ArrayList eventBehaviors = new ArrayList(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. if (behaviors != null) { List 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 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 clist = new ArrayList(component.getChildren()); for (UIComponent c : clist) { publishAfterViewEvents(context, application, c); } } if (component.getFacetCount() > 0) { Collection clist = new ArrayList(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 children = component.getChildren(); for (UIComponent c : children) { disconnectFromView(context, application, c); } } if (component.getFacetCount() > 0) { Map 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 EMPTY_ITERATOR = new Iterator() { 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, 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 attributes; private transient Map pdMap; private transient ConcurrentMap readMap; 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 attributes = (Map) 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 attributes = (Map) component.getStateHelper().get(PropertyKeys.attributes); if (null == result) { PropertyDescriptor pd = getPropertyDescriptor(key); if (pd != null) { try { if (null == readMap){ readMap = new ConcurrentHashMap(); } Method readMethod = readMap.get(key); if (null == readMethod) { readMethod = pd.getReadMethod(); Method putResult = readMap.putIfAbsent(key, readMethod); if (null != putResult) { readMethod = putResult; } } 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 sProperties = (List) 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 map) { if (map == null) { throw new NullPointerException(); } for (Map.Entry 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 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 keySet() { Map attributes = getAttributes(); if (attributes != null) return Collections.unmodifiableSet(attributes.keySet()); return Collections.emptySet(); } public Collection values() { Map attributes = getAttributes(); if (attributes != null) return Collections.unmodifiableCollection(attributes.values()); return Collections.emptyList(); } public Set> entrySet() { Map 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 getAttributes() { return (Map) component.getStateHelper().get( PropertyKeys.attributes); } private Object putAttribute(String key, Object value) { return component.getStateHelper().put(PropertyKeys.attributes, key, value); } /** *

Return the PropertyDescriptor for the specified * property name for this {@link UIComponent}'s implementation class, * if any; otherwise, return null.

* * @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 { 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 collection) { Iterator elements = (new ArrayList(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 collection) { Iterator elements = (new ArrayList(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 iterator() { return (new ChildrenListIterator(this)); } public ListIterator listIterator() { return (new ChildrenListIterator(this)); } public ListIterator listIterator(int index) { return (new ChildrenListIterator(this, index)); } public UIComponent remove(int index) { UIComponent child = get(index); child.setParent(null); super.remove(index); return (child); } public boolean remove(Object elementObj) { UIComponent element = (UIComponent) elementObj; if (element == null) { throw new NullPointerException(); } if (super.indexOf(element) != -1) { element.setParent(null); } if (super.remove(element)) { 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 { 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 { private Iterator 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 { private UIComponent component; public FacetsMap(UIComponent component) { super(3, 1.0f); this.component = component; } public void clear() { Iterator keys = keySet().iterator(); while (keys.hasNext()) { keys.next(); keys.remove(); } super.clear(); } public Set> entrySet() { return (new FacetsMapEntrySet(this)); } public Set 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 map) { if (map == null) { throw new NullPointerException(); } for (Map.Entry 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 values() { return (new FacetsMapValues(this)); } Iterator keySetIterator() { return ((new ArrayList(super.keySet())).iterator()); } } // Private implementation of Set for FacetsMap.getEntrySet() private static class FacetsMapEntrySet extends AbstractSet> { public FacetsMapEntrySet(FacetsMap map) { this.map = map; } private FacetsMap map = null; public boolean add(Map.Entry o) { throw new UnsupportedOperationException(); } public boolean addAll(Collection> 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> 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 { 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> { public FacetsMapEntrySetIterator(FacetsMap map) { this.map = map; this.iterator = map.keySetIterator(); } private FacetsMap map = null; private Iterator iterator = null; private Map.Entry last = null; public boolean hasNext() { return (iterator.hasNext()); } public Map.Entry 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 { public FacetsMapKeySet(FacetsMap map) { this.map = map; } private FacetsMap map = null; public boolean add(String o) { throw new UnsupportedOperationException(); } public boolean addAll(Collection 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 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 { public FacetsMapKeySetIterator(FacetsMap map) { this.map = map; this.iterator = map.keySetIterator(); } private FacetsMap map = null; private Iterator 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 { 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 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 { public FacetsMapValuesIterator(FacetsMap map) { this.map = map; this.iterator = map.keySetIterator(); } private FacetsMap map = null; private Iterator 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>{ private Map> unmodifiableMap; private Map> modifiableMap; private BehaviorsMap(Map> modifiableMap) { this.modifiableMap = modifiableMap; this.unmodifiableMap = Collections.unmodifiableMap(modifiableMap); } public Set>> entrySet() { return unmodifiableMap.entrySet(); } private Map> getModifiableMap() { return modifiableMap; } } }