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