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 *
017 */
018package org.apache.bcel.classfile;
019
020import java.io.DataInput;
021import java.io.DataOutputStream;
022import java.io.IOException;
023import java.util.Arrays;
024import java.util.Iterator;
025import java.util.stream.Stream;
026
027import org.apache.bcel.Const;
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     * Construct object from input stream.
043     *
044     * @param name_index Index of name
045     * @param length Content length in bytes
046     * @param input Input stream
047     * @param constant_pool Array of constants
048     * @throws IOException if an I/O Exception occurs in readUnsignedShort
049     */
050    LineNumberTable(final int name_index, final int length, final DataInput input, final ConstantPool constant_pool) throws IOException {
051        this(name_index, length, (LineNumber[]) null, constant_pool);
052        final int line_number_table_length = input.readUnsignedShort();
053        lineNumberTable = new LineNumber[line_number_table_length];
054        for (int i = 0; i < line_number_table_length; i++) {
055            lineNumberTable[i] = new LineNumber(input);
056        }
057    }
058
059    /*
060     * @param name_index Index of name
061     *
062     * @param length Content length in bytes
063     *
064     * @param lineNumberTable Table of line/numbers pairs
065     *
066     * @param constant_pool Array of constants
067     */
068    public LineNumberTable(final int name_index, final int length, final LineNumber[] line_number_table, final ConstantPool constant_pool) {
069        super(Const.ATTR_LINE_NUMBER_TABLE, name_index, length, constant_pool);
070        this.lineNumberTable = line_number_table;
071    }
072
073    /*
074     * Initialize from another object. Note that both objects use the same references (shallow copy). Use copy() for a
075     * physical copy.
076     */
077    public LineNumberTable(final LineNumberTable c) {
078        this(c.getNameIndex(), c.getLength(), c.getLineNumberTable(), c.getConstantPool());
079    }
080
081    /**
082     * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
083     * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
084     *
085     * @param v Visitor object
086     */
087    @Override
088    public void accept(final Visitor v) {
089        v.visitLineNumberTable(this);
090    }
091
092    /**
093     * @return deep copy of this attribute
094     */
095    @Override
096    public Attribute copy(final ConstantPool constantPool) {
097        // TODO could use the lower level constructor and thereby allow
098        // lineNumberTable to be made final
099        final LineNumberTable c = (LineNumberTable) clone();
100        c.lineNumberTable = new LineNumber[lineNumberTable.length];
101        Arrays.setAll(c.lineNumberTable, i -> lineNumberTable[i].copy());
102        c.setConstantPool(constantPool);
103        return c;
104    }
105
106    /**
107     * Dump line number table attribute to file stream in binary format.
108     *
109     * @param file Output file stream
110     * @throws IOException if an I/O Exception occurs in writeShort
111     */
112    @Override
113    public void dump(final DataOutputStream file) throws IOException {
114        super.dump(file);
115        file.writeShort(lineNumberTable.length);
116        for (final LineNumber lineNumber : lineNumberTable) {
117            lineNumber.dump(file);
118        }
119    }
120
121    /**
122     * @return Array of (pc offset, line number) pairs.
123     */
124    public LineNumber[] getLineNumberTable() {
125        return lineNumberTable;
126    }
127
128    /**
129     * Map byte code positions to source code lines.
130     *
131     * @param pos byte code offset
132     * @return corresponding line in source code
133     */
134    public int getSourceLine(final int pos) {
135        int l = 0;
136        int r = lineNumberTable.length - 1;
137        if (r < 0) {
138            return -1;
139        }
140        int min_index = -1;
141        int min = -1;
142        /*
143         * Do a binary search since the array is ordered.
144         */
145        do {
146            final int i = l + r >>> 1;
147            final int j = lineNumberTable[i].getStartPC();
148            if (j == pos) {
149                return lineNumberTable[i].getLineNumber();
150            }
151            if (pos < j) {
152                r = i - 1;
153            } else {
154                l = i + 1;
155            }
156            /*
157             * If exact match can't be found (which is the most common case) return the line number that corresponds to the greatest
158             * index less than pos.
159             */
160            if (j < pos && j > min) {
161                min = j;
162                min_index = i;
163            }
164        } while (l <= r);
165        /*
166         * It's possible that we did not find any valid entry for the bytecode offset we were looking for.
167         */
168        if (min_index < 0) {
169            return -1;
170        }
171        return lineNumberTable[min_index].getLineNumber();
172    }
173
174    public int getTableLength() {
175        return lineNumberTable == null ? 0 : lineNumberTable.length;
176    }
177
178    @Override
179    public Iterator<LineNumber> iterator() {
180        return Stream.of(lineNumberTable).iterator();
181    }
182
183    /**
184     * @param lineNumberTable the line number entries for this table
185     */
186    public void setLineNumberTable(final LineNumber[] lineNumberTable) {
187        this.lineNumberTable = lineNumberTable;
188    }
189
190    /**
191     * @return String representation.
192     */
193    @Override
194    public String toString() {
195        final StringBuilder buf = new StringBuilder();
196        final StringBuilder line = new StringBuilder();
197        final String newLine = System.getProperty("line.separator", "\n");
198        for (int i = 0; i < lineNumberTable.length; i++) {
199            line.append(lineNumberTable[i].toString());
200            if (i < lineNumberTable.length - 1) {
201                line.append(", ");
202            }
203            if (line.length() > MAX_LINE_LENGTH && i < lineNumberTable.length - 1) {
204                line.append(newLine);
205                buf.append(line);
206                line.setLength(0);
207            }
208        }
209        buf.append(line);
210        return buf.toString();
211    }
212}