Coverage Report - net.sf.beanform.util.ReflectionUtils
 
Classes in this File Line Coverage Branch Coverage Complexity
ReflectionUtils
100% 
100% 
0
 
 1  
 // Copyright 2006 Daniel Gredler
 2  
 //
 3  
 // Licensed under the Apache License, Version 2.0 (the "License");
 4  
 // you may not use this file except in compliance with the License.
 5  
 // You may obtain a copy of the License at
 6  
 //
 7  
 //     http://www.apache.org/licenses/LICENSE-2.0
 8  
 //
 9  
 // Unless required by applicable law or agreed to in writing, software
 10  
 // distributed under the License is distributed on an "AS IS" BASIS,
 11  
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  
 // See the License for the specific language governing permissions and
 13  
 // limitations under the License.
 14  
 
 15  
 package net.sf.beanform.util;
 16  
 
 17  
 import java.beans.BeanInfo;
 18  
 import java.beans.IntrospectionException;
 19  
 import java.beans.Introspector;
 20  
 import java.beans.PropertyDescriptor;
 21  
 import java.io.IOException;
 22  
 import java.io.InputStream;
 23  
 import java.lang.reflect.Field;
 24  
 import java.lang.reflect.InvocationTargetException;
 25  
 import java.lang.reflect.Method;
 26  
 
 27  
 import org.apache.commons.logging.Log;
 28  
 import org.apache.commons.logging.LogFactory;
 29  
 import org.apache.hivemind.ApplicationRuntimeException;
 30  
 import org.apache.hivemind.util.Defense;
 31  
 import org.apache.tapestry.request.IUploadFile;
 32  
 
 33  
 /**
 34  
  * Utility class for reflection-related operations.
 35  
  *
 36  
  * @author Daniel Gredler
 37  
  */
 38  
 public final class ReflectionUtils {
 39  
 
 40  1
     private final static Log LOG = LogFactory.getLog( ReflectionUtils.class );
 41  
 
 42  1
     ReflectionUtils() {
 43  
         // Empty.
 44  1
     }
 45  
 
 46  
     public static void setFieldValue( Object object, String fieldName, Object value )
 47  
     throws NoSuchFieldException, IllegalAccessException {
 48  11
         Defense.notNull( object, "object" );
 49  11
         Defense.notNull( fieldName, "fieldName" );
 50  21
         for( Class clazz = object.getClass(); clazz != null; clazz = clazz.getSuperclass() ) {
 51  188
             for( Field field : clazz.getDeclaredFields() ) {
 52  178
                 if( field.getName().equals( fieldName ) ) {
 53  10
                     field.setAccessible( true );
 54  10
                     field.set( object, value );
 55  10
                     return;
 56  
                 }
 57  
             }
 58  
         }
 59  1
         throw new NoSuchFieldException( "Unable to find field '" + object.getClass().getName() + "#" + fieldName + "'." );
 60  
     }
 61  
 
 62  
     public static Object getFieldValue( Object object, String fieldName )
 63  
     throws NoSuchFieldException, IllegalAccessException {
 64  8
         Defense.notNull( object, "object" );
 65  8
         Defense.notNull( fieldName, "fieldName" );
 66  16
         for( Class clazz = object.getClass(); clazz != null; clazz = clazz.getSuperclass() ) {
 67  196
             for( Field field : clazz.getDeclaredFields() ) {
 68  188
                 if( field.getName().equals( fieldName ) ) {
 69  7
                     field.setAccessible( true );
 70  7
                     Object value = field.get( object );
 71  7
                     return value;
 72  
                 }
 73  
             }
 74  
         }
 75  1
         throw new NoSuchFieldException( "Unable to find field '" + object.getClass().getName() + "#" + fieldName + "'." );
 76  
     }
 77  
 
 78  
     public static Field getField( Class clazz, String... names )
 79  
     throws NoSuchFieldException {
 80  221
         return getFieldRecursive( clazz, names, 0 );
 81  
     }
 82  
 
 83  
     private static Field getFieldRecursive( Class clazz, String[] names, int level )
 84  
     throws NoSuchFieldException {
 85  248
         Field f = clazz.getDeclaredField( names[ level ] );
 86  204
         boolean lastLevel = ( names.length == level + 1 );
 87  204
         if( lastLevel ) return f;
 88  27
         else return getFieldRecursive( f.getType(), names, level + 1 );
 89  
     }
 90  
 
 91  
     public static Object getPropertyValue( Object object, String... names )
 92  
     throws IllegalAccessException, InvocationTargetException {
 93  3
         return getPropertyValueRecursive( object, names, 0 );
 94  
     }
 95  
 
 96  
     private static Object getPropertyValueRecursive( Object object, String[] names, int level )
 97  
     throws IllegalAccessException, InvocationTargetException {
 98  7
         if( object == null ) return null;
 99  7
         Class clazz = object.getClass();
 100  7
         PropertyDescriptor pd = getDescriptor( clazz, names[ level ] );
 101  7
         object = pd.getReadMethod().invoke( object );
 102  7
         boolean lastLevel = ( names.length == level + 1 );
 103  7
         if( lastLevel ) return object;
 104  4
         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  15
         setPropertyValueRecursive( value, object, names, 0 );
 110  12
     }
 111  
 
 112  
     private static void setPropertyValueRecursive( Object value, Object object, String[] names, int level )
 113  
     throws IllegalAccessException, InvocationTargetException, InstantiationException {
 114  21
         Class clazz = object.getClass();
 115  21
         PropertyDescriptor pd = getDescriptor( clazz, names[ level ] );
 116  21
         boolean lastLevel = ( names.length == level + 1 );
 117  21
         if( lastLevel ) {
 118  14
             Method setter = pd.getWriteMethod();
 119  14
             Class type = setter.getParameterTypes()[ 0 ];
 120  14
             value = convertToType( value, type );
 121  11
             setter.invoke( object, value );
 122  11
         }
 123  
         else {
 124  7
             Object nextObject = pd.getReadMethod().invoke( object );
 125  7
             if( nextObject == null ) {
 126  3
                 if( value != null ) {
 127  2
                     nextObject = pd.getReadMethod().getReturnType().newInstance();
 128  2
                     pd.getWriteMethod().invoke( object, nextObject );
 129  2
                 }
 130  
                 else {
 131  1
                     if( LOG.isDebugEnabled() ) {
 132  1
                         StringBuilder msg = new StringBuilder( "Aborting attempt to set property '" );
 133  1
                         for( String name : names ) msg.append( name ).append( '.' );
 134  1
                         msg.deleteCharAt( msg.length() - 1 );
 135  1
                         msg.append( "' to null: the '" ).append( names[ level ] );
 136  1
                         msg.append( "' portion of the property is null." );
 137  1
                         LOG.debug( msg );
 138  
                     }
 139  1
                     return;
 140  
                 }
 141  
             }
 142  6
             setPropertyValueRecursive( value, nextObject, names, level + 1 );
 143  
         }
 144  17
     }
 145  
 
 146  
     public static PropertyDescriptor getPropertyDescriptor( Class clazz, String... names ) {
 147  193
         return getPropertyDescriptorRecursive( clazz, names, 0 );
 148  
     }
 149  
 
 150  
     private static PropertyDescriptor getPropertyDescriptorRecursive( Class clazz, String[] names, int level ) {
 151  222
         PropertyDescriptor pd = getDescriptor( clazz, names[ level ] );
 152  220
         boolean lastLevel = ( names.length == level + 1 );
 153  220
         if( lastLevel ) return pd;
 154  29
         else return getPropertyDescriptorRecursive( pd.getPropertyType(), names, level + 1 );
 155  
     }
 156  
 
 157  
     private static PropertyDescriptor getDescriptor( Class clazz, String name ) {
 158  
         try {
 159  250
             BeanInfo info = Introspector.getBeanInfo( clazz, errorTrigger );
 160  249
             PropertyDescriptor[] descriptors = info.getPropertyDescriptors();
 161  1632
             for( PropertyDescriptor descriptor : descriptors ) {
 162  1631
                 if( descriptor.getName().equals( name ) ) {
 163  248
                     return descriptor;
 164  
                 }
 165  
             }
 166  
         }
 167  1
         catch( IntrospectionException e ) {
 168  1
             String msg = ReflectionUtilsMessages.cantFindProperty( name, clazz );
 169  1
             throw new ApplicationRuntimeException( msg, e );
 170  1
         }
 171  1
         String msg = ReflectionUtilsMessages.cantFindProperty( name, clazz );
 172  1
         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  14
         if( value != null && type.isInstance( value ) == false ) {
 183  
             // x -> y
 184  10
             if( value instanceof String ) {
 185  
                 // string -> y
 186  5
                 String s = (String) value;
 187  5
                 if( Short.class.equals( type ) || short.class.equals( type ) ) {
 188  
                     // string -> short
 189  1
                     value = Short.parseShort( s );
 190  1
                 }
 191  4
                 else if( Integer.class.equals( type ) || int.class.equals( type ) ) {
 192  
                     // string -> integer
 193  1
                     value = Integer.parseInt( s );
 194  1
                 }
 195  3
                 else if( Long.class.equals( type ) || long.class.equals( type ) ) {
 196  
                     // string -> long
 197  1
                     value = Long.parseLong( s );
 198  1
                 }
 199  2
                 else if( Float.class.equals( type ) || float.class.equals( type ) ) {
 200  
                     // string -> float
 201  1
                     value = Float.parseFloat( s );
 202  1
                 }
 203  1
                 else if( Double.class.equals( type ) || double.class.equals( type ) ) {
 204  
                     // string -> double
 205  1
                     value = Double.parseDouble( s );
 206  
                 }
 207  5
             }
 208  5
             else if( value instanceof IUploadFile && byte[].class.equals( type ) ) {
 209  
                 // IUploadFile -> byte[]
 210  5
                 IUploadFile file = (IUploadFile) value;
 211  5
                 long size = file.getSize();
 212  5
                 if( size < 1 ) {
 213  1
                     value = null;
 214  1
                 }
 215  4
                 else if( size > Integer.MAX_VALUE ) {
 216  1
                     String msg = "File is too large; the maximum allowable size is " + Integer.MAX_VALUE + " bytes.";
 217  1
                     throw new ApplicationRuntimeException( msg );
 218  
                 }
 219  
                 else {
 220  3
                     byte[] bytes = new byte[ (int) size ];
 221  3
                     InputStream input = file.getStream();
 222  
                     try {
 223  3
                         int read = input.read( bytes );
 224  2
                         if( read != size ) {
 225  1
                             String msg = "Error reading file; expected " + size + " bytes, but got " + read + ".";
 226  1
                             throw new ApplicationRuntimeException( msg );
 227  
                         }
 228  
                     }
 229  1
                     catch( IOException e ) {
 230  1
                         String msg = "Error reading file: " + e.getMessage();
 231  1
                         throw new ApplicationRuntimeException( msg, e );
 232  1
                     }
 233  1
                     value = bytes;
 234  
                 }
 235  
             }
 236  
         }
 237  11
         return value;
 238  
     }
 239  
 
 240  
     // ------------------------------------------------------------------------------------------------------------ //
 241  
     // ------------------------------------------- unit testing support ------------------------------------------- //
 242  
     // ------------------------------------------------------------------------------------------------------------ //
 243  
 
 244  1
     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  2
         if( errorTrigger != null ) errorTrigger = null;
 252  1
         else errorTrigger = BeanInfo.class;
 253  2
     }
 254  
 
 255  
 }