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&lt;String,Pattern&gt; <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>, () -&gt; 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&lt;String[],Result&gt; <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>}, () -&gt; computeResult());
077 *    Result <jv>r</jv> = <jv>cache</jv>.get(<jk>new</jk> String[]{<js>"a"</js>, <js>"b"</js>}, () -&gt; 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&lt;String,Integer&gt; <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&lt;Class&lt;?&gt;,ClassMeta&gt; <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&lt;Class&lt;?&gt;,List&lt;AnnotationInfo&lt;Annotation&gt;&gt;&gt; <jv>annotationsCache</jv> =
133 *       Cache.&lt;Class&lt;?&gt;,List&lt;AnnotationInfo&lt;Annotation&gt;&gt;&gt;<jsm>create</jsm>()
134 *          .supplier(<jk>this</jk>::findClassAnnotations)
135 *          .build();
136 *
137 *    <jc>// Disabled cache for testing</jc>
138 *    Cache&lt;String,Object&gt; <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 &gt; 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&lt;String,Pattern&gt; <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&lt;Class&lt;?&gt;,List&lt;Method&gt;&gt; <jv>cache2</jv> =
172    *       Cache.&lt;Class&lt;?&gt;,List&lt;Method&gt;&gt;<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&lt;String,Object&gt; <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&lt;Class&lt;?&gt;,ClassMeta&gt; <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&lt;String,Pattern&gt; <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&lt;String,Pattern&gt; <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&lt;String,MessageFormat&gt; <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&lt;Cache&lt;String,MessageFormat&gt;&gt; <jv>cache</jv> =
376       *       ThreadLocal.<jsm>withInitial</jsm>(() -&gt; 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&lt;Class&lt;?&gt;,ClassMeta&gt; <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&lt;Class&lt;?&gt;,List&lt;AnnotationInfo&lt;Annotation&gt;&gt;&gt; <jv>cache</jv> =
426    *       Cache.&lt;Class&lt;?&gt;,List&lt;AnnotationInfo&lt;Annotation&gt;&gt;&gt;<jsm>create</jsm>()
427    *          .supplier(<jv>key</jv> -&gt; 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&lt;String,Pattern&gt; <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&lt;String,Pattern&gt; <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&lt;String,Pattern&gt; <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>, () -&gt; 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>, () -&gt; 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}