001: package jaxx.introspection;
002:
003: import java.beans.*;
004: import java.lang.reflect.*;
005: import java.util.*;
006:
007: import jaxx.reflect.*;
008: import jaxx.reflect.MethodDescriptor;
009:
010: /** Performs introspection on a <code>ClassDescriptor</code>. Ideally, I could just have copied Sun's Introspector
011: * and changed a few things, but the licensing terms are incompatible. This implementation is incomplete -- it only
012: * bothers to report info that JAXX actually checks. It also relaxes some of Introspector's rules a bit, but I
013: * don't believe it results in any meaningful incompatibilities.
014: * <p>
015: * JAXX uses its own introspector rather than the built-in
016: * <code>java.beans.Introspector</code> so that it can introspect {@link jaxx.reflect.ClassDescriptor},
017: * not just <code>java.lang.Class</code>.
018: */
019: public class JAXXIntrospector {
020: private ClassDescriptor classDescriptor;
021: private Map/*<String, PropertyDescriptor>*/propertyDescriptors = new HashMap/*<String, PropertyDescriptor>*/();
022: private Map/*<String, EventSetDescriptor>*/eventSetDescriptors = new HashMap/*<String, EventSetDescriptor>*/();
023:
024: private JAXXIntrospector(ClassDescriptor classDescriptor) {
025: this .classDescriptor = classDescriptor;
026: }
027:
028: /** Returns the <code>JAXXBeanInfo</code> for a given class.
029: *
030: *@param classDescriptor the class to introspect
031: *@return the <code>JAXXBeanInfo</code> for the bean class
032: *@throws IntrospectionException if an error occurs
033: */
034: public static JAXXBeanInfo getJAXXBeanInfo(
035: ClassDescriptor classDescriptor)
036: throws IntrospectionException {
037: JAXXIntrospector introspector = new JAXXIntrospector(
038: classDescriptor);
039: return introspector.createBeanInfo();
040: }
041:
042: private JAXXBeanInfo createBeanInfo() {
043: ClassDescriptor explicitInfoClass = classDescriptor;
044: BeanInfo explicitBeanInfo = null;
045: while (explicitInfoClass != null) {
046: explicitBeanInfo = getExplicitBeanInfo(explicitInfoClass);
047: if (explicitBeanInfo != null)
048: break;
049: explicitInfoClass = explicitInfoClass.getSuperclass();
050: }
051:
052: if (explicitBeanInfo != null) {
053: PropertyDescriptor[] explicitProperties = explicitBeanInfo
054: .getPropertyDescriptors();
055: for (int i = 0; i < explicitProperties.length; i++) {
056: Class type = explicitProperties[i].getPropertyType();
057: Method readMethod = explicitProperties[i]
058: .getReadMethod();
059: Method writeMethod = explicitProperties[i]
060: .getWriteMethod();
061: try {
062: ClassDescriptor typeDescriptor = ClassDescriptorLoader
063: .getClassDescriptor(type.getName(), type
064: .getClassLoader());
065: JAXXPropertyDescriptor propertyDescriptor = new JAXXPropertyDescriptor(
066: classDescriptor,
067: explicitProperties[i].getName(),
068: readMethod != null ? classDescriptor
069: .getMethodDescriptor(readMethod
070: .getName(),
071: new ClassDescriptor[0])
072: : null,
073: writeMethod != null ? classDescriptor
074: .getMethodDescriptor(
075: writeMethod.getName(),
076: new ClassDescriptor[] { typeDescriptor })
077: : null);
078: propertyDescriptor.setBound(explicitProperties[i]
079: .isBound());
080: Enumeration/*<String>*/attributeNames = explicitProperties[i]
081: .attributeNames();
082: while (attributeNames.hasMoreElements()) {
083: String name = (String) attributeNames
084: .nextElement();
085: propertyDescriptor.setValue(name,
086: explicitProperties[i].getValue(name));
087: }
088: propertyDescriptors.put(propertyDescriptor
089: .getName(), propertyDescriptor);
090: } catch (ClassNotFoundException e) {
091: throw new RuntimeException(
092: "Internal error: Could not find ClassDescriptor corresponding to Java "
093: + type, e);
094: } catch (NoSuchMethodException e) {
095: throw new RuntimeException(
096: "Internal error: Could not find expected MethodDescriptor in "
097: + classDescriptor, e);
098: }
099: }
100: }
101:
102: // if the class broadcasts PropertyChangeEvent, assume all properties are bound (java.beans.Introspector
103: // does the same)
104: boolean propertyChangeSource;
105: try {
106: classDescriptor
107: .getMethodDescriptor(
108: "addPropertyChangeListener",
109: new ClassDescriptor[] { ClassDescriptorLoader
110: .getClassDescriptor(PropertyChangeListener.class) });
111: propertyChangeSource = true;
112: } catch (NoSuchMethodException e) {
113: propertyChangeSource = false;
114: }
115:
116: MethodDescriptor[] methods = classDescriptor
117: .getMethodDescriptors();
118: for (int i = 0; i < methods.length; i++) {
119: String name = methods[i].getName();
120: if (name.startsWith("get") && name.length() > 3
121: && Character.isUpperCase(name.charAt(3))
122: && methods[i].getParameterTypes().length == 0) {
123: String propertyName = Introspector.decapitalize(name
124: .substring(3));
125: if (!propertyDescriptors.containsKey(propertyName))
126: propertyDescriptors.put(propertyName,
127: new JAXXPropertyDescriptor(classDescriptor,
128: propertyName, methods[i], null,
129: propertyChangeSource));
130: } else if (name.startsWith("is") && name.length() > 2
131: && Character.isUpperCase(name.charAt(2))
132: && methods[i].getParameterTypes().length == 0) {
133: String propertyName = Introspector.decapitalize(name
134: .substring(2));
135: if (!propertyDescriptors.containsKey(propertyName))
136: propertyDescriptors.put(propertyName,
137: new JAXXPropertyDescriptor(classDescriptor,
138: propertyName, methods[i], null,
139: propertyChangeSource));
140: } else if (name.startsWith("set") && name.length() > 3
141: && Character.isUpperCase(name.charAt(3))
142: && methods[i].getParameterTypes().length == 1) {
143: String propertyName = Introspector.decapitalize(name
144: .substring(3));
145: if (!propertyDescriptors.containsKey(propertyName))
146: propertyDescriptors.put(propertyName,
147: new JAXXPropertyDescriptor(classDescriptor,
148: propertyName, null, methods[i],
149: propertyChangeSource));
150: } else if (name.startsWith("add") && name.length() > 3
151: && Character.isUpperCase(name.charAt(3))) {
152: ClassDescriptor[] parameters = methods[i]
153: .getParameterTypes();
154: if (parameters.length != 1
155: || !ClassDescriptorLoader.getClassDescriptor(
156: EventListener.class).isAssignableFrom(
157: parameters[0]))
158: continue; // not an event listener method
159: try {
160: String eventSetName = methods[i].getName()
161: .substring(3);
162: MethodDescriptor remove = classDescriptor
163: .getMethodDescriptor("remove"
164: + eventSetName, parameters);
165: eventSetDescriptors.put(eventSetName,
166: new JAXXEventSetDescriptor(classDescriptor,
167: eventSetName, methods[i], remove,
168: parameters[0]
169: .getMethodDescriptors()));
170: } catch (NoSuchMethodException e) {
171: continue; // no matching remove method, not a valid event
172: }
173: }
174: }
175:
176: JAXXBeanDescriptor beanDescriptor = new JAXXBeanDescriptor(
177: classDescriptor);
178: if (explicitBeanInfo != null) {
179: BeanDescriptor explicitBeanDescriptor = explicitBeanInfo
180: .getBeanDescriptor();
181: if (explicitBeanDescriptor != null) {
182: Enumeration/*<String>*/attributeNames = explicitBeanDescriptor
183: .attributeNames();
184: while (attributeNames.hasMoreElements()) {
185: String name = (String) attributeNames.nextElement();
186: beanDescriptor.setValue(name,
187: explicitBeanDescriptor.getValue(name));
188: }
189: }
190: }
191:
192: return new JAXXBeanInfo(
193: beanDescriptor,
194: (JAXXPropertyDescriptor[]) propertyDescriptors
195: .values()
196: .toArray(
197: new JAXXPropertyDescriptor[propertyDescriptors
198: .size()]),
199: (JAXXEventSetDescriptor[]) eventSetDescriptors
200: .values()
201: .toArray(
202: new JAXXEventSetDescriptor[eventSetDescriptors
203: .size()]));
204: }
205:
206: private static BeanInfo getExplicitBeanInfo(
207: ClassDescriptor classDescriptor) {
208: try {
209: Class beanClass = Class.forName(classDescriptor.getName(),
210: true, classDescriptor.getClassLoader()); // see if there is a class by that name in this package
211: Method findExplicitBeanInfo = Introspector.class
212: .getDeclaredMethod("findExplicitBeanInfo",
213: new Class[] { Class.class });
214: findExplicitBeanInfo.setAccessible(true);
215: return (BeanInfo) findExplicitBeanInfo.invoke(null,
216: new Object[] { beanClass });
217: } catch (ClassNotFoundException e) {
218: return null; // happens for uncompiled classes
219: } catch (NoClassDefFoundError e) {
220: return null; // wrong case, etc.
221: } catch (NoSuchMethodException e) {
222: throw new RuntimeException(
223: "Error: could not find method 'findExplicitBeanInfo' in java.beans.Introspector. You are most likely running a version of Java against which JAXX has not been tested.");
224: } catch (InvocationTargetException e) {
225: throw new RuntimeException(e);
226: } catch (IllegalAccessException e) {
227: throw new RuntimeException(e);
228: }
229: }
230: }
|