1 // Copyright 2014 The Bazel Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package com.google.devtools.common.options; 15 16 import com.google.common.annotations.VisibleForTesting; 17 import com.google.common.primitives.Primitives; 18 import com.google.common.reflect.TypeToken; 19 20 import java.lang.reflect.Method; 21 import java.lang.reflect.ParameterizedType; 22 import java.lang.reflect.Type; 23 import java.lang.reflect.TypeVariable; 24 25 /** 26 * A helper class for {@link OptionsParserImpl} to help checking the return type 27 * of a {@link Converter} against the type of a field or the element type of a 28 * list. 29 * 30 * <p>This class has to go through considerable contortion to get the correct result 31 * from the Java reflection system, unfortunately. If the generic reflection part 32 * had been better designed, some of this would not be necessary. 33 */ 34 class GenericTypeHelper { 35 36 /** 37 * Returns the raw type of t, if t is either a raw or parameterized type. 38 * Otherwise, this method throws an {@link AssertionError}. 39 */ 40 @VisibleForTesting 41 static Class<?> getRawType(Type t) { 42 if (t instanceof Class<?>) { 43 return (Class<?>) t; 44 } else if (t instanceof ParameterizedType) { 45 return (Class<?>) ((ParameterizedType) t).getRawType(); 46 } else { 47 throw new AssertionError("A known concrete type is not concrete"); 48 } 49 } 50 51 /** 52 * If type is a parameterized type, searches the given type variable in the list 53 * of declared type variables, and then returns the corresponding actual type. 54 * Returns null if the type variable is not defined by type. 55 */ 56 private static Type matchTypeVariable(Type type, TypeVariable<?> variable) { 57 if (type instanceof ParameterizedType) { 58 Class<?> rawInterfaceType = getRawType(type); 59 TypeVariable<?>[] typeParameters = rawInterfaceType.getTypeParameters(); 60 for (int i = 0; i < typeParameters.length; i++) { 61 if (variable.equals(typeParameters[i])) { 62 return ((ParameterizedType) type).getActualTypeArguments()[i]; 63 } 64 } 65 } 66 return null; 67 } 68 69 /** 70 * Resolves the return type of a method, in particular if the generic return 71 * type ({@link Method#getGenericReturnType()}) is a type variable 72 * ({@link TypeVariable}), by checking all super-classes and directly 73 * implemented interfaces. 74 * 75 * <p>The method m must be defined by the given type or by its raw class type. 76 * 77 * @throws AssertionError if the generic return type could not be resolved 78 */ 79 // TODO(bazel-team): also check enclosing classes and indirectly implemented 80 // interfaces, which can also contribute type variables. This doesn't happen 81 // in the existing use cases. 82 public static Type getActualReturnType(Type type, Method method) { 83 Type returnType = method.getGenericReturnType(); 84 if (returnType instanceof Class<?>) { 85 return returnType; 86 } else if (returnType instanceof ParameterizedType) { 87 return returnType; 88 } else if (returnType instanceof TypeVariable<?>) { 89 TypeVariable<?> variable = (TypeVariable<?>) returnType; 90 while (type != null) { 91 Type candidate = matchTypeVariable(type, variable); 92 if (candidate != null) { 93 return candidate; 94 } 95 96 Class<?> rawType = getRawType(type); 97 for (Type interfaceType : rawType.getGenericInterfaces()) { 98 candidate = matchTypeVariable(interfaceType, variable); 99 if (candidate != null) { 100 return candidate; 101 } 102 } 103 104 type = rawType.getGenericSuperclass(); 105 } 106 } 107 throw new AssertionError("The type " + returnType 108 + " is not a Class, ParameterizedType, or TypeVariable"); 109 } 110 111 /** 112 * Determines if a value of a particular type (from) is assignable to a field of 113 * a particular type (to). Also allows assigning wrapper types to primitive 114 * types. 115 * 116 * <p>The checks done here should be identical to the checks done by 117 * {@link java.lang.reflect.Field#set}. I.e., if this method returns true, a 118 * subsequent call to {@link java.lang.reflect.Field#set} should succeed. 119 */ 120 public static boolean isAssignableFrom(Type to, Type from) { 121 if (to instanceof Class<?>) { 122 Class<?> toClass = (Class<?>) to; 123 if (toClass.isPrimitive()) { 124 return Primitives.wrap(toClass).equals(from); 125 } 126 } 127 return TypeToken.of(to).isSupertypeOf(from); 128 } 129 130 private GenericTypeHelper() { 131 // Prevents Java from creating a public constructor. 132 } 133 } 134