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 object from 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       * @param nameIndex Index of name
61       *
62       * @param length Content length in bytes
63       *
64       * @param lineNumberTable Table of line/numbers pairs
65       *
66       * @param constantPool Array of constants
67       */
68      public LineNumberTable(final int nameIndex, final int length, final LineNumber[] lineNumberTable, final ConstantPool constantPool) {
69          super(Const.ATTR_LINE_NUMBER_TABLE, nameIndex, length, constantPool);
70          this.lineNumberTable = lineNumberTable != null ? lineNumberTable : LineNumber.EMPTY_ARRAY;
71          Args.requireU2(this.lineNumberTable.length, "lineNumberTable.length");
72      }
73  
74      /*
75       * Initialize from another object. Note that both objects use the same references (shallow copy). Use copy() for a
76       * physical copy.
77       */
78      public LineNumberTable(final LineNumberTable c) {
79          this(c.getNameIndex(), c.getLength(), c.getLineNumberTable(), c.getConstantPool());
80      }
81  
82      /**
83       * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
84       * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
85       *
86       * @param v Visitor object
87       */
88      @Override
89      public void accept(final Visitor v) {
90          v.visitLineNumberTable(this);
91      }
92  
93      /**
94       * @return deep copy of this attribute
95       */
96      @Override
97      public Attribute copy(final ConstantPool constantPool) {
98          // TODO could use the lower level constructor and thereby allow
99          // lineNumberTable to be made final
100         final LineNumberTable c = (LineNumberTable) clone();
101         c.lineNumberTable = new LineNumber[lineNumberTable.length];
102         Arrays.setAll(c.lineNumberTable, i -> lineNumberTable[i].copy());
103         c.setConstantPool(constantPool);
104         return c;
105     }
106 
107     /**
108      * Dump line number table attribute to file stream in binary format.
109      *
110      * @param file Output file stream
111      * @throws IOException if an I/O Exception occurs in writeShort
112      */
113     @Override
114     public void dump(final DataOutputStream file) throws IOException {
115         super.dump(file);
116         file.writeShort(lineNumberTable.length);
117         for (final LineNumber lineNumber : lineNumberTable) {
118             lineNumber.dump(file);
119         }
120     }
121 
122     /**
123      * @return Array of (pc offset, line number) pairs.
124      */
125     public LineNumber[] getLineNumberTable() {
126         return lineNumberTable;
127     }
128 
129     /**
130      * Map byte code positions to source code lines.
131      *
132      * @param pos byte code offset
133      * @return corresponding line in source code
134      */
135     public int getSourceLine(final int pos) {
136         int l = 0;
137         int r = lineNumberTable.length - 1;
138         if (r < 0) {
139             return -1;
140         }
141         int minIndex = -1;
142         int min = -1;
143         /*
144          * Do a binary search since the array is ordered.
145          */
146         do {
147             final int i = l + r >>> 1;
148             final int j = lineNumberTable[i].getStartPC();
149             if (j == pos) {
150                 return lineNumberTable[i].getLineNumber();
151             }
152             if (pos < j) {
153                 r = i - 1;
154             } else {
155                 l = i + 1;
156             }
157             /*
158              * If exact match can't be found (which is the most common case) return the line number that corresponds to the greatest
159              * index less than pos.
160              */
161             if (j < pos && j > min) {
162                 min = j;
163                 minIndex = i;
164             }
165         } while (l <= r);
166         /*
167          * It's possible that we did not find any valid entry for the bytecode offset we were looking for.
168          */
169         if (minIndex < 0) {
170             return -1;
171         }
172         return lineNumberTable[minIndex].getLineNumber();
173     }
174 
175     public int getTableLength() {
176         return lineNumberTable == null ? 0 : lineNumberTable.length;
177     }
178 
179     @Override
180     public Iterator<LineNumber> iterator() {
181         return Stream.of(lineNumberTable).iterator();
182     }
183 
184     /**
185      * @param lineNumberTable the line number entries for this table
186      */
187     public void setLineNumberTable(final LineNumber[] lineNumberTable) {
188         this.lineNumberTable = lineNumberTable;
189     }
190 
191     /**
192      * @return String representation.
193      */
194     @Override
195     public String toString() {
196         final StringBuilder buf = new StringBuilder();
197         final StringBuilder line = new StringBuilder();
198         final String newLine = System.getProperty("line.separator", "\n");
199         for (int i = 0; i < lineNumberTable.length; i++) {
200             line.append(lineNumberTable[i].toString());
201             if (i < lineNumberTable.length - 1) {
202                 line.append(", ");
203             }
204             if (line.length() > MAX_LINE_LENGTH && i < lineNumberTable.length - 1) {
205                 line.append(newLine);
206                 buf.append(line);
207                 line.setLength(0);
208             }
209         }
210         buf.append(line);
211         return buf.toString();
212     }
213 }