WildcardType to ParameterizedType - PullRequest
       25

WildcardType to ParameterizedType

0 голосов
/ 08 сентября 2018

У меня есть свойство bean типа List<? extends Number> и список возможных классов реализации с их конструкторами. Я рекурсивно ищу кандидатов на параметры конструктора.

Один из возможных конструкторов: ArrayList(Collection<? extends E>)

Я пытаюсь разрешить параметры типа и параметры конструктора с помощью Guava, но когда есть цепочка подстановочных типов, я получаю что-то вроде: ? extends capture#3-of ? extends capture#2-of ? extends capture#1-of ? extends ...

public static <T extends Object> void main(String[] args) throws Exception {
    TypeToken<?> propTt = new TypeToken<List<? extends Number>>() {
    };

    TypeToken<?> candidate = propTt.getSubtype(ArrayList.class);

    TypeToken<?> constructorResult;

    Constructor<?> cons = ArrayList.class.getConstructor(Collection.class);

    // java.util.ArrayList<? extends java.lang.Number>
    constructorResult = candidate.constructor(cons).getReturnType();
    System.out.println(constructorResult);


    // java.util.Collection<? extends E>
    Type param = cons.getGenericParameterTypes()[0];
    System.out.println(param);

    // java.util.Collection<? extends capture#1-of ? extends class java.lang.Number>
    TypeToken<?> resolvedParam = constructorResult.resolveType(param);
    System.out.println(resolvedParam);
}

Это происходит, хотя мы не можем использовать подстановочные знаки в качестве параметров типа для new. Недопустимо следующее:

List<?> x = new ArrayList<?>();
List<? extends Number> y = new ArrayList<? extends Number>();

Вместо этого мы пишем:

List<?> x = new ArrayList<Object>();
List<? extends Number> y = new ArrayList<Number>();

или автоматизируйте последний, используя оператор алмаза.

Требуемый тип resolvedParam: java.util.Collection<? extends java.lang.Number>.

com.google.common.reflect.Types имеет метод newParameterizedType(), в котором я мог бы передать бывшие подстановочные знаки, вручную разрешенные для параметризованных типов или классов, но этот метод является закрытым для пакета. Я не уверен, что мой обходной путь:

package com.google.common.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.checkerframework.checker.nullness.qual.Nullable;

@SuppressWarnings("serial")
public class TestWildcard {
    public static <T extends Object> void main(String[] args) throws Exception {
        TypeToken<?> propTt = new TypeToken<List<? extends Number>>() {
        };

        TypeToken<?> candidate = propTt.getSubtype(ArrayList.class);

        TypeToken<?> constructorResult;

        Constructor<?> cons = ArrayList.class.getConstructor(Collection.class);

        // new java.util.ArrayList<java.lang.Number>();
        constructorResult = resolveConstructorResult(candidate);
        System.out.println(constructorResult);

        // java.util.Collection<? extends E>
        Type param = cons.getGenericParameterTypes()[0];
        System.out.println(param);

        // java.util.Collection<? extends capture#1-of ? extends class java.lang.Number>
        TypeToken<?> resolvedParam = constructorResult.resolveType(param);
        System.out.println(resolvedParam);
    }

    static TypeToken<?> resolveConstructorResult(TypeToken<?> candidate) {
        Type ct = candidate.getType();
        if (ct instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) ct;
            Class<?> ptClazz = ((Class<?>) pt.getRawType());
            TypeVariable<?>[] tvars = ptClazz.getTypeParameters();
            Type[] targs = pt.getActualTypeArguments();
            boolean doIt = false;
            for (int i = 0; i < targs.length; i++) {
                if (targs[i] instanceof WildcardType) {
                    WildcardType wt = (WildcardType) targs[i];
                    TypeToken<?> ubound1 = TypeToken.of(wt.getUpperBounds()[0]);
                    TypeToken<?> ubound2 = TypeToken.of(tvars[i].getBounds()[0]);
                    if (ubound1.isSubtypeOf(ubound2)) {
                        doIt = true;
                        targs[i] = ubound1.getType();
                    } else if (ubound2.isSubtypeOf(ubound1)) {
                        doIt = true;
                        targs[i] = ubound2.getType();
                    }
                }
            }
            if (doIt) {
                pt = GuavaReflectAccessHelper.newParameterizedTypeWithOwner(pt.getOwnerType(), ptClazz, targs);
                candidate = TypeToken.of(pt);
            }
        }
        return candidate;
    }

    private static class GuavaReflectAccessHelper {

        public static ParameterizedType newParameterizedTypeWithOwner(@Nullable Type ownerType, Class<?> rawType,
                Type... arguments) {
            return Types.newParameterizedTypeWithOwner(ownerType, rawType, arguments);
        }
    }
}

Ответы [ 2 ]

0 голосов
/ 13 сентября 2018

Пользовательский newParameterizedType может быть реализован с помощью TypeResolver.where() (не путать с TypeToken.where()). Связанную часть в моем resolveConstructorResult() в вопросе можно изменить следующим образом:

        if (doIt) {
            pt = (ParameterizedType)newParameterizedType(ptClazz, targs);
            candidate = TypeToken.of(pt);
        }
    }
    return candidate;
}

public static Type newParameterizedType(Class<?> cls, Type... args) {
    TypeResolver resolver = new TypeResolver();
    TypeVariable<?>[] tvars = cls.getTypeParameters();
    for (int i = 0; i < args.length; i++) {
        resolver = resolver.where(tvars[i], args[i]);
    }
    return resolver.resolveType(dumbToGenericType(cls).getType());
}

@SuppressWarnings("unchecked")
public static <T> TypeToken<? extends T> dumbToGenericType(Class<T> cls) {
    return (TypeToken<T>)TypeToken.of(Object.class).getSubtype(cls);
}
0 голосов
/ 09 сентября 2018

Преобразование подстановочного типа в ParameterizedType здесь звучит не очень хорошо, но вы можете просто реализовать этот интерфейс самостоятельно.

Или используйте другую библиотеку, которая поддерживает более динамическое создание: https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/reflect/TypeUtils.html
И, вероятно, многие другие библиотеки.

В гуаве вы можете делать только что-то подобное:

static <K, V> TypeToken<Map<K, V>> mapToken(TypeToken<K> keyToken, TypeToken<V> valueToken) {
  return new TypeToken<Map<K, V>>() {}
    .where(new TypeParameter<K>() {}, keyToken)
    .where(new TypeParameter<V>() {}, valueToken);
}
...
TypeToken<Map<String, BigInteger>> mapToken = mapToken(
   TypeToken.of(String.class),
   TypeToken.of(BigInteger.class));
TypeToken<Map<Integer, Queue<String>>> complexToken = mapToken(
   TypeToken.of(Integer.class),
   new TypeToken<Queue<String>>() {});

Это может быть достаточно для вас.

...