1 /* gnu.java.beans.IntrospectionIncubator
2 Copyright (C) 1998, 2004 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., 51 Franklin Street, Fifth Floor, 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. */
39 package gnu
.java
.beans
;
41 import gnu
.java
.lang
.ArrayHelper
;
42 import gnu
.java
.lang
.ClassHelper
;
44 import java
.beans
.BeanInfo
;
45 import java
.beans
.EventSetDescriptor
;
46 import java
.beans
.IndexedPropertyDescriptor
;
47 import java
.beans
.IntrospectionException
;
48 import java
.beans
.Introspector
;
49 import java
.beans
.MethodDescriptor
;
50 import java
.beans
.PropertyDescriptor
;
51 import java
.lang
.reflect
.Array
;
52 import java
.lang
.reflect
.Method
;
53 import java
.lang
.reflect
.Modifier
;
54 import java
.util
.Enumeration
;
55 import java
.util
.Hashtable
;
56 import java
.util
.Vector
;
59 ** IntrospectionIncubator takes in a bunch of Methods, and
60 ** Introspects only those Methods you give it.<br/>
62 ** See {@link addMethod(Method)} for details which rules apply to
65 ** @author John Keiser
66 ** @author Robert Schuster
67 ** @see gnu.java.beans.ExplicitBeanInfo
68 ** @see java.beans.BeanInfo
71 public class IntrospectionIncubator
{
72 Hashtable propertyMethods
= new Hashtable();
73 Hashtable listenerMethods
= new Hashtable();
74 Vector otherMethods
= new Vector();
76 Class propertyStopClass
;
78 Class methodStopClass
;
80 public IntrospectionIncubator() {
83 /** Examines the given method and files it in a suitable collection.
84 * It files the method as a property method if it finds:
86 * <li>boolean "is" getter</li>
87 * <li>"get" style getter</li>
88 * <li>single argument setter</li>
89 * <li>indiced setter and getter</li>
91 * It files the method as a listener method if all of these rules apply:
93 * <li>the method name starts with "add" or "remove"</li>
94 * <li>there is only a single argument</li>
95 * <li>the argument type is a subclass of <code>java.util.EventListener</code></li>
97 * All public methods are filed as such.
99 * @param method The method instance to examine.
101 public void addMethod(Method method
) {
102 if(Modifier
.isPublic(method
.getModifiers())) {
103 String name
= ClassHelper
.getTruncatedName(method
.getName());
104 Class retType
= method
.getReturnType();
105 Class
[] params
= method
.getParameterTypes();
106 boolean isVoid
= retType
.equals(java
.lang
.Void
.TYPE
);
107 Class methodClass
= method
.getDeclaringClass();
109 /* Accepts the method for examination if no stop class is given or the method is declared in a subclass of the stop class.
110 * The rules for this are described in {@link java.beans.Introspector.getBeanInfo(Class, Class)}.
111 * This block finds out whether the method is a suitable getter or setter method (or read/write method).
113 if(isReachable(propertyStopClass
, methodClass
)) {
114 /* At this point a method may regarded as a property's read or write method if its name
115 * starts with "is", "get" or "set". However, if a method is static it cannot be part
118 if(Modifier
.isStatic(method
.getModifiers())) {
119 // files method as other because it is static
120 otherMethods
.addElement(method
);
121 } else if(name
.startsWith("is")
122 && retType
.equals(java
.lang
.Boolean
.TYPE
)
123 && params
.length
== 0) {
124 // files method as boolean "is" style getter
125 addToPropertyHash(name
,method
,IS
);
126 } else if(name
.startsWith("get") && !isVoid
) {
127 if(params
.length
== 0) {
128 // files as legal non-argument getter
129 addToPropertyHash(name
,method
,GET
);
130 } else if(params
.length
== 1 && params
[0].equals(java
.lang
.Integer
.TYPE
)) {
131 // files as legal indiced getter
132 addToPropertyHash(name
,method
,GET_I
);
134 // files as other because the method's signature is not Bean-like
135 otherMethods
.addElement(method
);
137 } else if(name
.startsWith("set") && isVoid
) {
138 if(params
.length
== 1) {
139 // files as legal single-argument setter method
140 addToPropertyHash(name
,method
,SET
);
141 } else if(params
.length
== 2 && params
[0].equals(java
.lang
.Integer
.TYPE
)) {
142 // files as legal indiced setter method
143 addToPropertyHash(name
,method
,SET_I
);
145 // files as other because the method's signature is not Bean-like
146 otherMethods
.addElement(method
);
151 if(isReachable(eventStopClass
, methodClass
)) {
152 if(name
.startsWith("add")
154 && params
.length
== 1
155 && java
.util
.EventListener
.class.isAssignableFrom(params
[0])) {
156 addToListenerHash(name
,method
,ADD
);
157 } else if(name
.startsWith("remove")
159 && params
.length
== 1
160 && java
.util
.EventListener
.class.isAssignableFrom(params
[0])) {
161 addToListenerHash(name
,method
,REMOVE
);
165 if(isReachable(methodStopClass
, methodClass
)) {
166 // files as reachable public method
167 otherMethods
.addElement(method
);
173 public void addMethods(Method
[] m
) {
174 for(int i
=0;i
<m
.length
;i
++) {
179 public void setPropertyStopClass(Class c
) {
180 propertyStopClass
= c
;
183 public void setEventStopClass(Class c
) {
187 public void setMethodStopClass(Class c
) {
192 public BeanInfoEmbryo
getBeanInfoEmbryo() throws IntrospectionException
{
193 BeanInfoEmbryo b
= new BeanInfoEmbryo();
199 findAddRemovePairs(b
);
200 for(int i
=0;i
<otherMethods
.size();i
++) {
201 MethodDescriptor newMethod
= new MethodDescriptor((Method
)otherMethods
.elementAt(i
));
202 if(!b
.hasMethod(newMethod
)) {
203 b
.addMethod(new MethodDescriptor((Method
)otherMethods
.elementAt(i
)));
209 public BeanInfo
getBeanInfo() throws IntrospectionException
{
210 return getBeanInfoEmbryo().getBeanInfo();
214 void findAddRemovePairs(BeanInfoEmbryo b
) throws IntrospectionException
{
215 Enumeration listenerEnum
= listenerMethods
.keys();
216 while(listenerEnum
.hasMoreElements()) {
217 DoubleKey k
= (DoubleKey
)listenerEnum
.nextElement();
218 Method
[] m
= (Method
[])listenerMethods
.get(k
);
219 if(m
[ADD
] != null && m
[REMOVE
] != null) {
220 EventSetDescriptor e
= new EventSetDescriptor(Introspector
.decapitalize(k
.getName()),
221 k
.getType(), k
.getType().getMethods(),
223 e
.setUnicast(ArrayHelper
.contains(m
[ADD
].getExceptionTypes(),java
.util
.TooManyListenersException
.class));
231 void findXXX(BeanInfoEmbryo b
, int funcType
) throws IntrospectionException
{
232 Enumeration keys
= propertyMethods
.keys();
233 while(keys
.hasMoreElements()) {
234 DoubleKey k
= (DoubleKey
)keys
.nextElement();
235 Method
[] m
= (Method
[])propertyMethods
.get(k
);
236 if(m
[funcType
] != null) {
237 PropertyDescriptor p
= new PropertyDescriptor(Introspector
.decapitalize(k
.getName()),
238 m
[IS
] != null ? m
[IS
] : m
[GET
],
241 p
.setConstrained(ArrayHelper
.contains(m
[SET
].getExceptionTypes(),java
.beans
.PropertyVetoException
.class));
243 if(!b
.hasProperty(p
)) {
250 void findXXXInt(BeanInfoEmbryo b
, int funcType
) throws IntrospectionException
{
251 Enumeration keys
= propertyMethods
.keys();
252 while(keys
.hasMoreElements()) {
253 DoubleKey k
= (DoubleKey
)keys
.nextElement();
254 Method
[] m
= (Method
[])propertyMethods
.get(k
);
255 if(m
[funcType
] != null) {
257 if(m
[SET_I
] != null) {
258 constrained
= ArrayHelper
.contains(m
[SET_I
].getExceptionTypes(),java
.beans
.PropertyVetoException
.class);
263 /** Find out if there is an array type get or set **/
264 Class arrayType
= Array
.newInstance(k
.getType(),0).getClass();
265 DoubleKey findSetArray
= new DoubleKey(arrayType
,k
.getName());
266 Method
[] m2
= (Method
[])propertyMethods
.get(findSetArray
);
267 IndexedPropertyDescriptor p
;
269 p
= new IndexedPropertyDescriptor(Introspector
.decapitalize(k
.getName()),
273 if(constrained
&& m2
[SET
] != null) {
274 constrained
= ArrayHelper
.contains(m2
[SET
].getExceptionTypes(),java
.beans
.PropertyVetoException
.class);
276 p
= new IndexedPropertyDescriptor(Introspector
.decapitalize(k
.getName()),
280 p
.setConstrained(constrained
);
281 if(!b
.hasProperty(p
)) {
288 static final int IS
=0;
289 static final int GET_I
=1;
290 static final int SET_I
=2;
291 static final int GET
=3;
292 static final int SET
=4;
294 static final int ADD
=0;
295 static final int REMOVE
=1;
297 void addToPropertyHash(String name
, Method method
, int funcType
) {
303 type
= java
.lang
.Boolean
.TYPE
;
304 newName
= name
.substring(2);
307 type
= method
.getReturnType();
308 newName
= name
.substring(3);
311 type
= method
.getParameterTypes()[1];
312 newName
= name
.substring(3);
315 type
= method
.getReturnType();
316 newName
= name
.substring(3);
319 type
= method
.getParameterTypes()[0];
320 newName
= name
.substring(3);
325 newName
= capitalize(newName
);
326 if (newName
.length() == 0)
329 DoubleKey k
= new DoubleKey(type
,newName
);
330 Method
[] methods
= (Method
[])propertyMethods
.get(k
);
331 if(methods
== null) {
332 methods
= new Method
[5];
333 propertyMethods
.put(k
,methods
);
335 methods
[funcType
] = method
;
338 void addToListenerHash(String name
, Method method
, int funcType
) {
344 type
= method
.getParameterTypes()[0];
345 newName
= name
.substring(3,name
.length()-8);
348 type
= method
.getParameterTypes()[0];
349 newName
= name
.substring(6,name
.length()-8);
354 newName
= capitalize(newName
);
355 if (newName
.length() == 0)
358 DoubleKey k
= new DoubleKey(type
,newName
);
359 Method
[] methods
= (Method
[])listenerMethods
.get(k
);
360 if(methods
== null) {
361 methods
= new Method
[2];
362 listenerMethods
.put(k
,methods
);
364 methods
[funcType
] = method
;
367 /* Determines whether <code>stopClass</code> is <code>null</code>
368 * or <code>declaringClass<code> is a true subclass of <code>stopClass</code>.
369 * This expression is useful to detect whether a method should be introspected or not.
370 * The rules for this are described in {@link java.beans.Introspector.getBeanInfo(Class, Class)}.
372 static boolean isReachable(Class stopClass
, Class declaringClass
) {
373 return stopClass
== null || (stopClass
.isAssignableFrom(declaringClass
) && !stopClass
.equals(declaringClass
));
376 /** Transforms a property name into a part of a method name.
377 * E.g. "value" becomes "Value" which can then concatenated with
378 * "set", "get" or "is" to form a valid method name.
380 * Implementation notes:
381 * If "" is the argument, it is returned without changes.
382 * If <code>null</code> is the argument, <code>null</code> is returned.
384 * @param name Name of a property.
385 * @return Part of a method name of a property.
387 static String
capitalize(String name
) {
389 if(Character
.isUpperCase(name
.charAt(0))) {
392 char[] c
= name
.toCharArray();
393 c
[0] = Character
.toLowerCase(c
[0]);
394 return new String(c
);
396 } catch(StringIndexOutOfBoundsException E
) {
398 } catch(NullPointerException E
) {
404 /** This class is a hashmap key that consists of a <code>Class</code> and a
405 * <code>String</code> element.
407 * It is used for XXX: find out what this is used for
409 * @author John Keiser
410 * @author Robert Schuster
416 DoubleKey(Class type
, String name
) {
429 public boolean equals(Object o
) {
430 if(o
instanceof DoubleKey
) {
431 DoubleKey d
= (DoubleKey
)o
;
432 return d
.type
.equals(type
) && d
.name
.equals(name
);
438 public int hashCode() {
439 return type
.hashCode() ^ name
.hashCode();