1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.juneau.commons.collections;
18
19 import static org.apache.juneau.commons.collections.CacheMode.*;
20 import static org.apache.juneau.junit.bct.BctAssertions.*;
21 import static org.junit.jupiter.api.Assertions.*;
22
23 import java.util.*;
24 import java.util.concurrent.*;
25 import java.util.concurrent.atomic.*;
26
27 import org.apache.juneau.*;
28 import org.junit.jupiter.api.*;
29
30 class Cache_Test extends TestBase {
31
32
33
34
35
36 @Test void a01_basicGet_cacheMiss() {
37 var cache = Cache.of(String.class, String.class).build();
38 var callCount = new AtomicInteger();
39
40 var result = cache.get("key1", () -> {
41 callCount.incrementAndGet();
42 return "value1";
43 });
44
45 assertEquals("value1", result);
46 assertEquals(1, callCount.get());
47 assertSize(1, cache);
48 assertEquals(0, cache.getCacheHits());
49 }
50
51 @Test void a02_basicGet_cacheHit() {
52 var cache = Cache.of(String.class, String.class).build();
53 var callCount = new AtomicInteger();
54
55
56 var result1 = cache.get("key1", () -> {
57 callCount.incrementAndGet();
58 return "value1";
59 });
60
61
62 var result2 = cache.get("key1", () -> {
63 callCount.incrementAndGet();
64 return "should not be called";
65 });
66
67 assertEquals("value1", result1);
68 assertEquals("value1", result2);
69 assertSame(result1, result2);
70 assertEquals(1, callCount.get());
71 assertSize(1, cache);
72 assertEquals(1, cache.getCacheHits());
73 }
74
75 @Test void a03_multipleKeys() {
76 var cache = Cache.of(String.class, Integer.class).build();
77
78 var v1 = cache.get("one", () -> 1);
79 var v2 = cache.get("two", () -> 2);
80 var v3 = cache.get("three", () -> 3);
81
82 assertEquals(1, v1);
83 assertEquals(2, v2);
84 assertEquals(3, v3);
85 assertSize(3, cache);
86 assertEquals(0, cache.getCacheHits());
87
88
89 var v1Again = cache.get("one", () -> 999);
90 var v2Again = cache.get("two", () -> 999);
91 var v3Again = cache.get("three", () -> 999);
92
93 assertEquals(1, v1Again);
94 assertEquals(2, v2Again);
95 assertEquals(3, v3Again);
96 assertEquals(3, cache.getCacheHits());
97 }
98
99
100
101
102
103 @Test void a04_nullKey_allowed() {
104 var cache = Cache.of(String.class, String.class)
105 .supplier(k -> "value-" + k)
106 .build();
107
108
109 assertEquals("value-null", cache.get(null, () -> "value-null"));
110
111
112 assertEquals("value-null", cache.get(null));
113 assertEquals(1, cache.getCacheHits());
114
115 assertEquals("value-null", cache.get(null));
116 assertEquals(2, cache.getCacheHits());
117 }
118
119
120
121
122
123 @Test void a06_size() {
124 var cache = Cache.of(String.class, Integer.class).build();
125
126 assertEmpty(cache);
127
128 cache.get("one", () -> 1);
129 assertSize(1, cache);
130
131 cache.get("two", () -> 2);
132 assertSize(2, cache);
133
134 cache.get("three", () -> 3);
135 assertSize(3, cache);
136
137
138 cache.get("one", () -> 999);
139 assertSize(3, cache);
140 }
141
142 @Test void a07_clear() {
143 var cache = Cache.of(String.class, Integer.class).build();
144
145 cache.get("one", () -> 1);
146 cache.get("two", () -> 2);
147 cache.get("one", () -> 999);
148
149 assertSize(2, cache);
150 assertEquals(1, cache.getCacheHits());
151
152 cache.clear();
153
154 assertEmpty(cache);
155 assertEquals(1, cache.getCacheHits());
156
157
158 var v1 = cache.get("one", () -> 10);
159 assertEquals(10, v1);
160 assertSize(1, cache);
161 }
162
163
164
165
166
167 @Test void a08_maxSize_eviction() {
168 var cache = Cache.of(String.class, Integer.class)
169 .maxSize(3)
170 .build();
171
172 cache.get("one", () -> 1);
173 cache.get("two", () -> 2);
174 cache.get("three", () -> 3);
175 assertSize(3, cache);
176
177
178 cache.get("four", () -> 4);
179 assertSize(4, cache);
180
181
182 cache.get("five", () -> 5);
183 assertSize(1, cache);
184 }
185
186 @Test void a09_maxSize_custom() {
187 var cache = Cache.of(String.class, String.class)
188 .maxSize(5)
189 .build();
190
191 for (var i = 1; i <= 5; i++) {
192 final int index = i;
193 cache.get("key" + index, () -> "value" + index);
194 }
195 assertSize(5, cache);
196
197
198 cache.get("key6", () -> "value6");
199 assertSize(6, cache);
200
201
202 cache.get("key7", () -> "value7");
203 assertSize(1, cache);
204 }
205
206
207
208
209
210 @Test void a10_disabled_neverCaches() {
211 var cache = Cache.of(String.class, String.class)
212 .cacheMode(NONE)
213 .build();
214 var callCount = new AtomicInteger();
215
216 var result1 = cache.get("key1", () -> {
217 callCount.incrementAndGet();
218 return "value" + callCount.get();
219 });
220
221 var result2 = cache.get("key1", () -> {
222 callCount.incrementAndGet();
223 return "value" + callCount.get();
224 });
225
226 assertEquals("value1", result1);
227 assertEquals("value2", result2);
228 assertEquals(2, callCount.get());
229 assertEmpty(cache);
230 assertEquals(0, cache.getCacheHits());
231 }
232
233 @Test void a11_disabled_sizeAlwaysZero() {
234 var cache = Cache.of(String.class, Integer.class)
235 .cacheMode(NONE)
236 .build();
237
238 cache.get("one", () -> 1);
239 cache.get("two", () -> 2);
240 cache.get("three", () -> 3);
241
242 assertEmpty(cache);
243 }
244
245 @Test void a12_disabled_clearHasNoEffect() {
246 var cache = Cache.of(String.class, Integer.class)
247 .cacheMode(NONE)
248 .build();
249
250 cache.clear();
251 assertEmpty(cache);
252 }
253
254
255
256
257
258 @Test void a13_weakMode_basicCaching() {
259 var cache = Cache.of(String.class, String.class)
260 .cacheMode(WEAK)
261 .build();
262 var callCount = new AtomicInteger();
263
264
265 var result1 = cache.get("key1", () -> {
266 callCount.incrementAndGet();
267 return "value1";
268 });
269
270
271 var result2 = cache.get("key1", () -> {
272 callCount.incrementAndGet();
273 return "should not be called";
274 });
275
276 assertEquals("value1", result1);
277 assertEquals("value1", result2);
278 assertSame(result1, result2);
279 assertEquals(1, callCount.get());
280 assertSize(1, cache);
281 assertEquals(1, cache.getCacheHits());
282 }
283
284 @Test void a14_weakMode_multipleKeys() {
285 var cache = Cache.of(String.class, Integer.class)
286 .cacheMode(WEAK)
287 .build();
288
289 cache.get("one", () -> 1);
290 cache.get("two", () -> 2);
291 cache.get("three", () -> 3);
292
293 assertSize(3, cache);
294 assertEquals(0, cache.getCacheHits());
295
296
297 assertEquals(1, cache.get("one", () -> 999));
298 assertEquals(2, cache.get("two", () -> 999));
299 assertEquals(3, cache.get("three", () -> 999));
300 assertEquals(3, cache.getCacheHits());
301 }
302
303 @Test void a15_weakMode_clear() {
304 var cache = Cache.of(String.class, Integer.class)
305 .cacheMode(WEAK)
306 .build();
307
308 cache.get("one", () -> 1);
309 cache.get("two", () -> 2);
310 assertSize(2, cache);
311
312 cache.clear();
313 assertEmpty(cache);
314 }
315
316 @Test void a16_weakMode_maxSize() {
317 var cache = Cache.of(String.class, Integer.class)
318 .cacheMode(WEAK)
319 .maxSize(3)
320 .build();
321
322 cache.get("one", () -> 1);
323 cache.get("two", () -> 2);
324 cache.get("three", () -> 3);
325 assertSize(3, cache);
326
327
328 cache.get("four", () -> 4);
329 assertSize(4, cache);
330
331
332 cache.get("five", () -> 5);
333 assertSize(1, cache);
334 }
335
336 @Test void a16b_weakMethod_basicCaching() {
337
338 var cache = Cache.of(String.class, String.class)
339 .weak()
340 .build();
341 var callCount = new AtomicInteger();
342
343
344 var result1 = cache.get("key1", () -> {
345 callCount.incrementAndGet();
346 return "value1";
347 });
348
349
350 var result2 = cache.get("key1", () -> {
351 callCount.incrementAndGet();
352 return "should not be called";
353 });
354
355 assertEquals("value1", result1);
356 assertEquals("value1", result2);
357 assertSame(result1, result2);
358 assertEquals(1, callCount.get());
359 assertSize(1, cache);
360 assertEquals(1, cache.getCacheHits());
361 }
362
363 @Test void a16c_weakMethod_chaining() {
364
365 var cache = Cache.of(String.class, Integer.class)
366 .weak()
367 .maxSize(100)
368 .supplier(k -> k.length())
369 .build();
370
371 var result = cache.get("hello");
372 assertEquals(5, result);
373 assertSize(1, cache);
374 }
375
376
377
378
379
380 @Test void a17_builder_defaults() {
381 var cache = Cache.of(String.class, String.class).build();
382
383
384 cache.get("key1", () -> "value1");
385 assertSize(1, cache);
386 }
387
388 @Test void a18_builder_chaining() {
389 var cache = Cache.of(String.class, String.class)
390 .maxSize(100)
391 .cacheMode(NONE)
392 .build();
393
394
395 cache.get("key1", () -> "value1");
396 assertEmpty(cache);
397 }
398
399
400
401
402
403 @Test void a19_cacheHits_countsCorrectly() {
404 var cache = Cache.of(String.class, Integer.class).build();
405
406 assertEquals(0, cache.getCacheHits());
407
408 cache.get("one", () -> 1);
409 assertEquals(0, cache.getCacheHits());
410
411 cache.get("one", () -> 999);
412 assertEquals(1, cache.getCacheHits());
413
414 cache.get("two", () -> 2);
415 assertEquals(1, cache.getCacheHits());
416
417 cache.get("one", () -> 999);
418 cache.get("two", () -> 999);
419 assertEquals(3, cache.getCacheHits());
420 }
421
422 @Test void a20_cacheHits_persistsAfterClear() {
423 var cache = Cache.of(String.class, Integer.class).build();
424
425 cache.get("one", () -> 1);
426 cache.get("one", () -> 999);
427 assertEquals(1, cache.getCacheHits());
428
429 cache.clear();
430 assertEquals(1, cache.getCacheHits());
431
432 cache.get("one", () -> 1);
433 cache.get("one", () -> 999);
434 assertEquals(2, cache.getCacheHits());
435 }
436
437
438
439
440
441 @Test void a21_concurrentAccess() throws Exception {
442 var cache = Cache.of(Integer.class, String.class).build();
443 var executor = Executors.newFixedThreadPool(10);
444 var callCount = new AtomicInteger();
445
446
447 var futures = new CompletableFuture[100];
448 for (var i = 0; i < 100; i++) {
449 futures[i] = CompletableFuture.runAsync(() -> {
450 cache.get(1, () -> {
451 callCount.incrementAndGet();
452 return "value";
453 });
454 }, executor);
455 }
456
457
458 CompletableFuture.allOf(futures).get(5, TimeUnit.SECONDS);
459
460
461
462 assertTrue(callCount.get() < 10, "Supplier called " + callCount.get() + " times");
463 assertSize(1, cache);
464
465 executor.shutdown();
466 }
467
468 @Test void a22_concurrentDifferentKeys() throws Exception {
469 var cache = Cache.of(Integer.class, String.class).build();
470 var executor = Executors.newFixedThreadPool(10);
471
472
473 var futures = new CompletableFuture[10];
474 for (var i = 0; i < 10; i++) {
475 final int key = i;
476 futures[i] = CompletableFuture.runAsync(() -> {
477 cache.get(key, () -> "value" + key);
478 }, executor);
479 }
480
481
482 CompletableFuture.allOf(futures).get(5, TimeUnit.SECONDS);
483
484 assertSize(10, cache);
485
486 executor.shutdown();
487 }
488
489
490
491
492
493 @Test void a23_integerKeys() {
494 var cache = Cache.of(Integer.class, String.class).build();
495
496 cache.get(1, () -> "one");
497 cache.get(2, () -> "two");
498
499 assertEquals("one", cache.get(1, () -> "should not call"));
500 assertSize(2, cache);
501 assertEquals(1, cache.getCacheHits());
502 }
503
504 @Test void a24_classKeys() {
505 var cache = Cache.of(Class.class, String.class).build();
506
507 cache.get(String.class, () -> "String");
508 cache.get(Integer.class, () -> "Integer");
509
510 assertEquals("String", cache.get(String.class, () -> "should not call"));
511 assertSize(2, cache);
512 }
513
514
515
516
517
518 @Test void a25_sameKeyDifferentValues_returnsFirstCached() {
519 var cache = Cache.of(String.class, String.class).build();
520
521 var result1 = cache.get("key", () -> "first");
522 var result2 = cache.get("key", () -> "second");
523
524 assertEquals("first", result1);
525 assertEquals("first", result2);
526 assertSize(1, cache);
527 }
528
529 @Test void a26_emptyCache_operations() {
530 var cache = Cache.of(String.class, String.class).build();
531
532 assertEmpty(cache);
533 assertEquals(0, cache.getCacheHits());
534 cache.clear();
535 assertEmpty(cache);
536 }
537
538 @Test void a27_maxSize_exactBoundary() {
539 var cache = Cache.of(Integer.class, String.class)
540 .maxSize(3)
541 .build();
542
543 cache.get(1, () -> "one");
544 cache.get(2, () -> "two");
545 cache.get(3, () -> "three");
546
547 assertSize(3, cache);
548
549
550 cache.get(1, () -> "should not call");
551 cache.get(2, () -> "should not call");
552 assertSize(3, cache);
553 assertEquals(2, cache.getCacheHits());
554 }
555
556
557
558
559
560 @Test void a28_logOnExit_withStringId() {
561
562 var cache = Cache.of(String.class, String.class)
563 .logOnExit("TestCache")
564 .build();
565
566
567 cache.get("key1", () -> "value1");
568 cache.get("key1", () -> "should not be called");
569 cache.get("key2", () -> "value2");
570
571
572 assertSize(2, cache);
573 assertEquals(1, cache.getCacheHits());
574
575
576
577
578 }
579
580 @Test void a29_logOnExit_withBooleanTrue() {
581
582 var cache = Cache.of(String.class, Integer.class)
583 .logOnExit(true, "MyCache")
584 .build();
585
586 cache.get("one", () -> 1);
587 cache.get("one", () -> 999);
588
589 assertSize(1, cache);
590 assertEquals(1, cache.getCacheHits());
591 }
592
593 @Test void a30_logOnExit_withBooleanFalse() {
594
595 var cache = Cache.of(String.class, Integer.class)
596 .logOnExit(false, "DisabledCache")
597 .build();
598
599 cache.get("one", () -> 1);
600 cache.get("two", () -> 2);
601
602 assertSize(2, cache);
603 assertEquals(0, cache.getCacheHits());
604 }
605
606 @Test void a31_logOnExit_chaining() {
607
608 var cache = Cache.of(String.class, String.class)
609 .maxSize(100)
610 .logOnExit("ChainedCache")
611 .supplier(k -> "value-" + k)
612 .build();
613
614 var result = cache.get("test");
615 assertEquals("value-test", result);
616 assertSize(1, cache);
617 }
618
619 @Test void a32_logOnExit_multipleCalls_lastWins() {
620
621 var cache = Cache.of(String.class, String.class)
622 .logOnExit("FirstId")
623 .logOnExit("SecondId")
624 .logOnExit(true, "FinalId")
625 .build();
626
627 cache.get("key", () -> "value");
628 assertSize(1, cache);
629
630
631 }
632
633
634
635
636
637 @Test void a33_put_directInsertion() {
638 var cache = Cache.of(String.class, String.class).build();
639
640
641 var previous = cache.put("key1", "value1");
642 assertNull(previous, "Should return null for new key");
643 assertEquals("value1", cache.get("key1", () -> "should not be called"));
644 assertSize(1, cache);
645 }
646
647 @Test void a34_put_overwritesExisting() {
648 var cache = Cache.of(String.class, String.class).build();
649
650 cache.put("key1", "value1");
651 var previous = cache.put("key1", "value2");
652 assertEquals("value1", previous, "Should return previous value");
653 assertEquals("value2", cache.get("key1", () -> "should not be called"));
654 assertSize(1, cache);
655 }
656
657 @Test void a35_put_withNullValue() {
658 var cache = Cache.of(String.class, String.class).build();
659
660 cache.put("key1", "value1");
661 var previous = cache.put("key1", null);
662 assertEquals("value1", previous);
663
664 assertFalse(cache.containsKey("key1"), "Key should be removed when null value is put");
665
666 var callCount = new AtomicInteger();
667 var result = cache.get("key1", () -> {
668 callCount.incrementAndGet();
669 return "supplied";
670 });
671 assertEquals("supplied", result);
672 assertEquals(1, callCount.get());
673
674 assertTrue(cache.containsKey("key1"), "Key should be in cache after get() with non-null supplier");
675 }
676
677 @Test void a35b_put_withNullValue_newKey() {
678 var cache = Cache.of(String.class, String.class).build();
679
680 var previous = cache.put("key1", null);
681 assertNull(previous);
682 assertFalse(cache.containsKey("key1"));
683 assertTrue(cache.isEmpty());
684 }
685
686
687
688
689
690 @Test void a36_isEmpty_newCache() {
691 var cache = Cache.of(String.class, String.class).build();
692 assertTrue(cache.isEmpty());
693 }
694
695 @Test void a37_isEmpty_afterPut() {
696 var cache = Cache.of(String.class, String.class).build();
697 cache.put("key1", "value1");
698 assertFalse(cache.isEmpty());
699 }
700
701 @Test void a38_isEmpty_afterGet() {
702 var cache = Cache.of(String.class, String.class).build();
703 cache.get("key1", () -> "value1");
704 assertFalse(cache.isEmpty());
705 }
706
707 @Test void a39_isEmpty_afterClear() {
708 var cache = Cache.of(String.class, String.class).build();
709 cache.put("key1", "value1");
710 cache.put("key2", "value2");
711 assertFalse(cache.isEmpty());
712 cache.clear();
713 assertTrue(cache.isEmpty());
714 }
715
716 @Test void a40_isEmpty_disabledCache() {
717 var cache = Cache.of(String.class, String.class)
718 .cacheMode(NONE)
719 .build();
720 cache.get("key1", () -> "value1");
721 assertTrue(cache.isEmpty(), "Disabled cache should always be empty");
722 }
723
724
725
726
727
728 @Test void a41_containsKey_notPresent() {
729 var cache = Cache.of(String.class, String.class).build();
730 assertFalse(cache.containsKey("key1"));
731 }
732
733 @Test void a42_containsKey_afterPut() {
734 var cache = Cache.of(String.class, String.class).build();
735 cache.put("key1", "value1");
736 assertTrue(cache.containsKey("key1"));
737 assertFalse(cache.containsKey("key2"));
738 }
739
740 @Test void a43_containsKey_afterGet() {
741 var cache = Cache.of(String.class, String.class).build();
742 cache.get("key1", () -> "value1");
743 assertTrue(cache.containsKey("key1"));
744 }
745
746 @Test void a44_containsKey_afterClear() {
747 var cache = Cache.of(String.class, String.class).build();
748 cache.put("key1", "value1");
749 assertTrue(cache.containsKey("key1"));
750 cache.clear();
751 assertFalse(cache.containsKey("key1"));
752 }
753
754 @Test void a45_containsKey_nullKey() {
755 var cache = Cache.of(String.class, String.class).build();
756
757 cache.get(null, () -> "value");
758 assertTrue(cache.containsKey(null));
759 }
760
761
762
763
764
765 @Test void a46_remove_existingKey() {
766 var cache = Cache.of(String.class, String.class).build();
767 cache.put("key1", "value1");
768 var removed = cache.remove("key1");
769 assertEquals("value1", removed);
770 assertFalse(cache.containsKey("key1"));
771 assertTrue(cache.isEmpty());
772 }
773
774 @Test void a47_remove_nonExistentKey() {
775 var cache = Cache.of(String.class, String.class).build();
776 var removed = cache.remove("key1");
777 assertNull(removed);
778 }
779
780 @Test void a48_remove_afterGet() {
781 var cache = Cache.of(String.class, String.class).build();
782 cache.get("key1", () -> "value1");
783 var removed = cache.remove("key1");
784 assertEquals("value1", removed);
785 assertFalse(cache.containsKey("key1"));
786 }
787
788 @Test void a49_remove_nullKey() {
789 var cache = Cache.of(String.class, String.class).build();
790 cache.put(null, "value1");
791 var removed = cache.remove(null);
792 assertEquals("value1", removed);
793 assertFalse(cache.containsKey(null));
794 }
795
796
797
798
799
800 @Test void a50_containsValue_present() {
801 var cache = Cache.of(String.class, String.class).build();
802 cache.put("key1", "value1");
803 cache.put("key2", "value2");
804 assertTrue(cache.containsValue("value1"));
805 assertTrue(cache.containsValue("value2"));
806 assertFalse(cache.containsValue("value3"));
807 }
808
809 @Test void a51_containsValue_notPresent() {
810 var cache = Cache.of(String.class, String.class).build();
811 assertFalse(cache.containsValue("value1"));
812 }
813
814 @Test void a52_containsValue_afterRemove() {
815 var cache = Cache.of(String.class, String.class).build();
816 cache.put("key1", "value1");
817 assertTrue(cache.containsValue("value1"));
818 cache.remove("key1");
819 assertFalse(cache.containsValue("value1"));
820 }
821
822 @Test void a53_containsValue_afterClear() {
823 var cache = Cache.of(String.class, String.class).build();
824 cache.put("key1", "value1");
825 cache.put("key2", "value2");
826 assertTrue(cache.containsValue("value1"));
827 cache.clear();
828 assertFalse(cache.containsValue("value1"));
829 assertFalse(cache.containsValue("value2"));
830 }
831
832 @Test void a54_containsValue_nullValue() {
833 var cache = Cache.of(String.class, String.class).build();
834
835 cache.get("key1", () -> null);
836 assertFalse(cache.containsValue(null));
837 }
838
839
840
841
842
843 @Test void a46_arrayKeys_contentBasedEquality() {
844 var cache = Cache.of(String[].class, String.class).build();
845
846 var key1 = new String[]{"a", "b", "c"};
847 var key2 = new String[]{"a", "b", "c"};
848
849 cache.get(key1, () -> "value1");
850
851 var result = cache.get(key2, () -> "should not be called");
852 assertEquals("value1", result);
853 assertEquals(1, cache.getCacheHits());
854 assertSize(1, cache);
855 }
856
857 @Test void a47_arrayKeys_differentContent() {
858 var cache = Cache.of(String[].class, String.class).build();
859
860 var key1 = new String[]{"a", "b", "c"};
861 var key2 = new String[]{"a", "b", "d"};
862
863 cache.get(key1, () -> "value1");
864 var result = cache.get(key2, () -> "value2");
865 assertEquals("value2", result);
866 assertSize(2, cache);
867 }
868
869 @Test void a48_arrayKeys_put() {
870 var cache = Cache.of(String[].class, String.class).build();
871
872 var key1 = new String[]{"a", "b"};
873 var key2 = new String[]{"a", "b"};
874
875 cache.put(key1, "value1");
876 assertTrue(cache.containsKey(key2));
877 assertEquals("value1", cache.get(key2, () -> "should not be called"));
878 }
879
880
881
882
883
884 @Test void a49_nullValue_notCached() {
885 var cache = Cache.of(String.class, String.class).build();
886 var callCount = new AtomicInteger();
887
888
889 var result1 = cache.get("key1", () -> {
890 callCount.incrementAndGet();
891 return null;
892 });
893 assertNull(result1);
894 assertEquals(1, callCount.get());
895
896
897 var result2 = cache.get("key1", () -> {
898 callCount.incrementAndGet();
899 return null;
900 });
901 assertNull(result2);
902 assertEquals(2, callCount.get());
903 assertTrue(cache.isEmpty(), "Null values should not be cached");
904 }
905
906 @Test void a50_nullValue_afterPut() {
907 var cache = Cache.of(String.class, String.class).build();
908 cache.put("key1", "value1");
909 cache.put("key1", null);
910
911 var callCount = new AtomicInteger();
912 var result = cache.get("key1", () -> {
913 callCount.incrementAndGet();
914 return "supplied";
915 });
916 assertEquals("supplied", result);
917 assertEquals(1, callCount.get());
918 }
919
920
921
922
923
924 @Test void a51_create_basic() {
925 var cache = Cache.<String, String>create()
926 .supplier(k -> "value-" + k)
927 .build();
928
929 var result = cache.get("test");
930 assertEquals("value-test", result);
931 assertSize(1, cache);
932 }
933
934 @Test void a52_create_withConfiguration() {
935 var cache = Cache.<String, Integer>create()
936 .maxSize(50)
937 .cacheMode(WEAK)
938 .supplier(k -> k.length())
939 .build();
940
941 var result = cache.get("hello");
942 assertEquals(5, result);
943 assertSize(1, cache);
944 }
945
946
947
948
949
950 @Test void a53_disableCaching() {
951 var cache = Cache.of(String.class, String.class)
952 .cacheMode(NONE)
953 .build();
954
955 var callCount = new AtomicInteger();
956 cache.get("key1", () -> {
957 callCount.incrementAndGet();
958 return "value1";
959 });
960 cache.get("key1", () -> {
961 callCount.incrementAndGet();
962 return "value1";
963 });
964
965 assertEquals(2, callCount.get(), "Supplier should be called every time when disabled");
966 assertTrue(cache.isEmpty());
967 assertEquals(0, cache.getCacheHits());
968 }
969
970
971
972
973
974 @Test void a54_threadLocal_basicCaching() throws Exception {
975 var cache = Cache.of(String.class, String.class)
976 .threadLocal()
977 .build();
978 var callCount = new AtomicInteger();
979
980
981 var result1 = cache.get("key1", () -> {
982 callCount.incrementAndGet();
983 return "value1";
984 });
985
986
987 var result2 = cache.get("key1", () -> {
988 callCount.incrementAndGet();
989 return "should not be called";
990 });
991
992 assertEquals("value1", result1);
993 assertEquals("value1", result2);
994 assertSame(result1, result2);
995 assertEquals(1, callCount.get());
996 assertSize(1, cache);
997 assertEquals(1, cache.getCacheHits());
998 }
999
1000 @Test void a55_threadLocal_eachThreadHasOwnCache() throws Exception {
1001 var cache = Cache.of(String.class, String.class)
1002 .threadLocal()
1003 .build();
1004 var executor = Executors.newFixedThreadPool(2);
1005 var threadValues = new ConcurrentHashMap<Thread, String>();
1006
1007
1008 var future1 = CompletableFuture.runAsync(() -> {
1009 var value = cache.get("key1", () -> "thread1-value");
1010 threadValues.put(Thread.currentThread(), value);
1011 }, executor);
1012
1013 var future2 = CompletableFuture.runAsync(() -> {
1014 var value = cache.get("key1", () -> "thread2-value");
1015 threadValues.put(Thread.currentThread(), value);
1016 }, executor);
1017
1018 CompletableFuture.allOf(future1, future2).get(5, TimeUnit.SECONDS);
1019
1020
1021 assertEquals(2, threadValues.size());
1022 assertTrue(threadValues.containsValue("thread1-value"));
1023 assertTrue(threadValues.containsValue("thread2-value"));
1024
1025
1026 var threadValues2 = new ConcurrentHashMap<Thread, String>();
1027 var threads = new ArrayList<>(threadValues.keySet());
1028
1029 future1 = CompletableFuture.runAsync(() -> {
1030 var value = cache.get("key1", () -> "should-not-be-called");
1031 threadValues2.put(Thread.currentThread(), value);
1032 }, executor);
1033
1034 future2 = CompletableFuture.runAsync(() -> {
1035 var value = cache.get("key1", () -> "should-not-be-called");
1036 threadValues2.put(Thread.currentThread(), value);
1037 }, executor);
1038
1039 CompletableFuture.allOf(future1, future2).get(5, TimeUnit.SECONDS);
1040
1041
1042 for (var thread : threads) {
1043 if (threadValues2.containsKey(thread)) {
1044 assertEquals(threadValues.get(thread), threadValues2.get(thread),
1045 "Thread " + thread + " should get its own cached value");
1046 }
1047 }
1048
1049 executor.shutdown();
1050 }
1051
1052 @Test void a56_threadLocal_multipleKeys() {
1053 var cache = Cache.of(String.class, Integer.class)
1054 .threadLocal()
1055 .build();
1056
1057 cache.get("one", () -> 1);
1058 cache.get("two", () -> 2);
1059 cache.get("three", () -> 3);
1060
1061 assertSize(3, cache);
1062 assertEquals(0, cache.getCacheHits());
1063
1064
1065 assertEquals(1, cache.get("one", () -> 999));
1066 assertEquals(2, cache.get("two", () -> 999));
1067 assertEquals(3, cache.get("three", () -> 999));
1068 assertEquals(3, cache.getCacheHits());
1069 }
1070
1071 @Test void a57_threadLocal_clear() {
1072 var cache = Cache.of(String.class, Integer.class)
1073 .threadLocal()
1074 .build();
1075
1076 cache.get("one", () -> 1);
1077 cache.get("two", () -> 2);
1078 assertSize(2, cache);
1079
1080 cache.clear();
1081 assertEmpty(cache);
1082 }
1083
1084 @Test void a58_threadLocal_maxSize() {
1085 var cache = Cache.of(String.class, Integer.class)
1086 .threadLocal()
1087 .maxSize(3)
1088 .build();
1089
1090 cache.get("one", () -> 1);
1091 cache.get("two", () -> 2);
1092 cache.get("three", () -> 3);
1093 assertSize(3, cache);
1094
1095
1096 cache.get("four", () -> 4);
1097 assertSize(4, cache);
1098
1099
1100 cache.get("five", () -> 5);
1101 assertSize(1, cache);
1102 }
1103
1104 @Test void a59_threadLocal_cacheHits() {
1105 var cache = Cache.of(String.class, Integer.class)
1106 .threadLocal()
1107 .build();
1108
1109 assertEquals(0, cache.getCacheHits());
1110
1111 cache.get("one", () -> 1);
1112 assertEquals(0, cache.getCacheHits());
1113
1114 cache.get("one", () -> 999);
1115 assertEquals(1, cache.getCacheHits());
1116
1117 cache.get("two", () -> 2);
1118 assertEquals(1, cache.getCacheHits());
1119
1120 cache.get("one", () -> 999);
1121 cache.get("two", () -> 999);
1122 assertEquals(3, cache.getCacheHits());
1123 }
1124
1125
1126
1127
1128
1129 @Test void a60_threadLocal_weakMode_basicCaching() {
1130 var cache = Cache.of(String.class, String.class)
1131 .threadLocal()
1132 .cacheMode(WEAK)
1133 .build();
1134 var callCount = new AtomicInteger();
1135
1136
1137 var result1 = cache.get("key1", () -> {
1138 callCount.incrementAndGet();
1139 return "value1";
1140 });
1141
1142
1143 var result2 = cache.get("key1", () -> {
1144 callCount.incrementAndGet();
1145 return "should not be called";
1146 });
1147
1148 assertEquals("value1", result1);
1149 assertEquals("value1", result2);
1150 assertSame(result1, result2);
1151 assertEquals(1, callCount.get());
1152 assertSize(1, cache);
1153 assertEquals(1, cache.getCacheHits());
1154 }
1155
1156 @Test void a61_threadLocal_weakMode_eachThreadHasOwnCache() throws Exception {
1157 var cache = Cache.of(String.class, String.class)
1158 .threadLocal()
1159 .cacheMode(WEAK)
1160 .build();
1161 var executor = Executors.newFixedThreadPool(2);
1162 var threadValues = new ConcurrentHashMap<Thread, String>();
1163
1164
1165 var future1 = CompletableFuture.runAsync(() -> {
1166 var value = cache.get("key1", () -> "thread1-value");
1167 threadValues.put(Thread.currentThread(), value);
1168 }, executor);
1169
1170 var future2 = CompletableFuture.runAsync(() -> {
1171 var value = cache.get("key1", () -> "thread2-value");
1172 threadValues.put(Thread.currentThread(), value);
1173 }, executor);
1174
1175 CompletableFuture.allOf(future1, future2).get(5, TimeUnit.SECONDS);
1176
1177
1178 assertEquals(2, threadValues.size());
1179 assertTrue(threadValues.containsValue("thread1-value"));
1180 assertTrue(threadValues.containsValue("thread2-value"));
1181
1182 executor.shutdown();
1183 }
1184
1185 @Test void a62_threadLocal_weakMode_clear() {
1186 var cache = Cache.of(String.class, Integer.class)
1187 .threadLocal()
1188 .cacheMode(WEAK)
1189 .build();
1190
1191 cache.get("one", () -> 1);
1192 cache.get("two", () -> 2);
1193 assertSize(2, cache);
1194
1195 cache.clear();
1196 assertEmpty(cache);
1197 }
1198
1199 @Test void a63_threadLocal_weakMode_maxSize() {
1200 var cache = Cache.of(String.class, Integer.class)
1201 .threadLocal()
1202 .cacheMode(WEAK)
1203 .maxSize(3)
1204 .build();
1205
1206 cache.get("one", () -> 1);
1207 cache.get("two", () -> 2);
1208 cache.get("three", () -> 3);
1209 assertSize(3, cache);
1210
1211
1212 cache.get("four", () -> 4);
1213 assertSize(4, cache);
1214
1215
1216 cache.get("five", () -> 5);
1217 assertSize(1, cache);
1218 }
1219 }
1220