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 object from 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     * @param nameIndex Index of name
061     *
062     * @param length Content length in bytes
063     *
064     * @param lineNumberTable Table of line/numbers pairs
065     *
066     * @param constantPool Array of constants
067     */
068    public LineNumberTable(final int nameIndex, final int length, final LineNumber[] lineNumberTable, final ConstantPool constantPool) {
069        super(Const.ATTR_LINE_NUMBER_TABLE, nameIndex, length, constantPool);
070        this.lineNumberTable = lineNumberTable != null ? lineNumberTable : LineNumber.EMPTY_ARRAY;
071        Args.requireU2(this.lineNumberTable.length, "lineNumberTable.length");
072    }
073
074    /*
075     * Initialize from another object. Note that both objects use the same references (shallow copy). Use copy() for a
076     * physical copy.
077     */
078    public LineNumberTable(final LineNumberTable c) {
079        this(c.getNameIndex(), c.getLength(), c.getLineNumberTable(), c.getConstantPool());
080    }
081
082    /**
083     * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
084     * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
085     *
086     * @param v Visitor object
087     */
088    @Override
089    public void accept(final Visitor v) {
090        v.visitLineNumberTable(this);
091    }
092
093    /**
094     * @return deep copy of this attribute
095     */
096    @Override
097    public Attribute copy(final ConstantPool constantPool) {
098        // TODO could use the lower level constructor and thereby allow
099        // 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}