1 /* 2 * Copyright (C) 2020 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.bindvalue; 18 19 import static com.google.common.collect.Iterables.getOnlyElement; 20 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList; 21 import static dagger.internal.codegen.xprocessing.XElements.asField; 22 23 import androidx.room.compiler.processing.XAnnotation; 24 import androidx.room.compiler.processing.XElement; 25 import androidx.room.compiler.processing.XElementKt; 26 import androidx.room.compiler.processing.XFieldElement; 27 import androidx.room.compiler.processing.XMethodElement; 28 import androidx.room.compiler.processing.XTypeElement; 29 import com.google.auto.value.AutoValue; 30 import com.google.common.collect.ImmutableList; 31 import com.google.common.collect.ImmutableSet; 32 import com.squareup.javapoet.ClassName; 33 import dagger.hilt.processor.internal.ClassNames; 34 import dagger.hilt.processor.internal.ProcessorErrors; 35 import dagger.hilt.processor.internal.Processors; 36 import dagger.internal.codegen.xprocessing.XAnnotations; 37 import dagger.internal.codegen.xprocessing.XElements; 38 import java.util.Collection; 39 import java.util.Optional; 40 41 /** 42 * Represents metadata for a test class that has {@code BindValue} fields. 43 */ 44 @AutoValue 45 abstract class BindValueMetadata { 46 static final ImmutableSet<ClassName> BIND_VALUE_ANNOTATIONS = 47 ImmutableSet.of( 48 ClassNames.ANDROID_BIND_VALUE); 49 static final ImmutableSet<ClassName> BIND_VALUE_INTO_SET_ANNOTATIONS = 50 ImmutableSet.of( 51 ClassNames.ANDROID_BIND_VALUE_INTO_SET); 52 static final ImmutableSet<ClassName> BIND_ELEMENTS_INTO_SET_ANNOTATIONS = 53 ImmutableSet.of( 54 ClassNames.ANDROID_BIND_ELEMENTS_INTO_SET); 55 static final ImmutableSet<ClassName> BIND_VALUE_INTO_MAP_ANNOTATIONS = 56 ImmutableSet.of( 57 ClassNames.ANDROID_BIND_VALUE_INTO_MAP); 58 59 /** 60 * @return the {@code TestRoot} annotated class's name. 61 */ testElement()62 abstract XTypeElement testElement(); 63 64 /** @return a {@link ImmutableSet} of elements annotated with @BindValue. */ bindValueElements()65 abstract ImmutableSet<BindValueElement> bindValueElements(); 66 67 /** 68 * @return a new BindValueMetadata instance. 69 */ create( XTypeElement testElement, Collection<XElement> bindValueElements)70 static BindValueMetadata create( 71 XTypeElement testElement, Collection<XElement> bindValueElements) { 72 73 ImmutableSet.Builder<BindValueElement> elements = ImmutableSet.builder(); 74 for (XElement element : bindValueElements) { 75 elements.add(BindValueElement.create(element)); 76 } 77 78 return new AutoValue_BindValueMetadata(testElement, elements.build()); 79 } 80 81 @AutoValue 82 abstract static class BindValueElement { fieldElement()83 abstract XFieldElement fieldElement(); 84 annotationName()85 abstract ClassName annotationName(); 86 qualifier()87 abstract Optional<XAnnotation> qualifier(); 88 mapKey()89 abstract Optional<XAnnotation> mapKey(); 90 getterElement()91 abstract Optional<XMethodElement> getterElement(); 92 create(XElement element)93 static BindValueElement create(XElement element) { 94 ImmutableList<ClassName> bindValues = 95 BindValueProcessingStep.getBindValueAnnotations(element); 96 ProcessorErrors.checkState( 97 bindValues.size() == 1, 98 element, 99 "Fields can be annotated with only one of @BindValue, @BindValueIntoMap," 100 + " @BindElementsIntoSet, @BindValueIntoSet. Found: %s", 101 bindValues.stream().map(m -> "@" + m.simpleName()).collect(toImmutableList())); 102 ClassName annotationClassName = getOnlyElement(bindValues); 103 104 ProcessorErrors.checkState( 105 XElementKt.isField(element), 106 element, 107 "@%s can only be used with fields. Found: %s", 108 annotationClassName.simpleName(), 109 XElements.toStableString(element)); 110 111 XFieldElement field = asField(element); 112 Optional<XMethodElement> propertyGetter = Optional.ofNullable(field.getGetter()); 113 if (propertyGetter.isPresent()) { 114 ProcessorErrors.checkState( 115 !propertyGetter.get().isPrivate(), 116 field, 117 "@%s field getter cannot be private. Found: %s", 118 annotationClassName.simpleName(), 119 XElements.toStableString(field)); 120 } else { 121 ProcessorErrors.checkState( 122 !XElements.isPrivate(field), 123 field, 124 "@%s fields cannot be private. Found: %s", 125 annotationClassName.simpleName(), 126 XElements.toStableString(field)); 127 } 128 129 ProcessorErrors.checkState( 130 !field.hasAnnotation(ClassNames.INJECT), 131 field, 132 "@%s fields cannot be used with @Inject annotation. Found %s", 133 annotationClassName.simpleName(), 134 XElements.toStableString(field)); 135 136 ImmutableList<XAnnotation> qualifiers = Processors.getQualifierAnnotations(field); 137 ProcessorErrors.checkState( 138 qualifiers.size() <= 1, 139 field, 140 "@%s fields cannot have more than one qualifier. Found %s", 141 annotationClassName.simpleName(), 142 qualifiers.stream().map(XAnnotations::toStableString).collect(toImmutableList())); 143 144 ImmutableList<XAnnotation> mapKeys = Processors.getMapKeyAnnotations(field); 145 Optional<XAnnotation> optionalMapKeys; 146 if (BIND_VALUE_INTO_MAP_ANNOTATIONS.contains(annotationClassName)) { 147 ProcessorErrors.checkState( 148 mapKeys.size() == 1, 149 field, 150 "@BindValueIntoMap fields must have exactly one @MapKey. Found %s", 151 mapKeys.stream().map(XAnnotations::toStableString).collect(toImmutableList())); 152 optionalMapKeys = Optional.of(mapKeys.get(0)); 153 } else { 154 ProcessorErrors.checkState( 155 mapKeys.isEmpty(), 156 field, 157 "@MapKey can only be used on @BindValueIntoMap fields, not @%s fields", 158 annotationClassName.simpleName()); 159 optionalMapKeys = Optional.empty(); 160 } 161 162 ImmutableList<XAnnotation> scopes = Processors.getScopeAnnotations(field); 163 ProcessorErrors.checkState( 164 scopes.isEmpty(), 165 field, 166 "@%s fields cannot be scoped. Found %s", 167 annotationClassName.simpleName(), 168 scopes.stream().map(XAnnotations::toStableString).collect(toImmutableList())); 169 170 return new AutoValue_BindValueMetadata_BindValueElement( 171 field, 172 annotationClassName, 173 qualifiers.isEmpty() 174 ? Optional.<XAnnotation>empty() 175 : Optional.<XAnnotation>of(qualifiers.get(0)), 176 optionalMapKeys, 177 propertyGetter); 178 } 179 } 180 } 181