• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.processor.internal.kotlin;
18 
19 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableMap;
20 
21 import androidx.room.compiler.processing.XFieldElement;
22 import androidx.room.compiler.processing.XMethodElement;
23 import androidx.room.compiler.processing.XTypeElement;
24 import com.google.auto.value.AutoValue;
25 import com.google.auto.value.extension.memoized.Memoized;
26 import com.google.common.collect.ImmutableMap;
27 import dagger.internal.codegen.extension.DaggerCollectors;
28 import dagger.internal.codegen.xprocessing.XElements;
29 import java.util.HashMap;
30 import java.util.Map;
31 import java.util.Optional;
32 import java.util.function.Function;
33 import javax.annotation.Nullable;
34 
35 /** Data class of a TypeElement and its Kotlin metadata. */
36 @AutoValue
37 abstract class KotlinMetadata {
38   // Kotlin suffix for fields that are for a delegated property.
39   // See:
40   // https://github.com/JetBrains/kotlin/blob/master/core/compiler.common.jvm/src/org/jetbrains/kotlin/load/java/JvmAbi.kt#L32
41   private static final String DELEGATED_PROPERTY_NAME_SUFFIX = "$delegate";
42 
43   // Map that associates field elements with its Kotlin synthetic method for annotations.
44   private final Map<XFieldElement, Optional<MethodForAnnotations>> elementFieldAnnotationMethodMap =
45       new HashMap<>();
46 
47   // Map that associates field elements with its Kotlin getter method.
48   private final Map<XFieldElement, Optional<XMethodElement>> elementFieldGetterMethodMap =
49       new HashMap<>();
50 
typeElement()51   abstract XTypeElement typeElement();
52 
classMetadata()53   abstract ClassMetadata classMetadata();
54 
55   @Memoized
methodDescriptors()56   ImmutableMap<String, XMethodElement> methodDescriptors() {
57     return typeElement().getDeclaredMethods().stream()
58         .collect(toImmutableMap(XMethodElement::getJvmDescriptor, Function.identity()));
59   }
60 
61   /** Returns true if any constructor of the defined a default parameter. */
62   @Memoized
containsConstructorWithDefaultParam()63   boolean containsConstructorWithDefaultParam() {
64     return classMetadata().constructors().stream()
65         .flatMap(constructor -> constructor.getParameters().stream())
66         .anyMatch(parameter -> parameter.declaresDefaultValue());
67   }
68 
69   /** Gets the synthetic method for annotations of a given field element. */
getSyntheticAnnotationMethod(XFieldElement fieldElement)70   Optional<XMethodElement> getSyntheticAnnotationMethod(XFieldElement fieldElement) {
71     return getAnnotationMethod(fieldElement)
72         .map(
73             methodForAnnotations -> {
74               if (methodForAnnotations == MethodForAnnotations.MISSING) {
75                 throw new IllegalStateException(
76                     "Method for annotations is missing for " + fieldElement);
77               }
78               return XElements.asMethod(methodForAnnotations.method());
79             });
80   }
81 
82   private Optional<MethodForAnnotations> getAnnotationMethod(XFieldElement fieldElement) {
83     return elementFieldAnnotationMethodMap.computeIfAbsent(
84         fieldElement, this::getAnnotationMethodUncached);
85   }
86 
87   private Optional<MethodForAnnotations> getAnnotationMethodUncached(XFieldElement fieldElement) {
88     return Optional.ofNullable(findProperty(fieldElement).getMethodForAnnotationsSignature())
89         .map(
90             signature ->
91                 Optional.ofNullable(methodDescriptors().get(signature))
92                     .map(MethodForAnnotations::create)
93                     // The method may be missing across different compilations.
94                     // See https://youtrack.jetbrains.com/issue/KT-34684
95                     .orElse(MethodForAnnotations.MISSING));
96   }
97 
98   /** Gets the getter method of a given field element corresponding to a property. */
99   Optional<XMethodElement> getPropertyGetter(XFieldElement fieldElement) {
100     return elementFieldGetterMethodMap.computeIfAbsent(
101         fieldElement, this::getPropertyGetterUncached);
102   }
103 
104   private Optional<XMethodElement> getPropertyGetterUncached(XFieldElement fieldElement) {
105     return Optional.ofNullable(findProperty(fieldElement).getGetterSignature())
106         .flatMap(signature -> Optional.ofNullable(methodDescriptors().get(signature)));
107   }
108 
109   private PropertyMetadata findProperty(XFieldElement field) {
110     String fieldDescriptor = field.getJvmDescriptor();
111     if (classMetadata().getPropertiesBySignature().containsKey(fieldDescriptor)) {
112       return classMetadata().getPropertiesBySignature().get(fieldDescriptor);
113     } else {
114       // Fallback to finding property by name, see: https://youtrack.jetbrains.com/issue/KT-35124
115       final String propertyName = getPropertyNameFromField(field);
116       return classMetadata().getPropertiesBySignature().values().stream()
117           .filter(property -> propertyName.contentEquals(property.getName())) // SUPPRESS_GET_NAME_CHECK
118           .collect(DaggerCollectors.onlyElement());
119     }
120   }
121 
122   private static String getPropertyNameFromField(XFieldElement field) {
123     String name = XElements.getSimpleName(field);
124     if (name.endsWith(DELEGATED_PROPERTY_NAME_SUFFIX)) {
125       return name.substring(0, name.length() - DELEGATED_PROPERTY_NAME_SUFFIX.length());
126     } else {
127       return name;
128     }
129   }
130 
131   /** Parse Kotlin class metadata from a given type element. */
132   static KotlinMetadata from(XTypeElement typeElement) {
133     return new AutoValue_KotlinMetadata(typeElement, ClassMetadata.of(typeElement));
134   }
135 
136   @AutoValue
137   abstract static class MethodForAnnotations {
138     static MethodForAnnotations create(XMethodElement method) {
139       return new AutoValue_KotlinMetadata_MethodForAnnotations(method);
140     }
141 
142     static final MethodForAnnotations MISSING = MethodForAnnotations.create(null);
143 
144     @Nullable
145     abstract XMethodElement method();
146   }
147 }
148