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.beans.BeanInfo;
22  import java.beans.IntrospectionException;
23  import java.beans.Introspector;
24  import java.beans.PropertyDescriptor;
25  import java.io.Serializable;
26  import java.lang.reflect.Method;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.HashMap;
30  import java.util.Map;
31  import java.util.Set;
32  import java.util.WeakHashMap;
33  
34  import javax.el.ValueExpression;
35  import javax.faces.FacesException;
36  import javax.faces.application.Resource;
37  import javax.faces.context.FacesContext;
38  
39  /**
40   * A custom implementation of the Map interface, where get and put calls
41   * try to access getter/setter methods of an associated UIComponent before
42   * falling back to accessing a real Map object.
43   * <p/>
44   * Some of the behaviours of this class don't really comply with the
45   * definitions of the Map class; for example the key parameter to all
46   * methods is required to be of type String only, and after clear(),
47   * calls to get can return non-null values. However the JSF spec
48   * requires that this class behave in the way implemented below. See
49   * UIComponent.getAttributes for more details.
50   * <p/>
51   * The term "property" is used here to refer to real javabean properties
52   * on the underlying UIComponent, while "attribute" refers to an entry
53   * in the associated Map.
54   *
55   * @author Manfred Geiler (latest modification by $Author: lu4242 $)
56   * @version $Revision: 1534237 $ $Date: 2013-10-21 10:47:20 -0500 (Mon, 21 Oct 2013) $
57   */
58  class _ComponentAttributesMap implements Map<String, Object>, Serializable
59  {
60      private static final long serialVersionUID = -9106832179394257866L;
61  
62      private static final Object[] EMPTY_ARGS = new Object[0];
63      
64      private final static String MARK_CREATED = "oam.vf.MARK_ID";
65      
66      private final static String FACET_NAME_KEY = "facelets.FACET_NAME";
67      
68      public final static String FACET_CREATED_UIPANEL_MARKER = "oam.vf.createdUIPanel";
69      
70      private final static String COMPONENT_ADDED_BY_HANDLER_MARKER = "oam.vf.addedByHandler";
71      
72      public static final String PROPERTY_DESCRIPTOR_MAP_KEY = "oam.cc.beanInfo.PDM";
73      
74      /**
75       * This variable works as a check to indicate the minimun lenght we need to check
76       * for the special attributes, and save some time in get(), containsKey() and 
77       * put() operations.
78       */
79      private final static int MIN_LENGHT_CHECK = MARK_CREATED.length();
80  
81      // The component that is read/written via this map.
82      private UIComponentBase _component;
83  
84      // We delegate instead of derive from HashMap, so that we can later
85      // optimize Serialization
86      // JSF 2.0 Changed getUnderlyingMap to point to StateHelper attributesMap
87      //private Map<String, Object> _attributes = null;
88  
89      // A cached hashmap of propertyName => PropertyDescriptor object for all
90      // the javabean properties of the associated component. This is built by
91      // introspection on the associated UIComponent. Don't serialize this as
92      // it can always be recreated when needed.
93      private transient Map<String, _PropertyDescriptorHolder> _propertyDescriptorMap = null;
94  
95      // Cache for component property descriptors
96      private static Map<Class<?>, Map<String, _PropertyDescriptorHolder>> propertyDescriptorCache =
97          new WeakHashMap<Class<?>, Map<String, _PropertyDescriptorHolder>>();
98      
99      private boolean _isCompositeComponent;
100     private boolean _isCompositeComponentSet;
101     
102     private BeanInfo _ccBeanInfo;
103 
104     /**
105      * Create a map backed by the specified component.
106      * <p/>
107      * This method is expected to be called when a component is first created.
108      */
109     _ComponentAttributesMap(UIComponentBase component)
110     {
111         _component = component;
112     }
113     
114     /**
115      * Create a map backed by the specified component. Attributes already
116      * associated with the component are provided in the specified Map
117      * class. A reference to the provided map is kept; this object's contents
118      * are updated during put calls on this instance.
119      * <p/>
120      * This method is expected to be called during the "restore view" phase.
121      */
122     //JSF 2.0 removed because _attributes has been replaced with StateHelper attributesMap
123     //_ComponentAttributesMap(UIComponent component, Map<String, Object> attributes)
124     //{
125     //    _component = component;
126         //_attributes = new HashMap<String, Object>(attributes);
127     //}
128     
129     /**
130      * Return the number of <i>attributes</i> in this map. Properties of the
131      * underlying UIComponent are not counted.
132      * <p/>
133      * Note that because the get method can read properties of the
134      * UIComponent and evaluate value-bindings, it is possible to have
135      * size return zero while calls to the get method return non-null
136      * values.
137      */
138     public int size()
139     {
140         return getUnderlyingMap().size();
141     }
142 
143     /**
144      * Clear all the <i>attributes</i> in this map. Properties of the
145      * underlying UIComponent are not modified.
146      * <p/>
147      * Note that because the get method can read properties of the
148      * UIComponent and evaluate value-bindings, it is possible to have
149      * calls to the get method return non-null values immediately after
150      * a call to clear.
151      */
152     public void clear()
153     {
154         getUnderlyingMap().clear();
155     }
156 
157     /**
158      * Return true if there are no <i>attributes</i> in this map. Properties
159      * of the underlying UIComponent are not counted.
160      * <p/>
161      * Note that because the get method can read properties of the
162      * UIComponent and evaluate value-bindings, it is possible to have
163      * isEmpty return true, while calls to the get method return non-null
164      * values.
165      */
166     public boolean isEmpty()
167     {
168         return getUnderlyingMap().isEmpty();
169     }
170 
171     /**
172      * Return true if there is an <i>attribute</i> with the specified name,
173      * but false if there is a javabean <i>property</i> of that name on the
174      * associated UIComponent.
175      * <p/>
176      * Note that it should be impossible for the attributes map to contain
177      * an entry with the same name as a javabean property on the associated
178      * UIComponent.
179      *
180      * @param key <i>must</i> be a String. Anything else will cause a
181      *            ClassCastException to be thrown.
182      */
183     public boolean containsKey(Object key)
184     {
185         checkKey(key);
186 
187         int keyLength = ((String)key).length();
188         if (keyLength >= MIN_LENGHT_CHECK)
189         {
190             if (MARK_CREATED.length() == keyLength &&
191                 MARK_CREATED.equals(key))
192             {
193                 return ((UIComponentBase)_component).getOamVfMarkCreated() != null;
194             }
195             else if (FACET_NAME_KEY.length() == keyLength &&
196                 FACET_NAME_KEY.equals(key))
197             {
198                 return _component.getOamVfFacetName() != null;
199             }
200             else if (COMPONENT_ADDED_BY_HANDLER_MARKER.length() == keyLength &&
201                 COMPONENT_ADDED_BY_HANDLER_MARKER.equals(key))
202             {
203                 return _component.isOamVfAddedByHandler();
204             }
205             else if (FACET_CREATED_UIPANEL_MARKER.length() == keyLength &&
206                 FACET_CREATED_UIPANEL_MARKER.equals(key))
207             {
208                 return _component.isOamVfFacetCreatedUIPanel();
209             }
210             // The most common call to this method comes from UIComponent.isCompositeComponent()
211             // to reduce the impact. This is better than two lookups, once over property descriptor map
212             // and the other one from the underlying map.
213             if (Resource.COMPONENT_RESOURCE_KEY.length() == keyLength &&
214                 Resource.COMPONENT_RESOURCE_KEY.equals(key))
215             {
216                 if (!_isCompositeComponentSet)
217                 {
218                     // Note we are not setting _isCompositeComponentSet, because when the component tree is built
219                     // using JSF 1.2 state saving, PostAddToViewEvent is propagated and the component is check 
220                     // if is a composite component, but the state is not restored, so the check return always
221                     // false. A check for processing events was added to prevent that scenario, but anyway that 
222                     // makes invalid set _isCompositeComponentSet to true on this location.
223                     _isCompositeComponent = getUnderlyingMap().containsKey(Resource.COMPONENT_RESOURCE_KEY);
224                 }
225                 return _isCompositeComponent;
226             }
227         }
228         return getPropertyDescriptor((String) key) == null ? getUnderlyingMap().containsKey(key) : false;
229     }
230 
231     /**
232      * Returns true if there is an <i>attribute</i> with the specified
233      * value. Properties of the underlying UIComponent aren't examined,
234      * nor value-bindings.
235      *
236      * @param value null is allowed
237      */
238     public boolean containsValue(Object value)
239     {
240         return getUnderlyingMap().containsValue(value);
241     }
242 
243     /**
244      * Return a collection of the values of all <i>attributes</i>. Property
245      * values are not included, nor value-bindings.
246      */
247     public Collection<Object> values()
248     {
249         return getUnderlyingMap().values();
250     }
251 
252     /**
253      * Call put(key, value) for each entry in the provided map.
254      */
255     public void putAll(Map<? extends String, ?> t)
256     {
257         for (Map.Entry<? extends String, ?> entry : t.entrySet())
258         {
259             put(entry.getKey(), entry.getValue());
260         }
261     }
262 
263     /**
264      * Return a set of all <i>attributes</i>. Properties of the underlying
265      * UIComponent are not included, nor value-bindings.
266      */
267     public Set<Map.Entry<String, Object>> entrySet()
268     {
269         return getUnderlyingMap().entrySet();
270     }
271 
272     /**
273      * Return a set of the keys for all <i>attributes</i>. Properties of the
274      * underlying UIComponent are not included, nor value-bindings.
275      */
276     public Set<String> keySet()
277     {
278         return getUnderlyingMap().keySet();
279     }
280 
281     /**
282      * In order: get the value of a <i>property</i> of the underlying
283      * UIComponent, read an <i>attribute</i> from this map, or evaluate
284      * the component's value-binding of the specified name.
285      *
286      * @param key must be a String. Any other type will cause ClassCastException.
287      */
288     public Object get(Object key)
289     {
290         checkKey(key);
291         
292         Object value;
293 
294         int keyLength = ((String)key).length();
295         if (keyLength >= MIN_LENGHT_CHECK)
296         {
297             if (MARK_CREATED.length() == keyLength &&
298                 MARK_CREATED.equals(key))
299             {
300                 return _component.getOamVfMarkCreated();
301             }
302             else if (FACET_NAME_KEY.length() == keyLength &&
303                 FACET_NAME_KEY.equals(key))
304             {
305                 return _component.getOamVfFacetName();
306             }
307             else if (COMPONENT_ADDED_BY_HANDLER_MARKER.length() == keyLength &&
308                 COMPONENT_ADDED_BY_HANDLER_MARKER.equals(key))
309             {
310                 return _component.isOamVfAddedByHandler();
311             }
312             else if (FACET_CREATED_UIPANEL_MARKER.length() == keyLength &&
313                 FACET_CREATED_UIPANEL_MARKER.equals(key))
314             {
315                 return _component.isOamVfFacetCreatedUIPanel();
316             }
317         }
318         // is there a javabean property to read?
319         _PropertyDescriptorHolder propertyDescriptor = getPropertyDescriptor((String) key);
320         if (propertyDescriptor != null)
321         {
322             value = getComponentProperty(propertyDescriptor);
323         }
324         else
325         {
326             // is there a literal value to read?
327             value = getUnderlyingMap().get(key);
328             if (value == null)
329             {
330                 // is there a value-binding to read?
331                 ValueExpression ve = _component.getValueExpression((String) key);
332                 if (ve != null)
333                 {
334                     value = ve.getValue(_component.getFacesContext().getELContext());
335                 }
336                 else
337                 {
338                     if (!_isCompositeComponentSet)
339                     {
340                         _isCompositeComponent = getUnderlyingMap().containsKey(Resource.COMPONENT_RESOURCE_KEY);
341                         _isCompositeComponentSet = true;
342                     }
343                     if (_isCompositeComponent)
344                     {
345                         BeanInfo ccBeanInfo = _ccBeanInfo != null ? _ccBeanInfo :
346                             (BeanInfo) getUnderlyingMap().get(UIComponent.BEANINFO_KEY);
347                         if (ccBeanInfo != null)
348                         {
349                             //Fast shortcut to allow fast lookup.
350                             Map<String, PropertyDescriptor> attributeMap = (Map<String, PropertyDescriptor>) 
351                                 ccBeanInfo.getBeanDescriptor().getValue(
352                                     PROPERTY_DESCRIPTOR_MAP_KEY);
353                             if (attributeMap != null)
354                             {
355                                 PropertyDescriptor attribute = attributeMap.get(key);
356                                 if (attribute != null)
357                                 {
358                                     String attributeName = attribute.getName();
359                                     boolean isKnownMethod = "action".equals(attributeName)
360                                             || "actionListener".equals(attributeName)
361                                             || "validator".equals(attributeName)
362                                             || "valueChangeListener".equals(attributeName);
363 
364                                     // <composite:attribute> method-signature attribute is 
365                                     // ValueExpression that must evaluate to String
366                                     ValueExpression methodSignatureExpression
367                                             = (ValueExpression) attribute.getValue("method-signature");
368                                     String methodSignature = null;
369                                     if (methodSignatureExpression != null)
370                                     {
371                                         // Check if the value expression holds a method signature
372                                         // Note that it could be null, so in that case we don't have to 
373                                         // do anything
374                                         methodSignature = (String) methodSignatureExpression.getValue(
375                                                                     _component.getFacesContext().getELContext());
376                                     }
377 
378                                     // either the attributeName has to be a knownMethod
379                                     // or there has to be a method-signature
380                                     if (isKnownMethod || methodSignature != null)
381                                     {
382                                         //In this case it is expecting a ValueExpression
383                                         return attribute.getValue("default");
384                                     }
385                                     else
386                                     {
387                                         value = attribute.getValue("default");
388                                     }
389                                 }
390                             }
391                             else
392                             {
393                                 // Failsafe if another implementation for composite components is set
394                                 for (PropertyDescriptor attribute : ccBeanInfo.getPropertyDescriptors())
395                                 {
396                                     if (attribute.getName().equals(key))
397                                     {
398                                         String attributeName = attribute.getName();
399                                         boolean isKnownMethod = "action".equals(attributeName)
400                                                 || "actionListener".equals(attributeName)
401                                                 || "validator".equals(attributeName)
402                                                 || "valueChangeListener".equals(attributeName);
403 
404                                         // <composite:attribute> method-signature attribute is 
405                                         // ValueExpression that must evaluate to String
406                                         ValueExpression methodSignatureExpression
407                                                 = (ValueExpression) attribute.getValue("method-signature");
408                                         String methodSignature = null;
409                                         if (methodSignatureExpression != null)
410                                         {
411                                             // Check if the value expression holds a method signature
412                                             // Note that it could be null, so in that case we don't have to 
413                                             // do anything
414                                             methodSignature = (String) methodSignatureExpression.getValue(
415                                                                         _component.getFacesContext().getELContext());
416                                         }
417 
418                                         // either the attributeName has to be a knownMethod
419                                         // or there has to be a method-signature
420                                         if (isKnownMethod || methodSignature != null)
421                                         {
422                                             //In this case it is expecting a ValueExpression
423                                             return attribute.getValue("default");
424                                         }
425                                         else
426                                         {
427                                             value = attribute.getValue("default");
428                                             break;
429                                         }
430                                     }
431                                 }
432                             }
433                             // We have to check for a ValueExpression and also evaluate it
434                             // here, because in the PropertyDescriptor the default values are
435                             // always stored as (Tag-)ValueExpressions.
436                             if (value != null && value instanceof ValueExpression)
437                             {
438                                 return ((ValueExpression) value).getValue(_component.getFacesContext().getELContext());
439                             }
440                         }
441                     }
442                     // no value found
443                     //return null;
444                 }
445             }
446         }
447         
448         // Otherwise, return the actual value from the get() method. 
449         return value;
450     }
451 
452     /**
453      * Remove the attribute with the specified name. An attempt to
454      * remove an entry whose name is that of a <i>property</i> on
455      * the underlying UIComponent will cause an IllegalArgumentException.
456      * Value-bindings for the underlying component are ignored.
457      *
458      * @param key must be a String. Any other type will cause ClassCastException.
459      */
460     public Object remove(Object key)
461     {
462         checkKey(key);
463         int keyLength = ((String)key).length();
464         if (keyLength >= MIN_LENGHT_CHECK)
465         {
466             if (MARK_CREATED.length() == keyLength &&
467                 MARK_CREATED.equals(key))
468             {
469                 Object oldValue = _component.getOamVfMarkCreated();
470                 _component.setOamVfMarkCreated(null);
471                 return oldValue;
472             }
473             else if (FACET_NAME_KEY.length() == keyLength &&
474                 FACET_NAME_KEY.equals(key))
475             {
476                 Object oldValue = _component.getOamVfFacetName();
477                 _component.setOamVfFacetName(null);
478                 return oldValue;
479             }
480             else if (COMPONENT_ADDED_BY_HANDLER_MARKER.length() == keyLength &&
481                 COMPONENT_ADDED_BY_HANDLER_MARKER.equals(key))
482             {
483                 Object oldValue = _component.isOamVfAddedByHandler();
484                 _component.setOamVfAddedByHandler(false);
485                 return oldValue;
486             }
487             else if (FACET_CREATED_UIPANEL_MARKER.length() == keyLength &&
488                 FACET_CREATED_UIPANEL_MARKER.equals(key))
489             {
490                 Object oldValue = _component.isOamVfFacetCreatedUIPanel();
491                 _component.setOamVfFacetCreatedUIPanel(false);
492                 return oldValue;
493             }
494             else if (UIComponent.BEANINFO_KEY.length() == keyLength 
495                 && UIComponent.BEANINFO_KEY.equals(key))
496             {
497                 _ccBeanInfo = null;
498             }
499         }
500         _PropertyDescriptorHolder propertyDescriptor = getPropertyDescriptor((String) key);
501         if (propertyDescriptor != null)
502         {
503             throw new IllegalArgumentException("Cannot remove component property attribute");
504         }
505         return _component.getStateHelper().remove(
506                 UIComponentBase.PropertyKeys.attributesMap, key);
507     }
508 
509     /**
510      * Store the provided value as a <i>property</i> on the underlying
511      * UIComponent, or as an <i>attribute</i> in a Map if no such property
512      * exists. Value-bindings associated with the component are ignored; to
513      * write to a value-binding, the value-binding must be explicitly
514      * retrieved from the component and evaluated.
515      * <p/>
516      * Note that this method is different from the get method, which
517      * does read from a value-binding if one exists. When a value-binding
518      * exists for a non-property, putting a value here essentially "masks"
519      * the value-binding until that attribute is removed.
520      * <p/>
521      * The put method is expected to return the previous value of the
522      * property/attribute (if any). Because UIComponent property getter
523      * methods typically try to evaluate any value-binding expression of
524      * the same name this can cause an EL expression to be evaluated,
525      * thus invoking a getter method on the user's model. This is fine
526      * when the returned value will be used; Unfortunately this is quite
527      * pointless when initialising a freshly created component with whatever
528      * attributes were specified in the view definition (eg JSP tag
529      * attributes). Because the UIComponent.getAttributes method
530      * only returns a Map class and this class must be package-private,
531      * there is no way of exposing a "putNoReturn" type method.
532      *
533      * @param key   String, null is not allowed
534      * @param value null is allowed
535      */
536     public Object put(String key, Object value)
537     {
538         if (key == null)
539         {
540             throw new NullPointerException("key");
541         }
542         int keyLength = ((String)key).length();
543         if (keyLength >= MIN_LENGHT_CHECK)
544         {
545             if (MARK_CREATED.length() == keyLength &&
546                 MARK_CREATED.equals(key))
547             {
548                 String oldValue = _component.getOamVfMarkCreated();
549                 _component.setOamVfMarkCreated((String)value);
550                 return oldValue;
551             }
552             else if (FACET_NAME_KEY.length() == keyLength &&
553                 FACET_NAME_KEY.equals(key))
554             {
555                 Object oldValue = _component.getOamVfFacetName();
556                 _component.setOamVfFacetName((String)value);
557                 return oldValue;
558             }
559             else if (COMPONENT_ADDED_BY_HANDLER_MARKER.length() == keyLength &&
560                 COMPONENT_ADDED_BY_HANDLER_MARKER.equals(key))
561             {
562                 Object oldValue = _component.isOamVfAddedByHandler();
563                 _component.setOamVfAddedByHandler((Boolean)value);
564                 return oldValue;
565             }
566             else if (FACET_CREATED_UIPANEL_MARKER.length() == keyLength &&
567                 FACET_CREATED_UIPANEL_MARKER.equals(key))
568             {
569                 Object oldValue = _component.isOamVfFacetCreatedUIPanel();
570                 _component.setOamVfFacetCreatedUIPanel((Boolean)value);
571                 return oldValue;
572             }
573         }
574         _PropertyDescriptorHolder propertyDescriptor = getPropertyDescriptor(key);
575         if (propertyDescriptor == null)
576         {
577             if (value == null)
578             {
579                 throw new NullPointerException("value is null for a not available property: " + key);
580             }
581         }
582         else
583         {
584             if (propertyDescriptor.getReadMethod() != null)
585             {
586                 Object oldValue = getComponentProperty(propertyDescriptor);
587                 setComponentProperty(propertyDescriptor, value);
588                 return oldValue;
589             }
590             setComponentProperty(propertyDescriptor, value);
591             return null;
592         }
593         // To keep this code in good shape, The fastest way to compare is look if the length first here
594         // because we avoid an unnecessary cast later on equals().
595         if ( Resource.COMPONENT_RESOURCE_KEY.length() == keyLength 
596              && Resource.COMPONENT_RESOURCE_KEY.equals(key))
597         {
598             _isCompositeComponent = true;
599             _isCompositeComponentSet = true;
600         }
601         if (UIComponent.BEANINFO_KEY.length() == keyLength 
602             && UIComponent.BEANINFO_KEY.equals(key))
603         {
604             _ccBeanInfo = (BeanInfo) value;
605         }
606         return _component.getStateHelper().put(UIComponentBase.PropertyKeys.attributesMap, key, value);
607     }
608 
609     /**
610      * Retrieve info about getter/setter methods for the javabean property
611      * of the specified name on the underlying UIComponent object.
612      * <p/>
613      * This method optimises access to javabean properties of the underlying
614      * UIComponent by maintaining a cache of ProperyDescriptor objects for
615      * that class.
616      * <p/>
617      * TODO: Consider making the cache shared between component instances;
618      * currently 100 UIInputText components means performing introspection
619      * on the UIInputText component 100 times.
620      */
621     private _PropertyDescriptorHolder getPropertyDescriptor(String key)
622     {
623         if (_propertyDescriptorMap == null)
624         {
625             // Try to get descriptor map from cache
626             _propertyDescriptorMap = propertyDescriptorCache.get(_component.getClass());
627             // Cache miss: create descriptor map and put it in cache
628             if (_propertyDescriptorMap == null)
629             {
630                 // Create descriptor map...
631                 BeanInfo beanInfo;
632                 try
633                 {
634                     beanInfo = Introspector.getBeanInfo(_component.getClass());
635                 }
636                 catch (IntrospectionException e)
637                 {
638                     throw new FacesException(e);
639                 }
640                 PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
641                 _propertyDescriptorMap = new HashMap<String, _PropertyDescriptorHolder>();
642                 for (int i = 0; i < propertyDescriptors.length; i++)
643                 {
644                     PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
645                     Method readMethod = propertyDescriptor.getReadMethod();
646                     if (readMethod != null)
647                     {
648                         _propertyDescriptorMap.put(propertyDescriptor.getName(),
649                                 new _PropertyDescriptorHolder(propertyDescriptor, readMethod));
650                     }
651                 }
652                 // ... and put it in cache
653                 synchronized(propertyDescriptorCache)
654                 {
655                     // Use a synchronized block to ensure proper operation on concurrent use cases.
656                     // This is a racy single check, because initialization over the same class could happen
657                     // multiple times, but the same result is always calculated. The synchronized block 
658                     // just ensure thread-safety, because only one thread will modify the cache map
659                     // at the same time.
660                     propertyDescriptorCache.put(_component.getClass(), _propertyDescriptorMap);
661                 }
662             }
663         }
664         return _propertyDescriptorMap.get(key);
665     }
666 
667 
668     /**
669      * Execute the getter method of the specified property on the underlying
670      * component.
671      *
672      * @param propertyDescriptor specifies which property to read.
673      * @return the value returned by the getter method.
674      * @throws IllegalArgumentException if the property is not readable.
675      * @throws FacesException           if any other problem occurs while invoking
676      *                                  the getter method.
677      */
678     private Object getComponentProperty(_PropertyDescriptorHolder propertyDescriptor)
679     {
680         Method readMethod = propertyDescriptor.getReadMethod();
681         if (readMethod == null)
682         {
683             throw new IllegalArgumentException("Component property " + propertyDescriptor.getName()
684                                                + " is not readable");
685         }
686         try
687         {
688             return readMethod.invoke(_component, EMPTY_ARGS);
689         }
690         catch (Exception e)
691         {
692             FacesContext facesContext = _component.getFacesContext();
693             throw new FacesException("Could not get property " + propertyDescriptor.getName() + " of component "
694                                      + _component.getClientId(facesContext), e);
695         }
696     }
697 
698     /**
699      * Execute the setter method of the specified property on the underlying
700      * component.
701      *
702      * @param propertyDescriptor specifies which property to write.
703      * @throws IllegalArgumentException if the property is not writable.
704      * @throws FacesException           if any other problem occurs while invoking
705      *                                  the getter method.
706      */
707     private void setComponentProperty(_PropertyDescriptorHolder propertyDescriptor, Object value)
708     {
709         Method writeMethod = propertyDescriptor.getWriteMethod();
710         if (writeMethod == null)
711         {
712             throw new IllegalArgumentException("Component property " + propertyDescriptor.getName()
713                                                + " is not writable");
714         }
715         try
716         {
717             writeMethod.invoke(_component, new Object[]{value});
718         }
719         catch (Exception e)
720         {
721             FacesContext facesContext = _component.getFacesContext();
722             throw new FacesException("Could not set property " + propertyDescriptor.getName() +
723                     " of component " + _component.getClientId(facesContext) + " to value : " + value + " with type : " +
724                     (value == null ? "null" : value.getClass().getName()), e);
725         }
726     }
727 
728     private void checkKey(Object key)
729     {
730         if (key == null)
731         {
732             throw new NullPointerException("key");
733         }
734         if (!(key instanceof String))
735         {
736             throw new ClassCastException("key is not a String");
737         }
738     }
739 
740     /**
741      * Return the map containing the attributes.
742      * <p/>
743      * This method is package-scope so that the UIComponentBase class can access it
744      * directly when serializing the component.
745      */
746     Map<String, Object> getUnderlyingMap()
747     {
748         StateHelper stateHelper = _component.getStateHelper(false);
749         Map<String, Object> attributes = null;
750         if (stateHelper != null)
751         {
752             attributes = (Map<String, Object>) stateHelper.get(UIComponentBase.PropertyKeys.attributesMap);
753         }
754         return attributes == null ? Collections.EMPTY_MAP : attributes;
755     }
756     
757     /**
758      * TODO: Document why this method is necessary, and why it doesn't try to
759      * compare the _component field.
760      */
761     @Override
762     public boolean equals(Object obj)
763     {
764         return getUnderlyingMap().equals(obj);
765     }
766 
767     @Override
768     public int hashCode()
769     {
770         return getUnderlyingMap().hashCode();
771     }
772 }