001: /*
002: * Copyright 2002-2007 the original author or authors.
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:
017: package org.springframework.core;
018:
019: import java.io.FileNotFoundException;
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:
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028: import org.objectweb.asm.ClassReader;
029: import org.objectweb.asm.Label;
030: import org.objectweb.asm.MethodVisitor;
031: import org.objectweb.asm.Opcodes;
032: import org.objectweb.asm.Type;
033: import org.objectweb.asm.commons.EmptyVisitor;
034:
035: import org.springframework.util.ClassUtils;
036:
037: /**
038: * Implementation of ParameterNameDiscover that uses the LocalVariableTable
039: * information in the method attributes to discover parameter names. Returns
040: * <code>null</code> if the class file was compiled without debug information.
041: *
042: * <p>Uses ObjectWeb's ASM library for analyzing class files.
043: *
044: * @author Adrian Colyer
045: * @author Juergen Hoeller
046: * @since 2.0
047: */
048: public class LocalVariableTableParameterNameDiscoverer implements
049: ParameterNameDiscoverer {
050:
051: private static Log logger = LogFactory
052: .getLog(LocalVariableTableParameterNameDiscoverer.class);
053:
054: public String[] getParameterNames(Method method) {
055: ParameterNameDiscoveringVisitor visitor = null;
056: try {
057: visitor = visitMethod(method);
058: if (visitor.foundTargetMember()) {
059: return visitor.getParameterNames();
060: }
061: } catch (IOException ex) {
062: // We couldn't load the class file, which is not fatal as it
063: // simply means this method of discovering parameter names won't work.
064: if (logger.isDebugEnabled()) {
065: logger
066: .debug(
067: "IOException whilst attempting to read '.class' file for class ["
068: + method.getDeclaringClass()
069: .getName()
070: + "] - unable to determine parameter names for method: "
071: + method, ex);
072: }
073: }
074: return null;
075: }
076:
077: public String[] getParameterNames(Constructor ctor) {
078: ParameterNameDiscoveringVisitor visitor = null;
079: try {
080: visitor = visitConstructor(ctor);
081: if (visitor.foundTargetMember()) {
082: return visitor.getParameterNames();
083: }
084: } catch (IOException ex) {
085: // We couldn't load the class file, which is not fatal as it
086: // simply means this method of discovering parameter names won't work.
087: if (logger.isDebugEnabled()) {
088: logger
089: .debug(
090: "IOException whilst attempting to read '.class' file for class ["
091: + ctor.getDeclaringClass()
092: .getName()
093: + "] - unable to determine parameter names for constructor: "
094: + ctor, ex);
095: }
096: }
097: return null;
098: }
099:
100: /**
101: * Visit the given method and discover its parameter names.
102: */
103: private ParameterNameDiscoveringVisitor visitMethod(Method method)
104: throws IOException {
105: ClassReader classReader = createClassReader(method
106: .getDeclaringClass());
107: FindMethodParameterNamesClassVisitor classVisitor = new FindMethodParameterNamesClassVisitor(
108: method);
109: classReader.accept(classVisitor, false);
110: return classVisitor;
111: }
112:
113: /**
114: * Visit the given constructor and discover its parameter names.
115: */
116: private ParameterNameDiscoveringVisitor visitConstructor(
117: Constructor ctor) throws IOException {
118: ClassReader classReader = createClassReader(ctor
119: .getDeclaringClass());
120: FindConstructorParameterNamesClassVisitor classVisitor = new FindConstructorParameterNamesClassVisitor(
121: ctor);
122: classReader.accept(classVisitor, false);
123: return classVisitor;
124: }
125:
126: /**
127: * Create a ClassReader for the given class.
128: */
129: private ClassReader createClassReader(Class clazz)
130: throws IOException {
131: InputStream is = clazz.getResourceAsStream(ClassUtils
132: .getClassFileName(clazz));
133: if (is == null) {
134: throw new FileNotFoundException("Class file for class ["
135: + clazz.getName() + "] not found");
136: }
137: try {
138: return new ClassReader(is);
139: } finally {
140: is.close();
141: }
142: }
143:
144: /**
145: * Helper class that looks for a given member name and descriptor, and then
146: * attempts to find the parameter names for that member.
147: */
148: private static abstract class ParameterNameDiscoveringVisitor
149: extends EmptyVisitor {
150:
151: private String methodNameToMatch;
152:
153: private String descriptorToMatch;
154:
155: private int numParamsExpected;
156:
157: /*
158: * the nth entry contains the slot index of the LVT table entry holding the
159: * argument name for the nth parameter
160: */
161: private int[] lvtSlotIndex;
162:
163: private boolean foundTargetMember = false;
164:
165: private String[] parameterNames;
166:
167: public ParameterNameDiscoveringVisitor(String name,
168: boolean isStatic, Class[] paramTypes) {
169: this .methodNameToMatch = name;
170: this .numParamsExpected = paramTypes.length;
171: computeLVTSlotIndices(isStatic, paramTypes);
172: }
173:
174: public void setDescriptorToMatch(String descriptor) {
175: this .descriptorToMatch = descriptor;
176: }
177:
178: public MethodVisitor visitMethod(int access, String name,
179: String desc, String signature, String[] exceptions) {
180: if (name.equals(this .methodNameToMatch)
181: && desc.equals(this .descriptorToMatch)) {
182: this .foundTargetMember = true;
183: return new LocalVariableTableVisitor(isStatic(access),
184: this , this .numParamsExpected, this .lvtSlotIndex);
185: } else {
186: // Not interested in this method...
187: return null;
188: }
189: }
190:
191: private boolean isStatic(int access) {
192: return ((access & Opcodes.ACC_STATIC) > 0);
193: }
194:
195: public boolean foundTargetMember() {
196: return this .foundTargetMember;
197: }
198:
199: public String[] getParameterNames() {
200: if (!foundTargetMember()) {
201: throw new IllegalStateException(
202: "Can't ask for parameter names when target member has not been found");
203: }
204:
205: return this .parameterNames;
206: }
207:
208: public void setParameterNames(String[] names) {
209: this .parameterNames = names;
210: }
211:
212: private void computeLVTSlotIndices(boolean isStatic,
213: Class[] paramTypes) {
214: this .lvtSlotIndex = new int[paramTypes.length];
215: int nextIndex = (isStatic ? 0 : 1);
216: for (int i = 0; i < paramTypes.length; i++) {
217: this .lvtSlotIndex[i] = nextIndex;
218: if (isWideType(paramTypes[i])) {
219: nextIndex += 2;
220: } else {
221: nextIndex++;
222: }
223: }
224: }
225:
226: private boolean isWideType(Class aType) {
227: return (aType == Long.TYPE || aType == Double.TYPE);
228: }
229: }
230:
231: private static class FindMethodParameterNamesClassVisitor extends
232: ParameterNameDiscoveringVisitor {
233:
234: public FindMethodParameterNamesClassVisitor(Method method) {
235: super (method.getName(), Modifier.isStatic(method
236: .getModifiers()), method.getParameterTypes());
237: setDescriptorToMatch(Type.getMethodDescriptor(method));
238: }
239: }
240:
241: private static class FindConstructorParameterNamesClassVisitor
242: extends ParameterNameDiscoveringVisitor {
243:
244: public FindConstructorParameterNamesClassVisitor(
245: Constructor cons) {
246: super ("<init>", false, cons.getParameterTypes());
247: Type[] pTypes = new Type[cons.getParameterTypes().length];
248: for (int i = 0; i < pTypes.length; i++) {
249: pTypes[i] = Type.getType(cons.getParameterTypes()[i]);
250: }
251: setDescriptorToMatch(Type.getMethodDescriptor(
252: Type.VOID_TYPE, pTypes));
253: }
254: }
255:
256: private static class LocalVariableTableVisitor extends EmptyVisitor {
257:
258: private boolean isStatic;
259: private ParameterNameDiscoveringVisitor memberVisitor;
260: private int numParameters;
261: private int[] lvtSlotIndices;
262: private String[] parameterNames;
263: private boolean hasLVTInfo = false;
264:
265: public LocalVariableTableVisitor(boolean isStatic,
266: ParameterNameDiscoveringVisitor memberVisitor,
267: int numParams, int[] lvtSlotIndices) {
268: this .isStatic = isStatic;
269: this .numParameters = numParams;
270: this .parameterNames = new String[this .numParameters];
271: this .memberVisitor = memberVisitor;
272: this .lvtSlotIndices = lvtSlotIndices;
273: }
274:
275: public void visitLocalVariable(String name, String description,
276: String signature, Label start, Label end, int index) {
277: this .hasLVTInfo = true;
278: if (isMethodArgumentSlot(index)) {
279: this .parameterNames[parameterNameIndexForSlot(index)] = name;
280: }
281: }
282:
283: public void visitEnd() {
284: if (this .hasLVTInfo || this .isStatic && numParameters == 0) {
285: // visitLocalVariable will never be called for static no args methods
286: // which doesn't use any local variables.
287: // This means that hasLVTInfo could be false for that kind of methods
288: // even if the class has local variable info.
289: this .memberVisitor
290: .setParameterNames(this .parameterNames);
291: }
292: }
293:
294: /**
295: * An lvt entry describes an argument (as opposed to a local var) if
296: * it appears in the lvtSlotIndices table
297: */
298: private boolean isMethodArgumentSlot(int index) {
299: for (int i = 0; i < this .lvtSlotIndices.length; i++) {
300: if (this .lvtSlotIndices[i] == index) {
301: return true;
302: }
303: }
304: return false;
305: }
306:
307: private int parameterNameIndexForSlot(int slot) {
308: for (int i = 0; i < this .lvtSlotIndices.length; i++) {
309: if (this .lvtSlotIndices[i] == slot) {
310: return i;
311: }
312: }
313: throw new IllegalStateException(
314: "Asked for index for a slot which failed the isMethodArgumentSlot test: "
315: + slot);
316: }
317: }
318:
319: }
|