Skip to content

Commit e5bab1b

Browse files
author
Hui-Wu
authored
Add DocumentId annotation, mark planned interface changes and call site updates. (#456)
This change adds a DocumentId annotation class, under util/ for now, that can be used to automatically populate document id values into user defined custom objects. The actual implementation of the population will be in a subsequent PR.
1 parent 741d2e2 commit e5bab1b

File tree

5 files changed

+99
-39
lines changed

5 files changed

+99
-39
lines changed

firebase-firestore/src/main/java/com/google/firebase/firestore/DocumentSnapshot.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,9 @@ public <T> T toObject(
200200
checkNotNull(
201201
serverTimestampBehavior, "Provided serverTimestampBehavior value must not be null.");
202202
Map<String, Object> data = getData(serverTimestampBehavior);
203-
return data == null ? null : CustomClassMapper.convertToCustomClass(data, valueType);
203+
return data == null
204+
? null
205+
: CustomClassMapper.convertToCustomClass(data, valueType, getReference());
204206
}
205207

206208
/**
@@ -353,7 +355,9 @@ public <T> T get(
353355
@NonNull Class<T> valueType,
354356
@NonNull ServerTimestampBehavior serverTimestampBehavior) {
355357
Object data = get(fieldPath, serverTimestampBehavior);
356-
return data == null ? null : CustomClassMapper.convertToCustomClass(data, valueType);
358+
return data == null
359+
? null
360+
: CustomClassMapper.convertToCustomClass(data, valueType, getReference());
357361
}
358362

359363
/**

firebase-firestore/src/main/java/com/google/firebase/firestore/UserDataConverter.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,23 @@ public UserDataConverter(DatabaseId databaseId) {
7373
this.databaseId = databaseId;
7474
}
7575

76-
/** Parse document data from a non-merge set() call. */
76+
/**
77+
* Parse document data from a non-merge set() call.
78+
*
79+
* @param input A map or POJO object representing document data.
80+
*/
7781
public ParsedSetData parseSetData(Object input) {
7882
ParseAccumulator accumulator = new ParseAccumulator(UserData.Source.Set);
7983
ObjectValue updateData = convertAndParseDocumentData(input, accumulator.rootContext());
8084
return accumulator.toSetData(updateData);
8185
}
8286

83-
/** Parse document data from a set() call with SetOptions.merge() set. */
87+
/**
88+
* Parse document data from a set() call with SetOptions.merge() set.
89+
*
90+
* @param input A map or POJO object representing document data.
91+
* @param fieldMask A {@link FieldMask} object representing the fields to be merged.
92+
*/
8493
public ParsedSetData parseMergeData(Object input, @Nullable FieldMask fieldMask) {
8594
ParseAccumulator accumulator = new ParseAccumulator(UserData.Source.MergeSet);
8695
ObjectValue updateData = convertAndParseDocumentData(input, accumulator.rootContext());

firebase-firestore/src/main/java/com/google/firebase/firestore/util/CustomClassMapper.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,12 @@ public static Map<String, Object> convertToPlainJavaTypes(Map<?, Object> update)
9191
*
9292
* @param object The representation of the JSON data
9393
* @param clazz The class of the object to convert to
94+
* @param docRef The value to set to {@link DocumentId} annotated fields in the custom class.
9495
* @return The POJO object.
9596
*/
96-
public static <T> T convertToCustomClass(Object object, Class<T> clazz) {
97+
public static <T> T convertToCustomClass(
98+
Object object, Class<T> clazz, DocumentReference docRef) {
99+
// TODO(wuandy): Use DeserializeContext to encapsulate ErrorPath and docRef.
97100
return deserializeToClass(object, clazz, ErrorPath.EMPTY);
98101
}
99102

@@ -754,6 +757,7 @@ private Type resolveType(Type type, Map<TypeVariable<Class<T>>, Type> types) {
754757
}
755758

756759
Map<String, Object> serialize(T object, ErrorPath path) {
760+
// TODO(wuandy): Add logic to skip @DocumentId annotated fields in serialization.
757761
if (!clazz.isAssignableFrom(object.getClass())) {
758762
throw new IllegalArgumentException(
759763
"Can't serialize object of class "
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.firestore.util;
16+
17+
import com.google.firebase.annotations.PublicApi;
18+
import com.google.firebase.firestore.DocumentReference;
19+
import com.google.firebase.firestore.DocumentSnapshot;
20+
import java.lang.annotation.ElementType;
21+
import java.lang.annotation.Retention;
22+
import java.lang.annotation.RetentionPolicy;
23+
import java.lang.annotation.Target;
24+
25+
/**
26+
* Annotation used to mark a POJO field to be automatically populated with the document's ID when
27+
* the POJO is created from a Firestore document (e.g. via {@link DocumentSnapshot#toObject}).
28+
*
29+
* <p>This annotation can only be applied to fields of String or {@link DocumentReference},
30+
* otherwise a runtime exception will be thrown.
31+
*
32+
* <p>When writing a POJO to Firestore, the @DocumentId-annotated field will be ignored, to allow
33+
* writing to any documents that are not the origin of the POJO.
34+
*/
35+
@PublicApi
36+
@Retention(RetentionPolicy.RUNTIME)
37+
@Target({ElementType.FIELD})
38+
@interface DocumentId {}

firebase-firestore/src/test/java/com/google/firebase/firestore/util/MapperTest.java

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static org.junit.Assert.assertNull;
2222
import static org.junit.Assert.fail;
2323

24+
import com.google.firebase.firestore.DocumentReference;
2425
import com.google.firebase.firestore.Exclude;
2526
import com.google.firebase.firestore.PropertyName;
2627
import com.google.firebase.firestore.ThrowOnExtraProperties;
@@ -899,7 +900,7 @@ public void setValue(String value) {
899900

900901
private static <T> T deserialize(String jsonString, Class<T> clazz) {
901902
Map<String, Object> json = fromSingleQuotedString(jsonString);
902-
return CustomClassMapper.convertToCustomClass(json, clazz);
903+
return CustomClassMapper.convertToCustomClass(json, clazz, null);
903904
}
904905

905906
private static Object serialize(Object object) {
@@ -919,6 +920,15 @@ private static void assertExceptionContains(String partialMessage, Runnable run)
919920
}
920921
}
921922

923+
private static <T> T convertToCustomClass(
924+
Object object, Class<T> clazz, DocumentReference docRef) {
925+
return CustomClassMapper.convertToCustomClass(object, clazz, docRef);
926+
}
927+
928+
private static <T> T convertToCustomClass(Object object, Class<T> clazz) {
929+
return CustomClassMapper.convertToCustomClass(object, clazz, null);
930+
}
931+
922932
@Test
923933
public void primitiveDeserializeString() {
924934
StringBean bean = deserialize("{'value': 'foo'}", StringBean.class);
@@ -1339,8 +1349,7 @@ public void beansCanContainMaps() {
13391349
public void beansCanContainUpperBoundedMaps() {
13401350
Date date = new Date(1491847082123L);
13411351
Map<String, Object> source = map("values", map("foo", date));
1342-
UpperBoundedMapBean bean =
1343-
CustomClassMapper.convertToCustomClass(source, UpperBoundedMapBean.class);
1352+
UpperBoundedMapBean bean = convertToCustomClass(source, UpperBoundedMapBean.class);
13441353
Map<String, Object> expected = map("foo", date);
13451354
assertEquals(expected, bean.values);
13461355
}
@@ -1349,8 +1358,7 @@ public void beansCanContainUpperBoundedMaps() {
13491358
public void beansCanContainMultiBoundedMaps() {
13501359
Date date = new Date(1491847082123L);
13511360
Map<String, Object> source = map("map", map("values", map("foo", date)));
1352-
MultiBoundedMapHolderBean bean =
1353-
CustomClassMapper.convertToCustomClass(source, MultiBoundedMapHolderBean.class);
1361+
MultiBoundedMapHolderBean bean = convertToCustomClass(source, MultiBoundedMapHolderBean.class);
13541362

13551363
Map<String, Object> expected = map("foo", date);
13561364
assertEquals(expected, bean.map.values);
@@ -1367,7 +1375,7 @@ public void beansCanContainUnboundedMaps() {
13671375
public void beansCanContainUnboundedTypeVariableMaps() {
13681376
Map<String, Object> source = map("map", map("values", map("foo", "bar")));
13691377
UnboundedTypeVariableMapHolderBean bean =
1370-
CustomClassMapper.convertToCustomClass(source, UnboundedTypeVariableMapHolderBean.class);
1378+
convertToCustomClass(source, UnboundedTypeVariableMapHolderBean.class);
13711379

13721380
Map<String, Object> expected = map("foo", "bar");
13731381
assertEquals(expected, bean.map.values);
@@ -1823,63 +1831,60 @@ public void objectAcceptsAnyObject() {
18231831

18241832
@Test
18251833
public void objectClassCanBePassedInAtTopLevel() {
1826-
assertEquals("foo", CustomClassMapper.convertToCustomClass("foo", Object.class));
1827-
assertEquals(1, CustomClassMapper.convertToCustomClass(1, Object.class));
1828-
assertEquals(1L, CustomClassMapper.convertToCustomClass(1L, Object.class));
1829-
assertEquals(true, CustomClassMapper.convertToCustomClass(true, Object.class));
1830-
assertEquals(1.1, CustomClassMapper.convertToCustomClass(1.1, Object.class));
1834+
assertEquals("foo", convertToCustomClass("foo", Object.class));
1835+
assertEquals(1, convertToCustomClass(1, Object.class));
1836+
assertEquals(1L, convertToCustomClass(1L, Object.class));
1837+
assertEquals(true, convertToCustomClass(true, Object.class));
1838+
assertEquals(1.1, convertToCustomClass(1.1, Object.class));
18311839
List<String> fooList = Collections.singletonList("foo");
1832-
assertEquals(fooList, CustomClassMapper.convertToCustomClass(fooList, Object.class));
1840+
assertEquals(fooList, convertToCustomClass(fooList, Object.class));
18331841
Map<String, String> fooMap = Collections.singletonMap("foo", "bar");
1834-
assertEquals(fooMap, CustomClassMapper.convertToCustomClass(fooMap, Object.class));
1842+
assertEquals(fooMap, convertToCustomClass(fooMap, Object.class));
18351843
}
18361844

18371845
@Test
18381846
public void primitiveClassesCanBePassedInTopLevel() {
1839-
assertEquals("foo", CustomClassMapper.convertToCustomClass("foo", String.class));
1840-
assertEquals((Integer) 1, CustomClassMapper.convertToCustomClass(1, Integer.class));
1841-
assertEquals((Long) 1L, CustomClassMapper.convertToCustomClass(1L, Long.class));
1842-
assertEquals(true, CustomClassMapper.convertToCustomClass(true, Boolean.class));
1843-
assertEquals((Double) 1.1, CustomClassMapper.convertToCustomClass(1.1, Double.class));
1847+
assertEquals("foo", convertToCustomClass("foo", String.class));
1848+
assertEquals((Integer) 1, convertToCustomClass(1, Integer.class));
1849+
assertEquals((Long) 1L, convertToCustomClass(1L, Long.class));
1850+
assertEquals(true, convertToCustomClass(true, Boolean.class));
1851+
assertEquals((Double) 1.1, convertToCustomClass(1.1, Double.class));
18441852
}
18451853

18461854
@Test
18471855
public void passingInListTopLevelThrows() {
18481856
assertExceptionContains(
18491857
"Class java.util.List has generic type parameters, please use GenericTypeIndicator "
18501858
+ "instead",
1851-
() -> CustomClassMapper.convertToCustomClass(Collections.singletonList("foo"), List.class));
1859+
() -> convertToCustomClass(Collections.singletonList("foo"), List.class));
18521860
}
18531861

18541862
@Test
18551863
public void passingInMapTopLevelThrows() {
18561864
assertExceptionContains(
18571865
"Class java.util.Map has generic type parameters, please use GenericTypeIndicator "
18581866
+ "instead",
1859-
() ->
1860-
CustomClassMapper.convertToCustomClass(
1861-
Collections.singletonMap("foo", "bar"), Map.class));
1867+
() -> convertToCustomClass(Collections.singletonMap("foo", "bar"), Map.class));
18621868
}
18631869

18641870
@Test
18651871
public void passingInCharacterTopLevelThrows() {
18661872
assertExceptionContains(
18671873
"Deserializing values to Character is not supported",
1868-
() -> CustomClassMapper.convertToCustomClass('1', Character.class));
1874+
() -> convertToCustomClass('1', Character.class));
18691875
}
18701876

18711877
@Test
18721878
public void passingInShortTopLevelThrows() {
18731879
assertExceptionContains(
18741880
"Deserializing values to Short is not supported",
1875-
() -> CustomClassMapper.convertToCustomClass(1, Short.class));
1881+
() -> convertToCustomClass(1, Short.class));
18761882
}
18771883

18781884
@Test
18791885
public void passingInByteTopLevelThrows() {
18801886
assertExceptionContains(
1881-
"Deserializing values to Byte is not supported",
1882-
() -> CustomClassMapper.convertToCustomClass(1, Byte.class));
1887+
"Deserializing values to Byte is not supported", () -> convertToCustomClass(1, Byte.class));
18831888
}
18841889

18851890
@Test
@@ -1916,13 +1921,13 @@ public void collectionsCantBeDeserialized() {
19161921

19171922
@Test
19181923
public void allowNullEverywhere() {
1919-
assertNull(CustomClassMapper.convertToCustomClass(null, Integer.class));
1920-
assertNull(CustomClassMapper.convertToCustomClass(null, String.class));
1921-
assertNull(CustomClassMapper.convertToCustomClass(null, Double.class));
1922-
assertNull(CustomClassMapper.convertToCustomClass(null, Long.class));
1923-
assertNull(CustomClassMapper.convertToCustomClass(null, Boolean.class));
1924-
assertNull(CustomClassMapper.convertToCustomClass(null, StringBean.class));
1925-
assertNull(CustomClassMapper.convertToCustomClass(null, Object.class));
1924+
assertNull(convertToCustomClass(null, Integer.class));
1925+
assertNull(convertToCustomClass(null, String.class));
1926+
assertNull(convertToCustomClass(null, Double.class));
1927+
assertNull(convertToCustomClass(null, Long.class));
1928+
assertNull(convertToCustomClass(null, Boolean.class));
1929+
assertNull(convertToCustomClass(null, StringBean.class));
1930+
assertNull(convertToCustomClass(null, Object.class));
19261931
}
19271932

19281933
@Test
@@ -2233,7 +2238,7 @@ public void deserializationFailureIncludesPath() {
22332238
Object serialized = Collections.singletonMap("value", (short) 1);
22342239

22352240
try {
2236-
CustomClassMapper.convertToCustomClass(serialized, ShortBean.class);
2241+
convertToCustomClass(serialized, ShortBean.class);
22372242
fail("should have thrown");
22382243
} catch (RuntimeException e) {
22392244
assertEquals(

0 commit comments

Comments
 (0)