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}