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.subject; 018 019import ca.stellardrift.permissionsex.subject.ImmutableSubjectData; 020import ca.stellardrift.permissionsex.subject.SubjectRef; 021import ca.stellardrift.permissionsex.subject.SubjectType; 022import ca.stellardrift.permissionsex.util.Change; 023import com.github.benmanes.caffeine.cache.Caffeine; 024 025import java.util.Collections; 026import java.util.Set; 027import java.util.concurrent.CompletableFuture; 028import java.util.concurrent.ConcurrentHashMap; 029import java.util.concurrent.atomic.AtomicReference; 030import java.util.function.Consumer; 031import java.util.function.UnaryOperator; 032 033/** 034 * An automatically updating reference to the latest data for a certain subject. 035 * 036 * @since 2.0.0 037 */ 038public class ToDataSubjectRefImpl<I> implements Consumer<ImmutableSubjectData>, SubjectRef.ToData<I> { 039 private final I identifier; 040 private final SubjectDataCacheImpl<I> cache; 041 private final Set<Consumer<ImmutableSubjectData>> updateListeners; 042 final AtomicReference<ImmutableSubjectData> data = new AtomicReference<>(); 043 private final boolean strongListeners; 044 045 /** 046 * Create a new reference to subject data. 047 * 048 * <p>Instances are accessible through a {@link SubjectDataCacheImpl} instance.</p> 049 * 050 * @param identifier The subject's identifier 051 * @param cache The cache to get data from and listen for changes from 052 * @param strongListeners Whether or not to hold strong references to listeners registered 053 */ 054 ToDataSubjectRefImpl(final I identifier, final SubjectDataCacheImpl<I> cache, final boolean strongListeners) { 055 this.identifier = identifier; 056 this.cache = cache; 057 this.strongListeners = strongListeners; 058 if (strongListeners) { 059 this.updateListeners = ConcurrentHashMap.newKeySet(); 060 } else { 061 this.updateListeners = Collections.newSetFromMap(Caffeine.newBuilder().weakKeys().<Consumer<ImmutableSubjectData>, Boolean>build().asMap()); 062 } 063 } 064 065 @Override 066 public SubjectType<I> type() { 067 return this.cache.type(); 068 } 069 070 @Override 071 public I identifier() { 072 return this.identifier; 073 } 074 075 @Override 076 public ImmutableSubjectData get() { 077 return this.data.get(); 078 } 079 080 @Override 081 public CompletableFuture<Change<ImmutableSubjectData>> update(UnaryOperator<ImmutableSubjectData> modifierFunc) { 082 ImmutableSubjectData data, newData; 083 do { 084 data = get(); 085 086 newData = modifierFunc.apply(data); 087 if (newData == data) { 088 return CompletableFuture.completedFuture(Change.of(data, newData)); 089 } 090 } while (!this.data.compareAndSet(data, newData)); 091 final ImmutableSubjectData finalData = data; 092 return this.cache.set(this.identifier, newData).thenApply(finalNew -> Change.of(finalData, finalNew)); 093 } 094 095 @Override 096 public void accept(ImmutableSubjectData newData) { 097 synchronized (data) { 098 this.data.set(newData); 099 this.updateListeners.forEach(cb -> cb.accept(newData)); 100 } 101 } 102 103 @Override 104 public boolean holdsListenersStrongly() { 105 return this.strongListeners; 106 } 107 108 @Override 109 public void onUpdate(Consumer<ImmutableSubjectData> listener) { 110 updateListeners.add(listener); 111 } 112 113 /** 114 * Get the cache this subject is held in. 115 * 116 * @return The cache holding this data 117 */ 118 public SubjectDataCacheImpl<I> getCache() { 119 return cache; 120 } 121 122 @Override 123 public CompletableFuture<Boolean> isRegistered() { 124 return getCache().isRegistered(this.identifier); 125 } 126 127 @Override 128 public CompletableFuture<ImmutableSubjectData> remove() { 129 return getCache().remove(this.identifier); 130 } 131}