/*
 * FindBugs - Find Bugs in Java programs
 * Copyright (C) 2003-2007 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.ba.ch;

import org.apache.bcel.generic.ArrayType;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.Type;

import edu.umd.cs.findbugs.FindBugsTestCase;
import edu.umd.cs.findbugs.RunnableWithExceptions;
import edu.umd.cs.findbugs.ba.ObjectTypeFactory;
import edu.umd.cs.findbugs.classfile.Global;
import edu.umd.cs.findbugs.detect.FindRefComparison;

/**
 * Tests for Subtypes2.
 * 
 * @author Bill Pugh
 * @author David Hovemeyer
 */
public class Subtypes2Test extends FindBugsTestCase {

	ObjectType typeSerializable;
	ObjectType typeClonable;
	ObjectType typeObject;
	ObjectType typeInteger;
	ObjectType typeString;
	
	ObjectType typeList;
	ObjectType typeCollection;
	ObjectType typeHashSet;
	ArrayType typeArrayClonable;
	ObjectType typeComparable;
	
	ArrayType typeArrayObject;
	ArrayType typeArrayInteger;
	ArrayType typeArrayString;
	ArrayType typeArrayComparable;
	ArrayType typeArrayArrayObject;
	ArrayType typeArrayArraySerializable;
	ArrayType typeArrayArrayString;
	ArrayType typeArrayInt;
	ArrayType typeArrayArrayInt;
	ArrayType typeArrayArrayArrayInt;
	ArrayType typeArrayChar;
	ArrayType typeArrayArrayChar;
	ArrayType typeArrayArrayArrayChar;
	ObjectType typeDynamicString;
	ObjectType typeStaticString;
	ObjectType typeParameterString;

	/* (non-Javadoc)
	 * @see junit.framework.TestCase#setUp()
	 */
	@Override
	protected void setUp() throws Exception {
		super.setUp();
		typeSerializable = ObjectTypeFactory.getInstance("java.io.Serializable");
		typeClonable = ObjectTypeFactory.getInstance("java.lang.Cloneable");
		typeObject = ObjectTypeFactory.getInstance("java.lang.Object");
		typeInteger = ObjectTypeFactory.getInstance("java.lang.Integer");
		typeString = ObjectTypeFactory.getInstance("java.lang.String");
		typeComparable = ObjectTypeFactory.getInstance("java.lang.Comparable");
		
		typeList = ObjectTypeFactory.getInstance("java.util.List");
		typeCollection  = ObjectTypeFactory.getInstance("java.util.Collection");
		typeHashSet  = ObjectTypeFactory.getInstance("java.util.HashSet");
		typeArrayClonable = new ArrayType(typeClonable,1);
		typeArrayComparable= new ArrayType(typeComparable,1);
		typeArrayObject = new ArrayType(typeObject,1);
		typeArrayInteger = new ArrayType(typeInteger,1);
		typeArrayString = new ArrayType(typeString, 1);
		typeArrayArrayObject = new ArrayType(typeObject, 2);
		typeArrayArraySerializable = new ArrayType(typeSerializable, 2);
		typeArrayArrayString = new ArrayType(typeString, 2);
		typeArrayInt = new ArrayType(Type.INT, 1);
		typeArrayArrayInt = new ArrayType(Type.INT, 2);
		typeArrayArrayArrayInt = new ArrayType(Type.INT, 3);
		typeArrayChar = new ArrayType(Type.CHAR, 1);
		typeArrayArrayChar = new ArrayType(Type.CHAR, 2);
		typeArrayArrayArrayChar = new ArrayType(Type.CHAR, 3);
		typeDynamicString = new FindRefComparison.DynamicStringType();
		typeStaticString = new FindRefComparison.StaticStringType();
		typeParameterString = new FindRefComparison.ParameterStringType();
	}

	private static Subtypes2 getSubtypes2() {
		return Global.getAnalysisCache().getDatabase(Subtypes2.class);
	}

	public void testStringSubtypeOfObject() throws Throwable {
		executeFindBugsTest(new RunnableWithExceptions(){
			/* (non-Javadoc)
			 * @see edu.umd.cs.findbugs.RunnableWithExceptions#run()
			 */
			public void run() throws Throwable {
				Subtypes2 test = getSubtypes2();

				assertTrue(test.isSubtype(typeString, typeObject));
			}
		});
	}

	public void testStringSubtypeOfSerializable() throws Throwable {
		executeFindBugsTest(new RunnableWithExceptions(){
			/* (non-Javadoc)
			 * @see edu.umd.cs.findbugs.RunnableWithExceptions#run()
			 */
			public void run() throws Throwable {
				Subtypes2 test = getSubtypes2();

				assertTrue(test.isSubtype(typeString, typeSerializable));
			}
		});
	}

	public void testIdentitySubtype() throws Throwable {
		executeFindBugsTest(new RunnableWithExceptions() {
			/* (non-Javadoc)
			 * @see edu.umd.cs.findbugs.RunnableWithExceptions#run()
			 */
			public void run() throws Throwable {
				Subtypes2 test = getSubtypes2();

				assertTrue(test.isSubtype(typeObject, typeObject));
				assertTrue(test.isSubtype(typeSerializable, typeSerializable));
				assertTrue(test.isSubtype(typeArrayClonable, typeArrayClonable));
			}
		});
	}

	public void testInterfaceIsSubtypeOfObject() throws Throwable {
		executeFindBugsTest(new RunnableWithExceptions() {
			public void run() throws ClassNotFoundException {
				Subtypes2 test = getSubtypes2();

				assertTrue(test.isSubtype(typeClonable, typeObject));
			}
		});
	}

	public void testArrays() throws Throwable {
		executeFindBugsTest(new RunnableWithExceptions() {
			/* (non-Javadoc)
			 * @see edu.umd.cs.findbugs.RunnableWithExceptions#run()
			 */
			public void run() throws Throwable {
				Subtypes2 test = getSubtypes2();

				assertTrue(test.isSubtype(typeArrayClonable, typeObject));
				assertTrue(test.isSubtype(typeArrayClonable, typeArrayObject));
			}
		});
	}

	public void testUnrelatedTypes() throws Throwable {
		executeFindBugsTest(new RunnableWithExceptions(){
			/* (non-Javadoc)
			 * @see edu.umd.cs.findbugs.RunnableWithExceptions#run()
			 */
			public void run() throws Throwable {
				Subtypes2 test = getSubtypes2();

				assertFalse(test.isSubtype(typeInteger, typeString));
			}
		});
	}

	public void testArraysWrongDimension() throws Throwable {
		executeFindBugsTest(new RunnableWithExceptions(){
			/* (non-Javadoc)
			 * @see edu.umd.cs.findbugs.RunnableWithExceptions#run()
			 */
			public void run() throws Throwable {
				Subtypes2 test = getSubtypes2();

				assertFalse(test.isSubtype(typeArrayArrayString, typeArrayString));
			}
		});
	}

	public void testMultidimensionalArrayIsSubtypeOfObjectArray() throws Throwable {
		executeFindBugsTest(new RunnableWithExceptions() {
			/* (non-Javadoc)
			 * @see edu.umd.cs.findbugs.RunnableWithExceptions#run()
			 */
			public void run() throws Throwable {
				Subtypes2 test = getSubtypes2();

				assertTrue(test.isSubtype(typeArrayArrayString, typeArrayObject));
				assertTrue(test.isSubtype(typeArrayArraySerializable, typeArrayObject));
				assertTrue(test.isSubtype(typeArrayArrayInt, typeArrayObject));
			}
		});
	}

	public void testArrayOfPrimitiveIsSubtypeOfObject() throws Throwable {
		executeFindBugsTest(new RunnableWithExceptions(){
			/* (non-Javadoc)
			 * @see edu.umd.cs.findbugs.RunnableWithExceptions#run()
			 */
			public void run() throws Exception {
				Subtypes2 test = getSubtypes2();

				assertTrue(test.isSubtype(typeArrayInt, typeObject));
			}
		});
	}

	public void testSpecialStringSubclasses() throws Exception {
		executeFindBugsTest(new RunnableWithExceptions() {
			/* (non-Javadoc)
			 * @see edu.umd.cs.findbugs.RunnableWithExceptions#run()
			 */
			public void run() throws Exception {
				Subtypes2 test = getSubtypes2();

				assertTrue(test.isSubtype(typeDynamicString, typeString));
				assertTrue(test.isSubtype(typeStaticString, typeString));
				assertTrue(test.isSubtype(typeParameterString, typeString));
			}
		});
	}

	public void testEasyFirstCommonSuperclass() throws Exception {
		executeFindBugsTest(new RunnableWithExceptions() {
			/* (non-Javadoc)
			 * @see edu.umd.cs.findbugs.RunnableWithExceptions#run()
			 */
			public void run() throws Throwable {
				Subtypes2 test = getSubtypes2();

				assertEquals(typeObject, test.getFirstCommonSuperclass(typeObject, typeObject));
				assertEquals(typeString, test.getFirstCommonSuperclass(typeString, typeString));
				assertEquals(typeObject, test.getFirstCommonSuperclass(typeString, typeObject));
				assertEquals(typeObject, test.getFirstCommonSuperclass(typeObject, typeString));

				// Slightly harder one
				assertEquals(typeComparable, test.getFirstCommonSuperclass(typeString, typeInteger));
			}
		});
	}

	public void testInterfaceFirstCommonSuperclass() throws Exception {
		executeFindBugsTest(new RunnableWithExceptions(){
			/* (non-Javadoc)
			 * @see edu.umd.cs.findbugs.RunnableWithExceptions#run()
			 */
			public void run() throws Throwable {
				Subtypes2 test = getSubtypes2();
				
				assertEquals(typeObject, test.getFirstCommonSuperclass(typeSerializable, typeObject));
				assertEquals(typeObject, test.getFirstCommonSuperclass(typeObject, typeSerializable));
				assertEquals(typeObject, test.getFirstCommonSuperclass(typeSerializable, typeClonable));
				
				assertEquals(typeSerializable, test.getFirstCommonSuperclass(typeSerializable, typeSerializable));
			}
		});
	}
	
	public void testArrayFirstCommonSuperclass() throws Exception {
		executeFindBugsTest(new RunnableWithExceptions(){
			/* (non-Javadoc)
			 * @see edu.umd.cs.findbugs.RunnableWithExceptions#run()
			 */
			public void run() throws Throwable {
				Subtypes2 test = getSubtypes2();

				assertEquals(typeObject, test.getFirstCommonSuperclass(typeArrayInteger, typeObject));
				assertEquals(typeObject, test.getFirstCommonSuperclass(typeSerializable, typeArrayClonable));
				
				assertEquals(typeArrayComparable, test.getFirstCommonSuperclass(typeArrayString, typeArrayInteger));
				
				assertEquals(typeArrayInt, test.getFirstCommonSuperclass(typeArrayInt, typeArrayInt));
				assertEquals(typeObject, test.getFirstCommonSuperclass(typeArrayChar, typeArrayInt));
				
				assertEquals(typeObject, test.getFirstCommonSuperclass(typeArrayString, typeArrayInt));
				
				assertEquals(typeArrayObject, test.getFirstCommonSuperclass(typeArrayArraySerializable, typeArrayString));
				
				assertEquals(typeObject, test.getFirstCommonSuperclass(typeArrayArrayString, typeArrayInt));
			}
		});
	}
	
	public void testArrayFirstCommonSuperclassTricky() throws Exception {
		executeFindBugsTest(new RunnableWithExceptions() {

			public void run() throws Throwable {
				Subtypes2 test = getSubtypes2();
				
				assertEquals(typeArrayObject, test.getFirstCommonSuperclass(typeArrayArrayInt, typeArrayArrayChar));
				assertEquals(typeArrayObject, test.getFirstCommonSuperclass(typeArrayArrayInt, typeArrayArrayArrayChar));
				assertEquals(typeArrayArrayObject, test.getFirstCommonSuperclass(typeArrayArrayArrayChar, typeArrayArrayArrayInt));

				// Sanity check
				assertEquals(typeArrayArrayArrayChar, test.getFirstCommonSuperclass(typeArrayArrayArrayChar, typeArrayArrayArrayChar));
			}
		});
	}
	
	public void testInterfaces() throws Exception {
		executeFindBugsTest(new RunnableWithExceptions() {

			public void run() throws Throwable {
				Subtypes2 test = getSubtypes2();
				assertEquals(typeCollection, test.getFirstCommonSuperclass(typeCollection, typeHashSet));
				assertEquals(typeCollection, test.getFirstCommonSuperclass(typeHashSet, typeCollection));
				assertEquals(typeCollection, test.getFirstCommonSuperclass(typeList, typeHashSet));
				
				
			}
		});
		/*
		ObjectType typeList;
		ObjectType typeMap;
		ObjectType typeCollection;
		ObjectType typeHashSet;
		*/

	}
}
