View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau.commons.collections;
18  
19  import static org.apache.juneau.commons.utils.CollectionUtils.*;
20  import static org.apache.juneau.junit.bct.BctAssertions.*;
21  import static org.junit.jupiter.api.Assertions.*;
22  
23  import java.util.*;
24  
25  import org.apache.juneau.*;
26  import org.junit.jupiter.api.*;
27  
28  class MultiSet_Test extends TestBase {
29  
30  	@Test void doTest() {
31  		List<String> l1, l2;
32  		MultiSet<String> ms;
33  
34  		l1 = l(a("1","2"));
35  		l2 = l(a("3","4"));
36  		ms = new MultiSet<>(l1, l2);
37  		var i1 = ms.iterator();
38  		assertTrue(i1.hasNext());
39  		assertEquals("1", i1.next());
40  		assertTrue(i1.hasNext());
41  		assertEquals("2", i1.next());
42  		assertTrue(i1.hasNext());
43  		assertEquals("3", i1.next());
44  		assertTrue(i1.hasNext());
45  		assertEquals("4", i1.next());
46  		assertFalse(i1.hasNext());
47  		assertThrows(NoSuchElementException.class, i1::next);
48  
49  		l1 = l(a("1","2"));
50  		l2 = l(a());
51  		ms = new MultiSet<>(l1, l2);
52  		var i2 = ms.iterator();
53  		assertTrue(i2.hasNext());
54  		assertEquals("1", i2.next());
55  		assertTrue(i2.hasNext());
56  		assertEquals("2", i2.next());
57  		assertFalse(i2.hasNext());
58  		assertThrows(NoSuchElementException.class, i2::next);
59  
60  		l1 = l(a());
61  		l2 = l(a("3","4"));
62  		ms = new MultiSet<>(l1, l2);
63  		var i3 = ms.iterator();
64  		assertTrue(i3.hasNext());
65  		assertEquals("3", i3.next());
66  		assertTrue(i3.hasNext());
67  		assertEquals("4", i3.next());
68  		assertFalse(i3.hasNext());
69  		assertThrows(NoSuchElementException.class, i3::next);
70  
71  		l1 = l(a());
72  		l2 = l(a());
73  		ms = new MultiSet<>(l1, l2);
74  		var i4 = ms.iterator();
75  		assertFalse(i4.hasNext());
76  		assertThrows(NoSuchElementException.class, i4::next);
77  
78  		l1 = l(a("1","2"));
79  		ms = new MultiSet<>(l1);
80  		var i5 = ms.iterator();
81  		assertTrue(i5.hasNext());
82  		assertEquals("1", i5.next());
83  		assertTrue(i5.hasNext());
84  		assertEquals("2", i5.next());
85  		assertFalse(i5.hasNext());
86  		assertThrows(NoSuchElementException.class, i5::next);
87  
88  		l1 = new LinkedList<>(l(a("1","2")));
89  		l2 = new LinkedList<>(l(a("3","4")));
90  		ms = new MultiSet<>(l1, l2);
91  		assertList(ms, "1", "2", "3", "4");
92  		assertList(ms.enumerator(), "1", "2", "3", "4");
93  		assertSize(4, ms);
94  
95  		var t = ms.iterator();
96  		t.next();
97  		t.remove();
98  		assertList(ms.enumerator(), "2", "3", "4");
99  
100 		t = ms.iterator();
101 		t.next();
102 		t.remove();
103 		assertList(ms.enumerator(), "3", "4");
104 
105 		t = ms.iterator();
106 		t.next();
107 		t.remove();
108 		assertList(ms.enumerator(), "4");
109 
110 		t = ms.iterator();
111 		t.next();
112 		t.remove();
113 		assertEmpty(ms.enumerator());
114 		assertEmpty(ms);
115 
116 		ms = new MultiSet<>();
117 		assertEmpty(ms);
118 		assertEmpty(ms);
119 
120 		assertThrows(IllegalArgumentException.class, ()->new MultiSet<>((Collection<String>)null));
121 		assertThrows(NoSuchElementException.class, ()->new MultiSet<String>().iterator().next());
122 		assertThrows(NoSuchElementException.class, ()->new MultiSet<String>().iterator().remove());
123 	}
124 
125 	@Test
126 	void hasNext_whenCurrentIteratorExhausted_butMoreCollectionsHaveElements() {
127 		// Test the hasNext() logic when current iterator is exhausted but remaining collections have elements
128 		var l1 = l(a("1", "2"));
129 		var l2 = l(a("3", "4"));
130 		var l3 = l(a("5", "6"));
131 		var ms = new MultiSet<>(l1, l2, l3);
132 		var it = ms.iterator();
133 
134 		// Exhaust the first collection's iterator
135 		assertTrue(it.hasNext());
136 		assertEquals("1", it.next());
137 		assertTrue(it.hasNext());
138 		assertEquals("2", it.next());
139 
140 		// Now i2.hasNext() should be false, but hasNext() should return true
141 		// because there are more collections with elements (testing lines 214-216)
142 		assertTrue(it.hasNext()); // Should check remaining collections
143 		assertEquals("3", it.next());
144 
145 		// Continue to exhaust second collection
146 		assertTrue(it.hasNext());
147 		assertEquals("4", it.next());
148 
149 		// Now should check third collection
150 		assertTrue(it.hasNext());
151 		assertEquals("5", it.next());
152 		assertTrue(it.hasNext());
153 		assertEquals("6", it.next());
154 		assertFalse(it.hasNext());
155 	}
156 
157 	@Test
158 	void hasNext_withEmptyCollectionsInBetween() {
159 		// Test hasNext() when there are empty collections between non-empty ones
160 		var l1 = l(a("1"));
161 		var l2 = l(new String[0]);
162 		var l3 = l(a("2"));
163 		var l4 = l(new String[0]);
164 		var l5 = l(a("3"));
165 		var ms = new MultiSet<>(l1, l2, l3, l4, l5);
166 		var it = ms.iterator();
167 
168 		// Exhaust first collection
169 		assertTrue(it.hasNext());
170 		assertEquals("1", it.next());
171 
172 		// Now hasNext() should skip empty collections and find l3
173 		assertTrue(it.hasNext()); // Should skip l2 (empty) and find l3
174 		assertEquals("2", it.next());
175 
176 		// Should skip l4 (empty) and find l5
177 		assertTrue(it.hasNext());
178 		assertEquals("3", it.next());
179 		assertFalse(it.hasNext());
180 	}
181 
182 	//====================================================================================================
183 	// toString()
184 	//====================================================================================================
185 
186 	@Test
187 	void toString_singleCollection() {
188 		var l1 = l(a("1", "2"));
189 		var ms = new MultiSet<>(l1);
190 
191 		var expected = "[" + l1.toString() + "]";
192 		assertEquals(expected, ms.toString());
193 	}
194 
195 	@Test
196 	void toString_multipleCollections() {
197 		var l1 = l(a("1", "2"));
198 		var l2 = l(a("3", "4"));
199 		var l3 = l(a("5", "6"));
200 		var ms = new MultiSet<>(l1, l2, l3);
201 
202 		var expected = "[" + l1.toString() + ", " + l2.toString() + ", " + l3.toString() + "]";
203 		assertEquals(expected, ms.toString());
204 	}
205 
206 	@Test
207 	void toString_emptyCollections() {
208 		var l1 = l(a());
209 		var l2 = l(a());
210 		var ms = new MultiSet<>(l1, l2);
211 
212 		var expected = "[" + l1.toString() + ", " + l2.toString() + "]";
213 		assertEquals(expected, ms.toString());
214 	}
215 
216 	@Test
217 	void toString_mixedEmptyAndNonEmpty() {
218 		List<String> l1 = l(a());
219 		var l2 = l(a("1", "2"));
220 		List<String> l3 = l(a());
221 		var ms = new MultiSet<>(l1, l2, l3);
222 
223 		var expected = "[" + l1.toString() + ", " + l2.toString() + ", " + l3.toString() + "]";
224 		assertEquals(expected, ms.toString());
225 	}
226 
227 	//====================================================================================================
228 	// equals() and hashCode()
229 	//====================================================================================================
230 
231 	@Test
232 	void equals_sameContents() {
233 		var l1 = l(a("1", "2"));
234 		var l2 = l(a("3", "4"));
235 		var multiSet1 = new MultiSet<>(l1, l2);
236 
237 		var l3 = l(a("1", "2"));
238 		var l4 = l(a("3", "4"));
239 		var multiSet2 = new MultiSet<>(l3, l4);
240 
241 		assertTrue(multiSet1.equals(multiSet2));
242 		assertTrue(multiSet2.equals(multiSet1));
243 	}
244 
245 	@Test
246 	void equals_differentContents() {
247 		var l1 = l(a("1", "2"));
248 		var multiSet1 = new MultiSet<>(l1);
249 
250 		var l2 = l(a("1", "3"));
251 		var multiSet2 = new MultiSet<>(l2);
252 
253 		assertFalse(multiSet1.equals(multiSet2));
254 		assertFalse(multiSet2.equals(multiSet1));
255 	}
256 
257 	@Test
258 	void equals_differentOrder() {
259 		var l1 = l(a("1", "2"));
260 		var l2 = l(a("3", "4"));
261 		var multiSet1 = new MultiSet<>(l1, l2);
262 
263 		var l3 = l(a("3", "4"));
264 		var l4 = l(a("1", "2"));
265 		var multiSet2 = new MultiSet<>(l3, l4);
266 
267 		assertTrue(multiSet1.equals(multiSet2)); // Order doesn't matter for sets
268 	}
269 
270 	@Test
271 	void equals_regularSet() {
272 		var l1 = l(a("1", "2", "3"));
273 		var multiSet = new MultiSet<>(l1);
274 
275 		var regularSet = new LinkedHashSet<>(l(a("1", "2", "3")));
276 
277 		assertTrue(multiSet.equals(regularSet));
278 		assertTrue(regularSet.equals(multiSet));
279 	}
280 
281 	@Test
282 	void equals_notASet() {
283 		var l1 = l(a("1", "2"));
284 		var multiSet = new MultiSet<>(l1);
285 
286 		assertFalse(multiSet.equals(null));
287 	}
288 
289 	@Test
290 	void hashCode_sameContents() {
291 		var l1 = l(a("1", "2", "3"));
292 		var multiSet1 = new MultiSet<>(l1);
293 
294 		var l2 = l(a("1", "2", "3"));
295 		var multiSet2 = new MultiSet<>(l2);
296 
297 		assertEquals(multiSet1.hashCode(), multiSet2.hashCode());
298 	}
299 
300 	@Test
301 	void hashCode_regularSet() {
302 		var l1 = l(a("1", "2", "3"));
303 		var multiSet = new MultiSet<>(l1);
304 
305 		var regularSet = new LinkedHashSet<>(l(a("1", "2", "3")));
306 
307 		assertEquals(multiSet.hashCode(), regularSet.hashCode());
308 	}
309 
310 	//====================================================================================================
311 	// Additional coverage for specific lines
312 	//====================================================================================================
313 
314 	@Test
315 	void iterator_hasNext_whenI2IsNull() {
316 		// Line 213: return false when i2 == null
317 		// This happens when MultiSet is created with no collections
318 		var ms = new MultiSet<String>();
319 		var it = ms.iterator();
320 		assertFalse(it.hasNext()); // i2 is null, should return false
321 	}
322 
323 	@SuppressWarnings("unlikely-arg-type")
324 	@Test
325 	void equals_notASet_otherTypes() {
326 		// Line 308: return (o instanceof Set o2) && ...
327 		// Test when object is not a Set (testing the instanceof check)
328 		var l1 = l(a("1", "2"));
329 		var multiSet = new MultiSet<>(l1);
330 
331 		// Not a Set - should return false immediately due to instanceof check
332 		assertFalse(multiSet.equals("not a set"));
333 		assertFalse(multiSet.equals(123));
334 		assertFalse(multiSet.equals(List.of("1", "2"))); // List is not a Set
335 	}
336 
337 	@Test
338 	void hashCode_withNullElements() {
339 		// Line 330: h += e == null ? 0 : e.hashCode()
340 		// Test hashCode with null elements
341 		var l1 = l(a("1", null));
342 		var l2 = l(a("2"));
343 		var multiSet = new MultiSet<>(l1, l2);
344 
345 		// Calculate expected hashCode manually (null contributes 0)
346 		int expectedHashCode = 0;
347 		expectedHashCode += "1".hashCode();
348 		expectedHashCode += 0; // null
349 		expectedHashCode += "2".hashCode();
350 
351 		assertEquals(expectedHashCode, multiSet.hashCode());
352 	}
353 }
354