Is there any method in ByteBuddy to convert a TypeDescription.Generic into an appropriate java.lang.reflect.Type?

555 views Asked by At

(The surface area of the ByteBuddy API is overwhelmingly enormous, which is why I'm asking the question.)

I'm aware that I can take a TypeDescription.Generic and determine its "sort" and proceed rather laboriously "by hand" from there, but often times I've found there is a method buried somewhere that will do this sort of tedious work for me.

EDIT: a commenter asked for the "tedious" recipe. Here it is (stand back; please note the various implementations of various Types are more or less what you'd expect them to be):

 public static final Type toType(final TypeDefinition type) throws ReflectiveOperationException {
    final Type returnValue;
    if (type == null) {
      returnValue = null;
    } else {
      final TypeDescription.Generic genericType = type.asGenericType();
      switch (type.getSort()) {
      case GENERIC_ARRAY:
        returnValue = new DefaultGenericArrayType(toType(type.getComponentType()));
        break;
      case NON_GENERIC:
        returnValue = Class.forName(type.getTypeName(), false, Thread.currentThread().getContextClassLoader());
        break;
      case PARAMETERIZED:
        final TypeDefinition ownerType = genericType.getOwnerType();
        final TypeDefinition rawType = type.asErasure();
        final List<? extends TypeDefinition> actualTypeArguments = genericType.getTypeArguments();
        if (actualTypeArguments == null || actualTypeArguments.isEmpty()) {
          returnValue = new DefaultParameterizedType(toType(ownerType), toType(rawType));
        } else {
          final Type[] actualJavaTypeArguments = new Type[actualTypeArguments.size()];
          for (int i = 0; i < actualTypeArguments.size(); i++) {
            actualJavaTypeArguments[i] = toType(actualTypeArguments.get(i));
          }
          returnValue = new DefaultParameterizedType(toType(ownerType), toType(rawType), actualJavaTypeArguments);
        }
        break;
      case VARIABLE:
        final TypeVariableSource typeVariableSource = genericType.getTypeVariableSource();
        final GenericDeclaration gd;
        if (typeVariableSource instanceof TypeDefinition typeDefinition) {
          gd = Class.forName(typeDefinition.asErasure().getTypeName(), false, Thread.currentThread().getContextClassLoader());
        } else if (typeVariableSource instanceof MethodDescription.InDefinedShape methodDescription) {
          // Reflection time
          final String name = methodDescription.getName();
          final Class<?> cls = Class.forName(methodDescription.getDeclaringType().asErasure().getTypeName(), false, Thread.currentThread().getContextClassLoader());
          final List<? extends TypeDefinition> parameterTypes = methodDescription.getParameters().asTypeList();
          final Class<?>[] parameterClasses = new Class<?>[parameterTypes.size()];
          for (int i = 0; i < parameterTypes.size(); i++) {
            parameterClasses[i] = Class.forName(parameterTypes.get(i).asErasure().getName(), false, Thread.currentThread().getContextClassLoader());
          }
          if (MethodDescription.CONSTRUCTOR_INTERNAL_NAME.equals(name)) {
            assert TypeDescription.VOID.equals(methodDescription.getReturnType());
            gd = cls.getDeclaredConstructor(parameterClasses);
          } else {
            assert !MethodDescription.TYPE_INITIALIZER_INTERNAL_NAME.equals(name);
            gd = cls.getDeclaredMethod(name, parameterClasses);            
          }
        } else {
          throw new IllegalArgumentException("Unexpected type variable source: " + typeVariableSource);
        }
        final TypeVariable<?>[] typeVariables = gd.getTypeParameters();
        TypeVariable<?> temp = null;
        for (final TypeVariable<?> typeVariable : typeVariables) {
          if (typeVariable.getName().equals(genericType.getSymbol())) {
            temp = typeVariable;
            break;
          }
        }
        assert temp != null;
        returnValue = temp;
        break;
      case VARIABLE_SYMBOLIC:
        throw new IllegalArgumentException("Unexpected type: " + type);
      case WILDCARD:
        final List<? extends TypeDefinition> upperBounds = genericType.getUpperBounds();
        final List<? extends TypeDefinition> lowerBounds = genericType.getLowerBounds();
        if (lowerBounds == null || lowerBounds.isEmpty()) {
          if (upperBounds == null || upperBounds.isEmpty() || (upperBounds.size() == 1 && TypeDescription.Generic.OBJECT.equals(upperBounds.get(0)))) {
            returnValue = UnboundedWildcardType.INSTANCE;
          } else {
            // Upper bounded.
            final Type[] upperJavaBounds = new Type[upperBounds.size()];
            for (int i = 0; i < upperBounds.size(); i++) {
              upperJavaBounds[i] = toType(upperBounds.get(i)); // XXX recursive
            }
            returnValue = new UpperBoundedWildcardType(upperJavaBounds);
          }
        } else {
          assert upperBounds == null || upperBounds.isEmpty() || (upperBounds.size() == 1 && TypeDescription.Generic.OBJECT.equals(upperBounds.get(0))) : "Unexpected upper bounds: " + upperBounds + "; lower bounds: " + lowerBounds;
          // Lower bounded.
          assert lowerBounds.size() == 1 : "Unexpected size in lower bounds: " + lowerBounds;
          returnValue = new LowerBoundedWildcardType(toType(lowerBounds.get(0))); // XXX recursive
        }
        break;        
      default:
        throw new IllegalArgumentException("Unexpected type: " + type);
      }
    }
    return returnValue;
  }
1

There are 1 answers

3
Rafael Winterhalter On BEST ANSWER

No, you can only convert a Type to a TypeDescription.Generic but there is no option to do it the other way. The easiest option to emulate this would probably be to define a class that defines a field of the given Type, to load this class and to read the field type using Java reflection.

The reason Byte Buddy cannot convert a description to a Type is that Byte Buddy abstracts out class loaders and that type variables might be detached from their declaring source.