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