code: public Type resolveType(ParameterizedType type) {
          Type[] unresolvedTypeArguments = type.getActualTypeArguments();
  
          /*
           * Indicates whether we managed to resolve any of type arguments. If we did not then there is no need to create a new
           * ParameterizedType with the old parameters. Instead, we return the original type.
           */
          boolean modified = false;
          Type[] resolvedTypeArguments = new Type[unresolvedTypeArguments.length];
  
          for (int i = 0; i < unresolvedTypeArguments.length; i++) {
              Type resolvedType = unresolvedTypeArguments[i];
              if (resolvedType instanceof TypeVariable<?>) {
                  resolvedType = resolveType((TypeVariable<?>) resolvedType);
              }
              if (resolvedType instanceof ParameterizedType) {
                  resolvedType = resolveType((ParameterizedType) resolvedType);
              }
              resolvedTypeArguments[i] = resolvedType;
              // This identity check is intentional. A different identity indicates that the type argument was resolved within #resolveType().
              if (unresolvedTypeArguments[i] != resolvedType) {
                  modified = true;
              }
          }
  
          if (modified) {
              return new ParameterizedTypeImpl(type.getRawType(), resolvedTypeArguments, type.getOwnerType());
          } else {
              return type;
          }
      }
docs: Resolves a given parameterized type. If the parameterized type contains no type variables it is returned untouched.
  Otherwise, a new {@link ParameterizedType} instance is returned in which each type variable is resolved using
  {@link #resolveType(TypeVariable)}.