All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.auto.value.extension.serializable.processor.SerializableAutoValueExtension Maven / Gradle / Ivy

There is a newer version: 1.11.0
Show newest version
/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.auto.value.extension.serializable.processor;

import static com.google.auto.value.extension.serializable.processor.ClassNames.SERIALIZABLE_AUTO_VALUE_NAME;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static java.util.stream.Collectors.joining;

import com.google.auto.common.GeneratedAnnotationSpecs;
import com.google.auto.common.MoreTypes;
import com.google.auto.service.AutoService;
import com.google.auto.value.extension.AutoValueExtension;
import com.google.auto.value.extension.serializable.serializer.SerializerFactoryLoader;
import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer;
import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory;
import com.google.common.base.Equivalence;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import java.io.Serializable;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;

/**
 * An AutoValue extension that enables classes with unserializable fields to be serializable.
 *
 * 

For this extension to work: * *

    *
  • The AutoValue class must implement {@link Serializable}. *
  • Unserializable fields in the AutoValue class must be supported by a {@link * com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension}. */ @AutoService(AutoValueExtension.class) public final class SerializableAutoValueExtension extends AutoValueExtension { @Override public boolean applicable(Context context) { return hasSerializableInterface(context) && hasSerializableAutoValueAnnotation(context); } @Override public IncrementalExtensionType incrementalType(ProcessingEnvironment processingEnvironment) { return IncrementalExtensionType.ISOLATING; } @Override public String generateClass( Context context, String className, String classToExtend, boolean isFinal) { return new Generator(context, className, classToExtend, isFinal).generate(); } private static final class Generator { private final Context context; private final String className; private final String classToExtend; private final boolean isFinal; private final ImmutableList propertyMirrors; private final ImmutableList typeVariableNames; private final ProxyGenerator proxyGenerator; Generator(Context context, String className, String classToExtend, boolean isFinal) { this.context = context; this.className = className; this.classToExtend = classToExtend; this.isFinal = isFinal; this.propertyMirrors = context.propertyTypes().entrySet().stream() .map( entry -> new PropertyMirror( /* type= */ entry.getValue(), /* name= */ entry.getKey(), /* method= */ context .properties() .get(entry.getKey()) .getSimpleName() .toString())) .collect(toImmutableList()); this.typeVariableNames = context.autoValueClass().getTypeParameters().stream() .map(TypeVariableName::get) .collect(toImmutableList()); TypeName classTypeName = getClassTypeName(ClassName.get(context.packageName(), className), typeVariableNames); this.proxyGenerator = new ProxyGenerator( classTypeName, typeVariableNames, propertyMirrors, buildSerializersMap()); } private String generate() { ClassName superclass = ClassName.get(context.packageName(), classToExtend); Optional generatedAnnotationSpec = GeneratedAnnotationSpecs.generatedAnnotationSpec( context.processingEnvironment().getElementUtils(), context.processingEnvironment().getSourceVersion(), SerializableAutoValueExtension.class); TypeSpec.Builder subclass = TypeSpec.classBuilder(className) .superclass(getClassTypeName(superclass, typeVariableNames)) .addTypeVariables(typeVariableNames) .addModifiers(isFinal ? Modifier.FINAL : Modifier.ABSTRACT) .addMethod(constructor()) .addMethod(writeReplace()) .addType(proxyGenerator.generate()); generatedAnnotationSpec.ifPresent(subclass::addAnnotation); return JavaFile.builder(context.packageName(), subclass.build()).build().toString(); } /** Creates a constructor that calls super with all the AutoValue fields. */ private MethodSpec constructor() { MethodSpec.Builder constructor = MethodSpec.constructorBuilder() .addStatement( "super($L)", propertyMirrors.stream().map(PropertyMirror::getName).collect(joining(", "))); for (PropertyMirror propertyMirror : propertyMirrors) { constructor.addParameter(TypeName.get(propertyMirror.getType()), propertyMirror.getName()); } return constructor.build(); } /** * Creates an implementation of writeReplace that delegates serialization to its inner Proxy * class. */ private MethodSpec writeReplace() { ImmutableList properties = propertyMirrors.stream() .map(propertyMirror -> CodeBlock.of("$L()", propertyMirror.getMethod())) .collect(toImmutableList()); return MethodSpec.methodBuilder("writeReplace") .returns(Object.class) .addStatement( "return new $T($L)", getClassTypeName( ClassName.get( context.packageName(), className, SerializableAutoValueExtension.ProxyGenerator.PROXY_CLASS_NAME), typeVariableNames), CodeBlock.join(properties, ", ")) .build(); } private ImmutableMap, Serializer> buildSerializersMap() { SerializerFactory factory = SerializerFactoryLoader.getFactory(context.processingEnvironment()); return propertyMirrors.stream() .map(PropertyMirror::getType) .map(MoreTypes.equivalence()::wrap) .distinct() .collect( toImmutableMap( Function.identity(), equivalence -> factory.getSerializer(equivalence.get()))); } /** Adds type parameters to the given {@link ClassName}, if available. */ private static TypeName getClassTypeName( ClassName className, List typeVariableNames) { return typeVariableNames.isEmpty() ? className : ParameterizedTypeName.get(className, typeVariableNames.toArray(new TypeName[] {})); } } /** A generator of nested serializable Proxy classes. */ private static final class ProxyGenerator { private static final String PROXY_CLASS_NAME = "Proxy$"; private final TypeName outerClassTypeName; private final ImmutableList typeVariableNames; private final ImmutableList propertyMirrors; private final ImmutableMap, Serializer> serializersMap; ProxyGenerator( TypeName outerClassTypeName, ImmutableList typeVariableNames, ImmutableList propertyMirrors, ImmutableMap, Serializer> serializersMap) { this.outerClassTypeName = outerClassTypeName; this.typeVariableNames = typeVariableNames; this.propertyMirrors = propertyMirrors; this.serializersMap = serializersMap; } private TypeSpec generate() { TypeSpec.Builder proxy = TypeSpec.classBuilder(PROXY_CLASS_NAME) .addModifiers(Modifier.STATIC) .addTypeVariables(typeVariableNames) .addSuperinterface(Serializable.class) .addField(serialVersionUid()) .addFields(properties()) .addMethod(constructor()) .addMethod(readResolve()); return proxy.build(); } private static FieldSpec serialVersionUid() { return FieldSpec.builder( long.class, "serialVersionUID", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) .initializer("0") .build(); } /** Maps each AutoValue property to a serializable type. */ private List properties() { return propertyMirrors.stream() .map( propertyMirror -> FieldSpec.builder( TypeName.get( serializersMap .get(MoreTypes.equivalence().wrap(propertyMirror.getType())) .proxyFieldType()), propertyMirror.getName(), Modifier.PRIVATE) .build()) .collect(toImmutableList()); } /** Creates a constructor that converts the AutoValue's properties to serializable values. */ private MethodSpec constructor() { MethodSpec.Builder constructor = MethodSpec.constructorBuilder(); for (PropertyMirror propertyMirror : propertyMirrors) { Serializer serializer = serializersMap.get(MoreTypes.equivalence().wrap(propertyMirror.getType())); String name = propertyMirror.getName(); constructor.addParameter(TypeName.get(propertyMirror.getType()), name); constructor.addStatement( CodeBlock.of("this.$L = $L", name, serializer.toProxy(CodeBlock.of(name)))); } return constructor.build(); } /** * Creates an implementation of {@code readResolve} that returns the serializable values in the * Proxy object back to their original types. */ private MethodSpec readResolve() { return MethodSpec.methodBuilder("readResolve") .returns(Object.class) .addException(Exception.class) .addStatement( "return new $T($L)", outerClassTypeName, CodeBlock.join( propertyMirrors.stream().map(this::resolve).collect(toImmutableList()), ", ")) .build(); } /** Maps a serializable type back to its original AutoValue property. */ private CodeBlock resolve(PropertyMirror propertyMirror) { return serializersMap .get(MoreTypes.equivalence().wrap(propertyMirror.getType())) .fromProxy(CodeBlock.of(propertyMirror.getName())); } } private static boolean hasSerializableInterface(Context context) { final TypeMirror serializableTypeMirror = context .processingEnvironment() .getElementUtils() .getTypeElement(Serializable.class.getCanonicalName()) .asType(); return context .processingEnvironment() .getTypeUtils() .isAssignable(context.autoValueClass().asType(), serializableTypeMirror); } private static boolean hasSerializableAutoValueAnnotation(Context context) { return context.autoValueClass().getAnnotationMirrors().stream() .map(AnnotationMirror::getAnnotationType) .map(MoreTypes::asTypeElement) .map(TypeElement::getQualifiedName) .anyMatch(name -> name.contentEquals(SERIALIZABLE_AUTO_VALUE_NAME)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy