1 /*
   2  * Copyright (c) 1996, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.beans;
  27 
  28 import java.lang.ref.Reference;
  29 import java.lang.reflect.Method;
  30 import java.lang.reflect.Modifier;
  31 
  32 /**
  33  * An EventSetDescriptor describes a group of events that a given Java
  34  * bean fires.
  35  * <P>
  36  * The given group of events are all delivered as method calls on a single
  37  * event listener interface, and an event listener object can be registered
  38  * via a call on a registration method supplied by the event source.
  39  */
  40 public class EventSetDescriptor extends FeatureDescriptor {
  41 
  42     private MethodDescriptor[] listenerMethodDescriptors;
  43     private MethodDescriptor addMethodDescriptor;
  44     private MethodDescriptor removeMethodDescriptor;
  45     private MethodDescriptor getMethodDescriptor;
  46 
  47     private Reference<Method[]> listenerMethodsRef;
  48     private Reference<? extends Class<?>> listenerTypeRef;
  49 
  50     private boolean unicast;
  51     private boolean inDefaultEventSet = true;
  52 
  53     /**
  54      * Creates an <TT>EventSetDescriptor</TT> assuming that you are
  55      * following the most simple standard design pattern where a named
  56      * event &quot;fred&quot; is (1) delivered as a call on the single method of
  57      * interface FredListener, (2) has a single argument of type FredEvent,
  58      * and (3) where the FredListener may be registered with a call on an
  59      * addFredListener method of the source component and removed with a
  60      * call on a removeFredListener method.
  61      *
  62      * @param sourceClass  The class firing the event.
  63      * @param eventSetName  The programmatic name of the event.  E.g. &quot;fred&quot;.
  64      *          Note that this should normally start with a lower-case character.
  65      * @param listenerType  The target interface that events
  66      *          will get delivered to.
  67      * @param listenerMethodName  The method that will get called when the event gets
  68      *          delivered to its target listener interface.
  69      * @exception IntrospectionException if an exception occurs during
  70      *              introspection.
  71      */
  72     public EventSetDescriptor(Class<?> sourceClass, String eventSetName,
  73                 Class<?> listenerType, String listenerMethodName)
  74                 throws IntrospectionException {
  75         this(sourceClass, eventSetName, listenerType,
  76              new String[] { listenerMethodName },
  77              Introspector.ADD_PREFIX + getListenerClassName(listenerType),
  78              Introspector.REMOVE_PREFIX + getListenerClassName(listenerType),
  79              Introspector.GET_PREFIX + getListenerClassName(listenerType) + "s");
  80 
  81         String eventName = NameGenerator.capitalize(eventSetName) + "Event";
  82         Method[] listenerMethods = getListenerMethods();
  83         if (listenerMethods.length > 0) {
  84             Class<?>[] args = getParameterTypes(getClass0(), listenerMethods[0]);
  85             // Check for EventSet compliance. Special case for vetoableChange. See 4529996
  86             if (!"vetoableChange".equals(eventSetName) && !args[0].getName().endsWith(eventName)) {
  87                 throw new IntrospectionException("Method \"" + listenerMethodName +
  88                                                  "\" should have argument \"" +
  89                                                  eventName + "\"");
  90             }
  91         }
  92     }
  93 
  94     private static String getListenerClassName(Class<?> cls) {
  95         String className = cls.getName();
  96         return className.substring(className.lastIndexOf('.') + 1);
  97     }
  98 
  99     /**
 100      * Creates an <TT>EventSetDescriptor</TT> from scratch using
 101      * string names.
 102      *
 103      * @param sourceClass  The class firing the event.
 104      * @param eventSetName The programmatic name of the event set.
 105      *          Note that this should normally start with a lower-case character.
 106      * @param listenerType  The Class of the target interface that events
 107      *          will get delivered to.
 108      * @param listenerMethodNames The names of the methods that will get called
 109      *          when the event gets delivered to its target listener interface.
 110      * @param addListenerMethodName  The name of the method on the event source
 111      *          that can be used to register an event listener object.
 112      * @param removeListenerMethodName  The name of the method on the event source
 113      *          that can be used to de-register an event listener object.
 114      * @exception IntrospectionException if an exception occurs during
 115      *              introspection.
 116      */
 117     public EventSetDescriptor(Class<?> sourceClass,
 118                 String eventSetName,
 119                 Class<?> listenerType,
 120                 String listenerMethodNames[],
 121                 String addListenerMethodName,
 122                 String removeListenerMethodName)
 123                 throws IntrospectionException {
 124         this(sourceClass, eventSetName, listenerType,
 125              listenerMethodNames, addListenerMethodName,
 126              removeListenerMethodName, null);
 127     }
 128 
 129     /**
 130      * This constructor creates an EventSetDescriptor from scratch using
 131      * string names.
 132      *
 133      * @param sourceClass  The class firing the event.
 134      * @param eventSetName The programmatic name of the event set.
 135      *          Note that this should normally start with a lower-case character.
 136      * @param listenerType  The Class of the target interface that events
 137      *          will get delivered to.
 138      * @param listenerMethodNames The names of the methods that will get called
 139      *          when the event gets delivered to its target listener interface.
 140      * @param addListenerMethodName  The name of the method on the event source
 141      *          that can be used to register an event listener object.
 142      * @param removeListenerMethodName  The name of the method on the event source
 143      *          that can be used to de-register an event listener object.
 144      * @param getListenerMethodName The method on the event source that
 145      *          can be used to access the array of event listener objects.
 146      * @exception IntrospectionException if an exception occurs during
 147      *              introspection.
 148      * @since 1.4
 149      */
 150     public EventSetDescriptor(Class<?> sourceClass,
 151                 String eventSetName,
 152                 Class<?> listenerType,
 153                 String listenerMethodNames[],
 154                 String addListenerMethodName,
 155                 String removeListenerMethodName,
 156                 String getListenerMethodName)
 157                 throws IntrospectionException {
 158         if (sourceClass == null || eventSetName == null || listenerType == null) {
 159             throw new NullPointerException();
 160         }
 161         setName(eventSetName);
 162         setClass0(sourceClass);
 163         setListenerType(listenerType);
 164 
 165         Method[] listenerMethods = new Method[listenerMethodNames.length];
 166         for (int i = 0; i < listenerMethodNames.length; i++) {
 167             // Check for null names
 168             if (listenerMethodNames[i] == null) {
 169                 throw new NullPointerException();
 170             }
 171             listenerMethods[i] = getMethod(listenerType, listenerMethodNames[i], 1);
 172         }
 173         setListenerMethods(listenerMethods);
 174 
 175         setAddListenerMethod(getMethod(sourceClass, addListenerMethodName, 1));
 176         setRemoveListenerMethod(getMethod(sourceClass, removeListenerMethodName, 1));
 177 
 178         // Be more forgiving of not finding the getListener method.
 179         Method method = Introspector.findMethod(sourceClass, getListenerMethodName, 0);
 180         if (method != null) {
 181             setGetListenerMethod(method);
 182         }
 183     }
 184 
 185     private static Method getMethod(Class<?> cls, String name, int args)
 186         throws IntrospectionException {
 187         if (name == null) {
 188             return null;
 189         }
 190         Method method = Introspector.findMethod(cls, name, args);
 191         if ((method == null) || Modifier.isStatic(method.getModifiers())) {
 192             throw new IntrospectionException("Method not found: " + name +
 193                                              " on class " + cls.getName());
 194         }
 195         return method;
 196     }
 197 
 198     /**
 199      * Creates an <TT>EventSetDescriptor</TT> from scratch using
 200      * <TT>java.lang.reflect.Method</TT> and <TT>java.lang.Class</TT> objects.
 201      *
 202      * @param eventSetName The programmatic name of the event set.
 203      * @param listenerType The Class for the listener interface.
 204      * @param listenerMethods  An array of Method objects describing each
 205      *          of the event handling methods in the target listener.
 206      * @param addListenerMethod  The method on the event source
 207      *          that can be used to register an event listener object.
 208      * @param removeListenerMethod  The method on the event source
 209      *          that can be used to de-register an event listener object.
 210      * @exception IntrospectionException if an exception occurs during
 211      *              introspection.
 212      */
 213     public EventSetDescriptor(String eventSetName,
 214                 Class<?> listenerType,
 215                 Method listenerMethods[],
 216                 Method addListenerMethod,
 217                 Method removeListenerMethod)
 218                 throws IntrospectionException {
 219         this(eventSetName, listenerType, listenerMethods,
 220              addListenerMethod, removeListenerMethod, null);
 221     }
 222 
 223     /**
 224      * This constructor creates an EventSetDescriptor from scratch using
 225      * java.lang.reflect.Method and java.lang.Class objects.
 226      *
 227      * @param eventSetName The programmatic name of the event set.
 228      * @param listenerType The Class for the listener interface.
 229      * @param listenerMethods  An array of Method objects describing each
 230      *          of the event handling methods in the target listener.
 231      * @param addListenerMethod  The method on the event source
 232      *          that can be used to register an event listener object.
 233      * @param removeListenerMethod  The method on the event source
 234      *          that can be used to de-register an event listener object.
 235      * @param getListenerMethod The method on the event source
 236      *          that can be used to access the array of event listener objects.
 237      * @exception IntrospectionException if an exception occurs during
 238      *              introspection.
 239      * @since 1.4
 240      */
 241     public EventSetDescriptor(String eventSetName,
 242                 Class<?> listenerType,
 243                 Method listenerMethods[],
 244                 Method addListenerMethod,
 245                 Method removeListenerMethod,
 246                 Method getListenerMethod)
 247                 throws IntrospectionException {
 248         setName(eventSetName);
 249         setListenerMethods(listenerMethods);
 250         setAddListenerMethod(addListenerMethod);
 251         setRemoveListenerMethod( removeListenerMethod);
 252         setGetListenerMethod(getListenerMethod);
 253         setListenerType(listenerType);
 254     }
 255 
 256     /**
 257      * Creates an <TT>EventSetDescriptor</TT> from scratch using
 258      * <TT>java.lang.reflect.MethodDescriptor</TT> and <TT>java.lang.Class</TT>
 259      *  objects.
 260      *
 261      * @param eventSetName The programmatic name of the event set.
 262      * @param listenerType The Class for the listener interface.
 263      * @param listenerMethodDescriptors  An array of MethodDescriptor objects
 264      *           describing each of the event handling methods in the
 265      *           target listener.
 266      * @param addListenerMethod  The method on the event source
 267      *          that can be used to register an event listener object.
 268      * @param removeListenerMethod  The method on the event source
 269      *          that can be used to de-register an event listener object.
 270      * @exception IntrospectionException if an exception occurs during
 271      *              introspection.
 272      */
 273     public EventSetDescriptor(String eventSetName,
 274                 Class<?> listenerType,
 275                 MethodDescriptor listenerMethodDescriptors[],
 276                 Method addListenerMethod,
 277                 Method removeListenerMethod)
 278                 throws IntrospectionException {
 279         setName(eventSetName);
 280         this.listenerMethodDescriptors = (listenerMethodDescriptors != null)
 281                 ? listenerMethodDescriptors.clone()
 282                 : null;
 283         setAddListenerMethod(addListenerMethod);
 284         setRemoveListenerMethod(removeListenerMethod);
 285         setListenerType(listenerType);
 286     }
 287 
 288     /**
 289      * Gets the <TT>Class</TT> object for the target interface.
 290      *
 291      * @return The Class object for the target interface that will
 292      * get invoked when the event is fired.
 293      */
 294     public Class<?> getListenerType() {
 295         return (this.listenerTypeRef != null)
 296                 ? this.listenerTypeRef.get()
 297                 : null;
 298     }
 299 
 300     private void setListenerType(Class<?> cls) {
 301         this.listenerTypeRef = getWeakReference(cls);
 302     }
 303 
 304     /**
 305      * Gets the methods of the target listener interface.
 306      *
 307      * @return An array of <TT>Method</TT> objects for the target methods
 308      * within the target listener interface that will get called when
 309      * events are fired.
 310      */
 311     public synchronized Method[] getListenerMethods() {
 312         Method[] methods = getListenerMethods0();
 313         if (methods == null) {
 314             if (listenerMethodDescriptors != null) {
 315                 methods = new Method[listenerMethodDescriptors.length];
 316                 for (int i = 0; i < methods.length; i++) {
 317                     methods[i] = listenerMethodDescriptors[i].getMethod();
 318                 }
 319             }
 320             setListenerMethods(methods);
 321         }
 322         return methods;
 323     }
 324 
 325     private void setListenerMethods(Method[] methods) {
 326         if (methods == null) {
 327             return;
 328         }
 329         if (listenerMethodDescriptors == null) {
 330             listenerMethodDescriptors = new MethodDescriptor[methods.length];
 331             for (int i = 0; i < methods.length; i++) {
 332                 listenerMethodDescriptors[i] = new MethodDescriptor(methods[i]);
 333             }
 334         }
 335         this.listenerMethodsRef = getSoftReference(methods);
 336     }
 337 
 338     private Method[] getListenerMethods0() {
 339         return (this.listenerMethodsRef != null)
 340                 ? this.listenerMethodsRef.get()
 341                 : null;
 342     }
 343 
 344     /**
 345      * Gets the <code>MethodDescriptor</code>s of the target listener interface.
 346      *
 347      * @return An array of <code>MethodDescriptor</code> objects for the target methods
 348      * within the target listener interface that will get called when
 349      * events are fired.
 350      */
 351     public synchronized MethodDescriptor[] getListenerMethodDescriptors() {
 352         return (this.listenerMethodDescriptors != null)
 353                 ? this.listenerMethodDescriptors.clone()
 354                 : null;
 355     }
 356 
 357     /**
 358      * Gets the method used to add event listeners.
 359      *
 360      * @return The method used to register a listener at the event source.
 361      */
 362     public synchronized Method getAddListenerMethod() {
 363         return getMethod(this.addMethodDescriptor);
 364     }
 365 
 366     private synchronized void setAddListenerMethod(Method method) {
 367         if (method == null) {
 368             return;
 369         }
 370         if (getClass0() == null) {
 371             setClass0(method.getDeclaringClass());
 372         }
 373         addMethodDescriptor = new MethodDescriptor(method);
 374         setTransient(method.getAnnotation(Transient.class));
 375     }
 376 
 377     /**
 378      * Gets the method used to remove event listeners.
 379      *
 380      * @return The method used to remove a listener at the event source.
 381      */
 382     public synchronized Method getRemoveListenerMethod() {
 383         return getMethod(this.removeMethodDescriptor);
 384     }
 385 
 386     private synchronized void setRemoveListenerMethod(Method method) {
 387         if (method == null) {
 388             return;
 389         }
 390         if (getClass0() == null) {
 391             setClass0(method.getDeclaringClass());
 392         }
 393         removeMethodDescriptor = new MethodDescriptor(method);
 394         setTransient(method.getAnnotation(Transient.class));
 395     }
 396 
 397     /**
 398      * Gets the method used to access the registered event listeners.
 399      *
 400      * @return The method used to access the array of listeners at the event
 401      *         source or null if it doesn't exist.
 402      * @since 1.4
 403      */
 404     public synchronized Method getGetListenerMethod() {
 405         return getMethod(this.getMethodDescriptor);
 406     }
 407 
 408     private synchronized void setGetListenerMethod(Method method) {
 409         if (method == null) {
 410             return;
 411         }
 412         if (getClass0() == null) {
 413             setClass0(method.getDeclaringClass());
 414         }
 415         getMethodDescriptor = new MethodDescriptor(method);
 416         setTransient(method.getAnnotation(Transient.class));
 417     }
 418 
 419     /**
 420      * Mark an event set as unicast (or not).
 421      *
 422      * @param unicast  True if the event set is unicast.
 423      */
 424     public void setUnicast(boolean unicast) {
 425         this.unicast = unicast;
 426     }
 427 
 428     /**
 429      * Normally event sources are multicast.  However there are some
 430      * exceptions that are strictly unicast.
 431      *
 432      * @return  <TT>true</TT> if the event set is unicast.
 433      *          Defaults to <TT>false</TT>.
 434      */
 435     public boolean isUnicast() {
 436         return unicast;
 437     }
 438 
 439     /**
 440      * Marks an event set as being in the &quot;default&quot; set (or not).
 441      * By default this is <TT>true</TT>.
 442      *
 443      * @param inDefaultEventSet <code>true</code> if the event set is in
 444      *                          the &quot;default&quot; set,
 445      *                          <code>false</code> if not
 446      */
 447     public void setInDefaultEventSet(boolean inDefaultEventSet) {
 448         this.inDefaultEventSet = inDefaultEventSet;
 449     }
 450 
 451     /**
 452      * Reports if an event set is in the &quot;default&quot; set.
 453      *
 454      * @return  <TT>true</TT> if the event set is in
 455      *          the &quot;default&quot; set.  Defaults to <TT>true</TT>.
 456      */
 457     public boolean isInDefaultEventSet() {
 458         return inDefaultEventSet;
 459     }
 460 
 461     /*
 462      * Package-private constructor
 463      * Merge two event set descriptors.  Where they conflict, give the
 464      * second argument (y) priority over the first argument (x).
 465      *
 466      * @param x  The first (lower priority) EventSetDescriptor
 467      * @param y  The second (higher priority) EventSetDescriptor
 468      */
 469     EventSetDescriptor(EventSetDescriptor x, EventSetDescriptor y) {
 470         super(x,y);
 471         listenerMethodDescriptors = x.listenerMethodDescriptors;
 472         if (y.listenerMethodDescriptors != null) {
 473             listenerMethodDescriptors = y.listenerMethodDescriptors;
 474         }
 475 
 476         listenerTypeRef = x.listenerTypeRef;
 477         if (y.listenerTypeRef != null) {
 478             listenerTypeRef = y.listenerTypeRef;
 479         }
 480 
 481         addMethodDescriptor = x.addMethodDescriptor;
 482         if (y.addMethodDescriptor != null) {
 483             addMethodDescriptor = y.addMethodDescriptor;
 484         }
 485 
 486         removeMethodDescriptor = x.removeMethodDescriptor;
 487         if (y.removeMethodDescriptor != null) {
 488             removeMethodDescriptor = y.removeMethodDescriptor;
 489         }
 490 
 491         getMethodDescriptor = x.getMethodDescriptor;
 492         if (y.getMethodDescriptor != null) {
 493             getMethodDescriptor = y.getMethodDescriptor;
 494         }
 495 
 496         unicast = y.unicast;
 497         if (!x.inDefaultEventSet || !y.inDefaultEventSet) {
 498             inDefaultEventSet = false;
 499         }
 500     }
 501 
 502     /*
 503      * Package-private dup constructor
 504      * This must isolate the new object from any changes to the old object.
 505      */
 506     EventSetDescriptor(EventSetDescriptor old) {
 507         super(old);
 508         if (old.listenerMethodDescriptors != null) {
 509             int len = old.listenerMethodDescriptors.length;
 510             listenerMethodDescriptors = new MethodDescriptor[len];
 511             for (int i = 0; i < len; i++) {
 512                 listenerMethodDescriptors[i] = new MethodDescriptor(
 513                                         old.listenerMethodDescriptors[i]);
 514             }
 515         }
 516         listenerTypeRef = old.listenerTypeRef;
 517 
 518         addMethodDescriptor = old.addMethodDescriptor;
 519         removeMethodDescriptor = old.removeMethodDescriptor;
 520         getMethodDescriptor = old.getMethodDescriptor;
 521 
 522         unicast = old.unicast;
 523         inDefaultEventSet = old.inDefaultEventSet;
 524     }
 525 
 526     void appendTo(StringBuilder sb) {
 527         appendTo(sb, "unicast", this.unicast);
 528         appendTo(sb, "inDefaultEventSet", this.inDefaultEventSet);
 529         appendTo(sb, "listenerType", this.listenerTypeRef);
 530         appendTo(sb, "getListenerMethod", getMethod(this.getMethodDescriptor));
 531         appendTo(sb, "addListenerMethod", getMethod(this.addMethodDescriptor));
 532         appendTo(sb, "removeListenerMethod", getMethod(this.removeMethodDescriptor));
 533     }
 534 
 535     private static Method getMethod(MethodDescriptor descriptor) {
 536         return (descriptor != null)
 537                 ? descriptor.getMethod()
 538                 : null;
 539     }
 540 }