/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package tests.api.java.lang.reflect;

import dalvik.annotation.KnownFailure;
import dalvik.annotation.TestTargets;
import dalvik.annotation.TestLevel;
import dalvik.annotation.TestTargetNew;
import dalvik.annotation.TestTargetClass;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;


/**
 * Tests type parameters declared on classes.
 */
@TestTargetClass(Constructor.class)
public class GenericTypesTest extends GenericReflectionTestsBase {
    
    static class GenericType<T>{
        T methodGenericType(T t){ return t;}
        @SuppressWarnings("hiding")
        <T> T hidingMethodGenericType(T t){ return t;}
        static <T> T staticMethodGenericType(T t){ return t;}
    }
    
    static class MultipleBoundedGenericTypes<T,S extends T>{
        void multipleBoundedGenericTypesTS(T t, S s){}
    }
    
    static class SimpleInheritance <T> extends GenericType<T>{}
    
    static class ConstructorGenericType<T>{
        ConstructorGenericType(T t){}
    }
    
    static class InnerClassTest<T>{
        class InnerClass {
            InnerClass(T t) {}
            void innerMethod(T t){}
        }
    }
    
    static class ExceptionTest<T extends Exception>{
        void exceptionTest() throws T{}
        class InnerClass{
            void innerExceptionTest() throws T{}
        }
    }
    
    static interface InterfaceTest<T>{}
    @TestTargetNew(
        level = TestLevel.PARTIAL,
        notes = "Check positive functionality.",
        method = "getGenericParameterTypes",
        args = {}
    )
    @SuppressWarnings("unchecked")
    public void testConstructorGenericType() throws Exception {
        Class<? extends ConstructorGenericType> clazz = ConstructorGenericType.class;
        TypeVariable<Class> typeVariable = getTypeParameter(clazz);
        Constructor<?> constructor = clazz.getDeclaredConstructor(Object.class);
        Type[] genericParameterTypes = constructor.getGenericParameterTypes();
        assertLenghtOne(genericParameterTypes);
        Type parameterType = genericParameterTypes[0];
        
        assertEquals(typeVariable, parameterType);
    }
    @TestTargetNew(
        level = TestLevel.PARTIAL,
        notes = "",
        method = "getGenericParameterTypes",
        args = {}
    )
    @SuppressWarnings("unchecked")
    public void testStaticMethodGenericType() throws Exception {
        Class<? extends GenericType> clazz = GenericType.class;
        TypeVariable<Class> typeVariable = getTypeParameter(clazz);
        
        Method method = clazz.getDeclaredMethod("staticMethodGenericType", Object.class);
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        assertLenghtOne(genericParameterTypes);
        Type parameterType = genericParameterTypes[0];
        assertNotEquals(typeVariable, parameterType);
        assertInstanceOf(TypeVariable.class, parameterType);
        assertEquals(method, ((TypeVariable)parameterType).getGenericDeclaration());
    }
    @TestTargetNew(
        level = TestLevel.PARTIAL,
        notes = "",
        method = "getGenericParameterTypes",
        args = {}
    )
    @SuppressWarnings("unchecked")
    public void testHidingMethodGenericType() throws Exception {
        Class<? extends GenericType> clazz = GenericType.class;
        TypeVariable<Class> typeVariable = getTypeParameter(clazz);
        
        Method method = clazz.getDeclaredMethod("hidingMethodGenericType",  Object.class);
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        assertLenghtOne(genericParameterTypes);
        Type parameterType = genericParameterTypes[0];
        assertNotEquals(typeVariable, parameterType);
        assertInstanceOf(TypeVariable.class, parameterType);
        assertEquals(method, ((TypeVariable)parameterType).getGenericDeclaration());
    }
    
    static class MultipleGenericTypes<T,S>{
        void multipleGenericTypesT(T t){}
        void multipleGenericTypesS(S s){}
        void multipleGenericTypesTS(T t, S s){}
    }
    @TestTargetNew(
        level = TestLevel.PARTIAL,
        notes = "",
        method = "getGenericParameterTypes",
        args = {}
    )
    @SuppressWarnings("unchecked")
    public void testMultipleGenericTypes() throws Exception {
        //Type parameters
        Class<? extends MultipleGenericTypes> clazz = MultipleGenericTypes.class;
        TypeVariable<?>[] typeParameters = clazz.getTypeParameters();
        assertEquals(2, typeParameters.length);
        TypeVariable<?> typeVariableT = typeParameters[0];
        assertEquals(clazz, typeVariableT.getGenericDeclaration());
        assertEquals("T", typeVariableT.getName());
        TypeVariable<?> typeVariableS = typeParameters[1];
        assertEquals("S", typeVariableS.getName());
        assertEquals(clazz, typeVariableS.getGenericDeclaration());
        
        // multipleGenericTypesT
        Method multipleGenericTypesT = clazz.getDeclaredMethod("multipleGenericTypesT", new Class[]{Object.class});
        Type[] multipleGenericTypesTTypes = multipleGenericTypesT.getGenericParameterTypes();
        assertLenghtOne(multipleGenericTypesTTypes);
        Type multipleGenericTypesTType = multipleGenericTypesTTypes[0];
        assertEquals(typeVariableT, multipleGenericTypesTType);
        
        // multipleGenericTypesS
        Method multipleGenericTypesS = clazz.getDeclaredMethod("multipleGenericTypesS", new Class[]{Object.class});
        Type[] multipleGenericTypesSTypes = multipleGenericTypesS.getGenericParameterTypes();
        assertLenghtOne(multipleGenericTypesSTypes);
        Type multipleGenericTypesSType = multipleGenericTypesSTypes[0];
        assertEquals(typeVariableS, multipleGenericTypesSType);
        
        // multipleGenericTypesS
        Method multipleGenericTypesTS = clazz.getDeclaredMethod("multipleGenericTypesTS", new Class[]{Object.class, Object.class});
        Type[] multipleGenericTypesTSTypes = multipleGenericTypesTS.getGenericParameterTypes();
        assertEquals(2, multipleGenericTypesTSTypes.length);
        Type multipleGenericTypesTSTypeT = multipleGenericTypesTSTypes[0];
        assertEquals(typeVariableT, multipleGenericTypesTSTypeT);
        Type multipleGenericTypesTSTypeS = multipleGenericTypesTSTypes[1];
        assertEquals(typeVariableS, multipleGenericTypesTSTypeS);
    }
    
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "getTypeParameters",
        args = {}
    )
    @SuppressWarnings("unchecked")
    public void testMultipleBoundedGenericTypes() throws Exception {
        //Type parameters
        Class<? extends MultipleBoundedGenericTypes> clazz = MultipleBoundedGenericTypes.class;
        TypeVariable<?>[] typeParameters = clazz.getTypeParameters();
        assertEquals(2, typeParameters.length);
        TypeVariable<?> typeVariableT = typeParameters[0];
        assertEquals(clazz, typeVariableT.getGenericDeclaration());
        assertEquals("T", typeVariableT.getName());
        TypeVariable<?> typeVariableS = typeParameters[1];
        assertEquals("S", typeVariableS.getName());
        assertEquals(clazz, typeVariableS.getGenericDeclaration());
        Type[] boundsS = typeVariableS.getBounds();
        assertLenghtOne(boundsS);
        Type boundS = boundsS[0];
        assertEquals(typeVariableT, boundS);
    }
    @TestTargetNew(
        level = TestLevel.PARTIAL,
        notes = "",
        method = "getGenericParameterTypes",
        args = {}
    )
    @SuppressWarnings("unchecked")
    @KnownFailure("Class GenericType can not be found, "
            + "maybe the wrong class loader is used to get the raw type?")
    public void testSimpleInheritance() throws Exception {
        Class<? extends SimpleInheritance> clazz = SimpleInheritance.class;
        TypeVariable<Class> subTypeVariable = getTypeParameter(clazz);
        
        assertInstanceOf(ParameterizedType.class, clazz.getGenericSuperclass());
        ParameterizedType parameterizedSuperType = (ParameterizedType) clazz.getGenericSuperclass();
        assertInstanceOf(Class.class, parameterizedSuperType.getRawType());
        
        TypeVariable<Class> superTypeParameter = getTypeParameter((Class<?>)parameterizedSuperType.getRawType());
        TypeVariable<Class> typeParameter = getTypeParameter(GenericType.class);
        assertEquals(superTypeParameter, typeParameter);
        
        assertNotEquals(subTypeVariable, superTypeParameter);
        
        Type[] actualTypeArguments = parameterizedSuperType.getActualTypeArguments();
        assertLenghtOne(actualTypeArguments);
        assertInstanceOf(TypeVariable.class, actualTypeArguments[0]);
        TypeVariable actualSuperTypeVariable = (TypeVariable) actualTypeArguments[0];
        assertEquals(subTypeVariable, actualSuperTypeVariable);
    }
    @TestTargetNew(
        level = TestLevel.PARTIAL,
        notes = "Doesn't check exceptions.",
        method = "getGenericParameterTypes",
        args = {}
    )
    @SuppressWarnings("unchecked")
    public void testInnerClassTest() throws Exception {
        Class<? extends InnerClassTest> clazz =InnerClassTest.class;
        TypeVariable<Class> typeVariable = getTypeParameter(clazz);
        
        Class<?>[] declaredClasses = clazz.getDeclaredClasses();
        assertLenghtOne(declaredClasses);
        Class<?> innerClazz = declaredClasses[0];
        assertEquals(InnerClassTest.InnerClass.class, innerClazz);
        
        //constructor 
        Constructor<?>[] declaredConstructors = innerClazz.getDeclaredConstructors();
        assertLenghtOne(declaredConstructors);
        Constructor<?> declaredConstructor = declaredConstructors[0];
        Type[] genericParameterTypes = declaredConstructor.getGenericParameterTypes();
        assertLenghtOne(genericParameterTypes);
        assertEquals(typeVariable, genericParameterTypes[0]);
        assertInstanceOf(TypeVariable.class, genericParameterTypes[0]);
        TypeVariable<?> constructorTypeVariable = (TypeVariable<?>) genericParameterTypes[0];
        assertEquals(clazz ,constructorTypeVariable.getGenericDeclaration());
        
        //method
        Method declaredMethods = innerClazz.getDeclaredMethod("innerMethod", Object.class);
        Type[] methodParameterTypes = declaredMethods.getGenericParameterTypes();
        assertLenghtOne(methodParameterTypes);
        assertEquals(typeVariable, methodParameterTypes[0]);
        assertInstanceOf(TypeVariable.class, methodParameterTypes[0]);
        TypeVariable<?> methodTypeVariable = (TypeVariable<?>) methodParameterTypes[0];
        assertEquals(clazz, methodTypeVariable.getGenericDeclaration());
    }
    @TestTargetNew(
        level = TestLevel.PARTIAL,
        notes = "Exceptions are not verified.",
        method = "getGenericExceptionTypes",
        args = {}
    )
    @SuppressWarnings("unchecked")
    public void testException() throws Exception {
        Class<? extends ExceptionTest> clazz = ExceptionTest.class;
        TypeVariable<Class> typeVariable = getTypeParameter(clazz);
        Method method = clazz.getDeclaredMethod("exceptionTest");
        Type[] genericExceptionTypes = method.getGenericExceptionTypes();
        assertLenghtOne(genericExceptionTypes);
        assertEquals(typeVariable, genericExceptionTypes[0]);
        
        Class<?>[] declaredClasses = clazz.getDeclaredClasses();
        assertLenghtOne(declaredClasses);
        Class<?> innerClazz = declaredClasses[0];
        assertEquals(ExceptionTest.InnerClass.class, innerClazz);
        
        //method
        Method declaredMethods = innerClazz.getDeclaredMethod("innerExceptionTest");
        Type[] exceptionTypes = declaredMethods.getGenericExceptionTypes();
        assertLenghtOne(exceptionTypes);
        assertEquals(typeVariable, exceptionTypes[0]);
        assertInstanceOf(TypeVariable.class, exceptionTypes[0]);
        TypeVariable<?> methodTypeVariable = (TypeVariable<?>) exceptionTypes[0];
        assertEquals(clazz, methodTypeVariable.getGenericDeclaration());
    }
}
