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