Skip to content

Commit d0a55d4

Browse files
authored
Add FieldDescriptor support to DataEncoder. (#1608)
The change updates `ObjectEncoderContext` with new overloads that use `FieldDescriptors` and deprecates that ones that take `String` field names. Additionally updates codegen logic in `firebase-encoder-processor` to infer and use `FieldDescriptors`.
1 parent 3ffa95f commit d0a55d4

File tree

18 files changed

+482
-50
lines changed

18 files changed

+482
-50
lines changed

encoders/firebase-encoders-json/api.txt

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package com.google.firebase.encoders {
1515
method @NonNull public static com.google.firebase.encoders.FieldDescriptor.Builder builder(@NonNull String);
1616
method @NonNull public String getName();
1717
method @Nullable public <T extends java.lang.annotation.Annotation> T getProperty(@NonNull Class<T>);
18+
method @NonNull public static com.google.firebase.encoders.FieldDescriptor of(@NonNull String);
1819
}
1920

2021
public static final class FieldDescriptor.Builder {
@@ -26,13 +27,19 @@ package com.google.firebase.encoders {
2627
}
2728

2829
public interface ObjectEncoderContext {
29-
method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, @Nullable Object) throws java.io.IOException;
30-
method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, double) throws java.io.IOException;
31-
method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, int) throws java.io.IOException;
32-
method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, long) throws java.io.IOException;
33-
method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, boolean) throws java.io.IOException;
30+
method @Deprecated @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, @Nullable Object) throws java.io.IOException;
31+
method @Deprecated @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, double) throws java.io.IOException;
32+
method @Deprecated @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, int) throws java.io.IOException;
33+
method @Deprecated @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, long) throws java.io.IOException;
34+
method @Deprecated @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull String, boolean) throws java.io.IOException;
35+
method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull com.google.firebase.encoders.FieldDescriptor, @Nullable Object) throws java.io.IOException;
36+
method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull com.google.firebase.encoders.FieldDescriptor, double) throws java.io.IOException;
37+
method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull com.google.firebase.encoders.FieldDescriptor, int) throws java.io.IOException;
38+
method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull com.google.firebase.encoders.FieldDescriptor, long) throws java.io.IOException;
39+
method @NonNull public com.google.firebase.encoders.ObjectEncoderContext add(@NonNull com.google.firebase.encoders.FieldDescriptor, boolean) throws java.io.IOException;
3440
method @NonNull public com.google.firebase.encoders.ObjectEncoderContext inline(@Nullable Object) throws java.io.IOException;
3541
method @NonNull public com.google.firebase.encoders.ObjectEncoderContext nested(@NonNull String) throws java.io.IOException;
42+
method @NonNull public com.google.firebase.encoders.ObjectEncoderContext nested(@NonNull com.google.firebase.encoders.FieldDescriptor) throws java.io.IOException;
3643
}
3744

3845
public interface ValueEncoder<T> {

encoders/firebase-encoders-json/src/json/java/com/google/firebase/encoders/json/JsonValueObjectEncoderContext.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import androidx.annotation.NonNull;
2020
import androidx.annotation.Nullable;
2121
import com.google.firebase.encoders.EncodingException;
22+
import com.google.firebase.encoders.FieldDescriptor;
2223
import com.google.firebase.encoders.ObjectEncoder;
2324
import com.google.firebase.encoders.ObjectEncoderContext;
2425
import com.google.firebase.encoders.ValueEncoder;
@@ -102,6 +103,38 @@ public JsonValueObjectEncoderContext add(@NonNull String name, boolean value) th
102103
return add(value);
103104
}
104105

106+
@NonNull
107+
@Override
108+
public ObjectEncoderContext add(@NonNull FieldDescriptor field, @Nullable Object obj)
109+
throws IOException {
110+
return add(field.getName(), obj);
111+
}
112+
113+
@NonNull
114+
@Override
115+
public ObjectEncoderContext add(@NonNull FieldDescriptor field, double value) throws IOException {
116+
return add(field.getName(), value);
117+
}
118+
119+
@NonNull
120+
@Override
121+
public ObjectEncoderContext add(@NonNull FieldDescriptor field, int value) throws IOException {
122+
return add(field.getName(), value);
123+
}
124+
125+
@NonNull
126+
@Override
127+
public ObjectEncoderContext add(@NonNull FieldDescriptor field, long value) throws IOException {
128+
return add(field.getName(), value);
129+
}
130+
131+
@NonNull
132+
@Override
133+
public ObjectEncoderContext add(@NonNull FieldDescriptor field, boolean value)
134+
throws IOException {
135+
return add(field.getName(), value);
136+
}
137+
105138
@NonNull
106139
@Override
107140
public ObjectEncoderContext inline(@Nullable Object value) throws IOException {
@@ -118,6 +151,12 @@ public ObjectEncoderContext nested(@NonNull String name) throws IOException {
118151
return childContext;
119152
}
120153

154+
@NonNull
155+
@Override
156+
public ObjectEncoderContext nested(@NonNull FieldDescriptor field) throws IOException {
157+
return nested(field.getName());
158+
}
159+
121160
@NonNull
122161
@Override
123162
public JsonValueObjectEncoderContext add(@Nullable String value) throws IOException {

encoders/firebase-encoders-json/src/main/java/com/google/firebase/encoders/FieldDescriptor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ public <T extends Annotation> T getProperty(@NonNull Class<T> type) {
6767
return (T) properties.get(type);
6868
}
6969

70+
@NonNull
71+
public static FieldDescriptor of(@NonNull String name) {
72+
return new FieldDescriptor(name, Collections.emptyMap());
73+
}
74+
7075
@NonNull
7176
public static Builder builder(@NonNull String name) {
7277
return new Builder(name);

encoders/firebase-encoders-json/src/main/java/com/google/firebase/encoders/ObjectEncoderContext.java

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,37 +35,99 @@ public interface ObjectEncoderContext {
3535
*
3636
* <p>{@code obj} can be an array. If the elements of the array are primitive types, they will be
3737
* directly encoded. Otherwise, the matching {@code Encoder} registered for the type will be used.
38-
* In this case, the value of the entry will be an encoded array obtained by sequencially applying
38+
* In this case, the value of the entry will be an encoded array obtained by sequentially applying
3939
* the encoder to each element of the array. Nested arrays are supported.
4040
*
4141
* <p>{@code obj} can be a {@link java.util.Collection}. The matching {@code Encoder} registered
4242
* for the type contained within the collection will be used. In this case, the value of the entry
43-
* will be an encoded array obtained by sequencially applying the encoder to each element of the
43+
* will be an encoded array obtained by sequentially applying the encoder to each element of the
4444
* array. Nested collections are supported.
4545
*
4646
* <p>If {@code obj} does not match any of the criteria above, or if there's no matching {@code
4747
* Encoder} for the type, an {@code EncodingException} will be thrown. Also, any exceptions thrown
4848
* by the encoders will be propagated.
49+
*
50+
* @deprecated Use {@link #add(FieldDescriptor, Object)} instead.
4951
*/
52+
@Deprecated
5053
@NonNull
5154
ObjectEncoderContext add(@NonNull String name, @Nullable Object obj) throws IOException;
5255

53-
/** Add an entry with {@code name} mapped to the encoded primitive type of {@code value}. */
56+
/**
57+
* Add an entry with {@code name} mapped to the encoded primitive type of {@code value}.
58+
*
59+
* @deprecated Use {@link #add(FieldDescriptor, double)} instead.
60+
*/
61+
@Deprecated
5462
@NonNull
5563
ObjectEncoderContext add(@NonNull String name, double value) throws IOException;
5664

57-
/** Add an entry with {@code name} mapped to the encoded primitive type of {@code value}. */
65+
/**
66+
* Add an entry with {@code name} mapped to the encoded primitive type of {@code value}.
67+
*
68+
* @deprecated Use {@link #add(FieldDescriptor, double)} instead.
69+
*/
70+
@Deprecated
5871
@NonNull
5972
ObjectEncoderContext add(@NonNull String name, int value) throws IOException;
6073

61-
/** Add an entry with {@code name} mapped to the encoded primitive type of {@code value}. */
74+
/**
75+
* Add an entry with {@code name} mapped to the encoded primitive type of {@code value}.
76+
*
77+
* @deprecated Use {@link #add(FieldDescriptor, double)} instead.
78+
*/
79+
@Deprecated
6280
@NonNull
6381
ObjectEncoderContext add(@NonNull String name, long value) throws IOException;
6482

65-
/** Add an entry with {@code name} mapped to the encoded primitive type of {@code value}. */
83+
/**
84+
* Add an entry with {@code name} mapped to the encoded primitive type of {@code value}.
85+
*
86+
* @deprecated Use {@link #add(FieldDescriptor, double)} instead.
87+
*/
88+
@Deprecated
6689
@NonNull
6790
ObjectEncoderContext add(@NonNull String name, boolean value) throws IOException;
6891

92+
/**
93+
* Add an entry with {@code field} mapped to the encoded version of {@code obj}.
94+
*
95+
* <p>{@code obj} can be a regular type with a matching {@code Encoder} registered. In this case,
96+
* the value of the entry will be the encoded version of {@code obj}.
97+
*
98+
* <p>{@code obj} can be an array. If the elements of the array are primitive types, they will be
99+
* directly encoded. Otherwise, the matching {@code Encoder} registered for the type will be used.
100+
* In this case, the value of the entry will be an encoded array obtained by sequentially applying
101+
* the encoder to each element of the array. Nested arrays are supported.
102+
*
103+
* <p>{@code obj} can be a {@link java.util.Collection}. The matching {@code Encoder} registered
104+
* for the type contained within the collection will be used. In this case, the value of the entry
105+
* will be an encoded array obtained by sequentially applying the encoder to each element of the
106+
* array. Nested collections are supported.
107+
*
108+
* <p>If {@code obj} does not match any of the criteria above, or if there's no matching {@code
109+
* Encoder} for the type, an {@code EncodingException} will be thrown. Also, any exceptions thrown
110+
* by the encoders will be propagated.
111+
*/
112+
@NonNull
113+
ObjectEncoderContext add(@NonNull FieldDescriptor field, @Nullable Object obj) throws IOException;
114+
115+
/** Add an entry with {@code field} mapped to the encoded primitive type of {@code value}. */
116+
@NonNull
117+
ObjectEncoderContext add(@NonNull FieldDescriptor field, double value) throws IOException;
118+
119+
/** Add an entry with {@code field} mapped to the encoded primitive type of {@code value}. */
120+
@NonNull
121+
ObjectEncoderContext add(@NonNull FieldDescriptor field, int value) throws IOException;
122+
123+
/** Add an entry with {@code field} mapped to the encoded primitive type of {@code value}. */
124+
@NonNull
125+
ObjectEncoderContext add(@NonNull FieldDescriptor field, long value) throws IOException;
126+
127+
/** Add an entry with {@code field} mapped to the encoded primitive type of {@code value}. */
128+
@NonNull
129+
ObjectEncoderContext add(@NonNull FieldDescriptor field, boolean value) throws IOException;
130+
69131
/**
70132
* Encodes a given object inline in current context.
71133
*
@@ -109,4 +171,28 @@ public interface ObjectEncoderContext {
109171
*/
110172
@NonNull
111173
ObjectEncoderContext nested(@NonNull String name) throws IOException;
174+
175+
/**
176+
* Begin a nested JSON object.
177+
*
178+
* <p>Unlike {@code add()} methods, this method returns a new "child" context that's used to
179+
* populate the nested JSON object. This context can only be used until the parent context is
180+
* mutated by calls to {@code add()} or {@code nested()}, violating this will result in a {@link
181+
* IllegalStateException}.
182+
*
183+
* <p>Nesting can be arbitrarily deep.
184+
*
185+
* <p>Example:
186+
*
187+
* <pre>{@code
188+
* ctx.add("key", "value");
189+
* ObjectEncoderContext nested = ctx.nested("nested");
190+
* nested.add("key", "value");
191+
*
192+
* // After this call the above nested context is invalid.
193+
* ctx.add("anotherKey", 1);
194+
* }</pre>
195+
*/
196+
@NonNull
197+
ObjectEncoderContext nested(@NonNull FieldDescriptor field) throws IOException;
112198
}

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

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818
import com.google.auto.service.AutoService;
1919
import com.google.auto.value.AutoValue;
2020
import com.google.firebase.encoders.annotations.Encodable;
21+
import com.google.firebase.encoders.processor.getters.AnnotationDescriptor;
22+
import com.google.firebase.encoders.processor.getters.AnnotationProperty;
2123
import com.google.firebase.encoders.processor.getters.Getter;
2224
import com.google.firebase.encoders.processor.getters.GetterFactory;
2325
import com.squareup.javapoet.ClassName;
26+
import com.squareup.javapoet.CodeBlock;
2427
import com.squareup.javapoet.FieldSpec;
2528
import com.squareup.javapoet.JavaFile;
2629
import com.squareup.javapoet.MethodSpec;
@@ -57,6 +60,7 @@
5760
@SupportedSourceVersion(SourceVersion.RELEASE_8)
5861
public class EncodableProcessor extends AbstractProcessor {
5962

63+
private static final String CODEGEN_VERSION = "2";
6064
static final String ENCODABLE_ANNOTATION = "com.google.firebase.encoders.annotations.Encodable";
6165
private Elements elements;
6266
private Types types;
@@ -84,6 +88,11 @@ public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv
8488
}
8589

8690
private void processClass(Element element) {
91+
if (types.isAssignable(
92+
element.asType(), elements.getTypeElement("java.lang.annotation.Annotation").asType())) {
93+
return;
94+
}
95+
8796
// generates class of the following shape:
8897
//
8998
// public class AutoFooEncoder implements Configurator {
@@ -107,7 +116,7 @@ private void processClass(Element element) {
107116
Modifier.PUBLIC,
108117
Modifier.STATIC,
109118
Modifier.FINAL)
110-
.initializer("1")
119+
.initializer(CODEGEN_VERSION)
111120
.build())
112121
.addField(
113122
FieldSpec.builder(
@@ -259,12 +268,47 @@ public VisitResult<Encoder> visit(TypeMirror type) {
259268
.addAnnotation(Override.class);
260269

261270
Set<TypeMirror> result = new LinkedHashSet<>();
271+
Set<FieldSpec> descriptorFields = new LinkedHashSet<>();
272+
ClassName fieldDescriptor = ClassName.get("com.google.firebase.encoders", "FieldDescriptor");
262273
for (Getter getter : getterFactory.allGetters((DeclaredType) type)) {
263274
result.addAll(getTypesToVisit(getter.getUnderlyingType()));
264275
if (getter.inline()) {
265276
methodBuilder.addCode("ctx.inline(value.$L);\n", getter.expression());
266277
} else {
267-
methodBuilder.addCode("ctx.add($S, value.$L);\n", getter.name(), getter.expression());
278+
CodeBlock.Builder codeBuilder;
279+
if (getter.annotationDescriptors().isEmpty()) {
280+
codeBuilder = CodeBlock.builder().add("$T.of($S)", fieldDescriptor, getter.name());
281+
} else {
282+
codeBuilder =
283+
CodeBlock.builder()
284+
.add("$T.builder($S)\n", fieldDescriptor, getter.name())
285+
.indent()
286+
.indent();
287+
for (AnnotationDescriptor desc : getter.annotationDescriptors()) {
288+
ClassName annotationBuilder =
289+
builderName(
290+
ClassName.get((TypeElement) desc.type().getAnnotationType().asElement()));
291+
codeBuilder.add(".withProperty($T.builder()\n", annotationBuilder);
292+
for (AnnotationProperty property : desc.properties()) {
293+
codeBuilder.add("$>.$L($L)\n$<", property.name(), property.value());
294+
}
295+
codeBuilder.add("$>.build())\n$<");
296+
}
297+
codeBuilder.add(".build()").unindent().unindent();
298+
}
299+
descriptorFields.add(
300+
FieldSpec.builder(
301+
fieldDescriptor,
302+
getter.name().toUpperCase() + "_DESCRIPTOR",
303+
Modifier.PRIVATE,
304+
Modifier.FINAL,
305+
Modifier.STATIC)
306+
.initializer(codeBuilder.build())
307+
.build());
308+
methodBuilder.addCode(
309+
"ctx.add($L_DESCRIPTOR, value.$L);\n",
310+
getter.name().toUpperCase(),
311+
getter.expression());
268312
}
269313
}
270314

@@ -281,12 +325,25 @@ public VisitResult<Encoder> visit(TypeMirror type) {
281325
FieldSpec.builder(className, "INSTANCE", Modifier.FINAL, Modifier.STATIC)
282326
.initializer("new $T()", className)
283327
.build())
328+
.addFields(descriptorFields)
284329
.addMethod(methodBuilder.build())
285330
.build();
286331
encoded.put(types.erasure(type), encoder);
287332
return VisitResult.of(result, Encoder.create(types.erasure(type), encoder));
288333
}
289334

335+
private ClassName builderName(ClassName annotation) {
336+
return ClassName.get(annotation.packageName(), compositeName(annotation));
337+
}
338+
339+
private String compositeName(ClassName annotation) {
340+
ClassName parentName = annotation.enclosingClassName();
341+
if (parentName == null) {
342+
return "At" + annotation.simpleName();
343+
}
344+
return compositeName(parentName) + annotation.simpleName();
345+
}
346+
290347
private Set<TypeMirror> getTypesToVisit(TypeMirror type) {
291348
TypeMirror date = elements.getTypeElement("java.util.Date").asType();
292349
TypeMirror enumType = types.erasure(elements.getTypeElement("java.lang.Enum").asType());

encoders/firebase-encoders-processor/src/main/java/com/google/firebase/encoders/processor/annotations/AnnotationBuilder.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import com.squareup.javapoet.ParameterizedTypeName;
2222
import com.squareup.javapoet.TypeName;
2323
import com.squareup.javapoet.TypeSpec;
24-
import com.squareup.javapoet.TypeSpec.Builder;
2524
import com.squareup.javapoet.WildcardTypeName;
2625
import java.lang.annotation.Annotation;
2726
import java.util.ArrayList;
@@ -80,7 +79,7 @@ public static TypeSpec generate(TypeElement element) {
8079

8180
private static TypeSpec createBuilder(
8281
ClassName builderName, ClassName annotationName, AnnotationImpl annotationImpl) {
83-
Builder annotationBuilder =
82+
TypeSpec.Builder annotationBuilder =
8483
TypeSpec.classBuilder(builderName)
8584
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
8685
.addType(annotationImpl.typeSpec);
@@ -172,7 +171,7 @@ private static String compositeName(ClassName annotation) {
172171

173172
private static AnnotationImpl createAnnotationImpl(TypeElement annotation) {
174173
String implName = annotation.getSimpleName().toString() + "Impl";
175-
Builder annotationImpl =
174+
TypeSpec.Builder annotationImpl =
176175
TypeSpec.classBuilder(implName)
177176
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
178177
.addSuperinterface(TypeName.get(annotation.asType()))

encoders/firebase-encoders-processor/src/main/java/com/google/firebase/encoders/processor/annotations/ToStringMethod.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import com.squareup.javapoet.ClassName;
1818
import com.squareup.javapoet.CodeBlock;
1919
import com.squareup.javapoet.MethodSpec;
20-
import com.squareup.javapoet.MethodSpec.Builder;
2120
import java.util.List;
2221
import javax.lang.model.element.ExecutableElement;
2322
import javax.lang.model.element.Modifier;
@@ -27,7 +26,7 @@
2726
final class ToStringMethod {
2827
static MethodSpec generate(TypeElement element) {
2928
ClassName.get(element).reflectionName();
30-
Builder result =
29+
MethodSpec.Builder result =
3130
MethodSpec.methodBuilder("toString")
3231
.addModifiers(Modifier.PUBLIC)
3332
.returns(String.class)

0 commit comments

Comments
 (0)