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 
IntrospectionIncubator()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 	 */
addMethod(Method method)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 
addMethods(Method[] m)173 	public void addMethods(Method[] m) {
174 		for(int i=0;i<m.length;i++) {
175 			addMethod(m[i]);
176 		}
177 	}
178 
setPropertyStopClass(Class c)179 	public void setPropertyStopClass(Class c) {
180 		propertyStopClass = c;
181 	}
182 
setEventStopClass(Class c)183 	public void setEventStopClass(Class c) {
184 		eventStopClass = c;
185 	}
186 
setMethodStopClass(Class c)187 	public void setMethodStopClass(Class c) {
188 		methodStopClass = c;
189 	}
190 
191 
getBeanInfoEmbryo()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 
getBeanInfo()209 	public BeanInfo getBeanInfo() throws IntrospectionException {
210 		return getBeanInfoEmbryo().getBeanInfo();
211 	}
212 
213 
findAddRemovePairs(BeanInfoEmbryo b)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 
findXXX(BeanInfoEmbryo b, int funcType)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 
findXXXInt(BeanInfoEmbryo b, int funcType)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 
addToPropertyHash(String name, Method method, int funcType)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 
addToListenerHash(String name, Method method, int funcType)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 	 */
isReachable(Class stopClass, Class declaringClass)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 	 */
capitalize(String name)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 
DoubleKey(Class type, String name)416 	DoubleKey(Class type, String name) {
417 		this.type = type;
418 		this.name = name;
419 	}
420 
getType()421 	Class getType() {
422 		return type;
423 	}
424 
getName()425 	String getName() {
426 		return name;
427 	}
428 
equals(Object o)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 
hashCode()438 	public int hashCode() {
439 		return type.hashCode() ^ name.hashCode();
440 	}
441 }
442