All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.common.reflect.Types Maven / Gradle / Ivy

Go to download

Guava is a suite of core and expanded libraries that include utility classes, google's collections, io classes, and much much more.

There is a newer version: 33.1.0-jre
Show newest version
/*
 * Copyright (C) 2011 The Guava Authors
 *
 * 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 com.google.common.reflect;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.transform;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import java.io.Serializable;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.security.AccessControlException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicReference;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * Utilities for working with {@link Type}.
 *
 * @author Ben Yu
 */
final class Types {

  /** Class#toString without the "class " and "interface " prefixes */
  private static final Function TYPE_NAME =
      new Function() {
        @Override
        public String apply(Type from) {
          return JavaVersion.CURRENT.typeName(from);
        }
      };

  private static final Joiner COMMA_JOINER = Joiner.on(", ").useForNull("null");

  /** Returns the array type of {@code componentType}. */
  static Type newArrayType(Type componentType) {
    if (componentType instanceof WildcardType) {
      WildcardType wildcard = (WildcardType) componentType;
      Type[] lowerBounds = wildcard.getLowerBounds();
      checkArgument(lowerBounds.length <= 1, "Wildcard cannot have more than one lower bounds.");
      if (lowerBounds.length == 1) {
        return supertypeOf(newArrayType(lowerBounds[0]));
      } else {
        Type[] upperBounds = wildcard.getUpperBounds();
        checkArgument(upperBounds.length == 1, "Wildcard should have only one upper bound.");
        return subtypeOf(newArrayType(upperBounds[0]));
      }
    }
    return JavaVersion.CURRENT.newArrayType(componentType);
  }

  /**
   * Returns a type where {@code rawType} is parameterized by {@code arguments} and is owned by
   * {@code ownerType}.
   */
  static ParameterizedType newParameterizedTypeWithOwner(
      @Nullable Type ownerType, Class rawType, Type... arguments) {
    if (ownerType == null) {
      return newParameterizedType(rawType, arguments);
    }
    // ParameterizedTypeImpl constructor already checks, but we want to throw NPE before IAE
    checkNotNull(arguments);
    checkArgument(rawType.getEnclosingClass() != null, "Owner type for unenclosed %s", rawType);
    return new ParameterizedTypeImpl(ownerType, rawType, arguments);
  }

  /** Returns a type where {@code rawType} is parameterized by {@code arguments}. */
  static ParameterizedType newParameterizedType(Class rawType, Type... arguments) {
    return new ParameterizedTypeImpl(
        ClassOwnership.JVM_BEHAVIOR.getOwnerType(rawType), rawType, arguments);
  }

  /** Decides what owner type to use for constructing {@link ParameterizedType} from a raw class. */
  private enum ClassOwnership {
    OWNED_BY_ENCLOSING_CLASS {
      @Override
      @Nullable
      Class getOwnerType(Class rawType) {
        return rawType.getEnclosingClass();
      }
    },
    LOCAL_CLASS_HAS_NO_OWNER {
      @Override
      @Nullable
      Class getOwnerType(Class rawType) {
        if (rawType.isLocalClass()) {
          return null;
        } else {
          return rawType.getEnclosingClass();
        }
      }
    };

    abstract @Nullable Class getOwnerType(Class rawType);

    static final ClassOwnership JVM_BEHAVIOR = detectJvmBehavior();

    private static ClassOwnership detectJvmBehavior() {
      class LocalClass {}
      Class subclass = new LocalClass() {}.getClass();
      ParameterizedType parameterizedType = (ParameterizedType) subclass.getGenericSuperclass();
      for (ClassOwnership behavior : ClassOwnership.values()) {
        if (behavior.getOwnerType(LocalClass.class) == parameterizedType.getOwnerType()) {
          return behavior;
        }
      }
      throw new AssertionError();
    }
  }

  /**
   * Returns a new {@link TypeVariable} that belongs to {@code declaration} with {@code name} and
   * {@code bounds}.
   */
  static  TypeVariable newArtificialTypeVariable(
      D declaration, String name, Type... bounds) {
    return newTypeVariableImpl(
        declaration, name, (bounds.length == 0) ? new Type[] {Object.class} : bounds);
  }

  /** Returns a new {@link WildcardType} with {@code upperBound}. */
  @VisibleForTesting
  static WildcardType subtypeOf(Type upperBound) {
    return new WildcardTypeImpl(new Type[0], new Type[] {upperBound});
  }

  /** Returns a new {@link WildcardType} with {@code lowerBound}. */
  @VisibleForTesting
  static WildcardType supertypeOf(Type lowerBound) {
    return new WildcardTypeImpl(new Type[] {lowerBound}, new Type[] {Object.class});
  }

  /**
   * Returns human readable string representation of {@code type}.
   *
   * 

The format is subject to change. */ static String toString(Type type) { return (type instanceof Class) ? ((Class) type).getName() : type.toString(); } static @Nullable Type getComponentType(Type type) { checkNotNull(type); final AtomicReference result = new AtomicReference<>(); new TypeVisitor() { @Override void visitTypeVariable(TypeVariable t) { result.set(subtypeOfComponentType(t.getBounds())); } @Override void visitWildcardType(WildcardType t) { result.set(subtypeOfComponentType(t.getUpperBounds())); } @Override void visitGenericArrayType(GenericArrayType t) { result.set(t.getGenericComponentType()); } @Override void visitClass(Class t) { result.set(t.getComponentType()); } }.visit(type); return result.get(); } /** * Returns {@code ? extends X} if any of {@code bounds} is a subtype of {@code X[]}; or null * otherwise. */ private static @Nullable Type subtypeOfComponentType(Type[] bounds) { for (Type bound : bounds) { Type componentType = getComponentType(bound); if (componentType != null) { // Only the first bound can be a class or array. // Bounds after the first can only be interfaces. if (componentType instanceof Class) { Class componentClass = (Class) componentType; if (componentClass.isPrimitive()) { return componentClass; } } return subtypeOf(componentType); } } return null; } private static final class GenericArrayTypeImpl implements GenericArrayType, Serializable { private final Type componentType; GenericArrayTypeImpl(Type componentType) { this.componentType = JavaVersion.CURRENT.usedInGenericType(componentType); } @Override public Type getGenericComponentType() { return componentType; } @Override public String toString() { return Types.toString(componentType) + "[]"; } @Override public int hashCode() { return componentType.hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof GenericArrayType) { GenericArrayType that = (GenericArrayType) obj; return Objects.equal(getGenericComponentType(), that.getGenericComponentType()); } return false; } private static final long serialVersionUID = 0; } private static final class ParameterizedTypeImpl implements ParameterizedType, Serializable { private final @Nullable Type ownerType; private final ImmutableList argumentsList; private final Class rawType; ParameterizedTypeImpl(@Nullable Type ownerType, Class rawType, Type[] typeArguments) { checkNotNull(rawType); checkArgument(typeArguments.length == rawType.getTypeParameters().length); disallowPrimitiveType(typeArguments, "type parameter"); this.ownerType = ownerType; this.rawType = rawType; this.argumentsList = JavaVersion.CURRENT.usedInGenericType(typeArguments); } @Override public Type[] getActualTypeArguments() { return toArray(argumentsList); } @Override public Type getRawType() { return rawType; } @Override public Type getOwnerType() { return ownerType; } @Override public String toString() { StringBuilder builder = new StringBuilder(); if (ownerType != null && JavaVersion.CURRENT.jdkTypeDuplicatesOwnerName()) { builder.append(JavaVersion.CURRENT.typeName(ownerType)).append('.'); } return builder .append(rawType.getName()) .append('<') .append(COMMA_JOINER.join(transform(argumentsList, TYPE_NAME))) .append('>') .toString(); } @Override public int hashCode() { return (ownerType == null ? 0 : ownerType.hashCode()) ^ argumentsList.hashCode() ^ rawType.hashCode(); } @Override public boolean equals(Object other) { if (!(other instanceof ParameterizedType)) { return false; } ParameterizedType that = (ParameterizedType) other; return getRawType().equals(that.getRawType()) && Objects.equal(getOwnerType(), that.getOwnerType()) && Arrays.equals(getActualTypeArguments(), that.getActualTypeArguments()); } private static final long serialVersionUID = 0; } private static TypeVariable newTypeVariableImpl( D genericDeclaration, String name, Type[] bounds) { TypeVariableImpl typeVariableImpl = new TypeVariableImpl(genericDeclaration, name, bounds); @SuppressWarnings("unchecked") TypeVariable typeVariable = Reflection.newProxy( TypeVariable.class, new TypeVariableInvocationHandler(typeVariableImpl)); return typeVariable; } /** * Invocation handler to work around a compatibility problem between Java 7 and Java 8. * *

Java 8 introduced a new method {@code getAnnotatedBounds()} in the {@link TypeVariable} * interface, whose return type {@code AnnotatedType[]} is also new in Java 8. That means that we * cannot implement that interface in source code in a way that will compile on both Java 7 and * Java 8. If we include the {@code getAnnotatedBounds()} method then its return type means it * won't compile on Java 7, while if we don't include the method then the compiler will complain * that an abstract method is unimplemented. So instead we use a dynamic proxy to get an * implementation. If the method being called on the {@code TypeVariable} instance has the same * name as one of the public methods of {@link TypeVariableImpl}, the proxy calls the same method * on its instance of {@code TypeVariableImpl}. Otherwise it throws {@link * UnsupportedOperationException}; this should only apply to {@code getAnnotatedBounds()}. This * does mean that users on Java 8 who obtain an instance of {@code TypeVariable} from {@link * TypeResolver#resolveType} will not be able to call {@code getAnnotatedBounds()} on it, but that * should hopefully be rare. * *

This workaround should be removed at a distant future time when we no longer support Java * versions earlier than 8. */ private static final class TypeVariableInvocationHandler implements InvocationHandler { private static final ImmutableMap typeVariableMethods; static { ImmutableMap.Builder builder = ImmutableMap.builder(); for (Method method : TypeVariableImpl.class.getMethods()) { if (method.getDeclaringClass().equals(TypeVariableImpl.class)) { try { method.setAccessible(true); } catch (AccessControlException e) { // OK: the method is accessible to us anyway. The setAccessible call is only for // unusual execution environments where that might not be true. } builder.put(method.getName(), method); } } typeVariableMethods = builder.build(); } private final TypeVariableImpl typeVariableImpl; TypeVariableInvocationHandler(TypeVariableImpl typeVariableImpl) { this.typeVariableImpl = typeVariableImpl; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); Method typeVariableMethod = typeVariableMethods.get(methodName); if (typeVariableMethod == null) { throw new UnsupportedOperationException(methodName); } else { try { return typeVariableMethod.invoke(typeVariableImpl, args); } catch (InvocationTargetException e) { throw e.getCause(); } } } } private static final class TypeVariableImpl { private final D genericDeclaration; private final String name; private final ImmutableList bounds; TypeVariableImpl(D genericDeclaration, String name, Type[] bounds) { disallowPrimitiveType(bounds, "bound for type variable"); this.genericDeclaration = checkNotNull(genericDeclaration); this.name = checkNotNull(name); this.bounds = ImmutableList.copyOf(bounds); } public Type[] getBounds() { return toArray(bounds); } public D getGenericDeclaration() { return genericDeclaration; } public String getName() { return name; } public String getTypeName() { return name; } @Override public String toString() { return name; } @Override public int hashCode() { return genericDeclaration.hashCode() ^ name.hashCode(); } @Override public boolean equals(Object obj) { if (NativeTypeVariableEquals.NATIVE_TYPE_VARIABLE_ONLY) { // equal only to our TypeVariable implementation with identical bounds if (obj != null && Proxy.isProxyClass(obj.getClass()) && Proxy.getInvocationHandler(obj) instanceof TypeVariableInvocationHandler) { TypeVariableInvocationHandler typeVariableInvocationHandler = (TypeVariableInvocationHandler) Proxy.getInvocationHandler(obj); TypeVariableImpl that = typeVariableInvocationHandler.typeVariableImpl; return name.equals(that.getName()) && genericDeclaration.equals(that.getGenericDeclaration()) && bounds.equals(that.bounds); } return false; } else { // equal to any TypeVariable implementation regardless of bounds if (obj instanceof TypeVariable) { TypeVariable that = (TypeVariable) obj; return name.equals(that.getName()) && genericDeclaration.equals(that.getGenericDeclaration()); } return false; } } } static final class WildcardTypeImpl implements WildcardType, Serializable { private final ImmutableList lowerBounds; private final ImmutableList upperBounds; WildcardTypeImpl(Type[] lowerBounds, Type[] upperBounds) { disallowPrimitiveType(lowerBounds, "lower bound for wildcard"); disallowPrimitiveType(upperBounds, "upper bound for wildcard"); this.lowerBounds = JavaVersion.CURRENT.usedInGenericType(lowerBounds); this.upperBounds = JavaVersion.CURRENT.usedInGenericType(upperBounds); } @Override public Type[] getLowerBounds() { return toArray(lowerBounds); } @Override public Type[] getUpperBounds() { return toArray(upperBounds); } @Override public boolean equals(Object obj) { if (obj instanceof WildcardType) { WildcardType that = (WildcardType) obj; return lowerBounds.equals(Arrays.asList(that.getLowerBounds())) && upperBounds.equals(Arrays.asList(that.getUpperBounds())); } return false; } @Override public int hashCode() { return lowerBounds.hashCode() ^ upperBounds.hashCode(); } @Override public String toString() { StringBuilder builder = new StringBuilder("?"); for (Type lowerBound : lowerBounds) { builder.append(" super ").append(JavaVersion.CURRENT.typeName(lowerBound)); } for (Type upperBound : filterUpperBounds(upperBounds)) { builder.append(" extends ").append(JavaVersion.CURRENT.typeName(upperBound)); } return builder.toString(); } private static final long serialVersionUID = 0; } private static Type[] toArray(Collection types) { return types.toArray(new Type[types.size()]); } private static Iterable filterUpperBounds(Iterable bounds) { return Iterables.filter(bounds, Predicates.not(Predicates.equalTo(Object.class))); } private static void disallowPrimitiveType(Type[] types, String usedAs) { for (Type type : types) { if (type instanceof Class) { Class cls = (Class) type; checkArgument(!cls.isPrimitive(), "Primitive type '%s' used as %s", cls, usedAs); } } } /** Returns the {@code Class} object of arrays with {@code componentType}. */ static Class getArrayClass(Class componentType) { // TODO(user): This is not the most efficient way to handle generic // arrays, but is there another way to extract the array class in a // non-hacky way (i.e. using String value class names- "[L...")? return Array.newInstance(componentType, 0).getClass(); } // TODO(benyu): Once behavior is the same for all Java versions we support, delete this. enum JavaVersion { JAVA6 { @Override GenericArrayType newArrayType(Type componentType) { return new GenericArrayTypeImpl(componentType); } @Override Type usedInGenericType(Type type) { checkNotNull(type); if (type instanceof Class) { Class cls = (Class) type; if (cls.isArray()) { return new GenericArrayTypeImpl(cls.getComponentType()); } } return type; } }, JAVA7 { @Override Type newArrayType(Type componentType) { if (componentType instanceof Class) { return getArrayClass((Class) componentType); } else { return new GenericArrayTypeImpl(componentType); } } @Override Type usedInGenericType(Type type) { return checkNotNull(type); } }, JAVA8 { @Override Type newArrayType(Type componentType) { return JAVA7.newArrayType(componentType); } @Override Type usedInGenericType(Type type) { return JAVA7.usedInGenericType(type); } @Override String typeName(Type type) { try { Method getTypeName = Type.class.getMethod("getTypeName"); return (String) getTypeName.invoke(type); } catch (NoSuchMethodException e) { throw new AssertionError("Type.getTypeName should be available in Java 8"); } catch (InvocationTargetException | IllegalAccessException e) { throw new RuntimeException(e); } } }, JAVA9 { @Override Type newArrayType(Type componentType) { return JAVA8.newArrayType(componentType); } @Override Type usedInGenericType(Type type) { return JAVA8.usedInGenericType(type); } @Override String typeName(Type type) { return JAVA8.typeName(type); } @Override boolean jdkTypeDuplicatesOwnerName() { return false; } }; static final JavaVersion CURRENT; static { if (AnnotatedElement.class.isAssignableFrom(TypeVariable.class)) { if (new TypeCapture>() {}.capture() .toString() .contains("java.util.Map.java.util.Map")) { CURRENT = JAVA8; } else { CURRENT = JAVA9; } } else if (new TypeCapture() {}.capture() instanceof Class) { CURRENT = JAVA7; } else { CURRENT = JAVA6; } } abstract Type newArrayType(Type componentType); abstract Type usedInGenericType(Type type); final ImmutableList usedInGenericType(Type[] types) { ImmutableList.Builder builder = ImmutableList.builder(); for (Type type : types) { builder.add(usedInGenericType(type)); } return builder.build(); } String typeName(Type type) { return Types.toString(type); } boolean jdkTypeDuplicatesOwnerName() { return true; } } /** * Per issue 1635, * In JDK 1.7.0_51-b13, {@link TypeVariableImpl#equals(Object)} is changed to no longer be equal * to custom TypeVariable implementations. As a result, we need to make sure our TypeVariable * implementation respects symmetry. Moreover, we don't want to reconstruct a native type variable * {@code } using our implementation unless some of its bounds have changed in resolution. This * avoids creating unequal TypeVariable implementation unnecessarily. When the bounds do change, * however, it's fine for the synthetic TypeVariable to be unequal to any native TypeVariable * anyway. */ static final class NativeTypeVariableEquals { static final boolean NATIVE_TYPE_VARIABLE_ONLY = !NativeTypeVariableEquals.class.getTypeParameters()[0].equals( newArtificialTypeVariable(NativeTypeVariableEquals.class, "X")); } private Types() {} }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy