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 import java.io.IOException;
     24 import java.io.InputStream;
     25 import java.lang.reflect.Constructor;
     26 import java.lang.reflect.Field;
     27 import java.lang.reflect.Member;
     28 import java.lang.reflect.Method;
     29 import java.util.Map;
     30 import org.objectweb.asm.AnnotationVisitor;
     31 import org.objectweb.asm.ClassReader;
     32 import org.objectweb.asm.ClassVisitor;
     33 import org.objectweb.asm.FieldVisitor;
     34 import org.objectweb.asm.Label;
     35 import org.objectweb.asm.MethodVisitor;
     36 import org.objectweb.asm.Opcodes;
     37 
     38 /**
     39  * Looks up line numbers for classes and their members.
     40  *
     41  * @author Chris Nokleberg
     42  */
     43 final class LineNumbers {
     44 
     45   private final Class type;
     46   private final Map<String, Integer> lines = Maps.newHashMap();
     47   private String source;
     48   private int firstLine = Integer.MAX_VALUE;
     49 
     50   /**
     51    * Reads line number information from the given class, if available.
     52    *
     53    * @param type the class to read line number information from
     54    * @throws IllegalArgumentException if the bytecode for the class cannot be found
     55    * @throws java.io.IOException if an error occurs while reading bytecode
     56    */
     57   public LineNumbers(Class type) throws IOException {
     58     this.type = type;
     59 
     60     if (!type.isArray()) {
     61       InputStream in = type.getResourceAsStream("/" + type.getName().replace('.', '/') + ".class");
     62       if (in != null) {
     63         try {
     64           new ClassReader(in).accept(new LineNumberReader(), ClassReader.SKIP_FRAMES);
     65         } finally {
     66           try {
     67             in.close();
     68           } catch (IOException ignored) {
     69           }
     70         }
     71       }
     72     }
     73   }
     74 
     75   /**
     76    * Get the source file name as read from the bytecode.
     77    *
     78    * @return the source file name if available, or null
     79    */
     80   public String getSource() {
     81     return source;
     82   }
     83 
     84   /**
     85    * Get the line number associated with the given member.
     86    *
     87    * @param member a field, constructor, or method belonging to the class used during construction
     88    * @return the wrapped line number, or null if not available
     89    * @throws IllegalArgumentException if the member does not belong to the class used during
     90    *     construction
     91    */
     92   public Integer getLineNumber(Member member) {
     93     Preconditions.checkArgument(
     94         type == member.getDeclaringClass(),
     95         "Member %s belongs to %s, not %s",
     96         member,
     97         member.getDeclaringClass(),
     98         type);
     99     return lines.get(memberKey(member));
    100   }
    101 
    102   /** Gets the first line number. */
    103   public int getFirstLine() {
    104     return firstLine == Integer.MAX_VALUE ? 1 : firstLine;
    105   }
    106 
    107   private String memberKey(Member member) {
    108     checkNotNull(member, "member");
    109 
    110     /*if[AOP]*/
    111     if (member instanceof Field) {
    112       return member.getName();
    113 
    114     } else if (member instanceof Method) {
    115       return member.getName() + org.objectweb.asm.Type.getMethodDescriptor((Method) member);
    116 
    117     } else if (member instanceof Constructor) {
    118       StringBuilder sb = new StringBuilder().append("<init>(");
    119       for (Class param : ((Constructor) member).getParameterTypes()) {
    120         sb.append(org.objectweb.asm.Type.getDescriptor(param));
    121       }
    122       return sb.append(")V").toString();
    123 
    124     } else {
    125       throw new IllegalArgumentException(
    126           "Unsupported implementation class for Member, " + member.getClass());
    127     }
    128     /*end[AOP]*/
    129     /*if[NO_AOP]
    130     return "<NO_MEMBER_KEY>";
    131     end[NO_AOP]*/
    132   }
    133 
    134   private class LineNumberReader extends ClassVisitor {
    135 
    136     private int line = -1;
    137     private String pendingMethod;
    138     private String name;
    139 
    140     LineNumberReader() {
    141       super(Opcodes.ASM6);
    142     }
    143 
    144     @Override
    145     public void visit(
    146         int version,
    147         int access,
    148         String name,
    149         String signature,
    150         String superName,
    151         String[] interfaces) {
    152       this.name = name;
    153     }
    154 
    155     @Override
    156     public MethodVisitor visitMethod(
    157         int access, String name, String desc, String signature, String[] exceptions) {
    158       if ((access & Opcodes.ACC_PRIVATE) != 0) {
    159         return null;
    160       }
    161       pendingMethod = name + desc;
    162       line = -1;
    163       return new LineNumberMethodVisitor();
    164     }
    165 
    166     @Override
    167     public void visitSource(String source, String debug) {
    168       LineNumbers.this.source = source;
    169     }
    170 
    171     public void visitLineNumber(int line, Label start) {
    172       if (line < firstLine) {
    173         firstLine = line;
    174       }
    175 
    176       this.line = line;
    177       if (pendingMethod != null) {
    178         lines.put(pendingMethod, line);
    179         pendingMethod = null;
    180       }
    181     }
    182 
    183     @Override
    184     public FieldVisitor visitField(
    185         int access, String name, String desc, String signature, Object value) {
    186       return null;
    187     }
    188 
    189     @Override
    190     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
    191       return new LineNumberAnnotationVisitor();
    192     }
    193 
    194     public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
    195       return new LineNumberAnnotationVisitor();
    196     }
    197 
    198     class LineNumberMethodVisitor extends MethodVisitor {
    199       LineNumberMethodVisitor() {
    200         super(Opcodes.ASM6);
    201       }
    202 
    203       @Override
    204       public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
    205         return new LineNumberAnnotationVisitor();
    206       }
    207 
    208       @Override
    209       public AnnotationVisitor visitAnnotationDefault() {
    210         return new LineNumberAnnotationVisitor();
    211       }
    212 
    213       @Override
    214       public void visitFieldInsn(int opcode, String owner, String name, String desc) {
    215         if (opcode == Opcodes.PUTFIELD
    216             && LineNumberReader.this.name.equals(owner)
    217             && !lines.containsKey(name)
    218             && line != -1) {
    219           lines.put(name, line);
    220         }
    221       }
    222 
    223       @Override
    224       public void visitLineNumber(int line, Label start) {
    225         LineNumberReader.this.visitLineNumber(line, start);
    226       }
    227     }
    228 
    229     class LineNumberAnnotationVisitor extends AnnotationVisitor {
    230       LineNumberAnnotationVisitor() {
    231         super(Opcodes.ASM6);
    232       }
    233 
    234       @Override
    235       public AnnotationVisitor visitAnnotation(String name, String desc) {
    236         return this;
    237       }
    238 
    239       @Override
    240       public AnnotationVisitor visitArray(String name) {
    241         return this;
    242       }
    243 
    244       public void visitLocalVariable(
    245           String name, String desc, String signature, Label start, Label end, int index) {}
    246     }
    247   }
    248 }
    249