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