private CodeBlock getInstanceCodeBlockWithPotentialCast( Element injectionSiteElement, Element bindingElement) { if (injectionSiteElement.equals(bindingElement)) { return CodeBlocks.format("instance"); } TypeName injectionSiteName = TypeName.get(injectionSiteElement.asType()); if (injectionSiteName instanceof ParameterizedTypeName) { injectionSiteName = ((ParameterizedTypeName) injectionSiteName).rawType; } return CodeBlocks.format("(($T) instance)", injectionSiteName); }
/** * Returns a code block that injects the instance's field or method by calling a static method on * the parent MembersInjector class. */ private CodeBlock delegateInjectMemberCodeBlock( ImmutableMap<BindingKey, FieldSpec> dependencyFields, InjectionSite injectionSite) { return CodeBlocks.format( "$L.$L($L);", javapoetMembersInjectorNameForType( MoreElements.asType(injectionSite.element().getEnclosingElement())), injectionSiteDelegateMethodName(injectionSite.element()), makeParametersCodeBlock( new ImmutableList.Builder<CodeBlock>() .add(CodeBlocks.format("instance")) .addAll(parameterCodeBlocks(dependencyFields, injectionSite.dependencies(), false)) .build())); }
/** Returns a code block that directly injects the instance's field or method. */ private CodeBlock directInjectMemberCodeBlock( MembersInjectionBinding binding, ImmutableMap<BindingKey, FieldSpec> dependencyFields, InjectionSite injectionSite) { return CodeBlocks.format( injectionSite.element().getKind().isField() ? "$L.$L = $L;" : "$L.$L($L);", getInstanceCodeBlockWithPotentialCast( injectionSite.element().getEnclosingElement(), binding.bindingElement()), injectionSite.element().getSimpleName(), makeParametersCodeBlock( parameterCodeBlocks(dependencyFields, injectionSite.dependencies(), true))); }
/** * Returns the parameters for injecting a member. * * @param passValue if {@code true}, each parameter code block will be the result of converting * the field from the framework type ({@link Provider}, {@link Producer}, etc.) to the real * value; if {@code false}, each parameter code block will be just the field */ private ImmutableList<CodeBlock> parameterCodeBlocks( ImmutableMap<BindingKey, FieldSpec> dependencyFields, ImmutableSet<DependencyRequest> dependencies, boolean passValue) { ImmutableList.Builder<CodeBlock> parameters = ImmutableList.builder(); for (DependencyRequest dependency : dependencies) { CodeBlock fieldCodeBlock = CodeBlocks.format("$L", dependencyFields.get(dependency.bindingKey()).name); parameters.add( passValue ? frameworkTypeUsageStatement(fieldCodeBlock, dependency.kind()) : fieldCodeBlock); } return parameters.build(); }
private MethodSpec injectorMethodForSubclasses( ImmutableMap<BindingKey, FieldSpec> dependencyFields, List<TypeVariableName> typeParameters, TypeName injectedTypeName, Element injectionElement, ImmutableSet<DependencyRequest> dependencies) { MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(injectionSiteDelegateMethodName(injectionElement)) .addModifiers(PUBLIC, STATIC) .addParameter(injectedTypeName, "instance") .addTypeVariables(typeParameters); ImmutableList.Builder<CodeBlock> providedParameters = ImmutableList.builder(); Set<String> parameterNames = new HashSet<>(); for (DependencyRequest dependency : dependencies) { FieldSpec field = dependencyFields.get(dependency.bindingKey()); ParameterSpec parameter = ParameterSpec.builder( field.type, staticInjectMethodDependencyParameterName(parameterNames, dependency, field)) .build(); methodBuilder.addParameter(parameter); providedParameters.add( frameworkTypeUsageStatement(CodeBlocks.format("$N", parameter), dependency.kind())); } if (injectionElement.getKind().isField()) { methodBuilder.addStatement( "instance.$L = $L", injectionElement.getSimpleName(), getOnlyElement(providedParameters.build())); } else { methodBuilder.addStatement( "instance.$L($L)", injectionElement.getSimpleName(), makeParametersCodeBlock(providedParameters.build())); } return methodBuilder.build(); }
private MethodSpec buildCreateMethod( ClassName mapKeyGeneratedTypeName, TypeElement annotationElement) { String createMethodName = "create" + annotationElement.getSimpleName(); MethodSpec.Builder createMethod = methodBuilder(createMethodName) .addAnnotation(AutoAnnotation.class) .addModifiers(PUBLIC, STATIC) .returns(TypeName.get(annotationElement.asType())); ImmutableList.Builder<CodeBlock> parameters = ImmutableList.builder(); for (ExecutableElement annotationMember : methodsIn(annotationElement.getEnclosedElements())) { String parameterName = annotationMember.getSimpleName().toString(); TypeName parameterType = TypeName.get(annotationMember.getReturnType()); createMethod.addParameter(parameterType, parameterName); parameters.add(CodeBlocks.format("$L", parameterName)); } ClassName autoAnnotationClass = mapKeyGeneratedTypeName.peerClass( "AutoAnnotation_" + mapKeyGeneratedTypeName.simpleName() + "_" + createMethodName); createMethod.addStatement( "return new $T($L)", autoAnnotationClass, makeParametersCodeBlock(parameters.build())); return createMethod.build(); }
@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. checkState(!binding.unresolved().isPresent()); TypeMirror keyType = binding.contributionType().equals(ContributionType.MAP) ? MapType.from(binding.key().type()).unwrappedValueType(Provider.class) : binding.key().type(); TypeName providedTypeName = TypeName.get(keyType); ParameterizedTypeName parameterizedFactoryName = factoryOf(providedTypeName); Optional<ParameterizedTypeName> factoryOfRawTypeName = Optional.absent(); TypeSpec.Builder factoryBuilder; Optional<MethodSpec.Builder> constructorBuilder = Optional.absent(); ImmutableList<TypeVariableName> typeParameters = bindingTypeElementTypeVariableNames(binding); ImmutableMap<BindingKey, FrameworkField> fields = generateBindingFieldsForDependencies(dependencyRequestMapper, binding); switch (binding.factoryCreationStrategy()) { case ENUM_INSTANCE: factoryBuilder = enumBuilder(generatedTypeName.simpleName()).addEnumConstant("INSTANCE"); // 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()) { factoryBuilder.addAnnotation(SUPPRESS_WARNINGS_RAWTYPES); // TODO(ronshapiro): instead of reassigning, introduce an optional/second parameter providedTypeName = ((ParameterizedTypeName) providedTypeName).rawType; factoryOfRawTypeName = Optional.of(factoryOf(providedTypeName)); } break; case CLASS_CONSTRUCTOR: factoryBuilder = classBuilder(generatedTypeName.simpleName()) .addTypeVariables(typeParameters) .addModifiers(FINAL); constructorBuilder = Optional.of(constructorBuilder().addModifiers(PUBLIC)); if (binding.bindingKind().equals(PROVISION) && !binding.bindingElement().getModifiers().contains(STATIC)) { addConstructorParameterAndTypeField( TypeName.get(binding.bindingTypeElement().asType()), "module", factoryBuilder, constructorBuilder.get()); } for (FrameworkField bindingField : fields.values()) { addConstructorParameterAndTypeField( bindingField.javapoetFrameworkType(), bindingField.name(), factoryBuilder, constructorBuilder.get()); } break; default: throw new AssertionError(); } factoryBuilder .addModifiers(PUBLIC) .addSuperinterface(factoryOfRawTypeName.or(parameterizedFactoryName)); // 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) .addTypeVariables(typeParameters) .returns(parameterizedFactoryName); List<ParameterSpec> params = constructorBuilder.isPresent() ? constructorBuilder.get().build().parameters : ImmutableList.<ParameterSpec>of(); createMethodBuilder.addParameters(params); switch (binding.factoryCreationStrategy()) { case ENUM_INSTANCE: if (typeParameters.isEmpty()) { createMethodBuilder.addStatement("return INSTANCE"); } else { // 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(SUPPRESS_WARNINGS_UNCHECKED); } break; case CLASS_CONSTRUCTOR: createMethodBuilder.addStatement( "return new $T($L)", javapoetParameterizedGeneratedTypeNameForBinding(binding), makeParametersCodeBlock(Lists.transform(params, CodeBlocks.PARAMETER_NAME))); break; default: throw new AssertionError(); } createMethod = Optional.of(createMethodBuilder.build()); break; default: createMethod = Optional.absent(); } if (constructorBuilder.isPresent()) { factoryBuilder.addMethod(constructorBuilder.get().build()); } List<CodeBlock> parameters = Lists.newArrayList(); for (DependencyRequest dependency : binding.dependencies()) { parameters.add( frameworkTypeUsageStatement( CodeBlocks.format("$L", fields.get(dependency.bindingKey()).name()), dependency.kind())); } CodeBlock parametersCodeBlock = makeParametersCodeBlock(parameters); MethodSpec.Builder getMethodBuilder = methodBuilder("get") .returns(providedTypeName) .addAnnotation(Override.class) .addModifiers(PUBLIC); if (binding.bindingKind().equals(PROVISION)) { CodeBlock.Builder providesMethodInvocationBuilder = CodeBlock.builder(); if (binding.bindingElement().getModifiers().contains(STATIC)) { providesMethodInvocationBuilder.add("$T", ClassName.get(binding.bindingTypeElement())); } else { providesMethodInvocationBuilder.add("module"); } providesMethodInvocationBuilder.add( ".$L($L)", binding.bindingElement().getSimpleName(), parametersCodeBlock); CodeBlock providesMethodInvocation = providesMethodInvocationBuilder.build(); if (binding.provisionType().equals(SET)) { TypeName paramTypeName = TypeName.get(MoreTypes.asDeclared(keyType).getTypeArguments().get(0)); // TODO(cgruber): only be explicit with the parameter if paramType contains wildcards. getMethodBuilder.addStatement( "return $T.<$T>singleton($L)", Collections.class, paramTypeName, providesMethodInvocation); } else if (binding.nullableType().isPresent() || nullableValidationType.equals(Diagnostic.Kind.WARNING)) { if (binding.nullableType().isPresent()) { getMethodBuilder.addAnnotation((ClassName) TypeName.get(binding.nullableType().get())); } getMethodBuilder.addStatement("return $L", providesMethodInvocation); } else { String failMsg = CANNOT_RETURN_NULL_FROM_NON_NULLABLE_PROVIDES_METHOD; getMethodBuilder .addStatement( "$T provided = $L", getMethodBuilder.build().returnType, providesMethodInvocation) .addCode("if (provided == null) { ") .addStatement("throw new $T($S)", NullPointerException.class, failMsg) .addCode("}") .addStatement("return provided"); } } else if (binding.membersInjectionRequest().isPresent()) { getMethodBuilder.addStatement( "$1T instance = new $1T($2L)", providedTypeName, parametersCodeBlock); getMethodBuilder.addStatement( "$L.injectMembers(instance)", fields.get(binding.membersInjectionRequest().get().bindingKey()).name()); getMethodBuilder.addStatement("return instance"); } else { getMethodBuilder.addStatement("return new $T($L)", providedTypeName, parametersCodeBlock); } factoryBuilder.addMethod(getMethodBuilder.build()); if (createMethod.isPresent()) { factoryBuilder.addMethod(createMethod.get()); } // TODO(gak): write a sensible toString return Optional.of(factoryBuilder); }
@Override Optional<TypeSpec.Builder> write(ClassName generatedTypeName, MembersInjectionBinding binding) { // Empty members injection bindings are special and don't need source files. if (binding.injectionSites().isEmpty()) { return Optional.absent(); } // We don't want to write out resolved bindings -- we want to write out the generic version. checkState(!binding.unresolved().isPresent()); ImmutableList<TypeVariableName> typeParameters = bindingTypeElementTypeVariableNames(binding); TypeSpec.Builder injectorTypeBuilder = TypeSpec.classBuilder(generatedTypeName.simpleName()) .addModifiers(PUBLIC, FINAL) .addTypeVariables(typeParameters); TypeName injectedTypeName = TypeName.get(binding.key().type()); TypeName implementedType = membersInjectorOf(injectedTypeName); injectorTypeBuilder.addSuperinterface(implementedType); MethodSpec.Builder injectMembersBuilder = MethodSpec.methodBuilder("injectMembers") .returns(TypeName.VOID) .addModifiers(PUBLIC) .addAnnotation(Override.class) .addParameter(injectedTypeName, "instance") .addCode("if (instance == null) {") .addStatement( "throw new $T($S)", NullPointerException.class, "Cannot inject members into a null reference") .addCode("}"); ImmutableMap<BindingKey, FrameworkField> fields = SourceFiles.generateBindingFieldsForDependencies(dependencyRequestMapper, binding); ImmutableMap.Builder<BindingKey, FieldSpec> dependencyFieldsBuilder = ImmutableMap.builder(); MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder().addModifiers(PUBLIC); // 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.) MethodSpec.Builder createMethodBuilder = MethodSpec.methodBuilder("create") .returns(implementedType) .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addTypeVariables(typeParameters); createMethodBuilder.addCode( "return new $T(", javapoetParameterizedGeneratedTypeNameForBinding(binding)); ImmutableList.Builder<CodeBlock> constructorInvocationParameters = ImmutableList.builder(); boolean usesRawFrameworkTypes = false; UniqueNames fieldNames = new UniqueNames(); for (Entry<BindingKey, FrameworkField> fieldEntry : fields.entrySet()) { BindingKey bindingKey = fieldEntry.getKey(); FrameworkField bindingField = fieldEntry.getValue(); // If the dependency type is not visible to this members injector, then use the raw framework // type for the field. boolean useRawFrameworkType = !VISIBLE_TO_MEMBERS_INJECTOR.visit(bindingKey.key().type(), binding); String fieldName = fieldNames.getUniqueName(bindingField.name()); TypeName fieldType = useRawFrameworkType ? bindingField.javapoetFrameworkType().rawType : bindingField.javapoetFrameworkType(); FieldSpec.Builder fieldBuilder = FieldSpec.builder(fieldType, fieldName, PRIVATE, FINAL); ParameterSpec.Builder parameterBuilder = ParameterSpec.builder(fieldType, fieldName); // If we're using the raw type for the field, then suppress the injectMembers method's // unchecked-type warning and the field's and the constructor and create-method's // parameters' raw-type warnings. if (useRawFrameworkType) { usesRawFrameworkTypes = true; fieldBuilder.addAnnotation(SUPPRESS_WARNINGS_RAWTYPES); parameterBuilder.addAnnotation(SUPPRESS_WARNINGS_RAWTYPES); } constructorBuilder.addParameter(parameterBuilder.build()); createMethodBuilder.addParameter(parameterBuilder.build()); FieldSpec field = fieldBuilder.build(); injectorTypeBuilder.addField(field); constructorBuilder.addStatement("assert $N != null", field); constructorBuilder.addStatement("this.$N = $N", field, field); dependencyFieldsBuilder.put(bindingKey, field); constructorInvocationParameters.add(CodeBlocks.format("$N", field)); } createMethodBuilder.addCode(CodeBlocks.join(constructorInvocationParameters.build(), ", ")); createMethodBuilder.addCode(");"); injectorTypeBuilder.addMethod(constructorBuilder.build()); injectorTypeBuilder.addMethod(createMethodBuilder.build()); Set<String> delegateMethods = new HashSet<>(); ImmutableMap<BindingKey, FieldSpec> dependencyFields = dependencyFieldsBuilder.build(); List<MethodSpec> injectMethodsForSubclasses = new ArrayList<>(); for (InjectionSite injectionSite : binding.injectionSites()) { injectMembersBuilder.addCode( visibleToMembersInjector(binding, injectionSite.element()) ? directInjectMemberCodeBlock(binding, dependencyFields, injectionSite) : delegateInjectMemberCodeBlock(dependencyFields, injectionSite)); if (!injectionSite.element().getModifiers().contains(PUBLIC) && injectionSite.element().getEnclosingElement().equals(binding.bindingElement()) && delegateMethods.add(injectionSiteDelegateMethodName(injectionSite.element()))) { injectMethodsForSubclasses.add( injectorMethodForSubclasses( dependencyFields, typeParameters, injectedTypeName, injectionSite.element(), injectionSite.dependencies())); } } if (usesRawFrameworkTypes) { injectMembersBuilder.addAnnotation(SUPPRESS_WARNINGS_UNCHECKED); } injectorTypeBuilder.addMethod(injectMembersBuilder.build()); for (MethodSpec methodSpec : injectMethodsForSubclasses) { injectorTypeBuilder.addMethod(methodSpec); } return Optional.of(injectorTypeBuilder); }