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 package java.beans;
26
27 import java.lang.ref.Reference;
28 import java.lang.reflect.Method;
29 import java.lang.reflect.Modifier;
30
31 import com.sun.beans.introspect.EventSetInfo;
32
33 /**
34  * An EventSetDescriptor describes a group of events that a given Java
35  * bean fires.
36  * <P>
37  * The given group of events are all delivered as method calls on a single
38  * event listener interface, and an event listener object can be registered
39  * via a call on a registration method supplied by the event source.
40  *
41  * @since 1.1
42  */

43 public class EventSetDescriptor extends FeatureDescriptor {
44
45     private MethodDescriptor[] listenerMethodDescriptors;
46     private MethodDescriptor addMethodDescriptor;
47     private MethodDescriptor removeMethodDescriptor;
48     private MethodDescriptor getMethodDescriptor;
49
50     private Reference<Method[]> listenerMethodsRef;
51     private Reference<? extends Class<?>> listenerTypeRef;
52
53     private boolean unicast;
54     private boolean inDefaultEventSet = true;
55
56     /**
57      * Creates an {@code EventSetDescriptor} assuming that you are
58      * following the most simple standard design pattern where a named
59      * event "fred" is (1) delivered as a call on the single method of
60      * interface FredListener, (2) has a single argument of type FredEvent,
61      * and (3) where the FredListener may be registered with a call on an
62      * addFredListener method of the source component and removed with a
63      * call on a removeFredListener method.
64      *
65      * @param sourceClass  The class firing the event.
66      * @param eventSetName  The programmatic name of the event.  E.g. "fred".
67      *          Note that this should normally start with a lower-case character.
68      * @param listenerType  The target interface that events
69      *          will get delivered to.
70      * @param listenerMethodName  The method that will get called when the event gets
71      *          delivered to its target listener interface.
72      * @exception IntrospectionException if an exception occurs during
73      *              introspection.
74      */

75     public EventSetDescriptor(Class<?> sourceClass, String eventSetName,
76                 Class<?> listenerType, String listenerMethodName)
77                 throws IntrospectionException {
78         this(sourceClass, eventSetName, listenerType,
79              new String[] { listenerMethodName },
80              Introspector.ADD_PREFIX + getListenerClassName(listenerType),
81              Introspector.REMOVE_PREFIX + getListenerClassName(listenerType),
82              Introspector.GET_PREFIX + getListenerClassName(listenerType) + "s");
83
84         String eventName = NameGenerator.capitalize(eventSetName) + "Event";
85         Method[] listenerMethods = getListenerMethods();
86         if (listenerMethods.length > 0) {
87             Class<?>[] args = getParameterTypes(getClass0(), listenerMethods[0]);
88             // Check for EventSet compliance. Special case for vetoableChange. See 4529996
89             if (!"vetoableChange".equals(eventSetName) && !args[0].getName().endsWith(eventName)) {
90                 throw new IntrospectionException("Method \"" + listenerMethodName +
91                                                  "\" should have argument \"" +
92                                                  eventName + "\"");
93             }
94         }
95     }
96
97     private static String getListenerClassName(Class<?> cls) {
98         String className = cls.getName();
99         return className.substring(className.lastIndexOf('.') + 1);
100     }
101
102     /**
103      * Creates an {@code EventSetDescriptor} from scratch using
104      * string names.
105      *
106      * @param sourceClass  The class firing the event.
107      * @param eventSetName The programmatic name of the event set.
108      *          Note that this should normally start with a lower-case character.
109      * @param listenerType  The Class of the target interface that events
110      *          will get delivered to.
111      * @param listenerMethodNames The names of the methods that will get called
112      *          when the event gets delivered to its target listener interface.
113      * @param addListenerMethodName  The name of the method on the event source
114      *          that can be used to register an event listener object.
115      * @param removeListenerMethodName  The name of the method on the event source
116      *          that can be used to de-register an event listener object.
117      * @exception IntrospectionException if an exception occurs during
118      *              introspection.
119      */

120     public EventSetDescriptor(Class<?> sourceClass,
121                 String eventSetName,
122                 Class<?> listenerType,
123                 String listenerMethodNames[],
124                 String addListenerMethodName,
125                 String removeListenerMethodName)
126                 throws IntrospectionException {
127         this(sourceClass, eventSetName, listenerType,
128              listenerMethodNames, addListenerMethodName,
129              removeListenerMethodName, null);
130     }
131
132     /**
133      * This constructor creates an EventSetDescriptor from scratch using
134      * string names.
135      *
136      * @param sourceClass  The class firing the event.
137      * @param eventSetName The programmatic name of the event set.
138      *          Note that this should normally start with a lower-case character.
139      * @param listenerType  The Class of the target interface that events
140      *          will get delivered to.
141      * @param listenerMethodNames The names of the methods that will get called
142      *          when the event gets delivered to its target listener interface.
143      * @param addListenerMethodName  The name of the method on the event source
144      *          that can be used to register an event listener object.
145      * @param removeListenerMethodName  The name of the method on the event source
146      *          that can be used to de-register an event listener object.
147      * @param getListenerMethodName The method on the event source that
148      *          can be used to access the array of event listener objects.
149      * @exception IntrospectionException if an exception occurs during
150      *              introspection.
151      * @since 1.4
152      */

153     public EventSetDescriptor(Class<?> sourceClass,
154                 String eventSetName,
155                 Class<?> listenerType,
156                 String listenerMethodNames[],
157                 String addListenerMethodName,
158                 String removeListenerMethodName,
159                 String getListenerMethodName)
160                 throws IntrospectionException {
161         if (sourceClass == null || eventSetName == null || listenerType == null) {
162             throw new NullPointerException();
163         }
164         setName(eventSetName);
165         setClass0(sourceClass);
166         setListenerType(listenerType);
167
168         Method[] listenerMethods = new Method[listenerMethodNames.length];
169         for (int i = 0; i < listenerMethodNames.length; i++) {
170             // Check for null names
171             if (listenerMethodNames[i] == null) {
172                 throw new NullPointerException();
173             }
174             listenerMethods[i] = getMethod(listenerType, listenerMethodNames[i], 1);
175         }
176         setListenerMethods(listenerMethods);
177
178         setAddListenerMethod(getMethod(sourceClass, addListenerMethodName, 1));
179         setRemoveListenerMethod(getMethod(sourceClass, removeListenerMethodName, 1));
180
181         // Be more forgiving of not finding the getListener method.
182         Method method = Introspector.findMethod(sourceClass, getListenerMethodName, 0);
183         if (method != null) {
184             setGetListenerMethod(method);
185         }
186     }
187
188     private static Method getMethod(Class<?> cls, String name, int args)
189         throws IntrospectionException {
190         if (name == null) {
191             return null;
192         }
193         Method method = Introspector.findMethod(cls, name, args);
194         if ((method == null) || Modifier.isStatic(method.getModifiers())) {
195             throw new IntrospectionException("Method not found: " + name +
196                                              " on class " + cls.getName());
197         }
198         return method;
199     }
200
201     /**
202      * Creates an {@code EventSetDescriptor} from scratch using
203      * {@code java.lang.reflect.Method} and {@code java.lang.Class} objects.
204      *
205      * @param eventSetName The programmatic name of the event set.
206      * @param listenerType The Class for the listener interface.
207      * @param listenerMethods  An array of Method objects describing each
208      *          of the event handling methods in the target listener.
209      * @param addListenerMethod  The method on the event source
210      *          that can be used to register an event listener object.
211      * @param removeListenerMethod  The method on the event source
212      *          that can be used to de-register an event listener object.
213      * @exception IntrospectionException if an exception occurs during
214      *              introspection.
215      */

216     public EventSetDescriptor(String eventSetName,
217                 Class<?> listenerType,
218                 Method listenerMethods[],
219                 Method addListenerMethod,
220                 Method removeListenerMethod)
221                 throws IntrospectionException {
222         this(eventSetName, listenerType, listenerMethods,
223              addListenerMethod, removeListenerMethod, null);
224     }
225
226     /**
227      * This constructor creates an EventSetDescriptor from scratch using
228      * java.lang.reflect.Method and java.lang.Class objects.
229      *
230      * @param eventSetName The programmatic name of the event set.
231      * @param listenerType The Class for the listener interface.
232      * @param listenerMethods  An array of Method objects describing each
233      *          of the event handling methods in the target listener.
234      * @param addListenerMethod  The method on the event source
235      *          that can be used to register an event listener object.
236      * @param removeListenerMethod  The method on the event source
237      *          that can be used to de-register an event listener object.
238      * @param getListenerMethod The method on the event source
239      *          that can be used to access the array of event listener objects.
240      * @exception IntrospectionException if an exception occurs during
241      *              introspection.
242      * @since 1.4
243      */

244     public EventSetDescriptor(String eventSetName,
245                 Class<?> listenerType,
246                 Method listenerMethods[],
247                 Method addListenerMethod,
248                 Method removeListenerMethod,
249                 Method getListenerMethod)
250                 throws IntrospectionException {
251         setName(eventSetName);
252         setListenerMethods(listenerMethods);
253         setAddListenerMethod(addListenerMethod);
254         setRemoveListenerMethod( removeListenerMethod);
255         setGetListenerMethod(getListenerMethod);
256         setListenerType(listenerType);
257     }
258
259     EventSetDescriptor(String base, EventSetInfo info, Method... methods) {
260         setName(Introspector.decapitalize(base));
261         setListenerMethods(methods);
262         setAddListenerMethod(info.getAddMethod());
263         setRemoveListenerMethod(info.getRemoveMethod());
264         setGetListenerMethod(info.getGetMethod());
265         setListenerType(info.getListenerType());
266         setUnicast(info.isUnicast());
267     }
268
269     /**
270      * Creates an {@code EventSetDescriptor} from scratch using
271      * {@code java.lang.reflect.MethodDescriptor} and {@code java.lang.Class}
272      *  objects.
273      *
274      * @param eventSetName The programmatic name of the event set.
275      * @param listenerType The Class for the listener interface.
276      * @param listenerMethodDescriptors  An array of MethodDescriptor objects
277      *           describing each of the event handling methods in the
278      *           target listener.
279      * @param addListenerMethod  The method on the event source
280      *          that can be used to register an event listener object.
281      * @param removeListenerMethod  The method on the event source
282      *          that can be used to de-register an event listener object.
283      * @exception IntrospectionException if an exception occurs during
284      *              introspection.
285      */

286     public EventSetDescriptor(String eventSetName,
287                 Class<?> listenerType,
288                 MethodDescriptor listenerMethodDescriptors[],
289                 Method addListenerMethod,
290                 Method removeListenerMethod)
291                 throws IntrospectionException {
292         setName(eventSetName);
293         this.listenerMethodDescriptors = (listenerMethodDescriptors != null)
294                 ? listenerMethodDescriptors.clone()
295                 : null;
296         setAddListenerMethod(addListenerMethod);
297         setRemoveListenerMethod(removeListenerMethod);
298         setListenerType(listenerType);
299     }
300
301     /**
302      * Gets the {@code Class} object for the target interface.
303      *
304      * @return The Class object for the target interface that will
305      * get invoked when the event is fired.
306      */

307     public Class<?> getListenerType() {
308         return (this.listenerTypeRef != null)
309                 ? this.listenerTypeRef.get()
310                 : null;
311     }
312
313     private void setListenerType(Class<?> cls) {
314         this.listenerTypeRef = getWeakReference(cls);
315     }
316
317     /**
318      * Gets the methods of the target listener interface.
319      *
320      * @return An array of {@code Method} objects for the target methods
321      * within the target listener interface that will get called when
322      * events are fired.
323      */

324     public synchronized Method[] getListenerMethods() {
325         Method[] methods = getListenerMethods0();
326         if (methods == null) {
327             if (listenerMethodDescriptors != null) {
328                 methods = new Method[listenerMethodDescriptors.length];
329                 for (int i = 0; i < methods.length; i++) {
330                     methods[i] = listenerMethodDescriptors[i].getMethod();
331                 }
332             }
333             setListenerMethods(methods);
334         }
335         return methods;
336     }
337
338     private void setListenerMethods(Method[] methods) {
339         if (methods == null) {
340             return;
341         }
342         if (listenerMethodDescriptors == null) {
343             listenerMethodDescriptors = new MethodDescriptor[methods.length];
344             for (int i = 0; i < methods.length; i++) {
345                 listenerMethodDescriptors[i] = new MethodDescriptor(methods[i]);
346             }
347         }
348         this.listenerMethodsRef = getSoftReference(methods);
349     }
350
351     private Method[] getListenerMethods0() {
352         return (this.listenerMethodsRef != null)
353                 ? this.listenerMethodsRef.get()
354                 : null;
355     }
356
357     /**
358      * Gets the {@code MethodDescriptor}s of the target listener interface.
359      *
360      * @return An array of {@code MethodDescriptor} objects for the target methods
361      * within the target listener interface that will get called when
362      * events are fired.
363      */

364     public synchronized MethodDescriptor[] getListenerMethodDescriptors() {
365         return (this.listenerMethodDescriptors != null)
366                 ? this.listenerMethodDescriptors.clone()
367                 : null;
368     }
369
370     /**
371      * Gets the method used to add event listeners.
372      *
373      * @return The method used to register a listener at the event source.
374      */

375     public synchronized Method getAddListenerMethod() {
376         return getMethod(this.addMethodDescriptor);
377     }
378
379     private synchronized void setAddListenerMethod(Method method) {
380         if (method == null) {
381             return;
382         }
383         if (getClass0() == null) {
384             setClass0(method.getDeclaringClass());
385         }
386         addMethodDescriptor = new MethodDescriptor(method);
387         setTransient(method.getAnnotation(Transient.class));
388     }
389
390     /**
391      * Gets the method used to remove event listeners.
392      *
393      * @return The method used to remove a listener at the event source.
394      */

395     public synchronized Method getRemoveListenerMethod() {
396         return getMethod(this.removeMethodDescriptor);
397     }
398
399     private synchronized void setRemoveListenerMethod(Method method) {
400         if (method == null) {
401             return;
402         }
403         if (getClass0() == null) {
404             setClass0(method.getDeclaringClass());
405         }
406         removeMethodDescriptor = new MethodDescriptor(method);
407         setTransient(method.getAnnotation(Transient.class));
408     }
409
410     /**
411      * Gets the method used to access the registered event listeners.
412      *
413      * @return The method used to access the array of listeners at the event
414      *         source or null if it doesn't exist.
415      * @since 1.4
416      */

417     public synchronized Method getGetListenerMethod() {
418         return getMethod(this.getMethodDescriptor);
419     }
420
421     private synchronized void setGetListenerMethod(Method method) {
422         if (method == null) {
423             return;
424         }
425         if (getClass0() == null) {
426             setClass0(method.getDeclaringClass());
427         }
428         getMethodDescriptor = new MethodDescriptor(method);
429         setTransient(method.getAnnotation(Transient.class));
430     }
431
432     /**
433      * Mark an event set as unicast (or not).
434      *
435      * @param unicast  True if the event set is unicast.
436      */

437     public void setUnicast(boolean unicast) {
438         this.unicast = unicast;
439     }
440
441     /**
442      * Normally event sources are multicast.  However there are some
443      * exceptions that are strictly unicast.
444      *
445      * @return  {@code trueif the event set is unicast.
446      *          Defaults to {@code false}.
447      */

448     public boolean isUnicast() {
449         return unicast;
450     }
451
452     /**
453      * Marks an event set as being in the "default" set (or not).
454      * By default this is {@code true}.
455      *
456      * @param inDefaultEventSet {@code trueif the event set is in
457      *                          the "default" set,
458      *                          {@code falseif not
459      */

460     public void setInDefaultEventSet(boolean inDefaultEventSet) {
461         this.inDefaultEventSet = inDefaultEventSet;
462     }
463
464     /**
465      * Reports if an event set is in the "default" set.
466      *
467      * @return  {@code trueif the event set is in
468      *          the "default" set.  Defaults to {@code true}.
469      */

470     public boolean isInDefaultEventSet() {
471         return inDefaultEventSet;
472     }
473
474     /*
475      * Package-private constructor
476      * Merge two event set descriptors.  Where they conflict, give the
477      * second argument (y) priority over the first argument (x).
478      *
479      * @param x  The first (lower priority) EventSetDescriptor
480      * @param y  The second (higher priority) EventSetDescriptor
481      */

482     EventSetDescriptor(EventSetDescriptor x, EventSetDescriptor y) {
483         super(x,y);
484         listenerMethodDescriptors = x.listenerMethodDescriptors;
485         if (y.listenerMethodDescriptors != null) {
486             listenerMethodDescriptors = y.listenerMethodDescriptors;
487         }
488
489         listenerTypeRef = x.listenerTypeRef;
490         if (y.listenerTypeRef != null) {
491             listenerTypeRef = y.listenerTypeRef;
492         }
493
494         addMethodDescriptor = x.addMethodDescriptor;
495         if (y.addMethodDescriptor != null) {
496             addMethodDescriptor = y.addMethodDescriptor;
497         }
498
499         removeMethodDescriptor = x.removeMethodDescriptor;
500         if (y.removeMethodDescriptor != null) {
501             removeMethodDescriptor = y.removeMethodDescriptor;
502         }
503
504         getMethodDescriptor = x.getMethodDescriptor;
505         if (y.getMethodDescriptor != null) {
506             getMethodDescriptor = y.getMethodDescriptor;
507         }
508
509         unicast = y.unicast;
510         if (!x.inDefaultEventSet || !y.inDefaultEventSet) {
511             inDefaultEventSet = false;
512         }
513     }
514
515     /*
516      * Package-private dup constructor
517      * This must isolate the new object from any changes to the old object.
518      */

519     EventSetDescriptor(EventSetDescriptor old) {
520         super(old);
521         if (old.listenerMethodDescriptors != null) {
522             int len = old.listenerMethodDescriptors.length;
523             listenerMethodDescriptors = new MethodDescriptor[len];
524             for (int i = 0; i < len; i++) {
525                 listenerMethodDescriptors[i] = new MethodDescriptor(
526                                         old.listenerMethodDescriptors[i]);
527             }
528         }
529         listenerTypeRef = old.listenerTypeRef;
530
531         addMethodDescriptor = old.addMethodDescriptor;
532         removeMethodDescriptor = old.removeMethodDescriptor;
533         getMethodDescriptor = old.getMethodDescriptor;
534
535         unicast = old.unicast;
536         inDefaultEventSet = old.inDefaultEventSet;
537     }
538
539     void appendTo(StringBuilder sb) {
540         appendTo(sb, "unicast"this.unicast);
541         appendTo(sb, "inDefaultEventSet"this.inDefaultEventSet);
542         appendTo(sb, "listenerType"this.listenerTypeRef);
543         appendTo(sb, "getListenerMethod", getMethod(this.getMethodDescriptor));
544         appendTo(sb, "addListenerMethod", getMethod(this.addMethodDescriptor));
545         appendTo(sb, "removeListenerMethod", getMethod(this.removeMethodDescriptor));
546     }
547
548     private static Method getMethod(MethodDescriptor descriptor) {
549         return (descriptor != null)
550                 ? descriptor.getMethod()
551                 : null;
552     }
553 }
554