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