Merge from mainline (gomp-merge-2005-02-26).
[official-gcc.git] / libjava / gnu / java / beans / IntrospectionIncubator.java
blob21bf984d24c96bed4de1a6c471dd42b2197f5c45
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)
9 any later version.
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
19 02111-1307 USA.
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.
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;
58 /**
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
63 ** the methods.
65 ** @author John Keiser
66 ** @author Robert Schuster
67 ** @see gnu.java.beans.ExplicitBeanInfo
68 ** @see java.beans.BeanInfo
69 **/
71 public class IntrospectionIncubator {
72 Hashtable propertyMethods = new Hashtable();
73 Hashtable listenerMethods = new Hashtable();
74 Vector otherMethods = new Vector();
76 Class propertyStopClass;
77 Class eventStopClass;
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:
85 * <lu>
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>
90 * </ul>
91 * It files the method as a listener method if all of these rules apply:
92 * <lu>
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>
96 * </ul>
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
116 * of a property.
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);
133 } else {
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);
144 } else {
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")
153 && isVoid
154 && params.length == 1
155 && java.util.EventListener.class.isAssignableFrom(params[0])) {
156 addToListenerHash(name,method,ADD);
157 } else if(name.startsWith("remove")
158 && isVoid
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++) {
175 addMethod(m[i]);
179 public void setPropertyStopClass(Class c) {
180 propertyStopClass = c;
183 public void setEventStopClass(Class c) {
184 eventStopClass = c;
187 public void setMethodStopClass(Class c) {
188 methodStopClass = c;
192 public BeanInfoEmbryo getBeanInfoEmbryo() throws IntrospectionException {
193 BeanInfoEmbryo b = new BeanInfoEmbryo();
194 findXXX(b,IS);
195 findXXXInt(b,GET_I);
196 findXXXInt(b,SET_I);
197 findXXX(b,GET);
198 findXXX(b,SET);
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)));
206 return b;
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(),
222 m[ADD],m[REMOVE]);
223 e.setUnicast(ArrayHelper.contains(m[ADD].getExceptionTypes(),java.util.TooManyListenersException.class));
224 if(!b.hasEvent(e)) {
225 b.addEvent(e);
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],
239 m[SET]);
240 if(m[SET] != null) {
241 p.setConstrained(ArrayHelper.contains(m[SET].getExceptionTypes(),java.beans.PropertyVetoException.class));
243 if(!b.hasProperty(p)) {
244 b.addProperty(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) {
256 boolean constrained;
257 if(m[SET_I] != null) {
258 constrained = ArrayHelper.contains(m[SET_I].getExceptionTypes(),java.beans.PropertyVetoException.class);
259 } else {
260 constrained = false;
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;
268 if(m2 == null) {
269 p = new IndexedPropertyDescriptor(Introspector.decapitalize(k.getName()),
270 null,null,
271 m[GET_I],m[SET_I]);
272 } else {
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()),
277 m2[GET],m2[SET],
278 m[GET_I],m[SET_I]);
280 p.setConstrained(constrained);
281 if(!b.hasProperty(p)) {
282 b.addProperty(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) {
298 String newName;
299 Class type;
301 switch(funcType) {
302 case IS:
303 type = java.lang.Boolean.TYPE;
304 newName = name.substring(2);
305 break;
306 case GET_I:
307 type = method.getReturnType();
308 newName = name.substring(3);
309 break;
310 case SET_I:
311 type = method.getParameterTypes()[1];
312 newName = name.substring(3);
313 break;
314 case GET:
315 type = method.getReturnType();
316 newName = name.substring(3);
317 break;
318 case SET:
319 type = method.getParameterTypes()[0];
320 newName = name.substring(3);
321 break;
322 default:
323 return;
325 newName = capitalize(newName);
327 DoubleKey k = new DoubleKey(type,newName);
328 Method[] methods = (Method[])propertyMethods.get(k);
329 if(methods == null) {
330 methods = new Method[5];
331 propertyMethods.put(k,methods);
333 methods[funcType] = method;
336 void addToListenerHash(String name, Method method, int funcType) {
337 String newName;
338 Class type;
340 switch(funcType) {
341 case ADD:
342 type = method.getParameterTypes()[0];
343 newName = name.substring(3,name.length()-8);
344 break;
345 case REMOVE:
346 type = method.getParameterTypes()[0];
347 newName = name.substring(6,name.length()-8);
348 break;
349 default:
350 return;
352 newName = capitalize(newName);
354 DoubleKey k = new DoubleKey(type,newName);
355 Method[] methods = (Method[])listenerMethods.get(k);
356 if(methods == null) {
357 methods = new Method[2];
358 listenerMethods.put(k,methods);
360 methods[funcType] = method;
363 /* Determines whether <code>stopClass</code> is <code>null</code>
364 * or <code>declaringClass<code> is a true subclass of <code>stopClass</code>.
365 * This expression is useful to detect whether a method should be introspected or not.
366 * The rules for this are described in {@link java.beans.Introspector.getBeanInfo(Class, Class)}.
368 static boolean isReachable(Class stopClass, Class declaringClass) {
369 return stopClass == null || (stopClass.isAssignableFrom(declaringClass) && !stopClass.equals(declaringClass));
372 /** Transforms a property name into a part of a method name.
373 * E.g. "value" becomes "Value" which can then concatenated with
374 * "set", "get" or "is" to form a valid method name.
376 * Implementation notes:
377 * If "" is the argument, it is returned without changes.
378 * If <code>null</code> is the argument, <code>null</code> is returned.
380 * @param name Name of a property.
381 * @return Part of a method name of a property.
383 static String capitalize(String name) {
384 try {
385 if(Character.isUpperCase(name.charAt(0))) {
386 return name;
387 } else {
388 char[] c = name.toCharArray();
389 c[0] = Character.toLowerCase(c[0]);
390 return new String(c);
392 } catch(StringIndexOutOfBoundsException E) {
393 return name;
394 } catch(NullPointerException E) {
395 return null;
400 /** This class is a hashmap key that consists of a <code>Class</code> and a
401 * <code>String</code> element.
403 * It is used for XXX: find out what this is used for
405 * @author John Keiser
406 * @author Robert Schuster
408 class DoubleKey {
409 Class type;
410 String name;
412 DoubleKey(Class type, String name) {
413 this.type = type;
414 this.name = name;
417 Class getType() {
418 return type;
421 String getName() {
422 return name;
425 public boolean equals(Object o) {
426 if(o instanceof DoubleKey) {
427 DoubleKey d = (DoubleKey)o;
428 return d.type.equals(type) && d.name.equals(name);
429 } else {
430 return false;
434 public int hashCode() {
435 return type.hashCode() ^ name.hashCode();