001 /** 002 * Copyright 2004-2013 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.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/ecl2.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 }