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