View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package javax.faces.component;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.Enumeration;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.Iterator;
29  import java.util.LinkedList;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Map;
33  import java.util.MissingResourceException;
34  import java.util.PropertyResourceBundle;
35  import java.util.ResourceBundle;
36  import java.util.Set;
37  
38  import javax.el.ELException;
39  import javax.el.ValueExpression;
40  import javax.faces.FacesException;
41  import javax.faces.application.Application;
42  import javax.faces.application.Resource;
43  import javax.faces.component.visit.VisitCallback;
44  import javax.faces.component.visit.VisitContext;
45  import javax.faces.component.visit.VisitHint;
46  import javax.faces.component.visit.VisitResult;
47  import javax.faces.context.FacesContext;
48  import javax.faces.el.ValueBinding;
49  import javax.faces.event.AbortProcessingException;
50  import javax.faces.event.ComponentSystemEvent;
51  import javax.faces.event.ComponentSystemEventListener;
52  import javax.faces.event.FacesEvent;
53  import javax.faces.event.FacesListener;
54  import javax.faces.event.PostRestoreStateEvent;
55  import javax.faces.event.SystemEvent;
56  import javax.faces.event.SystemEventListener;
57  import javax.faces.event.SystemEventListenerHolder;
58  import javax.faces.render.Renderer;
59  import javax.faces.view.ViewDeclarationLanguage;
60  
61  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFComponent;
62  
63  /**
64   *
65   * see Javadoc of <a href="http://java.sun.com/javaee/javaserverfaces/1.2/docs/api/index.html">J
66   * SF Specification</a>
67   *
68   * @author Manfred Geiler (latest modification by $Author: lu4242 $)
69   * @version $Revision: 1297574 $ $Date: 2012-03-06 11:58:54 -0500 (Tue, 06 Mar 2012) $
70   */
71  @JSFComponent(type = "javax.faces.Component", family = "javax.faces.Component", desc = "abstract base component", configExcluded = true)
72  public abstract class UIComponent implements PartialStateHolder, SystemEventListenerHolder, ComponentSystemEventListener {
73      // TODO: Reorder methods, this class is a mess
74      /**
75       * Constant used in component attribute map to retrieve the BeanInfo of a composite
76       * component.
77       *
78       * @see javax.faces.view.ViewDeclarationLanguage#getComponentMetadata(FacesContext, Resource)
79       * @see javax.faces.view.ViewDeclarationLanguage#retargetAttachedObjects(FacesContext, UIComponent, List)
80       * @see javax.faces.view.ViewDeclarationLanguage#retargetMethodExpressions(FacesContext, UIComponent)
81       * @see javax.faces.application.Application#createComponent(FacesContext, Resource)
82       */
83      public static final String BEANINFO_KEY = "javax.faces.component.BEANINFO_KEY";
84  
85      /**
86       * Constant used in BeanInfo descriptor as a key for retrieve an alternate component type
87       * for create the composite base component. 
88       *
89       * @see javax.faces.application.Application#createComponent(FacesContext, Resource)
90       */
91      public static final String COMPOSITE_COMPONENT_TYPE_KEY = "javax.faces.component.COMPOSITE_COMPONENT_TYPE";
92  
93      /**
94       * Constant used to define the facet inside this component that store the component hierarchy
95       * generated by a composite component implementation, and then rendered. In other words, 
96       * note that direct children of a component are not rendered, instead components inside 
97       * this face are rendered.
98       */
99      public static final String COMPOSITE_FACET_NAME = "javax.faces.component.COMPOSITE_FACET_NAME";
100 
101     /**
102      * Constant used to store the current component that is being processed.
103      *
104      * @see #pushComponentToEL(FacesContext, UIComponent)
105      * @see #popComponentFromEL(FacesContext)
106      */
107     public static final String CURRENT_COMPONENT = "javax.faces.component.CURRENT_COMPONENT";
108 
109     /**
110      * Constant used to store the current composite component that is being processed. 
111      *
112      * @see #pushComponentToEL(FacesContext, UIComponent)
113      * @see #popComponentFromEL(FacesContext)
114      */
115     public static final String CURRENT_COMPOSITE_COMPONENT = "javax.faces.component.CURRENT_COMPOSITE_COMPONENT";
116 
117     /**
118      * This constant has two usages. The first one is in component attribute map to identify the 
119      * facet name under this component is child of its parent. The second one is on BeanInfo descriptor
120      * as a key for a Map&lt;String, PropertyDescriptor&gt; that contains metadata information defined
121      * by composite:facet tag and composite:implementation(because this one fills the facet referenced
122      * by COMPOSITE_FACET_NAME constant). 
123      */
124     public static final String FACETS_KEY = "javax.faces.component.FACETS_KEY";
125 
126     /**
127      * Constant used in component attribute map to store the {@link javax.faces.view.Location} object
128      * where the definition of this component is.
129      */
130     public static final String VIEW_LOCATION_KEY = "javax.faces.component.VIEW_LOCATION_KEY";
131 
132     /**
133      * The key under which the component stack is stored in the FacesContext.
134      * ATTENTION: this constant is duplicate in CompositeComponentExpressionUtils.
135      */
136     private static final String _COMPONENT_STACK = "componentStack:" + UIComponent.class.getName();
137 
138     Map<Class<? extends SystemEvent>, List<SystemEventListener>> _systemEventListenerClassMap;
139 
140     /**
141      * @deprecated
142      */
143     @Deprecated
144     protected Map<String, ValueExpression> bindings;
145     /**
146      * Used to cache the map created using getResourceBundleMap() method, since this method could be called several
147      * times when rendering the composite component. This attribute may not be serialized, so transient is used (There
148      * are some very few special cases when UIComponent instances are serializable like t:schedule, so it is better if
149      * transient is used).
150      */
151     private transient Map<String, String> _resourceBundleMap = null;
152     private boolean _inView = false;
153     private StateHelper _stateHelper = null;
154 
155     /**
156      * In JSF 2.0 bindings map was deprecated, and replaced with a map
157      * inside stateHelper. We need this one here because stateHelper needs
158      * to be implemented from here and internally it depends from this property.
159      */
160     private boolean _initialStateMarked = false;
161 
162     public UIComponent()
163     {
164     }
165 
166     public abstract Map<String, Object> getAttributes();
167 
168     /**
169      *
170      * {@inheritDoc}
171      *
172      * @since 2.0
173      */
174     public boolean initialStateMarked()
175     {
176         return _initialStateMarked;
177     }
178 
179     /**
180      * Invokes the <code>invokeContextCallback</code> method with the component, specified by <code>clientId</code>.
181      * 
182      * @param context
183      *            <code>FacesContext</code> for the current request
184      * @param clientId
185      *            the id of the desired <code>UIComponent</code> clazz
186      * @param callback
187      *            Implementation of the <code>ContextCallback</code> to be called
188      * @return has component been found ?
189      * @throws javax.faces.FacesException
190      */
191     public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback)
192             throws FacesException
193     {
194         // java.lang.NullPointerException - if any of the arguments are null
195         if (context == null || clientId == null || callback == null)
196         {
197             throw new NullPointerException();
198         }
199 
200         pushComponentToEL(context, this);
201         try
202         {
203             // searching for this component?
204             boolean found = clientId.equals(this.getClientId(context));
205             if (found)
206             {
207                 try
208                 {
209                     callback.invokeContextCallback(context, this);
210                 }
211                 catch (Exception e)
212                 {
213                     throw new FacesException(e);
214                 }
215                 return found;
216             }
217             // Searching for this component's children/facets
218             for (Iterator<UIComponent> it = this.getFacetsAndChildren(); !found && it.hasNext(); )
219             {
220                 found = it.next().invokeOnComponent(context, clientId, callback);
221             }
222             return found;
223         }
224         finally
225         {
226             //all components must call popComponentFromEl after visiting is finished
227             popComponentFromEL(context);
228         }
229     }
230 
231     /**
232      *
233      * @param component
234      * @return true if the component is a composite component otherwise false is returned
235      *
236      *
237      * @throws NullPointerException if the component is null
238      * @since 2.0
239      */
240     public static boolean isCompositeComponent(UIComponent component)
241     {
242 
243         //since _isCompositeComponent does it the same way we do it here also although I
244         //would prefer following method
245 
246         //return component.getRendererType().equals("javax.faces.Composite");
247 
248         return component.getAttributes().containsKey(Resource.COMPONENT_RESOURCE_KEY);
249     }
250 
251     /**
252      * Indicate if this component is inside a view,
253      * or in other words is contained by an UIViewRoot
254      * instance (which represents the view). If this component
255      * is a UIViewRoot instance, the components "always"
256      * is on the view.
257      *
258      * By default it is false but for UIViewRoot instances is
259      * true. 
260      *
261      * @return
262      *
263      * @since 2.0
264      */
265     public boolean isInView()
266     {
267         return _inView;
268     }
269 
270     public abstract boolean isRendered();
271 
272     public void markInitialState()
273     {
274         _initialStateMarked = true;
275     }
276 
277     /**
278      *
279      * This method indicates if a component is visitable
280      * according to the hints passed by the VisitContext parameter!
281      *
282      * This method internally is used by visitTree and if it returns false
283      * it short circuits the visitTree execution.
284      *
285      *
286      *
287      * @param context
288      * @return
289      *
290      * @since 2.0
291      */
292     protected boolean isVisitable(VisitContext context)
293     {
294 
295         Collection<VisitHint> hints = context.getHints();
296 
297         if (hints.contains(VisitHint.SKIP_TRANSIENT) && this.isTransient())
298         {
299             return false;
300         }
301 
302         if (hints.contains(VisitHint.SKIP_UNRENDERED) && !this.isRendered())
303         {
304             return false;
305         }
306 
307         //executable cannot be handled here because we do not have any method to determine
308         //whether a component is executable or not, this seems to be a hole in the spec!
309         //but we can resolve it on ppr context level, where it is needed!
310         //maybe in the long run we can move it down here, if it makes sense
311 
312         return true;
313     }
314 
315     /**
316      * @deprecated Replaced by setValueExpression
317      */
318     public abstract void setValueBinding(String name, ValueBinding binding);
319 
320     public void setValueExpression(String name, ValueExpression expression)
321     {
322         if (name == null)
323         {
324             throw new NullPointerException("name");
325         }
326         if (name.equals("id"))
327         {
328             throw new IllegalArgumentException("Can't set a ValueExpression for the 'id' property.");
329         }
330         if (name.equals("parent"))
331         {
332             throw new IllegalArgumentException("Can't set a ValueExpression for the 'parent' property.");
333         }
334 
335         if (expression == null)
336         {
337             //if (bindings != null) {
338             //    bindings.remove(name);
339             //    if (bindings.isEmpty()) {
340             //        bindings = null;
341             //    }
342             //}
343             getStateHelper().remove(PropertyKeys.bindings, name);
344         }
345         else
346         {
347             if (expression.isLiteralText())
348             {
349                 try
350                 {
351                     Object value = expression.getValue(getFacesContext().getELContext());
352                     getAttributes().put(name, value);
353                     return;
354                 }
355                 catch (ELException e)
356                 {
357                     throw new FacesException(e);
358                 }
359             }
360 
361             //if (bindings == null) {
362             //    bindings = new HashMap<String, ValueExpression>();
363             //}
364             //
365             //bindings.put(name, expression);
366             getStateHelper().put(PropertyKeys.bindings, name, expression);
367         }
368     }
369 
370     public String getClientId()
371     {
372         return getClientId(getFacesContext());
373     }
374 
375     public abstract String getClientId(FacesContext context);
376 
377     /**
378      * search for the nearest parent composite component, if no parent is found
379      * it has to return null!
380      *
381      * if the component itself is null we have to return null as well!
382      *
383      * @param component the component to start from
384      * @return the parent composite component if found otherwise null
385      *
386      * @since 2.0
387      */
388     public static UIComponent getCompositeComponentParent(UIComponent component)
389     {
390 
391         if (component == null)
392         {
393             return null;
394         }
395         UIComponent parent = component;
396 
397         do
398         {
399             parent = parent.getParent();
400             if (parent != null && UIComponent.isCompositeComponent(parent))
401             {
402                 return parent;
403             }
404         } while (parent != null);
405         return null;
406     }
407 
408     /**
409      * @since 1.2
410      */
411     public String getContainerClientId(FacesContext ctx)
412     {
413         if (ctx == null)
414         {
415             throw new NullPointerException("FacesContext ctx");
416         }
417 
418         return getClientId(ctx);
419     }
420 
421     /**
422      *
423      * @param context
424      * @return
425      *
426      * @since 2.0
427      */
428     public static UIComponent getCurrentComponent(FacesContext context)
429     {
430         return (UIComponent) context.getAttributes().get(UIComponent.CURRENT_COMPONENT);
431     }
432 
433     /**
434      *
435      * @param context
436      * @return
437      *
438      * @since 2.0
439      */
440     public static UIComponent getCurrentCompositeComponent(FacesContext context)
441     {
442         return (UIComponent) context.getAttributes().get(UIComponent.CURRENT_COMPOSITE_COMPONENT);
443     }
444 
445     public abstract String getFamily();
446 
447     public abstract String getId();
448 
449     public List<SystemEventListener> getListenersForEventClass(Class<? extends SystemEvent> eventClass)
450     {
451         List<SystemEventListener> listeners;
452         if (_systemEventListenerClassMap == null)
453         {
454             listeners = Collections.emptyList();
455         }
456         else
457         {
458             listeners = _systemEventListenerClassMap.get(eventClass);
459             if (listeners == null)
460             {
461                 listeners = Collections.emptyList();
462             }
463             else
464             {
465                 listeners = Collections.unmodifiableList(listeners);
466             }
467         }
468 
469         return listeners;
470     }
471 
472     /**
473      *
474      * @return
475      *
476      * @since 2.0
477      */
478     public UIComponent getNamingContainer()
479     {
480         // Starting with "this", return the closest component in the ancestry that is a NamingContainer 
481         // or null if none can be found.
482         UIComponent component = this;
483         do
484         {
485             if (component instanceof NamingContainer)
486             {
487                 return component;
488             }
489 
490             component = component.getParent();
491         } while (component != null);
492 
493         return null;
494     }
495 
496     public abstract void setId(String id);
497 
498     /**
499      * Define if the component is on the view or not.
500      * <p>
501      * This value is set in the following conditions:
502      * </p>
503      * <ul>
504      * <li>Component / Facet added: if the parent isInView = true, 
505      *     set it to true and all their children or facets,
506      *     otherwise take no action</li>
507      * <li>Component / Facet removed: if the parent isInView = false,
508      *     set it to false and all their children or facets,
509      *     otherwise take no action</li>
510      * <ul>
511      * @param isInView
512      *
513      * @since 2.0
514      */
515     public void setInView(boolean isInView)
516     {
517         _inView = isInView;
518     }
519 
520     /**
521      * For JSF-framework internal use only. Don't call this method to add components to the component tree. Use
522      * <code>parent.getChildren().add(child)</code> instead.
523      */
524     public abstract void setParent(UIComponent parent);
525 
526     /**
527      * Returns the parent of the component. Children can be added to or removed from a component even if this method
528      * returns null for the child.
529      */
530     public abstract UIComponent getParent();
531 
532     public abstract void setRendered(boolean rendered);
533 
534     public abstract String getRendererType();
535 
536     public abstract void setRendererType(String rendererType);
537 
538     public abstract boolean getRendersChildren();
539 
540     public Map<String, String> getResourceBundleMap()
541     {
542         if (_resourceBundleMap == null)
543         {
544             FacesContext context = getFacesContext();
545             Locale locale = context.getViewRoot().getLocale();
546             ClassLoader loader = _ClassUtils.getContextClassLoader();
547 
548             try
549             {
550                 // looks for a ResourceBundle with a base name equal to the fully qualified class
551                 // name of the current UIComponent this and Locale equal to the Locale of the current UIViewRoot.
552                 _resourceBundleMap = new BundleMap(ResourceBundle.getBundle(getClass().getName(), locale, loader));
553             }
554             catch (MissingResourceException e)
555             {
556                 // If no such bundle is found, and the component is a composite component
557                 if (this._isCompositeComponent())
558                 {
559                     // No need to check componentResource (the resource used to build the composite
560                     // component instance) to null since it is already done on this._isCompositeComponent()
561                     Resource componentResource = (Resource) getAttributes().get(Resource.COMPONENT_RESOURCE_KEY);
562                     // Let resourceName be the resourceName of the Resource for this composite component,
563                     // replacing the file extension with ".properties"
564                     int extensionIndex = componentResource.getResourceName().lastIndexOf('.');
565                     String resourceName = (extensionIndex < 0
566                             ? componentResource.getResourceName()
567                             : componentResource.getResourceName().substring(0, extensionIndex)) + ".properties";
568 
569                     // Let libraryName be the libraryName of the the Resource for this composite component.
570                     // Call ResourceHandler.createResource(java.lang.String,java.lang.String), passing the derived
571                     // resourceName and
572                     // libraryName.
573                     Resource bundleResource = context.getApplication().getResourceHandler()
574                             .createResource(resourceName, componentResource.getLibraryName());
575 
576                     if (bundleResource != null)
577                     {
578                         // If the resultant Resource exists and can be found, the InputStream for the resource
579                         // is used to create a ResourceBundle. If either of the two previous steps for obtaining the
580                         // ResourceBundle
581                         // for this component is successful, the ResourceBundle is wrapped in a Map<String, String> and
582                         // returned.
583                         try
584                         {
585                             _resourceBundleMap
586                                     = new BundleMap(new PropertyResourceBundle(bundleResource.getInputStream()));
587                         }
588                         catch (IOException e1)
589                         {
590                             // Nothing happens, then resourceBundleMap is set as empty map
591                         }
592                     }
593                 }
594                 // Otherwise Collections.EMPTY_MAP is returned.
595                 if (_resourceBundleMap == null)
596                 {
597                     _resourceBundleMap = Collections.emptyMap();
598                 }
599             }
600         }
601 
602         return _resourceBundleMap;
603     }
604 
605     /**
606      * @deprecated Replaced by getValueExpression
607      */
608     public abstract ValueBinding getValueBinding(String name);
609 
610     public ValueExpression getValueExpression(String name)
611     {
612         if (name == null)
613         {
614             throw new NullPointerException("name can not be null");
615         }
616 
617         Map<String, Object> bindings = (Map<String, Object>) getStateHelper().
618                 get(PropertyKeys.bindings);
619 
620         if (bindings == null)
621         {
622             if (!(this instanceof UIComponentBase))
623             {
624                 // if the component does not inherit from UIComponentBase and don't implements JSF 1.2 or later
625                 ValueBinding vb = getValueBinding(name);
626                 if (vb != null)
627                 {
628                     //bindings = new HashMap<String, ValueExpression>();
629                     ValueExpression ve = new _ValueBindingToValueExpression(vb);
630                     getStateHelper().put(PropertyKeys.bindings, name, ve);
631                     return ve;
632                 }
633             }
634         }
635         else
636         {
637             //return bindings.get(name);
638             return (ValueExpression) bindings.get(name);
639         }
640         return null;
641     }
642 
643     public abstract List<UIComponent> getChildren();
644 
645     public abstract int getChildCount();
646 
647     public abstract UIComponent findComponent(String expr);
648 
649     public abstract Map<String, UIComponent> getFacets();
650 
651     public abstract UIComponent getFacet(String name);
652 
653     public abstract Iterator<UIComponent> getFacetsAndChildren();
654 
655     public abstract void broadcast(FacesEvent event) throws AbortProcessingException;
656 
657     /**
658      * {@inheritDoc}
659      *
660      * @since 2.0
661      */
662     public void clearInitialState()
663     {
664         _initialStateMarked = false;
665     }
666 
667     public abstract void decode(FacesContext context);
668 
669     public abstract void encodeBegin(FacesContext context) throws IOException;
670 
671     public abstract void encodeChildren(FacesContext context) throws IOException;
672 
673     public abstract void encodeEnd(FacesContext context) throws IOException;
674 
675     public void encodeAll(FacesContext context) throws IOException
676     {
677         if (context == null)
678         {
679             throw new NullPointerException();
680         }
681 
682         pushComponentToEL(context, this);
683         try
684         {
685             if (!isRendered())
686             {
687                 return;
688             }
689         }
690         finally
691         {
692             popComponentFromEL(context);
693         }
694 
695         //if (isRendered()) {
696         this.encodeBegin(context);
697 
698         // rendering children
699         if (this.getRendersChildren())
700         {
701             this.encodeChildren(context);
702         } // let children render itself
703         else
704         {
705             if (this.getChildCount() > 0)
706             {
707                 for (int i = 0; i < this.getChildCount(); i++)
708                 {
709                     UIComponent comp = this.getChildren().get(i);
710                     comp.encodeAll(context);
711                 }
712             }
713         }
714         this.encodeEnd(context);
715         //}
716     }
717 
718     protected abstract void addFacesListener(FacesListener listener);
719 
720     protected abstract FacesListener[] getFacesListeners(Class clazz);
721 
722     protected abstract void removeFacesListener(FacesListener listener);
723 
724     public abstract void queueEvent(FacesEvent event);
725 
726     public abstract void processRestoreState(FacesContext context, Object state);
727 
728     public abstract void processDecodes(FacesContext context);
729 
730     public void processEvent(ComponentSystemEvent event) throws AbortProcessingException
731     {
732         // The default implementation performs the following action. If the argument event is an instance of
733         // AfterRestoreStateEvent,
734         if (event instanceof PostRestoreStateEvent)
735         {
736 
737             // call this.getValueExpression(java.lang.String) passing the literal string "binding"
738             ValueExpression expression = getValueExpression("binding");
739 
740             // If the result is non-null, set the value of the ValueExpression to be this.
741             if (expression != null)
742             {
743                 expression.setValue(getFacesContext().getELContext(), this);
744             }
745 
746             //we issue a PostRestoreStateEvent
747             //we issue it here because the spec clearly states what UIComponent is allowed to do
748             //the main issue is that the spec does not say anything about a global dispatch on this level
749             //but a quick blackbox test against the ri revealed that the event clearly is dispatched
750             //at restore level for every component so we either issue it here or in UIViewRoot and/or the facelet
751             // and jsp restore state triggers, a central point is preferrble so we do it here
752             //TODO ask the EG the spec clearly contradicts blackbox RI behavior here 
753 
754             //getFacesContext().getApplication().publishEvent(getFacesContext(),
755             // PostRestoreStateEvent.class, UIComponent.class, this);
756         }
757 
758     }
759 
760     public abstract void processValidators(FacesContext context);
761 
762     public abstract void processUpdates(FacesContext context);
763 
764     public abstract java.lang.Object processSaveState(FacesContext context);
765 
766     public void subscribeToEvent(Class<? extends SystemEvent> eventClass,
767                                  ComponentSystemEventListener componentListener)
768     {
769         // The default implementation creates an inner SystemEventListener instance that wraps argument
770         // componentListener as the listener argument.
771         if (eventClass == null)
772         {
773             throw new NullPointerException("eventClass required");
774         }
775         if (componentListener == null)
776         {
777             throw new NullPointerException("componentListener required");
778         }
779 
780         SystemEventListener listener = new EventListenerWrapper(this, componentListener);
781 
782         // Make sure the map exists
783         if (_systemEventListenerClassMap == null)
784         {
785             _systemEventListenerClassMap = new HashMap<Class<? extends SystemEvent>, List<SystemEventListener>>();
786         }
787 
788         List<SystemEventListener> listeners = _systemEventListenerClassMap.get(eventClass);
789         // Make sure the list for class exists
790         if (listeners == null)
791         {
792             // how many listeners per event type can single component have? 
793             // We use 3 here as expected number, but it is a question 
794             listeners = new _DeltaList<SystemEventListener>(new ArrayList<SystemEventListener>(3));
795             _systemEventListenerClassMap.put(eventClass, listeners);
796         }
797 
798         // Deal with contains? Spec is silent
799         listeners.add(listener);
800     }
801 
802     public void unsubscribeFromEvent(Class<? extends SystemEvent> eventClass,
803                                      ComponentSystemEventListener componentListener)
804     {
805         /*
806          * When doing the comparison to determine if an existing listener is equal to the argument componentListener
807          * (and thus must be removed), the equals() method on the existing listener must be invoked, passing the
808          * argument componentListener, rather than the other way around.
809          * 
810          * -=Simon Lessard=- What is that supposed to mean? Are we supposed to keep
811          * an internal map of created listener wrappers?
812          * -= Leonardo Uribe=- Yes, it is supposed a wrapper should be used to hold listener references, to prevent
813          * serialize component instances on the state.
814          */
815         if (eventClass == null)
816         {
817             throw new NullPointerException("eventClass required");
818         }
819         if (componentListener == null)
820         {
821             throw new NullPointerException("componentListener required");
822         }
823 
824         if (_systemEventListenerClassMap != null)
825         {
826             List<SystemEventListener> listeners = _systemEventListenerClassMap.get(eventClass);
827 
828             if (listeners != null && !listeners.isEmpty())
829             {
830                 for (Iterator<SystemEventListener> it = listeners.iterator(); it.hasNext(); )
831                 {
832                     ComponentSystemEventListener listener
833                             = ((EventListenerWrapper) it.next()).getComponentSystemEventListener();
834                     if (listener != null && listener.equals(componentListener))
835                     {
836                         it.remove();
837                         break;
838                     }
839                 }
840             }
841         }
842     }
843 
844     /**
845      * The visit tree method, visit tree walks over a subtree and processes
846      * the callback object to perform some operation on the subtree
847      * <p>
848      * there are some details in the implementation which according to the spec have
849      * to be in place:
850      * a) before calling the callback and traversing into the subtree  pushComponentToEL
851      * has to be called
852      * b) after the processing popComponentFromEL has to be performed to remove the component
853      * from the el
854      * </p>
855      * <p>
856      * The tree traversal optimizations are located in the visit context and can be replaced
857      * via the VisitContextFactory in the faces-config factory section
858      * </p>
859      *
860      * @param context the visit context which handles the processing details
861      * @param callback the callback to be performed
862      * @return false if the processing is not done true if we can shortcut
863      * the visiting because we are done with everything
864      *
865      * @since 2.0
866      */
867     public boolean visitTree(VisitContext context, VisitCallback callback)
868     {
869         try
870         {
871             pushComponentToEL(context.getFacesContext(), this);
872 
873             if (!isVisitable(context))
874             {
875                 return false;
876             }
877 
878             VisitResult res = context.invokeVisitCallback(this, callback);
879             switch (res)
880             {
881                 //we are done nothing has to be processed anymore
882                 case COMPLETE:
883                     return true;
884 
885                 case REJECT:
886                     return false;
887 
888                 //accept
889                 default:
890                     if (getFacetCount() > 0)
891                     {
892                         for (UIComponent facet : getFacets().values())
893                         {
894                             if (facet.visitTree(context, callback))
895                             {
896                                 return true;
897                             }
898                         }
899                     }
900                     int childCount = getChildCount();
901                     if (childCount > 0)
902                     {
903                         for (int i = 0; i < childCount; i++)
904                         {
905                             UIComponent child = getChildren().get(i);
906                             if (child.visitTree(context, callback))
907                             {
908                                 return true;
909                             }
910                         }
911                     }
912                     return false;
913             }
914         }
915         finally
916         {
917             //all components must call popComponentFromEl after visiting is finished
918             popComponentFromEL(context.getFacesContext());
919         }
920     }
921 
922     protected abstract FacesContext getFacesContext();
923 
924     protected abstract Renderer getRenderer(FacesContext context);
925 
926     /**
927      * Note that id, clientId properties
928      * never change its value after the component is populated,
929      * so we don't need to store it on StateHelper or restore it when
930      * initialStateMarked == true
931      * (Note that rendererType is suspicious, in theory this field is
932      * initialized on constructor, but on 1.1 and 1.2 is saved and restored,
933      * so to keep backward behavior we put it on StateHelper )
934      *
935      * Also, facesListeners can't be wrapped on StateHelper because it
936      * needs to handle PartialStateHolder instances when it is saved and
937      * restored and this interface does not implement PartialStateHolder,
938      * so we can't propagate calls to markInitialState and clearInitialState,
939      * in other words, the List wrapped by StateHelper does not handle
940      * PartialStateHolder items.
941      *
942      * "bindings" map does not need to deal with PartialStateHolder instances,
943      *  so we can use StateHelper feature (handle delta for this map or in
944      *  other words track add/removal from bindings map as delta).
945      */
946     enum PropertyKeys
947     {
948         rendered,
949         rendererType,
950         attributesMap,
951         bindings,
952         facesListeners
953     }
954 
955     protected StateHelper getStateHelper()
956     {
957         return getStateHelper(true);
958     }
959 
960     /**
961      * returns a delta state saving enabled state helper
962      * for the current component
963      * @param create if true a state helper is created if not already existing
964      * @return an implementation of the StateHelper interface or null if none exists and create is set to false
965      */
966     protected StateHelper getStateHelper(boolean create)
967     {
968         if (_stateHelper != null)
969         {
970             return _stateHelper;
971         }
972         if (create)
973         {
974             _stateHelper = new _DeltaStateHelper(this);
975         }
976         return _stateHelper;
977     }
978 
979     @SuppressWarnings("unchecked")
980     public final void popComponentFromEL(FacesContext context)
981     {
982         Map<Object, Object> contextAttributes = context.getAttributes();
983 
984         // Pop the current UIComponent from the FacesContext attributes map so that the previous 
985         // UIComponent, if any, becomes the current component.
986         List<UIComponent> componentStack
987                 = (List<UIComponent>) contextAttributes.get(UIComponent._COMPONENT_STACK);
988 
989         UIComponent oldCurrent = (UIComponent) contextAttributes.get(UIComponent.CURRENT_COMPONENT);
990 
991         UIComponent newCurrent = null;
992         if (componentStack != null && !componentStack.isEmpty())
993         {
994             if (!this.equals(oldCurrent))
995             {
996                 //Check on the componentStack if it can be found
997                 int componentIndex = componentStack.lastIndexOf(this);
998                 if (componentIndex >= 0)
999                 {
1000                     //for (int i = 0; i < (componentIndex + 1); i++)
1001                     for (int i = componentStack.size()-1; i >= componentIndex ; i--)
1002                     {
1003                         newCurrent = componentStack.remove(componentStack.size()-1);
1004                     }
1005                 }
1006                 else
1007                 {
1008                     //Component not found on the stack. Do not pop.
1009                     return;
1010                 }
1011             }
1012             else
1013             {
1014                 newCurrent = componentStack.remove(componentStack.size()-1);
1015             }
1016         }
1017         else
1018         {
1019             //Reset the current composite component
1020             contextAttributes.put(UIComponent.CURRENT_COMPOSITE_COMPONENT, null);
1021         }
1022         oldCurrent = (UIComponent) contextAttributes.put(UIComponent.CURRENT_COMPONENT, newCurrent);
1023 
1024         if (oldCurrent != null && oldCurrent._isCompositeComponent())
1025         {
1026             // Recalculate the current composite component
1027             if (newCurrent != null)
1028             {
1029                 if (newCurrent._isCompositeComponent())
1030                 {
1031                     contextAttributes.put(UIComponent.CURRENT_COMPOSITE_COMPONENT, newCurrent);
1032                 }
1033                 else
1034                 {
1035                     UIComponent previousCompositeComponent = null;
1036                     for (int i = componentStack.size()-1; i >= 0; i--)
1037                     {
1038                         UIComponent component = componentStack.get(i);
1039                         if (component._isCompositeComponent())
1040                         {
1041                             previousCompositeComponent = component;
1042                             break;
1043                         }
1044                     }
1045                     contextAttributes.put(UIComponent.CURRENT_COMPOSITE_COMPONENT, previousCompositeComponent);
1046                 }
1047             }
1048         }
1049     }
1050 
1051     @SuppressWarnings("unchecked")
1052     public final void pushComponentToEL(FacesContext context, UIComponent component)
1053     {
1054         if (component == null)
1055         {
1056             component = this;
1057         }
1058         Map<Object, Object> contextAttributes = context.getAttributes();
1059         UIComponent currentComponent = (UIComponent) contextAttributes.get(UIComponent.CURRENT_COMPONENT);
1060 
1061         if (currentComponent != null)
1062         {
1063             List<UIComponent> componentStack
1064                     = (List<UIComponent>) contextAttributes.get(UIComponent._COMPONENT_STACK);
1065             if (componentStack == null)
1066             {
1067                 componentStack = new ArrayList<UIComponent>();
1068                 contextAttributes.put(UIComponent._COMPONENT_STACK, componentStack);
1069             }
1070 
1071             componentStack.add(currentComponent);
1072         }
1073 
1074         // Push the current UIComponent this to the FacesContext  attribute map using the key CURRENT_COMPONENT 
1075         // saving the previous UIComponent associated with CURRENT_COMPONENT for a subsequent call to 
1076         // popComponentFromEL(javax.faces.context.FacesContext).
1077         contextAttributes.put(UIComponent.CURRENT_COMPONENT, component);
1078 
1079         if (component._isCompositeComponent())
1080         {
1081             contextAttributes.put(UIComponent.CURRENT_COMPOSITE_COMPONENT, component);
1082         }
1083     }
1084 
1085     /**
1086      * @since 1.2
1087      */
1088     public int getFacetCount()
1089     {
1090         // not sure why the RI has this method in both
1091         // UIComponent and UIComponentBase
1092         Map<String, UIComponent> facets = getFacets();
1093         return facets == null ? 0 : facets.size();
1094     }
1095 
1096     private boolean _isCompositeComponent()
1097     {
1098         //moved to the static method
1099         return UIComponent.isCompositeComponent(this);
1100     }
1101 
1102     private static class BundleMap implements Map<String, String>
1103     {
1104 
1105         private ResourceBundle _bundle;
1106         private List<String> _values;
1107 
1108         public BundleMap(ResourceBundle bundle)
1109         {
1110             _bundle = bundle;
1111         }
1112 
1113         // Optimized methods
1114         public String get(Object key)
1115         {
1116             try
1117             {
1118                 return (String) _bundle.getObject(key.toString());
1119             }
1120             catch (Exception e)
1121             {
1122                 return "???" + key + "???";
1123             }
1124         }
1125 
1126         public boolean isEmpty()
1127         {
1128             return !_bundle.getKeys().hasMoreElements();
1129         }
1130 
1131         public boolean containsKey(Object key)
1132         {
1133             try
1134             {
1135                 return _bundle.getObject(key.toString()) != null;
1136             }
1137             catch (MissingResourceException e)
1138             {
1139                 return false;
1140             }
1141         }
1142 
1143         // Unoptimized methods
1144         public Collection<String> values()
1145         {
1146             if (_values == null)
1147             {
1148                 _values = new ArrayList<String>();
1149                 for (Enumeration<String> enumer = _bundle.getKeys(); enumer.hasMoreElements(); )
1150                 {
1151                     String v = _bundle.getString(enumer.nextElement());
1152                     _values.add(v);
1153                 }
1154             }
1155             return _values;
1156         }
1157 
1158         public int size()
1159         {
1160             return values().size();
1161         }
1162 
1163         public boolean containsValue(Object value)
1164         {
1165             return values().contains(value);
1166         }
1167 
1168         public Set<Map.Entry<String, String>> entrySet()
1169         {
1170             Set<Entry<String, String>> set = new HashSet<Entry<String, String>>();
1171             for (Enumeration<String> enumer = _bundle.getKeys(); enumer.hasMoreElements(); )
1172             {
1173                 final String k = enumer.nextElement();
1174                 set.add(new Map.Entry<String, String>()
1175                 {
1176 
1177                     public String getKey()
1178                     {
1179                         return k;
1180                     }
1181 
1182                     public String getValue()
1183                     {
1184                         return (String) _bundle.getObject(k);
1185                     }
1186 
1187                     public String setValue(String value)
1188                     {
1189                         throw new UnsupportedOperationException();
1190                     }
1191                 });
1192             }
1193 
1194             return set;
1195         }
1196 
1197         public Set<String> keySet()
1198         {
1199             Set<String> set = new HashSet<String>();
1200             for (Enumeration<String> enumer = _bundle.getKeys(); enumer.hasMoreElements(); )
1201             {
1202                 set.add(enumer.nextElement());
1203             }
1204             return set;
1205         }
1206 
1207         // Unsupported methods
1208         public String remove(Object key)
1209         {
1210             throw new UnsupportedOperationException();
1211         }
1212 
1213         public void putAll(Map<? extends String, ? extends String> t)
1214         {
1215             throw new UnsupportedOperationException();
1216         }
1217 
1218         public String put(String key, String value)
1219         {
1220             throw new UnsupportedOperationException();
1221         }
1222 
1223         public void clear()
1224         {
1225             throw new UnsupportedOperationException();
1226         }
1227     }
1228 
1229     static class EventListenerWrapper implements SystemEventListener, PartialStateHolder
1230     {
1231 
1232         private Class<?> componentClass;
1233         private ComponentSystemEventListener listener;
1234 
1235         private boolean _initialStateMarked;
1236 
1237         private int listenerCapability;
1238 
1239         private static final int LISTENER_SAVE_STATE_HOLDER = 1;
1240         private static final int LISTENER_SAVE_PARTIAL_STATE_HOLDER = 2;
1241         private static final int LISTENER_TYPE_COMPONENT = 4;
1242         private static final int LISTENER_TYPE_RENDERER = 8;
1243         private static final int LISTENER_TYPE_OTHER = 16;
1244 
1245         public EventListenerWrapper()
1246         {
1247             //need a no-arg constructor for state saving purposes
1248             super();
1249         }
1250 
1251         /**
1252          * Note we have two cases:
1253          *
1254          * 1. listener is an instance of UIComponent. In this case we cannot save and restore
1255          *    it because we need to point to the real component, but we can assume the instance
1256          *    is the same because UIComponent.subscribeToEvent says so. Also take into account
1257          *    this case is the reason why we need a wrapper for UIComponent.subscribeToEvent
1258          * 2. listener is an instance of Renderer. In this case we can assume the same renderer
1259          *    used by the source component is the one used by the listener (ListenerFor). 
1260          * 3. listener is an instance of ComponentSystemEventListener but not from UIComponent.
1261          *    In this case, the instance could implement StateHolder, PartialStateHolder or do
1262          *    implement anything, so we have to deal with that case as usual.
1263          *
1264          * @param component
1265          * @param listener
1266          */
1267         public EventListenerWrapper(UIComponent component, ComponentSystemEventListener listener)
1268         {
1269             assert component != null;
1270             assert listener != null;
1271 
1272             this.componentClass = component.getClass();
1273             this.listener = listener;
1274 
1275             initListenerCapability();
1276         }
1277 
1278         private void initListenerCapability()
1279         {
1280             this.listenerCapability = 0;
1281             if (this.listener instanceof UIComponent)
1282             {
1283                 this.listenerCapability = LISTENER_TYPE_COMPONENT;
1284             }
1285             else if (this.listener instanceof Renderer)
1286             {
1287                 this.listenerCapability = LISTENER_TYPE_RENDERER;
1288             }
1289             else
1290             {
1291                 if (this.listener instanceof PartialStateHolder)
1292                 {
1293                     this.listenerCapability = LISTENER_TYPE_OTHER | LISTENER_SAVE_PARTIAL_STATE_HOLDER;
1294                 }
1295                 else if (this.listener instanceof StateHolder)
1296                 {
1297                     this.listenerCapability = LISTENER_TYPE_OTHER | LISTENER_SAVE_STATE_HOLDER;
1298                 }
1299                 else
1300                 {
1301                     this.listenerCapability = LISTENER_TYPE_OTHER;
1302                 }
1303             }
1304         }
1305 
1306         @Override
1307         public boolean equals(Object o)
1308         {
1309             if (o == this)
1310             {
1311                 return true;
1312             }
1313             else if (o instanceof EventListenerWrapper)
1314             {
1315                 EventListenerWrapper other = (EventListenerWrapper) o;
1316                 return componentClass.equals(other.componentClass) && listener.equals(other.listener);
1317             }
1318             else
1319             {
1320                 return false;
1321             }
1322         }
1323 
1324         @Override
1325         public int hashCode()
1326         {
1327             return componentClass.hashCode() + listener.hashCode();
1328         }
1329 
1330         public boolean isListenerForSource(Object source)
1331         {
1332             // and its implementation of SystemEventListener.isListenerForSource(java.lang.Object) must return true
1333             // if the instance class of this UIComponent is assignable from the argument to isListenerForSource.
1334 
1335             return source.getClass().isAssignableFrom(componentClass);
1336         }
1337 
1338         public ComponentSystemEventListener getComponentSystemEventListener()
1339         {
1340             return listener;
1341         }
1342 
1343         public void processEvent(SystemEvent event)
1344         {
1345             // This inner class must call through to the argument componentListener in its implementation of
1346             // SystemEventListener.processEvent(javax.faces.event.SystemEvent)
1347 
1348             assert event instanceof ComponentSystemEvent;
1349 
1350             listener.processEvent((ComponentSystemEvent) event);
1351         }
1352 
1353         public void clearInitialState()
1354         {
1355             //if (!(listener instanceof UIComponent) && listener instanceof PartialStateHolder)
1356             if ((listenerCapability & LISTENER_SAVE_PARTIAL_STATE_HOLDER) != 0)
1357             {
1358                 ((PartialStateHolder) listener).clearInitialState();
1359             }
1360             _initialStateMarked = false;
1361         }
1362 
1363         public boolean initialStateMarked()
1364         {
1365             //if (!(listener instanceof UIComponent) && listener instanceof PartialStateHolder)
1366             if ((listenerCapability & LISTENER_SAVE_PARTIAL_STATE_HOLDER) != 0)
1367             {
1368                 return ((PartialStateHolder) listener).initialStateMarked();
1369             }
1370             //return false;
1371             return _initialStateMarked;
1372         }
1373 
1374         public void markInitialState()
1375         {
1376             //if (!(listener instanceof UIComponent) && listener instanceof PartialStateHolder)
1377             if ((listenerCapability & LISTENER_SAVE_PARTIAL_STATE_HOLDER) != 0)
1378             {
1379                 ((PartialStateHolder) listener).markInitialState();
1380             }
1381             _initialStateMarked = true;
1382         }
1383 
1384         public boolean isTransient()
1385         {
1386             //if ( listener instanceof StateHolder)
1387             if ((listenerCapability & LISTENER_SAVE_PARTIAL_STATE_HOLDER) != 0 ||
1388                     (listenerCapability & LISTENER_SAVE_STATE_HOLDER) != 0)
1389             {
1390                 return ((StateHolder) listener).isTransient();
1391             }
1392             return false;
1393         }
1394 
1395         public void restoreState(FacesContext context, Object state)
1396         {
1397             if (state == null)
1398             {
1399                 return;
1400             }
1401             Object[] values = (Object[]) state;
1402             componentClass = (Class) values[0];
1403             if (values[1] instanceof _AttachedDeltaWrapper)
1404             {
1405                 ((StateHolder) listener).restoreState(context,
1406                         ((_AttachedDeltaWrapper) values[1]).getWrappedStateObject());
1407             }
1408             else
1409             {
1410                 //Full restore
1411                 listenerCapability = (Integer) values[2];
1412 
1413                 if ((listenerCapability & LISTENER_TYPE_COMPONENT) != 0)
1414                 {
1415                     listener = UIComponent.getCurrentComponent(context);
1416                 }
1417                 else if ((listenerCapability & LISTENER_TYPE_RENDERER) != 0)
1418                 {
1419                     listener = (ComponentSystemEventListener)
1420                             UIComponent.getCurrentComponent(context).getRenderer(context);
1421                 }
1422                 else
1423                 {
1424                     listener = (ComponentSystemEventListener)
1425                             UIComponentBase.restoreAttachedState(context, values[1]);
1426                 }
1427                 /*
1428                 listener = values[1] == null ? 
1429                         UIComponent.getCurrentComponent(context) : 
1430                             (ComponentSystemEventListener) UIComponentBase.restoreAttachedState(context, values[1]);
1431                             */
1432             }
1433         }
1434 
1435         public Object saveState(FacesContext context)
1436         {
1437             if (!initialStateMarked())
1438             {
1439                 /*
1440                 Object[] state = new Object[2];
1441                 state[0] = componentClass;
1442                 if (!(listener instanceof UIComponent))
1443                 {
1444                     state[1] = UIComponentBase.saveAttachedState(context, listener);
1445                 }
1446                 return state;
1447                 */
1448                 Object[] state = new Object[3];
1449                 state[0] = componentClass;
1450                 //If this is not a component or a renderer, save it calling UIComponent.saveAttachedState
1451                 if (!((listenerCapability & LISTENER_TYPE_COMPONENT) != 0 ||
1452                         (listenerCapability & LISTENER_TYPE_RENDERER) != 0))
1453                 {
1454                     state[1] = UIComponentBase.saveAttachedState(context, listener);
1455                 }
1456                 else
1457                 {
1458                     state[1] = null;
1459                 }
1460                 state[2] = (Integer) listenerCapability;
1461                 return state;
1462             }
1463             else
1464             {
1465                 // If initialStateMarked() == true means two things:
1466                 // 1. PSS is being used
1467                 if ((listenerCapability & LISTENER_TYPE_COMPONENT) != 0)
1468                 {
1469                     return null;
1470                 }
1471                 else if ((listenerCapability & LISTENER_TYPE_RENDERER) != 0)
1472                 {
1473                     return null;
1474                 }
1475                 else
1476                 {
1477                     if ((listenerCapability & LISTENER_SAVE_STATE_HOLDER) != 0 ||
1478                             (listenerCapability & LISTENER_SAVE_PARTIAL_STATE_HOLDER) != 0)
1479                     {
1480                         Object listenerSaved = ((StateHolder) listener).saveState(context);
1481                         if (listenerSaved == null)
1482                         {
1483                             return null;
1484                         }
1485                         return new Object[]{componentClass,
1486                                             new _AttachedDeltaWrapper(listener.getClass(), listenerSaved)};
1487                     }
1488                     else
1489                     {
1490                         //This is not necessary, because the instance is considered serializable!
1491                         return null;
1492                     }
1493                 }
1494                 /*
1495                 Object listenerSaved = ((StateHolder) listener).saveState(context);
1496                 if (listenerSaved == null)
1497                 {
1498                     return null;
1499                 }
1500                 return new Object[]{componentClass, new _AttachedDeltaWrapper(listener.getClass(), listenerSaved)};
1501                 */
1502             }
1503         }
1504 
1505         public void setTransient(boolean newTransientValue)
1506         {
1507             if ((listenerCapability & LISTENER_SAVE_PARTIAL_STATE_HOLDER) != 0 ||
1508                     (listenerCapability & LISTENER_SAVE_STATE_HOLDER) != 0)
1509             {
1510                 ((StateHolder) listener).setTransient(newTransientValue);
1511             }
1512         }
1513     }
1514 }