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