1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
184 if( value instanceof String ) {
185
186 String s = (String) value;
187 if( Short.class.equals( type ) || short.class.equals( type ) ) {
188
189 value = Short.parseShort( s );
190 }
191 else if( Integer.class.equals( type ) || int.class.equals( type ) ) {
192
193 value = Integer.parseInt( s );
194 }
195 else if( Long.class.equals( type ) || long.class.equals( type ) ) {
196
197 value = Long.parseLong( s );
198 }
199 else if( Float.class.equals( type ) || float.class.equals( type ) ) {
200
201 value = Float.parseFloat( s );
202 }
203 else if( Double.class.equals( type ) || double.class.equals( type ) ) {
204
205 value = Double.parseDouble( s );
206 }
207 }
208 else if( value instanceof IUploadFile && byte[].class.equals( type ) ) {
209
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
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 }