Java tutorial
/* * Copyright (c) 2013-2014 the original author or 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 io.werval.runtime.util; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.Array; import java.lang.reflect.GenericArrayType; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; import static java.util.Collections.synchronizedMap; /** * Enhanced type resolution utilities. */ // Based on the Jonathan Halterman work derived from org.springframework.core.GenericTypeResolver public final class TypeResolver { /** * An unknown type. */ @SuppressWarnings("PublicInnerClass") public static final class Unknown { private Unknown() { } } /** * Cache of type variable/argument pairs. */ private static final Map<Class<?>, Reference<Map<TypeVariable<?>, Type>>> CACHE; /** * Enable/disable cache. */ private static boolean cacheEnabled; static { CACHE = synchronizedMap(new WeakHashMap<Class<?>, Reference<Map<TypeVariable<?>, Type>>>()); cacheEnabled = true; } /** * Enables the internal caching of TypeVariables. */ public static void enableCache() { cacheEnabled = true; } /** * Disables the internal caching of TypeVariables. */ public static void disableCache() { CACHE.clear(); cacheEnabled = false; } /** * Returns the raw class representing the type argument for the {@code targetType} resolved upwards from the * {@code initialType}. * * If no arguments can be resolved then {@code Unknown.class} is returned. * * @param <T> Parameterized target type * @param <I> Parameterized initial type * @param initialType to resolve upwards from * @param targetType to resolve arguments for * * @return type argument for {@code initialType} else {@code null} if no type arguments are declared * * @throws IllegalArgumentException if more or less than one type argument is resolved for the given types */ public static <T, I extends T> Class<?> resolveArgument(Class<I> initialType, Class<T> targetType) { return resolveArgument(resolveGenericType(initialType, targetType), initialType); } /** * Resolves the type argument for the {@code genericType} using type variable information from the * {@code sourceType}. * * If {@code genericType} is an instance of class, then {@code genericType} is returned. * If no arguments can be resolved then {@code Unknown.class} is returned. * * @param genericType to resolve upwards from * @param targetType to resolve arguments for * * @return type argument for {@code initialType} else {@code null} if no type arguments are declared * * @throws IllegalArgumentException if more or less than one type argument is resolved for the given types */ public static Class<?> resolveArgument(Type genericType, Class<?> targetType) { Class<?>[] arguments = resolveArguments(genericType, targetType); if (arguments == null) { return Unknown.class; } if (arguments.length != 1) { throw new IllegalArgumentException("Expected 1 type argument on generic type " + targetType.getName() + " but found " + arguments.length); } return arguments[0]; } /** * Returns an array of raw classes representing type arguments for the {@code targetType} resolved * upwards from the {@code initialType}. * * Arguments for {@code targetType} that cannot be resolved to a Class are returned as {@code Unknown.class}. * If no arguments can be resolved then {@code null} is returned. * * @param <T> Parameterized target type * @param <I> Parameterized initial type * @param initialType to resolve upwards from * @param targetType to resolve arguments for * * @return array of raw classes representing type arguments for {@code initialType} * else {@code null} if no type arguments are declared */ public static <T, I extends T> Class<?>[] resolveArguments(Class<I> initialType, Class<T> targetType) { return resolveArguments(resolveGenericType(initialType, targetType), initialType); } /** * Resolves the arguments for the {@code genericType} using the type variable information for the * {@code targetType}. * * Returns {@code null} if {@code genericType} is not parameterized or if arguments cannot be resolved. * * @param genericType Generic type * @param targetType Target type * * @return {@code null} if {@code genericType} is not parameterized or if arguments cannot be resolved */ public static Class<?>[] resolveArguments(Type genericType, Class<?> targetType) { Class<?>[] result = null; if (genericType instanceof ParameterizedType) { ParameterizedType paramType = (ParameterizedType) genericType; Type[] arguments = paramType.getActualTypeArguments(); result = new Class<?>[arguments.length]; for (int i = 0; i < arguments.length; i++) { result[i] = resolveClass(arguments[i], targetType); } } else if (genericType instanceof TypeVariable) { result = new Class<?>[1]; result[0] = resolveClass(genericType, targetType); } return result; } /** * Resolves the generic Type for the {@code targetType} by walking the type hierarchy upwards from the * {@code initialType}. * * @param initialType Initial type * @param targetType Target type * * @return Generic type */ public static Type resolveGenericType(Type initialType, Class<?> targetType) { Class<?> rawType; if (initialType instanceof ParameterizedType) { rawType = (Class<?>) ((ParameterizedType) initialType).getRawType(); } else { rawType = (Class<?>) initialType; } if (targetType.equals(rawType)) { return initialType; } Type result; if (targetType.isInterface()) { for (Type superInterface : rawType.getGenericInterfaces()) { if (superInterface != null && !superInterface.equals(Object.class)) { result = resolveGenericType(superInterface, targetType); if (result != null) { return result; } } } } Type superType = rawType.getGenericSuperclass(); if (superType != null && !superType.equals(Object.class)) { result = resolveGenericType(superType, targetType); if (result != null) { return result; } } return null; } /** * Resolves the raw class for the given {@code genericType}, using the type variable information from the * {@code targetType}. * * @param genericType Generic type * @param targetType Target type * * @return Raw class */ public static Class<?> resolveClass(Type genericType, Class<?> targetType) { if (genericType instanceof Class) { return (Class<?>) genericType; } else if (genericType instanceof ParameterizedType) { return resolveClass(((ParameterizedType) genericType).getRawType(), targetType); } else if (genericType instanceof GenericArrayType) { GenericArrayType arrayType = (GenericArrayType) genericType; Class<?> compoment = resolveClass(arrayType.getGenericComponentType(), targetType); return Array.newInstance(compoment, 0).getClass(); } else if (genericType instanceof TypeVariable) { TypeVariable<?> variable = (TypeVariable<?>) genericType; genericType = getTypeVariableMap(targetType).get(variable); genericType = genericType == null ? resolveBound(variable) : resolveClass(genericType, targetType); } return genericType instanceof Class ? (Class<?>) genericType : Unknown.class; } private static Map<TypeVariable<?>, Type> getTypeVariableMap(final Class<?> targetType) { Reference<Map<TypeVariable<?>, Type>> ref = CACHE.get(targetType); Map<TypeVariable<?>, Type> map = ref != null ? ref.get() : null; if (map == null) { map = new HashMap<>(); // Populate interfaces buildTypeVariableMap(targetType.getGenericInterfaces(), map); // Populate super classes and interfaces Type genericType = targetType.getGenericSuperclass(); Class<?> type = targetType.getSuperclass(); while (type != null && !Object.class.equals(type)) { if (genericType instanceof ParameterizedType) { buildTypeVariableMap((ParameterizedType) genericType, map); } buildTypeVariableMap(type.getGenericInterfaces(), map); genericType = type.getGenericSuperclass(); type = type.getSuperclass(); } // Populate enclosing classes type = targetType; while (type.isMemberClass()) { genericType = type.getGenericSuperclass(); if (genericType instanceof ParameterizedType) { buildTypeVariableMap((ParameterizedType) genericType, map); } type = type.getEnclosingClass(); } if (cacheEnabled) { CACHE.put(targetType, new WeakReference<>(map)); } } return map; } /** * Populates the {@code map} with with variable/argument pairs for the given {@code types}. */ static void buildTypeVariableMap(final Type[] types, final Map<TypeVariable<?>, Type> map) { for (Type type : types) { if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) type; buildTypeVariableMap(parameterizedType, map); Type rawType = parameterizedType.getRawType(); if (rawType instanceof Class) { buildTypeVariableMap(((Class<?>) rawType).getGenericInterfaces(), map); } } else if (type instanceof Class) { buildTypeVariableMap(((Class<?>) type).getGenericInterfaces(), map); } } } /** * Populates the {@code typeVariableMap} with type arguments and parameters for the given {@code type}. */ private static void buildTypeVariableMap(ParameterizedType type, Map<TypeVariable<?>, Type> typeVariableMap) { if (type.getRawType() instanceof Class) { TypeVariable<?>[] typeVariables = ((Class<?>) type.getRawType()).getTypeParameters(); Type[] typeArguments = type.getActualTypeArguments(); for (int i = 0; i < typeArguments.length; i++) { TypeVariable<?> variable = typeVariables[i]; Type typeArgument = typeArguments[i]; if (typeArgument instanceof Class) { typeVariableMap.put(variable, typeArgument); } else if (typeArgument instanceof GenericArrayType) { typeVariableMap.put(variable, typeArgument); } else if (typeArgument instanceof ParameterizedType) { typeVariableMap.put(variable, typeArgument); } else if (typeArgument instanceof TypeVariable) { TypeVariable<?> typeVariableArgument = (TypeVariable<?>) typeArgument; Type resolvedType = typeVariableMap.get(typeVariableArgument); if (resolvedType == null) { resolvedType = resolveBound(typeVariableArgument); } typeVariableMap.put(variable, resolvedType); } } } } /** * Resolves the first bound for the {@code typeVariable}, returning {@code Unknown.class} if none can be resolved. * * @param typeVariable TypeVariable * * @return Type. */ public static Type resolveBound(TypeVariable<?> typeVariable) { Type[] bounds = typeVariable.getBounds(); if (bounds.length == 0) { return Unknown.class; } Type bound = bounds[0]; if (bound instanceof TypeVariable) { bound = resolveBound((TypeVariable<?>) bound); } return bound == Object.class ? Unknown.class : bound; } private TypeResolver() { } }