001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016// begin Kuali Foundation modification
017package org.kuali.rice.kns.web.struts.form.pojo;
018
019import org.apache.commons.beanutils.DynaBean;
020import org.apache.commons.beanutils.DynaProperty;
021import org.apache.commons.beanutils.MappedPropertyDescriptor;
022import org.apache.commons.beanutils.MethodUtils;
023import org.apache.commons.beanutils.NestedNullException;
024import org.apache.commons.beanutils.PropertyUtils;
025import org.apache.commons.beanutils.PropertyUtilsBean;
026import org.apache.commons.beanutils.WrapDynaBean;
027import org.apache.commons.collections.FastHashMap;
028import org.apache.log4j.Logger;
029import org.apache.ojb.broker.metadata.ClassDescriptor;
030import org.apache.ojb.broker.metadata.ClassNotPersistenceCapableException;
031import org.apache.ojb.broker.metadata.CollectionDescriptor;
032import org.apache.ojb.broker.metadata.DescriptorRepository;
033import org.apache.ojb.broker.metadata.MetadataManager;
034import org.kuali.rice.core.web.format.Formatter;
035import org.kuali.rice.krad.bo.PersistableBusinessObject;
036import org.kuali.rice.krad.service.KRADServiceLocator;
037import org.kuali.rice.krad.service.PersistenceStructureService;
038import org.kuali.rice.krad.util.ObjectUtils;
039
040import java.beans.IndexedPropertyDescriptor;
041import java.beans.IntrospectionException;
042import java.beans.PropertyDescriptor;
043import java.lang.reflect.InvocationTargetException;
044import java.lang.reflect.Method;
045import java.util.ArrayList;
046import java.util.Collection;
047import java.util.HashMap;
048import java.util.List;
049import java.util.Map;
050
051/**
052 * begin Kuali Foundation modification
053 * This class is used to access the properties of a Pojo bean.
054 * deleted author tag
055 * end Kuali Foundation modification
056 */
057// Kuali Foundation modification: class originally SLPropertyUtilsBean
058public class PojoPropertyUtilsBean extends PropertyUtilsBean {
059
060    public static final Logger LOG = Logger.getLogger(PojoPropertyUtilsBean.class.getName());
061
062    /**
063     * Thin interface for determining the appropriate item class for a collection property
064     */
065    public static interface CollectionItemClassProvider {
066        public Class getCollectionItemClass(Object bean, String property);
067    }
068
069    /**
070     * CollectionItemClassProvider backed by OJB metadata
071     */
072    public static class PersistenceStructureServiceProvider implements CollectionItemClassProvider {
073        protected static PersistenceStructureService persistenceStructureService = null;
074        protected static PersistenceStructureService getPersistenceStructureService() {
075            if (persistenceStructureService == null) {
076                persistenceStructureService = KRADServiceLocator.getPersistenceStructureService();
077            }
078            return persistenceStructureService;
079        }
080
081        @Override
082        public Class getCollectionItemClass(Object bean, String property) {
083            Map<String, Class> collectionObjectTypes = getPersistenceStructureService().listCollectionObjectTypes(bean.getClass());
084            return collectionObjectTypes.get(property);
085        }
086    }
087
088    // default is to consult OJB
089    protected static CollectionItemClassProvider collectionItemClassProvider = new PersistenceStructureServiceProvider();
090
091        // begin Kuali Foundation modification
092    public PojoPropertyUtilsBean() {
093        super();
094    }
095    // end Kuali Foundation modification
096
097    public Object getProperty(Object bean, String key) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
098        // begin Kuali Foundation modification
099        if (!(bean instanceof PojoForm))
100            return super.getProperty(bean, key);
101
102        PojoForm form = (PojoForm) bean;
103        Map unconvertedValues = form.getUnconvertedValues();
104
105        if (unconvertedValues.containsKey(key))
106            return unconvertedValues.get(key);
107
108        Object val = getNestedProperty(bean, key);
109        Class type = (val!=null)?val.getClass():null;
110        if ( type == null ) {
111            try {
112                type = getPropertyType(bean, key);
113            } catch ( Exception ex ) {
114                type = String.class;
115                LOG.warn( "Unable to get property type for Class: " + bean.getClass().getName() + "/Property: " + key );
116            }
117        }
118        return (Formatter.isSupportedType(type) ? form.formatValue(val, key, type) : val);
119        // end Kuali Foundation modification
120    }
121
122        // begin Kuali Foundation modification
123    private Map<String,List<Method>> cache = new HashMap<String, List<Method>>();
124    private static Map<String,Method> readMethodCache = new HashMap<String, Method>();
125    private IntrospectionException introspectionException = new IntrospectionException( "" );
126
127    public Object fastGetNestedProperty(Object obj, String propertyName) throws IntrospectionException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
128        //logger.debug("entering fastGetNestedProperty");
129
130        List<Method> methods = (List<Method>) cache.get(propertyName + obj.getClass().getName());
131        if (methods == null) {
132            methods = new ArrayList<Method>();
133            Object currentObj = obj;
134            Class<?> currentObjClass = currentObj.getClass();
135
136            for (String currentPropertyName : propertyName.split("\\.") ) {
137                String cacheKey = currentObjClass.getName() + currentPropertyName;
138                Method readMethod = readMethodCache.get( cacheKey );
139                if ( readMethod == null ) {
140                        synchronized (readMethodCache) {
141                            // if the read method was resolved to an error, repeat the exception
142                            // rather than performing the reflection calls below
143                            if ( readMethodCache.containsKey(cacheKey) ) {
144                                throw introspectionException;
145                            }
146                            try {
147                                try {
148                                    readMethod = currentObjClass.getMethod("get" + currentPropertyName.substring(0, 1).toUpperCase() + currentPropertyName.substring(1), (Class[])null);
149                                } catch (NoSuchMethodException e) {
150                                    readMethod = currentObjClass.getMethod("is" + currentPropertyName.substring(0, 1).toUpperCase() + currentPropertyName.substring(1), (Class[])null);
151                                }
152                            } catch ( NoSuchMethodException ex ) {
153                                // cache failures to prevent re-checking of the parameter
154                                readMethodCache.put( cacheKey, null );
155                                throw introspectionException;
156                            }
157                            readMethodCache.put(cacheKey, readMethod );
158                                        }
159                }
160                methods.add(readMethod);
161                currentObjClass = readMethod.getReturnType();
162            }
163            synchronized (cache) {
164                cache.put(propertyName + obj.getClass().getName(), methods);
165                        }
166        }
167
168        for ( Method method : methods ) {
169            obj = method.invoke(obj, (Object[])null);
170        }
171
172        //logger.debug("exiting fastGetNestedProperty");
173
174        return obj;
175    }
176        // end Kuali Foundation modification
177
178    /*
179     *  Kuali modification to make isWriteable work like it did in beanUtils 1.7.
180     *  Checking for nested nulls caused exceptions in rice 2.0.
181     */
182    @Override
183    public boolean isWriteable(Object bean, String name) {
184        // Validate method parameters
185        if (bean == null) {
186            throw new IllegalArgumentException("No bean specified");
187        }
188        if (name == null) {
189            throw new IllegalArgumentException("No name specified for bean class '" +
190                    bean.getClass() + "'");
191        }
192
193        // Remove any subscript from the final name value
194        name = getResolver().getProperty(name);
195
196        // Treat WrapDynaBean as special case - may be a read-only property
197        // (see Jira issue# BEANUTILS-61)
198        if (bean instanceof WrapDynaBean) {
199            bean = ((WrapDynaBean)bean).getInstance();
200        }
201
202        // Return the requested result
203        if (bean instanceof DynaBean) {
204            // All DynaBean properties are writeable
205            return (((DynaBean) bean).getDynaClass().getDynaProperty(name) != null);
206        } else {
207            try {
208                PropertyDescriptor desc =
209                        getPropertyDescriptor(bean, name);
210                if (desc != null) {
211                    Method writeMethod = desc.getWriteMethod();
212                    if (writeMethod == null) {
213                        if (desc instanceof IndexedPropertyDescriptor) {
214                            writeMethod = ((IndexedPropertyDescriptor) desc).getIndexedWriteMethod();
215                        } else if (desc instanceof MappedPropertyDescriptor) {
216                            writeMethod = ((MappedPropertyDescriptor) desc).getMappedWriteMethod();
217                        }
218                        writeMethod = MethodUtils.getAccessibleMethod(bean.getClass(), writeMethod);
219                    }
220                    return (writeMethod != null);
221                } else {
222                    return (false);
223                }
224            } catch (IllegalAccessException e) {
225                return (false);
226            } catch (InvocationTargetException e) {
227                return (false);
228            } catch (NoSuchMethodException e) {
229                return (false);
230            }
231        }
232
233    }
234
235    /**
236     * begin Kuali Foundation modification
237     * removed comments and @<no space>since javadoc attribute
238     * end Kuali Foundation modification
239     * @see org.apache.commons.beanutils.PropertyUtilsBean#getNestedProperty(java.lang.Object, java.lang.String)
240     */
241    public Object getNestedProperty(Object arg0, String arg1) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
242                // begin Kuali Foundation modification
243        try {
244            try {
245                return fastGetNestedProperty(arg0, arg1);
246            }
247            catch (Exception e) {
248                return super.getNestedProperty(arg0, arg1);
249            }
250        }
251        catch (NestedNullException e) {
252            return getUnreachableNestedProperty(arg0, arg1);
253        }
254        catch (InvocationTargetException e1) {
255            return getUnreachableNestedProperty(arg0, arg1);
256        }
257        // removed commented code
258        // end Kuali Foundation modification
259    }
260
261    /**
262     * Customization of superclass getNestedProperty which transparently creates indexed property items
263     * {@inheritDoc}
264     */
265    public Object getIndexedProperty(Object bean, String name, int index) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
266        try {
267            return super.getIndexedProperty(bean, name, index);
268        } catch (IndexOutOfBoundsException ioobe) {
269            return generateIndexedProperty(bean, name, index, ioobe);
270        }
271    }
272
273    protected Object generateIndexedProperty(Object nestedBean, String property, int index, IndexOutOfBoundsException ioobe)  throws IllegalAccessException, InvocationTargetException,
274            NoSuchMethodException {
275
276        if (!(nestedBean instanceof PersistableBusinessObject)) throw ioobe;
277
278        // we can only grow lists
279        if (!List.class.isAssignableFrom(getPropertyType(nestedBean, property))) throw ioobe;
280
281        List list= (List) getProperty(nestedBean, property);
282
283        Class c = collectionItemClassProvider.getCollectionItemClass(nestedBean, property);
284
285        if (c == null) {
286            throw new RuntimeException("Unable to determined item class for collection '" + property + "' on bean of type '" + nestedBean.getClass() + "'");
287        }
288
289        Object value;
290        try {
291            value = c.newInstance();
292        } catch (InstantiationException ie) {
293            throw new RuntimeException("Error instantiating item class: " + c);
294        }
295
296        // fill any missing indices
297        while (list.size() <= index) {
298            list.add(null);
299        }
300        list.set(index, value);
301
302        return super.getIndexedProperty(nestedBean, property, index);
303    }
304
305    // begin Kuali Foundation modification
306    /**
307     * helper method makes sure we don't return "" for collections
308     */
309    private Object getUnreachableNestedProperty(Object arg0, String arg1) {
310        try {
311            PropertyDescriptor propertyDescriptor  = getPropertyDescriptor(arg0, arg1);
312            if (propertyDescriptor == null || Collection.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
313                return null;
314            }
315        } catch (IllegalAccessException e) {
316            // ignore
317        } catch (InvocationTargetException e) {
318            // ignore
319        } catch (NoSuchMethodException e) {
320            // ignore
321        }
322
323        return "";
324    }
325    // end Kuali Foundation modification
326
327
328    // begin Kuali Foundation modification 
329    /**
330     * begin Kuali Foundation modification
331     * Set the value of the (possibly nested) property of the specified name, for the specified bean, with no type conversions.
332     *
333     * @param bean Bean whose property is to be modified
334     * @param name Possibly nested name of the property to be modified
335     * @param value Value to which the property is to be set
336     *
337     * @exception IllegalAccessException if the caller does not have access to the property accessor method
338     * @exception IllegalArgumentException if <code>bean</code> or <code>name</code> is null
339     * @exception IllegalArgumentException if a nested reference to a property returns null
340     * @exception InvocationTargetException if the property accessor method throws an exception
341     * @exception NoSuchMethodException if an accessor method for this propety cannot be found
342     * end Kuali Foundation modification
343     */
344    public void setNestedProperty(Object bean, String name, Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
345
346        if (bean == null) {
347                if (LOG.isDebugEnabled()) LOG.debug("No bean specified, name = " + name + ", value = " + value);
348                return;
349        }
350        if (name == null) {
351            throw new IllegalArgumentException("No name specified");
352        }
353
354        Object propBean = null;
355        int indexOfINDEXED_DELIM = -1;
356        int indexOfMAPPED_DELIM = -1;
357        while (true) {
358            int delim = name.indexOf(PropertyUtils.NESTED_DELIM);
359            if (delim < 0) {
360                break;
361            }
362            String next = name.substring(0, delim);
363            indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM);
364            indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM);
365            if (bean instanceof Map) {
366                propBean = ((Map) bean).get(next);
367            }
368            else if (indexOfMAPPED_DELIM >= 0) {
369                propBean = getMappedProperty(bean, next);
370            }
371            else if (indexOfINDEXED_DELIM >= 0) {
372                propBean = getIndexedProperty(bean, next);
373            }
374            else {
375                propBean = getSimpleProperty(bean, next);
376            }
377            if (ObjectUtils.isNull(propBean)) {
378                Class propertyType = getPropertyType(bean, next);
379                if (propertyType != null) {
380                        Object newInstance = ObjectUtils.createNewObjectFromClass(propertyType);
381                    setSimpleProperty(bean, next, newInstance);
382                    propBean = getSimpleProperty(bean, next);
383                }
384            }
385            bean = propBean;
386            name = name.substring(delim + 1);
387        }
388
389        indexOfINDEXED_DELIM = name.indexOf(PropertyUtils.INDEXED_DELIM);
390        indexOfMAPPED_DELIM = name.indexOf(PropertyUtils.MAPPED_DELIM);
391
392        if (bean instanceof Map) {
393            // check to see if the class has a standard property
394            PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
395            if (descriptor == null) {
396                // no - then put the value into the map
397                ((Map) bean).put(name, value);
398            }
399            else {
400                // yes - use that instead
401                setSimpleProperty(bean, name, value);
402            }
403        }
404        else if (indexOfMAPPED_DELIM >= 0) {
405            setMappedProperty(bean, name, value);
406        }
407        else if (indexOfINDEXED_DELIM >= 0) {
408            setIndexedProperty(bean, name, value);
409        }
410        else {
411            setSimpleProperty(bean, name, value);
412        }
413    }
414    // end Kuali Foundation modification
415
416        // begin Kuali Foundation modification
417    /**
418     * <p>
419     * Retrieve the property descriptor for the specified property of the specified bean, or return <code>null</code> if there is
420     * no such descriptor. This method resolves indexed and nested property references in the same manner as other methods in this
421     * class, except that if the last (or only) name element is indexed, the descriptor for the last resolved property itself is
422     * returned.
423     * </p>
424     *
425     * <p>
426     * <strong>FIXME </strong>- Does not work with DynaBeans.
427     * </p>
428     *
429     * @param bean Bean for which a property descriptor is requested
430     * @param name Possibly indexed and/or nested name of the property for which a property descriptor is requested
431     *
432     * @exception IllegalAccessException if the caller does not have access to the property accessor method
433     * @exception IllegalArgumentException if <code>bean</code> or <code>name</code> is null
434     * @exception IllegalArgumentException if a nested reference to a property returns null
435     * @exception InvocationTargetException if the property accessor method throws an exception
436     * @exception NoSuchMethodException if an accessor method for this propety cannot be found
437     */
438    public PropertyDescriptor getPropertyDescriptor(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
439        if (bean == null) {
440                if (LOG.isDebugEnabled()) LOG.debug("No bean specified, name = " + name);
441                return null;
442        }
443        if (name == null) {
444            throw new IllegalArgumentException("No name specified");
445        }
446        try {
447            // Resolve nested references
448            Object propBean = null;
449            while (true) {
450                int delim = findNextNestedIndex(name);
451                //int delim = name.indexOf(PropertyUtils.NESTED_DELIM);
452                if (delim < 0) {
453                    break;
454                }
455                String next = name.substring(0, delim);
456                int indexOfINDEXED_DELIM = next.indexOf(PropertyUtils.INDEXED_DELIM);
457                int indexOfMAPPED_DELIM = next.indexOf(PropertyUtils.MAPPED_DELIM);
458                if (indexOfMAPPED_DELIM >= 0 && (indexOfINDEXED_DELIM < 0 || indexOfMAPPED_DELIM < indexOfINDEXED_DELIM)) {
459                    propBean = getMappedProperty(bean, next);
460                }
461                else {
462                    if (indexOfINDEXED_DELIM >= 0) {
463                        propBean = getIndexedProperty(bean, next);
464                    }
465                    else {
466                        propBean = getSimpleProperty(bean, next);
467                    }
468                }
469                if (ObjectUtils.isNull(propBean)) {
470                    Class propertyType = getPropertyType(bean, next);
471                    if (propertyType != null) {
472                        Object newInstance = ObjectUtils.createNewObjectFromClass(propertyType);
473                        setSimpleProperty(bean, next, newInstance);
474                        propBean = getSimpleProperty(bean, next);
475                    }
476                }
477                bean = propBean;
478                name = name.substring(delim + 1);
479            }
480    
481            // Remove any subscript from the final name value
482            int left = name.indexOf(PropertyUtils.INDEXED_DELIM);
483            if (left >= 0) {
484                name = name.substring(0, left);
485            }
486            left = name.indexOf(PropertyUtils.MAPPED_DELIM);
487            if (left >= 0) {
488                name = name.substring(0, left);
489            }
490    
491            // Look up and return this property from our cache
492            // creating and adding it to the cache if not found.
493            if ((bean == null) || (name == null)) {
494                return (null);
495            }
496    
497            PropertyDescriptor descriptors[] = getPropertyDescriptors(bean);
498            if (descriptors != null) {
499    
500                for (int i = 0; i < descriptors.length; i++) {
501                    if (name.equals(descriptors[i].getName()))
502                        return (descriptors[i]);
503                }
504            }
505    
506            PropertyDescriptor result = null;
507            FastHashMap mappedDescriptors = getMappedPropertyDescriptors(bean);
508            if (mappedDescriptors == null) {
509                mappedDescriptors = new FastHashMap();
510                mappedDescriptors.setFast(true);
511            }
512            result = (PropertyDescriptor) mappedDescriptors.get(name);
513            if (result == null) {
514                // not found, try to create it
515                try {
516                    result = new MappedPropertyDescriptor(name, bean.getClass());
517                }
518                catch (IntrospectionException ie) {
519                }
520                if (result != null) {
521                    mappedDescriptors.put(name, result);
522                }
523            }
524    
525            return result;
526        } catch ( RuntimeException ex ) {
527            LOG.error( "Unable to get property descriptor for " + bean.getClass().getName() + " . " + name
528                    + "\n" + ex.getClass().getName() + ": " + ex.getMessage() );
529            throw ex;
530        }
531    }
532    // end Kuali Foundation modification
533
534    private int findNextNestedIndex(String expression)
535    {
536        // walk back from the end to the start
537        // and find the first index that
538        int bracketCount = 0;
539        for (int i=0, size=expression.length(); i<size ; i++) {
540            char at = expression.charAt(i);
541            switch (at) {
542                case PropertyUtils.NESTED_DELIM:
543                    if (bracketCount < 1) {
544                        return i;
545                    }
546                    break;
547
548                case PropertyUtils.MAPPED_DELIM:
549                case PropertyUtils.INDEXED_DELIM:
550                    // not bothered which
551                    ++bracketCount;
552                    break;
553
554                case PropertyUtils.MAPPED_DELIM2:
555                case PropertyUtils.INDEXED_DELIM2:
556                    // not bothered which
557                    --bracketCount;
558                    break;
559            }
560        }
561        // can't find any
562        return -1;
563    }
564
565    /**
566     * Set the value of the specified simple property of the specified bean,
567     * with no type conversions.
568     *
569     * @param bean Bean whose property is to be modified
570     * @param name Name of the property to be modified
571     * @param value Value to which the property should be set
572     *
573     * @exception IllegalAccessException if the caller does not have
574     *  access to the property accessor method
575     * @exception IllegalArgumentException if <code>bean</code> or
576     *  <code>name</code> is null
577     * @exception IllegalArgumentException if the property name is
578     *  nested or indexed
579     * @exception InvocationTargetException if the property accessor method
580     *  throws an exception
581     * @exception NoSuchMethodException if an accessor method for this
582     *  propety cannot be found
583     */
584    public void setSimpleProperty(Object bean,
585                                         String name, Object value)
586            throws IllegalAccessException, InvocationTargetException,
587            NoSuchMethodException {
588
589        if (bean == null) {
590                if (LOG.isDebugEnabled()) LOG.debug("No bean specified, name = " + name + ", value = " + value);
591                return;
592        }
593        if (name == null) {
594            throw new IllegalArgumentException("No name specified");
595        }
596
597        // Validate the syntax of the property name
598        if (name.indexOf(PropertyUtils.NESTED_DELIM) >= 0) {
599            throw new IllegalArgumentException
600                    ("Nested property names are not allowed");
601        } else if (name.indexOf(PropertyUtils.INDEXED_DELIM) >= 0) {
602            throw new IllegalArgumentException
603                    ("Indexed property names are not allowed");
604        } else if (name.indexOf(PropertyUtils.MAPPED_DELIM) >= 0) {
605            throw new IllegalArgumentException
606                    ("Mapped property names are not allowed");
607        }
608
609        // Retrieve the property setter method for the specified property
610        PropertyDescriptor descriptor =
611                getPropertyDescriptor(bean, name);
612        if (descriptor == null) {
613            throw new NoSuchMethodException("Unknown property '" +
614                    name + "'");
615        }
616        Method writeMethod = getWriteMethod(descriptor);
617        if (writeMethod == null) {
618            //throw new NoSuchMethodException("Property '" + name + "' has no setter method");
619                LOG.warn("Bean: " + bean.getClass().getName() + ", Property '" + name + "' has no setter method");
620                return;
621        }
622
623        // Call the property setter method
624        Object values[] = new Object[1];
625        values[0] = value;
626        if (LOG.isDebugEnabled()) {
627            String valueClassName =
628                value == null ? "<null>" : value.getClass().getName();
629            LOG.debug("setSimpleProperty: Invoking method " + writeMethod
630                      + " with value " + value + " (class " + valueClassName + ")");
631        }
632        
633        
634        invokeMethod(writeMethod, bean, values);
635
636    }
637    
638    /** This just catches and wraps IllegalArgumentException. */
639    private Object invokeMethod(
640                        Method method, 
641                        Object bean, 
642                        Object[] values) 
643                            throws
644                                IllegalAccessException,
645                                InvocationTargetException {
646        try {
647            
648            return method.invoke(bean, values);
649        
650        } catch (IllegalArgumentException e) {
651            
652            LOG.error("Method invocation failed.", e);
653            throw new IllegalArgumentException(
654                "Cannot invoke " + method.getDeclaringClass().getName() + "." 
655                + method.getName() + " - " + e.getMessage());
656            
657        }
658    }
659    
660    public Class getPropertyType(Object bean, String name)
661            throws IllegalAccessException, InvocationTargetException,
662            NoSuchMethodException {
663
664        if (bean == null) {
665            throw new IllegalArgumentException("No bean specified");
666        }
667        if (name == null) {
668            throw new IllegalArgumentException("No name specified for bean class '" +
669                    bean.getClass() + "'");
670        }
671
672        // Resolve nested references
673        while (getResolver().hasNested(name)) {
674            String next = getResolver().next(name);
675            Object nestedBean = getProperty(bean, next);
676            if (nestedBean == null) {
677                Class<?>[] paramTypes = {};
678                Method method = null;
679                try {
680                    method = bean.getClass().getMethod("get" + next.substring(0, 1).toUpperCase() + next.substring(1), (Class[])null);
681                } catch (NoSuchMethodException e) {
682                    method = bean.getClass().getMethod("is" + next.substring(0, 1).toUpperCase() + next.substring(1), (Class[])null);
683                }
684                try {
685                    nestedBean = ObjectUtils.createNewObjectFromClass(method.getReturnType());
686                                } catch (RuntimeException e) {
687                                        NestedNullException nne = new NestedNullException
688                    ("Null property value for '" + next +
689                    "' on bean class '" + bean.getClass() + "'");
690                    nne.initCause(e);
691                    throw nne;
692                                }
693            }
694            bean = nestedBean;
695            name = getResolver().remove(name);
696        }
697
698        // Remove any subscript from the final name value
699        name = getResolver().getProperty(name);
700
701        // Special handling for DynaBeans
702        if (bean instanceof DynaBean) {
703            DynaProperty descriptor =
704                    ((DynaBean) bean).getDynaClass().getDynaProperty(name);
705            if (descriptor == null) {
706                return (null);
707            }
708            Class type = descriptor.getType();
709            if (type == null) {
710                return (null);
711            } else if (type.isArray()) {
712                return (type.getComponentType());
713            } else {
714                return (type);
715            }
716        }
717
718        PropertyDescriptor descriptor =
719                getPropertyDescriptor(bean, name);
720        if (descriptor == null) {
721            return (null);
722        } else if (descriptor instanceof IndexedPropertyDescriptor) {
723            return (((IndexedPropertyDescriptor) descriptor).
724                    getIndexedPropertyType());
725        } else if (descriptor instanceof MappedPropertyDescriptor) {
726            return (((MappedPropertyDescriptor) descriptor).
727                    getMappedPropertyType());
728        } else {
729            return (descriptor.getPropertyType());
730        }
731
732    }
733}