/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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 org.apache.webbeans.test.util;

import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Set;

import org.apache.webbeans.config.OwbParametrizedTypeImpl;
import org.apache.webbeans.util.GenericsUtil;
import org.junit.Assert;
import org.junit.Test;

public class GenericsUtilTest {
    @Test
    public void stackOverFlowProtection() throws Exception
    {
        final Type type = new OwbParametrizedTypeImpl(null, ByDefaultIllDoAStackOverFlowIngenericUtils.class, ByDefaultIllDoAStackOverFlowIngenericUtils.class.getTypeParameters());
        final Collection<Method> methods = new ArrayList<Method>(asList(ByDefaultIllDoAStackOverFlowIngenericUtils.class.getMethods()));
        methods.removeAll(asList(Object.class.getMethods()));
        final Type actualType = methods.iterator().next().getTypeParameters()[0];
        GenericsUtil.resolveTypes(new Type[] { actualType }, type);
    }

    @Test
    public void resolveType() throws NoSuchFieldException {
        Field field = AbstractObject.class.getDeclaredField("field");
        Assert.assertEquals(Object.class, GenericsUtil.resolveType(SimpleObject.class, field));
        assertEquals(String.class, GenericsUtil.resolveType(StringObject.class, field));

        Type t = GenericsUtil.resolveType(GenericObject.class, field);
        assertTrue(t instanceof TypeVariable);
        assertEquals("T", ((TypeVariable<?>)t).getName());

        Type number = GenericsUtil.resolveType(GenericNumberObject.class, field);
        assertTrue(number instanceof TypeVariable);
        assertEquals(Number.class, GenericsUtil.getRawType(number));

        assertEquals(Integer.class, GenericsUtil.resolveType(IntegerObject.class, field));
        GenericArrayType genericArrayType = (GenericArrayType)GenericsUtil.resolveType(GenericArrayObject.class, field);
        assertTrue(genericArrayType.getGenericComponentType() instanceof TypeVariable);
        assertEquals("V", ((TypeVariable)genericArrayType.getGenericComponentType()).getName());
        assertEquals(Object[].class, GenericsUtil.getRawType(genericArrayType));
        assertEquals(Long[].class, GenericsUtil.resolveType(LongArrayObject.class, field));
        
        Type subInterfaceType = GenericsUtil.resolveType(InterfaceObject.class, field);
        assertTrue(subInterfaceType instanceof TypeVariable);
        assertEquals(SubInterface.class, GenericsUtil.getRawType(subInterfaceType));

        assertEquals(JustAPlainClass.class, GenericsUtil.getRawType(JustAPlainClass.class));

        Set<Type> typeClosure = GenericsUtil.getTypeClosure(JustAPlainClass.class);
        assertEquals(2, typeClosure.size());
    }

    @Test
    public void testContainsWildcardTypes() throws Exception
    {
        Assert.assertFalse(GenericsUtil.containsWildcardType(StringObject.class));
        Assert.assertTrue(GenericsUtil.containsWildcardType(GenericNumberObject.class.getMethod("getObject").getGenericReturnType()));
        Assert.assertFalse(GenericsUtil.containsWildcardType(GenericObject.class.getMethod("getObject").getGenericReturnType()));
    }

    public static abstract class AbstractObject<V>
    {
    
        private V field;
    }

    private static class SimpleObject extends AbstractObject
    {

    }

    private static class StringObject extends AbstractObject<String>
    {

    }

    private static class GenericObject<T, V> extends AbstractObject<T>
    {

        public GenericObject<T, V> getObject()
        {
            return new GenericObject<T, V>();
        }

    }

    private static class GenericNumberObject<X, Y extends Number & Comparable<Integer>> extends AbstractObject<Y>
    {

        public GenericNumberObject<?, ? extends Comparable<Integer>> getObject()
        {
            return new GenericNumberObject<X, Integer>();
        }
    }

    private static class IntegerObject extends GenericNumberObject<String, Integer>
    {

    }

    private static class GenericArrayObject<V> extends GenericObject<V[], V>
    {

    }

    private static class LongArrayObject extends GenericArrayObject<Long>
    {

    }

    private static class InterfaceObject<V extends SubInterface & SuperInterface> extends AbstractObject<V>
    {

    }

    private interface SuperInterface
    {

    }

    private interface SubInterface extends SuperInterface
    {

    }

    private static class JustAPlainClass
    {

    }

    public static class Methods
    {
        public AbstractObject raw()
        {
            return new AbstractObject() {
            };
        }

        public <T> AbstractObject<T> generic()
        {
            return new AbstractObject<T>() {
            };
        }
    }

    public static class ByDefaultIllDoAStackOverFlowIngenericUtils<C, R>
    {
        public <P extends Comparable<? super P>> ByDefaultIllDoAStackOverFlowIngenericUtils<C, R> foo(
                final ByDefaultIllDoAStackOverFlowIngenericUtils<? super C, P> att, P value)
        {
            return this;
        }
    }
}
