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 Cache4_Test extends TestBase {
29
30
31
32
33
34 @Test
35 void a01_defaultSupplier_basic() {
36 var callCount = new AtomicInteger();
37 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
38 .supplier((k1, k2, k3, k4) -> {
39 callCount.incrementAndGet();
40 return k1 + ":" + k2 + ":" + k3 + ":" + k4;
41 })
42 .build();
43
44 var result1 = x.get("en", "US", "formal", 1);
45 var result2 = x.get("en", "US", "formal", 1);
46
47 assertEquals("en:US:formal:1", result1);
48 assertEquals("en:US:formal:1", result2);
49 assertEquals(1, callCount.get());
50 assertEquals(1, x.getCacheHits());
51 }
52
53 @Test
54 void a02_overrideSupplier() {
55 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
56 .supplier((k1, k2, k3, k4) -> "DEFAULT")
57 .build();
58
59 var result = x.get("en", "US", "formal", 1, () -> "OVERRIDE");
60
61 assertEquals("OVERRIDE", result);
62 }
63
64 @Test
65 void a03_nullKeys() {
66 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
67 .supplier((k1, k2, k3, k4) -> "value-" + k1 + "-" + k2 + "-" + k3 + "-" + k4)
68 .build();
69
70
71 assertEquals("value-null-US-formal-1", x.get(null, "US", "formal", 1));
72 assertEquals("value-en-null-formal-1", x.get("en", null, "formal", 1));
73 assertEquals("value-en-US-null-1", x.get("en", "US", null, 1));
74 assertEquals("value-en-US-formal-null", x.get("en", "US", "formal", null));
75 assertEquals("value-null-null-null-null", x.get(null, null, null, null));
76
77
78 assertEquals("value-null-US-formal-1", x.get(null, "US", "formal", 1));
79 assertEquals("value-en-null-formal-1", x.get("en", null, "formal", 1));
80 }
81
82 @Test
83 void a04_disableCaching() {
84 var callCount = new AtomicInteger();
85 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
86 .cacheMode(NONE)
87 .supplier((k1, k2, k3, k4) -> {
88 callCount.incrementAndGet();
89 return "value";
90 })
91 .build();
92
93 x.get("en", "US", "formal", 1);
94 x.get("en", "US", "formal", 1);
95
96 assertEquals(2, callCount.get());
97 assertEmpty(x);
98 }
99
100 @Test
101 void a04b_weakMode_basicCaching() {
102 var callCount = new AtomicInteger();
103 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
104 .cacheMode(WEAK)
105 .supplier((k1, k2, k3, k4) -> {
106 callCount.incrementAndGet();
107 return k1 + ":" + k2 + ":" + k3 + ":" + k4;
108 })
109 .build();
110
111
112 var result1 = x.get("en", "US", "formal", 1);
113
114
115 var result2 = x.get("en", "US", "formal", 1);
116
117 assertEquals("en:US:formal:1", result1);
118 assertEquals("en:US:formal:1", result2);
119 assertSame(result1, result2);
120 assertEquals(1, callCount.get());
121 assertSize(1, x);
122 assertEquals(1, x.getCacheHits());
123 }
124
125 @Test
126 void a04c_weakMode_multipleKeys() {
127 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
128 .cacheMode(WEAK)
129 .supplier((k1, k2, k3, k4) -> k1 + ":" + k2 + ":" + k3 + ":" + k4)
130 .build();
131
132 x.get("en", "US", "formal", 1);
133 x.get("fr", "FR", "formal", 2);
134 x.get("de", "DE", "formal", 3);
135
136 assertSize(3, x);
137 assertEquals(0, x.getCacheHits());
138
139
140 assertEquals("en:US:formal:1", x.get("en", "US", "formal", 1));
141 assertEquals("fr:FR:formal:2", x.get("fr", "FR", "formal", 2));
142 assertEquals("de:DE:formal:3", x.get("de", "DE", "formal", 3));
143 assertEquals(3, x.getCacheHits());
144 }
145
146 @Test
147 void a04d_weakMode_clear() {
148 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
149 .cacheMode(WEAK)
150 .supplier((k1, k2, k3, k4) -> "value")
151 .build();
152
153 x.get("en", "US", "formal", 1);
154 x.get("fr", "FR", "formal", 2);
155 assertSize(2, x);
156
157 x.clear();
158 assertEmpty(x);
159 }
160
161 @Test
162 void a04e_weakMode_maxSize() {
163 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
164 .cacheMode(WEAK)
165 .maxSize(2)
166 .supplier((k1, k2, k3, k4) -> "value")
167 .build();
168
169 x.get("en", "US", "formal", 1);
170 x.get("fr", "FR", "formal", 2);
171 assertSize(2, x);
172
173
174 x.get("de", "DE", "formal", 3);
175 assertSize(3, x);
176
177
178 x.get("es", "ES", "formal", 4);
179 assertSize(1, x);
180 }
181
182 @Test
183 void a04f_weakMethod_basicCaching() {
184
185 var callCount = new AtomicInteger();
186 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
187 .weak()
188 .supplier((k1, k2, k3, k4) -> {
189 callCount.incrementAndGet();
190 return k1 + ":" + k2 + ":" + k3 + ":" + k4;
191 })
192 .build();
193
194
195 var result1 = x.get("en", "US", "formal", 1);
196
197
198 var result2 = x.get("en", "US", "formal", 1);
199
200 assertEquals("en:US:formal:1", result1);
201 assertEquals("en:US:formal:1", result2);
202 assertSame(result1, result2);
203 assertEquals(1, callCount.get());
204 assertSize(1, x);
205 assertEquals(1, x.getCacheHits());
206 }
207
208 @Test
209 void a04g_weakMethod_chaining() {
210
211 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
212 .weak()
213 .maxSize(100)
214 .supplier((k1, k2, k3, k4) -> k1 + ":" + k2 + ":" + k3 + ":" + k4)
215 .build();
216
217 var result = x.get("en", "US", "formal", 1);
218 assertEquals("en:US:formal:1", result);
219 assertSize(1, x);
220 }
221
222 @Test
223 void a05_maxSize() {
224 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
225 .maxSize(2)
226 .supplier((k1, k2, k3, k4) -> "value")
227 .build();
228
229 x.get("en", "US", "formal", 1);
230 x.get("fr", "FR", "formal", 2);
231 assertSize(2, x);
232
233 x.get("de", "DE", "formal", 3);
234 assertSize(3, x);
235
236 x.get("es", "ES", "formal", 4);
237 assertSize(1, x);
238 }
239
240 @Test
241 void a06_cacheHitsTracking() {
242 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
243 .supplier((k1, k2, k3, k4) -> "value")
244 .build();
245
246 x.get("en", "US", "formal", 1);
247 assertEquals(0, x.getCacheHits());
248
249 x.get("en", "US", "formal", 1);
250 x.get("en", "US", "formal", 1);
251 assertEquals(2, x.getCacheHits());
252 }
253
254
255
256
257
258 @Test
259 void b01_put() {
260 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class).build();
261 var previous = x.put("en", "US", "formal", 1, "value");
262 assertNull(previous);
263 assertEquals("value", x.get("en", "US", "formal", 1, () -> "should not be called"));
264 }
265
266 @Test
267 void b01b_put_withNullValue() {
268 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class).build();
269 x.put("en", "US", "formal", 1, "value1");
270 var previous = x.put("en", "US", "formal", 1, null);
271 assertEquals("value1", previous);
272 assertFalse(x.containsKey("en", "US", "formal", 1));
273 }
274
275 @Test
276 void b02_isEmpty() {
277 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class).build();
278 assertTrue(x.isEmpty());
279 x.put("en", "US", "formal", 1, "value");
280 assertFalse(x.isEmpty());
281 x.clear();
282 assertTrue(x.isEmpty());
283 }
284
285 @Test
286 void b03_containsKey() {
287 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class).build();
288 assertFalse(x.containsKey("en", "US", "formal", 1));
289 x.put("en", "US", "formal", 1, "value");
290 assertTrue(x.containsKey("en", "US", "formal", 1));
291 }
292
293
294
295
296
297 @Test
298 void c01_create() {
299 var x = Cache4.<String, String, String, Integer, String>create()
300 .supplier((k1, k2, k3, k4) -> k1 + ":" + k2 + ":" + k3 + ":" + k4)
301 .build();
302 var result = x.get("en", "US", "formal", 1);
303 assertEquals("en:US:formal:1", result);
304 }
305
306 @Test
307 void c02_disableCaching() {
308 var callCount = new AtomicInteger();
309 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
310 .cacheMode(NONE)
311 .supplier((k1, k2, k3, k4) -> {
312 callCount.incrementAndGet();
313 return "value";
314 })
315 .build();
316 x.get("en", "US", "formal", 1);
317 x.get("en", "US", "formal", 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 = Cache4.of(String.class, String.class, String.class, Integer.class, String.class).build();
326 x.get("en", "US", "formal", 1, () -> {
327 callCount.incrementAndGet();
328 return null;
329 });
330 x.get("en", "US", "formal", 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 = Cache4.of(String.class, String.class, String.class, Integer.class, String.class).build();
345 x.put("en", "US", "formal", 1, "value1");
346 var removed = x.remove("en", "US", "formal", 1);
347 assertEquals("value1", removed);
348 assertFalse(x.containsKey("en", "US", "formal", 1));
349 }
350
351 @Test
352 void d02_containsValue() {
353 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class).build();
354 x.put("en", "US", "formal", 1, "value1");
355 assertTrue(x.containsValue("value1"));
356 assertFalse(x.containsValue("value2"));
357 }
358
359 @Test
360 void d03_containsValue_nullValue() {
361 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class).build();
362
363 x.get("en", "US", "formal", 1, () -> null);
364 assertFalse(x.containsValue(null));
365
366 var x2 = Cache4.of(String.class, 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 = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
377 .logOnExit("TestCache4")
378 .supplier((k1, k2, k3, k4) -> k1 + ":" + k2 + ":" + k3 + ":" + k4)
379 .build();
380 x.get("en", "US", "formal", 1);
381 assertSize(1, x);
382 }
383
384 @Test
385 void e02_logOnExit_withBoolean() {
386 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
387 .logOnExit(true, "MyCache4")
388 .supplier((k1, k2, k3, k4) -> k1 + ":" + k2 + ":" + k3 + ":" + k4)
389 .build();
390 x.get("en", "US", "formal", 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 = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
402 .threadLocal()
403 .supplier((k1, k2, k3, k4) -> {
404 callCount.incrementAndGet();
405 return k1 + ":" + k2 + ":" + k3 + ":" + k4;
406 })
407 .build();
408
409
410 var result1 = x.get("en", "US", "formal", 1);
411
412
413 var result2 = x.get("en", "US", "formal", 1);
414
415 assertEquals("en:US:formal:1", result1);
416 assertEquals("en:US:formal: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 = Cache4.of(String.class, 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", "formal", 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", "formal", 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 = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
455 .threadLocal()
456 .supplier((k1, k2, k3, k4) -> k1 + ":" + k2 + ":" + k3 + ":" + k4)
457 .build();
458
459 x.get("en", "US", "formal", 1);
460 x.get("fr", "FR", "informal", 2);
461 x.get("de", "DE", "formal", 3);
462
463 assertSize(3, x);
464 assertEquals(0, x.getCacheHits());
465
466
467 assertEquals("en:US:formal:1", x.get("en", "US", "formal", 1));
468 assertEquals("fr:FR:informal:2", x.get("fr", "FR", "informal", 2));
469 assertEquals("de:DE:formal:3", x.get("de", "DE", "formal", 3));
470 assertEquals(3, x.getCacheHits());
471 }
472
473 @Test
474 void f04_threadLocal_clear() {
475 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
476 .threadLocal()
477 .supplier((k1, k2, k3, k4) -> "value")
478 .build();
479
480 x.get("en", "US", "formal", 1);
481 x.get("fr", "FR", "informal", 2);
482 assertSize(2, x);
483
484 x.clear();
485 assertEmpty(x);
486 }
487
488 @Test
489 void f05_threadLocal_maxSize() {
490 var x = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
491 .threadLocal()
492 .maxSize(2)
493 .supplier((k1, k2, k3, k4) -> "value")
494 .build();
495
496 x.get("en", "US", "formal", 1);
497 x.get("fr", "FR", "informal", 2);
498 assertSize(2, x);
499
500
501 x.get("de", "DE", "formal", 3);
502 assertSize(3, x);
503
504
505 x.get("es", "ES", "informal", 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 = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
517 .threadLocal()
518 .cacheMode(WEAK)
519 .supplier((k1, k2, k3, k4) -> {
520 callCount.incrementAndGet();
521 return k1 + ":" + k2 + ":" + k3 + ":" + k4;
522 })
523 .build();
524
525
526 var result1 = x.get("en", "US", "formal", 1);
527
528
529 var result2 = x.get("en", "US", "formal", 1);
530
531 assertEquals("en:US:formal:1", result1);
532 assertEquals("en:US:formal: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 = Cache4.of(String.class, 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", "formal", 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", "formal", 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 = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
572 .threadLocal()
573 .cacheMode(WEAK)
574 .supplier((k1, k2, k3, k4) -> "value")
575 .build();
576
577 x.get("en", "US", "formal", 1);
578 x.get("fr", "FR", "informal", 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 = Cache4.of(String.class, String.class, String.class, Integer.class, String.class)
588 .threadLocal()
589 .cacheMode(WEAK)
590 .maxSize(2)
591 .supplier((k1, k2, k3, k4) -> "value")
592 .build();
593
594 x.get("en", "US", "formal", 1);
595 x.get("fr", "FR", "informal", 2);
596 assertSize(2, x);
597
598
599 x.get("de", "DE", "formal", 3);
600 assertSize(3, x);
601
602
603 x.get("es", "ES", "informal", 4);
604 assertSize(1, x);
605 }
606 }
607