content: /*
   * Copyright (C) 2014 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 android.hardware.camera2.utils;
  
  import android.annotation.UnsupportedAppUsage;
  import java.lang.reflect.Array;
  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;
  
  import static com.android.internal.util.Preconditions.*;
  
  /**
   * Super type token; allows capturing generic types at runtime by forcing them to be reified.
   *
   * <p>Usage example: <pre>{@code
   *      // using anonymous classes (preferred)
   *      TypeReference&lt;Integer> intToken = new TypeReference&lt;Integer>() {{ }};
   *
   *      // using named classes
   *      class IntTypeReference extends TypeReference&lt;Integer> {...}
   *      TypeReference&lt;Integer> intToken = new IntTypeReference();
   * }</p></pre>
   *
   * <p>Unlike the reference implementation, this bans nested TypeVariables; that is all
   * dynamic types must equal to the static types.</p>
   *
   * <p>See <a href="http://gafter.blogspot.com/2007/05/limitation-of-super-type-tokens.html">
   * http://gafter.blogspot.com/2007/05/limitation-of-super-type-tokens.html</a>
   * for more details.</p>
   */
  public abstract class TypeReference<T> {
      private final Type mType;
      private final int mHash;
  
      /**
       * Create a new type reference for {@code T}.
       *
       * @throws IllegalArgumentException if {@code T}'s actual type contains a type variable
       *
       * @see TypeReference
       */
      @UnsupportedAppUsage
      protected TypeReference() {
          ParameterizedType thisType = (ParameterizedType)getClass().getGenericSuperclass();
  
          // extract the "T" from TypeReference<T>
          mType = thisType.getActualTypeArguments()[0];
  
          /*
           * Prohibit type references with type variables such as
           *
           *    class GenericListToken<T> extends TypeReference<List<T>>
           *
           * Since the "T" there is not known without an instance of T, type equality would
           * consider *all* Lists equal regardless of T. Allowing this would defeat
           * some of the type safety of a type reference.
           */
          if (containsTypeVariable(mType)) {
              throw new IllegalArgumentException(
                      "Including a type variable in a type reference is not allowed");
          }
          mHash = mType.hashCode();
      }
  
      /**
       * Return the dynamic {@link Type} corresponding to the captured type {@code T}.
       */
      public Type getType() {
          return mType;
      }
  
      private TypeReference(Type type) {
          mType = type;
          if (containsTypeVariable(mType)) {
              throw new IllegalArgumentException(
                      "Including a type variable in a type reference is not allowed");
          }
          mHash = mType.hashCode();
      }
  
      private static class SpecializedTypeReference<T> extends TypeReference<T> {
          public SpecializedTypeReference(Class<T> klass) {
              super(klass);
          }
      }
  
      @SuppressWarnings("rawtypes")
      private static class SpecializedBaseTypeReference extends TypeReference {
          public SpecializedBaseTypeReference(Type type) {
              super(type);
          }
      }
  
      /**
       * Create a specialized type reference from a dynamic class instance,
       * bypassing the standard compile-time checks.
       *
       * <p>As with a regular type reference, the {@code klass} must not contain
       * any type variables.</p>
       *
       * @param klass a non-{@code null} {@link Class} instance
       *
       * @return a type reference which captures {@code T} at runtime
       *
       * @throws IllegalArgumentException if {@code T} had any type variables
       */
      public static <T> TypeReference<T> createSpecializedTypeReference(Class<T> klass) {
          return new SpecializedTypeReference<T>(klass);
      }
  
      /**
       * Create a specialized type reference from a dynamic {@link Type} instance,
       * bypassing the standard compile-time checks.
       *
       * <p>As with a regular type reference, the {@code type} must not contain
       * any type variables.</p>
       *
       * @param type a non-{@code null} {@link Type} instance
       *
       * @return a type reference which captures {@code T} at runtime
       *
       * @throws IllegalArgumentException if {@code type} had any type variables
       */
      @UnsupportedAppUsage
      public static TypeReference<?> createSpecializedTypeReference(Type type) {
          return new SpecializedBaseTypeReference(type);
      }
  
      /**
       * Returns the raw type of T.
       *
       * <p><ul>
       * <li>If T is a Class itself, T itself is returned.
       * <li>If T is a ParameterizedType, the raw type of the parameterized type is returned.
       * <li>If T is a GenericArrayType, the returned type is the corresponding array class.
       * For example: {@code List<Integer>[]} => {@code List[]}.
       * <li>If T is a type variable or a wildcard type, the raw type of the first upper bound is
       * returned. For example: {@code <X extends Foo>} => {@code Foo}.
       * </ul>
       *
       * @return the raw type of {@code T}
       */
      @SuppressWarnings("unchecked")
      public final Class<? super T> getRawType() {
          return (Class<? super T>)getRawType(mType);
      }
  
      private static final Class<?> getRawType(Type type) {
          if (type == null) {
              throw new NullPointerException("type must not be null");
          }
  
          if (type instanceof Class<?>) {
              return (Class<?>)type;
          } else if (type instanceof ParameterizedType) {
              return (Class<?>)(((ParameterizedType)type).getRawType());
          } else if (type instanceof GenericArrayType) {
              return getArrayClass(getRawType(((GenericArrayType)type).getGenericComponentType()));
          } else if (type instanceof WildcardType) {
              // Should be at most 1 upper bound, but treat it like an array for simplicity
              return getRawType(((WildcardType) type).getUpperBounds());
          } else if (type instanceof TypeVariable) {
              throw new AssertionError("Type variables are not allowed in type references");
          } else {
              // Impossible
              throw new AssertionError("Unhandled branch to get raw type for type " + type);
          }
      }
  
      private static final Class<?> getRawType(Type[] types) {
          if (types == null) {
              return null;
          }
  
          for (Type type : types) {
              Class<?> klass = getRawType(type);
              if (klass !=  null) {
                  return klass;
              }
          }
  
          return null;
      }
  
      private static final Class<?> getArrayClass(Class<?> componentType) {
          return Array.newInstance(componentType, 0).getClass();
      }
  
      /**
       * Get the component type, e.g. {@code T} from {@code T[]}.
       *
       * @return component type, or {@code null} if {@code T} is not an array
       */
      public TypeReference<?> getComponentType() {
          Type componentType = getComponentType(mType);
  
          return (componentType != null) ?
                  createSpecializedTypeReference(componentType) :
                  null;
      }
  
      private static Type getComponentType(Type type) {
          checkNotNull(type, "type must not be null");
  
          if (type instanceof Class<?>) {
              return ((Class<?>) type).getComponentType();
          } else if (type instanceof ParameterizedType) {
              return null;
          } else if (type instanceof GenericArrayType) {
              return ((GenericArrayType)type).getGenericComponentType();
          } else if (type instanceof WildcardType) {
              // Should be at most 1 upper bound, but treat it like an array for simplicity
              throw new UnsupportedOperationException("TODO: support wild card components");
          } else if (type instanceof TypeVariable) {
              throw new AssertionError("Type variables are not allowed in type references");
          } else {
              // Impossible
              throw new AssertionError("Unhandled branch to get component type for type " + type);
          }
      }
  
      /**
       * Compare two objects for equality.
       *
       * <p>A TypeReference is only equal to another TypeReference if their captured type {@code T}
       * is also equal.</p>
       */
      @Override
      public boolean equals(Object o) {
          // Note that this comparison could inaccurately return true when comparing types
          // with nested type variables; therefore we ban type variables in the constructor.
          return o instanceof TypeReference<?> && mType.equals(((TypeReference<?>)o).mType);
      }
  
      /**
       * {@inheritDoc}
       */
      @Override
      public int hashCode() {
          return mHash;
      }
  
      /**
       * Check if the {@code type} contains a {@link TypeVariable} recursively.
       *
       * <p>Intuitively, a type variable is a type in a type expression that refers to a generic
       * type which is not known at the definition of the expression (commonly seen when
       * type parameters are used, e.g. {@code class Foo<T>}).</p>
       *
       * <p>See <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.4">
       * http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.4</a>
       * for a more formal definition of a type variable</p>.
       *
       * @param type a type object ({@code null} is allowed)
       * @return {@code true} if there were nested type variables; {@code false} otherwise
       */
      public static boolean containsTypeVariable(Type type) {
          if (type == null) {
              // Trivially false
              return false;
          } else if (type instanceof TypeVariable<?>) {
              /*
               * T -> trivially true
               */
              return true;
          } else if (type instanceof Class<?>) {
              /*
               * class Foo -> no type variable
               * class Foo<T> - has a type variable
               *
               * This also covers the case of class Foo<T> extends ... / implements ...
               * since everything on the right hand side would either include a type variable T
               * or have no type variables.
               */
              Class<?> klass = (Class<?>)type;
  
              // Empty array => class is not generic
              if (klass.getTypeParameters().length != 0) {
                  return true;
              } else {
                  // Does the outer class(es) contain any type variables?
  
                  /*
                   * class Outer<T> {
                   *   class Inner {
                   *      T field;
                   *   }
                   * }
                   *
                   * In this case 'Inner' has no type parameters itself, but it still has a type
                   * variable as part of the type definition.
                   */
                  return containsTypeVariable(klass.getDeclaringClass());
              }
          } else if (type instanceof ParameterizedType) {
              /*
               * This is the "Foo<T1, T2, T3, ... Tn>" in the scope of a
               *
               *      // no type variables here, T1-Tn are known at this definition
               *      class X extends Foo<T1, T2, T3, ... Tn>
               *
               *      // T1 is a type variable, T2-Tn are known at this definition
               *      class X<T1> extends Foo<T1, T2, T3, ... Tn>
               */
              ParameterizedType p = (ParameterizedType) type;
  
              // This needs to be recursively checked
              for (Type arg : p.getActualTypeArguments()) {
                  if (containsTypeVariable(arg)) {
                      return true;
                  }
              }
  
              return false;
          } else if (type instanceof WildcardType) {
              WildcardType wild = (WildcardType) type;
  
              /*
               * This is is the "?" inside of a
               *
               *       Foo<?> --> unbounded; trivially no type variables
               *       Foo<? super T> --> lower bound; does T have a type variable?
               *       Foo<? extends T> --> upper bound; does T have a type variable?
               */
  
              /*
               *  According to JLS 4.5.1
               *  (http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#4.5.1):
               *
               *  - More than 1 lower/upper bound is illegal
               *  - Both a lower and upper bound is illegal
               *
               *  However, we use this 'array OR array' approach for readability
               */
              return containsTypeVariable(wild.getLowerBounds()) ||
                      containsTypeVariable(wild.getUpperBounds());
          }
  
          return false;
      }
  
      /**
       * {@inheritDoc}
       */
      @Override
      public String toString() {
          StringBuilder builder = new StringBuilder();
          builder.append("TypeReference<");
          toString(getType(), builder);
          builder.append(">");
  
          return builder.toString();
      }
  
      private static void toString(Type type, StringBuilder out) {
          if (type == null) {
              return;
          } else if (type instanceof TypeVariable<?>) {
              // T
              out.append(((TypeVariable<?>)type).getName());
          } else if (type instanceof Class<?>) {
              Class<?> klass = (Class<?>)type;
  
              out.append(klass.getName());
              toString(klass.getTypeParameters(), out);
          } else if (type instanceof ParameterizedType) {
               // "Foo<T1, T2, T3, ... Tn>"
              ParameterizedType p = (ParameterizedType) type;
  
              out.append(((Class<?>)p.getRawType()).getName());
              toString(p.getActualTypeArguments(), out);
          } else if (type instanceof GenericArrayType) {
              GenericArrayType gat = (GenericArrayType)type;
  
              toString(gat.getGenericComponentType(), out);
              out.append("[]");
          } else { // WildcardType, BoundedType
              // TODO:
              out.append(type.toString());
          }
      }
  
      private static void toString(Type[] types, StringBuilder out) {
          if (types == null) {
              return;
          } else if (types.length == 0) {
              return;
          }
  
          out.append("<");
  
          for (int i = 0; i < types.length; ++i) {
              toString(types[i], out);
              if (i != types.length - 1) {
                  out.append(", ");
              }
          }
  
          out.append(">");
      }
  
      /**
       * Check if any of the elements in this array contained a type variable.
       *
       * <p>Empty and null arrays trivially have no type variables.</p>
       *
       * @param typeArray an array ({@code null} is ok) of types
       * @return true if any elements contained a type variable; false otherwise
       */
      private static boolean containsTypeVariable(Type[] typeArray) {
          if (typeArray == null) {
              return false;
          }
  
          for (Type type : typeArray) {
              if (containsTypeVariable(type)) {
                  return true;
              }
          }
  
          return false;
      }
  }
  