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

    }
}
