package ru.vyarus.java.generics.resolver.util; import ru.vyarus.java.generics.resolver.context.container.ExplicitTypeVariable; import ru.vyarus.java.generics.resolver.error.IncompatibleTypesException; import ru.vyarus.java.generics.resolver.error.UnknownGenericException; import ru.vyarus.java.generics.resolver.util.walk.MatchVariablesVisitor; import ru.vyarus.java.generics.resolver.util.walk.TypesWalker; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.*; /** * Utility class for {@link TypeVariable} handling logic. Many api methods use type resolution * ({@link GenericsUtils#resolveTypeVariables(Type, Map)}) to get rid of type variables containing inside types. * Also, many apis will throw error about unknown type if they face {@link TypeVariable} inside. * <p> * To overcome this limitations in cases when you really need to preserve variable in type {@link ExplicitTypeVariable} * must be used. It is considered as normal type by all api methods. * <p> * There are two ways to replace types: * <ul> * <li>Use {@link #trackRootVariables(Class)} to preserve root class generics (useful for type * tracking)</li> * <li>Use {@link #preserveVariables(Type)} to replace all variables in type into {@link ExplicitTypeVariable}</li> * </ul> * <p> * Types with variables can be used as templates. To dynamically create real type from such template use * {@link #resolveAllTypeVariables(Type, Map)} (with required replacements provided in map). All type * variables could be found with {@link GenericsUtils#findVariables(Type)}. * <p> * Note that this class is considered as additional low level api. Use it only if you need to work with variables, * otherwise don't use (for example, don't use {@link TypeVariableUtils#resolveAllTypeVariables(Type, Map)} in all * cases, use usual {@link GenericsUtils#resolveTypeVariables(Type, Map)} instead. * * @author Vyacheslav Rusakov * @since 15.12.2018 */ public final class TypeVariableUtils { private TypeVariableUtils() { } /** * Used when it is important to track when root type generics will go. For example: * {@code class Root<T> implements Base<List<T>>}. When resolution applied, unknown root type T * will be replaced with special type {@link ExplicitTypeVariable}, which does not cause exceptions on type * resolution, but allows to preserve root type variables. After resolution actual types could be constructed * depending on root type parametrization (dynamic calculation). * * @param type class to analyze * @param ignoreClasses classes to ignore during analysis (may be null) * @return resolved generics for all types in class hierarchy with root variables preserved */ @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") public static Map<Class<?>, LinkedHashMap<String, Type>> trackRootVariables( final Class<?> type, final List<Class<?>> ignoreClasses) { // leave type variables to track where would they go final LinkedHashMap<String, Type> rootGenerics = new LinkedHashMap<String, Type>(); for (TypeVariable var : type.getTypeParameters()) { // special variables type, known by resolver (no exceptions for unknown generics will be thrown) rootGenerics.put(var.getName(), new ExplicitTypeVariable(var)); } return GenericsResolutionUtils.resolve(type, rootGenerics, Collections.<Class<?>, LinkedHashMap<String, Type>>emptyMap(), ignoreClasses == null ? Collections.<Class<?>>emptyList() : ignoreClasses); } /** * Shortcut for {@link #trackRootVariables(Class, List)} to simplify usage without ignore classes. * * @param type class to analyze * @return resolved generics for all types in class hierarchy with root variables preserved */ public static Map<Class<?>, LinkedHashMap<String, Type>> trackRootVariables(final Class type) { return trackRootVariables(type, null); } /** * Match explicit variables ({@link ExplicitTypeVariable}) in type with provided type. For example, suppose * you have type {@code List<E>} (with {@link ExplicitTypeVariable} as E variable) and * real type {@code List<String>}. This method will match variable E to String from real type. * <p> * WARNING: if provided template type will contain {@link TypeVariable} - they would not be detected! * Because method resolve all variables to its raw declaration. Use {@link #preserveVariables(Type)} in order * to replace variables before matching. It is not don't automatically to avoid redundant calls (this api * considered as low level api). * * @param template type with variables * @param real type to compare and resolve variables from * @return map of resolved variables or empty map * @throws IncompatibleTypesException when provided types are not compatible * @see #trackRootVariables(Class, List) for variables tracking form root class * @see #preserveVariables(Type) for variables preserving in types */ public static Map<TypeVariable, Type> matchVariables(final Type template, final Type real) { final MatchVariablesVisitor visitor = new MatchVariablesVisitor(); TypesWalker.walk(template, real, visitor); if (visitor.isHierarchyError()) { throw new IncompatibleTypesException( "Type %s variables can't be matched from type %s because they " + "are not compatible", template, real); } final Map<TypeVariable, Type> res = visitor.getMatched(); // to be sure that right type does not contain variables for (Map.Entry<TypeVariable, Type> entry : res.entrySet()) { entry.setValue(resolveAllTypeVariables(entry.getValue(), visitor.getMatchedMap())); } return res; } /** * Shortcut for {@link #matchVariables(Type, Type)} which return map of variable names instaed of raw * variable objects. * * @param template type with variables * @param real type to compare and resolve variables from * @return map of resolved variables or empty map * @throws IllegalArgumentException when provided types are nto compatible */ public static Map<String, Type> matchVariableNames(final Type template, final Type real) { final Map<TypeVariable, Type> match = matchVariables(template, real); if (match.isEmpty()) { return Collections.emptyMap(); } final Map<String, Type> res = new HashMap<String, Type>(); for (Map.Entry<TypeVariable, Type> entry : match.entrySet()) { res.put(entry.getKey().getName(), entry.getValue()); } return res; } /** * The same as {@link GenericsUtils#resolveTypeVariables(Type[], Map)}, except it also process * {@link ExplicitTypeVariable} variables. Useful for special cases when variables tracking is used. * For example, type resolved with variables as a template and then used to create dynamic types * (according to context parametrization). * * @param types types to resolve * @param generics root class generics mapping and {@link ExplicitTypeVariable} variable values. * @return types without variables */ public static Type[] resolveAllTypeVariables(final Type[] types, final Map<String, Type> generics) { return GenericsUtils.resolveTypeVariables(types, generics, true); } /** * The same as {@link GenericsUtils#resolveTypeVariables(Type, Map)}, except it also process * {@link ExplicitTypeVariable} variables. Useful for special cases when variables tracking is used. * For example, type resolved with variables as a template and then used to create dynamic types * (according to context parametrization). * * @param type type to resolve * @param generics root class generics mapping and {@link ExplicitTypeVariable} variable values. * @return resolved type * @throws UnknownGenericException when found generic not declared on type (e.g. method generic) * @see #preserveVariables(Type) */ public static Type resolveAllTypeVariables(final Type type, final Map<String, Type> generics) { return GenericsUtils.resolveTypeVariables(type, generics, true); } /** * In contrast to {@link #resolveAllTypeVariables(Type, Map)} which replace generics in type according to * generics map, this method replace variables with their upper bound. For example, variable defined as * {@code class Root<T extends String>} and for type {@code List<T>} result will be {@code List<String>} * (variable T replaced by upper bound - String). * * @param type type to replace variables into. * @return type with all variables resolved as upper bound */ public static Type resolveAllTypeVariables(final Type type) { final List<TypeVariable> vars = GenericsUtils.findVariables(type); if (vars.isEmpty()) { // no variables in type - nothing to replace return type; } final LinkedHashMap<String, Type> generics = new LinkedHashMap<String, Type>(); // important to resolve vars in correct order for (TypeVariable var : GenericsUtils.orderVariablesForResolution(vars)) { generics.put(var.getName(), GenericsResolutionUtils.resolveRawGeneric(var, generics)); } // finally resolve variables with pre-computed upper bounds return resolveAllTypeVariables(type, generics); } /** * Replace all {@link TypeVariable} into {@link ExplicitTypeVariable} to preserve variables. * This may be required because in many places type variables are resolved into raw declaration bound. * For example, useful for {@link TypesWalker} api. * * @param type type possibly containing variables * @return same type if it doesn't contain variables or type with all {@link TypeVariable} replaced by * {@link ExplicitTypeVariable} * @see #resolveAllTypeVariables(Type, Map) to replace explicit varaibles */ @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") public static Type preserveVariables(final Type type) { final List<TypeVariable> vars = GenericsUtils.findVariables(type); if (vars.isEmpty()) { return type; } final Map<String, Type> preservation = new HashMap<String, Type>(); for (TypeVariable var : vars) { preservation.put(var.getName(), new ExplicitTypeVariable(var)); } // replace TypeVariable to ExplicitTypeVariable return GenericsUtils.resolveTypeVariables(type, preservation); } }