| /* |
| * Copyright 2004,2004 The Apache Software Foundation. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.bsf.util; |
| |
| import java.beans.BeanInfo; |
| import java.beans.Beans; |
| import java.beans.EventSetDescriptor; |
| import java.beans.FeatureDescriptor; |
| import java.beans.IndexedPropertyDescriptor; |
| import java.beans.IntrospectionException; |
| import java.beans.Introspector; |
| import java.beans.PropertyDescriptor; |
| import java.io.IOException; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| |
| import org.apache.bsf.util.event.EventAdapter; |
| import org.apache.bsf.util.event.EventAdapterRegistry; |
| import org.apache.bsf.util.event.EventProcessor; |
| import org.apache.bsf.util.type.TypeConvertor; |
| import org.apache.bsf.util.type.TypeConvertorRegistry; |
| |
| /** |
| * This file is a collection of reflection utilities. There are utilities |
| * for creating beans, getting bean infos, setting/getting properties, |
| * and binding events. |
| * |
| * @author Sanjiva Weerawarana |
| * @author Joseph Kesselman |
| */ |
| public class ReflectionUtils { |
| |
| ////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Add an event processor as a listener to some event coming out of an |
| * object. |
| * |
| * @param source event source |
| * @param eventSetName name of event set from event src to bind to |
| * @param processor event processor the event should be delegated to |
| * when it occurs; either via processEvent or |
| * processExceptionableEvent. |
| * |
| * @exception IntrospectionException if unable to introspect |
| * @exception IllegalArgumentException if event set is unknown |
| * @exception IllegalAccessException if the event adapter class or |
| * initializer is not accessible. |
| * @exception InstantiationException if event adapter instantiation fails |
| * @exception InvocationTargetException if something goes wrong while |
| * running add event listener method |
| */ |
| public static void addEventListener (Object source, String eventSetName, |
| EventProcessor processor) |
| throws IntrospectionException, IllegalArgumentException, |
| IllegalAccessException, InstantiationException, |
| InvocationTargetException { |
| // find the event set descriptor for this event |
| BeanInfo bi = Introspector.getBeanInfo (source.getClass ()); |
| EventSetDescriptor esd = (EventSetDescriptor) |
| findFeatureByName ("event", eventSetName, bi.getEventSetDescriptors ()); |
| |
| if (esd == null) // no events found, maybe a proxy from OpenOffice.org? |
| { |
| throw new IllegalArgumentException ("event set '" + eventSetName + |
| "' unknown for source type '" + source.getClass () + "'"); |
| } |
| |
| // get the class object for the event |
| Class listenerType=esd.getListenerType(); // get ListenerType class object from EventSetDescriptor |
| |
| // find an event adapter class of the right type |
| Class adapterClass = EventAdapterRegistry.lookup (listenerType); |
| if (adapterClass == null) { |
| throw new IllegalArgumentException ("event adapter for listener type " + |
| "'" + listenerType + "' (eventset " + |
| "'" + eventSetName + "') unknown"); |
| } |
| |
| // create the event adapter and give it the event processor |
| EventAdapter adapter = (EventAdapter) adapterClass.newInstance (); |
| adapter.setEventProcessor (processor); |
| |
| // bind the adapter to the source bean |
| Method addListenerMethod; |
| Object[] args; |
| if (eventSetName.equals ("propertyChange") || |
| eventSetName.equals ("vetoableChange")) { |
| // In Java 1.2, beans may have direct listener adding methods |
| // for property and vetoable change events which take the |
| // property name as a filter to be applied at the event source. |
| // The filter property of the event processor should be used |
| // in this case to support the source-side filtering. |
| // |
| // ** TBD **: the following two lines need to change appropriately |
| addListenerMethod = esd.getAddListenerMethod (); |
| args = new Object[] {adapter}; |
| } |
| else |
| { |
| addListenerMethod = esd.getAddListenerMethod (); |
| args = new Object[] {adapter}; |
| } |
| addListenerMethod.invoke (source, args); |
| } |
| ////////////////////////////////////////////////////////////////////////// |
| |
| |
| /** |
| * Create a bean using given class loader and using the appropriate |
| * constructor for the given args of the given arg types. |
| |
| * @param cld the class loader to use. If null, Class.forName is used. |
| * @param className name of class to instantiate |
| * @param argTypes array of argument types |
| * @param args array of arguments |
| * |
| * @return the newly created bean |
| * |
| * @exception ClassNotFoundException if class is not loaded |
| * @exception NoSuchMethodException if constructor can't be found |
| * @exception InstantiationException if class can't be instantiated |
| * @exception IllegalAccessException if class is not accessible |
| * @exception IllegalArgumentException if argument problem |
| * @exception InvocationTargetException if constructor excepted |
| * @exception IOException if I/O error in beans.instantiate |
| */ |
| public static Bean createBean (ClassLoader cld, String className, |
| Class[] argTypes, Object[] args) |
| throws ClassNotFoundException, NoSuchMethodException, |
| InstantiationException, IllegalAccessException, |
| IllegalArgumentException, InvocationTargetException, |
| IOException { |
| if (argTypes != null) { |
| // find the right constructor and use that to create bean |
| Class cl = (cld != null) ? cld.loadClass (className) |
| : Thread.currentThread().getContextClassLoader().loadClass (className); // rgf, 2006-01-05 |
| // : Class.forName (className); |
| |
| Constructor c = MethodUtils.getConstructor (cl, argTypes); |
| return new Bean (cl, c.newInstance (args)); |
| } else { |
| // create the bean with no args constructor |
| Object obj = Beans.instantiate (cld, className); |
| return new Bean (obj.getClass (), obj); |
| } |
| } |
| ////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Create a bean using given class loader and using the appropriate |
| * constructor for the given args. Figures out the arg types and |
| * calls above. |
| |
| * @param cld the class loader to use. If null, Class.forName is used. |
| * @param className name of class to instantiate |
| * @param args array of arguments |
| * |
| * @return the newly created bean |
| * |
| * @exception ClassNotFoundException if class is not loaded |
| * @exception NoSuchMethodException if constructor can't be found |
| * @exception InstantiationException if class can't be instantiated |
| * @exception IllegalAccessException if class is not accessible |
| * @exception IllegalArgumentException if argument problem |
| * @exception InvocationTargetException if constructor excepted |
| * @exception IOException if I/O error in beans.instantiate |
| */ |
| public static Bean createBean (ClassLoader cld, String className, |
| Object[] args) |
| throws ClassNotFoundException, NoSuchMethodException, |
| InstantiationException, IllegalAccessException, |
| IllegalArgumentException, InvocationTargetException, |
| IOException { |
| Class[] argTypes = null; |
| if (args != null) { |
| argTypes = new Class[args.length]; |
| for (int i = 0; i < args.length; i++) { |
| argTypes[i] = (args[i] != null) ? args[i].getClass () : null; |
| } |
| } |
| return createBean (cld, className, argTypes, args); |
| } |
| ////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * locate the item in the fds array whose name is as given. returns |
| * null if not found. |
| */ |
| private static |
| FeatureDescriptor findFeatureByName (String featureType, String name, |
| FeatureDescriptor[] fds) { |
| for (int i = 0; i < fds.length; i++) { |
| if (name.equals (fds[i].getName())) { |
| return fds[i]; |
| } |
| } |
| return null; |
| } |
| public static Bean getField (Object target, String fieldName) |
| throws IllegalArgumentException, IllegalAccessException { |
| // This is to handle how we do static fields. |
| Class targetClass = (target instanceof Class) |
| ? (Class) target |
| : target.getClass (); |
| |
| try { |
| Field f = targetClass.getField (fieldName); |
| Class fieldType = f.getType (); |
| |
| // Get the value and return it. |
| Object value = f.get (target); |
| return new Bean (fieldType, value); |
| } catch (NoSuchFieldException e) { |
| throw new IllegalArgumentException ("field '" + fieldName + "' is " + |
| "unknown for '" + target + "'"); |
| } |
| } |
| ////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Get a property of a bean. |
| * |
| * @param target the object whose prop is to be gotten |
| * @param propName name of the property to set |
| * @param index index to get (if property is indexed) |
| * |
| * @exception IntrospectionException if unable to introspect |
| * @exception IllegalArgumentException if problems with args: if the |
| * property is unknown, or if the property is given an index |
| * when its not, or if the property is not writeable, or if |
| * the given value cannot be assigned to the it (type mismatch). |
| * @exception IllegalAccessException if read method is not accessible |
| * @exception InvocationTargetException if read method excepts |
| */ |
| public static Bean getProperty (Object target, String propName, |
| Integer index) |
| throws IntrospectionException, IllegalArgumentException, |
| IllegalAccessException, InvocationTargetException { |
| // find the property descriptor |
| BeanInfo bi = Introspector.getBeanInfo (target.getClass ()); |
| PropertyDescriptor pd = (PropertyDescriptor) |
| findFeatureByName ("property", propName, bi.getPropertyDescriptors ()); |
| if (pd == null) { |
| throw new IllegalArgumentException ("property '" + propName + "' is " + |
| "unknown for '" + target + "'"); |
| } |
| |
| // get read method and type of property |
| Method rm; |
| Class propType; |
| if (index != null) { |
| // if index != null, then property is indexed - pd better be so too |
| if (!(pd instanceof IndexedPropertyDescriptor)) { |
| throw new IllegalArgumentException ("attempt to get non-indexed " + |
| "property '" + propName + |
| "' as being indexed"); |
| } |
| IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd; |
| rm = ipd.getIndexedReadMethod (); |
| propType = ipd.getIndexedPropertyType (); |
| } else { |
| rm = pd.getReadMethod (); |
| propType = pd.getPropertyType (); |
| } |
| |
| if (rm == null) { |
| throw new IllegalArgumentException ("property '" + propName + |
| "' is not readable"); |
| } |
| |
| // now get the value |
| Object propVal = null; |
| if (index != null) { |
| propVal = rm.invoke (target, new Object[] {index}); |
| } else { |
| propVal = rm.invoke (target, null); |
| } |
| return new Bean (propType, propVal); |
| } |
| public static void setField (Object target, String fieldName, Bean value, |
| TypeConvertorRegistry tcr) |
| throws IllegalArgumentException, IllegalAccessException { |
| // This is to handle how we do static fields. |
| Class targetClass = (target instanceof Class) |
| ? (Class) target |
| : target.getClass (); |
| |
| try { |
| Field f = targetClass.getField (fieldName); |
| Class fieldType = f.getType (); |
| |
| // type convert the value if necessary |
| Object fieldVal = null; |
| boolean okeydokey = true; |
| if (fieldType.isAssignableFrom (value.type)) { |
| fieldVal = value.value; |
| } else if (tcr != null) { |
| TypeConvertor cvtor = tcr.lookup (value.type, fieldType); |
| if (cvtor != null) { |
| fieldVal = cvtor.convert (value.type, fieldType, value.value); |
| } else { |
| okeydokey = false; |
| } |
| } else { |
| okeydokey = false; |
| } |
| if (!okeydokey) { |
| throw new IllegalArgumentException ("unable to assign '" + value.value + |
| "' to field '" + fieldName + "'"); |
| } |
| |
| // now set the value |
| f.set (target, fieldVal); |
| } catch (NoSuchFieldException e) { |
| throw new IllegalArgumentException ("field '" + fieldName + "' is " + |
| "unknown for '" + target + "'"); |
| } |
| } |
| ////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Set a property of a bean to a given value. |
| * |
| * @param target the object whose prop is to be set |
| * @param propName name of the property to set |
| * @param index index to set (if property is indexed) |
| * @param value the property value |
| * @param valueType the type of the above (needed when its null) |
| * @param tcr type convertor registry to use to convert value type to |
| * property type if necessary |
| * |
| * @exception IntrospectionException if unable to introspect |
| * @exception IllegalArgumentException if problems with args: if the |
| * property is unknown, or if the property is given an index |
| * when its not, or if the property is not writeable, or if |
| * the given value cannot be assigned to the it (type mismatch). |
| * @exception IllegalAccessException if write method is not accessible |
| * @exception InvocationTargetException if write method excepts |
| */ |
| public static void setProperty (Object target, String propName, |
| Integer index, Object value, |
| Class valueType, TypeConvertorRegistry tcr) |
| throws IntrospectionException, IllegalArgumentException, |
| IllegalAccessException, InvocationTargetException { |
| // find the property descriptor |
| BeanInfo bi = Introspector.getBeanInfo (target.getClass ()); |
| PropertyDescriptor pd = (PropertyDescriptor) |
| findFeatureByName ("property", propName, bi.getPropertyDescriptors ()); |
| if (pd == null) { |
| throw new IllegalArgumentException ("property '" + propName + "' is " + |
| "unknown for '" + target + "'"); |
| } |
| |
| // get write method and type of property |
| Method wm; |
| Class propType; |
| if (index != null) { |
| // if index != null, then property is indexed - pd better be so too |
| if (!(pd instanceof IndexedPropertyDescriptor)) { |
| throw new IllegalArgumentException ("attempt to set non-indexed " + |
| "property '" + propName + |
| "' as being indexed"); |
| } |
| IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd; |
| wm = ipd.getIndexedWriteMethod (); |
| propType = ipd.getIndexedPropertyType (); |
| } else { |
| wm = pd.getWriteMethod (); |
| propType = pd.getPropertyType (); |
| } |
| |
| if (wm == null) { |
| throw new IllegalArgumentException ("property '" + propName + |
| "' is not writeable"); |
| } |
| |
| // type convert the value if necessary |
| Object propVal = null; |
| boolean okeydokey = true; |
| if (propType.isAssignableFrom (valueType)) { |
| propVal = value; |
| } else if (tcr != null) { |
| TypeConvertor cvtor = tcr.lookup (valueType, propType); |
| if (cvtor != null) { |
| propVal = cvtor.convert (valueType, propType, value); |
| } else { |
| okeydokey = false; |
| } |
| } else { |
| okeydokey = false; |
| } |
| if (!okeydokey) { |
| throw new IllegalArgumentException ("unable to assign '" + value + |
| "' to property '" + propName + "'"); |
| } |
| |
| // now set the value |
| if (index != null) { |
| wm.invoke (target, new Object[] {index, propVal}); |
| } else { |
| wm.invoke (target, new Object[] {propVal}); |
| } |
| } |
| } |