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 Thrown 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     * @param c The instance to copy.
083     */
084    public LineNumberTable(final LineNumberTable c) {
085        this(c.getNameIndex(), c.getLength(), c.getLineNumberTable(), c.getConstantPool());
086    }
087
088    /**
089     * Called by objects that are traversing the nodes of the tree implicitly defined by the contents of a Java class.
090     * I.e., the hierarchy of methods, fields, attributes, etc. spawns a tree of objects.
091     *
092     * @param v Visitor object.
093     */
094    @Override
095    public void accept(final Visitor v) {
096        v.visitLineNumberTable(this);
097    }
098
099    /**
100     * @return deep copy of this attribute.
101     */
102    @Override
103    public Attribute copy(final ConstantPool constantPool) {
104        // TODO could use the lower level constructor and thereby allow
105        // lineNumberTable to be made final
106        final LineNumberTable c = (LineNumberTable) clone();
107        c.lineNumberTable = new LineNumber[lineNumberTable.length];
108        Arrays.setAll(c.lineNumberTable, i -> lineNumberTable[i].copy());
109        c.setConstantPool(constantPool);
110        return c;
111    }
112
113    /**
114     * Dumps line number table attribute to file stream in binary format.
115     *
116     * @param file Output file stream.
117     * @throws IOException Thrown if an I/O Exception occurs in writeShort.
118     */
119    @Override
120    public void dump(final DataOutputStream file) throws IOException {
121        super.dump(file);
122        file.writeShort(Args.requireU2(lineNumberTable.length, "lineNumberTable.length"));
123        for (final LineNumber lineNumber : lineNumberTable) {
124            lineNumber.dump(file);
125        }
126    }
127
128    /**
129     * Gets the line number table.
130     *
131     * @return Array of (pc offset, line number) pairs.
132     */
133    public LineNumber[] getLineNumberTable() {
134        return lineNumberTable;
135    }
136
137    /**
138     * Map byte code positions to source code lines.
139     *
140     * @param pos byte code offset.
141     * @return corresponding line in source code.
142     */
143    public int getSourceLine(final int pos) {
144        int l = 0;
145        int r = lineNumberTable.length - 1;
146        if (r < 0) {
147            return -1;
148        }
149        int minIndex = -1;
150        int min = -1;
151        /*
152         * Do a binary search since the array is ordered.
153         */
154        do {
155            final int i = l + r >>> 1;
156            final int j = lineNumberTable[i].getStartPC();
157            if (j == pos) {
158                return lineNumberTable[i].getLineNumber();
159            }
160            if (pos < j) {
161                r = i - 1;
162            } else {
163                l = i + 1;
164            }
165            /*
166             * If exact match can't be found (which is the most common case) return the line number that corresponds to the greatest
167             * index less than pos.
168             */
169            if (j < pos && j > min) {
170                min = j;
171                minIndex = i;
172            }
173        } while (l <= r);
174        /*
175         * It's possible that we did not find any valid entry for the bytecode offset we were looking for.
176         */
177        if (minIndex < 0) {
178            return -1;
179        }
180        return lineNumberTable[minIndex].getLineNumber();
181    }
182
183    /**
184     * Gets the length of the line number table.
185     *
186     * @return The length of the line number table.
187     */
188    public int getTableLength() {
189        return lineNumberTable.length;
190    }
191
192    @Override
193    public Iterator<LineNumber> iterator() {
194        return Stream.of(lineNumberTable).iterator();
195    }
196
197    /**
198     * Sets the line number table.
199     *
200     * @param lineNumberTable The line number entries for this table.
201     */
202    public void setLineNumberTable(final LineNumber[] lineNumberTable) {
203        this.lineNumberTable = lineNumberTable != null ? lineNumberTable : LineNumber.EMPTY_ARRAY;
204    }
205
206    /**
207     * @return String representation.
208     */
209    @Override
210    public String toString() {
211        final StringBuilder buf = new StringBuilder();
212        final StringBuilder line = new StringBuilder();
213        final String newLine = SystemProperties.getLineSeparator(() -> "\n");
214        for (int i = 0; i < lineNumberTable.length; i++) {
215            line.append(lineNumberTable[i].toString());
216            if (i < lineNumberTable.length - 1) {
217                line.append(", ");
218            }
219            if (line.length() > MAX_LINE_LENGTH && i < lineNumberTable.length - 1) {
220                line.append(newLine);
221                buf.append(line);
222                line.setLength(0);
223            }
224        }
225        buf.append(line);
226        return buf.toString();
227    }
228}