tiger.Utils.java Source code

Java tutorial

Introduction

Here is the source code for tiger.Utils.java

Source

// Copyright 2016 Google Inc.
//
// 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 tiger;

import static com.google.auto.common.MoreElements.isAnnotationPresent;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;

import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ArrayTypeName;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import com.squareup.javapoet.WildcardTypeName;

import dagger.Lazy;
import dagger.MapKey;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.ElementsIntoSet;
import dagger.multibindings.IntoMap;
import dagger.multibindings.IntoSet;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.processing.ProcessingEnvironment;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Qualifier;
import javax.inject.Scope;
import javax.inject.Singleton;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;

/**
 * Misc utilities to help reusing codes.
 */
class Utils {

    private static final String PACKAGED_INJECTOR_NAME = "PackagedInjector";
    private static final String MULTI_BINDING_INJECTOR_NAME = "MultiBindingInjector";
    private static final Logger LOGGER = Logger.getLogger("Utils");

    static {
        LOGGER.setLevel(Level.SEVERE);
    }

    public static boolean isSingletonScoped(Elements elementUtils, Element element) {
        DeclaredType scopeType = Utils.getScopeType(element);
        boolean result = scopeType != null
                && scopeType.asElement().equals(elementUtils.getTypeElement(Singleton.class.getCanonicalName()));
        return result;
    }

    /**
     * Return the binding qualifier of the given {@link Element}, null if none.
     */
    @Nullable
    public static AnnotationMirror getQualifier(Element element) {
        return getAnnotationMirrorWithMetaAnnotation(element, Qualifier.class);
    }

    /**
     * Return the binding qualifier of the given {@link Element}, null if none.
     */
    @Nullable
    public static AnnotationMirror getMapKey(Element element) {
        return getAnnotationMirrorWithMetaAnnotation(element, MapKey.class);
    }

    /**
     * Return the annotation with given metaAnnotation, null if none.
     */
    @Nullable
    public static AnnotationMirror getAnnotationMirrorWithMetaAnnotation(Element element,
            Class<? extends Annotation> metaAnnotation) {
        for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
            DeclaredType annotationType = annotationMirror.getAnnotationType();
            if (annotationType.asElement().getAnnotation(metaAnnotation) != null) {
                return annotationMirror;
            }
        }
        return null;
    }

    /**
     * Returns value for the key in the given annotation, null if key does not exist.
     */
    @Nullable
    public static AnnotationValue getAnnotationValue(AnnotationMirror annotationMirror, String key) {
        for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror
                .getElementValues().entrySet()) {
            if (entry.getKey().getSimpleName().contentEquals(key)) {
                return entry.getValue();
            }
        }
        return null;
    }

    /**
     * Returns type for the key in the given annotation, null if key does not exist.
     */
    @Nullable
    public static TypeMirror getAnnotationValueType(AnnotationMirror annotationMirror, String key) {
        for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror
                .getElementValues().entrySet()) {
            if (entry.getKey().getSimpleName().contentEquals(key)) {
                return entry.getKey().getReturnType();
            }
        }
        return null;
    }

    /**
     * Returns type mirror of given element of the given mirror, null if the element does not exist.
     */
    @Nullable
    public static TypeMirror getElementTypeMirror(AnnotationMirror annotationMirror, String key) {
        for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror
                .getElementValues().entrySet()) {
            if (entry.getKey().getSimpleName().contentEquals(key)) {
                return entry.getKey().getReturnType();
            }
        }
        return null;
    }

    /**
     * Returns specified annotation, null if not exist.
     */
    @Nullable
    public static AnnotationMirror getAnnotationMirror(Element element,
            Class<? extends Annotation> annotationClass) {
        for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
            DeclaredType annotationType = annotationMirror.getAnnotationType();
            if (((TypeElement) annotationType.asElement()).getQualifiedName()
                    .contentEquals(annotationClass.getCanonicalName())) {
                return annotationMirror;
            }
        }
        return null;
    }

    /**
     * Return the binding qualifier of the given {@link Element}, null if none.
     */
    @Nullable
    public static DeclaredType getScopeType(Element element) {
        for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
            DeclaredType annotationType = annotationMirror.getAnnotationType();
            if (isScopeTypeElement((TypeElement) annotationType.asElement())) {
                return annotationType;
            }
        }
        return null;
    }

    /**
     * Return if the given {@link TypeElement} is annotated with {@link Scope}.
     */
    public static boolean isScopeTypeElement(TypeElement element) {
        return element.getAnnotation(Scope.class) != null;
    }

    public static boolean isProvidesMethod(Element element, ProcessingEnvironment env) {
        return element.getKind().equals(ElementKind.METHOD) && hasAnnotationMirror(element, Provides.class, env);
    }

    /**
     * Returns specified annotation, null does not exist.
     */
    public static boolean hasAnnotationMirror(Element element, Class<? extends Annotation> annotation,
            ProcessingEnvironment env) {
        Elements elements = env.getElementUtils();
        for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
            if (annotationMirror.getAnnotationType().asElement()
                    .equals(elements.getTypeElement(annotation.getCanonicalName()))) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if the given type can be bound to some type.
     */
    public static boolean isBindableType(TypeMirror type) {
        boolean result;
        if (type.getKind().isPrimitive()) {
            result = true;
        } else {
            TypeKind typeKind = type.getKind();
            switch (typeKind) {
            case DECLARED:
                DeclaredType declaredType = (DeclaredType) type;
                List<? extends TypeMirror> args = declaredType.getTypeArguments();
                result = true;
                for (TypeMirror argumentType : args) {
                    if (!isBindableType(argumentType)) {
                        result = false;
                        break;
                    }
                }
                break;
            case WILDCARD:
                result = true;
                break;
            default:
                result = false;
            }
        }
        LOGGER.log(Level.INFO, String.format("isBindableType: %s : %s : %s", type, type.getKind(), result));
        return result;
    }

    public static NewBindingKey getKeyProvidedByMethod(ExecutableElement method) {
        return NewBindingKey.get(method.getReturnType(), getQualifier(method));
    }

    /**
     * Return {@link NewDependencyInfo} for the generalized {@link NewBindingKey} for
     * the give key. Null if not applicable or not exist.
     */
    public static NewDependencyInfo getDependencyInfoByGeneric(
            SetMultimap<NewBindingKey, NewDependencyInfo> dependencies, NewBindingKey key) {
        TypeName typeName = key.getTypeName();
        Preconditions.checkArgument(key.getQualifier() == null,
                String.format("Binding to %s is supposed to be resolved through generic type of %s"
                        + "but has non-null qualifier.", key, typeName));
        if (typeName instanceof ParameterizedTypeName) {
            ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) typeName;
            ClassName rawTypeName = parameterizedTypeName.rawType;
            NewBindingKey rawKey = NewBindingKey.get(rawTypeName);
            if (dependencies.containsKey(rawKey)) {
                NewDependencyInfo dependencyInfo = Iterables.getOnlyElement(dependencies.get(rawKey));
                TypeName formalTypeName = dependencyInfo.getDependant().getTypeName();
                Preconditions.checkState(formalTypeName instanceof ParameterizedTypeName,
                        String.format(
                                "Formal type %s is not of type ParameterizedTypeName. Related actual type is %s",
                                formalTypeName, parameterizedTypeName));

                Map<TypeVariableName, TypeName> mapTypeVariableToSpecialized = getMapFromTypeVariableToSpecialized(
                        parameterizedTypeName, (ParameterizedTypeName) formalTypeName);
                Set<NewBindingKey> specializedDependencies = specializeIfNeeded(dependencyInfo.getDependencies(),
                        mapTypeVariableToSpecialized);
                return new NewDependencyInfo(key, specializedDependencies, dependencyInfo.getSourceClassElement(),
                        dependencyInfo.getProvisionMethodElement(), dependencyInfo.getType());
            }
        }
        return null;
    }

    public static Map<TypeVariableName, TypeName> getMapFromTypeVariableToSpecialized(ParameterizedTypeName actual,
            ParameterizedTypeName formal) {
        Preconditions.checkArgument(formal.typeArguments.size() == actual.typeArguments.size(),
                String.format("Incompatible actual type %s and formal type %s.", actual, formal));
        Preconditions.checkArgument(!formal.typeArguments.isEmpty(),
                String.format("formal type %s and actual type %s has no argument.", formal, actual));

        Map<TypeVariableName, TypeName> result = new HashMap<>();
        for (int i = 0; i < formal.typeArguments.size(); i++) {
            Preconditions.checkArgument(formal.typeArguments.get(i) instanceof TypeVariableName,
                    String.format("formal type %s has non-TypeVariableName parameter.", formal));
            Preconditions.checkArgument(!(actual.typeArguments.get(i) instanceof TypeVariableName),
                    String.format("actual type %s has TypeVariableName parameter.", actual));
            result.put(((TypeVariableName) formal.typeArguments.get(i)), actual.typeArguments.get(i));
        }

        return result;
    }

    public static Set<NewBindingKey> specializeIfNeeded(Set<NewBindingKey> keys,
            Map<TypeVariableName, TypeName> map) {
        Set<NewBindingKey> result = new HashSet<>();
        for (NewBindingKey k : keys) {
            result.add(specializeIfNeeded(k, map));
        }

        return result;
    }

    /**
     * Returns a {@link TypeName} with TypeVariable replaced by specialType if
     * applicable.
     */
    public static NewBindingKey specializeIfNeeded(NewBindingKey dependencyKey,
            Map<TypeVariableName, TypeName> map) {
        AnnotationSpec qualifier = dependencyKey.getQualifier();
        TypeName typeName = specializeIfNeeded(dependencyKey.getTypeName(), map);
        return NewBindingKey.get(typeName, qualifier);
    }

    /**
     * Returns true if the given type can be bound to some type. Note: this should
     * not be used with raw type of generic type.
     */
    public static boolean isBindable(TypeName typeName) {
        if (typeName instanceof ParameterizedTypeName) {
            for (TypeName t : ((ParameterizedTypeName) typeName).typeArguments) {
                if (!isBindable(t)) {
                    return false;
                }
            }
            return true;
        } else if (typeName instanceof ClassName) {
            return true;
        } else if (typeName instanceof WildcardTypeName) {
            return true;
        } else
            return typeName.isPrimitive();
    }

    public static PackageElement getPackage(TypeElement typeElement) {
        Element result = typeElement.getEnclosingElement();
        ElementKind elementKind = result.getKind();
        while (!elementKind.equals(ElementKind.PACKAGE)) {
            Preconditions.checkState(elementKind.isClass() || elementKind.isInterface(),
                    String.format("Utils.getPackage: unexpected kind: %s for type: %s", elementKind, typeElement));
            result = result.getEnclosingElement();
            elementKind = result.getKind();
        }
        return (PackageElement) result;
    }

    public static String getPackageString(TypeElement typeElement) {

        return getPackage(typeElement).getQualifiedName().toString();
    }

    public static List<NewBindingKey> getDependenciesFromExecutableElement(ExecutableElement executableElement) {
        List<NewBindingKey> keys = new ArrayList<>();
        for (VariableElement variableElement : executableElement.getParameters()) {
            keys.add(NewBindingKey.get(variableElement));
        }
        return keys;
    }

    public static String getQualifiedName(TypeElement typeElement) {
        return typeElement.getQualifiedName().toString();
    }

    public static String getGetMethodName(TypeElement cls) {
        return getGetMethodName(ClassName.get(cls));
    }

    public static String getCanonicalName(ClassName className) {
        Joiner joiner = Joiner.on(".");
        return joiner.join(Lists.asList(className.packageName(), className.simpleNames().toArray(new String[0])));
    }

    public static List<VariableElement> getInjectedFields(TypeElement cls, ProcessingEnvironment env) {
        List<VariableElement> result = new ArrayList<>();
        for (Element element : cls.getEnclosedElements()) {
            if (element.getKind().equals(ElementKind.FIELD) && isInjected(element, env)) {
                result.add((VariableElement) element);
            }
        }
        return result;
    }

    public static List<ExecutableElement> getInjectedMethods(TypeElement cls, ProcessingEnvironment env) {
        List<ExecutableElement> result = new ArrayList<>();
        for (Element element : cls.getEnclosedElements()) {
            if (element.getKind().equals(ElementKind.METHOD) && isInjected(element, env)) {
                result.add((ExecutableElement) element);
            }
        }
        return result;
    }

    public static boolean hasInjectedFieldsOrMethods(TypeElement cls, ProcessingEnvironment env) {
        return !getInjectedFields(cls, env).isEmpty() || !getInjectedMethods(cls, env).isEmpty();
    }

    public static boolean isInjectionMethod(Element element) {
        if (!element.getKind().equals(ElementKind.METHOD)) {
            return false;
        }
        ExecutableElement executableElement = (ExecutableElement) element;
        return executableElement.getReturnType().getKind().equals(TypeKind.VOID)
                && (executableElement.getParameters().size() == 1);
    }

    public static boolean isProvisionMethodInInjector(Element element) {
        if (!element.getKind().equals(ElementKind.METHOD)) {
            return false;
        }
        ExecutableElement executableElement = (ExecutableElement) element;
        return !executableElement.getReturnType().getKind().equals(TypeKind.VOID)
                && (executableElement.getParameters().size() == 0);
    }

    public static boolean hasAnonymousParentClass(TypeElement cls) {
        Preconditions.checkNotNull(cls);
        do {
            if (cls.getSimpleName().length() == 0) {
                return true;
            }
            TypeMirror parentClass = cls.getSuperclass();
            if (parentClass.getKind().equals(TypeKind.NONE)) {
                return false;
            }

            cls = (TypeElement) ((DeclaredType) parentClass).asElement();
        } while (true);
    }

    /**
     * Returns the injected ctor, null if none.
     */
    public static ExecutableElement findInjectedCtor(TypeElement cls, ProcessingEnvironment env) {
        for (Element element : cls.getEnclosedElements()) {
            if (element.getKind().equals(ElementKind.CONSTRUCTOR) && isInjected(element, env)) {
                return (ExecutableElement) element;
            }
        }
        return null;
    }

    /**
     * Returns whether the given {@link AccessibleObject} is injected.
     */
    public static boolean isInjected(Element element, ProcessingEnvironment env) {
        return hasAnnotationMirror(element, Inject.class, env);
    }

    public static TypeName getTypeName(TypeMirror typeMirror) throws ResolveTypeMirrorException {
        try {
            return TypeName.get(typeMirror);
        } catch (Exception e) {
            throw new ResolveTypeMirrorException(typeMirror);
        }
    }

    /**
     * Returns if the key has built-in binding.
     */
    public static boolean hasBuiltinBinding(NewBindingKey key) {
        return hasBuiltinBinding(key.getTypeName());
    }

    public static boolean hasBuiltinBinding(TypeName type) {
        if (!(type instanceof ParameterizedTypeName)) {
            return false;
        }
        ParameterizedTypeName typeName = (ParameterizedTypeName) type;

        ClassName rawType = typeName.rawType;
        return rawType.equals(ClassName.get(Provider.class)) || rawType.equals(ClassName.get(Lazy.class));
    }

    /**
     * Return null means the give key is not bound, which is an error. We cannot
     * return empty Set in this case because that means the binding exists and
     * depends on nothing.
     */
    @Nullable
    public static Set<NewDependencyInfo> getDependencyInfo(
            SetMultimap<NewBindingKey, NewDependencyInfo> dependencies, NewBindingKey key) {
        if (dependencies.containsKey(key)) {
            return dependencies.get(key);
        } else if (hasBuiltinBinding(key)) {
            return getDependencyInfo(dependencies, getElementKeyForBuiltinBinding(key));
        } else if (isMap(key)) {
            // Handle the case that value is dagger built-in type.
            NewBindingKey peeledKey = peelMapWithBuiltinValue(key);
            if (peeledKey != null) {
                return getDependencyInfo(dependencies, peeledKey);
            } else {
                return null;
            }
        } else {
            NewDependencyInfo dependencyInfo = getDependencyInfoByGeneric(dependencies, key);
            if (dependencyInfo == null) {
                return null;
            } else {
                return Sets.newHashSet(dependencyInfo);
            }
        }
    }

    /**
     * If the key comes with value of type that has dagger builtin binding, return one
     * with the type replaced by the element of the original value type, null otherwise.
     * Nested built-in binding like Lazy<Lazy<Foo>>, Provider<Lazy<Foo>>, etc, are not 
     * supported.
     */
    @Nullable
    public static NewBindingKey peelMapWithBuiltinValue(NewBindingKey key) {
        Preconditions.checkState(isMap(key), String.format("Expect a map but got %s", key));
        ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) key.getTypeName();
        TypeName valueType = parameterizedTypeName.typeArguments.get(1);
        if (hasBuiltinBinding(valueType)) {
            TypeName mapKeyType = parameterizedTypeName.typeArguments.get(0);
            TypeName elementType = Iterables.getOnlyElement(((ParameterizedTypeName) valueType).typeArguments);
            TypeName newType = ParameterizedTypeName.get(ClassName.get(Map.class), mapKeyType, elementType);
            return NewBindingKey.get(newType, key.getQualifier());
        }

        return null;
    }

    public static boolean isMapWithBuiltinValueType(NewBindingKey key) {
        return isMap(key) && peelMapWithBuiltinValue(key) != null;
    }

    /**
     * Returns if key is a map with type variables.
     */
    public static boolean isMap(NewBindingKey key) {
        TypeName typeName = key.getTypeName();
        if (!(typeName instanceof ParameterizedTypeName)) {
            return false;
        }
        ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) typeName;
        return parameterizedTypeName.rawType.equals(ClassName.get(Map.class));
    }

    /**
     * Return {@link NewBindingKey} for element of the give {@link NewBindingKey} that
     * has built-in binding, null if not built-in building.
     */
    @Nullable
    public static NewBindingKey getElementKeyForBuiltinBinding(NewBindingKey key) {
        if (!hasBuiltinBinding(key)) {
            return null;
        }
        ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) key.getTypeName();

        TypeName typeName = Iterables.getOnlyElement(parameterizedTypeName.typeArguments);
        AnnotationSpec qualifier = key.getQualifier();
        return NewBindingKey.get(typeName, qualifier);
    }

    /**
     * Changes give tree to {@link SetMultimap} from parent to children.
     */
    public static <T> SetMultimap<T, T> reverseTree(Map<T, T> childToParentMap) {
        SetMultimap<T, T> parentToChildrenMap = HashMultimap.create();
        for (Map.Entry<T, T> entry : childToParentMap.entrySet()) {
            parentToChildrenMap.put(entry.getValue(), entry.getKey());
        }
        return parentToChildrenMap;
    }

    /**
     * Returns width-first ordered component from the give map.
     */
    public static <T> List<T> getOrderedScopes(Map<T, T> childToParentMap) {
        List<T> result = new ArrayList<>();
        SetMultimap<T, T> parentToChildrenMap = reverseTree(childToParentMap);
        T rootElement = Iterables.getOnlyElement(
                Sets.difference(Sets.newHashSet(childToParentMap.values()), childToParentMap.keySet()));
        result.add(rootElement);
        for (int i = 0; i < result.size(); i++) {
            result.addAll(parentToChildrenMap.get(result.get(i)));
        }
        return result;
    }

    public static Set<TypeElement> getNonNullaryCtorOnes(Set<TypeElement> elements) {
        Set<TypeElement> result = new HashSet<>();
        for (TypeElement typeElement : elements) {
            boolean hasCtor = false;
            boolean hasNullaryCtor = false;
            for (Element element : typeElement.getEnclosedElements()) {
                if (element.getKind().equals(ElementKind.CONSTRUCTOR)) {
                    hasCtor = true;
                    ExecutableElement ctor = (ExecutableElement) element;
                    if (ctor.getParameters().isEmpty()) {
                        hasNullaryCtor = true;
                        break;
                    }
                }
            }
            if (!hasNullaryCtor && hasCtor) {
                result.add(typeElement);
            }
        }
        return result;
    }

    /**
     * Returns if the moduleType has provision methods.
     */
    public static boolean hasProvisionMethod(DeclaredType moduleType) {
        TypeElement moduleElement = (TypeElement) moduleType.asElement();
        Preconditions.checkArgument(moduleElement.getAnnotation(Module.class) != null,
                String.format("not module: %s.", moduleType));
        for (Element element : moduleElement.getEnclosedElements()) {
            if (element.getKind().equals(ElementKind.METHOD) && (element.getAnnotation(Provides.class) != null)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns the scope of this module, null for unscoped modules. Dagger requires that each module
     * can only contain no more than one scope type of scoped binding. Unscoped bindings are not
     * limited.
     * TODO(freeman): supported included modules.
     */
    @Nullable
    public static TypeElement getModuleScope(DeclaredType moduleType) {
        TypeElement moduleElement = (TypeElement) moduleType.asElement();
        Preconditions.checkArgument(moduleElement.getAnnotation(Module.class) != null,
                String.format("not module: %s.", moduleType));
        for (Element element : moduleElement.getEnclosedElements()) {
            if (element.getKind().equals(ElementKind.METHOD) && (element.getAnnotation(Provides.class) != null)) {
                for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
                    Annotation scope = annotationMirror.getAnnotationType().asElement().getAnnotation(Scope.class);
                    if (scope != null) {
                        return (TypeElement) annotationMirror.getAnnotationType().asElement();
                    }
                }
            }
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    public static Set<TypeElement> findAllModulesRecursively(TypeElement inModule) {
        Set<TypeElement> result = new HashSet<>();

        result.add(inModule);

        for (AnnotationMirror annotationMirror : inModule.getAnnotationMirrors()) {
            for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotationMirror
                    .getElementValues().entrySet()) {
                ExecutableElement key = entry.getKey();

                /** Checks for {@link Module.includes}. */
                if (key.getSimpleName().contentEquals("includes")) {

                    for (AnnotationValue annotationValue : (List<AnnotationValue>) entry.getValue().getValue()) {
                        TypeElement childModule = (TypeElement) ((DeclaredType) annotationValue.getValue())
                                .asElement();
                        result.addAll(findAllModulesRecursively(childModule));
                    }
                }
            }
        }

        return result;
    }

    public static Set<TypeElement> findAllModulesRecursively(Collection<TypeElement> inModules) {
        Set<TypeElement> result = new HashSet<>();

        for (TypeElement module : inModules) {
            result.addAll(findAllModulesRecursively(module));
        }

        return result;
    }

    public static List<TypeElement> sortByFullName(Collection<TypeElement> typeElements) {
        Ordering<TypeElement> ordering = new Ordering<TypeElement>() {
            @Override
            public int compare(TypeElement left, TypeElement right) {
                return left.getQualifiedName().toString().compareTo(right.getQualifiedName().toString());
            }
        };
        return ordering.immutableSortedCopy(typeElements);
    }

    /**
     * Returns "com_Foo" for com.Foo, "com_Foo_com_Bar_com_Baz" for Foo<Bar, Baz>.
     * upper_bounds_UpperBound_Foo for "? extends Foo" and
     * lower_bounds_LowerBound_Foo for "? super Foo". Assuming raw types are not
     * used, there will be not conflicts.
     */
    public static String getSourceCodeName(TypeName typeName) {
        Preconditions.checkNotNull(typeName);

        if (typeName.isPrimitive()) {
            return typeName.toString();
        } else if (typeName instanceof ClassName) {
            return getClassCanonicalName((ClassName) typeName).replace(".", "_");
        } else if (typeName instanceof ParameterizedTypeName) {
            ParameterizedTypeName p = (ParameterizedTypeName) typeName;
            StringBuilder builder = new StringBuilder(getSourceCodeName(p.rawType));
            for (TypeName t : p.typeArguments) {
                builder.append("_").append(getSourceCodeName(t));
            }
            return builder.toString();
        } else if (typeName instanceof WildcardTypeName) {
            WildcardTypeName w = (WildcardTypeName) typeName;
            if (w.upperBounds.size() > 0) {
                return "upper_bounds_" + getSourceCodeName(w.upperBounds.get(0));
            } else {
                Preconditions.checkState(w.lowerBounds.size() > 0);
                return "lower_bounds_" + getSourceCodeName(w.lowerBounds.get(0));
            }
        } else if (typeName instanceof ArrayTypeName) {
            ArrayTypeName arrayTypeName = (ArrayTypeName) typeName;
            return "ArrayOf" + getSourceCodeName(arrayTypeName.componentType);
        }
        throw new RuntimeException("Unexpected type: " + typeName);
    }

    public static String getSourceCodeName(TypeElement typeElement) {
        return getSourceCodeName(TypeName.get(typeElement.asType()));
    }

    /**
     * Adds set method for type to builder. 
     */
    public static void addSetMethod(ClassName builderParentClassName, TypeSpec.Builder builder, ClassName type) {
        String fullName = getSourceCodeName(type);
        String simpleName = type.simpleName();
        String methodName = Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
        builder.addField(type, fullName, Modifier.PRIVATE);
        String argName = "arg";
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(methodName).addModifiers(Modifier.PUBLIC)
                .addParameter(type, argName).returns(ClassName.get(builderParentClassName.packageName(),
                        builderParentClassName.simpleName(), "Builder"))
                .addCode("this.$N = $N;", fullName, argName).addCode("return this;");
        builder.addMethod(methodBuilder.build());
    }

    public static <K, V> SetMultimap<V, K> reverseSetMultimap(SetMultimap<K, V> map) {
        SetMultimap<V, K> result = HashMultimap.create();
        for (Map.Entry<K, V> entry : map.entries()) {
            result.put(entry.getValue(), entry.getKey());
        }
        return result;
    }

    public static boolean isTypeElementEqual(TypeElement a, TypeElement b) {
        return a.getQualifiedName().contentEquals(b.getQualifiedName());
    }

    /**
     * Returns whether the give class is a Dagger {@link Module}.
     */
    public static boolean isModule(Class<?> clazz) {
        return clazz.getAnnotation(Module.class) != null;
    }

    /**
     * Returns whether the given {@link AccessibleObject} is a {@link Provides}
     * one.
     */
    public static <T extends AccessibleObject> boolean isProvides(T t) {
        return t.getAnnotation(Provides.class) != null;
    }

    /**
     * Returns whether the given annotation is a {@link Qualifier}.
     */
    public static boolean isQualifierAnnotation(Annotation annotation) {
        return annotation.annotationType().getAnnotation(Qualifier.class) != null;
    }

    /**
     * Returns qualifier annotation for the given method, null if none.
     */
    public static Annotation getQualifierAnnotation(AccessibleObject accessibleObject) {
        for (Annotation annotation : accessibleObject.getAnnotations()) {
            if (isQualifierAnnotation(annotation)) {
                return annotation;
            }
        }

        return null;
    }

    /**
     * Returns qualifier annotation for the given method, null if none.
     */
    @Nullable
    public static DeclaredType getQualifierAnnotation(Element element) {
        for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
            DeclaredType annotationType = annotationMirror.getAnnotationType();
            if (annotationType.asElement().getAnnotation(Qualifier.class) != null) {
                return annotationType;
            }
        }

        return null;
    }

    /**
     * Returns the injected ctor, null if none.
     */
    public static Constructor<?> findInjectedCtor(Class<?> clazz) {
        Constructor<?>[] ctors = clazz.getDeclaredConstructors();
        for (Constructor<?> ctor : ctors) {
            if (isInjected(ctor)) {
                return ctor;
            }
        }

        return null;
    }

    /**
     * Returns whether the given {@link AccessibleObject} is injected.
     */
    public static boolean isInjected(AccessibleObject accessibleObject) {
        Annotation[] annotations = accessibleObject.getAnnotations();
        for (Annotation annotation : annotations) {
            Class<?> type = annotation.annotationType();
            if (type.getName().equals(Inject.class.getName())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns included {@link Module}s of the specified {@link Module}
     * recursively.
     */
    private static List<Class<?>> getIncludedModules(Class<?> module) {
        List<Class<?>> result = new ArrayList<>();
        Module childModule = module.getAnnotation(Module.class);
        Class<?>[] includes = childModule.includes();
        result.addAll(Lists.newArrayList(includes));
        for (Class<?> clazz : includes) {
            LOGGER.log(Level.INFO, "module: " + module + " child: " + clazz);
            result.addAll(getIncludedModules(clazz));
        }
        return result;
    }

    /**
     * Returns {@link Module}s referenced by the given {@link Module}s
     * recursively.
     */
    public static Set<Class<?>> findAllModules(Set<Class<?>> modules) {
        Set<Class<?>> result = new HashSet<>();

        for (Class<?> module : modules) {
            result.addAll(getIncludedModules(module));
        }

        result.addAll(modules);
        return result;
    }

    public static boolean hasInjectedFieldsOrMethods(Class<?> clazz) {
        return !getInjectedFields(clazz).isEmpty() || !getInjectedMethods(clazz).isEmpty();
    }

    public static List<Field> getInjectedFields(Class<?> clazz) {
        return getInjected(Arrays.asList(clazz.getDeclaredFields()));
    }

    public static List<Method> getInjectedMethods(Class<?> clazz) {
        return getInjected(Arrays.asList(clazz.getDeclaredMethods()));
    }

    public static List<Method> getProvisionMethods(Class<?> clazz) {
        return filterProvides(Arrays.asList(clazz.getDeclaredMethods()));
    }

    private static <T extends AccessibleObject> List<T> getInjected(Iterable<T> all) {
        List<T> result = new ArrayList<>();
        for (T t : all) {
            if (Utils.isInjected(t)) {
                result.add(t);
            }
        }

        return result;
    }

    private static <T extends AccessibleObject> List<T> filterProvides(Iterable<T> all) {
        List<T> result = new ArrayList<>();
        for (T t : all) {
            if (isProvides(t)) {
                result.add(t);
            }
        }

        return result;
    }

    /**
     * Returns a string with the first char lowered if it is in upper case, other
     * return the original one.
     */
    public static String lowerFirst(String s) {
        if (s.isEmpty()) {
            return s;
        }

        char c = s.charAt(0);
        if (Character.isLowerCase(c)) {
            return s;
        }

        c = Character.toLowerCase(c);
        return c + s.substring(1);
    }

    private static Annotation[] getParameterQualifierAnnotations(Annotation[][] annotationArrays) {
        Annotation[] result = new Annotation[annotationArrays.length];
        for (int i = 0; i < annotationArrays.length; i++) {
            Annotation[] annotations = annotationArrays[i];
            boolean found = false;
            for (Annotation annotation : annotations) {
                if (isQualifierAnnotation(annotation)) {
                    result[i] = annotation;
                    found = true;
                    break;
                }
            }
            if (!found) {
                result[i] = null;
            }
        }

        return result;
    }

    private static Annotation[] getParameterQualifierAnnotations(Constructor<?> ctor) {
        Annotation[][] annotationArrays = ctor.getParameterAnnotations();
        return getParameterQualifierAnnotations(annotationArrays);
    }

    /*
       * Return an array including the qualifier annotation for each parameter, null
       * if no qualifier for the parameter. Size of returned array is same as the
       * number of parameters.
       */
    private static Annotation[] getParameterQualifierAnnotations(Method method) {
        Annotation[][] annotationArrays = method.getParameterAnnotations();
        return getParameterQualifierAnnotations(annotationArrays);
    }

    public static Provides.Type getProvidesType(Method method) {
        if (method.getAnnotation(IntoSet.class) != null) {
            return Provides.Type.SET;
        } else if (method.getAnnotation(ElementsIntoSet.class) != null) {
            return Provides.Type.SET_VALUES;
        } else if (method.getAnnotation(IntoMap.class) != null) {
            return Provides.Type.MAP;
        }
        return method.getAnnotation(Provides.class).type();
    }

    public static Provides.Type getProvidesType(ExecutableElement method) {
        if (isAnnotationPresent(method, IntoMap.class)) {
            return Provides.Type.MAP;
        } else if (isAnnotationPresent(method, IntoSet.class)) {
            return Provides.Type.SET;
        } else if (isAnnotationPresent(method, ElementsIntoSet.class)) {
            return Provides.Type.SET_VALUES;
        }
        return method.getAnnotation(Provides.class).type();
    }

    public static String getClassBinaryName(ClassName className) {
        StringBuilder builder = new StringBuilder(className.packageName()).append(".");

        for (String name : className.simpleNames()) {
            builder.append(name).append("$");
        }
        builder.delete(builder.length() - 1, builder.length());

        return builder.toString();
    }

    public static String getClassCanonicalName(ClassName className) {
        return getClassBinaryName(className).replace("$", ".");
    }

    public static boolean hasAnonymousParentClass(Class<?> cls) {
        Preconditions.checkNotNull(cls);
        try {
            do {
                if (cls.isAnonymousClass()) {
                    return true;
                }
                cls = cls.getEnclosingClass();
            } while (cls != null);
            return false;
        } catch (IncompatibleClassChangeError e) {
            // We somehow run into this, ignore it.
            LOGGER.info(String.format("class: %s, exception: %s.", cls, e));
            return false;
        }
    }

    /**
     * Returns a {@link TypeName} with all the {@link TypeVariableName} in the
     * given typeName replaced with the {@link TypeName} found in map the the
     * {@link TypeVariableName} if necessary.
     */
    public static TypeName specializeIfNeeded(TypeName typeName, Map<TypeVariableName, TypeName> map) {
        if (typeName instanceof TypeVariableName) {
            Preconditions.checkArgument(map.containsKey(typeName));
            return map.get(typeName);
        } else if (typeName instanceof ParameterizedTypeName) {
            ParameterizedTypeName parameterizedTypeName = (ParameterizedTypeName) typeName;
            ClassName rawName = parameterizedTypeName.rawType;
            List<TypeName> parameterTypes = new ArrayList<>();
            for (TypeName t : parameterizedTypeName.typeArguments) {
                parameterTypes.add(specializeIfNeeded(t, map));
            }
            return ParameterizedTypeName.get(rawName, parameterTypes.toArray(new TypeName[0]));
        } else {
            return typeName;
        }
    }

    public static String getPackagedInjectorSimpleName(Class<? extends Annotation> scope) {
        return String.format("%s%s", scope.getCanonicalName().replace(".", "_"), PACKAGED_INJECTOR_NAME);
    }

    public static String getPackagedInjectorSimpleName(TypeElement scope) {
        return String.format("%s%s", scope.getQualifiedName().toString().replace(".", "_"), PACKAGED_INJECTOR_NAME);
    }

    public static String getMultiBindingInjectorSimpleName(Class<? extends Annotation> scope) {
        return String.format("%s%s", scope.getCanonicalName().replace(".", "_"), MULTI_BINDING_INJECTOR_NAME);
    }

    public static String getMultiBindingInjectorSimpleName(TypeElement scope) {
        return String.format("%s%s", scope.getQualifiedName().toString().replace(".", "_"),
                MULTI_BINDING_INJECTOR_NAME);
    }

    public static boolean isMultiBindingInjector(ClassName packagedInjectorClassName) {
        return packagedInjectorClassName.simpleName().contains(MULTI_BINDING_INJECTOR_NAME);
    }

    public static String getGetMethodName(Class<?> cls) {
        return getGetMethodName(ClassName.get(cls));
    }

    public static String getGetMethodName(ClassName className) {
        return String.format(Locale.US, "get_%s", getClassCanonicalName(className).replace(".", "_"));
    }

    /**
     * Returns true if the method is a injection method, i.e., has single
     * parameter and returns void.
     */
    public static boolean isInjectionMethod(Method method) {
        return method.getReturnType().equals(Void.TYPE) && method.getParameterTypes().length == 1;
    }

}