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.junit.jupiter.api.Assertions.*;
20 import static org.apache.juneau.commons.collections.CacheMode.*;
21 import static org.apache.juneau.junit.bct.BctAssertions.*;
22
23 import java.util.concurrent.atomic.*;
24
25 import org.apache.juneau.*;
26 import org.junit.jupiter.api.*;
27
28 class Cache3_Test extends TestBase {
29
30
31
32
33
34 @Test
35 void a01_defaultSupplier_basic() {
36 var callCount = new AtomicInteger();
37 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
38 .supplier((k1, k2, k3) -> {
39 callCount.incrementAndGet();
40 return k1 + ":" + k2 + ":" + k3;
41 })
42 .build();
43
44 var result1 = x.get("en", "US", 1);
45 var result2 = x.get("en", "US", 1);
46
47 assertEquals("en:US:1", result1);
48 assertEquals("en:US:1", result2);
49 assertEquals(1, callCount.get());
50 assertEquals(1, x.getCacheHits());
51 }
52
53 @Test
54 void a02_overrideSupplier() {
55 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
56 .supplier((k1, k2, k3) -> "DEFAULT")
57 .build();
58
59 var result = x.get("en", "US", 1, () -> "OVERRIDE");
60
61 assertEquals("OVERRIDE", result);
62 }
63
64 @Test
65 void a03_nullKeys() {
66 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
67 .supplier((k1, k2, k3) -> "value-" + k1 + "-" + k2 + "-" + k3)
68 .build();
69
70
71 assertEquals("value-null-US-1", x.get(null, "US", 1));
72 assertEquals("value-en-null-1", x.get("en", null, 1));
73 assertEquals("value-en-US-null", x.get("en", "US", null));
74 assertEquals("value-null-null-null", x.get(null, null, null));
75
76
77 assertEquals("value-null-US-1", x.get(null, "US", 1));
78 assertEquals("value-en-null-1", x.get("en", null, 1));
79 }
80
81 @Test
82 void a04_disableCaching() {
83 var callCount = new AtomicInteger();
84 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
85 .cacheMode(NONE)
86 .supplier((k1, k2, k3) -> {
87 callCount.incrementAndGet();
88 return "value";
89 })
90 .build();
91
92 x.get("en", "US", 1);
93 x.get("en", "US", 1);
94
95 assertEquals(2, callCount.get());
96 assertEmpty(x);
97 }
98
99 @Test
100 void a04b_weakMode_basicCaching() {
101 var callCount = new AtomicInteger();
102 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
103 .cacheMode(WEAK)
104 .supplier((k1, k2, k3) -> {
105 callCount.incrementAndGet();
106 return k1 + ":" + k2 + ":" + k3;
107 })
108 .build();
109
110
111 var result1 = x.get("en", "US", 1);
112
113
114 var result2 = x.get("en", "US", 1);
115
116 assertEquals("en:US:1", result1);
117 assertEquals("en:US:1", result2);
118 assertSame(result1, result2);
119 assertEquals(1, callCount.get());
120 assertSize(1, x);
121 assertEquals(1, x.getCacheHits());
122 }
123
124 @Test
125 void a04c_weakMode_multipleKeys() {
126 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
127 .cacheMode(WEAK)
128 .supplier((k1, k2, k3) -> k1 + ":" + k2 + ":" + k3)
129 .build();
130
131 x.get("en", "US", 1);
132 x.get("fr", "FR", 2);
133 x.get("de", "DE", 3);
134
135 assertSize(3, x);
136 assertEquals(0, x.getCacheHits());
137
138
139 assertEquals("en:US:1", x.get("en", "US", 1));
140 assertEquals("fr:FR:2", x.get("fr", "FR", 2));
141 assertEquals("de:DE:3", x.get("de", "DE", 3));
142 assertEquals(3, x.getCacheHits());
143 }
144
145 @Test
146 void a04d_weakMode_clear() {
147 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
148 .cacheMode(WEAK)
149 .supplier((k1, k2, k3) -> "value")
150 .build();
151
152 x.get("en", "US", 1);
153 x.get("fr", "FR", 2);
154 assertSize(2, x);
155
156 x.clear();
157 assertEmpty(x);
158 }
159
160 @Test
161 void a04e_weakMode_maxSize() {
162 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
163 .cacheMode(WEAK)
164 .maxSize(2)
165 .supplier((k1, k2, k3) -> "value")
166 .build();
167
168 x.get("en", "US", 1);
169 x.get("fr", "FR", 2);
170 assertSize(2, x);
171
172
173 x.get("de", "DE", 3);
174 assertSize(3, x);
175
176
177 x.get("es", "ES", 4);
178 assertSize(1, x);
179 }
180
181 @Test
182 void a04f_weakMethod_basicCaching() {
183
184 var callCount = new AtomicInteger();
185 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
186 .weak()
187 .supplier((k1, k2, k3) -> {
188 callCount.incrementAndGet();
189 return k1 + ":" + k2 + ":" + k3;
190 })
191 .build();
192
193
194 var result1 = x.get("en", "US", 1);
195
196
197 var result2 = x.get("en", "US", 1);
198
199 assertEquals("en:US:1", result1);
200 assertEquals("en:US:1", result2);
201 assertSame(result1, result2);
202 assertEquals(1, callCount.get());
203 assertSize(1, x);
204 assertEquals(1, x.getCacheHits());
205 }
206
207 @Test
208 void a04g_weakMethod_chaining() {
209
210 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
211 .weak()
212 .maxSize(100)
213 .supplier((k1, k2, k3) -> k1 + ":" + k2 + ":" + k3)
214 .build();
215
216 var result = x.get("en", "US", 1);
217 assertEquals("en:US:1", result);
218 assertSize(1, x);
219 }
220
221 @Test
222 void a05_maxSize() {
223 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
224 .maxSize(2)
225 .supplier((k1, k2, k3) -> "value")
226 .build();
227
228 x.get("en", "US", 1);
229 x.get("fr", "FR", 2);
230 assertSize(2, x);
231
232 x.get("de", "DE", 3);
233 assertSize(3, x);
234
235 x.get("es", "ES", 4);
236 assertSize(1, x);
237 }
238
239 @Test
240 void a06_cacheHitsTracking() {
241 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
242 .supplier((k1, k2, k3) -> "value")
243 .build();
244
245 x.get("en", "US", 1);
246 assertEquals(0, x.getCacheHits());
247
248 x.get("en", "US", 1);
249 x.get("en", "US", 1);
250 assertEquals(2, x.getCacheHits());
251 }
252
253
254
255
256
257 @Test
258 void b01_put() {
259 var x = Cache3.of(String.class, String.class, Integer.class, String.class).build();
260 var previous = x.put("en", "US", 1, "value");
261 assertNull(previous);
262 assertEquals("value", x.get("en", "US", 1, () -> "should not be called"));
263 }
264
265 @Test
266 void b01b_put_withNullValue() {
267 var x = Cache3.of(String.class, String.class, Integer.class, String.class).build();
268 x.put("en", "US", 1, "value1");
269 var previous = x.put("en", "US", 1, null);
270 assertEquals("value1", previous);
271 assertFalse(x.containsKey("en", "US", 1));
272 }
273
274 @Test
275 void b02_isEmpty() {
276 var x = Cache3.of(String.class, String.class, Integer.class, String.class).build();
277 assertTrue(x.isEmpty());
278 x.put("en", "US", 1, "value");
279 assertFalse(x.isEmpty());
280 x.clear();
281 assertTrue(x.isEmpty());
282 }
283
284 @Test
285 void b03_containsKey() {
286 var x = Cache3.of(String.class, String.class, Integer.class, String.class).build();
287 assertFalse(x.containsKey("en", "US", 1));
288 x.put("en", "US", 1, "value");
289 assertTrue(x.containsKey("en", "US", 1));
290 assertFalse(x.containsKey("fr", "FR", 2));
291 }
292
293
294
295
296
297 @Test
298 void c01_create() {
299 var x = Cache3.<String, String, Integer, String>create()
300 .supplier((k1, k2, k3) -> k1 + ":" + k2 + ":" + k3)
301 .build();
302 var result = x.get("en", "US", 1);
303 assertEquals("en:US:1", result);
304 }
305
306 @Test
307 void c02_disableCaching() {
308 var callCount = new AtomicInteger();
309 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
310 .cacheMode(NONE)
311 .supplier((k1, k2, k3) -> {
312 callCount.incrementAndGet();
313 return "value";
314 })
315 .build();
316 x.get("en", "US", 1);
317 x.get("en", "US", 1);
318 assertEquals(2, callCount.get());
319 assertTrue(x.isEmpty());
320 }
321
322 @Test
323 void c03_nullValue_notCached() {
324 var callCount = new AtomicInteger();
325 var x = Cache3.of(String.class, String.class, Integer.class, String.class).build();
326 x.get("en", "US", 1, () -> {
327 callCount.incrementAndGet();
328 return null;
329 });
330 x.get("en", "US", 1, () -> {
331 callCount.incrementAndGet();
332 return null;
333 });
334 assertEquals(2, callCount.get());
335 assertTrue(x.isEmpty());
336 }
337
338
339
340
341
342 @Test
343 void d01_remove() {
344 var x = Cache3.of(String.class, String.class, Integer.class, String.class).build();
345 x.put("en", "US", 1, "value1");
346 var removed = x.remove("en", "US", 1);
347 assertEquals("value1", removed);
348 assertFalse(x.containsKey("en", "US", 1));
349 }
350
351 @Test
352 void d02_containsValue() {
353 var x = Cache3.of(String.class, String.class, Integer.class, String.class).build();
354 x.put("en", "US", 1, "value1");
355 assertTrue(x.containsValue("value1"));
356 assertFalse(x.containsValue("value2"));
357 }
358
359 @Test
360 void d03_containsValue_nullValue() {
361 var x = Cache3.of(String.class, String.class, Integer.class, String.class).build();
362
363 x.get("en", "US", 1, () -> null);
364 assertFalse(x.containsValue(null));
365
366 var x2 = Cache3.of(String.class, String.class, Integer.class, String.class).build();
367 assertFalse(x2.containsValue(null));
368 }
369
370
371
372
373
374 @Test
375 void e01_logOnExit_withStringId() {
376 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
377 .logOnExit("TestCache3")
378 .supplier((k1, k2, k3) -> k1 + ":" + k2 + ":" + k3)
379 .build();
380 x.get("en", "US", 1);
381 assertSize(1, x);
382 }
383
384 @Test
385 void e02_logOnExit_withBoolean() {
386 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
387 .logOnExit(true, "MyCache3")
388 .supplier((k1, k2, k3) -> k1 + ":" + k2 + ":" + k3)
389 .build();
390 x.get("en", "US", 1);
391 assertSize(1, x);
392 }
393
394
395
396
397
398 @Test
399 void f01_threadLocal_basicCaching() {
400 var callCount = new AtomicInteger();
401 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
402 .threadLocal()
403 .supplier((k1, k2, k3) -> {
404 callCount.incrementAndGet();
405 return k1 + ":" + k2 + ":" + k3;
406 })
407 .build();
408
409
410 var result1 = x.get("en", "US", 1);
411
412
413 var result2 = x.get("en", "US", 1);
414
415 assertEquals("en:US:1", result1);
416 assertEquals("en:US:1", result2);
417 assertSame(result1, result2);
418 assertEquals(1, callCount.get());
419 assertSize(1, x);
420 assertEquals(1, x.getCacheHits());
421 }
422
423 @Test
424 void f02_threadLocal_eachThreadHasOwnCache() throws Exception {
425 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
426 .threadLocal()
427 .build();
428 var executor = java.util.concurrent.Executors.newFixedThreadPool(2);
429 var threadValues = new java.util.concurrent.ConcurrentHashMap<Thread, String>();
430
431
432 var future1 = java.util.concurrent.CompletableFuture.runAsync(() -> {
433 var value = x.get("en", "US", 1, () -> "thread1-value");
434 threadValues.put(Thread.currentThread(), value);
435 }, executor);
436
437 var future2 = java.util.concurrent.CompletableFuture.runAsync(() -> {
438 var value = x.get("en", "US", 1, () -> "thread2-value");
439 threadValues.put(Thread.currentThread(), value);
440 }, executor);
441
442 java.util.concurrent.CompletableFuture.allOf(future1, future2).get(5, java.util.concurrent.TimeUnit.SECONDS);
443
444
445 assertEquals(2, threadValues.size());
446 assertTrue(threadValues.containsValue("thread1-value"));
447 assertTrue(threadValues.containsValue("thread2-value"));
448
449 executor.shutdown();
450 }
451
452 @Test
453 void f03_threadLocal_multipleKeys() {
454 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
455 .threadLocal()
456 .supplier((k1, k2, k3) -> k1 + ":" + k2 + ":" + k3)
457 .build();
458
459 x.get("en", "US", 1);
460 x.get("fr", "FR", 2);
461 x.get("de", "DE", 3);
462
463 assertSize(3, x);
464 assertEquals(0, x.getCacheHits());
465
466
467 assertEquals("en:US:1", x.get("en", "US", 1));
468 assertEquals("fr:FR:2", x.get("fr", "FR", 2));
469 assertEquals("de:DE:3", x.get("de", "DE", 3));
470 assertEquals(3, x.getCacheHits());
471 }
472
473 @Test
474 void f04_threadLocal_clear() {
475 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
476 .threadLocal()
477 .supplier((k1, k2, k3) -> "value")
478 .build();
479
480 x.get("en", "US", 1);
481 x.get("fr", "FR", 2);
482 assertSize(2, x);
483
484 x.clear();
485 assertEmpty(x);
486 }
487
488 @Test
489 void f05_threadLocal_maxSize() {
490 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
491 .threadLocal()
492 .maxSize(2)
493 .supplier((k1, k2, k3) -> "value")
494 .build();
495
496 x.get("en", "US", 1);
497 x.get("fr", "FR", 2);
498 assertSize(2, x);
499
500
501 x.get("de", "DE", 3);
502 assertSize(3, x);
503
504
505 x.get("es", "ES", 4);
506 assertSize(1, x);
507 }
508
509
510
511
512
513 @Test
514 void g01_threadLocal_weakMode_basicCaching() {
515 var callCount = new AtomicInteger();
516 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
517 .threadLocal()
518 .cacheMode(WEAK)
519 .supplier((k1, k2, k3) -> {
520 callCount.incrementAndGet();
521 return k1 + ":" + k2 + ":" + k3;
522 })
523 .build();
524
525
526 var result1 = x.get("en", "US", 1);
527
528
529 var result2 = x.get("en", "US", 1);
530
531 assertEquals("en:US:1", result1);
532 assertEquals("en:US:1", result2);
533 assertSame(result1, result2);
534 assertEquals(1, callCount.get());
535 assertSize(1, x);
536 assertEquals(1, x.getCacheHits());
537 }
538
539 @Test
540 void g02_threadLocal_weakMode_eachThreadHasOwnCache() throws Exception {
541 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
542 .threadLocal()
543 .cacheMode(WEAK)
544 .build();
545 var executor = java.util.concurrent.Executors.newFixedThreadPool(2);
546 var threadValues = new java.util.concurrent.ConcurrentHashMap<Thread, String>();
547
548
549 var future1 = java.util.concurrent.CompletableFuture.runAsync(() -> {
550 var value = x.get("en", "US", 1, () -> "thread1-value");
551 threadValues.put(Thread.currentThread(), value);
552 }, executor);
553
554 var future2 = java.util.concurrent.CompletableFuture.runAsync(() -> {
555 var value = x.get("en", "US", 1, () -> "thread2-value");
556 threadValues.put(Thread.currentThread(), value);
557 }, executor);
558
559 java.util.concurrent.CompletableFuture.allOf(future1, future2).get(5, java.util.concurrent.TimeUnit.SECONDS);
560
561
562 assertEquals(2, threadValues.size());
563 assertTrue(threadValues.containsValue("thread1-value"));
564 assertTrue(threadValues.containsValue("thread2-value"));
565
566 executor.shutdown();
567 }
568
569 @Test
570 void g03_threadLocal_weakMode_clear() {
571 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
572 .threadLocal()
573 .cacheMode(WEAK)
574 .supplier((k1, k2, k3) -> "value")
575 .build();
576
577 x.get("en", "US", 1);
578 x.get("fr", "FR", 2);
579 assertSize(2, x);
580
581 x.clear();
582 assertEmpty(x);
583 }
584
585 @Test
586 void g04_threadLocal_weakMode_maxSize() {
587 var x = Cache3.of(String.class, String.class, Integer.class, String.class)
588 .threadLocal()
589 .cacheMode(WEAK)
590 .maxSize(2)
591 .supplier((k1, k2, k3) -> "value")
592 .build();
593
594 x.get("en", "US", 1);
595 x.get("fr", "FR", 2);
596 assertSize(2, x);
597
598
599 x.get("de", "DE", 3);
600 assertSize(3, x);
601
602
603 x.get("es", "ES", 4);
604 assertSize(1, x);
605 }
606 }
607