001    package org.trails.descriptor;
002    
003    import java.beans.*;
004    import java.lang.reflect.InvocationTargetException;
005    import java.util.ArrayList;
006    import java.util.List;
007    
008    import org.apache.commons.beanutils.BeanUtils;
009    import org.apache.commons.logging.Log;
010    import org.apache.commons.logging.LogFactory;
011    import org.trails.util.Utils;
012    
013    /**
014     * Generate descriptors using reflection on the underlying class.
015     * ReflectionDescriptorFactory.buildClassDescriptor() is the only public method
016     * here.
017     */
018    public class ReflectionDescriptorFactory implements DescriptorFactory
019    {
020            protected static final Log LOG = LogFactory.getLog(ReflectionDescriptorFactory.class);
021    
022            private List propertyExcludes = new ArrayList();
023    
024            private List methodExcludes = new ArrayList();
025    
026            /**
027             * Given a type, build a property descriptor
028             *
029             * @param type The type to build for
030             * @return a completed property descriptor
031             */
032            public IClassDescriptor buildClassDescriptor(Class type)
033            {
034                    try
035                    {
036                            IClassDescriptor descriptor = new TrailsClassDescriptor(type);
037                            BeanInfo beanInfo = Introspector.getBeanInfo(type);
038                            BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
039                            // Note there's various places and ways to uncamelcase the display name. However
040                            // we don't want to un-camelcase the possibly customized displayName
041                            // Also, because Introspector uses static methods, it's less clean to replace it 
042                            // with a custom implementation. Proxy doesn't scale well and an aspect would 
043                            // only work if it's run. So decided to deal with uncamelcasing displayname here
044                            beanDescriptor.setDisplayName(Utils.unCamelCase(beanDescriptor.getDisplayName()) );
045                            BeanUtils.copyProperties(descriptor, beanInfo.getBeanDescriptor());
046                            descriptor.setPropertyDescriptors(buildPropertyDescriptors(type,beanInfo, descriptor));
047                            descriptor.setMethodDescriptors(buildMethodDescriptors(type, beanInfo, descriptor));
048                            return descriptor;
049    
050                    } catch (IllegalAccessException e)
051                    {
052                            LOG.error(e.getMessage());
053                            e.printStackTrace();
054                    } catch (InvocationTargetException e)
055                    {
056                            LOG.error(e.getMessage());
057                            e.printStackTrace();
058                    } catch (Exception e)
059                    {
060                            LOG.error(e.toString());
061                            e.printStackTrace();
062                    }
063                    return null;
064            }
065    
066            /**
067             * Build the set of property descriptors for this type
068             *
069             * @param beanType                        the aggregating class
070             * @param beanInfo                        the BeanInfo, already gathered
071             * @param parentClassDescriptor reference to the aggregating class, used for recovery with
072             *                              graph traversal
073             * @return ObjectReferenceDescriptor if this property is an association,
074             *         otherwise a TrailsPropertyDescriptor
075             * @throws Exception
076             */
077            protected ArrayList<IPropertyDescriptor> buildPropertyDescriptors(
078                    Class beanType, BeanInfo beanInfo,
079                    IClassDescriptor parentClassDescriptor) throws Exception
080            {
081                    ArrayList<IPropertyDescriptor> result = new ArrayList<IPropertyDescriptor>();
082                    for (PropertyDescriptor beanPropDescriptor : beanInfo.getPropertyDescriptors())
083                    {
084                            if (!isExcluded(beanPropDescriptor.getName(), getPropertyExcludes()))
085                            {
086                                    beanPropDescriptor.setDisplayName(Utils.unCamelCase(beanPropDescriptor.getDisplayName()) );
087                                    
088                                    Class<?> propertyType = beanPropDescriptor.getPropertyType();
089                                    TrailsPropertyDescriptor propDescriptor = new TrailsPropertyDescriptor(beanType,propertyType);
090                                    BeanUtils.copyProperties(propDescriptor, beanPropDescriptor);
091                                    result.add(propDescriptor);
092                            }
093                    }
094                    return result;
095            }
096    
097            protected ArrayList<IMethodDescriptor> buildMethodDescriptors(Class type, BeanInfo beanInfo,
098                                                                                                                                      IClassDescriptor parentClassDescriptor)
099                            throws Exception
100            {
101                    ArrayList<IMethodDescriptor> result = new ArrayList<IMethodDescriptor>();
102                    for (MethodDescriptor beanMethodDescriptor : beanInfo.getMethodDescriptors())
103                    {
104                            if (!isExcluded(beanMethodDescriptor.getMethod().getName(), getMethodExcludes()))
105                            {
106                                    TrailsMethodDescriptor methodDescriptor = new TrailsMethodDescriptor(type,
107                                                    beanMethodDescriptor.getMethod().getName(), beanMethodDescriptor.getMethod().getReturnType(),
108                                                    beanMethodDescriptor.getMethod().getParameterTypes());
109                                    methodDescriptor.setDisplayName(Utils.unCamelCase(beanMethodDescriptor.getDisplayName()));
110                                    result.add(methodDescriptor);
111                            }
112                    }
113                    return result;
114            }
115    
116            protected boolean isExcluded(String name, List excludes)
117            {
118                    for (Object exclude1 : excludes)
119                    {
120                            String exclude = (String) exclude1;
121    
122                            if (name.matches(exclude))
123                            {
124                                    return true;
125                            }
126                    }
127    
128                    return false;
129            }
130    
131            public List getMethodExcludes()
132            {
133                    return methodExcludes;
134            }
135    
136            public void setMethodExcludes(List methodExcludes)
137            {
138                    this.methodExcludes = methodExcludes;
139            }
140    
141            public List getPropertyExcludes()
142            {
143                    return propertyExcludes;
144            }
145    
146            public void setPropertyExcludes(List propertyExcludes)
147            {
148                    this.propertyExcludes = propertyExcludes;
149            }
150    }