1 /**
2  * Copyright (C) 2006 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.inject.internal.util;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 
21 import com.google.common.base.Preconditions;
22 import com.google.common.collect.Maps;
23 
24 import org.objectweb.asm.AnnotationVisitor;
25 import org.objectweb.asm.ClassReader;
26 import org.objectweb.asm.ClassVisitor;
27 import org.objectweb.asm.FieldVisitor;
28 import org.objectweb.asm.Label;
29 import org.objectweb.asm.MethodVisitor;
30 import org.objectweb.asm.Opcodes;
31 
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.lang.reflect.Constructor;
35 import java.lang.reflect.Field;
36 import java.lang.reflect.Member;
37 import java.lang.reflect.Method;
38 import java.util.Map;
39 
40 /**
41  * Looks up line numbers for classes and their members.
42  *
43  * @author Chris Nokleberg
44  */
45 final class LineNumbers {
46 
47   private final Class type;
48   private final Map<String, Integer> lines = Maps.newHashMap();
49   private String source;
50   private int firstLine = Integer.MAX_VALUE;
51 
52   /**
53    * Reads line number information from the given class, if available.
54    *
55    * @param type the class to read line number information from
56    * @throws IllegalArgumentException if the bytecode for the class cannot be found
57    * @throws java.io.IOException if an error occurs while reading bytecode
58    */
LineNumbers(Class type)59   public LineNumbers(Class type) throws IOException {
60     this.type = type;
61 
62     if (!type.isArray()) {
63       InputStream in = type.getResourceAsStream("/" + type.getName().replace('.', '/') + ".class");
64       if (in != null) {
65         new ClassReader(in).accept(new LineNumberReader(), ClassReader.SKIP_FRAMES);
66       }
67     }
68   }
69 
70   /**
71    * Get the source file name as read from the bytecode.
72    *
73    * @return the source file name if available, or null
74    */
getSource()75   public String getSource() {
76     return source;
77   }
78 
79   /**
80    * Get the line number associated with the given member.
81    *
82    * @param member a field, constructor, or method belonging to the class used during construction
83    * @return the wrapped line number, or null if not available
84    * @throws IllegalArgumentException if the member does not belong to the class used during
85    * construction
86    */
getLineNumber(Member member)87   public Integer getLineNumber(Member member) {
88     Preconditions.checkArgument(type == member.getDeclaringClass(),
89         "Member %s belongs to %s, not %s", member, member.getDeclaringClass(), type);
90     return lines.get(memberKey(member));
91   }
92 
93   /** Gets the first line number. */
getFirstLine()94   public int getFirstLine() {
95     return firstLine == Integer.MAX_VALUE ? 1 : firstLine;
96   }
97 
memberKey(Member member)98   private String memberKey(Member member) {
99     checkNotNull(member, "member");
100 
101     /*if[AOP]*/
102     if (member instanceof Field) {
103       return member.getName();
104 
105     } else if (member instanceof Method) {
106       return member.getName() + org.objectweb.asm.Type.getMethodDescriptor((Method) member);
107 
108     } else if (member instanceof Constructor) {
109       StringBuilder sb = new StringBuilder().append("<init>(");
110       for (Class param : ((Constructor) member).getParameterTypes()) {
111           sb.append(org.objectweb.asm.Type.getDescriptor(param));
112       }
113       return sb.append(")V").toString();
114 
115     } else {
116       throw new IllegalArgumentException(
117           "Unsupported implementation class for Member, " + member.getClass());
118     }
119     /*end[AOP]*/
120     /*if[NO_AOP]
121     return "<NO_MEMBER_KEY>";
122     end[NO_AOP]*/
123   }
124 
125   private class LineNumberReader extends ClassVisitor {
126 
127     private int line = -1;
128     private String pendingMethod;
129     private String name;
130 
LineNumberReader()131     LineNumberReader() {
132       super(Opcodes.ASM5);
133     }
134 
visit(int version, int access, String name, String signature, String superName, String[] interfaces)135     public void visit(int version, int access, String name, String signature,
136         String superName, String[] interfaces) {
137       this.name = name;
138     }
139 
visitMethod(int access, String name, String desc, String signature, String[] exceptions)140     public MethodVisitor visitMethod(int access, String name, String desc,
141         String signature, String[] exceptions) {
142       if ((access & Opcodes.ACC_PRIVATE) != 0) {
143         return null;
144       }
145       pendingMethod = name + desc;
146       line = -1;
147       return new LineNumberMethodVisitor();
148     }
149 
visitSource(String source, String debug)150     public void visitSource(String source, String debug) {
151       LineNumbers.this.source = source;
152     }
153 
visitLineNumber(int line, Label start)154     public void visitLineNumber(int line, Label start) {
155       if (line < firstLine) {
156         firstLine = line;
157       }
158 
159       this.line = line;
160       if (pendingMethod != null) {
161         lines.put(pendingMethod, line);
162         pendingMethod = null;
163       }
164     }
165 
visitField(int access, String name, String desc, String signature, Object value)166     public FieldVisitor visitField(int access, String name, String desc,
167         String signature, Object value) {
168       return null;
169     }
170 
visitAnnotation(String desc, boolean visible)171     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
172       return new LineNumberAnnotationVisitor();
173     }
174 
visitParameterAnnotation(int parameter, String desc, boolean visible)175     public AnnotationVisitor visitParameterAnnotation(int parameter,
176         String desc, boolean visible) {
177       return new LineNumberAnnotationVisitor();
178     }
179 
180     class LineNumberMethodVisitor extends MethodVisitor {
LineNumberMethodVisitor()181       LineNumberMethodVisitor() {
182         super(Opcodes.ASM5);
183       }
184 
visitAnnotation(String desc, boolean visible)185       public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
186         return new LineNumberAnnotationVisitor();
187       }
188 
visitAnnotationDefault()189       public AnnotationVisitor visitAnnotationDefault() {
190         return new LineNumberAnnotationVisitor();
191       }
192 
visitFieldInsn(int opcode, String owner, String name, String desc)193       public void visitFieldInsn(int opcode, String owner, String name,
194           String desc) {
195         if (opcode == Opcodes.PUTFIELD && LineNumberReader.this.name.equals(owner)
196             && !lines.containsKey(name) && line != -1) {
197           lines.put(name, line);
198         }
199       }
200 
visitLineNumber(int line, Label start)201       public void visitLineNumber(int line, Label start) {
202         LineNumberReader.this.visitLineNumber(line, start);
203       }
204     }
205 
206     class LineNumberAnnotationVisitor extends AnnotationVisitor {
LineNumberAnnotationVisitor()207       LineNumberAnnotationVisitor() {
208         super(Opcodes.ASM5);
209       }
visitAnnotation(String name, String desc)210       public AnnotationVisitor visitAnnotation(String name, String desc) {
211         return this;
212       }
visitArray(String name)213       public AnnotationVisitor visitArray(String name) {
214         return this;
215       }
visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index)216       public void visitLocalVariable(String name, String desc, String signature,
217           Label start, Label end, int index) {
218       }
219 
220     }
221 
222   }
223 }
224