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