001: /*
002: * Copyright 2004,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.bsf.util;
018:
019: import java.beans.BeanInfo;
020: import java.beans.Beans;
021: import java.beans.EventSetDescriptor;
022: import java.beans.FeatureDescriptor;
023: import java.beans.IndexedPropertyDescriptor;
024: import java.beans.IntrospectionException;
025: import java.beans.Introspector;
026: import java.beans.PropertyDescriptor;
027: import java.io.IOException;
028: import java.lang.reflect.Constructor;
029: import java.lang.reflect.Field;
030: import java.lang.reflect.InvocationTargetException;
031: import java.lang.reflect.Method;
032:
033: import org.apache.bsf.util.event.EventAdapter;
034: import org.apache.bsf.util.event.EventAdapterRegistry;
035: import org.apache.bsf.util.event.EventProcessor;
036: import org.apache.bsf.util.type.TypeConvertor;
037: import org.apache.bsf.util.type.TypeConvertorRegistry;
038:
039: /**
040: * This file is a collection of reflection utilities. There are utilities
041: * for creating beans, getting bean infos, setting/getting properties,
042: * and binding events.
043: *
044: * @author Sanjiva Weerawarana
045: * @author Joseph Kesselman
046: */
047: public class ReflectionUtils {
048:
049: //////////////////////////////////////////////////////////////////////////
050:
051: /**
052: * Add an event processor as a listener to some event coming out of an
053: * object.
054: *
055: * @param source event source
056: * @param eventSetName name of event set from event src to bind to
057: * @param processor event processor the event should be delegated to
058: * when it occurs; either via processEvent or
059: * processExceptionableEvent.
060: *
061: * @exception IntrospectionException if unable to introspect
062: * @exception IllegalArgumentException if event set is unknown
063: * @exception IllegalAccessException if the event adapter class or
064: * initializer is not accessible.
065: * @exception InstantiationException if event adapter instantiation fails
066: * @exception InvocationTargetException if something goes wrong while
067: * running add event listener method
068: */
069: public static void addEventListener(Object source,
070: String eventSetName, EventProcessor processor)
071: throws IntrospectionException, IllegalArgumentException,
072: IllegalAccessException, InstantiationException,
073: InvocationTargetException {
074: // find the event set descriptor for this event
075: BeanInfo bi = Introspector.getBeanInfo(source.getClass());
076: EventSetDescriptor esd = (EventSetDescriptor) findFeatureByName(
077: "event", eventSetName, bi.getEventSetDescriptors());
078:
079: if (esd == null) // no events found, maybe a proxy from OpenOffice.org?
080: {
081: throw new IllegalArgumentException("event set '"
082: + eventSetName + "' unknown for source type '"
083: + source.getClass() + "'");
084: }
085:
086: // get the class object for the event
087: Class listenerType = esd.getListenerType(); // get ListenerType class object from EventSetDescriptor
088:
089: // find an event adapter class of the right type
090: Class adapterClass = EventAdapterRegistry.lookup(listenerType);
091: if (adapterClass == null) {
092: throw new IllegalArgumentException(
093: "event adapter for listener type " + "'"
094: + listenerType + "' (eventset " + "'"
095: + eventSetName + "') unknown");
096: }
097:
098: // create the event adapter and give it the event processor
099: EventAdapter adapter = (EventAdapter) adapterClass
100: .newInstance();
101: adapter.setEventProcessor(processor);
102:
103: // bind the adapter to the source bean
104: Method addListenerMethod;
105: Object[] args;
106: if (eventSetName.equals("propertyChange")
107: || eventSetName.equals("vetoableChange")) {
108: // In Java 1.2, beans may have direct listener adding methods
109: // for property and vetoable change events which take the
110: // property name as a filter to be applied at the event source.
111: // The filter property of the event processor should be used
112: // in this case to support the source-side filtering.
113: //
114: // ** TBD **: the following two lines need to change appropriately
115: addListenerMethod = esd.getAddListenerMethod();
116: args = new Object[] { adapter };
117: } else {
118: addListenerMethod = esd.getAddListenerMethod();
119: args = new Object[] { adapter };
120: }
121: addListenerMethod.invoke(source, args);
122: }
123:
124: //////////////////////////////////////////////////////////////////////////
125:
126: /**
127: * Create a bean using given class loader and using the appropriate
128: * constructor for the given args of the given arg types.
129:
130: * @param cld the class loader to use. If null, Class.forName is used.
131: * @param className name of class to instantiate
132: * @param argTypes array of argument types
133: * @param args array of arguments
134: *
135: * @return the newly created bean
136: *
137: * @exception ClassNotFoundException if class is not loaded
138: * @exception NoSuchMethodException if constructor can't be found
139: * @exception InstantiationException if class can't be instantiated
140: * @exception IllegalAccessException if class is not accessible
141: * @exception IllegalArgumentException if argument problem
142: * @exception InvocationTargetException if constructor excepted
143: * @exception IOException if I/O error in beans.instantiate
144: */
145: public static Bean createBean(ClassLoader cld, String className,
146: Class[] argTypes, Object[] args)
147: throws ClassNotFoundException, NoSuchMethodException,
148: InstantiationException, IllegalAccessException,
149: IllegalArgumentException, InvocationTargetException,
150: IOException {
151: if (argTypes != null) {
152: // find the right constructor and use that to create bean
153: Class cl = (cld != null) ? cld.loadClass(className)
154: : Thread.currentThread().getContextClassLoader()
155: .loadClass(className); // rgf, 2006-01-05
156: // : Class.forName (className);
157:
158: Constructor c = MethodUtils.getConstructor(cl, argTypes);
159: return new Bean(cl, c.newInstance(args));
160: } else {
161: // create the bean with no args constructor
162: Object obj = Beans.instantiate(cld, className);
163: return new Bean(obj.getClass(), obj);
164: }
165: }
166:
167: //////////////////////////////////////////////////////////////////////////
168:
169: /**
170: * Create a bean using given class loader and using the appropriate
171: * constructor for the given args. Figures out the arg types and
172: * calls above.
173:
174: * @param cld the class loader to use. If null, Class.forName is used.
175: * @param className name of class to instantiate
176: * @param args array of arguments
177: *
178: * @return the newly created bean
179: *
180: * @exception ClassNotFoundException if class is not loaded
181: * @exception NoSuchMethodException if constructor can't be found
182: * @exception InstantiationException if class can't be instantiated
183: * @exception IllegalAccessException if class is not accessible
184: * @exception IllegalArgumentException if argument problem
185: * @exception InvocationTargetException if constructor excepted
186: * @exception IOException if I/O error in beans.instantiate
187: */
188: public static Bean createBean(ClassLoader cld, String className,
189: Object[] args) throws ClassNotFoundException,
190: NoSuchMethodException, InstantiationException,
191: IllegalAccessException, IllegalArgumentException,
192: InvocationTargetException, IOException {
193: Class[] argTypes = null;
194: if (args != null) {
195: argTypes = new Class[args.length];
196: for (int i = 0; i < args.length; i++) {
197: argTypes[i] = (args[i] != null) ? args[i].getClass()
198: : null;
199: }
200: }
201: return createBean(cld, className, argTypes, args);
202: }
203:
204: //////////////////////////////////////////////////////////////////////////
205:
206: /**
207: * locate the item in the fds array whose name is as given. returns
208: * null if not found.
209: */
210: private static FeatureDescriptor findFeatureByName(
211: String featureType, String name, FeatureDescriptor[] fds) {
212: for (int i = 0; i < fds.length; i++) {
213: if (name.equals(fds[i].getName())) {
214: return fds[i];
215: }
216: }
217: return null;
218: }
219:
220: public static Bean getField(Object target, String fieldName)
221: throws IllegalArgumentException, IllegalAccessException {
222: // This is to handle how we do static fields.
223: Class targetClass = (target instanceof Class) ? (Class) target
224: : target.getClass();
225:
226: try {
227: Field f = targetClass.getField(fieldName);
228: Class fieldType = f.getType();
229:
230: // Get the value and return it.
231: Object value = f.get(target);
232: return new Bean(fieldType, value);
233: } catch (NoSuchFieldException e) {
234: throw new IllegalArgumentException("field '" + fieldName
235: + "' is " + "unknown for '" + target + "'");
236: }
237: }
238:
239: //////////////////////////////////////////////////////////////////////////
240:
241: /**
242: * Get a property of a bean.
243: *
244: * @param target the object whose prop is to be gotten
245: * @param propName name of the property to set
246: * @param index index to get (if property is indexed)
247: *
248: * @exception IntrospectionException if unable to introspect
249: * @exception IllegalArgumentException if problems with args: if the
250: * property is unknown, or if the property is given an index
251: * when its not, or if the property is not writeable, or if
252: * the given value cannot be assigned to the it (type mismatch).
253: * @exception IllegalAccessException if read method is not accessible
254: * @exception InvocationTargetException if read method excepts
255: */
256: public static Bean getProperty(Object target, String propName,
257: Integer index) throws IntrospectionException,
258: IllegalArgumentException, IllegalAccessException,
259: InvocationTargetException {
260: // find the property descriptor
261: BeanInfo bi = Introspector.getBeanInfo(target.getClass());
262: PropertyDescriptor pd = (PropertyDescriptor) findFeatureByName(
263: "property", propName, bi.getPropertyDescriptors());
264: if (pd == null) {
265: throw new IllegalArgumentException("property '" + propName
266: + "' is " + "unknown for '" + target + "'");
267: }
268:
269: // get read method and type of property
270: Method rm;
271: Class propType;
272: if (index != null) {
273: // if index != null, then property is indexed - pd better be so too
274: if (!(pd instanceof IndexedPropertyDescriptor)) {
275: throw new IllegalArgumentException(
276: "attempt to get non-indexed " + "property '"
277: + propName + "' as being indexed");
278: }
279: IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
280: rm = ipd.getIndexedReadMethod();
281: propType = ipd.getIndexedPropertyType();
282: } else {
283: rm = pd.getReadMethod();
284: propType = pd.getPropertyType();
285: }
286:
287: if (rm == null) {
288: throw new IllegalArgumentException("property '" + propName
289: + "' is not readable");
290: }
291:
292: // now get the value
293: Object propVal = null;
294: if (index != null) {
295: propVal = rm.invoke(target, new Object[] { index });
296: } else {
297: propVal = rm.invoke(target, null);
298: }
299: return new Bean(propType, propVal);
300: }
301:
302: public static void setField(Object target, String fieldName,
303: Bean value, TypeConvertorRegistry tcr)
304: throws IllegalArgumentException, IllegalAccessException {
305: // This is to handle how we do static fields.
306: Class targetClass = (target instanceof Class) ? (Class) target
307: : target.getClass();
308:
309: try {
310: Field f = targetClass.getField(fieldName);
311: Class fieldType = f.getType();
312:
313: // type convert the value if necessary
314: Object fieldVal = null;
315: boolean okeydokey = true;
316: if (fieldType.isAssignableFrom(value.type)) {
317: fieldVal = value.value;
318: } else if (tcr != null) {
319: TypeConvertor cvtor = tcr.lookup(value.type, fieldType);
320: if (cvtor != null) {
321: fieldVal = cvtor.convert(value.type, fieldType,
322: value.value);
323: } else {
324: okeydokey = false;
325: }
326: } else {
327: okeydokey = false;
328: }
329: if (!okeydokey) {
330: throw new IllegalArgumentException("unable to assign '"
331: + value.value + "' to field '" + fieldName
332: + "'");
333: }
334:
335: // now set the value
336: f.set(target, fieldVal);
337: } catch (NoSuchFieldException e) {
338: throw new IllegalArgumentException("field '" + fieldName
339: + "' is " + "unknown for '" + target + "'");
340: }
341: }
342:
343: //////////////////////////////////////////////////////////////////////////
344:
345: /**
346: * Set a property of a bean to a given value.
347: *
348: * @param target the object whose prop is to be set
349: * @param propName name of the property to set
350: * @param index index to set (if property is indexed)
351: * @param value the property value
352: * @param valueType the type of the above (needed when its null)
353: * @param tcr type convertor registry to use to convert value type to
354: * property type if necessary
355: *
356: * @exception IntrospectionException if unable to introspect
357: * @exception IllegalArgumentException if problems with args: if the
358: * property is unknown, or if the property is given an index
359: * when its not, or if the property is not writeable, or if
360: * the given value cannot be assigned to the it (type mismatch).
361: * @exception IllegalAccessException if write method is not accessible
362: * @exception InvocationTargetException if write method excepts
363: */
364: public static void setProperty(Object target, String propName,
365: Integer index, Object value, Class valueType,
366: TypeConvertorRegistry tcr) throws IntrospectionException,
367: IllegalArgumentException, IllegalAccessException,
368: InvocationTargetException {
369: // find the property descriptor
370: BeanInfo bi = Introspector.getBeanInfo(target.getClass());
371: PropertyDescriptor pd = (PropertyDescriptor) findFeatureByName(
372: "property", propName, bi.getPropertyDescriptors());
373: if (pd == null) {
374: throw new IllegalArgumentException("property '" + propName
375: + "' is " + "unknown for '" + target + "'");
376: }
377:
378: // get write method and type of property
379: Method wm;
380: Class propType;
381: if (index != null) {
382: // if index != null, then property is indexed - pd better be so too
383: if (!(pd instanceof IndexedPropertyDescriptor)) {
384: throw new IllegalArgumentException(
385: "attempt to set non-indexed " + "property '"
386: + propName + "' as being indexed");
387: }
388: IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
389: wm = ipd.getIndexedWriteMethod();
390: propType = ipd.getIndexedPropertyType();
391: } else {
392: wm = pd.getWriteMethod();
393: propType = pd.getPropertyType();
394: }
395:
396: if (wm == null) {
397: throw new IllegalArgumentException("property '" + propName
398: + "' is not writeable");
399: }
400:
401: // type convert the value if necessary
402: Object propVal = null;
403: boolean okeydokey = true;
404: if (propType.isAssignableFrom(valueType)) {
405: propVal = value;
406: } else if (tcr != null) {
407: TypeConvertor cvtor = tcr.lookup(valueType, propType);
408: if (cvtor != null) {
409: propVal = cvtor.convert(valueType, propType, value);
410: } else {
411: okeydokey = false;
412: }
413: } else {
414: okeydokey = false;
415: }
416: if (!okeydokey) {
417: throw new IllegalArgumentException("unable to assign '"
418: + value + "' to property '" + propName + "'");
419: }
420:
421: // now set the value
422: if (index != null) {
423: wm.invoke(target, new Object[] { index, propVal });
424: } else {
425: wm.invoke(target, new Object[] { propVal });
426: }
427: }
428: }
|