001 /*
002 * Copyright 2011 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 1.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/ecl1.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 package org.kuali.student.datadictionary.util;
017
018 import java.beans.BeanInfo;
019 import java.beans.IntrospectionException;
020 import java.beans.Introspector;
021 import java.beans.PropertyDescriptor;
022 import java.lang.reflect.Method;
023 import java.util.Date;
024 import java.util.List;
025 import java.util.Stack;
026 import org.kuali.rice.core.api.uif.DataType;
027 import org.kuali.rice.krad.datadictionary.AttributeDefinition;
028 import org.kuali.rice.krad.datadictionary.CollectionDefinition;
029 import org.kuali.rice.krad.datadictionary.ComplexAttributeDefinition;
030 import org.kuali.rice.krad.datadictionary.DataDictionaryDefinitionBase;
031 import org.kuali.rice.krad.datadictionary.DataObjectEntry;
032
033 public class Bean2DictionaryConverter {
034
035 private Class<?> clazz;
036 private Stack<DataDictionaryDefinitionBase> parentFields;
037 private Stack<Class<?>> parentClasses;
038
039 public Bean2DictionaryConverter(Class<?> clazz, Stack<DataDictionaryDefinitionBase> parentFields, Stack<Class<?>> parentClasses) {
040 this.clazz = clazz;
041 this.parentFields = parentFields;
042 this.parentClasses = parentClasses;
043 }
044
045 public DataObjectEntry convert() {
046 DataObjectEntry ode = new DataObjectEntry();
047 ode.setDataObjectClass(clazz);
048 addFields("", ode);
049 return ode;
050 }
051
052 public void addFields(String debuggingContext, DataObjectEntry ode) {
053 BeanInfo beanInfo;
054 try {
055 beanInfo = Introspector.getBeanInfo(clazz);
056 } catch (IntrospectionException ex) {
057 throw new RuntimeException(ex);
058 }
059 for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
060 if (Class.class.equals(pd.getPropertyType())) {
061 continue;
062 }
063 if ("_futureElements".equals(pd.getName())) {
064 continue;
065 }
066 if ("attributes".equals(pd.getName())) {
067 continue;
068 }
069 String name = calcName(pd.getName());
070 Class<?> actualClass = calcActualClass(clazz, pd);
071 DataType dt = calcDataType(debuggingContext + "." + clazz.getSimpleName() + "." + name, actualClass);
072 DataDictionaryDefinitionBase dddef = calcDataDictionaryDefinition(pd, dt);
073 if (dddef instanceof AttributeDefinition) {
074 AttributeDefinition ad = (AttributeDefinition) dddef;
075 ode.getAttributes().add(ad);
076 } else if (dddef instanceof ComplexAttributeDefinition) {
077 ComplexAttributeDefinition cad = (ComplexAttributeDefinition) dddef;
078 ode.getComplexAttributes().add(cad);
079 if (!parentClasses.contains(clazz)) {
080 parentFields.push(dddef);
081 parentClasses.push(clazz);
082 Bean2DictionaryConverter subConverter = new Bean2DictionaryConverter(actualClass, parentFields, parentClasses);
083 subConverter.addFields(debuggingContext + "." + clazz.getSimpleName() + name, ode);
084 parentFields.pop();
085 parentClasses.pop();
086 }
087 } else if (dddef instanceof CollectionDefinition) {
088 CollectionDefinition cd = (CollectionDefinition) dddef;
089 ode.getCollections().add(cd);
090 // TODO: handle collections of primitives
091 // DataType == null means it is a complex
092 // TODO: add back in this logic once they fix the jira about collectoin definition not working right
093 // if (dt == null) {
094 // if (!parentClasses.contains(clazz)) {
095 // parentFields.push(dddef);
096 // parentClasses.push(clazz);
097 // Bean2DictionaryConverter subConverter = new Bean2DictionaryConverter(actualClass, parentFields, parentClasses);
098 // subConverter.addFields(debuggingContext + "." + clazz.getSimpleName() + name, ode);
099 // 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 pt = workAround(clazz, pd.getReadMethod().getName());
172 }
173 // else {
174 // throw new NullPointerException (clazz.getName() + "." + pd.getName() + " has no corresponding read method");
175 // }
176 }
177
178 if (List.class.equals(pt)) {
179 pt = ComplexSubstructuresHelper.getActualClassFromList(clazz, pd.getName());
180 }
181 return pt;
182 }
183
184 private static Class<?> workAround(Class<?> currentTargetClass, String methodName) {
185 Method method = findMethodImplFirst(currentTargetClass, methodName);
186 return method.getReturnType();
187 }
188
189 /**
190 * Got this code from:
191 * http://raulraja.com/2009/09/12/java-beans-introspector-odd-behavio/
192 *
193 * workaround for introspector odd behavior with javabeans that implement interfaces with comaptible return types
194 * but instrospection is unable to find the right accessors
195 *
196 * @param currentTargetClass the class being evaluated
197 * @param methodName the method name we are looking for
198 * @param argTypes the arg types for the method name
199 * @return a method if found
200 */
201 private static Method findMethodImplFirst(Class<?> currentTargetClass, String methodName, Class<?>... argTypes) {
202 Method method = null;
203 if (currentTargetClass != null && methodName != null) {
204 try {
205 method = currentTargetClass.getMethod(methodName, argTypes);
206 } catch (Throwable t) {
207 // nothing we can do but continue
208 }
209 //Is the method in one of our parent classes
210 if (method == null) {
211 Class<?> superclass = currentTargetClass.getSuperclass();
212 if (!superclass.equals(Object.class)) {
213 method = findMethodImplFirst(superclass, methodName, argTypes);
214 }
215 }
216 }
217 return method;
218 }
219
220 public static DataType calcDataType(String context, Class<?> pt) {
221 if (int.class.equals(pt) || Integer.class.equals(pt)) {
222 return DataType.INTEGER;
223 } else if (long.class.equals(pt) || Long.class.equals(pt)) {
224 return DataType.LONG;
225 } else if (double.class.equals(pt) || Double.class.equals(pt)) {
226 return DataType.DOUBLE;
227 } else if (float.class.equals(pt) || Float.class.equals(pt)) {
228 return DataType.FLOAT;
229 } else if (boolean.class.equals(pt) || Boolean.class.equals(pt)) {
230 return DataType.BOOLEAN;
231 } else if (Date.class.equals(pt)) {
232 return DataType.DATE;
233 } else if (String.class.equals(pt)) {
234 return DataType.STRING;
235 } else if (List.class.equals(pt)) {
236 throw new RuntimeException("Found list can't have a list of lists, List<List<?>> in " + context);
237 } else if (Enum.class.isAssignableFrom(pt)) {
238 return DataType.STRING;
239 } else if (Object.class.equals(pt)) {
240 return DataType.STRING;
241 } else if (pt.getName().startsWith("org.kuali.student.")) {
242 return null;
243 } else {
244 throw new RuntimeException("Found unknown/unhandled type of object in bean " + pt.getName() + " in " + context);
245 }
246 }
247 }