blob: e72dded4fc0ab606dac9de693a9d60692f41d2a7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.bsf.BSFManager; // rgf, 20070917
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 java.util.TreeSet;
import java.util.Comparator;
import java.util.Iterator;
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
*/
/* 2007-09-21: Rony G. Flatscher, new class loading sequence:
- supplied class loader (given as an argument)
- Thread's context class loader
- BSFManager's defining class loader
2011-10-29: Rony G. Flatscher, in case an event is not found, create a
user-friendly error message that lists all available event names
2011-10-29: Rony G. Flatscher, make sure that the context class loader
is used only, if not null
*/
public class ReflectionUtils {
// rgf, 20070921: class loaders that we might need to load classes
static ClassLoader bsfManagerDefinedCL=BSFManager.getDefinedClassLoader();
//////////////////////////////////////////////////////////////////////////
/**
* 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 arrESD[]=bi.getEventSetDescriptors ();
EventSetDescriptor esd=(EventSetDescriptor) findFeatureByName ("event", eventSetName, arrESD);
if (esd == null) // no events found, maybe a proxy from OpenOffice.org?
{
String errMsg="event set '" + eventSetName +"' unknown for source type '" + source.getClass () + "': ";
if (arrESD.length==0) // no event sets found in class!
{
errMsg=errMsg+"class does not implement any event methods following Java's event pattern!";
}
else
{
// errMsg=errMsg+"class defines the following event set(s): {";
errMsg=errMsg+"class defines the following event set(s): ";
// sort ESD by Name
TreeSet ts=new TreeSet(new Comparator () {
public int compare(Object o1, Object o2) {return ((EventSetDescriptor)o1).getName().compareToIgnoreCase(((EventSetDescriptor)o2).getName());}
public boolean equals(Object o1, Object o2) {return ((EventSetDescriptor)o1).getName().equalsIgnoreCase (((EventSetDescriptor)o2).getName());}
});
for (int i=0;i<arrESD.length;i++)
{
ts.add(arrESD[i]);
}
Iterator it=ts.iterator(); // get iterator
int i=0;
while (it.hasNext()) // iterate in sorted order
{
EventSetDescriptor tmpESD=(EventSetDescriptor) it.next();
if (i>0)
{
errMsg=errMsg+", ";
}
errMsg=errMsg+"\n\t"+'\''+tmpESD.getName()+"'={"; // event set name
// iterate over listener methods and display their names in sorted order
Method m[]=tmpESD.getListenerMethods();
TreeSet tsM=new TreeSet(new Comparator () {
public int compare(Object o1, Object o2) {return ((Method)o1).getName().compareToIgnoreCase(((Method)o2).getName());}
public boolean equals(Object o1, Object o2) {return ((Method)o1).getName().equalsIgnoreCase (((Method)o2).getName());}
});
for (int j=0;j<m.length;j++)
{
tsM.add(m[j]);
}
Iterator itM=tsM.iterator();
int j=0;
while (itM.hasNext())
{
if (j>0)
{
errMsg=errMsg+',';
}
errMsg=errMsg+'\''+((Method) itM.next()).getName()+'\'';
j++;
}
errMsg=errMsg+'}'; // close event method set
i++;
}
errMsg=errMsg+"."; // close set of event sets
}
throw new IllegalArgumentException (errMsg);
}
// 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) {
// if class loader given, use that one, else try
// the Thread's context class loader (if set) and then
// the BSFMananger defining class loader
Class cl=null;
ClassNotFoundException exCTX=null;
// -----------------------------
if (cld != null) { // class loader supplied as argument
try { // CL passed as argument
cl=cld.loadClass(className);
}
catch (ClassNotFoundException e02) {
exCTX=e02;
}
}
if (cl==null) {
// load context class loader, only use it, if not null
ClassLoader tccl=Thread.currentThread().getContextClassLoader();
if (tccl!=null) {
try { // CTXCL
cl=tccl.loadClass(className);
}
catch (ClassNotFoundException e01) {}
}
}
if (cl==null) { // class not loaded yet
// defined CL
if (cld != bsfManagerDefinedCL) { // if not used already, attempt to load
cl=bsfManagerDefinedCL.loadClass(className);
}
else { // classloader was already used, hence re-throw exception
throw exCTX; // re-throw very first exception
}
}
// -----------------------------
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});
}
}
}