/* * 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 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. * *

Usage example:

{@code
 *      // using anonymous classes (preferred)
 *      TypeReference<Integer> intToken = new TypeReference<Integer>() {{ }};
 *
 *      // using named classes
 *      class IntTypeReference extends TypeReference<Integer> {...}
 *      TypeReference<Integer> intToken = new IntTypeReference();
 * }

* *

Unlike the reference implementation, this bans nested TypeVariables; that is all * dynamic types must equal to the static types.

* *

See * http://gafter.blogspot.com/2007/05/limitation-of-super-type-tokens.html * for more details.

*/ public abstract class TypeReference { 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 */ protected TypeReference() { ParameterizedType thisType = (ParameterizedType)getClass().getGenericSuperclass(); // extract the "T" from TypeReference mType = thisType.getActualTypeArguments()[0]; /* * Prohibit type references with type variables such as * * class GenericListToken extends TypeReference> * * 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 extends TypeReference { public SpecializedTypeReference(Class 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. * *

As with a regular type reference, the {@code klass} must not contain * any type variables.

* * @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 TypeReference createSpecializedTypeReference(Class klass) { return new SpecializedTypeReference(klass); } /** * Create a specialized type reference from a dynamic {@link Type} instance, * bypassing the standard compile-time checks. * *

As with a regular type reference, the {@code type} must not contain * any type variables.

* * @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 */ public static TypeReference createSpecializedTypeReference(Type type) { return new SpecializedBaseTypeReference(type); } /** * Returns the raw type of T. * *

    *
  • If T is a Class itself, T itself is returned. *
  • If T is a ParameterizedType, the raw type of the parameterized type is returned. *
  • If T is a GenericArrayType, the returned type is the corresponding array class. * For example: {@code List[]} => {@code List[]}. *
  • If T is a type variable or a wildcard type, the raw type of the first upper bound is * returned. For example: {@code } => {@code Foo}. *
* * @return the raw type of {@code T} */ @SuppressWarnings("unchecked") public final Class getRawType() { return (Class)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. * *

A TypeReference is only equal to another TypeReference if their captured type {@code T} * is also equal.

*/ @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. * *

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}).

* *

See * http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.4 * for a more formal definition of a type variable

. * * @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 - has a type variable * * This also covers the case of class Foo 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 { * 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" in the scope of a * * // no type variables here, T1-Tn are known at this definition * class X extends Foo * * // T1 is a type variable, T2-Tn are known at this definition * class X extends Foo */ 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 --> lower bound; does T have a type variable? * Foo --> 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" 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. * *

Empty and null arrays trivially have no type variables.

* * @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; } }