Skip to content

Package: BeanELResolver

BeanELResolver

nameinstructionbranchcomplexitylinemethod
BeanELResolver()
M: 11 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
BeanELResolver(boolean)
M: 11 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
getBeanProperty(ELContext, Object, Object)
M: 50 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 10 C: 0
0%
M: 1 C: 0
0%
getCommonPropertyType(ELContext, Object)
M: 6 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 3 C: 0
0%
M: 1 C: 0
0%
getFeatureDescriptors(ELContext, Object)
M: 56 C: 0
0%
M: 6 C: 0
0%
M: 4 C: 0
0%
M: 14 C: 0
0%
M: 1 C: 0
0%
getType(ELContext, Object, Object)
M: 32 C: 0
0%
M: 10 C: 0
0%
M: 6 C: 0
0%
M: 9 C: 0
0%
M: 1 C: 0
0%
getValue(ELContext, Object, Object)
M: 70 C: 0
0%
M: 8 C: 0
0%
M: 5 C: 0
0%
M: 18 C: 0
0%
M: 1 C: 0
0%
invoke(ELContext, Object, Object, Class[], Object[])
M: 50 C: 0
0%
M: 8 C: 0
0%
M: 5 C: 0
0%
M: 9 C: 0
0%
M: 1 C: 0
0%
isReadOnly(ELContext, Object, Object)
M: 27 C: 0
0%
M: 8 C: 0
0%
M: 5 C: 0
0%
M: 8 C: 0
0%
M: 1 C: 0
0%
setValue(ELContext, Object, Object, Object)
M: 117 C: 0
0%
M: 12 C: 0
0%
M: 7 C: 0
0%
M: 23 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 1997, 2021 Oracle and/or its affiliates and others.
3: * All rights reserved.
4: * Copyright 2004 The Apache Software Foundation
5: *
6: * Licensed under the Apache License, Version 2.0 (the "License");
7: * you may not use this file except in compliance with the License.
8: * 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, software
13: * distributed under the License is distributed on an "AS IS" BASIS,
14: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15: * See the License for the specific language governing permissions and
16: * limitations under the License.
17: */
18:
19: package jakarta.el;
20:
21: import static java.lang.Boolean.TRUE;
22: import static jakarta.el.ELUtil.getExceptionMessageString;
23:
24: import java.beans.BeanInfo;
25: import java.beans.FeatureDescriptor;
26: import java.beans.IntrospectionException;
27: import java.beans.Introspector;
28: import java.beans.PropertyDescriptor;
29: import java.lang.ref.ReferenceQueue;
30: import java.lang.ref.SoftReference;
31: import java.lang.reflect.InvocationTargetException;
32: import java.lang.reflect.Method;
33: import java.util.ArrayList;
34: import java.util.HashMap;
35: import java.util.Iterator;
36: import java.util.Map;
37: import java.util.concurrent.ConcurrentHashMap;
38:
39: /**
40: * Defines property resolution behavior on objects using the JavaBeans component architecture.
41: *
42: * <p>
43: * This resolver handles base objects of any type, as long as the base is not <code>null</code>. It accepts any object
44: * as a property or method, and coerces it to a string.
45: *
46: * <p>
47: * For property resolution, the property string is used to find a JavaBeans compliant property on the base object. The
48: * value is accessed using JavaBeans getters and setters.
49: * </p>
50: *
51: * <p>
52: * For method resolution, the method string is the name of the method in the bean. The parameter types can be optionally
53: * specified to identify the method. If the parameter types are not specified, the parameter objects are used in the
54: * method resolution.
55: * </p>
56: *
57: * <p>
58: * The JavaBeans specification predates the introduction of default method implementations defined on an interface. In
59: * addition to the JavaBeans specification requirements for looking up property getters, property setters and methods,
60: * this resolver also considers default methods and includes them in the results.
61: * </p>
62: *
63: * <p>
64: * This resolver can be constructed in read-only mode, which means that {@link #isReadOnly} will always return
65: * <code>true</code> and {@link #setValue} will always throw <code>PropertyNotWritableException</code>.
66: * </p>
67: *
68: * <p>
69: * <code>ELResolver</code>s are combined together using {@link CompositeELResolver}s, to define rich semantics for
70: * evaluating an expression. See the javadocs for {@link ELResolver} for details.
71: * </p>
72: *
73: * <p>
74: * Because this resolver handles base objects of any type, it should be placed near the end of a composite resolver.
75: * Otherwise, it will claim to have resolved a property before any resolvers that come after it get a chance to test if
76: * they can do so as well.
77: * </p>
78: *
79: * @see CompositeELResolver
80: * @see ELResolver
81: *
82: * @since Jakarta Server Pages 2.1
83: */
84: public class BeanELResolver extends ELResolver {
85:
86: static private class BPSoftReference extends SoftReference<BeanProperties> {
87: final Class<?> key;
88:
89: BPSoftReference(Class<?> key, BeanProperties beanProperties, ReferenceQueue<BeanProperties> refQ) {
90: super(beanProperties, refQ);
91: this.key = key;
92: }
93: }
94:
95: static private class SoftConcurrentHashMap extends ConcurrentHashMap<Class<?>, BeanProperties> {
96:
97: private static final long serialVersionUID = -178867497897782229L;
98: private static final int CACHE_INIT_SIZE = 1024;
99: private ConcurrentHashMap<Class<?>, BPSoftReference> map = new ConcurrentHashMap<>(CACHE_INIT_SIZE);
100: private ReferenceQueue<BeanProperties> refQ = new ReferenceQueue<>();
101:
102: // Remove map entries that have been placed on the queue by GC.
103: private void cleanup() {
104: BPSoftReference BPRef = null;
105: while ((BPRef = (BPSoftReference) refQ.poll()) != null) {
106: map.remove(BPRef.key);
107: }
108: }
109:
110: @Override
111: public BeanProperties put(Class<?> key, BeanProperties value) {
112: cleanup();
113: BPSoftReference prev = map.put(key, new BPSoftReference(key, value, refQ));
114: return prev == null ? null : prev.get();
115: }
116:
117: @Override
118: public BeanProperties putIfAbsent(Class<?> key, BeanProperties value) {
119: cleanup();
120: BPSoftReference prev = map.putIfAbsent(key, new BPSoftReference(key, value, refQ));
121: return prev == null ? null : prev.get();
122: }
123:
124: @Override
125: public BeanProperties get(Object key) {
126: cleanup();
127: BPSoftReference BPRef = map.get(key);
128: if (BPRef == null) {
129: return null;
130: }
131: if (BPRef.get() == null) {
132: // value has been garbage collected, remove entry in map
133: map.remove(key);
134: return null;
135: }
136: return BPRef.get();
137: }
138: }
139:
140: private boolean isReadOnly;
141:
142: private final SoftConcurrentHashMap properties = new SoftConcurrentHashMap();
143:
144: /*
145: * Defines a property for a bean.
146: */
147: final static class BeanProperty {
148:
149: private Method readMethod;
150: private Method writeMethod;
151: private PropertyDescriptor descriptor;
152:
153: public BeanProperty(Class<?> baseClass, PropertyDescriptor descriptor) {
154: this.descriptor = descriptor;
155: readMethod = ELUtil.getMethod(baseClass, descriptor.getReadMethod());
156: writeMethod = ELUtil.getMethod(baseClass, descriptor.getWriteMethod());
157: }
158:
159: public Class<?> getPropertyType() {
160: return descriptor.getPropertyType();
161: }
162:
163: public boolean isReadOnly() {
164: return getWriteMethod() == null;
165: }
166:
167: public Method getReadMethod() {
168: return readMethod;
169: }
170:
171: public Method getWriteMethod() {
172: return writeMethod;
173: }
174: }
175:
176: /*
177: * Defines the properties for a bean.
178: */
179: final static class BeanProperties {
180:
181: private final Map<String, BeanProperty> propertyMap = new HashMap<>();
182:
183: public BeanProperties(Class<?> baseClass) {
184: PropertyDescriptor[] descriptors;
185: try {
186: BeanInfo info = Introspector.getBeanInfo(baseClass);
187: descriptors = info.getPropertyDescriptors();
188: for (PropertyDescriptor descriptor : descriptors) {
189: propertyMap.put(descriptor.getName(), new BeanProperty(baseClass, descriptor));
190: }
191: /**
192: * Populating from any interfaces solves two distinct problems:
193: * 1. When running under a security manager, classes may be
194: * unaccessible but have accessible interfaces.
195: * 2. It enables default methods to be included.
196: */
197: populateFromInterfaces(baseClass, baseClass);
198: } catch (IntrospectionException ie) {
199: throw new ELException(ie);
200: }
201:
202: }
203:
204: private void populateFromInterfaces(Class<?> baseClass, Class<?> aClass) throws IntrospectionException {
205: Class<?> interfaces[] = aClass.getInterfaces();
206: if (interfaces.length > 0) {
207: for (Class<?> ifs : interfaces) {
208: BeanInfo info = Introspector.getBeanInfo(ifs);
209: PropertyDescriptor[] pds = info.getPropertyDescriptors();
210: for (PropertyDescriptor pd : pds) {
211: if (!this.propertyMap.containsKey(pd.getName())) {
212: this.propertyMap.put(pd.getName(), new BeanProperty(
213: baseClass, pd));
214: }
215: }
216: }
217: }
218: Class<?> superclass = aClass.getSuperclass();
219: if (superclass != null) {
220: populateFromInterfaces(baseClass, superclass);
221: }
222: }
223:
224: public BeanProperty getBeanProperty(String property) {
225: return propertyMap.get(property);
226: }
227: }
228:
229: /**
230: * Creates a new read/write <code>BeanELResolver</code>.
231: */
232: public BeanELResolver() {
233: this.isReadOnly = false;
234: }
235:
236: /**
237: * Creates a new <code>BeanELResolver</code> whose read-only status is determined by the given parameter.
238: *
239: * @param isReadOnly <code>true</code> if this resolver cannot modify beans; <code>false</code> otherwise.
240: */
241: public BeanELResolver(boolean isReadOnly) {
242: this.isReadOnly = isReadOnly;
243: }
244:
245: /**
246: * If the base object is not <code>null</code>, returns the most general acceptable type that can be set on this bean
247: * property.
248: *
249: * <p>
250: * If the base is not <code>null</code>, the <code>propertyResolved</code> property of the <code>ELContext</code> object
251: * must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
252: * this method is called, the caller should ignore the return value.
253: * </p>
254: *
255: * <p>
256: * The provided property will first be coerced to a <code>String</code>. If there is a <code>BeanInfoProperty</code>
257: * for this property, there were no errors retrieving it and neither the property nor the resolver are read-only,
258: * the <code>propertyType</code> of the <code>propertyDescriptor</code> is returned. If the property is resolved but
259: * either the property or the resolver is read-only then {@code null} will be returned. Otherwise, a
260: * <code>PropertyNotFoundException</code> is thrown.
261: * </p>
262: *
263: * @param context The context of this evaluation.
264: * @param base The bean to analyze.
265: * @param property The name of the property to analyze. Will be coerced to a <code>String</code>.
266: * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then
267: * the most general acceptable type which must be {@code null} if the either the property or the resolver is
268: * read-only; otherwise undefined
269: * @throws NullPointerException if context is <code>null</code>
270: * @throws PropertyNotFoundException if <code>base</code> is not <code>null</code> and the specified property does not
271: * exist or is not readable.
272: * @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown
273: * exception must be included as the cause property of this exception, if available.
274: */
275: @Override
276: public Class<?> getType(ELContext context, Object base, Object property) {
277:• if (context == null) {
278: throw new NullPointerException();
279: }
280:
281:• if (base == null || property == null) {
282: return null;
283: }
284:
285: BeanProperty beanProperty = getBeanProperty(context, base, property);
286: context.setPropertyResolved(true);
287:
288:• if (isReadOnly || beanProperty.isReadOnly()) {
289: return null;
290: }
291:
292: return beanProperty.getPropertyType();
293: }
294:
295: /**
296: * If the base object is not <code>null</code>, returns the current value of the given property on this bean.
297: *
298: * <p>
299: * If the base is not <code>null</code>, the <code>propertyResolved</code> property of the <code>ELContext</code> object
300: * must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
301: * this method is called, the caller should ignore the return value.
302: * </p>
303: *
304: * <p>
305: * The provided property name will first be coerced to a <code>String</code>. If the property is a readable property of
306: * the base object, as per the JavaBeans specification, then return the result of the getter call. If the getter throws
307: * an exception, it is propagated to the caller. If the property is not found or is not readable, a
308: * <code>PropertyNotFoundException</code> is thrown.
309: * </p>
310: *
311: * @param context The context of this evaluation.
312: * @param base The bean on which to get the property.
313: * @param property The name of the property to get. Will be coerced to a <code>String</code>.
314: * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then
315: * the value of the given property. Otherwise, undefined.
316: * @throws NullPointerException if context is <code>null</code>.
317: * @throws PropertyNotFoundException if <code>base</code> is not <code>null</code> and the specified property does not
318: * exist or is not readable.
319: * @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown
320: * exception must be included as the cause property of this exception, if available.
321: */
322: @Override
323: public Object getValue(ELContext context, Object base, Object property) {
324:• if (context == null) {
325: throw new NullPointerException();
326: }
327:
328:• if (base == null || property == null) {
329: return null;
330: }
331:
332: Method method = getBeanProperty(context, base, property).getReadMethod();
333:• if (method == null) {
334: throw new PropertyNotFoundException(
335: getExceptionMessageString(context, "propertyNotReadable", new Object[] { base.getClass().getName(), property.toString() }));
336: }
337:
338: Object value;
339: try {
340: value = method.invoke(base, new Object[0]);
341: context.setPropertyResolved(base, property);
342: } catch (ELException ex) {
343: throw ex;
344: } catch (InvocationTargetException ite) {
345: throw new ELException(ite.getCause());
346: } catch (Exception ex) {
347: throw new ELException(ex);
348: }
349:
350: return value;
351: }
352:
353: /**
354: * If the base object is not <code>null</code>, attempts to set the value of the given property on this bean.
355: *
356: * <p>
357: * If the base is not <code>null</code>, the <code>propertyResolved</code> property of the <code>ELContext</code> object
358: * must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
359: * this method is called, the caller can safely assume no value was set.
360: * </p>
361: *
362: * <p>
363: * If this resolver was constructed in read-only mode, this method will always throw
364: * <code>PropertyNotWritableException</code>.
365: * </p>
366: *
367: * <p>
368: * The provided property name will first be coerced to a <code>String</code>. If property is a writable property of
369: * <code>base</code> (as per the JavaBeans Specification), the setter method is called (passing <code>value</code>). If
370: * the property exists but does not have a setter, then a <code>PropertyNotFoundException</code> is thrown. If the
371: * property does not exist, a <code>PropertyNotFoundException</code> is thrown.
372: * </p>
373: *
374: * @param context The context of this evaluation.
375: * @param base The bean on which to set the property.
376: * @param property The name of the property to set. Will be coerced to a <code>String</code>.
377: * @param val The value to be associated with the specified key.
378: * @throws NullPointerException if context is <code>null</code>.
379: * @throws PropertyNotFoundException if <code>base</code> is not <code>null</code> and the specified property does not
380: * exist.
381: * @throws PropertyNotWritableException if this resolver was constructed in read-only mode, or if there is no setter for
382: * the property.
383: * @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown
384: * exception must be included as the cause property of this exception, if available.
385: */
386: @Override
387: public void setValue(ELContext context, Object base, Object property, Object val) {
388:• if (context == null) {
389: throw new NullPointerException();
390: }
391:
392:• if (base == null || property == null) {
393: return;
394: }
395:
396:• if (isReadOnly) {
397: throw new PropertyNotWritableException(getExceptionMessageString(context, "resolverNotwritable", new Object[] { base.getClass().getName() }));
398: }
399:
400: Method method = getBeanProperty(context, base, property).getWriteMethod();
401:• if (method == null) {
402: throw new PropertyNotWritableException(
403: getExceptionMessageString(context, "propertyNotWritable", new Object[] { base.getClass().getName(), property.toString() }));
404: }
405:
406: try {
407: method.invoke(base, new Object[] { val });
408: context.setPropertyResolved(base, property);
409: } catch (ELException ex) {
410: throw ex;
411: } catch (InvocationTargetException ite) {
412: throw new ELException(ite.getCause());
413: } catch (Exception ex) {
414:• if (null == val) {
415: val = "null";
416: }
417: String message = getExceptionMessageString(context, "setPropertyFailed", new Object[] { property.toString(), base.getClass().getName(), val });
418: throw new ELException(message, ex);
419: }
420: }
421:
422: /**
423: * If the base object is not <code>null</code>, invoke the method, with the given parameters on this bean. The return
424: * value from the method is returned.
425: *
426: * <p>
427: * If the base is not <code>null</code>, the <code>propertyResolved</code> property of the <code>ELContext</code> object
428: * must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
429: * this method is called, the caller should ignore the return value.
430: * </p>
431: *
432: * <p>
433: * The provided method object will first be coerced to a <code>String</code>. The methods in the bean is then examined
434: * and an attempt will be made to select one for invocation. If no suitable can be found, a
435: * <code>MethodNotFoundException</code> is thrown.
436: *
437: * If the given paramTypes is not <code>null</code>, select the method with the given name and parameter types.
438: *
439: * Else select the method with the given name that has the same number of parameters. If there are more than one such
440: * method, the method selection process is undefined.
441: *
442: * Else select the method with the given name that takes a variable number of arguments.
443: *
444: * Note the resolution for overloaded methods will likely be clarified in a future version of the spec.
445: *
446: * The provide parameters are coerced to the corresponding parameter types of the method, and the method is then
447: * invoked.
448: *
449: * @param context The context of this evaluation.
450: * @param base The bean on which to invoke the method
451: * @param methodName The simple name of the method to invoke. Will be coerced to a <code>String</code>. If method is
452: * "<init>"or "<clinit>" a MethodNotFoundException is thrown.
453: * @param paramTypes An array of Class objects identifying the method's formal parameter types, in declared order. Use
454: * an empty array if the method has no parameters. Can be <code>null</code>, in which case the method's formal parameter
455: * types are assumed to be unknown.
456: * @param params The parameters to pass to the method, or <code>null</code> if no parameters.
457: * @return The result of the method invocation (<code>null</code> if the method has a <code>void</code> return type).
458: * @throws MethodNotFoundException if no suitable method can be found.
459: * @throws ELException if an exception was thrown while performing (base, method) resolution. The thrown exception must
460: * be included as the cause property of this exception, if available. If the exception thrown is an
461: * <code>InvocationTargetException</code>, extract its <code>cause</code> and pass it to the <code>ELException</code>
462: * constructor.
463: * @since Jakarta Expression Language 2.2
464: */
465: @Override
466: public Object invoke(ELContext context, Object base, Object methodName, Class<?>[] paramTypes, Object[] params) {
467:• if (base == null || methodName == null) {
468: return null;
469: }
470:
471: Method method = ELUtil.findMethod(base.getClass(), methodName.toString(), paramTypes, params, false);
472:
473:• for (Object param : params) {
474: // If the parameters is a LambdaExpression, set the ELContext
475: // for its evaluation
476:• if (param instanceof LambdaExpression) {
477: ((LambdaExpression) param).setELContext(context);
478: }
479: }
480:
481: Object ret = ELUtil.invokeMethod(context, method, base, params);
482: context.setPropertyResolved(base, methodName);
483: return ret;
484: }
485:
486: /**
487: * If the base object is not <code>null</code>, returns whether a call to {@link #setValue} will always fail.
488: *
489: * <p>
490: * If the base is not <code>null</code>, the <code>propertyResolved</code> property of the <code>ELContext</code> object
491: * must be set to <code>true</code> by this resolver, before returning. If this property is not <code>true</code> after
492: * this method is called, the caller can safely assume no value was set.
493: * </p>
494: *
495: * <p>
496: * If this resolver was constructed in read-only mode, this method will always return <code>true</code>.
497: * </p>
498: *
499: * <p>
500: * The provided property name will first be coerced to a <code>String</code>. If property is a writable property of
501: * <code>base</code>, <code>false</code> is returned. If the property is found but is not writable, <code>true</code> is
502: * returned. If the property is not found, a <code>PropertyNotFoundException</code> is thrown.
503: * </p>
504: *
505: * @param context The context of this evaluation.
506: * @param base The bean to analyze.
507: * @param property The name of the property to analyzed. Will be coerced to a <code>String</code>.
508: * @return If the <code>propertyResolved</code> property of <code>ELContext</code> was set to <code>true</code>, then
509: * <code>true</code> if calling the <code>setValue</code> method will always fail or <code>false</code> if it is
510: * possible that such a call may succeed; otherwise undefined.
511: * @throws NullPointerException if context is <code>null</code>
512: * @throws PropertyNotFoundException if <code>base</code> is not <code>null</code> and the specified property does not
513: * exist.
514: * @throws ELException if an exception was thrown while performing the property or variable resolution. The thrown
515: * exception must be included as the cause property of this exception, if available.
516: */
517: @Override
518: public boolean isReadOnly(ELContext context, Object base, Object property) {
519:• if (context == null) {
520: throw new NullPointerException();
521: }
522:
523:• if (base == null || property == null) {
524: return false;
525: }
526:
527: context.setPropertyResolved(true);
528:• if (isReadOnly) {
529: return true;
530: }
531:
532: return getBeanProperty(context, base, property).isReadOnly();
533: }
534:
535: /**
536: * If the base object is not <code>null</code>, returns an <code>Iterator</code> containing the set of JavaBeans
537: * properties available on the given object. Otherwise, returns <code>null</code>.
538: *
539: * <p>
540: * The <code>Iterator</code> returned must contain zero or more instances of {@link java.beans.FeatureDescriptor}. Each
541: * info object contains information about a property in the bean, as obtained by calling the
542: * <code>BeanInfo.getPropertyDescriptors</code> method. The <code>FeatureDescriptor</code> is initialized using the same
543: * fields as are present in the <code>PropertyDescriptor</code>, with the additional required named attributes
544: * "<code>type</code>" and "<code>resolvableAtDesignTime</code>" set as follows:
545: * <ul>
546: * <li>{@link ELResolver#TYPE} - The runtime type of the property, from
547: * <code>PropertyDescriptor.getPropertyType()</code>.</li>
548: * <li>{@link ELResolver#RESOLVABLE_AT_DESIGN_TIME} - <code>true</code>.</li>
549: * </ul>
550: *
551: *
552: * @param context The context of this evaluation.
553: * @param base The bean to analyze.
554: * @return An <code>Iterator</code> containing zero or more <code>FeatureDescriptor</code> objects, each representing a
555: * property on this bean, or <code>null</code> if the <code>base</code> object is <code>null</code>.
556: *
557: * @deprecated This method will be removed without replacement in EL 6.0
558: */
559: @Deprecated(forRemoval = true, since = "5.0")
560: @Override
561: public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
562:• if (base == null) {
563: return null;
564: }
565:
566: BeanInfo info = null;
567: try {
568: info = Introspector.getBeanInfo(base.getClass());
569: } catch (Exception ex) {
570: }
571:
572:• if (info == null) {
573: return null;
574: }
575:
576: ArrayList<FeatureDescriptor> featureDescriptors = new ArrayList<>(info.getPropertyDescriptors().length);
577:• for (PropertyDescriptor propertyDescriptor : info.getPropertyDescriptors()) {
578: propertyDescriptor.setValue("type", propertyDescriptor.getPropertyType());
579: propertyDescriptor.setValue("resolvableAtDesignTime", TRUE);
580: featureDescriptors.add(propertyDescriptor);
581: }
582:
583: return featureDescriptors.iterator();
584: }
585:
586: /**
587: * If the base object is not <code>null</code>, returns the most general type that this resolver accepts for the
588: * <code>property</code> argument. Otherwise, returns <code>null</code>.
589: *
590: * <p>
591: * Assuming the base is not <code>null</code>, this method will always return <code>Object.class</code>. This is because
592: * any object is accepted as a key and is coerced into a string.
593: * </p>
594: *
595: * @param context The context of this evaluation.
596: * @param base The bean to analyze.
597: * @return <code>null</code> if base is <code>null</code> otherwise <code>Object.class</code>.
598: */
599: @Override
600: public Class<?> getCommonPropertyType(ELContext context, Object base) {
601:• if (base == null) {
602: return null;
603: }
604:
605: return Object.class;
606: }
607:
608: private BeanProperty getBeanProperty(ELContext context, Object base, Object prop) {
609: String property = prop.toString();
610: Class<?> baseClass = base.getClass();
611:
612: BeanProperties beanProperties = properties.get(baseClass);
613:• if (beanProperties == null) {
614: beanProperties = new BeanProperties(baseClass);
615: properties.put(baseClass, beanProperties);
616: }
617:
618: BeanProperty beanProperty = beanProperties.getBeanProperty(property);
619:• if (beanProperty == null) {
620: throw new PropertyNotFoundException(getExceptionMessageString(context, "propertyNotFound", new Object[] { baseClass.getName(), property }));
621: }
622:
623: return beanProperty;
624: }
625: }