diff --git a/api/pom.xml b/api/pom.xml index 1b5c3d59..fed2c6af 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -5,7 +5,7 @@ luckperms me.lucko.luckperms - 2.12-SNAPSHOT + 2.13-SNAPSHOT 4.0.0 diff --git a/api/src/main/java/me/lucko/luckperms/api/Contexts.java b/api/src/main/java/me/lucko/luckperms/api/Contexts.java index 5e545c51..8272b33a 100644 --- a/api/src/main/java/me/lucko/luckperms/api/Contexts.java +++ b/api/src/main/java/me/lucko/luckperms/api/Contexts.java @@ -22,9 +22,8 @@ package me.lucko.luckperms.api; -import com.google.common.collect.ImmutableMap; +import me.lucko.luckperms.api.context.ContextSet; -import java.util.Collections; import java.util.Map; /** @@ -41,23 +40,19 @@ public class Contexts { * @return a context that will not apply any filters */ public static Contexts allowAll() { - return new Contexts(Collections.emptyMap(), true, true, true, true, true, true); + return new Contexts(ContextSet.empty(), true, true, true, true, true, true); } - public static Contexts of(Map context, boolean includeGlobal, boolean includeGlobalWorld, boolean applyGroups, boolean applyGlobalGroups, boolean applyGlobalWorldGroups) { - return new Contexts(context, includeGlobal, includeGlobalWorld, applyGroups, applyGlobalGroups, applyGlobalWorldGroups); - } - - public static Contexts of(Map context, boolean includeGlobal, boolean includeGlobalWorld, boolean applyGroups, boolean applyGlobalGroups, boolean applyGlobalWorldGroups, boolean op) { + public static Contexts of(ContextSet context, boolean includeGlobal, boolean includeGlobalWorld, boolean applyGroups, boolean applyGlobalGroups, boolean applyGlobalWorldGroups, boolean op) { return new Contexts(context, includeGlobal, includeGlobalWorld, applyGroups, applyGlobalGroups, applyGlobalWorldGroups, op); } - public Contexts(Map context, boolean includeGlobal, boolean includeGlobalWorld, boolean applyGroups, boolean applyGlobalGroups, boolean applyGlobalWorldGroups, boolean op) { + public Contexts(ContextSet context, boolean includeGlobal, boolean includeGlobalWorld, boolean applyGroups, boolean applyGlobalGroups, boolean applyGlobalWorldGroups, boolean op) { if (context == null) { throw new NullPointerException("context"); } - this.context = ImmutableMap.copyOf(context); + this.context = context.makeImmutable(); this.includeGlobal = includeGlobal; this.includeGlobalWorld = includeGlobalWorld; this.applyGroups = applyGroups; @@ -66,16 +61,34 @@ public class Contexts { this.op = op; } + @SuppressWarnings("deprecation") + @Deprecated + public static Contexts of(Map context, boolean includeGlobal, boolean includeGlobalWorld, boolean applyGroups, boolean applyGlobalGroups, boolean applyGlobalWorldGroups) { + return new Contexts(context, includeGlobal, includeGlobalWorld, applyGroups, applyGlobalGroups, applyGlobalWorldGroups); + } + + @SuppressWarnings("deprecation") + @Deprecated + public static Contexts of(Map context, boolean includeGlobal, boolean includeGlobalWorld, boolean applyGroups, boolean applyGlobalGroups, boolean applyGlobalWorldGroups, boolean op) { + return new Contexts(context, includeGlobal, includeGlobalWorld, applyGroups, applyGlobalGroups, applyGlobalWorldGroups, op); + } + + @Deprecated + public Contexts(Map context, boolean includeGlobal, boolean includeGlobalWorld, boolean applyGroups, boolean applyGlobalGroups, boolean applyGlobalWorldGroups, boolean op) { + this(context == null ? null : ContextSet.fromMap(context), includeGlobal, includeGlobalWorld, applyGroups, applyGlobalGroups, applyGlobalWorldGroups, op); + } + + @SuppressWarnings("deprecation") + @Deprecated public Contexts(Map context, boolean includeGlobal, boolean includeGlobalWorld, boolean applyGroups, boolean applyGlobalGroups, boolean applyGlobalWorldGroups) { this(context, includeGlobal, includeGlobalWorld, applyGroups, applyGlobalGroups, applyGlobalWorldGroups, false); } /** * The contexts that apply for this lookup - * * The keys for servers and worlds are defined as static values. */ - private final Map context; + private final ContextSet context; /** * The mode to parse defaults on Bukkit @@ -110,12 +123,23 @@ public class Contexts { /** * Gets the contexts that apply for this lookup - * @return an immutable map of context key value pairs + * @return an immutable set of context key value pairs + * @since 2.13 */ - public Map getContext() { + public ContextSet getContexts() { return this.context; } + /** + * Gets the contexts that apply for this lookup + * @return an immutable map of context key value pairs + * @deprecated in favour of {@link #getContexts()} + */ + @Deprecated + public Map getContext() { + return this.context.toMap(); + } + /** * Gets if OP defaults should be included * @return true if op defaults should be included @@ -166,7 +190,7 @@ public class Contexts { public String toString() { return "Contexts(" + - "context=" + this.getContext() + ", " + + "context=" + this.getContexts() + ", " + "op=" + this.isOp() + ", " + "includeGlobal=" + this.isIncludeGlobal() + ", " + "includeGlobalWorld=" + this.isIncludeGlobalWorld() + ", " + @@ -190,8 +214,8 @@ public class Contexts { if (o == this) return true; if (!(o instanceof Contexts)) return false; final Contexts other = (Contexts) o; - final Object this$context = this.getContext(); - final Object other$context = other.getContext(); + final Object this$context = this.getContexts(); + final Object other$context = other.getContexts(); if (this$context == null ? other$context != null : !this$context.equals(other$context)) return false; if (this.isOp() != other.isOp()) return false; if (this.isIncludeGlobal() != other.isIncludeGlobal()) return false; @@ -210,7 +234,7 @@ public class Contexts { public int hashCode() { final int PRIME = 59; int result = 1; - final Object $context = this.getContext(); + final Object $context = this.getContexts(); result = result * PRIME + ($context == null ? 43 : $context.hashCode()); result = result * PRIME + (this.isOp() ? 79 : 97); result = result * PRIME + (this.isIncludeGlobal() ? 79 : 97); diff --git a/api/src/main/java/me/lucko/luckperms/api/MetaUtils.java b/api/src/main/java/me/lucko/luckperms/api/MetaUtils.java index 9145c83a..dd26e880 100644 --- a/api/src/main/java/me/lucko/luckperms/api/MetaUtils.java +++ b/api/src/main/java/me/lucko/luckperms/api/MetaUtils.java @@ -258,7 +258,7 @@ public class MetaUtils { int priority = Integer.MIN_VALUE; String meta = null; - for (Node n : holder.getAllNodes()) { + for (Node n : holder.getAllNodes(Contexts.allowAll())) { if (!n.getValue()) { continue; } diff --git a/api/src/main/java/me/lucko/luckperms/api/Node.java b/api/src/main/java/me/lucko/luckperms/api/Node.java index f521cf74..67796d4f 100644 --- a/api/src/main/java/me/lucko/luckperms/api/Node.java +++ b/api/src/main/java/me/lucko/luckperms/api/Node.java @@ -22,10 +22,9 @@ package me.lucko.luckperms.api; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import me.lucko.luckperms.api.context.ContextSet; + +import java.util.*; /** * Represents an immutable node object @@ -109,15 +108,40 @@ public interface Node extends Map.Entry { * @param context the context key value pairs * @param worldAndServer if world and server contexts should be checked * @return true if the node should apply + * @since 2.13 */ - boolean shouldApplyWithContext(Map context, boolean worldAndServer); + boolean shouldApplyWithContext(ContextSet context, boolean worldAndServer); /** * If this node should apply in the given context * @param context the context key value pairs * @return true if the node should apply + * @since 2.13 */ - boolean shouldApplyWithContext(Map context); + boolean shouldApplyWithContext(ContextSet context); + + /** + * If this node should apply in the given context + * @param context the context key value pairs + * @param worldAndServer if world and server contexts should be checked + * @return true if the node should apply + * @deprecated in favour of {@link #shouldApplyWithContext(ContextSet, boolean)} + */ + @Deprecated + default boolean shouldApplyWithContext(Map context, boolean worldAndServer) { + return shouldApplyWithContext(ContextSet.fromMap(context), worldAndServer); + } + + /** + * If this node should apply in the given context + * @param context the context key value pairs + * @return true if the node should apply + * @deprecated in favour of {@link #shouldApplyWithContext(ContextSet)} + */ + @Deprecated + default boolean shouldApplyWithContext(Map context) { + return shouldApplyWithContext(ContextSet.fromMap(context)); + } /** * Similar to {@link #shouldApplyOnServer(String, boolean, boolean)}, except this method accepts a List @@ -186,8 +210,18 @@ public interface Node extends Map.Entry { /** * @return the extra contexts required for this node to apply + * @deprecated in favour of {@link #getContexts()} */ - Map getExtraContexts(); + @Deprecated + default Map getExtraContexts() { + return getContexts().toMap(); + } + + /** + * @return the extra contexts required for this node to apply + * @since 2.13 + */ + ContextSet getContexts(); /** * Converts this node into a serialized form @@ -302,7 +336,9 @@ public interface Node extends Map.Entry { Builder setServer(String server) throws IllegalArgumentException; Builder withExtraContext(String key, String value); Builder withExtraContext(Map map); + Builder withExtraContext(Set> context); Builder withExtraContext(Map.Entry entry); + Builder withExtraContext(ContextSet set); Node build(); } diff --git a/api/src/main/java/me/lucko/luckperms/api/User.java b/api/src/main/java/me/lucko/luckperms/api/User.java index 4755b2d8..70a13a7a 100644 --- a/api/src/main/java/me/lucko/luckperms/api/User.java +++ b/api/src/main/java/me/lucko/luckperms/api/User.java @@ -22,10 +22,12 @@ package me.lucko.luckperms.api; +import me.lucko.luckperms.api.caching.UserData; import me.lucko.luckperms.exceptions.ObjectAlreadyHasException; import me.lucko.luckperms.exceptions.ObjectLacksException; import java.util.List; +import java.util.Optional; import java.util.UUID; /** @@ -64,6 +66,13 @@ public interface User extends PermissionHolder { */ void refreshPermissions(); + /** + * Gets the user's {@link UserData} cache, if they have one setup. + * @return an optional, possibly containing the user's cached lookup data. + * @since 2.13 + */ + Optional getUserDataCache(); + /** * Check to see if the user is a member of a group * @param group The group to check membership of diff --git a/api/src/main/java/me/lucko/luckperms/api/caching/MetaData.java b/api/src/main/java/me/lucko/luckperms/api/caching/MetaData.java new file mode 100644 index 00000000..efa39e9f --- /dev/null +++ b/api/src/main/java/me/lucko/luckperms/api/caching/MetaData.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2016 Lucko (Luck) + * + * 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.api.caching; + +import java.util.Map; +import java.util.SortedMap; + +/** + * Holds cached Meta lookup data for a specific set of contexts + * @since 2.13 + */ +public interface MetaData { + + /** + * Gets an immutable copy of the meta this user has + * @return an immutable map of meta + */ + Map getMeta(); + + /** + * Gets an immutable sorted map of all of the prefixes the user has, whereby the first value is the highest priority prefix. + * @return a sorted map of prefixes + */ + SortedMap getPrefixes(); + + /** + * Gets an immutable sorted map of all of the suffixes the user has, whereby the first value is the highest priority suffix. + * @return a sorted map of suffixes + */ + SortedMap getSuffixes(); + + /** + * Gets the user's highest priority prefix, or null if the user has no prefixes + * @return a prefix string, or null + */ + String getPrefix(); + + /** + * Gets the user's highest priority suffix, or null if the user has no suffixes + * @return a suffix string, or null + */ + String getSuffix(); + +} diff --git a/api/src/main/java/me/lucko/luckperms/api/caching/PermissionData.java b/api/src/main/java/me/lucko/luckperms/api/caching/PermissionData.java new file mode 100644 index 00000000..f6d7bf4a --- /dev/null +++ b/api/src/main/java/me/lucko/luckperms/api/caching/PermissionData.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2016 Lucko (Luck) + * + * 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.api.caching; + +import me.lucko.luckperms.api.Tristate; + +import java.util.Map; + +/** + * Holds cached Permission lookup data for a specific set of contexts + * @since 2.13 + */ +public interface PermissionData { + + /** + * Gets a permission value for the given permission node + * @param permission the permission node + * @return a tristate result + * @throws NullPointerException if permission is null + */ + Tristate getPermissionValue(String permission); + + /** + * Invalidates the underlying permission calculator cache. + * Can be called to allow for an update in defaults. + */ + void invalidateCache(); + + /** + * Gets an immutable copy of the permission map backing the permission calculator + * @return an immutable set of permissions + */ + Map getImmutableBacking(); + +} diff --git a/api/src/main/java/me/lucko/luckperms/api/caching/UserData.java b/api/src/main/java/me/lucko/luckperms/api/caching/UserData.java new file mode 100644 index 00000000..8a58868b --- /dev/null +++ b/api/src/main/java/me/lucko/luckperms/api/caching/UserData.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2016 Lucko (Luck) + * + * 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.api.caching; + +import me.lucko.luckperms.api.Contexts; + +import java.util.Set; + +/** + * Holds cached permission and meta lookup data for a {@link me.lucko.luckperms.api.User}. + * Data is only likely to be available for online users. All calls will account for inheritance, as well as any + * default data provided by the platform. This calls are heavily cached and are therefore fast. + * + * @since 2.13 + */ +public interface UserData { + + /** + * Gets PermissionData from the cache, given a specified context. + * If the data is not cached, it is calculated. Therefore, this call could be costly. + * @param contexts the contexts to get the permission data in + * @return a permission data instance + * @throws NullPointerException if contexts is null + */ + PermissionData getPermissionData(Contexts contexts); + + /** + * Gets MetaData from the cache, given a specified context. + * If the data is not cached, it is calculated. Therefore, this call could be costly. + * @param contexts the contexts to get the permission data in + * @return a meta data instance + * @throws NullPointerException if contexts is null + */ + MetaData getMetaData(Contexts contexts); + + /** + * Calculates permission data, bypassing the cache. + * @param contexts the contexts to get permission data in + * @return a permission data instance + * @throws NullPointerException if contexts is null + */ + PermissionData calculatePermissions(Contexts contexts); + + /** + * Calculates meta data, bypassing the cache. + * @param contexts the contexts to get meta data in + * @return a meta data instance + * @throws NullPointerException if contexts is null + */ + MetaData calculateMeta(Contexts contexts); + + /** + * Calculates permission data and stores it in the cache. If there is already data cached for the given contexts, + * and if the resultant output is different, the cached value is updated. + * @param contexts the contexts to recalculate in. + * @throws NullPointerException if contexts is null + */ + void recalculatePermissions(Contexts contexts); + + /** + * Calculates meta data and stores it in the cache. If there is already data cached for the given contexts, + * and if the resultant output is different, the cached value is updated. + * @param contexts the contexts to recalculate in. + * @throws NullPointerException if contexts is null + */ + void recalculateMeta(Contexts contexts); + + /** + * Calls {@link #recalculatePermissions(Contexts)} for all current loaded contexts + */ + void recalculatePermissions(); + + /** + * Calls {@link #recalculateMeta(Contexts)} for all current loaded contexts + */ + void recalculateMeta(); + + /** + * Calls {@link #preCalculate(Contexts)} for the given contexts + * @param contexts a set of contexts + * @throws NullPointerException if contexts is null + */ + void preCalculate(Set contexts); + + /** + * Ensures that PermissionData and MetaData is cached for a context. If the cache does not contain any data for the + * context, it will be calculated and saved. + * @param contexts the contexts to pre-calculate for + * @throws NullPointerException if contexts is null + */ + void preCalculate(Contexts contexts); + + /** + * Invalidates all of the underlying Permission calculators. + * Can be called to allow for an update in defaults. + */ + void invalidatePermissionCalculators(); + +} diff --git a/api/src/main/java/me/lucko/luckperms/api/context/ContextSet.java b/api/src/main/java/me/lucko/luckperms/api/context/ContextSet.java new file mode 100644 index 00000000..184d1014 --- /dev/null +++ b/api/src/main/java/me/lucko/luckperms/api/context/ContextSet.java @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2016 Lucko (Luck) + * + * 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.api.context; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Holds contexts. + * All contained contexts are immutable, and unlike {@link MutableContextSet}, contexts cannot be added or removed. + * + * @since 2.13 + */ +public class ContextSet { + + /** + * Make a singleton ContextSet from a context pair + * @param key the key + * @param value the value + * @return a new ContextSet containing one KV pair + * @throws NullPointerException if key or value is null + */ + public static ContextSet singleton(String key, String value) { + if (key == null) { + throw new NullPointerException("key"); + } + if (value == null) { + throw new NullPointerException("value"); + } + + MutableContextSet set = new MutableContextSet(); + set.add(key, value); + return set.immutableCopy(); + } + + /** + * Creates a ContextSet from an existing map + * @param map the map to copy from + * @return a new ContextSet representing the pairs from the map + * @throws NullPointerException if the map is null + */ + public static ContextSet fromMap(Map map) { + if (map == null) { + throw new NullPointerException("map"); + } + + MutableContextSet set = new MutableContextSet(); + set.addAll(map); + return set.immutableCopy(); + } + + /** + * Creates a ContextSet from an existing iterable of Map Entries + * @param iterable the iterable to copy from + * @return a new ContextSet representing the pairs in the iterable + * @throws NullPointerException if the iterable is null + */ + public static ContextSet fromEntries(Iterable> iterable) { + if (iterable == null) { + throw new NullPointerException("iterable"); + } + + MutableContextSet set = new MutableContextSet(); + set.addAll(iterable); + return set.immutableCopy(); + } + + /** + * Creates a new ContextSet from an existing set. + * Only really useful for converting between mutable and immutable types. + * @param contextSet the context set to copy from + * @return a new ContextSet with the same content and the one provided + * @throws NullPointerException if contextSet is null + */ + public static ContextSet fromSet(ContextSet contextSet) { + if (contextSet == null) { + throw new NullPointerException("contextSet"); + } + + MutableContextSet set = new MutableContextSet(); + set.addAll(contextSet.toSet()); + return set.immutableCopy(); + } + + /** + * Creates a new empty ContextSet. + * @return a new ContextSet + */ + public static ContextSet empty() { + return new ContextSet(); + } + + final Set> contexts; + + public ContextSet() { + this.contexts = new HashSet<>(); + } + + protected ContextSet(Set> contexts) { + this.contexts = contexts; + } + + /** + * Check to see if this set is in an immutable form + * @return true if the set is immutable + */ + public boolean isImmutable() { + return true; + } + + /** + * If the set is mutable, this method will return an immutable copy. Otherwise just returns itself. + * @return an immutable ContextSet + */ + public ContextSet makeImmutable() { + return this; + } + + /** + * Converts this ContextSet to an immutable {@link Set} of {@link Map.Entry}s. + * @return an immutable set + */ + public Set> toSet() { + synchronized (contexts) { + return ImmutableSet.copyOf(contexts); + } + } + + /** + * Converts this ContextSet to an immutable {@link Map} + * + * NOTE: Use of this method may result in data being lost. ContextSets can contain lots of different values for + * one key. + * + * @return an immutable map + */ + public Map toMap() { + synchronized (contexts) { + return ImmutableMap.copyOf(contexts.stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + } + } + + /** + * Check if the set contains at least one value for the given key. + * @param key the key to check for + * @return true if the set contains a value for the key + * @throws NullPointerException if the key is null + */ + public boolean containsKey(String key) { + if (key == null) { + throw new NullPointerException("key"); + } + + synchronized (contexts) { + for (Map.Entry e : contexts) { + if (e.getKey().equalsIgnoreCase(key)) { + return true; + } + } + } + return false; + } + + /** + * Gets a set of all of the values mapped to the given key + * @param key the key to find values for + * @return a set of values + * @throws NullPointerException if the key is null + */ + public Set getValues(String key) { + if (key == null) { + throw new NullPointerException("key"); + } + + synchronized (contexts) { + return ImmutableSet.copyOf(contexts.stream() + .filter(e -> e.getKey().equalsIgnoreCase(key)) + .map(Map.Entry::getValue) + .collect(Collectors.toSet()) + ); + } + } + + /** + * Check if thr set contains a given key mapped to a given value + * @param key the key to look for + * @param value the value to look for (case sensitive) + * @return true if the set contains the KV pair + * @throws NullPointerException if the key or value is null + */ + public boolean has(String key, String value) { + if (key == null) { + throw new NullPointerException("key"); + } + if (value == null) { + throw new NullPointerException("value"); + } + + synchronized (contexts) { + for (Map.Entry e : contexts) { + if (!e.getKey().equalsIgnoreCase(key)) { + continue; + } + + if (!e.getValue().equals(value)) { + continue; + } + + return true; + } + } + return false; + } + + /** + * Same as {@link #has(String, String)}, except ignores the case of the value. + * @param key the key to look for + * @param value the value to look for + * @return true if the set contains the KV pair + * @throws NullPointerException if the key or value is null + */ + public boolean hasIgnoreCase(String key, String value) { + if (key == null) { + throw new NullPointerException("key"); + } + if (value == null) { + throw new NullPointerException("value"); + } + + synchronized (contexts) { + for (Map.Entry e : contexts) { + if (!e.getKey().equalsIgnoreCase(key)) { + continue; + } + + if (!e.getValue().equalsIgnoreCase(value)) { + continue; + } + + return true; + } + } + return false; + } + + /** + * Check if the set is empty + * @return true if the set is empty + */ + public boolean isEmpty() { + synchronized (contexts) { + return contexts.isEmpty(); + } + } + + /** + * Gets the number of key-value context pairs in the set + * @return the size of the set + */ + public int size() { + synchronized (contexts) { + return contexts.size(); + } + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof ContextSet)) return false; + final ContextSet other = (ContextSet) o; + + final Object thisContexts = this.contexts; + final Object otherContexts = other.contexts; + return thisContexts == null ? otherContexts == null : thisContexts.equals(otherContexts); + } + + @Override + public int hashCode() { + return 59 + (this.contexts == null ? 43 : this.contexts.hashCode()); + } +} diff --git a/api/src/main/java/me/lucko/luckperms/api/context/IContextCalculator.java b/api/src/main/java/me/lucko/luckperms/api/context/IContextCalculator.java index 38aa7bd0..2561e9bd 100644 --- a/api/src/main/java/me/lucko/luckperms/api/context/IContextCalculator.java +++ b/api/src/main/java/me/lucko/luckperms/api/context/IContextCalculator.java @@ -22,6 +22,7 @@ package me.lucko.luckperms.api.context; +import java.util.HashMap; import java.util.Map; /** @@ -34,11 +35,40 @@ public interface IContextCalculator { /** * Gives the subject all of the applicable contexts they meet - * @param subject the subject to add contexts tp + * @param subject the subject to add contexts to * @param accumulator a map of contexts to add to * @return the map + * @deprecated in favour of {@link #giveApplicableContext(Object, MutableContextSet)}. Older implementations of this interface + * will still work, as the replacement method is given as a default, and falls back to using this method. */ - Map giveApplicableContext(T subject, Map accumulator); + @Deprecated + default Map giveApplicableContext(T subject, Map accumulator) { + MutableContextSet acc = new MutableContextSet(); + giveApplicableContext(subject, acc); + + accumulator.putAll(acc.toMap()); + return accumulator; + } + + /** + * Gives the subject all of the applicable contexts they meet + * + *

You MUST implement this method. The default is only provided for backwards compatibility with + * {@link #giveApplicableContext(Object, Map)}. + * + * @param subject the subject to add contexts to + * @param accumulator a map of contexts to add to + * @return the map + * @since 2.13 + */ + @SuppressWarnings("deprecation") + default MutableContextSet giveApplicableContext(T subject, MutableContextSet accumulator) { + Map acc = new HashMap<>(); + giveApplicableContext(subject, acc); + + accumulator.addAll(acc.entrySet()); + return accumulator; + } /** * Checks to see if a context is applicable to a subject diff --git a/api/src/main/java/me/lucko/luckperms/api/context/MutableContextSet.java b/api/src/main/java/me/lucko/luckperms/api/context/MutableContextSet.java new file mode 100644 index 00000000..7aa71f8f --- /dev/null +++ b/api/src/main/java/me/lucko/luckperms/api/context/MutableContextSet.java @@ -0,0 +1,272 @@ +/* + * Copyright (c) 2016 Lucko (Luck) + * + * 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.api.context; + +import com.google.common.collect.Maps; + +import java.util.HashSet; +import java.util.Map; + +/** + * Holds contexts + * All contained contexts are immutable, but contexts can be added or removed from the set. + * + * @since 2.13 + */ +public class MutableContextSet extends ContextSet { + + /** + * Make a singleton MutableContextSet from a context pair + * @param key the key + * @param value the value + * @return a new MutableContextSet containing one KV pair + * @throws NullPointerException if key or value is null + */ + public static MutableContextSet singleton(String key, String value) { + MutableContextSet set = new MutableContextSet(); + set.add(key, value); + return set; + } + + /** + * Creates a MutableContextSet from an existing map + * @param map the map to copy from + * @return a new MutableContextSet representing the pairs from the map + * @throws NullPointerException if the map is null + */ + public static MutableContextSet fromMap(Map map) { + MutableContextSet set = new MutableContextSet(); + set.addAll(map); + return set; + } + + /** + * Creates a MutableContextSet from an existing iterable of Map Entries + * @param iterable the iterable to copy from + * @return a new MutableContextSet representing the pairs in the iterable + * @throws NullPointerException if the iterable is null + */ + public static MutableContextSet fromEntries(Iterable> iterable) { + MutableContextSet set = new MutableContextSet(); + set.addAll(iterable); + return set; + } + + /** + * Creates a new MutableContextSet from an existing set. + * Only really useful for converting between mutable and immutable types. + * @param contextSet the context set to copy from + * @return a new MutableContextSet with the same content and the one provided + * @throws NullPointerException if contextSet is null + */ + public static MutableContextSet fromSet(ContextSet contextSet) { + MutableContextSet set = new MutableContextSet(); + set.addAll(contextSet.toSet()); + return set; + } + + /** + * Creates a new empty MutableContextSet. + * @return a new MutableContextSet + */ + public static MutableContextSet empty() { + return new MutableContextSet(); + } + + @Override + public boolean isImmutable() { + return false; + } + + @Override + public ContextSet makeImmutable() { + return immutableCopy(); + } + + /** + * Returns an immutable copy of this set. + * @return an immutable copy of this set + */ + public ContextSet immutableCopy() { + synchronized (contexts) { + return new ContextSet(new HashSet<>(contexts)); + } + } + + /** + * Adds a new key value pair to the set + * @param key the key to add + * @param value the value to add + * @throws NullPointerException if the key or value is null + */ + public void add(String key, String value) { + if (key == null) { + throw new NullPointerException("key"); + } + if (value == null) { + throw new NullPointerException("value"); + } + + synchronized (contexts) { + contexts.add(Maps.immutableEntry(key, value)); + } + } + + /** + * Adds a new key value pair to the set + * @param entry the entry to add + * @throws NullPointerException if the entry is null + */ + public void add(Map.Entry entry) { + if (entry == null) { + throw new NullPointerException("context"); + } + + synchronized (contexts) { + contexts.add(Maps.immutableEntry(entry.getKey(), entry.getValue())); + } + } + + /** + * Adds an iterable containing contexts to the set + * @param iterable an iterable of key value context pairs + * @throws NullPointerException if iterable is null + */ + public void addAll(Iterable> iterable) { + if (iterable == null) { + throw new NullPointerException("contexts"); + } + + synchronized (this.contexts) { + for (Map.Entry e : iterable) { + this.contexts.add(Maps.immutableEntry(e.getKey(), e.getValue())); + } + } + } + + /** + * Adds the entry set of a map to the set + * @param map the map to add from + * @throws NullPointerException if the map is null + */ + public void addAll(Map map) { + if (map == null) { + throw new NullPointerException("contexts"); + } + addAll(map.entrySet()); + } + + /** + * Adds of of the values in another ContextSet to this set + * @param contextSet the set to add from + * @throws NullPointerException if the contextSet is null + */ + public void addAll(ContextSet contextSet) { + if (contextSet == null) { + throw new NullPointerException("contextSet"); + } + + synchronized (this.contexts) { + this.contexts.addAll(contextSet.toSet()); + } + } + + /** + * Remove a key value pair from this set + * @param key the key to remove + * @param value the value to remove (case sensitive) + * @throws NullPointerException if the key or value is null + */ + public void remove(String key, String value) { + if (key == null) { + throw new NullPointerException("key"); + } + if (value == null) { + throw new NullPointerException("value"); + } + + synchronized (contexts) { + contexts.removeIf(e -> e.getKey().equalsIgnoreCase(key) && e.getValue().equals(value)); + } + } + + /** + * Same as {@link #remove(String, String)}, except ignores the case of the value + * @param key the key to remove + * @param value the value to remove + * @throws NullPointerException if the key or value is null + */ + public void removeIgnoreCase(String key, String value) { + if (key == null) { + throw new NullPointerException("key"); + } + if (value == null) { + throw new NullPointerException("value"); + } + + synchronized (contexts) { + contexts.removeIf(e -> e.getKey().equalsIgnoreCase(key) && e.getValue().equalsIgnoreCase(value)); + } + } + + /** + * Removes all pairs with the given key + * @param key the key to remove + * @throws NullPointerException if the key is null + */ + public void removeAll(String key) { + if (key == null) { + throw new NullPointerException("key"); + } + + synchronized (contexts) { + contexts.removeIf(e -> e.getKey().equalsIgnoreCase(key)); + } + } + + /** + * Clears the set + */ + public void clear() { + synchronized (contexts) { + contexts.clear(); + } + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof ContextSet)) return false; + final ContextSet other = (ContextSet) o; + + final Object thisContexts = this.contexts; + final Object otherContexts = other.contexts; + return thisContexts == null ? otherContexts == null : thisContexts.equals(otherContexts); + } + + @Override + public int hashCode() { + return 59 + (this.contexts == null ? 43 : this.contexts.hashCode()); + } + +} diff --git a/bukkit-legacy/pom.xml b/bukkit-legacy/pom.xml index 48bf8069..f2f03178 100644 --- a/bukkit-legacy/pom.xml +++ b/bukkit-legacy/pom.xml @@ -5,7 +5,7 @@ luckperms me.lucko.luckperms - 2.12-SNAPSHOT + 2.13-SNAPSHOT 4.0.0 diff --git a/bukkit-placeholders/pom.xml b/bukkit-placeholders/pom.xml index 65ce97e0..325e2200 100644 --- a/bukkit-placeholders/pom.xml +++ b/bukkit-placeholders/pom.xml @@ -5,7 +5,7 @@ luckperms me.lucko.luckperms - 2.12-SNAPSHOT + 2.13-SNAPSHOT 4.0.0 diff --git a/bukkit/pom.xml b/bukkit/pom.xml index 82c1b6c4..643847b6 100644 --- a/bukkit/pom.xml +++ b/bukkit/pom.xml @@ -5,7 +5,7 @@ luckperms me.lucko.luckperms - 2.12-SNAPSHOT + 2.13-SNAPSHOT 4.0.0 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 01114bb6..f6927dd8 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/LPBukkitPlugin.java @@ -28,6 +28,8 @@ import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.Logger; import me.lucko.luckperms.api.LuckPermsApi; import me.lucko.luckperms.api.PlatformType; +import me.lucko.luckperms.api.context.ContextSet; +import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.bukkit.calculators.AutoOPListener; import me.lucko.luckperms.bukkit.calculators.DefaultsProvider; import me.lucko.luckperms.bukkit.vault.VaultHook; @@ -274,32 +276,32 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { @Override public Set getPreProcessContexts(boolean op) { - Set> c = new HashSet<>(); - c.add(Collections.emptyMap()); - c.add(Collections.singletonMap("server", getConfiguration().getServer())); + Set c = new HashSet<>(); + c.add(ContextSet.empty()); + c.add(ContextSet.singleton("server", getConfiguration().getServer())); // Pre process all worlds c.addAll(getServer().getWorlds().stream() .map(World::getName) .map(s -> { - Map map = new HashMap<>(); - map.put("server", getConfiguration().getServer()); - map.put("world", s); - return map; + MutableContextSet set = new MutableContextSet(); + set.add("server", getConfiguration().getServer()); + set.add("world", s); + return set.makeImmutable(); }) .collect(Collectors.toList()) ); // Pre process the separate Vault server, if any if (!getConfiguration().getServer().equals(getConfiguration().getVaultServer())) { - c.add(Collections.singletonMap("server", getConfiguration().getVaultServer())); + c.add(ContextSet.singleton("server", getConfiguration().getVaultServer())); c.addAll(getServer().getWorlds().stream() .map(World::getName) .map(s -> { - Map map = new HashMap<>(); - map.put("server", getConfiguration().getVaultServer()); - map.put("world", s); - return map; + MutableContextSet set = new MutableContextSet(); + set.add("server", getConfiguration().getVaultServer()); + set.add("world", s); + return set.makeImmutable(); }) .collect(Collectors.toList()) ); @@ -309,8 +311,8 @@ public class LPBukkitPlugin extends JavaPlugin implements LuckPermsPlugin { // Convert to full Contexts contexts.addAll(c.stream() - .map(map -> new Contexts( - map, + .map(set -> new Contexts( + set, getConfiguration().isIncludingGlobalPerms(), getConfiguration().isIncludingGlobalWorldPerms(), true, diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/WorldCalculator.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/WorldCalculator.java index 76f0ce06..fb025432 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/WorldCalculator.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/WorldCalculator.java @@ -26,6 +26,7 @@ import com.google.common.collect.Maps; import lombok.Getter; import lombok.RequiredArgsConstructor; import me.lucko.luckperms.api.context.ContextCalculator; +import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.common.LuckPermsPlugin; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -48,11 +49,11 @@ public class WorldCalculator extends ContextCalculator implements Listen private final Map worldCache = new ConcurrentHashMap<>(); @Override - public Map giveApplicableContext(Player subject, Map accumulator) { + public MutableContextSet giveApplicableContext(Player subject, MutableContextSet accumulator) { String world = getWorld(subject); if (world != null) { - accumulator.put(WORLD_KEY, world); + accumulator.add(Maps.immutableEntry(WORLD_KEY, world)); } return accumulator; diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/inject/LPPermissible.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/inject/LPPermissible.java index f3ae1b11..49e02516 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/inject/LPPermissible.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/inject/LPPermissible.java @@ -26,6 +26,7 @@ import lombok.Getter; import lombok.NonNull; import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.Tristate; +import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.bukkit.LPBukkitPlugin; import me.lucko.luckperms.common.users.User; import org.bukkit.Bukkit; @@ -66,7 +67,7 @@ public class LPPermissible extends PermissibleBase { public Contexts calculateContexts() { return new Contexts( - plugin.getContextManager().giveApplicableContext(parent, new HashMap<>()), + plugin.getContextManager().giveApplicableContext(parent, new MutableContextSet()), plugin.getConfiguration().isIncludingGlobalPerms(), plugin.getConfiguration().isIncludingGlobalWorldPerms(), true, diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/VaultChatHook.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/VaultChatHook.java index acb843b2..120dec5f 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/VaultChatHook.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/VaultChatHook.java @@ -25,7 +25,8 @@ package me.lucko.luckperms.bukkit.vault; import lombok.NonNull; import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.Node; -import me.lucko.luckperms.common.caching.MetaData; +import me.lucko.luckperms.api.caching.MetaData; +import me.lucko.luckperms.api.context.ContextSet; import me.lucko.luckperms.common.core.PermissionHolder; import me.lucko.luckperms.common.groups.Group; import me.lucko.luckperms.common.users.User; @@ -214,7 +215,7 @@ public class VaultChatHook extends Chat { context.put("world", world); } - for (Node n : group.getAllNodes(null, new Contexts(context, perms.isIncludeGlobal(), true, true, true, true))) { + for (Node n : group.getAllNodes(null, new Contexts(ContextSet.fromMap(context), perms.isIncludeGlobal(), true, true, true, true, false))) { if (!n.getValue()) { continue; } diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/VaultPermissionHook.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/VaultPermissionHook.java index 59fdda03..89d15482 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/VaultPermissionHook.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/vault/VaultPermissionHook.java @@ -27,6 +27,7 @@ import lombok.NonNull; import lombok.Setter; import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.api.context.ContextSet; import me.lucko.luckperms.bukkit.LPBukkitPlugin; import me.lucko.luckperms.common.core.PermissionHolder; import me.lucko.luckperms.common.groups.Group; @@ -146,7 +147,7 @@ public class VaultPermissionHook extends Permission { context.put("world", world); } context.put("server", server); - return new Contexts(context, isIncludeGlobal(), true, true, true, true); + return new Contexts(ContextSet.fromMap(context), isIncludeGlobal(), true, true, true, true, false); } @Override diff --git a/bungee/pom.xml b/bungee/pom.xml index 8a29dcb7..46ce2648 100644 --- a/bungee/pom.xml +++ b/bungee/pom.xml @@ -5,7 +5,7 @@ luckperms me.lucko.luckperms - 2.12-SNAPSHOT + 2.13-SNAPSHOT 4.0.0 diff --git a/bungee/src/main/java/me/lucko/luckperms/bungee/BackendServerCalculator.java b/bungee/src/main/java/me/lucko/luckperms/bungee/BackendServerCalculator.java index c27288f9..69c57644 100644 --- a/bungee/src/main/java/me/lucko/luckperms/bungee/BackendServerCalculator.java +++ b/bungee/src/main/java/me/lucko/luckperms/bungee/BackendServerCalculator.java @@ -24,6 +24,7 @@ package me.lucko.luckperms.bungee; import com.google.common.collect.Maps; import me.lucko.luckperms.api.context.ContextCalculator; +import me.lucko.luckperms.api.context.MutableContextSet; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.event.ServerSwitchEvent; import net.md_5.bungee.api.plugin.Listener; @@ -35,11 +36,11 @@ public class BackendServerCalculator extends ContextCalculator im private static final String WORLD_KEY = "world"; @Override - public Map giveApplicableContext(ProxiedPlayer subject, Map accumulator) { + public MutableContextSet giveApplicableContext(ProxiedPlayer subject, MutableContextSet accumulator) { String server = getServer(subject); if (server != null) { - accumulator.put(WORLD_KEY, server); + accumulator.add(Maps.immutableEntry(WORLD_KEY, server)); } return accumulator; diff --git a/bungee/src/main/java/me/lucko/luckperms/bungee/BungeeListener.java b/bungee/src/main/java/me/lucko/luckperms/bungee/BungeeListener.java index 9a205890..089a34ee 100644 --- a/bungee/src/main/java/me/lucko/luckperms/bungee/BungeeListener.java +++ b/bungee/src/main/java/me/lucko/luckperms/bungee/BungeeListener.java @@ -23,6 +23,7 @@ package me.lucko.luckperms.bungee; import me.lucko.luckperms.api.Contexts; +import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.api.event.events.UserFirstLoginEvent; import me.lucko.luckperms.common.constants.Message; import me.lucko.luckperms.common.core.UuidCache; @@ -38,7 +39,6 @@ import net.md_5.bungee.api.event.PostLoginEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.event.EventHandler; -import java.util.HashMap; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -72,12 +72,13 @@ public class BungeeListener extends AbstractListener implements Listener { } Contexts contexts = new Contexts( - plugin.getContextManager().giveApplicableContext(player, new HashMap<>()), + plugin.getContextManager().giveApplicableContext(player, MutableContextSet.empty()), plugin.getConfiguration().isIncludingGlobalPerms(), plugin.getConfiguration().isIncludingGlobalWorldPerms(), true, plugin.getConfiguration().isApplyingGlobalGroups(), - plugin.getConfiguration().isApplyingGlobalWorldGroups() + plugin.getConfiguration().isApplyingGlobalWorldGroups(), + false ); e.setHasPermission(user.getUserData().getPermissionData(contexts).getPermissionValue(e.getPermission()).asBoolean()); 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 534fe660..a5bf2539 100644 --- a/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java +++ b/bungee/src/main/java/me/lucko/luckperms/bungee/LPBungeePlugin.java @@ -27,6 +27,8 @@ import me.lucko.luckperms.ApiHandler; import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.Logger; import me.lucko.luckperms.api.PlatformType; +import me.lucko.luckperms.api.context.ContextSet; +import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.common.LuckPermsPlugin; import me.lucko.luckperms.common.api.ApiProvider; import me.lucko.luckperms.common.calculators.CalculatorFactory; @@ -212,28 +214,29 @@ public class LPBungeePlugin extends Plugin implements LuckPermsPlugin { @Override public Set getPreProcessContexts(boolean op) { - Set> c = new HashSet<>(); - c.add(Collections.emptyMap()); - c.add(Collections.singletonMap("server", getConfiguration().getServer())); + Set c = new HashSet<>(); + c.add(ContextSet.empty()); + c.add(ContextSet.singleton("server", getConfiguration().getServer())); c.addAll(getProxy().getServers().values().stream() .map(ServerInfo::getName) .map(s -> { - Map map = new HashMap<>(); - map.put("server", getConfiguration().getServer()); - map.put("world", s); - return map; + MutableContextSet set = new MutableContextSet(); + set.add("server", getConfiguration().getServer()); + set.add("world", s); + return set.makeImmutable(); }) .collect(Collectors.toList()) ); return c.stream() - .map(map -> new Contexts( - map, + .map(set -> new Contexts( + set, getConfiguration().isIncludingGlobalPerms(), getConfiguration().isIncludingGlobalWorldPerms(), true, getConfiguration().isApplyingGlobalGroups(), - getConfiguration().isApplyingGlobalWorldGroups() + getConfiguration().isApplyingGlobalWorldGroups(), + false )) .collect(Collectors.toSet()); } diff --git a/common/pom.xml b/common/pom.xml index 919eec82..474ba3ee 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -5,7 +5,7 @@ luckperms me.lucko.luckperms - 2.12-SNAPSHOT + 2.13-SNAPSHOT 4.0.0 diff --git a/common/src/main/java/me/lucko/luckperms/common/api/ApiProvider.java b/common/src/main/java/me/lucko/luckperms/common/api/ApiProvider.java index 659fa0d6..6b7abf7f 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/ApiProvider.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/ApiProvider.java @@ -69,7 +69,7 @@ public class ApiProvider implements LuckPermsApi { @Override public double getApiVersion() { - return 2.12; + return 2.13; } @Override diff --git a/common/src/main/java/me/lucko/luckperms/common/api/internal/PermissionHolderLink.java b/common/src/main/java/me/lucko/luckperms/common/api/internal/PermissionHolderLink.java index 9e90824e..f9331736 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/internal/PermissionHolderLink.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/internal/PermissionHolderLink.java @@ -25,6 +25,7 @@ package me.lucko.luckperms.common.api.internal; import lombok.AllArgsConstructor; import lombok.NonNull; import me.lucko.luckperms.api.*; +import me.lucko.luckperms.api.context.ContextSet; import me.lucko.luckperms.exceptions.ObjectAlreadyHasException; import me.lucko.luckperms.exceptions.ObjectLacksException; @@ -247,7 +248,7 @@ public class PermissionHolderLink implements PermissionHolder { if (world != null && !world.equals("")) { context.put("world", world); } - return master.exportNodes(new Contexts(context, true, true, true, true, true), false); + return master.exportNodes(new Contexts(ContextSet.fromMap(context), true, true, true, true, true, false), false); } @Override @@ -259,7 +260,7 @@ public class PermissionHolderLink implements PermissionHolder { if (world != null && !world.equals("")) { context.put("world", world); } - return master.exportNodes(new Contexts(context, true, true, true, true, true), false); + return master.exportNodes(new Contexts(ContextSet.fromMap(context), true, true, true, true, true, false), false); } @Override @@ -283,7 +284,7 @@ public class PermissionHolderLink implements PermissionHolder { if (world != null && !world.equals("")) { extraContext.put("world", world); } - return master.exportNodes(new Contexts(extraContext, includeGlobal, includeGlobal, applyGroups, true, true), false); + return master.exportNodes(new Contexts(ContextSet.fromMap(extraContext), includeGlobal, includeGlobal, applyGroups, true, true, false), false); } @Override diff --git a/common/src/main/java/me/lucko/luckperms/common/api/internal/UserLink.java b/common/src/main/java/me/lucko/luckperms/common/api/internal/UserLink.java index 31beb3bc..d4369848 100644 --- a/common/src/main/java/me/lucko/luckperms/common/api/internal/UserLink.java +++ b/common/src/main/java/me/lucko/luckperms/common/api/internal/UserLink.java @@ -27,10 +27,12 @@ import lombok.Getter; import lombok.NonNull; import me.lucko.luckperms.api.Group; import me.lucko.luckperms.api.User; +import me.lucko.luckperms.api.caching.UserData; import me.lucko.luckperms.exceptions.ObjectAlreadyHasException; import me.lucko.luckperms.exceptions.ObjectLacksException; import java.util.List; +import java.util.Optional; import java.util.UUID; import static me.lucko.luckperms.common.api.internal.Utils.*; @@ -82,6 +84,11 @@ public class UserLink extends PermissionHolderLink implements User { master.getRefreshBuffer().requestDirectly(); } + @Override + public Optional getUserDataCache() { + return Optional.ofNullable(master.getUserData()); + } + @Override public boolean isInGroup(@NonNull Group group) { checkGroup(group); diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/MetaData.java b/common/src/main/java/me/lucko/luckperms/common/caching/MetaCache.java similarity index 89% rename from common/src/main/java/me/lucko/luckperms/common/caching/MetaData.java rename to common/src/main/java/me/lucko/luckperms/common/caching/MetaCache.java index d6612514..d65a8eb6 100644 --- a/common/src/main/java/me/lucko/luckperms/common/caching/MetaData.java +++ b/common/src/main/java/me/lucko/luckperms/common/caching/MetaCache.java @@ -28,6 +28,8 @@ import lombok.RequiredArgsConstructor; import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.LocalizedNode; import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.api.caching.MetaData; +import me.lucko.luckperms.api.context.MutableContextSet; import java.util.*; @@ -35,7 +37,7 @@ import java.util.*; * Holds a user's cached meta for a given context */ @RequiredArgsConstructor -public class MetaData { +public class MetaCache implements MetaData { private final Contexts contexts; private final SortedMap prefixes = new TreeMap<>(Comparator.reverseOrder()); @@ -46,9 +48,11 @@ public class MetaData { public void loadMeta(SortedSet nodes) { invalidateCache(); - Map contexts = new HashMap<>(this.contexts.getContext()); - String server = contexts.remove("server"); - String world = contexts.remove("world"); + MutableContextSet contexts = MutableContextSet.fromSet(this.contexts.getContexts()); + String server = contexts.getValues("server").stream().findAny().orElse(null); + String world = contexts.getValues("world").stream().findAny().orElse(null); + contexts.removeAll("server"); + contexts.removeAll("world"); for (LocalizedNode ln : nodes) { Node n = ln.getNode(); @@ -105,7 +109,7 @@ public class MetaData { } } - public void invalidateCache() { + private void invalidateCache() { synchronized (meta) { meta.clear(); } @@ -117,24 +121,28 @@ public class MetaData { } } + @Override public Map getMeta() { synchronized (meta) { return ImmutableMap.copyOf(meta); } } + @Override public SortedMap getPrefixes() { synchronized (prefixes) { return ImmutableSortedMap.copyOfSorted(prefixes); } } + @Override public SortedMap getSuffixes() { synchronized (suffixes) { return ImmutableSortedMap.copyOfSorted(suffixes); } } + @Override public String getPrefix() { synchronized (prefixes) { if (prefixes.isEmpty()) { @@ -145,6 +153,7 @@ public class MetaData { } } + @Override public String getSuffix() { synchronized (suffixes) { if (suffixes.isEmpty()) { diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/PermissionData.java b/common/src/main/java/me/lucko/luckperms/common/caching/PermissionCache.java similarity index 92% rename from common/src/main/java/me/lucko/luckperms/common/caching/PermissionData.java rename to common/src/main/java/me/lucko/luckperms/common/caching/PermissionCache.java index bc96e0e7..c757d08a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/caching/PermissionData.java +++ b/common/src/main/java/me/lucko/luckperms/common/caching/PermissionCache.java @@ -26,6 +26,7 @@ import com.google.common.collect.ImmutableMap; import lombok.NonNull; import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.Tristate; +import me.lucko.luckperms.api.caching.PermissionData; import me.lucko.luckperms.common.calculators.CalculatorFactory; import me.lucko.luckperms.common.calculators.PermissionCalculator; import me.lucko.luckperms.common.users.User; @@ -36,7 +37,7 @@ import java.util.concurrent.ConcurrentHashMap; /** * Holds a user's cached permissions for a given context */ -public class PermissionData { +public class PermissionCache implements PermissionData { /** * The raw set of permission strings. @@ -50,11 +51,12 @@ public class PermissionData { */ private final PermissionCalculator calculator; - public PermissionData(Contexts contexts, User user, CalculatorFactory calculatorFactory) { + public PermissionCache(Contexts contexts, User user, CalculatorFactory calculatorFactory) { permissions = new ConcurrentHashMap<>(); calculator = calculatorFactory.build(contexts, user, permissions); } + @Override public void invalidateCache() { calculator.invalidateCache(); } @@ -71,10 +73,12 @@ public class PermissionData { } } + @Override public Map getImmutableBacking() { return ImmutableMap.copyOf(permissions); } + @Override public Tristate getPermissionValue(@NonNull String permission) { return calculator.getPermissionValue(permission); } diff --git a/common/src/main/java/me/lucko/luckperms/common/caching/UserData.java b/common/src/main/java/me/lucko/luckperms/common/caching/UserCache.java similarity index 53% rename from common/src/main/java/me/lucko/luckperms/common/caching/UserData.java rename to common/src/main/java/me/lucko/luckperms/common/caching/UserCache.java index 4b3a5acd..78ae4710 100644 --- a/common/src/main/java/me/lucko/luckperms/common/caching/UserData.java +++ b/common/src/main/java/me/lucko/luckperms/common/caching/UserCache.java @@ -28,8 +28,12 @@ import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import lombok.NonNull; import lombok.RequiredArgsConstructor; import me.lucko.luckperms.api.Contexts; +import me.lucko.luckperms.api.caching.MetaData; +import me.lucko.luckperms.api.caching.PermissionData; +import me.lucko.luckperms.api.caching.UserData; import me.lucko.luckperms.common.calculators.CalculatorFactory; import me.lucko.luckperms.common.users.User; @@ -39,7 +43,7 @@ import java.util.Set; * Holds an easily accessible cache of a user's data in a number of contexts */ @RequiredArgsConstructor -public class UserData { +public class UserCache implements UserData { /** * The user whom this data instance is representing @@ -51,124 +55,87 @@ public class UserData { */ private final CalculatorFactory calculatorFactory; - private final LoadingCache permission = CacheBuilder.newBuilder() - .build(new CacheLoader() { + private final LoadingCache permission = CacheBuilder.newBuilder() + .build(new CacheLoader() { @Override - public PermissionData load(Contexts contexts) { + public PermissionCache load(Contexts contexts) { return calculatePermissions(contexts); } @Override - public ListenableFuture reload(Contexts contexts, PermissionData oldData) { + public ListenableFuture reload(Contexts contexts, PermissionCache oldData) { oldData.comparePermissions(user.exportNodes(contexts, true)); return Futures.immediateFuture(oldData); } }); - private final LoadingCache meta = CacheBuilder.newBuilder() - .build(new CacheLoader() { + private final LoadingCache meta = CacheBuilder.newBuilder() + .build(new CacheLoader() { @Override - public MetaData load(Contexts contexts) { + public MetaCache load(Contexts contexts) { return calculateMeta(contexts); } @Override - public ListenableFuture reload(Contexts contexts, MetaData oldData) { + public ListenableFuture reload(Contexts contexts, MetaCache oldData) { oldData.loadMeta(user.getAllNodes(null, contexts)); return Futures.immediateFuture(oldData); } }); - /** - * Gets PermissionData from the cache, given a specified context. - * If the data is not cached, it is calculated. Therefore, this call could be costly. - * @param contexts the contexts to get the permission data in - * @return a permission data instance - */ - public PermissionData getPermissionData(Contexts contexts) { + @Override + public PermissionData getPermissionData(@NonNull Contexts contexts) { return permission.getUnchecked(contexts); } - /** - * Gets MetaData from the cache, given a specified context. - * If the data is not cached, it is calculated. Therefore, this call could be costly. - * @param contexts the contexts to get the permission data in - * @return a meta data instance - */ - public MetaData getMetaData(Contexts contexts) { + @Override + public MetaData getMetaData(@NonNull Contexts contexts) { return meta.getUnchecked(contexts); } - /** - * Calculates permission data, bypassing the cache. - * @param contexts the contexts to get permission data in - * @return a permission data instance - */ - public PermissionData calculatePermissions(Contexts contexts) { - PermissionData data = new PermissionData(contexts, user, calculatorFactory); + @Override + public PermissionCache calculatePermissions(@NonNull Contexts contexts) { + PermissionCache data = new PermissionCache(contexts, user, calculatorFactory); data.setPermissions(user.exportNodes(contexts, true)); return data; } - /** - * Calculates meta data, bypassing the cache. - * @param contexts the contexts to get meta data in - * @return a meta data instance - */ - public MetaData calculateMeta(Contexts contexts) { - MetaData data = new MetaData(contexts); + @Override + public MetaCache calculateMeta(@NonNull Contexts contexts) { + MetaCache data = new MetaCache(contexts); data.loadMeta(user.getAllNodes(null, contexts)); return data; } - /** - * Calculates permission data and stores it in the cache. If there is already data cached for the given contexts, - * and if the resultant output is different, the cached value is updated. - * @param contexts the contexts to recalculate in. - */ - public void recalculatePermissions(Contexts contexts) { + @Override + public void recalculatePermissions(@NonNull Contexts contexts) { permission.refresh(contexts); } - /** - * Calculates meta data and stores it in the cache. If there is already data cached for the given contexts, - * and if the resultant output is different, the cached value is updated. - * @param contexts the contexts to recalculate in. - */ - public void recalculateMeta(Contexts contexts) { + @Override + public void recalculateMeta(@NonNull Contexts contexts) { meta.refresh(contexts); } - /** - * Calls {@link #recalculatePermissions(Contexts)} for all current loaded contexts - */ + @Override public void recalculatePermissions() { Set keys = ImmutableSet.copyOf(permission.asMap().keySet()); keys.forEach(permission::refresh); } - /** - * Calls {@link #recalculateMeta(Contexts)} for all current loaded contexts - */ + @Override public void recalculateMeta() { Set keys = ImmutableSet.copyOf(meta.asMap().keySet()); keys.forEach(meta::refresh); } - /** - * Calls {@link #preCalculate(Contexts)} for the given contexts - * @param contexts a set of contexts - */ - public void preCalculate(Set contexts) { + @Override + public void preCalculate(@NonNull Set contexts) { contexts.forEach(this::preCalculate); } - /** - * Ensures that PermissionData and MetaData is cached for a context. If the cache does not contain any data for the - * context, it will be calculated and saved. - * @param contexts the contexts to pre-calculate for - */ - public void preCalculate(Contexts contexts) { + @Override + public void preCalculate(@NonNull Contexts contexts) { permission.getUnchecked(contexts); meta.getUnchecked(contexts); } @@ -178,6 +145,7 @@ public class UserData { meta.invalidateAll(); } + @Override public void invalidatePermissionCalculators() { permission.asMap().values().forEach(PermissionData::invalidateCache); } diff --git a/common/src/main/java/me/lucko/luckperms/common/contexts/ContextManager.java b/common/src/main/java/me/lucko/luckperms/common/contexts/ContextManager.java index 998dd6ff..db85500e 100644 --- a/common/src/main/java/me/lucko/luckperms/common/contexts/ContextManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/contexts/ContextManager.java @@ -24,6 +24,7 @@ package me.lucko.luckperms.common.contexts; import me.lucko.luckperms.api.context.ContextListener; import me.lucko.luckperms.api.context.IContextCalculator; +import me.lucko.luckperms.api.context.MutableContextSet; import java.util.List; import java.util.Map; @@ -47,7 +48,7 @@ public class ContextManager { listeners.add(listener); } - public Map giveApplicableContext(T subject, Map accumulator) { + public MutableContextSet giveApplicableContext(T subject, MutableContextSet accumulator) { for (IContextCalculator calculator : calculators) { calculator.giveApplicableContext(subject, accumulator); } diff --git a/common/src/main/java/me/lucko/luckperms/common/contexts/ServerCalculator.java b/common/src/main/java/me/lucko/luckperms/common/contexts/ServerCalculator.java index f6ae4d18..728b2d75 100644 --- a/common/src/main/java/me/lucko/luckperms/common/contexts/ServerCalculator.java +++ b/common/src/main/java/me/lucko/luckperms/common/contexts/ServerCalculator.java @@ -22,8 +22,10 @@ package me.lucko.luckperms.common.contexts; +import com.google.common.collect.Maps; import lombok.AllArgsConstructor; import me.lucko.luckperms.api.context.ContextCalculator; +import me.lucko.luckperms.api.context.MutableContextSet; import java.util.Map; @@ -32,8 +34,8 @@ public class ServerCalculator extends ContextCalculator { private final String server; @Override - public Map giveApplicableContext(T subject, Map accumulator) { - accumulator.put("server", server); + public MutableContextSet giveApplicableContext(T subject, MutableContextSet accumulator) { + accumulator.add(Maps.immutableEntry("server", server)); return accumulator; } diff --git a/common/src/main/java/me/lucko/luckperms/common/core/Node.java b/common/src/main/java/me/lucko/luckperms/common/core/Node.java index e9e479da..36632ac3 100644 --- a/common/src/main/java/me/lucko/luckperms/common/core/Node.java +++ b/common/src/main/java/me/lucko/luckperms/common/core/Node.java @@ -23,9 +23,10 @@ package me.lucko.luckperms.common.core; import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableMap; import lombok.*; import me.lucko.luckperms.api.Tristate; +import me.lucko.luckperms.api.context.ContextSet; +import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.common.constants.Patterns; import me.lucko.luckperms.common.utils.ArgumentChecker; @@ -60,7 +61,8 @@ public class Node implements me.lucko.luckperms.api.Node { private long expireAt = 0L; - private final Map extraContexts; + @Getter + private final ContextSet contexts; // Cache the state private Tristate isPrefix = Tristate.UNDEFINED; @@ -74,9 +76,9 @@ public class Node implements me.lucko.luckperms.api.Node { * @param expireAt the time when the node will expire * @param server the server this node applies on * @param world the world this node applies on - * @param extraContexts any additional contexts applying to this node + * @param contexts any additional contexts applying to this node */ - public Node(String permission, boolean value, boolean override, long expireAt, String server, String world, Map extraContexts) { + public Node(String permission, boolean value, boolean override, long expireAt, String server, String world, ContextSet contexts) { if (permission == null || permission.equals("")) { throw new IllegalArgumentException("Empty permission"); } @@ -99,12 +101,7 @@ public class Node implements me.lucko.luckperms.api.Node { this.expireAt = expireAt; this.server = server; this.world = world; - - ImmutableMap.Builder contexts = ImmutableMap.builder(); - if (extraContexts != null) { - contexts.putAll(extraContexts); - } - this.extraContexts = contexts.build(); + this.contexts = contexts == null ? ContextSet.empty() : contexts.makeImmutable(); } @Override @@ -176,11 +173,6 @@ public class Node implements me.lucko.luckperms.api.Node { return isTemporary() && expireAt < (System.currentTimeMillis() / 1000L); } - @Override - public Map getExtraContexts() { - return ImmutableMap.copyOf(extraContexts); - } - @Override public boolean isGroupNode() { return getPermission().toLowerCase().startsWith("group."); @@ -316,31 +308,28 @@ public class Node implements me.lucko.luckperms.api.Node { } @Override - public boolean shouldApplyWithContext(Map context, boolean worldAndServer) { - if (extraContexts.isEmpty() && !isServerSpecific() && !isWorldSpecific()) { + public boolean shouldApplyWithContext(ContextSet context, boolean worldAndServer) { + if (contexts.isEmpty() && !isServerSpecific() && !isWorldSpecific()) { return true; } if (worldAndServer) { if (isWorldSpecific()) { if (context == null) return false; - if (!context.containsKey("world")) return false; - if (!context.get("world").equalsIgnoreCase(world)) return false; + if (!context.hasIgnoreCase("world", world)) return false; } if (isServerSpecific()) { if (context == null) return false; - if (!context.containsKey("server")) return false; - if (!context.get("server").equalsIgnoreCase(server)) return false; + if (!context.hasIgnoreCase("server", server)) return false; } } - if (!extraContexts.isEmpty()) { + if (!contexts.isEmpty()) { if (context == null) return false; - for (Map.Entry c : extraContexts.entrySet()) { - if (!context.containsKey(c.getKey())) return false; - if (!context.get(c.getKey()).equalsIgnoreCase(c.getValue())) return false; + for (Map.Entry c : contexts.toSet()) { + if (!context.hasIgnoreCase(c.getKey(), c.getValue())) return false; } } @@ -348,7 +337,7 @@ public class Node implements me.lucko.luckperms.api.Node { } @Override - public boolean shouldApplyWithContext(Map context) { + public boolean shouldApplyWithContext(ContextSet context) { return shouldApplyWithContext(context, true); } @@ -478,9 +467,9 @@ public class Node implements me.lucko.luckperms.api.Node { } } - if (!extraContexts.isEmpty()) { + if (!contexts.isEmpty()) { builder.append("("); - for (Map.Entry entry : extraContexts.entrySet()) { + for (Map.Entry entry : contexts.toSet()) { builder.append(entry.getKey()).append("=").append(entry.getValue()).append(","); } @@ -533,7 +522,7 @@ public class Node implements me.lucko.luckperms.api.Node { return false; } - if (!other.getExtraContexts().equals(this.getExtraContexts())) { + if (!other.getContexts().equals(this.getContexts())) { return false; } @@ -570,7 +559,7 @@ public class Node implements me.lucko.luckperms.api.Node { return false; } - if (!other.getExtraContexts().equals(this.getExtraContexts())) { + if (!other.getContexts().equals(this.getContexts())) { return false; } @@ -603,7 +592,7 @@ public class Node implements me.lucko.luckperms.api.Node { return false; } - if (!other.getExtraContexts().equals(this.getExtraContexts())) { + if (!other.getContexts().equals(this.getContexts())) { return false; } @@ -681,8 +670,7 @@ public class Node implements me.lucko.luckperms.api.Node { private String server = null; private String world = null; private long expireAt = 0L; - - private final Map extraContexts = new HashMap<>(); + private final MutableContextSet extraContexts = new MutableContextSet(); Builder(String permission, boolean shouldConvertContexts) { if (!shouldConvertContexts) { @@ -696,7 +684,7 @@ public class Node implements me.lucko.luckperms.api.Node { this.permission = contextParts.get(1); try { - extraContexts.putAll(Splitter.on(',').withKeyValueSeparator('=').split(contextParts.get(0))); + extraContexts.addAll(Splitter.on(',').withKeyValueSeparator('=').split(contextParts.get(0))); } catch (IllegalArgumentException e) { e.printStackTrace(); } @@ -711,6 +699,7 @@ public class Node implements me.lucko.luckperms.api.Node { this.server = other.getServer().orElse(null); this.world = other.getWorld().orElse(null); this.expireAt = other.isPermanent() ? 0L : other.getExpiryUnixTime(); + this.extraContexts.addAll(other.getContexts()); } @Override @@ -760,7 +749,7 @@ public class Node implements me.lucko.luckperms.api.Node { @Override public me.lucko.luckperms.api.Node.Builder withExtraContext(@NonNull String key, @NonNull String value) { - switch (key) { + switch (key.toLowerCase()) { case "server": setServer(value); break; @@ -768,25 +757,37 @@ public class Node implements me.lucko.luckperms.api.Node { setWorld(value); break; default: - this.extraContexts.put(key, value); + this.extraContexts.add(key, value); break; } return this; } - @Override - public me.lucko.luckperms.api.Node.Builder withExtraContext(Map map) { - map.entrySet().forEach(this::withExtraContext); - return this; - } - @Override public me.lucko.luckperms.api.Node.Builder withExtraContext(Map.Entry entry) { withExtraContext(entry.getKey(), entry.getValue()); return this; } + @Override + public me.lucko.luckperms.api.Node.Builder withExtraContext(Map map) { + withExtraContext(ContextSet.fromMap(map)); + return this; + } + + @Override + public me.lucko.luckperms.api.Node.Builder withExtraContext(Set> context) { + withExtraContext(ContextSet.fromEntries(context)); + return this; + } + + @Override + public me.lucko.luckperms.api.Node.Builder withExtraContext(ContextSet set) { + set.toSet().forEach(this::withExtraContext); + return this; + } + @Override public me.lucko.luckperms.api.Node build() { return new Node(permission, value, override, expireAt, server, world, extraContexts); diff --git a/common/src/main/java/me/lucko/luckperms/common/core/PermissionHolder.java b/common/src/main/java/me/lucko/luckperms/common/core/PermissionHolder.java index 9f62e785..07f18d57 100644 --- a/common/src/main/java/me/lucko/luckperms/common/core/PermissionHolder.java +++ b/common/src/main/java/me/lucko/luckperms/common/core/PermissionHolder.java @@ -32,6 +32,7 @@ import me.lucko.luckperms.api.Contexts; import me.lucko.luckperms.api.LocalizedNode; import me.lucko.luckperms.api.Node; import me.lucko.luckperms.api.Tristate; +import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.api.event.events.*; import me.lucko.luckperms.common.LuckPermsPlugin; import me.lucko.luckperms.common.api.internal.GroupLink; @@ -368,11 +369,11 @@ public abstract class PermissionHolder { .filter(Node::isGroupNode) .collect(Collectors.toSet()); - Map contexts = new HashMap<>(context.getContext()); - String server = contexts.get("server"); - String world = contexts.get("world"); - contexts.remove("server"); - contexts.remove("world"); + MutableContextSet contexts = MutableContextSet.fromSet(context.getContexts()); + String server = contexts.getValues("server").stream().findAny().orElse(null); + String world = contexts.getValues("world").stream().findAny().orElse(null); + contexts.removeAll("server"); + contexts.removeAll("world"); parents.removeIf(node -> !node.shouldApplyOnServer(server, context.isApplyGlobalGroups(), plugin.getConfiguration().isApplyingRegex()) || @@ -419,11 +420,11 @@ public abstract class PermissionHolder { allNodes = new TreeSet<>((SortedSet) getPermissions(true)); } - Map contexts = new HashMap<>(context.getContext()); - String server = contexts.get("server"); - String world = contexts.get("world"); - contexts.remove("server"); - contexts.remove("world"); + MutableContextSet contexts = MutableContextSet.fromSet(context.getContexts()); + String server = contexts.getValues("server").stream().findAny().orElse(null); + String world = contexts.getValues("world").stream().findAny().orElse(null); + contexts.removeAll("server"); + contexts.removeAll("world"); allNodes.removeIf(node -> !node.shouldApplyOnServer(server, context.isIncludeGlobal(), plugin.getConfiguration().isApplyingRegex()) || diff --git a/common/src/main/java/me/lucko/luckperms/common/users/User.java b/common/src/main/java/me/lucko/luckperms/common/users/User.java index 4cb7b5f1..55b88d77 100644 --- a/common/src/main/java/me/lucko/luckperms/common/users/User.java +++ b/common/src/main/java/me/lucko/luckperms/common/users/User.java @@ -26,10 +26,11 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import lombok.ToString; +import me.lucko.luckperms.api.caching.UserData; import me.lucko.luckperms.api.event.events.UserPermissionRefreshEvent; import me.lucko.luckperms.common.LuckPermsPlugin; import me.lucko.luckperms.common.api.internal.UserLink; -import me.lucko.luckperms.common.caching.UserData; +import me.lucko.luckperms.common.caching.UserCache; import me.lucko.luckperms.common.core.PermissionHolder; import me.lucko.luckperms.common.utils.BufferedRequest; import me.lucko.luckperms.common.utils.Identifiable; @@ -61,7 +62,7 @@ public class User extends PermissionHolder implements Identifiable refreshBuffer = new BufferedRequest(1000L, r -> getPlugin().doAsync(r)) { @@ -103,7 +104,7 @@ public class User extends PermissionHolder implements Identifiable getServer() { - return node.getServer(); - } - - @Override - public Optional getWorld() { - return node.getWorld(); - } - - @Override - public boolean isServerSpecific() { - return node.isServerSpecific(); - } - - @Override - public boolean isWorldSpecific() { - return node.isWorldSpecific(); - } - - @Override - public boolean shouldApplyOnServer(String server, boolean includeGlobal, boolean applyRegex) { - return node.shouldApplyOnServer(server, includeGlobal, applyRegex); - } - - @Override - public boolean shouldApplyOnWorld(String world, boolean includeGlobal, boolean applyRegex) { - return node.shouldApplyOnWorld(world, includeGlobal, applyRegex); - } - - @Override - public boolean shouldApplyWithContext(Map context, boolean worldAndServer) { - return node.shouldApplyWithContext(context, worldAndServer); - } - - @Override - public boolean shouldApplyWithContext(Map context) { - return node.shouldApplyWithContext(context); - } - - @Override - public boolean shouldApplyOnAnyServers(List servers, boolean includeGlobal) { - return node.shouldApplyOnAnyServers(servers, includeGlobal); - } - - @Override - public boolean shouldApplyOnAnyWorlds(List worlds, boolean includeGlobal) { - return node.shouldApplyOnAnyWorlds(worlds, includeGlobal); - } - - @Override - public List resolveWildcard(List possibleNodes) { - return node.resolveWildcard(possibleNodes); - } - - @Override - public List resolveShorthand() { - return node.resolveShorthand(); - } - - @Override - public boolean isTemporary() { - return node.isTemporary(); - } - - @Override - public boolean isPermanent() { - return node.isPermanent(); - } - - @Override - public long getExpiryUnixTime() { - return node.getExpiryUnixTime(); - } - - @Override - public Date getExpiry() { - return node.getExpiry(); - } - - @Override - public long getSecondsTilExpiry() { - return node.getSecondsTilExpiry(); - } - - @Override - public boolean hasExpired() { - return node.hasExpired(); - } - - @Override - public Map getExtraContexts() { - return node.getExtraContexts(); - } - - @Override - public String toSerializedNode() { - return node.toSerializedNode(); - } - - @Override - public boolean isGroupNode() { - return node.isGroupNode(); - } - - @Override - public String getGroupName() { - return node.getGroupName(); - } - - @Override - public boolean isWildcard() { - return node.isWildcard(); - } - - @Override - public int getWildcardLevel() { - return node.getWildcardLevel(); - } - - @Override - public boolean isMeta() { - return node.isMeta(); - } - - @Override - public Map.Entry getMeta() { - return node.getMeta(); - } - - @Override - public boolean isPrefix() { - return node.isPrefix(); - } - - @Override - public Map.Entry getPrefix() { - return node.getPrefix(); - } - - @Override - public boolean isSuffix() { - return node.isSuffix(); - } - - @Override - public Map.Entry getSuffix() { - return node.getSuffix(); - } - - @Override - public boolean equalsIgnoringValue(Node other) { - return node.equalsIgnoringValue(other); - } - - @Override - public boolean almostEquals(Node other) { - return node.almostEquals(other); - } - - @Override - public boolean equalsIgnoringValueOrTemp(Node other) { - return node.equalsIgnoringValueOrTemp(other); - } } diff --git a/pom.xml b/pom.xml index cdf7856e..f43107ac 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ me.lucko.luckperms luckperms - 2.12-SNAPSHOT + 2.13-SNAPSHOT common api @@ -38,7 +38,7 @@ UTF-8 - 2.12 + 2.13 diff --git a/sponge/pom.xml b/sponge/pom.xml index 45148c02..6f13a848 100644 --- a/sponge/pom.xml +++ b/sponge/pom.xml @@ -5,7 +5,7 @@ luckperms me.lucko.luckperms - 2.12-SNAPSHOT + 2.13-SNAPSHOT 4.0.0 diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/SpongeCalculatorFactory.java b/sponge/src/main/java/me/lucko/luckperms/sponge/SpongeCalculatorFactory.java index cf23e4a1..ea6b3687 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/SpongeCalculatorFactory.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/SpongeCalculatorFactory.java @@ -49,7 +49,7 @@ public class SpongeCalculatorFactory implements CalculatorFactory { if (plugin.getConfiguration().isApplyingRegex()) { processors.add(new RegexProcessor(map)); } - processors.add(new DefaultsProcessor(plugin.getService(), LuckPermsService.convertContexts(contexts.getContext()))); + processors.add(new DefaultsProcessor(plugin.getService(), LuckPermsService.convertContexts(contexts.getContexts()))); return new PermissionCalculator(plugin, user.getName(), plugin.getConfiguration().isDebugPermissionChecks(), processors); } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/SpongeListener.java b/sponge/src/main/java/me/lucko/luckperms/sponge/SpongeListener.java index 3243033a..cf902643 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/SpongeListener.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/SpongeListener.java @@ -22,7 +22,8 @@ package me.lucko.luckperms.sponge; -import me.lucko.luckperms.common.caching.UserData; +import me.lucko.luckperms.api.caching.UserData; +import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.common.constants.Message; import me.lucko.luckperms.common.users.User; import me.lucko.luckperms.common.utils.AbstractListener; @@ -34,9 +35,7 @@ import org.spongepowered.api.profile.GameProfile; import org.spongepowered.api.text.serializer.TextSerializers; import org.spongepowered.api.world.World; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -77,7 +76,7 @@ public class SpongeListener extends AbstractListener { // Attempt to pre-process some permissions for the user to save time later. Might not work, but it's better than nothing. Optional p = e.getCause().first(Player.class); if (p.isPresent()) { - Map context = plugin.getContextManager().giveApplicableContext(p.get(), new HashMap<>()); + MutableContextSet context = plugin.getContextManager().giveApplicableContext(p.get(), MutableContextSet.empty()); List worlds = plugin.getGame().getServer().getWorlds().stream() .map(World::getName) @@ -88,8 +87,9 @@ public class SpongeListener extends AbstractListener { data.preCalculate(plugin.getService().calculateContexts(LuckPermsService.convertContexts(context))); for (String world : worlds) { - Map modified = new HashMap<>(context); - modified.put("world", world); + MutableContextSet modified = MutableContextSet.fromSet(context); + modified.removeAll("world"); + modified.add("world", world); data.preCalculate(plugin.getService().calculateContexts(LuckPermsService.convertContexts(modified))); } }); diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/contexts/SpongeCalculatorLink.java b/sponge/src/main/java/me/lucko/luckperms/sponge/contexts/SpongeCalculatorLink.java index 34a9084c..112b8ea8 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/contexts/SpongeCalculatorLink.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/contexts/SpongeCalculatorLink.java @@ -24,6 +24,7 @@ package me.lucko.luckperms.sponge.contexts; import lombok.AllArgsConstructor; import me.lucko.luckperms.api.context.ContextCalculator; +import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.sponge.service.LuckPermsService; import org.spongepowered.api.service.context.Context; import org.spongepowered.api.service.permission.Subject; @@ -36,12 +37,12 @@ public class SpongeCalculatorLink extends ContextCalculator { private final org.spongepowered.api.service.context.ContextCalculator calculator; @Override - public Map giveApplicableContext(Subject subject, Map accumulator) { + public MutableContextSet giveApplicableContext(Subject subject, MutableContextSet accumulator) { Set contexts = LuckPermsService.convertContexts(accumulator); calculator.accumulateContexts(subject, contexts); accumulator.clear(); - accumulator.putAll(LuckPermsService.convertContexts(contexts)); + accumulator.addAll(LuckPermsService.convertContexts(contexts)); return accumulator; } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/contexts/WorldCalculator.java b/sponge/src/main/java/me/lucko/luckperms/sponge/contexts/WorldCalculator.java index c31b9cfc..88f9d928 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/contexts/WorldCalculator.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/contexts/WorldCalculator.java @@ -24,6 +24,7 @@ package me.lucko.luckperms.sponge.contexts; import lombok.RequiredArgsConstructor; import me.lucko.luckperms.api.context.ContextCalculator; +import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.common.commands.Util; import me.lucko.luckperms.sponge.LPSpongePlugin; import org.spongepowered.api.entity.living.player.Player; @@ -39,7 +40,7 @@ public class WorldCalculator extends ContextCalculator { private final LPSpongePlugin plugin; @Override - public Map giveApplicableContext(Subject subject, Map accumulator) { + public MutableContextSet giveApplicableContext(Subject subject, MutableContextSet accumulator) { UUID uuid = Util.parseUuid(subject.getIdentifier()); if (uuid == null) { return accumulator; @@ -50,7 +51,7 @@ public class WorldCalculator extends ContextCalculator { return accumulator; } - accumulator.put(Context.WORLD_KEY, p.get().getWorld().getName()); + accumulator.add(Context.WORLD_KEY, p.get().getWorld().getName()); return accumulator; } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsGroupSubject.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsGroupSubject.java index c974f151..751f8de9 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsGroupSubject.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsGroupSubject.java @@ -28,13 +28,13 @@ import lombok.Getter; import lombok.NonNull; import me.lucko.luckperms.api.LocalizedNode; import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.common.groups.Group; import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.service.context.Context; import org.spongepowered.api.service.permission.NodeTree; import org.spongepowered.api.service.permission.Subject; import org.spongepowered.api.service.permission.SubjectCollection; -import org.spongepowered.api.service.permission.SubjectData; import org.spongepowered.api.util.Tristate; import java.util.List; @@ -156,7 +156,7 @@ public class LuckPermsGroupSubject implements Subject { @Override public Set getActiveContexts() { - return SubjectData.GLOBAL_CONTEXT; + return LuckPermsService.convertContexts(service.getPlugin().getContextManager().giveApplicableContext(this, MutableContextSet.empty())); } private Optional getChatMeta(Set contexts, boolean prefix) { 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 c561ad38..b140512e 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 @@ -22,10 +22,15 @@ package me.lucko.luckperms.sponge.service; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; import lombok.*; import me.lucko.luckperms.api.Contexts; +import me.lucko.luckperms.api.context.ContextSet; import me.lucko.luckperms.sponge.LPSpongePlugin; import me.lucko.luckperms.sponge.contexts.SpongeCalculatorLink; import me.lucko.luckperms.sponge.service.collections.GroupCollection; @@ -48,28 +53,25 @@ import java.util.stream.Collectors; /** * The LuckPerms implementation of the Sponge Permission Service */ +@Getter public class LuckPermsService implements PermissionService { public static final String SERVER_CONTEXT = "server"; - @Getter private final LPSpongePlugin plugin; - - @Getter private final SubjectStorage storage; - - @Getter private final UserCollection userSubjects; - - @Getter private final GroupCollection groupSubjects; - - @Getter private final PersistedCollection defaultSubjects; - - @Getter private final Set descriptionSet; - private final Map subjects; + @Getter(value = AccessLevel.NONE) + private final LoadingCache collections = CacheBuilder.newBuilder() + .build(new CacheLoader() { + @Override + public SubjectCollection load(String s) { + return new SimpleCollection(LuckPermsService.this, s); + } + }); public LuckPermsService(LPSpongePlugin plugin) { this.plugin = plugin; @@ -81,10 +83,9 @@ public class LuckPermsService implements PermissionService { defaultSubjects = new PersistedCollection(this, "defaults"); defaultSubjects.loadAll(); - subjects = new ConcurrentHashMap<>(); - subjects.put(PermissionService.SUBJECTS_USER, userSubjects); - subjects.put(PermissionService.SUBJECTS_GROUP, groupSubjects); - subjects.put("defaults", defaultSubjects); + collections.put(PermissionService.SUBJECTS_USER, userSubjects); + collections.put(PermissionService.SUBJECTS_GROUP, groupSubjects); + collections.put("defaults", defaultSubjects); descriptionSet = ConcurrentHashMap.newKeySet(); } @@ -100,16 +101,12 @@ public class LuckPermsService implements PermissionService { @Override public SubjectCollection getSubjects(String s) { - if (!subjects.containsKey(s)) { - subjects.put(s, new SimpleCollection(this, s)); - } - - return subjects.get(s); + return collections.getUnchecked(s.toLowerCase()); } @Override public Map getKnownSubjects() { - return ImmutableMap.copyOf(subjects); + return ImmutableMap.copyOf(collections.asMap()); } @Override @@ -150,16 +147,17 @@ public class LuckPermsService implements PermissionService { plugin.getConfiguration().isIncludingGlobalWorldPerms(), true, plugin.getConfiguration().isApplyingGlobalGroups(), - plugin.getConfiguration().isApplyingGlobalWorldGroups() + plugin.getConfiguration().isApplyingGlobalWorldGroups(), + false ); } - public static Map convertContexts(Set contexts) { - return contexts.stream().collect(Collectors.toMap(Context::getKey, Context::getValue)); + public static ContextSet convertContexts(Set contexts) { + return ContextSet.fromEntries(contexts.stream().map(c -> Maps.immutableEntry(c.getKey(), c.getValue())).collect(Collectors.toSet())); } - public static Set convertContexts(Map contexts) { - return contexts.entrySet().stream().map(e -> new Context(e.getKey(), e.getValue())).collect(Collectors.toSet()); + public static Set convertContexts(ContextSet contexts) { + return contexts.toSet().stream().map(e -> new Context(e.getKey(), e.getValue())).collect(Collectors.toSet()); } public static Tristate convertTristate(me.lucko.luckperms.api.Tristate tristate) { diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsSubjectData.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsSubjectData.java index 30cbc916..2479f61e 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsSubjectData.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsSubjectData.java @@ -28,6 +28,7 @@ import com.google.common.collect.ImmutableSet; import lombok.AllArgsConstructor; import lombok.Getter; import me.lucko.luckperms.api.Node; +import me.lucko.luckperms.api.context.ContextSet; import me.lucko.luckperms.common.core.PermissionHolder; import me.lucko.luckperms.common.groups.Group; import me.lucko.luckperms.common.users.User; @@ -65,7 +66,7 @@ public class LuckPermsSubjectData implements SubjectData { Map, Map> perms = new HashMap<>(); for (Node n : enduring ? holder.getNodes() : holder.getTransientNodes()) { - Set contexts = LuckPermsService.convertContexts(n.getExtraContexts()); + Set contexts = LuckPermsService.convertContexts(n.getContexts()); if (n.isServerSpecific()) { contexts.add(new Context(LuckPermsService.SERVER_CONTEXT, n.getServer().get())); @@ -185,7 +186,7 @@ public class LuckPermsSubjectData implements SubjectData { continue; } - Set contexts = LuckPermsService.convertContexts(n.getExtraContexts()); + Set contexts = LuckPermsService.convertContexts(n.getContexts()); if (n.isServerSpecific()) { contexts.add(new Context(LuckPermsService.SERVER_CONTEXT, n.getServer().get())); @@ -225,7 +226,7 @@ public class LuckPermsSubjectData implements SubjectData { public boolean addParent(Set set, Subject subject) { if (subject instanceof LuckPermsGroupSubject) { LuckPermsGroupSubject permsSubject = ((LuckPermsGroupSubject) subject); - Map contexts = LuckPermsService.convertContexts(set); + ContextSet contexts = LuckPermsService.convertContexts(set); try { if (enduring) { @@ -249,7 +250,7 @@ public class LuckPermsSubjectData implements SubjectData { public boolean removeParent(Set set, Subject subject) { if (subject instanceof LuckPermsGroupSubject) { LuckPermsGroupSubject permsSubject = ((LuckPermsGroupSubject) subject); - Map contexts = LuckPermsService.convertContexts(set); + ContextSet contexts = LuckPermsService.convertContexts(set); try { if (enduring) { @@ -295,7 +296,7 @@ public class LuckPermsSubjectData implements SubjectData { @Override public boolean clearParents(Set set) { - Map context = LuckPermsService.convertContexts(set); + ContextSet context = LuckPermsService.convertContexts(set); List toRemove = (enduring ? holder.getNodes() : holder.getTransientNodes()).stream() .filter(Node::isGroupNode) @@ -336,7 +337,7 @@ public class LuckPermsSubjectData implements SubjectData { continue; } - Set contexts = LuckPermsService.convertContexts(n.getExtraContexts()); + Set contexts = LuckPermsService.convertContexts(n.getContexts()); if (n.isServerSpecific()) { contexts.add(new Context(LuckPermsService.SERVER_CONTEXT, n.getServer().get())); @@ -384,7 +385,7 @@ public class LuckPermsSubjectData implements SubjectData { @Override public Map getOptions(Set set) { ImmutableMap.Builder options = ImmutableMap.builder(); - Map contexts = LuckPermsService.convertContexts(set); + ContextSet contexts = LuckPermsService.convertContexts(set); int prefixPriority = Integer.MIN_VALUE; int suffixPriority = Integer.MIN_VALUE; @@ -431,7 +432,7 @@ public class LuckPermsSubjectData implements SubjectData { @Override public boolean setOption(Set set, String key, String value) { - Map context = LuckPermsService.convertContexts(set); + ContextSet context = LuckPermsService.convertContexts(set); key = escapeCharacters(key); value = escapeCharacters(value); @@ -455,7 +456,7 @@ public class LuckPermsSubjectData implements SubjectData { @Override public boolean clearOptions(Set set) { - Map context = LuckPermsService.convertContexts(set); + ContextSet context = LuckPermsService.convertContexts(set); List toRemove = (enduring ? holder.getNodes() : holder.getTransientNodes()).stream() .filter(n -> n.isMeta() || n.isPrefix() || n.isSuffix()) diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsUserSubject.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsUserSubject.java index 1c47f4f6..e314a3ae 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsUserSubject.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/LuckPermsUserSubject.java @@ -26,7 +26,8 @@ import com.google.common.collect.ImmutableList; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NonNull; -import me.lucko.luckperms.common.caching.MetaData; +import me.lucko.luckperms.api.caching.MetaData; +import me.lucko.luckperms.api.context.MutableContextSet; import me.lucko.luckperms.common.users.User; import org.spongepowered.api.Sponge; import org.spongepowered.api.command.CommandSource; @@ -36,8 +37,10 @@ import org.spongepowered.api.service.permission.Subject; import org.spongepowered.api.service.permission.SubjectCollection; import org.spongepowered.api.util.Tristate; -import java.util.*; -import java.util.stream.Collectors; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; @EqualsAndHashCode(of = "user") public class LuckPermsUserSubject implements Subject { @@ -171,8 +174,6 @@ public class LuckPermsUserSubject implements Subject { @Override public Set getActiveContexts() { - return service.getPlugin().getContextManager().giveApplicableContext(this, new HashMap<>()).entrySet().stream() - .map(e -> new Context(e.getKey(), e.getValue())) - .collect(Collectors.toSet()); + return LuckPermsService.convertContexts(service.getPlugin().getContextManager().giveApplicableContext(this, MutableContextSet.empty())); } } 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 e6d0d5ee..5ba9f9af 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 @@ -22,6 +22,9 @@ package me.lucko.luckperms.sponge.service.persisted; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -35,7 +38,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; /** @@ -48,34 +50,35 @@ public class PersistedCollection implements SubjectCollection { @Getter private final String identifier; - private final Map subjects = new ConcurrentHashMap<>(); + private final LoadingCache subjects = CacheBuilder.newBuilder() + .build(new CacheLoader() { + @Override + public PersistedSubject load(String s) { + return new PersistedSubject(s, service, PersistedCollection.this); + } + }); public void loadAll() { Map holders = service.getStorage().loadAllFromFile(identifier); for (Map.Entry e : holders.entrySet()) { - PersistedSubject subject = new PersistedSubject(e.getKey(), service, this); + PersistedSubject subject = get(e.getKey()); subject.loadData(e.getValue()); - subjects.put(e.getKey(), subject); } } @Override - public synchronized Subject get(@NonNull String id) { - if (!subjects.containsKey(id)) { - subjects.put(id, new PersistedSubject(id, service, this)); - } - - return subjects.get(id); + public PersistedSubject get(@NonNull String id) { + return subjects.getUnchecked(id.toLowerCase()); } @Override public boolean hasRegistered(@NonNull String id) { - return subjects.containsKey(id); + return subjects.asMap().containsKey(id.toLowerCase()); } @Override public Iterable getAllSubjects() { - return subjects.values().stream().map(s -> (Subject) s).collect(Collectors.toList()); + return subjects.asMap().values().stream().map(s -> (Subject) s).collect(Collectors.toList()); } @Override @@ -86,7 +89,7 @@ public class PersistedCollection implements SubjectCollection { @Override public Map getAllWithPermission(@NonNull Set contexts, @NonNull String node) { Map m = new HashMap<>(); - for (Subject subject : subjects.values()) { + for (Subject subject : subjects.asMap().values()) { Tristate ts = subject.getPermissionValue(contexts, node); if (ts != Tristate.UNDEFINED) { m.put(subject, ts.asBoolean()); 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 1e6f2a75..633b8e31 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,13 +25,14 @@ package me.lucko.luckperms.sponge.service.persisted; import com.google.common.collect.ImmutableList; import lombok.Getter; import lombok.NonNull; +import me.lucko.luckperms.api.context.MutableContextSet; +import me.lucko.luckperms.common.utils.BufferedRequest; import me.lucko.luckperms.sponge.service.LuckPermsService; import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.service.context.Context; import org.spongepowered.api.service.permission.MemorySubjectData; import org.spongepowered.api.service.permission.Subject; import org.spongepowered.api.service.permission.SubjectCollection; -import org.spongepowered.api.service.permission.SubjectData; import org.spongepowered.api.util.Tristate; import java.io.IOException; @@ -51,6 +52,19 @@ public class PersistedSubject implements Subject { private final SubjectCollection containingCollection; private final PersistedSubjectData subjectData; private final MemorySubjectData transientSubjectData; + private final BufferedRequest saveBuffer = new BufferedRequest(1000L, r -> PersistedSubject.this.service.getPlugin().doAsync(r)) { + @Override + protected Void perform() { + service.getPlugin().doAsync(() -> { + try { + service.getStorage().saveToFile(PersistedSubject.this); + } catch (IOException e) { + e.printStackTrace(); + } + }); + return null; + } + }; public PersistedSubject(String identifier, LuckPermsService service, SubjectCollection containingCollection) { this.identifier = identifier; @@ -67,13 +81,7 @@ public class PersistedSubject implements Subject { } public void save() { - service.getPlugin().doAsync(() -> { - try { - service.getStorage().saveToFile(this); - } catch (IOException e) { - e.printStackTrace(); - } - }); + saveBuffer.request(); } @Override @@ -178,6 +186,6 @@ public class PersistedSubject implements Subject { @Override public Set getActiveContexts() { - return SubjectData.GLOBAL_CONTEXT; + return LuckPermsService.convertContexts(service.getPlugin().getContextManager().giveApplicableContext(this, MutableContextSet.empty())); } } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/SubjectDataHolder.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/SubjectDataHolder.java index e0103d36..b324efb6 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/SubjectDataHolder.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/SubjectDataHolder.java @@ -22,6 +22,8 @@ package me.lucko.luckperms.sponge.service.persisted; +import lombok.ToString; +import me.lucko.luckperms.api.context.ContextSet; import org.spongepowered.api.service.context.Context; import org.spongepowered.api.service.permission.MemorySubjectData; import org.spongepowered.api.service.permission.PermissionService; @@ -35,6 +37,7 @@ import static me.lucko.luckperms.sponge.service.LuckPermsService.convertContexts /** * Holds SubjectData in a "gson friendly" format for serialization */ +@ToString public class SubjectDataHolder { private final Map, Map> permissions; private final Map, Map> options; @@ -43,17 +46,17 @@ public class SubjectDataHolder { public SubjectDataHolder(Map, Map> options, Map, Map> permissions, Map, List>> parents) { this.options = new HashMap<>(); for (Map.Entry, Map> e : options.entrySet()) { - this.options.put(convertContexts(e.getKey()), new HashMap<>(e.getValue())); + this.options.put(convertContexts(e.getKey()).toMap(), new HashMap<>(e.getValue())); } this.permissions = new HashMap<>(); for (Map.Entry, Map> e : permissions.entrySet()) { - this.permissions.put(convertContexts(e.getKey()), new HashMap<>(e.getValue())); + this.permissions.put(convertContexts(e.getKey()).toMap(), new HashMap<>(e.getValue())); } this.parents = new HashMap<>(); for (Map.Entry, List>> e : parents.entrySet()) { - this.parents.put(convertContexts(e.getKey()), e.getValue().stream().map(p -> new AbstractMap.SimpleEntry<>(p.getKey(), p.getValue())).collect(Collectors.toList())); + this.parents.put(convertContexts(e.getKey()).toMap(), e.getValue().stream().map(p -> new AbstractMap.SimpleEntry<>(p.getKey(), p.getValue())).collect(Collectors.toList())); } } @@ -77,20 +80,23 @@ public class SubjectDataHolder { public void copyTo(MemorySubjectData subjectData, PermissionService service) { for (Map.Entry, Map> e : permissions.entrySet()) { + Set contexts = convertContexts(ContextSet.fromMap(e.getKey())); for (Map.Entry perm : e.getValue().entrySet()) { - subjectData.setPermission(convertContexts(e.getKey()), perm.getKey(), Tristate.fromBoolean(perm.getValue())); + subjectData.setPermission(contexts, perm.getKey(), Tristate.fromBoolean(perm.getValue())); } } for (Map.Entry, Map> e : options.entrySet()) { + Set contexts = convertContexts(ContextSet.fromMap(e.getKey())); for (Map.Entry option : e.getValue().entrySet()) { - subjectData.setOption(convertContexts(e.getKey()), option.getKey(), option.getValue()); + subjectData.setOption(contexts, option.getKey(), option.getValue()); } } for (Map.Entry, List>> e : parents.entrySet()) { + Set contexts = convertContexts(ContextSet.fromMap(e.getKey())); for (Map.Entry parent : e.getValue()) { - subjectData.addParent(convertContexts(e.getKey()), service.getSubjects(parent.getKey()).get(parent.getValue())); + subjectData.addParent(contexts, service.getSubjects(parent.getKey()).get(parent.getValue())); } } } diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/SubjectStorage.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/SubjectStorage.java index 90489d30..e222499a 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/SubjectStorage.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/persisted/SubjectStorage.java @@ -58,7 +58,7 @@ public class SubjectStorage { collection.mkdirs(); } - File subjectFile = new File(collection, subject.getIdentifier().toLowerCase() + ".json"); + File subjectFile = new File(collection, subject.getIdentifier() + ".json"); saveToFile(subject, subjectFile); } @@ -110,8 +110,8 @@ public class SubjectStorage { return null; } - File subject = new File(collection, subjectName.toLowerCase() + ".json"); - return new AbstractMap.SimpleEntry<>(subjectName.toLowerCase(), loadFromFile(subject).getValue()); + File subject = new File(collection, subjectName + ".json"); + return new AbstractMap.SimpleEntry<>(subjectName, loadFromFile(subject).getValue()); } public Map.Entry loadFromFile(File file) throws IOException { @@ -120,7 +120,7 @@ public class SubjectStorage { } String s = Files.toString(file, Charset.defaultCharset()); - return new AbstractMap.SimpleEntry<>(file.getName().substring(file.getName().length() - 5).toLowerCase(), loadFromString(s)); + return new AbstractMap.SimpleEntry<>(file.getName().substring(0, file.getName().length() - 5), loadFromString(s)); } public SubjectDataHolder loadFromString(String s) { diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/simple/SimpleCollection.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/simple/SimpleCollection.java index 0d02dc43..9e2e0250 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/simple/SimpleCollection.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/simple/SimpleCollection.java @@ -22,6 +22,9 @@ package me.lucko.luckperms.sponge.service.simple; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; @@ -35,7 +38,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; /** * Super simple SubjectCollection implementation @@ -47,25 +50,27 @@ public class SimpleCollection implements SubjectCollection { @Getter private final String identifier; - private final Map subjects = new ConcurrentHashMap<>(); + private final LoadingCache subjects = CacheBuilder.newBuilder() + .build(new CacheLoader() { + @Override + public SimpleSubject load(String s) { + return new SimpleSubject(s, service, SimpleCollection.this); + } + }); @Override - public synchronized Subject get(@NonNull String id) { - if (!subjects.containsKey(id)) { - subjects.put(id, new SimpleSubject(id, service, this)); - } - - return subjects.get(id); + public Subject get(@NonNull String id) { + return subjects.getUnchecked(id.toLowerCase()); } @Override public boolean hasRegistered(@NonNull String id) { - return subjects.containsKey(id); + return subjects.asMap().containsKey(id.toLowerCase()); } @Override public Iterable getAllSubjects() { - return subjects.values(); + return subjects.asMap().values().stream().map(s -> (Subject) s).collect(Collectors.toList()); } @Override @@ -76,7 +81,7 @@ public class SimpleCollection implements SubjectCollection { @Override public Map getAllWithPermission(@NonNull Set contexts, @NonNull String node) { Map m = new HashMap<>(); - for (Subject subject : subjects.values()) { + for (Subject subject : subjects.asMap().values()) { Tristate ts = subject.getPermissionValue(contexts, node); if (ts != Tristate.UNDEFINED) { m.put(subject, ts.asBoolean()); diff --git a/sponge/src/main/java/me/lucko/luckperms/sponge/service/simple/SimpleSubject.java b/sponge/src/main/java/me/lucko/luckperms/sponge/service/simple/SimpleSubject.java index b8686283..433565cd 100644 --- a/sponge/src/main/java/me/lucko/luckperms/sponge/service/simple/SimpleSubject.java +++ b/sponge/src/main/java/me/lucko/luckperms/sponge/service/simple/SimpleSubject.java @@ -25,9 +25,14 @@ package me.lucko.luckperms.sponge.service.simple; import com.google.common.collect.ImmutableList; import lombok.Getter; import lombok.NonNull; +import me.lucko.luckperms.api.context.MutableContextSet; +import me.lucko.luckperms.sponge.service.LuckPermsService; import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.service.context.Context; -import org.spongepowered.api.service.permission.*; +import org.spongepowered.api.service.permission.MemorySubjectData; +import org.spongepowered.api.service.permission.Subject; +import org.spongepowered.api.service.permission.SubjectCollection; +import org.spongepowered.api.service.permission.SubjectData; import org.spongepowered.api.util.Tristate; import java.util.ArrayList; @@ -42,11 +47,11 @@ import java.util.Set; public class SimpleSubject implements Subject { private final String identifier; - private final PermissionService service; + private final LuckPermsService service; private final SubjectCollection containingCollection; private final MemorySubjectData subjectData; - public SimpleSubject(String identifier, PermissionService service, SubjectCollection containingCollection) { + public SimpleSubject(String identifier, LuckPermsService service, SubjectCollection containingCollection) { this.identifier = identifier; this.service = service; this.containingCollection = containingCollection; @@ -147,6 +152,6 @@ public class SimpleSubject implements Subject { @Override public Set getActiveContexts() { - return SubjectData.GLOBAL_CONTEXT; + return LuckPermsService.convertContexts(service.getPlugin().getContextManager().giveApplicableContext(this, MutableContextSet.empty())); } }