001 // Copyright 2006-2007 Daniel Gredler 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package net.sf.beanform.util; 016 017 import java.beans.BeanInfo; 018 import java.beans.IntrospectionException; 019 import java.beans.Introspector; 020 import java.beans.PropertyDescriptor; 021 import java.io.IOException; 022 import java.io.InputStream; 023 import java.lang.reflect.Field; 024 import java.lang.reflect.InvocationTargetException; 025 import java.lang.reflect.Method; 026 027 import org.apache.commons.logging.Log; 028 import org.apache.commons.logging.LogFactory; 029 import org.apache.hivemind.ApplicationRuntimeException; 030 import org.apache.hivemind.util.Defense; 031 import org.apache.tapestry.request.IUploadFile; 032 033 /** 034 * Utility class for reflection-related operations. 035 * 036 * @author Daniel Gredler 037 */ 038 public final class ReflectionUtils { 039 040 private final static Log LOG = LogFactory.getLog( ReflectionUtils.class ); 041 042 ReflectionUtils() { 043 // Empty. 044 } 045 046 public static void setFieldValue( Object object, String fieldName, Object value ) 047 throws NoSuchFieldException, IllegalAccessException { 048 Defense.notNull( object, "object" ); 049 Defense.notNull( fieldName, "fieldName" ); 050 for( Class clazz = object.getClass(); clazz != null; clazz = clazz.getSuperclass() ) { 051 for( Field field : clazz.getDeclaredFields() ) { 052 if( field.getName().equals( fieldName ) ) { 053 field.setAccessible( true ); 054 field.set( object, value ); 055 return; 056 } 057 } 058 } 059 throw new NoSuchFieldException( "Unable to find field '" + object.getClass().getName() + "#" + fieldName + "'." ); 060 } 061 062 public static Object getFieldValue( Object object, String fieldName ) 063 throws NoSuchFieldException, IllegalAccessException { 064 Defense.notNull( object, "object" ); 065 Defense.notNull( fieldName, "fieldName" ); 066 for( Class clazz = object.getClass(); clazz != null; clazz = clazz.getSuperclass() ) { 067 for( Field field : clazz.getDeclaredFields() ) { 068 if( field.getName().equals( fieldName ) ) { 069 field.setAccessible( true ); 070 Object value = field.get( object ); 071 return value; 072 } 073 } 074 } 075 throw new NoSuchFieldException( "Unable to find field '" + object.getClass().getName() + "#" + fieldName + "'." ); 076 } 077 078 public static Field getField( Class clazz, String... names ) 079 throws NoSuchFieldException { 080 return getFieldRecursive( clazz, names, 0 ); 081 } 082 083 private static Field getFieldRecursive( Class clazz, String[] names, int level ) 084 throws NoSuchFieldException { 085 Field f = clazz.getDeclaredField( names[ level ] ); 086 boolean lastLevel = ( names.length == level + 1 ); 087 if( lastLevel ) return f; 088 else return getFieldRecursive( f.getType(), names, level + 1 ); 089 } 090 091 public static Object getPropertyValue( Object object, String... names ) 092 throws IllegalAccessException, InvocationTargetException { 093 return getPropertyValueRecursive( object, names, 0 ); 094 } 095 096 private static Object getPropertyValueRecursive( Object object, String[] names, int level ) 097 throws IllegalAccessException, InvocationTargetException { 098 if( object == null ) return null; 099 Class clazz = object.getClass(); 100 PropertyDescriptor pd = getDescriptor( clazz, names[ level ] ); 101 object = pd.getReadMethod().invoke( object ); 102 boolean lastLevel = ( names.length == level + 1 ); 103 if( lastLevel ) return object; 104 else return getPropertyValueRecursive( object, names, level + 1 ); 105 } 106 107 public static void setPropertyValue( Object value, Object object, String... names ) 108 throws IllegalAccessException, InvocationTargetException, InstantiationException { 109 setPropertyValueRecursive( value, object, names, 0 ); 110 } 111 112 private static void setPropertyValueRecursive( Object value, Object object, String[] names, int level ) 113 throws IllegalAccessException, InvocationTargetException, InstantiationException { 114 Class clazz = object.getClass(); 115 PropertyDescriptor pd = getDescriptor( clazz, names[ level ] ); 116 boolean lastLevel = ( names.length == level + 1 ); 117 if( lastLevel ) { 118 Method setter = pd.getWriteMethod(); 119 Class type = setter.getParameterTypes()[ 0 ]; 120 value = convertToType( value, type ); 121 setter.invoke( object, value ); 122 } 123 else { 124 Object nextObject = pd.getReadMethod().invoke( object ); 125 if( nextObject == null ) { 126 if( value != null ) { 127 nextObject = pd.getReadMethod().getReturnType().newInstance(); 128 pd.getWriteMethod().invoke( object, nextObject ); 129 } 130 else { 131 if( LOG.isDebugEnabled() ) { 132 StringBuilder msg = new StringBuilder( "Aborting attempt to set property '" ); 133 for( String name : names ) msg.append( name ).append( '.' ); 134 msg.deleteCharAt( msg.length() - 1 ); 135 msg.append( "' to null: the '" ).append( names[ level ] ); 136 msg.append( "' portion of the property is null." ); 137 LOG.debug( msg ); 138 } 139 return; 140 } 141 } 142 setPropertyValueRecursive( value, nextObject, names, level + 1 ); 143 } 144 } 145 146 public static PropertyDescriptor getPropertyDescriptor( Class clazz, String... names ) { 147 return getPropertyDescriptorRecursive( clazz, names, 0 ); 148 } 149 150 private static PropertyDescriptor getPropertyDescriptorRecursive( Class clazz, String[] names, int level ) { 151 PropertyDescriptor pd = getDescriptor( clazz, names[ level ] ); 152 boolean lastLevel = ( names.length == level + 1 ); 153 if( lastLevel ) return pd; 154 else return getPropertyDescriptorRecursive( pd.getPropertyType(), names, level + 1 ); 155 } 156 157 private static PropertyDescriptor getDescriptor( Class clazz, String name ) { 158 try { 159 BeanInfo info = Introspector.getBeanInfo( clazz, errorTrigger ); 160 PropertyDescriptor[] descriptors = info.getPropertyDescriptors(); 161 for( PropertyDescriptor descriptor : descriptors ) { 162 if( descriptor.getName().equals( name ) ) { 163 return descriptor; 164 } 165 } 166 } 167 catch( IntrospectionException e ) { 168 String msg = ReflectionUtilsMessages.cantFindProperty( name, clazz ); 169 throw new ApplicationRuntimeException( msg, e ); 170 } 171 String msg = ReflectionUtilsMessages.cantFindProperty( name, clazz ); 172 throw new ApplicationRuntimeException( msg ); 173 } 174 175 /** 176 * Converts the specified value to the specified type. Our current use case only 177 * requires that we convert from strings to numbers and from IUploadFile's to byte[]'s, 178 * so this is <b>not</b> a generic type conversion method. If this method is given 179 * a conversion that it is not meant to handle, it just returns the original value. 180 */ 181 private static Object convertToType( Object value, Class type ) { 182 if( value != null && type.isInstance( value ) == false ) { 183 // x -> y 184 if( value instanceof String ) { 185 // string -> y 186 String s = (String) value; 187 if( Short.class.equals( type ) || short.class.equals( type ) ) { 188 // string -> short 189 value = Short.parseShort( s ); 190 } 191 else if( Integer.class.equals( type ) || int.class.equals( type ) ) { 192 // string -> integer 193 value = Integer.parseInt( s ); 194 } 195 else if( Long.class.equals( type ) || long.class.equals( type ) ) { 196 // string -> long 197 value = Long.parseLong( s ); 198 } 199 else if( Float.class.equals( type ) || float.class.equals( type ) ) { 200 // string -> float 201 value = Float.parseFloat( s ); 202 } 203 else if( Double.class.equals( type ) || double.class.equals( type ) ) { 204 // string -> double 205 value = Double.parseDouble( s ); 206 } 207 } 208 else if( value instanceof IUploadFile && byte[].class.equals( type ) ) { 209 // IUploadFile -> byte[] 210 IUploadFile file = (IUploadFile) value; 211 long size = file.getSize(); 212 if( size < 1 ) { 213 value = null; 214 } 215 else if( size > Integer.MAX_VALUE ) { 216 String msg = "File is too large; the maximum allowable size is " + Integer.MAX_VALUE + " bytes."; 217 throw new ApplicationRuntimeException( msg ); 218 } 219 else { 220 byte[] bytes = new byte[ (int) size ]; 221 InputStream input = file.getStream(); 222 try { 223 int read = input.read( bytes ); 224 if( read != size ) { 225 String msg = "Error reading file; expected " + size + " bytes, but got " + read + "."; 226 throw new ApplicationRuntimeException( msg ); 227 } 228 } 229 catch( IOException e ) { 230 String msg = "Error reading file: " + e.getMessage(); 231 throw new ApplicationRuntimeException( msg, e ); 232 } 233 value = bytes; 234 } 235 } 236 } 237 return value; 238 } 239 240 // ------------------------------------------------------------------------------------------------------------ // 241 // ------------------------------------------- unit testing support ------------------------------------------- // 242 // ------------------------------------------------------------------------------------------------------------ // 243 244 private static Class errorTrigger = null; 245 246 /** 247 * Calling this method will cause subsequent {@link #getDescriptor(Class, String)} calls to result 248 * in <tt>IntrospectionException</tt>s that will allow us to test error-handling functionality. 249 */ 250 static void triggerIntrospectionException() { 251 if( errorTrigger != null ) errorTrigger = null; 252 else errorTrigger = BeanInfo.class; 253 } 254 255 }