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    }