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.function;
18  
19  import static org.junit.jupiter.api.Assertions.*;
20  
21  import java.util.concurrent.atomic.AtomicInteger;
22  
23  import org.apache.juneau.*;
24  import org.junit.jupiter.api.*;
25  
26  class ResettableSupplier_Test extends TestBase {
27  
28  	//------------------------------------------------------------------------------------------------------------------
29  	// Basic tests.
30  	//------------------------------------------------------------------------------------------------------------------
31  	@Test void a01_basic() {
32  		var callCount = new AtomicInteger();
33  		ResettableSupplier<String> supplier = new ResettableSupplier<>(() -> {
34  			callCount.incrementAndGet();
35  			return "result";
36  		});
37  
38  		// First call should invoke the supplier
39  		assertEquals("result", supplier.get());
40  		assertEquals(1, callCount.get());
41  
42  		// Second call should return cached value
43  		assertEquals("result", supplier.get());
44  		assertEquals(1, callCount.get()); // Should not increment
45  	}
46  
47  	@Test void a02_returnsNull() {
48  		var callCount = new AtomicInteger();
49  		ResettableSupplier<String> supplier = new ResettableSupplier<>(() -> {
50  			callCount.incrementAndGet();
51  			return null;
52  		});
53  
54  		// First call should invoke the supplier and cache null
55  		assertNull(supplier.get());
56  		assertEquals(1, callCount.get());
57  
58  		// Second call should return cached null
59  		assertNull(supplier.get());
60  		assertEquals(1, callCount.get()); // Should not increment
61  	}
62  
63  	@Test void a03_reset() {
64  		var callCount = new AtomicInteger();
65  		ResettableSupplier<String> supplier = new ResettableSupplier<>(() -> {
66  			callCount.incrementAndGet();
67  			return "result" + callCount.get();
68  		});
69  
70  		// First call
71  		assertEquals("result1", supplier.get());
72  		assertEquals(1, callCount.get());
73  
74  		// Second call - cached
75  		assertEquals("result1", supplier.get());
76  		assertEquals(1, callCount.get());
77  
78  		// Reset
79  		supplier.reset();
80  
81  		// Third call - should recompute
82  		assertEquals("result2", supplier.get());
83  		assertEquals(2, callCount.get());
84  
85  		// Fourth call - cached again
86  		assertEquals("result2", supplier.get());
87  		assertEquals(2, callCount.get());
88  	}
89  
90  	@Test void a04_multipleResets() {
91  		var callCount = new AtomicInteger();
92  		ResettableSupplier<Integer> supplier = new ResettableSupplier<>(() -> {
93  			callCount.incrementAndGet();
94  			return callCount.get();
95  		});
96  
97  		assertEquals(1, supplier.get());
98  		supplier.reset();
99  		assertEquals(2, supplier.get());
100 		supplier.reset();
101 		assertEquals(3, supplier.get());
102 		supplier.reset();
103 		assertEquals(4, supplier.get());
104 		assertEquals(4, callCount.get());
105 	}
106 
107 	@Test void a05_resetBeforeFirstCall() {
108 		var callCount = new AtomicInteger();
109 		ResettableSupplier<String> supplier = new ResettableSupplier<>(() -> {
110 			callCount.incrementAndGet();
111 			return "result";
112 		});
113 
114 		// Reset before any call should be safe
115 		supplier.reset();
116 
117 		// First call should still work
118 		assertEquals("result", supplier.get());
119 		assertEquals(1, callCount.get());
120 	}
121 
122 	@Test void a06_threadSafety_raceCondition() throws InterruptedException {
123 		var callCount = new AtomicInteger();
124 		ResettableSupplier<String> supplier = new ResettableSupplier<>(() -> {
125 			callCount.incrementAndGet();
126 			// Simulate some work
127 			try {
128 				Thread.sleep(10);
129 			} catch (InterruptedException e) {
130 				Thread.currentThread().interrupt();
131 			}
132 			return "result" + callCount.get();
133 		});
134 
135 		// Multiple threads calling get() simultaneously
136 		var threads = new Thread[10];
137 		var results = new String[10];
138 		for (int i = 0; i < 10; i++) {
139 			final int index = i;
140 			threads[i] = new Thread(() -> {
141 				results[index] = supplier.get();
142 			});
143 		}
144 
145 		// Start all threads
146 		for (var thread : threads) {
147 			thread.start();
148 		}
149 
150 		// Wait for all threads
151 		for (var thread : threads) {
152 			thread.join();
153 		}
154 
155 		// All results should be the same (cached value)
156 		var firstResult = results[0];
157 		for (var result : results) {
158 			assertEquals(firstResult, result);
159 		}
160 
161 		// Supplier should have been called at least once, but possibly multiple times due to race
162 		// The important thing is that all threads got the same result
163 		assertTrue(callCount.get() >= 1);
164 	}
165 
166 	@Test void a07_resetAndGetConcurrently() throws InterruptedException {
167 		var callCount = new AtomicInteger();
168 		ResettableSupplier<Integer> supplier = new ResettableSupplier<>(() -> {
169 			callCount.incrementAndGet();
170 			return callCount.get();
171 		});
172 
173 		var resetThread = new Thread(() -> {
174 			for (int i = 0; i < 10; i++) {
175 				supplier.reset();
176 				try {
177 					Thread.sleep(5);  // Increased sleep to increase chance of race
178 				} catch (InterruptedException e) {
179 					Thread.currentThread().interrupt();
180 				}
181 			}
182 		});
183 
184 		var getThread = new Thread(() -> {
185 			for (int i = 0; i < 100; i++) {
186 				supplier.get();
187 				try {
188 					Thread.sleep(1);
189 				} catch (InterruptedException e) {
190 					Thread.currentThread().interrupt();
191 				}
192 			}
193 		});
194 
195 		resetThread.start();
196 		getThread.start();
197 
198 		resetThread.join();
199 		getThread.join();
200 
201 		// Should have called supplier multiple times due to resets
202 		// Note: This is a race condition test, so results may vary
203 		// We just verify that the supplier was called at least once
204 		assertTrue(callCount.get() >= 1, "Supplier should have been called at least once");
205 	}
206 
207 	@Test void a08_set() {
208 		var callCount = new AtomicInteger();
209 		ResettableSupplier<String> supplier = new ResettableSupplier<>(() -> {
210 			callCount.incrementAndGet();
211 			return "computed";
212 		});
213 
214 		// Set a value before any get() call
215 		supplier.set("injected");
216 		assertEquals("injected", supplier.get());
217 		assertEquals(0, callCount.get()); // Supplier should not have been called
218 
219 		// Subsequent get() calls should return the set value
220 		assertEquals("injected", supplier.get());
221 		assertEquals(0, callCount.get()); // Still not called
222 
223 		// Set a different value
224 		supplier.set("newValue");
225 		assertEquals("newValue", supplier.get());
226 		assertEquals(0, callCount.get()); // Still not called
227 
228 		// Reset clears the set value, next get() will invoke supplier
229 		supplier.reset();
230 		assertEquals("computed", supplier.get());
231 		assertEquals(1, callCount.get()); // Now supplier is called
232 
233 		// Set after get() has been called
234 		supplier.set("overridden");
235 		assertEquals("overridden", supplier.get());
236 		assertEquals(1, callCount.get()); // Should not increment
237 	}
238 
239 	@Test void a09_setNull() {
240 		var callCount = new AtomicInteger();
241 		ResettableSupplier<String> supplier = new ResettableSupplier<>(() -> {
242 			callCount.incrementAndGet();
243 			return "computed";
244 		});
245 
246 		// Set null value
247 		supplier.set(null);
248 		assertNull(supplier.get());
249 		assertEquals(0, callCount.get()); // Supplier should not have been called
250 
251 		// Subsequent get() calls should return null
252 		assertNull(supplier.get());
253 		assertEquals(0, callCount.get()); // Still not called
254 
255 		// Reset and get() should invoke supplier
256 		supplier.reset();
257 		assertEquals("computed", supplier.get());
258 		assertEquals(1, callCount.get());
259 	}
260 
261 	//------------------------------------------------------------------------------------------------------------------
262 	// isSupplied()
263 	//------------------------------------------------------------------------------------------------------------------
264 	@Test void b01_isSupplied_notCalled() {
265 		var supplier = new ResettableSupplier<>(() -> "value");
266 		assertTrue(supplier.isSupplied()); // Not called yet
267 	}
268 
269 	@Test void b02_isSupplied_afterGet() {
270 		var supplier = new ResettableSupplier<>(() -> "value");
271 		supplier.get();
272 		assertFalse(supplier.isSupplied()); // Has been called
273 	}
274 
275 	@Test void b03_isSupplied_afterReset() {
276 		var supplier = new ResettableSupplier<>(() -> "value");
277 		supplier.get();
278 		supplier.reset();
279 		assertTrue(supplier.isSupplied()); // Reset, so not supplied anymore
280 	}
281 
282 	@Test void b04_isSupplied_afterSet() {
283 		var supplier = new ResettableSupplier<>(() -> "value");
284 		supplier.set("injected");
285 		assertFalse(supplier.isSupplied()); // Has a value (even if set directly)
286 	}
287 
288 	//------------------------------------------------------------------------------------------------------------------
289 	// copy()
290 	//------------------------------------------------------------------------------------------------------------------
291 	@Test void c01_copy_notCalled() {
292 		var callCount = new AtomicInteger();
293 		var supplier = new ResettableSupplier<>(() -> {
294 			callCount.incrementAndGet();
295 			return "value" + callCount.get();
296 		});
297 		var copy = supplier.copy();
298 
299 		// Copy should use original supplier
300 		assertEquals("value1", copy.get());
301 		assertEquals(1, callCount.get());
302 	}
303 
304 	@Test void c02_copy_afterGet() {
305 		var callCount = new AtomicInteger();
306 		var supplier = new ResettableSupplier<>(() -> {
307 			callCount.incrementAndGet();
308 			return "value" + callCount.get();
309 		});
310 		supplier.get(); // Cache the value
311 		var copy = supplier.copy();
312 
313 		// Copy should use cached value
314 		assertEquals("value1", copy.get());
315 		assertEquals(1, callCount.get()); // Original supplier not called again
316 	}
317 
318 	@Test void c03_copy_independent() {
319 		var supplier = new ResettableSupplier<>(() -> "value");
320 		var copy = supplier.copy();
321 
322 		// Copy is independent
323 		supplier.reset();
324 		assertEquals("value", copy.get()); // Copy still has cached value
325 	}
326 
327 	//------------------------------------------------------------------------------------------------------------------
328 	// map() - overridden to return ResettableSupplier
329 	//------------------------------------------------------------------------------------------------------------------
330 	@Test void d01_map_present() {
331 		var supplier = new ResettableSupplier<>(() -> "hello");
332 		var mapped = supplier.map(String::length);
333 		assertEquals(5, mapped.get());
334 	}
335 
336 	@Test void d02_map_empty() {
337 		@SuppressWarnings("cast")
338 		var supplier = new ResettableSupplier<>(() -> (String)null);
339 		var mapped = supplier.map(String::length);
340 		assertNull(mapped.get());
341 	}
342 
343 	@Test void d03_map_cached() {
344 		var callCount = new AtomicInteger();
345 		var supplier = new ResettableSupplier<>(() -> {
346 			callCount.incrementAndGet();
347 			return "hello";
348 		});
349 		var mapped = supplier.map(String::length);
350 
351 		// First call
352 		assertEquals(5, mapped.get());
353 		assertEquals(1, callCount.get());
354 
355 		// Second call - should use cached value from original supplier
356 		assertEquals(5, mapped.get());
357 		assertEquals(1, callCount.get()); // Original supplier not called again
358 	}
359 
360 	@Test void d04_map_independent() {
361 		var supplier = new ResettableSupplier<>(() -> "hello");
362 		var mapped = supplier.map(String::length);
363 
364 		// Reset original
365 		supplier.reset();
366 		// Mapped should still have cached value
367 		assertEquals(5, mapped.get());
368 	}
369 
370 	//------------------------------------------------------------------------------------------------------------------
371 	// filter() - overridden to return ResettableSupplier
372 	//------------------------------------------------------------------------------------------------------------------
373 	@Test void e01_filter_matches() {
374 		var supplier = new ResettableSupplier<>(() -> "hello");
375 		var filtered = supplier.filter(s -> s.length() > 3);
376 		assertEquals("hello", filtered.get());
377 	}
378 
379 	@Test void e02_filter_noMatch() {
380 		var supplier = new ResettableSupplier<>(() -> "hi");
381 		var filtered = supplier.filter(s -> s.length() > 3);
382 		assertNull(filtered.get());
383 	}
384 
385 	@Test void e03_filter_empty() {
386 		@SuppressWarnings("cast")
387 		var supplier = new ResettableSupplier<>(() -> (String)null);
388 		var filtered = supplier.filter(s -> s.length() > 3);
389 		assertNull(filtered.get());
390 	}
391 
392 	@Test void e04_filter_cached() {
393 		var callCount = new AtomicInteger();
394 		var supplier = new ResettableSupplier<>(() -> {
395 			callCount.incrementAndGet();
396 			return "hello";
397 		});
398 		var filtered = supplier.filter(s -> s.length() > 3);
399 
400 		// First call
401 		assertEquals("hello", filtered.get());
402 		assertEquals(1, callCount.get());
403 
404 		// Second call - should use cached value
405 		assertEquals("hello", filtered.get());
406 		assertEquals(1, callCount.get());
407 	}
408 
409 	//------------------------------------------------------------------------------------------------------------------
410 	// OptionalSupplier methods
411 	//------------------------------------------------------------------------------------------------------------------
412 	@Test void f01_isPresent() {
413 		var supplier = new ResettableSupplier<>(() -> "value");
414 		assertTrue(supplier.isPresent());
415 		assertFalse(supplier.isEmpty());
416 	}
417 
418 	@Test void f02_isEmpty() {
419 		var supplier = new ResettableSupplier<>(() -> null);
420 		assertFalse(supplier.isPresent());
421 		assertTrue(supplier.isEmpty());
422 	}
423 
424 	@Test void f03_orElse() {
425 		var supplier = new ResettableSupplier<>(() -> "value");
426 		assertEquals("value", supplier.orElse("default"));
427 	}
428 
429 	@Test void f04_orElse_empty() {
430 		var supplier = new ResettableSupplier<>(() -> null);
431 		assertEquals("default", supplier.orElse("default"));
432 	}
433 
434 	@Test void f05_orElseGet() {
435 		var callCount = new AtomicInteger();
436 		var supplier = new ResettableSupplier<>(() -> "value");
437 		var result = supplier.orElseGet(() -> {
438 			callCount.incrementAndGet();
439 			return "default";
440 		});
441 		assertEquals("value", result);
442 		assertEquals(0, callCount.get());
443 	}
444 
445 	@Test void f06_orElseGet_empty() {
446 		var callCount = new AtomicInteger();
447 		var supplier = new ResettableSupplier<>(() -> null);
448 		var result = supplier.orElseGet(() -> {
449 			callCount.incrementAndGet();
450 			return "default";
451 		});
452 		assertEquals("default", result);
453 		assertEquals(1, callCount.get());
454 	}
455 
456 	@Test void f07_orElseThrow() {
457 		var supplier = new ResettableSupplier<>(() -> "value");
458 		assertEquals("value", supplier.orElseThrow(() -> new RuntimeException("should not throw")));
459 	}
460 
461 	@Test void f08_orElseThrow_empty() {
462 		var supplier = new ResettableSupplier<>(() -> null);
463 		assertThrows(RuntimeException.class, () -> supplier.orElseThrow(() -> new RuntimeException("expected")));
464 	}
465 
466 	@Test void f09_ifPresent() {
467 		var callCount = new AtomicInteger();
468 		var supplier = new ResettableSupplier<>(() -> "value");
469 		supplier.ifPresent(s -> callCount.incrementAndGet());
470 		assertEquals(1, callCount.get());
471 	}
472 
473 	@Test void f10_ifPresent_empty() {
474 		var callCount = new AtomicInteger();
475 		var supplier = new ResettableSupplier<>(() -> null);
476 		supplier.ifPresent(s -> callCount.incrementAndGet());
477 		assertEquals(0, callCount.get());
478 	}
479 
480 	@Test void f11_ifPresentOrElse() {
481 		var presentCount = new AtomicInteger();
482 		var emptyCount = new AtomicInteger();
483 		var supplier = new ResettableSupplier<>(() -> "value");
484 		supplier.ifPresentOrElse(
485 			s -> presentCount.incrementAndGet(),
486 			() -> emptyCount.incrementAndGet()
487 		);
488 		assertEquals(1, presentCount.get());
489 		assertEquals(0, emptyCount.get());
490 	}
491 
492 	@Test void f12_ifPresentOrElse_empty() {
493 		var presentCount = new AtomicInteger();
494 		var emptyCount = new AtomicInteger();
495 		var supplier = new ResettableSupplier<>(() -> null);
496 		supplier.ifPresentOrElse(
497 			s -> presentCount.incrementAndGet(),
498 			() -> emptyCount.incrementAndGet()
499 		);
500 		assertEquals(0, presentCount.get());
501 		assertEquals(1, emptyCount.get());
502 	}
503 
504 	@Test void f13_toOptional() {
505 		var supplier = new ResettableSupplier<>(() -> "value");
506 		var optional = supplier.toOptional();
507 		assertTrue(optional.isPresent());
508 		assertEquals("value", optional.get());
509 	}
510 
511 	@Test void f14_toOptional_empty() {
512 		var supplier = new ResettableSupplier<>(() -> null);
513 		var optional = supplier.toOptional();
514 		assertFalse(optional.isPresent());
515 	}
516 
517 	@Test void f15_flatMap() {
518 		var supplier = new ResettableSupplier<>(() -> "hello");
519 		var mapped = supplier.flatMap(s -> OptionalSupplier.ofNullable(s.length()));
520 		assertEquals(5, mapped.get());
521 	}
522 
523 	@Test void f16_flatMap_empty() {
524 		@SuppressWarnings("cast")
525 		var supplier = new ResettableSupplier<>(() -> (String)null);
526 		var mapped = supplier.flatMap(s -> OptionalSupplier.ofNullable(s.length()));
527 		assertNull(mapped.get());
528 	}
529 }
530