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 }