Create embedded-5_0-branch branch for development on ARM embedded cores.
[official-gcc.git] / embedded-5_0-branch / libjava / classpath / gnu / java / beans / IntrospectionIncubator.java
blob978429a1ec38be282d40878dddd2daa45fa032f4
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., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 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 * <ul>
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 * <ul>
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);
326 if (newName.length() == 0)
327 return;
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) {
339 String newName;
340 Class type;
342 switch(funcType) {
343 case ADD:
344 type = method.getParameterTypes()[0];
345 newName = name.substring(3,name.length()-8);
346 break;
347 case REMOVE:
348 type = method.getParameterTypes()[0];
349 newName = name.substring(6,name.length()-8);
350 break;
351 default:
352 return;
354 newName = capitalize(newName);
355 if (newName.length() == 0)
356 return;
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) {
388 try {
389 if(Character.isUpperCase(name.charAt(0))) {
390 return name;
391 } else {
392 char[] c = name.toCharArray();
393 c[0] = Character.toLowerCase(c[0]);
394 return new String(c);
396 } catch(StringIndexOutOfBoundsException E) {
397 return name;
398 } catch(NullPointerException E) {
399 return null;
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
412 class DoubleKey {
413 Class type;
414 String name;
416 DoubleKey(Class type, String name) {
417 this.type = type;
418 this.name = name;
421 Class getType() {
422 return type;
425 String getName() {
426 return 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);
433 } else {
434 return false;
438 public int hashCode() {
439 return type.hashCode() ^ name.hashCode();