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 import org.slf4j.Logger;
033 import org.slf4j.LoggerFactory;
034
035 public class Bean2DictionaryConverter {
036
037 private static final Logger log = LoggerFactory.getLogger(Bean2DictionaryConverter.class);
038
039 private Class<?> clazz;
040 private Stack<DataDictionaryDefinitionBase> parentFields;
041 private Stack<Class<?>> parentClasses;
042
043 public Bean2DictionaryConverter(Class<?> clazz, Stack<DataDictionaryDefinitionBase> parentFields, Stack<Class<?>> parentClasses) {
044 this.clazz = clazz;
045 this.parentFields = parentFields;
046 this.parentClasses = parentClasses;
047 }
048
049 public DataObjectEntry convert() {
050 DataObjectEntry ode = new DataObjectEntry();
051 ode.setDataObjectClass(clazz);
052 addFields("", ode);
053 return ode;
054 }
055
056 public void addFields(String debuggingContext, DataObjectEntry ode) {
057 BeanInfo beanInfo;
058 try {
059 beanInfo = Introspector.getBeanInfo(clazz);
060 } catch (IntrospectionException ex) {
061 throw new RuntimeException(ex);
062 }
063 for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
064 if (Class.class.equals(pd.getPropertyType())) {
065 continue;
066 }
067 if ("_futureElements".equals(pd.getName())) {
068 continue;
069 }
070 if ("attributes".equals(pd.getName())) {
071 continue;
072 }
073 String name = calcName(pd.getName());
074 Class<?> actualClass = calcActualClass(clazz, pd);
075 DataType dt = calcDataType(debuggingContext + "." + clazz.getSimpleName() + "." + name, actualClass);
076 DataDictionaryDefinitionBase dddef = calcDataDictionaryDefinition(pd, dt);
077 if (dddef instanceof AttributeDefinition) {
078 AttributeDefinition ad = (AttributeDefinition) dddef;
079 ode.getAttributes().add(ad);
080 } else if (dddef instanceof ComplexAttributeDefinition) {
081 ComplexAttributeDefinition cad = (ComplexAttributeDefinition) dddef;
082 ode.getComplexAttributes().add(cad);
083 if (!parentClasses.contains(clazz)) {
084 parentFields.push(dddef);
085 parentClasses.push(clazz);
086 Bean2DictionaryConverter subConverter = new Bean2DictionaryConverter(actualClass, parentFields, parentClasses);
087 subConverter.addFields(debuggingContext + "." + clazz.getSimpleName() + name, ode);
088 parentFields.pop();
089 parentClasses.pop();
090 }
091 } else if (dddef instanceof CollectionDefinition) {
092 CollectionDefinition cd = (CollectionDefinition) dddef;
093 ode.getCollections().add(cd);
094 // TODO: handle collections of primitives
095 // DataType == null means it is a complex
096 // TODO: add back in this logic once they fix the jira about collectoin definition not working right
097 // if (dt == null) {
098 // if (!parentClasses.contains(clazz)) {
099 // 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 }