1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.jdo.impl.model.java.reflection;
18
19 import java.beans.BeanInfo;
20 import java.beans.Introspector;
21 import java.beans.IntrospectionException;
22 import java.beans.PropertyDescriptor;
23
24 import java.lang.reflect.Method;
25 import java.lang.reflect.Modifier;
26
27 import java.security.AccessController;
28 import java.security.PrivilegedAction;
29
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.List;
34
35 import org.apache.jdo.model.ModelFatalException;
36 import org.apache.jdo.model.java.JavaMethod;
37 import org.apache.jdo.model.java.JavaType;
38 import org.apache.jdo.util.I18NHelper;
39
40 /***
41 * Helper class to introspect a ReflectionJavaType representing a class to
42 * find its properties.
43 *
44 * @author Michael Bouschen
45 * @since JDO 2.0
46 */
47 public class ReflectionJavaTypeIntrospector
48 {
49 /*** I18N support */
50 private final static I18NHelper msg = I18NHelper.getInstance(
51 "org.apache.jdo.impl.model.java.Bundle");
52
53 /***
54 * Adds declared properties to the specified ReflectionJavaType instance.
55 * @param beanClass the class to be introspected
56 */
57 public void addDeclaredJavaProperties(ReflectionJavaType beanClass)
58 {
59 Class clazz = beanClass.getJavaClass();
60 PropertyDescriptor[] descrs =
61 getPublicAndProtectedPropertyDescriptors(clazz);
62 if (descrs != null) {
63 for (int i = 0; i < descrs.length; i++) {
64 PropertyDescriptor descr = descrs[i];
65 if (descr == null) continue;
66 String name = descr.getName();
67 JavaType type =
68 beanClass.getJavaTypeForClass(descr.getPropertyType());
69 Method getter = descr.getReadMethod();
70 JavaMethod javaGetter = (getter == null) ? null :
71 beanClass.createJavaMethod(getter);
72 Method setter = descr.getWriteMethod();
73 JavaMethod javaSetter = (setter == null) ? null :
74 beanClass.createJavaMethod(setter);
75 beanClass.createJavaProperty(name, javaGetter,
76 javaSetter, type);
77 }
78 }
79 }
80
81
82
83 /***
84 * Returns an array of PropertyDescriptor instances representing the
85 * declared public properties of the specified class.
86 * @param clazz the class to be introspected
87 * @return array of PropertyDescriptor instances for declared public
88 * properties.
89 */
90 private PropertyDescriptor[] getPublicPropertyDescriptors(Class clazz) {
91 try {
92 BeanInfo beanInfo = Introspector.getBeanInfo(
93 clazz, clazz.getSuperclass());
94 return beanInfo.getPropertyDescriptors();
95 }
96 catch (IntrospectionException ex) {
97 throw new ModelFatalException(msg.msg(
98 "ERR_CannotIntrospectClass", clazz.getName()), ex);
99 }
100 }
101
102
103
104 /***
105 * Returns an array of PropertyDescriptor instances representing the
106 * declared public and protected properties of the specified class.
107 * @param clazz the class to be introspected
108 * @return array of PropertyDescriptor instances for declared public and
109 * protected properties.
110 */
111 private PropertyDescriptor[] getPublicAndProtectedPropertyDescriptors(
112 Class clazz) {
113 return new PropertyStore(clazz).getPropertyDescriptors();
114 }
115
116 /***
117 * Helper class to introspect a class in order to find properties.
118 * The class provides a public method {@link #getPropertyDescriptors()}
119 * returning an array of PropertyDescriptors. Each PropertyDescriptor
120 * represents a public or protected property of the class specified as
121 * constructor argument. This code is inspired by the implementation
122 * of java.beans.Introspector class.
123 * <p>
124 * Class PropertyStore uses the following algorithm to identify the
125 * properties:
126 * <ul>
127 * <li>Iterate the declared non-static methods that are public or
128 * protected.</li>
129 * <li>A no-arg method returning a value and having a name staring with
130 * "get" is a potential getter method of a property.</li>
131 * <li>A no-arg method returning a boolean value and a name starting with
132 * "is" is a potential getter method of a property.</li>
133 * <li>A void method with a single argument and having a name starting
134 * with "set" is a potential setter method of a property.</li>
135 * <li>If there exsists an "is" and a "get" method, the "is" method is
136 * used as the getter method. </li>
137 * <li>If there is a getter method and multiple setter methods, it chooses
138 * the setter where the argument has exactly the same type as the getter
139 * return type.</li>
140 * <li>If there no such matching getter method, none of the setter methods
141 * correspond to a property.</li>
142 * </ul>
143 */
144 static class PropertyStore extends HashMap {
145
146 private static final String GET_PREFIX = "get";
147 private static final int GET_PREFIX_LENGTH = 3;
148 private static final String SET_PREFIX = "set";
149 private static final int SET_PREFIX_LENGTH = 3;
150 private static final String IS_PREFIX = "is";
151 private static final int IS_PREFIX_LENGTH = 2;
152
153 /*** The declared method instances for the specified class. */
154 private final Method[] declaredMethods;
155
156 /*** Constructor. */
157 public PropertyStore(final Class clazz) {
158 this.declaredMethods = (Method[]) AccessController.doPrivileged(
159 new PrivilegedAction() {
160 public Object run() {
161 return clazz.getDeclaredMethods();
162 }});
163 }
164
165 /***
166 * Returns an array of PropertyDescriptors. Each PropertyDescriptor
167 * represents a public or protected property of the class specified as
168 * constructor argument.
169 * @return array of public and protected properties
170 */
171 public PropertyDescriptor[] getPropertyDescriptors() {
172
173 for (int i = 0; i < declaredMethods.length; i++) {
174 Method method = declaredMethods[i];
175 int mods = method.getModifiers();
176
177
178
179 if (Modifier.isStatic(mods) ||
180 (!Modifier.isPublic(mods) && !Modifier.isProtected(mods))) {
181 continue;
182 }
183
184 String name = method.getName();
185 Class paramTypes[] = method.getParameterTypes();
186 Class resultType = method.getReturnType();
187
188 switch (paramTypes.length) {
189 case 0:
190
191 if (name.startsWith(GET_PREFIX) &&
192 resultType != void.class) {
193 String propName = Introspector.decapitalize(
194 name.substring(GET_PREFIX_LENGTH));
195 addGetter(propName, method);
196 }
197 else if (name.startsWith(IS_PREFIX) &&
198 resultType == boolean.class) {
199 String propName = Introspector.decapitalize(
200 name.substring(IS_PREFIX_LENGTH));
201 addGetter(propName, method);
202 }
203 break;
204 case 1:
205
206 if (name.startsWith(SET_PREFIX) &&
207 resultType == void.class) {
208 String propName = Introspector.decapitalize(
209 name.substring(GET_PREFIX_LENGTH));
210 addSetter(propName, method);
211 }
212 break;
213 }
214 }
215
216
217 List properties = processProperties();
218 return (PropertyDescriptor[]) properties.toArray(
219 new PropertyDescriptor[properties.size()]);
220 }
221
222 /***
223 * Adds a getter method to the methods list for the property with the
224 * specified name.
225 * @param propName the name of the property.
226 * @param method the getter method.
227 */
228 private void addGetter(String propName, Method method) {
229 try {
230 addPropertyDescriptor(
231 propName, new PropertyDescriptor(propName, method, null));
232 }
233 catch (IntrospectionException ex) {
234 throw new ModelFatalException(
235 msg.msg("ERR_CannotCreatePropertyDescriptor",
236 propName, method.getName()), ex);
237 }
238 }
239
240 /***
241 * Adds a setter method to the methods list for the property with the
242 * specified name.
243 * @param propName the name of the property.
244 * @param method the setter method.
245 */
246 private void addSetter(String propName, Method method) {
247 try {
248 addPropertyDescriptor(
249 propName, new PropertyDescriptor(propName, null, method));
250 }
251 catch (IntrospectionException ex) {
252 throw new ModelFatalException(
253 msg.msg("ERR_CannotCreatePropertyDescriptor",
254 propName, method.getName()), ex);
255 }
256 }
257
258 /***
259 * Adds a the specified (incomplete) PropertyDescriptor to the list of
260 * PropertyDescriptor candidates managed by this PropertyStore. The
261 * method initializes the list of PropertyDescriptors, in case it is
262 * the first PropertyDescriptor for the property with the specified
263 * name.
264 * @param propName the name of the property.
265 * @param pd new PropertyDescriptor.
266 */
267 private synchronized void addPropertyDescriptor(
268 String propName, PropertyDescriptor pd) {
269 if (pd == null) {
270
271 return;
272 }
273
274 List list = (List) get(propName);
275 if (list == null) {
276 list = new ArrayList();
277 put(propName, list);
278 }
279 list.add(pd);
280 }
281
282 /***
283 * The method returns a list of PropertyDescriptors for the properties
284 * managed by this PropertyStore. It iterates the all properties
285 * and analyzes the candidate PropertyDescriptors (by calling method
286 * {@link #processProperty(List)}.
287 * @return list of PropertyDescriptors
288 */
289 private synchronized List processProperties() {
290 List result = new ArrayList();
291 for (Iterator i = values().iterator(); i.hasNext();) {
292 PropertyDescriptor pd = processProperty((List) i.next());
293 if (pd != null) {
294 result.add(pd);
295 }
296 }
297 return result;
298 }
299
300 /***
301 * The method analyzes the specified list of candidate
302 * PropertyDescriptors and returns a single PropertyDescriptor
303 * describing the property. It iterates the candidate list in order to
304 * find a getter PropertyDescriptor. If there is such a
305 * PropertyDescriptor it looks for a corresponding setter
306 * PropertyDescriptor and updates the getter PropertyDescriptor with
307 * the write method of the setter. It then returns the getter
308 * PropertyDescriptor. If there is no getter PropertyDescriptor and a
309 * single setter PropertyDescriptor it returns the setter
310 * PropertyDescriptor. Otherwise it returns <code>null</code> which
311 * means the list of candidate PropertyDescriptors does not qualify
312 * for a valid property.
313 * @param candidates the list of candidate PropertyDescriptors
314 * @return a PropertyDescriptor describing a property or
315 * <code>null</code> if the candidate PropertyDescriptors do not
316 * qualify for a valid property.
317 */
318 private PropertyDescriptor processProperty(List candidates) {
319 if (candidates == null)
320 return null;
321
322 PropertyDescriptor getter = null;
323 PropertyDescriptor setter = null;
324
325
326 for (Iterator i = candidates.iterator(); i.hasNext();) {
327 PropertyDescriptor pd = (PropertyDescriptor) i.next();
328 if (pd.getReadMethod() != null) {
329 if (getter != null) {
330
331
332 String name = getter.getReadMethod().getName();
333 if (!name.startsWith(IS_PREFIX)) {
334 getter = pd;
335 }
336 }
337 else {
338
339 getter = pd;
340 }
341 }
342 }
343
344
345
346
347 for (Iterator i = candidates.iterator(); i.hasNext();) {
348 PropertyDescriptor pd = (PropertyDescriptor) i.next();
349 if (pd.getWriteMethod() != null) {
350 if (getter != null) {
351 if (pd.getPropertyType() == getter.getPropertyType()) {
352
353
354 setter = pd;
355 break;
356 }
357 }
358 else if (setter != null) {
359
360
361 setter = null;
362 break;
363 }
364 else {
365
366 setter = pd;
367 }
368 }
369 }
370
371
372 if (getter != null) {
373 if (setter != null) {
374
375
376 try {
377 getter.setWriteMethod(setter.getWriteMethod());
378 }
379 catch (IntrospectionException ex) {
380 throw new ModelFatalException(
381 msg.msg("ERR_CannotSetWriteMethod",
382 getter.getName()), ex);
383 }
384 }
385 return getter;
386 }
387 return setter;
388 }
389 }
390
391 }
392