001    /***
002     *
003     * Copyright (c) 2007 Paul Hammant
004     * All rights reserved.
005     *
006     * Redistribution and use in source and binary forms, with or without
007     * modification, are permitted provided that the following conditions
008     * are met:
009     * 1. Redistributions of source code must retain the above copyright
010     *    notice, this list of conditions and the following disclaimer.
011     * 2. Redistributions in binary form must reproduce the above copyright
012     *    notice, this list of conditions and the following disclaimer in the
013     *    documentation and/or other materials provided with the distribution.
014     * 3. Neither the name of the copyright holders nor the names of its
015     *    contributors may be used to endorse or promote products derived from
016     *    this software without specific prior written permission.
017     *
018     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
022     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
024     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
025     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
026     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
027     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
028     * THE POSSIBILITY OF SUCH DAMAGE.
029     */
030    
031    package com.thoughtworks.paranamer;
032    
033    import java.lang.reflect.Constructor;
034    import java.lang.reflect.Method;
035    import java.lang.reflect.Field;
036    import java.lang.reflect.Modifier;
037    import java.lang.reflect.AccessibleObject;
038    
039    /**
040     * Default implementation of Paranamer
041     *
042     * @author Paul Hammant
043     * @author Mauro Talevi
044     * @author Guilherme Silveira
045     */
046    public class DefaultParanamer implements Paranamer {
047    
048        private static final String COMMA = ",";
049        private static final String SPACE = " ";
050    
051        public static final String __PARANAMER_DATA = "v1.0 \n"
052            + "<init> \n"
053            + "toString \n"
054            + "lookupParameterNames java.lang.AccessibleObject methodOrCtor \n"
055            + "getParameterTypeName java.lang.Class cls\n";
056    
057        public DefaultParanamer() {
058        }
059    
060        public String[] lookupParameterNames(AccessibleObject methodOrConstructor) {
061            return lookupParameterNames(methodOrConstructor, true);
062        }
063    
064        public String[] lookupParameterNames(AccessibleObject methodOrCtor, boolean throwExceptionIfMissing) {
065            // Oh for some commonality between Constructor and Method !!
066            Class[] types = null;
067            Class declaringClass = null;
068            String name = null;
069            if (methodOrCtor instanceof Method) {
070                Method method = (Method) methodOrCtor;
071                types = method.getParameterTypes();
072                name = method.getName();
073                declaringClass = method.getDeclaringClass();
074            } else {
075                Constructor constructor = (Constructor) methodOrCtor;
076                types = constructor.getParameterTypes();
077                declaringClass = constructor.getDeclaringClass();
078                name = "<init>";
079            }
080    
081            if (types.length == 0) {
082                return EMPTY_NAMES;
083            }
084            final String parameterTypeNames = getParameterTypeNamesCSV(types);
085            final String[] names = getParameterNames(declaringClass, parameterTypeNames, name + SPACE);
086            if ( names == null ){
087                if (throwExceptionIfMissing) {
088                throw new ParameterNamesNotFoundException("No parameter names found for class '"+declaringClass+"', methodOrCtor " + name
089                        +" and parameter types "+parameterTypeNames);
090                } else {
091                    return Paranamer.EMPTY_NAMES;
092                }
093            }
094            return names;
095        }
096    
097        private static String[] getParameterNames(Class declaringClass, String parameterTypes, String prefix) {
098            String data = getParameterListResource(declaringClass);
099            String line = findFirstMatchingLine(data, prefix + parameterTypes + SPACE);
100            String[] parts = line.split(SPACE);
101            // assumes line structure: constructorName parameterTypes parameterNames
102            if (parts.length == 3 && parts[1].equals(parameterTypes)) {
103                String parameterNames = parts[2];
104                return parameterNames.split(COMMA);
105            }
106            return Paranamer.EMPTY_NAMES;
107        }
108    
109        /**
110         * @Deperecated Use 'new CachingParanamer(new AdaptiveParanamer())' instead.
111         */
112        public int areParameterNamesAvailable(Class clazz, String constructorOrMethodName) {
113            String data = getParameterListResource(clazz);
114    
115            if (data == null) {
116                return NO_PARAMETER_NAMES_LIST;
117            }
118    
119            String line = findFirstMatchingLine(data, constructorOrMethodName + SPACE);
120            if (line.length() == 0) {
121                return NO_PARAMETER_NAMES_FOR_CLASS_AND_MEMBER;
122            }
123    
124            return PARAMETER_NAMES_FOUND;
125        }
126    
127        private static String getParameterTypeNamesCSV(Class[] parameterTypes) {
128            StringBuffer sb = new StringBuffer();
129            for (int i = 0; i < parameterTypes.length; i++) {
130                sb.append(getParameterTypeName(parameterTypes[i]));
131                if (i < parameterTypes.length - 1) {
132                    sb.append(COMMA);
133                }
134            }
135            return sb.toString();
136        }
137    
138        private static String getParameterListResource(Class declaringClass) {
139            try {
140                Field field = declaringClass.getDeclaredField("__PARANAMER_DATA");
141                // TODO create acc test which finds field?
142                // TODO create acc test which does not find field?
143                // TODO create acc test what to do with private? access anyway?
144                // TODO create acc test with non static field?
145                // TODO create acc test with another type of field?
146                if(!Modifier.isStatic(field.getModifiers()) || !field.getType().equals(String.class)) {
147                    return null;
148                }
149                return (String) field.get(null);
150            } catch (NoSuchFieldException e) {
151                return null;
152            } catch (IllegalAccessException e) {
153                return null;
154            }
155        }
156    
157        /**
158         * Filter the mappings and only return lines matching the prefix passed in.
159         * @param data the data encoding the mappings
160         * @param prefix the String prefix
161         * @return A list of lines that match the prefix
162         */
163        private static String findFirstMatchingLine(String data, String prefix) {
164            if (data == null) {
165                return "";
166            }
167            int ix = data.indexOf(prefix);
168            if (ix >= 0) {
169                int iy = data.indexOf("\n", ix);
170                if(iy >0) {
171                    return data.substring(ix,iy);
172                }
173            }
174            return "";
175        }
176    
177    
178        private static String getParameterTypeName(Class cls){ 
179            String parameterTypeNameName = cls.getName();
180            int arrayNestingDepth = 0;
181            int ix = parameterTypeNameName.indexOf("[");
182            while (ix>-1){
183                arrayNestingDepth++;
184                parameterTypeNameName=parameterTypeNameName.replaceFirst("(\\[\\w)|(\\[)","");
185                ix = parameterTypeNameName.indexOf("[");
186            }
187            parameterTypeNameName =parameterTypeNameName.replaceFirst(";","");
188            for (int k=0;k<arrayNestingDepth;k++){
189                parameterTypeNameName = parameterTypeNameName+"[]";
190            }
191            return    parameterTypeNameName;
192    
193        }
194    
195        public String toString() {
196            return super.toString();
197        }
198    }