blob: 9dcf08516807732114d44f9e43d7e2ca7dc2ae21 [file] [log] [blame]
/*
* 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});
}
}
}