1 /* 2 * Copyright (C) 2019 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.hilt.android.processor.internal.androidentrypoint; 18 19 import static androidx.room.compiler.processing.XTypeKt.isInt; 20 import static dagger.internal.codegen.xprocessing.XTypes.isDeclared; 21 import static dagger.internal.codegen.xprocessing.XTypes.isPrimitive; 22 23 import androidx.room.compiler.processing.JavaPoetExtKt; 24 import androidx.room.compiler.processing.XConstructorElement; 25 import androidx.room.compiler.processing.XExecutableParameterElement; 26 import androidx.room.compiler.processing.XFiler; 27 import androidx.room.compiler.processing.XProcessingEnv; 28 import androidx.room.compiler.processing.XType; 29 import androidx.room.compiler.processing.XTypeParameterElement; 30 import com.squareup.javapoet.AnnotationSpec; 31 import com.squareup.javapoet.ClassName; 32 import com.squareup.javapoet.JavaFile; 33 import com.squareup.javapoet.MethodSpec; 34 import com.squareup.javapoet.TypeSpec; 35 import dagger.hilt.android.processor.internal.AndroidClassNames; 36 import dagger.hilt.processor.internal.Processors; 37 import java.util.List; 38 39 /** Generates an Hilt View class for the @AndroidEntryPoint annotated class. */ 40 public final class ViewGenerator { 41 private final XProcessingEnv env; 42 private final AndroidEntryPointMetadata metadata; 43 private final ClassName generatedClassName; 44 ViewGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata)45 public ViewGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata) { 46 this.env = env; 47 this.metadata = metadata; 48 generatedClassName = metadata.generatedClassName(); 49 } 50 51 // @Generated("ViewGenerator") 52 // abstract class Hilt_$CLASS extends $BASE implements 53 // ComponentManagerHolder<ViewComponentManager<$CLASS_EntryPoint>> { 54 // ... 55 // } generate()56 public void generate() { 57 // Note: we do not use the Generators helper methods here because injection is called 58 // from the constructor where the double-check pattern doesn't work (due to the super 59 // constructor being called before fields are initialized) and because it isn't necessary 60 // since the object isn't done constructing yet. 61 62 TypeSpec.Builder builder = 63 TypeSpec.classBuilder(generatedClassName.simpleName()) 64 .superclass(metadata.baseClassName()) 65 .addModifiers(metadata.generatedClassModifiers()); 66 67 JavaPoetExtKt.addOriginatingElement(builder, metadata.element()); 68 Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT); 69 Processors.addGeneratedAnnotation(builder, env, getClass()); 70 Generators.copyLintAnnotations(metadata.element(), builder); 71 Generators.copySuppressAnnotations(metadata.element(), builder); 72 73 metadata.baseElement().getTypeParameters().stream() 74 .map(XTypeParameterElement::getTypeVariableName) 75 .forEachOrdered(builder::addTypeVariable); 76 77 Generators.addComponentOverride(metadata, builder); 78 Generators.addInjectionMethods(metadata, builder); 79 80 metadata.baseElement().getConstructors().stream() 81 .filter(this::isConstructorVisibleToGeneratedClass) 82 .map(this::constructorMethod) 83 .forEach(builder::addMethod); 84 85 env.getFiler() 86 .write( 87 JavaFile.builder(generatedClassName.packageName(), builder.build()).build(), 88 XFiler.Mode.Isolating); 89 } 90 isConstructorVisibleToGeneratedClass(XConstructorElement constructor)91 private boolean isConstructorVisibleToGeneratedClass(XConstructorElement constructor) { 92 if (Processors.hasJavaPackagePrivateVisibility(constructor) && !isInOurPackage(constructor)) { 93 return false; 94 } else if (constructor.isPrivate()) { 95 return false; 96 } 97 98 // We extend the base class, so both protected and public methods are always accessible. 99 return true; 100 } 101 102 /** 103 * Returns a pass-through constructor matching the base class's provided constructorElement. The 104 * generated constructor simply calls super(), then inject(). 105 * 106 * <p>Eg 107 * 108 * <pre> 109 * Hilt_$CLASS(Context context, ...) { 110 * super(context, ...); 111 * inject(); 112 * } 113 * </pre> 114 */ constructorMethod(XConstructorElement constructor)115 private MethodSpec constructorMethod(XConstructorElement constructor) { 116 MethodSpec.Builder builder = Generators.copyConstructor(constructor).toBuilder(); 117 118 // TODO(b/210544481): Once this bug is fixed we should require that the user adds this 119 // annotation to their constructor and we'll propagate it from there rather than trying to 120 // guess whether this needs @TargetApi from the signature. This check is a bit flawed. For 121 // example, the user could write a 5 parameter constructor that calls the restricted 4 parameter 122 // constructor and we would miss adding @TargetApi to it. 123 if (isRestrictedApiConstructor(constructor)) { 124 // 4 parameter constructors are only available on @TargetApi(21). 125 builder.addAnnotation( 126 AnnotationSpec.builder(AndroidClassNames.TARGET_API).addMember("value", "21").build()); 127 } 128 129 builder.addStatement("inject()"); 130 131 return builder.build(); 132 } 133 isRestrictedApiConstructor(XConstructorElement constructor)134 private boolean isRestrictedApiConstructor(XConstructorElement constructor) { 135 if (constructor.getParameters().size() != 4) { 136 return false; 137 } 138 139 List<XExecutableParameterElement> constructorParams = constructor.getParameters(); 140 for (int i = 0; i < constructorParams.size(); i++) { 141 XType type = constructorParams.get(i).getType(); 142 switch (i) { 143 case 0: 144 if (!isFirstRestrictedParameter(type)) { 145 return false; 146 } 147 break; 148 case 1: 149 if (!isSecondRestrictedParameter(type)) { 150 return false; 151 } 152 break; 153 case 2: 154 if (!isThirdRestrictedParameter(type)) { 155 return false; 156 } 157 break; 158 case 3: 159 if (!isFourthRestrictedParameter(type)) { 160 return false; 161 } 162 break; 163 default: 164 return false; 165 } 166 } 167 168 return true; 169 } 170 isFourthRestrictedParameter(XType type)171 private static boolean isFourthRestrictedParameter(XType type) { 172 return isPrimitive(type) && isInt(type); 173 } 174 isThirdRestrictedParameter(XType type)175 private static boolean isThirdRestrictedParameter(XType type) { 176 return isPrimitive(type) && isInt(type); 177 } 178 isSecondRestrictedParameter(XType type)179 private static boolean isSecondRestrictedParameter(XType type) { 180 return isDeclared(type) 181 && Processors.isAssignableFrom(type.getTypeElement(), AndroidClassNames.ATTRIBUTE_SET); 182 } 183 isFirstRestrictedParameter(XType type)184 private static boolean isFirstRestrictedParameter(XType type) { 185 return isDeclared(type) 186 && Processors.isAssignableFrom(type.getTypeElement(), AndroidClassNames.CONTEXT); 187 } 188 isInOurPackage(XConstructorElement constructor)189 private boolean isInOurPackage(XConstructorElement constructor) { 190 return constructor 191 .getEnclosingElement() 192 .getPackageName() 193 .contentEquals(metadata.element().getPackageName()); 194 } 195 } 196