/* * 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}); } } }