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 }