Source for gnu.java.beans.IntrospectionIncubator

   1: /* gnu.java.beans.IntrospectionIncubator
   2:    Copyright (C) 1998, 2004 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10:  
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package gnu.java.beans;
  40: 
  41: import gnu.java.lang.ArrayHelper;
  42: import gnu.java.lang.ClassHelper;
  43: 
  44: import java.beans.BeanInfo;
  45: import java.beans.EventSetDescriptor;
  46: import java.beans.IndexedPropertyDescriptor;
  47: import java.beans.IntrospectionException;
  48: import java.beans.Introspector;
  49: import java.beans.MethodDescriptor;
  50: import java.beans.PropertyDescriptor;
  51: import java.lang.reflect.Array;
  52: import java.lang.reflect.Method;
  53: import java.lang.reflect.Modifier;
  54: import java.util.Enumeration;
  55: import java.util.Hashtable;
  56: import java.util.Vector;
  57: 
  58: /**
  59:  ** IntrospectionIncubator takes in a bunch of Methods, and
  60:  ** Introspects only those Methods you give it.<br/>
  61:  **
  62:  ** See {@link addMethod(Method)} for details which rules apply to
  63:  ** the methods.
  64:  **
  65:  ** @author John Keiser
  66:  ** @author Robert Schuster
  67:  ** @see gnu.java.beans.ExplicitBeanInfo
  68:  ** @see java.beans.BeanInfo
  69:  **/
  70: 
  71: public class IntrospectionIncubator {
  72:     Hashtable propertyMethods = new Hashtable();
  73:     Hashtable listenerMethods = new Hashtable();
  74:     Vector otherMethods = new Vector();
  75: 
  76:     Class propertyStopClass;
  77:     Class eventStopClass;
  78:     Class methodStopClass;
  79: 
  80:     public IntrospectionIncubator() {
  81:     }
  82: 
  83:     /** Examines the given method and files it in a suitable collection.
  84:      * It files the method as a property method if it finds:
  85:      * <ul>
  86:      * <li>boolean "is" getter</li>
  87:      * <li>"get" style getter</li>
  88:      * <li>single argument setter</li>
  89:      * <li>indiced setter and getter</li> 
  90:      * </ul>
  91:      * It files the method as a listener method if all of these rules apply:
  92:      * <ul>
  93:      * <li>the method name starts with "add" or "remove"</li>
  94:      * <li>there is only a single argument</li>
  95:      * <li>the argument type is a subclass of <code>java.util.EventListener</code></li>
  96:      * </ul>
  97:      * All public methods are filed as such. 
  98:      *   
  99:      * @param method The method instance to examine.
 100:      */
 101:     public void addMethod(Method method) {
 102:         if(Modifier.isPublic(method.getModifiers())) {
 103:             String name = ClassHelper.getTruncatedName(method.getName());
 104:             Class retType = method.getReturnType();
 105:             Class[] params = method.getParameterTypes();
 106:             boolean isVoid = retType.equals(java.lang.Void.TYPE);
 107:             Class methodClass = method.getDeclaringClass();
 108:             
 109:             /* Accepts the method for examination if no stop class is given or the method is declared in a subclass of the stop class.
 110:              * The rules for this are described in {@link java.beans.Introspector.getBeanInfo(Class, Class)}.
 111:              * This block finds out whether the method is a suitable getter or setter method (or read/write method).  
 112:              */
 113:             if(isReachable(propertyStopClass, methodClass)) {
 114:                 /* At this point a method may regarded as a property's read or write method if its name
 115:                  * starts with "is", "get" or "set". However, if a method is static it cannot be part
 116:                  * of a property.
 117:                  */
 118:                 if(Modifier.isStatic(method.getModifiers())) {
 119:                     // files method as other because it is static
 120:                     otherMethods.addElement(method);
 121:                 } else if(name.startsWith("is")
 122:                    && retType.equals(java.lang.Boolean.TYPE)
 123:                    && params.length == 0) {
 124:                        // files method as boolean "is" style getter
 125:                     addToPropertyHash(name,method,IS);
 126:                 } else if(name.startsWith("get") && !isVoid) {
 127:                     if(params.length == 0) {
 128:                         // files as legal non-argument getter
 129:                         addToPropertyHash(name,method,GET);
 130:                     } else if(params.length == 1 && params[0].equals(java.lang.Integer.TYPE)) {
 131:                         // files as legal indiced getter
 132:                         addToPropertyHash(name,method,GET_I);
 133:                     } else {
 134:                         // files as other because the method's signature is not Bean-like
 135:                         otherMethods.addElement(method);
 136:                     }
 137:                 } else if(name.startsWith("set") && isVoid) {
 138:                     if(params.length == 1) {
 139:                         // files as legal single-argument setter method
 140:                         addToPropertyHash(name,method,SET);
 141:                     } else if(params.length == 2 && params[0].equals(java.lang.Integer.TYPE)) {
 142:                         // files as legal indiced setter method
 143:                         addToPropertyHash(name,method,SET_I);
 144:                     } else {
 145:                         // files as other because the method's signature is not Bean-like
 146:                         otherMethods.addElement(method);
 147:                     }
 148:                 }
 149:             }
 150:             
 151:             if(isReachable(eventStopClass, methodClass)) {
 152:                 if(name.startsWith("add")
 153:                           && isVoid
 154:                           && params.length == 1
 155:                           && java.util.EventListener.class.isAssignableFrom(params[0])) {
 156:                     addToListenerHash(name,method,ADD);
 157:                 } else if(name.startsWith("remove")
 158:                           && isVoid
 159:                           && params.length == 1
 160:                           && java.util.EventListener.class.isAssignableFrom(params[0])) {
 161:                     addToListenerHash(name,method,REMOVE);
 162:                 }
 163:             }
 164:              
 165:             if(isReachable(methodStopClass, methodClass)) {
 166:                 // files as reachable public method
 167:                 otherMethods.addElement(method);
 168:             }
 169:             
 170:         }
 171:     }
 172: 
 173:     public void addMethods(Method[] m) {
 174:         for(int i=0;i<m.length;i++) {
 175:             addMethod(m[i]);
 176:         }
 177:     }
 178: 
 179:     public void setPropertyStopClass(Class c) {
 180:         propertyStopClass = c;
 181:     }
 182: 
 183:     public void setEventStopClass(Class c) {
 184:         eventStopClass = c;
 185:     }
 186: 
 187:     public void setMethodStopClass(Class c) {
 188:         methodStopClass = c;
 189:     }
 190: 
 191: 
 192:     public BeanInfoEmbryo getBeanInfoEmbryo() throws IntrospectionException {
 193:         BeanInfoEmbryo b = new BeanInfoEmbryo();
 194:         findXXX(b,IS);
 195:         findXXXInt(b,GET_I);
 196:         findXXXInt(b,SET_I);
 197:         findXXX(b,GET);
 198:         findXXX(b,SET);
 199:         findAddRemovePairs(b);
 200:         for(int i=0;i<otherMethods.size();i++) {
 201:             MethodDescriptor newMethod = new MethodDescriptor((Method)otherMethods.elementAt(i));
 202:             if(!b.hasMethod(newMethod)) {
 203:                 b.addMethod(new MethodDescriptor((Method)otherMethods.elementAt(i)));
 204:             }
 205:         }
 206:         return b;
 207:     }
 208: 
 209:     public BeanInfo getBeanInfo() throws IntrospectionException {
 210:         return getBeanInfoEmbryo().getBeanInfo();
 211:     }
 212: 
 213: 
 214:     void findAddRemovePairs(BeanInfoEmbryo b) throws IntrospectionException {
 215:         Enumeration listenerEnum = listenerMethods.keys();
 216:         while(listenerEnum.hasMoreElements()) {
 217:             DoubleKey k = (DoubleKey)listenerEnum.nextElement();
 218:             Method[] m = (Method[])listenerMethods.get(k);
 219:             if(m[ADD] != null && m[REMOVE] != null) {
 220:                 EventSetDescriptor e = new EventSetDescriptor(Introspector.decapitalize(k.getName()),
 221:                                                               k.getType(), k.getType().getMethods(),
 222:                                                               m[ADD],m[REMOVE]);
 223:                 e.setUnicast(ArrayHelper.contains(m[ADD].getExceptionTypes(),java.util.TooManyListenersException.class));
 224:                 if(!b.hasEvent(e)) {
 225:                     b.addEvent(e);
 226:                 }
 227:             }
 228:         }
 229:     }
 230: 
 231:     void findXXX(BeanInfoEmbryo b, int funcType) throws IntrospectionException {
 232:         Enumeration keys = propertyMethods.keys();
 233:         while(keys.hasMoreElements()) {
 234:             DoubleKey k = (DoubleKey)keys.nextElement();
 235:             Method[] m = (Method[])propertyMethods.get(k);
 236:             if(m[funcType] != null) {
 237:                 PropertyDescriptor p = new PropertyDescriptor(Introspector.decapitalize(k.getName()),
 238:                                                      m[IS] != null ? m[IS] : m[GET],
 239:                                                      m[SET]);
 240:                 if(m[SET] != null) {
 241:                     p.setConstrained(ArrayHelper.contains(m[SET].getExceptionTypes(),java.beans.PropertyVetoException.class));
 242:                 }
 243:                 if(!b.hasProperty(p)) {
 244:                     b.addProperty(p);
 245:                 }
 246:             }
 247:         }
 248:     }
 249: 
 250:     void findXXXInt(BeanInfoEmbryo b, int funcType) throws IntrospectionException {
 251:         Enumeration keys = propertyMethods.keys();
 252:         while(keys.hasMoreElements()) {
 253:             DoubleKey k = (DoubleKey)keys.nextElement();
 254:             Method[] m = (Method[])propertyMethods.get(k);
 255:             if(m[funcType] != null) {
 256:                 boolean constrained;
 257:                 if(m[SET_I] != null) {
 258:                     constrained = ArrayHelper.contains(m[SET_I].getExceptionTypes(),java.beans.PropertyVetoException.class);
 259:                 } else {
 260:                     constrained = false;
 261:                 }
 262: 
 263:                 /** Find out if there is an array type get or set **/
 264:                 Class arrayType = Array.newInstance(k.getType(),0).getClass();
 265:                 DoubleKey findSetArray = new DoubleKey(arrayType,k.getName());
 266:                 Method[] m2 = (Method[])propertyMethods.get(findSetArray);
 267:                 IndexedPropertyDescriptor p;
 268:                 if(m2 == null) {
 269:                     p = new IndexedPropertyDescriptor(Introspector.decapitalize(k.getName()),
 270:                                                           null,null,
 271:                                                           m[GET_I],m[SET_I]);
 272:                 } else {
 273:                     if(constrained && m2[SET] != null) {
 274:                         constrained = ArrayHelper.contains(m2[SET].getExceptionTypes(),java.beans.PropertyVetoException.class);
 275:                     }
 276:                     p = new IndexedPropertyDescriptor(Introspector.decapitalize(k.getName()),
 277:                                                           m2[GET],m2[SET],
 278:                                                           m[GET_I],m[SET_I]);
 279:                 }
 280:                 p.setConstrained(constrained);
 281:                 if(!b.hasProperty(p)) {
 282:                     b.addProperty(p);
 283:                 }
 284:             }
 285:         }
 286:     }
 287: 
 288:     static final int IS=0;
 289:     static final int GET_I=1;
 290:     static final int SET_I=2;
 291:     static final int GET=3;
 292:     static final int SET=4;
 293: 
 294:     static final int ADD=0;
 295:     static final int REMOVE=1;
 296: 
 297:     void addToPropertyHash(String name, Method method, int funcType) {
 298:         String newName;
 299:         Class type;
 300: 
 301:         switch(funcType) {
 302:             case IS:
 303:                 type = java.lang.Boolean.TYPE;
 304:                 newName = name.substring(2);
 305:                 break;
 306:             case GET_I:
 307:                 type = method.getReturnType();
 308:                 newName = name.substring(3);
 309:                 break;
 310:             case SET_I:
 311:                 type = method.getParameterTypes()[1];
 312:                 newName = name.substring(3);
 313:                 break;
 314:             case GET:
 315:                 type = method.getReturnType();
 316:                 newName = name.substring(3);
 317:                 break;
 318:             case SET:
 319:                 type = method.getParameterTypes()[0];
 320:                 newName = name.substring(3);
 321:                 break;
 322:             default:
 323:                 return;
 324:         }
 325:         newName = capitalize(newName);
 326:         if (newName.length() == 0)
 327:             return;
 328: 
 329:         DoubleKey k = new DoubleKey(type,newName);
 330:         Method[] methods = (Method[])propertyMethods.get(k);
 331:         if(methods == null) {
 332:             methods = new Method[5];
 333:             propertyMethods.put(k,methods);
 334:         }
 335:         methods[funcType] = method;
 336:     }
 337: 
 338:     void addToListenerHash(String name, Method method, int funcType) {
 339:         String newName;
 340:         Class type;
 341: 
 342:         switch(funcType) {
 343:             case ADD:
 344:                 type = method.getParameterTypes()[0];
 345:                 newName = name.substring(3,name.length()-8);
 346:                 break;
 347:             case REMOVE:
 348:                 type = method.getParameterTypes()[0];
 349:                 newName = name.substring(6,name.length()-8);
 350:                 break;
 351:             default:
 352:                 return;
 353:         }
 354:         newName = capitalize(newName);
 355:         if (newName.length() == 0)
 356:             return;
 357: 
 358:         DoubleKey k = new DoubleKey(type,newName);
 359:         Method[] methods = (Method[])listenerMethods.get(k);
 360:         if(methods == null) {
 361:             methods = new Method[2];
 362:             listenerMethods.put(k,methods);
 363:         }
 364:         methods[funcType] = method;
 365:     }
 366: 
 367:     /* Determines whether <code>stopClass</code> is <code>null</code>
 368:      * or <code>declaringClass<code> is a true subclass of <code>stopClass</code>.
 369:      * This expression is useful to detect whether a method should be introspected or not.
 370:      * The rules for this are described in {@link java.beans.Introspector.getBeanInfo(Class, Class)}.
 371:      */
 372:     static boolean isReachable(Class stopClass, Class declaringClass) {
 373:         return stopClass == null || (stopClass.isAssignableFrom(declaringClass) && !stopClass.equals(declaringClass));
 374:     }
 375: 
 376:     /** Transforms a property name into a part of a method name.
 377:      * E.g. "value" becomes "Value" which can then concatenated with
 378:      * "set", "get" or "is" to form a valid method name.
 379:      * 
 380:      * Implementation notes:
 381:      * If "" is the argument, it is returned without changes.
 382:      * If <code>null</code> is the argument, <code>null</code> is returned.
 383:      * 
 384:      * @param name Name of a property.
 385:      * @return Part of a method name of a property.
 386:      */
 387:     static String capitalize(String name) {
 388:         try {
 389:             if(Character.isUpperCase(name.charAt(0))) {
 390:                 return name;
 391:             } else {
 392:                 char[] c = name.toCharArray();
 393:                 c[0] = Character.toLowerCase(c[0]);
 394:                 return new String(c);
 395:             }
 396:         } catch(StringIndexOutOfBoundsException E) {
 397:             return name;
 398:         } catch(NullPointerException E) {
 399:             return null;
 400:         }
 401:     }
 402: }
 403: 
 404: /** This class is a hashmap key that consists of a <code>Class</code> and a
 405:  * <code>String</code> element.
 406:  * 
 407:  * It is used for XXX: find out what this is used for
 408:  * 
 409:  * @author John Keiser
 410:  * @author Robert Schuster
 411:  */ 
 412: class DoubleKey {
 413:     Class type;
 414:     String name;
 415: 
 416:     DoubleKey(Class type, String name) {
 417:         this.type = type;
 418:         this.name = name;
 419:     }
 420: 
 421:     Class getType() {
 422:         return type;
 423:     }
 424: 
 425:     String getName() {
 426:         return name;
 427:     }
 428: 
 429:     public boolean equals(Object o) {
 430:         if(o instanceof DoubleKey) {
 431:             DoubleKey d = (DoubleKey)o;
 432:             return d.type.equals(type) && d.name.equals(name);
 433:         } else {
 434:             return false;
 435:         }
 436:     }
 437: 
 438:     public int hashCode() {
 439:         return type.hashCode() ^ name.hashCode();
 440:     }
 441: }