1 /* 2 * Copyright (C) 2018 The Dagger Authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package dagger.internal.codegen.writing; 18 19 import static com.squareup.javapoet.MethodSpec.constructorBuilder; 20 import static com.squareup.javapoet.MethodSpec.methodBuilder; 21 import static com.squareup.javapoet.TypeSpec.classBuilder; 22 import static dagger.internal.codegen.langmodel.Accessibility.isElementAccessibleFrom; 23 import static dagger.internal.codegen.xprocessing.XTypeElements.isNested; 24 import static javax.lang.model.element.Modifier.FINAL; 25 import static javax.lang.model.element.Modifier.PRIVATE; 26 import static javax.lang.model.element.Modifier.PUBLIC; 27 import static javax.lang.model.element.Modifier.STATIC; 28 29 import androidx.room.compiler.processing.XConstructorElement; 30 import androidx.room.compiler.processing.XElement; 31 import androidx.room.compiler.processing.XFiler; 32 import androidx.room.compiler.processing.XProcessingEnv; 33 import androidx.room.compiler.processing.XTypeElement; 34 import com.google.common.collect.ImmutableList; 35 import com.squareup.javapoet.ClassName; 36 import com.squareup.javapoet.CodeBlock; 37 import com.squareup.javapoet.TypeSpec; 38 import dagger.internal.codegen.base.ModuleKind; 39 import dagger.internal.codegen.base.SourceFileGenerator; 40 import dagger.internal.codegen.binding.SourceFiles; 41 import dagger.internal.codegen.langmodel.Accessibility; 42 import java.util.Optional; 43 import javax.inject.Inject; 44 45 /** Convenience methods for generating and using module constructor proxy methods. */ 46 public final class ModuleProxies { ModuleProxies()47 private ModuleProxies() {} 48 49 /** Generates a {@code public static} proxy method for constructing module instances. */ 50 // TODO(dpb): See if this can become a SourceFileGenerator<ModuleDescriptor> instead. Doing so may 51 // cause ModuleProcessingStep to defer elements multiple times. 52 public static final class ModuleConstructorProxyGenerator 53 extends SourceFileGenerator<XTypeElement> { 54 55 @Inject ModuleConstructorProxyGenerator(XFiler filer, XProcessingEnv processingEnv)56 ModuleConstructorProxyGenerator(XFiler filer, XProcessingEnv processingEnv) { 57 super(filer, processingEnv); 58 } 59 60 @Override originatingElement(XTypeElement moduleElement)61 public XElement originatingElement(XTypeElement moduleElement) { 62 return moduleElement; 63 } 64 65 @Override topLevelTypes(XTypeElement moduleElement)66 public ImmutableList<TypeSpec.Builder> topLevelTypes(XTypeElement moduleElement) { 67 ModuleKind.checkIsModule(moduleElement); 68 return nonPublicNullaryConstructor(moduleElement).isPresent() 69 ? ImmutableList.of(buildProxy(moduleElement)) 70 : ImmutableList.of(); 71 } 72 buildProxy(XTypeElement moduleElement)73 private TypeSpec.Builder buildProxy(XTypeElement moduleElement) { 74 return classBuilder(constructorProxyTypeName(moduleElement)) 75 .addModifiers(PUBLIC, FINAL) 76 .addMethod(constructorBuilder().addModifiers(PRIVATE).build()) 77 .addMethod( 78 methodBuilder("newInstance") 79 .addModifiers(PUBLIC, STATIC) 80 .returns(moduleElement.getClassName()) 81 .addStatement("return new $T()", moduleElement.getClassName()) 82 .build()); 83 } 84 } 85 86 /** The name of the class that hosts the module constructor proxy method. */ constructorProxyTypeName(XTypeElement moduleElement)87 private static ClassName constructorProxyTypeName(XTypeElement moduleElement) { 88 ModuleKind.checkIsModule(moduleElement); 89 ClassName moduleClassName = moduleElement.getClassName(); 90 return moduleClassName 91 .topLevelClassName() 92 .peerClass(SourceFiles.classFileName(moduleClassName) + "_Proxy"); 93 } 94 95 /** 96 * The module constructor being proxied. A proxy is generated if it is not publicly accessible and 97 * has no arguments. If an implicit reference to the enclosing class exists, or the module is 98 * abstract, no proxy method can be generated. 99 */ nonPublicNullaryConstructor( XTypeElement moduleElement)100 private static Optional<XConstructorElement> nonPublicNullaryConstructor( 101 XTypeElement moduleElement) { 102 ModuleKind.checkIsModule(moduleElement); 103 if (moduleElement.isAbstract() || (isNested(moduleElement) && !moduleElement.isStatic())) { 104 return Optional.empty(); 105 } 106 return moduleElement.getConstructors().stream() 107 .filter(constructor -> !Accessibility.isElementPubliclyAccessible(constructor)) 108 .filter(constructor -> !constructor.isPrivate()) 109 .filter(constructor -> constructor.getParameters().isEmpty()) 110 .findAny(); 111 } 112 113 /** 114 * Returns a code block that creates a new module instance, either by invoking the nullary 115 * constructor if it's accessible from {@code requestingClass} or else by invoking the 116 * constructor's generated proxy method. 117 */ newModuleInstance(XTypeElement moduleElement, ClassName requestingClass)118 public static CodeBlock newModuleInstance(XTypeElement moduleElement, ClassName requestingClass) { 119 ModuleKind.checkIsModule(moduleElement); 120 String packageName = requestingClass.packageName(); 121 return nonPublicNullaryConstructor(moduleElement) 122 .filter(constructor -> !isElementAccessibleFrom(constructor, packageName)) 123 .map( 124 constructor -> 125 CodeBlock.of("$T.newInstance()", constructorProxyTypeName(moduleElement))) 126 .orElse(CodeBlock.of("new $T()", moduleElement.getClassName())); 127 } 128 } 129