/*
 * FindBugs - Find bugs in Java programs
 * Copyright (C) 2004,2005 Dave Brosius <dbrosius@users.sourceforge.net>
 * Copyright (C) 2004,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.classfile.Code;
import org.apache.bcel.classfile.Method;

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.MethodAnnotation;
import edu.umd.cs.findbugs.StatelessDetector;
import edu.umd.cs.findbugs.ba.XClass;
import edu.umd.cs.findbugs.classfile.CheckedAnalysisException;
import edu.umd.cs.findbugs.classfile.ClassDescriptor;
import edu.umd.cs.findbugs.classfile.Global;

public class WrongMapIterator extends BytecodeScanningDetector implements   StatelessDetector {
	private BugAccumulator bugAccumulator;
	private static final int SAW_NOTHING = 0;
	private static final int SAW_MAP_LOAD1 = 1;
	private static final int SAW_KEYSET = 2;
	private static final int SAW_KEYSET_STORE = 3;    
	private static final int SAW_ITERATOR = 4;
	private static final int SAW_ITERATOR_STORE = 5;
	private static final int SAW_ITERATOR_LOAD = 6;
	private static final int SAW_NEXT = 7;
	private static final int SAW_CHECKCAST_ON_NEXT = 8;
	private static final int SAW_KEY_STORE = 9;
	private static final int NEED_KEYSET_LOAD = 10;
	private static final int SAW_MAP_LOAD2 = 11;    
	private static final int SAW_KEY_LOAD = 12;

	private int state;
	private int loadedRegister;
	private int mapRegister;
	private int keySetRegister;
	private int iteratorRegister;
	private int keyRegister;


	public WrongMapIterator(BugReporter bugReporter) {
		this.bugAccumulator = new BugAccumulator(bugReporter);
	}



	@Override
	public void visit(Method obj) {
		state = SAW_NOTHING;
		loadedRegister = -1;
		mapRegister = -1;
		keySetRegister = -1;
		iteratorRegister = -1;
		keyRegister = -1;
	}
	
	@Override
	public void visit(Code code) {
		super.visit(code);
		bugAccumulator.reportAccumulatedBugs();
	}
	
	/**
	 * Determine from the class descriptor for a variable whether that variable
	 * implements java.util.Map. 
	 * @param d class descriptor for variable we want to check implements Map
	 * @return true iff the descriptor corresponds to an implementor of Map
	 */
	private static boolean implementsMap(ClassDescriptor d) {
        while(d != null) {
            try {
                // True if variable is itself declared as a Map
                if("java.util.Map".equals(d.getDottedClassName())) {
                    return true;
                }
                XClass classNameAndInfo =
                    Global.getAnalysisCache().
                        getClassAnalysis(XClass.class, d);
                ClassDescriptor is[] =
                    classNameAndInfo.getInterfaceDescriptorList();
                d = classNameAndInfo.getSuperclassDescriptor();
                for(ClassDescriptor i : is) {
                    if("java.util.Map".equals(i.getDottedClassName())) {
                        return true;
                    }
                }
            } catch (CheckedAnalysisException e) {
                d = null;
            }
        }
        return false;
	}

	@Override
	public void sawOpcode(int seen) {
		switch (state)
		{
			case SAW_NOTHING:
				loadedRegister = getLoadStoreRegister( seen, true );
				if (loadedRegister >= 0)
					state = SAW_MAP_LOAD1;
			break;

			case SAW_MAP_LOAD1:
			    // Doesn't check to see if the target object is a Map
				if (((seen == INVOKEINTERFACE) || (seen == INVOKEVIRTUAL))
				&&  ("keySet".equals(getNameConstantOperand()))
				&&  ("()Ljava/util/Set;".equals(getSigConstantOperand()))
				// Following check solves sourceforge bug 1830576
				&&  implementsMap(getClassDescriptorOperand())) {
					mapRegister = loadedRegister;
					state = SAW_KEYSET;
				}
				else {
					state = SAW_NOTHING;
				}
			break;

			case SAW_KEYSET:
				keySetRegister = getLoadStoreRegister( seen, false );
				if (keySetRegister >= 0)
					state = SAW_KEYSET_STORE;
				else if ((seen == INVOKEINTERFACE)
				&&  ("iterator".equals(getNameConstantOperand()))
				&&  ("()Ljava/util/Iterator;".equals(getSigConstantOperand())))
					state = SAW_ITERATOR;
				else
					state = SAW_NOTHING;
			break;

			case SAW_KEYSET_STORE:	
				if ((seen == INVOKEINTERFACE)
				&&  ("iterator".equals(getNameConstantOperand()))
				&&  ("()Ljava/util/Iterator;".equals(getSigConstantOperand())))
					state = SAW_ITERATOR;
				else
					state = NEED_KEYSET_LOAD;
			break;

			case NEED_KEYSET_LOAD:
				loadedRegister = getLoadStoreRegister( seen, true );
				if (loadedRegister == iteratorRegister)
					state = SAW_ITERATOR;
			break;

			case SAW_ITERATOR:
				iteratorRegister = getLoadStoreRegister( seen, false );
				if (iteratorRegister >= 0)
					state = SAW_ITERATOR_STORE;
				else
					state = SAW_NOTHING;
			break;

			case SAW_ITERATOR_STORE:
				loadedRegister = getLoadStoreRegister( seen, true );
				if (loadedRegister == iteratorRegister)
					state = SAW_ITERATOR_LOAD;
			break;

			case SAW_ITERATOR_LOAD:
				if ((seen == INVOKEINTERFACE)
				&&  ("next".equals(getNameConstantOperand()))
				&&  ("()Ljava/lang/Object;".equals(getSigConstantOperand())))
					state = SAW_NEXT;
				else
					state = SAW_ITERATOR_STORE;
			break;

			case SAW_NEXT:
				if (seen == CHECKCAST)
					state = SAW_CHECKCAST_ON_NEXT;
				   else {
					   keyRegister = getLoadStoreRegister( seen, false );
					   if (keyRegister >= 0)
						   state = SAW_KEY_STORE;
					   else
						   state = SAW_NOTHING;
				   }
			 break;

			case SAW_CHECKCAST_ON_NEXT:
				keyRegister = getLoadStoreRegister( seen, false );
				if (keyRegister >= 0)
					state = SAW_KEY_STORE;
			break;

			case SAW_KEY_STORE:
				loadedRegister = getLoadStoreRegister( seen, true );
				if (loadedRegister == mapRegister)
					state = SAW_MAP_LOAD2;
			break;

			case SAW_MAP_LOAD2:
				loadedRegister = getLoadStoreRegister( seen, true );
				if (loadedRegister == keyRegister)
					state = SAW_KEY_LOAD;
				else
					state = SAW_KEY_STORE;
			break;

			case SAW_KEY_LOAD:
				if (((seen == INVOKEINTERFACE) || (seen == INVOKEVIRTUAL))
				&&  ("get".equals(getNameConstantOperand()))
				&&  ("(Ljava/lang/Object;)Ljava/lang/Object;".equals(getSigConstantOperand()))) {
					MethodAnnotation ma = MethodAnnotation.fromVisitedMethod(this);
					bugAccumulator.accumulateBug(
						new BugInstance(this, "WMI_WRONG_MAP_ITERATOR", NORMAL_PRIORITY)
														.addClass(this)
														.addMethod(ma),this);
					state = SAW_NOTHING;
				}
			break;
		}
	}

	private int getLoadStoreRegister(int seen, boolean doLoad)
	{
		switch (seen)
		{
			case ALOAD_0:
			case ALOAD_1:
			case ALOAD_2:
			case ALOAD_3:
				if (doLoad)
					return seen - ALOAD_0;
			break;

			case ALOAD:
				if (doLoad)
					return getRegisterOperand();
			break;

			case ASTORE_0:
			case ASTORE_1:
			case ASTORE_2:
			case ASTORE_3:
				if (!doLoad)
					return seen - ASTORE_0;
			break;

			case ASTORE:
				if (!doLoad)
					return getRegisterOperand();
			break;
		}

		return -1;
	}
}
