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 com.google.auto.common.MoreElements; 20 import com.google.auto.common.Visibility; 21 import com.squareup.javapoet.AnnotationSpec; 22 import com.squareup.javapoet.ClassName; 23 import com.squareup.javapoet.JavaFile; 24 import com.squareup.javapoet.MethodSpec; 25 import com.squareup.javapoet.TypeSpec; 26 import com.squareup.javapoet.TypeVariableName; 27 import dagger.hilt.android.processor.internal.AndroidClassNames; 28 import dagger.hilt.processor.internal.Processors; 29 import java.io.IOException; 30 import java.util.List; 31 import javax.annotation.processing.ProcessingEnvironment; 32 import javax.lang.model.element.Element; 33 import javax.lang.model.element.ExecutableElement; 34 import javax.lang.model.element.TypeElement; 35 import javax.lang.model.element.VariableElement; 36 import javax.lang.model.type.TypeKind; 37 import javax.lang.model.type.TypeMirror; 38 import javax.lang.model.util.ElementFilter; 39 40 /** Generates an Hilt View class for the @AndroidEntryPoint annotated class. */ 41 public final class ViewGenerator { 42 private final ProcessingEnvironment env; 43 private final AndroidEntryPointMetadata metadata; 44 private final ClassName generatedClassName; 45 ViewGenerator(ProcessingEnvironment env, AndroidEntryPointMetadata metadata)46 public ViewGenerator(ProcessingEnvironment env, AndroidEntryPointMetadata metadata) { 47 this.env = env; 48 this.metadata = metadata; 49 50 generatedClassName = metadata.generatedClassName(); 51 } 52 53 // @Generated("ViewGenerator") 54 // abstract class Hilt_$CLASS extends $BASE implements 55 // ComponentManagerHolder<ViewComponentManager<$CLASS_EntryPoint>> { 56 // ... 57 // } generate()58 public void generate() throws IOException { 59 // Note: we do not use the Generators helper methods here because injection is called 60 // from the constructor where the double-check pattern doesn't work (due to the super 61 // constructor being called before fields are initialized) and because it isn't necessary 62 // since the object isn't done constructing yet. 63 64 TypeSpec.Builder builder = 65 TypeSpec.classBuilder(generatedClassName.simpleName()) 66 .addOriginatingElement(metadata.element()) 67 .superclass(metadata.baseClassName()) 68 .addModifiers(metadata.generatedClassModifiers()); 69 70 Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT); 71 Processors.addGeneratedAnnotation(builder, env, getClass()); 72 Generators.copyLintAnnotations(metadata.element(), builder); 73 74 metadata.baseElement().getTypeParameters().stream() 75 .map(TypeVariableName::get) 76 .forEachOrdered(builder::addTypeVariable); 77 78 Generators.addComponentOverride(metadata, builder); 79 80 Generators.addInjectionMethods(metadata, builder); 81 82 ElementFilter.constructorsIn(metadata.baseElement().getEnclosedElements()).stream() 83 .filter(this::isConstructorVisibleToGeneratedClass) 84 .forEach(constructor -> builder.addMethod(constructorMethod(constructor))); 85 86 JavaFile.builder(generatedClassName.packageName(), builder.build()) 87 .build() 88 .writeTo(env.getFiler()); 89 } 90 isConstructorVisibleToGeneratedClass(ExecutableElement constructorElement)91 private boolean isConstructorVisibleToGeneratedClass(ExecutableElement constructorElement) { 92 if (Visibility.ofElement(constructorElement) == Visibility.DEFAULT 93 && !isInOurPackage(constructorElement)) { 94 return false; 95 } else if (Visibility.ofElement(constructorElement) == Visibility.PRIVATE) { 96 return false; 97 } 98 99 // We extend the base class, so both protected and public methods are always accessible. 100 return true; 101 } 102 103 /** 104 * Returns a pass-through constructor matching the base class's provided constructorElement. The 105 * generated constructor simply calls super(), then inject(). 106 * 107 * <p>Eg 108 * 109 * <pre> 110 * Hilt_$CLASS(Context context, ...) { 111 * super(context, ...); 112 * inject(); 113 * } 114 * </pre> 115 */ constructorMethod(ExecutableElement constructorElement)116 private MethodSpec constructorMethod(ExecutableElement constructorElement) { 117 MethodSpec.Builder constructor = 118 Generators.copyConstructor(constructorElement).toBuilder(); 119 120 if (isRestrictedApiConstructor(constructorElement)) { 121 // 4 parameter constructors are only available on @TargetApi(21). 122 constructor.addAnnotation( 123 AnnotationSpec.builder(AndroidClassNames.TARGET_API).addMember("value", "21").build()); 124 } 125 126 constructor.addStatement("inject()"); 127 128 return constructor.build(); 129 } 130 isRestrictedApiConstructor(ExecutableElement constructor)131 private boolean isRestrictedApiConstructor(ExecutableElement constructor) { 132 if (constructor.getParameters().size() != 4) { 133 return false; 134 } 135 136 List<? extends VariableElement> constructorParams = constructor.getParameters(); 137 for (int i = 0; i < constructorParams.size(); i++) { 138 TypeMirror type = constructorParams.get(i).asType(); 139 Element element = env.getTypeUtils().asElement(type); 140 switch (i) { 141 case 0: 142 if (!isFirstRestrictedParameter(element)) { 143 return false; 144 } 145 break; 146 case 1: 147 if (!isSecondRestrictedParameter(element)) { 148 return false; 149 } 150 break; 151 case 2: 152 if (!isThirdRestrictedParameter(type)) { 153 return false; 154 } 155 break; 156 case 3: 157 if (!isFourthRestrictedParameter(type)) { 158 return false; 159 } 160 break; 161 default: 162 return false; 163 } 164 } 165 166 return true; 167 } 168 isFourthRestrictedParameter(TypeMirror type)169 private static boolean isFourthRestrictedParameter(TypeMirror type) { 170 return type.getKind().isPrimitive() 171 && Processors.getPrimitiveType(type).getKind() == TypeKind.INT; 172 } 173 isThirdRestrictedParameter(TypeMirror type)174 private static boolean isThirdRestrictedParameter(TypeMirror type) { 175 return type.getKind().isPrimitive() 176 && Processors.getPrimitiveType(type).getKind() == TypeKind.INT; 177 } 178 isSecondRestrictedParameter(Element element)179 private static boolean isSecondRestrictedParameter(Element element) { 180 return element instanceof TypeElement 181 && Processors.isAssignableFrom( 182 MoreElements.asType(element), AndroidClassNames.ATTRIBUTE_SET); 183 } 184 isFirstRestrictedParameter(Element element)185 private static boolean isFirstRestrictedParameter(Element element) { 186 return element instanceof TypeElement 187 && Processors.isAssignableFrom(MoreElements.asType(element), AndroidClassNames.CONTEXT); 188 } 189 isInOurPackage(ExecutableElement constructorElement)190 private boolean isInOurPackage(ExecutableElement constructorElement) { 191 return MoreElements.getPackage(constructorElement) 192 .equals(MoreElements.getPackage(metadata.element())); 193 } 194 } 195