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