View Javadoc

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      private final static Log LOG = LogFactory.getLog( ReflectionUtils.class );
41  
42      ReflectionUtils() {
43          // Empty.
44      }
45  
46      public static void setFieldValue( Object object, String fieldName, Object value )
47      throws NoSuchFieldException, IllegalAccessException {
48          Defense.notNull( object, "object" );
49          Defense.notNull( fieldName, "fieldName" );
50          for( Class clazz = object.getClass(); clazz != null; clazz = clazz.getSuperclass() ) {
51              for( Field field : clazz.getDeclaredFields() ) {
52                  if( field.getName().equals( fieldName ) ) {
53                      field.setAccessible( true );
54                      field.set( object, value );
55                      return;
56                  }
57              }
58          }
59          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          Defense.notNull( object, "object" );
65          Defense.notNull( fieldName, "fieldName" );
66          for( Class clazz = object.getClass(); clazz != null; clazz = clazz.getSuperclass() ) {
67              for( Field field : clazz.getDeclaredFields() ) {
68                  if( field.getName().equals( fieldName ) ) {
69                      field.setAccessible( true );
70                      Object value = field.get( object );
71                      return value;
72                  }
73              }
74          }
75          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          return getFieldRecursive( clazz, names, 0 );
81      }
82  
83      private static Field getFieldRecursive( Class clazz, String[] names, int level )
84      throws NoSuchFieldException {
85          Field f = clazz.getDeclaredField( names[ level ] );
86          boolean lastLevel = ( names.length == level + 1 );
87          if( lastLevel ) return f;
88          else return getFieldRecursive( f.getType(), names, level + 1 );
89      }
90  
91      public static Object getPropertyValue( Object object, String... names )
92      throws IllegalAccessException, InvocationTargetException {
93          return getPropertyValueRecursive( object, names, 0 );
94      }
95  
96      private static Object getPropertyValueRecursive( Object object, String[] names, int level )
97      throws IllegalAccessException, InvocationTargetException {
98          if( object == null ) return null;
99          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 }