Java tutorial
/* * Copyright (c) 2015 Andrew Coates * * 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 org.datalorax.populace.core.util; import org.apache.commons.lang3.Validate; import java.lang.reflect.*; import java.util.*; /** * @author Andrew Coates - 04/03/2015. */ public final class TypeUtils { private static final List<Class<?>> PRIMITIVE_TYPES = Collections.unmodifiableList(Arrays.asList(boolean.class, byte.class, char.class, short.class, int.class, long.class, float.class, double.class)); private static final List<Class<?>> BOXED_PRIMITIVE_TYPES = Collections .unmodifiableList(Arrays.asList(Boolean.class, Byte.class, Character.class, Short.class, Integer.class, Long.class, Float.class, Double.class)); private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_BOXED_TYPES = new HashMap<Class<?>, Class<?>>() { { put(boolean.class, Boolean.class); put(byte.class, Byte.class); put(char.class, Character.class); put(short.class, Short.class); put(int.class, Integer.class); put(long.class, Long.class); put(float.class, Float.class); put(double.class, Double.class); } }; public static List<Class<?>> getPrimitiveTypes() { return PRIMITIVE_TYPES; } public static List<Class<?>> getBoxedPrimitiveTypes() { return BOXED_PRIMITIVE_TYPES; } public static Class<?> getBoxedTypeForPrimitive(Class<?> primitiveType) { final Class<?> boxed = PRIMITIVE_TO_BOXED_TYPES.get(primitiveType); Validate.notNull(boxed, "Not a primitive type: %s", primitiveType); return boxed; } /** * Gets a single type argument from the set of type arguments of a class/interface based on a declaring class of the * supplied {@code typeVariable}. * * For instance, given the parameterised type representing {@code Map<String,Integer>} and the * {@code typeVariable} of {@code Map.class.getTypeParameters()[0]}, then this method will return String.class. * * This method will work even if the type represented by {@code type} is a subtype of the required type and does * not itself have any template arguments. For example, this method will determine that both of the parameters for * the interface {@link Map} are {@link Object} for the subtype {@link java.util.Properties Properties} even though * the subtype does not directly implement the {@code Map} interface. * * If the parameterized {@code type}'s type arguments are {@link java.lang.reflect.TypeVariable}s or * {@link java.lang.reflect.WildcardType}, then these are returned. * * If the {@code type} is not a parameterised type, but a raw {@code Class}, and {@code typeVariable} is a valid * {@link java.lang.reflect.TypeVariable} of the type or its super type/interfaces, then the method will return the * {@link java.lang.reflect.Type type} representing the type argument. For example, if {@code type} is * {@code ArrayList.class} and {@code typeVariable} is {@code List.class.getTypeParameters()[0]}, then the method will * return the equivalent of {@code ArrayList.class.getTypeParameters()[0]} * * This method throws {@link java.lang.IllegalArgumentException} if {@code type} is not assignable to {@code toClass}. * It returns an Object.class if the actual type parameter can not be determined. * * @param type the type from which to determine the type parameters of {@code toClass} * @param typeVariable the specific typeVariable of {@code toClass} to retrieve. * @return the {@code Class} of the type argument, or null if {@code type} is not assignable to {@code toClass} * @throws java.lang.IllegalArgumentException if {@code type} is not assignable to {@code toClass}. */ public static Type getTypeArgument(final Type type, final TypeVariable<? extends Class<?>> typeVariable) { final Class<?> toClass = typeVariable.getGenericDeclaration(); if (toClass.equals(type)) { return typeVariable; } final Map<TypeVariable<?>, Type> typeArguments = org.apache.commons.lang3.reflect.TypeUtils .getTypeArguments(type, toClass); if (typeArguments == null) { throw new IllegalArgumentException(type + " is not assignable to " + toClass); } final Type typeArg = typeArguments.get(typeVariable); return typeArg == null ? Object.class : typeArg; } /** * Gets a single type argument from the set of type arguments of a class/interface based on a * the {@code toClass} subtype. For instance, given the parameterised type representing {@code Map<String,Integer>} * , the {@code toClass} value of {@code Map.class}, and the {@code typeVariable} of * {@code Map.class.getTypeParameters()[0]}, then this method will return String.class. This method will work even * if the type represented by {@code type} is a subtype of the required type and does not itself have any template * arguments. For example, this method will determine that both of the parameters for the interface {@link Map} are * {@link Object} for the subtype {@link java.util.Properties Properties} even though the subtype does not directly * implement the {@code Map} interface. If the {@code type} is not a parameterised type, but a raw {@code Class} then * the method will return {@code Object.class} * <p> * This method throws {@link java.lang.IllegalArgumentException} if {@code type} is not assignable to {@code toClass}. * It returns an Object.class if the actual type parameter can not be determined. * * @param type the type from which to determine the type parameters of {@code toClass} * @param toClass the class whose type parameter is to be determined based on the subtype {@code type} * @param typeVariable the specific typeVariable of {@code toClass} to retrieve. * @param <T> The type of {@code toClass} * @return the {@code Class} of the type argument, or null if {@code type} is not assignable to {@code toClass} * @throws java.lang.IllegalArgumentException if {@code type} is not assignable to {@code toClass}. * @deprecated use {@link #getTypeArgument(java.lang.reflect.Type, java.lang.reflect.TypeVariable)} */ @SuppressWarnings("UnusedParameters") @Deprecated // Todo(v2.x): remove deprecated public static <T> Type getTypeArgument(final Type type, final Class<T> toClass, final TypeVariable<Class<T>> typeVariable) { return getTypeArgument(type, typeVariable); } /** * Get the array component type of {@code type}. * @param type the type to be checked * @return component type or null if type is not an array type */ public static Type getArrayComponentType(final Type type) { return org.apache.commons.lang3.reflect.TypeUtils.getArrayComponentType(type); } /** * Learn whether the specified type denotes an array type. * * @param type the type to be checked * @return {@code true} if {@code type} is an array class or a {@link java.lang.reflect.GenericArrayType}. * @see org.apache.commons.lang3.reflect.TypeUtils#isArrayType(java.lang.reflect.Type) */ public static boolean isArrayType(final Type type) { return org.apache.commons.lang3.reflect.TypeUtils.isArrayType(type); } /** * Checks if the subject type may be implicitly cast to the target type following the Java generics rules. * * @param type the subject type to be assigned to the target type * @param toType the target type * @return {@code true} if {@code type} is assignable to {@code toType}. * @see org.apache.commons.lang3.reflect.TypeUtils#isAssignable(Type, Type) */ public static boolean isAssignable(final Type type, final Type toType) { return org.apache.commons.lang3.reflect.TypeUtils.isAssignable(type, toType); } /** * Create a wild card type instance with no bounds * * @return {@link java.lang.reflect.WildcardType} */ public static WildcardType wildcardType() { return org.apache.commons.lang3.reflect.TypeUtils.wildcardType().withUpperBounds(Object.class) // implicit .build(); } /** * Create a wild card type instance with the specified {@code lowerBounds} * * @param lowerBounds the lower bounds to add. * @return {@link java.lang.reflect.WildcardType} */ public static WildcardType wildcardTypeWithLowerBounds(final Type... lowerBounds) { return org.apache.commons.lang3.reflect.TypeUtils.wildcardType().withUpperBounds(Object.class) // implicit .withLowerBounds(lowerBounds).build(); } /** * Create a wild card type instance with the specified {@code upperBounds} * * @param upperBounds the upper bounds to add. * @return {@link java.lang.reflect.WildcardType} */ public static WildcardType wildcardTypeWithUpperBounds(final Type... upperBounds) { return org.apache.commons.lang3.reflect.TypeUtils.wildcardType() .withUpperBounds(upperBounds.length == 0 ? new Type[] { Object.class } : upperBounds).build(); } /** * Create a generic array type instance. * * @param componentType the type of the elements of the array. For example the component type of {@code boolean[]} * is {@code boolean} * @return {@link GenericArrayType} * @see org.apache.commons.lang3.reflect.TypeUtils#genericArrayType(java.lang.reflect.Type) */ public static GenericArrayType genericArrayType(final Type componentType) { return org.apache.commons.lang3.reflect.TypeUtils.genericArrayType(componentType); } /** * Create a parameterised type instance. * * @param raw the raw class to create a parameterized type instance for * @param typeArguments the types used for parameterisation * @return {@link ParameterizedType} * * @see org.apache.commons.lang3.reflect.TypeUtils#parameterize(java.lang.Class, Type...) */ public static ParameterizedType parameterise(final Class<?> raw, final Type... typeArguments) { return org.apache.commons.lang3.reflect.TypeUtils.parameterize(raw, typeArguments); } /** * Get the raw {@link Class} from the {@code type} provided * * @param type to resolve * @param assigningType type to be resolved against * @return the resolved {@link Class} object or {@code null} if the type could not be resolved * * @see org.apache.commons.lang3.reflect.TypeUtils#getRawType(java.lang.reflect.Type, java.lang.reflect.Type) */ public static Class<?> getRawType(final Type type, final Type assigningType) { return org.apache.commons.lang3.reflect.TypeUtils.getRawType(type, assigningType); } /** * Returns an abbreviated generic name for logging purposes. Package names are abbreviated to a single character. * Generic info is included. * * @param type the type whose name should be abbreviated. * @return the abbreviated class name */ public static String abbreviatedName(final Type type) { if (type instanceof Class) { return abbreviatedName(((Class<?>) type).getName()); } if (type instanceof ParameterizedType) { final ParameterizedType pt = (ParameterizedType) type; return abbreviatedName(pt.getTypeName()); } throw new IllegalArgumentException("Type not supported: " + type); } /** * Ensures a consistent implementation of the different sub types i.e. {@link java.lang.reflect.ParameterizedType}, * {@link java.lang.Class}, {@link java.lang.reflect.TypeVariable} and {@link java.lang.reflect.GenericArrayType}. * <p> * This is useful if you need to compare them, as implementations from different vendors do not generally compare as * equal, even if the type information they convey is equivalent. * <p> * Calling this method with two equivalent types, from two different implementations, will result in two {@code type}s * that will be equal i.e. {@code ensureTypeImpl(t1).equals(ensureTypeImpl(t2))} will return true. * * @param type the type, from any implementation, for which a consistent implementation is required. * @return the same type, but from a consistent implementation. */ public static Type ensureConsistentType(final Type type) { if (type instanceof Class) { return type; // Final class, so only one impl. } if (type instanceof ParameterizedType) { return ensureConsistentParameterisedType((ParameterizedType) type); } if (type instanceof TypeVariable) { return type; } if (type instanceof WildcardType) { return ensureConsistentWildcard((WildcardType) type); } if (type instanceof GenericArrayType) { return ensureConsistentGenericArrayType((GenericArrayType) type); } throw new IllegalArgumentException("Unexpected type: " + type.getClass()); } /** * Returns true is the types are equal or one is a subtype of the other. * * @param first the first type to compare * @param second the second type to compare * @return true if they are related, false otherwise. */ public static boolean areRelatedTypes(final Class<?> first, final Class<?> second) { return first.equals(second) || first.isAssignableFrom(second) || second.isAssignableFrom(first); } public static Class<?> getMostDerivedClass(final Class<?> a, final Class<?> b) { if (a.isAssignableFrom(b)) { return b; } if (b.isAssignableFrom(a)) { return a; } throw new IllegalArgumentException("Unrelated types: " + a + " & " + b); } private static Type ensureConsistentParameterisedType(final ParameterizedType type) { final Type[] consistentTypeArgs = Arrays.stream(type.getActualTypeArguments()) .map(TypeUtils::ensureConsistentType).toArray(Type[]::new); return parameterise(getRawType(type, null), consistentTypeArgs); } private static Type ensureConsistentWildcard(final WildcardType type) { final Type[] lowerBounds = Arrays.stream(type.getLowerBounds()).map(TypeUtils::ensureConsistentType) .toArray(Type[]::new); final Type[] upperBounds = Arrays.stream(type.getUpperBounds()).map(TypeUtils::ensureConsistentType) .toArray(Type[]::new); return org.apache.commons.lang3.reflect.TypeUtils.wildcardType().withLowerBounds(lowerBounds) .withUpperBounds(upperBounds).build(); } private static Type ensureConsistentGenericArrayType(final GenericArrayType type) { final Type componentType = ensureConsistentType(type.getGenericComponentType()); return org.apache.commons.lang3.reflect.TypeUtils.genericArrayType(componentType); } private static String abbreviatedName(final String typeName) { final StringBuilder builder = new StringBuilder(); int startOfLastWord = 0; for (int i = 0; i != typeName.length(); ++i) { final char c = typeName.charAt(i); if (c == '.') { builder.append(typeName.charAt(startOfLastWord)).append('.'); startOfLastWord = i + 1; } else if (c == '<' || '>' == c) { builder.append(typeName.substring(startOfLastWord, i + 1)); startOfLastWord = i + 1; } } builder.append(typeName.substring(startOfLastWord)); return builder.toString(); } }