/**
* 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.google.common.collect.MapMaker;
import com.google.common.collect.Maps;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
public class SubjectDataReference implements Caching<ImmutableSubjectData> {
private final String identifier;
private final SubjectCache cache;
private final Set<Consumer<ImmutableSubjectData>> updateListeners;
final AtomicReference<ImmutableSubjectData> data = new AtomicReference<>();
private final boolean strongListeners;
/**
* Create a new reference to subject data
* Instances are accessible through a {@link SubjectCache} instance
*
* @param identifier The subject's identifier
* @param cache The cache to get data from and listen for changes from
* @param strongListeners Whether or not to hold strong references to listeners registered
*/
SubjectDataReference(String identifier, SubjectCache cache, boolean strongListeners) {
this.identifier = identifier;
this.cache = cache;
this.strongListeners = strongListeners;
if (strongListeners) {
updateListeners = Collections.newSetFromMap(new ConcurrentHashMap<>());
} else {
updateListeners = Collections.newSetFromMap(new MapMaker().weakKeys().concurrencyLevel(10).makeMap());
}
}
public ImmutableSubjectData get() {
return this.data.get();
}
public CompletableFuture<Change<ImmutableSubjectData>> update(Function<ImmutableSubjectData, ImmutableSubjectData> modifierFunc) {
ImmutableSubjectData data, newData;
do {
data = get();
newData = modifierFunc.apply(data);
if (newData == data) {
return CompletableFuture.completedFuture(new Change<>(data, newData));
}
} while (!this.data.compareAndSet(data, newData));
final ImmutableSubjectData finalData = data;
return this.cache.set(this.identifier, newData).thenApply(finalNew -> new Change<>(finalData, finalNew));
}
@Override
public void clearCache(ImmutableSubjectData newData) {
synchronized (data) {
this.data.set(newData);
this.updateListeners.forEach(cb -> cb.accept(newData));
}
}
/**
* Get whether or not this reference will hold strong references to stored listeners.
* If the return value is false, registering a listener object with this reference will
* not prevent it from being garbage collected, so the listener must be held somewhere
* else for it to continue being called.
* @return Whether or not listeners are held strongly.
*/
public boolean holdsListenersStrongly() {
return this.strongListeners;
}
/**
* Register a listener to be called when an update is performed
* @param listener The listener to register
*/
public void onUpdate(Consumer<ImmutableSubjectData> listener) {
updateListeners.add(listener);
}
public SubjectCache getCache() {
return cache;
}
public Map.Entry<String, String> getIdentifier() {
return Maps.immutableEntry(getCache().getType(), this.identifier);
}
}