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: 1402825 $ $Date: 2012-10-27 12:09:22 -0500 (Sat, 27 Oct 2012) $
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 }