/** * PermissionsEx * Copyright (C) zml and PermissionsEx contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ninja.leaping.permissionsex.data; import com.github.benmanes.caffeine.cache.AsyncLoadingCache; import com.github.benmanes.caffeine.cache.Caffeine; import com.google.common.base.Preconditions; import ninja.leaping.permissionsex.backend.DataStore; import ninja.leaping.permissionsex.rank.RankLadder; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; public class RankLadderCache { private final DataStore dataStore; private final AsyncLoadingCache<String, RankLadder> cache; private final Map<String, Caching<RankLadder>> cacheHolders = new ConcurrentHashMap<>(); private final CacheListenerHolder<String, RankLadder> listeners; public RankLadderCache(final DataStore dataStore) { this(null, dataStore); } public RankLadderCache(final RankLadderCache existing, final DataStore dataStore) { this.dataStore = dataStore; cache = Caffeine.newBuilder() .maximumSize(256) .buildAsync(((key, executor) -> dataStore.getRankLadder(key, clearListener(key)))); if (existing != null) { listeners = existing.listeners; existing.cache.synchronous().asMap().forEach((key, rankLadder) -> { get(key, null).thenAccept(data -> listeners.call(key, data)); }); } else { listeners = new CacheListenerHolder<>(); } } public CompletableFuture<RankLadder> get(String identifier, Caching<RankLadder> listener) { Preconditions.checkNotNull(identifier, "identifier"); CompletableFuture<RankLadder> ret = cache.get(identifier); ret.thenRun(() -> { if (listener != null) { listeners.addListener(identifier, listener); } }); return ret; } public CompletableFuture<RankLadder> update(String identifier, Function<RankLadder, RankLadder> updateFunc) { return cache.get(identifier) .thenCompose(oldLadder -> { RankLadder newLadder = updateFunc.apply(oldLadder); if (oldLadder == newLadder) { return CompletableFuture.completedFuture(newLadder); } return set(identifier, newLadder); }); } public void load(String identifier) { Preconditions.checkNotNull(identifier, "identifier"); cache.synchronous().refresh(identifier); } public void invalidate(String identifier) { Preconditions.checkNotNull(identifier, "identifier"); cache.synchronous().invalidate(identifier); cacheHolders.remove(identifier); listeners.removeAll(identifier); } public CompletableFuture<Boolean> has(String identifier) { Preconditions.checkNotNull(identifier, "identifier"); if (cache.synchronous().getIfPresent(identifier) != null) { return CompletableFuture.completedFuture(true); } else { return dataStore.hasRankLadder(identifier); } } public CompletableFuture<RankLadder> set(String identifier, RankLadder newData) { Preconditions.checkNotNull(identifier, "identifier"); return dataStore.setRankLadder(identifier, newData); } private Caching<RankLadder> clearListener(final String name) { Caching<RankLadder> ret = newData -> { cache.synchronous().put(name, newData); listeners.call(name, newData); }; cacheHolders.put(name, ret); return ret; } public void addListener(String identifier, Caching<RankLadder> listener) { Preconditions.checkNotNull(identifier, "identifier"); Preconditions.checkNotNull(listener, "listener"); listeners.addListener(identifier, listener); } public Iterable<String> getAll() { return dataStore.getAllRankLadders(); } }