1 /* 2 * Copyright (C) 2011 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.reflect; 18 19 import static com.google.common.base.Preconditions.checkArgument; 20 import static com.google.common.base.Preconditions.checkNotNull; 21 import static com.google.common.collect.Iterables.transform; 22 23 import com.google.common.annotations.VisibleForTesting; 24 import com.google.common.base.Function; 25 import com.google.common.base.Joiner; 26 import com.google.common.base.Objects; 27 import com.google.common.base.Predicates; 28 import com.google.common.collect.ImmutableList; 29 import com.google.common.collect.ImmutableMap; 30 import com.google.common.collect.Iterables; 31 32 import java.io.Serializable; 33 import java.lang.reflect.AnnotatedElement; 34 import java.lang.reflect.Array; 35 import java.lang.reflect.GenericArrayType; 36 import java.lang.reflect.GenericDeclaration; 37 import java.lang.reflect.InvocationHandler; 38 import java.lang.reflect.InvocationTargetException; 39 import java.lang.reflect.Method; 40 import java.lang.reflect.ParameterizedType; 41 import java.lang.reflect.Proxy; 42 import java.lang.reflect.Type; 43 import java.lang.reflect.TypeVariable; 44 import java.lang.reflect.WildcardType; 45 import java.security.AccessControlException; 46 import java.util.Arrays; 47 import java.util.Collection; 48 import java.util.concurrent.atomic.AtomicReference; 49 50 import javax.annotation.Nullable; 51 52 /** 53 * Utilities for working with {@link Type}. 54 * 55 * @author Ben Yu 56 */ 57 final class Types { 58 59 /** Class#toString without the "class " and "interface " prefixes */ 60 private static final Function<Type, String> TYPE_NAME = 61 new Function<Type, String>() { 62 @Override public String apply(Type from) { 63 return JavaVersion.CURRENT.typeName(from); 64 } 65 }; 66 67 private static final Joiner COMMA_JOINER = Joiner.on(", ").useForNull("null"); 68 69 /** Returns the array type of {@code componentType}. */ newArrayType(Type componentType)70 static Type newArrayType(Type componentType) { 71 if (componentType instanceof WildcardType) { 72 WildcardType wildcard = (WildcardType) componentType; 73 Type[] lowerBounds = wildcard.getLowerBounds(); 74 checkArgument(lowerBounds.length <= 1, "Wildcard cannot have more than one lower bounds."); 75 if (lowerBounds.length == 1) { 76 return supertypeOf(newArrayType(lowerBounds[0])); 77 } else { 78 Type[] upperBounds = wildcard.getUpperBounds(); 79 checkArgument(upperBounds.length == 1, "Wildcard should have only one upper bound."); 80 return subtypeOf(newArrayType(upperBounds[0])); 81 } 82 } 83 return JavaVersion.CURRENT.newArrayType(componentType); 84 } 85 86 /** 87 * Returns a type where {@code rawType} is parameterized by 88 * {@code arguments} and is owned by {@code ownerType}. 89 */ newParameterizedTypeWithOwner( @ullable Type ownerType, Class<?> rawType, Type... arguments)90 static ParameterizedType newParameterizedTypeWithOwner( 91 @Nullable Type ownerType, Class<?> rawType, Type... arguments) { 92 if (ownerType == null) { 93 return newParameterizedType(rawType, arguments); 94 } 95 // ParameterizedTypeImpl constructor already checks, but we want to throw NPE before IAE 96 checkNotNull(arguments); 97 checkArgument(rawType.getEnclosingClass() != null, "Owner type for unenclosed %s", rawType); 98 return new ParameterizedTypeImpl(ownerType, rawType, arguments); 99 } 100 101 /** 102 * Returns a type where {@code rawType} is parameterized by 103 * {@code arguments}. 104 */ newParameterizedType(Class<?> rawType, Type... arguments)105 static ParameterizedType newParameterizedType(Class<?> rawType, Type... arguments) { 106 return new ParameterizedTypeImpl( 107 ClassOwnership.JVM_BEHAVIOR.getOwnerType(rawType), rawType, arguments); 108 } 109 110 /** Decides what owner type to use for constructing {@link ParameterizedType} from a raw class. */ 111 private enum ClassOwnership { 112 113 OWNED_BY_ENCLOSING_CLASS { 114 @Nullable 115 @Override getOwnerType(Class<?> rawType)116 Class<?> getOwnerType(Class<?> rawType) { 117 return rawType.getEnclosingClass(); 118 } 119 }, 120 LOCAL_CLASS_HAS_NO_OWNER { 121 @Nullable 122 @Override getOwnerType(Class<?> rawType)123 Class<?> getOwnerType(Class<?> rawType) { 124 if (rawType.isLocalClass()) { 125 return null; 126 } else { 127 return rawType.getEnclosingClass(); 128 } 129 } 130 }; 131 getOwnerType(Class<?> rawType)132 @Nullable abstract Class<?> getOwnerType(Class<?> rawType); 133 134 static final ClassOwnership JVM_BEHAVIOR = detectJvmBehavior(); 135 detectJvmBehavior()136 private static ClassOwnership detectJvmBehavior() { 137 class LocalClass<T> {} 138 Class<?> subclass = new LocalClass<String>() {}.getClass(); 139 ParameterizedType parameterizedType = (ParameterizedType) 140 subclass.getGenericSuperclass(); 141 for (ClassOwnership behavior : ClassOwnership.values()) { 142 if (behavior.getOwnerType(LocalClass.class) == parameterizedType.getOwnerType()) { 143 return behavior; 144 } 145 } 146 throw new AssertionError(); 147 } 148 } 149 150 /** 151 * Returns a new {@link TypeVariable} that belongs to {@code declaration} with 152 * {@code name} and {@code bounds}. 153 */ newArtificialTypeVariable( D declaration, String name, Type... bounds)154 static <D extends GenericDeclaration> TypeVariable<D> newArtificialTypeVariable( 155 D declaration, String name, Type... bounds) { 156 return newTypeVariableImpl( 157 declaration, 158 name, 159 (bounds.length == 0) 160 ? new Type[] { Object.class } 161 : bounds); 162 } 163 164 /** Returns a new {@link WildcardType} with {@code upperBound}. */ subtypeOf(Type upperBound)165 @VisibleForTesting static WildcardType subtypeOf(Type upperBound) { 166 return new WildcardTypeImpl(new Type[0], new Type[] { upperBound }); 167 } 168 169 /** Returns a new {@link WildcardType} with {@code lowerBound}. */ supertypeOf(Type lowerBound)170 @VisibleForTesting static WildcardType supertypeOf(Type lowerBound) { 171 return new WildcardTypeImpl(new Type[] { lowerBound }, new Type[] { Object.class }); 172 } 173 174 /** 175 * Returns human readable string representation of {@code type}. 176 * <ul> 177 * <li> For array type {@code Foo[]}, {@code "com.mypackage.Foo[]"} are 178 * returned. 179 * <li> For any class, {@code theClass.getName()} are returned. 180 * <li> For all other types, {@code type.toString()} are returned. 181 * </ul> 182 */ toString(Type type)183 static String toString(Type type) { 184 return (type instanceof Class) 185 ? ((Class<?>) type).getName() 186 : type.toString(); 187 } 188 getComponentType(Type type)189 @Nullable static Type getComponentType(Type type) { 190 checkNotNull(type); 191 final AtomicReference<Type> result = new AtomicReference<Type>(); 192 new TypeVisitor() { 193 @Override void visitTypeVariable(TypeVariable<?> t) { 194 result.set(subtypeOfComponentType(t.getBounds())); 195 } 196 @Override void visitWildcardType(WildcardType t) { 197 result.set(subtypeOfComponentType(t.getUpperBounds())); 198 } 199 @Override void visitGenericArrayType(GenericArrayType t) { 200 result.set(t.getGenericComponentType()); 201 } 202 @Override void visitClass(Class<?> t) { 203 result.set(t.getComponentType()); 204 } 205 }.visit(type); 206 return result.get(); 207 } 208 209 /** 210 * Returns {@code ? extends X} if any of {@code bounds} is a subtype of {@code X[]}; or null 211 * otherwise. 212 */ subtypeOfComponentType(Type[] bounds)213 @Nullable private static Type subtypeOfComponentType(Type[] bounds) { 214 for (Type bound : bounds) { 215 Type componentType = getComponentType(bound); 216 if (componentType != null) { 217 // Only the first bound can be a class or array. 218 // Bounds after the first can only be interfaces. 219 if (componentType instanceof Class) { 220 Class<?> componentClass = (Class<?>) componentType; 221 if (componentClass.isPrimitive()) { 222 return componentClass; 223 } 224 } 225 return subtypeOf(componentType); 226 } 227 } 228 return null; 229 } 230 231 private static final class GenericArrayTypeImpl 232 implements GenericArrayType, Serializable { 233 234 private final Type componentType; 235 GenericArrayTypeImpl(Type componentType)236 GenericArrayTypeImpl(Type componentType) { 237 this.componentType = JavaVersion.CURRENT.usedInGenericType(componentType); 238 } 239 getGenericComponentType()240 @Override public Type getGenericComponentType() { 241 return componentType; 242 } 243 toString()244 @Override public String toString() { 245 return Types.toString(componentType) + "[]"; 246 } 247 hashCode()248 @Override public int hashCode() { 249 return componentType.hashCode(); 250 } 251 equals(Object obj)252 @Override public boolean equals(Object obj) { 253 if (obj instanceof GenericArrayType) { 254 GenericArrayType that = (GenericArrayType) obj; 255 return Objects.equal( 256 getGenericComponentType(), that.getGenericComponentType()); 257 } 258 return false; 259 } 260 261 private static final long serialVersionUID = 0; 262 } 263 264 private static final class ParameterizedTypeImpl 265 implements ParameterizedType, Serializable { 266 267 private final Type ownerType; 268 private final ImmutableList<Type> argumentsList; 269 private final Class<?> rawType; 270 ParameterizedTypeImpl( @ullable Type ownerType, Class<?> rawType, Type[] typeArguments)271 ParameterizedTypeImpl( 272 @Nullable Type ownerType, Class<?> rawType, Type[] typeArguments) { 273 checkNotNull(rawType); 274 checkArgument(typeArguments.length == rawType.getTypeParameters().length); 275 disallowPrimitiveType(typeArguments, "type parameter"); 276 this.ownerType = ownerType; 277 this.rawType = rawType; 278 this.argumentsList = JavaVersion.CURRENT.usedInGenericType(typeArguments); 279 } 280 getActualTypeArguments()281 @Override public Type[] getActualTypeArguments() { 282 return toArray(argumentsList); 283 } 284 getRawType()285 @Override public Type getRawType() { 286 return rawType; 287 } 288 getOwnerType()289 @Override public Type getOwnerType() { 290 return ownerType; 291 } 292 toString()293 @Override public String toString() { 294 StringBuilder builder = new StringBuilder(); 295 if (ownerType != null) { 296 builder.append(JavaVersion.CURRENT.typeName(ownerType)).append('.'); 297 } 298 builder.append(rawType.getName()) 299 .append('<') 300 .append(COMMA_JOINER.join(transform(argumentsList, TYPE_NAME))) 301 .append('>'); 302 return builder.toString(); 303 } 304 hashCode()305 @Override public int hashCode() { 306 return (ownerType == null ? 0 : ownerType.hashCode()) 307 ^ argumentsList.hashCode() ^ rawType.hashCode(); 308 } 309 equals(Object other)310 @Override public boolean equals(Object other) { 311 if (!(other instanceof ParameterizedType)) { 312 return false; 313 } 314 ParameterizedType that = (ParameterizedType) other; 315 return getRawType().equals(that.getRawType()) 316 && Objects.equal(getOwnerType(), that.getOwnerType()) 317 && Arrays.equals( 318 getActualTypeArguments(), that.getActualTypeArguments()); 319 } 320 321 private static final long serialVersionUID = 0; 322 } 323 newTypeVariableImpl( D genericDeclaration, String name, Type[] bounds)324 private static <D extends GenericDeclaration> TypeVariable<D> newTypeVariableImpl( 325 D genericDeclaration, String name, Type[] bounds) { 326 TypeVariableImpl<D> typeVariableImpl = 327 new TypeVariableImpl<D>(genericDeclaration, name, bounds); 328 @SuppressWarnings("unchecked") 329 TypeVariable<D> typeVariable = Reflection.newProxy( 330 TypeVariable.class, new TypeVariableInvocationHandler(typeVariableImpl)); 331 return typeVariable; 332 } 333 334 /** 335 * Invocation handler to work around a compatibility problem between Java 7 and Java 8. 336 * 337 * <p>Java 8 introduced a new method {@code getAnnotatedBounds()} in the {@link TypeVariable} 338 * interface, whose return type {@code AnnotatedType[]} is also new in Java 8. That means that we 339 * cannot implement that interface in source code in a way that will compile on both Java 7 and 340 * Java 8. If we include the {@code getAnnotatedBounds()} method then its return type means 341 * it won't compile on Java 7, while if we don't include the method then the compiler will 342 * complain that an abstract method is unimplemented. So instead we use a dynamic proxy to 343 * get an implementation. If the method being called on the {@code TypeVariable} instance has 344 * the same name as one of the public methods of {@link TypeVariableImpl}, the proxy calls 345 * the same method on its instance of {@code TypeVariableImpl}. Otherwise it throws {@link 346 * UnsupportedOperationException}; this should only apply to {@code getAnnotatedBounds()}. This 347 * does mean that users on Java 8 who obtain an instance of {@code TypeVariable} from {@link 348 * TypeResolver#resolveType} will not be able to call {@code getAnnotatedBounds()} on it, but that 349 * should hopefully be rare. 350 * 351 * <p>This workaround should be removed at a distant future time when we no longer support Java 352 * versions earlier than 8. 353 */ 354 private static final class TypeVariableInvocationHandler implements InvocationHandler { 355 private static final ImmutableMap<String, Method> typeVariableMethods; 356 static { 357 ImmutableMap.Builder<String, Method> builder = ImmutableMap.builder(); 358 for (Method method : TypeVariableImpl.class.getMethods()) { 359 if (method.getDeclaringClass().equals(TypeVariableImpl.class)) { 360 try { 361 method.setAccessible(true); 362 } catch (AccessControlException e) { 363 // OK: the method is accessible to us anyway. The setAccessible call is only for 364 // unusual execution environments where that might not be true. 365 } method.getName()366 builder.put(method.getName(), method); 367 } 368 } 369 typeVariableMethods = builder.build(); 370 } 371 372 private final TypeVariableImpl<?> typeVariableImpl; 373 TypeVariableInvocationHandler(TypeVariableImpl<?> typeVariableImpl)374 TypeVariableInvocationHandler(TypeVariableImpl<?> typeVariableImpl) { 375 this.typeVariableImpl = typeVariableImpl; 376 } 377 invoke(Object proxy, Method method, Object[] args)378 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 379 String methodName = method.getName(); 380 Method typeVariableMethod = typeVariableMethods.get(methodName); 381 if (typeVariableMethod == null) { 382 throw new UnsupportedOperationException(methodName); 383 } else { 384 try { 385 return typeVariableMethod.invoke(typeVariableImpl, args); 386 } catch (InvocationTargetException e) { 387 throw e.getCause(); 388 } 389 } 390 } 391 } 392 393 private static final class TypeVariableImpl<D extends GenericDeclaration> { 394 395 private final D genericDeclaration; 396 private final String name; 397 private final ImmutableList<Type> bounds; 398 TypeVariableImpl(D genericDeclaration, String name, Type[] bounds)399 TypeVariableImpl(D genericDeclaration, String name, Type[] bounds) { 400 disallowPrimitiveType(bounds, "bound for type variable"); 401 this.genericDeclaration = checkNotNull(genericDeclaration); 402 this.name = checkNotNull(name); 403 this.bounds = ImmutableList.copyOf(bounds); 404 } 405 getBounds()406 public Type[] getBounds() { 407 return toArray(bounds); 408 } 409 getGenericDeclaration()410 public D getGenericDeclaration() { 411 return genericDeclaration; 412 } 413 getName()414 public String getName() { 415 return name; 416 } 417 getTypeName()418 public String getTypeName() { 419 return name; 420 } 421 toString()422 @Override public String toString() { 423 return name; 424 } 425 hashCode()426 @Override public int hashCode() { 427 return genericDeclaration.hashCode() ^ name.hashCode(); 428 } 429 equals(Object obj)430 @Override public boolean equals(Object obj) { 431 if (NativeTypeVariableEquals.NATIVE_TYPE_VARIABLE_ONLY) { 432 // equal only to our TypeVariable implementation with identical bounds 433 if (obj != null 434 && Proxy.isProxyClass(obj.getClass()) 435 && Proxy.getInvocationHandler(obj) instanceof TypeVariableInvocationHandler) { 436 TypeVariableInvocationHandler typeVariableInvocationHandler = 437 (TypeVariableInvocationHandler) Proxy.getInvocationHandler(obj); 438 TypeVariableImpl<?> that = typeVariableInvocationHandler.typeVariableImpl; 439 return name.equals(that.getName()) 440 && genericDeclaration.equals(that.getGenericDeclaration()) 441 && bounds.equals(that.bounds); 442 } 443 return false; 444 } else { 445 // equal to any TypeVariable implementation regardless of bounds 446 if (obj instanceof TypeVariable) { 447 TypeVariable<?> that = (TypeVariable<?>) obj; 448 return name.equals(that.getName()) 449 && genericDeclaration.equals(that.getGenericDeclaration()); 450 } 451 return false; 452 } 453 } 454 } 455 456 static final class WildcardTypeImpl implements WildcardType, Serializable { 457 458 private final ImmutableList<Type> lowerBounds; 459 private final ImmutableList<Type> upperBounds; 460 WildcardTypeImpl(Type[] lowerBounds, Type[] upperBounds)461 WildcardTypeImpl(Type[] lowerBounds, Type[] upperBounds) { 462 disallowPrimitiveType(lowerBounds, "lower bound for wildcard"); 463 disallowPrimitiveType(upperBounds, "upper bound for wildcard"); 464 this.lowerBounds = JavaVersion.CURRENT.usedInGenericType(lowerBounds); 465 this.upperBounds = JavaVersion.CURRENT.usedInGenericType(upperBounds); 466 } 467 getLowerBounds()468 @Override public Type[] getLowerBounds() { 469 return toArray(lowerBounds); 470 } 471 getUpperBounds()472 @Override public Type[] getUpperBounds() { 473 return toArray(upperBounds); 474 } 475 equals(Object obj)476 @Override public boolean equals(Object obj) { 477 if (obj instanceof WildcardType) { 478 WildcardType that = (WildcardType) obj; 479 return lowerBounds.equals(Arrays.asList(that.getLowerBounds())) 480 && upperBounds.equals(Arrays.asList(that.getUpperBounds())); 481 } 482 return false; 483 } 484 hashCode()485 @Override public int hashCode() { 486 return lowerBounds.hashCode() ^ upperBounds.hashCode(); 487 } 488 toString()489 @Override public String toString() { 490 StringBuilder builder = new StringBuilder("?"); 491 for (Type lowerBound : lowerBounds) { 492 builder.append(" super ").append(JavaVersion.CURRENT.typeName(lowerBound)); 493 } 494 for (Type upperBound : filterUpperBounds(upperBounds)) { 495 builder.append(" extends ").append(JavaVersion.CURRENT.typeName(upperBound)); 496 } 497 return builder.toString(); 498 } 499 500 private static final long serialVersionUID = 0; 501 } 502 toArray(Collection<Type> types)503 private static Type[] toArray(Collection<Type> types) { 504 return types.toArray(new Type[types.size()]); 505 } 506 filterUpperBounds(Iterable<Type> bounds)507 private static Iterable<Type> filterUpperBounds(Iterable<Type> bounds) { 508 return Iterables.filter( 509 bounds, Predicates.not(Predicates.<Type>equalTo(Object.class))); 510 } 511 disallowPrimitiveType(Type[] types, String usedAs)512 private static void disallowPrimitiveType(Type[] types, String usedAs) { 513 for (Type type : types) { 514 if (type instanceof Class) { 515 Class<?> cls = (Class<?>) type; 516 checkArgument(!cls.isPrimitive(), 517 "Primitive type '%s' used as %s", cls, usedAs); 518 } 519 } 520 } 521 522 /** Returns the {@code Class} object of arrays with {@code componentType}. */ getArrayClass(Class<?> componentType)523 static Class<?> getArrayClass(Class<?> componentType) { 524 // TODO(user): This is not the most efficient way to handle generic 525 // arrays, but is there another way to extract the array class in a 526 // non-hacky way (i.e. using String value class names- "[L...")? 527 return Array.newInstance(componentType, 0).getClass(); 528 } 529 530 // TODO(benyu): Once we are on Java 8, delete this abstraction 531 enum JavaVersion { 532 533 JAVA6 { newArrayType(Type componentType)534 @Override GenericArrayType newArrayType(Type componentType) { 535 return new GenericArrayTypeImpl(componentType); 536 } usedInGenericType(Type type)537 @Override Type usedInGenericType(Type type) { 538 checkNotNull(type); 539 if (type instanceof Class) { 540 Class<?> cls = (Class<?>) type; 541 if (cls.isArray()) { 542 return new GenericArrayTypeImpl(cls.getComponentType()); 543 } 544 } 545 return type; 546 } 547 }, 548 JAVA7 { newArrayType(Type componentType)549 @Override Type newArrayType(Type componentType) { 550 if (componentType instanceof Class) { 551 return getArrayClass((Class<?>) componentType); 552 } else { 553 return new GenericArrayTypeImpl(componentType); 554 } 555 } usedInGenericType(Type type)556 @Override Type usedInGenericType(Type type) { 557 return checkNotNull(type); 558 } 559 }, 560 JAVA8 { newArrayType(Type componentType)561 @Override Type newArrayType(Type componentType) { 562 return JAVA7.newArrayType(componentType); 563 } usedInGenericType(Type type)564 @Override Type usedInGenericType(Type type) { 565 return JAVA7.usedInGenericType(type); 566 } typeName(Type type)567 @Override String typeName(Type type) { 568 try { 569 Method getTypeName = Type.class.getMethod("getTypeName"); 570 return (String) getTypeName.invoke(type); 571 } catch (NoSuchMethodException e) { 572 throw new AssertionError("Type.getTypeName should be available in Java 8"); 573 } catch (InvocationTargetException e) { 574 throw new RuntimeException(e); 575 } catch (IllegalAccessException e) { 576 throw new RuntimeException(e); 577 } 578 } 579 } 580 ; 581 582 static final JavaVersion CURRENT; 583 static { 584 if (AnnotatedElement.class.isAssignableFrom(TypeVariable.class)) { 585 CURRENT = JAVA8; 586 } else if (new TypeCapture<int[]>() {}.capture() instanceof Class) { 587 CURRENT = JAVA7; 588 } else { 589 CURRENT = JAVA6; 590 } 591 } 592 newArrayType(Type componentType)593 abstract Type newArrayType(Type componentType); usedInGenericType(Type type)594 abstract Type usedInGenericType(Type type); typeName(Type type)595 String typeName(Type type) { 596 return Types.toString(type); 597 } 598 usedInGenericType(Type[] types)599 final ImmutableList<Type> usedInGenericType(Type[] types) { 600 ImmutableList.Builder<Type> builder = ImmutableList.builder(); 601 for (Type type : types) { 602 builder.add(usedInGenericType(type)); 603 } 604 return builder.build(); 605 } 606 } 607 608 /** 609 * Per https://code.google.com/p/guava-libraries/issues/detail?id=1635, 610 * In JDK 1.7.0_51-b13, TypeVariableImpl.equals() is changed to no longer be equal to custom 611 * TypeVariable implementations. As a result, we need to make sure our TypeVariable implementation 612 * respects symmetry. 613 * Moreover, we don't want to reconstruct a native type variable <A> using our implementation 614 * unless some of its bounds have changed in resolution. This avoids creating unequal TypeVariable 615 * implementation unnecessarily. When the bounds do change, however, it's fine for the synthetic 616 * TypeVariable to be unequal to any native TypeVariable anyway. 617 */ 618 static final class NativeTypeVariableEquals<X> { 619 static final boolean NATIVE_TYPE_VARIABLE_ONLY = 620 !NativeTypeVariableEquals.class.getTypeParameters()[0].equals( 621 newArtificialTypeVariable(NativeTypeVariableEquals.class, "X")); 622 } 623 Types()624 private Types() {} 625 } 626