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 }