/*
 * Copyright 2016 HuntBugs contributors
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package one.util.huntbugs.detect;

import java.util.List;

import com.strobel.assembler.metadata.MethodReference;
import com.strobel.decompiler.ast.AstCode;
import com.strobel.decompiler.ast.Block;
import com.strobel.decompiler.ast.Condition;
import com.strobel.decompiler.ast.Expression;
import com.strobel.decompiler.ast.Loop;
import com.strobel.decompiler.ast.LoopType;
import com.strobel.decompiler.ast.Node;

import one.util.huntbugs.registry.MethodContext;
import one.util.huntbugs.registry.anno.AstNodes;
import one.util.huntbugs.registry.anno.AstVisitor;
import one.util.huntbugs.registry.anno.WarningDefinition;
import one.util.huntbugs.util.NodeChain;
import one.util.huntbugs.util.Nodes;
import one.util.huntbugs.util.Types;
import one.util.huntbugs.warning.Roles;

/**
 * @author Tagir Valeev
 *
 */
@WarningDefinition(category = "Multithreading", name = "WaitUnconditional", maxScore = 65)
@WarningDefinition(category = "Multithreading", name = "WaitNotInLoop", maxScore = 65)
@WarningDefinition(category = "Multithreading", name = "NotifyNaked", maxScore = 50)
public class WaitContract {
    @AstVisitor(nodes=AstNodes.EXPRESSIONS)
    public void visit(Expression expr, NodeChain parents, MethodContext mc) {
        if (expr.getCode() == AstCode.InvokeVirtual) {
            MethodReference mr = (MethodReference) expr.getOperand();
            if (mr.getName().equals("wait") && Types.isObject(mr.getDeclaringType())) {
                NodeChain cur = parents;
                boolean sawCondition = false;
                boolean sawLoop = false;
                boolean sawSynchronizedBlock = false;
                while (cur != null) {
                    if (!sawSynchronizedBlock && cur.getNode() instanceof Condition) {
                        sawCondition = true;
                    }
                    if (cur.getNode() instanceof Loop) {
                        sawLoop = true;
                        if (!sawSynchronizedBlock && ((Loop) cur.getNode()).getLoopType() == LoopType.PreCondition) {
                            sawCondition = true;
                        }
                    }
                    if (Nodes.isSynchorizedBlock(cur.getNode())) {
                        sawSynchronizedBlock = true;
                    }
                    cur = cur.getParent();
                }
                if (!sawLoop || !sawCondition) {
                    mc.report(sawCondition ? "WaitNotInLoop" : "WaitUnconditional", mr.getSignature().equals("()V") ? 0
                            : 15, expr.getArguments().get(0), Roles.CALLED_METHOD.create(mr));
                }
            }
            if((mr.getName().equals("notify") || mr.getName().equals("notifyAll")) && mr.getSignature().equals("()V")) {
                if(parents.getNode() instanceof Block) {
                    List<Node> body = ((Block) parents.getNode()).getBody();
                    if (!body.isEmpty() && body.get(0) == expr
                        && (body.size() == 1 || body.size() == 2 && Nodes.isOp(body.get(1), AstCode.MonitorExit))
                        && parents.getParent() != null
                        && Nodes.isSynchorizedBlock(parents.getParent().getNode())
                        && !parents.getParent().getParent().isSynchronized()) {
                        mc.report("NotifyNaked", 0, expr.getArguments().get(0), Roles.CALLED_METHOD.create(mr));
                    }
                }
            }
        }
    }
}

@@ -656,24 +656,80 @@
 util.List;%0A%0A
+import com.strobel.assembler.metadata.MethodDefinition;%0A
 import com.s
@@ -771,16 +771,16 @@
 erence;%0A
-
 import c
@@ -1220,16 +1220,70 @@
 isitor;%0A
+import one.util.huntbugs.registry.anno.MethodVisitor;%0A
 import o
@@ -1786,16 +1786,16 @@
 e = 50)%0A
-
 public c
@@ -1814,16 +1814,134 @@
 tract %7B%0A
+    @MethodVisitor%0A    public boolean checkMethod(MethodDefinition md) %7B%0A        return !md.isSynthetic();%0A    %7D%0A    %0A
     @Ast

exclude synthetic methods