package org.checkerframework.checker.lock;

import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.Tree.Kind;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.javacutil.TypesUtils;

public class LockTreeAnnotator extends TreeAnnotator {

    public LockTreeAnnotator(AnnotatedTypeFactory atypeFactory) {
        super(atypeFactory);
    }

    @Override
    public Void visitBinary(BinaryTree node, AnnotatedTypeMirror type) {
        // For any binary operation whose LHS or RHS can be a non-boolean type,
        // and whose resulting type is necessarily boolean, the resulting annotation
        // on the boolean type must be @GuardedBy({}).

        // There is no need to enforce that the annotation on the result of &&, ||, etc.
        // is @GuardedBy({}) since for such operators, both operands are of type
        // @GuardedBy({}) boolean to begin with.

        if (isBinaryComparisonOrInstanceOfOperator(node.getKind())
                || TypesUtils.isString(type.getUnderlyingType())) {
            // A boolean or String is always @GuardedBy({}). LockVisitor determines whether
            // the LHS and RHS of this operation can be legally dereferenced.
            type.replaceAnnotation(((LockAnnotatedTypeFactory) atypeFactory).GUARDEDBY);

            return null;
        }

        return super.visitBinary(node, type);
    }

    /** Indicates that the result of the operation is a boolean value. */
    private static boolean isBinaryComparisonOrInstanceOfOperator(Kind opKind) {
        switch (opKind) {
            case EQUAL_TO:
            case NOT_EQUAL_TO:
                // Technically, <=, <, > and >= are irrelevant for visitBinary, since currently
                // boxed primitives cannot be annotated with @GuardedBy(...), but they are left here
                // in case that rule changes.
            case LESS_THAN:
            case LESS_THAN_EQUAL:
            case GREATER_THAN:
            case GREATER_THAN_EQUAL:
            case INSTANCE_OF:
                return true;
            default:
        }

        return false;
    }

    @Override
    public Void visitCompoundAssignment(CompoundAssignmentTree node, AnnotatedTypeMirror type) {
        if (TypesUtils.isString(type.getUnderlyingType())) {
            type.replaceAnnotation(((LockAnnotatedTypeFactory) atypeFactory).GUARDEDBY);
        }

        return super.visitCompoundAssignment(node, type);
    }
}
