mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
CE-847 Add overload of getResult that takes the lookup function to use if not found - much more clear & useful.
This commit is contained in:
@ -30,6 +30,7 @@ import java.util.LinkedHashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
@ -58,8 +59,16 @@ public class Memoization<K, V>
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
** Get the memoized Value for a given input Key.
|
||||||
**
|
**
|
||||||
|
** But note, this looks the same to the caller, whether the key just wasn't in
|
||||||
|
** the internal map (e.g., had never been looked up), or if it was previously looked
|
||||||
|
** up, and that returned null. In either case, the optional will be empty.
|
||||||
|
**
|
||||||
|
** See getMemoizedResult for where we can tell the difference (and we would
|
||||||
|
** generally want to call that.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
@Deprecated
|
||||||
public Optional<V> getResult(K key)
|
public Optional<V> getResult(K key)
|
||||||
{
|
{
|
||||||
MemoizedResult<V> result = map.get(key);
|
MemoizedResult<V> result = map.get(key);
|
||||||
@ -77,7 +86,57 @@ public class Memoization<K, V>
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
** Get the memoized Value for a given input Key - computing it if it wasn't previously
|
||||||
|
** memoized (or expired).
|
||||||
**
|
**
|
||||||
|
** In here, if the optional is empty, it means the value is null (whether that
|
||||||
|
** came form memoization, or from the lookupFunction, you don't care - the answer
|
||||||
|
** is null).
|
||||||
|
*******************************************************************************/
|
||||||
|
public Optional<V> getResult(K key, UnsafeFunction<K, V, ?> lookupFunction)
|
||||||
|
{
|
||||||
|
MemoizedResult<V> result = map.get(key);
|
||||||
|
if(result != null)
|
||||||
|
{
|
||||||
|
if(result.getTime().isAfter(Instant.now().minus(timeout)))
|
||||||
|
{
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
// ok, we have a memoized value, and it's not expired, so we can return it. //
|
||||||
|
// of course, it might be a memoized null, so we use .ofNullable. //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
return (Optional.ofNullable(result.getResult()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// ok - either we never memoized this key, or it's expired, so, apply the lookup function, //
|
||||||
|
// store the result, and then return the value (in an Optional.ofNullable) //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
try
|
||||||
|
{
|
||||||
|
V value = lookupFunction.apply(key);
|
||||||
|
storeResult(key, value);
|
||||||
|
return (Optional.ofNullable(value));
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
LOG.warn("Uncaught Exception while executing a Memoization lookupFunction (to avoid this log, add a catch in the lookupFunction)", e);
|
||||||
|
storeResult(key, null);
|
||||||
|
return (Optional.empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Get a memoized result, optionally containing a Value, for a given input Key.
|
||||||
|
**
|
||||||
|
** In this method (contrasted with getResult), if the returned Optional is empty,
|
||||||
|
** it means that we haven't ever looked up or memoized the key (or it's expired).
|
||||||
|
**
|
||||||
|
** If the returned Optional is not empty, then it means we've memoized something
|
||||||
|
** (and it's not expired) - so if the Value from the MemoizedResult is null,
|
||||||
|
** then null is the proper memoized value.
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public Optional<MemoizedResult<V>> getMemoizedResult(K key)
|
public Optional<MemoizedResult<V>> getMemoizedResult(K key)
|
||||||
{
|
{
|
||||||
@ -86,7 +145,7 @@ public class Memoization<K, V>
|
|||||||
{
|
{
|
||||||
if(result.getTime().isAfter(Instant.now().minus(timeout)))
|
if(result.getTime().isAfter(Instant.now().minus(timeout)))
|
||||||
{
|
{
|
||||||
return (Optional.ofNullable(result));
|
return (Optional.of(result));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,4 +240,47 @@ public class Memoization<K, V>
|
|||||||
{
|
{
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for timeout
|
||||||
|
*******************************************************************************/
|
||||||
|
public Duration getTimeout()
|
||||||
|
{
|
||||||
|
return (this.timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for timeout
|
||||||
|
*******************************************************************************/
|
||||||
|
public Memoization<K, V> withTimeout(Duration timeout)
|
||||||
|
{
|
||||||
|
this.timeout = timeout;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for maxSize
|
||||||
|
*******************************************************************************/
|
||||||
|
public Integer getMaxSize()
|
||||||
|
{
|
||||||
|
return (this.maxSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Fluent setter for maxSize
|
||||||
|
*******************************************************************************/
|
||||||
|
public Memoization<K, V> withMaxSize(Integer maxSize)
|
||||||
|
{
|
||||||
|
this.maxSize = maxSize;
|
||||||
|
return (this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -32,11 +32,14 @@ import java.util.concurrent.ExecutorService;
|
|||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import com.kingsrook.qqq.backend.core.BaseTest;
|
import com.kingsrook.qqq.backend.core.BaseTest;
|
||||||
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
||||||
|
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
@ -122,6 +125,58 @@ class MemoizationTest extends BaseTest
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
@Test
|
||||||
|
void testLookupFunction()
|
||||||
|
{
|
||||||
|
AtomicInteger lookupFunctionCallCounter = new AtomicInteger(0);
|
||||||
|
|
||||||
|
Memoization<String, Integer> memoization = new Memoization<>();
|
||||||
|
|
||||||
|
UnsafeFunction<String, Integer, Exception> lookupFunction = numberString ->
|
||||||
|
{
|
||||||
|
lookupFunctionCallCounter.getAndIncrement();
|
||||||
|
|
||||||
|
if(numberString.equals("null"))
|
||||||
|
{
|
||||||
|
return (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Integer.parseInt(numberString);
|
||||||
|
};
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// get "1" twice - should return 1 each time, and call the lookup function exactly once //
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
assertThat(memoization.getResult("1", lookupFunction)).isPresent().contains(1);
|
||||||
|
assertEquals(1, lookupFunctionCallCounter.get());
|
||||||
|
|
||||||
|
assertThat(memoization.getResult("1", lookupFunction)).isPresent().contains(1);
|
||||||
|
assertEquals(1, lookupFunctionCallCounter.get());
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// now get "null" twice - should return null each time, and call the lookup function exactly once more //
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
assertThat(memoization.getResult("null", lookupFunction)).isEmpty();
|
||||||
|
assertEquals(2, lookupFunctionCallCounter.get());
|
||||||
|
|
||||||
|
assertThat(memoization.getResult("null", lookupFunction)).isEmpty();
|
||||||
|
assertEquals(2, lookupFunctionCallCounter.get());
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// now make a call that throws twice - again, should return null each time, and only do one more loookup call //
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
assertThat(memoization.getResult(null, lookupFunction)).isEmpty();
|
||||||
|
assertEquals(3, lookupFunctionCallCounter.get());
|
||||||
|
|
||||||
|
assertThat(memoization.getResult(null, lookupFunction)).isEmpty();
|
||||||
|
assertEquals(3, lookupFunctionCallCounter.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
Reference in New Issue
Block a user