/*
 * fb-contrib - Auxiliary detectors for Java programs
 * Copyright (C) 2005-2017 Dave Brosius
 *
 * 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 com.mebigfatguy.fbcontrib.detect;

import java.util.Set;

import org.apache.bcel.Constants;
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.JavaClass;

import com.mebigfatguy.fbcontrib.utils.BugType;
import com.mebigfatguy.fbcontrib.utils.FQMethod;
import com.mebigfatguy.fbcontrib.utils.SignatureUtils;
import com.mebigfatguy.fbcontrib.utils.UnmodifiableSet;
import com.mebigfatguy.fbcontrib.utils.Values;

import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.OpcodeStack;
import edu.umd.cs.findbugs.OpcodeStack.CustomUserValue;
import edu.umd.cs.findbugs.ba.ClassContext;

/**
 * looks for various issues around input/output/streaming library use
 */
@CustomUserValue
public class IOIssues extends BytecodeScanningDetector {

    enum IOIUserValue {
        BUFFER, READER
    };

    private static final String ANY_PARMS = "(*)";
    private static Set<FQMethod> COPY_METHODS = UnmodifiableSet.create(
    //@formatter:off
            new FQMethod("java/nio/file/Files", "copy", ANY_PARMS),
            new FQMethod("org/apache/commons/io/IOUtils", "copy", ANY_PARMS),
            new FQMethod("org/apache/commons/io/IOUtils", "copyLarge", ANY_PARMS),
            new FQMethod("org/springframework/util/FileCopyUtils", "copy", ANY_PARMS),
            new FQMethod("org/springframework/util/FileCopyUtils", "copyToByteArray", ANY_PARMS),
            new FQMethod("com/google/common/io/Files", "copy", ANY_PARMS),
            new FQMethod("org/apache/poi/openxml4j/opc/StreamHelper", "copyStream", ANY_PARMS)
    //@formatter:on
    );

    private static final Set<String> BUFFERED_CLASSES = UnmodifiableSet.create(
    //@formatter:off
            "java.io.BufferedInputStream",
            "java.io.BufferedOutputStream",
            "java.io.BufferedReader",
            "java.io.BufferedWriter"
    //@formatter:on
    );

    private JavaClass readerClass;

    private BugReporter bugReporter;
    private OpcodeStack stack;
    private int clsVersion;

    /**
     * constructs a IOI detector given the reporter to report bugs on
     *
     * @param bugReporter
     *            the sync of bug reports
     */
    public IOIssues(BugReporter bugReporter) {
        this.bugReporter = bugReporter;

        try {
            readerClass = Repository.lookupClass("java.io.Reader");
        } catch (ClassNotFoundException cnfe) {
            bugReporter.reportMissingClass(cnfe);
        }

    }

    /**
     * implements the visitor to create and tear down the opcode stack
     *
     * @param clsContext
     *            the context object of the currently parsed class
     */
    @Override
    public void visitClassContext(ClassContext clsContext) {

        try {
            stack = new OpcodeStack();
            clsVersion = clsContext.getJavaClass().getMajor();
            super.visitClassContext(clsContext);
        } finally {
            stack = null;
        }
    }

    /**
     * implements the visitor to reset the opcode stack
     *
     * @param obj
     *            the currently parsed code block
     */
    @Override
    public void visitCode(Code obj) {

        stack.resetForMethodEntry(this);
        super.visitCode(obj);
    }

    /**
     * implements the visitor to look for common api copy utilities to copy streams where the passed in Stream is Buffered. Since these libraries already handle
     * the buffering, you are just slowing them down by the extra copy. Also look for copies where the source is a Reader, as this is just wasteful. Can't wrap
     * my head around whether a Writer output is sometime valid, might be, so for now ignoring that. Also reports uses of java.io.FileInputStream and
     * java.io.FileOutputStream on java >= 1.7 as those classes have finalize methods that junk up gc.
     *
     * @param seen
     *            the currently parsed opcode
     */
    @Override
    public void sawOpcode(int seen) {
        IOIUserValue uvSawBuffer = null;

        try {
            switch (seen) {
                case INVOKESPECIAL:
                    uvSawBuffer = processInvokeSpecial();
                break;

                case INVOKESTATIC:
                    processInvokeStatic();
                break;

                default:
                break;
            }
        } catch (ClassNotFoundException cnfe) {
            bugReporter.reportMissingClass(cnfe);
        } finally {
            stack.sawOpcode(this, seen);
            if ((uvSawBuffer != null) && (stack.getStackDepth() > 0)) {
                OpcodeStack.Item itm = stack.getStackItem(0);
                itm.setUserValue(uvSawBuffer);
            }
        }
    }

    private IOIUserValue processInvokeSpecial() throws ClassNotFoundException {
        String methodName = getNameConstantOperand();

        if (Values.CONSTRUCTOR.equals(methodName)) {
            String clsName = getDottedClassConstantOperand();
            if (BUFFERED_CLASSES.contains(clsName)) {
                return IOIUserValue.BUFFER;
            } else if ("java.io.FileInputStream".equals(clsName) || "java.io.FileOutputStream".equals(clsName)) {
                if (clsVersion >= Constants.MAJOR_1_7) {
                    bugReporter.reportBug(new BugInstance(this, BugType.IOI_USE_OF_FILE_STREAM_CONSTRUCTORS.name(), NORMAL_PRIORITY).addClass(this)
                            .addMethod(this).addSourceLine(this));
                }
            } else if (readerClass != null) {
                JavaClass cls = Repository.lookupClass(clsName);
                if (cls.instanceOf(readerClass)) {
                    return IOIUserValue.READER;
                }
            }
        }

        return null;
    }

    private void processInvokeStatic() {
        String clsName = getClassConstantOperand();
        String methodName = getNameConstantOperand();
        FQMethod m = new FQMethod(clsName, methodName, ANY_PARMS);
        if (COPY_METHODS.contains(m)) {
            String signature = getSigConstantOperand();
            int numArguments = SignatureUtils.getNumParameters(signature);
            if (stack.getStackDepth() >= numArguments) {
                for (int i = 0; i < numArguments; i++) {
                    OpcodeStack.Item itm = stack.getStackItem(i);
                    IOIUserValue uv = (IOIUserValue) itm.getUserValue();
                    if (uv != null) {
                        switch (uv) {
                            case BUFFER:
                                bugReporter.reportBug(new BugInstance(this, BugType.IOI_DOUBLE_BUFFER_COPY.name(), NORMAL_PRIORITY).addClass(this)
                                        .addMethod(this).addSourceLine(this));
                            break;

                            case READER:
                                bugReporter.reportBug(new BugInstance(this, BugType.IOI_COPY_WITH_READER.name(), NORMAL_PRIORITY).addClass(this).addMethod(this)
                                        .addSourceLine(this));
                        }
                        break;
                    }
                }
            }
        }
    }

}

@@ -4699,16 +4699,23 @@
 ream on 
+%7B@code 
 java %3E= 
@@ -4717,16 +4717,17 @@
 a %3E= 1.7
+%7D
  as thos

fix javadoc