| /* gnu.java.beans.IntrospectionIncubator | |
| Copyright (C) 1998, 2004 Free Software Foundation, Inc. | |
| This file is part of GNU Classpath. | |
| GNU Classpath is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 2, or (at your option) | |
| any later version. | |
| GNU Classpath is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GNU Classpath; see the file COPYING. If not, write to the | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | |
| 02110-1301 USA. | |
| Linking this library statically or dynamically with other modules is | |
| making a combined work based on this library. Thus, the terms and | |
| conditions of the GNU General Public License cover the whole | |
| combination. | |
| As a special exception, the copyright holders of this library give you | |
| permission to link this library with independent modules to produce an | |
| executable, regardless of the license terms of these independent | |
| modules, and to copy and distribute the resulting executable under | |
| terms of your choice, provided that you also meet, for each linked | |
| independent module, the terms and conditions of the license of that | |
| module. An independent module is a module which is not derived from | |
| or based on this library. If you modify this library, you may extend | |
| this exception to your version of the library, but you are not | |
| obligated to do so. If you do not wish to do so, delete this | |
| exception statement from your version. */ | |
| package gnu.java.beans; | |
| import gnu.java.lang.ArrayHelper; | |
| import gnu.java.lang.ClassHelper; | |
| import java.beans.BeanInfo; | |
| import java.beans.EventSetDescriptor; | |
| import java.beans.IndexedPropertyDescriptor; | |
| import java.beans.IntrospectionException; | |
| import java.beans.Introspector; | |
| import java.beans.MethodDescriptor; | |
| import java.beans.PropertyDescriptor; | |
| import java.lang.reflect.Array; | |
| import java.lang.reflect.Method; | |
| import java.lang.reflect.Modifier; | |
| import java.util.Enumeration; | |
| import java.util.Hashtable; | |
| import java.util.Vector; | |
| /** | |
| ** IntrospectionIncubator takes in a bunch of Methods, and | |
| ** Introspects only those Methods you give it.<br/> | |
| ** | |
| ** See {@link addMethod(Method)} for details which rules apply to | |
| ** the methods. | |
| ** | |
| ** @author John Keiser | |
| ** @author Robert Schuster | |
| ** @see gnu.java.beans.ExplicitBeanInfo | |
| ** @see java.beans.BeanInfo | |
| **/ | |
| public class IntrospectionIncubator { | |
| Hashtable propertyMethods = new Hashtable(); | |
| Hashtable listenerMethods = new Hashtable(); | |
| Vector otherMethods = new Vector(); | |
| Class propertyStopClass; | |
| Class eventStopClass; | |
| Class methodStopClass; | |
| public IntrospectionIncubator() { | |
| } | |
| /** Examines the given method and files it in a suitable collection. | |
| * It files the method as a property method if it finds: | |
| * <ul> | |
| * <li>boolean "is" getter</li> | |
| * <li>"get" style getter</li> | |
| * <li>single argument setter</li> | |
| * <li>indiced setter and getter</li> | |
| * </ul> | |
| * It files the method as a listener method if all of these rules apply: | |
| * <ul> | |
| * <li>the method name starts with "add" or "remove"</li> | |
| * <li>there is only a single argument</li> | |
| * <li>the argument type is a subclass of <code>java.util.EventListener</code></li> | |
| * </ul> | |
| * All public methods are filed as such. | |
| * | |
| * @param method The method instance to examine. | |
| */ | |
| public void addMethod(Method method) { | |
| if(Modifier.isPublic(method.getModifiers())) { | |
| String name = ClassHelper.getTruncatedName(method.getName()); | |
| Class retType = method.getReturnType(); | |
| Class[] params = method.getParameterTypes(); | |
| boolean isVoid = retType.equals(java.lang.Void.TYPE); | |
| Class methodClass = method.getDeclaringClass(); | |
| /* Accepts the method for examination if no stop class is given or the method is declared in a subclass of the stop class. | |
| * The rules for this are described in {@link java.beans.Introspector.getBeanInfo(Class, Class)}. | |
| * This block finds out whether the method is a suitable getter or setter method (or read/write method). | |
| */ | |
| if(isReachable(propertyStopClass, methodClass)) { | |
| /* At this point a method may regarded as a property's read or write method if its name | |
| * starts with "is", "get" or "set". However, if a method is static it cannot be part | |
| * of a property. | |
| */ | |
| if(Modifier.isStatic(method.getModifiers())) { | |
| // files method as other because it is static | |
| otherMethods.addElement(method); | |
| } else if(name.startsWith("is") | |
| && retType.equals(java.lang.Boolean.TYPE) | |
| && params.length == 0) { | |
| // files method as boolean "is" style getter | |
| addToPropertyHash(name,method,IS); | |
| } else if(name.startsWith("get") && !isVoid) { | |
| if(params.length == 0) { | |
| // files as legal non-argument getter | |
| addToPropertyHash(name,method,GET); | |
| } else if(params.length == 1 && params[0].equals(java.lang.Integer.TYPE)) { | |
| // files as legal indiced getter | |
| addToPropertyHash(name,method,GET_I); | |
| } else { | |
| // files as other because the method's signature is not Bean-like | |
| otherMethods.addElement(method); | |
| } | |
| } else if(name.startsWith("set") && isVoid) { | |
| if(params.length == 1) { | |
| // files as legal single-argument setter method | |
| addToPropertyHash(name,method,SET); | |
| } else if(params.length == 2 && params[0].equals(java.lang.Integer.TYPE)) { | |
| // files as legal indiced setter method | |
| addToPropertyHash(name,method,SET_I); | |
| } else { | |
| // files as other because the method's signature is not Bean-like | |
| otherMethods.addElement(method); | |
| } | |
| } | |
| } | |
| if(isReachable(eventStopClass, methodClass)) { | |
| if(name.startsWith("add") | |
| && isVoid | |
| && params.length == 1 | |
| && java.util.EventListener.class.isAssignableFrom(params[0])) { | |
| addToListenerHash(name,method,ADD); | |
| } else if(name.startsWith("remove") | |
| && isVoid | |
| && params.length == 1 | |
| && java.util.EventListener.class.isAssignableFrom(params[0])) { | |
| addToListenerHash(name,method,REMOVE); | |
| } | |
| } | |
| if(isReachable(methodStopClass, methodClass)) { | |
| // files as reachable public method | |
| otherMethods.addElement(method); | |
| } | |
| } | |
| } | |
| public void addMethods(Method[] m) { | |
| for(int i=0;i<m.length;i++) { | |
| addMethod(m[i]); | |
| } | |
| } | |
| public void setPropertyStopClass(Class c) { | |
| propertyStopClass = c; | |
| } | |
| public void setEventStopClass(Class c) { | |
| eventStopClass = c; | |
| } | |
| public void setMethodStopClass(Class c) { | |
| methodStopClass = c; | |
| } | |
| public BeanInfoEmbryo getBeanInfoEmbryo() throws IntrospectionException { | |
| BeanInfoEmbryo b = new BeanInfoEmbryo(); | |
| findXXX(b,IS); | |
| findXXXInt(b,GET_I); | |
| findXXXInt(b,SET_I); | |
| findXXX(b,GET); | |
| findXXX(b,SET); | |
| findAddRemovePairs(b); | |
| for(int i=0;i<otherMethods.size();i++) { | |
| MethodDescriptor newMethod = new MethodDescriptor((Method)otherMethods.elementAt(i)); | |
| if(!b.hasMethod(newMethod)) { | |
| b.addMethod(new MethodDescriptor((Method)otherMethods.elementAt(i))); | |
| } | |
| } | |
| return b; | |
| } | |
| public BeanInfo getBeanInfo() throws IntrospectionException { | |
| return getBeanInfoEmbryo().getBeanInfo(); | |
| } | |
| void findAddRemovePairs(BeanInfoEmbryo b) throws IntrospectionException { | |
| Enumeration listenerEnum = listenerMethods.keys(); | |
| while(listenerEnum.hasMoreElements()) { | |
| DoubleKey k = (DoubleKey)listenerEnum.nextElement(); | |
| Method[] m = (Method[])listenerMethods.get(k); | |
| if(m[ADD] != null && m[REMOVE] != null) { | |
| EventSetDescriptor e = new EventSetDescriptor(Introspector.decapitalize(k.getName()), | |
| k.getType(), k.getType().getMethods(), | |
| m[ADD],m[REMOVE]); | |
| e.setUnicast(ArrayHelper.contains(m[ADD].getExceptionTypes(),java.util.TooManyListenersException.class)); | |
| if(!b.hasEvent(e)) { | |
| b.addEvent(e); | |
| } | |
| } | |
| } | |
| } | |
| void findXXX(BeanInfoEmbryo b, int funcType) throws IntrospectionException { | |
| Enumeration keys = propertyMethods.keys(); | |
| while(keys.hasMoreElements()) { | |
| DoubleKey k = (DoubleKey)keys.nextElement(); | |
| Method[] m = (Method[])propertyMethods.get(k); | |
| if(m[funcType] != null) { | |
| PropertyDescriptor p = new PropertyDescriptor(Introspector.decapitalize(k.getName()), | |
| m[IS] != null ? m[IS] : m[GET], | |
| m[SET]); | |
| if(m[SET] != null) { | |
| p.setConstrained(ArrayHelper.contains(m[SET].getExceptionTypes(),java.beans.PropertyVetoException.class)); | |
| } | |
| if(!b.hasProperty(p)) { | |
| b.addProperty(p); | |
| } | |
| } | |
| } | |
| } | |
| void findXXXInt(BeanInfoEmbryo b, int funcType) throws IntrospectionException { | |
| Enumeration keys = propertyMethods.keys(); | |
| while(keys.hasMoreElements()) { | |
| DoubleKey k = (DoubleKey)keys.nextElement(); | |
| Method[] m = (Method[])propertyMethods.get(k); | |
| if(m[funcType] != null) { | |
| boolean constrained; | |
| if(m[SET_I] != null) { | |
| constrained = ArrayHelper.contains(m[SET_I].getExceptionTypes(),java.beans.PropertyVetoException.class); | |
| } else { | |
| constrained = false; | |
| } | |
| /** Find out if there is an array type get or set **/ | |
| Class arrayType = Array.newInstance(k.getType(),0).getClass(); | |
| DoubleKey findSetArray = new DoubleKey(arrayType,k.getName()); | |
| Method[] m2 = (Method[])propertyMethods.get(findSetArray); | |
| IndexedPropertyDescriptor p; | |
| if(m2 == null) { | |
| p = new IndexedPropertyDescriptor(Introspector.decapitalize(k.getName()), | |
| null,null, | |
| m[GET_I],m[SET_I]); | |
| } else { | |
| if(constrained && m2[SET] != null) { | |
| constrained = ArrayHelper.contains(m2[SET].getExceptionTypes(),java.beans.PropertyVetoException.class); | |
| } | |
| p = new IndexedPropertyDescriptor(Introspector.decapitalize(k.getName()), | |
| m2[GET],m2[SET], | |
| m[GET_I],m[SET_I]); | |
| } | |
| p.setConstrained(constrained); | |
| if(!b.hasProperty(p)) { | |
| b.addProperty(p); | |
| } | |
| } | |
| } | |
| } | |
| static final int IS=0; | |
| static final int GET_I=1; | |
| static final int SET_I=2; | |
| static final int GET=3; | |
| static final int SET=4; | |
| static final int ADD=0; | |
| static final int REMOVE=1; | |
| void addToPropertyHash(String name, Method method, int funcType) { | |
| String newName; | |
| Class type; | |
| switch(funcType) { | |
| case IS: | |
| type = java.lang.Boolean.TYPE; | |
| newName = name.substring(2); | |
| break; | |
| case GET_I: | |
| type = method.getReturnType(); | |
| newName = name.substring(3); | |
| break; | |
| case SET_I: | |
| type = method.getParameterTypes()[1]; | |
| newName = name.substring(3); | |
| break; | |
| case GET: | |
| type = method.getReturnType(); | |
| newName = name.substring(3); | |
| break; | |
| case SET: | |
| type = method.getParameterTypes()[0]; | |
| newName = name.substring(3); | |
| break; | |
| default: | |
| return; | |
| } | |
| newName = capitalize(newName); | |
| if (newName.length() == 0) | |
| return; | |
| DoubleKey k = new DoubleKey(type,newName); | |
| Method[] methods = (Method[])propertyMethods.get(k); | |
| if(methods == null) { | |
| methods = new Method[5]; | |
| propertyMethods.put(k,methods); | |
| } | |
| methods[funcType] = method; | |
| } | |
| void addToListenerHash(String name, Method method, int funcType) { | |
| String newName; | |
| Class type; | |
| switch(funcType) { | |
| case ADD: | |
| type = method.getParameterTypes()[0]; | |
| newName = name.substring(3,name.length()-8); | |
| break; | |
| case REMOVE: | |
| type = method.getParameterTypes()[0]; | |
| newName = name.substring(6,name.length()-8); | |
| break; | |
| default: | |
| return; | |
| } | |
| newName = capitalize(newName); | |
| if (newName.length() == 0) | |
| return; | |
| DoubleKey k = new DoubleKey(type,newName); | |
| Method[] methods = (Method[])listenerMethods.get(k); | |
| if(methods == null) { | |
| methods = new Method[2]; | |
| listenerMethods.put(k,methods); | |
| } | |
| methods[funcType] = method; | |
| } | |
| /* Determines whether <code>stopClass</code> is <code>null</code> | |
| * or <code>declaringClass<code> is a true subclass of <code>stopClass</code>. | |
| * This expression is useful to detect whether a method should be introspected or not. | |
| * The rules for this are described in {@link java.beans.Introspector.getBeanInfo(Class, Class)}. | |
| */ | |
| static boolean isReachable(Class stopClass, Class declaringClass) { | |
| return stopClass == null || (stopClass.isAssignableFrom(declaringClass) && !stopClass.equals(declaringClass)); | |
| } | |
| /** Transforms a property name into a part of a method name. | |
| * E.g. "value" becomes "Value" which can then concatenated with | |
| * "set", "get" or "is" to form a valid method name. | |
| * | |
| * Implementation notes: | |
| * If "" is the argument, it is returned without changes. | |
| * If <code>null</code> is the argument, <code>null</code> is returned. | |
| * | |
| * @param name Name of a property. | |
| * @return Part of a method name of a property. | |
| */ | |
| static String capitalize(String name) { | |
| try { | |
| if(Character.isUpperCase(name.charAt(0))) { | |
| return name; | |
| } else { | |
| char[] c = name.toCharArray(); | |
| c[0] = Character.toLowerCase(c[0]); | |
| return new String(c); | |
| } | |
| } catch(StringIndexOutOfBoundsException E) { | |
| return name; | |
| } catch(NullPointerException E) { | |
| return null; | |
| } | |
| } | |
| } | |
| /** This class is a hashmap key that consists of a <code>Class</code> and a | |
| * <code>String</code> element. | |
| * | |
| * It is used for XXX: find out what this is used for | |
| * | |
| * @author John Keiser | |
| * @author Robert Schuster | |
| */ | |
| class DoubleKey { | |
| Class type; | |
| String name; | |
| DoubleKey(Class type, String name) { | |
| this.type = type; | |
| this.name = name; | |
| } | |
| Class getType() { | |
| return type; | |
| } | |
| String getName() { | |
| return name; | |
| } | |
| public boolean equals(Object o) { | |
| if(o instanceof DoubleKey) { | |
| DoubleKey d = (DoubleKey)o; | |
| return d.type.equals(type) && d.name.equals(name); | |
| } else { | |
| return false; | |
| } | |
| } | |
| public int hashCode() { | |
| return type.hashCode() ^ name.hashCode(); | |
| } | |
| } |