33
33
import java .io .Serializable ;
34
34
import java .util .AbstractMap ;
35
35
import java .util .Arrays ;
36
+ import java .util .BitSet ;
36
37
import java .util .Collection ;
37
38
import java .util .Collections ;
38
39
import java .util .Comparator ;
40
+ import java .util .HashSet ;
39
41
import java .util .Iterator ;
40
42
import java .util .Map ;
41
43
import java .util .Map .Entry ;
44
+ import java .util .Set ;
42
45
import java .util .SortedMap ;
43
46
import javax .annotation .CheckForNull ;
44
47
import org .checkerframework .checker .nullness .qual .Nullable ;
@@ -337,7 +340,7 @@ public static <K, V> Builder<K, V> builderWithExpectedSize(int expectedSize) {
337
340
}
338
341
339
342
static void checkNoConflict (
340
- boolean safe , String conflictDescription , Entry <?, ?> entry1 , Entry <?, ?> entry2 ) {
343
+ boolean safe , String conflictDescription , Object entry1 , Object entry2 ) {
341
344
if (!safe ) {
342
345
throw conflictException (conflictDescription , entry1 , entry2 );
343
346
}
@@ -384,6 +387,11 @@ public static class Builder<K, V> {
384
387
@ Nullable Object [] alternatingKeysAndValues ;
385
388
int size ;
386
389
boolean entriesUsed ;
390
+ /**
391
+ * If non-null, a duplicate key we found in a previous buildKeepingLast() or buildOrThrow()
392
+ * call. A later buildOrThrow() can simply report this duplicate immediately.
393
+ */
394
+ @ Nullable DuplicateKey duplicateKey ;
387
395
388
396
/**
389
397
* Creates a new builder. The returned builder is equivalent to the builder generated by {@link
@@ -498,10 +506,46 @@ Builder<K, V> combine(Builder<K, V> other) {
498
506
return this ;
499
507
}
500
508
501
- /*
502
- * TODO(kevinb): Should build() and the ImmutableBiMap & ImmutableSortedMap
503
- * versions throw an IllegalStateException instead?
504
- */
509
+ private ImmutableMap <K , V > build (boolean throwIfDuplicateKeys ) {
510
+ if (throwIfDuplicateKeys && duplicateKey != null ) {
511
+ throw duplicateKey .exception ();
512
+ }
513
+ /*
514
+ * If entries is full, then this implementation may end up using the entries array
515
+ * directly and writing over the entry objects with non-terminal entries, but this is
516
+ * safe; if this Builder is used further, it will grow the entries array (so it can't
517
+ * affect the original array), and future build() calls will always copy any entry
518
+ * objects that cannot be safely reused.
519
+ */
520
+ // localAlternatingKeysAndValues is an alias for the alternatingKeysAndValues field, except if
521
+ // we end up removing duplicates in a copy of the array.
522
+ @ Nullable Object [] localAlternatingKeysAndValues ;
523
+ int localSize = size ;
524
+ if (valueComparator == null ) {
525
+ localAlternatingKeysAndValues = alternatingKeysAndValues ;
526
+ } else {
527
+ if (entriesUsed ) {
528
+ alternatingKeysAndValues = Arrays .copyOf (alternatingKeysAndValues , 2 * size );
529
+ }
530
+ localAlternatingKeysAndValues = alternatingKeysAndValues ;
531
+ if (!throwIfDuplicateKeys ) {
532
+ // We want to retain only the last-put value for any given key, before sorting.
533
+ // This could be improved, but orderEntriesByValue is rather rarely used anyway.
534
+ localAlternatingKeysAndValues = lastEntryForEachKey (localAlternatingKeysAndValues , size );
535
+ if (localAlternatingKeysAndValues .length < alternatingKeysAndValues .length ) {
536
+ localSize = localAlternatingKeysAndValues .length >>> 1 ;
537
+ }
538
+ }
539
+ sortEntries (localAlternatingKeysAndValues , localSize , valueComparator );
540
+ }
541
+ entriesUsed = true ;
542
+ ImmutableMap <K , V > map =
543
+ RegularImmutableMap .create (localSize , localAlternatingKeysAndValues , this );
544
+ if (throwIfDuplicateKeys && duplicateKey != null ) {
545
+ throw duplicateKey .exception ();
546
+ }
547
+ return map ;
548
+ }
505
549
506
550
/**
507
551
* Returns a newly-created immutable map. The iteration order of the returned map is the order
@@ -527,40 +571,84 @@ public ImmutableMap<K, V> build() {
527
571
* @throws IllegalArgumentException if duplicate keys were added
528
572
* @since 31.0
529
573
*/
530
- @ SuppressWarnings ("unchecked" )
531
574
public ImmutableMap <K , V > buildOrThrow () {
532
- /*
533
- * If entries is full, then this implementation may end up using the entries array
534
- * directly and writing over the entry objects with non-terminal entries, but this is
535
- * safe; if this Builder is used further, it will grow the entries array (so it can't
536
- * affect the original array), and future build() calls will always copy any entry
537
- * objects that cannot be safely reused.
538
- */
539
- sortEntries ();
540
- entriesUsed = true ;
541
- return RegularImmutableMap .create (size , alternatingKeysAndValues );
575
+ return build (true );
542
576
}
543
577
544
- void sortEntries () {
545
- if (valueComparator != null ) {
546
- if (entriesUsed ) {
547
- alternatingKeysAndValues = Arrays .copyOf (alternatingKeysAndValues , 2 * size );
548
- }
549
- Entry <K , V >[] entries = new Entry [size ];
550
- for (int i = 0 ; i < size ; i ++) {
551
- // requireNonNull is safe because the first `2*size` elements have been filled in.
552
- entries [i ] =
553
- new AbstractMap .SimpleImmutableEntry <K , V >(
554
- (K ) requireNonNull (alternatingKeysAndValues [2 * i ]),
555
- (V ) requireNonNull (alternatingKeysAndValues [2 * i + 1 ]));
578
+ /**
579
+ * Returns a newly-created immutable map, using the last value for any key that was added more
580
+ * than once. The iteration order of the returned map is the order in which entries were
581
+ * inserted into the builder, unless {@link #orderEntriesByValue} was called, in which case
582
+ * entries are sorted by value. If a key was added more than once, it appears in iteration order
583
+ * based on the first time it was added, again unless {@link #orderEntriesByValue} was called.
584
+ *
585
+ * @since NEXT
586
+ */
587
+ public ImmutableMap <K , V > buildKeepingLast () {
588
+ return build (false );
589
+ }
590
+
591
+ static <V > void sortEntries (
592
+ @ Nullable Object [] alternatingKeysAndValues , int size , Comparator <V > valueComparator ) {
593
+ @ SuppressWarnings ({"rawtypes" , "unchecked" })
594
+ Entry <Object , V >[] entries = new Entry [size ];
595
+ for (int i = 0 ; i < size ; i ++) {
596
+ // requireNonNull is safe because the first `2*size` elements have been filled in.
597
+ Object key = requireNonNull (alternatingKeysAndValues [2 * i ]);
598
+ @ SuppressWarnings ("unchecked" )
599
+ V value = (V ) requireNonNull (alternatingKeysAndValues [2 * i + 1 ]);
600
+ entries [i ] = new AbstractMap .SimpleImmutableEntry <Object , V >(key , value );
601
+ }
602
+ Arrays .sort (
603
+ entries , 0 , size , Ordering .from (valueComparator ).onResultOf (Maps .<V >valueFunction ()));
604
+ for (int i = 0 ; i < size ; i ++) {
605
+ alternatingKeysAndValues [2 * i ] = entries [i ].getKey ();
606
+ alternatingKeysAndValues [2 * i + 1 ] = entries [i ].getValue ();
607
+ }
608
+ }
609
+
610
+ private @ Nullable Object [] lastEntryForEachKey (
611
+ @ Nullable Object [] localAlternatingKeysAndValues , int size ) {
612
+ Set <Object > seenKeys = new HashSet <>();
613
+ BitSet dups = new BitSet (); // slots that are overridden by a later duplicate key
614
+ for (int i = size - 1 ; i >= 0 ; i --) {
615
+ Object key = requireNonNull (localAlternatingKeysAndValues [2 * i ]);
616
+ if (!seenKeys .add (key )) {
617
+ dups .set (i );
556
618
}
557
- Arrays .sort (
558
- entries , 0 , size , Ordering .from (valueComparator ).onResultOf (Maps .<V >valueFunction ()));
559
- for (int i = 0 ; i < size ; i ++) {
560
- alternatingKeysAndValues [2 * i ] = entries [i ].getKey ();
561
- alternatingKeysAndValues [2 * i + 1 ] = entries [i ].getValue ();
619
+ }
620
+ if (dups .isEmpty ()) {
621
+ return localAlternatingKeysAndValues ;
622
+ }
623
+ Object [] newAlternatingKeysAndValues = new Object [(size - dups .cardinality ()) * 2 ];
624
+ for (int inI = 0 , outI = 0 ; inI < size * 2 ; ) {
625
+ if (dups .get (inI >>> 1 )) {
626
+ inI += 2 ;
627
+ } else {
628
+ newAlternatingKeysAndValues [outI ++] =
629
+ requireNonNull (localAlternatingKeysAndValues [inI ++]);
630
+ newAlternatingKeysAndValues [outI ++] =
631
+ requireNonNull (localAlternatingKeysAndValues [inI ++]);
562
632
}
563
633
}
634
+ return newAlternatingKeysAndValues ;
635
+ }
636
+
637
+ static final class DuplicateKey {
638
+ private final Object key ;
639
+ private final Object value1 ;
640
+ private final Object value2 ;
641
+
642
+ DuplicateKey (Object key , Object value1 , Object value2 ) {
643
+ this .key = key ;
644
+ this .value1 = value1 ;
645
+ this .value2 = value2 ;
646
+ }
647
+
648
+ IllegalArgumentException exception () {
649
+ return new IllegalArgumentException (
650
+ "Multiple entries with same key: " + key + "=" + value1 + " and " + key + "=" + value2 );
651
+ }
564
652
}
565
653
}
566
654
0 commit comments