3434 */
3535public final class Tags implements Iterable <Tag > {
3636
37- private static final Tags EMPTY = new Tags (new Tag [] {});
37+ private static final Tags EMPTY = new Tags (new Tag [] {}, 0 );
3838
39- private final Tag [] tags ;
39+ /**
40+ * A private array of {@code Tag} objects containing the sorted and deduplicated tags.
41+ */
42+ private final Tag [] sortedSet ;
43+
44+ /**
45+ * The number of valid tags present in the {@link #sortedSet} array.
46+ */
47+ private final int length ;
48+
49+ /**
50+ * A private constructor that initializes a {@code Tags} object with a sorted set of
51+ * tags and its length.
52+ * @param sortedSet an ordered set of unique tags by key
53+ * @param length the number of valid tags in the {@code sortedSet}
54+ */
55+ private Tags (Tag [] sortedSet , int length ) {
56+ this .sortedSet = sortedSet ;
57+ this .length = length ;
58+ }
4059
41- private int last ;
60+ /**
61+ * Checks if the first {@code length} elements of the {@code tags} array form an
62+ * ordered set of tags.
63+ * @param tags an array of tags.
64+ * @param length the number of items to check.
65+ * @return {@code true} if the first {@code length} items of {@code tags} form an
66+ * ordered set; otherwise {@code false}.
67+ */
68+ private static boolean isSortedSet (Tag [] tags , int length ) {
69+ if (length > tags .length ) {
70+ return false ;
71+ }
72+ for (int i = 0 ; i < length - 1 ; i ++) {
73+ int cmp = tags [i ].compareTo (tags [i + 1 ]);
74+ if (cmp >= 0 ) {
75+ return false ;
76+ }
77+ }
78+ return true ;
79+ }
4280
43- private Tags (Tag [] tags ) {
44- this .tags = tags ;
45- Arrays .sort (this .tags );
46- dedup ();
81+ /**
82+ * Constructs a {@code Tags} collection from the provided array of tags.
83+ * @param tags an array of {@code Tag} objects, possibly unordered and/or containing
84+ * duplicates.
85+ * @return a {@code Tags} instance with a deduplicated and ordered set of tags.
86+ */
87+ private static Tags make (Tag [] tags ) {
88+ int len = tags .length ;
89+ if (!isSortedSet (tags , len )) {
90+ Arrays .sort (tags );
91+ len = dedup (tags );
92+ }
93+ return new Tags (tags , len );
4794 }
4895
49- private void dedup () {
96+ /**
97+ * Removes duplicate tags from an ordered array of tags.
98+ * @param tags an ordered array of {@code Tag} objects.
99+ * @return the number of unique tags in the {@code tags} array after removing
100+ * duplicates.
101+ */
102+ private static int dedup (Tag [] tags ) {
50103 int n = tags .length ;
51104
52105 if (n == 0 || n == 1 ) {
53- last = n ;
54- return ;
106+ return n ;
55107 }
56108
57109 // index of next unique element
@@ -62,7 +114,53 @@ private void dedup() {
62114 tags [j ++] = tags [i ];
63115
64116 tags [j ++] = tags [n - 1 ];
65- last = j ;
117+ return j ;
118+ }
119+
120+ /**
121+ * Constructs a {@code Tags} instance by merging two sets of tags in time proportional
122+ * to the sum of their sizes.
123+ * @param other the set of tags to merge with this one.
124+ * @return a {@code Tags} instance with the merged sets of tags.
125+ */
126+ private Tags merged (Tags other ) {
127+ if (other .length == 0 ) {
128+ return this ;
129+ }
130+ if (Objects .equals (this , other )) {
131+ return this ;
132+ }
133+ Tag [] sortedSet = new Tag [this .length + other .length ];
134+ int sortedIdx = 0 , thisIdx = 0 , otherIdx = 0 ;
135+ while (thisIdx < this .length && otherIdx < other .length ) {
136+ int cmp = this .sortedSet [thisIdx ].compareTo (other .sortedSet [otherIdx ]);
137+ if (cmp > 0 ) {
138+ sortedSet [sortedIdx ] = other .sortedSet [otherIdx ];
139+ otherIdx ++;
140+ }
141+ else if (cmp < 0 ) {
142+ sortedSet [sortedIdx ] = this .sortedSet [thisIdx ];
143+ thisIdx ++;
144+ }
145+ else {
146+ // In case of key conflict prefer tag from other set
147+ sortedSet [sortedIdx ] = other .sortedSet [otherIdx ];
148+ thisIdx ++;
149+ otherIdx ++;
150+ }
151+ sortedIdx ++;
152+ }
153+ int thisRemaining = this .length - thisIdx ;
154+ if (thisRemaining > 0 ) {
155+ System .arraycopy (this .sortedSet , thisIdx , sortedSet , sortedIdx , thisRemaining );
156+ sortedIdx += thisRemaining ;
157+ }
158+ int otherRemaining = other .length - otherIdx ;
159+ if (otherIdx < other .sortedSet .length ) {
160+ System .arraycopy (other .sortedSet , otherIdx , sortedSet , sortedIdx , otherRemaining );
161+ sortedIdx += otherRemaining ;
162+ }
163+ return new Tags (sortedSet , sortedIdx );
66164 }
67165
68166 /**
@@ -99,10 +197,7 @@ public Tags and(@Nullable Tag... tags) {
99197 if (blankVarargs (tags )) {
100198 return this ;
101199 }
102- Tag [] newTags = new Tag [last + tags .length ];
103- System .arraycopy (this .tags , 0 , newTags , 0 , last );
104- System .arraycopy (tags , 0 , newTags , last , tags .length );
105- return new Tags (newTags );
200+ return and (make (tags ));
106201 }
107202
108203 /**
@@ -116,11 +211,10 @@ public Tags and(@Nullable Iterable<? extends Tag> tags) {
116211 return this ;
117212 }
118213
119- if (this .tags . length == 0 ) {
214+ if (this .length == 0 ) {
120215 return Tags .of (tags );
121216 }
122-
123- return and (Tags .of (tags ).tags );
217+ return merged (Tags .of (tags ));
124218 }
125219
126220 @ Override
@@ -134,12 +228,12 @@ private class ArrayIterator implements Iterator<Tag> {
134228
135229 @ Override
136230 public boolean hasNext () {
137- return currentIndex < last ;
231+ return currentIndex < length ;
138232 }
139233
140234 @ Override
141235 public Tag next () {
142- return tags [currentIndex ++];
236+ return sortedSet [currentIndex ++];
143237 }
144238
145239 @ Override
@@ -151,7 +245,7 @@ public void remove() {
151245
152246 @ Override
153247 public Spliterator <Tag > spliterator () {
154- return Spliterators .spliterator (tags , 0 , last , Spliterator .IMMUTABLE | Spliterator .ORDERED
248+ return Spliterators .spliterator (sortedSet , 0 , length , Spliterator .IMMUTABLE | Spliterator .ORDERED
155249 | Spliterator .DISTINCT | Spliterator .NONNULL | Spliterator .SORTED );
156250 }
157251
@@ -166,8 +260,8 @@ public Stream<Tag> stream() {
166260 @ Override
167261 public int hashCode () {
168262 int result = 1 ;
169- for (int i = 0 ; i < last ; i ++) {
170- result = 31 * result + tags [i ].hashCode ();
263+ for (int i = 0 ; i < length ; i ++) {
264+ result = 31 * result + sortedSet [i ].hashCode ();
171265 }
172266 return result ;
173267 }
@@ -178,14 +272,14 @@ public boolean equals(@Nullable Object obj) {
178272 }
179273
180274 private boolean tagsEqual (Tags obj ) {
181- if (tags == obj .tags )
275+ if (sortedSet == obj .sortedSet )
182276 return true ;
183277
184- if (last != obj .last )
278+ if (length != obj .length )
185279 return false ;
186280
187- for (int i = 0 ; i < last ; i ++) {
188- if (!tags [i ].equals (obj .tags [i ]))
281+ for (int i = 0 ; i < length ; i ++) {
282+ if (!sortedSet [i ].equals (obj .sortedSet [i ]))
189283 return false ;
190284 }
191285
@@ -229,10 +323,10 @@ else if (tags instanceof Tags) {
229323 }
230324 else if (tags instanceof Collection ) {
231325 Collection <? extends Tag > tagsCollection = (Collection <? extends Tag >) tags ;
232- return new Tags (tagsCollection .toArray (new Tag [0 ]));
326+ return make (tagsCollection .toArray (new Tag [0 ]));
233327 }
234328 else {
235- return new Tags (StreamSupport .stream (tags .spliterator (), false ).toArray (Tag []::new ));
329+ return make (StreamSupport .stream (tags .spliterator (), false ).toArray (Tag []::new ));
236330 }
237331 }
238332
@@ -244,7 +338,7 @@ else if (tags instanceof Collection) {
244338 * @return a new {@code Tags} instance
245339 */
246340 public static Tags of (String key , String value ) {
247- return new Tags (new Tag [] { Tag .of (key , value ) });
341+ return new Tags (new Tag [] { Tag .of (key , value ) }, 1 );
248342 }
249343
250344 /**
@@ -264,7 +358,7 @@ public static Tags of(@Nullable String... keyValues) {
264358 for (int i = 0 ; i < keyValues .length ; i += 2 ) {
265359 tags [i / 2 ] = Tag .of (keyValues [i ], keyValues [i + 1 ]);
266360 }
267- return new Tags (tags );
361+ return make (tags );
268362 }
269363
270364 private static boolean blankVarargs (@ Nullable Object [] args ) {
0 commit comments