/*
 * Bytecode Analysis Framework
 * Copyright (C) 2003,2004 University of Maryland
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package edu.umd.cs.findbugs.ba;

import java.util.IdentityHashMap;

import org.apache.bcel.classfile.LineNumber;
import org.apache.bcel.classfile.LineNumberTable;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.MethodGen;

import edu.umd.cs.findbugs.SystemProperties;

/**
 * Summarize line numbers (and other source information)
 * for a method.
 */
public class LineNumberMap {
	/**
	 * Set this property to true to get debug print statements.
	 */
	private static final boolean DEBUG = SystemProperties.getBoolean("lnm.debug");

	/**
	 * When this is true, the workaround for the bug in BCEL 5.0's
	 * LineNumberTable class is disabled.
	 */
	private static final boolean LINE_NUMBER_BUG = SystemProperties.getBoolean("lineNumberBug");

	private MethodGen methodGen;
	private IdentityHashMap<InstructionHandle, LineNumber> lineNumberMap;
	private boolean hasLineNumbers;

	/**
	 * Constructor.
	 *
	 * @param methodGen the method to summarize line numbers for
	 */
	public LineNumberMap(MethodGen methodGen) {
		this.methodGen = methodGen;
		lineNumberMap = new IdentityHashMap<InstructionHandle, LineNumber>();
		hasLineNumbers = false;
	}

	/**
	 * Build the line number information.
	 * Should be called before any other methods.
	 */
	public void build() {
		int numGood = 0, numBytecodes = 0;

		if (DEBUG) {
			System.out.println("Method: " + methodGen.getName() + " - " + methodGen.getSignature() +
					"in class " + methodGen.getClassName());
		}

		// Associate line number information with each InstructionHandle
		LineNumberTable table = methodGen.getLineNumberTable(methodGen.getConstantPool());

		if (table != null && table.getTableLength() > 0) {
			checkTable(table);
			InstructionHandle handle = methodGen.getInstructionList().getStart();
			while (handle != null) {
				int bytecodeOffset = handle.getPosition();
				if (bytecodeOffset < 0)
					throw new IllegalStateException("Bad bytecode offset: " + bytecodeOffset);
				if (DEBUG) System.out.println("Looking for source line for bytecode offset " + bytecodeOffset);
				int sourceLine;
				try {
					sourceLine = table.getSourceLine(bytecodeOffset);
				} catch (ArrayIndexOutOfBoundsException e) {
					if (LINE_NUMBER_BUG)
						throw e;
					else
						sourceLine = -1;
				}
				if (sourceLine >= 0)
					++numGood;
				lineNumberMap.put(handle,
						new LineNumber(bytecodeOffset, sourceLine));
				handle = handle.getNext();
				++numBytecodes;
			}
			hasLineNumbers = true;

			if (DEBUG) System.out.println("\t" + numGood + "/" + numBytecodes + " had valid line numbers");
		}
	}

	private void checkTable(LineNumberTable table) {
		if (DEBUG) System.out.println("line number table has length " + table.getTableLength());
		LineNumber[] entries = table.getLineNumberTable();
		int lastBytecode = -1;
		for (int i = 0; i < entries.length; ++i) {
			LineNumber ln = entries[i];
			if (DEBUG) System.out.println("Entry " + i + ": pc=" + ln.getStartPC() + ", line=" + ln.getLineNumber());
			int pc = ln.getStartPC();
			if (pc <= lastBytecode) throw new IllegalStateException("LineNumberTable is not sorted");
		}
	}

	/**
	 * Does this method have line number information?
	 */
	public boolean hasLineNumbers() {
		return hasLineNumbers;
	}

	/**
	 * Find the line number information for instruction whose
	 * handle is given.
	 *
	 * @param handle the InstructionHandle
	 * @return the LineNumber object containing bytecode offset and source line number
	 */
	public LineNumber lookupLineNumber(InstructionHandle handle) {
		return lineNumberMap.get(handle);
	}
}
