/*
 * Copyright (C) 2008 Google Inc.
 *
 * 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 com.google.gson;

import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;

/**
 * A static factory class used to construct the "TypeInfo" objects.
 *
 * @author Inderjeet Singh
 * @author Joel Leitch
 */
final class TypeInfoFactory {

  private TypeInfoFactory() {
    // Not instantiable since it provides factory methods only.
  }

  public static TypeInfoArray getTypeInfoForArray(Type type) {
    Preconditions.checkArgument(TypeUtils.isArray(type));
    return new TypeInfoArray(type);
  }

  /**
   * Evaluates the "actual" type for the field.  If the field is a "TypeVariable" or has a
   * "TypeVariable" in a parameterized type then it evaluates the real type.
   *
   * @param f the actual field object to retrieve the type from
   * @param typeDefiningF the type that contains the field {@code f}
   * @return the type information for the field
   */
  public static TypeInfo getTypeInfoForField(Field f, Type typeDefiningF) {
    Class<?> classDefiningF = TypeUtils.toRawClass(typeDefiningF);
    Type type = f.getGenericType();
    Type actualType = getActualType(type, typeDefiningF, classDefiningF);
    return new TypeInfo(actualType);
  }

  private static Type getActualType(
      Type typeToEvaluate, Type parentType, Class<?> rawParentClass) {
    if (typeToEvaluate instanceof Class) {
      return typeToEvaluate;
    } else if (typeToEvaluate instanceof ParameterizedType) {
      ParameterizedType castedType = (ParameterizedType) typeToEvaluate;
      Type owner = castedType.getOwnerType();
      Type[] actualTypeParameters =
          extractRealTypes(castedType.getActualTypeArguments(), parentType, rawParentClass);
      Type rawType = castedType.getRawType();
      return new ParameterizedTypeImpl(rawType, actualTypeParameters, owner);
    } else if (typeToEvaluate instanceof GenericArrayType) {
      GenericArrayType castedType = (GenericArrayType) typeToEvaluate;
      Type componentType = castedType.getGenericComponentType();
      Type actualType = getActualType(componentType, parentType, rawParentClass);
      if (componentType.equals(actualType)) {
        return castedType;
      } else {
        if (actualType instanceof Class) {
          return TypeUtils.wrapWithArray(TypeUtils.toRawClass(actualType));
        } else {
          return new GenericArrayTypeImpl(actualType);
        }
      }
    } else if (typeToEvaluate instanceof TypeVariable) {
      // The class definition has the actual types used for the type variables.
      // Find the matching actual type for the Type Variable used for the field.
      // For example, class Foo<A> { A a; }
      // new Foo<Integer>(); defines the actual type of A to be Integer.
      // So, to find the type of the field a, we will have to look at the class'
      // actual type arguments.
      TypeVariable<?> fieldTypeVariable = (TypeVariable<?>) typeToEvaluate;
      TypeVariable<?>[] classTypeVariables = rawParentClass.getTypeParameters();
      ParameterizedType objParameterizedType = (ParameterizedType) parentType;
      int indexOfActualTypeArgument = getIndex(classTypeVariables, fieldTypeVariable);
      Type[] actualTypeArguments = objParameterizedType.getActualTypeArguments();
      return actualTypeArguments[indexOfActualTypeArgument];
    } else if (typeToEvaluate instanceof WildcardType) {
      WildcardType castedType = (WildcardType) typeToEvaluate;
      return getActualType(castedType.getUpperBounds()[0], parentType, rawParentClass);
    } else {
      throw new IllegalArgumentException("Type \'" + typeToEvaluate + "\' is not a Class, "
          + "ParameterizedType, GenericArrayType or TypeVariable. Can't extract type.");
    }
  }

  private static Type[] extractRealTypes(
      Type[] actualTypeArguments, Type parentType, Class<?> rawParentClass) {
    Preconditions.checkNotNull(actualTypeArguments);

    Type[] retTypes = new Type[actualTypeArguments.length];
    for (int i = 0; i < actualTypeArguments.length; ++i) {
      retTypes[i] = getActualType(actualTypeArguments[i], parentType, rawParentClass);
    }
    return retTypes;
  }

  private static int getIndex(TypeVariable<?>[] types, TypeVariable<?> type) {
    for (int i = 0; i < types.length; ++i) {
      if (type.equals(types[i])) {
        return i;
      }
    }
    throw new IllegalStateException(
        "How can the type variable not be present in the class declaration!");
  }
}

@@ -3169,24 +3169,79 @@
 Variable) %7B%0A
+      if (parentType instanceof ParameterizedType) %7B%0A  
       // The
@@ -3308,16 +3308,18 @@
 iables.%0A
+  
       //
@@ -3393,24 +3393,26 @@
 ield.%0A      
+  
 // For examp
@@ -3443,16 +3443,18 @@
 %7D%0A      
+ 
+ 
 // new F
@@ -3512,16 +3512,18 @@
 nteger.%0A
+  
       //
@@ -3601,16 +3601,18 @@
 '%0A      
+  
 // actua
@@ -3631,24 +3631,26 @@
 ents.%0A      
+ 
+ 
 TypeVariable
@@ -3699,32 +3699,34 @@
 typeToEvaluate;%0A
+  
       TypeVariab
@@ -3792,24 +3792,26 @@
 rs();%0A      
+  
 Parameterize
@@ -3877,16 +3877,18 @@
 ;%0A      
+ 
+ 
 int inde
@@ -3956,24 +3956,26 @@
 eVariable);%0A
+  
       Type%5B%5D
@@ -4046,24 +4046,26 @@
 ts();%0A      
+  
 return actua
@@ -4107,16 +4107,317 @@
 ument%5D;%0A
+      %7D else %7B%0A        throw new UnsupportedOperationException(%22Expecting parameterized type, got %22 + parentType%0A            + %22.%5Cn Are you missing the use of TypeToken idiom?%5Cn See %22 %0A            + %22http://sites.google.com/site/gson/gson-user-guide#TOC-Serializing-and-Deserializing-Gener%22);%0A      %7D%0A
     %7D el

Throwing a helpful error in cases where the user forgot to use the TypeToken idiom.