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