Java tutorial
/* * Copyright (C) 2013 The Dagger 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 dagger.internal.codegen; import static com.google.auto.common.MoreElements.getLocalAndInheritedMethods; import static com.google.auto.common.MoreElements.hasModifiers; import static com.google.auto.common.MoreTypes.asDeclared; import static com.google.common.collect.Lists.asList; import static dagger.internal.codegen.SourceFiles.generateBindingFieldsForDependencies; import static dagger.internal.codegen.SourceFiles.simpleVariableName; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toList; import static javax.lang.model.element.ElementKind.CONSTRUCTOR; import static javax.lang.model.element.Modifier.ABSTRACT; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.STATIC; import com.google.auto.common.MoreElements; import com.google.auto.common.MoreTypes; import com.google.common.base.Function; import com.google.common.base.Joiner; import java.util.Optional; import com.google.common.collect.*; import com.squareup.javapoet.*; import dagger.*; import dagger.multibindings.ClassKey; import dagger.multibindings.IntKey; import dagger.multibindings.LongKey; import dagger.multibindings.StringKey; import dagger.producers.Produces; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.util.stream.Collector; import javax.inject.Provider; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; import java.lang.annotation.Annotation; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collector; import javax.inject.Named; import javax.lang.model.element.*; import javax.lang.model.type.*; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleElementVisitor6; import javax.lang.model.util.SimpleTypeVisitor6; import javax.lang.model.util.Types; /** * Utilities for handling types in annotation processors */ final class Util { /** * Returns true if the passed {@link TypeElement} requires a passed instance in order to be used * within a component. */ static boolean requiresAPassedInstance(Elements elements, Types types, TypeElement typeElement) { ImmutableSet<ExecutableElement> methods = getLocalAndInheritedMethods(typeElement, types, elements); boolean foundInstanceMethod = false; for (ExecutableElement method : methods) { if (method.getModifiers().contains(ABSTRACT) && !MoreElements.isAnnotationPresent(method, Binds.class)) { /* We found an abstract method that isn't a @Binds method. That automatically means that * a user will have to provide an instance because we don't know which subclass to use. */ return true; } else if (!method.getModifiers().contains(STATIC) && isAnyAnnotationPresent(method, Provides.class, Produces.class)) { foundInstanceMethod = true; } } if (foundInstanceMethod) { return !componentCanMakeNewInstances(typeElement); } return false; } /** * Returns true if and only if a component can instantiate new instances (typically of a module) * rather than requiring that they be passed. */ static boolean componentCanMakeNewInstances(TypeElement typeElement) { switch (typeElement.getKind()) { case CLASS: break; case ENUM: case ANNOTATION_TYPE: case INTERFACE: return false; default: throw new AssertionError("TypeElement cannot have kind: " + typeElement.getKind()); } if (typeElement.getModifiers().contains(ABSTRACT)) { return false; } if (requiresEnclosingInstance(typeElement)) { return false; } for (Element enclosed : typeElement.getEnclosedElements()) { if (enclosed.getKind().equals(CONSTRUCTOR) && ((ExecutableElement) enclosed).getParameters().isEmpty() && !enclosed.getModifiers().contains(PRIVATE)) { return true; } } return false; } private static boolean requiresEnclosingInstance(TypeElement typeElement) { switch (typeElement.getNestingKind()) { case TOP_LEVEL: return false; case MEMBER: return !typeElement.getModifiers().contains(STATIC); case ANONYMOUS: case LOCAL: return true; default: throw new AssertionError("TypeElement cannot have nesting kind: " + typeElement.getNestingKind()); } } static ImmutableSet<ExecutableElement> getUnimplementedMethods(Elements elements, Types types, TypeElement type) { return FluentIterable.from(getLocalAndInheritedMethods(type, types, elements)) .filter(hasModifiers(ABSTRACT)).toSet(); } /** * A function that returns the input as a {@link DeclaredType}. */ static final Function<TypeElement, DeclaredType> AS_DECLARED_TYPE = typeElement -> asDeclared( typeElement.asType()); /** * A visitor that returns the input or the closest enclosing element that is a * {@link TypeElement}. */ static final ElementVisitor<TypeElement, Void> ENCLOSING_TYPE_ELEMENT = new SimpleElementVisitor6<TypeElement, Void>() { @Override protected TypeElement defaultAction(Element e, Void p) { return visit(e.getEnclosingElement()); } @Override public TypeElement visitType(TypeElement e, Void p) { return e; } }; /** * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose * {@linkplain AnnotationMirror#getAnnotationType() annotation type} has the same canonical name * as any of that of {@code annotationClasses}. */ // TODO(dpb): Move to MoreElements. static boolean isAnyAnnotationPresent(Element element, Iterable<? extends Class<? extends Annotation>> annotationClasses) { for (Class<? extends Annotation> annotation : annotationClasses) { if (MoreElements.isAnnotationPresent(element, annotation)) { return true; } } return false; /* case MEMBER: return !typeElement.getModifiers().contains(STATIC); case ANONYMOUS: case LOCAL: return true; default: throw new AssertionError( "TypeElement cannot have nesting kind: " + typeElement.getNestingKind()); }*/ } /** * Returns a {@link Collector} that accumulates the input elements into a new {@link * ImmutableList}, in encounter order. */ static <T> Collector<T, ?, ImmutableList<T>> toImmutableList() { return collectingAndThen(toList(), ImmutableList::copyOf); } /** * Returns a {@link Collector} that accumulates the input elements into a new {@link * ImmutableSet}, in encounter order. */ static <T> Collector<T, ?, ImmutableSet<T>> toImmutableSet() { return collectingAndThen(toList(), ImmutableSet::copyOf); } @SafeVarargs static boolean isAnyAnnotationPresent(Element element, Class<? extends Annotation> first, Class<? extends Annotation>... otherAnnotations) { return isAnyAnnotationPresent(element, asList(first, otherAnnotations)); } /** * Returns {@code true} iff the given element has an {@link AnnotationMirror} whose {@linkplain * AnnotationMirror#getAnnotationType() annotation type} is equivalent to {@code annotationType}. */ // TODO(dpb): Move to MoreElements. static boolean isAnnotationPresent(Element element, TypeMirror annotationType) { return element.getAnnotationMirrors().stream().map(AnnotationMirror::getAnnotationType) .anyMatch(candidate -> MoreTypes.equivalence().equivalent(candidate, annotationType)); } /** * The elements in {@code elements} that are annotated with an annotation of type * {@code annotation}. */ static <E extends Element> FluentIterable<E> elementsWithAnnotation(Iterable<E> elements, final Class<? extends Annotation> annotation) { return FluentIterable.from(elements) .filter(element -> MoreElements.isAnnotationPresent(element, annotation)); } /** * A function that returns the simple name of an element. */ static final Function<Element, String> ELEMENT_SIMPLE_NAME = element -> element.getSimpleName().toString(); /** * A {@link Comparator} that puts absent {@link Optional}s before present ones, and compares * present {@link Optional}s by their values. */ static <C extends Comparable<C>> Comparator<Optional<C>> optionalComparator() { return Comparator.comparing((Optional<C> optional) -> optional.isPresent()).thenComparing(Optional::get); } public static String toParameterName(String simpleName) { return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1); } public static String extractPackage(TypeMirror classType) { return classType.toString().replaceAll("." + convertDataClassToString(classType), ""); } public static String extractClassName(TypeMirror classType) { return convertDataClassToString(classType); } public static String extractClassName(String s) { int index = s.lastIndexOf("."); return s.substring(index + 1); } public static String convertDataClassToString(TypeMirror dataClass) { String s = dataClass.toString(); int index = s.lastIndexOf("."); return s.substring(index + 1); } static ClassName getDelegateTypeName(Key key) { if (key.multibindingContributionIdentifier().isPresent()) { final Key.MultibindingContributionIdentifier identifier = key.multibindingContributionIdentifier() .get(); return identifier.getDelegateTypeName(); } final TypeMirror returnType = key.type(); // find qualifier annotations final Optional<? extends AnnotationMirror> qualifier = key.qualifier(); String simpleQualifierName = ""; String simpleQualifierValue = ""; if (qualifier.isPresent()) { Optional<String> qualifierValue = qualifier.get().getElementValues().entrySet().stream() .filter(e -> e.getKey().getSimpleName().contentEquals("value")) .filter(e -> e.getKey().getReturnType().toString().equals(String.class.getName())) .map(e -> e.getValue().getValue().toString()).findFirst(); simpleQualifierName = MoreAnnotationMirrors.simpleName(qualifier.get()).toString(); if (qualifierValue.isPresent()) { simpleQualifierValue = qualifierValue.get(); } } StringBuilder sb = new StringBuilder(); if (qualifier.isPresent()) { sb.append(capitalize(simpleQualifierName).trim()); if (!simpleQualifierValue.trim().isEmpty()) { sb.append(capitalize(simpleQualifierValue).trim()); } else { sb.append(capitalize(extractClassName(typeToString(returnType))).trim()); } } else { sb.append(capitalize(extractClassName(typeToString(returnType))).trim()); } final ClassName name = ClassName.bestGuess(String.format("delegates.%sDelegate", sb.toString())); return name; } static String getDelegateFieldName(Key key) { if (key.multibindingContributionIdentifier().isPresent()) { final Key.MultibindingContributionIdentifier identifier = key.multibindingContributionIdentifier() .get(); return identifier.getDelegateFieldName(); } final TypeMirror returnType = key.type(); // find qualifier annotations final Optional<? extends AnnotationMirror> qualifier = key.qualifier(); Optional<String> qualifierValue = Optional.empty(); String simpleQualifierName = ""; String simpleQualifierValue = ""; if (qualifier.isPresent()) { qualifierValue = qualifier.get().getElementValues().entrySet().stream() .filter(e -> e.getKey().getSimpleName().toString().equals("value")) .filter(e -> e.getKey().getReturnType().toString().equals(String.class.getName())) .map(e -> e.getValue().getValue().toString()).findFirst(); simpleQualifierName = MoreAnnotationMirrors.simpleName(qualifier.get()).toString(); if (qualifierValue.isPresent()) { simpleQualifierValue = qualifierValue.get(); } } StringBuilder sb = new StringBuilder(); if (qualifier.isPresent()) { sb.append(lowerCaseFirstLetter(simpleQualifierName)); if (!simpleQualifierValue.isEmpty()) { sb.append(transformValue(simpleQualifierValue, sb)); } else { sb.append(transformValue(extractClassName(typeToString(returnType)), sb)); } } else { sb.append(lowerCaseFirstLetter(extractClassName(typeToString(returnType)))); } sb.append("Delegate"); return sb.toString(); } private static String transformValue(String simpleMapValueName, StringBuilder sb) { return sb.length() == 0 ? lowerCaseFirstLetter(simpleMapValueName) : capitalize(simpleMapValueName); } /** * Returns a string for {@code type}. Primitive types are always boxed. */ public static String typeToString(TypeMirror type) { StringBuilder result = new StringBuilder(); typeToString(type, result, '.', false); return result.toString(); } /** * Returns a string for the raw type of {@code type}. Primitive types are always boxed. */ public static String rawTypeToString(TypeMirror type, char innerClassSeparator) { if (!(type instanceof DeclaredType)) { throw new IllegalArgumentException("Unexpected type: " + type); } StringBuilder result = new StringBuilder(); DeclaredType declaredType = (DeclaredType) type; rawTypeToString(result, (TypeElement) declaredType.asElement(), innerClassSeparator, false); return result.toString(); } public static PackageElement getPackage(Element type) { while (type.getKind() != ElementKind.PACKAGE) { type = type.getEnclosingElement(); } return (PackageElement) type; } static void rawTypeToString(StringBuilder result, TypeElement type, char innerClassSeparator, boolean typeParam) { if (typeParam) { final String s = MoreElements.asType(type).getSimpleName().toString(); result.append(s); return; } String packageName = getPackage(type).getQualifiedName().toString(); String qualifiedName = type.getQualifiedName().toString(); if (packageName.isEmpty()) { result.append(qualifiedName.replace('.', innerClassSeparator)); } else { result.append(packageName); result.append('.'); result.append(qualifiedName.substring(packageName.length() + 1).replace('.', innerClassSeparator)); } } /** * Appends a string for {@code type} to {@code result}. Primitive types are * always boxed. * * @param innerClassSeparator either '.' or '$', which will appear in a * class name like "java.lang.Map.Entry" or "java.lang.Map$Entry". * Use '.' for references to existing types in code. Use '$' to define new * class names and for strings that will be used by runtime reflection. */ public static void typeToString(final TypeMirror type, final StringBuilder result, final char innerClassSeparator, boolean typeParam) { type.accept(new SimpleTypeVisitor6<Void, Void>() { @Override public Void visitDeclared(DeclaredType declaredType, Void v) { TypeElement typeElement = (TypeElement) declaredType.asElement(); rawTypeToString(result, typeElement, innerClassSeparator, typeParam); List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments(); if (!typeArguments.isEmpty()) { result.append("Of"); for (int i = 0; i < typeArguments.size(); i++) { if (i != 0) { result.append("And"); } typeToString(typeArguments.get(i), result, '\0', true); } } return null; } @Override public Void visitPrimitive(PrimitiveType primitiveType, Void v) { result.append(box((PrimitiveType) type)); return null; } @Override public Void visitArray(ArrayType arrayType, Void v) { TypeMirror type = arrayType.getComponentType(); if (type instanceof PrimitiveType) { result.append("ArrayOf" + extractClassName(type)); // Don't box, since this is an array. } else { typeToString(arrayType.getComponentType(), result, innerClassSeparator, true); } result.append("_"); return null; } @Override public Void visitTypeVariable(TypeVariable typeVariable, Void v) { result.append(typeVariable.asElement().getSimpleName()); return null; } @Override public Void visitWildcard(WildcardType t, Void aVoid) { return null; } @Override public Void visitError(ErrorType errorType, Void v) { // Error type found, a type may not yet have been generated, but we need the type // so we can generate the correct code in anticipation of the type being available // to the compiler. // Paramterized types which don't exist are returned as an error type whose name is "<any>" if ("<any>".equals(errorType.toString())) { throw new IllegalStateException( "Type reported as <any> is likely a not-yet generated parameterized type."); } // TODO(cgruber): Figure out a strategy for non-FQCN cases. result.append(errorType.toString()); return null; } @Override protected Void defaultAction(TypeMirror typeMirror, Void v) { throw new UnsupportedOperationException( "Unexpected TypeKind " + typeMirror.getKind() + " for " + typeMirror); } }, null); } static TypeName box(PrimitiveType primitiveType) { switch (primitiveType.getKind()) { case BYTE: return ClassName.get(Byte.class); case SHORT: return ClassName.get(Short.class); case INT: return ClassName.get(Integer.class); case LONG: return ClassName.get(Long.class); case FLOAT: return ClassName.get(Float.class); case DOUBLE: return ClassName.get(Double.class); case BOOLEAN: return ClassName.get(Boolean.class); case CHAR: return ClassName.get(Character.class); case VOID: return ClassName.get(Void.class); default: throw new AssertionError(); } } public static String capitalize(String original) { if (original == null || original.length() == 0) { return original; } return original.substring(0, 1).toUpperCase() + original.substring(1); } public static String lowerCaseFirstLetter(String original) { if (original == null || original.length() == 0) { return original; } return original.substring(0, 1).toLowerCase() + original.substring(1); } public static boolean bindingCanBeProvidedInTest(ContributionBinding binding) { final ImmutableList<ContributionBinding.Kind> kinds = ImmutableList.of(ContributionBinding.Kind.PROVISION, ContributionBinding.Kind.INJECTION, ContributionBinding.Kind.BUILDER_BINDING); final ContributionBinding.Kind kind = binding.bindingKind(); return kinds.contains(kind); } public static boolean bindingSupportsTestDelegate(ContributionBinding binding) { return bindingCanBeProvidedInTest(binding) && !binding.genericParameter() && !binding.ignoreStubGeneration(); } private Util() { } public static void createDelegateMethod(TypeName generatedTypeName, TypeSpec.Builder classBuilder, ContributionBinding binding) { try { if (bindingSupportsTestDelegate(binding)) { final String delegateFieldName = Util.getDelegateFieldName(binding.key()); final ClassName delegateType = getDelegateTypeName(binding.key()); final String methodName = getDelegateMethodName(delegateType); final MethodSpec.Builder delegateMethodBuilder = MethodSpec.methodBuilder(methodName); delegateMethodBuilder.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT); classBuilder.addMethod(delegateMethodBuilder.returns(generatedTypeName) .addParameter(delegateType, delegateFieldName).build()); } } catch (Exception e) { } } public static void createMockInstanceMethod(TypeName generatedTypeName, TypeSpec.Builder classBuilder, ContributionBinding binding) { try { if (bindingSupportsTestDelegate(binding)) { final ClassName delegateType = getDelegateTypeName(binding.key()); final String methodName = getDelegateMethodName(delegateType); final MethodSpec.Builder delegateMethodBuilder = MethodSpec.methodBuilder(methodName); delegateMethodBuilder.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT); classBuilder.addMethod(delegateMethodBuilder .addParameter(ParameterizedTypeName.get(ClassName.get(Provider.class), ClassName.get(binding.contributedType())), "provider") .returns(generatedTypeName).build()); } } catch (Exception e) { } } public static void createMockMethodImplementation(TypeName generatedTypeName, TypeSpec.Builder classBuilder, ContributionBinding binding) { try { if (bindingSupportsTestDelegate(binding)) { final String delegateFieldName = Util.getDelegateFieldName(binding.key()); final ClassName delegateType = getDelegateTypeName(binding.key()); final TypeName contributedTypeName = ClassName.get(binding.contributedType()); final ParameterizedTypeName providerType = ParameterizedTypeName.get(ClassName.get(Provider.class), contributedTypeName); final String methodName = getDelegateMethodName(delegateType); final MethodSpec.Builder delegateMethodBuilder = MethodSpec.methodBuilder(methodName); delegateMethodBuilder.addModifiers(Modifier.PUBLIC); final CodeBlock params = createParametersCodeBlock(binding); classBuilder.addMethod(delegateMethodBuilder.returns(generatedTypeName) .addParameter(providerType, "provider", Modifier.FINAL) .addStatement( "this.$L = new $T() {\n" + " public $T get($L) { \n" + " return provider.get();\n" + " }\n" + "};", delegateFieldName, delegateType, contributedTypeName, params) .addStatement("return this").build()); } } catch (Exception e) { } } protected static CodeBlock createParametersCodeBlock(ContributionBinding binding) { final ImmutableMap<BindingKey, FrameworkField> map = generateBindingFieldsForDependencies(binding); return binding.explicitDependencies().stream().map(request -> createCodeBlock(request, map)) .collect(CodeBlocks.joiningCodeBlocks(",")); } private static CodeBlock createCodeBlock(DependencyRequest request, ImmutableMap<BindingKey, FrameworkField> map) { FrameworkField field = map.get(request.bindingKey()); TypeName type = request.kind() == DependencyRequest.Kind.INSTANCE ? field.type().typeArguments.get(0) : field.type(); return CodeBlock.of("$T $L", type, field.name()); } public static void createDelegateMethodImplementation(TypeName generatedTypeName, TypeSpec.Builder classBuilder, ContributionBinding binding) { try { if (bindingSupportsTestDelegate(binding)) { final String delegateFieldName = Util.getDelegateFieldName(binding.key()); final ClassName delegateType = getDelegateTypeName(binding.key()); final String methodName = getDelegateMethodName(delegateType); final MethodSpec.Builder delegateMethodBuilder = MethodSpec.methodBuilder(methodName); delegateMethodBuilder.addModifiers(Modifier.PUBLIC); classBuilder.addMethod(delegateMethodBuilder.returns(generatedTypeName) .addParameter(delegateType, delegateFieldName) .addStatement("this.$L = $L", delegateFieldName, CodeBlock.of(delegateFieldName)) .addStatement("return this").build()); } } catch (Exception e) { } } public static void createDelegateField(TypeSpec.Builder classBuilder, ContributionBinding binding) { try { if (bindingSupportsTestDelegate(binding)) { final String delegateFieldName = Util.getDelegateFieldName(binding.key()); final ClassName delegateType = getDelegateTypeName(binding.key()); final FieldSpec.Builder builder = FieldSpec.builder(delegateType, delegateFieldName); builder.addModifiers(Modifier.PRIVATE); final FieldSpec fieldSpec = builder.build(); classBuilder.addField(fieldSpec); } } catch (Exception e) { } } public static void createDelegateFieldAndMethod(TypeName generatedTypeName, TypeSpec.Builder classBuilder, ContributionBinding binding, Map<Key, String> delegateFieldNames, boolean publicMethod) { try { if (bindingSupportsTestDelegate(binding)) { final String delegateFieldName = Util.getDelegateFieldName(binding.key()); final ClassName delegateType = getDelegateTypeName(binding.key()); final FieldSpec.Builder builder = FieldSpec.builder(delegateType, delegateFieldName); builder.addModifiers(Modifier.PRIVATE); delegateFieldNames.put(binding.key(), delegateFieldName); final FieldSpec fieldSpec = builder.build(); classBuilder.addField(fieldSpec); final String methodName = getDelegateMethodName(delegateType); final MethodSpec.Builder delegateMethodBuilder = MethodSpec.methodBuilder(methodName); if (publicMethod) { delegateMethodBuilder.addModifiers(Modifier.PUBLIC); } classBuilder.addMethod(delegateMethodBuilder.returns(generatedTypeName) .addParameter(delegateType, delegateFieldName) .addStatement("this.$N = $L", fieldSpec, CodeBlock.of(delegateFieldName)) .addStatement("return this").build()); } } catch (Exception e) { } } public static String getProvisionMethodName(ContributionBinding binding) { return "get" + getDelegateTypeName(binding.key()).simpleName().replaceAll("Delegate$", ""); } public static String getDelegateMethodName(ClassName delegateType) { return "with" + delegateType.simpleName().replaceAll("Delegate$", ""); } public static ClassName getDaggerComponentClassName(ClassName componentDefinitionClassName) { String componentName = "Dagger" + Joiner.on('_').join(componentDefinitionClassName.simpleNames()); //componentDefinitionClassName = ClassName.bestGuess("factories." + componentName); return componentDefinitionClassName.topLevelClassName().peerClass(componentName); } public static ClassName getDaggerComponentClassName(Element component) { return getDaggerComponentClassName(ClassName.bestGuess(typeToString(component.asType()))); } public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) { Map<Object, Boolean> seen = new ConcurrentHashMap<>(); return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null; } public static final String METHOD_NAME_GET_INJECTOR = "getInjector"; public static final ClassName TYPENAME_INJECTOR = ClassName.bestGuess("injector.Injector"); public static final String SIMPLE_NAME_INJECTOR_APPLICATION = "DaggerHookApplication"; public static final String FIELDNAME_INJECTOR = "injector"; public static final ClassName TYPENAME_INJECTOR_SPEC = ClassName.bestGuess("injector.InjectorSpec"); public static final ClassName TYPENAME_ANDROID_APPLICATION = ClassName.bestGuess("android.app.Application"); public static final ClassName TYPENAME_DAGGER_ANDROID_APPLICATION = TYPENAME_ANDROID_APPLICATION .topLevelClassName().peerClass("DaggerApplication"); }