/*
 * 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.Repository;
import org.apache.bcel.classfile.Code;

import edu.umd.cs.findbugs.BugAccumulator;
import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.SourceLineAnnotation;
import edu.umd.cs.findbugs.StatelessDetector;
import edu.umd.cs.findbugs.ba.ch.Subtypes2;

public class ReadReturnShouldBeChecked extends BytecodeScanningDetector implements StatelessDetector {

    boolean sawRead = false;

    boolean sawSkip = false;

    boolean recentCallToAvailable = false;

    int sawAvailable = 0;

    boolean wasBufferedInputStream = false;

    BugAccumulator accumulator;

    private int locationOfCall;

    private String lastCallClass = null, lastCallMethod = null, lastCallSig = null;

    public ReadReturnShouldBeChecked(BugReporter bugReporter) {
        this.accumulator = new BugAccumulator(bugReporter);
    }

    @Override
    public void visit(Code obj) {
        sawAvailable = 0;
        sawRead = false;
        sawSkip = false;
        super.visit(obj);
        accumulator.reportAccumulatedBugs();
    }

    private boolean isInputStream() {

        if (lastCallClass.startsWith("["))
            return false;
        return (Subtypes2.instanceOf(lastCallClass, "java.io.InputStream")
                || Subtypes2.instanceOf(lastCallClass, "java.io.DataInput") || Subtypes2.instanceOf(lastCallClass,
                "java.io.Reader")) && !Subtypes2.instanceOf(lastCallClass, "java.io.ByteArrayInputStream");

    }

    private boolean isBufferedInputStream() {
        try {
            if (lastCallClass.startsWith("["))
                return false;
            return Repository.instanceOf(lastCallClass, "java.io.BufferedInputStream");
        } catch (ClassNotFoundException e) {
            return false;
        }
    }

    private boolean isImageIOInputStream() {
        try {
            if (lastCallClass.startsWith("["))
                return false;
            return Repository.instanceOf(lastCallClass, "javax.imageio.stream.ImageInputStream");
        } catch (ClassNotFoundException e) {
            return false;
        }
    }

    @Override
    public void sawOpcode(int seen) {

        if (seen == INVOKEVIRTUAL || seen == INVOKEINTERFACE) {
            lastCallClass = getDottedClassConstantOperand();
            lastCallMethod = getNameConstantOperand();
            lastCallSig = getSigConstantOperand();
        }

        if (seen == INVOKEVIRTUAL || seen == INVOKEINTERFACE)
            if (getNameConstantOperand().equals("available") && getSigConstantOperand().equals("()I")
                    || getNameConstantOperand().startsWith("get") && getNameConstantOperand().endsWith("Length")
                    && getSigConstantOperand().equals("()I") || getClassConstantOperand().equals("java/io/File")
                    && getNameConstantOperand().equals("length") && getSigConstantOperand().equals("()J")) {
                sawAvailable = 70;
                return;
            }
        sawAvailable--;
        if ((seen == INVOKEVIRTUAL || seen == INVOKEINTERFACE)
                && getNameConstantOperand().equals("read")

                && (getSigConstantOperand().equals("([B)I") || getSigConstantOperand().equals("([BII)I")
                        || getSigConstantOperand().equals("([C)I") || getSigConstantOperand().equals("([CII)I"))
                && isInputStream()) {
            sawRead = true;
            recentCallToAvailable = sawAvailable > 0;
            locationOfCall = getPC();
            return;
        }
        if ((seen == INVOKEVIRTUAL || seen == INVOKEINTERFACE)
                && (getNameConstantOperand().equals("skip") && getSigConstantOperand().equals("(J)J") || getNameConstantOperand()
                        .equals("skipBytes") && getSigConstantOperand().equals("(I)I")) && isInputStream()
                && !isImageIOInputStream()) {
            // if not ByteArrayInput Stream
            // and either no recent calls to length
            // or it is a BufferedInputStream

            wasBufferedInputStream = isBufferedInputStream();
            sawSkip = true;
            locationOfCall = getPC();
            recentCallToAvailable = sawAvailable > 0 && !wasBufferedInputStream;
            return;

        }

        if ((seen == POP) || (seen == POP2)) {

            if (sawRead) {
                accumulator.accumulateBug(
                        new BugInstance(this, "RR_NOT_CHECKED", recentCallToAvailable ? LOW_PRIORITY : NORMAL_PRIORITY)
                                .addClassAndMethod(this).addCalledMethod(lastCallClass, lastCallMethod, lastCallSig, false),
                        SourceLineAnnotation.fromVisitedInstruction(getClassContext(), this, locationOfCall));

            } else if (sawSkip) {

                accumulator.accumulateBug(
                        new BugInstance(this, "SR_NOT_CHECKED", (wasBufferedInputStream ? HIGH_PRIORITY
                                : recentCallToAvailable ? LOW_PRIORITY : NORMAL_PRIORITY)).addClassAndMethod(this)
                                .addCalledMethod(lastCallClass, lastCallMethod, lastCallSig, false), SourceLineAnnotation
                                .fromVisitedInstruction(getClassContext(), this, locationOfCall));
            }
        }
        sawRead = false;
        sawSkip = false;
    }
}
