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.common;
17  
18  import java.lang.reflect.Method;
19  import java.lang.reflect.Modifier;
20  
21  /**
22   * 
23   * @author <a href="mailto:dmitri@apache.org">Dmitri Plotnikov</a>
24   * @version $Id: AccessorMethodParser.java 155436 2005-02-26 13:17:48Z dirkv $
25   */
26  public class AccessorMethodParser {
27      
28      /**
29       * If a method parsed by this parser must have a number or parameters,
30       * override and return that number.  
31       */
32      protected int requiredParameterCount() {
33          return -1;
34      }
35  
36      /**
37       * If a method parsed by this parser must have a certain prefix,
38       * override and return a non-null prefix string  
39       */
40      protected String requiredPrefix() {
41          return null;
42      }
43  
44      /**
45       * To check constraints on the return type of methods parsed
46       * by this parser, override and perform the check.
47       *  
48       * @param javaClass The return type of the method (never null)
49       * @return boolean True if the return type passes the parser's constraints
50       */    
51      protected boolean testReturnType(Class javaClass) {
52          return true;
53      }
54      
55      /**
56       * To check constraints on the type of a parameter, override 
57       * and perform the check.
58       *  
59       * @param javaClass The return type of the method (never null)
60       * @return boolean True if the return type passes the parser's constraints
61       */    
62      protected boolean testParameterType(int index, Class parameterType) {
63          return true;
64      }
65      
66      /**
67       * If a method parsed by this parser must have a certain suffix,
68       * override this method, check that it does and remove the
69       * suffix.
70       */
71      protected String testAndRemoveSuffix(String methodName) {
72          return methodName;
73      }
74      
75      /**
76       * Returns true if the character can be the first character of a Capitalized
77       * property name.
78       */
79      protected boolean testFirstCharacterOfPropertyName(char ch) {
80          return Character.isUpperCase(ch);
81      }
82      
83      /**
84       * Extract the value type from the method. Depending on the type
85       * of method, it could be the return type or the type of a parameter.
86       */
87      protected Class getValueType(Method method) {
88          return null;
89      }
90      
91      /**
92       * Extract the parameter type from the method, if it has one. 
93       * For example a mapped property "get" method might have
94       * a "key" parameter.
95       */
96      protected Class getParameterType(Method method) {
97          return null;
98      }
99  
100     /**
101      * Parses the supplied method according to the parser's configuration.
102      * If the parse process fails, returns null.
103      * 
104      * @param method
105      * @return AccessorMethodParseResults
106      */
107     public AccessorMethodParseResults parse(Method method) {
108         int modifiers = method.getModifiers();
109 
110         // An accessor methods must be public and non-static
111         if (!Modifier.isPublic(modifiers) || Modifier.isStatic(modifiers)) {
112             return null;
113         }
114 
115         Class returnType = method.getReturnType();
116         if (returnType == null) {
117             returnType = Void.TYPE; 
118         }
119         if (!testReturnType(returnType)) {
120             return null;
121         }
122         
123         int reqParamCount = requiredParameterCount();
124         if (reqParamCount != -1) {
125             Class paramTypes[] = method.getParameterTypes();
126             if (paramTypes.length != reqParamCount) {
127                 return null;
128             }
129             
130             for (int i = 0; i < paramTypes.length; i++) {
131                 if (!testParameterType(i, paramTypes[i])) {
132                     return null;
133                 }
134             }
135         }
136         
137         String propertyName = getPropertyName(method);
138         if (propertyName == null) {
139             return null;
140         }
141         
142         return new AccessorMethodParseResults(
143                         method, 
144                         propertyName, 
145                         getValueType(method), 
146                         getParameterType(method));
147     }
148     
149     /**
150      * Parse method name and return the corresponding property name.
151      * Return null if the method name does not satisfy the parser's
152      * grammar.
153      * <p>
154      * The default implementation of the method checks if the 
155      * method name starts with the specified prefix followed
156      * by an optionally capitalized property name.
157      * 
158      * @param methodName
159      * @return String
160      */
161     protected String getPropertyName(Method method) {
162         String name = method.getName();
163         name = testAndRemoveSuffix(name);
164         if (name == null) {
165             return null;
166         }
167         
168         String prefix = requiredPrefix(); 
169         if (prefix == null) {
170             return name;
171         }
172         
173         int prefixLength = prefix.length();
174         
175         if (name.length() <= prefixLength) {
176             return null;
177         }
178         
179         if (!name.startsWith(prefix)) {
180             return null;
181         }
182         
183         if (!testFirstCharacterOfPropertyName(name.charAt(prefixLength))) {
184             return null;
185         }
186         
187         return decapitalize(name.substring(prefixLength));
188     }
189     
190     /**
191      * Changes the first character of the <code>string</code>
192      * to lower case, unless the second character is
193      * upper case.  This is consistent with the JavaBean specification.
194      * 
195      * @param candidate
196      * @return String
197      */
198     protected String decapitalize(String string) {
199         char firstChar = string.charAt(0);
200         if (!Character.isUpperCase(firstChar)) {
201             return string;
202         }
203         
204         int len = string.length();
205         if (len == 1) {
206             return String.valueOf(Character.toLowerCase(firstChar));
207         }
208         else if (Character.isLowerCase(string.charAt(1))) {
209             char buffer[] = new char[len];
210             buffer[0] = Character.toLowerCase(firstChar);
211             string.getChars(1, len, buffer, 1);
212             return new String(buffer);
213         }
214         
215         return string;
216     }
217 }