001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.commons.collections; 018 019import static java.util.Collections.*; 020import static org.apache.juneau.commons.collections.CacheMode.*; 021import static org.apache.juneau.commons.utils.Shorts.*; 022import static org.apache.juneau.commons.utils.SystemUtils.*; 023 024import java.util.*; 025import java.util.concurrent.*; 026import java.util.concurrent.atomic.*; 027import java.util.function.*; 028 029import org.apache.juneau.commons.function.*; 030 031/** 032 * Simple in-memory cache for storing and retrieving objects by key. 033 * 034 * <h5 class='section'>Overview:</h5> 035 * <p> 036 * This class uses {@link ConcurrentHashMap} internally to provide a thread-safe caching layer with automatic 037 * value computation, cache eviction, and statistics tracking. It's designed for caching expensive-to-compute 038 * or frequently-accessed objects to improve performance. 039 * 040 * <h5 class='section'>Features:</h5> 041 * <ul class='spaced-list'> 042 * <li>Thread-safe concurrent access without external synchronization 043 * <li>Automatic cache eviction when maximum size is reached 044 * <li>Lazy computation via {@link Supplier} pattern 045 * <li>Default supplier support for simplified access 046 * <li>Built-in hit/miss statistics tracking 047 * <li>Optional logging of cache statistics on JVM shutdown 048 * <li>Can be disabled entirely via builder or system property 049 * <li><b>Array Support:</b> Arrays can be used as keys with proper content-based hashing and equality 050 * </ul> 051 * 052 * <h5 class='section'>Usage:</h5> 053 * <p class='bjava'> 054 * <jc>// Create a cache with default supplier</jc> 055 * Cache<String,Pattern> <jv>patternCache</jv> = Cache 056 * .<jsm>of</jsm>(String.<jk>class</jk>, Pattern.<jk>class</jk>) 057 * .maxSize(100) 058 * .supplier(Pattern::compile) 059 * .build(); 060 * 061 * <jc>// Retrieve using default supplier</jc> 062 * Pattern <jv>pattern1</jv> = <jv>patternCache</jv>.get(<js>"[a-z]+"</js>); 063 * 064 * <jc>// Or override the supplier</jc> 065 * Pattern <jv>pattern2</jv> = <jv>patternCache</jv>.get(<js>"[0-9]+"</js>, () -> Pattern.compile(<js>"[0-9]+"</js>, Pattern.CASE_INSENSITIVE)); 066 * </p> 067 * 068 * <h5 class='section'>Array Support:</h5> 069 * <p> 070 * Unlike standard {@link HashMap} which uses identity-based equality for array keys, 071 * this class properly handles arrays using content-based comparison: 072 * 073 * <p class='bjava'> 074 * <jc>// Arrays work correctly as keys</jc> 075 * Cache<String[],Result> <jv>cache</jv> = Cache.<jsm>of</jsm>(String[].<jk>class</jk>, Result.<jk>class</jk>).build(); 076 * <jv>cache</jv>.get(<jk>new</jk> String[]{<js>"a"</js>, <js>"b"</js>}, () -> computeResult()); 077 * Result <jv>r</jv> = <jv>cache</jv>.get(<jk>new</jk> String[]{<js>"a"</js>, <js>"b"</js>}, () -> computeResult()); <jc>// Cache hit!</jc> 078 * </p> 079 * 080 * <h5 class='section'>Cache Behavior:</h5> 081 * <ul class='spaced-list'> 082 * <li>When a key is requested: 083 * <ul> 084 * <li>If the key exists in the cache, the cached value is returned (cache hit) 085 * <li>If the key doesn't exist, the supplier is invoked to compute the value 086 * <li>The computed value is stored in the cache and returned (cache miss) 087 * </ul> 088 * <li>When the cache exceeds {@link Builder#maxSize(int)}, the entire cache is cleared 089 * <li>If the cache is disabled, the supplier is always invoked without caching 090 * <li>Null keys always bypass the cache and invoke the supplier 091 * </ul> 092 * 093 * <h5 class='section'>Environment Variables:</h5> 094 * <p> 095 * The following system properties can be used to configure default cache behavior: 096 * <ul class='spaced-list'> 097 * <li><c>juneau.cache.mode</c> - Cache mode: NONE/WEAK/FULL (default: FULL, case-insensitive) 098 * <li><c>juneau.cache.maxSize</c> - Maximum cache size before eviction (default: 1000) 099 * <li><c>juneau.cache.logOnExit</c> - Log cache statistics on shutdown (default: <jk>false</jk>) 100 * </ul> 101 * 102 * <h5 class='section'>Thread Safety:</h5> 103 * <p> 104 * This class is thread-safe and can be safely used from multiple threads without external synchronization. 105 * However, note that when the cache is cleared due to exceeding max size, there's a small window where 106 * multiple threads might compute the same value. This is acceptable for most use cases as it only affects 107 * performance, not correctness. 108 * 109 * <h5 class='section'>Performance Considerations:</h5> 110 * <ul class='spaced-list'> 111 * <li>Cache operations are O(1) average time complexity 112 * <li>The {@link #get(Object, Supplier)} method uses {@link ConcurrentHashMap#putIfAbsent(Object, Object)} 113 * to minimize redundant computation in concurrent scenarios 114 * <li>When max size is exceeded, the entire cache is cleared in a single operation 115 * <li>Statistics tracking uses {@link LongAdder} for thread-safe counting without locking 116 * <li>For arrays, content-based hashing via {@link Arrays#hashCode(Object[])} ensures proper cache hits 117 * </ul> 118 * 119 * <h5 class='section'>Examples:</h5> 120 * <p class='bjava'> 121 * <jc>// Simple cache with defaults using of()</jc> 122 * Cache<String,Integer> <jv>cache</jv> = Cache.<jsm>of</jsm>(String.<jk>class</jk>, Integer.<jk>class</jk>).build(); 123 * 124 * <jc>// Cache with custom configuration using of()</jc> 125 * Cache<Class<?>,ClassMeta> <jv>classMetaCache</jv> = Cache 126 * .<jsm>of</jsm>(Class.<jk>class</jk>, ClassMeta.<jk>class</jk>) 127 * .maxSize(500) 128 * .logOnExit(<js>"ClassMeta"</js>) 129 * .build(); 130 * 131 * <jc>// Complex generics using create()</jc> 132 * Cache<Class<?>,List<AnnotationInfo<Annotation>>> <jv>annotationsCache</jv> = 133 * Cache.<Class<?>,List<AnnotationInfo<Annotation>>><jsm>create</jsm>() 134 * .supplier(<jk>this</jk>::findClassAnnotations) 135 * .build(); 136 * 137 * <jc>// Disabled cache for testing</jc> 138 * Cache<String,Object> <jv>disabledCache</jv> = Cache 139 * .<jsm>of</jsm>(String.<jk>class</jk>, Object.<jk>class</jk>) 140 * .disableCaching() 141 * .build(); 142 * </p> 143 * 144 * <h5 class='section'>See Also:</h5> 145 * <ul> 146 * <li class='link'><a class="doclink" href="../../../../../index.html#juneau-commons">Overview > juneau-commons</a> 147 * </ul> 148 * 149 * @param <K> The key type. Can be an array type for content-based key matching. 150 * @param <V> The value type. 151 */ 152@SuppressWarnings({ 153 "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants 154 "java:S5164" // Cleanup method provided: cleanup() 155}) 156public class Cache<K,V> { 157 158 /** 159 * Builder for creating configured {@link Cache} instances. 160 * 161 * <h5 class='section'>Example:</h5> 162 * <p class='bjava'> 163 * <jc>// Using of() for simple types</jc> 164 * Cache<String,Pattern> <jv>cache1</jv> = Cache 165 * .<jsm>of</jsm>(String.<jk>class</jk>, Pattern.<jk>class</jk>) 166 * .maxSize(200) 167 * .logOnExit(<js>"Pattern"</js>) 168 * .build(); 169 * 170 * <jc>// Using create() for complex generics</jc> 171 * Cache<Class<?>,List<Method>> <jv>cache2</jv> = 172 * Cache.<Class<?>,List<Method>><jsm>create</jsm>() 173 * .supplier(<jk>this</jk>::findMethods) 174 * .build(); 175 * </p> 176 * 177 * <h5 class='section'>See Also:</h5> 178 * <ul> 179 * <li class='jm'>{@link Cache#of(Class, Class)} 180 * <li class='jm'>{@link Cache#create()} 181 * </ul> 182 * 183 * @param <K> The key type. 184 * @param <V> The value type. 185 */ 186 public static class Builder<K,V> { 187 188 // Resolved once per JVM. Looking these up per builder is expensive because Settings iterates every 189 // registered PropertySource (including the manifest scanner), and Cache builders are created very frequently. 190 private static final CacheMode DEFAULT_CACHE_MODE = env("juneau.cache.mode", CacheMode.FULL); 191 private static final int DEFAULT_MAX_SIZE = env("juneau.cache.maxSize", 1000); 192 private static final boolean DEFAULT_LOG_ON_EXIT = env("juneau.cache.logOnExit", false); 193 194 CacheMode cacheMode; 195 int maxSize; 196 String id; 197 boolean logOnExit; 198 boolean threadLocal; 199 Function<K,V> supplier; 200 201 Builder() { 202 cacheMode = DEFAULT_CACHE_MODE; 203 maxSize = DEFAULT_MAX_SIZE; 204 logOnExit = DEFAULT_LOG_ON_EXIT; 205 id = "Cache"; 206 } 207 208 /** 209 * Builds a new {@link Cache} instance with the configured settings. 210 * 211 * @return A new immutable {@link Cache} instance. 212 */ 213 public Cache<K,V> build() { 214 return new Cache<>(this); 215 } 216 217 /** 218 * Sets the caching mode for this cache. 219 * 220 * <p> 221 * Available modes: 222 * <ul> 223 * <li>{@link CacheMode#NONE NONE} - No caching (always invoke supplier) 224 * <li>{@link CacheMode#WEAK WEAK} - Weak caching (uses {@link WeakHashMap}) 225 * <li>{@link CacheMode#FULL FULL} - Full caching (uses {@link ConcurrentHashMap}, default) 226 * </ul> 227 * 228 * <h5 class='section'>Example:</h5> 229 * <p class='bjava'> 230 * <jc>// No caching for testing</jc> 231 * Cache<String,Object> <jv>cache1</jv> = Cache.<jsm>of</jsm>(String.<jk>class</jk>, Object.<jk>class</jk>) 232 * .cacheMode(CacheMode.<jsf>NONE</jsf>) 233 * .build(); 234 * 235 * <jc>// Weak caching for Class metadata</jc> 236 * Cache<Class<?>,ClassMeta> <jv>cache2</jv> = Cache.<jsm>of</jsm>(Class.<jk>class</jk>, ClassMeta.<jk>class</jk>) 237 * .cacheMode(CacheMode.<jsf>WEAK</jsf>) 238 * .build(); 239 * 240 * <jc>// Full caching (default)</jc> 241 * Cache<String,Pattern> <jv>cache3</jv> = Cache.<jsm>of</jsm>(String.<jk>class</jk>, Pattern.<jk>class</jk>) 242 * .cacheMode(CacheMode.<jsf>FULL</jsf>) 243 * .build(); 244 * </p> 245 * 246 * @param value The caching mode. 247 * @return This object for method chaining. 248 */ 249 public Builder<K,V> cacheMode(CacheMode value) { 250 cacheMode = value; 251 return this; 252 } 253 254 /** 255 * Conditionally enables logging of cache statistics when the JVM exits. 256 * 257 * <p> 258 * When enabled, the cache will register a shutdown hook that logs the cache name, 259 * total cache hits, and total cache misses (size of cache) to help analyze cache effectiveness. 260 * 261 * @param value Whether to enable logging on exit. 262 * @param idValue The identifier to use in the log message. 263 * @return This object for method chaining. 264 */ 265 public Builder<K,V> logOnExit(boolean value, String idValue) { 266 id = idValue; 267 logOnExit = value; 268 return this; 269 } 270 271 /** 272 * Enables logging of cache statistics when the JVM exits. 273 * 274 * <p> 275 * When enabled, the cache will register a shutdown hook that logs the cache name, 276 * total cache hits, and total cache misses (size of cache) to help analyze cache effectiveness. 277 * 278 * <p> 279 * Example output: 280 * <p class='bconsole'> 281 * Pattern cache: hits=1523, misses: 47 282 * </p> 283 * 284 * <p> 285 * This is useful for: 286 * <ul> 287 * <li>Performance tuning and identifying caching opportunities 288 * <li>Determining optimal max size values 289 * <li>Monitoring cache efficiency in production 290 * </ul> 291 * 292 * @param value The identifier to use in the log message. 293 * @return This object for method chaining. 294 */ 295 public Builder<K,V> logOnExit(String value) { 296 id = value; 297 logOnExit = true; 298 return this; 299 } 300 301 /** 302 * Specifies the maximum number of entries allowed in this cache. 303 * 304 * <p> 305 * When the cache size exceeds this value, the <em>entire</em> cache is cleared to make room for new entries. 306 * This is a simple eviction strategy that avoids the overhead of LRU/LFU tracking. 307 * 308 * <p> 309 * Default value: 1000 (or value of system property <c>juneau.cache.maxSize</c>) 310 * 311 * <h5 class='section'>Notes:</h5> 312 * <ul> 313 * <li>Setting this too low may cause excessive cache clearing and reduce effectiveness 314 * <li>Setting this too high may consume excessive memory 315 * <li>For unbounded caching, use {@link Integer#MAX_VALUE} (not recommended for production) 316 * </ul> 317 * 318 * @param value The maximum number of cache entries. Must be positive. 319 * @return This object for method chaining. 320 */ 321 public Builder<K,V> maxSize(int value) { 322 maxSize = value; 323 return this; 324 } 325 326 /** 327 * Specifies the default supplier function for computing values when keys are not found. 328 * 329 * <p> 330 * This supplier will be used by {@link Cache#get(Object)} when a key is not in the cache. 331 * Individual lookups can override this supplier using {@link Cache#get(Object, Supplier)}. 332 * 333 * <h5 class='section'>Example:</h5> 334 * <p class='bjava'> 335 * Cache<String,Pattern> <jv>cache</jv> = Cache 336 * .<jsm>of</jsm>(String.<jk>class</jk>, Pattern.<jk>class</jk>) 337 * .supplier(Pattern::compile) 338 * .build(); 339 * 340 * <jc>// Uses default supplier</jc> 341 * Pattern <jv>p</jv> = <jv>cache</jv>.get(<js>"[a-z]+"</js>); 342 * </p> 343 * 344 * @param value The default supplier function. Can be <jk>null</jk> (calls to {@link Cache#get(Object)} will then always return <jk>null</jk>). 345 * @return This object for method chaining. 346 */ 347 public Builder<K,V> supplier(Function<K,V> value) { 348 supplier = value; 349 return this; 350 } 351 352 /** 353 * Enables thread-local caching. 354 * 355 * <p> 356 * When enabled, each thread gets its own separate cache instance. This is useful for 357 * thread-unsafe objects like {@link java.text.MessageFormat} that need to be cached per thread. 358 * 359 * <p> 360 * This is a shortcut for wrapping a cache in a {@link ThreadLocal}, but provides a cleaner API. 361 * 362 * <h5 class='section'>Example:</h5> 363 * <p class='bjava'> 364 * <jc>// Thread-local cache for MessageFormat instances</jc> 365 * Cache<String,MessageFormat> <jv>cache</jv> = Cache 366 * .<jsm>of</jsm>(String.<jk>class</jk>, MessageFormat.<jk>class</jk>) 367 * .maxSize(100) 368 * .<jsm>threadLocal</jsm>() 369 * .build(); 370 * </p> 371 * 372 * <p> 373 * This is equivalent to: 374 * <p class='bjava'> 375 * ThreadLocal<Cache<String,MessageFormat>> <jv>cache</jv> = 376 * ThreadLocal.<jsm>withInitial</jsm>(() -> Cache 377 * .<jsm>of</jsm>(String.<jk>class</jk>, MessageFormat.<jk>class</jk>) 378 * .maxSize(100) 379 * .build()); 380 * </p> 381 * 382 * @return This object for method chaining. 383 */ 384 public Builder<K,V> threadLocal() { 385 threadLocal = true; 386 return this; 387 } 388 389 /** 390 * Sets the caching mode to {@link CacheMode#WEAK WEAK}. 391 * 392 * <p> 393 * This is a shortcut for calling <c>cacheMode(CacheMode.WEAK)</c>. 394 * 395 * <p> 396 * Weak caching uses {@link WeakHashMap} for storage, allowing cache entries to be 397 * garbage collected when keys are no longer strongly referenced elsewhere. 398 * 399 * <h5 class='section'>Example:</h5> 400 * <p class='bjava'> 401 * <jc>// Weak caching for Class metadata</jc> 402 * Cache<Class<?>,ClassMeta> <jv>cache</jv> = Cache.<jsm>of</jsm>(Class.<jk>class</jk>, ClassMeta.<jk>class</jk>) 403 * .<jsm>weak</jsm>() 404 * .build(); 405 * </p> 406 * 407 * @return This object for method chaining. 408 * @see #cacheMode(CacheMode) 409 */ 410 public Builder<K,V> weak() { 411 return cacheMode(WEAK); 412 } 413 } 414 415 /** 416 * Creates a new {@link Builder} for constructing a cache with explicit type parameters. 417 * 418 * <p> 419 * This variant allows you to specify the cache's generic types explicitly without passing 420 * the class objects, which is useful when working with complex parameterized types. 421 * 422 * <h5 class='section'>Example:</h5> 423 * <p class='bjava'> 424 * <jc>// Working with complex generic types</jc> 425 * Cache<Class<?>,List<AnnotationInfo<Annotation>>> <jv>cache</jv> = 426 * Cache.<Class<?>,List<AnnotationInfo<Annotation>>><jsm>create</jsm>() 427 * .supplier(<jv>key</jv> -> findAnnotations(<jv>key</jv>)) 428 * .build(); 429 * </p> 430 * 431 * @param <K> The key type. 432 * @param <V> The value type. 433 * @return A new builder for configuring the cache. 434 */ 435 public static <K,V> Builder<K,V> create() { 436 return new Builder<>(); 437 } 438 439 /** 440 * Creates a new {@link Builder} for constructing a cache. 441 * 442 * <h5 class='section'>Example:</h5> 443 * <p class='bjava'> 444 * Cache<String,Pattern> <jv>cache</jv> = Cache 445 * .<jsm>of</jsm>(String.<jk>class</jk>, Pattern.<jk>class</jk>) 446 * .maxSize(100) 447 * .build(); 448 * </p> 449 * 450 * @param <K> The key type. 451 * @param <V> The value type. 452 * @param key The key type class (used for type safety). 453 * @param type The value type class. 454 * @return A new builder for configuring the cache. 455 */ 456 @SuppressWarnings({ 457 "java:S1172" // Parameters required for type inference in public API 458 }) 459 public static <K,V> Builder<K,V> of(Class<K> key, Class<V> type) { 460 return new Builder<>(); 461 } 462 463 // Internal map holding cached values. 464 // In WEAK mode this is a WeakHashMap keyed DIRECTLY on K (see mapKey()) so entries honor the weak-key 465 // contract; in all other modes array keys are wrapped in a Tuple1<K> (see mapKey()) that provides content-based 466 // equality; other keys are used directly. The static key type is therefore Object. 467 // If threadLocal is true, this is null and threadLocalMap is used instead. 468 private final Map<Object,V> map; 469 private final ThreadLocal<Map<Object,V>> threadLocalMap; 470 471 private final boolean isThreadLocal; 472 473 private final boolean isWeak; 474 475 private final int maxSize; 476 477 private final boolean disableCaching; 478 479 private final Function<K,V> supplier; 480 481 private final LongAdder cacheHits = new LongAdder(); 482 483 /** 484 * Constructor. 485 * 486 * @param builder The builder containing configuration settings. 487 */ 488 protected Cache(Builder<K,V> builder) { 489 this.maxSize = builder.maxSize; 490 this.disableCaching = builder.cacheMode == NONE; 491 this.supplier = builder.supplier != null ? builder.supplier : key -> null; 492 this.isThreadLocal = builder.threadLocal; 493 this.isWeak = builder.cacheMode == WEAK; 494 495 if (isThreadLocal) { 496 // Thread-local mode: each thread gets its own map 497 if (isWeak) { 498 // WEAK mode keys the map directly on K (no Tuple1 wrapper) so entries stay 499 // reachable exactly as long as the caller's key does. See mapKey(). 500 this.threadLocalMap = ThreadLocal.withInitial(() -> synchronizedMap(new WeakHashMap<>())); 501 } else { 502 this.threadLocalMap = ThreadLocal.withInitial(() -> new ConcurrentHashMap<>()); 503 } 504 this.map = null; 505 } else { 506 // Normal mode: shared map across all threads 507 if (isWeak) { 508 // WEAK mode keys the map directly on K (no Tuple1 wrapper) so entries stay 509 // reachable exactly as long as the caller's key does. See mapKey(). 510 this.map = synchronizedMap(new WeakHashMap<>()); 511 } else { 512 this.map = new ConcurrentHashMap<>(); 513 } 514 this.threadLocalMap = null; 515 } 516 if (builder.logOnExit) { 517 shutdownMessage(() -> builder.id + ": hits=" + cacheHits.intValue() + ", misses: " + size()); 518 } 519 } 520 521 /** 522 * Removes all entries from the cache. 523 */ 524 public void clear() { 525 getMap().clear(); 526 } 527 528 /** 529 * Cleans up thread-local storage for the current thread. 530 * 531 * <p> 532 * This method should be called when a thread is finished using this cache to prevent memory leaks 533 * in thread pool environments where threads are reused. 534 * Only has an effect if this cache was created with thread-local mode enabled. 535 */ 536 public void cleanup() { 537 if (isThreadLocal && threadLocalMap != null) 538 threadLocalMap.remove(); 539 } 540 541 /** 542 * Returns <jk>true</jk> if the cache contains a mapping for the specified key. 543 * 544 * @param key The key to check. Can be <jk>null</jk> (null keys are supported like any other key). 545 * @return <jk>true</jk> if the cache contains the key. 546 */ 547 public boolean containsKey(K key) { 548 return getMap().containsKey(mapKey(key)); 549 } 550 551 /** 552 * Returns <jk>true</jk> if the cache contains one or more entries with the specified value. 553 * 554 * @param value The value to check. Can be <jk>null</jk>, in which case this method returns <jk>false</jk> (the cache never stores <jk>null</jk> values). 555 * @return <jk>true</jk> if the cache contains the value. 556 */ 557 public boolean containsValue(V value) { 558 // ConcurrentHashMap doesn't allow null values, so null can never be in the cache 559 if (value == null) 560 return false; 561 return getMap().containsValue(value); 562 } 563 564 /** 565 * Retrieves a cached value by key using the default supplier. 566 * 567 * <p> 568 * This method uses the default supplier configured via {@link Builder#supplier(Function)}. 569 * If no default supplier was configured, this method will throw a {@link NullPointerException}. 570 * 571 * <h5 class='section'>Example:</h5> 572 * <p class='bjava'> 573 * Cache<String,Pattern> <jv>cache</jv> = Cache 574 * .<jsm>of</jsm>(String.<jk>class</jk>, Pattern.<jk>class</jk>) 575 * .supplier(Pattern::compile) 576 * .build(); 577 * 578 * <jc>// Uses default supplier</jc> 579 * Pattern <jv>p</jv> = <jv>cache</jv>.get(<js>"[0-9]+"</js>); 580 * </p> 581 * 582 * @param key The cache key. Can be <jk>null</jk> (null keys are supported like any other key). 583 * @return The cached or computed value. May be <jk>null</jk> if the supplier returns <jk>null</jk>. 584 * @throws NullPointerException if no default supplier was configured. 585 */ 586 public V get(K key) { 587 if (! disableCaching) { 588 V v = getMap().get(mapKey(key)); 589 if (v != null) { 590 cacheHits.increment(); 591 return v; 592 } 593 } 594 return get(key, () -> supplier.apply(key)); 595 } 596 597 /** 598 * Retrieves a cached value by key, computing it if necessary using the provided supplier. 599 * 600 * <p> 601 * This method implements the cache-aside pattern: 602 * <ol> 603 * <li>If the key exists in the cache, return the cached value (cache hit) 604 * <li>If the key doesn't exist, invoke the supplier to compute the value 605 * <li>Store the computed value in the cache using {@link ConcurrentHashMap#putIfAbsent(Object, Object)} 606 * <li>Return the value 607 * </ol> 608 * 609 * <h5 class='section'>Behavior:</h5> 610 * <ul class='spaced-list'> 611 * <li>If the cache is disabled, always invokes the supplier without caching 612 * <li>If the cache exceeds {@link Builder#maxSize(int)}, clears all entries before storing the new value 613 * <li>Thread-safe: Multiple threads can safely call this method concurrently 614 * <li>The supplier may be called multiple times for the same key in concurrent scenarios 615 * (due to {@link ConcurrentHashMap#putIfAbsent(Object, Object)} semantics) 616 * <li><b>Array Keys:</b> Arrays are matched by content, not identity 617 * </ul> 618 * 619 * <h5 class='section'>Example:</h5> 620 * <p class='bjava'> 621 * Cache<String,Pattern> <jv>cache</jv> = Cache.<jsm>of</jsm>(String.<jk>class</jk>, Pattern.<jk>class</jk>).build(); 622 * 623 * <jc>// First call: compiles pattern and caches it</jc> 624 * Pattern <jv>p1</jv> = <jv>cache</jv>.get(<js>"[0-9]+"</js>, () -> Pattern.compile(<js>"[0-9]+"</js>)); 625 * 626 * <jc>// Second call: returns cached pattern instantly</jc> 627 * Pattern <jv>p2</jv> = <jv>cache</jv>.get(<js>"[0-9]+"</js>, () -> Pattern.compile(<js>"[0-9]+"</js>)); 628 * 629 * <jsm>assert</jsm> <jv>p1</jv> == <jv>p2</jv>; <jc>// Same instance</jc> 630 * </p> 631 * 632 * @param key The cache key. Can be <jk>null</jk> (null keys are supported like any other key). 633 * @param supplier The supplier to compute the value if it's not in the cache. Must not be <jk>null</jk> or an {@link IllegalArgumentException} is thrown. 634 * @return The cached or computed value. May be <jk>null</jk> if the supplier returns <jk>null</jk>. 635 */ 636 public V get(K key, Supplier<V> supplier) { 637 reqnn("supplier", supplier); 638 if (disableCaching) 639 return supplier.get(); 640 var m = getMap(); 641 var wrapped = mapKey(key); 642 V v = m.get(wrapped); 643 if (v == null) { 644 if (size() > maxSize) 645 clear(); 646 v = supplier.get(); 647 if (v == null) 648 m.remove(wrapped); 649 else 650 m.putIfAbsent(wrapped, v); 651 } else { 652 cacheHits.increment(); 653 } 654 return v; 655 } 656 657 /** 658 * Returns the total number of cache hits since this cache was created. 659 * 660 * <p> 661 * A cache hit occurs when {@link #get(Object)} or {@link #get(Object, Supplier)} finds an existing 662 * cached value for the requested key, avoiding the need to invoke the supplier. 663 * 664 * <h5 class='section'>Cache Effectiveness:</h5> 665 * <p> 666 * You can calculate the cache hit ratio using: 667 * <p class='bjava'> 668 * <jk>int</jk> <jv>hits</jv> = <jv>cache</jv>.getCacheHits(); 669 * <jk>int</jk> <jv>misses</jv> = <jv>cache</jv>.size(); 670 * <jk>int</jk> <jv>total</jv> = <jv>hits</jv> + <jv>misses</jv>; 671 * <jk>double</jk> <jv>hitRatio</jv> = (<jk>double</jk>) <jv>hits</jv> / <jv>total</jv>; <jc>// 0.0 to 1.0</jc> 672 * </p> 673 * 674 * <h5 class='section'>Notes:</h5> 675 * <ul> 676 * <li>This counter is never reset, even when {@link #clear()} is called 677 * <li>Thread-safe using {@link LongAdder} 678 * <li>Returns 0 if the cache is disabled 679 * </ul> 680 * 681 * @return The total number of cache hits since creation. 682 */ 683 public int getCacheHits() { return cacheHits.intValue(); } 684 685 /** 686 * Returns <jk>true</jk> if the cache contains no entries. 687 * 688 * @return <jk>true</jk> if the cache is empty. 689 */ 690 public boolean isEmpty() { return getMap().isEmpty(); } 691 692 /** 693 * Associates the specified value with the specified key in this cache. 694 * 695 * @param key The cache key. Can be <jk>null</jk> (null keys are supported like any other key). 696 * @param value The value to associate with the key. 697 * @return The previous value associated with the key, or <jk>null</jk> if there was no mapping. 698 */ 699 public V put(K key, V value) { 700 var m = getMap(); 701 if (value == null) 702 return m.remove(mapKey(key)); 703 return m.put(mapKey(key), value); 704 } 705 706 /** 707 * Removes the entry for the specified key from the cache. 708 * 709 * @param key The key to remove. Can be <jk>null</jk> (null keys are supported like any other key). 710 * @return The previous value associated with the key, or <jk>null</jk> if there was no mapping. 711 */ 712 public V remove(K key) { 713 return getMap().remove(mapKey(key)); 714 } 715 716 /** 717 * Returns the number of entries in the cache. 718 * 719 * @return The number of cached entries. 720 */ 721 public int size() { 722 return getMap().size(); 723 } 724 725 /** 726 * Gets the map for the current thread. 727 * 728 * @return The map for the current thread. 729 */ 730 private Map<Object,V> getMap() { return isThreadLocal ? threadLocalMap.get() : map; } 731 732 /** 733 * Computes the backing-map key for the given caller key. 734 * 735 * <p> 736 * In {@link CacheMode#WEAK WEAK} mode the key is used <b>directly</b> as the {@link WeakHashMap} key so that 737 * an entry stays reachable exactly as long as the caller's key {@code K} is strongly reachable elsewhere, and 738 * becomes GC-eligible the moment the caller drops it - the documented weak-key contract. 739 * 740 * <p> 741 * WEAK mode must <b>never</b> wrap the key in a {@link Tuple1}, nor retain such a wrapper in any shared map. 742 * A {@link Tuple1} strongly holds its own weak key {@code K}, so a cache-held wrapper creates a 743 * {@code Cache -> Tuple1 -> K} strong path that pins the weak key forever - a self-reference 744 * leak that silently turns WEAK mode into a never-evicting FULL cache. Do not "optimize" the wrapper back in 745 * for WEAK mode. (Consequently array keys are matched by identity, not content, in WEAK mode; that is 746 * inherent to weak keying, whose retention necessarily tracks the caller's exact key object.) 747 * 748 * <p> 749 * All other modes wrap array keys (and <jk>null</jk>, which {@link ConcurrentHashMap} rejects) in a 750 * {@link Tuple1} for content-based equality. Any other key is used directly, so the common lookup path takes 751 * no lock and allocates nothing. 752 * 753 * @param key The caller's key. 754 * @return The object to use as the backing-map key. 755 */ 756 private Object mapKey(K key) { 757 return isWeak || (key != null && ! key.getClass().isArray()) ? key : Tuple1.of(key); 758 } 759}