• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 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  * http://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.auto.value.extension.serializable.processor;
17 
18 import static com.google.auto.value.extension.serializable.processor.ClassNames.SERIALIZABLE_AUTO_VALUE_NAME;
19 import static com.google.common.collect.ImmutableList.toImmutableList;
20 import static com.google.common.collect.ImmutableMap.toImmutableMap;
21 import static java.util.stream.Collectors.joining;
22 
23 import com.google.auto.common.GeneratedAnnotationSpecs;
24 import com.google.auto.common.MoreTypes;
25 import com.google.auto.service.AutoService;
26 import com.google.auto.value.extension.AutoValueExtension;
27 import com.google.auto.value.extension.serializable.serializer.SerializerFactoryLoader;
28 import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer;
29 import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory;
30 import com.google.common.base.Equivalence;
31 import com.google.common.collect.ImmutableList;
32 import com.google.common.collect.ImmutableMap;
33 import com.squareup.javapoet.AnnotationSpec;
34 import com.squareup.javapoet.ClassName;
35 import com.squareup.javapoet.CodeBlock;
36 import com.squareup.javapoet.FieldSpec;
37 import com.squareup.javapoet.JavaFile;
38 import com.squareup.javapoet.MethodSpec;
39 import com.squareup.javapoet.ParameterizedTypeName;
40 import com.squareup.javapoet.TypeName;
41 import com.squareup.javapoet.TypeSpec;
42 import com.squareup.javapoet.TypeVariableName;
43 import java.io.Serializable;
44 import java.util.List;
45 import java.util.Optional;
46 import java.util.function.Function;
47 import javax.annotation.processing.ProcessingEnvironment;
48 import javax.lang.model.element.AnnotationMirror;
49 import javax.lang.model.element.Modifier;
50 import javax.lang.model.element.TypeElement;
51 import javax.lang.model.type.TypeMirror;
52 
53 /**
54  * An AutoValue extension that enables classes with unserializable fields to be serializable.
55  *
56  * <p>For this extension to work:
57  *
58  * <ul>
59  *   <li>The AutoValue class must implement {@link Serializable}.
60  *   <li>Unserializable fields in the AutoValue class must be supported by a {@link
61  *       com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension}.
62  */
63 @AutoService(AutoValueExtension.class)
64 public final class SerializableAutoValueExtension extends AutoValueExtension {
65 
66   @Override
applicable(Context context)67   public boolean applicable(Context context) {
68     return hasSerializableInterface(context) && hasSerializableAutoValueAnnotation(context);
69   }
70 
71   @Override
incrementalType(ProcessingEnvironment processingEnvironment)72   public IncrementalExtensionType incrementalType(ProcessingEnvironment processingEnvironment) {
73     return IncrementalExtensionType.ISOLATING;
74   }
75 
76   @Override
generateClass( Context context, String className, String classToExtend, boolean isFinal)77   public String generateClass(
78       Context context, String className, String classToExtend, boolean isFinal) {
79     return new Generator(context, className, classToExtend, isFinal).generate();
80   }
81 
82   private static final class Generator {
83     private final Context context;
84     private final String className;
85     private final String classToExtend;
86     private final boolean isFinal;
87     private final ImmutableList<PropertyMirror> propertyMirrors;
88     private final ImmutableList<TypeVariableName> typeVariableNames;
89     private final ProxyGenerator proxyGenerator;
90 
Generator(Context context, String className, String classToExtend, boolean isFinal)91     Generator(Context context, String className, String classToExtend, boolean isFinal) {
92       this.context = context;
93       this.className = className;
94       this.classToExtend = classToExtend;
95       this.isFinal = isFinal;
96 
97       this.propertyMirrors =
98           context.propertyTypes().entrySet().stream()
99               .map(
100                   entry ->
101                       new PropertyMirror(
102                           /* type= */ entry.getValue(),
103                           /* name= */ entry.getKey(),
104                           /* method= */ context
105                               .properties()
106                               .get(entry.getKey())
107                               .getSimpleName()
108                               .toString()))
109               .collect(toImmutableList());
110       this.typeVariableNames =
111           context.autoValueClass().getTypeParameters().stream()
112               .map(TypeVariableName::get)
113               .collect(toImmutableList());
114 
115       TypeName classTypeName =
116           getClassTypeName(ClassName.get(context.packageName(), className), typeVariableNames);
117       this.proxyGenerator =
118           new ProxyGenerator(
119               classTypeName, typeVariableNames, propertyMirrors, buildSerializersMap());
120     }
121 
generate()122     private String generate() {
123       ClassName superclass = ClassName.get(context.packageName(), classToExtend);
124       Optional<AnnotationSpec> generatedAnnotationSpec =
125           GeneratedAnnotationSpecs.generatedAnnotationSpec(
126               context.processingEnvironment().getElementUtils(),
127               context.processingEnvironment().getSourceVersion(),
128               SerializableAutoValueExtension.class);
129 
130       TypeSpec.Builder subclass =
131           TypeSpec.classBuilder(className)
132               .superclass(getClassTypeName(superclass, typeVariableNames))
133               .addTypeVariables(typeVariableNames)
134               .addModifiers(isFinal ? Modifier.FINAL : Modifier.ABSTRACT)
135               .addMethod(constructor())
136               .addMethod(writeReplace())
137               .addType(proxyGenerator.generate());
138       generatedAnnotationSpec.ifPresent(subclass::addAnnotation);
139 
140       return JavaFile.builder(context.packageName(), subclass.build()).build().toString();
141     }
142 
143     /** Creates a constructor that calls super with all the AutoValue fields. */
constructor()144     private MethodSpec constructor() {
145       MethodSpec.Builder constructor =
146           MethodSpec.constructorBuilder()
147               .addStatement(
148                   "super($L)",
149                   propertyMirrors.stream().map(PropertyMirror::getName).collect(joining(", ")));
150 
151       for (PropertyMirror propertyMirror : propertyMirrors) {
152         constructor.addParameter(TypeName.get(propertyMirror.getType()), propertyMirror.getName());
153       }
154 
155       return constructor.build();
156     }
157 
158     /**
159      * Creates an implementation of writeReplace that delegates serialization to its inner Proxy
160      * class.
161      */
writeReplace()162     private MethodSpec writeReplace() {
163       ImmutableList<CodeBlock> properties =
164           propertyMirrors.stream()
165               .map(propertyMirror -> CodeBlock.of("$L()", propertyMirror.getMethod()))
166               .collect(toImmutableList());
167 
168       return MethodSpec.methodBuilder("writeReplace")
169           .returns(Object.class)
170           .addStatement(
171               "return new $T($L)",
172               getClassTypeName(
173                   ClassName.get(
174                       context.packageName(),
175                       className,
176                       SerializableAutoValueExtension.ProxyGenerator.PROXY_CLASS_NAME),
177                   typeVariableNames),
178               CodeBlock.join(properties, ", "))
179           .build();
180     }
181 
buildSerializersMap()182     private ImmutableMap<Equivalence.Wrapper<TypeMirror>, Serializer> buildSerializersMap() {
183       SerializerFactory factory =
184           SerializerFactoryLoader.getFactory(context.processingEnvironment());
185       return propertyMirrors.stream()
186           .map(PropertyMirror::getType)
187           .map(MoreTypes.equivalence()::wrap)
188           .distinct()
189           .collect(
190               toImmutableMap(
191                   Function.identity(), equivalence -> factory.getSerializer(equivalence.get())));
192     }
193 
194     /** Adds type parameters to the given {@link ClassName}, if available. */
getClassTypeName( ClassName className, List<TypeVariableName> typeVariableNames)195     private static TypeName getClassTypeName(
196         ClassName className, List<TypeVariableName> typeVariableNames) {
197       return typeVariableNames.isEmpty()
198           ? className
199           : ParameterizedTypeName.get(className, typeVariableNames.toArray(new TypeName[] {}));
200     }
201   }
202 
203   /** A generator of nested serializable Proxy classes. */
204   private static final class ProxyGenerator {
205     private static final String PROXY_CLASS_NAME = "Proxy$";
206 
207     private final TypeName outerClassTypeName;
208     private final ImmutableList<TypeVariableName> typeVariableNames;
209     private final ImmutableList<PropertyMirror> propertyMirrors;
210     private final ImmutableMap<Equivalence.Wrapper<TypeMirror>, Serializer> serializersMap;
211 
ProxyGenerator( TypeName outerClassTypeName, ImmutableList<TypeVariableName> typeVariableNames, ImmutableList<PropertyMirror> propertyMirrors, ImmutableMap<Equivalence.Wrapper<TypeMirror>, Serializer> serializersMap)212     ProxyGenerator(
213         TypeName outerClassTypeName,
214         ImmutableList<TypeVariableName> typeVariableNames,
215         ImmutableList<PropertyMirror> propertyMirrors,
216         ImmutableMap<Equivalence.Wrapper<TypeMirror>, Serializer> serializersMap) {
217       this.outerClassTypeName = outerClassTypeName;
218       this.typeVariableNames = typeVariableNames;
219       this.propertyMirrors = propertyMirrors;
220       this.serializersMap = serializersMap;
221     }
222 
generate()223     private TypeSpec generate() {
224       TypeSpec.Builder proxy =
225           TypeSpec.classBuilder(PROXY_CLASS_NAME)
226               .addModifiers(Modifier.STATIC)
227               .addTypeVariables(typeVariableNames)
228               .addSuperinterface(Serializable.class)
229               .addField(serialVersionUid())
230               .addFields(properties())
231               .addMethod(constructor())
232               .addMethod(readResolve());
233 
234       return proxy.build();
235     }
236 
serialVersionUid()237     private static FieldSpec serialVersionUid() {
238       return FieldSpec.builder(
239               long.class, "serialVersionUID", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
240           .initializer("0")
241           .build();
242     }
243 
244     /** Maps each AutoValue property to a serializable type. */
properties()245     private List<FieldSpec> properties() {
246       return propertyMirrors.stream()
247           .map(
248               propertyMirror ->
249                   FieldSpec.builder(
250                           TypeName.get(
251                               serializersMap
252                                   .get(MoreTypes.equivalence().wrap(propertyMirror.getType()))
253                                   .proxyFieldType()),
254                           propertyMirror.getName(),
255                           Modifier.PRIVATE)
256                       .build())
257           .collect(toImmutableList());
258     }
259 
260     /** Creates a constructor that converts the AutoValue's properties to serializable values. */
constructor()261     private MethodSpec constructor() {
262       MethodSpec.Builder constructor = MethodSpec.constructorBuilder();
263 
264       for (PropertyMirror propertyMirror : propertyMirrors) {
265         Serializer serializer =
266             serializersMap.get(MoreTypes.equivalence().wrap(propertyMirror.getType()));
267         String name = propertyMirror.getName();
268 
269         constructor.addParameter(TypeName.get(propertyMirror.getType()), name);
270         constructor.addStatement(
271             CodeBlock.of("this.$L = $L", name, serializer.toProxy(CodeBlock.of(name))));
272       }
273 
274       return constructor.build();
275     }
276 
277     /**
278      * Creates an implementation of {@code readResolve} that returns the serializable values in the
279      * Proxy object back to their original types.
280      */
readResolve()281     private MethodSpec readResolve() {
282       return MethodSpec.methodBuilder("readResolve")
283           .returns(Object.class)
284           .addException(Exception.class)
285           .addStatement(
286               "return new $T($L)",
287               outerClassTypeName,
288               CodeBlock.join(
289                   propertyMirrors.stream().map(this::resolve).collect(toImmutableList()), ", "))
290           .build();
291     }
292 
293     /** Maps a serializable type back to its original AutoValue property. */
resolve(PropertyMirror propertyMirror)294     private CodeBlock resolve(PropertyMirror propertyMirror) {
295       return serializersMap
296           .get(MoreTypes.equivalence().wrap(propertyMirror.getType()))
297           .fromProxy(CodeBlock.of(propertyMirror.getName()));
298     }
299   }
300 
hasSerializableInterface(Context context)301   private static boolean hasSerializableInterface(Context context) {
302     final TypeMirror serializableTypeMirror =
303         context
304             .processingEnvironment()
305             .getElementUtils()
306             .getTypeElement(Serializable.class.getCanonicalName())
307             .asType();
308 
309     return context
310         .processingEnvironment()
311         .getTypeUtils()
312         .isAssignable(context.autoValueClass().asType(), serializableTypeMirror);
313   }
314 
hasSerializableAutoValueAnnotation(Context context)315   private static boolean hasSerializableAutoValueAnnotation(Context context) {
316     return context.autoValueClass().getAnnotationMirrors().stream()
317         .map(AnnotationMirror::getAnnotationType)
318         .map(MoreTypes::asTypeElement)
319         .map(TypeElement::getQualifiedName)
320         .anyMatch(name -> name.contentEquals(SERIALIZABLE_AUTO_VALUE_NAME));
321   }
322 }
323