001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.bcel.classfile;
020
021import java.io.DataInput;
022import java.io.DataOutputStream;
023import java.io.IOException;
024import java.util.Arrays;
025import java.util.Iterator;
026import java.util.stream.Stream;
027
028import org.apache.bcel.Const;
029import org.apache.bcel.util.Args;
030import org.apache.commons.lang3.SystemProperties;
031
032/**
033 * This class represents a table of line numbers for debugging purposes. This attribute is used by the <em>Code</em>
034 * attribute. It contains pairs of PCs and line numbers.
035 *
036 * @see Code
037 * @see LineNumber
038 */
039public final class LineNumberTable extends Attribute implements Iterable<LineNumber> {
040
041    private static final int MAX_LINE_LENGTH = 72;
042    private LineNumber[] lineNumberTable; // Table of line/numbers pairs
043
044    /**
045     * Constructs a new instance from a data input stream.
046     *
047     * @param nameIndex Index of name
048     * @param length Content length in bytes
049     * @param input Input stream
050     * @param constantPool Array of constants
051     * @throws IOException if an I/O Exception occurs in readUnsignedShort
052     */
053    LineNumberTable(final int nameIndex, final int length, final DataInput input, final ConstantPool constantPool) throws IOException {
054        this(nameIndex, length, (LineNumber[]) null, constantPool);
055        final int lineNumberTableLength = input.readUnsignedShort();
056        lineNumberTable = new LineNumber[lineNumberTableLength];
057        for (int i = 0; i < lineNumberTableLength; i++) {
058            lineNumberTable[i] = new LineNumber(input);
059        }
060    }
061
062    /**
063     * Constructs a new instance.
064     *
065     * @param nameIndex Index of name
066     * @param length Content length in bytes
067     * @param lineNumberTable Table of line/numbers pairs
068     * @param constantPool Array of constants
069     */
070    public LineNumberTable(final int nameIndex, final int length, final LineNumber[] lineNumberTable, final ConstantPool constantPool) {
071        super(Const.ATTR_LINE_NUMBER_TABLE, nameIndex, length, constantPool);
072        this.lineNumberTable = lineNumberTable != null ? lineNumberTable : LineNumber.EMPTY_ARRAY;
073        Args.requireU2(this.lineNumberTable.length, "lineNumberTable.length");
074    }
075
076    /**
077     * Constructs a new instance from another.
078     * <p>
079     * Note that both objects use the same references (shallow copy). Use copy() for a physical copy.
080     * </p>
081     */
082    public LineNumberTable(final LineNumberTable c) {
083        this(c.getNameIndex(), c.getLength(), c.getLineNumberTable(), c.getConstantPool());
084    }
085
086    /**
087     * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
088     * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
089     *
090     * @param v Visitor object
091     */
092    @Override
093    public void accept(final Visitor v) {
094        v.visitLineNumberTable(this);
095    }
096
097    /**
098     * @return deep copy of this attribute
099     */
100    @Override
101    public Attribute copy(final ConstantPool constantPool) {
102        // TODO could use the lower level constructor and thereby allow
103        // lineNumberTable to be made final
104        final LineNumberTable c = (LineNumberTable) clone();
105        c.lineNumberTable = new LineNumber[lineNumberTable.length];
106        Arrays.setAll(c.lineNumberTable, i -> lineNumberTable[i].copy());
107        c.setConstantPool(constantPool);
108        return c;
109    }
110
111    /**
112     * Dump line number table attribute to file stream in binary format.
113     *
114     * @param file Output file stream
115     * @throws IOException if an I/O Exception occurs in writeShort
116     */
117    @Override
118    public void dump(final DataOutputStream file) throws IOException {
119        super.dump(file);
120        file.writeShort(lineNumberTable.length);
121        for (final LineNumber lineNumber : lineNumberTable) {
122            lineNumber.dump(file);
123        }
124    }
125
126    /**
127     * @return Array of (pc offset, line number) pairs.
128     */
129    public LineNumber[] getLineNumberTable() {
130        return lineNumberTable;
131    }
132
133    /**
134     * Map byte code positions to source code lines.
135     *
136     * @param pos byte code offset
137     * @return corresponding line in source code
138     */
139    public int getSourceLine(final int pos) {
140        int l = 0;
141        int r = lineNumberTable.length - 1;
142        if (r < 0) {
143            return -1;
144        }
145        int minIndex = -1;
146        int min = -1;
147        /*
148         * Do a binary search since the array is ordered.
149         */
150        do {
151            final int i = l + r >>> 1;
152            final int j = lineNumberTable[i].getStartPC();
153            if (j == pos) {
154                return lineNumberTable[i].getLineNumber();
155            }
156            if (pos < j) {
157                r = i - 1;
158            } else {
159                l = i + 1;
160            }
161            /*
162             * If exact match can't be found (which is the most common case) return the line number that corresponds to the greatest
163             * index less than pos.
164             */
165            if (j < pos && j > min) {
166                min = j;
167                minIndex = i;
168            }
169        } while (l <= r);
170        /*
171         * It's possible that we did not find any valid entry for the bytecode offset we were looking for.
172         */
173        if (minIndex < 0) {
174            return -1;
175        }
176        return lineNumberTable[minIndex].getLineNumber();
177    }
178
179    public int getTableLength() {
180        return lineNumberTable.length;
181    }
182
183    @Override
184    public Iterator<LineNumber> iterator() {
185        return Stream.of(lineNumberTable).iterator();
186    }
187
188    /**
189     * @param lineNumberTable the line number entries for this table
190     */
191    public void setLineNumberTable(final LineNumber[] lineNumberTable) {
192        this.lineNumberTable = lineNumberTable != null ? lineNumberTable : LineNumber.EMPTY_ARRAY;
193    }
194
195    /**
196     * @return String representation.
197     */
198    @Override
199    public String toString() {
200        final StringBuilder buf = new StringBuilder();
201        final StringBuilder line = new StringBuilder();
202        final String newLine = SystemProperties.getLineSeparator(() -> "\n");
203        for (int i = 0; i < lineNumberTable.length; i++) {
204            line.append(lineNumberTable[i].toString());
205            if (i < lineNumberTable.length - 1) {
206                line.append(", ");
207            }
208            if (line.length() > MAX_LINE_LENGTH && i < lineNumberTable.length - 1) {
209                line.append(newLine);
210                buf.append(line);
211                line.setLength(0);
212            }
213        }
214        buf.append(line);
215        return buf.toString();
216    }
217}