/*
 * Copyright 2014-present Facebook, 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.facebook.buck.util;

import com.google.common.base.Optional;
import com.google.common.primitives.Primitives;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.Collection;

import javax.annotation.Nullable;

public class Types {

  private Types() {
    // Utility class.
  }

  /**
   * Determine the "base type" of a field. That is, the following will be returned:
   * <ul>
   *   <li>{@code String} -&gt; {@code String.class}
   *   <li>{@code Optional&lt;String&gt;} -&gt; {@code String.class}
   *   <li>{@code Set&lt;String&gt;} -&gt; {@code String.class}
   *   <li>{@code Collection&lt;? extends Comparable&gt;} -&gt; {@code Comparable.class}
   *   <li>{@code Collection&lt;? super Comparable} -&gt; {@code Object.class}
   * </ul>
   */
  public static Type getBaseType(Field field) {
    Type type = getFirstNonOptionalType(field);

    if (type instanceof ParameterizedType) {
      type = ((ParameterizedType) type).getActualTypeArguments()[0];
    }

    if (type instanceof WildcardType) {
      type = ((WildcardType) type).getUpperBounds()[0];
    }

    return Primitives.wrap((Class<?>) type);
  }

  /**
   * @return The raw type of the {@link Collection} a field represents, even if contained in an
   *    {@link Optional}, but without the ParameterizedType information.
   */
  @SuppressWarnings("unchecked")
  @Nullable
  public static Class<? extends Collection<?>> getContainerClass(Field field) {
    Type type = getFirstNonOptionalType(field);

    if (!(type instanceof ParameterizedType)) {
      return null;
    }

    Type rawType = ((ParameterizedType) type).getRawType();
    if (!(rawType instanceof Class)) {
      return null;
    }

    Class<?> clazz = (Class<?>) rawType;
    if (!(Collection.class.isAssignableFrom(clazz))) {
      return null;
    }

    return (Class<? extends Collection<?>>) clazz;
  }

  /**
   * Get the first complete {@link Type} in a signature that's non-optional, complete with the
   * information from the {@link ParameterizedType}.
   *
   * <ul>
   *   <li>String -&gt; String
   *   <li>Optional&lt;String$gt; -&gt; String
   *   <li>ImmutableSet&lt;String&gt; -&gt; ImmutableSet&lt;String&gt;
   *   <li>Optional&lt;ImmutableSet&lt;String&gt;&gt; -&gt; ImmutableSet&lt;String&gt;
   * </ul>
   */
  public static Type getFirstNonOptionalType(Field field) {
    boolean isOptional = Optional.class.isAssignableFrom(field.getType());
    if (isOptional) {
      Type type = field.getGenericType();

      if (type instanceof ParameterizedType) {
        return ((ParameterizedType) type).getActualTypeArguments()[0];
      } else {
        throw new RuntimeException("Unexpected type parameter for Optional: " + type);
      }
    } else {
      return field.getGenericType();
    }
  }
}

@@ -673,16 +673,150 @@
 tional;%0A
+import com.google.common.cache.CacheBuilder;%0Aimport com.google.common.cache.CacheLoader;%0Aimport com.google.common.cache.LoadingCache;%0A
 import c
@@ -1030,16 +1030,64 @@
 lection;
+%0Aimport java.util.concurrent.ExecutionException;
 %0A%0Aimport
@@ -1137,16 +1137,1016 @@
 ypes %7B%0A%0A
+  private static final LoadingCache%3CField, Type%3E FIRST_NON_OPTIONAL_TYPE_CACHE =%0A      CacheBuilder.newBuilder()%0A                  .weakValues()%0A                  .build(new CacheLoader%3CField, Type%3E() %7B%0A                    @Override%0A                    public Type load(Field field) throws Exception %7B%0A                      boolean isOptional = Optional.class.isAssignableFrom(field.getType());%0A                      if (isOptional) %7B%0A                        Type type = field.getGenericType();%0A%0A                        if (type instanceof ParameterizedType) %7B%0A                          return ((ParameterizedType) type).getActualTypeArguments()%5B0%5D;%0A                        %7D else %7B%0A                          throw new RuntimeException(%0A                              %22Unexpected type parameter for Optional: %22 + type);%0A                        %7D%0A                      %7D else %7B%0A                        return field.getGenericType();%0A                      %7D%0A                    %7D%0A                  %7D);%0A%0A
   privat
@@ -4248,279 +4248,106 @@
     
-boolean isOptional = Optional.class.isAssignableFrom(field.getType());%0A    if (isOptional) %7B%0A      Type type = field.getGenericType(
+try %7B%0A      return FIRST_NON_OPTIONAL_TYPE_CACHE.get(field
 );%0A
-%0A 
     
- if (type instanceof ParameterizedType) %7B%0A        return ((ParameterizedType) type).getActualTypeArguments()%5B0%5D;%0A      %7D else
+%7D catch (ExecutionException e)
  %7B%0A
-  
     
@@ -4379,115 +4379,9 @@
 ion(
-%22Unexpected type parameter for Optional: %22 + type);%0A      %7D%0A    %7D else %7B%0A      return field.getGenericType(
+e
 );%0A 

Add cache for getFirstNonOptionalType