]>
Commit | Line | Data |
---|---|---|
6c80c45e | 1 | /* java.beans.PropertyDescriptor |
fbddd18f | 2 | Copyright (C) 1998, 2001, 2004 Free Software Foundation, Inc. |
6c80c45e TT |
3 | |
4 | This file is part of GNU Classpath. | |
5 | ||
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) | |
9 | any later version. | |
10 | ||
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. | |
15 | ||
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 | |
19 | 02111-1307 USA. | |
20 | ||
92aaa246 MW |
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 | |
24 | combination. | |
25 | ||
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. */ | |
6c80c45e | 37 | |
6c80c45e TT |
38 | package java.beans; |
39 | ||
cb611e3e | 40 | import java.lang.reflect.Method; |
6c80c45e TT |
41 | |
42 | /** | |
43 | ** PropertyDescriptor describes information about a JavaBean property, | |
44 | ** by which we mean a property that has been exposed via a pair of | |
45 | ** get and set methods. (There may be no get method, which means | |
46 | ** the property is write-only, or no set method, which means the | |
47 | ** the property is read-only.)<P> | |
48 | ** | |
49 | ** The constraints put on get and set methods are:<P> | |
50 | ** <OL> | |
51 | ** <LI>A get method must have signature | |
52 | ** <CODE><propertyType> <getMethodName>()</CODE></LI> | |
53 | ** <LI>A set method must have signature | |
54 | ** <CODE>void <setMethodName>(<propertyType>)</CODE></LI> | |
55 | ** <LI>Either method type may throw any exception.</LI> | |
56 | ** <LI>Both methods must be public.</LI> | |
57 | ** </OL> | |
58 | ** | |
59 | ** @author John Keiser | |
fbddd18f RS |
60 | ** @author Robert Schuster <thebohemian@gmx.net> |
61 | ** @since 1.1 | |
62 | ** @status updated to 1.4 | |
6c80c45e TT |
63 | **/ |
64 | ||
fbddd18f RS |
65 | public class PropertyDescriptor extends FeatureDescriptor |
66 | { | |
67 | Class propertyType; | |
68 | Method getMethod; | |
69 | Method setMethod; | |
70 | ||
71 | Class propertyEditorClass; | |
72 | boolean bound; | |
73 | boolean constrained; | |
74 | ||
75 | PropertyDescriptor(String name) | |
76 | { | |
77 | setName(name); | |
b20fcd47 | 78 | } |
fbddd18f RS |
79 | |
80 | /** Create a new PropertyDescriptor by introspection. | |
81 | ** This form of constructor creates the PropertyDescriptor by | |
82 | ** looking for a getter method named <CODE>get<name>()</CODE> | |
83 | ** (or, optionally, if the property is boolean, | |
84 | ** <CODE>is<name>()</CODE>) and | |
85 | ** <CODE>set<name>()</CODE> in class | |
86 | ** <CODE><beanClass></CODE>, where <name> has its | |
87 | ** first letter capitalized by the constructor.<P> | |
88 | ** | |
89 | ** Note that using this constructor the given property must be read- <strong>and</strong> | |
90 | ** writeable. If the implementation does not both, a read and a write method, an | |
91 | ** <code>IntrospectionException</code> is thrown. | |
92 | ** | |
93 | ** <B>Implementation note:</B> If there is both are both isXXX and | |
94 | ** getXXX methods, the former is used in preference to the latter. | |
95 | ** We do not check that an isXXX method returns a boolean. In both | |
96 | ** cases, this matches the behaviour of JDK 1.4<P> | |
97 | ** | |
98 | ** @param name the programmatic name of the property, usually | |
99 | ** starting with a lowercase letter (e.g. fooManChu | |
100 | ** instead of FooManChu). | |
101 | ** @param beanClass the class the get and set methods live in. | |
102 | ** @exception IntrospectionException if the methods are not found | |
103 | ** or invalid. | |
104 | **/ | |
105 | public PropertyDescriptor(String name, Class beanClass) | |
106 | throws IntrospectionException | |
107 | { | |
108 | setName(name); | |
109 | if (name.length() == 0) | |
110 | { | |
111 | throw new IntrospectionException("empty property name"); | |
112 | } | |
113 | String caps = Character.toUpperCase(name.charAt(0)) + name.substring(1); | |
114 | findMethods(beanClass, "is" + caps, "get" + caps, "set" + caps); | |
115 | ||
116 | if (getMethod == null) | |
117 | { | |
118 | throw new IntrospectionException( | |
119 | "Cannot find a is" + caps + " or get" + caps + " method"); | |
120 | } | |
121 | ||
122 | if (setMethod == null) | |
123 | { | |
124 | throw new IntrospectionException( | |
125 | "Cannot find a " + caps + " method"); | |
126 | } | |
127 | ||
128 | // finally check the methods compatibility | |
129 | checkMethods(getMethod, setMethod); | |
b20fcd47 | 130 | } |
fbddd18f RS |
131 | |
132 | /** Create a new PropertyDescriptor by introspection. | |
133 | ** This form of constructor allows you to specify the | |
134 | ** names of the get and set methods to search for.<P> | |
135 | ** | |
136 | ** <B>Implementation note:</B> If there is a get method (or | |
137 | ** boolean isXXX() method), then the return type of that method | |
138 | ** is used to find the set method. If there is no get method, | |
139 | ** then the set method is searched for exhaustively.<P> | |
140 | ** | |
141 | ** <B>Spec note:</B> | |
142 | ** If there is no get method and multiple set methods with | |
143 | ** the same name and a single parameter (different type of course), | |
144 | ** then an IntrospectionException is thrown. While Sun's spec | |
145 | ** does not state this, it can make Bean behavior different on | |
146 | ** different systems (since method order is not guaranteed) and as | |
147 | ** such, can be treated as a bug in the spec. I am not aware of | |
148 | ** whether Sun's implementation catches this. | |
149 | ** | |
150 | ** @param name the programmatic name of the property, usually | |
151 | ** starting with a lowercase letter (e.g. fooManChu | |
152 | ** instead of FooManChu). | |
153 | ** @param beanClass the class the get and set methods live in. | |
154 | ** @param getMethodName the name of the get method or <code>null</code> if the property is write-only. | |
155 | ** @param setMethodName the name of the set method or <code>null</code> if the property is read-only. | |
156 | ** @exception IntrospectionException if the methods are not found | |
157 | ** or invalid. | |
158 | **/ | |
159 | public PropertyDescriptor( | |
160 | String name, | |
161 | Class beanClass, | |
162 | String getMethodName, | |
163 | String setMethodName) | |
164 | throws IntrospectionException | |
165 | { | |
166 | setName(name); | |
167 | findMethods(beanClass, getMethodName, null, setMethodName); | |
168 | ||
169 | if (getMethod == null && getMethodName != null) | |
170 | { | |
171 | throw new IntrospectionException( | |
172 | "Cannot find a getter method called " + getMethodName); | |
173 | } | |
174 | ||
175 | if (setMethod == null && setMethodName != null) | |
176 | { | |
177 | throw new IntrospectionException( | |
178 | "Cannot find a setter method called " + setMethodName); | |
179 | } | |
180 | ||
181 | checkMethods(getMethod, setMethod); | |
b20fcd47 | 182 | } |
fbddd18f RS |
183 | |
184 | /** Create a new PropertyDescriptor using explicit Methods. | |
185 | ** Note that the methods will be checked for conformance to standard | |
186 | ** Property method rules, as described above at the top of this class. | |
187 | **<br> | |
188 | ** It is possible to call this method with both <code>Method</code> arguments | |
189 | ** being <code>null</code>. In such a case the property type is <code>null</code>. | |
190 | ** | |
191 | ** @param name the programmatic name of the property, usually | |
192 | ** starting with a lowercase letter (e.g. fooManChu | |
193 | ** instead of FooManChu). | |
194 | ** @param readMethod the read method or <code>null</code> if the property is write-only. | |
195 | ** @param writeMethod the write method or <code>null</code> if the property is read-only. | |
196 | ** @exception IntrospectionException if the methods are not found | |
197 | ** or invalid. | |
198 | **/ | |
199 | public PropertyDescriptor( | |
200 | String name, | |
201 | Method readMethod, | |
202 | Method writeMethod) | |
203 | throws IntrospectionException | |
204 | { | |
205 | setName(name); | |
206 | getMethod = readMethod; | |
207 | setMethod = writeMethod; | |
208 | ||
209 | if (getMethod != null) | |
210 | { | |
211 | this.propertyType = getMethod.getReturnType(); | |
212 | } | |
213 | else if (setMethod != null) | |
214 | { | |
215 | this.propertyType = setMethod.getParameterTypes()[0]; | |
216 | } | |
217 | ||
218 | checkMethods(getMethod, setMethod); | |
b20fcd47 | 219 | } |
fbddd18f RS |
220 | |
221 | /** Get the property type. | |
222 | ** This is the type the get method returns and the set method | |
223 | ** takes in. | |
224 | **/ | |
225 | public Class getPropertyType() | |
226 | { | |
227 | return propertyType; | |
b20fcd47 | 228 | } |
fbddd18f RS |
229 | |
230 | /** Get the get method. Why they call it readMethod here and | |
231 | ** get everywhere else is beyond me. | |
232 | **/ | |
233 | public Method getReadMethod() | |
234 | { | |
235 | return getMethod; | |
236 | } | |
237 | ||
238 | /** Sets the read method.<br/> | |
239 | * The read method is used to retrieve the value of a property. A legal | |
240 | * read method must have no arguments. Its return type must not be | |
241 | * <code>void</code>. If this methods succeeds the property type | |
242 | * is adjusted to the return type of the read method.<br/> | |
243 | * <br/> | |
244 | * It is legal to set the read and the write method to <code>null</code> | |
245 | * or provide method which have been declared in distinct classes. | |
246 | * | |
247 | * @param readMethod The new method to be used or <code>null</code>. | |
248 | * @throws IntrospectionException If the given method is invalid. | |
249 | * @since 1.2 | |
250 | */ | |
251 | public void setReadMethod(Method readMethod) throws IntrospectionException | |
252 | { | |
253 | checkMethods(readMethod, setMethod); | |
254 | ||
255 | getMethod = readMethod; | |
256 | } | |
257 | ||
258 | /** Get the set method. Why they call it writeMethod here and | |
259 | ** set everywhere else is beyond me. | |
260 | **/ | |
261 | public Method getWriteMethod() | |
262 | { | |
263 | return setMethod; | |
264 | } | |
265 | ||
266 | /** Sets the write method.<br/> | |
267 | * The write method is used to set the value of a property. A legal write method | |
268 | * must have a single argument which can be assigned to the property. If no | |
269 | * read method exists the property type changes to the argument type of the | |
270 | * write method.<br/> | |
271 | * <br/> | |
272 | * It is legal to set the read and the write method to <code>null</code> | |
273 | * or provide method which have been declared in distinct classes. | |
274 | * | |
275 | * @param writeMethod The new method to be used or <code>null</code>. | |
276 | * @throws IntrospectionException If the given method is invalid. | |
277 | * @since 1.2 | |
278 | */ | |
279 | public void setWriteMethod(Method writeMethod) | |
280 | throws IntrospectionException | |
281 | { | |
282 | propertyType = checkMethods(getMethod, writeMethod); | |
283 | ||
284 | setMethod = writeMethod; | |
b20fcd47 | 285 | } |
fbddd18f RS |
286 | |
287 | /** Get whether the property is bound. Defaults to false. **/ | |
288 | public boolean isBound() | |
289 | { | |
290 | return bound; | |
b20fcd47 | 291 | } |
fbddd18f RS |
292 | |
293 | /** Set whether the property is bound. | |
294 | ** As long as the the bean implements addPropertyChangeListener() and | |
295 | ** removePropertyChangeListener(), setBound(true) may safely be called.<P> | |
296 | ** If these things are not true, then the behavior of the system | |
297 | ** will be undefined.<P> | |
298 | ** | |
299 | ** When a property is bound, its set method is required to fire the | |
300 | ** <CODE>PropertyChangeListener.propertyChange())</CODE> event | |
301 | ** after the value has changed. | |
302 | ** @param bound whether the property is bound or not. | |
303 | **/ | |
304 | public void setBound(boolean bound) | |
305 | { | |
306 | this.bound = bound; | |
b20fcd47 | 307 | } |
fbddd18f RS |
308 | |
309 | /** Get whether the property is constrained. Defaults to false. **/ | |
310 | public boolean isConstrained() | |
311 | { | |
312 | return constrained; | |
313 | } | |
314 | ||
315 | /** Set whether the property is constrained. | |
316 | ** If the set method throws <CODE>java.beans.PropertyVetoException</CODE> | |
317 | ** (or subclass thereof) and the bean implements addVetoableChangeListener() | |
318 | ** and removeVetoableChangeListener(), then setConstrained(true) may safely | |
319 | ** be called. Otherwise, the system behavior is undefined. | |
320 | ** <B>Spec note:</B> given those strict parameters, it would be nice if it | |
321 | ** got set automatically by detection, but oh well.<P> | |
322 | ** When a property is constrained, its set method is required to:<P> | |
323 | ** <OL> | |
324 | ** <LI>Fire the <CODE>VetoableChangeListener.vetoableChange()</CODE> | |
325 | ** event notifying others of the change and allowing them a chance to | |
326 | ** say it is a bad thing.</LI> | |
327 | ** <LI>If any of the listeners throws a PropertyVetoException, then | |
328 | ** it must fire another vetoableChange() event notifying the others | |
329 | ** of a reversion to the old value (though, of course, the change | |
330 | ** was never made). Then it rethrows the PropertyVetoException and | |
331 | ** exits.</LI> | |
332 | ** <LI>If all has gone well to this point, the value may be changed.</LI> | |
333 | ** </OL> | |
334 | ** @param constrained whether the property is constrained or not. | |
335 | **/ | |
336 | public void setConstrained(boolean constrained) | |
337 | { | |
338 | this.constrained = constrained; | |
b20fcd47 | 339 | } |
fbddd18f RS |
340 | |
341 | /** Get the PropertyEditor class. Defaults to null. **/ | |
342 | public Class getPropertyEditorClass() | |
343 | { | |
344 | return propertyEditorClass; | |
345 | } | |
346 | ||
347 | /** Set the PropertyEditor class. If the class does not implement | |
348 | ** the PropertyEditor interface, you will likely get an exception | |
349 | ** late in the game. | |
350 | ** @param propertyEditorClass the PropertyEditor class for this | |
351 | ** class to use. | |
352 | **/ | |
353 | public void setPropertyEditorClass(Class propertyEditorClass) | |
354 | { | |
355 | this.propertyEditorClass = propertyEditorClass; | |
356 | } | |
357 | ||
358 | private void findMethods( | |
359 | Class beanClass, | |
360 | String getMethodName1, | |
361 | String getMethodName2, | |
362 | String setMethodName) | |
363 | throws IntrospectionException | |
364 | { | |
365 | try | |
366 | { | |
367 | // Try the first get method name | |
368 | if (getMethodName1 != null) | |
369 | { | |
370 | try | |
371 | { | |
372 | getMethod = | |
373 | beanClass.getMethod(getMethodName1, new Class[0]); | |
374 | } | |
375 | catch (NoSuchMethodException e) | |
376 | {} | |
377 | } | |
378 | ||
379 | // Fall back to the second get method name | |
380 | if (getMethod == null && getMethodName2 != null) | |
381 | { | |
382 | try | |
383 | { | |
384 | getMethod = | |
385 | beanClass.getMethod(getMethodName2, new Class[0]); | |
386 | } | |
387 | catch (NoSuchMethodException e) | |
388 | {} | |
389 | } | |
390 | ||
391 | // Try the set method name | |
392 | if (setMethodName != null) | |
393 | { | |
394 | if (getMethod != null) | |
395 | { | |
396 | // If there is a get method, use its return type to help | |
397 | // select the corresponding set method. | |
398 | Class propertyType = getMethod.getReturnType(); | |
399 | if (propertyType == Void.TYPE) | |
400 | { | |
401 | String msg = | |
402 | "The property's read method has return type 'void'"; | |
403 | throw new IntrospectionException(msg); | |
404 | } | |
405 | ||
406 | Class[] setArgs = new Class[] { propertyType }; | |
407 | try | |
408 | { | |
409 | setMethod = beanClass.getMethod(setMethodName, setArgs); | |
410 | } | |
411 | catch (NoSuchMethodException e) | |
412 | {} | |
413 | } | |
414 | else if (getMethodName1 == null && getMethodName2 == null) | |
415 | { | |
416 | // If this is a write-only property, choose the first set method | |
417 | // with the required name, one parameter and return type 'void' | |
418 | Method[] methods = beanClass.getMethods(); | |
419 | for (int i = 0; i < methods.length; i++) | |
420 | { | |
421 | if (methods[i].getName().equals(setMethodName) | |
422 | && methods[i].getParameterTypes().length == 1 | |
423 | && methods[i].getReturnType() == Void.TYPE) | |
424 | { | |
425 | setMethod = methods[i]; | |
426 | break; | |
427 | } | |
428 | } | |
429 | } | |
430 | } | |
431 | } | |
432 | catch (SecurityException e) | |
433 | { | |
434 | // FIXME -- shouldn't we just allow SecurityException to propagate? | |
435 | String msg = | |
436 | "SecurityException thrown on attempt to access methods."; | |
437 | throw new IntrospectionException(msg); | |
438 | } | |
439 | } | |
440 | ||
441 | /** Checks whether the given <code>Method</code> instances are legal read and | |
442 | * write methods. The following requirements must be met:<br/> | |
443 | * <ul> | |
444 | * <li>the read method must not have an argument</li> | |
445 | * <li>the read method must have a non void return type</li> | |
446 | * <li>the read method may not exist</li> | |
447 | * <li>the write method must have a single argument</li> | |
448 | * <li>the property type and the read method's return type must be assignable from the | |
449 | * write method's argument type</li> | |
450 | * <li>the write method may not exist</li> | |
451 | * <ul> | |
452 | * While checking the methods a common new property type is calculated. If the method | |
453 | * succeeds this property type is returned.<br/> | |
454 | * <br/> | |
455 | * For compatibility this has to be noted:<br/> | |
456 | * The two methods are allowed to be defined in two distinct classes and may both be null. | |
457 | * | |
458 | * @param readMethod The new read method to check. | |
459 | * @param writeMethod The new write method to check. | |
460 | * @return The common property type of the two method. | |
461 | * @throws IntrospectionException If any of the above requirements are not met. | |
462 | */ | |
463 | private Class checkMethods(Method readMethod, Method writeMethod) | |
464 | throws IntrospectionException | |
465 | { | |
466 | Class newPropertyType = propertyType; | |
467 | ||
468 | // a valid read method has zero arguments and a non-void return type. | |
469 | if (readMethod != null) | |
470 | { | |
471 | if (readMethod.getParameterTypes().length > 0) | |
472 | { | |
473 | throw new IntrospectionException("read method has unexpected parameters"); | |
474 | } | |
475 | ||
476 | newPropertyType = readMethod.getReturnType(); | |
477 | ||
478 | if (newPropertyType == Void.TYPE) | |
479 | { | |
480 | throw new IntrospectionException("read method return type is void"); | |
481 | } | |
482 | } | |
483 | ||
484 | // a valid write method has one argument which can be assigned to the property | |
485 | if (writeMethod != null) | |
486 | { | |
487 | if (writeMethod.getParameterTypes().length != 1) | |
488 | { | |
489 | String msg = "write method does not have exactly one parameter"; | |
490 | throw new IntrospectionException(msg); | |
491 | } | |
492 | ||
493 | if (readMethod == null) | |
494 | { | |
495 | // changes the property type if there is no read method | |
496 | newPropertyType = writeMethod.getParameterTypes()[0]; | |
497 | } | |
498 | else | |
499 | { | |
500 | // checks whether the write method can be assigned to the return type of the read | |
501 | // method (if this is not the case, the methods are not compatible) | |
502 | // note: newPropertyType may be null if no methods or method names have been | |
503 | // delivered in the constructor. | |
504 | if (newPropertyType != null | |
505 | && !newPropertyType.isAssignableFrom( | |
506 | writeMethod.getParameterTypes()[0])) | |
507 | { | |
508 | // note: newPropertyType is the same as readMethod.getReturnType() at this point | |
509 | throw new IntrospectionException("read and write method are not compatible"); | |
510 | } | |
511 | ||
512 | /* note: the check whether both method are defined in related classes makes sense but is not | |
513 | * done in the JDK. | |
514 | * I leave this code here in case someone at Sun decides to add that functionality in later versions (rschuster) | |
515 | if ((!readMethod | |
516 | .getDeclaringClass() | |
517 | .isAssignableFrom(writeMethod.getDeclaringClass())) | |
518 | && (!writeMethod | |
519 | .getDeclaringClass() | |
520 | .isAssignableFrom(readMethod.getDeclaringClass()))) | |
521 | { | |
522 | String msg = | |
523 | "set and get methods are not in the same class."; | |
524 | throw new IntrospectionException(msg); | |
525 | } | |
526 | */ | |
527 | ||
528 | } | |
529 | } | |
530 | ||
531 | return newPropertyType; | |
532 | } | |
533 | ||
534 | /** Compares this <code>PropertyDescriptor</code> against the | |
535 | * given object. | |
536 | * Two PropertyDescriptors are equals if | |
537 | * <ul> | |
538 | * <li>the read methods are equal</li> | |
539 | * <li>the write methods are equal</li> | |
540 | * <li>the property types are equals</li> | |
541 | * <li>the property editor classes are equal</li> | |
542 | * <li>the flags (constrained and bound) are equal</li> | |
543 | * </ul> | |
544 | * @return Whether both objects are equal according to the rules given above. | |
545 | * @since 1.4 | |
546 | */ | |
547 | public boolean equals(Object o) | |
548 | { | |
549 | if (o instanceof PropertyDescriptor) | |
550 | { | |
551 | PropertyDescriptor that = (PropertyDescriptor) o; | |
552 | ||
553 | // compares the property types and checks the case where both are null | |
554 | boolean samePropertyType = | |
555 | (propertyType == null) | |
556 | ? that.propertyType == null | |
557 | : propertyType.equals(that.propertyType); | |
558 | ||
559 | // compares the property editor classes and checks the case where both are null | |
560 | boolean samePropertyEditorClass = | |
561 | (propertyEditorClass == null) | |
562 | ? that.propertyEditorClass == null | |
563 | : propertyEditorClass.equals(that.propertyEditorClass); | |
564 | ||
565 | // compares the flags for equality | |
566 | boolean sameFlags = | |
567 | bound == that.bound && constrained == that.constrained; | |
568 | ||
569 | // compares the read methods and checks the case where both are null | |
570 | boolean sameReadMethod = | |
571 | (getMethod == null) | |
572 | ? that.getMethod == null | |
573 | : getMethod.equals(that.getMethod); | |
574 | ||
575 | boolean sameWriteMethod = | |
576 | (setMethod == null) | |
577 | ? that.setMethod == null | |
578 | : setMethod.equals(that.setMethod); | |
579 | ||
580 | return samePropertyType | |
581 | && sameFlags | |
582 | && sameReadMethod | |
583 | && sameWriteMethod | |
584 | && samePropertyEditorClass; | |
585 | } | |
586 | else | |
587 | { | |
588 | return false; | |
589 | } | |
590 | ||
591 | } | |
592 | ||
6c80c45e | 593 | } |