diff options
author | alvinlao <alvinlao@google.com> | 2020-04-01 10:21:31 -0700 |
---|---|---|
committer | Chris Povirk <beigetangerine@gmail.com> | 2020-04-10 11:30:09 -0400 |
commit | f91d2fef64ffb10c0bf394c56e72b9c55754cf6d (patch) | |
tree | f67105c14371275e275a420559c2a8d3bd69e26e | |
parent | e4ab0e7a0d4a1505b8b83104231f35f5e45ccd64 (diff) | |
download | auto-f91d2fef64ffb10c0bf394c56e72b9c55754cf6d.tar.gz |
Release the SerializableAutoValue extension.
RELNOTES=Release the SerializableAutoValue extension.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=304211473
28 files changed, 2296 insertions, 3 deletions
diff --git a/value/annotations/pom.xml b/value/annotations/pom.xml index c284f2f0..d0d4a6bb 100644 --- a/value/annotations/pom.xml +++ b/value/annotations/pom.xml @@ -53,6 +53,7 @@ <includes> <include>com/google/auto/value/*</include> <include>com/google/auto/value/extension/memoized/*</include> + <include>com/google/auto/value/extension/serializable/*</include> </includes> </configuration> </plugin> diff --git a/value/pom.xml b/value/pom.xml index 8c36feec..ffcd9d74 100644 --- a/value/pom.xml +++ b/value/pom.xml @@ -122,7 +122,7 @@ <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> - <version>4.12</version> + <version>4.13</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> diff --git a/value/processor/pom.xml b/value/processor/pom.xml index da1c52a5..0aa9ec8e 100644 --- a/value/processor/pom.xml +++ b/value/processor/pom.xml @@ -119,6 +119,12 @@ <artifactId>compile-testing</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <version>3.1.0</version> + <scope>test</scope> + </dependency> </dependencies> <build> @@ -141,6 +147,8 @@ <includes> <include>com/google/auto/value/processor/**/*.java</include> <include>com/google/auto/value/extension/memoized/processor/**/*.java</include> + <include>com/google/auto/value/extension/serializable/processor/**/*.java</include> + <include>com/google/auto/value/extension/serializable/serializer/**/*.java</include> </includes> </configuration> </plugin> diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/SerializableAutoValue.java b/value/src/main/java/com/google/auto/value/extension/serializable/SerializableAutoValue.java new file mode 100644 index 00000000..b100e793 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/serializable/SerializableAutoValue.java @@ -0,0 +1,30 @@ +/* + * 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; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotates {@link com.google.auto.value.AutoValue @AutoValue} classes that implement {@link + * java.io.Serializable}. A serializable subclass is generated for classes with normally + * un-serializable fields like {@link java.util.Optional}. + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface SerializableAutoValue {} diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/processor/ClassNames.java b/value/src/main/java/com/google/auto/value/extension/serializable/processor/ClassNames.java new file mode 100644 index 00000000..96a95762 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/serializable/processor/ClassNames.java @@ -0,0 +1,24 @@ +/* + * 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; + +/** Names of classes that are referenced in /processor. */ +final class ClassNames { + static final String SERIALIZABLE_AUTO_VALUE_NAME = + "com.google.auto.value.extension.serializable.SerializableAutoValue"; + + private ClassNames() {} +} diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/processor/PropertyMirror.java b/value/src/main/java/com/google/auto/value/extension/serializable/processor/PropertyMirror.java new file mode 100644 index 00000000..966ce44a --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/serializable/processor/PropertyMirror.java @@ -0,0 +1,56 @@ +/* + * 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 javax.lang.model.type.TypeMirror; + +/** + * A POJO containing information about an AutoValue's property. + * + * <p>For example, given this AutoValue property: <code>abstract T getX();</code> + * + * <ol> + * <li>The type would be T. + * <li>The name would be x. + * <li>The method would be getX. + */ +final class PropertyMirror { + + private final TypeMirror type; + private final String name; + private final String method; + + PropertyMirror(TypeMirror type, String name, String method) { + this.type = type; + this.name = name; + this.method = method; + } + + /** Gets the AutoValue property's type. */ + TypeMirror getType() { + return type; + } + + /** Gets the AutoValue property's name. */ + String getName() { + return name; + } + + /** Gets the AutoValue property accessor method. */ + String getMethod() { + return method; + } +} diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java new file mode 100644 index 00000000..e8cfc270 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtension.java @@ -0,0 +1,310 @@ +/* + * 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.AutoValueExtension.Context; +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.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 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. + * + * <p>For this extension to work: + * + * <ul> + * <li>The AutoValue class must implement {@link Serializable}. + * <li>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 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<PropertyMirror> propertyMirrors; + private final ImmutableList<TypeVariableName> 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<AnnotationSpec> 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<CodeBlock> 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<TypeMirror, Serializer> buildSerializersMap() { + SerializerFactory factory = + SerializerFactoryLoader.getFactory(context.processingEnvironment()); + return propertyMirrors.stream() + .collect( + toImmutableMap( + PropertyMirror::getType, + propertyMirror -> factory.getSerializer(propertyMirror.getType()))); + } + + /** Adds type parameters to the given {@link ClassName}, if available. */ + private static TypeName getClassTypeName( + ClassName className, List<TypeVariableName> 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<TypeVariableName> typeVariableNames; + private final ImmutableList<PropertyMirror> propertyMirrors; + private final ImmutableMap<TypeMirror, Serializer> serializersMap; + + ProxyGenerator( + TypeName outerClassTypeName, + ImmutableList<TypeVariableName> typeVariableNames, + ImmutableList<PropertyMirror> propertyMirrors, + ImmutableMap<TypeMirror, 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<FieldSpec> properties() { + return propertyMirrors.stream() + .map( + propertyMirror -> + FieldSpec.builder( + TypeName.get( + serializersMap.get(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(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(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)); + } +} diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoader.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoader.java new file mode 100644 index 00000000..12984b05 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoader.java @@ -0,0 +1,60 @@ +/* + * 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.serializer; + +import com.google.auto.value.extension.serializable.serializer.impl.SerializerFactoryImpl; +import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension; +import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory; +import com.google.auto.value.processor.SimpleServiceLoader; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; +import javax.annotation.processing.ProcessingEnvironment; +import javax.tools.Diagnostic; + +/** + * Builds a {@link SerializerFactory} populated with discovered {@link SerializerExtension} + * instances. + */ +public final class SerializerFactoryLoader { + + /** + * Returns a {@link SerializerFactory} with {@link SerializerExtension} instances provided by the + * {@link java.util.ServiceLoader}. + */ + public static SerializerFactory getFactory(ProcessingEnvironment processingEnv) { + return new SerializerFactoryImpl(loadExtensions(processingEnv), processingEnv); + } + + private static ImmutableList<SerializerExtension> loadExtensions( + ProcessingEnvironment processingEnv) { + try { + return ImmutableList.copyOf( + SimpleServiceLoader.load( + SerializerExtension.class, SerializerFactoryLoader.class.getClassLoader())); + } catch (Throwable t) { + processingEnv + .getMessager() + .printMessage( + Diagnostic.Kind.ERROR, + "An exception occurred while looking for SerializerExtensions. No extensions will" + + " function.\n" + + Throwables.getStackTraceAsString(t)); + return ImmutableList.of(); + } + } + + private SerializerFactoryLoader() {} +} diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/IdentitySerializerFactory.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/IdentitySerializerFactory.java new file mode 100644 index 00000000..83b86341 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/IdentitySerializerFactory.java @@ -0,0 +1,60 @@ +/* + * 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.serializer.impl; + +import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer; +import com.squareup.javapoet.CodeBlock; +import javax.lang.model.type.TypeMirror; + +/** Creates identity {@link Serializer} instances. */ +public final class IdentitySerializerFactory { + + /** Returns a {@link Serializer} that leaves the type as is. */ + public static Serializer getSerializer(TypeMirror typeMirror) { + return new IdentitySerializer(typeMirror); + } + + private static class IdentitySerializer implements Serializer { + + private final TypeMirror typeMirror; + + IdentitySerializer(TypeMirror typeMirror) { + this.typeMirror = typeMirror; + } + + @Override + public TypeMirror proxyFieldType() { + return typeMirror; + } + + @Override + public CodeBlock toProxy(CodeBlock expression) { + return expression; + } + + @Override + public CodeBlock fromProxy(CodeBlock expression) { + return expression; + } + + @Override + public boolean isIdentity() { + return true; + } + } + + private IdentitySerializerFactory() {} +} diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java new file mode 100644 index 00000000..7ff4f19d --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtension.java @@ -0,0 +1,120 @@ +/* + * 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.serializer.impl; + +import com.google.auto.common.MoreTypes; +import com.google.auto.service.AutoService; +import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer; +import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension; +import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory; +import com.google.auto.value.extension.serializable.serializer.runtime.FunctionWithExceptions; +import com.google.common.collect.ImmutableList; +import com.squareup.javapoet.CodeBlock; +import java.util.Optional; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +/** + * A {@link SerializerExtension} that deserializes objects inside an {@link ImmutableList}. + * + * <p>Enables unserializable objects inside an ImmutableList to be serializable. + */ +@AutoService(SerializerExtension.class) +public final class ImmutableListSerializerExtension implements SerializerExtension { + + public ImmutableListSerializerExtension() {} + + @Override + public Optional<Serializer> getSerializer( + TypeMirror typeMirror, SerializerFactory factory, ProcessingEnvironment processingEnv) { + if (!isImmutableList(typeMirror)) { + return Optional.empty(); + } + + // Extract the T of ImmutableList<T>. + TypeMirror containedType = getContainedType(typeMirror); + Serializer containedTypeSerializer = factory.getSerializer(containedType); + + // We don't need this serializer if the T of ImmutableList<T> is serializable. + if (containedTypeSerializer.isIdentity()) { + return Optional.empty(); + } + + return Optional.of(new ImmutableListSerializer(containedTypeSerializer, processingEnv)); + } + + private static class ImmutableListSerializer implements Serializer { + + private final Serializer containedTypeSerializer; + private final ProcessingEnvironment processingEnv; + + ImmutableListSerializer( + Serializer containedTypeSerializer, ProcessingEnvironment processingEnv) { + this.containedTypeSerializer = containedTypeSerializer; + this.processingEnv = processingEnv; + } + + @Override + public TypeMirror proxyFieldType() { + TypeElement immutableListTypeElement = + processingEnv.getElementUtils().getTypeElement(ImmutableList.class.getCanonicalName()); + TypeMirror containedProxyType = containedTypeSerializer.proxyFieldType(); + return processingEnv + .getTypeUtils() + .getDeclaredType(immutableListTypeElement, containedProxyType); + } + + @Override + public CodeBlock toProxy(CodeBlock expression) { + CodeBlock element = CodeBlock.of("value$$"); + return CodeBlock.of( + "$L.stream().map($T.wrapper($L -> $L)).collect($T.toImmutableList())", + expression, + FunctionWithExceptions.class, + element, + containedTypeSerializer.toProxy(element), + ImmutableList.class); + } + + @Override + public CodeBlock fromProxy(CodeBlock expression) { + CodeBlock element = CodeBlock.of("value$$"); + return CodeBlock.of( + "$L.stream().map($T.wrapper($L -> $L)).collect($T.toImmutableList())", + expression, + FunctionWithExceptions.class, + element, + containedTypeSerializer.fromProxy(element), + ImmutableList.class); + } + } + + private static boolean isImmutableList(TypeMirror type) { + if (type.getKind() != TypeKind.DECLARED) { + return false; + } + + return MoreTypes.asTypeElement(type) + .getQualifiedName() + .contentEquals("com.google.common.collect.ImmutableList"); + } + + private static TypeMirror getContainedType(TypeMirror type) { + return MoreTypes.asDeclared(type).getTypeArguments().get(0); + } +} diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java new file mode 100644 index 00000000..9d571e3b --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtension.java @@ -0,0 +1,166 @@ +/* + * 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.serializer.impl; + +import com.google.auto.common.MoreTypes; +import com.google.auto.service.AutoService; +import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer; +import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension; +import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory; +import com.google.auto.value.extension.serializable.serializer.runtime.FunctionWithExceptions; +import com.google.common.collect.ImmutableMap; +import com.squareup.javapoet.CodeBlock; +import java.util.Optional; +import java.util.function.Function; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +/** + * A {@link SerializerExtension} that deserializes objects inside an {@link ImmutableMap}. + * + * <p>Enables unserializable objects inside an ImmutableMap to be serializable. + */ +@AutoService(SerializerExtension.class) +public final class ImmutableMapSerializerExtension implements SerializerExtension { + + public ImmutableMapSerializerExtension() {} + + @Override + public Optional<Serializer> getSerializer( + TypeMirror typeMirror, SerializerFactory factory, ProcessingEnvironment processingEnv) { + if (!isImmutableMap(typeMirror)) { + return Optional.empty(); + } + + // Extract the K, V of ImmutableMap<K, V>. + TypeMirror keyType = getKeyType(typeMirror); + TypeMirror valueType = getValueType(typeMirror); + Serializer keyTypeSerializer = factory.getSerializer(keyType); + Serializer valueTypeSerializer = factory.getSerializer(valueType); + + // We don't need this serializer if the K and V of ImmutableMap<K, V> are serializable. + if (keyTypeSerializer.isIdentity() && valueTypeSerializer.isIdentity()) { + return Optional.empty(); + } + + return Optional.of( + new ImmutableMapSerializer( + keyType, valueType, keyTypeSerializer, valueTypeSerializer, processingEnv)); + } + + private static class ImmutableMapSerializer implements Serializer { + + private final TypeMirror keyType; + private final TypeMirror valueType; + private final TypeMirror keyProxyType; + private final TypeMirror valueProxyType; + private final Serializer keyTypeSerializer; + private final Serializer valueTypeSerializer; + private final ProcessingEnvironment processingEnv; + + ImmutableMapSerializer( + TypeMirror keyType, + TypeMirror valueType, + Serializer keyTypeSerializer, + Serializer valueTypeSerializer, + ProcessingEnvironment processingEnv) { + this.keyType = keyType; + this.valueType = valueType; + this.keyProxyType = keyTypeSerializer.proxyFieldType(); + this.valueProxyType = valueTypeSerializer.proxyFieldType(); + this.keyTypeSerializer = keyTypeSerializer; + this.valueTypeSerializer = valueTypeSerializer; + this.processingEnv = processingEnv; + } + + @Override + public TypeMirror proxyFieldType() { + TypeElement immutableMapTypeElement = + processingEnv.getElementUtils().getTypeElement(ImmutableMap.class.getCanonicalName()); + return processingEnv + .getTypeUtils() + .getDeclaredType(immutableMapTypeElement, keyProxyType, valueProxyType); + } + + @Override + public CodeBlock toProxy(CodeBlock expression) { + return CodeBlock.of( + "$L.entrySet().stream().collect($T.toImmutableMap($L, $L))", + expression, + ImmutableMap.class, + generateKeyMapFunction(keyType, keyProxyType, keyTypeSerializer::toProxy), + generateValueMapFunction(valueType, valueProxyType, valueTypeSerializer::toProxy)); + } + + @Override + public CodeBlock fromProxy(CodeBlock expression) { + return CodeBlock.of( + "$L.entrySet().stream().collect($T.toImmutableMap($L, $L))", + expression, + ImmutableMap.class, + generateKeyMapFunction(keyProxyType, keyType, keyTypeSerializer::fromProxy), + generateValueMapFunction(valueProxyType, valueType, valueTypeSerializer::fromProxy)); + } + + private static CodeBlock generateKeyMapFunction( + TypeMirror originalType, + TypeMirror transformedType, + Function<CodeBlock, CodeBlock> proxyMap) { + CodeBlock element = CodeBlock.of("element$$"); + return CodeBlock.of( + "value$$ -> $T.<$T, $T>wrapper($L -> $L).apply(value$$.getKey())", + FunctionWithExceptions.class, + originalType, + transformedType, + element, + proxyMap.apply(element)); + } + + private static CodeBlock generateValueMapFunction( + TypeMirror originalType, + TypeMirror transformedType, + Function<CodeBlock, CodeBlock> proxyMap) { + CodeBlock element = CodeBlock.of("element$$"); + return CodeBlock.of( + "value$$ -> $T.<$T, $T>wrapper($L -> $L).apply(value$$.getValue())", + FunctionWithExceptions.class, + originalType, + transformedType, + element, + proxyMap.apply(element)); + } + } + + private static boolean isImmutableMap(TypeMirror type) { + if (type.getKind() != TypeKind.DECLARED) { + return false; + } + + return MoreTypes.asTypeElement(type) + .getQualifiedName() + .contentEquals("com.google.common.collect.ImmutableMap"); + } + + private static TypeMirror getKeyType(TypeMirror type) { + return MoreTypes.asDeclared(type).getTypeArguments().get(0); + } + + private static TypeMirror getValueType(TypeMirror type) { + return MoreTypes.asDeclared(type).getTypeArguments().get(1); + } +} diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/OptionalSerializerExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/OptionalSerializerExtension.java new file mode 100644 index 00000000..a5025a8f --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/OptionalSerializerExtension.java @@ -0,0 +1,106 @@ +/* + * 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.serializer.impl; + +import com.google.auto.common.MoreTypes; +import com.google.auto.service.AutoService; +import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer; +import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension; +import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory; +import com.squareup.javapoet.CodeBlock; +import java.util.Optional; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +/** + * A {@link SerializerExtension} that enables {@link Optional} types to be serialized. + * + * <p>The type argument {@code T} of {@code Optional<T>} is queried against the {@link + * SerializerFactory}. + */ +@AutoService(SerializerExtension.class) +public final class OptionalSerializerExtension implements SerializerExtension { + + public OptionalSerializerExtension() {} + + /** Creates a {@link Serializer} that supports {@link Optional} types. */ + @Override + public Optional<Serializer> getSerializer( + TypeMirror typeMirror, SerializerFactory factory, ProcessingEnvironment processingEnv) { + if (!isOptional(typeMirror)) { + return Optional.empty(); + } + + // Extract the T of Optional<T>. + TypeMirror containedType = getContainedType(typeMirror); + Serializer containedTypeSerializer = factory.getSerializer(containedType); + + return Optional.of(new OptionalSerializer(containedTypeSerializer)); + } + + private static class OptionalSerializer implements Serializer { + + private final Serializer containedTypeSerializer; + + OptionalSerializer(Serializer containedTypeSerializer) { + this.containedTypeSerializer = containedTypeSerializer; + } + + @Override + public TypeMirror proxyFieldType() { + // If this is an Optional<String> then the proxy field type is String. + // If this is an Optional<Foo>, and the proxy field type for Foo is Bar, then the proxy field + // type for Optional<Foo> is Bar. + return containedTypeSerializer.proxyFieldType(); + } + + @Override + public CodeBlock toProxy(CodeBlock expression) { + return CodeBlock.of( + "$L.isPresent() ? $L : null", + expression, + containedTypeSerializer.toProxy(CodeBlock.of("$L.get()", expression))); + } + + @Override + public CodeBlock fromProxy(CodeBlock expression) { + return CodeBlock.of( + "$T.ofNullable($L == null ? null : $L)", + Optional.class, + expression, + containedTypeSerializer.fromProxy(expression)); + } + } + + /** Checks if the given type is an {@link Optional}. */ + private static boolean isOptional(TypeMirror type) { + if (type.getKind() != TypeKind.DECLARED) { + return false; + } + + return MoreTypes.asTypeElement(type).getQualifiedName().contentEquals("java.util.Optional"); + } + + /** + * Gets the given type's first type argument. + * + * <p>Returns the {@code T} in {@code Optional<T>}. + */ + private static TypeMirror getContainedType(TypeMirror type) { + return MoreTypes.asDeclared(type).getTypeArguments().get(0); + } +} diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java new file mode 100644 index 00000000..57741f91 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImpl.java @@ -0,0 +1,48 @@ +/* + * 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.serializer.impl; + +import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer; +import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension; +import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory; +import com.google.common.collect.ImmutableList; +import java.util.Optional; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.type.TypeMirror; + +/** A concrete implementation of {@link SerializerFactory}. */ +public final class SerializerFactoryImpl implements SerializerFactory { + + private final ImmutableList<SerializerExtension> extensions; + private final ProcessingEnvironment env; + + public SerializerFactoryImpl( + ImmutableList<SerializerExtension> extensions, ProcessingEnvironment env) { + this.extensions = extensions; + this.env = env; + } + + @Override + public Serializer getSerializer(TypeMirror typeMirror) { + for (SerializerExtension extension : extensions) { + Optional<Serializer> serializer = extension.getSerializer(typeMirror, this, env); + if (serializer.isPresent()) { + return serializer.get(); + } + } + return IdentitySerializerFactory.getSerializer(typeMirror); + } +} diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/Serializer.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/Serializer.java new file mode 100644 index 00000000..96b9308c --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/Serializer.java @@ -0,0 +1,40 @@ +/* + * 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.serializer.interfaces; + +import com.squareup.javapoet.CodeBlock; +import javax.lang.model.type.TypeMirror; + +/** + * A Serializer, at compile time, generates code to map an unserializable type to a serializable + * type. It also generates the reverse code to re-create the original type. + */ +public interface Serializer { + + /** The proxy type the original unserializable type will be mapped to. */ + TypeMirror proxyFieldType(); + + /** Creates an expression that converts the original type to the proxy type. */ + CodeBlock toProxy(CodeBlock expression); + + /** Creates an expression that converts the proxy type back to the original type. */ + CodeBlock fromProxy(CodeBlock expression); + + /** Returns true if this is an identity {@link Serializer}. */ + default boolean isIdentity() { + return false; + } +} diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerExtension.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerExtension.java new file mode 100644 index 00000000..6e38b9b7 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerExtension.java @@ -0,0 +1,51 @@ +/* + * 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.serializer.interfaces; + +import java.util.Optional; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.type.TypeMirror; + +/** + * A SerializerExtension allows unserializable types to be serialized by SerializableAutoValue. + * + * <p>Extensions are discovered at compile time using the {@link java.util.ServiceLoader} APIs, + * allowing them to run without any additional annotations. To be found by {@code ServiceLoader}, an + * extension class must be public with a public no-arg constructor, and its fully-qualified name + * must appear in a file called {@code + * META-INF/services/com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension} + * in a jar that is on the compiler's {@code -classpath} or {@code -processorpath}. + * + * <p>When SerializableAutoValue maps each field in an AutoValue to a serializable proxy object, it + * asks each SerializerExtension whether it can generate code to make the given type serializable. A + * SerializerExtension replies that it can by returning a non-empty {@link Serializer}. + * + * <p>A SerializerExtension is also provided with a SerializerFactory, which it can use to query + * nested types. + */ +public interface SerializerExtension { + + /** + * Returns a {@link Serializer} if this {@link SerializerExtension} applies to the given {@code + * type}. Otherwise, {@code Optional.empty} is returned. + * + * @param type the type being serialized + * @param factory a {@link SerializerFactory} that can be used to serialize nested types + * @param processingEnv the processing environment provided by the annotation processing framework + */ + Optional<Serializer> getSerializer( + TypeMirror type, SerializerFactory factory, ProcessingEnvironment processingEnv); +} diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java new file mode 100644 index 00000000..d05c88b2 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/interfaces/SerializerFactory.java @@ -0,0 +1,29 @@ +/* + * 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.serializer.interfaces; + +import javax.lang.model.type.TypeMirror; + +/** + * A factory that returns a {@link Serializer} for any given {@link TypeMirror}. + * + * <p>Defaults to an identity serializer if no SerializerExtensions are suitable. + */ +public interface SerializerFactory { + + /** Returns a {@link Serializer} for the given {@link TypeMirror}. */ + Serializer getSerializer(TypeMirror type); +} diff --git a/value/src/main/java/com/google/auto/value/extension/serializable/serializer/runtime/FunctionWithExceptions.java b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/runtime/FunctionWithExceptions.java new file mode 100644 index 00000000..93aa6009 --- /dev/null +++ b/value/src/main/java/com/google/auto/value/extension/serializable/serializer/runtime/FunctionWithExceptions.java @@ -0,0 +1,41 @@ +/* + * 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.serializer.runtime; + +import java.util.function.Function; + +/** A utility for lambdas that throw exceptions. */ +public final class FunctionWithExceptions { + + /** Creates a wrapper for lambdas that converts checked exceptions to runtime exceptions. */ + public static <I, O> Function<I, O> wrapper(FunctionWithException<I, O> fe) { + return arg -> { + try { + return fe.apply(arg); + } catch (Exception e) { + throw new RuntimeException(e); + } + }; + } + + /** A function that can throw an exception. */ + @FunctionalInterface + public interface FunctionWithException<I, O> { + O apply(I i) throws Exception; + } + + private FunctionWithExceptions() {} +} diff --git a/value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java b/value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java index 5f2e47b2..55f9ae4e 100644 --- a/value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java +++ b/value/src/main/java/com/google/auto/value/processor/SimpleServiceLoader.java @@ -39,10 +39,10 @@ import java.util.ServiceConfigurationError; * @see <a href="https://github.com/google/auto/issues/718">Issue #718</a> * @see <a href="https://bugs.openjdk.java.net/browse/JDK-8156014">JDK-8156014</a> */ -final class SimpleServiceLoader { +public final class SimpleServiceLoader { private SimpleServiceLoader() {} - static <T> ImmutableList<T> load(Class<? extends T> service, ClassLoader loader) { + public static <T> ImmutableList<T> load(Class<? extends T> service, ClassLoader loader) { String resourceName = "META-INF/services/" + service.getName(); List<URL> resourceUrls; try { diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java new file mode 100644 index 00000000..9974d512 --- /dev/null +++ b/value/src/test/java/com/google/auto/value/extension/serializable/processor/SerializableAutoValueExtensionTest.java @@ -0,0 +1,387 @@ +/* + * 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.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; +import static org.junit.Assert.assertThrows; + +import com.google.auto.value.AutoValue; +import com.google.auto.value.extension.serializable.SerializableAutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.testing.SerializableTester; +import java.io.ByteArrayOutputStream; +import java.io.NotSerializableException; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Optional; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class SerializableAutoValueExtensionTest { + private static final String A = "a"; + private static final int B = 1; + private static final String C = "c"; + private static final int D = 2; + + @SerializableAutoValue + @AutoValue + abstract static class DummySerializableAutoValue implements Serializable { + // Primitive fields + abstract String a(); + + abstract int b(); + + // Optional fields + abstract Optional<String> optionalC(); + + abstract Optional<Integer> optionalD(); + + static DummySerializableAutoValue.Builder builder() { + return new AutoValue_SerializableAutoValueExtensionTest_DummySerializableAutoValue.Builder(); + } + + @AutoValue.Builder + abstract static class Builder { + abstract DummySerializableAutoValue.Builder setA(String value); + + abstract DummySerializableAutoValue.Builder setB(int value); + + abstract DummySerializableAutoValue.Builder setOptionalC(String value); + + abstract DummySerializableAutoValue.Builder setOptionalD(int value); + + abstract DummySerializableAutoValue build(); + } + } + + @Test + public void allFieldsAreSet_noEmpty() { + DummySerializableAutoValue autoValue = + DummySerializableAutoValue.builder() + .setA(A) + .setB(B) + .setOptionalC(C) + .setOptionalD(D) + .build(); + + assertThat(autoValue.a()).isEqualTo(A); + assertThat(autoValue.b()).isEqualTo(B); + assertThat(autoValue.optionalC()).hasValue(C); + assertThat(autoValue.optionalD()).hasValue(D); + } + + @Test + public void allFieldsAreSet_withMixedEmpty() { + DummySerializableAutoValue autoValue = + DummySerializableAutoValue.builder().setA(A).setB(B).setOptionalC(C).build(); + + assertThat(autoValue.a()).isEqualTo(A); + assertThat(autoValue.b()).isEqualTo(B); + assertThat(autoValue.optionalC()).hasValue(C); + assertThat(autoValue.optionalD()).isEmpty(); + } + + @Test + public void allFieldsAreSet_withEmpty() { + DummySerializableAutoValue autoValue = + DummySerializableAutoValue.builder().setA(A).setB(B).build(); + + assertThat(autoValue.a()).isEqualTo(A); + assertThat(autoValue.b()).isEqualTo(B); + assertThat(autoValue.optionalC()).isEmpty(); + assertThat(autoValue.optionalD()).isEmpty(); + } + + @Test + public void allFieldsAreSerialized_noEmpty() { + DummySerializableAutoValue autoValue = + DummySerializableAutoValue.builder() + .setA(A) + .setB(B) + .setOptionalC(C) + .setOptionalD(D) + .build(); + + DummySerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue); + + assertThat(actualAutoValue).isEqualTo(autoValue); + } + + @Test + public void allFieldsAreSerialized_withEmpty() { + DummySerializableAutoValue autoValue = + DummySerializableAutoValue.builder().setA(A).setB(B).build(); + + DummySerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue); + + assertThat(actualAutoValue).isEqualTo(autoValue); + } + + @Test + public void allFieldsAreSerialized_withMixedEmpty() { + DummySerializableAutoValue autoValue = + DummySerializableAutoValue.builder().setA(A).setB(B).setOptionalC(C).build(); + + DummySerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue); + + assertThat(actualAutoValue).isEqualTo(autoValue); + } + + @SerializableAutoValue + @AutoValue + abstract static class PrefixSerializableAutoValue implements Serializable { + // Primitive fields + abstract String getA(); + + abstract boolean isB(); + + // Optional fields + abstract Optional<String> getC(); + + abstract Optional<Boolean> getD(); + + static PrefixSerializableAutoValue.Builder builder() { + return new AutoValue_SerializableAutoValueExtensionTest_PrefixSerializableAutoValue.Builder(); + } + + @AutoValue.Builder + abstract static class Builder { + abstract PrefixSerializableAutoValue.Builder a(String value); + + abstract PrefixSerializableAutoValue.Builder b(boolean value); + + abstract PrefixSerializableAutoValue.Builder c(String value); + + abstract PrefixSerializableAutoValue.Builder d(boolean value); + + abstract PrefixSerializableAutoValue build(); + } + } + + @Test + public void allPrefixFieldsAreSerialized_noEmpty() { + PrefixSerializableAutoValue autoValue = + PrefixSerializableAutoValue.builder().a("A").b(true).c("C").d(false).build(); + + PrefixSerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue); + + assertThat(actualAutoValue).isEqualTo(autoValue); + } + + @Test + public void allPrefixFieldsAreSerialized_WithEmpty() { + PrefixSerializableAutoValue autoValue = + PrefixSerializableAutoValue.builder().a("A").b(true).build(); + + PrefixSerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue); + + assertThat(actualAutoValue).isEqualTo(autoValue); + } + + @SerializableAutoValue + @AutoValue + abstract static class NotSerializable { + static NotSerializable create() { + return new AutoValue_SerializableAutoValueExtensionTest_NotSerializable(Optional.of("A")); + } + + abstract Optional<String> optionalA(); + } + + @Test + public void missingImplementsSerializableThrowsException() throws Exception { + NotSerializable autoValue = NotSerializable.create(); + ByteArrayOutputStream bo = new ByteArrayOutputStream(); + ObjectOutputStream so = new ObjectOutputStream(bo); + + assertThrows(NotSerializableException.class, () -> so.writeObject(autoValue)); + } + + @AutoValue + abstract static class NotSerializableNoAnnotation implements Serializable { + static NotSerializableNoAnnotation create() { + return new AutoValue_SerializableAutoValueExtensionTest_NotSerializableNoAnnotation( + Optional.of("A")); + } + + abstract Optional<String> optionalA(); + } + + @Test + public void missingSerializableAutoValueAnnotationThrowsException() throws Exception { + NotSerializableNoAnnotation autoValue = NotSerializableNoAnnotation.create(); + ByteArrayOutputStream bo = new ByteArrayOutputStream(); + ObjectOutputStream so = new ObjectOutputStream(bo); + + assertThrows(NotSerializableException.class, () -> so.writeObject(autoValue)); + } + + @SerializableAutoValue + @AutoValue + // Technically all type parameters should extend serializable, but for the purposes of testing, + // only one type parameter is bounded. + abstract static class HasTypeParameters<T extends Serializable, S> implements Serializable { + abstract T a(); + + abstract Optional<S> optionalB(); + + static <T extends Serializable, S> Builder<T, S> builder() { + return new AutoValue_SerializableAutoValueExtensionTest_HasTypeParameters.Builder<>(); + } + + @AutoValue.Builder + abstract static class Builder<T extends Serializable, S> { + abstract Builder<T, S> setA(T value); + + abstract Builder<T, S> setOptionalB(S value); + + abstract HasTypeParameters<T, S> build(); + } + } + + @Test + public void typeParameterizedFieldsAreSet_noEmpty() { + HasTypeParameters<String, Integer> autoValue = + HasTypeParameters.<String, Integer>builder().setA(A).setOptionalB(B).build(); + + assertThat(autoValue.a()).isEqualTo(A); + assertThat(autoValue.optionalB()).hasValue(B); + } + + @Test + public void typeParameterizedFieldsAreSet_withEmpty() { + HasTypeParameters<String, Integer> autoValue = + HasTypeParameters.<String, Integer>builder().setA(A).build(); + + assertThat(autoValue.a()).isEqualTo(A); + assertThat(autoValue.optionalB()).isEmpty(); + } + + @Test + public void typeParameterizedFieldsAreSerializable_noEmpty() { + HasTypeParameters<String, Integer> autoValue = + HasTypeParameters.<String, Integer>builder().setA(A).setOptionalB(B).build(); + + HasTypeParameters<String, Integer> actualAutoValue = SerializableTester.reserialize(autoValue); + + assertThat(actualAutoValue).isEqualTo(autoValue); + } + + @Test + public void typeParameterizedFieldsAreSerializable_withEmpty() { + HasTypeParameters<String, Integer> autoValue = + HasTypeParameters.<String, Integer>builder().setA(A).build(); + + HasTypeParameters<String, Integer> actualAutoValue = SerializableTester.reserialize(autoValue); + + assertThat(actualAutoValue).isEqualTo(autoValue); + } + + @SerializableAutoValue + @AutoValue + abstract static class ImmutableListSerializableAutoValue implements Serializable { + abstract ImmutableList<Optional<String>> payload(); + + static ImmutableListSerializableAutoValue.Builder builder() { + return new AutoValue_SerializableAutoValueExtensionTest_ImmutableListSerializableAutoValue + .Builder(); + } + + @AutoValue.Builder + abstract static class Builder { + abstract ImmutableListSerializableAutoValue.Builder setPayload( + ImmutableList<Optional<String>> payload); + + abstract ImmutableListSerializableAutoValue build(); + } + } + + @Test + public void immutableList_emptyListSerialized() { + ImmutableListSerializableAutoValue autoValue = + ImmutableListSerializableAutoValue.builder().setPayload(ImmutableList.of()).build(); + + ImmutableListSerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue); + + assertThat(actualAutoValue).isEqualTo(autoValue); + } + + @Test + public void immutableList_allFieldsSetAndSerialized() { + ImmutableListSerializableAutoValue autoValue = + ImmutableListSerializableAutoValue.builder() + .setPayload(ImmutableList.of(Optional.of("a1"), Optional.of("a2"))) + .build(); + + ImmutableListSerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue); + + assertThat(actualAutoValue).isEqualTo(autoValue); + } + + @SerializableAutoValue + @AutoValue + abstract static class ImmutableMapSerializableAutoValue implements Serializable { + abstract ImmutableMap<Optional<String>, String> a(); + + abstract ImmutableMap<String, Optional<String>> b(); + + static ImmutableMapSerializableAutoValue.Builder builder() { + return new AutoValue_SerializableAutoValueExtensionTest_ImmutableMapSerializableAutoValue + .Builder(); + } + + @AutoValue.Builder + abstract static class Builder { + abstract ImmutableMapSerializableAutoValue.Builder setA( + ImmutableMap<Optional<String>, String> a); + + abstract ImmutableMapSerializableAutoValue.Builder setB( + ImmutableMap<String, Optional<String>> b); + + abstract ImmutableMapSerializableAutoValue build(); + } + } + + @Test + public void immutableMap_emptyMapSerialized() { + ImmutableMapSerializableAutoValue autoValue = + ImmutableMapSerializableAutoValue.builder() + .setA(ImmutableMap.of()) + .setB(ImmutableMap.of()) + .build(); + + ImmutableMapSerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue); + + assertThat(actualAutoValue).isEqualTo(autoValue); + } + + @Test + public void immutableMap_allFieldsSetAndSerialized() { + ImmutableMapSerializableAutoValue autoValue = + ImmutableMapSerializableAutoValue.builder() + .setA(ImmutableMap.of(Optional.of("key"), "value")) + .setB(ImmutableMap.of("key", Optional.of("value"))) + .build(); + + ImmutableMapSerializableAutoValue actualAutoValue = SerializableTester.reserialize(autoValue); + + assertThat(actualAutoValue).isEqualTo(autoValue); + } +} diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoaderTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoaderTest.java new file mode 100644 index 00000000..81410761 --- /dev/null +++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/SerializerFactoryLoaderTest.java @@ -0,0 +1,39 @@ +/* + * 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.serializer; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer; +import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory; +import com.google.auto.value.extension.serializable.serializer.utils.CompilationAbstractTest; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class SerializerFactoryLoaderTest extends CompilationAbstractTest { + + @Test + public void getFactory_extensionsLoaded() throws Exception { + SerializerFactory factory = SerializerFactoryLoader.getFactory(mockProcessingEnvironment); + + Serializer actualSerializer = factory.getSerializer(typeMirrorOf(String.class)); + + assertThat(actualSerializer.getClass().getName()) + .contains("TestStringSerializerFactory$TestStringSerializer"); + } +} diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/IdentitySerializerFactoryTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/IdentitySerializerFactoryTest.java new file mode 100644 index 00000000..87800b3d --- /dev/null +++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/IdentitySerializerFactoryTest.java @@ -0,0 +1,70 @@ +/* + * 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.serializer.impl; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.auto.value.extension.serializable.serializer.utils.CompilationAbstractTest; +import com.squareup.javapoet.CodeBlock; +import javax.lang.model.type.TypeMirror; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class IdentitySerializerFactoryTest extends CompilationAbstractTest { + + @Test + public void proxyFieldType_isUnchanged() throws Exception { + TypeMirror typeMirror = typeMirrorOf(String.class); + + TypeMirror actualTypeMirror = + IdentitySerializerFactory.getSerializer(typeMirror).proxyFieldType(); + + assertThat(actualTypeMirror).isSameInstanceAs(typeMirror); + } + + @Test + public void toProxy_isUnchanged() throws Exception { + TypeMirror typeMirror = typeMirrorOf(String.class); + CodeBlock inputExpression = CodeBlock.of("x"); + + CodeBlock outputExpression = + IdentitySerializerFactory.getSerializer(typeMirror).toProxy(inputExpression); + + assertThat(outputExpression).isSameInstanceAs(inputExpression); + } + + @Test + public void fromProxy_isUnchanged() throws Exception { + TypeMirror typeMirror = typeMirrorOf(String.class); + CodeBlock inputExpression = CodeBlock.of("x"); + + CodeBlock outputExpression = + IdentitySerializerFactory.getSerializer(typeMirror).fromProxy(inputExpression); + + assertThat(outputExpression).isSameInstanceAs(inputExpression); + } + + @Test + public void isIdentity() throws Exception { + TypeMirror typeMirror = typeMirrorOf(String.class); + + boolean actualIsIdentity = IdentitySerializerFactory.getSerializer(typeMirror).isIdentity(); + + assertThat(actualIsIdentity).isTrue(); + } +} diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtensionTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtensionTest.java new file mode 100644 index 00000000..159059d3 --- /dev/null +++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableListSerializerExtensionTest.java @@ -0,0 +1,122 @@ +/* + * 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.serializer.impl; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; + +import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer; +import com.google.auto.value.extension.serializable.serializer.utils.CompilationAbstractTest; +import com.google.auto.value.extension.serializable.serializer.utils.FakeSerializerFactory; +import com.google.common.collect.ImmutableList; +import com.squareup.javapoet.CodeBlock; +import java.util.Optional; +import javax.lang.model.type.TypeMirror; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class ImmutableListSerializerExtensionTest extends CompilationAbstractTest { + + private static final String FUNCTION_WITH_EXCEPTIONS = + "com.google.auto.value.extension.serializable.serializer.runtime.FunctionWithExceptions"; + private static final String IMMUTABLE_LIST = "com.google.common.collect.ImmutableList"; + + private ImmutableListSerializerExtension extension; + private FakeSerializerFactory fakeSerializerFactory; + + @Before + public void setUpExtension() { + extension = new ImmutableListSerializerExtension(); + fakeSerializerFactory = new FakeSerializerFactory(); + fakeSerializerFactory.setReturnIdentitySerializer(false); + } + + @Test + public void getSerializer_nonImmutableList_emptyReturned() { + TypeMirror typeMirror = typeMirrorOf(String.class); + + Optional<Serializer> actualSerializer = + extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment); + + assertThat(actualSerializer).isEmpty(); + } + + @Test + public void getSerializer_immutableListWithSerializableContainedType_emptyReturned() { + fakeSerializerFactory.setReturnIdentitySerializer(true); + TypeMirror typeMirror = typeMirrorOf(ImmutableList.class); + + Optional<Serializer> actualSerializer = + extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment); + + assertThat(actualSerializer).isEmpty(); + } + + @Test + public void getSerializer_immutableList_serializerReturned() { + TypeMirror typeMirror = typeMirrorOf(ImmutableList.class); + + Serializer actualSerializer = + extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get(); + + assertThat(actualSerializer.getClass().getName()) + .contains("ImmutableListSerializerExtension$ImmutableListSerializer"); + } + + @Test + public void proxyFieldType() { + TypeMirror typeMirror = declaredTypeOf(ImmutableList.class, Integer.class); + + Serializer serializer = + extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get(); + TypeMirror actualTypeMirror = serializer.proxyFieldType(); + + assertThat(typeUtils.isSameType(actualTypeMirror, typeMirror)).isTrue(); + } + + @Test + public void toProxy() { + TypeMirror typeMirror = declaredTypeOf(ImmutableList.class, Integer.class); + + Serializer serializer = + extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get(); + CodeBlock actualCodeBlock = serializer.toProxy(CodeBlock.of("x")); + + assertThat(actualCodeBlock.toString()) + .isEqualTo( + String.format( + "x.stream().map(%s.wrapper(value$ -> value$)).collect(%s.toImmutableList())", + FUNCTION_WITH_EXCEPTIONS, IMMUTABLE_LIST)); + } + + @Test + public void fromProxy() { + TypeMirror typeMirror = declaredTypeOf(ImmutableList.class, Integer.class); + + Serializer serializer = + extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get(); + CodeBlock actualCodeBlock = serializer.fromProxy(CodeBlock.of("x")); + + assertThat(actualCodeBlock.toString()) + .isEqualTo( + String.format( + "x.stream().map(%s.wrapper(value$ -> value$)).collect(%s.toImmutableList())", + FUNCTION_WITH_EXCEPTIONS, IMMUTABLE_LIST)); + } +} diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtensionTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtensionTest.java new file mode 100644 index 00000000..a7006f54 --- /dev/null +++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/ImmutableMapSerializerExtensionTest.java @@ -0,0 +1,140 @@ +/* + * 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.serializer.impl; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; + +import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer; +import com.google.auto.value.extension.serializable.serializer.utils.CompilationAbstractTest; +import com.google.auto.value.extension.serializable.serializer.utils.FakeSerializerFactory; +import com.google.common.collect.ImmutableMap; +import com.squareup.javapoet.CodeBlock; +import java.util.Optional; +import javax.lang.model.type.TypeMirror; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class ImmutableMapSerializerExtensionTest extends CompilationAbstractTest { + + private static final String FUNCTION_WITH_EXCEPTIONS = + "com.google.auto.value.extension.serializable.serializer.runtime.FunctionWithExceptions"; + private static final String IMMUTABLE_MAP = "com.google.common.collect.ImmutableMap"; + private static final String INTEGER = "java.lang.Integer"; + private static final String STRING = "java.lang.String"; + + private ImmutableMapSerializerExtension extension; + private FakeSerializerFactory fakeSerializerFactory; + + @Before + public void setUpExtension() { + extension = new ImmutableMapSerializerExtension(); + fakeSerializerFactory = new FakeSerializerFactory(); + fakeSerializerFactory.setReturnIdentitySerializer(false); + } + + @Test + public void getSerializer_nonImmutableMap_emptyReturned() { + TypeMirror typeMirror = typeMirrorOf(String.class); + + Optional<Serializer> actualSerializer = + extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment); + + assertThat(actualSerializer).isEmpty(); + } + + @Test + public void getSerializer_immutableMapWithSerializableContainedTypes_emptyReturned() { + fakeSerializerFactory.setReturnIdentitySerializer(true); + TypeMirror typeMirror = typeMirrorOf(ImmutableMap.class); + + Optional<Serializer> actualSerializer = + extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment); + + assertThat(actualSerializer).isEmpty(); + } + + @Test + public void getSerializer_immutableMap_serializerReturned() { + TypeMirror typeMirror = typeMirrorOf(ImmutableMap.class); + + Serializer actualSerializer = + extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get(); + + assertThat(actualSerializer.getClass().getName()) + .contains("ImmutableMapSerializerExtension$ImmutableMapSerializer"); + } + + @Test + public void proxyFieldType() { + TypeMirror typeMirror = declaredTypeOf(ImmutableMap.class, Integer.class, String.class); + + Serializer serializer = + extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get(); + TypeMirror actualTypeMirror = serializer.proxyFieldType(); + + assertThat(typeUtils.isSameType(actualTypeMirror, typeMirror)).isTrue(); + } + + @Test + public void toProxy() { + TypeMirror typeMirror = declaredTypeOf(ImmutableMap.class, Integer.class, String.class); + + Serializer serializer = + extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get(); + CodeBlock actualCodeBlock = serializer.toProxy(CodeBlock.of("x")); + + assertThat(actualCodeBlock.toString()) + .isEqualTo( + String.format( + "x.entrySet().stream().collect(%s.toImmutableMap(value$ -> %s.<%s," + + " %s>wrapper(element$ -> element$).apply(value$.getKey()), value$ -> %s.<%s," + + " %s>wrapper(element$ -> element$).apply(value$.getValue())))", + IMMUTABLE_MAP, + FUNCTION_WITH_EXCEPTIONS, + INTEGER, + INTEGER, + FUNCTION_WITH_EXCEPTIONS, + STRING, + STRING)); + } + + @Test + public void fromProxy() { + TypeMirror typeMirror = declaredTypeOf(ImmutableMap.class, Integer.class, String.class); + + Serializer serializer = + extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get(); + CodeBlock actualCodeBlock = serializer.fromProxy(CodeBlock.of("x")); + + assertThat(actualCodeBlock.toString()) + .isEqualTo( + String.format( + "x.entrySet().stream().collect(%s.toImmutableMap(value$ -> %s.<%s," + + " %s>wrapper(element$ -> element$).apply(value$.getKey()), value$ -> %s.<%s," + + " %s>wrapper(element$ -> element$).apply(value$.getValue())))", + IMMUTABLE_MAP, + FUNCTION_WITH_EXCEPTIONS, + INTEGER, + INTEGER, + FUNCTION_WITH_EXCEPTIONS, + STRING, + STRING)); + } +} diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/OptionalSerializerExtensionTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/OptionalSerializerExtensionTest.java new file mode 100644 index 00000000..e817911e --- /dev/null +++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/OptionalSerializerExtensionTest.java @@ -0,0 +1,98 @@ +/* + * 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.serializer.impl; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth8.assertThat; + +import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer; +import com.google.auto.value.extension.serializable.serializer.utils.CompilationAbstractTest; +import com.google.auto.value.extension.serializable.serializer.utils.FakeSerializerFactory; +import com.squareup.javapoet.CodeBlock; +import java.util.Optional; +import javax.lang.model.type.TypeMirror; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class OptionalSerializerExtensionTest extends CompilationAbstractTest { + + private OptionalSerializerExtension extension; + private FakeSerializerFactory fakeSerializerFactory; + + @Before + public void setUpExtension() { + extension = new OptionalSerializerExtension(); + fakeSerializerFactory = new FakeSerializerFactory(); + } + + @Test + public void getSerializer_nonOptional_emptyReturned() { + TypeMirror typeMirror = typeMirrorOf(String.class); + + Optional<Serializer> actualSerializer = + extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment); + + assertThat(actualSerializer).isEmpty(); + } + + @Test + public void getSerializer_optional_serializerReturned() { + TypeMirror typeMirror = typeMirrorOf(Optional.class); + + Serializer actualSerializer = + extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get(); + + assertThat(actualSerializer.getClass().getName()) + .contains("OptionalSerializerExtension$OptionalSerializer"); + } + + @Test + public void proxyFieldType() { + TypeMirror typeMirror = declaredTypeOf(Optional.class, Integer.class); + + Serializer serializer = + extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get(); + TypeMirror actualTypeMirror = serializer.proxyFieldType(); + + assertThat(actualTypeMirror).isEqualTo(typeMirrorOf(Integer.class)); + } + + @Test + public void toProxy() { + TypeMirror typeMirror = declaredTypeOf(Optional.class, Integer.class); + + Serializer serializer = + extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get(); + CodeBlock actualCodeBlock = serializer.toProxy(CodeBlock.of("x")); + + assertThat(actualCodeBlock.toString()).isEqualTo("x.isPresent() ? x.get() : null"); + } + + @Test + public void fromProxy() { + TypeMirror typeMirror = declaredTypeOf(Optional.class, Integer.class); + + Serializer serializer = + extension.getSerializer(typeMirror, fakeSerializerFactory, mockProcessingEnvironment).get(); + CodeBlock actualCodeBlock = serializer.fromProxy(CodeBlock.of("x")); + + assertThat(actualCodeBlock.toString()) + .isEqualTo("java.util.Optional.ofNullable(x == null ? null : x)"); + } +} diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImplTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImplTest.java new file mode 100644 index 00000000..e5150f90 --- /dev/null +++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/impl/SerializerFactoryImplTest.java @@ -0,0 +1,53 @@ +/* + * 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.serializer.impl; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer; +import com.google.auto.value.extension.serializable.serializer.utils.CompilationAbstractTest; +import com.google.auto.value.extension.serializable.serializer.utils.TestStringSerializerFactory; +import com.google.common.collect.ImmutableList; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public final class SerializerFactoryImplTest extends CompilationAbstractTest { + + @Test + public void getSerializer_emptyFactories_identitySerializerReturned() throws Exception { + SerializerFactoryImpl factory = + new SerializerFactoryImpl(ImmutableList.of(), mockProcessingEnvironment); + + Serializer actualSerializer = factory.getSerializer(typeMirrorOf(String.class)); + + assertThat(actualSerializer.getClass().getName()) + .contains("IdentitySerializerFactory$IdentitySerializer"); + } + + @Test + public void getSerializer_factoriesProvided_factoryReturned() throws Exception { + SerializerFactoryImpl factory = + new SerializerFactoryImpl( + ImmutableList.of(new TestStringSerializerFactory()), mockProcessingEnvironment); + + Serializer actualSerializer = factory.getSerializer(typeMirrorOf(String.class)); + + assertThat(actualSerializer.getClass().getName()) + .contains("TestStringSerializerFactory$TestStringSerializer"); + } +} diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/CompilationAbstractTest.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/CompilationAbstractTest.java new file mode 100644 index 00000000..0eb634a6 --- /dev/null +++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/CompilationAbstractTest.java @@ -0,0 +1,82 @@ +/* + * 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.serializer.utils; + +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import com.google.testing.compile.CompilationRule; +import java.util.Arrays; +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import org.junit.Before; +import org.junit.Rule; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(JUnit4.class) +public abstract class CompilationAbstractTest { + + @Rule public final CompilationRule compilationRule = new CompilationRule(); + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock protected ProcessingEnvironment mockProcessingEnvironment; + @Mock protected Messager mockMessager; + + protected Types typeUtils; + protected Elements elementUtils; + + @Before + public final void setUp() { + typeUtils = compilationRule.getTypes(); + elementUtils = compilationRule.getElements(); + + when(mockProcessingEnvironment.getTypeUtils()).thenReturn(typeUtils); + when(mockProcessingEnvironment.getElementUtils()).thenReturn(elementUtils); + when(mockProcessingEnvironment.getMessager()).thenReturn(mockMessager); + } + + protected TypeElement typeElementOf(Class<?> c) { + return elementUtils.getTypeElement(c.getCanonicalName()); + } + + protected TypeMirror typeMirrorOf(Class<?> c) { + return typeElementOf(c).asType(); + } + + protected DeclaredType declaredTypeOf(Class<?> enclosingClass, Class<?> containedClass) { + return typeUtils.getDeclaredType(typeElementOf(enclosingClass), typeMirrorOf(containedClass)); + } + + protected DeclaredType declaredTypeOf(Class<?> enclosingClass, Class<?>... classArgs) { + return typeUtils.getDeclaredType( + typeElementOf(enclosingClass), + Iterables.toArray( + Arrays.stream(classArgs) + .map(this::typeMirrorOf) + .collect(ImmutableList.toImmutableList()), + TypeMirror.class)); + } +} diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java new file mode 100644 index 00000000..388977fb --- /dev/null +++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/FakeSerializerFactory.java @@ -0,0 +1,75 @@ +/* + * 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.serializer.utils; + +import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer; +import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory; +import com.squareup.javapoet.CodeBlock; +import javax.lang.model.type.TypeMirror; + +/** A fake {@link SerializerFactory} that returns an identity serializer used for tests. */ +public final class FakeSerializerFactory implements SerializerFactory { + + private boolean isIdentity = true; + + public FakeSerializerFactory() {} + + /** + * Set if this factory should return a serializer that is considered an identity serializer. + * + * <p>The underlying fake serializer implementation will always be an identity serializer. This + * only changes the {@link Serializer#isIdentity} return value. + */ + public void setReturnIdentitySerializer(boolean isIdentity) { + this.isIdentity = isIdentity; + } + + @Override + public Serializer getSerializer(TypeMirror type) { + return new FakeIdentitySerializer(type, isIdentity); + } + + private static class FakeIdentitySerializer implements Serializer { + + private final TypeMirror typeMirror; + private final boolean isIdentity; + + FakeIdentitySerializer(TypeMirror typeMirror, boolean isIdentity) { + this.typeMirror = typeMirror; + this.isIdentity = isIdentity; + } + + @Override + public TypeMirror proxyFieldType() { + return typeMirror; + } + + @Override + public CodeBlock toProxy(CodeBlock expression) { + return expression; + } + + @Override + public CodeBlock fromProxy(CodeBlock expression) { + return expression; + } + + @Override + public boolean isIdentity() { + return isIdentity; + } + } +} diff --git a/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/TestStringSerializerFactory.java b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/TestStringSerializerFactory.java new file mode 100644 index 00000000..7c0f2047 --- /dev/null +++ b/value/src/test/java/com/google/auto/value/extension/serializable/serializer/utils/TestStringSerializerFactory.java @@ -0,0 +1,77 @@ +/* + * 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.serializer.utils; + +import com.google.auto.common.MoreElements; +import com.google.auto.common.MoreTypes; +import com.google.auto.service.AutoService; +import com.google.auto.value.extension.serializable.serializer.interfaces.Serializer; +import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerExtension; +import com.google.auto.value.extension.serializable.serializer.interfaces.SerializerFactory; +import com.squareup.javapoet.CodeBlock; +import java.util.Optional; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +/** A test implementation of {@link SerializerExtension} that overwrites a string field's value. */ +@AutoService(SerializerExtension.class) +public final class TestStringSerializerFactory implements SerializerExtension { + + public TestStringSerializerFactory() {} + + @Override + public Optional<Serializer> getSerializer( + TypeMirror typeMirror, SerializerFactory factory, ProcessingEnvironment processingEnv) { + if (typeMirror.getKind() != TypeKind.DECLARED) { + return Optional.empty(); + } + + DeclaredType declaredType = MoreTypes.asDeclared(typeMirror); + TypeElement typeElement = MoreElements.asType(declaredType.asElement()); + if (typeElement.getQualifiedName().contentEquals("java.lang.String")) { + return Optional.of(new TestStringSerializer(typeMirror)); + } + + return Optional.empty(); + } + + private static class TestStringSerializer implements Serializer { + + private final TypeMirror typeMirror; + + TestStringSerializer(TypeMirror typeMirror) { + this.typeMirror = typeMirror; + } + + @Override + public TypeMirror proxyFieldType() { + return typeMirror; + } + + @Override + public CodeBlock toProxy(CodeBlock expression) { + return CodeBlock.of("$S", "test"); + } + + @Override + public CodeBlock fromProxy(CodeBlock expression) { + return CodeBlock.of("$S", "test"); + } + } +} |