001/* 002 * PermissionsEx 003 * Copyright (C) zml and PermissionsEx contributors 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package ca.stellardrift.permissionsex.impl.rank; 018 019import ca.stellardrift.permissionsex.datastore.DataStore; 020import ca.stellardrift.permissionsex.impl.util.CacheListenerHolder; 021import ca.stellardrift.permissionsex.rank.RankLadder; 022import com.github.benmanes.caffeine.cache.AsyncLoadingCache; 023import com.github.benmanes.caffeine.cache.Caffeine; 024import org.checkerframework.checker.nullness.qual.Nullable; 025 026import java.util.Map; 027import java.util.Objects; 028import java.util.concurrent.CompletableFuture; 029import java.util.concurrent.ConcurrentHashMap; 030import java.util.function.Consumer; 031import java.util.function.UnaryOperator; 032import java.util.stream.Stream; 033 034/** 035 * Access information about rank ladders. 036 */ 037public class RankLadderCache implements ca.stellardrift.permissionsex.rank.RankLadderCollection { 038 private final DataStore dataStore; 039 private final AsyncLoadingCache<String, RankLadder> cache; 040 private final Map<String, Consumer<RankLadder>> cacheHolders = new ConcurrentHashMap<>(); 041 private final CacheListenerHolder<String, RankLadder> listeners; 042 043 public RankLadderCache(final DataStore dataStore) { 044 this(null, dataStore); 045 } 046 047 public RankLadderCache(final @Nullable RankLadderCache existing, final DataStore dataStore) { 048 this.dataStore = dataStore; 049 cache = Caffeine.newBuilder() 050 .maximumSize(256) 051 .buildAsync((key, executor) -> dataStore.getRankLadder(key, clearListener(key))); 052 if (existing != null) { 053 listeners = existing.listeners; 054 existing.cache.synchronous().asMap().forEach((key, rankLadder) -> { 055 get(key, null).thenAccept(data -> listeners.call(key, data)); 056 }); 057 } else { 058 listeners = new CacheListenerHolder<>(); 059 } 060 } 061 062 @Override 063 public CompletableFuture<RankLadder> get(final String identifier, final @Nullable Consumer<RankLadder> listener) { 064 Objects.requireNonNull(identifier, "identifier"); 065 066 CompletableFuture<RankLadder> ret = cache.get(identifier); 067 ret.thenRun(() -> { 068 if (listener != null) { 069 listeners.addListener(identifier, listener); 070 } 071 }); 072 return ret; 073 } 074 075 @Override 076 public CompletableFuture<RankLadder> update(final String identifier, final UnaryOperator<RankLadder> updateFunc) { 077 return cache.get(identifier) 078 .thenCompose(oldLadder -> { 079 RankLadder newLadder = updateFunc.apply(oldLadder); 080 if (oldLadder == newLadder) { 081 return CompletableFuture.completedFuture(newLadder); 082 } 083 return set(identifier, newLadder); 084 085 }); 086 } 087 088 public void load(final String identifier) { 089 Objects.requireNonNull(identifier, "identifier"); 090 cache.synchronous().refresh(identifier); 091 } 092 093 public void invalidate(final String identifier) { 094 Objects.requireNonNull(identifier, "identifier"); 095 096 cache.synchronous().invalidate(identifier); 097 cacheHolders.remove(identifier); 098 listeners.removeAll(identifier); 099 } 100 101 @Override 102 public CompletableFuture<Boolean> has(String identifier) { 103 Objects.requireNonNull(identifier, "identifier"); 104 105 if (cache.synchronous().getIfPresent(identifier) != null) { 106 return CompletableFuture.completedFuture(true); 107 } else { 108 return dataStore.hasRankLadder(identifier); 109 } 110 } 111 112 @Override 113 public CompletableFuture<RankLadder> set(String identifier, RankLadder newData) { 114 Objects.requireNonNull(identifier, "identifier"); 115 116 return dataStore.setRankLadder(identifier, newData); 117 } 118 119 private Consumer<RankLadder> clearListener(final String name) { 120 Consumer<RankLadder> ret = newData -> { 121 cache.synchronous().put(name, newData); 122 listeners.call(name, newData); 123 }; 124 cacheHolders.put(name, ret); 125 return ret; 126 } 127 128 @Override 129 public void addListener(String identifier, Consumer<RankLadder> listener) { 130 Objects.requireNonNull(identifier, "identifier"); 131 Objects.requireNonNull(listener, "listener"); 132 133 listeners.addListener(identifier, listener); 134 } 135 136 @Override 137 public Stream<String> names() { 138 return dataStore.getAllRankLadders(); 139 } 140}