/*
 * 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.Locale;

import org.apache.bcel.Repository;
import org.apache.bcel.classfile.Field;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.LocalVariable;
import org.apache.bcel.classfile.LocalVariableTable;
import org.apache.bcel.classfile.Method;

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

import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.Detector;
import edu.umd.cs.findbugs.ba.ClassContext;
import edu.umd.cs.findbugs.visitclass.PreorderVisitor;

/**
 * looks for fields and local variables that have Map, Set, List in their names but the variable is a collection of a different basic type.
 */
public class CollectionNamingConfusion extends PreorderVisitor implements Detector {

    private JavaClass mapInterface;
    private JavaClass setInterface;
    private JavaClass listInterface;
    private JavaClass queueInterface;

    private BugReporter bugReporter;
    private ClassContext classContext;

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

        try {
            mapInterface = Repository.lookupClass(Values.SLASHED_JAVA_UTIL_MAP);
            setInterface = Repository.lookupClass(Values.SLASHED_JAVA_UTIL_SET);
            listInterface = Repository.lookupClass(Values.SLASHED_JAVA_UTIL_LIST);
            queueInterface = Repository.lookupClass(Values.SLASHED_JAVA_UTIL_QUEUE);
        } catch (ClassNotFoundException cnfe) {
            bugReporter.reportMissingClass(cnfe);
            mapInterface = null;
            setInterface = null;
            listInterface = null;
            queueInterface = null;

        }
    }

    /**
     * overrides the visitor to make sure that the static initializer was able to load the map class
     *
     * @param classContext
     *            the currently parsed class
     */
    @Override
    public void visitClassContext(ClassContext classContext) {
        if (mapInterface != null) {
            this.classContext = classContext;
            classContext.getJavaClass().accept(this);
        }
    }

    /**
     * overrides the visitor to look for fields where the name has 'Map', 'Set', 'List' in it but the type of that field isn't that.
     *
     * @param obj
     *            the currently parsed field
     */
    @Override
    public void visitField(Field obj) {
        if (checkConfusedName(obj.getName(), obj.getSignature())) {
            bugReporter.reportBug(new BugInstance(this, BugType.CNC_COLLECTION_NAMING_CONFUSION.name(), NORMAL_PRIORITY).addClass(this).addField(this)
                    .addString(obj.getName()));
        }
    }

    /**
     * overrides the visitor to look for local variables where the name has 'Map', 'Set', 'List' in it but the type of that field isn't that. note that this
     * only is useful if compiled with debug labels.
     *
     * @param obj
     *            the currently parsed method
     */
    @Override
    public void visitMethod(Method obj) {
        LocalVariableTable lvt = obj.getLocalVariableTable();
        if (lvt != null) {
            LocalVariable[] lvs = lvt.getLocalVariableTable();
            for (LocalVariable lv : lvs) {
                if (checkConfusedName(lv.getName(), lv.getSignature())) {
                    bugReporter.reportBug(new BugInstance(this, BugType.CNC_COLLECTION_NAMING_CONFUSION.name(), NORMAL_PRIORITY).addClass(this)
                            .addString(lv.getName()).addSourceLine(this.classContext, this, lv.getStartPC()));
                }
            }
        }
    }

    /**
     * looks for a name that mentions a collection type but the wrong type for the variable
     *
     * @param methodOrVariableName
     *            the method or variable name
     * @param signature
     *            the variable signature
     * @return whether the name doesn't match the type
     */
    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "EXS_EXCEPTION_SOFTENING_RETURN_FALSE", justification = "No other simple way to determine whether class exists")
    private boolean checkConfusedName(String methodOrVariableName, String signature) {
        try {
            String name = methodOrVariableName.toLowerCase(Locale.ENGLISH);
            if ((name.endsWith("map") || (name.endsWith("set") && !name.endsWith("toset")) || name.endsWith("list") || name.endsWith("queue"))
                    && signature.startsWith("Ljava/util/")) {
                String clsName = SignatureUtils.stripSignature(signature);
                JavaClass cls = Repository.lookupClass(clsName);
                if ((cls.implementationOf(mapInterface) && !name.endsWith("map")) || (cls.implementationOf(setInterface) && !name.endsWith("set"))
                        || (cls.implementationOf(listInterface) && !name.endsWith("list"))
                        || (cls.implementationOf(queueInterface) && !name.endsWith("queue"))) {
                    return true;
                }
            }
        } catch (ClassNotFoundException cnfe) {
            bugReporter.reportMissingClass(cnfe);
        }

        return false;
    }

    /**
     * implements the visitor by does nothing
     */
    @Override
    public void report() {
        // not used, implements the interface
    }
}

@@ -5942,32 +5942,33 @@
              %7C%7C 
+(
 (cls.implementat
@@ -5979,32 +5979,73 @@
 f(listInterface)
+ %7C%7C cls.implementationOf(queueInterface))
  && !name.endsWi
@@ -6054,17 +6054,16 @@
 (%22list%22)
-)
 %0A       
@@ -6083,48 +6083,15 @@
     
-%7C%7C (cls.implementationOf(queueInterface)
+       
  && 

fix CNC fp, allow list and queue to be aliases of one another, for #234