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<String> <jv>supplier</jv> = <jk>new</jk> Memoizer<>(() -> 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<String> <jv>supplier</jv> = <jk>new</jk> Memoizer<>(() -> <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}