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.function;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022import java.util.concurrent.atomic.*;
023import java.util.function.*;
024
025/**
026 * A thread-safe supplier that caches the result of the first call and supports resetting the cache.
027 *
028 * <p>
029 * This class extends {@link NullableSupplier} to provide both standard {@link Supplier#get()} functionality
030 * and a {@link #reset()} method to clear the cache, forcing recomputation on the next call to {@link #get()}.
031 * It also inherits all Optional-like convenience methods from {@link NullableSupplier}.
032 *
033 * <h5 class='section'>Usage:</h5>
034 * <p class='bjava'>
035 *    <jc>// Create a memoizer</jc>
036 *    Memoizer&lt;String&gt; <jv>supplier</jv> = <jk>new</jk> Memoizer&lt;&gt;(() -&gt; expensiveComputation());
037 *
038 *    <jc>// First call computes and caches</jc>
039 *    String <jv>result1</jv> = <jv>supplier</jv>.get();
040 *
041 *    <jc>// Subsequent calls return cached value</jc>
042 *    String <jv>result2</jv> = <jv>supplier</jv>.get();
043 *
044 *    <jc>// Use Optional-like methods</jc>
045 *    <jk>if</jk> (<jv>supplier</jv>.isPresent()) {
046 *       String <jv>upper</jv> = <jv>supplier</jv>.map(String::toUpperCase).orElse(<js>"default"</js>);
047 *    }
048 *
049 *    <jc>// Reset forces recomputation on next get()</jc>
050 *    <jv>supplier</jv>.reset();
051 *    String <jv>result3</jv> = <jv>supplier</jv>.get();  <jc>// Recomputes</jc>
052 * </p>
053 *
054 * <h5 class='section'>Thread Safety:</h5>
055 * <p>
056 * This class is thread-safe for both {@link #get()} and {@link #reset()} operations.
057 * If multiple threads call get() simultaneously after a reset, the supplier may be invoked
058 * multiple times, but only one result will be cached.
059 *
060 * <h5 class='section'>Notes:</h5>
061 * <ul>
062 *    <li>The supplier may be called multiple times if threads race, but only one result is cached.
063 *    <li>The cached value can be <jk>null</jk> if the supplier returns <jk>null</jk>.
064 *    <li>After reset, the next get() will recompute the value.
065 *    <li>This is particularly useful for testing when configuration changes and cached values need to be recalculated.
066 *    <li>Inherits all Optional-like methods from {@link NullableSupplier} (isPresent(), isEmpty(), map(), orElse(), etc.).
067 * </ul>
068 *
069 * <h5 class='section'>See Also:</h5>
070 * <ul>
071 *    <li class='jm'>{@link Suppliers#memoizer(Supplier)}
072 *    <li class='jc'>{@link NullableSupplier} - Base interface providing Optional-like methods
073 * </ul>
074 *
075 * @param <T> The type of value supplied.
076 */
077@SuppressWarnings({
078   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
079   "java:S2789" // null check on Optional is intentional - AtomicReference uses null to represent "not initialized" state
080})
081public class Memoizer<T> implements NullableSupplier<T> {
082
083   private final Supplier<T> supplier;
084   private final AtomicReference<Optional<T>> cache = new AtomicReference<>();
085
086   /**
087    * Constructor.
088    *
089    * @param supplier The underlying supplier to call when computing values.  Must not be <jk>null</jk>.
090    */
091   public Memoizer(Supplier<T> supplier) {
092      this.supplier = reqnn("supplier", supplier);
093   }
094
095   /**
096    * Returns the cached value if present, otherwise computes it using the underlying supplier.
097    *
098    * @return The cached or newly computed value.
099    */
100   @Override
101   public T get() {
102      Optional<T> h = cache.get();
103      if (h == null) {
104         h = o(supplier.get());
105         if (! cache.compareAndSet(null, h)) {
106            // Another thread beat us, use their value
107            h = cache.get();
108         }
109      }
110      return h.orElse(null);
111   }
112
113   /**
114    * Clears the cached value, forcing the next call to {@link #get()} to recompute the value.
115    *
116    * <p>
117    * This method is thread-safe and can be called from multiple threads. After reset,
118    * the next get() call will invoke the underlying supplier to compute a fresh value.
119    */
120   public void reset() {
121      cache.set(null);
122   }
123
124   /**
125    * Sets the cached value directly without invoking the underlying supplier.
126    *
127    * <p>
128    * This method allows you to override the cached value, bypassing the supplier.
129    * Subsequent calls to {@link #get()} will return the set value until {@link #reset()} is called
130    * or the value is set again.
131    *
132    * <p>
133    * This method is thread-safe and is particularly useful for testing when you need to
134    * inject a specific value without invoking the supplier.
135    *
136    * <h5 class='section'>Example:</h5>
137    * <p class='bjava'>
138    *    <jc>// Create a memoizer</jc>
139    *    Memoizer&lt;String&gt; <jv>supplier</jv> = <jk>new</jk> Memoizer&lt;&gt;(() -&gt; <js>"computed"</js>);
140    *
141    *    <jc>// Set a value directly without invoking the supplier</jc>
142    *    <jv>supplier</jv>.<jsm>set</jsm>(<js>"injected"</js>);
143    *
144    *    <jc>// get() returns the injected value</jc>
145    *    assertEquals(<js>"injected"</js>, <jv>supplier</jv>.<jsm>get</jsm>());
146    *
147    *    <jc>// Reset clears the cache, next get() will invoke the supplier</jc>
148    *    <jv>supplier</jv>.<jsm>reset</jsm>();
149    *    assertEquals(<js>"computed"</js>, <jv>supplier</jv>.<jsm>get</jsm>());
150    * </p>
151    *
152    * @param value The value to cache. Can be <jk>null</jk>.
153    */
154   public void set(T value) {
155      cache.set(o(value));
156   }
157
158   /**
159    * Returns <jk>true</jk> if the supplier has not yet been called (cache is empty).
160    *
161    * <p>
162    * This method checks whether the cached value is <jk>null</jk>, which indicates that
163    * {@link #get()} has not yet been called, or the cache was reset.
164    *
165    * @return <jk>true</jk> if the supplier has not been called yet, <jk>false</jk> if a value has been cached.
166    */
167   public boolean isSupplied() {
168      return cache.get() == null;
169   }
170
171   /**
172    * Creates a copy of this memoizer.
173    *
174    * <p>
175    * If a value has been cached, the copy will use a supplier that returns that cached value.
176    * If no value has been cached yet, the copy will use the original supplier.
177    *
178    * @return A new {@link Memoizer} instance with the same state as this memoizer.
179    */
180   public Memoizer<T> copy() {
181      Optional<T> o = cache.get();
182      if (o == null)
183         return new Memoizer<>(supplier);
184      return new Memoizer<>(o::get);
185   }
186
187   /**
188    * If a value is present, applies the provided mapping function to it and returns a Memoizer describing the result.
189    *
190    * <p>
191    * The returned Memoizer maintains its own cache, independent of this memoizer.
192    * Resetting the mapped memoizer does not affect this memoizer, and vice versa.
193    *
194    * @param <U> The type of the result of the mapping function.
195    * @param mapper A mapping function to apply to the value, if present. Must not be <jk>null</jk>.
196    * @return A Memoizer describing the result of applying a mapping function to the value of this Memoizer, if a value is present, otherwise an empty Memoizer.
197    */
198   @Override
199   public <U> Memoizer<U> map(Function<? super T,? extends U> mapper) {
200      reqnn("mapper", mapper);
201      return new Memoizer<>(() -> {
202         T value = get();
203         return nn(value) ? mapper.apply(value) : null;
204      });
205   }
206
207   /**
208    * If a value is present, and the value matches the given predicate, returns a Memoizer describing the value, otherwise returns an empty Memoizer.
209    *
210    * <p>
211    * The returned Memoizer maintains its own cache, independent of this memoizer.
212    * Resetting the filtered memoizer does not affect this memoizer, and vice versa.
213    *
214    * @param predicate A predicate to apply to the value, if present. Must not be <jk>null</jk>.
215    * @return A Memoizer describing the value of this Memoizer if a value is present and the value matches the given predicate, otherwise an empty Memoizer.
216    */
217   @Override
218   public Memoizer<T> filter(Predicate<? super T> predicate) {
219      reqnn("predicate", predicate);
220      return new Memoizer<>(() -> {
221         T value = get();
222         return (nn(value) && predicate.test(value)) ? value : null;
223      });
224   }
225}