/*
 * 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.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;

/**
 * Tests generic reflection in more complicated cases. In particular: Scoping of
 * type parameters, equality of type parameters, wildcard types, parameterized
 * types and multiple bounds.
 */
@TestTargetClass(Method.class)
public class GenericReflectionCornerCases extends GenericReflectionTestsBase {

    static class Pair<T, S> {}

    static class WildcardEquality<T> {
        void wildcardEquality(Pair<? extends T, ? extends T> param) {}
    }
    @TestTargets({
        @TestTargetNew(
            level = TestLevel.PARTIAL,
            notes = "Stress test.",
            method = "getTypeParameters",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.PARTIAL,
            notes = "Stress test.",
            method = "getGenericParameterTypes",
            args = {}
        )
    })
    @SuppressWarnings("unchecked")
    public void testWildcardEquality() throws Exception {
        Class<? extends WildcardEquality> clazz = WildcardEquality.class;

        Method method = clazz.getDeclaredMethod("wildcardEquality", Pair.class);
        TypeVariable<?>[] typeParameters = clazz.getTypeParameters();
        assertLenghtOne(typeParameters);
        TypeVariable<?> typeParameter = typeParameters[0];

        Type[] parameterTypes = method.getGenericParameterTypes();
        assertLenghtOne(parameterTypes);
        Type parameter = parameterTypes[0];
        assertInstanceOf(ParameterizedType.class, parameter);
        ParameterizedType paramType = (ParameterizedType) parameter;
        Type[] actualTypeArguments = paramType.getActualTypeArguments();
        assertEquals(2, actualTypeArguments.length);

        Type firstArgument = actualTypeArguments[0];
        assertInstanceOf(WildcardType.class, firstArgument);
        WildcardType firstWildcardArgument = (WildcardType) firstArgument;
        Type secondArgument = actualTypeArguments[1];
        assertInstanceOf(WildcardType.class, secondArgument);
        WildcardType secondWildcardArgument = (WildcardType) secondArgument;

        assertEquals(firstWildcardArgument, secondWildcardArgument);

        Type[] firstWildcardArgumentUpperBounds = firstWildcardArgument.getUpperBounds();
        assertLenghtOne(firstWildcardArgumentUpperBounds);
        Type firstWildcardArgumentUpperBoundsType = firstWildcardArgumentUpperBounds[0];

        Type[] secondWildcardArgumentUpperBounds = secondWildcardArgument.getUpperBounds();
        assertLenghtOne(secondWildcardArgumentUpperBounds);
        Type secondWildcardArgumentUpperBoundsType = secondWildcardArgumentUpperBounds[0];

        assertEquals(firstWildcardArgumentUpperBoundsType, secondWildcardArgumentUpperBoundsType);

        assertEquals(typeParameter, firstWildcardArgumentUpperBoundsType);
        assertEquals(typeParameter, secondWildcardArgumentUpperBoundsType);
    }

    static class WildcardUnEquality<T> {
        void wildcardUnEquality(Pair<? extends T, ? super T> param) {}
    }
    @TestTargets({
        @TestTargetNew(
            level = TestLevel.PARTIAL,
            notes = "Stress test.",
            method = "getTypeParameters",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.PARTIAL,
            notes = "Stress test.",
            method = "getGenericParameterTypes",
            args = {}
        )
    })
    @SuppressWarnings("unchecked")
    public void testWildcardUnEquality() throws Exception {
        Class<? extends WildcardUnEquality> clazz = WildcardUnEquality.class;

        Method method = clazz.getDeclaredMethod("wildcardUnEquality", Pair.class);
        TypeVariable<?>[] typeParameters = clazz.getTypeParameters();
        assertLenghtOne(typeParameters);
        TypeVariable<?> typeParameter = typeParameters[0];

        Type[] parameterTypes = method.getGenericParameterTypes();
        assertLenghtOne(parameterTypes);
        Type parameter = parameterTypes[0];
        assertInstanceOf(ParameterizedType.class, parameter);
        ParameterizedType paramType = (ParameterizedType) parameter;
        Type[] actualTypeArguments = paramType.getActualTypeArguments();
        assertEquals(2, actualTypeArguments.length);

        Type firstArgument = actualTypeArguments[0];
        assertInstanceOf(WildcardType.class, firstArgument);
        WildcardType firstWildcardArgument = (WildcardType) firstArgument;
        Type secondArgument = actualTypeArguments[1];
        assertInstanceOf(WildcardType.class, secondArgument);
        WildcardType secondWildcardArgument = (WildcardType) secondArgument;

        assertNotEquals(firstWildcardArgument, secondWildcardArgument);

        Type[] firstWildcardArgumentUpperBounds = firstWildcardArgument.getUpperBounds();
        assertLenghtOne(firstWildcardArgumentUpperBounds);
        Type firstWildcardArgumentUpperBoundsType = firstWildcardArgumentUpperBounds[0];

        Type[] secondWildcardArgumentLowerBounds = secondWildcardArgument.getLowerBounds();
        assertLenghtOne(secondWildcardArgumentLowerBounds);
        Type secondWildcardArgumentLoweroundsType = secondWildcardArgumentLowerBounds[0];

        assertEquals(firstWildcardArgumentUpperBoundsType, secondWildcardArgumentLoweroundsType);
        assertEquals(typeParameter, firstWildcardArgumentUpperBoundsType);
        assertEquals(typeParameter, secondWildcardArgumentLoweroundsType);
    }

    static class MultipleBoundedWildcardUnEquality<T extends Object & Comparable<MultipleBoundedWildcardUnEquality<T>>> {
        void multipleBoundedWildcardUnEquality(Pair<? extends T, ? super T> param) {}
    }
    @TestTargets({
        @TestTargetNew(
            level = TestLevel.PARTIAL,
            notes = "Stress test.",
            method = "getTypeParameters",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.PARTIAL,
            notes = "Stress test.",
            method = "getGenericParameterTypes",
            args = {}
        )
    })
    @SuppressWarnings("unchecked")
    @KnownFailure("Class MultipleBoundedWildcardUnEquality can not be found, "
            + "maybe the wrong class loader is used to get the raw type?")
    public void testMultipleBoundedWildcardUnEquality() throws Exception {
        Class<? extends MultipleBoundedWildcardUnEquality> clazz = MultipleBoundedWildcardUnEquality.class;

        // new WildcardEquality<Object>().wildcardEquality(new Pair<String,
        // Integer>());

        Method method = clazz.getDeclaredMethod("multipleBoundedWildcardUnEquality", Pair.class);
        TypeVariable<?>[] typeParameters = clazz.getTypeParameters();
        assertLenghtOne(typeParameters);
        TypeVariable<?> typeParameter = typeParameters[0];
        Type[] typeParameterBounds = typeParameter.getBounds();
        assertEquals(2, typeParameterBounds.length);
        assertEquals(Object.class, typeParameterBounds[0]);
        assertInstanceOf(ParameterizedType.class, typeParameterBounds[1]);
        ParameterizedType parameterizedType = (ParameterizedType) typeParameterBounds[1];
        assertEquals(Comparable.class, parameterizedType.getRawType());
        Type[] typeArguments = parameterizedType.getActualTypeArguments();
        assertLenghtOne(typeArguments);
        assertInstanceOf(ParameterizedType.class, typeArguments[0]);
        ParameterizedType type = (ParameterizedType) typeArguments[0];
        assertEquals(typeParameter, type.getActualTypeArguments()[0]);
        assertEquals(MultipleBoundedWildcardUnEquality.class, type.getRawType());

        Type[] parameterTypes = method.getGenericParameterTypes();
        assertLenghtOne(parameterTypes);
        Type parameter = parameterTypes[0];
        assertInstanceOf(ParameterizedType.class, parameter);
        ParameterizedType paramType = (ParameterizedType) parameter;
        Type[] actualTypeArguments = paramType.getActualTypeArguments();
        assertEquals(2, actualTypeArguments.length);

        Type firstArgument = actualTypeArguments[0];
        assertInstanceOf(WildcardType.class, firstArgument);
        WildcardType firstWildcardArgument = (WildcardType) firstArgument;
        Type secondArgument = actualTypeArguments[1];
        assertInstanceOf(WildcardType.class, secondArgument);
        WildcardType secondWildcardArgument = (WildcardType) secondArgument;

        assertNotEquals(firstWildcardArgument, secondWildcardArgument);

        Type[] firstWildcardArgumentUpperBounds = firstWildcardArgument.getUpperBounds();
        assertLenghtOne(firstWildcardArgumentUpperBounds);
        Type firstWildcardArgumentUpperBoundsType = firstWildcardArgumentUpperBounds[0];

        Type[] secondWildcardArgumentLowerBounds = secondWildcardArgument.getLowerBounds();
        assertLenghtOne(secondWildcardArgumentLowerBounds);
        Type secondWildcardArgumentLoweroundsType = secondWildcardArgumentLowerBounds[0];

        assertEquals(firstWildcardArgumentUpperBoundsType, secondWildcardArgumentLoweroundsType);
    }

    static class MultipleBoundedWildcardEquality<T extends Object & Comparable<MultipleBoundedWildcardEquality<T>>> {
        void multipleBoundedWildcardEquality(Pair<? extends T, ? extends T> param) {}
    }
    @TestTargets({
        @TestTargetNew(
            level = TestLevel.PARTIAL,
            notes = "Stress test.",
            method = "getTypeParameters",
            args = {}
        ),
        @TestTargetNew(
            level = TestLevel.PARTIAL,
            notes = "Stress test.",
            method = "getGenericParameterTypes",
            args = {}
        )
    })
    @SuppressWarnings("unchecked")
    @KnownFailure("Class MultipleBoundedWildcardEquality can not be found, "
            + "maybe the wrong class loader is used to get the raw type?")
    public void testMultipleBoundedWildcard() throws Exception {
        Class<? extends MultipleBoundedWildcardEquality> clazz = MultipleBoundedWildcardEquality.class;

        Method method = clazz.getDeclaredMethod("multipleBoundedWildcardEquality", Pair.class);
        TypeVariable<?>[] typeParameters = clazz.getTypeParameters();
        assertLenghtOne(typeParameters);
        TypeVariable<?> typeParameter = typeParameters[0];
        Type[] typeParameterBounds = typeParameter.getBounds();
        assertEquals(2, typeParameterBounds.length);
        assertEquals(Object.class, typeParameterBounds[0]);
        assertInstanceOf(ParameterizedType.class, typeParameterBounds[1]);
        ParameterizedType parameterizedType = (ParameterizedType) typeParameterBounds[1];
        assertEquals(Comparable.class, parameterizedType.getRawType());
        Type[] typeArguments = parameterizedType.getActualTypeArguments();
        assertLenghtOne(typeArguments);
        assertInstanceOf(ParameterizedType.class, typeArguments[0]);
        ParameterizedType type = (ParameterizedType) typeArguments[0];
        assertEquals(typeParameter, type.getActualTypeArguments()[0]);
        assertEquals(MultipleBoundedWildcardEquality.class, type.getRawType());

        Type[] parameterTypes = method.getGenericParameterTypes();
        assertLenghtOne(parameterTypes);
        Type parameter = parameterTypes[0];
        assertInstanceOf(ParameterizedType.class, parameter);
        ParameterizedType paramType = (ParameterizedType) parameter;
        Type[] actualTypeArguments = paramType.getActualTypeArguments();
        assertEquals(2, actualTypeArguments.length);

        Type firstArgument = actualTypeArguments[0];
        assertInstanceOf(WildcardType.class, firstArgument);
        WildcardType firstWildcardArgument = (WildcardType) firstArgument;
        Type secondArgument = actualTypeArguments[1];
        assertInstanceOf(WildcardType.class, secondArgument);
        WildcardType secondWildcardArgument = (WildcardType) secondArgument;

        assertEquals(firstWildcardArgument, secondWildcardArgument);

        Type[] firstWildcardArgumentUpperBounds = firstWildcardArgument.getUpperBounds();
        assertLenghtOne(firstWildcardArgumentUpperBounds);
        Type firstWildcardArgumentUpperBoundsType = firstWildcardArgumentUpperBounds[0];

        Type[] secondWildcardArgumentUpperBounds = secondWildcardArgument.getUpperBounds();
        assertLenghtOne(secondWildcardArgumentUpperBounds);
        Type secondWildcardArgumentLoweroundsType = secondWildcardArgumentUpperBounds[0];

        assertEquals(firstWildcardArgumentUpperBoundsType, secondWildcardArgumentLoweroundsType);
    }
}
