    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 *  Unless required by applicable law or agreed to in writing, software
    012 *  distributed under the License is distributed on an "AS IS" BASIS,
    013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 *  See the License for the specific language governing permissions and
    015 *  limitations under the License.
    016 */
    017package org.apache.bcel.classfile;
    018
    019import java.io.DataInput;
    020import java.io.DataOutputStream;
    021import java.io.IOException;
    022import java.util.Arrays;
    023import java.util.Iterator;
    024import java.util.stream.Stream;
    025
    026import org.apache.bcel.Const;
    027import org.apache.bcel.util.Args;
    028
    029/**
    030 * This class represents a table of line numbers for debugging purposes. This attribute is used by the <em>Code</em>
    031 * attribute. It contains pairs of PCs and line numbers.
    032 *
    033 * @see Code
    034 * @see LineNumber
    035 */
    036public final class LineNumberTable extends Attribute implements Iterable<LineNumber> {
    037
    038    private static final int MAX_LINE_LENGTH = 72;
    039    private LineNumber[] lineNumberTable; // Table of line/numbers pairs
    040
    041    /**
    042     * Constructs a new instance from a data input stream.
    043     *
    044     * @param nameIndex Index of name
    045     * @param length Content length in bytes
    046     * @param input Input stream
    047     * @param constantPool Array of constants
    048     * @throws IOException if an I/O Exception occurs in readUnsignedShort
    049     */
    050    LineNumberTable(final int nameIndex, final int length, final DataInput input, final ConstantPool constantPool) throws IOException {
    051        this(nameIndex, length, (LineNumber[]) null, constantPool);
    052        final int lineNumberTableLength = input.readUnsignedShort();
    053        lineNumberTable = new LineNumber[lineNumberTableLength];
    054        for (int i = 0; i < lineNumberTableLength; i++) {
    055            lineNumberTable[i] = new LineNumber(input);
    056        }
    057    }
    058
    059    /**
    060     * Constructs a new instance.
    061     *
    062     * @param nameIndex Index of name
    063     * @param length Content length in bytes
    064     * @param lineNumberTable Table of line/numbers pairs
    065     * @param constantPool Array of constants
    066     */
    067    public LineNumberTable(final int nameIndex, final int length, final LineNumber[] lineNumberTable, final ConstantPool constantPool) {
    068        super(Const.ATTR_LINE_NUMBER_TABLE, nameIndex, length, constantPool);
    069        this.lineNumberTable = lineNumberTable != null ? lineNumberTable : LineNumber.EMPTY_ARRAY;
    070        Args.requireU2(this.lineNumberTable.length, "lineNumberTable.length");
    071    }
    072
    073    /**
    074     * Constructs a new instance from another.
    075     * <p>
    076     * Note that both objects use the same references (shallow copy). Use copy() for a physical copy.
    077     * </p>
    078     */
    079    public LineNumberTable(final LineNumberTable c) {
    080        this(c.getNameIndex(), c.getLength(), c.getLineNumberTable(), c.getConstantPool());
    081    }
    082
    083    /**
    084     * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
    085     * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
    086     *
    087     * @param v Visitor object
    088     */
    089    @Override
    090    public void accept(final Visitor v) {
    091        v.visitLineNumberTable(this);
    092    }
    093
    094    /**
    095     * @return deep copy of this attribute
    096     */
    097    @Override
    098    public Attribute copy(final ConstantPool constantPool) {
    099        // 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}