001: /**
002: *
003: *
004: * @author Adrian Price
005: */package org.obe.util;
006:
007: import org.obe.OBERuntimeException;
008:
009: import java.beans.FeatureDescriptor;
010: import java.beans.IntrospectionException;
011: import java.beans.Introspector;
012: import java.beans.PropertyDescriptor;
013: import java.io.Serializable;
014: import java.lang.reflect.Method;
015: import java.lang.reflect.Modifier;
016: import java.util.*;
017:
018: /**
019: * Provides helpers for performing reflection-based introspections.
020: *
021: * @author Adrian Price
022: */
023: public final class ClassUtils {
024: private static final String INVALID_TYPE = " is not a valid Java type";
025: private static final Object[][] _primitives = {
026: { "boolean", boolean.class, "B" },
027: { "byte", byte.class, "Y" }, { "char", char.class, "C" },
028: { "double", double.class, "D" },
029: { "float", float.class, "F" }, { "int", int.class, "I" },
030: { "long", long.class, "L" }, { "short", short.class, "S" },
031: { "void", void.class, null } };
032: private static final Map _primitiveArrayTypeNames;
033: private static final Map _primitiveTypes;
034: public static final Comparator featureDescriptorComparator;
035:
036: static {
037: // Must build these maps in the static initializer, so that success does
038: // depend upon the relative ordering of the static field declarations.
039: _primitiveArrayTypeNames = Collections
040: .unmodifiableMap(new HashMap() {
041: {
042: for (int i = 0; i < _primitives.length; i++) {
043: Object[] primitive = _primitives[i];
044: put(primitive[0], primitive[2]);
045: }
046: }
047: });
048: _primitiveTypes = Collections.unmodifiableMap(new HashMap() {
049: {
050: for (int i = 0; i < _primitives.length; i++) {
051: Object[] primitive = _primitives[i];
052: put(primitive[0], primitive[1]);
053: }
054: }
055: });
056: featureDescriptorComparator = new Comparator() {
057: public int compare(Object o1, Object o2) {
058: return ((FeatureDescriptor) o1).getName().compareTo(
059: ((FeatureDescriptor) o2).getName());
060: }
061: };
062: }
063:
064: /**
065: * Returns the class that matches the supplied names.
066: * <p/>
067: * <em>N.B. The class name must be specified in Java source form, not the
068: * JVM runtime form (which uses non-reversible names for primitive types and
069: * somewhat obfuscated names for array types).</em>
070: *
071: * @param className Class name.
072: * @return Class for supplied name.
073: * @throws ClassNotFoundException
074: */
075: public static Class classForName(String className)
076: throws ClassNotFoundException {
077:
078: // If it is an array, determine the number of dimensions.
079: int dimensions = 0;
080: int index = className.indexOf('[');
081: if (index != -1) {
082: boolean lbracket = false;
083: for (int j = index, n = className.length(); j < n; j++) {
084: char ch = className.charAt(j);
085: switch (ch) {
086: case '[':
087: lbracket = true;
088: dimensions++;
089: continue;
090: case ']':
091: if (!lbracket) {
092: throw new IllegalArgumentException(className
093: + INVALID_TYPE);
094: }
095: lbracket = false;
096: continue;
097: default:
098: // Ignore whitespace.
099: if (Character.isWhitespace(ch))
100: break;
101: throw new IllegalArgumentException(className
102: + INVALID_TYPE);
103: }
104: }
105: if (lbracket) {
106: // Closing rbracket missing.
107: throw new IllegalArgumentException(className
108: + INVALID_TYPE);
109: }
110: className = className.substring(0, index).trim();
111: }
112:
113: // Does it look like a Java primitive type?
114: Class javaClass = null;
115: boolean primitive = false;
116: if (className.indexOf('.') == -1) {
117: javaClass = (Class) _primitiveTypes.get(className);
118: if (javaClass != null) {
119: primitive = true;
120: } else {
121: try {
122: // Is it an unqualified java.lang class?
123: String s = "java.lang." + className;
124: javaClass = Class.forName(s);
125: className = s;
126: } catch (ClassNotFoundException e) {
127: javaClass = Class.forName(className);
128: }
129: }
130: }
131:
132: // If it's an array, add the classname [+L prefix and ; suffix.
133: if (dimensions != 0) {
134: javaClass = null;
135: if (primitive) {
136: className = (String) _primitiveArrayTypeNames
137: .get(className);
138: }
139: StringBuffer sb = new StringBuffer();
140: for (int j = 0; j < dimensions; j++)
141: sb.append('[');
142: sb.append('L');
143: sb.append(className);
144: sb.append(';');
145: className = sb.toString();
146: }
147:
148: if (javaClass == null)
149: javaClass = Class.forName(className);
150:
151: return javaClass;
152: }
153:
154: /**
155: * Returns an array of classes to match the supplied names.
156: * <p/>
157: * <em>N.B. Class names must be specified in Java source form, not the JVM
158: * runtime form (which uses non-reversible names for primitive types and
159: * somewhat obfuscated names for array types).</em>
160: *
161: * @param classNames Class names.
162: * @return Classes for supplied names or <code>null</code> if
163: * <code>classNames</code> is <code>null</code>.
164: * @throws ClassNotFoundException
165: */
166: public static Class[] classesForNames(String[] classNames)
167: throws ClassNotFoundException {
168:
169: if (classNames == null)
170: return null;
171:
172: Class[] parmTypes = new Class[classNames.length];
173: for (int i = 0; i < parmTypes.length; i++)
174: parmTypes[i] = classForName(classNames[i]);
175: return parmTypes;
176: }
177:
178: /**
179: * Returns the method that matches a specified signature.
180: *
181: * @param className The class that defines the method.
182: * @param methodSig The method signature in standard Java source form.
183: * @return The Method object.
184: * @throws ClassNotFoundException If the class could be loaded.
185: * @throws NoSuchMethodException If no matching method was found.
186: */
187: public static Method findMethod(String className, String methodSig)
188: throws ClassNotFoundException, NoSuchMethodException {
189:
190: // If no opening parenthesis, methodSig is the method name.
191: String methodName;
192: StringTokenizer strtok = null;
193: if (methodSig.indexOf('(') == -1) {
194: methodName = methodSig;
195: } else {
196: strtok = new StringTokenizer(methodSig);
197: methodName = strtok.nextToken("(, )");
198: }
199:
200: // Extract the method name and parameter types.
201: Class[] parmTypes = null;
202: if (methodName == null) {
203: methodName = "main";
204: } else if (strtok != null) {
205: int argc = strtok.countTokens();
206: String[] classNames = new String[argc];
207: for (int i = 0; i < argc; i++)
208: classNames[i] = strtok.nextToken();
209: parmTypes = classesForNames(classNames);
210: }
211:
212: // Find the method.
213: return findMethod(className, methodName, parmTypes);
214: }
215:
216: private static Method findMethod(String className,
217: String methodName, Class[] parmTypes)
218: throws ClassNotFoundException, NoSuchMethodException {
219:
220: // Make sure we can load the class.
221: Class targetClass = Class.forName(className);
222:
223: // If no parameter types were supplied, search for a single method
224: // with the required name.
225: Method method = null;
226: if (parmTypes == null) {
227: boolean foundMatch = false;
228: Method[] methods = targetClass.getMethods();
229: for (int i = 0; i < methods.length; i++) {
230: Method meth = methods[i];
231: if (meth.getName().equals(methodName)) {
232: if (foundMatch) {
233: throw new IllegalArgumentException("Method '"
234: + methodName
235: + "' is overloaded; please supply "
236: + "the full method signature");
237: }
238: foundMatch = true;
239: method = meth;
240: }
241: }
242: if (method == null)
243: throw new NoSuchMethodException(methodName);
244: } else {
245: // Otherwise, find the specific method and make sure it's public.
246: method = targetClass.getMethod(methodName, parmTypes);
247: if (!Modifier.isPublic(method.getModifiers())) {
248: throw new IllegalArgumentException("Method "
249: + signatureFromMethod(method, false, false,
250: true, true, true, false)
251: + " is not public");
252: }
253: }
254: return method;
255: }
256:
257: private static Class[] getInterfaceInheritancePath(Class ifClass,
258: Class super IfClass) {
259:
260: // TODO: Introspect interface inheritance properly!!!
261: Class[] classes;
262: if (super IfClass == null)
263: classes = new Class[] { ifClass };
264: else
265: classes = new Class[] { ifClass, super IfClass };
266:
267: return classes;
268: }
269:
270: public static String[] getPropertyNames(
271: PropertyDescriptor[] propDescs) {
272: String[] propNames = new String[propDescs.length];
273: for (int i = 0; i < propDescs.length; i++)
274: propNames[i] = propDescs[i].getName();
275: return propNames;
276: }
277:
278: /**
279: * Introspects properties of the specified class(es) into a map.
280: *
281: * @param beanClass
282: * @param stopClass
283: * @return Array of <code>PropertyDescriptor</code>.
284: */
285: public static PropertyDescriptor[] introspect(Class beanClass,
286: Class stopClass) {
287:
288: try {
289: PropertyDescriptor[] propDescs;
290: if (!beanClass.isInterface() || stopClass == null) {
291: propDescs = Introspector.getBeanInfo(beanClass,
292: stopClass).getPropertyDescriptors();
293: } else {
294: List props = new ArrayList();
295: Class[] classes = getInterfaceInheritancePath(
296: beanClass, stopClass);
297: for (int i = 0; i < classes.length; i++) {
298: // Get the properties for the class.
299: // N.B. This has been re-written to introspect both classes
300: // separately because Introspector doesn't recognize a
301: // superinterface as it does a superclass (because
302: // Class.getSuperclass() returns null for an interface).
303: // Thus passing superinterface as stopClass doesn't work.
304: propDescs = Introspector.getBeanInfo(classes[i])
305: .getPropertyDescriptors();
306:
307: // Filter to include only primitive and serializable types.
308: for (int j = 0; j < propDescs.length; j++) {
309: PropertyDescriptor propDesc = propDescs[j];
310: Class propertyType = propDesc.getPropertyType();
311: if (propertyType.isPrimitive()
312: || Serializable.class
313: .isAssignableFrom(propertyType)) {
314:
315: props.add(propDesc);
316: }
317: }
318: }
319: propDescs = (PropertyDescriptor[]) props
320: .toArray(new PropertyDescriptor[props.size()]);
321: }
322:
323: // Sort on property name.
324: Arrays.sort(propDescs, featureDescriptorComparator);
325:
326: return propDescs;
327: } catch (IntrospectionException e) {
328: throw new OBERuntimeException(e);
329: }
330: }
331:
332: /**
333: * Converts an array of classes to an array of class names.
334: *
335: * @param parmTypes Array of classes.
336: * @return A corresponding array of class names in Java source format.
337: */
338: public static String[] namesForClasses(Class[] parmTypes) {
339: String[] classNames = new String[parmTypes.length];
340: StringBuffer sb = new StringBuffer();
341: for (int i = 0, dims = 0; i < parmTypes.length; i++, dims = 0) {
342: Class parmType = parmTypes[i];
343: while (parmType.isArray()) {
344: parmType = parmType.getComponentType();
345: dims++;
346: }
347: String className = parmType.getName();
348: if (dims != 0) {
349: sb.setLength(0);
350: sb.append(className);
351: for (int j = dims; j > 0; j--)
352: sb.append("[]");
353: className = sb.toString();
354: }
355: classNames[i] = className;
356: }
357: return classNames;
358: }
359:
360: /**
361: * Generates a string method description from a Method object.
362: *
363: * @param method Method object.
364: * @return String representation of the method in JVM runtime format.
365: */
366: public static String signatureFromMethod(Method method) {
367: return signatureFromMethod(method, true, true, true, true,
368: true, true);
369: }
370:
371: /**
372: * Generates a string method description from a Method object.
373: *
374: * @param method Method object.
375: * @param includeModifiers <code>true</code> to include the modifiers.
376: * @param includeReturn <code>true</code> to include the return type.
377: * @param includeClass <code>true</code> to include the class name.
378: * @param includeMethod <code>true</code> to include the method name.
379: * @param includeParameterTypes <code>true</code> to include the parameters.
380: * @param includeExceptions <code>true</code> to include the exceptions.
381: * @return String representation of the method in JVM runtime format.
382: */
383: public static String signatureFromMethod(Method method,
384: boolean includeModifiers, boolean includeReturn,
385: boolean includeClass, boolean includeMethod,
386: boolean includeParameterTypes, boolean includeExceptions) {
387:
388: StringBuffer sb = new StringBuffer();
389: if (includeModifiers)
390: sb.append(Modifier.toString(method.getModifiers())).append(
391: ' ');
392: if (includeReturn)
393: sb.append(method.getReturnType().getName()).append(' ');
394: if (includeClass)
395: sb.append(method.getDeclaringClass().getName());
396: if (includeMethod) {
397: if (includeClass)
398: sb.append('.');
399: sb.append(method.getName());
400: }
401: if (includeParameterTypes) {
402: if (includeMethod)
403: sb.append('(');
404: Class[] parmTypes = method.getParameterTypes();
405: for (int i = 0; i < parmTypes.length; i++) {
406: if (i != 0)
407: sb.append(", ");
408: sb.append(parmTypes[i].getName());
409: }
410: if (includeMethod)
411: sb.append(')');
412: }
413: if (includeExceptions) {
414: Class[] exceptionTypes = method.getExceptionTypes();
415: int n = exceptionTypes.length;
416: if (n != 0) {
417: sb.append(" throws ");
418: for (int i = 0; i < n; i++) {
419: Class exceptionType = exceptionTypes[i];
420: if (i != 0)
421: sb.append(", ");
422: sb.append(exceptionType.getName());
423: }
424: }
425: }
426: return sb.toString();
427: }
428:
429: private ClassUtils() {
430: }
431: }
|