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.containers;
17 
18 import static com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationFinder.hasCrossProfileCallbackAnnotation;
19 import static java.util.stream.Collectors.joining;
20 import static java.util.stream.Collectors.toCollection;
21 
22 import com.google.android.enterprise.connectedapps.annotations.Cacheable;
23 import com.google.android.enterprise.connectedapps.annotations.CrossProfile;
24 import com.google.android.enterprise.connectedapps.annotations.CrossProfileCallback;
25 import com.google.android.enterprise.connectedapps.processor.SupportedTypes;
26 import com.google.android.enterprise.connectedapps.processor.TypeUtils;
27 import com.google.auto.value.AutoValue;
28 import com.squareup.javapoet.ClassName;
29 import com.squareup.javapoet.TypeName;
30 import java.util.Collection;
31 import java.util.LinkedHashSet;
32 import java.util.Optional;
33 import java.util.function.Function;
34 import javax.lang.model.element.Element;
35 import javax.lang.model.element.ExecutableElement;
36 import javax.lang.model.element.Modifier;
37 import javax.lang.model.element.TypeElement;
38 import javax.lang.model.element.VariableElement;
39 import javax.lang.model.type.TypeMirror;
40 import javax.lang.model.util.Elements;
41 
42 /** Wrapper of a {@link CrossProfile} annotated method. */
43 @AutoValue
44 public abstract class CrossProfileMethodInfo {
45 
methodElement()46   public abstract ExecutableElement methodElement();
47 
identifier()48   public abstract int identifier();
49 
isStatic()50   public abstract boolean isStatic();
51 
isCacheable()52   public abstract boolean isCacheable();
53 
simpleName()54   public String simpleName() {
55     return methodElement().getSimpleName().toString();
56   }
57 
returnType()58   public TypeMirror returnType() {
59     return methodElement().getReturnType();
60   }
61 
returnTypeTypeName()62   public TypeName returnTypeTypeName() {
63     return ClassName.get(returnType());
64   }
65 
thrownExceptions()66   public Collection<TypeName> thrownExceptions() {
67     return methodElement().getThrownTypes().stream()
68         .map(ClassName::get)
69         .collect(toCollection(LinkedHashSet::new));
70   }
71 
automaticallyResolvedParameterTypes(SupportedTypes supportedTypes)72   public Collection<TypeMirror> automaticallyResolvedParameterTypes(SupportedTypes supportedTypes) {
73     return parameterTypes().stream()
74         .filter(supportedTypes::isAutomaticallyResolved)
75         .collect(toCollection(LinkedHashSet::new));
76   }
77 
78   /**
79    * Specify behaviour when encountering parameters of a type which is automatically resolved by the
80    * SDK.
81    */
82   public enum AutomaticallyResolvedParameterFilterBehaviour {
83     /** Do not change the parameters. */
84     LEAVE_AUTOMATICALLY_RESOLVED_PARAMETERS,
85     /** Remove the parameters and act as if they are not present. */
86     REMOVE_AUTOMATICALLY_RESOLVED_PARAMETERS,
87     /** Replace the parameter with the variable specified in the type configuration. */
88     REPLACE_AUTOMATICALLY_RESOLVED_PARAMETERS
89   }
90 
91   /**
92    * A string of parameter names separated by commas.
93    *
94    * <p>This is useful when generating a call for this method, using the same parameter names.
95    *
96    * <p>Parameters which are automatically resolved will be removed.
97    */
commaSeparatedParameters( SupportedTypes supportedTypes, AutomaticallyResolvedParameterFilterBehaviour filterBehaviour)98   public String commaSeparatedParameters(
99       SupportedTypes supportedTypes,
100       AutomaticallyResolvedParameterFilterBehaviour filterBehaviour) {
101     return commaSeparatedParameters(supportedTypes, filterBehaviour, Function.identity());
102   }
103 
104   /**
105    * A string of parameter names separated by commas.
106    *
107    * <p>This is useful when generating a call for this method, using the same parameter names.
108    *
109    * <p>Parameters which are automatically resolved will be removed.
110    */
commaSeparatedParameters( SupportedTypes supportedTypes, AutomaticallyResolvedParameterFilterBehaviour filterBehaviour, Function<String, String> map)111   public String commaSeparatedParameters(
112       SupportedTypes supportedTypes,
113       AutomaticallyResolvedParameterFilterBehaviour filterBehaviour,
114       Function<String, String> map) {
115     if (filterBehaviour
116         == AutomaticallyResolvedParameterFilterBehaviour.REMOVE_AUTOMATICALLY_RESOLVED_PARAMETERS) {
117       return methodElement().getParameters().stream()
118           .filter(p -> !supportedTypes.isAutomaticallyResolved(p.asType()))
119           .map(p -> p.getSimpleName().toString())
120           .map(map)
121           .collect(joining(", "));
122     } else if (filterBehaviour
123         == AutomaticallyResolvedParameterFilterBehaviour
124             .REPLACE_AUTOMATICALLY_RESOLVED_PARAMETERS) {
125       return methodElement().getParameters().stream()
126           .map(
127               p ->
128                   supportedTypes.isAutomaticallyResolved(p.asType())
129                       ? supportedTypes.getAutomaticallyResolvedReplacement(p.asType())
130                       : p.getSimpleName().toString())
131           .map(map)
132           .collect(joining(", "));
133     } else if (filterBehaviour
134         == AutomaticallyResolvedParameterFilterBehaviour.LEAVE_AUTOMATICALLY_RESOLVED_PARAMETERS) {
135       return methodElement().getParameters().stream()
136           .map(p -> p.getSimpleName().toString())
137           .map(map)
138           .collect(joining(", "));
139     }
140     throw new IllegalArgumentException("Invalid filter behaviour: " + filterBehaviour);
141   }
142 
143   /** An unordered collection of the types used in the parameters of this method. */
parameterTypes()144   public Collection<TypeMirror> parameterTypes() {
145     return methodElement().getParameters().stream()
146         .map(Element::asType)
147         .collect(toCollection(LinkedHashSet::new));
148   }
149 
150   /**
151    * True if both {@link #isCrossProfileCallback(GeneratorContext)} and {@link
152    * #isFuture(CrossProfileTypeInfo)} are {@code False}.
153    */
isBlocking(GeneratorContext context, CrossProfileTypeInfo type)154   public boolean isBlocking(GeneratorContext context, CrossProfileTypeInfo type) {
155     return !isCrossProfileCallback(context) && !isFuture(type);
156   }
157 
158   /** True if any argument is annotated with {@link CrossProfileCallback}. */
isCrossProfileCallback(GeneratorContext generatorContext)159   public boolean isCrossProfileCallback(GeneratorContext generatorContext) {
160     return getCrossProfileCallbackParam(generatorContext).isPresent();
161   }
162 
163   /** True if there is only a single {@link CrossProfileCallback} argument and it is simple. */
isSimpleCrossProfileCallback(GeneratorContext generatorContext)164   public boolean isSimpleCrossProfileCallback(GeneratorContext generatorContext) {
165     Optional<CrossProfileCallbackParameterInfo> param =
166         getCrossProfileCallbackParam(generatorContext);
167 
168     if (param.isPresent()) {
169       return param.get().crossProfileCallbackInterface().isSimple();
170     }
171 
172     return false;
173   }
174 
175   /** True if the return type is a supported {@code Future} type. */
isFuture(CrossProfileTypeInfo type)176   public boolean isFuture(CrossProfileTypeInfo type) {
177     return isFuture(type.supportedTypes(), methodElement());
178   }
179 
isFuture(SupportedTypes supportedTypes, ExecutableElement method)180   public static boolean isFuture(SupportedTypes supportedTypes, ExecutableElement method) {
181     return supportedTypes.isFuture(TypeUtils.removeTypeArguments(method.getReturnType()));
182   }
183 
184   /** Return the {@link CrossProfileCallback} annotated parameter, if any. */
getCrossProfileCallbackParam( GeneratorContext generatorContext)185   public Optional<CrossProfileCallbackParameterInfo> getCrossProfileCallbackParam(
186       GeneratorContext generatorContext) {
187     return getCrossProfileCallbackParam(generatorContext, methodElement());
188   }
189 
getCrossProfileCallbackParam( Context context, ExecutableElement method)190   public static Optional<CrossProfileCallbackParameterInfo> getCrossProfileCallbackParam(
191       Context context, ExecutableElement method) {
192     return method.getParameters().stream()
193         .filter(v -> isCrossProfileCallbackInterface(context.elements(), v.asType()))
194         .findFirst()
195         .map(e -> (VariableElement) e)
196         .map(e -> CrossProfileCallbackParameterInfo.create(context, e));
197   }
198 
isCrossProfileCallbackInterface(Elements elements, TypeMirror type)199   private static boolean isCrossProfileCallbackInterface(Elements elements, TypeMirror type) {
200     TypeElement typeElement = elements.getTypeElement(type.toString());
201     return typeElement != null && hasCrossProfileCallbackAnnotation(typeElement);
202   }
203 
create( int identifier, ValidatorCrossProfileTypeInfo type, ExecutableElement methodElement, Context context)204   public static CrossProfileMethodInfo create(
205       int identifier,
206       ValidatorCrossProfileTypeInfo type,
207       ExecutableElement methodElement,
208       Context context) {
209     return new AutoValue_CrossProfileMethodInfo(
210         methodElement,
211         identifier,
212         methodElement.getModifiers().contains(Modifier.STATIC),
213         methodElement.getAnnotation(Cacheable.class) != null);
214   }
215 }
216