/************************************************************************** * Copyright (c) 2003 by Acunia N.V. All rights reserved. * * * * This software is copyrighted by and is the sole property of Acunia N.V. * * and its licensors, if any. All rights, title, ownership, or other * * interests in the software remain the property of Acunia N.V. and its * * licensors, if any. * * * * This software may only be used in accordance with the corresponding * * license agreement. Any unauthorized use, duplication, transmission, * * distribution or disclosure of this software is expressly forbidden. * * * * This Copyright notice may not be removed or modified without prior * * written consent of Acunia N.V. * * * * Acunia N.V. reserves the right to modify this software without notice. * * * * Acunia N.V. * * Philips-site 5, box 3 info@acunia.com * * 3001 Leuven http://www.acunia.com * * Belgium - EUROPE * **************************************************************************/ package java.beans; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import com.acunia.wonka.beans.BeanInfoImpl; /** * Used to learn about the properties, events and methods supported by a Java Bean * * TODO: Implement caching * Implement getBeanInfo(beanClass, stopClass) and getBeanInfo(beanClass, args) * Support indexed properties */ public class Introspector { public static final int USE_ALL_BEANINFO = 1; public static final int IGNORE_IMMEDIATE_BEANINFO = 2; public static final int IGNORE_ALL_BEANINFO = 3; private static String[] beanInfoSearchPath = {"com.acunia.wonka.java.beans.info"}; /** * Utility method to take a string and convert it to Java capitalization */ public static String decapitalize(String name) { // 1st case, name already starts with lower case if (Character.isLowerCase(name.charAt(0))) return name; else { // 2nd case, more than one char, 1st and 2nd uppercase if (name.length() > 1 && Character.isUpperCase(name.charAt(1))) return name; else { // normal case, change 1st char to lower case return Character.toLowerCase(name.charAt(0)) + name.substring(1); } } } /** * Sets the array of package names which are search for BeanInfo classes */ public static void setBeanInfoSearchPath(String[] path) { beanInfoSearchPath = path; } /** * Gets the array of package names which are searched for BeanInfo classes */ public static String[] getBeanInfoSearchPath() { return beanInfoSearchPath; } /** * Flushes the Introspector cache */ public static void flushCaches() { // TODO: Implement caching } /** * Flush the specified class from the Introspector cache */ public static void flushFromCaches(Class clz) { //TODO: Implement caching } /** * Introspect the JavaBean */ public static BeanInfo getBeanInfo(Class beanClass) throws IntrospectionException { // STEP 1 - look for BeanInfo in beanClass package String beanInfoClassName = beanClass.getName() + "BeanInfo"; String beanClassPackage = beanClass.getPackage().getName(); Object object = getBeanInfoInstance(beanClassPackage + "." + beanInfoClassName); if (object != null) return (BeanInfo) object; // STEP 2 - look for BeanInfo on search path if (beanInfoSearchPath != null) { for (int i=0; i<beanInfoSearchPath.length; i++) { object = getBeanInfoInstance(beanInfoSearchPath[i] + "." + beanInfoClassName); if (object != null) return (BeanInfo) object; } } // STEP 3 - Introspect and return a default BeanInfo BeanDescriptor beanDescriptor = new BeanDescriptor(beanClass); EventSetDescriptor[] eventSetDescriptors = getEventSetDescriptors(beanClass); MethodDescriptor[] methodDescriptors = getMethodDescriptors(beanClass); PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(beanClass); // get the superclass BeanInfo BeanInfo[] additionalBeanInfo = null; Class superclass = beanClass.getSuperclass(); if (superclass != null) { additionalBeanInfo = new BeanInfo[1]; additionalBeanInfo[0] = getBeanInfo(superclass); } return new BeanInfoImpl(additionalBeanInfo, beanDescriptor, -1, -1, eventSetDescriptors, null, methodDescriptors, propertyDescriptors); } /** * Tries to load the specified class */ private static Object getBeanInfoInstance(String className) { try { Class beanInfoClass = Class.forName(className); return beanInfoClass.newInstance(); } catch (ClassNotFoundException e) { return null; } catch (IllegalAccessException e) { return null; } catch (InstantiationException e) { return null; } } /** * Creates the EventSetDescriptor array for the bean */ private static EventSetDescriptor[] getEventSetDescriptors(Class beanClass) throws IntrospectionException { Method[] methods = beanClass.getMethods(); ArrayList eventList = new ArrayList(); for (int i=0; i<methods.length; i++) { // check for an add listener method String methodName = methods[i].getName(); Class[] params = methods[i].getParameterTypes(); if (validEventListenerParams(params)) { String listenerClassName = params[0].getName(); if (methodName.equals("add"+listenerClassName)) { // try to find remove method Method removeMethod = null; for (int j=0; j<methods.length && removeMethod == null; j++) { if (removeMethodMatches(methods[j], listenerClassName, params)) removeMethod = methods[j]; } // create EventSetDescriptor String eventSetName = listenerClassName.substring(0, listenerClassName.indexOf("Listener")); EventSetDescriptor event = new EventSetDescriptor(eventSetName, params[0], params[0].getMethods(), methods[i], removeMethod); // check if unicast event Class[] addExceptions = methods[i].getExceptionTypes(); boolean unicast = false; for (int j=0; j<addExceptions.length && !unicast; j++) { if (addExceptions[j].getName().equals("TooManyListenersException") && addExceptions[j].getPackage().getName().equals("java.util")) unicast = true; } event.setUnicast(unicast); eventList.add(event); } } } // convert event list to array EventSetDescriptor[] events = new EventSetDescriptor[eventList.size()]; for (int i=0; i<events.length; i++) events[i] = (EventSetDescriptor) eventList.get(i); return events; } /** * Checks that the parameter of an event method is valid */ private static boolean validEventListenerParams(Class[] params) { // check only one param if (params.length != 1) return false; // check name ends with listener String paramClassName = params[0].getName(); if (!paramClassName.endsWith("Listener")) return false; // check we extend java.util.EventListener Class[] interfaces = params[0].getInterfaces(); boolean found = false; for (int i=0; i<interfaces.length && !found; i++) { if (interfaces[i].getName().equals("EventListener") && interfaces[i].getPackage().getName().equals("java.util")) found = true; } return found; } /** * Checks if a matching remove method for an event add method has been found */ private static boolean removeMethodMatches(Method method, String listenerClassName, Class[] testParams) { // check method name if (!method.getName().equals("remove"+listenerClassName)) return false; // check method params Class[] methodParams = method.getParameterTypes(); if (methodParams.length != 1 && !methodParams[0].equals(testParams[0])) return false; return true; } /** * Returns the method descriptors for the bean */ private static MethodDescriptor[] getMethodDescriptors(Class beanClass) { Method[] methods = beanClass.getMethods(); MethodDescriptor[] methodDescriptors = new MethodDescriptor[methods.length]; for (int i=0; i<methods.length; i++) methodDescriptors[i] = new MethodDescriptor(methods[i]); return methodDescriptors; } /** * Retruns the property descriptors for the beans */ private static PropertyDescriptor[] getPropertyDescriptors(Class beanClass) throws IntrospectionException { Method[] methods = beanClass.getMethods(); HashMap properties = new HashMap(); for (int i=0; i<methods.length; i++) { // check for get/set/is method String methodName = methods[i].getName(); if (methodName.startsWith("get")) { String propertyName = Introspector.decapitalize(methodName.substring(3)); // check if we have already recorded this property if (properties.containsKey(propertyName)) { PropertyDescriptor p = (PropertyDescriptor) properties.get(propertyName); p.setReadMethod(methods[i]); } else properties.put(propertyName, new PropertyDescriptor(propertyName, methods[i], null)); } else if (methodName.startsWith("set")) { String propertyName = Introspector.decapitalize(methodName.substring(3)); // check for single param Class[] params = methods[i].getParameterTypes(); if (params.length == 1) { // check if we have already recorded this property if (properties.containsKey(propertyName)) { PropertyDescriptor p = (PropertyDescriptor) properties.get(propertyName); p.setWriteMethod(methods[i]); } else properties.put(propertyName, new PropertyDescriptor(propertyName, null, methods[i])); } } else if (methodName.startsWith("is")) { String propertyName = Introspector.decapitalize(methodName.substring(2)); // check if we have already recorded this property if (properties.containsKey(propertyName)) { PropertyDescriptor p = (PropertyDescriptor) properties.get(propertyName); p.setReadMethod(methods[i]); } else properties.put(propertyName, new PropertyDescriptor(propertyName, methods[i], null)); } } // convert to array PropertyDescriptor[] propertyDescriptors = new PropertyDescriptor[properties.entrySet().size()]; Iterator it = properties.values().iterator(); int i = 0; while (it.hasNext()) { propertyDescriptors[i] = (PropertyDescriptor) it.next(); i++; } return propertyDescriptors; } }