package com.peterphi.std.guice.common.cached.util;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.peterphi.std.guice.common.cached.CacheManager;
import com.peterphi.std.threading.Timeout;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * Wrapper around a single item guava cache as a workaround for google not implementing <a
 * href="https://github.com/google/guava/issues/872">the request for a single item cache made in 2012</a>.
 * <p>
 * This is a light wrapper around {@link Cache}
 *
 * @param <T> the type of value cached
 */
public class SingleItemCache<T>
{
	/**
	 * The static key used for the cache "map"
	 */
	private static final Integer KEY = 0;

	private final Cache<Integer, T> cache;
	private final Callable<T> supplier;
	private String name;


	/**
	 * Create a new cache, caching the result of <code>supplier</code> for a maximum of <code>validity</code>. A new Cache will be
	 * constructed using {@link CacheBuilder}
	 *
	 * @param supplier a function that generates some cache-worthy result; see {@link #get()} for information on how/when this
	 *                 method is called. The supplier MUST NOT return null
	 * @param validity the time after write that the cached result from <code>supplier</code> should expire
	 */
	public SingleItemCache(Callable<T> supplier, Timeout validity)
	{
		this(CacheBuilder.newBuilder().expireAfterWrite(validity.getMilliseconds(), TimeUnit.MILLISECONDS), supplier);
	}


	/**
	 * Create a new cache, caching the result of <code>supplier</code> using a cache built by <code>builder</code>.
	 *
	 * @param builder  the cache builder; will be largely untouched, although the maximum size will be set to 1 (since the cache
	 *                 will only store a single item)
	 * @param supplier a function that generates some cache-worthy result; see {@link #get()} for information on how/when this
	 *                 method is called. The supplier MUST NOT return null
	 */
	public SingleItemCache(CacheBuilder builder, Callable<T> supplier)
	{
		this.supplier = supplier;
		this.cache = builder.maximumSize(1).build();

		// Assign a name and register it with the Cache Manager
		setName("unnamed single item cache");
	}


	/**
	 * Get the value if it's sitting in the cache, otherwise return null
	 *
	 * @return the value from the cache, or null if no value was cached
	 * @see Cache#getIfPresent(Object)
	 */
	public T getIfPresent()
	{
		return cache.getIfPresent(KEY);
	}


	/**
	 * Return the cached value (or regenerate it).
	 *
	 * @return the cached result, or a newly-generated value if the cache had expired (or had been actively invalidated)
	 * @throws RuntimeException if the supplier function passed to the constructor throws
	 * @see Cache#get(Object, Callable)
	 */
	public T get()
	{
		try
		{
			return cache.get(KEY, supplier);
		}
		catch (ExecutionException e)
		{
			final Throwable cause = e.getCause();

			// Try to rethrow the actual exception so it's easier to understand
			if (cause == null)
				throw new RuntimeException(e);
			else if (cause instanceof RuntimeException)
				throw (RuntimeException) cause;
			else if (cause instanceof Error)
				throw (Error) cause;
			else
				throw new RuntimeException("Unexpected error loading item into cache: " + cause.getMessage(), cause);
		}
	}


	/**
	 * Invalidate any cached result, such that subsequent calls to {@link #get()} will re-generate the value
	 *
	 * @see Cache#invalidateAll()
	 */
	public void invalidate()
	{
		cache.invalidateAll();
	}


	public void setName(final String name)
	{
		this.name = name;

		CacheManager.register(name, cache);
	}


	public String getName()
	{
		return name;
	}


	@Override
	public String toString()
	{
		return "SingleItemCache{name=" + name + ",supplier=" + supplier + "}";
	}
}

@@ -2125,24 +2125,811 @@
 upplier)%0A%09%7B%0A
+%09%09this(builder, supplier, true);%0A%09%7D%0A%0A%0A%09/**%0A%09 * Create a new cache, caching the result of %3Ccode%3Esupplier%3C/code%3E using a cache built by %3Ccode%3Ebuilder%3C/code%3E.%0A%09 *%0A%09 * @param builder         the cache builder; will be largely untouched, although the maximum size will be set to 1 (since the%0A%09 *                        cache will only store a single item)%0A%09 * @param supplier        a function that generates some cache-worthy result; see %7B@link #get()%7D for information on how/when%0A%09 *                        this method is called. The supplier MUST NOT return null%0A%09 * @param useCacheManager if true, will not be registered with the CacheManager to allow cache to be cleared manually%0A%09 */%0A%09public SingleItemCache(CacheBuilder builder, Callable%3CT%3E supplier, final boolean useCacheManager)%0A%09%7B%0A
 %09%09this.suppl
@@ -2992,16 +2992,44 @@
 ild();%0A%0A
+%09%09if (useCacheManager)%0A%09%09%7B%0A%09
 %09%09// Ass
@@ -3080,16 +3080,17 @@
 nager%0A%09%09
+%09
 setName(
@@ -3119,16 +3119,20 @@
 ache%22);%0A
+%09%09%7D%0A
 %09%7D%0A%0A%0A%09/*

Add constructor flag to allow a SingleItemCache not to be registered with the CacheManager (designed for caches where they should not appear centrally in a manually-clearable cache - e.g. transient caches which live for the duration of some operation and would otherwise be registered and disappear at a high rate)