| /* 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(); |
| } |
| } |