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.ProtoParcelableWrapperGenerator.getGeneratedProtoWrapperClassName; 19 import static java.util.stream.Collectors.toSet; 20 21 import com.google.android.enterprise.connectedapps.annotations.CustomParcelableWrapper; 22 import com.google.android.enterprise.connectedapps.processor.TypeUtils; 23 import com.google.auto.value.AutoValue; 24 import com.squareup.javapoet.ClassName; 25 import java.util.ArrayList; 26 import java.util.Collection; 27 import java.util.Collections; 28 import java.util.HashSet; 29 import javax.lang.model.element.Element; 30 import javax.lang.model.element.ExecutableElement; 31 import javax.lang.model.element.TypeElement; 32 import javax.lang.model.type.TypeMirror; 33 import javax.lang.model.util.Elements; 34 35 /** Information about a Parcelable Wrapper. */ 36 @AutoValue 37 public abstract class ParcelableWrapper { 38 39 /** The type of the Wrapper. This controls how supporting code is generated. */ 40 public enum WrapperType { 41 DEFAULT, // Copied from a resource 42 PROTO, // Generated by ProtoParcelableWrapperGenerator 43 CUSTOM // Included in classpath 44 } 45 46 public static final String PARCELABLE_WRAPPER_PACKAGE = 47 "com.google.android.enterprise.connectedapps.parcelablewrappers"; 48 wrappedType()49 public abstract TypeMirror wrappedType(); 50 defaultWrapperClassName()51 public abstract ClassName defaultWrapperClassName(); 52 wrapperClassName()53 public abstract ClassName wrapperClassName(); 54 wrapperType()55 public abstract WrapperType wrapperType(); 56 create( TypeMirror wrappedType, ClassName defaultWrapperClassName, WrapperType wrapperType)57 private static ParcelableWrapper create( 58 TypeMirror wrappedType, ClassName defaultWrapperClassName, WrapperType wrapperType) { 59 return create(wrappedType, defaultWrapperClassName, defaultWrapperClassName, wrapperType); 60 } 61 create( TypeMirror wrappedType, ClassName defaultWrapperClassName, ClassName wrapperClassName, WrapperType wrapperType)62 public static ParcelableWrapper create( 63 TypeMirror wrappedType, 64 ClassName defaultWrapperClassName, 65 ClassName wrapperClassName, 66 WrapperType wrapperType) { 67 return new AutoValue_ParcelableWrapper( 68 wrappedType, defaultWrapperClassName, wrapperClassName, wrapperType); 69 } 70 createCustomParcelableWrappers( Context context, Collection<TypeElement> customParcelableWrappers)71 public static Collection<ParcelableWrapper> createCustomParcelableWrappers( 72 Context context, Collection<TypeElement> customParcelableWrappers) { 73 Collection<ParcelableWrapper> wrappers = new ArrayList<>(); 74 75 addCustomParcelableWrappers(context, wrappers, customParcelableWrappers); 76 77 return wrappers; 78 } 79 createGlobalParcelableWrappers( Context context, Collection<ExecutableElement> methods)80 public static Collection<ParcelableWrapper> createGlobalParcelableWrappers( 81 Context context, Collection<ExecutableElement> methods) { 82 Collection<ParcelableWrapper> wrappers = new ArrayList<>(); 83 84 addDefaultParcelableWrappers(context, wrappers); 85 86 Collection<TypeMirror> usedTypes = extractTypesFromMethods(methods); 87 88 addGeneratedProtoParcelableWrappers(context, wrappers, usedTypes); 89 90 return wrappers; 91 } 92 extractTypesFromMethods( Collection<ExecutableElement> methods)93 private static Collection<TypeMirror> extractTypesFromMethods( 94 Collection<ExecutableElement> methods) { 95 return methods.stream() 96 .flatMap(m -> extractReturnTypeAndParameters(m).stream()) 97 .flatMap(t -> extractTypeArgumentsIfWrapped(t).stream()) 98 .collect(toSet()); 99 } 100 extractReturnTypeAndParameters(ExecutableElement method)101 private static Collection<TypeMirror> extractReturnTypeAndParameters(ExecutableElement method) { 102 Collection<TypeMirror> types = new HashSet<>(); 103 types.add(method.getReturnType()); 104 types.addAll(method.getParameters().stream().map(Element::asType).collect(toSet())); 105 return types; 106 } 107 extractTypeArgumentsIfWrapped(TypeMirror type)108 private static Collection<TypeMirror> extractTypeArgumentsIfWrapped(TypeMirror type) { 109 if (TypeUtils.isGeneric(type)) { 110 return extractTypeArgumentsFromGeneric(type); 111 } 112 if (TypeUtils.isArray(type)) { 113 return extractTypeArgumentsIfWrapped(TypeUtils.extractTypeFromArray(type)); 114 } 115 116 return Collections.singleton(type); 117 } 118 extractTypeArgumentsFromGeneric(TypeMirror type)119 private static Collection<TypeMirror> extractTypeArgumentsFromGeneric(TypeMirror type) { 120 Collection<TypeMirror> types = new HashSet<>(); 121 types.add(TypeUtils.removeTypeArguments(type)); 122 123 types.addAll( 124 TypeUtils.extractTypeArguments(type).stream() 125 .flatMap(t -> extractTypeArgumentsIfWrapped(t).stream()) 126 .collect(toSet())); 127 return types; 128 } 129 addCustomParcelableWrappers( Context context, Collection<ParcelableWrapper> wrappers, Collection<TypeElement> customParcelableWrappers)130 private static void addCustomParcelableWrappers( 131 Context context, 132 Collection<ParcelableWrapper> wrappers, 133 Collection<TypeElement> customParcelableWrappers) { 134 for (TypeElement parcelableWrapper : customParcelableWrappers) { 135 addCustomParcelableWrapper(context, wrappers, parcelableWrapper); 136 } 137 } 138 addCustomParcelableWrapper( Context context, Collection<ParcelableWrapper> wrappers, TypeElement parcelableWrapper)139 private static void addCustomParcelableWrapper( 140 Context context, Collection<ParcelableWrapper> wrappers, TypeElement parcelableWrapper) { 141 142 CustomParcelableWrapper customParcelableWrapperAnnotation = 143 parcelableWrapper.getAnnotation(CustomParcelableWrapper.class); 144 145 if (customParcelableWrapperAnnotation == null) { 146 // This will be dealt with as part of early validation 147 return; 148 } 149 150 ParcelableWrapperAnnotationInfo annotationInfo = 151 ParcelableWrapperAnnotationInfo.extractFromParcelableWrapperAnnotation( 152 context, customParcelableWrapperAnnotation); 153 wrappers.add( 154 ParcelableWrapper.create( 155 annotationInfo.originalType().asType(), 156 ClassName.get(parcelableWrapper), 157 WrapperType.CUSTOM)); 158 } 159 addDefaultParcelableWrappers( Context context, Collection<ParcelableWrapper> wrappers)160 private static void addDefaultParcelableWrappers( 161 Context context, Collection<ParcelableWrapper> wrappers) { 162 Elements elements = context.elements(); 163 tryAddWrapper( 164 elements, 165 wrappers, 166 "java.util.Collection", 167 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableCollection")); 168 169 tryAddWrapper( 170 elements, 171 wrappers, 172 "java.util.List", 173 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableList")); 174 175 tryAddWrapper( 176 elements, 177 wrappers, 178 "java.util.Map", 179 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableMap")); 180 181 tryAddWrapper( 182 elements, 183 wrappers, 184 "java.util.Set", 185 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableSet")); 186 187 tryAddWrapper( 188 elements, 189 wrappers, 190 "java.util.Optional", 191 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableOptional")); 192 193 tryAddWrapper( 194 elements, 195 wrappers, 196 "com.google.common.base.Optional", 197 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableGuavaOptional")); 198 199 tryAddWrapper( 200 elements, 201 wrappers, 202 "com.google.common.collect.ImmutableMap", 203 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableImmutableMap")); 204 205 tryAddWrapper( 206 elements, 207 wrappers, 208 "com.google.common.collect.ImmutableMultimap", 209 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableImmutableMultimap")); 210 211 tryAddWrapper( 212 elements, 213 wrappers, 214 "com.google.common.collect.ImmutableSetMultimap", 215 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableImmutableSetMultimap")); 216 217 tryAddWrapper( 218 elements, 219 wrappers, 220 "com.google.common.collect.ImmutableListMultimap", 221 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableImmutableListMultimap")); 222 223 tryAddWrapper( 224 elements, 225 wrappers, 226 "com.google.common.collect.ImmutableSortedMap", 227 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableImmutableSortedMap")); 228 229 tryAddWrapper( 230 elements, 231 wrappers, 232 "com.google.common.collect.ImmutableList", 233 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableImmutableList")); 234 235 tryAddWrapper( 236 elements, 237 wrappers, 238 "com.google.common.collect.ImmutableSet", 239 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableImmutableSet")); 240 241 tryAddWrapper( 242 elements, 243 wrappers, 244 "com.google.common.collect.ImmutableMultiset", 245 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableImmutableMultiset")); 246 247 tryAddWrapper( 248 elements, 249 wrappers, 250 "com.google.common.collect.ImmutableSortedSet", 251 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableImmutableSortedSet")); 252 253 tryAddWrapper( 254 elements, 255 wrappers, 256 "com.google.common.collect.ImmutableSortedMultiset", 257 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableImmutableSortedMultiset")); 258 259 tryAddWrapper( 260 elements, 261 wrappers, 262 "com.google.common.collect.ImmutableBiMap", 263 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableImmutableBiMap")); 264 265 tryAddWrapper( 266 elements, 267 wrappers, 268 "com.google.common.collect.ImmutableCollection", 269 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableImmutableCollection")); 270 271 tryAddWrapper( 272 elements, 273 wrappers, 274 "android.util.Pair", 275 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelablePair")); 276 277 tryAddWrapper( 278 elements, 279 wrappers, 280 "android.graphics.Bitmap", 281 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableBitmap")); 282 283 tryAddWrapper( 284 elements, 285 wrappers, 286 "android.graphics.drawable.Drawable", 287 ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableDrawable")); 288 289 addArrayWrappers(context, wrappers); 290 } 291 addGeneratedProtoParcelableWrappers( Context context, Collection<ParcelableWrapper> wrappers, Collection<TypeMirror> usedTypes)292 private static void addGeneratedProtoParcelableWrappers( 293 Context context, Collection<ParcelableWrapper> wrappers, Collection<TypeMirror> usedTypes) { 294 TypeElement protoElement = context.elements().getTypeElement("com.google.protobuf.MessageLite"); 295 if (protoElement == null) { 296 // Protos are not included at compile-time 297 return; 298 } 299 TypeMirror proto = protoElement.asType(); 300 301 Collection<TypeMirror> protoTypes = 302 usedTypes.stream() 303 // <any> is the value when the compiler encounters a type which isn't accessible 304 // or does not exist. This passes the types.isAssignable filter, which makes such 305 // bugs hard to debug. This will already fail because the Java compiler won't allow 306 // it - so this is just to suppress strange test failures 307 .filter(t -> !t.toString().equals("<any>")) 308 .filter(t -> context.types().isAssignable(t, proto)) 309 .collect(toSet()); 310 311 for (TypeMirror protoType : protoTypes) { 312 wrappers.add( 313 ParcelableWrapper.create( 314 protoType, getGeneratedProtoWrapperClassName(protoType), WrapperType.PROTO)); 315 } 316 } 317 addArrayWrappers(Context context, Collection<ParcelableWrapper> wrappers)318 private static void addArrayWrappers(Context context, Collection<ParcelableWrapper> wrappers) { 319 TypeElement typeElement = context.elements().getTypeElement("java.lang.Object"); 320 TypeMirror typeMirror = context.types().getArrayType(typeElement.asType()); 321 322 ClassName wrapperClassName = ClassName.get(PARCELABLE_WRAPPER_PACKAGE, "ParcelableArray"); 323 324 wrappers.add(ParcelableWrapper.create(typeMirror, wrapperClassName, WrapperType.DEFAULT)); 325 } 326 tryAddWrapper( Elements elements, Collection<ParcelableWrapper> wrappers, String typeQualifiedName, ClassName wrapperClassName)327 private static void tryAddWrapper( 328 Elements elements, 329 Collection<ParcelableWrapper> wrappers, 330 String typeQualifiedName, 331 ClassName wrapperClassName) { 332 TypeElement typeElement = elements.getTypeElement(typeQualifiedName); 333 334 if (typeElement == null) { 335 // The type isn't supported at compile-time - so won't be included in this app 336 return; 337 } 338 339 wrappers.add( 340 ParcelableWrapper.create(typeElement.asType(), wrapperClassName, WrapperType.DEFAULT)); 341 } 342 } 343