/** * Copyright 2013 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.neba.core.util; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.WildcardType; import java.util.*; /** * @author Olaf Otto */ public final class ReflectionUtil { private static final int DEFAULT_COLLECTION_SIZE = 32; /** * Resolves the {@link Type} of the lower bound of a single type argument of a * {@link ParameterizedType}. * <p/> * <pre> * private List<MyModel> myModel -> MyModel. * private Optional<MyModel> myModel -> MyModel. * </pre> * * @param type must not be <code>null</code>. * @return never null. */ public static Type getLowerBoundOfSingleTypeParameter(Type type) { if (type == null) { throw new IllegalArgumentException("Method parameter type must not be null."); } // The generic type may contain the generic type declarations, e.g. List<String>. if (!(type instanceof ParameterizedType)) { throw new IllegalArgumentException("Cannot obtain the component type of " + type + ", it does not declare generic type parameters."); } // Only the ParametrizedType contains reflection information about the actual type. ParameterizedType parameterizedType = (ParameterizedType) type; Type[] typeArguments = parameterizedType.getActualTypeArguments(); // We expect exactly one argument representing the model type. if (typeArguments.length != 1) { signalUnsupportedNumberOfTypeDeclarations(type); } Type typeArgument = typeArguments[0]; // Wildcard type <X ... Y> if (typeArgument instanceof WildcardType) { WildcardType wildcardType = (WildcardType) typeArgument; Type[] lowerBounds = wildcardType.getLowerBounds(); if (lowerBounds.length == 0) { throw new IllegalArgumentException("Cannot obtain the generic type of " + type + ", it has a wildcard declaration with an upper" + " bound (<? extends Y>) and is thus read-only." + " Only simple type parameters (e.g. <MyType>)" + " or lower bound wildcards (e.g. <? super MyModel>)" + " are supported."); } typeArgument = lowerBounds[0]; } return typeArgument; } private static void signalUnsupportedNumberOfTypeDeclarations(Type type) { throw new IllegalArgumentException("Cannot obtain the component type of " + type + ", it must have exactly one parameter type, e.g. <MyModel>."); } /** * Provides an implementation instance for the desired collection type. * Example: If the collection type is {@link List}, a suitable list implementation is instantiated. * * @param collectionType must not be <code>null</code>. * @return never <code>null</code>. Throws an {@link IllegalStateException} * if the collection type is not supported. */ @SuppressWarnings("unchecked") public static <K, T extends Collection<K>> Collection<K> instantiateCollectionType(Class<T> collectionType, int length) { if (collectionType == null) { throw new IllegalArgumentException("Method parameter collectionType must not be null."); } T collection; // This includes java.util.Collection if (collectionType.isAssignableFrom(List.class)) { collection = (T) new ArrayList<K>(length); } else if (collectionType.isAssignableFrom(Set.class)) { collection = (T) new LinkedHashSet<K>(length); } else { throw new IllegalArgumentException("Unable to instantiate a collection of type " + collectionType + ". Only collections assignable from " + List.class + " or " + Set.class + " are supported."); } return collection; } /** * @param type must not be <code>null</code>. * @return whether the given collection type can be instantiated using {@link #instantiateCollectionType(Class)}. */ public static boolean isInstantiableCollectionType(Class<?> type) { if (type == null) { throw new IllegalArgumentException("Method argument type must not be null."); } for (Class<?> supportedType : getInstantiableCollectionTypes()) { if (type.isAssignableFrom(supportedType)) { return true; } } return false; } /** * @return an array of the types a collection type * must be {@link Class#isAssignableFrom(Class) assignable from} in order to be * {@link #isInstantiableCollectionType(Class) instantiable}. */ public static Class<?>[] getInstantiableCollectionTypes() { return new Class[]{List.class, Set.class}; } /** * Creates an instance with a default capacity. * * @see #instantiateCollectionType(Class, int) */ public static <K, T extends Collection<K>> Collection<K> instantiateCollectionType(Class<T> collectionType) { return instantiateCollectionType(collectionType, DEFAULT_COLLECTION_SIZE); } private ReflectionUtil() { } }