@@ -103,32 +103,29 @@ public static final TagMap fromMapImmutable(Map<String, ?> map) {
103103 }
104104
105105 private final Object [] buckets ;
106+ private int size ;
106107 private boolean frozen ;
107108
108109 public TagMap () {
109110 // needs to be a power of 2 for bucket masking calculation to work as intended
110- this .buckets = new Object [1 << 5 ];
111+ this .buckets = new Object [1 << 4 ];
112+ this .size = 0 ;
111113 this .frozen = false ;
112114 }
113115
114116 /** Used for inexpensive immutable */
115117 private TagMap (Object [] buckets ) {
116118 this .buckets = buckets ;
119+ this .size = 0 ;
117120 this .frozen = true ;
118121 }
119122
120- @ Deprecated
121123 @ Override
122124 public final int size () {
123- return this .computeSize () ;
125+ return this .size ;
124126 }
125127
126- /**
127- * Computes the size of the TagMap
128- *
129- * <p>computeSize is fast but is an O(n) operation.
130- */
131- public final int computeSize () {
128+ final int computeSize () {
132129 Object [] thisBuckets = this .buckets ;
133130
134131 int size = 0 ;
@@ -145,18 +142,12 @@ public final int computeSize() {
145142 return size ;
146143 }
147144
148- @ Deprecated
149145 @ Override
150- public boolean isEmpty () {
151- return this .checkIfEmpty ( );
146+ public final boolean isEmpty () {
147+ return ( this .size == 0 );
152148 }
153-
154- /**
155- * Checks if TagMap is empty
156- *
157- * <p>checkIfEmpty is fast but is an O(n) operation.
158- */
159- public final boolean checkIfEmpty () {
149+
150+ final boolean checkIfEmpty () {
160151 Object [] thisBuckets = this .buckets ;
161152
162153 for (int i = 0 ; i < thisBuckets .length ; ++i ) {
@@ -337,32 +328,39 @@ public final Entry putEntry(Entry newEntry) {
337328 if (bucket == null ) {
338329 thisBuckets [bucketIndex ] = newEntry ;
339330
331+ this .size += 1 ;
340332 return null ;
341333 } else if (bucket instanceof Entry ) {
342334 Entry existingEntry = (Entry ) bucket ;
343335 if (existingEntry .matches (newEntry .tag )) {
344336 thisBuckets [bucketIndex ] = newEntry ;
345337
338+ // replaced existing entry - no size change
346339 return existingEntry ;
347340 } else {
348341 thisBuckets [bucketIndex ] =
349342 new BucketGroup (existingEntry .hash (), existingEntry , newHash , newEntry );
350343
344+ this .size += 1 ;
351345 return null ;
352346 }
353347 } else if (bucket instanceof BucketGroup ) {
354348 BucketGroup lastGroup = (BucketGroup ) bucket ;
355349
356350 BucketGroup containingGroup = lastGroup .findContainingGroupInChain (newHash , newEntry .tag );
357351 if (containingGroup != null ) {
352+ // replaced existing entry - no size change
358353 return containingGroup ._replace (newHash , newEntry );
359354 }
360355
361356 if (!lastGroup .insertInChain (newHash , newEntry )) {
362357 thisBuckets [bucketIndex ] = new BucketGroup (newHash , newEntry , lastGroup );
363358 }
359+ this .size += 1 ;
364360 return null ;
365361 }
362+
363+ // unreachable
366364 return null ;
367365 }
368366
@@ -394,6 +392,18 @@ private final void putAll(Entry[] tagEntries, int size) {
394392 }
395393 }
396394
395+ public final void putAll (Map <? extends String , ? extends Object > map ) {
396+ this .checkWriteAccess ();
397+
398+ if (map instanceof TagMap ) {
399+ this .putAll ((TagMap ) map );
400+ } else {
401+ for (Map .Entry <? extends String , ?> entry : map .entrySet ()) {
402+ this .put (entry .getKey (), entry .getValue ());
403+ }
404+ }
405+ }
406+
397407 /**
398408 * Similar to {@link Map#putAll(Map)} but optimized to quickly copy from TagMap to another
399409 *
@@ -422,10 +432,13 @@ public final void putAll(TagMap that) {
422432
423433 if (thatBucket instanceof Entry ) {
424434 thisBuckets [i ] = thatBucket ;
435+ this .size += 1 ;
425436 } else if (thatBucket instanceof BucketGroup ) {
426437 BucketGroup thatGroup = (BucketGroup ) thatBucket ;
427438
428- thisBuckets [i ] = thatGroup .cloneChain ();
439+ BucketGroup thisNewGroup = thatGroup .cloneChain ();
440+ thisBuckets [i ] = thisNewGroup ;
441+ this .size += thisNewGroup .sizeInChain ();
429442 }
430443 } else if (thisBucket instanceof Entry ) {
431444 // This bucket is a single entry, medium complexity case
@@ -442,28 +455,39 @@ public final void putAll(TagMap that) {
442455
443456 if (thisHash == thatHash && thisEntry .matches (thatEntry .tag ())) {
444457 thisBuckets [i ] = thatEntry ;
458+ // replacing entry, no size change
445459 } else {
446460 thisBuckets [i ] =
447461 new BucketGroup (
448462 thisHash , thisEntry ,
449463 thatHash , thatEntry );
464+ this .size += 1 ;
450465 }
451466 } else if (thatBucket instanceof BucketGroup ) {
452467 BucketGroup thatGroup = (BucketGroup ) thatBucket ;
453468
454469 // Clone the other group, then place this entry into that group
455470 BucketGroup thisNewGroup = thatGroup .cloneChain ();
471+ int thisNewGroupSize = thisNewGroup .sizeInChain ();
456472
457473 Entry incomingEntry = thisNewGroup .findInChain (thisHash , thisEntry .tag ());
458474 if (incomingEntry != null ) {
459- // there's already an entry w/ the same tag from the incoming TagMap - done
475+ // there's already an entry w/ the same tag from the incoming TagMap
476+ // incoming entry clobbers the existing try, so we're done
460477 thisBuckets [i ] = thisNewGroup ;
478+ this .size +=
479+ thisNewGroupSize
480+ - 1 ; // overlapping group - subtract one for clobbered existing entry
461481 } else if (thisNewGroup .insertInChain (thisHash , thisEntry )) {
462482 // able to add thisEntry into the existing groups
463483 thisBuckets [i ] = thisNewGroup ;
484+ this .size +=
485+ thisNewGroupSize ; // non overlapping group - existing entry already accounted for
464486 } else {
465487 // unable to add into the existing groups
466488 thisBuckets [i ] = new BucketGroup (thisHash , thisEntry , thisNewGroup );
489+ this .size +=
490+ thisNewGroupSize ; // non overlapping group - existing entry already accounted for
467491 }
468492 }
469493 } else if (thisBucket instanceof BucketGroup ) {
@@ -475,27 +499,27 @@ public final void putAll(TagMap that) {
475499 Entry thatEntry = (Entry ) thatBucket ;
476500 int thatHash = thatEntry .hash ();
477501
478- if (!thisGroup .replaceOrInsertInChain (thatHash , thatEntry )) {
502+ if (thisGroup .replaceInChain (thatHash , thatEntry ) != null ) {
503+ // replaced existing entry no size change
504+ } else if (thisGroup .insertInChain (thatHash , thatEntry )) {
505+ this .size += 1 ;
506+ } else {
479507 thisBuckets [i ] = new BucketGroup (thatHash , thatEntry , thisGroup );
508+ this .size += 1 ;
480509 }
481510 } else if (thatBucket instanceof BucketGroup ) {
482511 // Most complicated case - need to walk that bucket group chain and update this chain
483512 BucketGroup thatGroup = (BucketGroup ) thatBucket ;
484513
485- thisBuckets [i ] = thisGroup .replaceOrInsertAllInChain (thatGroup );
486- }
487- }
488- }
489- }
514+ // Taking the easy / expensive way out for updating size
515+ int thisPrevGroupSize = thisGroup .sizeInChain ();
490516
491- public final void putAll ( Map <? extends String , ? extends Object > map ) {
492- this . checkWriteAccess ();
517+ BucketGroup thisNewGroup = thisGroup . replaceOrInsertAllInChain ( thatGroup );
518+ int thisNewGroupSize = thisNewGroup . sizeInChain ();
493519
494- if (map instanceof TagMap ) {
495- this .putAll ((TagMap ) map );
496- } else {
497- for (Map .Entry <? extends String , ?> entry : map .entrySet ()) {
498- this .put (entry .getKey (), entry .getValue ());
520+ thisBuckets [i ] = thisNewGroup ;
521+ this .size += (thisNewGroupSize - thisPrevGroupSize );
522+ }
499523 }
500524 }
501525 }
@@ -563,6 +587,8 @@ public final Entry removeEntry(String tag) {
563587 Entry existingEntry = (Entry ) bucket ;
564588 if (existingEntry .matches (tag )) {
565589 thisBuckets [bucketIndex ] = null ;
590+
591+ this .size -= 1 ;
566592 return existingEntry ;
567593 } else {
568594 return null ;
@@ -580,6 +606,7 @@ public final Entry removeEntry(String tag) {
580606 this .buckets [bucketIndex ] = lastGroup .removeGroupInChain (containingGroup );
581607 }
582608
609+ this .size -= 1 ;
583610 return existingEntry ;
584611 }
585612 return null ;
@@ -697,6 +724,7 @@ public final void clear() {
697724 this .checkWriteAccess ();
698725
699726 Arrays .fill (this .buckets , null );
727+ this .size = 0 ;
700728 }
701729
702730 /** Freeze the TagMap preventing further modification - returns <code>this</code> TagMap */
@@ -716,39 +744,50 @@ public final void checkWriteAccess() {
716744 if (this .frozen ) throw new IllegalStateException ("TagMap frozen" );
717745 }
718746
719- // final void check() {
720- // Object[] thisBuckets = this.buckets;
721- //
722- // for ( int i = 0; i < thisBuckets.length; ++i ) {
723- // Object thisBucket = thisBuckets[i];
724- //
725- // if ( thisBucket instanceof Entry ) {
726- // Entry thisEntry = (Entry)thisBucket;
727- // int thisHash = thisEntry.hash();
728- //
729- // int expectedBucket = thisHash & (thisBuckets.length - 1);
730- // assert expectedBucket == i;
731- // } else if ( thisBucket instanceof BucketGroup ) {
732- // BucketGroup thisGroup = (BucketGroup)thisBucket;
733- //
734- // for ( BucketGroup curGroup = thisGroup;
735- // curGroup != null;
736- // curGroup = curGroup.prev )
737- // {
738- // for ( int j = 0; j < BucketGroup.LEN; ++j ) {
739- // Entry thisEntry = curGroup._entryAt(i);
740- // if ( thisEntry == null ) continue;
741- //
742- // int thisHash = thisEntry.hash();
743- // assert curGroup._hashAt(i) == thisHash;
744- //
745- // int expectedBucket = thisHash & (thisBuckets.length - 1);
746- // assert expectedBucket == i;
747- // }
748- // }
749- // }
750- // }
751- // }
747+ final void checkIntegrity () {
748+ Object [] thisBuckets = this .buckets ;
749+
750+ for ( int i = 0 ; i < thisBuckets .length ; ++i ) {
751+ Object thisBucket = thisBuckets [i ];
752+
753+ if ( thisBucket instanceof Entry ) {
754+ Entry thisEntry = (Entry )thisBucket ;
755+ int thisHash = thisEntry .hash ();
756+
757+ int expectedBucket = thisHash & (thisBuckets .length - 1 );
758+ if ( expectedBucket != i ) {
759+ throw new IllegalStateException ("incorrect bucket" );
760+ }
761+ } else if ( thisBucket instanceof BucketGroup ) {
762+ BucketGroup thisGroup = (BucketGroup )thisBucket ;
763+
764+ for ( BucketGroup curGroup = thisGroup ;
765+ curGroup != null ;
766+ curGroup = curGroup .prev )
767+ {
768+ for ( int j = 0 ; j < BucketGroup .LEN ; ++j ) {
769+ Entry thisEntry = curGroup ._entryAt (i );
770+ if ( thisEntry == null ) continue ;
771+
772+ int thisHash = thisEntry .hash ();
773+ assert curGroup ._hashAt (i ) == thisHash ;
774+
775+ int expectedBucket = thisHash & (thisBuckets .length - 1 );
776+ if ( expectedBucket != i ) {
777+ throw new IllegalStateException ("incorrect bucket" );
778+ }
779+ }
780+ }
781+ }
782+ }
783+
784+ if ( this .size != this .computeSize () ) {
785+ throw new IllegalStateException ("incorrect size" );
786+ }
787+ if ( this .isEmpty () != this .checkIfEmpty () ) {
788+ throw new IllegalStateException ("incorrect empty status" );
789+ }
790+ }
752791
753792 @ Override
754793 public final String toString () {
@@ -1882,10 +1921,6 @@ BucketGroup replaceOrInsertAllInChain(BucketGroup thatHeadGroup) {
18821921 return thisNewestHeadGroup ;
18831922 }
18841923
1885- boolean replaceOrInsertInChain (int hash , Entry entry ) {
1886- return (this .replaceInChain (hash , entry ) != null ) || this .insertInChain (hash , entry );
1887- }
1888-
18891924 Entry replaceInChain (int hash , Entry entry ) {
18901925 for (BucketGroup curGroup = this ; curGroup != null ; curGroup = curGroup .prev ) {
18911926 Entry prevEntry = curGroup ._replace (hash , entry );
@@ -2097,7 +2132,7 @@ public String toString() {
20972132 StringBuilder builder = new StringBuilder (32 );
20982133 builder .append ('[' );
20992134 for (int i = 0 ; i < BucketGroup .LEN ; ++i ) {
2100- if (builder . length () != 0 ) builder .append (", " );
2135+ if (i != 0 ) builder .append (", " );
21012136
21022137 builder .append (this ._entryAt (i ));
21032138 }
0 commit comments