Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
_ComponentAttributesMap |
|
| 0.0;0 |
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 javax.el.ValueExpression; | |
22 | import javax.faces.FacesException; | |
23 | import javax.faces.context.FacesContext; | |
24 | import java.beans.BeanInfo; | |
25 | import java.beans.IntrospectionException; | |
26 | import java.beans.Introspector; | |
27 | import java.beans.PropertyDescriptor; | |
28 | import java.io.Serializable; | |
29 | import java.lang.reflect.Method; | |
30 | import java.util.*; | |
31 | ||
32 | /** | |
33 | * A custom implementation of the Map interface, where get and put calls | |
34 | * try to access getter/setter methods of an associated UIComponent before | |
35 | * falling back to accessing a real Map object. | |
36 | * <p/> | |
37 | * Some of the behaviours of this class don't really comply with the | |
38 | * definitions of the Map class; for example the key parameter to all | |
39 | * methods is required to be of type String only, and after clear(), | |
40 | * calls to get can return non-null values. However the JSF spec | |
41 | * requires that this class behave in the way implemented below. See | |
42 | * UIComponent.getAttributes for more details. | |
43 | * <p/> | |
44 | * The term "property" is used here to refer to real javabean properties | |
45 | * on the underlying UIComponent, while "attribute" refers to an entry | |
46 | * in the associated Map. | |
47 | * | |
48 | * @author Manfred Geiler (latest modification by $Author: lu4242 $) | |
49 | * @version $Revision: 949071 $ $Date: 2010-05-27 21:22:17 -0500 (Thu, 27 May 2010) $ | |
50 | */ | |
51 | class _ComponentAttributesMap | |
52 | implements Map, Serializable | |
53 | { | |
54 | private static final long serialVersionUID = -9106832179394257866L; | |
55 | ||
56 | 0 | private static final Object[] EMPTY_ARGS = new Object[0]; |
57 | ||
58 | // The component that is read/written via this map. | |
59 | private UIComponent _component; | |
60 | ||
61 | // We delegate instead of derive from HashMap, so that we can later | |
62 | // optimize Serialization | |
63 | 0 | private Map<Object, Object> _attributes = null; |
64 | ||
65 | // A cached hashmap of propertyName => PropertyDescriptor object for all | |
66 | // the javabean properties of the associated component. This is built by | |
67 | // introspection on the associated UIComponent. Don't serialize this as | |
68 | // it can always be recreated when needed. | |
69 | 0 | private transient Map<String, PropertyDescriptor> _propertyDescriptorMap = null; |
70 | ||
71 | // Cache for component property descriptors | |
72 | 0 | private static Map<Class, Map<String, PropertyDescriptor>> _propertyDescriptorCache = new WeakHashMap<Class, Map<String, PropertyDescriptor>>(); |
73 | ||
74 | /** | |
75 | * Create a map backed by the specified component. | |
76 | * <p/> | |
77 | * This method is expected to be called when a component is first created. | |
78 | */ | |
79 | _ComponentAttributesMap(UIComponent component) | |
80 | 0 | { |
81 | 0 | _component = component; |
82 | 0 | _attributes = new HashMap<Object, Object>(); |
83 | 0 | } |
84 | ||
85 | /** | |
86 | * Create a map backed by the specified component. Attributes already | |
87 | * associated with the component are provided in the specified Map | |
88 | * class. A reference to the provided map is kept; this object's contents | |
89 | * are updated during put calls on this instance. | |
90 | * <p/> | |
91 | * This method is expected to be called during the "restore view" phase. | |
92 | */ | |
93 | _ComponentAttributesMap(UIComponent component, Map<Object, Object> attributes) | |
94 | 0 | { |
95 | 0 | _component = component; |
96 | 0 | _attributes = new HashMap(attributes); |
97 | 0 | } |
98 | ||
99 | /** | |
100 | * Return the number of <i>attributes</i> in this map. Properties of the | |
101 | * underlying UIComponent are not counted. | |
102 | * <p/> | |
103 | * Note that because the get method can read properties of the | |
104 | * UIComponent and evaluate value-bindings, it is possible to have | |
105 | * size return zero while calls to the get method return non-null | |
106 | * values. | |
107 | */ | |
108 | public int size() | |
109 | { | |
110 | 0 | return _attributes.size(); |
111 | } | |
112 | ||
113 | /** | |
114 | * Clear all the <i>attributes</i> in this map. Properties of the | |
115 | * underlying UIComponent are not modified. | |
116 | * <p/> | |
117 | * Note that because the get method can read properties of the | |
118 | * UIComponent and evaluate value-bindings, it is possible to have | |
119 | * calls to the get method return non-null values immediately after | |
120 | * a call to clear. | |
121 | */ | |
122 | public void clear() | |
123 | { | |
124 | 0 | _attributes.clear(); |
125 | 0 | } |
126 | ||
127 | /** | |
128 | * Return true if there are no <i>attributes</i> in this map. Properties | |
129 | * of the underlying UIComponent are not counted. | |
130 | * <p/> | |
131 | * Note that because the get method can read properties of the | |
132 | * UIComponent and evaluate value-bindings, it is possible to have | |
133 | * isEmpty return true, while calls to the get method return non-null | |
134 | * values. | |
135 | */ | |
136 | public boolean isEmpty() | |
137 | { | |
138 | 0 | return _attributes.isEmpty(); |
139 | } | |
140 | ||
141 | /** | |
142 | * Return true if there is an <i>attribute</i> with the specified name, | |
143 | * but false if there is a javabean <i>property</i> of that name on the | |
144 | * associated UIComponent. | |
145 | * <p/> | |
146 | * Note that it should be impossible for the attributes map to contain | |
147 | * an entry with the same name as a javabean property on the associated | |
148 | * UIComponent. | |
149 | * | |
150 | * @param key <i>must</i> be a String. Anything else will cause a | |
151 | * ClassCastException to be thrown. | |
152 | */ | |
153 | public boolean containsKey(Object key) | |
154 | { | |
155 | 0 | checkKey(key); |
156 | ||
157 | 0 | return getPropertyDescriptor((String) key) == null ? _attributes.containsKey(key) : false; |
158 | } | |
159 | ||
160 | /** | |
161 | * Returns true if there is an <i>attribute</i> with the specified | |
162 | * value. Properties of the underlying UIComponent aren't examined, | |
163 | * nor value-bindings. | |
164 | * | |
165 | * @param value null is allowed | |
166 | */ | |
167 | public boolean containsValue(Object value) | |
168 | { | |
169 | 0 | return _attributes.containsValue(value); |
170 | } | |
171 | ||
172 | /** | |
173 | * Return a collection of the values of all <i>attributes</i>. Property | |
174 | * values are not included, nor value-bindings. | |
175 | */ | |
176 | public Collection<Object> values() | |
177 | { | |
178 | 0 | return _attributes.values(); |
179 | } | |
180 | ||
181 | /** | |
182 | * Call put(key, value) for each entry in the provided map. | |
183 | */ | |
184 | public void putAll(Map t) | |
185 | { | |
186 | 0 | for (Iterator it = t.entrySet().iterator(); it.hasNext();) |
187 | { | |
188 | 0 | Map.Entry entry = (Entry) it.next(); |
189 | 0 | put(entry.getKey(), entry.getValue()); |
190 | 0 | } |
191 | 0 | } |
192 | ||
193 | /** | |
194 | * Return a set of all <i>attributes</i>. Properties of the underlying | |
195 | * UIComponent are not included, nor value-bindings. | |
196 | */ | |
197 | public Set entrySet() | |
198 | { | |
199 | 0 | return _attributes.entrySet(); |
200 | } | |
201 | ||
202 | /** | |
203 | * Return a set of the keys for all <i>attributes</i>. Properties of the | |
204 | * underlying UIComponent are not included, nor value-bindings. | |
205 | */ | |
206 | public Set<Object> keySet() | |
207 | { | |
208 | 0 | return _attributes.keySet(); |
209 | } | |
210 | ||
211 | /** | |
212 | * In order: get the value of a <i>property</i> of the underlying | |
213 | * UIComponent, read an <i>attribute</i> from this map, or evaluate | |
214 | * the component's value-binding of the specified name. | |
215 | * | |
216 | * @param key must be a String. Any other type will cause ClassCastException. | |
217 | */ | |
218 | public Object get(Object key) | |
219 | { | |
220 | 0 | checkKey(key); |
221 | ||
222 | // is there a javabean property to read? | |
223 | 0 | PropertyDescriptor propertyDescriptor |
224 | = getPropertyDescriptor((String) key); | |
225 | 0 | if (propertyDescriptor != null) |
226 | { | |
227 | 0 | return getComponentProperty(propertyDescriptor); |
228 | } | |
229 | ||
230 | // is there a literal value to read? | |
231 | 0 | Object mapValue = _attributes.get(key); |
232 | 0 | if (mapValue != null) |
233 | { | |
234 | 0 | return mapValue; |
235 | } | |
236 | ||
237 | // is there a value-binding to read? | |
238 | 0 | ValueExpression ve = _component.getValueExpression((String) key); |
239 | 0 | if (ve != null) |
240 | { | |
241 | 0 | return ve.getValue(_component.getFacesContext().getELContext()); |
242 | } | |
243 | ||
244 | // no value found | |
245 | 0 | return null; |
246 | } | |
247 | ||
248 | /** | |
249 | * Remove the attribute with the specified name. An attempt to | |
250 | * remove an entry whose name is that of a <i>property</i> on | |
251 | * the underlying UIComponent will cause an IllegalArgumentException. | |
252 | * Value-bindings for the underlying component are ignored. | |
253 | * | |
254 | * @param key must be a String. Any other type will cause ClassCastException. | |
255 | */ | |
256 | public Object remove(Object key) | |
257 | { | |
258 | 0 | checkKey(key); |
259 | 0 | PropertyDescriptor propertyDescriptor = getPropertyDescriptor((String) key); |
260 | 0 | if (propertyDescriptor != null) |
261 | { | |
262 | 0 | throw new IllegalArgumentException("Cannot remove component property attribute"); |
263 | } | |
264 | 0 | return _attributes.remove(key); |
265 | } | |
266 | ||
267 | /** | |
268 | * Store the provided value as a <i>property</i> on the underlying | |
269 | * UIComponent, or as an <i>attribute</i> in a Map if no such property | |
270 | * exists. Value-bindings associated with the component are ignored; to | |
271 | * write to a value-binding, the value-binding must be explicitly | |
272 | * retrieved from the component and evaluated. | |
273 | * <p/> | |
274 | * Note that this method is different from the get method, which | |
275 | * does read from a value-binding if one exists. When a value-binding | |
276 | * exists for a non-property, putting a value here essentially "masks" | |
277 | * the value-binding until that attribute is removed. | |
278 | * <p/> | |
279 | * The put method is expected to return the previous value of the | |
280 | * property/attribute (if any). Because UIComponent property getter | |
281 | * methods typically try to evaluate any value-binding expression of | |
282 | * the same name this can cause an EL expression to be evaluated, | |
283 | * thus invoking a getter method on the user's model. This is fine | |
284 | * when the returned value will be used; Unfortunately this is quite | |
285 | * pointless when initialising a freshly created component with whatever | |
286 | * attributes were specified in the view definition (eg JSP tag | |
287 | * attributes). Because the UIComponent.getAttributes method | |
288 | * only returns a Map class and this class must be package-private, | |
289 | * there is no way of exposing a "putNoReturn" type method. | |
290 | * | |
291 | * @param key String, null is not allowed | |
292 | * @param value null is allowed | |
293 | */ | |
294 | public Object put(Object key, Object value) | |
295 | { | |
296 | 0 | checkKey(key); |
297 | ||
298 | 0 | PropertyDescriptor propertyDescriptor = getPropertyDescriptor((String) key); |
299 | 0 | if (propertyDescriptor == null) |
300 | { | |
301 | 0 | if (value == null) |
302 | { | |
303 | 0 | throw new NullPointerException("value is null for a not available property: " + key); |
304 | } | |
305 | } | |
306 | else | |
307 | { | |
308 | 0 | if (propertyDescriptor.getReadMethod() != null) |
309 | { | |
310 | 0 | Object oldValue = getComponentProperty(propertyDescriptor); |
311 | 0 | setComponentProperty(propertyDescriptor, value); |
312 | 0 | return oldValue; |
313 | } | |
314 | 0 | setComponentProperty(propertyDescriptor, value); |
315 | 0 | return null; |
316 | } | |
317 | 0 | return _attributes.put(key, value); |
318 | } | |
319 | ||
320 | /** | |
321 | * Retrieve info about getter/setter methods for the javabean property | |
322 | * of the specified name on the underlying UIComponent object. | |
323 | * <p/> | |
324 | * This method optimises access to javabean properties of the underlying | |
325 | * UIComponent by maintaining a cache of ProperyDescriptor objects for | |
326 | * that class. | |
327 | * <p/> | |
328 | * TODO: Consider making the cache shared between component instances; | |
329 | * currently 100 UIInputText components means performing introspection | |
330 | * on the UIInputText component 100 times. | |
331 | */ | |
332 | private PropertyDescriptor getPropertyDescriptor(String key) | |
333 | { | |
334 | 0 | if (_propertyDescriptorMap == null) |
335 | { | |
336 | // Try to get descriptor map from cache | |
337 | 0 | _propertyDescriptorMap = _propertyDescriptorCache.get(_component.getClass()); |
338 | // Cache miss: create descriptor map and put it in cache | |
339 | 0 | if (_propertyDescriptorMap == null) |
340 | { | |
341 | // Create descriptor map... | |
342 | BeanInfo beanInfo; | |
343 | try | |
344 | { | |
345 | 0 | beanInfo = Introspector.getBeanInfo(_component.getClass()); |
346 | } | |
347 | 0 | catch (IntrospectionException e) |
348 | { | |
349 | 0 | throw new FacesException(e); |
350 | 0 | } |
351 | 0 | PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); |
352 | 0 | _propertyDescriptorMap = new HashMap<String, PropertyDescriptor>(); |
353 | 0 | for (int i = 0; i < propertyDescriptors.length; i++) |
354 | { | |
355 | 0 | PropertyDescriptor propertyDescriptor = propertyDescriptors[i]; |
356 | 0 | if (propertyDescriptor.getReadMethod() != null) |
357 | { | |
358 | 0 | _propertyDescriptorMap.put(propertyDescriptor.getName(), |
359 | propertyDescriptor); | |
360 | } | |
361 | } | |
362 | // ... and put it in cache | |
363 | 0 | _propertyDescriptorCache.put(_component.getClass(), _propertyDescriptorMap); |
364 | } | |
365 | } | |
366 | 0 | return _propertyDescriptorMap.get(key); |
367 | } | |
368 | ||
369 | ||
370 | /** | |
371 | * Execute the getter method of the specified property on the underlying | |
372 | * component. | |
373 | * | |
374 | * @param propertyDescriptor specifies which property to read. | |
375 | * @return the value returned by the getter method. | |
376 | * @throws IllegalArgumentException if the property is not readable. | |
377 | * @throws FacesException if any other problem occurs while invoking | |
378 | * the getter method. | |
379 | */ | |
380 | private Object getComponentProperty(PropertyDescriptor propertyDescriptor) | |
381 | { | |
382 | 0 | Method readMethod = propertyDescriptor.getReadMethod(); |
383 | 0 | if (readMethod == null) |
384 | { | |
385 | 0 | throw new IllegalArgumentException("Component property " + propertyDescriptor.getName() + " is not readable"); |
386 | } | |
387 | try | |
388 | { | |
389 | 0 | return readMethod.invoke(_component, EMPTY_ARGS); |
390 | } | |
391 | 0 | catch (Exception e) |
392 | { | |
393 | 0 | FacesContext facesContext = _component.getFacesContext(); |
394 | 0 | throw new FacesException("Could not get property " + propertyDescriptor.getName() + " of component " + _component.getClientId(facesContext), e); |
395 | } | |
396 | } | |
397 | ||
398 | /** | |
399 | * Execute the setter method of the specified property on the underlying | |
400 | * component. | |
401 | * | |
402 | * @param propertyDescriptor specifies which property to write. | |
403 | * @throws IllegalArgumentException if the property is not writable. | |
404 | * @throws FacesException if any other problem occurs while invoking | |
405 | * the getter method. | |
406 | */ | |
407 | private void setComponentProperty(PropertyDescriptor propertyDescriptor, Object value) | |
408 | { | |
409 | 0 | Method writeMethod = propertyDescriptor.getWriteMethod(); |
410 | 0 | if (writeMethod == null) |
411 | { | |
412 | 0 | throw new IllegalArgumentException("Component property " + propertyDescriptor.getName() + " is not writable"); |
413 | } | |
414 | try | |
415 | { | |
416 | 0 | writeMethod.invoke(_component, new Object[]{value}); |
417 | } | |
418 | 0 | catch (Exception e) |
419 | { | |
420 | 0 | FacesContext facesContext = _component.getFacesContext(); |
421 | 0 | throw new FacesException("Could not set property " + propertyDescriptor.getName() + |
422 | " of component " + _component.getClientId(facesContext) + " to value : " + value + " with type : " + | |
423 | (value == null ? "null" : value.getClass().getName()), e); | |
424 | 0 | } |
425 | 0 | } |
426 | ||
427 | private void checkKey(Object key) | |
428 | { | |
429 | 0 | if (key == null) |
430 | { | |
431 | 0 | throw new NullPointerException("key"); |
432 | } | |
433 | 0 | if (!(key instanceof String)) |
434 | { | |
435 | 0 | throw new ClassCastException("key is not a String"); |
436 | } | |
437 | 0 | } |
438 | ||
439 | /** | |
440 | * Return the map containing the attributes. | |
441 | * <p/> | |
442 | * This method is package-scope so that the UIComponentBase class can access it | |
443 | * directly when serializing the component. | |
444 | */ | |
445 | Map<Object, Object> getUnderlyingMap() | |
446 | { | |
447 | 0 | return _attributes; |
448 | } | |
449 | ||
450 | /** | |
451 | * TODO: Document why this method is necessary, and why it doesn't try to | |
452 | * compare the _component field. | |
453 | */ | |
454 | public boolean equals(Object obj) | |
455 | { | |
456 | 0 | return _attributes.equals(obj); |
457 | } | |
458 | ||
459 | public int hashCode() | |
460 | { | |
461 | 0 | return _attributes.hashCode(); |
462 | } | |
463 | } |