/**
* Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.seedstack.seed.core.internal.guice;
import com.google.common.reflect.TypeToken;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import com.google.inject.util.Types;
import org.seedstack.seed.SeedException;
import org.seedstack.seed.core.internal.CoreErrorCode;
import org.seedstack.shed.reflect.AnnotationPredicates;
import org.seedstack.shed.reflect.Annotations;
import javax.inject.Qualifier;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* Utilities for Guice bindings.
*/
public final class BindingUtils {
private BindingUtils() {
// no instantiation allowed
}
/**
* Construct a checked map of bindable key with the corresponding implementation class.
* <p>
* Given the following injectee class and implementation classes :
* <pre>
* interface Sort<T> {...}
*
* {@literal @}Named("quick")
* class StringQuickSort implements Sort<String> {...}
*
* {@literal @}Named("bubble")
* class BubbleSort implements Sort<String> {...}
*
* class LongQuickSort implements Sort<Long> {...}
* </pre>
* The method will return a map with:
* <pre>
* Key[type=Sort<String>, annotation = Named[value = "quick"]] -> StringQuickSort
* Key[type=Sort<String>, annotation = Named[value = "bubble"]] -> BubbleSort
* Key[type=Sort<Long>] -> LongQuickSort
* </pre>
* Possible injection patterns will be:
* <pre>
* {@literal @}Inject {@literal @}Name("quick")
* Sort@lt;String> stringQuickSort;
*
* {@literal @}Inject {@literal @}Name("bubble")
* Sort@lt;String> bubbleSort;
*
* {@literal @}Inject
* Sort@lt;Long> longQuickSort;
* </pre>
*
* @param injecteeClass the parent class to reach
* @param firstImplClass the first class
* @param restImplClasses the sub classes
* @return a multimap with typeliterals for keys and a list of associated subclasses for values
* @throws SeedException when duplicates keys are found.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static <T> Map<Key<T>, Class<? extends T>> resolveBindingDefinitions(Class<T> injecteeClass, Class<? extends T> firstImplClass, Class<? extends T>... restImplClasses) {
Map<Key<T>, Class<? extends T>> typeLiterals = new HashMap<>();
List<Class<? extends T>> subClasses = new ArrayList<>();
if (firstImplClass != null) {
subClasses.add(firstImplClass);
}
if (restImplClasses != null && restImplClasses.length > 0) {
subClasses.addAll(Arrays.asList(restImplClasses));
}
for (Class<? extends T> subClass : subClasses) {
if (isBindable(subClass)) {
Type resolvedType = TypeToken.of(subClass).getSupertype((Class) injecteeClass).getType();
TypeLiteral<T> parentTypeLiteral;
if (resolvedType == null) {
parentTypeLiteral = TypeLiteral.get(injecteeClass);
} else {
parentTypeLiteral = (TypeLiteral<T>) TypeLiteral.get(resolvedType);
}
Optional<Annotation> qualifier = Annotations.on(subClass)
.traversingSuperclasses()
.traversingInterfaces()
.findAll()
.filter(AnnotationPredicates.annotationAnnotatedWith(Qualifier.class, false))
.findFirst();
Key<T> key = qualifier.map(annotation -> Key.get(parentTypeLiteral, annotation)).orElseGet(() -> Key.get(parentTypeLiteral));
if (typeLiterals.containsKey(key)) {
throw SeedException.createNew(CoreErrorCode.DUPLICATED_BINDING_KEY)
.put("duplicatedKey", key)
.put("firstClass", subClass.getName())
.put("secondClass", typeLiterals.get(key).getName());
}
typeLiterals.put(key, subClass);
}
}
return typeLiterals;
}
/**
* Same as {@link #resolveBindingDefinitions(Class, Class, Class...)}.
*
* @param injecteeClass the parent class to reach
* @param implClasses the sub classes collection
* @return a multimap with typeliterals for keys and a list of associated subclasses for values
*/
@SuppressWarnings("unchecked")
public static <T> Map<Key<T>, Class<? extends T>> resolveBindingDefinitions(Class<T> injecteeClass, Collection<Class<? extends T>> implClasses) {
if (implClasses != null && !implClasses.isEmpty()) {
return resolveBindingDefinitions(injecteeClass, null, implClasses.toArray(new Class[implClasses.size()]));
}
return new HashMap<>();
}
/**
* Checks if the class is not an interface, an abstract class or a class with unresolved generics.
*
* @param subClass the class to check
* @return true if the class verify the condition, false otherwise
*/
public static boolean isBindable(Class<?> subClass) {
return !subClass.isInterface() && !Modifier.isAbstract(subClass.getModifiers()) && subClass.getTypeParameters().length == 0;
}
/**
* Resolve the key for injectee class, including qualifier from implClass and resolved type variables.
* <p>
* Useful when we can not resolve type variable from one implementation.
*
* @param injecteeClass the injectee class
* @param genericImplClass the generic implementation
* @param typeVariableClasses the type variable classes
* @return {@link com.google.inject.Key}
*/
@SuppressWarnings("unchecked")
public static <T> Key<T> resolveKey(Class<T> injecteeClass, Class<? extends T> genericImplClass, Type... typeVariableClasses) {
Optional<Annotation> qualifier = Annotations.on(genericImplClass)
.findAll()
.filter(AnnotationPredicates.annotationAnnotatedWith(Qualifier.class, false))
.findFirst();
TypeLiteral<T> genericInterface = (TypeLiteral<T>) TypeLiteral.get(Types.newParameterizedType(injecteeClass, typeVariableClasses));
return qualifier.map(annotation -> Key.get(genericInterface, annotation)).orElseGet(() -> Key.get(genericInterface));
}
/**
* Tests if the class is a proxy.
*
* @param proxyClass The class to test.
* @return true if class is proxy false otherwise.
*/
public static boolean isProxy(Class<?> proxyClass) {
return proxyClass.getName().contains("EnhancerByGuice");
}
/**
* Return the non proxy class if needed.
*
* @param toClean The class to clean.
* @return the cleaned class.
*/
public static Class<?> cleanProxy(Class<?> toClean) {
if (BindingUtils.isProxy(toClean)) {
return toClean.getSuperclass();
}
return toClean;
}
}