• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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