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.TestUtils.assertThrowsWithMessage;
20  import static org.apache.juneau.commons.utils.CollectionUtils.*;
21  import static org.apache.juneau.junit.bct.BctAssertions.*;
22  import static org.junit.jupiter.api.Assertions.*;
23  
24  import java.util.*;
25  
26  import org.apache.juneau.*;
27  import org.junit.jupiter.api.*;
28  
29  class SimpleMap_Test extends TestBase {
30  
31  	//====================================================================================================
32  	// Null key support
33  	//====================================================================================================
34  	@Test
35  	void a01_nullKey_singleEntry() {
36  		var keys = a((String)null);
37  		var values = a("value1");
38  
39  		var map = new SimpleMap<>(keys, values);
40  
41  		assertSize(1, map);
42  		assertEquals("value1", map.get(null));
43  		assertTrue(map.containsKey(null));
44  	}
45  
46  	@Test
47  	void a02_nullKey_withOtherKeys() {
48  		var keys = a("key1", null, "key3");
49  		var values = a("value1", "value2", "value3");
50  
51  		var map = new SimpleMap<>(keys, values);
52  
53  		assertSize(3, map);
54  		assertEquals("value1", map.get("key1"));
55  		assertEquals("value2", map.get(null));
56  		assertEquals("value3", map.get("key3"));
57  		assertTrue(map.containsKey(null));
58  	}
59  
60  	@Test
61  	void a03_nullKey_cannotModify() {
62  		var keys = a("key1", null);
63  		var values = a("value1", "value2");
64  
65  		var map = new SimpleMap<>(keys, values);
66  
67  		assertThrows(UnsupportedOperationException.class, () -> {
68  			map.put(null, "newValue");
69  		});
70  
71  		// Verify original value unchanged
72  		assertEquals("value2", map.get(null));
73  	}
74  
75  	@Test
76  	void a04_nullKey_withNullValue() {
77  		var keys = a((String)null);
78  		var values = a((String)null);
79  
80  		SimpleMap<String,String> map = new SimpleMap<>(keys, values);
81  
82  		assertSize(1, map);
83  		assertNull(map.get(null));
84  		assertTrue(map.containsKey(null));
85  	}
86  
87  	@Test
88  	void a05_nullKey_entrySet() {
89  		var keys = a("key1", null, "key3");
90  		var values = a("value1", "value2", "value3");
91  
92  		var map = new SimpleMap<>(keys, values);
93  
94  		var foundNullKey = false;
95  		for (var entry : map.entrySet()) {
96  			if (entry.getKey() == null) {
97  				foundNullKey = true;
98  				assertEquals("value2", entry.getValue());
99  
100 				// Verify entry is also unmodifiable
101 				assertThrows(UnsupportedOperationException.class, () -> {
102 					entry.setValue("modified");
103 				});
104 			}
105 		}
106 		assertTrue(foundNullKey, "Null key not found in entrySet");
107 	}
108 
109 	@Test
110 	void a06_nullKey_keySet() {
111 		var keys = a("key1", null, "key3");
112 		var values = a("value1", "value2", "value3");
113 
114 		var map = new SimpleMap<>(keys, values);
115 
116 		assertTrue(map.keySet().contains(null), "Null key not found in keySet");
117 		assertSize(3, map.keySet());
118 	}
119 
120 	//====================================================================================================
121 	// Duplicate key detection
122 	//====================================================================================================
123 	@Test
124 	void b01_duplicateKey_nonNullKeys() {
125 		var keys = a("key1", "key2", "key1");
126 		var values = a("value1", "value2", "value3");
127 
128 		assertThrowsWithMessage(IllegalArgumentException.class, "Duplicate key found: key1", () -> {
129 			new SimpleMap<>(keys, values);
130 		});
131 	}
132 
133 	@Test
134 	void b02_duplicateKey_nullKeys() {
135 		var keys = a(null, "key2", null);
136 		var values = a("value1", "value2", "value3");
137 
138 		assertThrowsWithMessage(IllegalArgumentException.class, "Duplicate key found: null", () -> {
139 			new SimpleMap<>(keys, values);
140 		});
141 	}
142 
143 	@Test
144 	void b03_duplicateKey_mixedNullAndNonNull() {
145 		var keys = a("key1", null, "key2", "key1");
146 		var values = a("value1", "value2", "value3", "value4");
147 
148 		assertThrowsWithMessage(IllegalArgumentException.class, "Duplicate key found: key1", () -> {
149 			new SimpleMap<>(keys, values);
150 		});
151 	}
152 
153 	@Test
154 	void b04_noDuplicateKeys_success() {
155 		var keys = a("key1", null, "key2", "key3");
156 		var values = a("value1", "value2", "value3", "value4");
157 
158 		var map = assertDoesNotThrow(() -> new SimpleMap<>(keys, values));
159 
160 		assertSize(4, map);
161 		assertEquals("value1", map.get("key1"));
162 		assertEquals("value2", map.get(null));
163 		assertEquals("value3", map.get("key2"));
164 		assertEquals("value4", map.get("key3"));
165 	}
166 
167 	//====================================================================================================
168 	// Immutability verification
169 	//====================================================================================================
170 	@Test
171 	void c01_put_throwsException() {
172 		var keys = a("key1");
173 		var values = a("value1");
174 
175 		var map = new SimpleMap<>(keys, values);
176 
177 		assertThrows(UnsupportedOperationException.class, () -> {
178 			map.put("key1", "newValue");
179 		});
180 
181 		assertEquals("value1", map.get("key1"));
182 	}
183 
184 	@Test
185 	void c02_entrySetValues_cannotModify() {
186 		var keys = a("key1", "key2");
187 		var values = a("value1", "value2");
188 
189 		var map = new SimpleMap<>(keys, values);
190 
191 		for (var entry : map.entrySet()) {
192 			assertThrows(UnsupportedOperationException.class, () -> {
193 				entry.setValue("modified");
194 			});
195 		}
196 	}
197 
198 	//====================================================================================================
199 	// Array length mismatch
200 	//====================================================================================================
201 	@Test
202 	void d01_arrayLengthMismatch_keysLonger() {
203 		var keys = a("key1", "key2", "key3");
204 		var values = a("value1", "value2");
205 
206 		assertThrowsWithMessage(IllegalArgumentException.class, java.util.List.of("array lengths differ", "3", "2"), () -> {
207 			new SimpleMap<>(keys, values);
208 		});
209 	}
210 
211 	@Test
212 	void d02_arrayLengthMismatch_valuesLonger() {
213 		var keys = a("key1", "key2");
214 		var values = a("value1", "value2", "value3", "value4");
215 
216 		assertThrowsWithMessage(IllegalArgumentException.class, java.util.List.of("array lengths differ", "2", "4"), () -> {
217 			new SimpleMap<>(keys, values);
218 		});
219 	}
220 
221 	@Test
222 	void d03_arrayLengthMismatch_emptyKeys() {
223 		var keys = new String[0];
224 		var values = a("value1");
225 
226 		assertThrowsWithMessage(IllegalArgumentException.class, "array lengths differ", () -> {
227 			new SimpleMap<>(keys, values);
228 		});
229 	}
230 
231 	@Test
232 	void d04_arrayLengthMismatch_emptyValues() {
233 		var keys = a("key1", "key2");
234 		var values = new String[0];
235 
236 		assertThrowsWithMessage(IllegalArgumentException.class, "array lengths differ", () -> {
237 			new SimpleMap<>(keys, values);
238 		});
239 	}
240 
241 	//====================================================================================================
242 	// Edge cases
243 	//====================================================================================================
244 	@Test
245 	void e01_emptyMap_noNullKeys() {
246 		var keys = new String[0];
247 		var values = new String[0];
248 
249 		var map = new SimpleMap<>(keys, values);
250 
251 		assertEmpty(map);
252 		assertNull(map.get(null));
253 		assertFalse(map.containsKey(null));
254 	}
255 
256 	@Test
257 	void e02_getLookup_nullKeyNotInMap() {
258 		var keys = a("key1", "key2");
259 		var values = a("value1", "value2");
260 
261 		var map = new SimpleMap<>(keys, values);
262 
263 		assertNull(map.get(null));
264 		assertFalse(map.containsKey(null));
265 	}
266 
267 	@Test
268 	void e03_complexTypes_nullKey() {
269 		var keys = a(1, null, 3);
270 		var values = a("one", "null-key", "three");
271 
272 		var map = new SimpleMap<>(keys, values);
273 
274 		assertEquals("one", map.get(1));
275 		assertEquals("null-key", map.get(null));
276 		assertEquals("three", map.get(3));
277 	}
278 
279 	//====================================================================================================
280 	// Thread safety verification
281 	//====================================================================================================
282 	@Test
283 	void f01_concurrentAccess_safe() throws InterruptedException {
284 		var keys = a("key1", null, "key3");
285 		var values = a("value1", "value2", "value3");
286 
287 		var map = new SimpleMap<>(keys, values);
288 
289 		// Create multiple threads reading from the map
290 		var threads = new Thread[10];
291 		for (var i = 0; i < threads.length; i++) {
292 			threads[i] = new Thread(() -> {
293 				for (var j = 0; j < 1000; j++) {
294 					assertEquals("value1", map.get("key1"));
295 					assertEquals("value2", map.get(null));
296 					assertEquals("value3", map.get("key3"));
297 				}
298 			});
299 			threads[i].start();
300 		}
301 
302 		// Wait for all threads to complete
303 		for (var thread : threads) {
304 			thread.join();
305 		}
306 
307 		// Verify map is still intact
308 		assertSize(3, map);
309 		assertEquals("value1", map.get("key1"));
310 		assertEquals("value2", map.get(null));
311 		assertEquals("value3", map.get("key3"));
312 	}
313 
314 	//====================================================================================================
315 	// toString(), equals(), hashCode()
316 	//====================================================================================================
317 
318 	@Test
319 	void e01_toString_standardFormat() {
320 		String[] keys = { "key1", "key2", "key3" };
321 		Object[] values = { "value1", "value2", "value3" };
322 		SimpleMap<String,Object> map = new SimpleMap<>(keys, values);
323 
324 		var result = map.toString();
325 		assertTrue(result.startsWith("{"));
326 		assertTrue(result.endsWith("}"));
327 		assertTrue(result.contains("key1=value1"));
328 		assertTrue(result.contains("key2=value2"));
329 		assertTrue(result.contains("key3=value3"));
330 	}
331 
332 	@Test
333 	void e02_toString_emptyMap() {
334 		String[] keys = {};
335 		Object[] values = {};
336 		SimpleMap<String,Object> map = new SimpleMap<>(keys, values);
337 
338 		assertEquals("{}", map.toString());
339 	}
340 
341 	@Test
342 	void e03_equals_sameContents() {
343 		String[] keys1 = { "key1", "key2" };
344 		Object[] values1 = { "value1", "value2" };
345 		SimpleMap<String,Object> map1 = new SimpleMap<>(keys1, values1);
346 
347 		String[] keys2 = { "key1", "key2" };
348 		Object[] values2 = { "value1", "value2" };
349 		SimpleMap<String,Object> map2 = new SimpleMap<>(keys2, values2);
350 
351 		assertTrue(map1.equals(map2));
352 		assertTrue(map2.equals(map1));
353 	}
354 
355 	@Test
356 	void e04_equals_differentContents() {
357 		String[] keys1 = { "key1", "key2" };
358 		Object[] values1 = { "value1", "value2" };
359 		SimpleMap<String,Object> map1 = new SimpleMap<>(keys1, values1);
360 
361 		String[] keys2 = { "key1", "key2" };
362 		Object[] values2 = { "value1", "value3" };
363 		SimpleMap<String,Object> map2 = new SimpleMap<>(keys2, values2);
364 
365 		assertFalse(map1.equals(map2));
366 		assertFalse(map2.equals(map1));
367 	}
368 
369 	@Test
370 	void e05_equals_regularMap() {
371 		String[] keys = { "key1", "key2" };
372 		Object[] values = { "value1", "value2" };
373 		SimpleMap<String,Object> map = new SimpleMap<>(keys, values);
374 
375 		var regularMap = new LinkedHashMap<String, Object>();
376 		regularMap.put("key1", "value1");
377 		regularMap.put("key2", "value2");
378 
379 		assertTrue(map.equals(regularMap));
380 		assertTrue(regularMap.equals(map));
381 	}
382 
383 	@Test
384 	void e06_equals_notAMap() {
385 		String[] keys = { "key1" };
386 		Object[] values = { "value1" };
387 		SimpleMap<String,Object> map = new SimpleMap<>(keys, values);
388 
389 		assertFalse(map.equals(null));
390 	}
391 
392 	@Test
393 	void e07_hashCode_sameContents() {
394 		String[] keys1 = { "key1", "key2" };
395 		Object[] values1 = { "value1", "value2" };
396 		SimpleMap<String,Object> map1 = new SimpleMap<>(keys1, values1);
397 
398 		String[] keys2 = { "key1", "key2" };
399 		Object[] values2 = { "value1", "value2" };
400 		SimpleMap<String,Object> map2 = new SimpleMap<>(keys2, values2);
401 
402 		assertEquals(map1.hashCode(), map2.hashCode());
403 	}
404 
405 	@Test
406 	void e08_hashCode_regularMap() {
407 		String[] keys = { "key1", "key2" };
408 		Object[] values = { "value1", "value2" };
409 		SimpleMap<String,Object> map = new SimpleMap<>(keys, values);
410 
411 		var regularMap = new LinkedHashMap<String, Object>();
412 		regularMap.put("key1", "value1");
413 		regularMap.put("key2", "value2");
414 
415 		assertEquals(map.hashCode(), regularMap.hashCode());
416 	}
417 }