@@ -71,11 +71,20 @@ private static boolean isSortedSet(Tag[] tags, int length) {
7171 if (length > tags .length ) {
7272 return false ;
7373 }
74+
75+ // This style is intentionally chosen to have only one array
76+ // access per iteration (tags[i].compareTo(tags[i + 1]) would
77+ // have two).
78+ Tag current = tags [0 ];
79+ Tag next ;
80+
7481 for (int i = 0 ; i < length - 1 ; i ++) {
75- int cmp = tags [i ].compareTo (tags [i + 1 ]);
82+ next = tags [i + 1 ];
83+ int cmp = current .compareTo (next );
7684 if (cmp >= 0 ) {
7785 return false ;
7886 }
87+ current = next ;
7988 }
8089 return true ;
8190 }
@@ -96,7 +105,8 @@ private static Tags toTags(Tag[] tags) {
96105 }
97106
98107 /**
99- * Removes duplicate tags from an ordered array of tags.
108+ * Removes duplicate tags from an ordered array of tags. In the case of several
109+ * consecutive tags with the same key, only the last one is preserved.
100110 * @param tags an ordered array of {@code Tag} objects.
101111 * @return the number of unique tags in the {@code tags} array after removing
102112 * duplicates.
@@ -111,9 +121,24 @@ private static int dedup(Tag[] tags) {
111121 // index of next unique element
112122 int j = 0 ;
113123
114- for (int i = 0 ; i < n - 1 ; i ++)
115- if (!tags [i ].getKey ().equals (tags [i + 1 ].getKey ()))
124+ // The following is intentionally written in this style to facilitate performance.
125+ // Normally one would just do tags[i].getKey().equals(tags[i + 1].getKey()),
126+ // but the tags[i + 1].getKey() value is exactly the same as tags[i].getKey()
127+ // for the next iteration, thus caching it in a variable halves the number
128+ // of lookups.
129+ // The compiler is very unlikely to do this for us because of the absence of
130+ // "this data is immutable" signs, even if MM doesn't enforce any HB
131+ // relations with external modifications in this case. You can run the
132+ // associated benchmarks to check the behavior for your specific JVM.
133+ String current = tags [0 ].getKey ();
134+ String next ;
135+
136+ for (int i = 0 ; i < n - 1 ; i ++) {
137+ next = tags [i + 1 ].getKey ();
138+ if (!current .equals (next ))
116139 tags [j ++] = tags [i ];
140+ current = next ;
141+ }
117142
118143 tags [j ++] = tags [n - 1 ];
119144 return j ;
@@ -126,12 +151,14 @@ private static int dedup(Tag[] tags) {
126151 * @return a {@code Tags} instance with the merged sets of tags.
127152 */
128153 private Tags merge (Tags other ) {
129- if (other . length == 0 ) {
130- return this ;
154+ if (length == 0 ) {
155+ return other ;
131156 }
132- if (Objects .equals (this , other )) {
157+
158+ if (other .length == 0 || tagsEqual (other )) {
133159 return this ;
134160 }
161+
135162 Tag [] sortedSet = new Tag [this .length + other .length ];
136163 int sortedIndex = 0 ;
137164 int thisIndex = 0 ;
@@ -215,13 +242,12 @@ public Tags and(@Nullable Tag... tags) {
215242 * @return a new {@code Tags} instance
216243 */
217244 public Tags and (@ Nullable Iterable <? extends Tag > tags ) {
218- if (tags == null || tags == EMPTY || !tags .iterator ().hasNext ()) {
219- return this ;
220- }
221-
222245 if (this .length == 0 ) {
223246 return Tags .of (tags );
224247 }
248+
249+ // Tags.of() will take care of nulls, empty iterables and so on
250+ // merge() then will check if the argument is empty and reduce to no-op
225251 return merge (Tags .of (tags ));
226252 }
227253
@@ -276,7 +302,7 @@ public int hashCode() {
276302
277303 @ Override
278304 public boolean equals (@ Nullable Object obj ) {
279- return this == obj || obj != null && getClass () == obj .getClass () && tagsEqual ((Tags ) obj );
305+ return this == obj || ( obj != null && getClass () == obj .getClass () && tagsEqual ((Tags ) obj ) );
280306 }
281307
282308 private boolean tagsEqual (Tags obj ) {
@@ -323,16 +349,19 @@ public static Tags concat(@Nullable Iterable<? extends Tag> tags, @Nullable Stri
323349 * @return a new {@code Tags} instance
324350 */
325351 public static Tags of (@ Nullable Iterable <? extends Tag > tags ) {
326- if (tags == null || tags == EMPTY || !tags .iterator ().hasNext ()) {
327- return Tags .empty ();
328- }
329- else if (tags instanceof Tags ) {
352+ if (tags instanceof Tags ) {
330353 return (Tags ) tags ;
331354 }
332355 else if (tags instanceof Collection ) {
333356 Collection <? extends Tag > tagsCollection = (Collection <? extends Tag >) tags ;
357+ if (tagsCollection .isEmpty ()) {
358+ return Tags .empty ();
359+ }
334360 return toTags (tagsCollection .toArray (EMPTY_TAG_ARRAY ));
335361 }
362+ else if (emptyIterable (tags )) {
363+ return Tags .empty ();
364+ }
336365 else {
337366 return toTags (StreamSupport .stream (tags .spliterator (), false ).toArray (Tag []::new ));
338367 }
@@ -346,7 +375,7 @@ else if (tags instanceof Collection) {
346375 * @return a new {@code Tags} instance
347376 */
348377 public static Tags of (String key , String value ) {
349- return new Tags ( new Tag [] { Tag .of (key , value ) }, 1 );
378+ return of ( Tag .of (key , value ));
350379 }
351380
352381 /**
@@ -373,13 +402,43 @@ private static boolean blankVarargs(@Nullable Object[] args) {
373402 return args == null || args .length == 0 || (args .length == 1 && args [0 ] == null );
374403 }
375404
405+ private static boolean emptyIterable (@ Nullable Iterable <? extends Tag > iterable ) {
406+ // Doing the checks in the ascending cost order
407+ if (iterable == null || iterable == EMPTY ) {
408+ return true ;
409+ }
410+
411+ if (iterable instanceof Tags ) {
412+ return ((Tags ) iterable ).length == 0 ;
413+ }
414+
415+ if (iterable instanceof Collection ) {
416+ return ((Collection <?>) iterable ).isEmpty ();
417+ }
418+
419+ // While the compiler can theoretically avoid Iterator allocation here
420+ // (via scalarization), it is not guaranteed, thus leaving this check as the last
421+ // one
422+ return !iterable .iterator ().hasNext ();
423+ }
424+
376425 /**
377426 * Return a new {@code Tags} instance containing tags constructed from the specified
378427 * tags.
379428 * @param tags the tags to add, elements mustn't be null
380429 * @return a new {@code Tags} instance
381430 */
382431 public static Tags of (@ Nullable Tag ... tags ) {
432+ if (tags == null || tags .length == 0 ) {
433+ return empty ();
434+ }
435+
436+ if (tags .length == 1 ) {
437+ // Avoiding all the sorting check machinery when we know it
438+ // is sorted by design.
439+ return new Tags (tags , 1 );
440+ }
441+
383442 return empty ().and (tags );
384443 }
385444
0 commit comments