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(constructor -> Generators.isConstructorVisibleToSubclass( 82 constructor, metadata.element())) 83 .map(this::constructorMethod) 84 .forEach(builder::addMethod); 85 86 env.getFiler() 87 .write( 88 JavaFile.builder(generatedClassName.packageName(), builder.build()).build(), 89 XFiler.Mode.Isolating); 90 } 91 92 /** 93 * Returns a pass-through constructor matching the base class's provided constructorElement. The 94 * generated constructor simply calls super(), then inject(). 95 * 96 * <p>Eg 97 * 98 * <pre> 99 * Hilt_$CLASS(Context context, ...) { 100 * super(context, ...); 101 * if (!isInEditMode()) { 102 * inject(); 103 * } 104 * } 105 * </pre> 106 */ constructorMethod(XConstructorElement constructor)107 private MethodSpec constructorMethod(XConstructorElement constructor) { 108 MethodSpec.Builder builder = Generators.copyConstructor(constructor).toBuilder(); 109 110 // TODO(b/210544481): Once this bug is fixed we should require that the user adds this 111 // annotation to their constructor and we'll propagate it from there rather than trying to 112 // guess whether this needs @TargetApi from the signature. This check is a bit flawed. For 113 // example, the user could write a 5 parameter constructor that calls the restricted 4 parameter 114 // constructor and we would miss adding @TargetApi to it. 115 if (isRestrictedApiConstructor(constructor)) { 116 // 4 parameter constructors are only available on @TargetApi(21). 117 builder.addAnnotation( 118 AnnotationSpec.builder(AndroidClassNames.TARGET_API).addMember("value", "21").build()); 119 } 120 121 builder.beginControlFlow("if(!isInEditMode())") 122 .addStatement("inject()") 123 .endControlFlow(); 124 125 return builder.build(); 126 } 127 isRestrictedApiConstructor(XConstructorElement constructor)128 private boolean isRestrictedApiConstructor(XConstructorElement constructor) { 129 if (constructor.getParameters().size() != 4) { 130 return false; 131 } 132 133 List<XExecutableParameterElement> constructorParams = constructor.getParameters(); 134 for (int i = 0; i < constructorParams.size(); i++) { 135 XType type = constructorParams.get(i).getType(); 136 switch (i) { 137 case 0: 138 if (!isFirstRestrictedParameter(type)) { 139 return false; 140 } 141 break; 142 case 1: 143 if (!isSecondRestrictedParameter(type)) { 144 return false; 145 } 146 break; 147 case 2: 148 if (!isThirdRestrictedParameter(type)) { 149 return false; 150 } 151 break; 152 case 3: 153 if (!isFourthRestrictedParameter(type)) { 154 return false; 155 } 156 break; 157 default: 158 return false; 159 } 160 } 161 162 return true; 163 } 164 isFourthRestrictedParameter(XType type)165 private static boolean isFourthRestrictedParameter(XType type) { 166 return isPrimitive(type) && isInt(type); 167 } 168 isThirdRestrictedParameter(XType type)169 private static boolean isThirdRestrictedParameter(XType type) { 170 return isPrimitive(type) && isInt(type); 171 } 172 isSecondRestrictedParameter(XType type)173 private static boolean isSecondRestrictedParameter(XType type) { 174 return isDeclared(type) 175 && Processors.isAssignableFrom(type.getTypeElement(), AndroidClassNames.ATTRIBUTE_SET); 176 } 177 isFirstRestrictedParameter(XType type)178 private static boolean isFirstRestrictedParameter(XType type) { 179 return isDeclared(type) 180 && Processors.isAssignableFrom(type.getTypeElement(), AndroidClassNames.CONTEXT); 181 } 182 } 183