Home | History | Annotate | Download | only in util
      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    */
     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    */
     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    */
     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. */
     94   public int getFirstLine() {
     95     return firstLine == Integer.MAX_VALUE ? 1 : firstLine;
     96   }
     97 
     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 
    131     LineNumberReader() {
    132       super(Opcodes.ASM5);
    133     }
    134 
    135     public void visit(int version, int access, String name, String signature,
    136         String superName, String[] interfaces) {
    137       this.name = name;
    138     }
    139 
    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 
    150     public void visitSource(String source, String debug) {
    151       LineNumbers.this.source = source;
    152     }
    153 
    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 
    166     public FieldVisitor visitField(int access, String name, String desc,
    167         String signature, Object value) {
    168       return null;
    169     }
    170 
    171     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
    172       return new LineNumberAnnotationVisitor();
    173     }
    174 
    175     public AnnotationVisitor visitParameterAnnotation(int parameter,
    176         String desc, boolean visible) {
    177       return new LineNumberAnnotationVisitor();
    178     }
    179 
    180     class LineNumberMethodVisitor extends MethodVisitor {
    181       LineNumberMethodVisitor() {
    182         super(Opcodes.ASM5);
    183       }
    184 
    185       public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
    186         return new LineNumberAnnotationVisitor();
    187       }
    188 
    189       public AnnotationVisitor visitAnnotationDefault() {
    190         return new LineNumberAnnotationVisitor();
    191       }
    192 
    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 
    201       public void visitLineNumber(int line, Label start) {
    202         LineNumberReader.this.visitLineNumber(line, start);
    203       }
    204     }
    205 
    206     class LineNumberAnnotationVisitor extends AnnotationVisitor {
    207       LineNumberAnnotationVisitor() {
    208         super(Opcodes.ASM5);
    209       }
    210       public AnnotationVisitor visitAnnotation(String name, String desc) {
    211         return this;
    212       }
    213       public AnnotationVisitor visitArray(String name) {
    214         return this;
    215       }
    216       public void visitLocalVariable(String name, String desc, String signature,
    217           Label start, Label end, int index) {
    218       }
    219 
    220     }
    221 
    222   }
    223 }
    224