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