1 /* java.beans.EventSetDescriptor
2 Copyright (C) 1998 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
41 import java
.lang
.reflect
.Method
;
42 import java
.lang
.reflect
.Modifier
;
43 import java
.util
.Vector
;
44 import gnu
.java
.lang
.ClassHelper
;
47 ** EventSetDescriptor describes the hookup between an event source
48 ** class and an event listener class.
50 ** EventSets have several attributes: the listener class, the events
51 ** that can be fired to the listener (methods in the listener class), and
52 ** an add and remove listener method from the event firer's class.<P>
54 ** The methods have these constraints on them:<P>
56 ** <LI>event firing methods: must have <CODE>void</CODE> return value. Any
57 ** parameters and exceptions are allowed. May be public, protected or
58 ** package-protected. (Don't ask me why that is, I'm just following the spec.
59 ** The only place it is even mentioned is in the Java Beans white paper, and
60 ** there it is only implied.)</LI>
61 ** <LI>add listener method: must have <CODE>void</CODE> return value. Must
62 ** take exactly one argument, of the listener class's type. May fire either
63 ** zero exceptions, or one exception of type <CODE>java.util.TooManyListenersException</CODE>.
64 ** Must be public.</LI>
65 ** <LI>remove listener method: must have <CODE>void</CODE> return value.
66 ** Must take exactly one argument, of the listener class's type. May not
67 ** fire any exceptions. Must be public.</LI>
70 ** A final constraint is that event listener classes must extend from EventListener.<P>
72 ** There are also various design patterns associated with some of the methods
73 ** of construction. Those are explained in more detail in the appropriate
76 ** <STRONG>Documentation Convention:</STRONG> for proper
77 ** Internalization of Beans inside an RAD tool, sometimes there
78 ** are two names for a property or method: a programmatic, or
79 ** locale-independent name, which can be used anywhere, and a
80 ** localized, display name, for ease of use. In the
81 ** documentation I will specify different String values as
82 ** either <EM>programmatic</EM> or <EM>localized</EM> to
83 ** make this distinction clear.
85 ** @author John Keiser
87 ** @version 1.1.0, 31 May 1998
90 public class EventSetDescriptor
extends FeatureDescriptor
{
91 private Method addListenerMethod
;
92 private Method removeListenerMethod
;
93 private Class listenerType
;
94 private MethodDescriptor
[] listenerMethodDescriptors
;
95 private Method
[] listenerMethods
;
97 private boolean unicast
;
98 private boolean inDefaultEventSet
= true;
100 /** Create a new EventSetDescriptor.
101 ** This version of the constructor enforces the rules imposed on the methods
102 ** described at the top of this class, as well as searching for:<P>
104 ** <LI>The event-firing method must be non-private with signature
105 ** <CODE>void <listenerMethodName>(<eventSetName>Event)</CODE>
106 ** (where <CODE><eventSetName></CODE> has its first character capitalized
107 ** by the constructor and the Event is a descendant of
108 ** <CODE>java.util.EventObject</CODE>) in class <CODE>listenerType</CODE>
109 ** (any exceptions may be thrown).
110 ** <B>Implementation note:</B> Note that there could conceivably be multiple
111 ** methods with this type of signature (example: java.util.MouseEvent vs.
112 ** my.very.own.MouseEvent). In this implementation, all methods fitting the
113 ** description will be put into the <CODE>EventSetDescriptor</CODE>, even
114 ** though the spec says only one should be chosen (they probably weren't thinking as
115 ** pathologically as I was). I don't like arbitrarily choosing things.
116 ** If your class has only one such signature, as most do, you'll have no problems.</LI>
117 ** <LI>The add and remove methods must be public and named
118 ** <CODE>void add<eventSetName>Listener(<listenerType>)</CODE> and
119 ** <CODE>void remove<eventSetName>Listener(<listenerType>)</CODE> in
120 ** in class <CODE>eventSourceClass</CODE>, where
121 ** <CODE><eventSetName></CODE> will have its first letter capitalized.
122 ** Standard exception rules (see class description) apply.</LI>
124 ** @param eventSourceClass the class containing the add/remove listener methods.
125 ** @param eventSetName the programmatic name of the event set, generally starting
126 ** with a lowercase letter (i.e. fooManChu instead of FooManChu). This will be used
127 ** to generate the name of the event object as well as the names of the add and
129 ** @param listenerType the class containing the event firing method.
130 ** @param listenerMethodName the name of the event firing method.
131 ** @exception IntrospectionException if listenerType is not an EventListener,
132 ** or if methods are not found or are invalid.
134 public EventSetDescriptor(Class eventSourceClass
,
137 String listenerMethodName
) throws IntrospectionException
{
138 setName(eventSetName
);
139 if(!java
.util
.EventListener
.class.isAssignableFrom(listenerType
)) {
140 throw new IntrospectionException("Listener type is not an EventListener.");
143 String
[] names
= new String
[1];
144 names
[0] = listenerMethodName
;
147 eventSetName
= Character
.toUpperCase(eventSetName
.charAt(0)) + eventSetName
.substring(1);
148 } catch(StringIndexOutOfBoundsException e
) {
152 findMethods(eventSourceClass
,listenerType
,names
,"add"+eventSetName
+"Listener","remove"+eventSetName
+"Listener",eventSetName
+"Event");
153 this.listenerType
= listenerType
;
154 checkAddListenerUnicast();
155 if(this.removeListenerMethod
.getExceptionTypes().length
> 0) {
156 throw new IntrospectionException("Listener remove method throws exceptions.");
160 /** Create a new EventSetDescriptor.
161 ** This form of the constructor allows you to specify the names of the methods and adds
162 ** no new constraints on top of the rules already described at the top of the class.<P>
164 ** @param eventSourceClass the class containing the add and remove listener methods.
165 ** @param eventSetName the programmatic name of the event set, generally starting
166 ** with a lowercase letter (i.e. fooManChu instead of FooManChu).
167 ** @param listenerType the class containing the event firing methods.
168 ** @param listenerMethodNames the names of the even firing methods.
169 ** @param addListenerMethodName the name of the add listener method.
170 ** @param removeListenerMethodName the name of the remove listener method.
171 ** @exception IntrospectionException if listenerType is not an EventListener
172 ** or if methods are not found or are invalid.
174 public EventSetDescriptor(Class eventSourceClass
,
177 String
[] listenerMethodNames
,
178 String addListenerMethodName
,
179 String removeListenerMethodName
) throws IntrospectionException
{
180 setName(eventSetName
);
181 if(!java
.util
.EventListener
.class.isAssignableFrom(listenerType
)) {
182 throw new IntrospectionException("Listener type is not an EventListener.");
185 findMethods(eventSourceClass
,listenerType
,listenerMethodNames
,addListenerMethodName
,removeListenerMethodName
,null);
186 this.listenerType
= listenerType
;
187 checkAddListenerUnicast();
188 if(this.removeListenerMethod
.getExceptionTypes().length
> 0) {
189 throw new IntrospectionException("Listener remove method throws exceptions.");
193 /** Create a new EventSetDescriptor.
194 ** This form of constructor allows you to explicitly say which methods do what, and
195 ** no reflection is done by the EventSetDescriptor. The methods are, however,
196 ** checked to ensure that they follow the rules set forth at the top of the class.
197 ** @param eventSetName the programmatic name of the event set, generally starting
198 ** with a lowercase letter (i.e. fooManChu instead of FooManChu).
199 ** @param listenerType the class containing the listenerMethods.
200 ** @param listenerMethods the event firing methods.
201 ** @param addListenerMethod the add listener method.
202 ** @param removeListenerMethod the remove listener method.
203 ** @exception IntrospectionException if the listenerType is not an EventListener,
204 ** or any of the methods are invalid.
206 public EventSetDescriptor(String eventSetName
,
208 Method
[] listenerMethods
,
209 Method addListenerMethod
,
210 Method removeListenerMethod
) throws IntrospectionException
{
211 setName(eventSetName
);
212 if(!java
.util
.EventListener
.class.isAssignableFrom(listenerType
)) {
213 throw new IntrospectionException("Listener type is not an EventListener.");
216 this.listenerMethods
= listenerMethods
;
217 this.addListenerMethod
= addListenerMethod
;
218 this.removeListenerMethod
= removeListenerMethod
;
219 this.listenerType
= listenerType
;
221 checkAddListenerUnicast();
222 if(this.removeListenerMethod
.getExceptionTypes().length
> 0) {
223 throw new IntrospectionException("Listener remove method throws exceptions.");
227 /** Create a new EventSetDescriptor.
228 ** This form of constructor allows you to explicitly say which methods do what, and
229 ** no reflection is done by the EventSetDescriptor. The methods are, however,
230 ** checked to ensure that they follow the rules set forth at the top of the class.
231 ** @param eventSetName the programmatic name of the event set, generally starting
232 ** with a lowercase letter (i.e. fooManChu instead of FooManChu).
233 ** @param listenerType the class containing the listenerMethods.
234 ** @param listenerMethodDescriptors the event firing methods.
235 ** @param addListenerMethod the add listener method.
236 ** @param removeListenerMethod the remove listener method.
237 ** @exception IntrospectionException if the listenerType is not an EventListener,
238 ** or any of the methods are invalid.
240 public EventSetDescriptor(String eventSetName
,
242 MethodDescriptor
[] listenerMethodDescriptors
,
243 Method addListenerMethod
,
244 Method removeListenerMethod
) throws IntrospectionException
{
245 setName(eventSetName
);
246 if(!java
.util
.EventListener
.class.isAssignableFrom(listenerType
)) {
247 throw new IntrospectionException("Listener type is not an EventListener.");
250 this.listenerMethodDescriptors
= listenerMethodDescriptors
;
251 this.listenerMethods
= new Method
[listenerMethodDescriptors
.length
];
252 for(int i
=0;i
<this.listenerMethodDescriptors
.length
;i
++) {
253 this.listenerMethods
[i
] = this.listenerMethodDescriptors
[i
].getMethod();
256 this.addListenerMethod
= addListenerMethod
;
257 this.removeListenerMethod
= removeListenerMethod
;
258 this.listenerType
= listenerType
;
260 checkAddListenerUnicast();
261 if(this.removeListenerMethod
.getExceptionTypes().length
> 0) {
262 throw new IntrospectionException("Listener remove method throws exceptions.");
266 /** Get the class that contains the event firing methods. **/
267 public Class
getListenerType() {
271 /** Get the event firing methods. **/
272 public Method
[] getListenerMethods() {
273 return listenerMethods
;
276 /** Get the event firing methods as MethodDescriptors. **/
277 public MethodDescriptor
[] getListenerMethodDescriptors() {
278 if(listenerMethodDescriptors
== null) {
279 listenerMethodDescriptors
= new MethodDescriptor
[listenerMethods
.length
];
280 for(int i
=0;i
<listenerMethods
.length
;i
++) {
281 listenerMethodDescriptors
[i
] = new MethodDescriptor(listenerMethods
[i
]);
284 return listenerMethodDescriptors
;
287 /** Get the add listener method. **/
288 public Method
getAddListenerMethod() {
289 return addListenerMethod
;
292 /** Get the remove listener method. **/
293 public Method
getRemoveListenerMethod() {
294 return removeListenerMethod
;
297 /** Set whether or not multiple listeners may be added.
298 ** @param unicast whether or not multiple listeners may be added.
300 public void setUnicast(boolean unicast
) {
301 this.unicast
= unicast
;
304 /** Get whether or not multiple listeners may be added. (Defaults to false.) **/
305 public boolean isUnicast() {
309 /** Set whether or not this is in the default event set.
310 ** @param inDefaultEventSet whether this is in the default event set.
312 public void setInDefaultEventSet(boolean inDefaultEventSet
) {
313 this.inDefaultEventSet
= inDefaultEventSet
;
316 /** Get whether or not this is in the default event set. (Defaults to true.)**/
317 public boolean isInDefaultEventSet() {
318 return inDefaultEventSet
;
321 private void checkAddListenerUnicast() throws IntrospectionException
{
322 Class
[] addListenerExceptions
= this.addListenerMethod
.getExceptionTypes();
323 if(addListenerExceptions
.length
> 1) {
324 throw new IntrospectionException("Listener add method throws too many exceptions.");
325 } else if(addListenerExceptions
.length
== 1
326 && !java
.util
.TooManyListenersException
.class.isAssignableFrom(addListenerExceptions
[0])) {
327 throw new IntrospectionException("Listener add method throws too many exceptions.");
331 private void checkMethods() throws IntrospectionException
{
332 if(!addListenerMethod
.getDeclaringClass().isAssignableFrom(removeListenerMethod
.getDeclaringClass())
333 && !removeListenerMethod
.getDeclaringClass().isAssignableFrom(addListenerMethod
.getDeclaringClass())) {
334 throw new IntrospectionException("add and remove listener methods do not come from the same class. This is bad.");
336 if(!addListenerMethod
.getReturnType().equals(java
.lang
.Void
.TYPE
)
337 || addListenerMethod
.getParameterTypes().length
!= 1
338 || !listenerType
.equals(addListenerMethod
.getParameterTypes()[0])
339 || !Modifier
.isPublic(addListenerMethod
.getModifiers())) {
340 throw new IntrospectionException("Add Listener Method invalid.");
342 if(!removeListenerMethod
.getReturnType().equals(java
.lang
.Void
.TYPE
)
343 || removeListenerMethod
.getParameterTypes().length
!= 1
344 || !listenerType
.equals(removeListenerMethod
.getParameterTypes()[0])
345 || removeListenerMethod
.getExceptionTypes().length
> 0
346 || !Modifier
.isPublic(removeListenerMethod
.getModifiers())) {
347 throw new IntrospectionException("Remove Listener Method invalid.");
350 for(int i
=0;i
<listenerMethods
.length
;i
++) {
351 if(!listenerMethods
[i
].getReturnType().equals(java
.lang
.Void
.TYPE
)
352 || Modifier
.isPrivate(listenerMethods
[i
].getModifiers())) {
353 throw new IntrospectionException("Event Method " + listenerMethods
[i
].getName() + " non-void or private.");
355 if(!listenerMethods
[i
].getDeclaringClass().isAssignableFrom(listenerType
)) {
356 throw new IntrospectionException("Event Method " + listenerMethods
[i
].getName() + " not from class " + listenerType
.getName());
361 private void findMethods(Class eventSourceClass
,
363 String listenerMethodNames
[],
364 String addListenerMethodName
,
365 String removeListenerMethodName
,
366 String absurdEventClassCheckName
) throws IntrospectionException
{
368 /* Find add listener method and remove listener method. */
369 Class
[] listenerArgList
= new Class
[1];
370 listenerArgList
[0] = listenerType
;
372 this.addListenerMethod
= eventSourceClass
.getMethod(addListenerMethodName
,listenerArgList
);
373 } catch(SecurityException E
) {
374 throw new IntrospectionException("SecurityException trying to access method " + addListenerMethodName
+ ".");
375 } catch(NoSuchMethodException E
) {
376 throw new IntrospectionException("Could not find method " + addListenerMethodName
+ ".");
379 if(this.addListenerMethod
== null || !this.addListenerMethod
.getReturnType().equals(java
.lang
.Void
.TYPE
)) {
380 throw new IntrospectionException("Add listener method does not exist, is not public, or is not void.");
384 this.removeListenerMethod
= eventSourceClass
.getMethod(removeListenerMethodName
,listenerArgList
);
385 } catch(SecurityException E
) {
386 throw new IntrospectionException("SecurityException trying to access method " + removeListenerMethodName
+ ".");
387 } catch(NoSuchMethodException E
) {
388 throw new IntrospectionException("Could not find method " + removeListenerMethodName
+ ".");
390 if(this.removeListenerMethod
== null || !this.removeListenerMethod
.getReturnType().equals(java
.lang
.Void
.TYPE
)) {
391 throw new IntrospectionException("Remove listener method does not exist, is not public, or is not void.");
394 /* Find the listener methods. */
397 methods
= ClassHelper
.getAllMethods(listenerType
);
398 } catch(SecurityException E
) {
399 throw new IntrospectionException("Security: You cannot access fields in this class.");
402 Vector chosenMethods
= new Vector();
403 boolean[] listenerMethodFound
= new boolean[listenerMethodNames
.length
];
404 for(int i
=0;i
<methods
.length
;i
++) {
405 if(Modifier
.isPrivate(methods
[i
].getModifiers())) {
408 Method currentMethod
= methods
[i
];
409 Class retval
= currentMethod
.getReturnType();
410 if(retval
.equals(java
.lang
.Void
.TYPE
)) {
411 for(int j
=0;j
<listenerMethodNames
.length
;j
++) {
412 if(currentMethod
.getName().equals(listenerMethodNames
[j
])
413 && (absurdEventClassCheckName
== null
414 || (currentMethod
.getParameterTypes().length
== 1
415 && ((currentMethod
.getParameterTypes()[0]).getName().equals(absurdEventClassCheckName
)
416 || (currentMethod
.getParameterTypes()[0]).getName().endsWith("."+absurdEventClassCheckName
)
421 chosenMethods
.addElement(currentMethod
);
422 listenerMethodFound
[j
] = true;
428 /* Make sure we found all the methods we were looking for. */
429 for(int i
=0;i
<listenerMethodFound
.length
;i
++) {
430 if(!listenerMethodFound
[i
]) {
431 throw new IntrospectionException("Could not find event method " + listenerMethodNames
[i
]);
435 /* Now that we've chosen the listener methods we want, store them. */
436 this.listenerMethods
= new Method
[chosenMethods
.size()];
437 for(int i
=0;i
<chosenMethods
.size();i
++) {
438 this.listenerMethods
[i
] = (Method
)chosenMethods
.elementAt(i
);