View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.bcel.classfile;
18  
19  import java.io.DataInput;
20  import java.io.DataOutputStream;
21  import java.io.IOException;
22  import java.util.Arrays;
23  import java.util.Iterator;
24  import java.util.stream.Stream;
25  
26  import org.apache.bcel.Const;
27  import org.apache.bcel.util.Args;
28  
29  /**
30   * This class represents a table of line numbers for debugging purposes. This attribute is used by the <em>Code</em>
31   * attribute. It contains pairs of PCs and line numbers.
32   *
33   * @see Code
34   * @see LineNumber
35   */
36  public final class LineNumberTable extends Attribute implements Iterable<LineNumber> {
37  
38      private static final int MAX_LINE_LENGTH = 72;
39      private LineNumber[] lineNumberTable; // Table of line/numbers pairs
40  
41      /**
42       * Constructs a new instance from a data input stream.
43       *
44       * @param nameIndex Index of name
45       * @param length Content length in bytes
46       * @param input Input stream
47       * @param constantPool Array of constants
48       * @throws IOException if an I/O Exception occurs in readUnsignedShort
49       */
50      LineNumberTable(final int nameIndex, final int length, final DataInput input, final ConstantPool constantPool) throws IOException {
51          this(nameIndex, length, (LineNumber[]) null, constantPool);
52          final int lineNumberTableLength = input.readUnsignedShort();
53          lineNumberTable = new LineNumber[lineNumberTableLength];
54          for (int i = 0; i < lineNumberTableLength; i++) {
55              lineNumberTable[i] = new LineNumber(input);
56          }
57      }
58  
59      /**
60       * Constructs a new instance.
61       *
62       * @param nameIndex Index of name
63       * @param length Content length in bytes
64       * @param lineNumberTable Table of line/numbers pairs
65       * @param constantPool Array of constants
66       */
67      public LineNumberTable(final int nameIndex, final int length, final LineNumber[] lineNumberTable, final ConstantPool constantPool) {
68          super(Const.ATTR_LINE_NUMBER_TABLE, nameIndex, length, constantPool);
69          this.lineNumberTable = lineNumberTable != null ? lineNumberTable : LineNumber.EMPTY_ARRAY;
70          Args.requireU2(this.lineNumberTable.length, "lineNumberTable.length");
71      }
72  
73      /**
74       * Constructs a new instance from another.
75       * <p>
76       * Note that both objects use the same references (shallow copy). Use copy() for a physical copy.
77       * </p>
78       */
79      public LineNumberTable(final LineNumberTable c) {
80          this(c.getNameIndex(), c.getLength(), c.getLineNumberTable(), c.getConstantPool());
81      }
82  
83      /**
84       * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
85       * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
86       *
87       * @param v Visitor object
88       */
89      @Override
90      public void accept(final Visitor v) {
91          v.visitLineNumberTable(this);
92      }
93  
94      /**
95       * @return deep copy of this attribute
96       */
97      @Override
98      public Attribute copy(final ConstantPool constantPool) {
99          // TODO could use the lower level constructor and thereby allow
100         // lineNumberTable to be made final
101         final LineNumberTable c = (LineNumberTable) clone();
102         c.lineNumberTable = new LineNumber[lineNumberTable.length];
103         Arrays.setAll(c.lineNumberTable, i -> lineNumberTable[i].copy());
104         c.setConstantPool(constantPool);
105         return c;
106     }
107 
108     /**
109      * Dump line number table attribute to file stream in binary format.
110      *
111      * @param file Output file stream
112      * @throws IOException if an I/O Exception occurs in writeShort
113      */
114     @Override
115     public void dump(final DataOutputStream file) throws IOException {
116         super.dump(file);
117         file.writeShort(lineNumberTable.length);
118         for (final LineNumber lineNumber : lineNumberTable) {
119             lineNumber.dump(file);
120         }
121     }
122 
123     /**
124      * @return Array of (pc offset, line number) pairs.
125      */
126     public LineNumber[] getLineNumberTable() {
127         return lineNumberTable;
128     }
129 
130     /**
131      * Map byte code positions to source code lines.
132      *
133      * @param pos byte code offset
134      * @return corresponding line in source code
135      */
136     public int getSourceLine(final int pos) {
137         int l = 0;
138         int r = lineNumberTable.length - 1;
139         if (r < 0) {
140             return -1;
141         }
142         int minIndex = -1;
143         int min = -1;
144         /*
145          * Do a binary search since the array is ordered.
146          */
147         do {
148             final int i = l + r >>> 1;
149             final int j = lineNumberTable[i].getStartPC();
150             if (j == pos) {
151                 return lineNumberTable[i].getLineNumber();
152             }
153             if (pos < j) {
154                 r = i - 1;
155             } else {
156                 l = i + 1;
157             }
158             /*
159              * If exact match can't be found (which is the most common case) return the line number that corresponds to the greatest
160              * index less than pos.
161              */
162             if (j < pos && j > min) {
163                 min = j;
164                 minIndex = i;
165             }
166         } while (l <= r);
167         /*
168          * It's possible that we did not find any valid entry for the bytecode offset we were looking for.
169          */
170         if (minIndex < 0) {
171             return -1;
172         }
173         return lineNumberTable[minIndex].getLineNumber();
174     }
175 
176     public int getTableLength() {
177         return lineNumberTable.length;
178     }
179 
180     @Override
181     public Iterator<LineNumber> iterator() {
182         return Stream.of(lineNumberTable).iterator();
183     }
184 
185     /**
186      * @param lineNumberTable the line number entries for this table
187      */
188     public void setLineNumberTable(final LineNumber[] lineNumberTable) {
189         this.lineNumberTable = lineNumberTable != null ? lineNumberTable : LineNumber.EMPTY_ARRAY;
190     }
191 
192     /**
193      * @return String representation.
194      */
195     @Override
196     public String toString() {
197         final StringBuilder buf = new StringBuilder();
198         final StringBuilder line = new StringBuilder();
199         final String newLine = System.getProperty("line.separator", "\n");
200         for (int i = 0; i < lineNumberTable.length; i++) {
201             line.append(lineNumberTable[i].toString());
202             if (i < lineNumberTable.length - 1) {
203                 line.append(", ");
204             }
205             if (line.length() > MAX_LINE_LENGTH && i < lineNumberTable.length - 1) {
206                 line.append(newLine);
207                 buf.append(line);
208                 line.setLength(0);
209             }
210         }
211         buf.append(line);
212         return buf.toString();
213     }
214 }