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