Java tutorial
/* * Copyright (C) 2014 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 dagger2.internal.codegen; import com.google.auto.common.MoreTypes; import com.google.common.base.Joiner; import com.google.common.base.Optional; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import dagger2.MembersInjector; import dagger2.Provides.Type; import dagger2.internal.Factory; import dagger2.internal.codegen.writer.ClassName; import dagger2.internal.codegen.writer.ClassWriter; import dagger2.internal.codegen.writer.ConstructorWriter; import dagger2.internal.codegen.writer.EnumWriter; import dagger2.internal.codegen.writer.FieldWriter; import dagger2.internal.codegen.writer.JavaWriter; import dagger2.internal.codegen.writer.MethodWriter; import dagger2.internal.codegen.writer.ParameterizedTypeName; import dagger2.internal.codegen.writer.Snippet; import dagger2.internal.codegen.writer.StringLiteral; import dagger2.internal.codegen.writer.TypeName; import dagger2.internal.codegen.writer.TypeNames; import dagger2.internal.codegen.writer.TypeVariableName; import dagger2.internal.codegen.writer.TypeWriter; import java.util.Collections; import java.util.List; import java.util.Map; import javax.annotation.Generated; import javax.annotation.processing.Filer; import javax.inject.Inject; import javax.lang.model.element.Element; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic; import static com.google.common.base.Preconditions.checkState; import static dagger2.Provides.Type.SET; import static dagger2.internal.codegen.ErrorMessages.CANNOT_RETURN_NULL_FROM_NON_NULLABLE_PROVIDES_METHOD; import static dagger2.internal.codegen.ProvisionBinding.Kind.PROVISION; import static dagger2.internal.codegen.SourceFiles.factoryNameForProvisionBinding; import static dagger2.internal.codegen.SourceFiles.frameworkTypeUsageStatement; import static dagger2.internal.codegen.SourceFiles.parameterizedFactoryNameForProvisionBinding; import static dagger2.internal.codegen.writer.Snippet.makeParametersSnippet; 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; /** * 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 DependencyRequestMapper dependencyRequestMapper; private final Diagnostic.Kind nullableValidationType; FactoryGenerator(Filer filer, DependencyRequestMapper dependencyRequestMapper, Diagnostic.Kind nullableValidationType) { super(filer); this.dependencyRequestMapper = dependencyRequestMapper; this.nullableValidationType = nullableValidationType; } @Override ClassName nameGeneratedType(ProvisionBinding binding) { return factoryNameForProvisionBinding(binding); } @Override Iterable<? extends Element> getOriginatingElements(ProvisionBinding binding) { return ImmutableSet.of(binding.bindingElement()); } @Override Optional<? extends Element> getElementForErrorReporting(ProvisionBinding binding) { return Optional.of(binding.bindingElement()); } @Override ImmutableSet<JavaWriter> write(ClassName generatedTypeName, ProvisionBinding binding) { // We don't want to write out resolved bindings -- we want to write out the generic version. checkState(!binding.hasNonDefaultTypeParameters()); TypeMirror keyType = binding.provisionType().equals(Type.MAP) ? Util.getProvidedValueTypeOfMap(MoreTypes.asDeclared(binding.key().type())) : binding.key().type(); TypeName providedTypeName = TypeNames.forTypeMirror(keyType); JavaWriter writer = JavaWriter.inPackage(generatedTypeName.packageName()); final TypeWriter factoryWriter; final Optional<ConstructorWriter> constructorWriter; List<TypeVariableName> typeParameters = Lists.newArrayList(); for (TypeParameterElement typeParameter : binding.bindingTypeElement().getTypeParameters()) { typeParameters.add(TypeVariableName.fromTypeParameterElement(typeParameter)); } switch (binding.factoryCreationStrategy()) { case ENUM_INSTANCE: EnumWriter enumWriter = writer.addEnum(generatedTypeName.simpleName()); enumWriter.addConstant("INSTANCE"); constructorWriter = Optional.absent(); factoryWriter = enumWriter; // If we have type parameters, then remove the parameters from our providedTypeName, // since we'll be implementing an erased version of it. if (!typeParameters.isEmpty()) { factoryWriter.annotate(SuppressWarnings.class).setValue("rawtypes"); providedTypeName = ((ParameterizedTypeName) providedTypeName).type(); } break; case CLASS_CONSTRUCTOR: ClassWriter classWriter = writer.addClass(generatedTypeName.simpleName()); classWriter.addTypeParameters(typeParameters); classWriter.addModifiers(FINAL); constructorWriter = Optional.of(classWriter.addConstructor()); constructorWriter.get().addModifiers(PUBLIC); factoryWriter = classWriter; if (binding.bindingKind().equals(PROVISION) && !binding.bindingElement().getModifiers().contains(STATIC)) { TypeName enclosingType = TypeNames.forTypeMirror(binding.bindingTypeElement().asType()); factoryWriter.addField(enclosingType, "module").addModifiers(PRIVATE, FINAL); constructorWriter.get().addParameter(enclosingType, "module"); constructorWriter.get().body().addSnippet("assert module != null;") .addSnippet("this.module = module;"); } break; default: throw new AssertionError(); } factoryWriter.annotate(Generated.class).setValue(ComponentProcessor.class.getName()); factoryWriter.addModifiers(PUBLIC); factoryWriter.addImplementedType( ParameterizedTypeName.create(ClassName.fromClass(Factory.class), providedTypeName)); MethodWriter getMethodWriter = factoryWriter.addMethod(providedTypeName, "get"); getMethodWriter.annotate(Override.class); getMethodWriter.addModifiers(PUBLIC); if (binding.memberInjectionRequest().isPresent()) { ParameterizedTypeName membersInjectorType = ParameterizedTypeName.create(MembersInjector.class, providedTypeName); factoryWriter.addField(membersInjectorType, "membersInjector").addModifiers(PRIVATE, FINAL); constructorWriter.get().addParameter(membersInjectorType, "membersInjector"); constructorWriter.get().body().addSnippet("assert membersInjector != null;") .addSnippet("this.membersInjector = membersInjector;"); } ImmutableMap<BindingKey, FrameworkField> fields = SourceFiles .generateBindingFieldsForDependencies(dependencyRequestMapper, binding.dependencies()); for (FrameworkField bindingField : fields.values()) { TypeName fieldType = bindingField.frameworkType(); FieldWriter field = factoryWriter.addField(fieldType, bindingField.name()); field.addModifiers(PRIVATE, FINAL); constructorWriter.get().addParameter(field.type(), field.name()); constructorWriter.get().body().addSnippet("assert %s != null;", field.name()) .addSnippet("this.%1$s = %1$s;", field.name()); } // 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.) 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). TypeName returnType = ParameterizedTypeName.create(ClassName.fromClass(Factory.class), TypeNames.forTypeMirror(keyType)); MethodWriter createMethodWriter = factoryWriter.addMethod(returnType, "create"); createMethodWriter.addTypeParameters(typeParameters); createMethodWriter.addModifiers(Modifier.PUBLIC, Modifier.STATIC); Map<String, TypeName> params = constructorWriter.isPresent() ? constructorWriter.get().parameters() : ImmutableMap.<String, TypeName>of(); for (Map.Entry<String, TypeName> param : params.entrySet()) { createMethodWriter.addParameter(param.getValue(), param.getKey()); } switch (binding.factoryCreationStrategy()) { case ENUM_INSTANCE: if (typeParameters.isEmpty()) { createMethodWriter.body().addSnippet("return INSTANCE;"); } else { // We use an unsafe cast here because the types are different. // It's safe because the type is never referenced anywhere. createMethodWriter.annotate(SuppressWarnings.class).setValue("unchecked"); createMethodWriter.body().addSnippet("return (Factory) INSTANCE;"); } break; case CLASS_CONSTRUCTOR: createMethodWriter.body().addSnippet("return new %s(%s);", parameterizedFactoryNameForProvisionBinding(binding), Joiner.on(", ").join(params.keySet())); break; default: throw new AssertionError(); } break; default: // do nothing. } List<Snippet> parameters = Lists.newArrayList(); for (DependencyRequest dependency : binding.dependencies()) { parameters.add(frameworkTypeUsageStatement(Snippet.format(fields.get(dependency.bindingKey()).name()), dependency.kind())); } Snippet parametersSnippet = makeParametersSnippet(parameters); if (binding.bindingKind().equals(PROVISION)) { Snippet providesMethodInvocation = Snippet.format("%s.%s(%s)", binding.bindingElement().getModifiers().contains(STATIC) ? ClassName.fromTypeElement(binding.bindingTypeElement()) : "module", binding.bindingElement().getSimpleName(), parametersSnippet); if (binding.provisionType().equals(SET)) { TypeName paramTypeName = TypeNames .forTypeMirror(MoreTypes.asDeclared(keyType).getTypeArguments().get(0)); // TODO(cgruber): only be explicit with the parameter if paramType contains wildcards. getMethodWriter.body().addSnippet("return %s.<%s>singleton(%s);", ClassName.fromClass(Collections.class), paramTypeName, providesMethodInvocation); } else if (binding.nullableType().isPresent() || nullableValidationType.equals(Diagnostic.Kind.WARNING)) { if (binding.nullableType().isPresent()) { getMethodWriter.annotate((ClassName) TypeNames.forTypeMirror(binding.nullableType().get())); } getMethodWriter.body().addSnippet("return %s;", providesMethodInvocation); } else { StringLiteral failMsg = StringLiteral .forValue(CANNOT_RETURN_NULL_FROM_NON_NULLABLE_PROVIDES_METHOD); getMethodWriter.body() .addSnippet(Snippet.format( Joiner.on('\n').join("%s provided = %s;", "if (provided == null) {", " throw new NullPointerException(%s);", "}", "return provided;"), getMethodWriter.returnType(), providesMethodInvocation, failMsg)); } } else if (binding.memberInjectionRequest().isPresent()) { getMethodWriter.body().addSnippet("%1$s instance = new %1$s(%2$s);", providedTypeName, parametersSnippet); getMethodWriter.body().addSnippet("membersInjector.injectMembers(instance);"); getMethodWriter.body().addSnippet("return instance;"); } else { getMethodWriter.body().addSnippet("return new %s(%s);", providedTypeName, parametersSnippet); } // TODO(gak): write a sensible toString return ImmutableSet.of(writer); } }