/** * 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.sponge; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import ninja.leaping.permissionsex.subject.CalculatedSubject; import ninja.leaping.permissionsex.exception.PermissionsLoadingException; import org.spongepowered.api.Sponge; import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.service.permission.Subject; import org.spongepowered.api.service.context.Context; import org.spongepowered.api.service.context.ContextCalculator; import org.spongepowered.api.util.Tristate; import org.spongepowered.api.util.annotation.NonnullByDefault; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; import static ninja.leaping.permissionsex.sponge.PEXSubjectData.parSet; /** * Permissions subject implementation */ @NonnullByDefault class PEXSubject implements Subject { private final PEXSubjectCollection collection; private final PEXSubjectData data; private final PEXSubjectData transientData; private volatile CalculatedSubject baked; private final String identifier; private final AtomicReference<ActiveContextsHolder> cachedContexts = new AtomicReference<>(); private static class ActiveContextsHolder { private final int updateTicks; private final Set<Context> contexts; private ActiveContextsHolder(int updateTicks, Set<Context> contexts) { this.updateTicks = updateTicks; this.contexts = contexts; } public int getUpdateTicks() { return updateTicks; } public Set<Context> getContexts() { return contexts; } } public PEXSubject(String identifier, PEXSubjectCollection collection) throws ExecutionException, PermissionsLoadingException { this.identifier = identifier; this.collection = collection; this.baked = collection.getCalculatedSubject(identifier); this.data = new PEXSubjectData(baked.data(), collection.getPlugin()); this.transientData = new PEXSubjectData(baked.transientData(), collection.getPlugin()); } private Timings time() { return collection.getPlugin().getTimings(); } @Override public String getIdentifier() { return identifier; } private String identifyUser() { final Optional<CommandSource> source = getCommandSource(); return getIdentifier() + (source.isPresent() ? "/" + source.get().getName() : ""); } public CalculatedSubject getBaked() { return this.baked; } @Override public Optional<CommandSource> getCommandSource() { return getContainingCollection().getCommandSource(this.identifier); } @Override public PEXSubjectCollection getContainingCollection() { return this.collection; } @Override public PEXSubjectData getSubjectData() { return data; } @Override public PEXSubjectData getTransientSubjectData() { return transientData; } @Override public Optional<String> getOption(Set<Context> contexts, String key) { time().onGetOption().startTimingIfSync(); try { Preconditions.checkNotNull(contexts, "contexts"); Preconditions.checkNotNull(key, "key"); return baked.getOption(parSet(contexts), key); } finally { time().onGetOption().stopTimingIfSync(); } } @Override public boolean hasPermission(Set<Context> contexts, String permission) { return getPermissionValue(contexts, permission).asBoolean(); } @Override public Tristate getPermissionValue(Set<Context> contexts, String permission) { time().onGetPermission().startTimingIfSync(); try { Preconditions.checkNotNull(contexts, "contexts"); Preconditions.checkNotNull(permission, "permission"); int ret = baked.getPermission(parSet(contexts), permission); return ret == 0 ? Tristate.UNDEFINED : ret > 0 ? Tristate.TRUE : Tristate.FALSE; } finally { time().onGetPermission().stopTimingIfSync(); } } @Override public boolean isChildOf(Set<Context> contexts, Subject parent) { Preconditions.checkNotNull(contexts, "contexts"); Preconditions.checkNotNull(parent, "parent"); return getParents(contexts).contains(parent); } @Override public Set<Context> getActiveContexts() { time().onGetActiveContexts().startTimingIfSync(); try { int ticks; ActiveContextsHolder holder, newHolder; do { ticks = Sponge.getGame().getServer().getRunningTimeTicks(); holder = this.cachedContexts.get(); if (holder != null && ticks == holder.getUpdateTicks()) { return holder.getContexts(); } Set<Context> set = new HashSet<>(); for (ContextCalculator<Subject> calc : this.collection.getPlugin().getContextCalculators()) { calc.accumulateContexts(this, set); } newHolder = new ActiveContextsHolder(ticks, ImmutableSet.copyOf(set)); } while (!this.cachedContexts.compareAndSet(holder, newHolder)); return newHolder.getContexts(); } finally { time().onGetActiveContexts().stopTimingIfSync(); } } @Override public List<Subject> getParents(final Set<Context> contexts) { time().onGetParents().startTimingIfSync(); try { Preconditions.checkNotNull(contexts, "contexts"); return Lists.transform(baked.getParents(parSet(contexts)), input -> collection.getPlugin().getSubjects(input.getKey()).get(input.getValue())); } finally { time().onGetParents().stopTimingIfSync(); } } @Override public boolean equals(Object other) { if (!(other instanceof PEXSubject)) { return false; } PEXSubject otherSubj = (PEXSubject) other; return this.identifier.equals(otherSubj.identifier) && this.data.equals(otherSubj.data); } }