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 androidx.room.compiler.processing.XElementKt.isField; 20 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 21 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 22 import static dagger.internal.codegen.xprocessing.XElements.asField; 23 import static dagger.internal.codegen.xprocessing.XElements.isStatic; 24 25 import androidx.room.compiler.processing.XAnnotation; 26 import androidx.room.compiler.processing.XElement; 27 import androidx.room.compiler.processing.XFieldElement; 28 import androidx.room.compiler.processing.XMethodElement; 29 import androidx.room.compiler.processing.XTypeElement; 30 import com.google.common.base.Equivalence; 31 import com.google.common.collect.ImmutableList; 32 import com.google.common.collect.ImmutableSet; 33 import com.squareup.javapoet.ClassName; 34 import dagger.hilt.processor.internal.ClassNames; 35 import dagger.internal.codegen.xprocessing.XAnnotations; 36 import dagger.internal.codegen.xprocessing.XElements; 37 import javax.inject.Inject; 38 39 /** Utility class for interacting with Kotlin Metadata. */ 40 public final class KotlinMetadataUtil { 41 42 private final KotlinMetadataFactory metadataFactory; 43 44 @Inject KotlinMetadataUtil(KotlinMetadataFactory metadataFactory)45 KotlinMetadataUtil(KotlinMetadataFactory metadataFactory) { 46 this.metadataFactory = metadataFactory; 47 } 48 49 /** 50 * Returns {@code true} if this element has the Kotlin Metadata annotation or if it is enclosed in 51 * an element that does. 52 */ hasMetadata(XElement element)53 public boolean hasMetadata(XElement element) { 54 return XElements.closestEnclosingTypeElement(element).hasAnnotation(ClassNames.KOTLIN_METADATA); 55 } 56 57 // TODO(kuanyingchou): Consider replacing it with `XAnnotated.getAnnotationsAnnotatedWith()` 58 // once b/278077018 is resolved. 59 /** 60 * Returns the annotations on the given {@code element} annotated with {@code annotationName}. 61 * 62 * <p>Note: If the given {@code element} is a non-static field this method will return annotations 63 * on both the backing field and the associated synthetic property (if one exists). 64 */ getAnnotationsAnnotatedWith( XElement element, ClassName annotationName)65 public ImmutableList<XAnnotation> getAnnotationsAnnotatedWith( 66 XElement element, ClassName annotationName) { 67 return getAnnotations(element).stream() 68 .filter(annotation -> annotation.getTypeElement().hasAnnotation(annotationName)) 69 .collect(toImmutableList()); 70 } 71 72 /** 73 * Returns the annotations on the given {@code element} that match the {@code annotationName}. 74 * 75 * <p>Note: If the given {@code element} is a non-static field this method will return annotations 76 * on both the backing field and the associated synthetic property (if one exists). 77 */ getAnnotations(XElement element)78 private ImmutableList<XAnnotation> getAnnotations(XElement element) { 79 ImmutableList<XAnnotation> annotations = ImmutableList.copyOf(element.getAllAnnotations()); 80 ImmutableList<XAnnotation> syntheticAnnotations = getSyntheticPropertyAnnotations(element); 81 if (syntheticAnnotations.isEmpty()) { 82 return annotations; 83 } 84 // Dedupe any annotation that appears on both the field and the property. 85 // Note: we reduce the number of annotations we have to dedupe by only checking equivalence on 86 // annotations that have the same class name as a synthetic annotation. This avoids hitting 87 // TypeNotPresentException on annotation values with error types unless it has the same class 88 // name as a synthetic annotation. 89 ImmutableSet<ClassName> syntheticAnnotationClassNames = 90 syntheticAnnotations.stream() 91 .map(XAnnotations::getClassName) 92 .collect(toImmutableSet()); 93 ImmutableSet<Equivalence.Wrapper<XAnnotation>> annotationEquivalenceWrappers = 94 annotations.stream() 95 .filter(annotation -> syntheticAnnotationClassNames.contains(annotation.getClassName())) 96 .map(XAnnotations.equivalence()::wrap) 97 .collect(toImmutableSet()); 98 ImmutableList<XAnnotation> uniqueSyntheticAnnotations = 99 syntheticAnnotations.stream() 100 .map(XAnnotations.equivalence()::wrap) 101 .filter(wrapper -> !annotationEquivalenceWrappers.contains(wrapper)) 102 .map(Equivalence.Wrapper::get) 103 .collect(toImmutableList()); 104 return uniqueSyntheticAnnotations.isEmpty() 105 ? annotations 106 : ImmutableList.<XAnnotation>builder() 107 .addAll(annotations) 108 .addAll(uniqueSyntheticAnnotations) 109 .build(); 110 } 111 112 /** 113 * Returns the synthetic annotations of a Kotlin property. 114 * 115 * <p>Note that this method only looks for additional annotations in the synthetic property 116 * method, if any, of a Kotlin property and not for annotations in its backing field. 117 */ getSyntheticPropertyAnnotations(XElement element)118 private ImmutableList<XAnnotation> getSyntheticPropertyAnnotations(XElement element) { 119 // Currently, we avoid trying to get annotations from properties on object class's (i.e. 120 // properties with static jvm backing fields) due to issues explained in CL/336150864. 121 if (!isField(element) || isStatic(element)) { 122 return ImmutableList.of(); 123 } 124 XFieldElement field = asField(element); 125 return hasMetadata(field) 126 ? metadataFactory 127 .create(field) 128 .getSyntheticAnnotationMethod(field) 129 .map(XMethodElement::getAllAnnotations) 130 .map(ImmutableList::copyOf) 131 .orElse(ImmutableList.<XAnnotation>of()) 132 : ImmutableList.of(); 133 } 134 containsConstructorWithDefaultParam(XTypeElement typeElement)135 public boolean containsConstructorWithDefaultParam(XTypeElement typeElement) { 136 return hasMetadata(typeElement) 137 && metadataFactory.create(typeElement).containsConstructorWithDefaultParam(); 138 } 139 } 140