1
2
3
4
5
6
7
8
9
10
11
12
13
14
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,v 1.6 2004/02/19 23:58:39 scolebourne Exp $
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
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 }