/*
 * FindBugs - Find bugs in Java programs
 * Copyright (C) 2003-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 org.apache.bcel.classfile.Code;

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

public class WaitInLoop extends BytecodeScanningDetector implements StatelessDetector {

	boolean sawWait = false;
	boolean sawAwait = false;
	boolean waitHasTimeout = false;
	boolean sawNotify = false;
	int notifyPC;
	int earliestJump = 0;
	int waitAt = 0;
	private BugReporter bugReporter;

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

	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}

	public void visit(Code obj) {
		sawWait = false;
		sawAwait = false;
		waitHasTimeout = false;
		sawNotify = false;
		earliestJump = 9999999;
		super.visit(obj);
		if ((sawWait || sawAwait) && waitAt < earliestJump) {
			String bugType = sawWait ? "WA_NOT_IN_LOOP" : "WA_AWAIT_NOT_IN_LOOP";
			bugReporter.reportBug(new BugInstance(this, bugType, waitHasTimeout ? LOW_PRIORITY : NORMAL_PRIORITY)
			        .addClassAndMethod(this)
			        .addSourceLine(this, waitAt));
		}
		if (sawNotify)
			bugReporter.reportBug(new BugInstance(this, "NO_NOTIFY_NOT_NOTIFYALL", LOW_PRIORITY)
			        .addClassAndMethod(this)
			        .addSourceLine(this, notifyPC));
	}

	public void sawOpcode(int seen) {

		if ((seen == INVOKEVIRTUAL || seen == INVOKEINTERFACE)
		        && getNameConstantOperand().equals("notify")
		        && getSigConstantOperand().equals("()V")) {
			sawNotify = true;
			notifyPC = getPC();
		}
		if (!(sawWait || sawAwait)
				&& (seen == INVOKEVIRTUAL || seen == INVOKEINTERFACE)
		        && (isMonitorWait() || isConditionAwait())) {

			if (getNameConstantOperand().equals("wait")) {
				sawWait = true;
			} else {
				sawAwait = true;
			}
			waitHasTimeout = !getSigConstantOperand().equals("()V");
			waitAt = getPC();
			earliestJump = getPC() + 1;
			return;
		}
		if (seen >= IFEQ && seen <= GOTO
		        || seen >= IFNULL && seen <= GOTO_W)
			earliestJump = Math.min(earliestJump, getBranchTarget());

	}

	private boolean isConditionAwait() {
		String name = getNameConstantOperand();
		String sig = getSigConstantOperand();
		
		if (name.equals("await") &&
				(sig.equals("()V") || sig.equals("(JLjava/util/concurrent/TimeUnit;)V")))
			return true;
		if (name.equals("awaitNanos") && sig.equals("(J)V"))
			return true;
		if (name.equals("awaitUninterruptibly") && sig.equals("()V"))
			return true;
		if (name.equals("awaitUntil") && sig.equals("(Ljava/util/Date;)V"))
			return true;
		
		return false;
	}

	private boolean isMonitorWait() {
		String name = getNameConstantOperand();
		String sig = getSigConstantOperand();

		return name.equals("wait")
		        && (sig.equals("()V") || sig.equals("(J)V") || sig.equals("(JI)V"));
	}


}

@@ -3030,32 +3030,80 @@
 ditionAwait() %7B%0A
+%09%09String className = getClassConstantOperand();%0A
 %09%09String name = 
@@ -3167,16 +3167,96 @@
 erand();
+%0A%0A%09%09if (!className.equals(%22java/util/concurrent/locks/Condition%22)) return false;
 %0A%09%09%0A%09%09if
@@ -3253,24 +3253,29 @@
 e;%0A%09%09%0A%09%09if (
+%0A%09%09%09%09
 name.equals(

make sure we only complain about await not being in a loop with invoked on a Condition