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