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 }