/*
 * Copyright 2018, TeamDev. All rights reserved.
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package io.spine.reflect;

import com.google.common.reflect.TypeParameter;
import com.google.common.reflect.TypeToken;

import javax.annotation.CheckReturnValue;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Utility class for working with {@code Type}s.
 *
 * @author Illia Shepilov
 */
public final class Types {

    /** Prevent instantiation of this utility class. */
    private Types() {
    }

    /**
     * Creates the parametrized {@code Type} of the map.
     *
     * @param keyClass   the class of keys are maintained by this map
     * @param valueClass the class  of mapped values
     * @param <K>        the type of keys are maintained by this map
     * @param <V>        the type of the values stored in this map
     * @return the type of the map
     */
    public static <K, V> Type mapTypeOf(Class<K> keyClass, Class<V> valueClass) {
        checkNotNull(keyClass);
        checkNotNull(valueClass);

        Type type = new TypeToken<Map<K, V>>() {}.where(new TypeParameter<K>() {}, keyClass)
                                                       .where(new TypeParameter<V>() {}, valueClass)
                                                       .getType();
        return type;
    }

    /**
     * Creates the parametrized {@code Type} of the list.
     *
     * @param elementClass the class of the list elements
     * @param <T>          the type of the elements in this list
     * @return the type of the list
     */
    public static <T> Type listTypeOf(Class<T> elementClass) {
        checkNotNull(elementClass);

        Type type =
                new TypeToken<List<T>>() {}.where(new TypeParameter<T>() {}, elementClass)
                                                 .getType();
        return type;
    }

    /**
     * Obtains the class of a generic type argument which is specified in the inheritance chain
     * of the passed class.
     *
     * @param cls               the end class for which we find the generic argument
     * @param genericSuperclass the superclass of the passed which has generic parameters
     * @param argNumber         the index of the generic parameter in the superclass
     * @param <T>               the type of superclass
     * @return the class of the generic type argument
     */
    @CheckReturnValue
    static <T> Class<?> getArgument(Class<? extends T> cls,
                                    Class<T> genericSuperclass,
                                    int argNumber) {
        checkNotNull(cls);
        checkNotNull(genericSuperclass);

        TypeToken<?> supertypeToken = TypeToken.of(cls)
                                               .getSupertype(genericSuperclass);
        ParameterizedType genericSupertype =
                (ParameterizedType) supertypeToken.getType();
        Type[] typeArguments = genericSupertype.getActualTypeArguments();
        Type typeArgument = typeArguments[argNumber];
        @SuppressWarnings("unchecked") // The type is ensured by the calling code.
                Class<?> result = (Class<?>) typeArgument;
        return result;
    }
}

@@ -2068,32 +2068,57 @@
 ll(valueClass);%0A
+        // @formatter:off
 %0A        Type ty
@@ -2147,24 +2147,41 @@
 %3CK, V%3E%3E() %7B%7D
+%0A                
 .where(new T
@@ -2216,47 +2216,8 @@
 ss)%0A
-                                       
     
@@ -2294,58 +2294,44 @@
     
-                                       .getType();
+.getType();%0A        // @formatter:on
 %0A   
@@ -2690,33 +2690,32 @@
 mentClass);%0A
-%0A
         
 Type type =%0A
@@ -2706,35 +2706,45 @@
     
-Type type =%0A               
+// @formatter:off%0A        Type type =
  new
@@ -2767,16 +2767,37 @@
 T%3E%3E() %7B%7D
+%0A                    
 .where(n
@@ -2860,48 +2860,44 @@
     
-                             .getType();
+.getType();%0A        // @formatter:on
 %0A   
@@ -3704,17 +3704,16 @@
 class);%0A
-%0A
         
@@ -3741,16 +3741,32 @@
 eToken =
+%0A               
  TypeTok
@@ -3776,38 +3776,16 @@
 of(cls)%0A
-                      
         

Fix formatting