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 */
25package java.beans;
26
27import java.lang.ref.Reference;
28import java.lang.reflect.Method;
29import java.lang.reflect.Modifier;
30
31import 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 */
43public 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 true} if 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 true} if the event set is in
457     *                          the "default" set,
458     *                          {@code false} if 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 true} if 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