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;
17 
18 import static com.google.android.enterprise.connectedapps.processor.annotationdiscovery.AnnotationFinder.hasCrossProfileCallbackAnnotation;
19 
20 import com.google.android.enterprise.connectedapps.processor.containers.Context;
21 import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileCallbackInterfaceInfo;
22 import com.google.android.enterprise.connectedapps.processor.containers.CrossProfileMethodInfo;
23 import com.google.android.enterprise.connectedapps.processor.containers.FutureWrapper;
24 import com.google.android.enterprise.connectedapps.processor.containers.ParcelableWrapper;
25 import com.google.android.enterprise.connectedapps.processor.containers.Type;
26 import com.google.auto.value.AutoValue;
27 import com.google.common.collect.ImmutableCollection;
28 import com.google.common.collect.ImmutableMap;
29 import com.squareup.javapoet.ClassName;
30 import com.squareup.javapoet.CodeBlock;
31 import java.util.Collection;
32 import java.util.LinkedHashMap;
33 import java.util.Map;
34 import java.util.Objects;
35 import java.util.Optional;
36 import javax.lang.model.element.ExecutableElement;
37 import javax.lang.model.element.TypeElement;
38 import javax.lang.model.element.VariableElement;
39 import javax.lang.model.type.TypeKind;
40 import javax.lang.model.type.TypeMirror;
41 import javax.lang.model.util.Elements;
42 import javax.lang.model.util.Types;
43 import org.checkerframework.checker.nullness.qual.Nullable;
44 
45 /**
46  * Utility methods for generating code related to valid types for use with the Connected Apps SDK.
47  */
48 public final class SupportedTypes {
49 
50   @Override
toString()51   public String toString() {
52     return "SupportedTypes{" +
53         "usableTypes=" + usableTypes +
54         '}';
55   }
56 
57   @Override
equals(Object o)58   public boolean equals(Object o) {
59     if (this == o) {
60       return true;
61     }
62     if (o == null || getClass() != o.getClass()) {
63       return false;
64     }
65     SupportedTypes that = (SupportedTypes) o;
66     return usableTypes.equals(that.usableTypes);
67   }
68 
69   @Override
hashCode()70   public int hashCode() {
71     return Objects.hash(usableTypes);
72   }
73 
74   /** Record of the current context for type checking. */
75   @AutoValue
76   public abstract static class TypeCheckContext {
77 
78     /** True if we are checking inside a generic type or an array. */
isWrapped()79     public abstract boolean isWrapped();
80 
isOnCrossProfileCallbackInterface()81     public abstract boolean isOnCrossProfileCallbackInterface();
82 
toBuilder()83     public abstract Builder toBuilder();
84 
create()85     public static TypeCheckContext create() {
86       return new AutoValue_SupportedTypes_TypeCheckContext.Builder()
87           .setWrapped(false)
88           .setOnCrossProfileCallbackInterface(false)
89           .build();
90     }
91 
createForCrossProfileCallbackInterface()92     public static TypeCheckContext createForCrossProfileCallbackInterface() {
93       return new AutoValue_SupportedTypes_TypeCheckContext.Builder()
94           .setWrapped(false)
95           .setOnCrossProfileCallbackInterface(true)
96           .build();
97     }
98 
99     @AutoValue.Builder
100     abstract static class Builder {
setWrapped(boolean wrapped)101       abstract Builder setWrapped(boolean wrapped);
102 
setOnCrossProfileCallbackInterface(boolean onCrossProfileCallbackInterface)103       abstract Builder setOnCrossProfileCallbackInterface(boolean onCrossProfileCallbackInterface);
104 
build()105       abstract TypeCheckContext build();
106     }
107   }
108 
109   private final ImmutableMap<String, Type> usableTypes;
110 
isFuture(TypeMirror type)111   public boolean isFuture(TypeMirror type) {
112     Type supportedType = get(type);
113     return supportedType != null && supportedType.isFuture();
114   }
115 
isValidReturnType(TypeMirror type)116   boolean isValidReturnType(TypeMirror type) {
117     // default behaviour is to check generics
118     return isValidReturnType(type, TypeCheckContext.create(), /* checkGenerics= */ true);
119   }
120 
isValidReturnType(TypeMirror type, TypeCheckContext context)121   boolean isValidReturnType(TypeMirror type, TypeCheckContext context) {
122     // default behaviour is to check generics
123     return isValidReturnType(type, context, /* checkGenerics= */ true);
124   }
125 
isValidReturnType(TypeMirror type, boolean checkGenerics)126   boolean isValidReturnType(TypeMirror type, boolean checkGenerics) {
127     return isValidReturnType(type, TypeCheckContext.create(), checkGenerics);
128   }
129 
isValidReturnType( TypeMirror type, TypeCheckContext context, boolean checkGenerics)130   private boolean isValidReturnType(
131       TypeMirror type, TypeCheckContext context, boolean checkGenerics) {
132 
133     if (TypeUtils.isArray(type)) {
134       TypeMirror wrappedType = TypeUtils.extractTypeFromArray(type);
135       if (TypeUtils.isGeneric(wrappedType)) {
136         return false; // We don't support generic arrays
137       }
138       if (TypeUtils.isArray(wrappedType)) {
139         // We don't support non-primitive multidimensional arrays
140         return TypeUtils.isPrimitiveArray(wrappedType);
141       }
142       return isValidReturnType(wrappedType, context, checkGenerics);
143     }
144 
145     return (TypeUtils.isGeneric(type) && checkGenerics)
146         ? isValidGenericReturnType(type, context)
147         : isValidReturnType(get(type), context);
148   }
149 
isValidReturnType(@ullable Type supportedType, TypeCheckContext context)150   private static boolean isValidReturnType(@Nullable Type supportedType, TypeCheckContext context) {
151     if (supportedType == null) {
152       return false;
153     }
154 
155     if (context.isWrapped() && !supportedType.isSupportedInsideWrapper()) {
156       return false;
157     }
158 
159     return supportedType.isAcceptableReturnType();
160   }
161 
isValidGenericReturnType(TypeMirror type, TypeCheckContext context)162   private boolean isValidGenericReturnType(TypeMirror type, TypeCheckContext context) {
163     TypeMirror genericType = TypeUtils.removeTypeArguments(type);
164     Type supportedType = get(genericType);
165 
166     if (supportedType == null) {
167       return false;
168     }
169 
170     if (!supportedType.isSupportedWithAnyGenericType()) {
171       // We need to recursively check all type arguments
172       for (TypeMirror typeArgument : TypeUtils.extractTypeArguments(type)) {
173         if (!isValidReturnType(typeArgument, context.toBuilder().setWrapped(true).build())) {
174           return false;
175         }
176       }
177     }
178 
179     return isValidReturnType(supportedType, context);
180   }
181 
182   /**
183    * Returns true if this type is automatically resolved.
184    *
185    * <p>An automatically resolved type does not need to have a value provided by the developer at
186    * runtime, and should instead use the value of
187    * {@link #getAutomaticallyResolvedReplacement(TypeMirror)}.
188    */
isAutomaticallyResolved(TypeMirror type)189   public boolean isAutomaticallyResolved(TypeMirror type) {
190     Type supportedType = get(type);
191     return supportedType != null && supportedType.getAutomaticallyResolvedReplacement().isPresent();
192   }
193 
getAutomaticallyResolvedReplacement(TypeMirror type)194   public String getAutomaticallyResolvedReplacement(TypeMirror type) {
195     Type supportedType = get(type);
196     return supportedType.getAutomaticallyResolvedReplacement().get();
197   }
198 
isValidParameterType(TypeMirror type)199   boolean isValidParameterType(TypeMirror type) {
200     // default behaviour is to check generics
201     return isValidParameterType(type, TypeCheckContext.create(), /* checkGenerics= */ true);
202   }
203 
isValidParameterType(TypeMirror type, TypeCheckContext context)204   boolean isValidParameterType(TypeMirror type, TypeCheckContext context) {
205     // default behaviour is to check generics
206     return isValidParameterType(type, context, /* checkGenerics= */ true);
207   }
208 
isValidParameterType(TypeMirror type, boolean checkGenerics)209   boolean isValidParameterType(TypeMirror type, boolean checkGenerics) {
210     return isValidParameterType(type, TypeCheckContext.create(), checkGenerics);
211   }
212 
isValidParameterType(TypeMirror type, TypeCheckContext context, boolean checkGenerics)213   boolean isValidParameterType(TypeMirror type, TypeCheckContext context, boolean checkGenerics) {
214     if (TypeUtils.isArray(type)) {
215       TypeMirror wrappedType = TypeUtils.extractTypeFromArray(type);
216       if (TypeUtils.isGeneric(wrappedType)) {
217         return false; // We don't support generic arrays
218       }
219       if (TypeUtils.isArray(wrappedType)) {
220         // We don't support non-primitive multidimensional arrays
221         return TypeUtils.isPrimitiveArray(wrappedType);
222       }
223       return isValidParameterType(
224           wrappedType, context.toBuilder().setWrapped(true).build(), checkGenerics);
225     }
226 
227     Type supportedType = get(TypeUtils.removeTypeArguments(type));
228     if (context.isOnCrossProfileCallbackInterface()) {
229       if (supportedType != null && !supportedType.isSupportedInsideCrossProfileCallback()) {
230         return false;
231       }
232     }
233 
234     if (context.isWrapped()) {
235       if (supportedType == null || !supportedType.isSupportedInsideWrapper()) {
236         return false;
237       }
238     }
239 
240     return (TypeUtils.isGeneric(type) && checkGenerics)
241         ? isValidGenericParameterType(type, context)
242         : isValidParameterType(get(type));
243   }
244 
isValidParameterType(Type supportedType)245   private static boolean isValidParameterType(Type supportedType) {
246     return supportedType != null && supportedType.isAcceptableParameterType();
247   }
248 
isValidGenericParameterType(TypeMirror type, TypeCheckContext context)249   private boolean isValidGenericParameterType(TypeMirror type, TypeCheckContext context) {
250     TypeMirror genericType = TypeUtils.removeTypeArguments(type);
251     Type supportedType = get(genericType);
252 
253     if (supportedType == null) {
254       return false;
255     }
256 
257     if (!supportedType.isSupportedWithAnyGenericType()) {
258       // We need to recursively check all type arguments
259       for (TypeMirror typeArgument : TypeUtils.extractTypeArguments(type)) {
260         if (!isValidParameterType(typeArgument, context.toBuilder().setWrapped(true).build())) {
261           return false;
262         }
263       }
264     }
265 
266     return isValidParameterType(supportedType);
267   }
268 
usableTypes()269   ImmutableCollection<Type> usableTypes() {
270     return usableTypes.values();
271   }
272 
get(TypeMirror type)273   private Type get(TypeMirror type) {
274     return usableTypes.getOrDefault(type.toString(), null);
275   }
276 
generatePutIntoBundleCode( String bundleName, Type type, String keyCode, String valueCode)277   CodeBlock generatePutIntoBundleCode(
278       String bundleName, Type type, String keyCode, String valueCode) {
279 
280     if (type.getPutIntoBundleCode().isPresent()) {
281       return CodeBlock.of(type.getPutIntoBundleCode().get(), bundleName, keyCode, valueCode);
282     }
283 
284     throw new IllegalArgumentException(
285         String.format("%s can not put into bundle", type.getQualifiedName()));
286   }
287 
generateGetFromBundleCode(String bundleName, Type type, String keyCode)288   CodeBlock generateGetFromBundleCode(String bundleName, Type type, String keyCode) {
289     if (type.getGetFromBundleCode().isPresent()) {
290       return CodeBlock.of(type.getGetFromBundleCode().get(), bundleName, keyCode);
291     }
292 
293     throw new IllegalArgumentException(
294         String.format("%s can not get from bundle", type.getQualifiedName()));
295   }
296 
generateWriteToParcelCode(String parcelName, Type type, String valueCode)297   CodeBlock generateWriteToParcelCode(String parcelName, Type type, String valueCode) {
298     if (type.getWriteToParcelCode().isPresent()) {
299       return CodeBlock.of(type.getWriteToParcelCode().get(), parcelName, valueCode);
300     }
301 
302     throw new IllegalArgumentException(
303         String.format("%s can not write to parcel", type.getQualifiedName()));
304   }
305 
generateReadFromParcelCode(String parcelName, Type type)306   CodeBlock generateReadFromParcelCode(String parcelName, Type type) {
307     if (type.getReadFromParcelCode().isPresent()) {
308       return CodeBlock.of(type.getReadFromParcelCode().get(), parcelName);
309     }
310 
311     throw new IllegalArgumentException(
312         String.format("%s can not read from parcel", type.getQualifiedName()));
313   }
314 
getType(TypeMirror type)315   public Type getType(TypeMirror type) {
316     String typeName = type.toString();
317     if (!usableTypes.containsKey(typeName)) {
318       throw new IllegalArgumentException(String.format("%s type not loaded", type));
319     }
320 
321     return get(type);
322   }
323 
SupportedTypes(Map<String, Type> usableTypes)324   private SupportedTypes(Map<String, Type> usableTypes) {
325     this.usableTypes = ImmutableMap.copyOf(usableTypes);
326   }
327 
createFromMethods( Context context, Collection<ParcelableWrapper> parcelableWrappers, Collection<FutureWrapper> futureWrappers, Collection<ExecutableElement> methods)328   public static SupportedTypes createFromMethods(
329       Context context,
330       Collection<ParcelableWrapper> parcelableWrappers,
331       Collection<FutureWrapper> futureWrappers,
332       Collection<ExecutableElement> methods) {
333     Map<String, Type> usableTypes = new LinkedHashMap<>();
334 
335     addDefaultTypes(context, usableTypes);
336     addParcelableWrapperTypes(usableTypes, parcelableWrappers);
337     addFutureWrapperTypes(usableTypes, futureWrappers);
338     addSupportForUsedTypes(context, usableTypes, methods);
339 
340     return new SupportedTypes(usableTypes);
341   }
342 
addSupportForUsedTypes( Context context, Map<String, Type> usableTypes, Collection<ExecutableElement> methods)343   private static void addSupportForUsedTypes(
344       Context context, Map<String, Type> usableTypes, Collection<ExecutableElement> methods) {
345     for (ExecutableElement method : methods) {
346       addSupportForUsedType(context, usableTypes, method.getReturnType());
347 
348       for (VariableElement parameter : method.getParameters()) {
349         addSupportForUsedType(context, usableTypes, parameter.asType());
350       }
351     }
352   }
353 
addSupportForUsedType( Context context, Map<String, Type> usableTypes, TypeMirror type)354   private static void addSupportForUsedType(
355       Context context, Map<String, Type> usableTypes, TypeMirror type) {
356     if (TypeUtils.isArray(type)) {
357       if (!TypeUtils.isPrimitiveArray(type)) {
358         addSupportForUsedType(context, usableTypes, TypeUtils.extractTypeFromArray(type));
359         type =
360             context
361                 .types()
362                 .getArrayType(context.elements().getTypeElement("java.lang.Object").asType());
363       }
364     }
365 
366     if (TypeUtils.isGeneric(type)) {
367       addSupportForGenericUsedType(context, usableTypes, type);
368       return;
369     }
370     Optional<Type> optionalSupportedType = getSupportedType(context, usableTypes, type);
371     if (!optionalSupportedType.isPresent()) {
372       // The type isn't supported
373       return;
374     }
375 
376     Type supportedType = optionalSupportedType.get();
377 
378     // We don't support generic callbacks so any callback interfaces can be picked up here
379     if (supportedType.isCrossProfileCallbackInterface()) {
380       for (TypeMirror typeMirror :
381           supportedType.getCrossProfileCallbackInterface().get().argumentTypes()) {
382         addSupportForUsedType(context, usableTypes, typeMirror);
383       }
384     }
385 
386     addUsableType(usableTypes, supportedType);
387   }
388 
addSupportForGenericUsedType( Context context, Map<String, Type> usableTypes, TypeMirror type)389   private static void addSupportForGenericUsedType(
390       Context context, Map<String, Type> usableTypes, TypeMirror type) {
391     TypeMirror genericType = TypeUtils.removeTypeArguments(type);
392 
393     Optional<Type> optionalSupportedType = getSupportedType(context, usableTypes, genericType);
394     if (!optionalSupportedType.isPresent()) {
395       // The base type isn't supported
396       return;
397     }
398 
399     Type supportedType = optionalSupportedType.get();
400 
401     addUsableType(usableTypes, supportedType);
402 
403     if (!supportedType.isSupportedWithAnyGenericType()) {
404       for (TypeMirror typeArgument : TypeUtils.extractTypeArguments(type)) {
405         addSupportForUsedType(context, usableTypes, typeArgument);
406       }
407     }
408   }
409 
getSupportedType( Context context, Map<String, Type> usableTypes, TypeMirror type)410   private static Optional<Type> getSupportedType(
411       Context context, Map<String, Type> usableTypes, TypeMirror type) {
412     Elements elements = context.elements();
413     Types types = context.types();
414     if (usableTypes.containsKey(type.toString())) {
415       return Optional.of(usableTypes.get(type.toString()));
416     }
417 
418     TypeMirror parcelable = elements.getTypeElement("android.os.Parcelable").asType();
419     if (types.isAssignable(type, parcelable)) {
420       return Optional.of(createParcelableType(type));
421     }
422 
423     TypeMirror serializable = elements.getTypeElement("java.io.Serializable").asType();
424     if (types.isAssignable(type, serializable)) {
425       return Optional.of(createSerializableType(type));
426     }
427 
428     TypeElement element = elements.getTypeElement(type.toString());
429 
430     if (element != null && hasCrossProfileCallbackAnnotation(element)) {
431       return Optional.of(createCrossProfileCallbackType(element));
432     }
433 
434     // We don't support this type - it will error in a later stage
435     return Optional.empty();
436   }
437 
createCrossProfileCallbackType(TypeElement type)438   private static Type createCrossProfileCallbackType(TypeElement type) {
439     return Type.builder()
440         .setTypeMirror(type.asType())
441         .setAcceptableReturnType(false)
442         .setAcceptableParameterType(true)
443         .setSupportedInsideWrapper(false)
444         .setSupportedInsideCrossProfileCallback(false)
445         .setCrossProfileCallbackInterface(CrossProfileCallbackInterfaceInfo.create(type))
446         .build();
447   }
448 
createParcelableType(TypeMirror typeMirror)449   private static Type createParcelableType(TypeMirror typeMirror) {
450     return Type.builder()
451         .setTypeMirror(typeMirror)
452         .setAcceptableReturnType(true)
453         .setAcceptableParameterType(true)
454         .setPutIntoBundleCode("$L.putParcelable($L, $L)")
455         .setGetFromBundleCode("$L.getParcelable($L)")
456         .setWriteToParcelCode("$L.writeParcelable($L, flags)")
457         .setReadFromParcelCode("$L.readParcelable(Bundler.class.getClassLoader())")
458         // Parcelables must take care of their own generic types
459         .setSupportedWithAnyGenericType(true)
460         .build();
461   }
462 
createSerializableType(TypeMirror typeMirror)463   private static Type createSerializableType(TypeMirror typeMirror) {
464     return Type.builder()
465         .setTypeMirror(typeMirror)
466         .setAcceptableReturnType(true)
467         .setAcceptableParameterType(true)
468         .setPutIntoBundleCode("$L.putSerializable($L, $L)")
469         .setGetFromBundleCode("$L.getSerializable($L)")
470         .setWriteToParcelCode("$L.writeSerializable($L)")
471         .setReadFromParcelCode("$L.readSerializable()")
472         // Serializables must take care of their own generic types
473         .setSupportedWithAnyGenericType(true)
474         .build();
475   }
476 
477   /** Create a {@link Builder} to create a new {@link SupportedTypes} with modified entries. */
asBuilder()478   public Builder asBuilder() {
479     return new Builder(usableTypes);
480   }
481 
addDefaultTypes(Context context, Map<String, Type> usableTypes)482   private static void addDefaultTypes(Context context, Map<String, Type> usableTypes) {
483     Elements elements = context.elements();
484     Types types = context.types();
485     addUsableType(
486         usableTypes,
487         Type.builder()
488             .setTypeMirror(types.getNoType(TypeKind.VOID))
489             .setAcceptableReturnType(true)
490             .setGetFromBundleCode("null")
491             .setReadFromParcelCode("null")
492             .build());
493     addUsableType(
494         usableTypes,
495         Type.builder()
496             .setTypeMirror(elements.getTypeElement("java.lang.Void").asType())
497             .setAcceptableReturnType(true)
498             .setGetFromBundleCode("null")
499             .setReadFromParcelCode("null")
500             .build());
501     addUsableType(
502         usableTypes,
503         Type.builder()
504             .setTypeMirror(elements.getTypeElement("java.lang.String").asType())
505             .setAcceptableReturnType(true)
506             .setAcceptableParameterType(true)
507             .setPutIntoBundleCode("$L.putString($L, $L)")
508             .setGetFromBundleCode("$L.getString($L)")
509             .setWriteToParcelCode("$L.writeString($L)")
510             .setReadFromParcelCode("$L.readString()")
511             .build());
512     addUsableType(
513         usableTypes,
514         Type.builder()
515             .setTypeMirror(elements.getTypeElement("java.lang.CharSequence").asType())
516             .setAcceptableReturnType(true)
517             .setAcceptableParameterType(true)
518             .setPutIntoBundleCode("$1L.putString($2L, $3L == null ? null : String.valueOf($3L))")
519             .setGetFromBundleCode("$L.getString($L)")
520             .setWriteToParcelCode("$L.writeString(String.valueOf($L))")
521             .setReadFromParcelCode("$L.readString()")
522             .build());
523     addUsableType(
524         usableTypes,
525         Type.builder()
526             .setTypeMirror(types.getPrimitiveType(TypeKind.BYTE))
527             .setAcceptableReturnType(true)
528             .setAcceptableParameterType(true)
529             .setPutIntoBundleCode("$L.putByte($L, $L)")
530             .setGetFromBundleCode("$L.getByte($L)")
531             .setWriteToParcelCode("$L.writeByte($L)")
532             .setReadFromParcelCode("$L.readByte()")
533             .build());
534     addUsableType(
535         usableTypes,
536         Type.builder()
537             .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.BYTE)).asType())
538             .setAcceptableReturnType(true)
539             .setAcceptableParameterType(true)
540             .setPutIntoBundleCode("$L.putByte($L, $L)")
541             .setGetFromBundleCode("$L.getByte($L)")
542             .setWriteToParcelCode("$L.writeByte($L)")
543             .setReadFromParcelCode("$L.readByte()")
544             .build());
545     addUsableType(
546         usableTypes,
547         Type.builder()
548             .setTypeMirror(types.getPrimitiveType(TypeKind.SHORT))
549             .setAcceptableReturnType(true)
550             .setAcceptableParameterType(true)
551             .setPutIntoBundleCode("$L.putShort($L, $L)")
552             .setGetFromBundleCode("$L.getShort($L)")
553             .setWriteToParcelCode("$L.writeInt($L)")
554             .setReadFromParcelCode("(short)$L.readInt()")
555             .build());
556     addUsableType(
557         usableTypes,
558         Type.builder()
559             .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.SHORT)).asType())
560             .setAcceptableReturnType(true)
561             .setAcceptableParameterType(true)
562             .setPutIntoBundleCode("$L.putShort($L, $L)")
563             .setGetFromBundleCode("$L.getShort($L)")
564             .setWriteToParcelCode("$L.writeInt($L)")
565             .setReadFromParcelCode("(short)$L.readInt()")
566             .build());
567     addUsableType(
568         usableTypes,
569         Type.builder()
570             .setTypeMirror(types.getPrimitiveType(TypeKind.INT))
571             .setAcceptableReturnType(true)
572             .setAcceptableParameterType(true)
573             .setPutIntoBundleCode("$L.putInt($L, $L)")
574             .setGetFromBundleCode("$L.getInt($L)")
575             .setWriteToParcelCode("$L.writeInt($L)")
576             .setReadFromParcelCode("$L.readInt()")
577             .build());
578     addUsableType(
579         usableTypes,
580         Type.builder()
581             .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.INT)).asType())
582             .setAcceptableReturnType(true)
583             .setAcceptableParameterType(true)
584             .setPutIntoBundleCode("$L.putInt($L, $L)")
585             .setGetFromBundleCode("$L.getInt($L)")
586             .setWriteToParcelCode("$L.writeInt($L)")
587             .setReadFromParcelCode("$L.readInt()")
588             .build());
589     addUsableType(
590         usableTypes,
591         Type.builder()
592             .setTypeMirror(types.getPrimitiveType(TypeKind.LONG))
593             .setAcceptableReturnType(true)
594             .setAcceptableParameterType(true)
595             .setPutIntoBundleCode("$L.putLong($L, $L)")
596             .setGetFromBundleCode("$L.getLong($L)")
597             .setWriteToParcelCode("$L.writeLong($L)")
598             .setReadFromParcelCode("$L.readLong()")
599             .build());
600     addUsableType(
601         usableTypes,
602         Type.builder()
603             .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.LONG)).asType())
604             .setAcceptableReturnType(true)
605             .setAcceptableParameterType(true)
606             .setPutIntoBundleCode("$L.putLong($L, $L)")
607             .setGetFromBundleCode("$L.getLong($L)")
608             .setWriteToParcelCode("$L.writeLong($L)")
609             .setReadFromParcelCode("$L.readLong()")
610             .build());
611     addUsableType(
612         usableTypes,
613         Type.builder()
614             .setTypeMirror(types.getPrimitiveType(TypeKind.FLOAT))
615             .setAcceptableReturnType(true)
616             .setAcceptableParameterType(true)
617             .setPutIntoBundleCode("$L.putFloat($L, $L)")
618             .setGetFromBundleCode("$L.getFloat($L)")
619             .setWriteToParcelCode("$L.writeFloat($L)")
620             .setReadFromParcelCode("$L.readFloat()")
621             .build());
622     addUsableType(
623         usableTypes,
624         Type.builder()
625             .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.FLOAT)).asType())
626             .setAcceptableReturnType(true)
627             .setAcceptableParameterType(true)
628             .setPutIntoBundleCode("$L.putFloat($L, $L)")
629             .setGetFromBundleCode("$L.getFloat($L)")
630             .setWriteToParcelCode("$L.writeFloat($L)")
631             .setReadFromParcelCode("$L.readFloat()")
632             .build());
633     addUsableType(
634         usableTypes,
635         Type.builder()
636             .setTypeMirror(types.getPrimitiveType(TypeKind.DOUBLE))
637             .setAcceptableReturnType(true)
638             .setAcceptableParameterType(true)
639             .setPutIntoBundleCode("$L.putDouble($L, $L)")
640             .setGetFromBundleCode("$L.getDouble($L)")
641             .setWriteToParcelCode("$L.writeDouble($L)")
642             .setReadFromParcelCode("$L.readDouble()")
643             .build());
644     addUsableType(
645         usableTypes,
646         Type.builder()
647             .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.DOUBLE)).asType())
648             .setAcceptableReturnType(true)
649             .setAcceptableParameterType(true)
650             .setPutIntoBundleCode("$L.putDouble($L, $L)")
651             .setGetFromBundleCode("$L.getDouble($L)")
652             .setWriteToParcelCode("$L.writeDouble($L)")
653             .setReadFromParcelCode("$L.readDouble()")
654             .build());
655     addUsableType(
656         usableTypes,
657         Type.builder()
658             .setTypeMirror(types.getPrimitiveType(TypeKind.CHAR))
659             .setAcceptableReturnType(true)
660             .setAcceptableParameterType(true)
661             .setPutIntoBundleCode("$L.putChar($L, $L)")
662             .setGetFromBundleCode("$L.getChar($L)")
663             .setWriteToParcelCode("$L.writeInt($L)")
664             .setReadFromParcelCode("(char)$L.readInt()")
665             .build());
666     addUsableType(
667         usableTypes,
668         Type.builder()
669             .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.CHAR)).asType())
670             .setAcceptableReturnType(true)
671             .setAcceptableParameterType(true)
672             .setPutIntoBundleCode("$L.putChar($L, $L)")
673             .setGetFromBundleCode("$L.getChar($L)")
674             .setWriteToParcelCode("$L.writeInt($L)")
675             .setReadFromParcelCode("(char)$L.readInt()")
676             .build());
677     addUsableType(
678         usableTypes,
679         Type.builder()
680             .setTypeMirror(types.getPrimitiveType(TypeKind.BOOLEAN))
681             .setAcceptableReturnType(true)
682             .setAcceptableParameterType(true)
683             .setPutIntoBundleCode("$L.putBoolean($L, $L)")
684             .setGetFromBundleCode("$L.getBoolean($L)")
685             .setWriteToParcelCode("$L.writeInt($L ? 1 : 0)")
686             .setReadFromParcelCode("($L.readInt() == 1)")
687             .build());
688     addUsableType(
689         usableTypes,
690         Type.builder()
691             .setTypeMirror(types.boxedClass(types.getPrimitiveType(TypeKind.BOOLEAN)).asType())
692             .setAcceptableReturnType(true)
693             .setAcceptableParameterType(true)
694             .setPutIntoBundleCode("$L.putBoolean($L, $L)")
695             .setGetFromBundleCode("$L.getBoolean($L)")
696             .setWriteToParcelCode("$L.writeInt($L ? 1 : 0)")
697             .setReadFromParcelCode("($L.readInt() == 1)")
698             .build());
699     addUsableType(
700         usableTypes,
701         Type.builder()
702             .setTypeMirror(elements.getTypeElement("android.content.Context").asType())
703             .setAcceptableParameterType(true)
704             .setAutomaticallyResolvedReplacement("context")
705             .setAcceptableReturnType(false)
706             .setSupportedInsideWrapper(false)
707             .setSupportedInsideCrossProfileCallback(false)
708             .build());
709 
710     addUsableType(
711         usableTypes,
712         Type.builder()
713             .setTypeMirror(elements.getTypeElement("android.os.Parcelable").asType())
714             .setAcceptableReturnType(true)
715             .setAcceptableParameterType(true)
716             .setPutIntoBundleCode("$L.putParcelable($L, $L)")
717             .setGetFromBundleCode("$L.getParcelable($L)")
718             .setWriteToParcelCode("$L.writeParcelable($L, flags)")
719             .setReadFromParcelCode("$L.readParcelable(Bundler.class.getClassLoader())")
720             .build());
721 
722     //region ****  Primitive Array Types  ****
723     addUsableType(
724         usableTypes,
725         Type.builder()
726             .setTypeMirror(types.getArrayType(types.getPrimitiveType(TypeKind.BOOLEAN)))
727             .setAcceptableParameterType(true)
728             .setAcceptableReturnType(true)
729             .setPutIntoBundleCode("$L.putBooleanArray($L, $L)")
730             .setGetFromBundleCode("$L.getBooleanArray($L)")
731             .setWriteToParcelCode("$L.writeBooleanArray($L)")
732             .setReadFromParcelCode("$L.createBooleanArray()")
733             .build());
734     addUsableType(
735         usableTypes,
736         Type.builder()
737             .setTypeMirror(types.getArrayType(types.getPrimitiveType(TypeKind.BYTE)))
738             .setAcceptableParameterType(true)
739             .setAcceptableReturnType(true)
740             .setPutIntoBundleCode("$L.putByteArray($L, $L)")
741             .setGetFromBundleCode("$L.getByteArray($L)")
742             .setWriteToParcelCode("$L.writeByteArray($L)")
743             .setReadFromParcelCode("$L.createByteArray()")
744             .build());
745     addUsableType(
746         usableTypes,
747         Type.builder()
748             .setTypeMirror(types.getArrayType(types.getPrimitiveType(TypeKind.CHAR)))
749             .setAcceptableParameterType(true)
750             .setAcceptableReturnType(true)
751             .setPutIntoBundleCode("$L.putCharArray($L, $L)")
752             .setGetFromBundleCode("$L.getCharArray($L)")
753             .setWriteToParcelCode("$L.writeCharArray($L)")
754             .setReadFromParcelCode("$L.createCharArray()")
755             .build());
756     addUsableType(
757         usableTypes,
758         Type.builder()
759             .setTypeMirror(types.getArrayType(types.getPrimitiveType(TypeKind.DOUBLE)))
760             .setAcceptableParameterType(true)
761             .setAcceptableReturnType(true)
762             .setPutIntoBundleCode("$L.putDoubleArray($L, $L)")
763             .setGetFromBundleCode("$L.getDoubleArray($L)")
764             .setWriteToParcelCode("$L.writeDoubleArray($L)")
765             .setReadFromParcelCode("$L.createDoubleArray()")
766             .build());
767     addUsableType(
768         usableTypes,
769         Type.builder()
770             .setTypeMirror(types.getArrayType(types.getPrimitiveType(TypeKind.FLOAT)))
771             .setAcceptableParameterType(true)
772             .setAcceptableReturnType(true)
773             .setPutIntoBundleCode("$L.putFloatArray($L, $L)")
774             .setGetFromBundleCode("$L.getFloatArray($L)")
775             .setWriteToParcelCode("$L.writeFloatArray($L)")
776             .setReadFromParcelCode("$L.createFloatArray()")
777             .build());
778     addUsableType(
779         usableTypes,
780         Type.builder()
781             .setTypeMirror(types.getArrayType(types.getPrimitiveType(TypeKind.INT)))
782             .setAcceptableParameterType(true)
783             .setAcceptableReturnType(true)
784             .setPutIntoBundleCode("$L.putIntArray($L, $L)")
785             .setGetFromBundleCode("$L.getIntArray($L)")
786             .setWriteToParcelCode("$L.writeIntArray($L)")
787             .setReadFromParcelCode("$L.createIntArray()")
788             .build());
789     addUsableType(
790         usableTypes,
791         Type.builder()
792             .setTypeMirror(types.getArrayType(types.getPrimitiveType(TypeKind.LONG)))
793             .setAcceptableParameterType(true)
794             .setAcceptableReturnType(true)
795             .setPutIntoBundleCode("$L.putLongArray($L, $L)")
796             .setGetFromBundleCode("$L.getLongArray($L)")
797             .setWriteToParcelCode("$L.writeLongArray($L)")
798             .setReadFromParcelCode("$L.createLongArray()")
799             .build());
800     //endregion ****  Primitive Array Types  ****
801 
802   }
803 
addUsableType(Map<String, Type> usableTypes, Type type)804   private static void addUsableType(Map<String, Type> usableTypes, Type type) {
805     usableTypes.put(type.getQualifiedName(), type);
806   }
807 
addParcelableWrapperTypes( Map<String, Type> usableTypes, Collection<ParcelableWrapper> parcelableWrappers)808   private static void addParcelableWrapperTypes(
809       Map<String, Type> usableTypes, Collection<ParcelableWrapper> parcelableWrappers) {
810     for (ParcelableWrapper parcelableWrapper : parcelableWrappers) {
811       addParcelableWrapperType(usableTypes, parcelableWrapper);
812     }
813   }
814 
addParcelableWrapperType( Map<String, Type> usableTypes, ParcelableWrapper parcelableWrapper)815   private static void addParcelableWrapperType(
816       Map<String, Type> usableTypes, ParcelableWrapper parcelableWrapper) {
817     String createParcelableCode = parcelableWrapper.wrapperClassName() + ".of(this, valueType, $L)";
818     // "this" will be a Bundler as this code is only run within a Bundler
819 
820     addUsableType(
821         usableTypes,
822         Type.builder()
823             .setTypeMirror(parcelableWrapper.wrappedType())
824             .setAcceptableReturnType(true)
825             .setAcceptableParameterType(true)
826             .setPutIntoBundleCode("$L.putParcelable($L, " + createParcelableCode + ")")
827             .setGetFromBundleCode(
828                 "((" + parcelableWrapper.wrapperClassName() + ") $L.getParcelable($L)).get()")
829             .setWriteToParcelCode("$L.writeParcelable(" + createParcelableCode + ", flags)")
830             .setReadFromParcelCode(
831                 "(("
832                     + parcelableWrapper.wrapperClassName()
833                     + ") $L.readParcelable(Bundler.class.getClassLoader())).get()")
834             .setParcelableWrapper(parcelableWrapper)
835             .build());
836   }
837 
addFutureWrapperTypes( Map<String, Type> usableTypes, Collection<FutureWrapper> futureWrappers)838   private static void addFutureWrapperTypes(
839       Map<String, Type> usableTypes, Collection<FutureWrapper> futureWrappers) {
840     for (FutureWrapper futureWrapper : futureWrappers) {
841       addFutureWrapperType(usableTypes, futureWrapper);
842     }
843   }
844 
addFutureWrapperType( Map<String, Type> usableTypes, FutureWrapper futureWrapper)845   private static void addFutureWrapperType(
846       Map<String, Type> usableTypes, FutureWrapper futureWrapper) {
847     addUsableType(
848         usableTypes,
849         Type.builder()
850             .setTypeMirror(futureWrapper.wrappedType())
851             .setAcceptableReturnType(true)
852             .setSupportedInsideWrapper(false)
853             .setFutureWrapper(futureWrapper)
854             .build());
855   }
856 
857   public static final class Builder {
858 
859     private Map<String, Type> usableTypes;
860 
Builder(Map<String, Type> usableTypes)861     private Builder(Map<String, Type> usableTypes) {
862       this.usableTypes = usableTypes;
863     }
864 
865     /** Filtering to only include used types and additional used types */
filterUsed( Context context, Collection<CrossProfileMethodInfo> methods, Collection<TypeElement> additionalUsedTypes)866     public Builder filterUsed(
867         Context context,
868         Collection<CrossProfileMethodInfo> methods,
869         Collection<TypeElement> additionalUsedTypes) {
870 
871       Map<String, Type> usedTypes = new LinkedHashMap<>();
872 
873       for (CrossProfileMethodInfo method : methods) {
874         copySupportedTypesForMethod(context, usedTypes, method);
875       }
876 
877       for (TypeElement type : additionalUsedTypes) {
878         // We do not want to recurse generics in additional types because generics will not be a
879         // supported type.
880         copySupportedType(context, usedTypes, type.asType(), /* recurseGenerics= */ false);
881       }
882 
883       this.usableTypes = usedTypes;
884 
885       return this;
886     }
887 
copySupportedTypesForMethod( Context context, Map<String, Type> usedTypes, CrossProfileMethodInfo method)888     private void copySupportedTypesForMethod(
889         Context context, Map<String, Type> usedTypes, CrossProfileMethodInfo method) {
890       copySupportedType(context, usedTypes, method.returnType());
891       for (TypeMirror argumentType : method.parameterTypes()) {
892         copySupportedType(context, usedTypes, argumentType);
893       }
894     }
895 
896     // default behaviour is to recurse generics
copySupportedType(Context context, Map<String, Type> usedTypes, TypeMirror type)897     private void copySupportedType(Context context, Map<String, Type> usedTypes, TypeMirror type) {
898       copySupportedType(context, usedTypes, type, true);
899     }
900 
copySupportedType( Context context, Map<String, Type> usedTypes, TypeMirror type, boolean recurseGenerics)901     private void copySupportedType(
902         Context context, Map<String, Type> usedTypes, TypeMirror type, boolean recurseGenerics) {
903       if (TypeUtils.isGeneric(type) && recurseGenerics) {
904         copySupportedGenericType(context, usedTypes, type);
905         return;
906       }
907 
908       if (TypeUtils.isArray(type)) {
909         if (!TypeUtils.isPrimitiveArray(type)) {
910           // Primitive arrays aren't resolved recursively
911           copySupportedType(context, usedTypes, TypeUtils.extractTypeFromArray(type));
912           type =
913               context
914                   .types()
915                   .getArrayType(context.elements().getTypeElement("java.lang.Object").asType());
916         }
917       }
918 
919       // The type must have been seen in when constructing the original so this should not
920       // be null
921       Type supportedType = usableTypes.get(type.toString());
922 
923       // We don't support generic callbacks so any callback interfaces can be picked up here
924       if (supportedType.isCrossProfileCallbackInterface()) {
925         for (TypeMirror typeMirror :
926             supportedType.getCrossProfileCallbackInterface().get().argumentTypes()) {
927           copySupportedType(context, usedTypes, typeMirror);
928         }
929       }
930 
931       copySupportedType(usedTypes, supportedType);
932     }
933 
copySupportedType(Map<String, Type> usedTypes, Type supportedType)934     private void copySupportedType(Map<String, Type> usedTypes, Type supportedType) {
935       addUsableType(usedTypes, supportedType);
936     }
937 
copySupportedGenericType( Context context, Map<String, Type> usedTypes, TypeMirror type)938     private void copySupportedGenericType(
939         Context context, Map<String, Type> usedTypes, TypeMirror type) {
940       TypeMirror genericType = TypeUtils.removeTypeArguments(type);
941 
942       // The type must have been seen in when constructing the oldSupportedTypes so this should not
943       // be null
944       Type supportedType = usableTypes.get(genericType.toString());
945 
946       if (!supportedType.isSupportedWithAnyGenericType()) {
947         // We need to recursively copy all type arguments
948         for (TypeMirror typeArgument : TypeUtils.extractTypeArguments(type)) {
949           copySupportedType(context, usedTypes, typeArgument);
950         }
951       }
952 
953       copySupportedType(usedTypes, supportedType);
954     }
955 
956     /** Add additional parcelable wrappers. */
addParcelableWrappers(Collection<ParcelableWrapper> parcelableWrappers)957     public Builder addParcelableWrappers(Collection<ParcelableWrapper> parcelableWrappers) {
958       Map<String, Type> newUsableTypes = new LinkedHashMap<>(usableTypes);
959 
960       addParcelableWrapperTypes(newUsableTypes, parcelableWrappers);
961 
962       usableTypes = newUsableTypes;
963 
964       return this;
965     }
966 
967     /** Add additional future wrappers. */
addFutureWrappers(Collection<FutureWrapper> futureWrappers)968     public Builder addFutureWrappers(Collection<FutureWrapper> futureWrappers) {
969       Map<String, Type> newUsableTypes = new LinkedHashMap<>(usableTypes);
970 
971       addFutureWrapperTypes(newUsableTypes, futureWrappers);
972 
973       usableTypes = newUsableTypes;
974 
975       return this;
976     }
977 
replaceWrapperPrefix(ClassName prefix)978     public Builder replaceWrapperPrefix(ClassName prefix) {
979       Map<String, Type> newUsableTypes = new LinkedHashMap<>();
980 
981       for (Type usableType : usableTypes.values()) {
982         if (usableType.getParcelableWrapper().isPresent()) {
983           replaceParcelableWrapperPrefix(newUsableTypes, prefix, usableType);
984         } else if (usableType.getFutureWrapper().isPresent()) {
985           replaceFutureWrapperPrefix(newUsableTypes, prefix, usableType);
986         } else {
987           addUsableType(newUsableTypes, usableType);
988         }
989       }
990 
991       usableTypes = newUsableTypes;
992 
993       return this;
994     }
995 
replaceParcelableWrapperPrefix( Map<String, Type> newUsableTypes, ClassName prefix, Type usableType)996     private void replaceParcelableWrapperPrefix(
997         Map<String, Type> newUsableTypes, ClassName prefix, Type usableType) {
998       ParcelableWrapper parcelableWrapper = usableType.getParcelableWrapper().get();
999 
1000       if (parcelableWrapper.wrapperType().equals(ParcelableWrapper.WrapperType.CUSTOM)) {
1001         // Custom types never get prefixed
1002         addUsableType(newUsableTypes, usableType);
1003         return;
1004       }
1005 
1006       addParcelableWrapperType(
1007           newUsableTypes,
1008           ParcelableWrapper.create(
1009               parcelableWrapper.wrappedType(),
1010               parcelableWrapper.defaultWrapperClassName(),
1011               prefix(prefix, parcelableWrapper.wrapperClassName()),
1012               parcelableWrapper.wrapperType()));
1013     }
1014 
replaceFutureWrapperPrefix( Map<String, Type> newUsableTypes, ClassName prefix, Type usableType)1015     private void replaceFutureWrapperPrefix(
1016         Map<String, Type> newUsableTypes, ClassName prefix, Type usableType) {
1017       FutureWrapper futureWrapper = usableType.getFutureWrapper().get();
1018 
1019       if (futureWrapper.wrapperType().equals(FutureWrapper.WrapperType.CUSTOM)) {
1020         // Custom types never get prefixed
1021         addUsableType(newUsableTypes, usableType);
1022         return;
1023       }
1024 
1025       addFutureWrapperType(
1026           newUsableTypes,
1027           FutureWrapper.create(
1028               futureWrapper.wrappedType(),
1029               futureWrapper.defaultWrapperClassName(),
1030               prefix(prefix, futureWrapper.wrapperClassName()),
1031               futureWrapper.wrapperType()));
1032     }
1033 
prefix(ClassName prefix, ClassName finalName)1034     private ClassName prefix(ClassName prefix, ClassName finalName) {
1035       return ClassName.get(
1036           prefix.packageName(), prefix.simpleName() + "_" + finalName.simpleName());
1037     }
1038 
1039     /** Build a new {@link SupportedTypes}. */
build()1040     public SupportedTypes build() {
1041       return new SupportedTypes(usableTypes);
1042     }
1043   }
1044 }
1045