Skip to content

Commit 3153a9c

Browse files
authored
Add AutoValue support. (#1035)
This is achieved by registering the generated auto value class in addition to the abstract class.
1 parent 73c39b1 commit 3153a9c

File tree

3 files changed

+184
-2
lines changed

3 files changed

+184
-2
lines changed

encoders/firebase-encoders-processor/firebase-encoders-processor.gradle

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,23 @@ dependencies {
2323
compileOnly 'javax.annotation:javax.annotation-api:1.3.2'
2424
implementation 'com.google.guava:guava:28.1-jre'
2525

26-
compileOnly "com.google.auto.value:auto-value-annotations:1.6.5"
26+
implementation "com.google.auto.value:auto-value-annotations:1.6.5"
2727

2828
annotationProcessor "com.google.auto.value:auto-value:1.6.2"
2929

3030
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
3131

3232
testImplementation 'com.google.testing.compile:compile-testing:0.18'
3333
testImplementation files(org.gradle.internal.jvm.Jvm.current().getToolsJar())
34+
testImplementation 'com.google.truth:truth:1.0'
3435

3536
}
3637

38+
// this is needed to bump guava to required version, otherwise tests fail.
39+
configurations.testImplementation.resolutionStrategy {
40+
force('com.google.guava:guava:28.1-jre')
41+
}
42+
3743
dependencies {
3844
implementation 'androidx.annotation:annotation:1.1.0'
3945
}

encoders/firebase-encoders-processor/src/main/java/com/google/firebase/encoders/processor/EncodableProcessor.java

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414

1515
package com.google.firebase.encoders.processor;
1616

17+
import androidx.annotation.VisibleForTesting;
1718
import com.google.auto.service.AutoService;
19+
import com.google.auto.value.AutoValue;
1820
import com.google.firebase.encoders.annotations.Encodable;
1921
import com.google.firebase.encoders.processor.getters.Getter;
2022
import com.google.firebase.encoders.processor.getters.GetterFactory;
@@ -28,9 +30,11 @@
2830
import com.squareup.javapoet.TypeSpec;
2931
import com.squareup.javapoet.WildcardTypeName;
3032
import java.io.IOException;
33+
import java.util.HashMap;
3134
import java.util.LinkedHashMap;
3235
import java.util.LinkedHashSet;
3336
import java.util.Map;
37+
import java.util.Optional;
3438
import java.util.Set;
3539
import javax.annotation.processing.AbstractProcessor;
3640
import javax.annotation.processing.ProcessingEnvironment;
@@ -56,7 +60,6 @@ public class EncodableProcessor extends AbstractProcessor {
5660
private Elements elements;
5761
private Types types;
5862
private GetterFactory getterFactory;
59-
private TypeTraversal traversal;
6063

6164
@Override
6265
public synchronized void init(ProcessingEnvironment processingEnvironment) {
@@ -120,25 +123,116 @@ private void processClass(Element element) {
120123
.addModifiers(Modifier.PUBLIC)
121124
.addAnnotation(Override.class);
122125

126+
Map<String, TypeSpec> autoValueSupportClasses = new HashMap<>();
127+
123128
for (Encoder encoder : encoders) {
124129
encoderBuilder.addType(encoder.code());
125130

126131
configureMethod.addCode(
127132
"cfg.registerEncoder($T.class, $N.INSTANCE);\n",
128133
types.erasure(encoder.type()),
129134
encoder.code());
135+
autoValueSupport(
136+
Names.packageName(element),
137+
element.getSimpleName().toString(),
138+
encoder,
139+
configureMethod)
140+
.ifPresent(
141+
spec -> {
142+
String packageName = Names.packageName(types.asElement(encoder.type()));
143+
autoValueSupportClasses.put(packageName, spec);
144+
});
130145
}
131146
encoderBuilder.addMethod(configureMethod.build());
132147

133148
JavaFile file = JavaFile.builder(Names.packageName(element), encoderBuilder.build()).build();
134149

135150
try {
136151
file.writeTo(processingEnv.getFiler());
152+
for (Map.Entry<String, TypeSpec> autoValue : autoValueSupportClasses.entrySet()) {
153+
JavaFile.builder(autoValue.getKey(), autoValue.getValue())
154+
.build()
155+
.writeTo(processingEnv.getFiler());
156+
}
137157
} catch (IOException ex) {
138158
throw new RuntimeException(ex);
139159
}
140160
}
141161

162+
private Optional<TypeSpec> autoValueSupport(
163+
String rootPackageName,
164+
String containingClassName,
165+
Encoder encoder,
166+
MethodSpec.Builder configureMethod) {
167+
Element element = types.asElement(encoder.type());
168+
AutoValue autoValue = element.getAnnotation(AutoValue.class);
169+
if (autoValue == null) {
170+
return Optional.empty();
171+
}
172+
String typePackageName = Names.packageName(element);
173+
174+
if (rootPackageName.equals(typePackageName)) {
175+
configureMethod.addCode(
176+
"cfg.registerEncoder(AutoValue_$T.class, $N.INSTANCE);\n",
177+
types.erasure(encoder.type()),
178+
encoder.code());
179+
return Optional.empty();
180+
}
181+
182+
// the generated class has a rather long name but provides uniqueness guarantees.
183+
TypeSpec supportClass =
184+
TypeSpec.classBuilder(
185+
String.format(
186+
"Encodable%s%s%sAutoValueSupport",
187+
packageNameToCamelCase(rootPackageName),
188+
containingClassName,
189+
element.getSimpleName()))
190+
.addModifiers(Modifier.FINAL, Modifier.PUBLIC)
191+
.addField(
192+
FieldSpec.builder(
193+
ParameterizedTypeName.get(
194+
ClassName.get(Class.class),
195+
WildcardTypeName.subtypeOf(ClassName.get(encoder.type()))),
196+
"TYPE",
197+
Modifier.PUBLIC,
198+
Modifier.STATIC,
199+
Modifier.FINAL)
200+
.initializer("AutoValue_$T.class", encoder.type())
201+
.build())
202+
.build();
203+
204+
String packageName = Names.packageName(types.asElement(encoder.type()));
205+
configureMethod.addCode(
206+
"cfg.registerEncoder($L.$N.TYPE, $N.INSTANCE);\n",
207+
packageName,
208+
supportClass,
209+
encoder.code());
210+
211+
return Optional.of(supportClass);
212+
}
213+
214+
@VisibleForTesting
215+
static String packageNameToCamelCase(String packageName) {
216+
if (packageName.isEmpty()) {
217+
return packageName;
218+
}
219+
packageName = Character.toUpperCase(packageName.charAt(0)) + packageName.substring(1);
220+
221+
int dotIndex = packageName.indexOf('.');
222+
while (dotIndex > -1) {
223+
String prefix = packageName.substring(0, dotIndex);
224+
String suffix = "";
225+
if (dotIndex < packageName.length() - 1) {
226+
suffix =
227+
Character.toUpperCase(packageName.charAt(dotIndex + 1))
228+
+ (dotIndex < packageName.length() - 2 ? packageName.substring(dotIndex + 2) : "");
229+
}
230+
packageName = prefix + suffix;
231+
dotIndex = packageName.indexOf('.');
232+
}
233+
return packageName;
234+
}
235+
142236
class GetterVisitor implements TypeVisitor<Encoder> {
143237
final Map<TypeMirror, TypeSpec> encoded = new LinkedHashMap<>();
144238

encoders/firebase-encoders-processor/src/test/java/com/google/firebase/encoders/processor/EncodableProcessorTest.java

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414

1515
package com.google.firebase.encoders.processor;
1616

17+
import static com.google.common.truth.Truth.assertThat;
1718
import static com.google.testing.compile.CompilationSubject.assertThat;
1819
import static com.google.testing.compile.Compiler.javac;
1920

21+
import com.google.auto.value.processor.AutoValueProcessor;
2022
import com.google.testing.compile.Compilation;
2123
import com.google.testing.compile.JavaFileObjects;
2224
import org.junit.Test;
@@ -197,4 +199,84 @@ public void compile_withOptional_shouldUseOrElseNull() {
197199
.contentsAsUtf8String()
198200
.contains("\"hello\", value.getOptional().orElse(null)");
199201
}
202+
203+
@Test
204+
public void compile_withAutoValueInSamePackage_shouldRegisterGeneratedSubclass() {
205+
Compilation result =
206+
javac()
207+
.withProcessors(new AutoValueProcessor(), new EncodableProcessor())
208+
.compile(
209+
JavaFileObjects.forSourceLines(
210+
"Foo",
211+
"import com.google.firebase.encoders.annotations.Encodable;",
212+
"import com.google.auto.value.AutoValue;",
213+
"@Encodable @AutoValue public abstract class Foo {",
214+
"public abstract String getField();",
215+
"}"));
216+
217+
assertThat(result)
218+
.generatedSourceFile("AutoFooEncoder")
219+
.contentsAsUtf8String()
220+
.contains("cfg.registerEncoder(AutoValue_Foo.class");
221+
}
222+
223+
@Test
224+
public void compile_withAutoValueInDifferentPackage_shouldRegisterGeneratedSubclass() {
225+
Compilation result =
226+
javac()
227+
.withProcessors(new AutoValueProcessor(), new EncodableProcessor())
228+
.compile(
229+
JavaFileObjects.forSourceLines(
230+
"com.example.Foo",
231+
"package com.example;",
232+
"import com.google.firebase.encoders.annotations.Encodable;",
233+
"@Encodable public class Foo {",
234+
"public com.example.sub.Member getField() { return null; }",
235+
"}"),
236+
JavaFileObjects.forSourceLines(
237+
"com.example.sub.Member",
238+
"package com.example.sub;",
239+
"import com.google.auto.value.AutoValue;",
240+
"@AutoValue public abstract class Member {",
241+
"public abstract String getField();",
242+
"}"));
243+
244+
assertThat(result).succeededWithoutWarnings();
245+
assertThat(result)
246+
.generatedSourceFile("com/example/AutoFooEncoder")
247+
.contentsAsUtf8String()
248+
.contains(
249+
"cfg.registerEncoder("
250+
+ "com.example.sub.EncodableComExampleFooMemberAutoValueSupport.TYPE,"
251+
+ " MemberEncoder.INSTANCE)");
252+
assertThat(result)
253+
.generatedSourceFile("com/example/sub/EncodableComExampleFooMemberAutoValueSupport")
254+
.contentsAsUtf8String()
255+
.contains("Class<? extends Member> TYPE = AutoValue_Member.class");
256+
}
257+
258+
@Test
259+
public void packageNameToCamelCase_withDefaultPackage_shouldReturnEmptyString() {
260+
assertThat(EncodableProcessor.packageNameToCamelCase("")).isEqualTo("");
261+
}
262+
263+
@Test
264+
public void packageNameToCamelCase_withValidPackage_shouldSuccessfullyReturn() {
265+
assertThat(EncodableProcessor.packageNameToCamelCase("com.example")).isEqualTo("ComExample");
266+
}
267+
268+
@Test
269+
public void packageNameToCamelCase_withInvalidPackage_shouldSuccessfullyReturn() {
270+
assertThat(EncodableProcessor.packageNameToCamelCase("com.example.")).isEqualTo("ComExample");
271+
}
272+
273+
@Test
274+
public void packageNameToCamelCase_withInvalidPackage2_shouldSuccessfullyReturn() {
275+
assertThat(EncodableProcessor.packageNameToCamelCase(".example")).isEqualTo("Example");
276+
}
277+
278+
@Test
279+
public void packageNameToCamelCase_withSubPackageOfOneChar_shouldSuccessfullyReturn() {
280+
assertThat(EncodableProcessor.packageNameToCamelCase("com.example.a")).isEqualTo("ComExampleA");
281+
}
200282
}

0 commit comments

Comments
 (0)