package br.ufrgs.inf.prosoft.aplcache.caching;
import br.ufrgs.inf.prosoft.cache.Caffeine;
import br.ufrgs.inf.prosoft.cache.KeyNotFoundException;
import br.ufrgs.inf.prosoft.jsonserialiser.JSONSerialiser;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class APLCache<V> {
private static final Logger LOGGER = Logger.getLogger(APLCache.class.getName());
private static final String APLCACHE_LOG = System.getenv("APLCACHE_LOG") != null && !System.getenv("APLCACHE_LOG").isEmpty() && !System.getenv("APLCACHE_LOG").equals("null")
? System.getenv("APLCACHE_LOG") : null;
private static final Map<String, Map<String, Long>> APLCACHE_METHOD_HAS_CACHEABLE_PARAMETERS = new HashMap<>();
private static String APLCACHE_CACHEABLE_PARAMETERS = System.getenv("APLCACHE_CACHEABLE_PARAMETERS");
private final Caffeine<String, V> caffeine;
public APLCache() {
this.caffeine = new Caffeine<>();
}
public APLCache(String name) {
this.caffeine = new Caffeine<>(name);
}
public APLCache(long ttl) {
this.caffeine = new Caffeine<>(ttl);
}
public APLCache(String name, long ttl) {
this.caffeine = new Caffeine<>(name, ttl);
}
private synchronized static void loadCacheableInputs() {
if (APLCACHE_CACHEABLE_PARAMETERS == null) return;
try (FileReader fileReader = new FileReader(APLCACHE_CACHEABLE_PARAMETERS)) {
JsonParser jsonParser = new JsonParser();
JsonObject jsonObject = jsonParser.parse(fileReader).getAsJsonObject();
jsonObject.entrySet().forEach(entry -> {
Map<String, Long> inputsHasTTL = new HashMap<>();
if (entry.getValue().isJsonArray()) entry.getValue().getAsJsonArray().forEach(parameter -> inputsHasTTL.put(parameter.getAsString(), 0L));
else entry.getValue().getAsJsonObject().entrySet().forEach(innerEntry -> inputsHasTTL.put(innerEntry.getKey(), innerEntry.getValue().getAsLong()));
APLCACHE_METHOD_HAS_CACHEABLE_PARAMETERS.put(entry.getKey(), inputsHasTTL);
});
LOGGER.log(Level.INFO, "cacheable inputs file loaded");
} catch (IOException ex) {
LOGGER.log(Level.SEVERE, "invalid cacheable inputs file");
}
APLCACHE_CACHEABLE_PARAMETERS = null;
}
private static void log(String message) {
if (APLCACHE_LOG == null) return;
try (FileWriter fileWriter = new FileWriter(APLCACHE_LOG, true)) {
fileWriter.write(message + "\n");
} catch (IOException ignored) {
}
}
public boolean isCacheable(String method, String serialisedParameters) {
loadCacheableInputs();
if (APLCACHE_METHOD_HAS_CACHEABLE_PARAMETERS.isEmpty()) {
LOGGER.log(Level.WARNING, "no method is cacheable");
return false;
}
Map<String, Long> cacheableInputsHasTTL = APLCACHE_METHOD_HAS_CACHEABLE_PARAMETERS.get(method);
if (cacheableInputsHasTTL == null) {
LOGGER.log(Level.WARNING, "method not cacheable: {0}", method);
return false;
}
if (cacheableInputsHasTTL.containsKey(serialisedParameters)) return true;
log(this.caffeine.getCachePerformance().getName() + " : " + serialisedParameters);
return false;
}
@Deprecated
public long getTTLforInput(String method, String input) {
loadCacheableInputs();
if (APLCACHE_METHOD_HAS_CACHEABLE_PARAMETERS.isEmpty()) {
LOGGER.log(Level.WARNING, "no method is cacheable");
return 0;
}
Map<String, Long> cacheableInputsHasTTL = APLCACHE_METHOD_HAS_CACHEABLE_PARAMETERS.get(method);
if (cacheableInputsHasTTL == null) {
LOGGER.log(Level.WARNING, "method not cacheable: {0}", method);
return 0;
}
Long recommendedTTL = cacheableInputsHasTTL.get(input);
if (recommendedTTL == null) return 0;
return recommendedTTL;
}
public V computeIfAbsent(Thread currentThread, Object[] parameters, Supplier<V> supplier, long timeToLive) {
return computeIfAbsent(currentThread.getStackTrace()[2], parameters, supplier, timeToLive);
}
public V computeIfAbsent(Thread currentThread, Object[] parameters, Supplier<V> supplier) {
return computeIfAbsent(currentThread.getStackTrace()[2], parameters, supplier, 0);
}
public V computeIfAbsent(StackTraceElement stackTraceElement, Object[] parameters, Supplier<V> supplier, long timeToLive) {
String methodName = stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName();
methodName = methodName.replace("$", ".");
return computeIfAbsent(methodName, parameters, supplier, timeToLive);
}
public V computeIfAbsent(String methodName, Object[] parameters, Supplier<V> supplier) {
return computeIfAbsent(methodName, parameters, supplier, 0);
}
public V computeIfAbsent(String methodName, Object[] parameters, Supplier<V> supplier, long timeToLive) {
String serialisedParameters = Stream.of(parameters).map(JSONSerialiser::serialise).collect(Collectors.joining(",", "[", "]"));
synchronized (serialisedParameters) {
try {
return this.caffeine.get(serialisedParameters);
} catch (KeyNotFoundException ex) {
V get = supplier.get();
if (isCacheable(methodName, serialisedParameters)) {
if (timeToLive != 0) this.caffeine.put(serialisedParameters, get, timeToLive);
else this.caffeine.put(serialisedParameters, get);
}
return get;
}
}
}
}