View Javadoc

1   /*
2    * Copyright 2011 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 1.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl1.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.student.datadictionary.util;
17  
18  import java.beans.BeanInfo;
19  import java.beans.IntrospectionException;
20  import java.beans.Introspector;
21  import java.beans.PropertyDescriptor;
22  import java.lang.reflect.Method;
23  import java.util.Date;
24  import java.util.List;
25  import java.util.Stack;
26  import org.kuali.rice.core.api.uif.DataType;
27  import org.kuali.rice.krad.datadictionary.AttributeDefinition;
28  import org.kuali.rice.krad.datadictionary.CollectionDefinition;
29  import org.kuali.rice.krad.datadictionary.ComplexAttributeDefinition;
30  import org.kuali.rice.krad.datadictionary.DataDictionaryDefinitionBase;
31  import org.kuali.rice.krad.datadictionary.DataObjectEntry;
32  
33  public class Bean2DictionaryConverter {
34  
35      private Class<?> clazz;
36      private Stack<DataDictionaryDefinitionBase> parentFields;
37      private Stack<Class<?>> parentClasses;
38  
39      public Bean2DictionaryConverter(Class<?> clazz, Stack<DataDictionaryDefinitionBase> parentFields, Stack<Class<?>> parentClasses) {
40          this.clazz = clazz;
41          this.parentFields = parentFields;
42          this.parentClasses = parentClasses;
43      }
44  
45      public DataObjectEntry convert() {
46          DataObjectEntry ode = new DataObjectEntry();
47          ode.setDataObjectClass(clazz);
48          addFields("", ode);
49          return ode;
50      }
51  
52      public void addFields(String debuggingContext, DataObjectEntry ode) {
53          BeanInfo beanInfo;
54          try {
55              beanInfo = Introspector.getBeanInfo(clazz);
56          } catch (IntrospectionException ex) {
57              throw new RuntimeException(ex);
58          }
59          for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
60              if (Class.class.equals(pd.getPropertyType())) {
61                  continue;
62              }
63              if ("_futureElements".equals(pd.getName())) {
64                  continue;
65              }
66              if ("attributes".equals(pd.getName())) {
67                  continue;
68              }
69              String name = calcName(pd.getName());
70              Class<?> actualClass = calcActualClass(clazz, pd);
71              DataType dt = calcDataType(debuggingContext + "." + clazz.getSimpleName() + "." + name, actualClass);
72              DataDictionaryDefinitionBase dddef = calcDataDictionaryDefinition(pd, dt);
73              if (dddef instanceof AttributeDefinition) {
74                  AttributeDefinition ad = (AttributeDefinition) dddef;
75                  ode.getAttributes().add(ad);
76              } else if (dddef instanceof ComplexAttributeDefinition) {
77                  ComplexAttributeDefinition cad = (ComplexAttributeDefinition) dddef;
78                  ode.getComplexAttributes().add(cad);
79                  if (!parentClasses.contains(clazz)) {
80                      parentFields.push(dddef);
81                      parentClasses.push(clazz);
82                      Bean2DictionaryConverter subConverter = new Bean2DictionaryConverter(actualClass, parentFields, parentClasses);
83                      subConverter.addFields(debuggingContext + "." + clazz.getSimpleName() + name, ode);
84                      parentFields.pop();
85                      parentClasses.pop();
86                  }
87              } else if (dddef instanceof CollectionDefinition) {
88                  CollectionDefinition cd = (CollectionDefinition) dddef;
89                  ode.getCollections().add(cd);
90                  // TODO: handle collections of primitives
91                  // DataType == null means it is a complex
92  //                TODO: add back in this logic once they fix the jira about collectoin definition not working right                
93  //                if (dt == null) {
94  //                    if (!parentClasses.contains(clazz)) {
95  //                        parentFields.push(dddef);
96  //                        parentClasses.push(clazz);
97  //                        Bean2DictionaryConverter subConverter = new Bean2DictionaryConverter(actualClass, parentFields, parentClasses);
98  //                        subConverter.addFields(debuggingContext + "." + clazz.getSimpleName() + name, ode);
99  //                        parentFields.pop();
100 //                        parentClasses.pop();
101 //                    }
102 //                }
103             }
104             if (dddef instanceof ComplexAttributeDefinition || dddef instanceof CollectionDefinition) {
105             }
106         }
107     }
108 
109     private DataDictionaryDefinitionBase calcDataDictionaryDefinition(PropertyDescriptor pd, DataType dataType) {
110         Class<?> pt = pd.getPropertyType();
111         if (List.class.equals(pt)) {
112             if (dataType != null) {
113                 System.out.println("WARNING: Can't handle lists of primitives just yet: " + calcName(pd.getName()));
114             }
115             CollectionDefinition cd = new CollectionDefinition();
116             cd.setName(calcName(pd.getName()));
117 //            cd.setDataObjectClass(pt);
118             return cd;
119         }
120         if (dataType != null) {
121             AttributeDefinition ad = new AttributeDefinition();
122             ad.setName(calcName(pd.getName()));
123             ad.setDataType(dataType);
124             return ad;
125         }
126         ComplexAttributeDefinition cad = new ComplexAttributeDefinition();
127         cad.setName(calcName(pd.getName()));
128 //        cad.setDataObjectEntry(pt);
129         return cad;
130     }
131 
132     private String calcName(String leafName) {
133         StringBuilder bldr = new StringBuilder();
134         if (!parentFields.isEmpty()) {
135             DataDictionaryDefinitionBase parent = parentFields.peek();
136             if (parent instanceof ComplexAttributeDefinition) {
137                 ComplexAttributeDefinition cad = (ComplexAttributeDefinition) parent;
138                 bldr.append(cad.getName());
139                 bldr.append(".");
140             } else if (parent instanceof CollectionDefinition) {
141                 CollectionDefinition cad = (CollectionDefinition) parent;
142                 bldr.append(cad.getName());
143                 bldr.append(".");
144             }
145         }
146         bldr.append(initLower(leafName));
147         return bldr.toString();
148     }
149 
150     private String initLower(String name) {
151         return name.substring(0, 1).toLowerCase() + name.substring(1);
152     }
153 
154     public static Class<?> calcActualClass(Class<?> clazz, PropertyDescriptor pd) {
155         Class<?> pt = null;
156         // there is a bug in the BeanInfo impl see workaround below
157         // pd.getPropertyType gets the interface not the info object
158         // for example: 
159         // info has...
160         // @Override
161         // ExpenditureInfo getExpenditure ();
162         // 
163         // and interface has
164         // Expenditure getExpenditure ();
165         // then pd.getPropertyType () returns Expenditure not ExpenditureInfo
166         // so...
167         // we use the work around if it gets an interface
168         pt = pd.getPropertyType();
169         if (pt.isInterface()) {
170             if (pd.getReadMethod() == null) {
171                 throw new NullPointerException (clazz.getName() + "." + pd.getName() + " has no read method");
172             }
173             pt = workAround(clazz, pd.getReadMethod().getName());
174         }
175         
176         if (List.class.equals(pt)) {
177             pt = ComplexSubstructuresHelper.getActualClassFromList(clazz, pd.getName());
178         }
179         return pt;
180     }
181 
182     private static Class<?> workAround(Class<?> currentTargetClass, String methodName) {
183         Method method = findMethodImplFirst(currentTargetClass, methodName);
184         return method.getReturnType();
185     }
186 
187     /**
188      * Got this code from:
189      * http://raulraja.com/2009/09/12/java-beans-introspector-odd-behavio/
190      * 
191      * workaround for introspector odd behavior with javabeans that implement interfaces with comaptible return types
192      * but instrospection is unable to find the right accessors
193      *
194      * @param currentTargetClass the class being evaluated
195      * @param methodName		 the method name we are looking for
196      * @param argTypes		   the arg types for the method name
197      * @return a method if found
198      */
199     private static Method findMethodImplFirst(Class<?> currentTargetClass, String methodName, Class<?>... argTypes) {
200         Method method = null;
201         if (currentTargetClass != null && methodName != null) {
202             try {
203                 method = currentTargetClass.getMethod(methodName, argTypes);
204             } catch (Throwable t) {
205                 // nothing we can do but continue
206             }
207             //Is the method in one of our parent classes
208             if (method == null) {
209                 Class<?> superclass = currentTargetClass.getSuperclass();
210                 if (!superclass.equals(Object.class)) {
211                     method = findMethodImplFirst(superclass, methodName, argTypes);
212                 }
213             }
214         }
215         return method;
216     }
217 
218     public static DataType calcDataType(String context, Class<?> pt) {
219         if (int.class.equals(pt) || Integer.class.equals(pt)) {
220             return DataType.INTEGER;
221         } else if (long.class.equals(pt) || Long.class.equals(pt)) {
222             return DataType.LONG;
223         } else if (double.class.equals(pt) || Double.class.equals(pt)) {
224             return DataType.DOUBLE;
225         } else if (float.class.equals(pt) || Float.class.equals(pt)) {
226             return DataType.FLOAT;
227         } else if (boolean.class.equals(pt) || Boolean.class.equals(pt)) {
228             return DataType.BOOLEAN;
229         } else if (Date.class.equals(pt)) {
230             return DataType.DATE;
231         } else if (String.class.equals(pt)) {
232             return DataType.STRING;
233         } else if (List.class.equals(pt)) {
234             throw new RuntimeException("Found list can't have a list of lists, List<List<?>> in " + context);
235         } else if (Enum.class.isAssignableFrom(pt)) {
236             return DataType.STRING;
237         } else if (Object.class.equals(pt)) {
238             return DataType.STRING;
239         } else if (pt.getName().startsWith("org.kuali.student.")) {
240             return null;
241         } else {
242             throw new RuntimeException("Found unknown/unhandled type of object in bean " + pt.getName() + " in " + context);
243         }
244     }
245 }