Source for java.beans.EventSetDescriptor

   1: /* java.beans.EventSetDescriptor
   2:    Copyright (C) 1998 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 java.beans;
  40: 
  41: import gnu.java.lang.ClassHelper;
  42: 
  43: import java.lang.reflect.Method;
  44: import java.lang.reflect.Modifier;
  45: import java.util.Vector;
  46: 
  47: /**
  48:  ** EventSetDescriptor describes the hookup between an event source
  49:  ** class and an event listener class.
  50:  **
  51:  ** EventSets have several attributes: the listener class, the events
  52:  ** that can be fired to the listener (methods in the listener class), and
  53:  ** an add and remove listener method from the event firer's class.<P>
  54:  **
  55:  ** The methods have these constraints on them:<P>
  56:  ** <UL>
  57:  ** <LI>event firing methods: must have <CODE>void</CODE> return value.  Any
  58:  ** parameters and exceptions are allowed.  May be public, protected or
  59:  ** package-protected. (Don't ask me why that is, I'm just following the spec.
  60:  ** The only place it is even mentioned is in the Java Beans white paper, and
  61:  ** there it is only implied.)</LI>
  62:  ** <LI>add listener method: must have <CODE>void</CODE> return value.  Must
  63:  ** take exactly one argument, of the listener class's type.  May fire either
  64:  ** zero exceptions, or one exception of type <CODE>java.util.TooManyListenersException</CODE>.
  65:  ** Must be public.</LI>
  66:  ** <LI>remove listener method: must have <CODE>void</CODE> return value.
  67:  ** Must take exactly one argument, of the listener class's type.  May not
  68:  ** fire any exceptions.  Must be public.</LI>
  69:  ** </UL>
  70:  **
  71:  ** A final constraint is that event listener classes must extend from EventListener.<P>
  72:  **
  73:  ** There are also various design patterns associated with some of the methods
  74:  ** of construction. Those are explained in more detail in the appropriate
  75:  ** constructors.<P>
  76:  **
  77:  ** <STRONG>Documentation Convention:</STRONG> for proper
  78:  ** Internalization of Beans inside an RAD tool, sometimes there
  79:  ** are two names for a property or method: a programmatic, or
  80:  ** locale-independent name, which can be used anywhere, and a
  81:  ** localized, display name, for ease of use.  In the
  82:  ** documentation I will specify different String values as
  83:  ** either <EM>programmatic</EM> or <EM>localized</EM> to
  84:  ** make this distinction clear.
  85:  **
  86:  ** @author John Keiser
  87:  ** @since JDK1.1
  88:  ** @version 1.1.0, 31 May 1998
  89:  **/
  90: 
  91: public class EventSetDescriptor extends FeatureDescriptor {
  92:     private Method addListenerMethod;
  93:     private Method removeListenerMethod;
  94:     private Class listenerType;
  95:     private MethodDescriptor[] listenerMethodDescriptors;
  96:     private Method[] listenerMethods;
  97: 
  98:     private boolean unicast;
  99:     private boolean inDefaultEventSet = true;
 100: 
 101:     /** Create a new EventSetDescriptor.
 102:      ** This version of the constructor enforces the rules imposed on the methods
 103:      ** described at the top of this class, as well as searching for:<P>
 104:      ** <OL>
 105:      ** <LI>The event-firing method must be non-private with signature
 106:      ** <CODE>void &lt;listenerMethodName&gt;(&lt;eventSetName&gt;Event)</CODE>
 107:      ** (where <CODE>&lt;eventSetName&gt;</CODE> has its first character capitalized
 108:      ** by the constructor and the Event is a descendant of
 109:      ** <CODE>java.util.EventObject</CODE>) in class <CODE>listenerType</CODE>
 110:      ** (any exceptions may be thrown).
 111:      ** <B>Implementation note:</B> Note that there could conceivably be multiple
 112:      ** methods with this type of signature (example: java.util.MouseEvent vs.
 113:      ** my.very.own.MouseEvent).  In this implementation, all methods fitting the
 114:      ** description will be put into the <CODE>EventSetDescriptor</CODE>, even
 115:      ** though the spec says only one should be chosen (they probably weren't thinking as
 116:      ** pathologically as I was).  I don't like arbitrarily choosing things.
 117:      ** If your class has only one such signature, as most do, you'll have no problems.</LI>
 118:      ** <LI>The add and remove methods must be public and named
 119:      ** <CODE>void add&lt;eventSetName&gt;Listener(&lt;listenerType&gt;)</CODE> and
 120:      ** <CODE>void remove&lt;eventSetName&gt;Listener(&lt;listenerType&gt;)</CODE> in
 121:      ** in class <CODE>eventSourceClass</CODE>, where
 122:      ** <CODE>&lt;eventSetName&gt;</CODE> will have its first letter capitalized.
 123:      ** Standard exception rules (see class description) apply.</LI>
 124:      ** </OL>
 125:      ** @param eventSourceClass the class containing the add/remove listener methods.
 126:      ** @param eventSetName the programmatic name of the event set, generally starting
 127:      ** with a lowercase letter (i.e. fooManChu instead of FooManChu).  This will be used
 128:      ** to generate the name of the event object as well as the names of the add and
 129:      ** remove methods.
 130:      ** @param listenerType the class containing the event firing method.
 131:      ** @param listenerMethodName the name of the event firing method.
 132:      ** @exception IntrospectionException if listenerType is not an EventListener,
 133:      **                                   or if methods are not found or are invalid.
 134:      **/
 135:     public EventSetDescriptor(Class  eventSourceClass,
 136:                   String eventSetName,
 137:                   Class  listenerType,
 138:                   String listenerMethodName) throws IntrospectionException {
 139:         setName(eventSetName);
 140:         if(!java.util.EventListener.class.isAssignableFrom(listenerType)) {
 141:             throw new IntrospectionException("Listener type is not an EventListener.");
 142:         }
 143: 
 144:         String[] names = new String[1];
 145:         names[0] = listenerMethodName;
 146: 
 147:         try {
 148:             eventSetName = Character.toUpperCase(eventSetName.charAt(0)) + eventSetName.substring(1);
 149:         } catch(StringIndexOutOfBoundsException e) {
 150:             eventSetName = "";
 151:         }
 152: 
 153:         findMethods(eventSourceClass,listenerType,names,"add"+eventSetName+"Listener","remove"+eventSetName+"Listener",eventSetName+"Event");
 154:         this.listenerType = listenerType;
 155:         checkAddListenerUnicast();
 156:         if(this.removeListenerMethod.getExceptionTypes().length > 0) {
 157:             throw new IntrospectionException("Listener remove method throws exceptions.");
 158:         }
 159:     }
 160: 
 161:     /** Create a new EventSetDescriptor.
 162:      ** This form of the constructor allows you to specify the names of the methods and adds
 163:      ** no new constraints on top of the rules already described at the top of the class.<P>
 164:      ** 
 165:      ** @param eventSourceClass the class containing the add and remove listener methods.
 166:      ** @param eventSetName the programmatic name of the event set, generally starting
 167:      ** with a lowercase letter (i.e. fooManChu instead of FooManChu).
 168:      ** @param listenerType the class containing the event firing methods.
 169:      ** @param listenerMethodNames the names of the even firing methods.
 170:      ** @param addListenerMethodName the name of the add listener method.
 171:      ** @param removeListenerMethodName the name of the remove listener method.
 172:      ** @exception IntrospectionException if listenerType is not an EventListener
 173:      **                                   or if methods are not found or are invalid.
 174:      **/
 175:     public EventSetDescriptor(Class eventSourceClass,
 176:                    String eventSetName,
 177:                    Class listenerType,
 178:                    String[] listenerMethodNames,
 179:                    String addListenerMethodName,
 180:                    String removeListenerMethodName) throws IntrospectionException {
 181:         setName(eventSetName);
 182:         if(!java.util.EventListener.class.isAssignableFrom(listenerType)) {
 183:             throw new IntrospectionException("Listener type is not an EventListener.");
 184:         }
 185: 
 186:         findMethods(eventSourceClass,listenerType,listenerMethodNames,addListenerMethodName,removeListenerMethodName,null);
 187:         this.listenerType = listenerType;
 188:         checkAddListenerUnicast();
 189:         if(this.removeListenerMethod.getExceptionTypes().length > 0) {
 190:             throw new IntrospectionException("Listener remove method throws exceptions.");
 191:         }
 192:     }
 193: 
 194:     /** Create a new EventSetDescriptor.
 195:      ** This form of constructor allows you to explicitly say which methods do what, and
 196:      ** no reflection is done by the EventSetDescriptor.  The methods are, however,
 197:      ** checked to ensure that they follow the rules set forth at the top of the class.
 198:      ** @param eventSetName the programmatic name of the event set, generally starting
 199:      ** with a lowercase letter (i.e. fooManChu instead of FooManChu).
 200:      ** @param listenerType the class containing the listenerMethods.
 201:      ** @param listenerMethods the event firing methods.
 202:      ** @param addListenerMethod the add listener method.
 203:      ** @param removeListenerMethod the remove listener method.
 204:      ** @exception IntrospectionException if the listenerType is not an EventListener,
 205:      **                                   or any of the methods are invalid.
 206:      **/
 207:     public EventSetDescriptor(String eventSetName,
 208:                    Class listenerType,
 209:                    Method[] listenerMethods,
 210:                    Method addListenerMethod,
 211:                    Method removeListenerMethod) throws IntrospectionException {
 212:         setName(eventSetName);
 213:         if(!java.util.EventListener.class.isAssignableFrom(listenerType)) {
 214:             throw new IntrospectionException("Listener type is not an EventListener.");
 215:         }
 216: 
 217:             this.listenerMethods = listenerMethods;
 218:         this.addListenerMethod = addListenerMethod;
 219:         this.removeListenerMethod = removeListenerMethod;
 220:         this.listenerType = listenerType;
 221:         checkMethods();
 222:         checkAddListenerUnicast();
 223:         if(this.removeListenerMethod.getExceptionTypes().length > 0) {
 224:             throw new IntrospectionException("Listener remove method throws exceptions.");
 225:         }
 226:     }
 227: 
 228:     /** Create a new EventSetDescriptor.
 229:      ** This form of constructor allows you to explicitly say which methods do what, and
 230:      ** no reflection is done by the EventSetDescriptor.  The methods are, however,
 231:      ** checked to ensure that they follow the rules set forth at the top of the class.
 232:      ** @param eventSetName the programmatic name of the event set, generally starting
 233:      ** with a lowercase letter (i.e. fooManChu instead of FooManChu).
 234:      ** @param listenerType the class containing the listenerMethods.
 235:      ** @param listenerMethodDescriptors the event firing methods.
 236:      ** @param addListenerMethod the add listener method.
 237:      ** @param removeListenerMethod the remove listener method.
 238:      ** @exception IntrospectionException if the listenerType is not an EventListener,
 239:      **                                   or any of the methods are invalid.
 240:      **/
 241:     public EventSetDescriptor(String eventSetName,
 242:                    Class listenerType,
 243:                    MethodDescriptor[] listenerMethodDescriptors,
 244:                    Method addListenerMethod,
 245:                    Method removeListenerMethod) throws IntrospectionException {
 246:         setName(eventSetName);
 247:         if(!java.util.EventListener.class.isAssignableFrom(listenerType)) {
 248:             throw new IntrospectionException("Listener type is not an EventListener.");
 249:         }
 250: 
 251:         this.listenerMethodDescriptors = listenerMethodDescriptors;
 252:         this.listenerMethods = new Method[listenerMethodDescriptors.length];
 253:         for(int i=0;i<this.listenerMethodDescriptors.length;i++) {
 254:             this.listenerMethods[i] = this.listenerMethodDescriptors[i].getMethod();
 255:         }
 256: 
 257:         this.addListenerMethod = addListenerMethod;
 258:         this.removeListenerMethod = removeListenerMethod;
 259:         this.listenerType = listenerType;
 260:         checkMethods();
 261:         checkAddListenerUnicast();
 262:         if(this.removeListenerMethod.getExceptionTypes().length > 0) {
 263:             throw new IntrospectionException("Listener remove method throws exceptions.");
 264:         }
 265:     }
 266: 
 267:     /** Get the class that contains the event firing methods. **/
 268:     public Class getListenerType() {
 269:         return listenerType;
 270:     }
 271: 
 272:     /** Get the event firing methods. **/
 273:     public Method[] getListenerMethods() {
 274:         return listenerMethods;
 275:     }
 276: 
 277:     /** Get the event firing methods as MethodDescriptors. **/
 278:     public MethodDescriptor[] getListenerMethodDescriptors() {
 279:         if(listenerMethodDescriptors == null) {
 280:             listenerMethodDescriptors = new MethodDescriptor[listenerMethods.length];
 281:             for(int i=0;i<listenerMethods.length;i++) {
 282:                 listenerMethodDescriptors[i] = new MethodDescriptor(listenerMethods[i]);
 283:             }
 284:         }
 285:         return listenerMethodDescriptors;
 286:     }
 287: 
 288:     /** Get the add listener method. **/
 289:     public Method getAddListenerMethod() {
 290:         return addListenerMethod;
 291:     }
 292: 
 293:     /** Get the remove listener method. **/
 294:     public Method getRemoveListenerMethod() {
 295:         return removeListenerMethod;
 296:     }
 297: 
 298:     /** Set whether or not multiple listeners may be added.
 299:      ** @param unicast whether or not multiple listeners may be added.
 300:      **/
 301:     public void setUnicast(boolean unicast) {
 302:         this.unicast = unicast;
 303:     }
 304: 
 305:     /** Get whether or not multiple listeners may be added.  (Defaults to false.) **/
 306:     public boolean isUnicast() {
 307:         return unicast;
 308:     }
 309: 
 310:     /** Set whether or not this is in the default event set.
 311:      ** @param inDefaultEventSet whether this is in the default event set.
 312:      **/
 313:     public void setInDefaultEventSet(boolean inDefaultEventSet) {
 314:         this.inDefaultEventSet = inDefaultEventSet;
 315:     }
 316: 
 317:     /** Get whether or not this is in the default event set.  (Defaults to true.)**/
 318:     public boolean isInDefaultEventSet() {
 319:         return inDefaultEventSet;
 320:     }
 321: 
 322:     private void checkAddListenerUnicast() throws IntrospectionException {
 323:         Class[] addListenerExceptions = this.addListenerMethod.getExceptionTypes();
 324:         if(addListenerExceptions.length > 1) {
 325:             throw new IntrospectionException("Listener add method throws too many exceptions.");
 326:         } else if(addListenerExceptions.length == 1
 327:               && !java.util.TooManyListenersException.class.isAssignableFrom(addListenerExceptions[0])) {
 328:             throw new IntrospectionException("Listener add method throws too many exceptions.");
 329:         }
 330:     }
 331: 
 332:     private void checkMethods() throws IntrospectionException {
 333:         if(!addListenerMethod.getDeclaringClass().isAssignableFrom(removeListenerMethod.getDeclaringClass())
 334:            && !removeListenerMethod.getDeclaringClass().isAssignableFrom(addListenerMethod.getDeclaringClass())) {
 335:             throw new IntrospectionException("add and remove listener methods do not come from the same class.  This is bad.");
 336:         }
 337:         if(!addListenerMethod.getReturnType().equals(java.lang.Void.TYPE)
 338:                    || addListenerMethod.getParameterTypes().length != 1
 339:            || !listenerType.equals(addListenerMethod.getParameterTypes()[0])
 340:            || !Modifier.isPublic(addListenerMethod.getModifiers())) {
 341:             throw new IntrospectionException("Add Listener Method invalid.");
 342:         }
 343:         if(!removeListenerMethod.getReturnType().equals(java.lang.Void.TYPE)
 344:                    || removeListenerMethod.getParameterTypes().length != 1
 345:            || !listenerType.equals(removeListenerMethod.getParameterTypes()[0])
 346:            || removeListenerMethod.getExceptionTypes().length > 0
 347:            || !Modifier.isPublic(removeListenerMethod.getModifiers())) {
 348:             throw new IntrospectionException("Remove Listener Method invalid.");
 349:         }
 350: 
 351:         for(int i=0;i<listenerMethods.length;i++) {
 352:             if(!listenerMethods[i].getReturnType().equals(java.lang.Void.TYPE)
 353:                || Modifier.isPrivate(listenerMethods[i].getModifiers())) {
 354:                 throw new IntrospectionException("Event Method " + listenerMethods[i].getName() + " non-void or private.");
 355:             }
 356:             if(!listenerMethods[i].getDeclaringClass().isAssignableFrom(listenerType)) {
 357:                 throw new IntrospectionException("Event Method " + listenerMethods[i].getName() + " not from class " + listenerType.getName());
 358:             }
 359:         }
 360:     }
 361: 
 362:     private void findMethods(Class eventSourceClass,
 363:         Class listenerType,
 364:         String listenerMethodNames[],
 365:         String addListenerMethodName,
 366:         String removeListenerMethodName,
 367:         String absurdEventClassCheckName) throws IntrospectionException {
 368: 
 369:         /* Find add listener method and remove listener method. */
 370:         Class[] listenerArgList = new Class[1];
 371:         listenerArgList[0] = listenerType;
 372:         try {
 373:             this.addListenerMethod = eventSourceClass.getMethod(addListenerMethodName,listenerArgList);
 374:         } catch(SecurityException E) {
 375:             throw new IntrospectionException("SecurityException trying to access method " + addListenerMethodName + ".");
 376:         } catch(NoSuchMethodException E) {
 377:             throw new IntrospectionException("Could not find method " + addListenerMethodName + ".");
 378:         }
 379: 
 380:         if(this.addListenerMethod == null || !this.addListenerMethod.getReturnType().equals(java.lang.Void.TYPE)) {
 381:             throw new IntrospectionException("Add listener method does not exist, is not public, or is not void.");
 382:         }
 383: 
 384:         try {
 385:             this.removeListenerMethod = eventSourceClass.getMethod(removeListenerMethodName,listenerArgList);
 386:         } catch(SecurityException E) {
 387:             throw new IntrospectionException("SecurityException trying to access method " + removeListenerMethodName + ".");
 388:         } catch(NoSuchMethodException E) {
 389:             throw new IntrospectionException("Could not find method " + removeListenerMethodName + ".");
 390:         }
 391:         if(this.removeListenerMethod == null || !this.removeListenerMethod.getReturnType().equals(java.lang.Void.TYPE)) {
 392:             throw new IntrospectionException("Remove listener method does not exist, is not public, or is not void.");
 393:         }
 394: 
 395:         /* Find the listener methods. */
 396:         Method[] methods;
 397:         try {
 398:             methods = ClassHelper.getAllMethods(listenerType);
 399:         } catch(SecurityException E) {
 400:             throw new IntrospectionException("Security: You cannot access fields in this class.");
 401:         }
 402: 
 403:         Vector chosenMethods = new Vector();
 404:         boolean[] listenerMethodFound = new boolean[listenerMethodNames.length];
 405:         for(int i=0;i<methods.length;i++) {
 406:             if(Modifier.isPrivate(methods[i].getModifiers())) {
 407:                 continue;
 408:             }
 409:             Method currentMethod = methods[i];
 410:             Class retval = currentMethod.getReturnType();
 411:             if(retval.equals(java.lang.Void.TYPE)) {
 412:                 for(int j=0;j<listenerMethodNames.length;j++) {
 413:                     if(currentMethod.getName().equals(listenerMethodNames[j])
 414:                        && (absurdEventClassCheckName == null
 415:                            || (currentMethod.getParameterTypes().length == 1
 416:                                && ((currentMethod.getParameterTypes()[0]).getName().equals(absurdEventClassCheckName)
 417:                                    || (currentMethod.getParameterTypes()[0]).getName().endsWith("."+absurdEventClassCheckName)
 418:                                   )
 419:                               )
 420:                           )
 421:                       ) {
 422:                         chosenMethods.addElement(currentMethod);
 423:                         listenerMethodFound[j] = true;
 424:                     }
 425:                 }
 426:             }
 427:         }
 428: 
 429:         /* Make sure we found all the methods we were looking for. */
 430:         for(int i=0;i<listenerMethodFound.length;i++) {
 431:             if(!listenerMethodFound[i]) {
 432:                 throw new IntrospectionException("Could not find event method " + listenerMethodNames[i]);
 433:             }
 434:         }
 435: 
 436:         /* Now that we've chosen the listener methods we want, store them. */
 437:         this.listenerMethods = new Method[chosenMethods.size()];
 438:         for(int i=0;i<chosenMethods.size();i++) {
 439:             this.listenerMethods[i] = (Method)chosenMethods.elementAt(i);
 440:         }
 441:     }
 442: }