Java tutorial
/* * Copyright (C) 2014 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.common.base.Preconditions.checkArgument; import static com.squareup.javapoet.MethodSpec.constructorBuilder; import static com.squareup.javapoet.MethodSpec.methodBuilder; import static com.squareup.javapoet.TypeSpec.classBuilder; import static dagger.internal.codegen.AnnotationSpecs.Suppression.RAWTYPES; import static dagger.internal.codegen.AnnotationSpecs.Suppression.UNCHECKED; import static dagger.internal.codegen.AnnotationSpecs.suppressWarnings; import static dagger.internal.codegen.CodeBlocks.makeParametersCodeBlock; import static dagger.internal.codegen.ContributionBinding.Kind.INJECTION; import static dagger.internal.codegen.ContributionBinding.Kind.PROVISION; import static dagger.internal.codegen.ErrorMessages.CANNOT_RETURN_NULL_FROM_NON_NULLABLE_PROVIDES_METHOD; import static dagger.internal.codegen.GwtCompatibility.gwtIncompatibleAnnotation; import static dagger.internal.codegen.Proxies.createProxy; import static dagger.internal.codegen.Proxies.shouldGenerateProxy; import static dagger.internal.codegen.SourceFiles.bindingTypeElementTypeVariableNames; import static dagger.internal.codegen.SourceFiles.frameworkTypeUsageStatement; import static dagger.internal.codegen.SourceFiles.generateBindingFieldsForDependencies; import static dagger.internal.codegen.SourceFiles.generatedClassNameForBinding; import static dagger.internal.codegen.SourceFiles.parameterizedGeneratedTypeNameForBinding; import static dagger.internal.codegen.TypeNames.factoryOf; import static dagger.internal.codegen.TypeNames.providerOf; import static dagger.internal.codegen.Util.getDelegateFieldName; import static dagger.internal.codegen.Util.getDelegateTypeName; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; import static javax.lang.model.element.Modifier.STATIC; import com.google.auto.common.MoreElements; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.FieldSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.TypeVariableName; import dagger.internal.Factory; import dagger.internal.MembersInjectors; import dagger.internal.Preconditions; import java.util.List; import java.util.Map; import java.util.Optional; import javax.annotation.processing.Filer; import javax.inject.Inject; import javax.inject.Named; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.tools.Diagnostic; /** * Generates {@link Factory} implementations from {@link ProvisionBinding} instances for * {@link Inject} constructors. * * @author Gregory Kick * @since 2.0 */ final class FactoryGenerator extends SourceFileGenerator<ProvisionBinding> { private final CompilerOptions compilerOptions; private final InjectValidator injectValidator; FactoryGenerator(Filer filer, Elements elements, CompilerOptions compilerOptions, InjectValidator injectValidator) { super(filer, elements); this.compilerOptions = compilerOptions; this.injectValidator = injectValidator; } @Override ClassName nameGeneratedType(ProvisionBinding binding) { return generatedClassNameForBinding(binding); } @Override Optional<? extends Element> getElementForErrorReporting(ProvisionBinding binding) { return binding.bindingElement(); } @Override Optional<TypeSpec.Builder> write(ClassName generatedTypeName, ProvisionBinding binding) { // We don't want to write out resolved bindings -- we want to write out the generic version. checkArgument(!binding.unresolved().isPresent()); checkArgument(binding.bindingElement().isPresent()); if (binding.bindingKind().equals(INJECTION) && !injectValidator.isValidType(binding.contributedType())) { return Optional.empty(); } TypeName providedTypeName = TypeName.get(binding.contributedType()); ParameterizedTypeName factoryTypeName = factoryOf(providedTypeName); ImmutableList<TypeVariableName> typeParameters = bindingTypeElementTypeVariableNames(binding); TypeSpec.Builder factoryBuilder = classBuilder(generatedTypeName).addModifiers(FINAL); // Use type parameters from the injected type or the module instance *only* if we require it. boolean factoryHasTypeParameters = (binding.bindingKind().equals(INJECTION) || binding.requiresModuleInstance()) && !typeParameters.isEmpty(); if (factoryHasTypeParameters) { factoryBuilder.addTypeVariables(typeParameters); } Optional<MethodSpec.Builder> constructorBuilder = Optional.empty(); UniqueNameSet uniqueFieldNames = new UniqueNameSet(); ImmutableMap.Builder<BindingKey, FieldSpec> fieldsBuilder = ImmutableMap.builder(); switch (binding.factoryCreationStrategy()) { case SINGLETON_INSTANCE: FieldSpec.Builder instanceFieldBuilder = FieldSpec .builder(generatedTypeName, "INSTANCE", PRIVATE, STATIC, FINAL) .initializer("new $T()", generatedTypeName); // if the factory has type parameters, we're ignoring them in the initializer if (factoryHasTypeParameters) { instanceFieldBuilder.addAnnotation(suppressWarnings(RAWTYPES)); } factoryBuilder.addField(instanceFieldBuilder.build()); break; case CLASS_CONSTRUCTOR: constructorBuilder = Optional.of(constructorBuilder().addModifiers(PUBLIC)); if (Util.bindingSupportsTestDelegate(binding)) { addConstructorParameterAndTypeField(getDelegateTypeName(binding.key()), getDelegateFieldName(binding.key()), factoryBuilder, constructorBuilder.get(), false); } if (binding.requiresModuleInstance()) { addConstructorParameterAndTypeField(TypeName.get(binding.bindingTypeElement().get().asType()), "module", factoryBuilder, constructorBuilder.get(), true); } for (Map.Entry<BindingKey, FrameworkField> entry : generateBindingFieldsForDependencies(binding) .entrySet()) { BindingKey bindingKey = entry.getKey(); FrameworkField bindingField = entry.getValue(); FieldSpec field = addConstructorParameterAndTypeField(bindingField.type(), uniqueFieldNames.getUniqueName(bindingField.name()), factoryBuilder, constructorBuilder.get(), true); fieldsBuilder.put(bindingKey, field); } break; case DELEGATE: return Optional.empty(); default: throw new AssertionError(); } ImmutableMap<BindingKey, FieldSpec> fields = fieldsBuilder.build(); factoryBuilder.addModifiers(PUBLIC).addSuperinterface(factoryTypeName); // If constructing a factory for @Inject or @Provides bindings, we use a static create method // so that generated components can avoid having to refer to the generic types // of the factory. (Otherwise they may have visibility problems referring to the types.) Optional<MethodSpec> createMethod; switch (binding.bindingKind()) { case INJECTION: case PROVISION: // The return type is usually the same as the implementing type, except in the case // of enums with type variables (where we cast). MethodSpec.Builder createMethodBuilder = methodBuilder("create").addModifiers(PUBLIC, STATIC) .returns(factoryTypeName); if (factoryHasTypeParameters) { createMethodBuilder.addTypeVariables(typeParameters); } List<ParameterSpec> params = constructorBuilder.isPresent() ? constructorBuilder.get().build().parameters : ImmutableList.of(); createMethodBuilder.addParameters(params); switch (binding.factoryCreationStrategy()) { case SINGLETON_INSTANCE: if (factoryHasTypeParameters) { // We use an unsafe cast here because the types are different. // It's safe because the type is never referenced anywhere. createMethodBuilder.addStatement("return ($T) INSTANCE", TypeNames.FACTORY); createMethodBuilder.addAnnotation(suppressWarnings(RAWTYPES, UNCHECKED)); } else { createMethodBuilder.addStatement("return INSTANCE"); } break; case CLASS_CONSTRUCTOR: createMethodBuilder.addStatement("return new $T($L)", parameterizedGeneratedTypeNameForBinding(binding), makeParametersCodeBlock(Lists.transform(params, input -> CodeBlock.of("$N", input)))); break; default: throw new AssertionError(); } createMethod = Optional.of(createMethodBuilder.build()); break; default: createMethod = Optional.empty(); } if (constructorBuilder.isPresent()) { factoryBuilder.addMethod(constructorBuilder.get().build()); } List<CodeBlock> parameters = Lists.newArrayList(); for (DependencyRequest dependency : binding.explicitDependencies()) { parameters.add(frameworkTypeUsageStatement(CodeBlock.of("$N", fields.get(dependency.bindingKey())), dependency.kind())); } CodeBlock parametersCodeBlock = makeParametersCodeBlock(parameters); MethodSpec.Builder getMethodBuilder = methodBuilder("get").returns(providedTypeName) .addAnnotation(Override.class).addModifiers(PUBLIC); final CodeBlock codeBlock = CodeBlock.of(getDelegateFieldName(binding.key())); if (binding.bindingKind().equals(PROVISION)) { if (Util.bindingSupportsTestDelegate(binding)) { final String delegateFieldName = Util.getDelegateFieldName(binding.key()); getMethodBuilder.beginControlFlow("if ($L != null)", CodeBlock.of(delegateFieldName)); getMethodBuilder.addStatement("return $L.get($L)", CodeBlock.of(delegateFieldName), parametersCodeBlock); getMethodBuilder.nextControlFlow("else"); } CodeBlock.Builder providesMethodInvocationBuilder = CodeBlock.builder(); if (binding.requiresModuleInstance()) { providesMethodInvocationBuilder.add("module"); } else { providesMethodInvocationBuilder.add("$T", ClassName.get(binding.bindingTypeElement().get())); } providesMethodInvocationBuilder.add(".$L($L)", binding.bindingElement().get().getSimpleName(), parametersCodeBlock); CodeBlock providesMethodInvocation = providesMethodInvocationBuilder.build(); getMethodBuilder.addStatement("return $L", providesMethodInvocation); if (Util.bindingSupportsTestDelegate(binding)) { getMethodBuilder.endControlFlow(); } /*if (binding.nullableType().isPresent() || compilerOptions.nullableValidationKind().equals(Diagnostic.Kind.WARNING)) { if (binding.nullableType().isPresent()) { getMethodBuilder.addAnnotation((ClassName) TypeName.get(binding.nullableType().get())); } getMethodBuilder.addStatement("return $L", providesMethodInvocation); } else { getMethodBuilder.addStatement("return $T.checkNotNull($L, $S)", Preconditions.class, providesMethodInvocation, CANNOT_RETURN_NULL_FROM_NON_NULLABLE_PROVIDES_METHOD); }*/ } else if (binding.membersInjectionRequest().isPresent()) { if (Util.bindingSupportsTestDelegate(binding)) { getMethodBuilder.beginControlFlow("if ($L != null)", codeBlock); getMethodBuilder.addStatement("return $T.injectMembers($N, $L.get($L))", MembersInjectors.class, fields.get(binding.membersInjectionRequest().get().bindingKey()), codeBlock, parametersCodeBlock); getMethodBuilder.nextControlFlow("else"); } getMethodBuilder.addStatement("return $T.injectMembers($N, new $T($L))", MembersInjectors.class, fields.get(binding.membersInjectionRequest().get().bindingKey()), providedTypeName, parametersCodeBlock); if (Util.bindingSupportsTestDelegate(binding)) { getMethodBuilder.endControlFlow(); } } else { if (Util.bindingSupportsTestDelegate(binding)) { getMethodBuilder.beginControlFlow("if ($L != null)", codeBlock); getMethodBuilder.addStatement("return $L.get($L)", codeBlock, parametersCodeBlock); getMethodBuilder.nextControlFlow("else"); } getMethodBuilder.addStatement("return new $T($L)", providedTypeName, parametersCodeBlock); if (Util.bindingSupportsTestDelegate(binding)) { getMethodBuilder.endControlFlow(); } } factoryBuilder.addMethod(getMethodBuilder.build()); if (createMethod.isPresent()) { factoryBuilder.addMethod(createMethod.get()); } proxyMethodFor(binding).ifPresent(factoryBuilder::addMethod); gwtIncompatibleAnnotation(binding).ifPresent(factoryBuilder::addAnnotation); return Optional.of(factoryBuilder); } /** * Returns a method to proxy access to the binding's {@link Binding#bindingElement()}, which * behaves according to the description in {@link Proxies}. Use here is further restricted by * whether or not members injection is required, since that is not yet implemented for proxy * methods, but will be added. */ // TODO(gak): support accessibility proxies for types with injected members as well private static Optional<MethodSpec> proxyMethodFor(ProvisionBinding binding) { ExecutableElement executableElement = MoreElements.asExecutable(binding.bindingElement().get()); if (binding.membersInjectionRequest().isPresent() || !shouldGenerateProxy(executableElement)) { return Optional.empty(); } return Optional.of(createProxy(executableElement)); } @CanIgnoreReturnValue private FieldSpec addConstructorParameterAndTypeField(TypeName typeName, String variableName, TypeSpec.Builder factoryBuilder, MethodSpec.Builder constructorBuilder, boolean assertCheck) { FieldSpec field = FieldSpec.builder(typeName, variableName, PRIVATE, FINAL).build(); factoryBuilder.addField(field); ParameterSpec parameter = ParameterSpec.builder(typeName, variableName).build(); constructorBuilder.addParameter(parameter); if (assertCheck) { constructorBuilder.addCode("assert $1N != null; this.$2N = $1N;", parameter, field); } else { constructorBuilder.addCode("this.$2N = $1N;", parameter, field); } return field; } }