/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2015 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.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 static com.sun.faces.util.Util.isAllNull; import static com.sun.faces.util.Util.isAnyNull; import static com.sun.faces.util.Util.isEmpty; import static java.beans.Introspector.getBeanInfo; import static java.lang.Boolean.TRUE; import static java.lang.Character.isDigit; import static java.lang.Character.isLetter; import static java.lang.Thread.currentThread; import static java.util.Collections.unmodifiableCollection; import static java.util.Collections.unmodifiableList; import static java.util.logging.Level.FINE; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; 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.Map.Entry; import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.logging.Level; import java.util.logging.Logger; 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.AbortProcessingException; import javax.faces.event.BehaviorEvent; import javax.faces.event.ComponentSystemEventListener; import javax.faces.event.FacesEvent; import javax.faces.event.FacesListener; import javax.faces.event.PostAddToViewEvent; import javax.faces.event.PostValidateEvent; import javax.faces.event.PreRemoveFromViewEvent; import javax.faces.event.PreRenderComponentEvent; import javax.faces.event.PreValidateEvent; import javax.faces.event.SystemEvent; import javax.faces.event.SystemEventListener; import javax.faces.render.Renderer; import com.sun.faces.application.ValueBindingValueExpressionAdapter; import com.sun.faces.application.ValueExpressionValueBindingAdapter; /** * <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> * 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"; private static final int MY_STATE = 0; private static final int CHILD_STATE = 1; /** * <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/> */ private Map<Class<?>, Map<String, PropertyDescriptor>> descriptors; /** * Reference to the map of <code>PropertyDescriptor</code>s for this class in the * <code>descriptors<code> <code>Map<code>. */ private Map<String, PropertyDescriptor> propertyDescriptorMap; 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]; /** * <p> * The <code>Map</code> containing our attributes, keyed by attribute name. * </p> */ private AttributesMap attributes; /** * <p> * The component identifier for this component. * </p> */ private String id; /** * <p> * The assigned client identifier for this component. * </p> */ private String clientId; /** * <p> * The parent component for this component. * </p> */ private UIComponent parent; /** * The <code>List</code> containing our child components. */ private List<UIComponent> children; /** * The <code>Map</code> containing our related facet components. */ private Map<String, UIComponent> facets; private AttachedObjectListHolder<FacesListener> listeners; /** * Flag indicating a desire to now participate in state saving. */ private boolean transientFlag; public UIComponentBase() { populateDescriptorsMapIfNecessary(); } @Override public Map<String, Object> getAttributes() { if (attributes == null) { attributes = new AttributesMap(this); } return attributes; } @Override public Map<String, Object> getPassThroughAttributes(boolean create) { @SuppressWarnings("unchecked") Map<String, Object> passThroughAttributes = (Map<String, Object>) this.getStateHelper().get(PropertyKeys.passThroughAttributes); if (passThroughAttributes == null && create) { passThroughAttributes = new PassThroughAttributesMap<>(); getStateHelper().put(PropertyKeys.passThroughAttributes, passThroughAttributes); } return passThroughAttributes; } // -------------------------------------------------------------- Properties /** * @throws NullPointerException {@inheritDoc} */ @Override public String getClientId(FacesContext context) { if (context == null) { throw new NullPointerException(); } // If the clientId is not yet set if (clientId == null) { UIComponent namingContainerAncestor = getNamingContainerAncestor(); // Give the parent the opportunity to first grab a unique clientId String parentId = getParentId(context, namingContainerAncestor); // Now resolve our own client id clientId = getId(); if (clientId == null) { setId(generateId(context, namingContainerAncestor)); clientId = getId(); } if (parentId != null) { clientId = addParentId(context, parentId, clientId); } // Allow the renderer to convert the clientId Renderer renderer = getRenderer(context); if (renderer != null) { clientId = renderer.convertClientId(context, clientId); } } return clientId; } @Override public String getId() { return id; } /** * @throws IllegalArgumentException {@inheritDoc} * @throws IllegalStateException {@inheritDoc} */ @Override 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 } @Override public UIComponent getParent() { return parent; } @Override 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 (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. getAttributes().put(ADDED, TRUE); doPostAddProcessing(FacesContext.getCurrentInstance(), this); // Remove the attribute once we've returned from the event // processing. this.getAttributes().remove(ADDED); } } } @Override public boolean isRendered() { return Boolean.valueOf(getStateHelper().eval(PropertyKeys.rendered, TRUE).toString()); } @Override public void setRendered(boolean rendered) { getStateHelper().put(PropertyKeys.rendered, rendered); } @Override public String getRendererType() { return (String) getStateHelper().eval(PropertyKeys.rendererType); } @Override public void setRendererType(String rendererType) { getStateHelper().put(PropertyKeys.rendererType, rendererType); } @Override public boolean getRendersChildren() { if (getRendererType() != null) { Renderer renderer = getRenderer(getFacesContext()); if (renderer != null) { return renderer.getRendersChildren(); } } return false; } // ------------------------------------------------- Tree Management Methods @Override public List<UIComponent> getChildren() { if (children == null) { children = new ChildrenList(this); } return children; } // Do not allocate the children List to answer this question @Override public int getChildCount() { if (children != null) { return children.size(); } return 0; } /** * @throws NullPointerException {@inheritDoc} */ @Override public UIComponent findComponent(String expression) { if (expression == null) { throw new NullPointerException(); } if (expression.isEmpty()) { // If an empty value is provided, fail fast. throw new IllegalArgumentException("\"\""); } final char sepChar = UINamingContainer.getSeparatorChar(FacesContext.getCurrentInstance()); // Identify the base component from which we will perform our search UIComponent base = findBaseComponent(expression, sepChar); if (expression.charAt(0) == sepChar) { // Treat remainder of the expression as relative expression = expression.substring(1); } // Evaluate the search expression (now guaranteed to be relative) return evaluateSearchExpression(base, expression, String.valueOf(sepChar)); } /** * {@inheritDoc} * * @throws NullPointerException {@inheritDoc} * @throws FacesException {@inheritDoc} * @since 1.2 */ @Override public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback) throws FacesException { return super.invokeOnComponent(context, clientId, callback); } // ------------------------------------------------ Facet Management Methods @Override public Map<String, UIComponent> getFacets() { if (facets == null) { facets = new FacetsMap(this); } return facets; } // Do not allocate the children List to answer this question @Override public int getFacetCount() { if (facets != null) { return facets.size(); } return 0; } // Do not allocate the facets Map to answer this question @Override public UIComponent getFacet(String name) { if (facets != null) { return facets.get(name); } return null; } @Override public Iterator<UIComponent> getFacetsAndChildren() { int childCount = getChildCount(), facetCount = getFacetCount(); // If there are neither facets nor children if (childCount == 0 && facetCount == 0) { return EMPTY_ITERATOR; } // If there are only facets and no children if (childCount == 0) { return unmodifiableCollection(getFacets().values()).iterator(); } // If there are only children and no facets if (facetCount == 0) { return unmodifiableList(getChildren()).iterator(); } // If there are both children and facets return new FacetsAndChildrenIterator(this); } // -------------------------------------------- Lifecycle Processing Methods /** * @throws AbortProcessingException {@inheritDoc} * @throws IllegalStateException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ @Override 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} */ @Override public void decode(FacesContext context) { if (context == null) { throw new NullPointerException(); } String rendererType = getRendererType(); if (rendererType != null) { Renderer renderer = getRenderer(context); if (renderer != null) { renderer.decode(context, this); } else { if (LOGGER.isLoggable(FINE)) { LOGGER.fine("Can't get Renderer for type " + rendererType); } } } } /** * @throws NullPointerException {@inheritDoc} */ @Override 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 = getRenderer(context); if (renderer != null) { renderer.encodeBegin(context, this); } else { if (LOGGER.isLoggable(FINE)) { LOGGER.fine("Can't get Renderer for type " + rendererType); } } } } /** * @throws NullPointerException {@inheritDoc} */ @Override public void encodeChildren(FacesContext context) throws IOException { if (context == null) { throw new NullPointerException(); } if (!isRendered()) { return; } if (getRendererType() != null) { Renderer renderer = 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} */ @Override public void encodeEnd(FacesContext context) throws IOException { if (context == null) { throw new NullPointerException(); } if (!isRendered()) { popComponentFromEL(context); return; } if (getRendererType() != null) { Renderer renderer = getRenderer(context); if (renderer != null) { renderer.encodeEnd(context, this); } // We've already logged for this component } popComponentFromEL(context); } // -------------------------------------------------- Event Listener Methods /** * <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); * } * ... * } * * 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); * } * ... * } * </pre> * * @param listener The {@link FacesListener} to be registered * @throws NullPointerException if <code>listener</code> is <code>null</code> */ @Override 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} */ @Override 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<>(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> */ @Override protected void removeFacesListener(FacesListener listener) { if (listener == null) { throw new NullPointerException(); } if (listeners != null) { listeners.remove(listener); } } /** * @throws IllegalStateException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ @Override public void queueEvent(FacesEvent event) { if (event == null) { throw new NullPointerException(); } UIComponent parent = getParent(); if (parent == null) { throw new IllegalStateException(); } 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 NullPointerException if any of the arguments are <code>null</code>. * * @since 2.1 */ @Override public void subscribeToEvent(Class<? extends SystemEvent> eventClass, ComponentSystemEventListener componentListener) { if (isAnyNull(eventClass, componentListener)) { throw new NullPointerException(); } if (initialStateMarked()) { initialState = false; } if (listenersByEventClass == null) { listenersByEventClass = new HashMap<>(3, 1.0f); } SystemEventListener facesLifecycleListener = new ComponentSystemEventListenerAdapter(componentListener, this); List<SystemEventListener> listenersForEventClass = listenersByEventClass.get(eventClass); if (listenersForEventClass == null) { listenersForEventClass = new ArrayList<>(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 NullPointerException if any of the arguments are <code>null</code>. * * @since 2.1 */ @Override public void unsubscribeFromEvent(Class<? extends SystemEvent> eventClass, ComponentSystemEventListener componentListener) { if (isAnyNull(eventClass, componentListener)) { throw new NullPointerException(); } List<SystemEventListener> listeners = getListenersForEventClass(eventClass); if (!isEmpty(listeners)) { for (Iterator<SystemEventListener> i = listeners.iterator(); i.hasNext();) { ComponentSystemEventListener existingListener = ((ComponentSystemEventListenerAdapter) i.next()).getWrapped(); if (existingListener.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 */ @Override public List<SystemEventListener> getListenersForEventClass(Class<? extends SystemEvent> eventClass) { if (eventClass == null) { throw new NullPointerException(); } if (listenersByEventClass != null) { return listenersByEventClass.get(eventClass); } return null; } // ------------------------------------------------ Lifecycle Phase Handlers /** * @throws NullPointerException {@inheritDoc} */ @Override 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<UIComponent> 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} */ @Override 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 application = context.getApplication(); application.publishEvent(context, PreValidateEvent.class, this); // Process all the facets and children of this component Iterator<UIComponent> kids = getFacetsAndChildren(); while (kids.hasNext()) { UIComponent kid = (UIComponent) kids.next(); kid.processValidators(context); } application.publishEvent(context, PostValidateEvent.class, this); } finally { popComponentFromEL(context); } } /** * @throws NullPointerException {@inheritDoc} */ @Override 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<UIComponent> kids = getFacetsAndChildren(); while (kids.hasNext()) { UIComponent kid = (UIComponent) kids.next(); kid.processUpdates(context); } } finally { popComponentFromEL(context); } } /** * @throws NullPointerException {@inheritDoc} */ @Override public Object processSaveState(FacesContext context) { if (context == null) { throw new NullPointerException(); } if (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 = getChildCount() + getFacetCount(); if (count > 0) { // This arraylist will store state List<Object> stateList = new ArrayList<>(count); // If we have children, add them to the stateList collectChildState(context, stateList); // If we have facets, add them to the stateList collectFacetsState(context, stateList); // 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} */ @Override 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]); // Process all the children of this component int i = restoreChildState(context, childState); // Process all of the facets of this component restoreFacetsState(context, childState, i); } finally { popComponentFromEL(context); } } // ------------------------------------------------------- Protected Methods @Override 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(); } @Override protected Renderer getRenderer(FacesContext context) { Renderer renderer = null; String rendererType = getRendererType(); if (rendererType != null) { renderer = context.getRenderKit().getRenderer(getFamily(), rendererType); if (renderer == null && LOGGER.isLoggable(FINE)) { LOGGER.fine("Can't get Renderer for type " + rendererType); } } else { if (LOGGER.isLoggable(FINE)) { String id = getId(); LOGGER.fine("No renderer-type for component " + id != null ? id : getClass().getName()); } } return renderer; } // ---------------------------------------------- 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(); } } } } } @Override public Object saveState(FacesContext context) { if (context == null) { throw new NullPointerException(); } Object[] values = null; 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 (isAllNull(savedFacesListeners, savedSysEventListeners, savedBehaviors, savedBindings, savedHelper)) { return null; } 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; } } @Override 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<Class<? extends SystemEvent>, List<SystemEventListener>> restoredListeners = restoreSystemEventListeners(context, values[1]); if (listenersByEventClass != null) { listenersByEventClass.putAll(restoredListeners); } else { listenersByEventClass = restoredListeners; } } 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]; } } } @Override public boolean isTransient() { return transientFlag; } @Override 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> * 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}. * * @return The state object to be saved. * @throws NullPointerException if the context argument is null. */ public static Object saveAttachedState(FacesContext context, Object attachedObject) { if (context == null) { throw new NullPointerException(); } if (attachedObject == null) { 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<>(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<>(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> * 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} * * @return the object restored from <code>stateObj</code>. * * @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 (InstantiationException | IllegalAccessException 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 (InstantiationException | IllegalAccessException 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<>(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<>(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 propertyDescriptorMap; } 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<>(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<>(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> * * @return the collection of event names. * @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> * * @return behaviors associated with this component. * @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> * * @return the default event name. */ 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<>(names.length, 1.0f); for (int i = 0; i < attachedBehaviors.length; i++) { Object[] attachedEventBehaviors = (Object[]) attachedBehaviors[i]; ArrayList<ClientBehavior> 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<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<>(component.getChildren()); for (UIComponent c : clist) { publishAfterViewEvents(context, application, c); } } if (component.getFacetCount() > 0) { Collection<UIComponent> 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<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>() { @Override public void remove() { throw new UnsupportedOperationException(); } @Override public UIComponent next() { throw new NoSuchElementException("Empty Iterator"); } @Override 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 ConcurrentMap<String, Method> readMap; private transient UIComponent component; private static final long serialVersionUID = -6773035086539772945L; // -------------------------------------------------------- Constructors private AttributesMap(UIComponent component) { this.component = component; this.pdMap = ((UIComponentBase) component).getDescriptorMap(); } @Override 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); } } @Override 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 { 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; } @Override 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); } } @Override 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()); } } @Override 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; } } } @Override public int size() { Map attributes = getAttributes(); return (attributes != null ? attributes.size() : 0); } @Override public boolean isEmpty() { Map attributes = getAttributes(); return (attributes == null || attributes.isEmpty()); } @Override public boolean containsValue(java.lang.Object value) { Map attributes = getAttributes(); return (attributes != null && attributes.containsValue(value)); } @Override public void clear() { component.getStateHelper().remove(PropertyKeys.attributes); component.getStateHelper().remove(PropertyKeysPrivate.attributesThatAreSet); } @Override public Set<String> keySet() { Map<String, Object> attributes = getAttributes(); if (attributes != null) return Collections.unmodifiableSet(attributes.keySet()); return Collections.emptySet(); } @Override public Collection<Object> values() { Map<String, Object> attributes = getAttributes(); if (attributes != null) return Collections.unmodifiableCollection(attributes.values()); return Collections.emptyList(); } @Override public Set<Entry<String, Object>> entrySet() { Map<String, Object> attributes = getAttributes(); if (attributes != null) return Collections.unmodifiableSet(attributes.entrySet()); return Collections.emptySet(); } @Override 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 | NullPointerException unused) { return false; } return true; } @Override 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 (InstantiationException | IllegalAccessException 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; } @Override 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); } } @Override public boolean add(UIComponent element) { if (element == null) { throw new NullPointerException(); } else { eraseParent(element); boolean result = super.add(element); element.setParent(component); return result; } } @Override 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); } @Override 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); } @Override 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(); } @Override public Iterator<UIComponent> iterator() { return (new ChildrenListIterator(this)); } @Override public ListIterator<UIComponent> listIterator() { return (new ChildrenListIterator(this)); } @Override public ListIterator<UIComponent> listIterator(int index) { return (new ChildrenListIterator(this, index)); } @Override public UIComponent remove(int index) { UIComponent child = get(index); child.setParent(null); super.remove(index); return (child); } @Override 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); } } @Override public boolean removeAll(Collection<?> collection) { boolean result = false; for (Object elements : collection) { if (remove(elements)) { result = true; } } return (result); } @Override 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); } @Override 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 @Override public boolean hasNext() { return (index < list.size()); } @Override public UIComponent next() { try { UIComponent o = list.get(index); last = index++; return (o); } catch (IndexOutOfBoundsException e) { throw new NoSuchElementException(String.valueOf(index)); } } @Override public void remove() { if (last == -1) { throw new IllegalStateException(); } list.remove(last); if (last < index) { index--; } last = -1; } // ListIterator methods @Override public void add(UIComponent o) { last = -1; list.add(index++, o); } @Override public boolean hasPrevious() { return (index > 1); } @Override public int nextIndex() { return (index); } @Override 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(); } } @Override public int previousIndex() { return (index - 1); } @Override 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; } } @Override public boolean hasNext() { this.update(); return this.iterator.hasNext(); } @Override public UIComponent next() { this.update(); return this.iterator.next(); } @Override 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; } @Override public void clear() { Iterator<String> keys = keySet().iterator(); while (keys.hasNext()) { keys.next(); keys.remove(); } super.clear(); } @Override public Set<Map.Entry<String, UIComponent>> entrySet() { return (new FacetsMapEntrySet(this)); } @Override public Set<String> keySet() { return (new FacetsMapKeySet(this)); } @Override 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); } @Override 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()); } } @Override public UIComponent remove(Object key) { UIComponent previous = get(key); if (previous != null) { previous.setParent(null); } super.remove(key); return (previous); } @Override public Collection<UIComponent> values() { return (new FacetsMapValues(this)); } Iterator<String> keySetIterator() { return ((new ArrayList<>(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; @Override public boolean add(Map.Entry<String, UIComponent> o) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends Map.Entry<String, UIComponent>> c) { throw new UnsupportedOperationException(); } @Override public void clear() { map.clear(); } @Override 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))); } } @Override public boolean isEmpty() { return (map.isEmpty()); } @Override public Iterator<Map.Entry<String, UIComponent>> iterator() { return (new FacetsMapEntrySetIterator(map)); } @Override 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); } } @Override public boolean removeAll(Collection c) { boolean result = false; for (Object element : c) { if (remove(element)) { result = true; } } return (result); } @Override 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); } @Override 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; @Override 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); } @Override public String getKey() { return (key); } @Override public UIComponent getValue() { return (map.get(key)); } @Override public int hashCode() { Object value = map.get(key); return (((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode())); } @Override 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; @Override public boolean hasNext() { return (iterator.hasNext()); } @Override public Map.Entry<String, UIComponent> next() { last = new FacetsMapEntrySetEntry(map, iterator.next()); return (last); } @Override 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(); } @Override public boolean addAll(Collection<? extends String> c) { throw new UnsupportedOperationException(); } @Override public void clear() { map.clear(); } @Override public boolean contains(Object o) { return (map.containsKey(o)); } @Override public boolean containsAll(Collection c) { for (Object item : c) { if (!map.containsKey(item)) { return (false); } } return (true); } @Override public boolean isEmpty() { return (map.isEmpty()); } @Override public Iterator<String> iterator() { return (new FacetsMapKeySetIterator(map)); } @Override public boolean remove(Object o) { if (map.containsKey(o)) { map.remove(o); return (true); } else { return (false); } } @Override public boolean removeAll(Collection c) { boolean result = false; for (Object item : c) { if (map.containsKey(item)) { map.remove(item); result = true; } } return (result); } @Override 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); } @Override 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; @Override public boolean hasNext() { return (iterator.hasNext()); } @Override public String next() { last = iterator.next(); return (last); } @Override 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; @Override public boolean add(UIComponent o) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } @Override public void clear() { map.clear(); } @Override public boolean isEmpty() { return (map.isEmpty()); } @Override public Iterator<UIComponent> iterator() { return (new FacetsMapValuesIterator(map)); } @Override 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; @Override public boolean hasNext() { return (iterator.hasNext()); } @Override public UIComponent next() { last = iterator.next(); return (map.get(last)); } @Override 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); } @Override public Set<Entry<String, List<ClientBehavior>>> entrySet() { return unmodifiableMap.entrySet(); } private Map<String, List<ClientBehavior>> getModifiableMap() { return modifiableMap; } } private static class PassThroughAttributesMap<K, V> extends ConcurrentHashMap<String, Object> implements Serializable { private static final long serialVersionUID = 4230540513272170861L; @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(); } } } @SuppressWarnings("unchecked") 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<String, Object> 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<>()); } descriptors = (Map<Class<?>, Map<String, PropertyDescriptor>>) applicationMap.get("com.sun.faces.compnent.COMPONENT_DESCRIPTORS_MAP"); propertyDescriptorMap = descriptors.get(clazz); } if (propertyDescriptorMap == null) { // We did not find the property descriptor map so we are now going to load it. PropertyDescriptor propertyDescriptors[] = getPropertyDescriptors(); if (propertyDescriptors != null) { propertyDescriptorMap = new HashMap<>(propertyDescriptors.length, 1.0f); for (PropertyDescriptor propertyDescriptor : propertyDescriptors) { propertyDescriptorMap.put(propertyDescriptor.getName(), propertyDescriptor); } if (LOGGER.isLoggable(FINE)) { LOGGER.log(FINE, "fine.component.populating_descriptor_map", new Object[] { clazz, currentThread().getName() }); } if (descriptors != null && !descriptors.containsKey(clazz)) { descriptors.put(clazz, propertyDescriptorMap); } } } } /** * <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() { try { return getBeanInfo(getClass()).getPropertyDescriptors(); } catch (IntrospectionException e) { throw new FacesException(e); } } private String addParentId(FacesContext context, String parentId, String childId) { return new StringBuilder(parentId.length() + 1 + childId.length()) .append(parentId) .append(UINamingContainer.getSeparatorChar(context)) .append(childId) .toString(); } private String getParentId(FacesContext context, UIComponent parent) { if (parent == null) { return null; } return parent.getContainerClientId(context); } private String generateId(FacesContext context, UIComponent namingContainerAncestor) { if (namingContainerAncestor instanceof UniqueIdVendor) { return ((UniqueIdVendor) namingContainerAncestor).createUniqueId(context, null); } return context.getViewRoot().createUniqueId(); } private UIComponent getNamingContainerAncestor() { UIComponent namingContainer = getParent(); while (namingContainer != null) { if (namingContainer instanceof NamingContainer) { return namingContainer; } namingContainer = namingContainer.getParent(); } return null; } /** * <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<UIComponent> children = parent.getChildren(); int index = children.indexOf(component); if (index >= 0) { children.remove(index); return; } } if (parent.getFacetCount() > 0) { Map<String, UIComponent> facets = parent.getFacets(); Iterator<Entry<String, UIComponent>> entries = facets.entrySet().iterator(); while (entries.hasNext()) { Map.Entry<String, UIComponent> 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 idLength = id.length(); if (idLength < 1) { throw new IllegalArgumentException("Empty id attribute is not allowed"); } for (int i = 0; i < idLength; i++) { char c = id.charAt(i); if (i == 0) { if (!isLetter(c) && c != '_') { throw new IllegalArgumentException(id); } } else { if (!isLetter(c) && !isDigit(c) && c != '-' && c != '_') { throw new IllegalArgumentException(id); } } } } private UIComponent findBaseComponent(String expression, final char sepChar) { // Identify the base component from which we will perform our search UIComponent base = this; if (expression.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 expression = expression.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(); } } return base; } private UIComponent evaluateSearchExpression(UIComponent base, String expression, final String SEPARATOR_STRING) { UIComponent result = null; String[] segments = expression.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 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 component = null; for (Iterator<UIComponent> i = base.getFacetsAndChildren(); i.hasNext();) { UIComponent kid = (UIComponent) i.next(); if (!(kid instanceof NamingContainer)) { if (checkId && id.equals(kid.getId())) { component = kid; break; } component = findComponent(kid, id, true); if (component != null) { break; } } else if (id.equals(kid.getId())) { component = kid; break; } } return component; } private List<Object> collectChildState(FacesContext context, List<Object> stateList) { if (getChildCount() > 0) { Iterator<UIComponent> kids = getChildren().iterator(); while (kids.hasNext()) { UIComponent kid = (UIComponent) kids.next(); if (!kid.isTransient()) { stateList.add(kid.processSaveState(context)); } } } return stateList; } private List<Object> collectFacetsState(FacesContext context, List<Object> stateList) { if (getFacetCount() > 0) { Iterator<Entry<String, UIComponent>> myFacets = getFacets().entrySet().iterator(); while (myFacets.hasNext()) { Map.Entry<String, UIComponent> entry = myFacets.next(); UIComponent facet = (UIComponent) entry.getValue(); if (!facet.isTransient()) { Object facetState = facet.processSaveState(context); Object[] facetSaveState = new Object[2]; facetSaveState[0] = entry.getKey(); facetSaveState[1] = facetState; stateList.add(facetSaveState); } } } return stateList; } private int restoreChildState(FacesContext context, Object[] childState) { int i = 0; // Process all the children of this component if (getChildCount() > 0) { for (UIComponent kid : getChildren()) { if (kid.isTransient()) { continue; } Object currentState = childState[i++]; if (currentState == null) { continue; } kid.processRestoreState(context, currentState); } } return i; } private void restoreFacetsState(FacesContext context, Object[] childState, int i) { if (getFacetCount() > 0) { int j = 0; int facetsSize = getFacets().size(); while (j < facetsSize) { Object[] facetSaveState = (Object[]) childState[i++]; if (facetSaveState != null) { String facetName = (String) facetSaveState[0]; Object facetState = facetSaveState[1]; UIComponent facet = getFacets().get(facetName); facet.processRestoreState(context, facetState); } ++j; } } } // ------------------------------------------- Deprecated code /** * {@inheritDoc} * * @throws NullPointerException {@inheritDoc} * @deprecated This has been replaced by {@link #getValueExpression}. */ @Override 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}. */ @Override 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); } } }