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: }