/*
 * FindBugs - Find bugs in Java programs
 * Copyright (C) 2005 Dave Brosius <dbrosius@users.sourceforge.net>
 * Copyright (C) 2005 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.detect;


import java.util.HashSet;
import java.util.Set;

import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantDouble;
import org.apache.bcel.classfile.ConstantFloat;
import org.apache.bcel.classfile.ConstantLong;

import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.StatelessDetector;

/**
 * Find occurrences of Math using constants, where the result of the
 * calculation can be determined statically. Replacing the math formula
 * with the constant performs better, and sometimes is more accurate.
 *
 * @author Dave Brosius
 */
public class UnnecessaryMath extends BytecodeScanningDetector implements StatelessDetector {
	static final int SEEN_NOTHING = 0;
	static final int SEEN_DCONST = 1;

	private BugReporter bugReporter;
	private int state = SEEN_NOTHING;
	private double constValue;

	private static final Set<String> zeroMethods = new HashSet<String>() 
											{{ add("acos");
											   add("asin");
											   add("atan");
											   add("atan2");
											   add("cbrt");
											   add("cos");
											   add("cosh");
											   add("exp");
											   add("expm1");
											   add("log");
											   add("log10");
											   add("pow");
											   add("sin");
											   add("sinh");
											   add("sqrt");
											   add("tan");
											   add("tanh");
											   add("toDegrees");
											   add("toRadians");
											}};
	private static final Set<String> oneMethods = new HashSet<String>()
											{{ add("acos");
											   add("asin");
											   add("atan");
											   add("cbrt");
											   add("exp");
											   add("log");
											   add("log10");
											   add("pow");
											   add("sqrt");
											   add("toDegrees");
											}};
	private static final Set<String> anyMethods = new HashSet<String>()
											{{ add("abs");
											   add("ceil");
											   add("floor");
											   add("rint");
											   add("round");
											}};

	public UnnecessaryMath(BugReporter bugReporter) {
		this.bugReporter = bugReporter;
	}



	@Override
		 public void visit(Code obj) {
		// Don't complain about unnecessary math calls in class initializers,
		// since they may be there to improve readability.
		if (getMethod().getName().equals("<clinit>"))
			return;

		state = SEEN_NOTHING;
		super.visit(obj);
	}

	@Override
		 public void sawOpcode(int seen) {
		if (state == SEEN_NOTHING) {
			if ((seen == DCONST_0) || (seen == DCONST_1)) {
				constValue = seen - DCONST_0;
				state = SEEN_DCONST;
			}			
			else if ((seen == LDC2_W) || (seen == LDC_W)) {
				state = SEEN_DCONST;
				Constant c = this.getConstantRefOperand();
				if (c instanceof ConstantDouble)
					constValue = ((ConstantDouble)c).getBytes();
				else if (c instanceof ConstantFloat)
					constValue = ((ConstantFloat)c).getBytes();
				else if (c instanceof ConstantLong)
					constValue = ((ConstantLong)c).getBytes();
				else
					state = SEEN_NOTHING;
			}
		} else if (state == SEEN_DCONST) {
			if (seen == INVOKESTATIC) {
				state = SEEN_NOTHING;
				if (getDottedClassConstantOperand().equals("java.lang.Math")) {
					String methodName = getNameConstantOperand();

					if (((constValue == 0.0) && zeroMethods.contains(methodName))
					||  ((constValue == 1.0) && oneMethods.contains(methodName))
					||   (anyMethods.contains(methodName))) {
						bugReporter.reportBug(new BugInstance(this, "UM_UNNECESSARY_MATH", LOW_PRIORITY)
												.addClassAndMethod(this)
												.addSourceLine(this));				
					}
				}
			}
			state = SEEN_NOTHING;
		}
	}
}

// vim:ts=4
