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.common; 017 018 import java.lang.reflect.Method; 019 import java.lang.reflect.Modifier; 020 021 /** 022 * 023 * @author <a href="mailto:dmitri@apache.org">Dmitri Plotnikov</a> 024 * @version $Id: AccessorMethodParser.java 155436 2005-02-26 13:17:48Z dirkv $ 025 */ 026 public class AccessorMethodParser { 027 028 /** 029 * If a method parsed by this parser must have a number or parameters, 030 * override and return that number. 031 */ 032 protected int requiredParameterCount() { 033 return -1; 034 } 035 036 /** 037 * If a method parsed by this parser must have a certain prefix, 038 * override and return a non-null prefix string 039 */ 040 protected String requiredPrefix() { 041 return null; 042 } 043 044 /** 045 * To check constraints on the return type of methods parsed 046 * by this parser, override and perform the check. 047 * 048 * @param javaClass The return type of the method (never null) 049 * @return boolean True if the return type passes the parser's constraints 050 */ 051 protected boolean testReturnType(Class javaClass) { 052 return true; 053 } 054 055 /** 056 * To check constraints on the type of a parameter, override 057 * and perform the check. 058 * 059 * @param javaClass The return type of the method (never null) 060 * @return boolean True if the return type passes the parser's constraints 061 */ 062 protected boolean testParameterType(int index, Class parameterType) { 063 return true; 064 } 065 066 /** 067 * If a method parsed by this parser must have a certain suffix, 068 * override this method, check that it does and remove the 069 * suffix. 070 */ 071 protected String testAndRemoveSuffix(String methodName) { 072 return methodName; 073 } 074 075 /** 076 * Returns true if the character can be the first character of a Capitalized 077 * property name. 078 */ 079 protected boolean testFirstCharacterOfPropertyName(char ch) { 080 return Character.isUpperCase(ch); 081 } 082 083 /** 084 * Extract the value type from the method. Depending on the type 085 * of method, it could be the return type or the type of a parameter. 086 */ 087 protected Class getValueType(Method method) { 088 return null; 089 } 090 091 /** 092 * Extract the parameter type from the method, if it has one. 093 * For example a mapped property "get" method might have 094 * a "key" parameter. 095 */ 096 protected Class getParameterType(Method method) { 097 return null; 098 } 099 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 }