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}