@@ -71,11 +71,20 @@ private static boolean isSortedSet(Tag[] tags, int length) {
71
71
if (length > tags .length ) {
72
72
return false ;
73
73
}
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
+
74
81
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 );
76
84
if (cmp >= 0 ) {
77
85
return false ;
78
86
}
87
+ current = next ;
79
88
}
80
89
return true ;
81
90
}
@@ -96,7 +105,8 @@ private static Tags toTags(Tag[] tags) {
96
105
}
97
106
98
107
/**
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.
100
110
* @param tags an ordered array of {@code Tag} objects.
101
111
* @return the number of unique tags in the {@code tags} array after removing
102
112
* duplicates.
@@ -111,9 +121,24 @@ private static int dedup(Tag[] tags) {
111
121
// index of next unique element
112
122
int j = 0 ;
113
123
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 ))
116
139
tags [j ++] = tags [i ];
140
+ current = next ;
141
+ }
117
142
118
143
tags [j ++] = tags [n - 1 ];
119
144
return j ;
@@ -126,12 +151,14 @@ private static int dedup(Tag[] tags) {
126
151
* @return a {@code Tags} instance with the merged sets of tags.
127
152
*/
128
153
private Tags merge (Tags other ) {
129
- if (other . length == 0 ) {
130
- return this ;
154
+ if (length == 0 ) {
155
+ return other ;
131
156
}
132
- if (Objects .equals (this , other )) {
157
+
158
+ if (other .length == 0 || tagsEqual (other )) {
133
159
return this ;
134
160
}
161
+
135
162
Tag [] sortedSet = new Tag [this .length + other .length ];
136
163
int sortedIndex = 0 ;
137
164
int thisIndex = 0 ;
@@ -215,13 +242,12 @@ public Tags and(@Nullable Tag... tags) {
215
242
* @return a new {@code Tags} instance
216
243
*/
217
244
public Tags and (@ Nullable Iterable <? extends Tag > tags ) {
218
- if (tags == null || tags == EMPTY || !tags .iterator ().hasNext ()) {
219
- return this ;
220
- }
221
-
222
245
if (this .length == 0 ) {
223
246
return Tags .of (tags );
224
247
}
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
225
251
return merge (Tags .of (tags ));
226
252
}
227
253
@@ -276,7 +302,7 @@ public int hashCode() {
276
302
277
303
@ Override
278
304
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 ) );
280
306
}
281
307
282
308
private boolean tagsEqual (Tags obj ) {
@@ -323,16 +349,19 @@ public static Tags concat(@Nullable Iterable<? extends Tag> tags, @Nullable Stri
323
349
* @return a new {@code Tags} instance
324
350
*/
325
351
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 ) {
330
353
return (Tags ) tags ;
331
354
}
332
355
else if (tags instanceof Collection ) {
333
356
Collection <? extends Tag > tagsCollection = (Collection <? extends Tag >) tags ;
357
+ if (tagsCollection .isEmpty ()) {
358
+ return Tags .empty ();
359
+ }
334
360
return toTags (tagsCollection .toArray (EMPTY_TAG_ARRAY ));
335
361
}
362
+ else if (emptyIterable (tags )) {
363
+ return Tags .empty ();
364
+ }
336
365
else {
337
366
return toTags (StreamSupport .stream (tags .spliterator (), false ).toArray (Tag []::new ));
338
367
}
@@ -346,7 +375,7 @@ else if (tags instanceof Collection) {
346
375
* @return a new {@code Tags} instance
347
376
*/
348
377
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 ));
350
379
}
351
380
352
381
/**
@@ -373,6 +402,26 @@ private static boolean blankVarargs(@Nullable Object[] args) {
373
402
return args == null || args .length == 0 || (args .length == 1 && args [0 ] == null );
374
403
}
375
404
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
+
376
425
/**
377
426
* Return a new {@code Tags} instance containing tags constructed from the specified
378
427
* tags.
0 commit comments