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 }