LineNumberTable.java

  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. import java.io.DataInput;
  19. import java.io.DataOutputStream;
  20. import java.io.IOException;
  21. import java.util.Arrays;
  22. import java.util.Iterator;
  23. import java.util.stream.Stream;

  24. import org.apache.bcel.Const;
  25. import org.apache.bcel.util.Args;

  26. /**
  27.  * This class represents a table of line numbers for debugging purposes. This attribute is used by the <em>Code</em>
  28.  * attribute. It contains pairs of PCs and line numbers.
  29.  *
  30.  * @see Code
  31.  * @see LineNumber
  32.  */
  33. public final class LineNumberTable extends Attribute implements Iterable<LineNumber> {

  34.     private static final int MAX_LINE_LENGTH = 72;
  35.     private LineNumber[] lineNumberTable; // Table of line/numbers pairs

  36.     /**
  37.      * Constructs a new instance from a data input stream.
  38.      *
  39.      * @param nameIndex Index of name
  40.      * @param length Content length in bytes
  41.      * @param input Input stream
  42.      * @param constantPool Array of constants
  43.      * @throws IOException if an I/O Exception occurs in readUnsignedShort
  44.      */
  45.     LineNumberTable(final int nameIndex, final int length, final DataInput input, final ConstantPool constantPool) throws IOException {
  46.         this(nameIndex, length, (LineNumber[]) null, constantPool);
  47.         final int lineNumberTableLength = input.readUnsignedShort();
  48.         lineNumberTable = new LineNumber[lineNumberTableLength];
  49.         for (int i = 0; i < lineNumberTableLength; i++) {
  50.             lineNumberTable[i] = new LineNumber(input);
  51.         }
  52.     }

  53.     /**
  54.      * Constructs a new instance.
  55.      *
  56.      * @param nameIndex Index of name
  57.      * @param length Content length in bytes
  58.      * @param lineNumberTable Table of line/numbers pairs
  59.      * @param constantPool Array of constants
  60.      */
  61.     public LineNumberTable(final int nameIndex, final int length, final LineNumber[] lineNumberTable, final ConstantPool constantPool) {
  62.         super(Const.ATTR_LINE_NUMBER_TABLE, nameIndex, length, constantPool);
  63.         this.lineNumberTable = lineNumberTable != null ? lineNumberTable : LineNumber.EMPTY_ARRAY;
  64.         Args.requireU2(this.lineNumberTable.length, "lineNumberTable.length");
  65.     }

  66.     /**
  67.      * Constructs a new instance from another.
  68.      * <p>
  69.      * Note that both objects use the same references (shallow copy). Use copy() for a physical copy.
  70.      * </p>
  71.      */
  72.     public LineNumberTable(final LineNumberTable c) {
  73.         this(c.getNameIndex(), c.getLength(), c.getLineNumberTable(), c.getConstantPool());
  74.     }

  75.     /**
  76.      * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
  77.      * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
  78.      *
  79.      * @param v Visitor object
  80.      */
  81.     @Override
  82.     public void accept(final Visitor v) {
  83.         v.visitLineNumberTable(this);
  84.     }

  85.     /**
  86.      * @return deep copy of this attribute
  87.      */
  88.     @Override
  89.     public Attribute copy(final ConstantPool constantPool) {
  90.         // TODO could use the lower level constructor and thereby allow
  91.         // lineNumberTable to be made final
  92.         final LineNumberTable c = (LineNumberTable) clone();
  93.         c.lineNumberTable = new LineNumber[lineNumberTable.length];
  94.         Arrays.setAll(c.lineNumberTable, i -> lineNumberTable[i].copy());
  95.         c.setConstantPool(constantPool);
  96.         return c;
  97.     }

  98.     /**
  99.      * Dump line number table attribute to file stream in binary format.
  100.      *
  101.      * @param file Output file stream
  102.      * @throws IOException if an I/O Exception occurs in writeShort
  103.      */
  104.     @Override
  105.     public void dump(final DataOutputStream file) throws IOException {
  106.         super.dump(file);
  107.         file.writeShort(lineNumberTable.length);
  108.         for (final LineNumber lineNumber : lineNumberTable) {
  109.             lineNumber.dump(file);
  110.         }
  111.     }

  112.     /**
  113.      * @return Array of (pc offset, line number) pairs.
  114.      */
  115.     public LineNumber[] getLineNumberTable() {
  116.         return lineNumberTable;
  117.     }

  118.     /**
  119.      * Map byte code positions to source code lines.
  120.      *
  121.      * @param pos byte code offset
  122.      * @return corresponding line in source code
  123.      */
  124.     public int getSourceLine(final int pos) {
  125.         int l = 0;
  126.         int r = lineNumberTable.length - 1;
  127.         if (r < 0) {
  128.             return -1;
  129.         }
  130.         int minIndex = -1;
  131.         int min = -1;
  132.         /*
  133.          * Do a binary search since the array is ordered.
  134.          */
  135.         do {
  136.             final int i = l + r >>> 1;
  137.             final int j = lineNumberTable[i].getStartPC();
  138.             if (j == pos) {
  139.                 return lineNumberTable[i].getLineNumber();
  140.             }
  141.             if (pos < j) {
  142.                 r = i - 1;
  143.             } else {
  144.                 l = i + 1;
  145.             }
  146.             /*
  147.              * If exact match can't be found (which is the most common case) return the line number that corresponds to the greatest
  148.              * index less than pos.
  149.              */
  150.             if (j < pos && j > min) {
  151.                 min = j;
  152.                 minIndex = i;
  153.             }
  154.         } while (l <= r);
  155.         /*
  156.          * It's possible that we did not find any valid entry for the bytecode offset we were looking for.
  157.          */
  158.         if (minIndex < 0) {
  159.             return -1;
  160.         }
  161.         return lineNumberTable[minIndex].getLineNumber();
  162.     }

  163.     public int getTableLength() {
  164.         return lineNumberTable.length;
  165.     }

  166.     @Override
  167.     public Iterator<LineNumber> iterator() {
  168.         return Stream.of(lineNumberTable).iterator();
  169.     }

  170.     /**
  171.      * @param lineNumberTable the line number entries for this table
  172.      */
  173.     public void setLineNumberTable(final LineNumber[] lineNumberTable) {
  174.         this.lineNumberTable = lineNumberTable != null ? lineNumberTable : LineNumber.EMPTY_ARRAY;
  175.     }

  176.     /**
  177.      * @return String representation.
  178.      */
  179.     @Override
  180.     public String toString() {
  181.         final StringBuilder buf = new StringBuilder();
  182.         final StringBuilder line = new StringBuilder();
  183.         final String newLine = System.getProperty("line.separator", "\n");
  184.         for (int i = 0; i < lineNumberTable.length; i++) {
  185.             line.append(lineNumberTable[i].toString());
  186.             if (i < lineNumberTable.length - 1) {
  187.                 line.append(", ");
  188.             }
  189.             if (line.length() > MAX_LINE_LENGTH && i < lineNumberTable.length - 1) {
  190.                 line.append(newLine);
  191.                 buf.append(line);
  192.                 line.setLength(0);
  193.             }
  194.         }
  195.         buf.append(line);
  196.         return buf.toString();
  197.     }
  198. }