View Javadoc

1   /*
2    * Copyright 2002-2004 The Apache Software Foundation
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.commons.clazz.reflect.extended;
17  
18  import java.lang.reflect.Method;
19  import java.util.Collection;
20  import java.util.HashMap;
21  import java.util.Iterator;
22  import java.util.Map;
23  
24  import org.apache.commons.clazz.reflect.ReflectedClazz;
25  import org.apache.commons.clazz.reflect.common.*;
26  
27  /**
28   * A ReflectedPropertyIntrospector that discovers mapped properties.
29   * 
30   * @author <a href="mailto:dmitri@apache.org">Dmitri Plotnikov</a>
31   * @version $Id: ReflectedMappedPropertyIntrospector.java 155436 2005-02-26 13:17:48Z dirkv $
32   */
33  public class ReflectedMappedPropertyIntrospector
34      extends ReflectedPropertyIntrospectorSupport 
35  {
36      protected static final AccessorMethodParser READ_METHOD_PARSER =
37          new ReadAccessorMethodParser();
38  
39      protected static final AccessorMethodParser WRITE_METHOD_PARSER =
40          new WriteAccessorMethodParser();
41  
42      protected static final AccessorMethodParser GET_METHOD_PARSER =
43          new GetAccessorMethodParser();
44  
45      protected static final AccessorMethodParser PUT_METHOD_PARSER =
46          new PutAccessorMethodParser();
47  
48      protected static final AccessorMethodParser REMOVE_METHOD_PARSER =
49          new RemoveAccessorMethodParser();
50  
51      protected static final AccessorMethodParser KEY_SET_METHOD_PARSER =
52          new KeySetAccessorMethodParser();
53                                      
54      /**
55       * Goes over methods of the supplied class and creates 
56       * ReflectedAccessorPairProperty objects for discovered properties.
57       */
58      public void introspectProperties(
59              ReflectedClazz clazz,
60              Class javaClass,
61              Map parseResultMap)
62      {
63          HashMap parseResultMapSingular = new HashMap();
64          Method methods[] = javaClass.getMethods();
65          ReflectedMappedPropertyParseResults parseResults;
66          AccessorMethodParseResults results;
67          for (int i = 0; i < methods.length; i++) {
68              Method method = methods[i];
69              
70              // Check getFooKeys() before we check getFooList(),
71              // because the parser for the latter is generic enough
72              // to include the former            
73              results = getKeySetAccessorMethodParser().parse(method);
74              if (results != null) {
75                  parseResults =
76                      getParseResults(
77                          clazz,
78                          parseResultMapSingular,
79                          results.getPropertyName());
80                  parseResults.setKeySetMethodParseResults(results);
81                  continue;
82              }
83  
84              results = getReadAccessorMethodParser().parse(method);
85              if (results != null) {
86                  parseResults =
87                      getParseResults(
88                          clazz,
89                          parseResultMap,
90                          results.getPropertyName());
91                  parseResults.setReadMethodParseResults(results);
92                  continue;
93              }
94  
95              results = getWriteAccessorMethodParser().parse(method);
96              if (results != null) {
97                  parseResults =
98                      getParseResults(
99                          clazz,
100                         parseResultMap,
101                         results.getPropertyName());
102                 parseResults.setWriteMethodParseResults(results);
103                 continue;
104             }
105 
106             results = getGetAccessorMethodParser().parse(method);
107             if (results != null) {
108                 parseResults =
109                     getParseResults(
110                         clazz,
111                         parseResultMapSingular,
112                         results.getPropertyName());
113                 parseResults.setGetMethodParseResults(results);
114                 continue;
115             }
116 
117             results = getPutAccessorMethodParser().parse(method);
118             if (results != null) {
119                 parseResults =
120                     getParseResults(
121                         clazz,
122                         parseResultMapSingular,
123                         results.getPropertyName());
124                 parseResults.setPutMethodParseResults(results);
125                 continue;
126             }
127             
128             results = getRemoveAccessorMethodParser().parse(method);
129             if (results != null) {
130                 parseResults =
131                     getParseResults(
132                         clazz,
133                         parseResultMapSingular,
134                         results.getPropertyName());
135                 parseResults.setRemoveMethodParseResults(results);
136                 continue;
137             }
138         }
139         
140         Iterator iter = parseResultMap.entrySet().iterator();
141         while (iter.hasNext()) {
142             Map.Entry entry = (Map.Entry) iter.next();
143             ReflectedMappedPropertyParseResults result = 
144                 (ReflectedMappedPropertyParseResults) entry.getValue();
145             if (!result.isMap()) {
146                 iter.remove();
147             }
148         }
149         
150         mergeSingularMethods(parseResultMap, parseResultMapSingular);
151     }
152 
153     protected AccessorMethodParser getReadAccessorMethodParser() {
154         return READ_METHOD_PARSER;
155     }
156     
157     protected AccessorMethodParser getWriteAccessorMethodParser() {
158         return WRITE_METHOD_PARSER;
159     }
160     
161     protected AccessorMethodParser getGetAccessorMethodParser() {
162         return GET_METHOD_PARSER;
163     }
164     
165     protected AccessorMethodParser getPutAccessorMethodParser() {
166         return PUT_METHOD_PARSER;
167     }
168     
169     protected AccessorMethodParser getRemoveAccessorMethodParser() {
170         return REMOVE_METHOD_PARSER;
171     }
172     
173     protected AccessorMethodParser getKeySetAccessorMethodParser() {
174         return KEY_SET_METHOD_PARSER;
175     }
176 
177     /**
178      * Combines data collected from singular methods like 
179      * <code>getFoo(key)</code> with parse results for plural methods 
180      * like <code>getFooMap()</code>. 
181      */    
182     protected void mergeSingularMethods(
183             Map parseResultMapPlural, Map parseResultMapSingular)
184     {
185         Iterator iter = parseResultMapSingular.values().iterator();
186         while (iter.hasNext()) {
187             
188             ReflectedMappedPropertyParseResults singular =
189                 (ReflectedMappedPropertyParseResults) iter.next();
190                 
191             ReflectedMappedPropertyParseResults plural =
192                 findBySingularName(
193                         parseResultMapPlural, singular.getPropertyName());
194             
195             if (plural != null) {
196                 plural.merge(singular);
197             }
198             else {
199                 // We don't have any plural methods - let's just use
200                 // the singular ones then
201                 parseResultMapPlural.put(singular.getPropertyName(), singular);
202             }
203         }
204     }
205 
206     /**
207      * Given a singular form of a property name, locates parse results
208      * for a property with the corresponding plural name.
209      */
210     protected ReflectedMappedPropertyParseResults findBySingularName(
211             Map parseResultMapPlural,
212             String singularName) 
213     {
214         ReflectedMappedPropertyParseResults plural =
215             (ReflectedMappedPropertyParseResults) 
216                 parseResultMapPlural.get(singularName);
217         if (plural != null) {
218             return plural;
219         }
220 
221         Iterator iter = parseResultMapPlural.entrySet().iterator();
222         while (iter.hasNext()) {
223             Map.Entry entry = (Map.Entry) iter.next();
224             if (isCorrectPluralForm(singularName, (String) entry.getKey())) {
225                 return (ReflectedMappedPropertyParseResults) entry.getValue();
226             }
227         }
228         return null;
229     }
230 
231     /**
232      * Returns <code>true</code> if the suffix is "s" or "Map".
233      *  
234      * @see ReflectedPropertyIntrospectorSupport#isCorrectPluralSuffix(String,String)
235      */
236     protected boolean isCorrectPluralSuffix(String singular, String suffix) {
237         return super.isCorrectPluralSuffix(singular, suffix)
238             || suffix.equals("Map");
239     }
240     
241      
242     /**
243      * Finds a ReflectedMappedPropertyParseResults for the given
244      * propertyName or creates a new one and puts it in the map.
245      */
246     protected ReflectedMappedPropertyParseResults getParseResults(
247             ReflectedClazz clazz,
248             Map parseResultMap,
249             String propertyName) 
250     {
251         ReflectedMappedPropertyParseResults parseResults =
252             (ReflectedMappedPropertyParseResults) parseResultMap.get(
253                 propertyName);
254         if (parseResults == null) {
255             parseResults =
256                 new ReflectedMappedPropertyParseResults(clazz, propertyName);
257             parseResultMap.put(propertyName, parseResults);
258         }
259         return parseResults;
260     }
261     
262     /**
263      * Creates a new ReflectedMappedProperty based on parse results. 
264      */
265     protected ReflectedAccessorPairProperty createProperty(
266         ReflectedClazz clazz,
267         ReflectedPropertyParseResults parseResults)
268     {
269         ReflectedMappedProperty property =
270             new ReflectedMappedProperty(clazz, parseResults.getPropertyName());
271 
272         ReflectedMappedPropertyParseResults parseResultsMapped =
273                 (ReflectedMappedPropertyParseResults) parseResults;
274 
275         property.setAliases(parseResultsMapped.getAliases());                 
276         property.setType(parseResultsMapped.getPropertyType());
277         property.setKeyType(parseResultsMapped.getKeyType());
278         property.setContentType(parseResultsMapped.getContentType());
279         property.setReadMethod(parseResultsMapped.getReadMethod());
280         property.setWriteMethod(parseResultsMapped.getWriteMethod());
281         property.setGetMethod(parseResultsMapped.getGetMethod());
282         property.setPutMethod(parseResultsMapped.getPutMethod());
283         property.setRemoveMethod(parseResultsMapped.getRemoveMethod());
284         property.setKeySetMethod(parseResultsMapped.getKeySetMethod());
285         return property;
286     }
287 
288     /**
289      * Parser for the <code>getFooMap()</code> method:
290      * <ul>
291      *  <li>Return type not void</li>
292      *  <li>Name starts with "get" followed by capitalized property name</li>
293      *  <li>No parameters</li>
294      * </ul>
295      * 
296      * We don't check if the parameter is a Map here. If it is not,
297      * we want to recognize the method and them mark the corresponding
298      * property as NotAProperty.
299      */
300     public static class ReadAccessorMethodParser extends AccessorMethodParser {
301         protected boolean testReturnType(Class returnType) {
302             return !returnType.equals(Void.TYPE);
303         }
304         protected String requiredPrefix() {
305             return "get";
306         }
307         protected int requiredParameterCount() {
308             return 0;
309         }
310         protected Class getValueType(Method method) {
311             return method.getReturnType();
312         }
313     }
314     
315     /**
316      * Parser for the <code>setFooMap(Map)</code> method:
317      * <ul>
318      *  <li>Return type void</li>
319      *  <li>Name starts with "set" followed by capitalized property name</li>
320      *  <li>One parameter</li>
321      * </ul>
322      * 
323      * We don't check if the parameter is a Map here. If it is not,
324      * we want to recognize the method and them mark the corresponding
325      * property as NotAProperty.
326      */            
327     public static class WriteAccessorMethodParser extends AccessorMethodParser {
328         protected boolean testReturnType(Class returnType) {
329             return returnType.equals(Void.TYPE);
330         }
331         protected int requiredParameterCount() {
332             return 1;
333         }
334         protected String requiredPrefix() {
335             return "set";
336         }
337         protected Class getValueType(Method method) {
338             return method.getParameterTypes()[0];
339         }
340     }
341     
342     /**
343      * Parser for the <code>getFoo(key)</code> method:
344      * <ul>
345      *  <li>Return type not void</li>
346      *  <li>Name starts with "get" followed by capitalized singular
347      *      form of the property name</li>
348      *  <li>One parameter</li>
349      * </ul>
350      */            
351     public static class GetAccessorMethodParser extends AccessorMethodParser {
352         protected boolean testReturnType(Class returnType) {
353             return !returnType.equals(Void.TYPE);
354         }
355         protected String requiredPrefix() {
356             return "get";
357         }
358         protected int requiredParameterCount() {
359             return 1;
360         }
361         protected Class getValueType(Method method) {
362             return method.getReturnType();
363         }
364         protected Class getParameterType(Method method) {
365             return method.getParameterTypes()[0];
366         }
367     }
368 
369     /**
370      * Parser for the <code>setFoo(key, value)</code> method:
371      * <ul>
372      *  <li>Return type void</li>
373      *  <li>Name starts with "set" followed by capitalized singular
374      *      form of the property name</li>
375      *  <li>Two parameters</li>
376      * </ul>
377      */                        
378     public static class PutAccessorMethodParser extends AccessorMethodParser {
379         protected String requiredPrefix() {
380             return "set";
381         }
382         protected int requiredParameterCount() {
383             return 2;
384         }
385         protected Class getValueType(Method method) {
386             return method.getParameterTypes()[1];
387         }
388         protected Class getParameterType(Method method) {
389             return method.getParameterTypes()[0];
390         }
391     }
392     
393     /**
394      * Parser for the <code>removeFoo(key)</code> method:
395      * <ul>
396      *  <li>Name starts with "remove" followed by capitalized singular
397      *      form of the property name</li>
398      *  <li>One parameter</li>
399      * </ul>
400      */                        
401     public static class RemoveAccessorMethodParser
402         extends AccessorMethodParser 
403     {
404         protected String requiredPrefix() {
405             return "remove";
406         }
407         protected int requiredParameterCount() {
408             return 1;
409         }
410         protected Class getValueType(Method method) {
411             Class returnType = method.getReturnType();
412             if (Void.TYPE.equals(returnType)) {
413                 return null;
414             }
415             return returnType;
416         }
417         protected Class getParameterType(Method method) {
418             return method.getParameterTypes()[0];
419         }
420     }
421     
422     /**
423      * Parser for the <code>getFooKeys()</code> method:
424      * <ul>
425      *  <li>Returns integer</li>
426      *  <li>Name starts with "get" followed by capitalized singular
427      *      form of the property name, followed by "Keys" or "KeySet"</li>
428      *  <li>No parameters</li>
429      * </ul>
430      */                        
431     public static class KeySetAccessorMethodParser
432         extends AccessorMethodParser 
433     {
434         protected boolean testReturnType(Class javaClass) {
435             return javaClass.isArray()
436                 || Collection.class.isAssignableFrom(javaClass);
437         }
438         protected String requiredPrefix() {
439             return "get";
440         }
441         protected int requiredParameterCount() {
442             return 0;
443         }
444         protected String testAndRemoveSuffix(String methodName) {
445             if (methodName.endsWith("Keys")) {
446                 return methodName.substring(0, methodName.length() - 4);
447             }
448             if (methodName.endsWith("KeySet")) {
449                 return methodName.substring(0, methodName.length() - 6);
450             }
451             return null;
452         }
453     }
454 }