diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java index a7431aebc..f8e824bb4 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java @@ -43,7 +43,7 @@ import me.lucko.luckperms.bukkit.model.server.LPDefaultsMap; import me.lucko.luckperms.bukkit.model.server.LPPermissionMap; import me.lucko.luckperms.bukkit.model.server.LPSubscriptionMap; import me.lucko.luckperms.bukkit.vault.VaultHookManager; -import me.lucko.luckperms.common.calculators.CalculatorFactory; +import me.lucko.luckperms.common.calculators.PlatformCalculatorFactory; import me.lucko.luckperms.common.commands.CommandPermission; import me.lucko.luckperms.common.commands.sender.Sender; import me.lucko.luckperms.common.config.ConfigKeys; @@ -139,7 +139,7 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin { } @Override - protected CalculatorFactory provideCalculatorFactory() { + protected PlatformCalculatorFactory provideCalculatorFactory() { return new BukkitCalculatorFactory(this); } diff --git a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java index 4c51540ae..4ac213545 100644 --- a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java +++ b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java @@ -34,7 +34,7 @@ import me.lucko.luckperms.bungee.contexts.RedisBungeeCalculator; import me.lucko.luckperms.bungee.listeners.BungeeConnectionListener; import me.lucko.luckperms.bungee.listeners.BungeePermissionCheckListener; import me.lucko.luckperms.bungee.messaging.BungeeMessagingFactory; -import me.lucko.luckperms.common.calculators.CalculatorFactory; +import me.lucko.luckperms.common.calculators.PlatformCalculatorFactory; import me.lucko.luckperms.common.commands.CommandManager; import me.lucko.luckperms.common.commands.sender.Sender; import me.lucko.luckperms.common.config.adapter.ConfigurationAdapter; @@ -120,7 +120,7 @@ public class LPBungeePlugin extends AbstractLuckPermsPlugin { } @Override - protected CalculatorFactory provideCalculatorFactory() { + protected PlatformCalculatorFactory provideCalculatorFactory() { return new BungeeCalculatorFactory(this); } diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/AbstractCachedData.java b/common/src/main/java/me/lucko/luckperms/common/caching/AbstractCachedData.java new file mode 100644 index 000000000..e9dcdfdd7 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/caching/AbstractCachedData.java @@ -0,0 +1,388 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.caching; + +import com.github.benmanes.caffeine.cache.CacheLoader; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; + +import me.lucko.luckperms.api.ChatMetaType; +import me.lucko.luckperms.api.Contexts; +import me.lucko.luckperms.api.FullySatisfiedContexts; +import me.lucko.luckperms.api.caching.CachedData; +import me.lucko.luckperms.api.caching.MetaContexts; +import me.lucko.luckperms.common.caching.type.MetaAccumulator; +import me.lucko.luckperms.common.caching.type.MetaCache; +import me.lucko.luckperms.common.caching.type.PermissionCache; +import me.lucko.luckperms.common.calculators.CalculatorFactory; +import me.lucko.luckperms.common.calculators.PermissionCalculator; +import me.lucko.luckperms.common.calculators.PermissionCalculatorMetadata; +import me.lucko.luckperms.common.metastacking.SimpleMetaStack; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; + +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import javax.annotation.Nonnull; + +/** + * Abstract implementation of {@link CachedData}. + */ +public abstract class AbstractCachedData implements CachedData { + + /** + * The plugin instance + */ + protected final LuckPermsPlugin plugin; + + /** + * The cache used for {@link PermissionCache} instances. + */ + private final LoadingCache permission = Caffeine.newBuilder() + .expireAfterAccess(2, TimeUnit.MINUTES) + .build(new PermissionCacheLoader()); + + /** + * The cache used for {@link MetaCache} instances. + */ + private final LoadingCache meta = Caffeine.newBuilder() + .expireAfterAccess(2, TimeUnit.MINUTES) + .build(new MetaCacheLoader()); + + public AbstractCachedData(LuckPermsPlugin plugin) { + this.plugin = plugin; + } + + /** + * Returns a {@link PermissionCalculatorMetadata} instance for the given {@link Contexts}. + * + * @param contexts the contexts the permission calculator is for + * @return the metadata instance + */ + protected abstract PermissionCalculatorMetadata getMetadataForContexts(Contexts contexts); + + /** + * Gets the {@link CalculatorFactory} used to build {@link PermissionCalculator}s. + * + * @return the calculator factory + */ + protected abstract CalculatorFactory getCalculatorFactory(); + + /** + * Upgrades the given {@link Contexts} to a {@link MetaContexts} instance using the default settings. + * + * @param contexts the contexts to upgrade + * @return a meta contexts instance + */ + protected abstract MetaContexts getDefaultMetaContexts(Contexts contexts); + + /** + * Resolves the owners permissions data according to the specification + * outlined by {@link FullySatisfiedContexts}. + * + * @return a map of permissions to back the {@link PermissionCache} + */ + protected abstract Map resolvePermissions(); + + /** + * Resolves the owners permissions data in the given {@link Contexts}. + * + * @param contexts the contexts + * @return a map of permissions to back the {@link PermissionCache} + */ + protected abstract Map resolvePermissions(Contexts contexts); + + /** + * Resolves the owners meta data according to the specification + * outlined by {@link FullySatisfiedContexts}. + * + * @param accumulator the accumulator to add resolved meta to + */ + protected abstract void resolveMeta(MetaAccumulator accumulator); + + /** + * Resolves the owners meta data in the given {@link Contexts}. + * + * @param accumulator the accumulator to add resolved meta to + * @param contexts the contexts + */ + protected abstract void resolveMeta(MetaAccumulator accumulator, MetaContexts contexts); + + /** + * Calculates a {@link PermissionCache} instance. + * + * @param contexts the contexts to calculate in + * @param data an old data instance to try to reuse - ignored if null + * @return the calculated instance + */ + private PermissionCache calculatePermissions(Contexts contexts, PermissionCache data) { + Objects.requireNonNull(contexts, "contexts"); + + if (data == null) { + PermissionCalculatorMetadata metadata = getMetadataForContexts(contexts); + data = new PermissionCache(contexts, metadata, getCalculatorFactory()); + } + + if (contexts == Contexts.allowAll()) { + data.setPermissions(resolvePermissions()); + } else { + data.setPermissions(resolvePermissions(contexts)); + } + + return data; + } + + /** + * Calculates a {@link MetaCache} instance. + * + * @param contexts the contexts to calculate in + * @param data an old data instance to try to reuse - ignored if null + * @return the calculated instance + */ + private MetaCache calculateMeta(MetaContexts contexts, MetaCache data) { + Objects.requireNonNull(contexts, "contexts"); + + if (data == null) { + data = new MetaCache(contexts); + } + + MetaAccumulator accumulator = newAccumulator(contexts); + if (contexts.getContexts() == Contexts.allowAll()) { + resolveMeta(accumulator); + } else { + resolveMeta(accumulator, contexts); + } + data.loadMeta(accumulator); + + return data; + } + + @Nonnull + @Override + public PermissionCache getPermissionData(@Nonnull Contexts contexts) { + Objects.requireNonNull(contexts, "contexts"); + + //noinspection ConstantConditions + return this.permission.get(contexts); + } + + @Nonnull + @Override + public MetaCache getMetaData(@Nonnull MetaContexts contexts) { + Objects.requireNonNull(contexts, "contexts"); + + //noinspection ConstantConditions + return this.meta.get(contexts); + } + + @Nonnull + @Override + public MetaCache getMetaData(@Nonnull Contexts contexts) { + Objects.requireNonNull(contexts, "contexts"); + return getMetaData(getDefaultMetaContexts(contexts)); + } + + @Nonnull + @Override + public PermissionCache calculatePermissions(@Nonnull Contexts contexts) { + Objects.requireNonNull(contexts, "contexts"); + return calculatePermissions(contexts, null); + } + + @Nonnull + @Override + public MetaCache calculateMeta(@Nonnull MetaContexts contexts) { + Objects.requireNonNull(contexts, "contexts"); + return calculateMeta(contexts, null); + } + + @Nonnull + @Override + public MetaCache calculateMeta(@Nonnull Contexts contexts) { + Objects.requireNonNull(contexts, "contexts"); + return calculateMeta(getDefaultMetaContexts(contexts)); + } + + @Override + public void recalculatePermissions(@Nonnull Contexts contexts) { + Objects.requireNonNull(contexts, "contexts"); + this.permission.refresh(contexts); + } + + @Override + public void recalculateMeta(@Nonnull MetaContexts contexts) { + Objects.requireNonNull(contexts, "contexts"); + this.meta.refresh(contexts); + } + + @Override + public void recalculateMeta(@Nonnull Contexts contexts) { + Objects.requireNonNull(contexts, "contexts"); + recalculateMeta(getDefaultMetaContexts(contexts)); + } + + @Nonnull + @Override + public CompletableFuture reloadPermissions(@Nonnull Contexts contexts) { + Objects.requireNonNull(contexts, "contexts"); + + // get the previous value - to use when recalculating + PermissionCache previous = this.permission.getIfPresent(contexts); + + // invalidate the entry + this.permission.invalidate(contexts); + + // repopulate the cache + return CompletableFuture.supplyAsync(() -> this.permission.get(contexts, c -> calculatePermissions(c, previous))); + } + + @Nonnull + @Override + public CompletableFuture reloadMeta(@Nonnull MetaContexts contexts) { + Objects.requireNonNull(contexts, "contexts"); + + // get the previous value - to use when recalculating + MetaCache previous = this.meta.getIfPresent(contexts); + + // invalidate the entry + this.meta.invalidate(contexts); + + // repopulate the cache + return CompletableFuture.supplyAsync(() -> this.meta.get(contexts, c -> calculateMeta(c, previous))); + } + + @Nonnull + @Override + public CompletableFuture reloadMeta(@Nonnull Contexts contexts) { + Objects.requireNonNull(contexts, "contexts"); + return reloadMeta(getDefaultMetaContexts(contexts)); + } + + @Override + public void recalculatePermissions() { + Set keys = this.permission.asMap().keySet(); + keys.forEach(this::recalculatePermissions); + } + + @Override + public void recalculateMeta() { + Set keys = this.meta.asMap().keySet(); + keys.forEach(this::recalculateMeta); + } + + @Nonnull + @Override + public CompletableFuture reloadPermissions() { + Set keys = new HashSet<>(this.permission.asMap().keySet()); + return CompletableFuture.allOf(keys.stream().map(this::reloadPermissions).toArray(CompletableFuture[]::new)); + } + + @Nonnull + @Override + public CompletableFuture reloadMeta() { + Set keys = new HashSet<>(this.meta.asMap().keySet()); + return CompletableFuture.allOf(keys.stream().map(this::reloadMeta).toArray(CompletableFuture[]::new)); + } + + @Override + public void preCalculate(@Nonnull Contexts contexts) { + Objects.requireNonNull(contexts, "contexts"); + + // pre-calculate just by requesting the data from this cache. + // if the data isn't already loaded, it will be calculated. + getPermissionData(contexts); + getMetaData(contexts); + } + + @Override + public void invalidatePermissions(@Nonnull Contexts contexts) { + Objects.requireNonNull(contexts, "contexts"); + this.permission.invalidate(contexts); + } + + @Override + public void invalidateMeta(@Nonnull MetaContexts contexts) { + Objects.requireNonNull(contexts, "contexts"); + this.meta.invalidate(contexts); + } + + @Override + public void invalidateMeta(@Nonnull Contexts contexts) { + Objects.requireNonNull(contexts, "contexts"); + this.meta.invalidate(getDefaultMetaContexts(contexts)); + } + + @Override + public void invalidatePermissionCalculators() { + this.permission.asMap().values().forEach(PermissionCache::invalidateCache); + } + + public void invalidateCaches() { + this.permission.invalidateAll(); + this.meta.invalidateAll(); + } + + public void doCacheCleanup() { + this.permission.cleanUp(); + this.meta.cleanUp(); + } + + private final class PermissionCacheLoader implements CacheLoader { + @Override + public PermissionCache load(@Nonnull Contexts contexts) { + return calculatePermissions(contexts); + } + + @Override + public PermissionCache reload(@Nonnull Contexts contexts, @Nonnull PermissionCache oldData) { + return calculatePermissions(contexts, oldData); + } + } + + private final class MetaCacheLoader implements CacheLoader { + @Override + public MetaCache load(@Nonnull MetaContexts contexts) { + return calculateMeta(contexts); + } + + @Override + public MetaCache reload(@Nonnull MetaContexts contexts, @Nonnull MetaCache oldData) { + return calculateMeta(contexts, oldData); + } + } + + private static MetaAccumulator newAccumulator(MetaContexts contexts) { + return new MetaAccumulator( + new SimpleMetaStack(contexts.getPrefixStackDefinition(), ChatMetaType.PREFIX), + new SimpleMetaStack(contexts.getSuffixStackDefinition(), ChatMetaType.SUFFIX) + ); + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/GroupCachedData.java b/common/src/main/java/me/lucko/luckperms/common/caching/GroupCachedData.java index 9198c9428..27b91aa4a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/caching/GroupCachedData.java +++ b/common/src/main/java/me/lucko/luckperms/common/caching/GroupCachedData.java @@ -25,8 +25,11 @@ package me.lucko.luckperms.common.caching; +import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.caching.GroupData; +import me.lucko.luckperms.common.calculators.PermissionCalculatorMetadata; import me.lucko.luckperms.common.model.Group; +import me.lucko.luckperms.common.references.HolderType; /** * Holds an easily accessible cache of a groups's data in a number of contexts @@ -38,7 +41,7 @@ public class GroupCachedData extends HolderCachedData implements GroupDat } @Override - protected String getHolderName() { - return this.holder.getName(); + protected PermissionCalculatorMetadata getMetadataForContexts(Contexts contexts) { + return PermissionCalculatorMetadata.of(HolderType.GROUP, this.holder.getFriendlyName(), contexts.getContexts()); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/HolderCachedData.java b/common/src/main/java/me/lucko/luckperms/common/caching/HolderCachedData.java index 3906b0c41..f0c0e372b 100644 --- a/common/src/main/java/me/lucko/luckperms/common/caching/HolderCachedData.java +++ b/common/src/main/java/me/lucko/luckperms/common/caching/HolderCachedData.java @@ -25,305 +25,56 @@ package me.lucko.luckperms.common.caching; -import com.github.benmanes.caffeine.cache.CacheLoader; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.LoadingCache; - -import me.lucko.luckperms.api.ChatMetaType; import me.lucko.luckperms.api.Contexts; -import me.lucko.luckperms.api.caching.CachedData; import me.lucko.luckperms.api.caching.MetaContexts; import me.lucko.luckperms.common.caching.type.MetaAccumulator; -import me.lucko.luckperms.common.caching.type.MetaCache; -import me.lucko.luckperms.common.caching.type.PermissionCache; -import me.lucko.luckperms.common.calculators.PermissionCalculatorMetadata; -import me.lucko.luckperms.common.metastacking.SimpleMetaStack; +import me.lucko.luckperms.common.calculators.CalculatorFactory; import me.lucko.luckperms.common.model.PermissionHolder; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nonnull; +import java.util.Map; /** * Holds an easily accessible cache of a holders data in a number of contexts */ -public abstract class HolderCachedData implements CachedData { +public abstract class HolderCachedData extends AbstractCachedData { /** * The holder whom this data instance is representing */ protected final T holder; - /** - * The cache used for {@link PermissionCache} instances. - */ - private final LoadingCache permission = Caffeine.newBuilder() - .expireAfterAccess(2, TimeUnit.MINUTES) - .build(new PermissionCacheLoader()); - - /** - * The cache used for {@link MetaCache} instances. - */ - private final LoadingCache meta = Caffeine.newBuilder() - .expireAfterAccess(2, TimeUnit.MINUTES) - .build(new MetaCacheLoader()); - public HolderCachedData(T holder) { + super(holder.getPlugin()); this.holder = holder; } - protected abstract String getHolderName(); - - /** - * Calculates a {@link PermissionCache} instance. - * - * @param contexts the contexts to calculate in - * @param data an old data instance to try to reuse - ignored if null - * @return the calculated instance - */ - private PermissionCache calculatePermissions(Contexts contexts, PermissionCache data) { - Objects.requireNonNull(contexts, "contexts"); - - if (data == null) { - PermissionCalculatorMetadata metadata = PermissionCalculatorMetadata.of(this.holder.getType(), getHolderName(), contexts.getContexts()); - data = new PermissionCache(contexts, metadata, this.holder.getPlugin().getCalculatorFactory()); - } - - if (contexts == Contexts.allowAll()) { - data.setPermissions(this.holder.exportNodesAndShorthand(true)); - } else { - data.setPermissions(this.holder.exportNodesAndShorthand(contexts, true)); - } - - return data; - } - - /** - * Calculates a {@link MetaCache} instance. - * - * @param contexts the contexts to calculate in - * @param data an old data instance to try to reuse - ignored if null - * @return the calculated instance - */ - private MetaCache calculateMeta(MetaContexts contexts, MetaCache data) { - Objects.requireNonNull(contexts, "contexts"); - - if (data == null) { - data = new MetaCache(contexts); - } - - if (contexts.getContexts() == Contexts.allowAll()) { - data.loadMeta(this.holder.accumulateMeta(newAccumulator(contexts))); - } else { - data.loadMeta(this.holder.accumulateMeta(newAccumulator(contexts), contexts.getContexts())); - } - - return data; - } - - @Nonnull @Override - public PermissionCache getPermissionData(@Nonnull Contexts contexts) { - Objects.requireNonNull(contexts, "contexts"); - - //noinspection ConstantConditions - return this.permission.get(contexts); - } - - @Nonnull - @Override - public MetaCache getMetaData(@Nonnull MetaContexts contexts) { - Objects.requireNonNull(contexts, "contexts"); - - //noinspection ConstantConditions - return this.meta.get(contexts); - } - - @Nonnull - @Override - public MetaCache getMetaData(@Nonnull Contexts contexts) { - Objects.requireNonNull(contexts, "contexts"); - return getMetaData(this.holder.getPlugin().getContextManager().formMetaContexts(contexts)); - } - - @Nonnull - @Override - public PermissionCache calculatePermissions(@Nonnull Contexts contexts) { - Objects.requireNonNull(contexts, "contexts"); - return calculatePermissions(contexts, null); - } - - @Nonnull - @Override - public MetaCache calculateMeta(@Nonnull MetaContexts contexts) { - Objects.requireNonNull(contexts, "contexts"); - return calculateMeta(contexts, null); - } - - @Nonnull - @Override - public MetaCache calculateMeta(@Nonnull Contexts contexts) { - Objects.requireNonNull(contexts, "contexts"); - return calculateMeta(this.holder.getPlugin().getContextManager().formMetaContexts(contexts)); + protected CalculatorFactory getCalculatorFactory() { + return this.plugin.getCalculatorFactory(); } @Override - public void recalculatePermissions(@Nonnull Contexts contexts) { - Objects.requireNonNull(contexts, "contexts"); - this.permission.refresh(contexts); + protected MetaContexts getDefaultMetaContexts(Contexts contexts) { + return this.plugin.getContextManager().formMetaContexts(contexts); } @Override - public void recalculateMeta(@Nonnull MetaContexts contexts) { - Objects.requireNonNull(contexts, "contexts"); - this.meta.refresh(contexts); + protected Map resolvePermissions() { + return this.holder.exportNodesAndShorthand(true); } @Override - public void recalculateMeta(@Nonnull Contexts contexts) { - Objects.requireNonNull(contexts, "contexts"); - recalculateMeta(this.holder.getPlugin().getContextManager().formMetaContexts(contexts)); - } - - @Nonnull - @Override - public CompletableFuture reloadPermissions(@Nonnull Contexts contexts) { - Objects.requireNonNull(contexts, "contexts"); - - // get the previous value - to use when recalculating - PermissionCache previous = this.permission.getIfPresent(contexts); - - // invalidate the entry - this.permission.invalidate(contexts); - - // repopulate the cache - return CompletableFuture.supplyAsync(() -> this.permission.get(contexts, c -> calculatePermissions(c, previous))); - } - - @Nonnull - @Override - public CompletableFuture reloadMeta(@Nonnull MetaContexts contexts) { - Objects.requireNonNull(contexts, "contexts"); - - // get the previous value - to use when recalculating - MetaCache previous = this.meta.getIfPresent(contexts); - - // invalidate the entry - this.meta.invalidate(contexts); - - // repopulate the cache - return CompletableFuture.supplyAsync(() -> this.meta.get(contexts, c -> calculateMeta(c, previous))); - } - - @Nonnull - @Override - public CompletableFuture reloadMeta(@Nonnull Contexts contexts) { - Objects.requireNonNull(contexts, "contexts"); - return reloadMeta(this.holder.getPlugin().getContextManager().formMetaContexts(contexts)); + protected Map resolvePermissions(Contexts contexts) { + return this.holder.exportNodesAndShorthand(contexts, true); } @Override - public void recalculatePermissions() { - Set keys = this.permission.asMap().keySet(); - keys.forEach(this::recalculatePermissions); + protected void resolveMeta(MetaAccumulator accumulator) { + this.holder.accumulateMeta(accumulator); } @Override - public void recalculateMeta() { - Set keys = this.meta.asMap().keySet(); - keys.forEach(this::recalculateMeta); + protected void resolveMeta(MetaAccumulator accumulator, MetaContexts contexts) { + this.holder.accumulateMeta(accumulator, contexts.getContexts()); } - - @Nonnull - @Override - public CompletableFuture reloadPermissions() { - Set keys = new HashSet<>(this.permission.asMap().keySet()); - return CompletableFuture.allOf(keys.stream().map(this::reloadPermissions).toArray(CompletableFuture[]::new)); - } - - @Nonnull - @Override - public CompletableFuture reloadMeta() { - Set keys = new HashSet<>(this.meta.asMap().keySet()); - return CompletableFuture.allOf(keys.stream().map(this::reloadMeta).toArray(CompletableFuture[]::new)); - } - - @Override - public void preCalculate(@Nonnull Contexts contexts) { - Objects.requireNonNull(contexts, "contexts"); - - // pre-calculate just by requesting the data from this cache. - // if the data isn't already loaded, it will be calculated. - getPermissionData(contexts); - getMetaData(contexts); - } - - @Override - public void invalidatePermissions(@Nonnull Contexts contexts) { - Objects.requireNonNull(contexts, "contexts"); - this.permission.invalidate(contexts); - } - - @Override - public void invalidateMeta(@Nonnull MetaContexts contexts) { - Objects.requireNonNull(contexts, "contexts"); - this.meta.invalidate(contexts); - } - - @Override - public void invalidateMeta(@Nonnull Contexts contexts) { - Objects.requireNonNull(contexts, "contexts"); - this.meta.invalidate(this.holder.getPlugin().getContextManager().formMetaContexts(contexts)); - } - - @Override - public void invalidatePermissionCalculators() { - this.permission.asMap().values().forEach(PermissionCache::invalidateCache); - } - - public void invalidateCaches() { - this.permission.invalidateAll(); - this.meta.invalidateAll(); - } - - public void doCacheCleanup() { - this.permission.cleanUp(); - this.meta.cleanUp(); - } - - private final class PermissionCacheLoader implements CacheLoader { - @Override - public PermissionCache load(@Nonnull Contexts contexts) { - return calculatePermissions(contexts); - } - - @Override - public PermissionCache reload(@Nonnull Contexts contexts, @Nonnull PermissionCache oldData) { - return calculatePermissions(contexts, oldData); - } - } - - private final class MetaCacheLoader implements CacheLoader { - @Override - public MetaCache load(@Nonnull MetaContexts contexts) { - return calculateMeta(contexts); - } - - @Override - public MetaCache reload(@Nonnull MetaContexts contexts, @Nonnull MetaCache oldData) { - return calculateMeta(contexts, oldData); - } - } - - private static MetaAccumulator newAccumulator(MetaContexts contexts) { - return new MetaAccumulator( - new SimpleMetaStack(contexts.getPrefixStackDefinition(), ChatMetaType.PREFIX), - new SimpleMetaStack(contexts.getSuffixStackDefinition(), ChatMetaType.SUFFIX) - ); - } - } diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/UserCachedData.java b/common/src/main/java/me/lucko/luckperms/common/caching/UserCachedData.java index 19d89e41d..a0849b505 100644 --- a/common/src/main/java/me/lucko/luckperms/common/caching/UserCachedData.java +++ b/common/src/main/java/me/lucko/luckperms/common/caching/UserCachedData.java @@ -25,8 +25,11 @@ package me.lucko.luckperms.common.caching; +import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.caching.UserData; +import me.lucko.luckperms.common.calculators.PermissionCalculatorMetadata; import me.lucko.luckperms.common.model.User; +import me.lucko.luckperms.common.references.HolderType; /** * Holds an easily accessible cache of a user's data in a number of contexts @@ -38,7 +41,7 @@ public class UserCachedData extends HolderCachedData implements UserData { } @Override - protected String getHolderName() { - return this.holder.getFriendlyName(); + protected PermissionCalculatorMetadata getMetadataForContexts(Contexts contexts) { + return PermissionCalculatorMetadata.of(HolderType.USER, this.holder.getFriendlyName(), contexts.getContexts()); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/type/MetaAccumulator.java b/common/src/main/java/me/lucko/luckperms/common/caching/type/MetaAccumulator.java index 809354ad4..49372d2e4 100644 --- a/common/src/main/java/me/lucko/luckperms/common/caching/type/MetaAccumulator.java +++ b/common/src/main/java/me/lucko/luckperms/common/caching/type/MetaAccumulator.java @@ -91,6 +91,10 @@ public class MetaAccumulator { } } + public void accumulateMeta(String key, String value) { + this.meta.put(key, value); + } + public void accumulateWeight(int weight) { this.weight = Math.max(this.weight, weight); } diff --git a/common/src/main/java/me/lucko/luckperms/common/calculators/AbstractCalculatorFactory.java b/common/src/main/java/me/lucko/luckperms/common/calculators/AbstractCalculatorFactory.java index 9b9cf075f..854cbac1c 100644 --- a/common/src/main/java/me/lucko/luckperms/common/calculators/AbstractCalculatorFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/calculators/AbstractCalculatorFactory.java @@ -30,7 +30,7 @@ import com.google.common.collect.MapMaker; import java.util.Collections; import java.util.Set; -public abstract class AbstractCalculatorFactory implements CalculatorFactory { +public abstract class AbstractCalculatorFactory implements PlatformCalculatorFactory { private final Set calculators = Collections.newSetFromMap(new MapMaker().weakKeys().makeMap()); protected PermissionCalculator registerCalculator(PermissionCalculator calculator) { diff --git a/common/src/main/java/me/lucko/luckperms/common/calculators/CalculatorFactory.java b/common/src/main/java/me/lucko/luckperms/common/calculators/CalculatorFactory.java index 948ac2928..8ad36d707 100644 --- a/common/src/main/java/me/lucko/luckperms/common/calculators/CalculatorFactory.java +++ b/common/src/main/java/me/lucko/luckperms/common/calculators/CalculatorFactory.java @@ -41,9 +41,4 @@ public interface CalculatorFactory { */ PermissionCalculator build(Contexts contexts, PermissionCalculatorMetadata metadata); - /** - * Invalidates all calculators build by this factory - */ - void invalidateAll(); - } diff --git a/common/src/main/java/me/lucko/luckperms/common/calculators/PlatformCalculatorFactory.java b/common/src/main/java/me/lucko/luckperms/common/calculators/PlatformCalculatorFactory.java new file mode 100644 index 000000000..f6a28624f --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/calculators/PlatformCalculatorFactory.java @@ -0,0 +1,39 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.common.calculators; + +/** + * Extension of {@link CalculatorFactory} which keeps a record of produced + * calculators and provides a means to invalidate them all in bulk. + */ +public interface PlatformCalculatorFactory extends CalculatorFactory { + + /** + * Invalidates all calculators build by this factory + */ + void invalidateAll(); + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/metastacking/StandardStackElements.java b/common/src/main/java/me/lucko/luckperms/common/metastacking/StandardStackElements.java index d3d622539..423985b77 100644 --- a/common/src/main/java/me/lucko/luckperms/common/metastacking/StandardStackElements.java +++ b/common/src/main/java/me/lucko/luckperms/common/metastacking/StandardStackElements.java @@ -43,12 +43,12 @@ import javax.annotation.Nonnull; * Contains the standard {@link MetaStackElement}s provided by LuckPerms. */ public final class StandardStackElements { - private static final HighestPriority HIGHEST_PRIORITY = new HighestPriority(); - private static final LowestPriority LOWEST_PRIORITY = new LowestPriority(); - private static final HighestPriorityOwn HIGHEST_PRIORITY_OWN = new HighestPriorityOwn(); - private static final LowestPriorityOwn LOWEST_PRIORITY_OWN = new LowestPriorityOwn(); - private static final HighestPriorityInherited HIGHEST_PRIORITY_INHERITED = new HighestPriorityInherited(); - private static final LowestPriorityInherited LOWEST_PRIORITY_INHERITED = new LowestPriorityInherited(); + public static final HighestPriority HIGHEST_PRIORITY = new HighestPriority(); + public static final LowestPriority LOWEST_PRIORITY = new LowestPriority(); + public static final HighestPriorityOwn HIGHEST_PRIORITY_OWN = new HighestPriorityOwn(); + public static final LowestPriorityOwn LOWEST_PRIORITY_OWN = new LowestPriorityOwn(); + public static final HighestPriorityInherited HIGHEST_PRIORITY_INHERITED = new HighestPriorityInherited(); + public static final LowestPriorityInherited LOWEST_PRIORITY_INHERITED = new LowestPriorityInherited(); public static Optional parseFromString(LuckPermsPlugin plugin, String s) { s = s.toLowerCase(); diff --git a/common/src/main/java/me/lucko/luckperms/common/plugin/AbstractLuckPermsPlugin.java b/common/src/main/java/me/lucko/luckperms/common/plugin/AbstractLuckPermsPlugin.java index 507621ba8..3d6ee539e 100644 --- a/common/src/main/java/me/lucko/luckperms/common/plugin/AbstractLuckPermsPlugin.java +++ b/common/src/main/java/me/lucko/luckperms/common/plugin/AbstractLuckPermsPlugin.java @@ -32,7 +32,7 @@ import me.lucko.luckperms.common.api.LuckPermsApiProvider; import me.lucko.luckperms.common.buffers.BufferedRequest; import me.lucko.luckperms.common.buffers.UpdateTaskBuffer; import me.lucko.luckperms.common.caching.handlers.CachedStateManager; -import me.lucko.luckperms.common.calculators.CalculatorFactory; +import me.lucko.luckperms.common.calculators.PlatformCalculatorFactory; import me.lucko.luckperms.common.commands.sender.Sender; import me.lucko.luckperms.common.commands.utils.CommandUtils; import me.lucko.luckperms.common.config.AbstractConfiguration; @@ -80,7 +80,7 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin { private BufferedRequest updateTaskBuffer; private InheritanceHandler inheritanceHandler; private CachedStateManager cachedStateManager; - private CalculatorFactory calculatorFactory; + private PlatformCalculatorFactory calculatorFactory; private LuckPermsApiProvider apiProvider; private EventFactory eventFactory; @@ -231,7 +231,7 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin { protected abstract MessagingFactory provideMessagingFactory(); protected abstract void registerCommands(); protected abstract void setupManagers(); - protected abstract CalculatorFactory provideCalculatorFactory(); + protected abstract PlatformCalculatorFactory provideCalculatorFactory(); protected abstract void setupContextManager(); protected abstract void setupPlatformHooks(); protected abstract void registerApiOnPlatform(LuckPermsApi api); @@ -314,7 +314,7 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin { } @Override - public CalculatorFactory getCalculatorFactory() { + public PlatformCalculatorFactory getCalculatorFactory() { return this.calculatorFactory; } diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/LPNukkitPlugin.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/LPNukkitPlugin.java index 48d2a174f..2ad693786 100644 --- a/nukkit/src/main/java/me/lucko/luckperms/nukkit/LPNukkitPlugin.java +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/LPNukkitPlugin.java @@ -27,7 +27,7 @@ package me.lucko.luckperms.nukkit; import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.LuckPermsApi; -import me.lucko.luckperms.common.calculators.CalculatorFactory; +import me.lucko.luckperms.common.calculators.PlatformCalculatorFactory; import me.lucko.luckperms.common.commands.CommandPermission; import me.lucko.luckperms.common.commands.sender.Sender; import me.lucko.luckperms.common.config.ConfigKeys; @@ -133,7 +133,7 @@ public class LPNukkitPlugin extends AbstractLuckPermsPlugin { } @Override - protected CalculatorFactory provideCalculatorFactory() { + protected PlatformCalculatorFactory provideCalculatorFactory() { return new NukkitCalculatorFactory(this); } diff --git a/sponge/sponge-service/src/main/java/me/lucko/luckperms/sponge/service/model/LPPermissionService.java b/sponge/sponge-service/src/main/java/me/lucko/luckperms/sponge/service/model/LPPermissionService.java index b5a1350d7..c64727bdc 100644 --- a/sponge/sponge-service/src/main/java/me/lucko/luckperms/sponge/service/model/LPPermissionService.java +++ b/sponge/sponge-service/src/main/java/me/lucko/luckperms/sponge/service/model/LPPermissionService.java @@ -26,12 +26,10 @@ package me.lucko.luckperms.sponge.service.model; import com.google.common.collect.ImmutableCollection; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import me.lucko.luckperms.common.contexts.ContextManager; import me.lucko.luckperms.common.plugin.LuckPermsPlugin; -import me.lucko.luckperms.sponge.service.reference.LPSubjectReference; import me.lucko.luckperms.sponge.service.reference.SubjectReferenceFactory; import org.spongepowered.api.plugin.PluginContainer; @@ -40,7 +38,6 @@ import org.spongepowered.api.service.permission.PermissionService; import org.spongepowered.api.service.permission.Subject; import org.spongepowered.api.text.Text; -import java.util.Collection; import java.util.Optional; import java.util.function.Predicate; @@ -79,8 +76,5 @@ public interface LPPermissionService { void registerContextCalculator(ContextCalculator calculator); - // utils - ImmutableList sortSubjects(Collection s); - - void invalidateAllCaches(LPSubject.CacheLevel cacheLevel); + void invalidateAllCaches(); } diff --git a/sponge/sponge-service/src/main/java/me/lucko/luckperms/sponge/service/model/LPSubject.java b/sponge/sponge-service/src/main/java/me/lucko/luckperms/sponge/service/model/LPSubject.java index fc214a30f..df62769b5 100644 --- a/sponge/sponge-service/src/main/java/me/lucko/luckperms/sponge/service/model/LPSubject.java +++ b/sponge/sponge-service/src/main/java/me/lucko/luckperms/sponge/service/model/LPSubject.java @@ -82,17 +82,6 @@ public interface LPSubject { } - void invalidateCaches(CacheLevel cacheLevel); - - /** - * The level of cache for invalidation - * - * Invalidating at {@link #PARENT} will also invalidate at - * {@link #PERMISSION} and {@link #OPTION}, and invalidating at - * {@link #PERMISSION} will also invalidate at {@link #OPTION}. - */ - enum CacheLevel { - PARENT, PERMISSION, OPTION - } + void invalidateCaches(); } diff --git a/sponge/sponge-service/src/main/java/me/lucko/luckperms/sponge/service/model/LPSubjectCollection.java b/sponge/sponge-service/src/main/java/me/lucko/luckperms/sponge/service/model/LPSubjectCollection.java index ef38441eb..610d2deb5 100644 --- a/sponge/sponge-service/src/main/java/me/lucko/luckperms/sponge/service/model/LPSubjectCollection.java +++ b/sponge/sponge-service/src/main/java/me/lucko/luckperms/sponge/service/model/LPSubjectCollection.java @@ -52,6 +52,19 @@ public interface LPSubjectCollection { Predicate getIdentifierValidityPredicate(); + // transient has priority for all collections except default + default ResolutionOrder getResolutionOrder() { + if (isDefaultsCollection()) { + return ResolutionOrder.TRANSIENT_LAST; + } else { + return ResolutionOrder.TRANSIENT_FIRST; + } + } + + default boolean isDefaultsCollection() { + return false; + } + CompletableFuture loadSubject(String identifier); Optional getSubject(String identifier); diff --git a/sponge/sponge-service/src/main/java/me/lucko/luckperms/sponge/service/model/ResolutionOrder.java b/sponge/sponge-service/src/main/java/me/lucko/luckperms/sponge/service/model/ResolutionOrder.java new file mode 100644 index 000000000..c53bbfa00 --- /dev/null +++ b/sponge/sponge-service/src/main/java/me/lucko/luckperms/sponge/service/model/ResolutionOrder.java @@ -0,0 +1,33 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.sponge.service.model; + +public enum ResolutionOrder { + + TRANSIENT_FIRST, + TRANSIENT_LAST + +} diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java index 7fa4f97ae..2f9420fcd 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/LPSpongePlugin.java @@ -27,7 +27,7 @@ package me.lucko.luckperms.sponge; import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.LuckPermsApi; -import me.lucko.luckperms.common.calculators.CalculatorFactory; +import me.lucko.luckperms.common.calculators.PlatformCalculatorFactory; import me.lucko.luckperms.common.commands.CommandManager; import me.lucko.luckperms.common.commands.CommandPermission; import me.lucko.luckperms.common.commands.abstraction.Command; @@ -53,7 +53,6 @@ import me.lucko.luckperms.sponge.messaging.SpongeMessagingFactory; import me.lucko.luckperms.sponge.service.LuckPermsService; import me.lucko.luckperms.sponge.service.event.UpdateEventHandler; import me.lucko.luckperms.sponge.service.model.LPPermissionService; -import me.lucko.luckperms.sponge.service.model.LPSubject; import me.lucko.luckperms.sponge.service.model.LPSubjectCollection; import me.lucko.luckperms.sponge.service.persisted.PersistedCollection; import me.lucko.luckperms.sponge.tasks.ServiceCacheHousekeepingTask; @@ -134,7 +133,7 @@ public class LPSpongePlugin extends AbstractLuckPermsPlugin { } @Override - protected CalculatorFactory provideCalculatorFactory() { + protected PlatformCalculatorFactory provideCalculatorFactory() { return new SpongeCalculatorFactory(this); } @@ -197,7 +196,7 @@ public class LPSpongePlugin extends AbstractLuckPermsPlugin { ((PersistedCollection) collection).loadAll(); } } - this.service.invalidateAllCaches(LPSubject.CacheLevel.PARENT); + this.service.invalidateAllCaches(); } private Path resolveConfig() { diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/processors/DefaultsProcessor.java b/sponge/src/main/java/me/lucko/luckperms/sponge/processors/DefaultsProcessor.java index 77530e001..9f47b703e 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/processors/DefaultsProcessor.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/processors/DefaultsProcessor.java @@ -28,19 +28,19 @@ package me.lucko.luckperms.sponge.processors; import me.lucko.luckperms.api.Tristate; import me.lucko.luckperms.api.context.ImmutableContextSet; import me.lucko.luckperms.common.processors.PermissionProcessor; -import me.lucko.luckperms.sponge.service.LuckPermsService; +import me.lucko.luckperms.sponge.service.model.LPPermissionService; import me.lucko.luckperms.sponge.service.model.LPSubject; public abstract class DefaultsProcessor implements PermissionProcessor { - private final LuckPermsService service; + private final LPPermissionService service; private final ImmutableContextSet contexts; - public DefaultsProcessor(LuckPermsService service, ImmutableContextSet contexts) { + public DefaultsProcessor(LPPermissionService service, ImmutableContextSet contexts) { this.service = service; this.contexts = contexts; } - protected abstract LPSubject getTypeDefaults(LuckPermsService service); + protected abstract LPSubject getTypeDefaults(LPPermissionService service); @Override public Tristate hasPermission(String permission) { diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/processors/FixedDefaultsProcessor.java b/sponge/src/main/java/me/lucko/luckperms/sponge/processors/FixedDefaultsProcessor.java new file mode 100644 index 000000000..c294a8f88 --- /dev/null +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/processors/FixedDefaultsProcessor.java @@ -0,0 +1,44 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.sponge.processors; + +import me.lucko.luckperms.api.context.ImmutableContextSet; +import me.lucko.luckperms.sponge.service.model.LPPermissionService; +import me.lucko.luckperms.sponge.service.model.LPSubject; + +public class FixedDefaultsProcessor extends DefaultsProcessor { + private final LPSubject defaultsSubject; + + public FixedDefaultsProcessor(LPPermissionService service, ImmutableContextSet contexts, LPSubject defaultsSubject) { + super(service, contexts); + this.defaultsSubject = defaultsSubject; + } + + @Override + protected LPSubject getTypeDefaults(LPPermissionService service) { + return this.defaultsSubject; + } +} diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/processors/GroupDefaultsProcessor.java b/sponge/src/main/java/me/lucko/luckperms/sponge/processors/GroupDefaultsProcessor.java index 2f03b8ffd..e3d44527c 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/processors/GroupDefaultsProcessor.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/processors/GroupDefaultsProcessor.java @@ -27,16 +27,16 @@ package me.lucko.luckperms.sponge.processors; import me.lucko.luckperms.api.context.ImmutableContextSet; import me.lucko.luckperms.common.processors.PermissionProcessor; -import me.lucko.luckperms.sponge.service.LuckPermsService; +import me.lucko.luckperms.sponge.service.model.LPPermissionService; import me.lucko.luckperms.sponge.service.model.LPSubject; public class GroupDefaultsProcessor extends DefaultsProcessor implements PermissionProcessor { - public GroupDefaultsProcessor(LuckPermsService service, ImmutableContextSet contexts) { + public GroupDefaultsProcessor(LPPermissionService service, ImmutableContextSet contexts) { super(service, contexts); } @Override - protected LPSubject getTypeDefaults(LuckPermsService service) { + protected LPSubject getTypeDefaults(LPPermissionService service) { return service.getGroupSubjects().getDefaults(); } } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/processors/UserDefaultsProcessor.java b/sponge/src/main/java/me/lucko/luckperms/sponge/processors/UserDefaultsProcessor.java index cd5784544..7096c6d34 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/processors/UserDefaultsProcessor.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/processors/UserDefaultsProcessor.java @@ -27,16 +27,16 @@ package me.lucko.luckperms.sponge.processors; import me.lucko.luckperms.api.context.ImmutableContextSet; import me.lucko.luckperms.common.processors.PermissionProcessor; -import me.lucko.luckperms.sponge.service.LuckPermsService; +import me.lucko.luckperms.sponge.service.model.LPPermissionService; import me.lucko.luckperms.sponge.service.model.LPSubject; public class UserDefaultsProcessor extends DefaultsProcessor implements PermissionProcessor { - public UserDefaultsProcessor(LuckPermsService service, ImmutableContextSet contexts) { + public UserDefaultsProcessor(LPPermissionService service, ImmutableContextSet contexts) { super(service, contexts); } @Override - protected LPSubject getTypeDefaults(LuckPermsService service) { + protected LPSubject getTypeDefaults(LPPermissionService service) { return service.getUserSubjects().getDefaults(); } } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsService.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsService.java index 959ae03df..a34e2b025 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsService.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsService.java @@ -27,12 +27,10 @@ package me.lucko.luckperms.sponge.service; import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import me.lucko.luckperms.common.contexts.ContextManager; -import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.utils.Predicates; import me.lucko.luckperms.sponge.LPSpongePlugin; import me.lucko.luckperms.sponge.contexts.SpongeProxiedContextCalculator; @@ -43,7 +41,6 @@ import me.lucko.luckperms.sponge.service.model.LPPermissionService; import me.lucko.luckperms.sponge.service.model.LPSubject; import me.lucko.luckperms.sponge.service.model.LPSubjectCollection; import me.lucko.luckperms.sponge.service.persisted.PersistedCollection; -import me.lucko.luckperms.sponge.service.reference.LPSubjectReference; import me.lucko.luckperms.sponge.service.reference.SubjectReferenceFactory; import me.lucko.luckperms.sponge.service.storage.SubjectStorage; @@ -54,11 +51,7 @@ import org.spongepowered.api.service.permission.Subject; import org.spongepowered.api.text.Text; import java.io.File; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -216,56 +209,14 @@ public class LuckPermsService implements LPPermissionService { } @Override - public ImmutableList sortSubjects(Collection s) { - List ret = new ArrayList<>(s); - ret.sort(Collections.reverseOrder((o1, o2) -> { - if (o1.equals(o2)) { - return 0; - } - - boolean o1isGroup = o1.getCollectionIdentifier().equals(PermissionService.SUBJECTS_GROUP); - boolean o2isGroup = o2.getCollectionIdentifier().equals(PermissionService.SUBJECTS_GROUP); - - if (o1isGroup != o2isGroup) { - return o1isGroup ? 1 : -1; - } - - // Neither are groups - if (!o1isGroup) { - return 1; - } - - Group g1 = this.plugin.getGroupManager().getIfLoaded(o1.getSubjectIdentifier()); - Group g2 = this.plugin.getGroupManager().getIfLoaded(o2.getSubjectIdentifier()); - - boolean g1Null = g1 == null; - boolean g2Null = g2 == null; - - if (g1Null != g2Null) { - return g1Null ? -1 : 1; - } - - // Both are null - if (g1Null) { - return 1; - } - - return Integer.compare(g1.getWeight().orElse(0), g2.getWeight().orElse(0)) == 1 ? 1 : -1; - })); - return ImmutableList.copyOf(ret); - } - - @Override - public void invalidateAllCaches(LPSubject.CacheLevel cacheLevel) { + public void invalidateAllCaches() { for (LPSubjectCollection collection : this.collections.asMap().values()) { for (LPSubject subject : collection.getLoadedSubjects()) { - subject.invalidateCaches(cacheLevel); + subject.invalidateCaches(); } } - if (cacheLevel != LPSubject.CacheLevel.OPTION) { - this.plugin.getCalculatorFactory().invalidateAll(); - } + this.plugin.getCalculatorFactory().invalidateAll(); } } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/SubjectComparator.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/SubjectComparator.java new file mode 100644 index 000000000..2c5633848 --- /dev/null +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/SubjectComparator.java @@ -0,0 +1,71 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.sponge.service; + +import me.lucko.luckperms.common.model.Group; +import me.lucko.luckperms.sponge.service.internal.GroupSubject; +import me.lucko.luckperms.sponge.service.reference.LPSubjectReference; + +import org.spongepowered.api.service.permission.PermissionService; + +import java.util.Comparator; + +public class SubjectComparator implements Comparator { + private static final Comparator INSTANCE = new SubjectComparator(); + private static final Comparator REVERSE = INSTANCE.reversed(); + + public static Comparator normal() { + return INSTANCE; + } + + public static Comparator reverse() { + return REVERSE; + } + + @Override + public int compare(LPSubjectReference o1, LPSubjectReference o2) { + if (o1.equals(o2)) { + return 0; + } + + boolean o1isGroup = o1.getCollectionIdentifier().equals(PermissionService.SUBJECTS_GROUP); + boolean o2isGroup = o2.getCollectionIdentifier().equals(PermissionService.SUBJECTS_GROUP); + + if (o1isGroup != o2isGroup) { + return o1isGroup ? 1 : -1; + } + + // Neither are groups + if (!o1isGroup) { + return 1; + } + + Group g1 = ((GroupSubject) o1.resolveLp().join()).getParent(); + Group g2 = ((GroupSubject) o2.resolveLp().join()).getParent(); + + return Integer.compare(g1.getWeight().orElse(0), g2.getWeight().orElse(0)) == 1 ? 1 : -1; + } +} diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/CalculatedSubject.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/CalculatedSubject.java new file mode 100644 index 000000000..63d3f7194 --- /dev/null +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/CalculatedSubject.java @@ -0,0 +1,410 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.sponge.service.calculated; + +import com.google.common.collect.ImmutableList; + +import me.lucko.luckperms.api.Contexts; +import me.lucko.luckperms.api.Tristate; +import me.lucko.luckperms.api.caching.MetaContexts; +import me.lucko.luckperms.api.context.ContextSet; +import me.lucko.luckperms.api.context.ImmutableContextSet; +import me.lucko.luckperms.api.metastacking.MetaStackDefinition; +import me.lucko.luckperms.common.caching.AbstractCachedData; +import me.lucko.luckperms.common.caching.type.MetaAccumulator; +import me.lucko.luckperms.common.calculators.CalculatorFactory; +import me.lucko.luckperms.common.calculators.PermissionCalculator; +import me.lucko.luckperms.common.calculators.PermissionCalculatorMetadata; +import me.lucko.luckperms.common.graph.TraversalAlgorithm; +import me.lucko.luckperms.common.metastacking.SimpleMetaStackDefinition; +import me.lucko.luckperms.common.metastacking.StandardStackElements; +import me.lucko.luckperms.common.plugin.LuckPermsPlugin; +import me.lucko.luckperms.common.processors.MapProcessor; +import me.lucko.luckperms.common.processors.PermissionProcessor; +import me.lucko.luckperms.common.processors.WildcardProcessor; +import me.lucko.luckperms.common.verbose.CheckOrigin; +import me.lucko.luckperms.sponge.processors.FixedDefaultsProcessor; +import me.lucko.luckperms.sponge.processors.SpongeWildcardProcessor; +import me.lucko.luckperms.sponge.service.inheritance.SubjectInheritanceGraph; +import me.lucko.luckperms.sponge.service.inheritance.SubjectInheritanceGraphs; +import me.lucko.luckperms.sponge.service.model.LPSubject; +import me.lucko.luckperms.sponge.service.reference.LPSubjectReference; + +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +public abstract class CalculatedSubject implements LPSubject { + private static final MetaStackDefinition DEFAULT_META_STACK = new SimpleMetaStackDefinition( + ImmutableList.of(StandardStackElements.HIGHEST_PRIORITY), + "", "", "" + ); + + private final LuckPermsPlugin plugin; + private final SubjectCachedData cachedData; + + protected CalculatedSubject(LuckPermsPlugin plugin) { + this.plugin = plugin; + this.cachedData = new SubjectCachedData(plugin); + } + + public abstract CalculatedSubjectData getSubjectData(); + public abstract CalculatedSubjectData getTransientSubjectData(); + + public Map getCombinedPermissions(ContextSet filter) { + Map permissions; + Map merging; + switch (getParentCollection().getResolutionOrder()) { + case TRANSIENT_FIRST: + permissions = getTransientSubjectData().resolvePermissions(filter); + merging = getSubjectData().resolvePermissions(filter); + break; + case TRANSIENT_LAST: + permissions = getSubjectData().resolvePermissions(filter); + merging = getTransientSubjectData().resolvePermissions(filter); + break; + default: + throw new AssertionError(); + } + + for (Map.Entry entry : merging.entrySet()) { + permissions.putIfAbsent(entry.getKey(), entry.getValue()); + } + return permissions; + } + + public Map getCombinedPermissions() { + Map permissions; + Map merging; + switch (getParentCollection().getResolutionOrder()) { + case TRANSIENT_FIRST: + permissions = getTransientSubjectData().resolvePermissions(); + merging = getSubjectData().resolvePermissions(); + break; + case TRANSIENT_LAST: + permissions = getSubjectData().resolvePermissions(); + merging = getTransientSubjectData().resolvePermissions(); + break; + default: + throw new AssertionError(); + } + + for (Map.Entry entry : merging.entrySet()) { + permissions.putIfAbsent(entry.getKey(), entry.getValue()); + } + return permissions; + } + + public Map resolveAllPermissions(ImmutableContextSet filter) { + SubjectInheritanceGraph graph = SubjectInheritanceGraphs.getGraph(filter); + Map result = new HashMap<>(); + + Iterable traversal = graph.traverse(TraversalAlgorithm.DEPTH_FIRST_PRE_ORDER, this); + for (CalculatedSubject subject : traversal) { + for (Map.Entry entry : subject.getCombinedPermissions(filter).entrySet()) { + result.putIfAbsent(entry.getKey(), entry.getValue()); + } + } + + return result; + } + + public Map resolveAllPermissions() { + SubjectInheritanceGraph graph = SubjectInheritanceGraphs.getGraph(); + Map result = new HashMap<>(); + + Iterable traversal = graph.traverse(TraversalAlgorithm.DEPTH_FIRST_PRE_ORDER, this); + for (CalculatedSubject subject : traversal) { + for (Map.Entry entry : subject.getCombinedPermissions().entrySet()) { + result.putIfAbsent(entry.getKey(), entry.getValue()); + } + } + + return result; + } + + public Set getCombinedParents(ContextSet filter) { + Set parents; + Set merging; + switch (getParentCollection().getResolutionOrder()) { + case TRANSIENT_FIRST: + parents = getTransientSubjectData().resolveParents(filter); + merging = getSubjectData().resolveParents(filter); + break; + case TRANSIENT_LAST: + parents = getSubjectData().resolveParents(filter); + merging = getTransientSubjectData().resolveParents(filter); + break; + default: + throw new AssertionError(); + } + + for (LPSubjectReference entry : merging) { + if (!parents.contains(entry)) { + parents.add(entry); + } + } + return parents; + } + + public Set getCombinedParents() { + Set parents; + Set merging; + switch (getParentCollection().getResolutionOrder()) { + case TRANSIENT_FIRST: + parents = getTransientSubjectData().resolveParents(); + merging = getSubjectData().resolveParents(); + break; + case TRANSIENT_LAST: + parents = getSubjectData().resolveParents(); + merging = getTransientSubjectData().resolveParents(); + break; + default: + throw new AssertionError(); + } + + for (LPSubjectReference entry : merging) { + if (!parents.contains(entry)) { + parents.add(entry); + } + } + return parents; + } + + public Set resolveAllParents(ImmutableContextSet filter) { + SubjectInheritanceGraph graph = SubjectInheritanceGraphs.getGraph(filter); + Set result = new LinkedHashSet<>(); + + Iterable traversal = graph.traverse(TraversalAlgorithm.DEPTH_FIRST_PRE_ORDER, this); + for (CalculatedSubject subject : traversal) { + for (LPSubjectReference entry : subject.getCombinedParents(filter)) { + if (!result.contains(entry)) { + result.add(entry); + } + } + } + + return result; + } + + public Set resolveAllParents() { + SubjectInheritanceGraph graph = SubjectInheritanceGraphs.getGraph(); + Set result = new LinkedHashSet<>(); + + Iterable traversal = graph.traverse(TraversalAlgorithm.DEPTH_FIRST_PRE_ORDER, this); + for (CalculatedSubject subject : traversal) { + for (LPSubjectReference entry : subject.getCombinedParents()) { + if (!result.contains(entry)) { + result.add(entry); + } + } + } + + return result; + } + + public Map getCombinedOptions(ContextSet filter) { + Map options; + Map merging; + switch (getParentCollection().getResolutionOrder()) { + case TRANSIENT_FIRST: + options = getTransientSubjectData().resolveOptions(filter); + merging = getSubjectData().resolveOptions(filter); + break; + case TRANSIENT_LAST: + options = getSubjectData().resolveOptions(filter); + merging = getTransientSubjectData().resolveOptions(filter); + break; + default: + throw new AssertionError(); + } + + for (Map.Entry entry : merging.entrySet()) { + options.putIfAbsent(entry.getKey(), entry.getValue()); + } + return options; + } + + public Map getCombinedOptions() { + Map options; + Map merging; + switch (getParentCollection().getResolutionOrder()) { + case TRANSIENT_FIRST: + options = getTransientSubjectData().resolveOptions(); + merging = getSubjectData().resolveOptions(); + break; + case TRANSIENT_LAST: + options = getSubjectData().resolveOptions(); + merging = getTransientSubjectData().resolveOptions(); + break; + default: + throw new AssertionError(); + } + + for (Map.Entry entry : merging.entrySet()) { + options.putIfAbsent(entry.getKey(), entry.getValue()); + } + return options; + } + + public Map resolveAllOptions(ImmutableContextSet filter) { + SubjectInheritanceGraph graph = SubjectInheritanceGraphs.getGraph(filter); + Map result = new HashMap<>(); + + Iterable traversal = graph.traverse(TraversalAlgorithm.DEPTH_FIRST_PRE_ORDER, this); + for (CalculatedSubject subject : traversal) { + for (Map.Entry entry : subject.getCombinedOptions(filter).entrySet()) { + result.putIfAbsent(entry.getKey(), entry.getValue()); + } + } + + return result; + } + + public Map resolveAllOptions() { + SubjectInheritanceGraph graph = SubjectInheritanceGraphs.getGraph(); + Map result = new HashMap<>(); + + Iterable traversal = graph.traverse(TraversalAlgorithm.DEPTH_FIRST_PRE_ORDER, this); + for (CalculatedSubject subject : traversal) { + for (Map.Entry entry : subject.getCombinedOptions().entrySet()) { + result.putIfAbsent(entry.getKey(), entry.getValue()); + } + } + + return result; + } + + public void resolveAllOptions(MetaAccumulator accumulator, ImmutableContextSet filter) { + SubjectInheritanceGraph graph = SubjectInheritanceGraphs.getGraph(filter); + Iterable traversal = graph.traverse(TraversalAlgorithm.DEPTH_FIRST_PRE_ORDER, this); + for (CalculatedSubject subject : traversal) { + for (Map.Entry entry : subject.getCombinedOptions(filter).entrySet()) { + accumulator.accumulateMeta(entry.getKey(), entry.getValue()); + } + } + } + + public void resolveAllOptions(MetaAccumulator accumulator) { + SubjectInheritanceGraph graph = SubjectInheritanceGraphs.getGraph(); + Iterable traversal = graph.traverse(TraversalAlgorithm.DEPTH_FIRST_PRE_ORDER, this); + for (CalculatedSubject subject : traversal) { + for (Map.Entry entry : subject.getCombinedOptions().entrySet()) { + accumulator.accumulateMeta(entry.getKey(), entry.getValue()); + } + } + } + + @Override + public Tristate getPermissionValue(ImmutableContextSet contexts, String permission) { + Contexts lookupContexts = Contexts.of(contexts, Contexts.global().getSettings()); + return this.cachedData.getPermissionData(lookupContexts).getPermissionValue(permission, CheckOrigin.INTERNAL); + } + + @Override + public boolean isChildOf(ImmutableContextSet contexts, LPSubjectReference parent) { + return resolveAllParents(contexts).contains(parent); + } + + @Override + public ImmutableList getParents(ImmutableContextSet contexts) { + return ImmutableList.copyOf(resolveAllParents(contexts)); + } + + @Override + public Optional getOption(ImmutableContextSet contexts, String key) { + Contexts lookupContexts = Contexts.of(contexts, Contexts.global().getSettings()); + Map meta = this.cachedData.getMetaData(lookupContexts).getMeta(); + return Optional.ofNullable(meta.get(key)); + } + + @Override + public void performCleanup() { + this.cachedData.doCacheCleanup(); + } + + @Override + public void invalidateCaches() { + this.cachedData.invalidateCaches(); + } + + private final class SubjectCachedData extends AbstractCachedData implements CalculatorFactory { + private SubjectCachedData(LuckPermsPlugin plugin) { + super(plugin); + } + + @Override + protected PermissionCalculatorMetadata getMetadataForContexts(Contexts contexts) { + return PermissionCalculatorMetadata.of(null, getParentCollection().getIdentifier() + "/" + getIdentifier(), contexts.getContexts()); + } + + @Override + protected CalculatorFactory getCalculatorFactory() { + return this; + } + + @Override + protected MetaContexts getDefaultMetaContexts(Contexts contexts) { + return MetaContexts.of(contexts, DEFAULT_META_STACK, DEFAULT_META_STACK); + } + + @Override + protected Map resolvePermissions() { + return resolveAllPermissions(); + } + + @Override + protected Map resolvePermissions(Contexts contexts) { + return resolveAllPermissions(contexts.getContexts().makeImmutable()); + } + + @Override + protected void resolveMeta(MetaAccumulator accumulator) { + resolveAllOptions(accumulator); + } + + @Override + protected void resolveMeta(MetaAccumulator accumulator, MetaContexts contexts) { + resolveAllOptions(accumulator, contexts.getContexts().getContexts().makeImmutable()); + } + + @Override + public PermissionCalculator build(Contexts contexts, PermissionCalculatorMetadata metadata) { + ImmutableList.Builder processors = ImmutableList.builder(); + processors.add(new MapProcessor()); + processors.add(new SpongeWildcardProcessor()); + processors.add(new WildcardProcessor()); + + if (!getParentCollection().isDefaultsCollection()) { + processors.add(new FixedDefaultsProcessor(getService(), contexts.getContexts().makeImmutable(), getDefaults())); + } + + return new PermissionCalculator(this.plugin, metadata, processors.build()); + } + } +} diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/CalculatedSubjectData.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/CalculatedSubjectData.java index c88bfbb5a..1ad898aa7 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/CalculatedSubjectData.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/CalculatedSubjectData.java @@ -25,22 +25,16 @@ package me.lucko.luckperms.sponge.service.calculated; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.LoadingCache; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import me.lucko.luckperms.api.Tristate; +import me.lucko.luckperms.api.context.ContextSet; import me.lucko.luckperms.api.context.ImmutableContextSet; -import me.lucko.luckperms.common.calculators.PermissionCalculator; -import me.lucko.luckperms.common.calculators.PermissionCalculatorMetadata; import me.lucko.luckperms.common.contexts.ContextSetComparator; import me.lucko.luckperms.common.model.NodeMapType; -import me.lucko.luckperms.common.processors.MapProcessor; -import me.lucko.luckperms.common.processors.PermissionProcessor; -import me.lucko.luckperms.common.verbose.CheckOrigin; -import me.lucko.luckperms.sponge.processors.SpongeWildcardProcessor; import me.lucko.luckperms.sponge.service.ProxyFactory; +import me.lucko.luckperms.sponge.service.SubjectComparator; import me.lucko.luckperms.sponge.service.model.LPPermissionService; import me.lucko.luckperms.sponge.service.model.LPSubject; import me.lucko.luckperms.sponge.service.model.LPSubjectData; @@ -49,6 +43,7 @@ import me.lucko.luckperms.sponge.service.reference.LPSubjectReference; import org.spongepowered.api.service.permission.SubjectData; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -57,7 +52,6 @@ import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; /** * In-memory implementation of {@link LPSubjectData}. @@ -71,22 +65,10 @@ public class CalculatedSubjectData implements LPSubjectData { private final Map> parents = new ConcurrentHashMap<>(); private final Map> options = new ConcurrentHashMap<>(); - private final LoadingCache permissionCache; - - public CalculatedSubjectData(LPSubject parentSubject, NodeMapType type, LPPermissionService service, String calculatorDisplayName) { + public CalculatedSubjectData(LPSubject parentSubject, NodeMapType type, LPPermissionService service) { this.parentSubject = parentSubject; this.type = type; this.service = service; - this.permissionCache = Caffeine.newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) - .build(contexts -> { - ImmutableList processors = ImmutableList.of(new MapProcessor(), new SpongeWildcardProcessor()); - PermissionCalculatorMetadata calcMetadata = PermissionCalculatorMetadata.of(null, calculatorDisplayName, contexts); - - CalculatorHolder holder = new CalculatorHolder(new PermissionCalculator(this.service.getPlugin(), calcMetadata, processors)); - holder.setPermissions(processPermissionsMap(contexts, this.permissions)); - return holder; - }); } @Override @@ -104,26 +86,12 @@ public class CalculatedSubjectData implements LPSubjectData { return this.type; } - public void cleanup() { - this.permissionCache.cleanUp(); - } - - public void invalidateLookupCache() { - this.permissionCache.invalidateAll(); - } - - public Tristate getPermissionValue(ImmutableContextSet contexts, String permission) { - CalculatorHolder calculatorHolder = Objects.requireNonNull(this.permissionCache.get(contexts)); - return calculatorHolder.getCalculator().getPermissionValue(permission, CheckOrigin.INTERNAL); - } - public void replacePermissions(Map> map) { this.permissions.clear(); for (Map.Entry> e : map.entrySet()) { this.permissions.put(e.getKey(), new ConcurrentHashMap<>(e.getValue())); } - this.permissionCache.invalidateAll(); - this.service.invalidateAllCaches(LPSubject.CacheLevel.PERMISSION); + this.service.invalidateAllCaches(); } public void replaceParents(Map> map) { @@ -133,7 +101,7 @@ public class CalculatedSubjectData implements LPSubjectData { set.addAll(e.getValue()); this.parents.put(e.getKey(), set); } - this.service.invalidateAllCaches(LPSubject.CacheLevel.PARENT); + this.service.invalidateAllCaches(); } public void replaceOptions(Map> map) { @@ -141,7 +109,7 @@ public class CalculatedSubjectData implements LPSubjectData { for (Map.Entry> e : map.entrySet()) { this.options.put(e.getKey(), new ConcurrentHashMap<>(e.getValue())); } - this.service.invalidateAllCaches(LPSubject.CacheLevel.OPTION); + this.service.invalidateAllCaches(); } @Override @@ -153,159 +121,10 @@ public class CalculatedSubjectData implements LPSubjectData { return map.build(); } - @Override - public CompletableFuture setPermission(ImmutableContextSet contexts, String permission, Tristate value) { - boolean b; - if (value == Tristate.UNDEFINED) { - Map perms = this.permissions.get(contexts); - b = perms != null && perms.remove(permission.toLowerCase()) != null; - } else { - Map perms = this.permissions.computeIfAbsent(contexts, c -> new ConcurrentHashMap<>()); - b = !Objects.equals(perms.put(permission.toLowerCase(), value.asBoolean()), value.asBoolean()); - } - if (b) { - this.permissionCache.invalidateAll(); - this.service.invalidateAllCaches(LPSubject.CacheLevel.PERMISSION); - } - return CompletableFuture.completedFuture(b); - } - - @Override - public CompletableFuture clearPermissions() { - if (this.permissions.isEmpty()) { - return CompletableFuture.completedFuture(false); - } else { - this.permissions.clear(); - this.permissionCache.invalidateAll(); - this.service.invalidateAllCaches(LPSubject.CacheLevel.PERMISSION); - return CompletableFuture.completedFuture(true); - } - } - - @Override - public CompletableFuture clearPermissions(ImmutableContextSet contexts) { - Map perms = this.permissions.get(contexts); - if (perms == null) { - return CompletableFuture.completedFuture(false); - } - - this.permissions.remove(contexts); - if (!perms.isEmpty()) { - this.permissionCache.invalidateAll(); - this.service.invalidateAllCaches(LPSubject.CacheLevel.PERMISSION); - return CompletableFuture.completedFuture(true); - } - return CompletableFuture.completedFuture(false); - } - - @Override - public ImmutableMap> getAllParents() { - ImmutableMap.Builder> map = ImmutableMap.builder(); - for (Map.Entry> e : this.parents.entrySet()) { - map.put(e.getKey(), this.service.sortSubjects(e.getValue())); - } - return map.build(); - } - - @Override - public CompletableFuture addParent(ImmutableContextSet contexts, LPSubjectReference parent) { - Set set = this.parents.computeIfAbsent(contexts, c -> ConcurrentHashMap.newKeySet()); - boolean b = set.add(parent); - if (b) { - this.service.invalidateAllCaches(LPSubject.CacheLevel.PARENT); - } - return CompletableFuture.completedFuture(b); - } - - @Override - public CompletableFuture removeParent(ImmutableContextSet contexts, LPSubjectReference parent) { - Set set = this.parents.get(contexts); - boolean b = set != null && set.remove(parent); - if (b) { - this.service.invalidateAllCaches(LPSubject.CacheLevel.PARENT); - } - return CompletableFuture.completedFuture(b); - } - - @Override - public CompletableFuture clearParents() { - if (this.parents.isEmpty()) { - return CompletableFuture.completedFuture(false); - } else { - this.parents.clear(); - this.service.invalidateAllCaches(LPSubject.CacheLevel.PARENT); - return CompletableFuture.completedFuture(true); - } - } - - @Override - public CompletableFuture clearParents(ImmutableContextSet contexts) { - Set set = this.parents.get(contexts); - if (set == null) { - return CompletableFuture.completedFuture(false); - } - - this.parents.remove(contexts); - this.service.invalidateAllCaches(LPSubject.CacheLevel.PARENT); - return CompletableFuture.completedFuture(!set.isEmpty()); - } - - @Override - public ImmutableMap> getAllOptions() { - ImmutableMap.Builder> map = ImmutableMap.builder(); - for (Map.Entry> e : this.options.entrySet()) { - map.put(e.getKey(), ImmutableMap.copyOf(e.getValue())); - } - return map.build(); - } - - @Override - public CompletableFuture setOption(ImmutableContextSet contexts, String key, String value) { - Map options = this.options.computeIfAbsent(contexts, c -> new ConcurrentHashMap<>()); - boolean b = !stringEquals(options.put(key.toLowerCase(), value), value); - if (b) { - this.service.invalidateAllCaches(LPSubject.CacheLevel.OPTION); - } - return CompletableFuture.completedFuture(b); - } - - @Override - public CompletableFuture unsetOption(ImmutableContextSet contexts, String key) { - Map options = this.options.get(contexts); - boolean b = options != null && options.remove(key.toLowerCase()) != null; - if (b) { - this.service.invalidateAllCaches(LPSubject.CacheLevel.OPTION); - } - return CompletableFuture.completedFuture(b); - } - - @Override - public CompletableFuture clearOptions() { - if (this.options.isEmpty()) { - return CompletableFuture.completedFuture(false); - } else { - this.options.clear(); - this.service.invalidateAllCaches(LPSubject.CacheLevel.OPTION); - return CompletableFuture.completedFuture(true); - } - } - - @Override - public CompletableFuture clearOptions(ImmutableContextSet contexts) { - Map map = this.options.get(contexts); - if (map == null) { - return CompletableFuture.completedFuture(false); - } - - this.options.remove(contexts); - this.service.invalidateAllCaches(LPSubject.CacheLevel.OPTION); - return CompletableFuture.completedFuture(!map.isEmpty()); - } - - private static Map processPermissionsMap(ImmutableContextSet filter, Map> input) { + public Map resolvePermissions(ContextSet filter) { // get relevant entries SortedMap> sorted = new TreeMap<>(ContextSetComparator.reverse()); - for (Map.Entry> entry : input.entrySet()) { + for (Map.Entry> entry : this.permissions.entrySet()) { if (!entry.getKey().isSatisfiedBy(filter)) { continue; } @@ -320,32 +139,257 @@ public class CalculatedSubjectData implements LPSubjectData { result.putIfAbsent(e.getKey(), e.getValue()); } } - return ImmutableMap.copyOf(result); + + return result; + } + + public Map resolvePermissions() { + // get relevant entries + SortedMap> sorted = new TreeMap<>(ContextSetComparator.reverse()); + for (Map.Entry> entry : this.permissions.entrySet()) { + sorted.put(entry.getKey(), entry.getValue()); + } + + // flatten + Map result = new HashMap<>(); + for (Map map : sorted.values()) { + for (Map.Entry e : map.entrySet()) { + result.putIfAbsent(e.getKey(), e.getValue()); + } + } + + return result; + } + + @Override + public CompletableFuture setPermission(ImmutableContextSet contexts, String permission, Tristate value) { + boolean b; + if (value == Tristate.UNDEFINED) { + Map perms = this.permissions.get(contexts); + b = perms != null && perms.remove(permission.toLowerCase()) != null; + } else { + Map perms = this.permissions.computeIfAbsent(contexts, c -> new ConcurrentHashMap<>()); + b = !Objects.equals(perms.put(permission.toLowerCase(), value.asBoolean()), value.asBoolean()); + } + if (b) { + this.service.invalidateAllCaches(); + } + return CompletableFuture.completedFuture(b); + } + + @Override + public CompletableFuture clearPermissions() { + if (this.permissions.isEmpty()) { + return CompletableFuture.completedFuture(false); + } else { + this.permissions.clear(); + this.service.invalidateAllCaches(); + return CompletableFuture.completedFuture(true); + } + } + + @Override + public CompletableFuture clearPermissions(ImmutableContextSet contexts) { + Map perms = this.permissions.get(contexts); + if (perms == null) { + return CompletableFuture.completedFuture(false); + } + + this.permissions.remove(contexts); + if (!perms.isEmpty()) { + this.service.invalidateAllCaches(); + return CompletableFuture.completedFuture(true); + } + return CompletableFuture.completedFuture(false); + } + + @Override + public ImmutableMap> getAllParents() { + ImmutableMap.Builder> map = ImmutableMap.builder(); + for (Map.Entry> e : this.parents.entrySet()) { + map.put(e.getKey(), ImmutableList.sortedCopyOf(SubjectComparator.reverse(), e.getValue())); + } + return map.build(); + } + + public Set resolveParents(ContextSet filter) { + // get relevant entries + SortedMap> sorted = new TreeMap<>(ContextSetComparator.reverse()); + for (Map.Entry> entry : this.parents.entrySet()) { + if (!entry.getKey().isSatisfiedBy(filter)) { + continue; + } + + sorted.put(entry.getKey(), entry.getValue()); + } + + // flatten + Set result = new LinkedHashSet<>(); + for (Set set : sorted.values()) { + for (LPSubjectReference e : set) { + if (!result.contains(e)) { + result.add(e); + } + } + } + return result; + } + + public Set resolveParents() { + // get relevant entries + SortedMap> sorted = new TreeMap<>(ContextSetComparator.reverse()); + for (Map.Entry> entry : this.parents.entrySet()) { + sorted.put(entry.getKey(), entry.getValue()); + } + + // flatten + Set result = new LinkedHashSet<>(); + for (Set set : sorted.values()) { + for (LPSubjectReference e : set) { + if (!result.contains(e)) { + result.add(e); + } + } + } + return result; + } + + @Override + public CompletableFuture addParent(ImmutableContextSet contexts, LPSubjectReference parent) { + Set set = this.parents.computeIfAbsent(contexts, c -> ConcurrentHashMap.newKeySet()); + boolean b = set.add(parent); + if (b) { + this.service.invalidateAllCaches(); + } + return CompletableFuture.completedFuture(b); + } + + @Override + public CompletableFuture removeParent(ImmutableContextSet contexts, LPSubjectReference parent) { + Set set = this.parents.get(contexts); + boolean b = set != null && set.remove(parent); + if (b) { + this.service.invalidateAllCaches(); + } + return CompletableFuture.completedFuture(b); + } + + @Override + public CompletableFuture clearParents() { + if (this.parents.isEmpty()) { + return CompletableFuture.completedFuture(false); + } else { + this.parents.clear(); + this.service.invalidateAllCaches(); + return CompletableFuture.completedFuture(true); + } + } + + @Override + public CompletableFuture clearParents(ImmutableContextSet contexts) { + Set set = this.parents.get(contexts); + if (set == null) { + return CompletableFuture.completedFuture(false); + } + + this.parents.remove(contexts); + this.service.invalidateAllCaches(); + return CompletableFuture.completedFuture(!set.isEmpty()); + } + + @Override + public ImmutableMap> getAllOptions() { + ImmutableMap.Builder> map = ImmutableMap.builder(); + for (Map.Entry> e : this.options.entrySet()) { + map.put(e.getKey(), ImmutableMap.copyOf(e.getValue())); + } + return map.build(); + } + + public Map resolveOptions(ContextSet filter) { + // get relevant entries + SortedMap> sorted = new TreeMap<>(ContextSetComparator.reverse()); + for (Map.Entry> entry : this.options.entrySet()) { + if (!entry.getKey().isSatisfiedBy(filter)) { + continue; + } + + sorted.put(entry.getKey(), entry.getValue()); + } + + // flatten + Map result = new HashMap<>(); + for (Map map : sorted.values()) { + for (Map.Entry e : map.entrySet()) { + result.putIfAbsent(e.getKey(), e.getValue()); + } + } + + return result; + } + + public Map resolveOptions() { + // get relevant entries + SortedMap> sorted = new TreeMap<>(ContextSetComparator.reverse()); + for (Map.Entry> entry : this.options.entrySet()) { + sorted.put(entry.getKey(), entry.getValue()); + } + + // flatten + Map result = new HashMap<>(); + for (Map map : sorted.values()) { + for (Map.Entry e : map.entrySet()) { + result.putIfAbsent(e.getKey(), e.getValue()); + } + } + + return result; + } + + @Override + public CompletableFuture setOption(ImmutableContextSet contexts, String key, String value) { + Map options = this.options.computeIfAbsent(contexts, c -> new ConcurrentHashMap<>()); + boolean b = !stringEquals(options.put(key.toLowerCase(), value), value); + if (b) { + this.service.invalidateAllCaches(); + } + return CompletableFuture.completedFuture(b); + } + + @Override + public CompletableFuture unsetOption(ImmutableContextSet contexts, String key) { + Map options = this.options.get(contexts); + boolean b = options != null && options.remove(key.toLowerCase()) != null; + if (b) { + this.service.invalidateAllCaches(); + } + return CompletableFuture.completedFuture(b); + } + + @Override + public CompletableFuture clearOptions() { + if (this.options.isEmpty()) { + return CompletableFuture.completedFuture(false); + } else { + this.options.clear(); + this.service.invalidateAllCaches(); + return CompletableFuture.completedFuture(true); + } + } + + @Override + public CompletableFuture clearOptions(ImmutableContextSet contexts) { + Map map = this.options.get(contexts); + if (map == null) { + return CompletableFuture.completedFuture(false); + } + + this.options.remove(contexts); + this.service.invalidateAllCaches(); + return CompletableFuture.completedFuture(!map.isEmpty()); } private static boolean stringEquals(String a, String b) { return a == null && b == null || a != null && b != null && a.equalsIgnoreCase(b); } - - private static class CalculatorHolder { - private final PermissionCalculator calculator; - private final Map permissions; - - public CalculatorHolder(PermissionCalculator calculator) { - this.calculator = calculator; - this.permissions = new ConcurrentHashMap<>(); - this.calculator.setSourcePermissions(this.permissions); - } - - public void setPermissions(Map permissions) { - this.permissions.clear(); - this.permissions.putAll(permissions); - this.calculator.setSourcePermissions(this.permissions); - this.calculator.invalidateCache(); - } - - public PermissionCalculator getCalculator() { - return this.calculator; - } - } } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/MonitoredSubjectData.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/MonitoredSubjectData.java index ff70c585f..291ce89e7 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/MonitoredSubjectData.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/calculated/MonitoredSubjectData.java @@ -44,8 +44,8 @@ public abstract class MonitoredSubjectData extends CalculatedSubjectData { return b; }; - public MonitoredSubjectData(LPSubject subject, NodeMapType type, LuckPermsService service, String calculatorDisplayName) { - super(subject, type, service, calculatorDisplayName); + public MonitoredSubjectData(LPSubject subject, NodeMapType type, LuckPermsService service) { + super(subject, type, service); } protected abstract void onUpdate(boolean success); diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/inheritance/SubjectInheritanceGraph.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/inheritance/SubjectInheritanceGraph.java new file mode 100644 index 000000000..038ed4a92 --- /dev/null +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/inheritance/SubjectInheritanceGraph.java @@ -0,0 +1,50 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.sponge.service.inheritance; + +import me.lucko.luckperms.common.graph.Graph; +import me.lucko.luckperms.common.graph.GraphTraversers; +import me.lucko.luckperms.common.graph.TraversalAlgorithm; +import me.lucko.luckperms.sponge.service.calculated.CalculatedSubject; + +/** + * A {@link Graph} which represents an "inheritance tree" for subjects. + */ +public interface SubjectInheritanceGraph extends Graph { + + /** + * Returns an iterable which will traverse this inheritance graph using the + * specified algorithm starting at the given node. + * + * @param algorithm the algorithm to use when traversing + * @param startNode the start node in the inheritance graph + * @return an iterable + */ + default Iterable traverse(TraversalAlgorithm algorithm, CalculatedSubject startNode) { + return GraphTraversers.traverseUsing(algorithm, this, startNode); + } + +} diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/inheritance/SubjectInheritanceGraphs.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/inheritance/SubjectInheritanceGraphs.java new file mode 100644 index 000000000..309b94618 --- /dev/null +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/inheritance/SubjectInheritanceGraphs.java @@ -0,0 +1,76 @@ +/* + * This file is part of LuckPerms, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.luckperms.sponge.service.inheritance; + +import me.lucko.luckperms.api.context.ImmutableContextSet; +import me.lucko.luckperms.sponge.service.calculated.CalculatedSubject; + +import java.util.stream.Collectors; + +public final class SubjectInheritanceGraphs { + private static final SubjectInheritanceGraph NON_CONTEXTUAL = new NonContextual(); + + + public static SubjectInheritanceGraph getGraph() { + return NON_CONTEXTUAL; + } + + public static SubjectInheritanceGraph getGraph(ImmutableContextSet contextSet) { + return new Contextual(contextSet); + } + + private static final class NonContextual implements SubjectInheritanceGraph { + @Override + public Iterable successors(CalculatedSubject subject) { + return subject.getCombinedParents().stream() + .filter(p -> p instanceof CalculatedSubject) + .map(p -> ((CalculatedSubject) p)) + .collect(Collectors.toList()); + } + } + + private static final class Contextual implements SubjectInheritanceGraph { + + /** + * The contexts to resolve inheritance in. + */ + private final ImmutableContextSet contextSet; + + private Contextual(ImmutableContextSet contextSet) { + this.contextSet = contextSet; + } + + @Override + public Iterable successors(CalculatedSubject subject) { + return subject.getCombinedParents(this.contextSet).stream() + .filter(p -> p instanceof CalculatedSubject) + .map(p -> ((CalculatedSubject) p)) + .collect(Collectors.toList()); + } + } + + private SubjectInheritanceGraphs() {} +} diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/internal/HolderSubject.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/internal/HolderSubject.java index aad01f17f..25668630c 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/internal/HolderSubject.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/internal/HolderSubject.java @@ -26,7 +26,6 @@ package me.lucko.luckperms.sponge.service.internal; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import me.lucko.luckperms.api.Tristate; import me.lucko.luckperms.api.caching.MetaData; @@ -38,12 +37,15 @@ import me.lucko.luckperms.common.verbose.CheckOrigin; import me.lucko.luckperms.sponge.LPSpongePlugin; import me.lucko.luckperms.sponge.service.LuckPermsService; import me.lucko.luckperms.sponge.service.ProxyFactory; +import me.lucko.luckperms.sponge.service.SubjectComparator; import me.lucko.luckperms.sponge.service.model.LPSubject; import me.lucko.luckperms.sponge.service.reference.LPSubjectReference; import org.spongepowered.api.service.permission.PermissionService; import org.spongepowered.api.service.permission.Subject; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -70,6 +72,10 @@ public abstract class HolderSubject implements LPSub }); } + public T getParent() { + return this.parent; + } + @Override public Subject sponge() { return ProxyFactory.toSponge(this); @@ -102,7 +108,7 @@ public abstract class HolderSubject implements LPSub @Override public ImmutableList getParents(ImmutableContextSet contexts) { - ImmutableSet.Builder subjects = ImmutableSet.builder(); + List subjects = new ArrayList<>(); for (Map.Entry entry : this.parent.getCachedData().getPermissionData(this.plugin.getContextManager().formContexts(contexts)).getImmutableBacking().entrySet()) { if (!entry.getValue()) { @@ -122,7 +128,8 @@ public abstract class HolderSubject implements LPSub subjects.addAll(getParentCollection().getDefaults().getParents(contexts)); subjects.addAll(this.plugin.getService().getDefaults().getParents(contexts)); - return getService().sortSubjects(subjects.build()); + subjects.sort(SubjectComparator.reverse()); + return ImmutableList.copyOf(subjects); } @Override @@ -154,7 +161,7 @@ public abstract class HolderSubject implements LPSub } @Override - public void invalidateCaches(CacheLevel cacheLevel) { + public void invalidateCaches() { // invalidate for all changes this.parent.getCachedData().invalidateCaches(); } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedCollection.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedCollection.java index 510d422d2..9b1205285 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedCollection.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedCollection.java @@ -58,6 +58,7 @@ import java.util.function.Predicate; public class PersistedCollection implements LPSubjectCollection { private final LuckPermsService service; private final String identifier; + private final boolean defaultsCollection; private final SubjectCollection spongeProxy; @@ -67,6 +68,7 @@ public class PersistedCollection implements LPSubjectCollection { public PersistedCollection(LuckPermsService service, String identifier) { this.service = service; this.identifier = identifier; + this.defaultsCollection = identifier.equals("defaults"); this.spongeProxy = ProxyFactory.toSponge(this); } @@ -100,6 +102,11 @@ public class PersistedCollection implements LPSubjectCollection { return Predicates.alwaysTrue(); } + @Override + public boolean isDefaultsCollection() { + return this.defaultsCollection; + } + @Override public CompletableFuture loadSubject(String identifier) { return CompletableFuture.completedFuture(this.subjects.get(identifier.toLowerCase())); diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedSubject.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedSubject.java index e14bf0901..9849f613f 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedSubject.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedSubject.java @@ -25,38 +25,27 @@ package me.lucko.luckperms.sponge.service.persisted; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.LoadingCache; -import com.google.common.collect.ImmutableList; - -import me.lucko.luckperms.api.Tristate; -import me.lucko.luckperms.api.context.ImmutableContextSet; import me.lucko.luckperms.common.buffers.BufferedRequest; import me.lucko.luckperms.common.model.NodeMapType; -import me.lucko.luckperms.common.verbose.CheckOrigin; import me.lucko.luckperms.sponge.service.LuckPermsService; import me.lucko.luckperms.sponge.service.ProxyFactory; +import me.lucko.luckperms.sponge.service.calculated.CalculatedSubject; import me.lucko.luckperms.sponge.service.calculated.CalculatedSubjectData; import me.lucko.luckperms.sponge.service.calculated.MonitoredSubjectData; import me.lucko.luckperms.sponge.service.model.LPSubject; import me.lucko.luckperms.sponge.service.model.LPSubjectData; -import me.lucko.luckperms.sponge.service.reference.LPSubjectReference; import me.lucko.luckperms.sponge.service.storage.SubjectStorageModel; import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.service.permission.Subject; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; import java.util.Optional; -import java.util.concurrent.TimeUnit; /** * A simple persistable Subject implementation */ -public class PersistedSubject implements LPSubject { +public class PersistedSubject extends CalculatedSubject implements LPSubject { private final String identifier; private final LuckPermsService service; @@ -65,18 +54,6 @@ public class PersistedSubject implements LPSubject { private final PersistedSubjectData subjectData; private final CalculatedSubjectData transientSubjectData; - private final LoadingCache permissionLookupCache = Caffeine.newBuilder() - .expireAfterAccess(20, TimeUnit.MINUTES) - .build(lookup -> lookupPermissionValue(lookup.contexts, lookup.node)); - - private final LoadingCache> parentLookupCache = Caffeine.newBuilder() - .expireAfterAccess(20, TimeUnit.MINUTES) - .build(this::lookupParents); - - private final LoadingCache> optionLookupCache = Caffeine.newBuilder() - .expireAfterAccess(20, TimeUnit.MINUTES) - .build(lookup -> lookupOptionValue(lookup.contexts, lookup.key)); - private final BufferedRequest saveBuffer = new BufferedRequest(1000L, 500L, r -> PersistedSubject.this.service.getPlugin().getBootstrap().getScheduler().doAsync(r)) { @Override protected Void perform() { @@ -90,12 +67,11 @@ public class PersistedSubject implements LPSubject { }; public PersistedSubject(String identifier, LuckPermsService service, PersistedCollection parentCollection) { + super(service.getPlugin()); this.identifier = identifier; this.service = service; this.parentCollection = parentCollection; - - String displayName = parentCollection.getIdentifier() + "/" + identifier; - this.subjectData = new PersistedSubjectData(this, NodeMapType.ENDURING, service, displayName + "/p") { + this.subjectData = new PersistedSubjectData(this, NodeMapType.ENDURING, service) { @Override protected void onUpdate(boolean success) { super.onUpdate(success); @@ -104,7 +80,7 @@ public class PersistedSubject implements LPSubject { } } }; - this.transientSubjectData = new MonitoredSubjectData(this, NodeMapType.TRANSIENT, service, displayName + "/t") { + this.transientSubjectData = new MonitoredSubjectData(this, NodeMapType.TRANSIENT, service) { @Override protected void onUpdate(boolean success) { if (success) { @@ -118,34 +94,6 @@ public class PersistedSubject implements LPSubject { this.service.getPlugin().getUpdateEventHandler().fireUpdateEvent(subjectData); } - @Override - public void invalidateCaches(CacheLevel type) { - this.optionLookupCache.invalidateAll(); - - if (type == CacheLevel.OPTION) { - return; - } - - this.permissionLookupCache.invalidateAll(); - this.subjectData.invalidateLookupCache(); - this.transientSubjectData.invalidateLookupCache(); - - if (type == CacheLevel.PERMISSION) { - return; - } - - this.parentLookupCache.invalidateAll(); - } - - @Override - public void performCleanup() { - this.subjectData.cleanup(); - this.transientSubjectData.cleanup(); - this.permissionLookupCache.cleanUp(); - this.parentLookupCache.cleanUp(); - this.optionLookupCache.cleanUp(); - } - public void loadData(SubjectStorageModel dataHolder) { this.subjectData.setSave(false); dataHolder.applyToData(this.subjectData); @@ -190,193 +138,4 @@ public class PersistedSubject implements LPSubject { public Optional getCommandSource() { return Optional.empty(); } - - private Tristate lookupPermissionValue(ImmutableContextSet contexts, String node) { - Tristate res; - - // if transient has priority - if (!this.parentCollection.getIdentifier().equals("defaults")) { - res = this.transientSubjectData.getPermissionValue(contexts, node); - if (res != Tristate.UNDEFINED) { - return res; - } - - res = this.subjectData.getPermissionValue(contexts, node); - if (res != Tristate.UNDEFINED) { - return res; - } - } else { - res = this.subjectData.getPermissionValue(contexts, node); - if (res != Tristate.UNDEFINED) { - return res; - } - - res = this.transientSubjectData.getPermissionValue(contexts, node); - if (res != Tristate.UNDEFINED) { - return res; - } - } - - for (LPSubjectReference parent : getParents(contexts)) { - res = parent.resolveLp().join().getPermissionValue(contexts, node); - if (res != Tristate.UNDEFINED) { - return res; - } - } - - if (getParentCollection().getIdentifier().equalsIgnoreCase("defaults")) { - return Tristate.UNDEFINED; - } - - res = getParentCollection().getDefaults().getPermissionValue(contexts, node); - if (res != Tristate.UNDEFINED) { - return res; - } - - res = this.service.getDefaults().getPermissionValue(contexts, node); - return res; - } - - private ImmutableList lookupParents(ImmutableContextSet contexts) { - List s = new ArrayList<>(); - s.addAll(this.subjectData.getParents(contexts)); - s.addAll(this.transientSubjectData.getParents(contexts)); - - if (!getParentCollection().getIdentifier().equalsIgnoreCase("defaults")) { - s.addAll(getParentCollection().getDefaults().getParents(contexts)); - s.addAll(this.service.getDefaults().getParents(contexts)); - } - - return this.service.sortSubjects(s); - } - - private Optional lookupOptionValue(ImmutableContextSet contexts, String key) { - Optional res; - - // if transient has priority - if (!this.parentCollection.getIdentifier().equals("defaults")) { - res = Optional.ofNullable(this.transientSubjectData.getOptions(contexts).get(key)); - if (res.isPresent()) { - return res; - } - - res = Optional.ofNullable(this.subjectData.getOptions(contexts).get(key)); - if (res.isPresent()) { - return res; - } - } else { - res = Optional.ofNullable(this.subjectData.getOptions(contexts).get(key)); - if (res.isPresent()) { - return res; - } - - res = Optional.ofNullable(this.transientSubjectData.getOptions(contexts).get(key)); - if (res.isPresent()) { - return res; - } - } - - for (LPSubjectReference parent : getParents(contexts)) { - res = parent.resolveLp().join().getOption(contexts, key); - if (res.isPresent()) { - return res; - } - } - - if (getParentCollection().getIdentifier().equalsIgnoreCase("defaults")) { - return Optional.empty(); - } - - res = getParentCollection().getDefaults().getOption(contexts, key); - if (res.isPresent()) { - return res; - } - - return this.service.getDefaults().getOption(contexts, key); - } - - @Override - public Tristate getPermissionValue(ImmutableContextSet contexts, String node) { - Objects.requireNonNull(contexts, "contexts"); - Objects.requireNonNull(node, "node"); - - Tristate t = this.permissionLookupCache.get(new PermissionLookupKey(node, contexts)); - this.service.getPlugin().getVerboseHandler().offerCheckData(CheckOrigin.INTERNAL, getParentCollection().getIdentifier() + "/" + this.identifier, contexts, node, t); - return t; - } - - @Override - public boolean isChildOf(ImmutableContextSet contexts, LPSubjectReference subject) { - Objects.requireNonNull(contexts, "contexts"); - Objects.requireNonNull(subject, "subject"); - - if (getParentCollection().getIdentifier().equalsIgnoreCase("defaults")) { - return this.subjectData.getParents(contexts).contains(subject) || - this.transientSubjectData.getParents(contexts).contains(subject); - } else { - return this.subjectData.getParents(contexts).contains(subject) || - this.transientSubjectData.getParents(contexts).contains(subject) || - getParentCollection().getDefaults().getParents(contexts).contains(subject) || - this.service.getDefaults().getParents(contexts).contains(subject); - } - } - - @Override - public ImmutableList getParents(ImmutableContextSet contexts) { - Objects.requireNonNull(contexts, "contexts"); - return this.parentLookupCache.get(contexts); - } - - @Override - public Optional getOption(ImmutableContextSet contexts, String key) { - return this.optionLookupCache.get(new OptionLookupKey(key, contexts)); - } - - private static final class PermissionLookupKey { - private final String node; - private final ImmutableContextSet contexts; - - public PermissionLookupKey(String node, ImmutableContextSet contexts) { - this.node = node; - this.contexts = contexts; - } - - @Override - public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof PermissionLookupKey)) return false; - final PermissionLookupKey other = (PermissionLookupKey) o; - - return this.node.equals(other.node) && this.contexts.equals(other.contexts); - } - - @Override - public int hashCode() { - return Objects.hash(this.node, this.contexts); - } - } - - public static final class OptionLookupKey { - private final String key; - private final ImmutableContextSet contexts; - - public OptionLookupKey(String key, ImmutableContextSet contexts) { - this.key = key; - this.contexts = contexts; - } - - @Override - public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof OptionLookupKey)) return false; - final OptionLookupKey other = (OptionLookupKey) o; - - return this.key.equals(other.key) && this.contexts.equals(other.contexts); - } - - @Override - public int hashCode() { - return Objects.hash(this.key, this.contexts); - } - } } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedSubjectData.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedSubjectData.java index 9440c3f77..842aef2a5 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedSubjectData.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/PersistedSubjectData.java @@ -36,8 +36,8 @@ public class PersistedSubjectData extends MonitoredSubjectData { private final PersistedSubject subject; private boolean save = true; - public PersistedSubjectData(PersistedSubject subject, NodeMapType type, LuckPermsService service, String calculatorDisplayName) { - super(subject, type, service, calculatorDisplayName); + public PersistedSubjectData(PersistedSubject subject, NodeMapType type, LuckPermsService service) { + super(subject, type, service); this.subject = subject; }