OSDN Git Service

Normalise whitespace in GNU Classpath.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / gnu / java / beans / IntrospectionIncubator.java
1 /* gnu.java.beans.IntrospectionIncubator
2    Copyright (C) 1998, 2004 Free Software Foundation, Inc.
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., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
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. */
37
38
39 package gnu.java.beans;
40
41 import gnu.java.lang.ArrayHelper;
42 import gnu.java.lang.ClassHelper;
43
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;
57
58 /**
59  ** IntrospectionIncubator takes in a bunch of Methods, and
60  ** Introspects only those Methods you give it.<br/>
61  **
62  ** See {@link addMethod(Method)} for details which rules apply to
63  ** the methods.
64  **
65  ** @author John Keiser
66  ** @author Robert Schuster
67  ** @see gnu.java.beans.ExplicitBeanInfo
68  ** @see java.beans.BeanInfo
69  **/
70
71 public class IntrospectionIncubator {
72         Hashtable propertyMethods = new Hashtable();
73         Hashtable listenerMethods = new Hashtable();
74         Vector otherMethods = new Vector();
75
76         Class propertyStopClass;
77         Class eventStopClass;
78         Class methodStopClass;
79
80         public IntrospectionIncubator() {
81         }
82
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.
98          *
99          * @param method The method instance to examine.
100          */
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();
108
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).
112                          */
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.
117                                  */
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);
136                                         }
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);
147                                         }
148                                 }
149                         }
150
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);
162                                 }
163                         }
164
165                         if(isReachable(methodStopClass, methodClass)) {
166                                 // files as reachable public method
167                                 otherMethods.addElement(method);
168                         }
169
170                 }
171         }
172
173         public void addMethods(Method[] m) {
174                 for(int i=0;i<m.length;i++) {
175                         addMethod(m[i]);
176                 }
177         }
178
179         public void setPropertyStopClass(Class c) {
180                 propertyStopClass = c;
181         }
182
183         public void setEventStopClass(Class c) {
184                 eventStopClass = c;
185         }
186
187         public void setMethodStopClass(Class c) {
188                 methodStopClass = c;
189         }
190
191
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)));
204                         }
205                 }
206                 return b;
207         }
208
209         public BeanInfo getBeanInfo() throws IntrospectionException {
210                 return getBeanInfoEmbryo().getBeanInfo();
211         }
212
213
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);
226                                 }
227                         }
228                 }
229         }
230
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));
242                                 }
243                                 if(!b.hasProperty(p)) {
244                                         b.addProperty(p);
245                                 }
246                         }
247                 }
248         }
249
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;
261                                 }
262
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);
275                                         }
276                                         p = new IndexedPropertyDescriptor(Introspector.decapitalize(k.getName()),
277                                                                           m2[GET],m2[SET],
278                                                                           m[GET_I],m[SET_I]);
279                                 }
280                                 p.setConstrained(constrained);
281                                 if(!b.hasProperty(p)) {
282                                         b.addProperty(p);
283                                 }
284                         }
285                 }
286         }
287
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;
293
294         static final int ADD=0;
295         static final int REMOVE=1;
296
297         void addToPropertyHash(String name, Method method, int funcType) {
298                 String newName;
299                 Class type;
300
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;
324                 }
325                 newName = capitalize(newName);
326                 if (newName.length() == 0)
327                         return;
328
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);
334                 }
335                 methods[funcType] = method;
336         }
337
338         void addToListenerHash(String name, Method method, int funcType) {
339                 String newName;
340                 Class type;
341
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;
353                 }
354                 newName = capitalize(newName);
355                 if (newName.length() == 0)
356                         return;
357
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);
363                 }
364                 methods[funcType] = method;
365         }
366
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)}.
371          */
372         static boolean isReachable(Class stopClass, Class declaringClass) {
373                 return stopClass == null || (stopClass.isAssignableFrom(declaringClass) && !stopClass.equals(declaringClass));
374         }
375
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.
379          *
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.
383          *
384          * @param name Name of a property.
385          * @return Part of a method name of a property.
386          */
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);
395                         }
396                 } catch(StringIndexOutOfBoundsException E) {
397                         return name;
398                 } catch(NullPointerException E) {
399                         return null;
400                 }
401         }
402 }
403
404 /** This class is a hashmap key that consists of a <code>Class</code> and a
405  * <code>String</code> element.
406  *
407  * It is used for XXX: find out what this is used for
408  *
409  * @author John Keiser
410  * @author Robert Schuster
411  */
412 class DoubleKey {
413         Class type;
414         String name;
415
416         DoubleKey(Class type, String name) {
417                 this.type = type;
418                 this.name = name;
419         }
420
421         Class getType() {
422                 return type;
423         }
424
425         String getName() {
426                 return name;
427         }
428
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;
435                 }
436         }
437
438         public int hashCode() {
439                 return type.hashCode() ^ name.hashCode();
440         }
441 }