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