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