Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Make it back to static
  • Loading branch information
wu-hui committed May 23, 2019
commit c7d95083481bd772f389f3375a4720dcc9819fd0
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ public Task<Void> set(@NonNull Object data, @NonNull SetOptions options) {
checkNotNull(options, "Provided options must not be null.");
ParsedSetData parsed =
options.isMerge()
? firestore.getDataConverter().parseMergeData(data, options.getFieldMask(), getId())
: firestore.getDataConverter().parseSetData(data, getId());
? firestore.getDataConverter().parseMergeData(data, options.getFieldMask())
: firestore.getDataConverter().parseSetData(data);
return firestore
.getClient()
.write(parsed.toMutationList(key, Precondition.NONE))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,7 @@ public <T> T toObject(
Map<String, Object> data = getData(serverTimestampBehavior);
return data == null
? null
: CustomClassMapper.withDocumentId(key.getPath().getLastSegment())
.convertToCustomClass(data, valueType);
: CustomClassMapper.convertToCustomClass(data, valueType, key.getPath().getLastSegment());
}

/**
Expand Down Expand Up @@ -358,8 +357,7 @@ public <T> T get(
Object data = get(fieldPath, serverTimestampBehavior);
return data == null
? null
: CustomClassMapper.withDocumentId(key.getPath().getLastSegment())
.convertToCustomClass(data, valueType);
: CustomClassMapper.convertToCustomClass(data, valueType, key.getPath().getFirstSegment());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,8 @@ public Transaction set(
checkNotNull(options, "Provided options must not be null.");
ParsedSetData parsed =
options.isMerge()
? firestore
.getDataConverter()
.parseMergeData(data, options.getFieldMask(), documentRef.getId())
: firestore.getDataConverter().parseSetData(data, documentRef.getId());
? firestore.getDataConverter().parseMergeData(data, options.getFieldMask())
: firestore.getDataConverter().parseSetData(data);
transaction.set(documentRef.getKey(), parsed);
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,10 @@ public UserDataConverter(DatabaseId databaseId) {
* Parse document data from a non-merge set() call.
*
* @param input A POJO object representing document data.
* @param expectedId If `input` has any fields annotated with {@link DocumentId}, they should
* either be null or match this parameter.
*/
public ParsedSetData parseSetData(Object input, String expectedId) {
public ParsedSetData parseSetData(Object input) {
ParseAccumulator accumulator = new ParseAccumulator(UserData.Source.Set);
ObjectValue updateData =
convertAndParseDocumentData(input, accumulator.rootContext(), expectedId);
ObjectValue updateData = convertAndParseDocumentData(input, accumulator.rootContext());
return accumulator.toSetData(updateData);
}

Expand All @@ -92,14 +89,10 @@ public ParsedSetData parseSetData(Object input, String expectedId) {
*
* @param input A POJO object representing document data.
* @param fieldMask A {@link FieldMask} object representing the fields to be merged.
* @param expectedId If `input` has any fields annotated with {@link DocumentId}, they should
* either be null or match this parameter.
*/
public ParsedSetData parseMergeData(
Object input, @Nullable FieldMask fieldMask, String expectedId) {
public ParsedSetData parseMergeData(Object input, @Nullable FieldMask fieldMask) {
ParseAccumulator accumulator = new ParseAccumulator(UserData.Source.MergeSet);
ObjectValue updateData =
convertAndParseDocumentData(input, accumulator.rootContext(), expectedId);
ObjectValue updateData = convertAndParseDocumentData(input, accumulator.rootContext());

if (fieldMask != null) {
// Verify that all elements specified in the field mask are part of the parsed context.
Expand Down Expand Up @@ -214,7 +207,7 @@ public FieldValue parseQueryValue(Object input) {

/** Converts a POJO to native types and then parses it into model types. */
private FieldValue convertAndParseFieldData(Object input, ParseContext context) {
Object converted = CustomClassMapper.withDocumentId(null).convertToPlainJavaTypes(input);
Object converted = CustomClassMapper.convertToPlainJavaTypes(input);
return parseData(converted, context);
}

Expand All @@ -223,8 +216,7 @@ private FieldValue convertAndParseFieldData(Object input, ParseContext context)
* conform to document data (i.e. it must parse into an ObjectValue model type) and will throw an
* appropriate error otherwise.
*/
private ObjectValue convertAndParseDocumentData(
Object input, ParseContext context, String expectedId) {
private ObjectValue convertAndParseDocumentData(Object input, ParseContext context) {
String badDocReason =
"Invalid data. Data must be a Map<String, Object> or a suitable POJO object, but it was ";

Expand All @@ -234,7 +226,7 @@ private ObjectValue convertAndParseDocumentData(
throw new IllegalArgumentException(badDocReason + "an array");
}

Object converted = CustomClassMapper.withDocumentId(expectedId).convertToPlainJavaTypes(input);
Object converted = CustomClassMapper.convertToPlainJavaTypes(input);
FieldValue value = parseData(converted, context);

if (!(value instanceof ObjectValue)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,8 @@ public WriteBatch set(
verifyNotCommitted();
ParsedSetData parsed =
options.isMerge()
? firestore
.getDataConverter()
.parseMergeData(data, options.getFieldMask(), documentRef.getId())
: firestore.getDataConverter().parseSetData(data, documentRef.getId());
? firestore.getDataConverter().parseMergeData(data, options.getFieldMask())
: firestore.getDataConverter().parseSetData(data);
mutations.addAll(parsed.toMutationList(documentRef.getKey(), Precondition.NONE));
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,35 +66,18 @@ private static void hardAssert(boolean assertion, String message) {
}
}

/**
* Returns a mapper with a given document ID. The document ID will be used to populate the {@link
* com.google.firebase.firestore.DocumentId} annotated fields of the POJO of the custom class;
* when converting a POJO to plain Java type, the @DocumentId-annotated fields should be either
* null or match the given document ID.
*/
// TODO: actually implement documentId population/validation logic during (de)serialization.
public static CustomClassMapper withDocumentId(String documentId) {
return new CustomClassMapper(documentId);
}

private CustomClassMapper(String documentId) {
this.documentId = documentId;
}

private String documentId = null;

/**
* Converts a Java representation of JSON data to standard library Java data types: Map, Array,
* String, Double, Integer and Boolean. POJOs are converted to Java Maps.
*
* @param object The representation of the JSON data
* @return JSON representation containing only standard library Java types
*/
public Object convertToPlainJavaTypes(Object object) {
public static Object convertToPlainJavaTypes(Object object) {
return serialize(object);
}

public Map<String, Object> convertToPlainJavaTypes(Map<?, Object> update) {
public static Map<String, Object> convertToPlainJavaTypes(Map<?, Object> update) {
Object converted = serialize(update);
hardAssert(converted instanceof Map);
@SuppressWarnings("unchecked")
Expand All @@ -108,18 +91,21 @@ public Map<String, Object> convertToPlainJavaTypes(Map<?, Object> update) {
*
* @param object The representation of the JSON data
* @param clazz The class of the object to convert to
* @param documentId The value to set to {@link com.google.firebase.firestore.DocumentId}
* annotated fields in the custom class.
* @return The POJO object.
*/
public <T> T convertToCustomClass(Object object, Class<T> clazz) {
public static <T> T convertToCustomClass(Object object, Class<T> clazz, String documentId) {
// TODO: Use DeserializeContext to encapsulate ErrorPath and documentId.
return deserializeToClass(object, clazz, ErrorPath.EMPTY);
}

private <T> Object serialize(T o) {
private static <T> Object serialize(T o) {
return serialize(o, ErrorPath.EMPTY);
}

@SuppressWarnings("unchecked")
private <T> Object serialize(T o, ErrorPath path) {
private static <T> Object serialize(T o, ErrorPath path) {
if (path.getLength() > MAX_DEPTH) {
throw serializeError(
path,
Expand Down Expand Up @@ -189,7 +175,7 @@ private <T> Object serialize(T o, ErrorPath path) {
} else {
Class<T> clazz = (Class<T>) o.getClass();
BeanMapper<T> mapper = loadOrCreateBeanMapperForClass(clazz);
return mapper.serialize(o, path, this);
return mapper.serialize(o, path);
}
}

Expand Down Expand Up @@ -770,7 +756,8 @@ private Type resolveType(Type type, Map<TypeVariable<Class<T>>, Type> types) {
}
}

Map<String, Object> serialize(T object, ErrorPath path, CustomClassMapper classMapper) {
Map<String, Object> serialize(T object, ErrorPath path) {
// TODO: Add logic to skip @DocumentId annotated fields in serialization.
if (!clazz.isAssignableFrom(object.getClass())) {
throw new IllegalArgumentException(
"Can't serialize object of class "
Expand Down Expand Up @@ -802,7 +789,7 @@ Map<String, Object> serialize(T object, ErrorPath path, CustomClassMapper classM
// Replace null ServerTimestamp-annotated fields with the sentinel.
serializedValue = FieldValue.serverTimestamp();
} else {
serializedValue = classMapper.serialize(propertyValue, path.child(property));
serializedValue = CustomClassMapper.serialize(propertyValue, path.child(property));
}
result.put(property, serializedValue);
}
Expand Down Expand Up @@ -1028,4 +1015,18 @@ public String toString() {
}
}
}

static class DeserializeContext {
final ErrorPath errorPath;
final String documentId;

DeserializeContext(ErrorPath path, String docId) {
errorPath = path;
documentId = docId;
}

DeserializeContext newInstanceWithErrorPath(ErrorPath newPath) {
return new DeserializeContext(newPath, documentId);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -899,11 +899,11 @@ public void setValue(String value) {

private static <T> T deserialize(String jsonString, Class<T> clazz) {
Map<String, Object> json = fromSingleQuotedString(jsonString);
return CustomClassMapper.withDocumentId(null).convertToCustomClass(json, clazz);
return CustomClassMapper.convertToCustomClass(json, clazz, null);
}

private static Object serialize(Object object) {
return CustomClassMapper.withDocumentId(null).convertToPlainJavaTypes(object);
return CustomClassMapper.convertToPlainJavaTypes(object);
}

private static void assertJson(String expected, Object actual) {
Expand Down Expand Up @@ -1340,8 +1340,7 @@ public void beansCanContainUpperBoundedMaps() {
Date date = new Date(1491847082123L);
Map<String, Object> source = map("values", map("foo", date));
UpperBoundedMapBean bean =
CustomClassMapper.withDocumentId(null)
.convertToCustomClass(source, UpperBoundedMapBean.class);
CustomClassMapper.convertToCustomClass(source, UpperBoundedMapBean.class, null);
Map<String, Object> expected = map("foo", date);
assertEquals(expected, bean.values);
}
Expand All @@ -1351,8 +1350,7 @@ public void beansCanContainMultiBoundedMaps() {
Date date = new Date(1491847082123L);
Map<String, Object> source = map("map", map("values", map("foo", date)));
MultiBoundedMapHolderBean bean =
CustomClassMapper.withDocumentId(null)
.convertToCustomClass(source, MultiBoundedMapHolderBean.class);
CustomClassMapper.convertToCustomClass(source, MultiBoundedMapHolderBean.class, null);

Map<String, Object> expected = map("foo", date);
assertEquals(expected, bean.map.values);
Expand All @@ -1369,8 +1367,8 @@ public void beansCanContainUnboundedMaps() {
public void beansCanContainUnboundedTypeVariableMaps() {
Map<String, Object> source = map("map", map("values", map("foo", "bar")));
UnboundedTypeVariableMapHolderBean bean =
CustomClassMapper.withDocumentId(null)
.convertToCustomClass(source, UnboundedTypeVariableMapHolderBean.class);
CustomClassMapper.convertToCustomClass(
source, UnboundedTypeVariableMapHolderBean.class, null);

Map<String, Object> expected = map("foo", "bar");
assertEquals(expected, bean.map.values);
Expand Down Expand Up @@ -1826,36 +1824,24 @@ public void objectAcceptsAnyObject() {

@Test
public void objectClassCanBePassedInAtTopLevel() {
assertEquals(
"foo", CustomClassMapper.withDocumentId(null).convertToCustomClass("foo", Object.class));
assertEquals(1, CustomClassMapper.withDocumentId(null).convertToCustomClass(1, Object.class));
assertEquals(1L, CustomClassMapper.withDocumentId(null).convertToCustomClass(1L, Object.class));
assertEquals(
true, CustomClassMapper.withDocumentId(null).convertToCustomClass(true, Object.class));
assertEquals(
1.1, CustomClassMapper.withDocumentId(null).convertToCustomClass(1.1, Object.class));
assertEquals("foo", CustomClassMapper.convertToCustomClass("foo", Object.class, null));
assertEquals(1, CustomClassMapper.convertToCustomClass(1, Object.class, null));
assertEquals(1L, CustomClassMapper.convertToCustomClass(1L, Object.class, null));
assertEquals(true, CustomClassMapper.convertToCustomClass(true, Object.class, null));
assertEquals(1.1, CustomClassMapper.convertToCustomClass(1.1, Object.class, null));
List<String> fooList = Collections.singletonList("foo");
assertEquals(
fooList,
CustomClassMapper.withDocumentId(null).convertToCustomClass(fooList, Object.class));
assertEquals(fooList, CustomClassMapper.convertToCustomClass(fooList, Object.class, null));
Map<String, String> fooMap = Collections.singletonMap("foo", "bar");
assertEquals(
fooMap, CustomClassMapper.withDocumentId(null).convertToCustomClass(fooMap, Object.class));
assertEquals(fooMap, CustomClassMapper.convertToCustomClass(fooMap, Object.class, null));
}

@Test
public void primitiveClassesCanBePassedInTopLevel() {
assertEquals(
"foo", CustomClassMapper.withDocumentId(null).convertToCustomClass("foo", String.class));
assertEquals(
(Integer) 1, CustomClassMapper.withDocumentId(null).convertToCustomClass(1, Integer.class));
assertEquals(
(Long) 1L, CustomClassMapper.withDocumentId(null).convertToCustomClass(1L, Long.class));
assertEquals(
true, CustomClassMapper.withDocumentId(null).convertToCustomClass(true, Boolean.class));
assertEquals(
(Double) 1.1,
CustomClassMapper.withDocumentId(null).convertToCustomClass(1.1, Double.class));
assertEquals("foo", CustomClassMapper.convertToCustomClass("foo", String.class, null));
assertEquals((Integer) 1, CustomClassMapper.convertToCustomClass(1, Integer.class, null));
assertEquals((Long) 1L, CustomClassMapper.convertToCustomClass(1L, Long.class, null));
assertEquals(true, CustomClassMapper.convertToCustomClass(true, Boolean.class, null));
assertEquals((Double) 1.1, CustomClassMapper.convertToCustomClass(1.1, Double.class, null));
}

@Test
Expand All @@ -1864,8 +1850,8 @@ public void passingInListTopLevelThrows() {
"Class java.util.List has generic type parameters, please use GenericTypeIndicator "
+ "instead",
() ->
CustomClassMapper.withDocumentId(null)
.convertToCustomClass(Collections.singletonList("foo"), List.class));
CustomClassMapper.convertToCustomClass(
Collections.singletonList("foo"), List.class, null));
}

@Test
Expand All @@ -1874,29 +1860,29 @@ public void passingInMapTopLevelThrows() {
"Class java.util.Map has generic type parameters, please use GenericTypeIndicator "
+ "instead",
() ->
CustomClassMapper.withDocumentId(null)
.convertToCustomClass(Collections.singletonMap("foo", "bar"), Map.class));
CustomClassMapper.convertToCustomClass(
Collections.singletonMap("foo", "bar"), Map.class, null));
}

@Test
public void passingInCharacterTopLevelThrows() {
assertExceptionContains(
"Deserializing values to Character is not supported",
() -> CustomClassMapper.withDocumentId(null).convertToCustomClass('1', Character.class));
() -> CustomClassMapper.convertToCustomClass('1', Character.class, null));
}

@Test
public void passingInShortTopLevelThrows() {
assertExceptionContains(
"Deserializing values to Short is not supported",
() -> CustomClassMapper.withDocumentId(null).convertToCustomClass(1, Short.class));
() -> CustomClassMapper.convertToCustomClass(1, Short.class, null));
}

@Test
public void passingInByteTopLevelThrows() {
assertExceptionContains(
"Deserializing values to Byte is not supported",
() -> CustomClassMapper.withDocumentId(null).convertToCustomClass(1, Byte.class));
() -> CustomClassMapper.convertToCustomClass(1, Byte.class, null));
}

@Test
Expand Down Expand Up @@ -1933,13 +1919,13 @@ public void collectionsCantBeDeserialized() {

@Test
public void allowNullEverywhere() {
assertNull(CustomClassMapper.withDocumentId(null).convertToCustomClass(null, Integer.class));
assertNull(CustomClassMapper.withDocumentId(null).convertToCustomClass(null, String.class));
assertNull(CustomClassMapper.withDocumentId(null).convertToCustomClass(null, Double.class));
assertNull(CustomClassMapper.withDocumentId(null).convertToCustomClass(null, Long.class));
assertNull(CustomClassMapper.withDocumentId(null).convertToCustomClass(null, Boolean.class));
assertNull(CustomClassMapper.withDocumentId(null).convertToCustomClass(null, StringBean.class));
assertNull(CustomClassMapper.withDocumentId(null).convertToCustomClass(null, Object.class));
assertNull(CustomClassMapper.convertToCustomClass(null, Integer.class, null));
assertNull(CustomClassMapper.convertToCustomClass(null, String.class, null));
assertNull(CustomClassMapper.convertToCustomClass(null, Double.class, null));
assertNull(CustomClassMapper.convertToCustomClass(null, Long.class, null));
assertNull(CustomClassMapper.convertToCustomClass(null, Boolean.class, null));
assertNull(CustomClassMapper.convertToCustomClass(null, StringBean.class, null));
assertNull(CustomClassMapper.convertToCustomClass(null, Object.class, null));
}

@Test
Expand Down Expand Up @@ -2250,7 +2236,7 @@ public void deserializationFailureIncludesPath() {
Object serialized = Collections.singletonMap("value", (short) 1);

try {
CustomClassMapper.withDocumentId(null).convertToCustomClass(serialized, ShortBean.class);
CustomClassMapper.convertToCustomClass(serialized, ShortBean.class, null);
fail("should have thrown");
} catch (RuntimeException e) {
assertEquals(
Expand Down