|
24 | 24 | import com.google.firestore.v1.Value;
|
25 | 25 | import com.google.protobuf.Timestamp;
|
26 | 26 | import com.google.type.LatLng;
|
| 27 | +import java.util.ArrayList; |
| 28 | +import java.util.Collections; |
27 | 29 | import java.util.Iterator;
|
28 | 30 | import java.util.List;
|
29 | 31 | import java.util.Map;
|
@@ -249,4 +251,93 @@ private static int compareMaps(MapValue left, MapValue right) {
|
249 | 251 | // Only equal if both iterators are exhausted.
|
250 | 252 | return Util.compareBooleans(iterator1.hasNext(), iterator2.hasNext());
|
251 | 253 | }
|
| 254 | + |
| 255 | + /** Generate the canonical ID for the provided field value (as used in Target serialization). */ |
| 256 | + public static String canonicalId(Value value) { |
| 257 | + StringBuilder builder = new StringBuilder(); |
| 258 | + canonifyValue(builder, value); |
| 259 | + return builder.toString(); |
| 260 | + } |
| 261 | + |
| 262 | + // TODO(mrschmidt): Use in target serialization and migrate all existing TargetData |
| 263 | + private static void canonifyValue(StringBuilder builder, Value value) { |
| 264 | + switch (value.getValueTypeCase()) { |
| 265 | + case NULL_VALUE: |
| 266 | + builder.append("null"); |
| 267 | + break; |
| 268 | + case BOOLEAN_VALUE: |
| 269 | + builder.append(value.getBooleanValue()); |
| 270 | + break; |
| 271 | + case INTEGER_VALUE: |
| 272 | + builder.append(value.getIntegerValue()); |
| 273 | + break; |
| 274 | + case DOUBLE_VALUE: |
| 275 | + builder.append(value.getDoubleValue()); |
| 276 | + break; |
| 277 | + case TIMESTAMP_VALUE: |
| 278 | + canonifyTimestamp(builder, value.getTimestampValue()); |
| 279 | + break; |
| 280 | + case STRING_VALUE: |
| 281 | + builder.append(value.getStringValue()); |
| 282 | + break; |
| 283 | + case BYTES_VALUE: |
| 284 | + builder.append(Util.toDebugString(value.getBytesValue())); |
| 285 | + break; |
| 286 | + case REFERENCE_VALUE: |
| 287 | + // TODO(mrschmidt): Use document key only |
| 288 | + builder.append(value.getReferenceValue()); |
| 289 | + break; |
| 290 | + case GEO_POINT_VALUE: |
| 291 | + canonifyGeoPoint(builder, value.getGeoPointValue()); |
| 292 | + break; |
| 293 | + case ARRAY_VALUE: |
| 294 | + canonifyArray(builder, value.getArrayValue()); |
| 295 | + break; |
| 296 | + case MAP_VALUE: |
| 297 | + canonifyObject(builder, value.getMapValue()); |
| 298 | + break; |
| 299 | + default: |
| 300 | + throw fail("Invalid value type: " + value.getValueTypeCase()); |
| 301 | + } |
| 302 | + } |
| 303 | + |
| 304 | + private static void canonifyTimestamp(StringBuilder builder, Timestamp timestamp) { |
| 305 | + builder.append(String.format("time(%s,%s)", timestamp.getSeconds(), timestamp.getNanos())); |
| 306 | + } |
| 307 | + |
| 308 | + private static void canonifyGeoPoint(StringBuilder builder, LatLng latLng) { |
| 309 | + builder.append(String.format("geo(%s,%s)", latLng.getLatitude(), latLng.getLongitude())); |
| 310 | + } |
| 311 | + |
| 312 | + private static void canonifyObject(StringBuilder builder, MapValue mapValue) { |
| 313 | + // Even though MapValue are likely sorted correctly based on their insertion order (e.g. when |
| 314 | + // received from the backend), local modifications can bring elements out of order. We need to |
| 315 | + // re-sort the elements to ensure that canonical IDs are independent of insertion order. |
| 316 | + List<String> keys = new ArrayList<>(mapValue.getFieldsMap().keySet()); |
| 317 | + Collections.sort(keys); |
| 318 | + |
| 319 | + builder.append("{"); |
| 320 | + boolean first = true; |
| 321 | + for (String key : keys) { |
| 322 | + if (!first) { |
| 323 | + builder.append(","); |
| 324 | + } else { |
| 325 | + first = false; |
| 326 | + } |
| 327 | + builder.append(key).append(":"); |
| 328 | + canonifyValue(builder, mapValue.getFieldsOrThrow(key)); |
| 329 | + } |
| 330 | + builder.append("}"); |
| 331 | + } |
| 332 | + |
| 333 | + private static void canonifyArray(StringBuilder builder, ArrayValue arrayValue) { |
| 334 | + builder.append("["); |
| 335 | + for (int i = 0; i < arrayValue.getValuesCount(); ++i) { |
| 336 | + canonifyValue(builder, arrayValue.getValues(i)); |
| 337 | + if (i != arrayValue.getValuesCount() - 1) { |
| 338 | + builder.append(","); |
| 339 | + } |
| 340 | + } |
| 341 | + builder.append("]"); |
| 342 | + } |
252 | 343 | }
|
0 commit comments