|
28 | 28 | import io.grpc.StatusRuntimeException;
|
29 | 29 | import java.security.SecureRandom;
|
30 | 30 | import java.util.ArrayList;
|
| 31 | +import java.util.Collection; |
31 | 32 | import java.util.Collections;
|
32 | 33 | import java.util.Comparator;
|
33 | 34 | import java.util.Iterator;
|
@@ -164,8 +165,8 @@ public static Continuation<Void, Void> voidErrorTransformer() {
|
164 | 165 | * Converts varargs from an update call to a list of objects, ensuring that the arguments
|
165 | 166 | * alternate between String/FieldPath and Objects.
|
166 | 167 | *
|
167 |
| - * @param fieldPathOffset The offset of the first field path in the original update API (used as |
168 |
| - * the index in error messages) |
| 168 | + * @param fieldPathOffset The offset of the first field path in the before update API (used as the |
| 169 | + * index in error messages) |
169 | 170 | */
|
170 | 171 | public static List<Object> collectUpdateArguments(
|
171 | 172 | int fieldPathOffset, Object field, Object val, Object... fieldsAndValues) {
|
@@ -277,9 +278,89 @@ public static StringBuilder repeatSequence(
|
277 | 278 | return sb;
|
278 | 279 | }
|
279 | 280 |
|
| 281 | + /** |
| 282 | + * Compares two collections for equality using the provided comparator. The method computes the |
| 283 | + * intersection and invokes `onAdd` for every element that is in `after` but not `before`. |
| 284 | + * `onDelete` is invoked for every element in `before` but missing from `after`. |
| 285 | + * |
| 286 | + * <p>The method creates a copy of both `before` and `after` and runs in O(n log n), where n is |
| 287 | + * the size of the two lists. |
| 288 | + * |
| 289 | + * @param before The elements that exist in the original set. |
| 290 | + * @param after The elements to diff against the original set. |
| 291 | + * @param comparator The comparator to use to define equality between elements. |
| 292 | + * @param onAdd A function to invoke for every element that is part of `after` but not `before`. |
| 293 | + * @param onDelete A function to invoke for every element that is part of `before` but not |
| 294 | + * `after`. |
| 295 | + */ |
| 296 | + public static <T> void diffCollections( |
| 297 | + Collection<T> before, |
| 298 | + Collection<T> after, |
| 299 | + Comparator<T> comparator, |
| 300 | + Consumer<T> onAdd, |
| 301 | + Consumer<T> onDelete) { |
| 302 | + List<T> beforeEntries = new ArrayList<>(before); |
| 303 | + Collections.sort(beforeEntries, comparator); |
| 304 | + Iterator<T> beforeIt = beforeEntries.iterator(); |
| 305 | + @Nullable T beforeValue = advanceIterator(beforeIt); |
| 306 | + |
| 307 | + List<T> afterEntries = new ArrayList<>(after); |
| 308 | + Collections.sort(afterEntries, comparator); |
| 309 | + Iterator<T> afterIt = afterEntries.iterator(); |
| 310 | + @Nullable T afterValue = advanceIterator(afterIt); |
| 311 | + |
| 312 | + while (beforeValue != null || afterValue != null) { |
| 313 | + boolean deleted = false; |
| 314 | + boolean added = false; |
| 315 | + |
| 316 | + if (beforeValue != null && afterValue != null) { |
| 317 | + int cmp = comparator.compare(beforeValue, afterValue); |
| 318 | + if (cmp < 0) { |
| 319 | + deleted = true; |
| 320 | + } else if (cmp > 0) { |
| 321 | + added = true; |
| 322 | + } |
| 323 | + } else if (beforeValue != null) { |
| 324 | + deleted = true; |
| 325 | + } else { |
| 326 | + added = true; |
| 327 | + } |
| 328 | + |
| 329 | + if (deleted) { |
| 330 | + onDelete.accept(beforeValue); |
| 331 | + beforeValue = advanceIterator(beforeIt); |
| 332 | + } else if (added) { |
| 333 | + onAdd.accept(afterValue); |
| 334 | + afterValue = advanceIterator(afterIt); |
| 335 | + } else { |
| 336 | + beforeValue = advanceIterator(beforeIt); |
| 337 | + afterValue = advanceIterator(afterIt); |
| 338 | + } |
| 339 | + } |
| 340 | + } |
| 341 | + |
| 342 | + /** |
| 343 | + * Compares two collections for equality using their natural ordering. The method computes the |
| 344 | + * intersection and invokes `onAdd` for every element that is in `after` but not `before`. |
| 345 | + * `onDelete` is invoked for every element in `before` but missing from `after`. |
| 346 | + * |
| 347 | + * <p>The method creates a copy of both `before` and `after` and runs in O(n log n), where n is |
| 348 | + * the size of the two lists. |
| 349 | + * |
| 350 | + * @param before The elements that exist in the original set. |
| 351 | + * @param after The elements to diff against the original set. |
| 352 | + * @param onAdd A function to invoke for every element that is part of `after` but not `before`. |
| 353 | + * @param onDelete A function to invoke for every element that is part of `before` but not |
| 354 | + * `after`. |
| 355 | + */ |
| 356 | + public static <T extends Comparable<T>> void diffCollections( |
| 357 | + Collection<T> before, Collection<T> after, Consumer<T> onAdd, Consumer<T> onDelete) { |
| 358 | + diffCollections(before, after, Comparable::compareTo, onAdd, onDelete); |
| 359 | + } |
| 360 | + |
280 | 361 | /** Returns the next element from the iterator or `null` if none available. */
|
281 | 362 | @Nullable
|
282 |
| - public static <T> T advanceIterator(Iterator<T> it) { |
| 363 | + private static <T> T advanceIterator(Iterator<T> it) { |
283 | 364 | return it.hasNext() ? it.next() : null;
|
284 | 365 | }
|
285 | 366 | }
|
0 commit comments