001    /*
002     * Copyright 2002-2004 The Apache Software Foundation
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.apache.commons.clazz.reflect.extended;
017    
018    import java.lang.reflect.Method;
019    import java.util.Collection;
020    import java.util.HashMap;
021    import java.util.Iterator;
022    import java.util.Map;
023    
024    import org.apache.commons.clazz.reflect.ReflectedClazz;
025    import org.apache.commons.clazz.reflect.common.*;
026    
027    /**
028     * A ReflectedPropertyIntrospector that discovers mapped properties.
029     * 
030     * @author <a href="mailto:dmitri@apache.org">Dmitri Plotnikov</a>
031     * @version $Id: ReflectedMappedPropertyIntrospector.java 155436 2005-02-26 13:17:48Z dirkv $
032     */
033    public class ReflectedMappedPropertyIntrospector
034        extends ReflectedPropertyIntrospectorSupport 
035    {
036        protected static final AccessorMethodParser READ_METHOD_PARSER =
037            new ReadAccessorMethodParser();
038    
039        protected static final AccessorMethodParser WRITE_METHOD_PARSER =
040            new WriteAccessorMethodParser();
041    
042        protected static final AccessorMethodParser GET_METHOD_PARSER =
043            new GetAccessorMethodParser();
044    
045        protected static final AccessorMethodParser PUT_METHOD_PARSER =
046            new PutAccessorMethodParser();
047    
048        protected static final AccessorMethodParser REMOVE_METHOD_PARSER =
049            new RemoveAccessorMethodParser();
050    
051        protected static final AccessorMethodParser KEY_SET_METHOD_PARSER =
052            new KeySetAccessorMethodParser();
053                                        
054        /**
055         * Goes over methods of the supplied class and creates 
056         * ReflectedAccessorPairProperty objects for discovered properties.
057         */
058        public void introspectProperties(
059                ReflectedClazz clazz,
060                Class javaClass,
061                Map parseResultMap)
062        {
063            HashMap parseResultMapSingular = new HashMap();
064            Method methods[] = javaClass.getMethods();
065            ReflectedMappedPropertyParseResults parseResults;
066            AccessorMethodParseResults results;
067            for (int i = 0; i < methods.length; i++) {
068                Method method = methods[i];
069                
070                // Check getFooKeys() before we check getFooList(),
071                // because the parser for the latter is generic enough
072                // to include the former            
073                results = getKeySetAccessorMethodParser().parse(method);
074                if (results != null) {
075                    parseResults =
076                        getParseResults(
077                            clazz,
078                            parseResultMapSingular,
079                            results.getPropertyName());
080                    parseResults.setKeySetMethodParseResults(results);
081                    continue;
082                }
083    
084                results = getReadAccessorMethodParser().parse(method);
085                if (results != null) {
086                    parseResults =
087                        getParseResults(
088                            clazz,
089                            parseResultMap,
090                            results.getPropertyName());
091                    parseResults.setReadMethodParseResults(results);
092                    continue;
093                }
094    
095                results = getWriteAccessorMethodParser().parse(method);
096                if (results != null) {
097                    parseResults =
098                        getParseResults(
099                            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    }