public class GuavaCacheUtil implements Cache {
    private static Logger log = LoggerFactory.getLogger(GuavaCacheUtil.class);
    private static final Object NULL_HOLDER = new NullHolder();
    private static final String DEFAULT_NAME = "system_cache";
    private final String name;
    private final com.google.common.cache.Cache<Object, Object> cache;
    private final boolean allowNullValues;
    private static GuavaCacheUtil guavaCacheUtil = new GuavaCacheUtil();
    public static GuavaCacheUtil builder() {
        return guavaCacheUtil;
    }
    private GuavaCacheUtil() {
        this(DEFAULT_NAME, CacheBuilder.newBuilder().maximumSize(100).expireAfterWrite(1, TimeUnit.HOURS).build());
    }
    private GuavaCacheUtil(String name, com.google.common.cache.Cache<Object, Object> cache) {
        this(name, cache, true);
    }
    private GuavaCacheUtil(String name, com.google.common.cache.Cache<Object, Object> cache, boolean allowNullValues) {
        Assert.notNull(name, "Name must not be null");
        Assert.notNull(cache, "Cache must not be null");
        this.name = name;
        this.cache = cache;
        this.allowNullValues = allowNullValues;
    }
    @Override
    public String getName() {
        return this.name;
    }
    @Override
    public Object getNativeCache() {
        return this.cache;
    }
    @Override
    public ValueWrapper get(Object key) {
        key = getKey(key.toString());
        Object value = this.cache.getIfPresent(key);
        log.info("getKey = {}, getObject = {}", key, JsonUtil.objectToJson(value));
        return toWrapper(value);
    }
    @Override
    public <T> T get(Object key, Class<T> aClass) {
        key = getKey(key.toString());
        T value = fromStoreValue(this.cache.getIfPresent(key), aClass);
        log.info("getKey = {}, getObject = {}", key, JsonUtil.objectToJson(value));
        return value;
    }
    @Override
    public <T> T get(Object o, Callable<T> callable) {
        return null;
    }
    @Override
    public void put(Object key, Object value) {
        key = getKey(key.toString());
        this.cache.put(key, toStoreValue(value));
        log.info("getKey = {}, getObject = {}", key, JsonUtil.objectToJson(value));
    }
    @Override
    public ValueWrapper putIfAbsent(Object key, Object value) {
        PutIfAbsentCallable callable = new PutIfAbsentCallable(value);
        try {
            Object result = this.cache.get(key, callable);
            return (callable.called ? null : toWrapper(result));
        } catch (ExecutionException e) {
            throw new IllegalStateException(e);
        }
    }
    @Override
    public void evict(Object key) {
        this.cache.invalidate(key);
        log.info("deleteKey = {}", key);
    }
    @Override
    public void clear() {
        this.cache.invalidateAll();
        log.info("clearAll");
    }
    private ValueWrapper toWrapper(Object value) {
        return (value != null ? new SimpleValueWrapper(fromStoreValue(value, null)) : null);
    }
    private static class NullHolder implements Serializable {
    }
    private Object toStoreValue(Object userValue) {
        if (this.allowNullValues && userValue == null) {
            return NULL_HOLDER;
        }
        return userValue;
    }
    private <T> T fromStoreValue(Object storeValue, Class<T> aClass) {
        if (this.allowNullValues && storeValue == NULL_HOLDER) {
            return null;
        }
        if (storeValue != null && aClass != null && !aClass.isInstance(storeValue)) {
            throw new IllegalStateException("Cached value is not of required type [" + aClass.getName() + "]: " + storeValue);
        }
        return (T) storeValue;
    }
    private class PutIfAbsentCallable implements Callable<Object> {
        private final Object value;
        private boolean called;
        public PutIfAbsentCallable(Object value) {
            this.value = value;
        }
        @Override
        public Object call() throws Exception {
            this.called = true;
            return toStoreValue(this.value);
        }
    }
    private String getKey(String key) {
        return name + ":" + key;
    }
}