/*
 * FindBugs - Find bugs in Java programs
 * Copyright (C) 2006 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 java.util.HashSet;
import java.util.Set;

import org.apache.bcel.classfile.Attribute;
import org.apache.bcel.classfile.Code;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;

import edu.umd.cs.findbugs.BugInstance;
import edu.umd.cs.findbugs.BugReporter;
import edu.umd.cs.findbugs.BytecodeScanningDetector;
import edu.umd.cs.findbugs.ba.AnalysisContext;
import edu.umd.cs.findbugs.ba.XFactory;
import edu.umd.cs.findbugs.ba.XMethod;
import edu.umd.cs.findbugs.util.ClassName;

public class UncallableMethodOfAnonymousClass extends BytecodeScanningDetector {

	BugReporter bugReporter;

	public UncallableMethodOfAnonymousClass(BugReporter bugReporter) {
		this.bugReporter = bugReporter;
	}

	boolean isInnerClass = false;

	@Override
	public void visit(JavaClass obj) {
		isInnerClass = ClassName.isAnonymous(getClassName());
	}

	boolean definedInThisClassOrSuper(JavaClass clazz, String method)
			throws ClassNotFoundException {
		if (clazz == null)
			return false;
		// System.out.println("Checking to see if " + method + " is defined in "
		// + clazz.getClassName());
		for (Method m : clazz.getMethods())
			if (method.equals(m.getName() + ":" + m.getSignature()))
				return true;

		return definedInSuperClassOrInterface(clazz, method);

	}

	boolean definedInSuperClassOrInterface(JavaClass clazz, String method)
			throws ClassNotFoundException {
		if (clazz == null)
			return false;
		JavaClass superClass = clazz.getSuperClass();
		if (definedInThisClassOrSuper(superClass, method))
			return true;
		for (JavaClass i : clazz.getInterfaces())
			if (definedInThisClassOrSuper(i, method))
				return true;
		return false;
	}

	Set<String> definedInClass(JavaClass clazz) {
		HashSet<String> result = new HashSet<String>();
		for(Method m : clazz.getMethods()) {
			if (!skip(m)) 
				result.add(m.getName()+m.getSignature());
		}
		return result;
	}
	
	static private boolean skip(Method obj) {
		String methodName = obj.getName();
        if (methodName.equals("<init>"))
			return true;
        if (methodName.equals("readResolve"))
            return true;
        if (methodName.equals("readObject"))
            return true;
        if (methodName.equals("readObjectNoData"))
            return true;
        if (methodName.equals("writeObject"))
            return true;
        if (methodName.equals("writeReplace"))
            return true;
		if (methodName.startsWith("access$"))
			return true;
		if (obj.isSynthetic())
			return true;
		if (obj.isPrivate())
			return true;
		if (obj.isAbstract()) return true;
		return false;
	}
	@Override
	public void visit(Method obj) {
		try {
			if (!isInnerClass)
				return;
			if (skip(obj)) return;

			JavaClass clazz = getThisClass();
			XMethod xmethod = XFactory.createXMethod(clazz, obj);
			XFactory factory = AnalysisContext.currentXFactory();
			if (!factory.isCalled(xmethod)
					&& !definedInSuperClassOrInterface(clazz, obj.getName()
							+ ":" + obj.getSignature())) {
				int priority = NORMAL_PRIORITY;
				JavaClass superClass = clazz.getSuperClass();
				String superClassName = superClass.getClassName();
				if (superClass.isInterface() || superClassName.equals("java.lang.Object"))
						priority = HIGH_PRIORITY;
				else if (definedInClass(superClass).containsAll(definedInClass(clazz)))
						priority = NORMAL_PRIORITY;
				else
					priority = HIGH_PRIORITY;
				Code code = null;
				for(Attribute a : obj.getAttributes()) 
					if (a instanceof Code) {
						code = (Code) a;
						break;
					}
				if (code != null && code.getLength() == 1) priority++; // TODO: why didn't FindBugs give a warning here before the null check was added?
				bugReporter.reportBug(new BugInstance("UMAC_UNCALLABLE_METHOD_OF_ANONYMOUS_CLASS",
						priority).addClassAndMethod(this));
			
			}

		} catch (ClassNotFoundException e) {
			bugReporter.reportMissingClass(e);
		}

	}

}

@@ -2822,88 +2822,92 @@
  %7B%0A%09
-%09String methodName = obj.getName();%0A        if (methodName.equals(%22%3Cinit%3E%22))%0A%09%09%09
+    if (obj.isSynthetic())%0A%09        return true;%0A%09    if (obj.isPrivate())%0A%09        
 retu
@@ -2919,83 +2919,128 @@
 ue;%0A
+%09
     
-    if (methodName.equals(%22readResolve%22))%0A            return true;%0A    
+if (obj.isAbstract()) return true;%0A%0A%09    String methodName = obj.getName();%0A%09    String sig = obj.getSignature();%0A%09
     
@@ -3066,26 +3066,19 @@
 ls(%22
-readObject%22))%0A    
+%3Cinit%3E%22))%0A%09
     
@@ -3090,36 +3090,33 @@
 eturn true;%0A
-    
+%09
     if (
 methodName.e
@@ -3107,151 +3107,116 @@
 if (
-methodName.equals(%22readObjectNoData%22))%0A            return true;%0A        if (methodName.equals(%22writeObject%22))%0A            return true;%0A
+sig.equals(%22()Ljava/lang/Object;%22) %0A%09            && (methodName.equals(%22readResolve%22) %0A%09            
         
 if (
@@ -3207,28 +3207,27 @@
             
-if (
+%7C%7C 
 methodName.e
@@ -3252,13 +3252,11 @@
 e%22))
-%0A    
+)%0A%09
     
@@ -3269,25 +3269,28 @@
 turn true;%0A%09
-%09
+    
 if (methodNa
@@ -3320,126 +3320,15 @@
 ))%0A%09
-%09%09return true;%0A%09%09if (obj.isSynthetic())%0A%09%09%09return true;%0A%09%09if (obj.isPrivate())%0A%09%09%09return true;%0A%09%09if (obj.isAbstract())
+       
  ret
@@ -3330,33 +3330,36 @@
   return true;%0A%09
-%09
+    
 return false;%0A%09%7D

handle readResolve and writeReplace