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}