• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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