1 /*
2  * Copyright 2021 Google LLC
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  *   https://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 package com.google.android.enterprise.connectedapps.processor.annotationdiscovery;
17 
18 import static java.util.stream.Collectors.toSet;
19 import static javax.lang.model.element.ElementKind.METHOD;
20 
21 import com.google.android.enterprise.connectedapps.annotations.CrossProfile;
22 import com.google.android.enterprise.connectedapps.annotations.CrossProfileCallback;
23 import com.google.android.enterprise.connectedapps.annotations.CrossProfileConfiguration;
24 import com.google.android.enterprise.connectedapps.annotations.CrossProfileConfigurations;
25 import com.google.android.enterprise.connectedapps.annotations.CrossProfileProvider;
26 import com.google.android.enterprise.connectedapps.annotations.CrossUser;
27 import com.google.android.enterprise.connectedapps.annotations.CrossUserCallback;
28 import com.google.android.enterprise.connectedapps.annotations.CrossUserConfiguration;
29 import com.google.android.enterprise.connectedapps.annotations.CrossUserConfigurations;
30 import com.google.android.enterprise.connectedapps.annotations.CrossUserProvider;
31 import com.google.android.enterprise.connectedapps.processor.ValidationMessageFormatter;
32 import com.google.android.enterprise.connectedapps.processor.containers.Context;
33 import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileAnnotationInfo;
34 import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileCallbackAnnotationInfo;
35 import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileConfigurationAnnotationInfo;
36 import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileConfigurationsAnnotationInfo;
37 import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileProviderAnnotationInfo;
38 import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileTestAnnotationInfo;
39 import com.google.android.enterprise.connectedapps.testing.annotations.CrossProfileTest;
40 import com.google.android.enterprise.connectedapps.testing.annotations.CrossUserTest;
41 import com.google.common.collect.ImmutableList;
42 import java.lang.annotation.Annotation;
43 import java.util.Set;
44 import java.util.function.Function;
45 import java.util.stream.Stream;
46 import javax.annotation.processing.RoundEnvironment;
47 import javax.lang.model.element.Element;
48 import javax.lang.model.element.ExecutableElement;
49 import javax.lang.model.element.TypeElement;
50 
51 /** Helper methods to discover all cross-profile annotations of a specific type on elements. */
52 public final class AnnotationFinder {
53 
54   private static final AnnotationStrings CROSS_PROFILE_ANNOTATION_STRINGS =
55       AnnotationStrings.builder()
56           .setCrossProfileAnnotationClass(CrossProfile.class)
57           .setCrossProfileCallbackAnnotationClass(CrossProfileCallback.class)
58           .setCrossProfileConfigurationAnnotationClass(CrossProfileConfiguration.class)
59           .setCrossProfileConfigurationsAnnotationClass(CrossProfileConfigurations.class)
60           .setCrossProfileProviderAnnotationClass(CrossProfileProvider.class)
61           .setCrossProfileTestAnnotationClass(CrossProfileTest.class)
62           .build();
63 
64   private static final AnnotationStrings CROSS_USER_ANNOTATION_STRINGS =
65       AnnotationStrings.builder()
66           .setCrossProfileAnnotationClass(CrossUser.class)
67           .setCrossProfileCallbackAnnotationClass(CrossUserCallback.class)
68           .setCrossProfileProviderAnnotationClass(CrossUserProvider.class)
69           .setCrossProfileConfigurationAnnotationClass(CrossUserConfiguration.class)
70           .setCrossProfileConfigurationsAnnotationClass(CrossUserConfigurations.class)
71           .setCrossProfileTestAnnotationClass(CrossUserTest.class)
72           .build();
73 
74   private static final ImmutableList<AnnotationStrings> SUPPORTED_ANNOTATIONS =
75       ImmutableList.of(CROSS_PROFILE_ANNOTATION_STRINGS, CROSS_USER_ANNOTATION_STRINGS);
76 
77   private static final AnnotationStrings DEFAULT_ANNOTATIONS = CROSS_PROFILE_ANNOTATION_STRINGS;
78 
79   private static final Set<Class<? extends Annotation>> crossProfileAnnotations =
80       annotationsOfType(AnnotationClasses::crossProfileAnnotationClass);
81 
82   private static final Set<Class<? extends Annotation>> crossProfileCallbackAnnotations =
83       annotationsOfType(AnnotationClasses::crossProfileCallbackAnnotationClass);
84 
85   private static final Set<Class<? extends Annotation>> crossProfileConfigurationAnnotations =
86       annotationsOfType(AnnotationClasses::crossProfileConfigurationAnnotationClass);
87 
88   private static final Set<Class<? extends Annotation>> crossProfileConfigurationsAnnotations =
89       annotationsOfType(AnnotationClasses::crossProfileConfigurationsAnnotationClass);
90 
91   private static final Set<Class<? extends Annotation>> crossProfileProviderAnnotations =
92       annotationsOfType(AnnotationClasses::crossProfileProviderAnnotationClass);
93 
94   private static final Set<Class<? extends Annotation>> crossProfileTestAnnotations =
95       annotationsOfType(AnnotationClasses::crossProfileTestAnnotationClass);
96 
annotationStrings()97   public static Iterable<AnnotationStrings> annotationStrings() {
98     return SUPPORTED_ANNOTATIONS;
99   }
100 
crossProfileAnnotationNames()101   public static AnnotationNames crossProfileAnnotationNames() {
102     return CROSS_PROFILE_ANNOTATION_STRINGS;
103   }
104 
crossUserAnnotationNames()105   public static AnnotationNames crossUserAnnotationNames() {
106     return CROSS_USER_ANNOTATION_STRINGS;
107   }
108 
validationMessageFormatterFor(Element element)109   public static ValidationMessageFormatter validationMessageFormatterFor(Element element) {
110     return ValidationMessageFormatter.forAnnotations(annotationNamesFor(element));
111   }
112 
annotationNamesFor(Element element)113   private static AnnotationNames annotationNamesFor(Element element) {
114     for (AnnotationStrings annotationStrings : SUPPORTED_ANNOTATIONS) {
115       if (hasAnyAnnotationsOfClass(element, annotationStrings)) {
116         return annotationStrings;
117       }
118     }
119 
120     return DEFAULT_ANNOTATIONS;
121   }
122 
validationMessageFormatterForClass( TypeElement typeElement)123   public static ValidationMessageFormatter validationMessageFormatterForClass(
124       TypeElement typeElement) {
125     return ValidationMessageFormatter.forAnnotations(annotationNamesForClass(typeElement));
126   }
127 
annotationNamesForClass(TypeElement typeElement)128   public static AnnotationNames annotationNamesForClass(TypeElement typeElement) {
129     for (AnnotationStrings annotationStrings : SUPPORTED_ANNOTATIONS) {
130       if (hasAnyAnnotationsOfClass(typeElement, annotationStrings)) {
131         return annotationStrings;
132       }
133 
134       for (ExecutableElement method :
135           typeElement.getEnclosedElements().stream()
136               .filter(element -> element.getKind() == METHOD)
137               .map(element -> (ExecutableElement) element)
138               .collect(toSet())) {
139         if (hasAnyAnnotationsOfClass(method, annotationStrings)) {
140           return annotationStrings;
141         }
142       }
143     }
144 
145     return DEFAULT_ANNOTATIONS;
146   }
147 
hasAnyAnnotationsOfClass( Element element, AnnotationClasses annotationClasses)148   private static boolean hasAnyAnnotationsOfClass(
149       Element element, AnnotationClasses annotationClasses) {
150     return hasAnnotationOfClass(element, annotationClasses.crossProfileAnnotationClass())
151         || hasAnnotationOfClass(element, annotationClasses.crossProfileCallbackAnnotationClass())
152         || hasAnnotationOfClass(element, annotationClasses.crossProfileProviderAnnotationClass())
153         || hasAnnotationOfClass(
154             element, annotationClasses.crossProfileConfigurationAnnotationClass())
155         || hasAnnotationOfClass(
156             element, annotationClasses.crossProfileConfigurationsAnnotationClass())
157         || hasAnnotationOfClass(element, annotationClasses.crossProfileTestAnnotationClass());
158   }
159 
hasAnnotationOfClass( Element element, Class<? extends Annotation> annotationClass)160   private static boolean hasAnnotationOfClass(
161       Element element, Class<? extends Annotation> annotationClass) {
162     return element.getAnnotation(annotationClass) != null;
163   }
164 
extractCrossProfileAnnotationInfo( Context context, Element annotatedElement)165   public static CrossProfileAnnotationInfo extractCrossProfileAnnotationInfo(
166       Context context, Element annotatedElement) {
167     return new CrossProfileAnnotationInfoExtractor()
168         .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, context, annotatedElement);
169   }
170 
extractCrossProfileCallbackAnnotationInfo( Context context, Element annotatedElement)171   public static CrossProfileCallbackAnnotationInfo extractCrossProfileCallbackAnnotationInfo(
172       Context context, Element annotatedElement) {
173     return new CrossProfileCallbackAnnotationInfoExtractor()
174         .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, context, annotatedElement);
175   }
176 
177   public static CrossProfileConfigurationAnnotationInfo
extractCrossProfileConfigurationAnnotationInfo(Context context, Element annotatedElement)178       extractCrossProfileConfigurationAnnotationInfo(Context context, Element annotatedElement) {
179     return new CrossProfileConfigurationAnnotationInfoExtractor()
180         .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, context, annotatedElement);
181   }
182 
183   public static CrossProfileConfigurationsAnnotationInfo
extractCrossProfileConfigurationsAnnotationInfo(Context context, Element annotatedElement)184       extractCrossProfileConfigurationsAnnotationInfo(Context context, Element annotatedElement) {
185     return new CrossProfileConfigurationsAnnotationInfoExtractor()
186         .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, context, annotatedElement);
187   }
188 
extractCrossProfileProviderAnnotationInfo( Context context, Element annotatedElement)189   public static CrossProfileProviderAnnotationInfo extractCrossProfileProviderAnnotationInfo(
190       Context context, Element annotatedElement) {
191     return new CrossProfileProviderAnnotationInfoExtractor()
192         .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, context, annotatedElement);
193   }
194 
extractCrossProfileTestAnnotationInfo( Context context, Element annotatedElement)195   public static CrossProfileTestAnnotationInfo extractCrossProfileTestAnnotationInfo(
196       Context context, Element annotatedElement) {
197     return new CrossProfileTestAnnotationInfoExtractor()
198         .extractAnnotationInfo(SUPPORTED_ANNOTATIONS, context, annotatedElement);
199   }
200 
hasCrossProfileAnnotation(Element element)201   public static boolean hasCrossProfileAnnotation(Element element) {
202     return hasAnyAnnotations(element, crossProfileAnnotations);
203   }
204 
hasCrossProfileCallbackAnnotation(Element element)205   public static boolean hasCrossProfileCallbackAnnotation(Element element) {
206     return hasAnyAnnotations(element, crossProfileCallbackAnnotations);
207   }
208 
hasCrossProfileConfigurationAnnotation(Element element)209   public static boolean hasCrossProfileConfigurationAnnotation(Element element) {
210     return hasAnyAnnotations(element, crossProfileConfigurationAnnotations);
211   }
212 
hasCrossProfileConfigurationsAnnotation(Element element)213   public static boolean hasCrossProfileConfigurationsAnnotation(Element element) {
214     return hasAnyAnnotations(element, crossProfileConfigurationsAnnotations);
215   }
216 
hasCrossProfileProviderAnnotation(Element element)217   public static boolean hasCrossProfileProviderAnnotation(Element element) {
218     return hasAnyAnnotations(element, crossProfileProviderAnnotations);
219   }
220 
hasAnyAnnotations( Element element, Set<Class<? extends Annotation>> annotations)221   private static boolean hasAnyAnnotations(
222       Element element, Set<Class<? extends Annotation>> annotations) {
223     return annotations.stream().anyMatch(annotation -> element.getAnnotation(annotation) != null);
224   }
225 
elementsAnnotatedWithCrossProfile( RoundEnvironment roundEnv)226   public static Stream<? extends Element> elementsAnnotatedWithCrossProfile(
227       RoundEnvironment roundEnv) {
228     return findElementsContainingAnnotations(roundEnv, crossProfileAnnotations);
229   }
230 
elementsAnnotatedWithCrossProfileCallback( RoundEnvironment roundEnv)231   public static Stream<? extends Element> elementsAnnotatedWithCrossProfileCallback(
232       RoundEnvironment roundEnv) {
233     return findElementsContainingAnnotations(roundEnv, crossProfileCallbackAnnotations);
234   }
235 
elementsAnnotatedWithCrossProfileConfiguration( RoundEnvironment roundEnv)236   public static Stream<? extends Element> elementsAnnotatedWithCrossProfileConfiguration(
237       RoundEnvironment roundEnv) {
238     return findElementsContainingAnnotations(roundEnv, crossProfileConfigurationAnnotations);
239   }
240 
elementsAnnotatedWithCrossProfileConfigurations( RoundEnvironment roundEnv)241   public static Stream<? extends Element> elementsAnnotatedWithCrossProfileConfigurations(
242       RoundEnvironment roundEnv) {
243     return findElementsContainingAnnotations(roundEnv, crossProfileConfigurationsAnnotations);
244   }
245 
elementsAnnotatedWithCrossProfileProvider( RoundEnvironment roundEnv)246   public static Stream<? extends Element> elementsAnnotatedWithCrossProfileProvider(
247       RoundEnvironment roundEnv) {
248     return findElementsContainingAnnotations(roundEnv, crossProfileProviderAnnotations);
249   }
250 
elementsAnnotatedWithCrossProfileTest( RoundEnvironment roundEnv)251   public static Stream<? extends Element> elementsAnnotatedWithCrossProfileTest(
252       RoundEnvironment roundEnv) {
253     return findElementsContainingAnnotations(roundEnv, crossProfileTestAnnotations);
254   }
255 
findElementsContainingAnnotations( RoundEnvironment roundEnv, Set<Class<? extends Annotation>> annotations)256   private static Stream<? extends Element> findElementsContainingAnnotations(
257       RoundEnvironment roundEnv, Set<Class<? extends Annotation>> annotations) {
258     return annotations.stream()
259         .flatMap(annotation -> roundEnv.getElementsAnnotatedWith(annotation).stream());
260   }
261 
annotationsOfType( Function<AnnotationClasses, Class<? extends Annotation>> annotationClassGetter)262   private static Set<Class<? extends Annotation>> annotationsOfType(
263       Function<AnnotationClasses, Class<? extends Annotation>> annotationClassGetter) {
264     return SUPPORTED_ANNOTATIONS.stream().map(annotationClassGetter).collect(toSet());
265   }
266 
AnnotationFinder()267   private AnnotationFinder() {}
268 }
269