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