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