001/** 002 * 003 * Licensed to the Apache Software Foundation (ASF) under one or more 004 * contributor license agreements. See the NOTICE file distributed with 005 * this work for additional information regarding copyright ownership. 006 * The ASF licenses this file to You under the Apache License, Version 2.0 007 * (the "License"); you may not use this file except in compliance with 008 * the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.xbean.recipe; 019 020import org.objectweb.asm.ClassReader; 021import org.objectweb.asm.ClassVisitor; 022import org.objectweb.asm.Label; 023import org.objectweb.asm.MethodVisitor; 024import org.objectweb.asm.Type; 025 026import java.io.IOException; 027import java.io.InputStream; 028import java.lang.reflect.Constructor; 029import java.lang.reflect.Method; 030import java.lang.reflect.Modifier; 031import java.util.ArrayList; 032import java.util.Arrays; 033import java.util.Collections; 034import java.util.HashMap; 035import java.util.List; 036import java.util.Map; 037import java.util.WeakHashMap; 038 039import static org.apache.xbean.asm9.original.commons.AsmConstants.ASM_VERSION; 040 041/** 042 * Implementation of ParameterNameLoader that uses ASM to read the parameter names from the local variable table in the 043 * class byte code. 044 * 045 * This wonderful piece of code was taken from org.springframework.core.LocalVariableTableParameterNameDiscover 046 */ 047public class AsmParameterNameLoader implements ParameterNameLoader { 048 /** 049 * Weak map from Constructor to List<String>. 050 */ 051 private final WeakHashMap<Constructor,List<String>> constructorCache = new WeakHashMap<Constructor,List<String>>(); 052 053 /** 054 * Weak map from Method to List<String>. 055 */ 056 private final WeakHashMap<Method,List<String>> methodCache = new WeakHashMap<Method,List<String>>(); 057 058 /** 059 * Gets the parameter names of the specified method or null if the class was compiled without debug symbols on. 060 * @param method the method for which the parameter names should be retrieved 061 * @return the parameter names or null if the class was compilesd without debug symbols on 062 */ 063 public List<String> get(Method method) { 064 // check the cache 065 if (methodCache.containsKey(method)) { 066 return methodCache.get(method); 067 } 068 069 Map<Method,List<String>> allMethodParameters = getAllMethodParameters(method.getDeclaringClass(), method.getName()); 070 return allMethodParameters.get(method); 071 } 072 073 /** 074 * Gets the parameter names of the specified constructor or null if the class was compiled without debug symbols on. 075 * @param constructor the constructor for which the parameters should be retrieved 076 * @return the parameter names or null if the class was compiled without debug symbols on 077 */ 078 public List<String> get(Constructor constructor) { 079 // check the cache 080 if (constructorCache.containsKey(constructor)) { 081 return constructorCache.get(constructor); 082 } 083 084 Map<Constructor,List<String>> allConstructorParameters = getAllConstructorParameters(constructor.getDeclaringClass()); 085 return allConstructorParameters.get(constructor); 086 } 087 088 /** 089 * Gets the parameter names of all constructor or null if the class was compiled without debug symbols on. 090 * @param clazz the class for which the constructor parameter names should be retrieved 091 * @return a map from Constructor object to the parameter names or null if the class was compiled without debug symbols on 092 */ 093 public Map<Constructor,List<String>> getAllConstructorParameters(Class clazz) { 094 // Determine the constructors? 095 List<Constructor> constructors = new ArrayList<Constructor>(Arrays.asList(clazz.getConstructors())); 096 constructors.addAll(Arrays.asList(clazz.getDeclaredConstructors())); 097 if (constructors.isEmpty()) { 098 return Collections.emptyMap(); 099 } 100 101 // Check the cache 102 if (constructorCache.containsKey(constructors.get(0))) { 103 Map<Constructor,List<String>> constructorParameters = new HashMap<Constructor,List<String>>(); 104 for (Constructor constructor : constructors) { 105 constructorParameters.put(constructor, constructorCache.get(constructor)); 106 } 107 return constructorParameters; 108 } 109 110 // Load the parameter names using ASM 111 Map<Constructor,List<String>> constructorParameters = new HashMap<Constructor,List<String>> (); 112 try { 113 ClassReader reader = AsmParameterNameLoader.createClassReader(clazz); 114 115 AsmParameterNameLoader.AllParameterNamesDiscoveringVisitor visitor = new AsmParameterNameLoader.AllParameterNamesDiscoveringVisitor(clazz); 116 reader.accept(visitor, 0); 117 118 Map exceptions = visitor.getExceptions(); 119 if (exceptions.size() == 1) { 120 throw new RuntimeException((Exception)exceptions.values().iterator().next()); 121 } 122 if (!exceptions.isEmpty()) { 123 throw new RuntimeException(exceptions.toString()); 124 } 125 126 constructorParameters = visitor.getConstructorParameters(); 127 } catch (IOException ex) { 128 } 129 130 // Cache the names 131 for (Constructor constructor : constructors) { 132 constructorCache.put(constructor, constructorParameters.get(constructor)); 133 } 134 return constructorParameters; 135 } 136 137 /** 138 * Gets the parameter names of all methods with the specified name or null if the class was compiled without debug symbols on. 139 * @param clazz the class for which the method parameter names should be retrieved 140 * @param methodName the of the method for which the parameters should be retrieved 141 * @return a map from Method object to the parameter names or null if the class was compiled without debug symbols on 142 */ 143 public Map<Method,List<String>> getAllMethodParameters(Class clazz, String methodName) { 144 // Determine the constructors? 145 Method[] methods = getMethods(clazz, methodName); 146 if (methods.length == 0) { 147 return Collections.emptyMap(); 148 } 149 150 // Check the cache 151 if (methodCache.containsKey(methods[0])) { 152 Map<Method,List<String>> methodParameters = new HashMap<Method,List<String>>(); 153 for (Method method : methods) { 154 methodParameters.put(method, methodCache.get(method)); 155 } 156 return methodParameters; 157 } 158 159 // Load the parameter names using ASM 160 Map<Method,List<String>> methodParameters = new HashMap<Method,List<String>>(); 161 try { 162 ClassReader reader = AsmParameterNameLoader.createClassReader(clazz); 163 164 AsmParameterNameLoader.AllParameterNamesDiscoveringVisitor visitor = new AsmParameterNameLoader.AllParameterNamesDiscoveringVisitor(clazz, methodName); 165 reader.accept(visitor, 0); 166 167 Map exceptions = visitor.getExceptions(); 168 if (exceptions.size() == 1) { 169 throw new RuntimeException((Exception)exceptions.values().iterator().next()); 170 } 171 if (!exceptions.isEmpty()) { 172 throw new RuntimeException(exceptions.toString()); 173 } 174 175 methodParameters = visitor.getMethodParameters(); 176 } catch (IOException ex) { 177 } 178 179 // Cache the names 180 for (Method method : methods) { 181 methodCache.put(method, methodParameters.get(method)); 182 } 183 return methodParameters; 184 } 185 186 private Method[] getMethods(Class clazz, String methodName) { 187 List<Method> methods = new ArrayList<Method>(Arrays.asList(clazz.getMethods())); 188 methods.addAll(Arrays.asList(clazz.getDeclaredMethods())); 189 List<Method> matchingMethod = new ArrayList<Method>(methods.size()); 190 for (Method method : methods) { 191 if (method.getName().equals(methodName)) { 192 matchingMethod.add(method); 193 } 194 } 195 return matchingMethod.toArray(new Method[matchingMethod.size()]); 196 } 197 198 private static ClassReader createClassReader(Class declaringClass) throws IOException { 199 InputStream in = null; 200 try { 201 ClassLoader classLoader = declaringClass.getClassLoader(); 202 in = classLoader.getResourceAsStream(declaringClass.getName().replace('.', '/') + ".class"); 203 ClassReader reader = new ClassReader(in); 204 return reader; 205 } finally { 206 if (in != null) { 207 try { 208 in.close(); 209 } catch (IOException ignored) { 210 } 211 } 212 } 213 } 214 215 private static class AllParameterNamesDiscoveringVisitor extends ClassVisitor { 216 private final Map<Constructor,List<String>> constructorParameters = new HashMap<Constructor,List<String>>(); 217 private final Map<Method,List<String>> methodParameters = new HashMap<Method,List<String>>(); 218 private final Map<String,Exception> exceptions = new HashMap<String,Exception>(); 219 private final String methodName; 220 private final Map<String,Method> methodMap = new HashMap<String,Method>(); 221 private final Map<String,Constructor> constructorMap = new HashMap<String,Constructor>(); 222 223 public AllParameterNamesDiscoveringVisitor(Class type, String methodName) { 224 super(ASM_VERSION); 225 this.methodName = methodName; 226 227 List<Method> methods = new ArrayList<Method>(Arrays.asList(type.getMethods())); 228 methods.addAll(Arrays.asList(type.getDeclaredMethods())); 229 for (Method method : methods) { 230 if (method.getName().equals(methodName)) { 231 methodMap.put(Type.getMethodDescriptor(method), method); 232 } 233 } 234 } 235 236 public AllParameterNamesDiscoveringVisitor(Class type) { 237 super(ASM_VERSION); 238 this.methodName = "<init>"; 239 240 List<Constructor> constructors = new ArrayList<Constructor>(Arrays.asList(type.getConstructors())); 241 constructors.addAll(Arrays.asList(type.getDeclaredConstructors())); 242 for (Constructor constructor : constructors) { 243 Type[] types = new Type[constructor.getParameterTypes().length]; 244 for (int j = 0; j < types.length; j++) { 245 types[j] = Type.getType(constructor.getParameterTypes()[j]); 246 } 247 constructorMap.put(Type.getMethodDescriptor(Type.VOID_TYPE, types), constructor); 248 } 249 } 250 251 public Map<Constructor, List<String>> getConstructorParameters() { 252 return constructorParameters; 253 } 254 255 public Map<Method, List<String>> getMethodParameters() { 256 return methodParameters; 257 } 258 259 public Map<String,Exception> getExceptions() { 260 return exceptions; 261 } 262 263 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 264 if (!name.equals(this.methodName)) { 265 return null; 266 } 267 268 try { 269 final List<String> parameterNames; 270 final boolean isStaticMethod; 271 272 if (methodName.equals("<init>")) { 273 Constructor constructor = constructorMap.get(desc); 274 if (constructor == null) { 275 return null; 276 } 277 parameterNames = new ArrayList<String>(constructor.getParameterTypes().length); 278 parameterNames.addAll(Collections.<String>nCopies(constructor.getParameterTypes().length, null)); 279 constructorParameters.put(constructor, parameterNames); 280 isStaticMethod = false; 281 } else { 282 Method method = methodMap.get(desc); 283 if (method == null) { 284 return null; 285 } 286 parameterNames = new ArrayList<String>(method.getParameterTypes().length); 287 parameterNames.addAll(Collections.<String>nCopies(method.getParameterTypes().length, null)); 288 methodParameters.put(method, parameterNames); 289 isStaticMethod = Modifier.isStatic(method.getModifiers()); 290 } 291 292 return new MethodVisitor(ASM_VERSION) { 293 // assume static method until we get a first parameter name 294 public void visitLocalVariable(String name, String description, String signature, Label start, Label end, int index) { 295 if (isStaticMethod) { 296 parameterNames.set(index, name); 297 } else if (index > 0) { 298 // for non-static the 0th arg is "this" so we need to offset by -1 299 parameterNames.set(index - 1, name); 300 } 301 } 302 }; 303 } catch (Exception e) { 304 this.exceptions.put(signature, e); 305 } 306 return null; 307 } 308 } 309}