• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.google.common.collect.Iterables.getOnlyElement;
20 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
21 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
22 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
23 
24 import androidx.room.compiler.processing.JavaPoetExtKt;
25 import androidx.room.compiler.processing.XFiler;
26 import androidx.room.compiler.processing.XMethodElement;
27 import androidx.room.compiler.processing.XProcessingEnv;
28 import androidx.room.compiler.processing.XType;
29 import androidx.room.compiler.processing.XTypeElement;
30 import androidx.room.compiler.processing.XTypeParameterElement;
31 import com.google.common.collect.ImmutableSet;
32 import com.squareup.javapoet.ClassName;
33 import com.squareup.javapoet.JavaFile;
34 import com.squareup.javapoet.MethodSpec;
35 import com.squareup.javapoet.ParameterSpec;
36 import com.squareup.javapoet.TypeSpec;
37 import dagger.hilt.android.processor.internal.AndroidClassNames;
38 import dagger.hilt.processor.internal.ClassNames;
39 import dagger.hilt.processor.internal.Processors;
40 import dagger.internal.codegen.xprocessing.XExecutableTypes;
41 import java.io.IOException;
42 import java.util.Optional;
43 import javax.lang.model.element.Modifier;
44 
45 /** Generates an Hilt BroadcastReceiver class for the @AndroidEntryPoint annotated class. */
46 public final class BroadcastReceiverGenerator {
47   private static final String ON_RECEIVE_DESCRIPTOR =
48       "onReceive(Landroid/content/Context;Landroid/content/Intent;)V";
49 
50   private final XProcessingEnv env;
51   private final AndroidEntryPointMetadata metadata;
52   private final ClassName generatedClassName;
53 
BroadcastReceiverGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata)54   public BroadcastReceiverGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata) {
55     this.env = env;
56     this.metadata = metadata;
57     generatedClassName = metadata.generatedClassName();
58   }
59 
60   // @Generated("BroadcastReceiverGenerator")
61   // abstract class Hilt_$CLASS extends $BASE {
62   //   ...
63   // }
generate()64   public void generate() throws IOException {
65     TypeSpec.Builder builder =
66         TypeSpec.classBuilder(generatedClassName.simpleName())
67             .superclass(metadata.baseClassName())
68             .addModifiers(metadata.generatedClassModifiers())
69             .addMethod(onReceiveMethod());
70 
71     // Add an annotation used as a marker to let the bytecode injector know this receiver
72     // will need to be injected with a super.onReceive call. This is only necessary if no concrete
73     // onReceive call is implemented in any of the super classes.
74     if (metadata.requiresBytecodeInjection() && !isOnReceiveImplemented(metadata.baseElement())) {
75       builder.addAnnotation(ClassNames.ON_RECEIVE_BYTECODE_INJECTION_MARKER);
76     }
77 
78     JavaPoetExtKt.addOriginatingElement(builder, metadata.element());
79     Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT);
80     Processors.addGeneratedAnnotation(builder, env, getClass());
81     Generators.copyConstructors(metadata.baseElement(), builder, metadata.element());
82 
83     metadata.baseElement().getTypeParameters().stream()
84         .map(XTypeParameterElement::getTypeVariableName)
85         .forEachOrdered(builder::addTypeVariable);
86 
87     Generators.addInjectionMethods(metadata, builder);
88     Generators.copyLintAnnotations(metadata.element(), builder);
89     Generators.copySuppressAnnotations(metadata.element(), builder);
90 
91     env.getFiler()
92         .write(
93             JavaFile.builder(generatedClassName.packageName(), builder.build()).build(),
94             XFiler.Mode.Isolating);
95   }
96 
isOnReceiveImplemented(XTypeElement typeElement)97   private static boolean isOnReceiveImplemented(XTypeElement typeElement) {
98     boolean isImplemented =
99         typeElement.getDeclaredMethods().stream()
100             .filter(method -> !method.isAbstract())
101             .anyMatch(method -> method.getJvmDescriptor().equals(ON_RECEIVE_DESCRIPTOR));
102     if (isImplemented) {
103       return true;
104     } else if (typeElement.getSuperClass() != null) {
105       return isOnReceiveImplemented(typeElement.getSuperClass().getTypeElement());
106     } else {
107       return false;
108     }
109   }
110 
111   // @Override
112   // public void onReceive(Context context, Intent intent) {
113   //   inject(context);
114   //   super.onReceive();
115   // }
onReceiveMethod()116   private MethodSpec onReceiveMethod() throws IOException {
117     MethodSpec.Builder method =
118         MethodSpec.methodBuilder("onReceive")
119             .addAnnotation(Override.class)
120             .addAnnotation(AndroidClassNames.CALL_SUPER)
121             .addModifiers(Modifier.PUBLIC)
122             .addParameter(ParameterSpec.builder(AndroidClassNames.CONTEXT, "context").build())
123             .addParameter(ParameterSpec.builder(AndroidClassNames.INTENT, "intent").build())
124             .addStatement("inject(context)");
125 
126     if (metadata.overridesAndroidEntryPointClass()) {
127       // We directly call super.onReceive here because we know Hilt base classes have a
128       // non-abstract onReceive method. However, because the Hilt base class may not be generated
129       // already we cannot fall down to the below logic to find it.
130       method.addStatement("super.onReceive(context, intent)");
131     } else {
132       // Get the onReceive method element from BroadcastReceiver.
133       XMethodElement onReceiveMethod =
134           getOnlyElement(
135               findMethodsByName(
136                   env.requireTypeElement(AndroidClassNames.BROADCAST_RECEIVER), "onReceive"));
137 
138       // If the base class or one of its super classes implements onReceive, call super.onReceive()
139       findMethodBySubsignature(metadata.baseElement(), onReceiveMethod)
140           .filter(onReceive -> !onReceive.isAbstract())
141           .ifPresent(onReceive -> method.addStatement("super.onReceive(context, intent)"));
142     }
143     return method.build();
144   }
145 
findMethodBySubsignature( XTypeElement typeElement, XMethodElement method)146   private Optional<XMethodElement> findMethodBySubsignature(
147       XTypeElement typeElement, XMethodElement method) {
148     String methodName = getSimpleName(method);
149     XType currentType = typeElement.getType();
150     Optional<XMethodElement> match = Optional.empty();
151     while (!match.isPresent() && currentType != null) {
152       match =
153           findMethodsByName(currentType.getTypeElement(), methodName).stream()
154               .filter(m -> XExecutableTypes.isSubsignature(m, method))
155               .collect(toOptional());
156       currentType = currentType.getTypeElement().getSuperClass();
157     }
158     return match;
159   }
160 
findMethodsByName( XTypeElement typeElement, String name)161   private static ImmutableSet<XMethodElement> findMethodsByName(
162       XTypeElement typeElement, String name) {
163     return typeElement.getDeclaredMethods().stream()
164         .filter(m -> getSimpleName(m).contentEquals(name))
165         .collect(toImmutableSet());
166   }
167 }
168