Add tracing to /lp verbose, API updates/cleanup, add login process event, and utilise string interning for faster context/node comparisons

This commit is contained in:
Luck
2017-09-10 21:23:54 +01:00
Unverified
parent 368700156c
commit 096885d91f
89 changed files with 1389 additions and 728 deletions
@@ -32,49 +32,59 @@ import java.util.Optional;
import javax.annotation.Nonnull;
/**
* Singleton for the {@link LuckPermsApi}.
* Provides static access to the {@link LuckPermsApi}.
*
* <p>Ideally, the ServiceManager for the platform should be used to obtain and cache an instance, however, this can be
* used if you need static access.</p>
* <p>Ideally, the ServiceManager for the platform should be used to obtain an instance,
* however, this provider can be used if you need static access.</p>
*/
public final class LuckPerms {
private static LuckPermsApi api = null;
private static LuckPermsApi instance = null;
/**
* Gets an instance of {@link LuckPermsApi}, throwing {@link IllegalStateException} if the API is not loaded.
* Gets an instance of the {@link LuckPermsApi},
* throwing {@link IllegalStateException} if an instance is not yet loaded.
*
* <p>Will never return null.</p>
*
* @return an api instance
* @throws IllegalStateException if the api is not loaded
*/
@Nonnull
public static LuckPermsApi getApi() {
if (api == null) {
if (instance == null) {
throw new IllegalStateException("API is not loaded.");
}
return api;
return instance;
}
/**
* Gets an instance of {@link LuckPermsApi}, if it is loaded.
*
* <p> Unlike {@link LuckPerms#getApi}, this method will not throw an {@link IllegalStateException} if the API is
* not loaded, rather return an empty {@link Optional}.
* <p>Unlike {@link LuckPerms#getApi}, this method will not throw an
* {@link IllegalStateException} if an instance is not yet loaded, rather return
* an empty {@link Optional}.
*
* @return an optional api instance
*/
@Nonnull
public static Optional<LuckPermsApi> getApiSafe() {
return Optional.ofNullable(api);
return Optional.ofNullable(instance);
}
/* method used by the implementation to set the singleton instance */
static void registerProvider(LuckPermsApi luckPermsApi) {
api = luckPermsApi;
/**
* Registers an instance of the {@link LuckPermsApi} with this provider.
*
* @param instance the instance
*/
static void registerProvider(LuckPermsApi instance) {
LuckPerms.instance = instance;
}
/* method used by the implementation to remove any previous instance */
/**
* Removes the current instance from this provider.
*/
static void unregisterProvider() {
api = null;
LuckPerms.instance = null;
}
private LuckPerms() {
@@ -28,6 +28,7 @@ package me.lucko.luckperms.api;
import com.google.common.base.Preconditions;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nonnull;
@@ -104,4 +105,22 @@ public enum ChatMetaType {
@Nonnull
public abstract Map.Entry<Integer, String> getEntry(@Nonnull Node node);
/**
* Parses a ChatMetaType from the given node.
*
* @param node the node
* @return the parsed chat meta type
* @since 3.4
*/
@Nonnull
public static Optional<ChatMetaType> ofNode(@Nonnull Node node) {
if (node.isPrefix()) {
return Optional.of(PREFIX);
} else if (node.isSuffix()) {
return Optional.of(SUFFIX);
} else {
return Optional.empty();
}
}
}
@@ -30,11 +30,29 @@ import me.lucko.luckperms.exceptions.ObjectLacksException;
import java.util.function.Supplier;
/**
* Represents the result of a mutation call.
*/
public enum DataMutateResult {
/**
* Indicates the mutation was a success
*/
SUCCESS(true, null),
/**
* Indicates the mutation failed because the subject already has something
*/
ALREADY_HAS(false, ObjectAlreadyHasException::new),
/**
* Indicates the mutation failed because the subject lacks something
*/
LACKS(false, ObjectLacksException::new),
/**
* Indicates the mutation failed
*/
FAIL(false, RuntimeException::new);
private boolean value;
@@ -51,10 +69,35 @@ public enum DataMutateResult {
}
}
/**
* Gets a boolean representation of the result.
*
* @return a boolean representation
*/
public boolean asBoolean() {
return value;
}
/**
* Gets if the result indicates a success
*
* @return if the result indicates a success
* @since 3.4
*/
public boolean wasSuccess() {
return value;
}
/**
* Gets if the result indicates a failure
*
* @return if the result indicates a failure
* @since 3.4
*/
public boolean wasFailure() {
return !value;
}
// allows us to throw checked exceptions without declaring it, as #throwException throws a number of
// exception types.
private static void sneakyThrow(Throwable t) {
@@ -35,7 +35,7 @@ import java.util.OptionalInt;
import javax.annotation.Nonnull;
/**
* A group which holds permission data.
* An inheritable holder of permission data.
*/
public interface Group extends PermissionHolder {
@@ -27,13 +27,15 @@ package me.lucko.luckperms.api;
import com.google.common.collect.Multimap;
import me.lucko.luckperms.api.context.ContextSet;
import java.util.Optional;
import java.util.OptionalLong;
import javax.annotation.Nonnull;
/**
* A relationship between a Holder and a permission
* A relationship between a PermissionHolder and a permission
*
* @param <T> the identifier type of the holder
* @since 2.17
@@ -90,10 +92,19 @@ public interface HeldPermission<T> {
* Gets the context for the permission.
*
* @return the context
* @deprecated in favour of {@link #getContexts()}.
*/
@Nonnull
@Deprecated
Multimap<String, String> getContext();
/**
* Gets the extra context for the permission.
*
* @return the extra context
*/
ContextSet getContexts();
/**
* Converts this permission into a Node
*
@@ -32,104 +32,119 @@ import java.util.Map;
import javax.annotation.Nonnull;
/**
* Read-only access to the LuckPerms configuration settings
* Wrapper around parts of the LuckPerms configuration file
*/
public interface LPConfiguration {
/**
* Returns the name of this server
* Gets the name of this server
*
* @return the name of this server
*/
@Nonnull
String getServer();
/**
* Returns how often a sync task will run in minutes
* Gets how often a sync task will run in minutes
*
* @return how often a sync task will run in minutes
*/
int getSyncTime();
/**
* Returns if the users on this server will have their global permissions applied
* Gets if the users on this server will have their global permissions applied
*
* @return if the users on this server will have their global permissions applied
*/
boolean getIncludeGlobalPerms();
/**
* Returns if the users on this server will have their global world permissions applied
* Gets if the users on this server will have their global world permissions applied
*
* @return if the users on this server will have their global world permissions applied
* @since 2.9
*/
boolean getIncludeGlobalWorldPerms();
/**
* Returns true if the platform is applying global groups
* Gets if the platform is applying global groups
*
* @return true if the platform is applying global groups
* @since 2.9
*/
boolean getApplyGlobalGroups();
/**
* Returns true if the platform is applying global world groups
* Gets if the platform is applying global world groups
*
* @return true if the platform is applying global world groups
* @since 2.9
*/
boolean getApplyGlobalWorldGroups();
/**
* Returns the online mode setting
* Gets the online mode setting
*
* @return the online mode setting
*/
boolean getOnlineMode();
/**
* Returns if LuckPerms is applying wildcard permissions
* Gets if LuckPerms is applying wildcard permissions
*
* @return if LuckPerms is applying wildcard permissions
*/
boolean getApplyWildcards();
/**
* Returns if LuckPerms is resolving and applying regex permissions
*
* @return if LuckPerms is resolving and applying regex permissions
*/
boolean getApplyRegex();
/**
* Returns if LuckPerms is expanding shorthand permissions
* Gets if LuckPerms is expanding shorthand permissions
*
* @return if LuckPerms is expanding shorthand permissions
*/
boolean getApplyShorthand();
/**
* Returns if LuckPerms will send notifications to users when permissions are modified
* Gets if LuckPerms will send notifications to users when permissions are modified
*
* @return if LuckPerms will send notifications to users when permissions are modified
* @since 2.7
*/
boolean getLogNotify();
/**
* Returns true if the vanilla op system is enabled
* Gets if the vanilla op system is enabled
*
* @return true if the vanilla op system is enabled
* @since 2.8
*/
boolean getEnableOps();
/**
* Returns true if opped players are allowed to use LuckPerms commands
* Gets if opped players are allowed to use LuckPerms commands
*
* @return true if opped players are allowed to use LuckPerms commands
* @since 2.8
*/
boolean getCommandsAllowOp();
/**
* Returns true if auto op is enabled
* Gets if auto op is enabled
*
* @return true if auto op is enabled
* @since 2.9
*/
boolean getAutoOp();
/**
* Returns the name of the server used within Vault operations
* Gets the name of the server used within Vault operations
*
* @return the name of the server used within Vault operations
* @since 2.7
*/
@@ -137,35 +152,40 @@ public interface LPConfiguration {
String getVaultServer();
/**
* Returns true if global permissions should be considered when retrieving meta or player groups
* Gets if global permissions should be considered when retrieving meta or player groups
*
* @return true if global permissions should be considered when retrieving meta or player groups
* @since 2.7
*/
boolean getVaultIncludeGlobal();
/**
* Returns the values set for data storage in the configuration
* Gets the values set for data storage in the configuration
*
* @return the values set for data storage in the configuration
*/
@Nonnull
DatastoreConfiguration getDatastoreConfig();
/**
* Returns the storage method string from the configuration
* Gets the storage method string from the configuration
*
* @return the storage method string from the configuration
*/
@Nonnull
String getStorageMethod();
/**
* Returns true if split storage is enabled
* Gets true if split storage is enabled
*
* @return true if split storage is enabled
* @since 2.7
*/
boolean getSplitStorage();
/**
* Returns a map of split storage options
* Gets a map of split storage options
*
* @return a map of split storage options, where the key is the storage section, and the value is the storage
* method. For example: key = user, value = json
* @since 2.7
@@ -25,6 +25,7 @@
package me.lucko.luckperms.api;
import me.lucko.luckperms.LuckPerms;
import me.lucko.luckperms.api.context.ContextCalculator;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.event.EventBus;
@@ -38,7 +39,13 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* The root API interface for LuckPerms
* The LuckPerms API.
*
* <p>This interface is the base of the entire API package. All API functions
* are accessed via this interface.</p>
*
* <p>An instance can be obtained via {@link LuckPerms#getApi()}, or the platforms
* Services Manager.</p>
*/
public interface LuckPermsApi {
@@ -41,6 +41,7 @@ import java.util.Set;
*/
@Deprecated
public class MetaUtils {
private static final String[] DELIMS = new String[]{".", "/", "-", "$"};
private static String escapeDelimiters(String s, String... delims) {
for (String delim : delims) {
@@ -68,7 +69,7 @@ public class MetaUtils {
throw new NullPointerException();
}
return escapeDelimiters(s, ".", "/", "-", "$");
return escapeDelimiters(s, DELIMS);
}
/**
@@ -86,7 +87,7 @@ public class MetaUtils {
s = s.replace("{SEP}", ".");
s = s.replace("{FSEP}", "/");
s = s.replace("{DSEP}", "$");
s = unescapeDelimiters(s, ".", "/", "-", "$");
s = unescapeDelimiters(s, DELIMS);
return s;
}
@@ -161,7 +162,7 @@ public class MetaUtils {
node = escapeCharacters(node);
for (Node n : holder.getPermissions()) {
if (!n.getValue() || !n.isMeta()) continue;
if (!n.getValuePrimitive() || !n.isMeta()) continue;
if (!n.shouldApplyOnServer(server, includeGlobal, false)) continue;
if (!n.shouldApplyOnWorld(world, includeGlobal, false)) continue;
@@ -233,7 +234,7 @@ public class MetaUtils {
int priority = Integer.MIN_VALUE;
String meta = null;
for (Node n : holder.getAllNodes(Contexts.allowAll())) {
if (!n.getValue()) continue;
if (!n.getValuePrimitive()) continue;
if (!n.shouldApplyOnServer(server, includeGlobal, false)) continue;
if (!n.shouldApplyOnWorld(world, includeGlobal, false)) continue;
@@ -37,16 +37,18 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* An immutable permission node
* Represents a permission node.
*
* <p>Use {@link LuckPermsApi#buildNode(String)} to get an instance.</p>
* <p>All implementations of this interface must be immutable.</p>
*
* <p>Use the {@link NodeFactory} to obtain and construct instances.</p>
*
* @since 2.6
*/
public interface Node extends Map.Entry<String, Boolean> {
/**
* Returns the actual permission string
* Gets the permission string
*
* @return the actual permission node
*/
@@ -54,36 +56,50 @@ public interface Node extends Map.Entry<String, Boolean> {
String getPermission();
/**
* Gets what value the permission is set to. A negated node would return <code>false</code>.
* Gets the value.
*
* <p>A negated node would return a value of <code>false</code>.</p>
*
* @return the permission's value
*/
@Override
@Nonnull
Boolean getValue();
default Boolean getValue() {
return getValuePrimitive();
}
/**
* Returns the value of this node as a tristate
* Gets the value.
*
* <p>A negated node would return a value of <code>false</code>.</p>
*
* @return the permission's value
*/
boolean getValuePrimitive();
/**
* Gets the value of this node as a {@link Tristate}
*
* @return the value of this node as a Tristate
*/
@Nonnull
default Tristate getTristate() {
return Tristate.fromBoolean(getValue());
return Tristate.fromBoolean(getValuePrimitive());
}
/**
* Returns if the node is negated
* Gets if the node is negated
*
* @return true if the node is negated
*/
default boolean isNegated() {
return !getValue();
return !getValuePrimitive();
}
/**
* If this node is set to override explicitly.
* This value does not persist across saves, and is therefore only useful for transient nodes
* Gets if this node is set to override explicitly.
*
* <p>This value does not persist across saves, and is therefore only useful for transient nodes</p>
*
* @return true if this node is set to override explicitly
*/
@@ -106,21 +122,21 @@ public interface Node extends Map.Entry<String, Boolean> {
Optional<String> getWorld();
/**
* Returns if this node is server specific
* Gets if this node is server specific
*
* @return true if this node is server specific
*/
boolean isServerSpecific();
/**
* Returns if this node is server specific
* Gets if this node is server specific
*
* @return true if this node is server specific
*/
boolean isWorldSpecific();
/**
* Returns if this node applies globally, and has no specific context
* Gets if this node applies globally, and therefore has no specific context
*
* @return true if this node applies globally, and has no specific context
* @since 3.1
@@ -128,7 +144,7 @@ public interface Node extends Map.Entry<String, Boolean> {
boolean appliesGlobally();
/**
* Returns if this node has any specific context in order to apply.
* Gets if this node has any specific context in order for it to apply
*
* @return true if this node has specific context
* @since 3.1
@@ -136,7 +152,7 @@ public interface Node extends Map.Entry<String, Boolean> {
boolean hasSpecificContext();
/**
* Returns if this node is able to apply in the given context
* Gets if this node is able to apply in the given context
*
* @param includeGlobal if global server values should apply
* @param includeGlobalWorld if global world values should apply
@@ -150,7 +166,7 @@ public interface Node extends Map.Entry<String, Boolean> {
boolean shouldApply(boolean includeGlobal, boolean includeGlobalWorld, @Nullable String server, @Nullable String world, @Nullable ContextSet context, boolean applyRegex);
/**
* If this node should apply on a specific server
* Gets if this node should apply on a specific server
*
* @param server the name of the server
* @param includeGlobal if global permissions should apply
@@ -160,7 +176,7 @@ public interface Node extends Map.Entry<String, Boolean> {
boolean shouldApplyOnServer(@Nullable String server, boolean includeGlobal, boolean applyRegex);
/**
* If this node should apply on a specific world
* Gets if this node should apply on a specific world
*
* @param world the name of the world
* @param includeGlobal if global permissions should apply
@@ -170,7 +186,7 @@ public interface Node extends Map.Entry<String, Boolean> {
boolean shouldApplyOnWorld(@Nullable String world, boolean includeGlobal, boolean applyRegex);
/**
* If this node should apply in the given context
* Gets 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
@@ -180,7 +196,7 @@ public interface Node extends Map.Entry<String, Boolean> {
boolean shouldApplyWithContext(@Nonnull ContextSet context, boolean worldAndServer);
/**
* If this node should apply in the given context
* Gets if this node should apply in the given context
*
* @param context the context key value pairs
* @return true if the node should apply
@@ -211,8 +227,10 @@ public interface Node extends Map.Entry<String, Boolean> {
*
* @param possibleNodes a list of possible permission nodes
* @return a list of permissions that match this wildcard
* @deprecated as this is no longer used internally to resolve wildcards
*/
@Nonnull
@Deprecated
List<String> resolveWildcard(@Nonnull List<String> possibleNodes);
/**
@@ -224,14 +242,14 @@ public interface Node extends Map.Entry<String, Boolean> {
List<String> resolveShorthand();
/**
* Returns if this node will expire in the future
* Gets if this node will expire in the future
*
* @return true if this node will expire in the future
*/
boolean isTemporary();
/**
* Returns if this node will not expire
* Gets if this node will not expire
*
* @return true if this node will not expire
*/
@@ -240,7 +258,7 @@ public interface Node extends Map.Entry<String, Boolean> {
}
/**
* Returns a unix timestamp in seconds when this node will expire
* Gets a unix timestamp in seconds when this node will expire
*
* @return the time in Unix time when this node will expire
* @throws IllegalStateException if the node is not temporary
@@ -248,7 +266,7 @@ public interface Node extends Map.Entry<String, Boolean> {
long getExpiryUnixTime() throws IllegalStateException;
/**
* Returns the date when this node will expire
* Gets the date when this node will expire
*
* @return the {@link Date} when this node will expire
* @throws IllegalStateException if the node is not temporary
@@ -257,7 +275,7 @@ public interface Node extends Map.Entry<String, Boolean> {
Date getExpiry() throws IllegalStateException;
/**
* Return the number of seconds until this permission will expire
* Gets the number of seconds until this permission will expire
*
* @return the number of seconds until this permission will expire
* @throws IllegalStateException if the node is not temporary
@@ -265,8 +283,9 @@ public interface Node extends Map.Entry<String, Boolean> {
long getSecondsTilExpiry() throws IllegalStateException;
/**
* Return true if the node has expired.
* This also returns false if the node is not temporary
* Gets if the node has expired.
*
* <p>This also returns false if the node is not temporary.</p>
*
* @return true if this node has expired
*/
@@ -301,14 +320,14 @@ public interface Node extends Map.Entry<String, Boolean> {
String toSerializedNode();
/**
* Returns if this is a group node
* Gets if this is a group node
*
* @return true if this is a group node
*/
boolean isGroupNode();
/**
* Returns the name of the group
* Gets the name of the group, if this is a group node.
*
* @return the name of the group
* @throws IllegalStateException if this is not a group node. See {@link #isGroupNode()}
@@ -317,7 +336,7 @@ public interface Node extends Map.Entry<String, Boolean> {
String getGroupName() throws IllegalStateException;
/**
* Returns if this node is a wildcard node
* Gets if this node is a wildcard node
*
* @return true if this node is a wildcard node
*/
@@ -332,7 +351,7 @@ public interface Node extends Map.Entry<String, Boolean> {
int getWildcardLevel() throws IllegalStateException;
/**
* Returns if this node is a meta node
* Gets if this node is a meta node
*
* @return true if this node is a meta node
*/
@@ -348,7 +367,7 @@ public interface Node extends Map.Entry<String, Boolean> {
Map.Entry<String, String> getMeta() throws IllegalStateException;
/**
* Returns if this node is a prefix node
* Gets if this node is a prefix node
*
* @return true if this node is a prefix node
*/
@@ -364,7 +383,7 @@ public interface Node extends Map.Entry<String, Boolean> {
Map.Entry<Integer, String> getPrefix() throws IllegalStateException;
/**
* Returns if this node is a suffix node
* Gets if this node is a suffix node
*
* @return true if this node is a suffix node
*/
@@ -28,27 +28,12 @@ package me.lucko.luckperms.api;
import javax.annotation.Nonnull;
/**
* Builds {@link Node} instances
* Assists with constructing {@link Node} instances.
*
* @since 2.17
*/
public interface NodeFactory {
/**
* Creates a node from a serialised node string
*
* @param serialisedPermission the serialised permission string
* @param value the value of the node
* @return a node instance
* @throws NullPointerException if the permission is null
* @deprecated since this format isn't used internally for permissions anymore
* @see Node#toSerializedNode()
*/
@Deprecated
@Nonnull
Node fromSerialisedNode(@Nonnull String serialisedPermission, boolean value);
/**
* Creates a new node builder from a given base permission string
*
@@ -69,20 +54,6 @@ public interface NodeFactory {
@Nonnull
Node.Builder newBuilderFromExisting(@Nonnull Node other);
/**
* Creates a node builder from a serialised node string
*
* @param serialisedPermission the serialised permission string
* @param value the value of the node
* @return a node builder instance
* @throws NullPointerException if the permission is null
* @deprecated since this format isn't used internally for permissions anymore
* @see Node#toSerializedNode()
*/
@Deprecated
@Nonnull
Node.Builder newBuilderFromSerialisedNode(@Nonnull String serialisedPermission, boolean value);
/**
* Creates a node builder from a group
@@ -141,4 +112,35 @@ public interface NodeFactory {
@Nonnull
Node.Builder makeSuffixNode(int priority, @Nonnull String suffix);
/**
* Creates a node from a serialised node string
*
* <p>This format is what was previously used in YAML/JSON storage files.</p>
*
* @param serialisedPermission the serialised permission string
* @param value the value of the node
* @return a node instance
* @throws NullPointerException if the permission is null
* @deprecated since this format isn't used internally for permissions anymore
* @see Node#toSerializedNode()
*/
@Deprecated
@Nonnull
Node fromSerialisedNode(@Nonnull String serialisedPermission, boolean value);
/**
* Creates a node builder from a serialised node string
*
* @param serialisedPermission the serialised permission string
* @param value the value of the node
* @return a node builder instance
* @throws NullPointerException if the permission is null
* @deprecated since this format isn't used internally for permissions anymore
* @see Node#toSerializedNode()
*/
@Deprecated
@Nonnull
Node.Builder newBuilderFromSerialisedNode(@Nonnull String serialisedPermission, boolean value);
}
@@ -43,14 +43,15 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* An object capable of holding permissions
* An object which holds permissions.
*
* <p>Any changes made will be lost unless the instance is saved back to the {@link Storage}.</p>
* <p>Any changes made to permission holding objects will be lost unless the
* instance is saved back to the {@link Storage}.</p>
*/
public interface PermissionHolder {
/**
* Gets the objects name
* Gets the objects name.
*
* <p>{@link User#getUuid()}, {@link User#getName()} or {@link Group#getName()} should normally be used instead of
* this method.</p>
@@ -123,7 +124,7 @@ public interface PermissionHolder {
SortedSet<? extends Node> getPermissions();
/**
* Similar to {@link #getPermissions()}, except without transient permissions
* Similar to {@link #getPermissions()}, except without transient permissions.
*
* <p>Unlike transient permissions, enduring permissions will be saved to storage, and exist after the session.</p>
*
@@ -134,7 +135,7 @@ public interface PermissionHolder {
Set<? extends Node> getEnduringPermissions();
/**
* Similar to {@link #getPermissions()}, except without enduring permissions
* Similar to {@link #getPermissions()}, except without enduring permissions.
*
* <p>Transient permissions only exist for the duration of the session.</p>
*
@@ -28,7 +28,7 @@ package me.lucko.luckperms.api;
import javax.annotation.Nonnull;
/**
* The platforms which LuckPerms can run on
* Represents a type of platform which LuckPerms can run on.
*
* @since 2.7
*/
@@ -44,6 +44,11 @@ public enum PlatformType {
this.friendlyName = friendlyName;
}
/**
* Gets a readable name for the platform type.
*
* @return a readable name
*/
@Nonnull
public String getFriendlyName() {
return friendlyName;
@@ -36,14 +36,14 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A means of loading and saving data to/from the Storage provider.
* A means of loading and saving permission data to/from the backend.
*
* <p>All methods return {@link CompletableFuture}s, which will be populated with the result once the data has been
* loaded asynchronously. Care should be taken when using the methods to ensure that the main server thread is not
* <p>All blocking methods return {@link CompletableFuture}s, which will be populated with the result once the data has been
* loaded/saved asynchronously. Care should be taken when using such methods to ensure that the main server thread is not
* blocked.</p>
*
* <p>Methods such as {@link CompletableFuture#get()} and equivalent should <strong>not</strong> be called on the main
* server thread. If you need to use the result of these operations on the main server thread, please register a
* server thread. If you need to use the result of these operations on the main server thread, register a
* callback using {@link CompletableFuture#thenAcceptAsync(Consumer, Executor)} and {@link #getSyncExecutor()}.</p>
*
* @since 2.14
@@ -59,7 +59,7 @@ public interface Storage {
String getName();
/**
* Return whether the storage instance is allowing logins on the platform.
* Gets whether the storage instance is allowing logins on the platform.
*
* @return true if logins are enabled
*/
@@ -144,7 +144,8 @@ public interface Storage {
/**
* Gets a set all "unique" user UUIDs.
* "Unique" meaning the user isn't just a member of the "default" group.
*
* <p>"Unique" meaning the user isn't just a member of the "default" group.</p>
*
* @return a set of uuids, or null if the operation failed.
*/
@@ -34,21 +34,24 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* An ordered collection of groups for easy promotions and demotions
* An ordered chain of {@link Group}s.
*/
public interface Track {
/**
* Gets the name of this track
*
* @return the name of this track
*/
@Nonnull
String getName();
/**
* Gets an ordered list of the groups on this track
* Gets a list of the groups on this track
*
* <p>Index 0 is the first/lowest group in (or start of) the track</p>
* <p>Index 0 is the first/lowest group in (or start of) the track.</p>
*
* <p>The returned collection is immutable, and cannot be modified.</p>
*
* @return an ordered {@link List} of the groups on this track
*/
@@ -28,37 +28,42 @@ package me.lucko.luckperms.api;
import javax.annotation.Nonnull;
/**
* Represents a permission setting.
* Represents three different states of a setting.
*
* <p>Consider a value of {@link #TRUE} to be a positive setting, {@link #FALSE} to be a "negated" setting,
* and a value of {@link #UNDEFINED} to be a non-existent value.</p>
* <p>Possible values:</p>
* <p></p>
* <ul>
* <li>{@link #TRUE} - a positive setting</li>
* <li>{@link #FALSE} - a negative (negated) setting</li>
* <li>{@link #UNDEFINED} - a non-existent setting</li>
* </ul>
*/
public enum Tristate {
/**
* A value indicating a holder has a permission set.
* A value indicating a positive setting
*/
TRUE(true),
/**
* A value indicating a holder has a negated value for a permission.
* A value indicating a negative (negated) setting
*/
FALSE(false),
/**
* A value indicating a holder doesn't have a value for a permission set.
* A value indicating a non-existent setting
*/
UNDEFINED(false);
/**
* Converts from {@link Boolean} a boolean
* Returns a {@link Tristate} from a boolean
*
* @param b the boolean
* @param val the boolean value
* @return {@link #TRUE} or {@link #FALSE}, depending on the value of the boolean.
*/
@Nonnull
public static Tristate fromBoolean(boolean b) {
return b ? TRUE : FALSE;
public static Tristate fromBoolean(boolean val) {
return val ? TRUE : FALSE;
}
private final boolean booleanValue;
@@ -38,7 +38,7 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A player holding permission data
* A player which holds permission data.
*/
public interface User extends PermissionHolder {
@@ -51,7 +51,9 @@ public interface User extends PermissionHolder {
UUID getUuid();
/**
* Gets the users username, or null if no username is associated with this user
* Gets the users username
*
* <p>Returns null if no username is associated with this user.</p>
*
* @return the users username
*/
@@ -96,6 +98,16 @@ public interface User extends PermissionHolder {
@Nonnull
UserData getCachedData();
/**
* Pre-calculates some values in the user's data cache.
*
* <p>Is it <b>not</b> necessary to call this method before
* using {@link #getCachedData()}.</p>
*
* @since 2.17
*/
void setupDataCache();
/**
* Check to see if the user is a direct member of a group
*
@@ -128,16 +140,6 @@ public interface User extends PermissionHolder {
@Nonnull
Optional<UserData> getUserDataCache();
/**
* Pre-calculates some values in the user's data cache.
*
* <p>Is it <b>not</b> necessary to call this method before
* using {@link #getCachedData()}.</p>
*
* @since 2.17
*/
void setupDataCache();
/**
* Check to see if a user is a member of a group on a specific server
*
@@ -32,34 +32,34 @@ import javax.annotation.Nonnull;
/**
* A UUID cache for online users, between external Mojang UUIDs, and internal LuckPerms UUIDs.
*
* <p>This UuidCache is a means of allowing users to have the same internal UUID across a network of offline mode
* servers or mixed offline mode and online mode servers. Platforms running in offline mode generate a UUID for a
* user when they first join the server, but this UUID will then not be consistent across the network. LuckPerms will
* instead check the datastore cache, to get a UUID for a user that is consistent across an entire network.</p>
* <p>A user's internal LuckPerms UUID is always the same as their Mojang one,
* unless <code>use-server-uuids</code> is disabled.</p>
*
* <p>If you want to get a user object from the Storage using the api on a server in offline mode, you will need to use
* this cache, OR use Storage#getUUID, for users that are not online.</p>
* <p>When this setting is disabled, this cache becomes active, and allows you to convert
* between 'internal' and 'server provided' uuids.</p>
*
* <p><strong>This is only effective for online players. Use {@link Storage#getUUID(String)} for offline players.</strong></p>
*/
public interface UuidCache {
/**
* Gets a users internal "LuckPerms" UUID, from the one given by the server.
* Gets a users "internal" LuckPerms UUID, from the one given by the server.
*
* @param external the UUID assigned by the server, through Player#getUniqueId or ProxiedPlayer#getUniqueId
* <p>When <code>use-server-uuids</code> is true, this returns the same UUID instance.</p>
*
* @param mojangUuid the UUID assigned by the server, through <code>Player#getUniqueId</code> or <code>ProxiedPlayer#getUniqueId</code>
* @return the corresponding internal UUID
*/
@Nonnull
UUID getUUID(@Nonnull UUID external);
UUID getUUID(@Nonnull UUID mojangUuid);
/**
* Gets a users external, server assigned or Mojang assigned unique id, from the internal one used within LuckPerms.
* Gets a users "external", server assigned unique id, from the internal one used within LuckPerms.
*
* @param internal the UUID used within LuckPerms, through User#getUuid
* @param internalUuid the UUID used within LuckPerms, through <code>User#getUuid</code>
* @return the corresponding external UUID
*/
@Nonnull
UUID getExternalUUID(@Nonnull UUID internal);
UUID getExternalUUID(@Nonnull UUID internalUuid);
}
@@ -33,8 +33,10 @@ import me.lucko.luckperms.api.metastacking.MetaStackDefinition;
import javax.annotation.Nonnull;
/**
* Represents the context for a meta lookup, consisting of a standard {@link Contexts} element, plus options to define how
* the meta stack should be constructed.
* Represents the context for a meta lookup.
*
* <p>Consisting of a standard {@link Contexts} element, plus options to define how
* the meta stack should be constructed.</p>
*
* @since 3.2
*/
@@ -35,7 +35,13 @@ import java.util.Set;
import javax.annotation.Nonnull;
/**
* Holder of contexts.
* A set of context pairs.
*
* <p>You can think of ContextSets as a wrapped <code>Multimap&lt;String, String&gt;</code>.
* Each key can be mapped to multiple values.</p>
*
* <p>Keys are automatically converted to lowercase when added, and are therefore
* case-insensitive. Values however are not.</p>
*
* <p>Implementations may be either mutable or immutable.</p>
*
@@ -72,18 +78,6 @@ public interface ContextSet {
return ImmutableContextSet.of(key1, value1, key2, value2);
}
/**
* Creates an ImmutableContextSet from an existing map
*
* @param map the map to copy from
* @return a new ImmutableContextSet representing the pairs from the map
* @throws NullPointerException if the map is null
*/
@Nonnull
static ImmutableContextSet fromMap(@Nonnull Map<String, String> map) {
return ImmutableContextSet.fromMap(map);
}
/**
* Creates an ImmutableContextSet from an existing iterable of Map Entries
*
@@ -96,6 +90,18 @@ public interface ContextSet {
return ImmutableContextSet.fromEntries(iterable);
}
/**
* Creates an ImmutableContextSet from an existing map
*
* @param map the map to copy from
* @return a new ImmutableContextSet representing the pairs from the map
* @throws NullPointerException if the map is null
*/
@Nonnull
static ImmutableContextSet fromMap(@Nonnull Map<String, String> map) {
return ImmutableContextSet.fromMap(map);
}
/**
* Creates an ImmutableContextSet from an existing multimap
*
@@ -133,7 +139,7 @@ public interface ContextSet {
}
/**
* Check to see if this set is in an immutable form
* Gets if this set is in an immutable form
*
* @return true if the set is immutable
*/
@@ -167,12 +173,14 @@ public interface ContextSet {
/**
* Converts this ContextSet to an immutable {@link Map}
*
* <b>NOTE: Use of this method may result in data being lost. ContextSets can contain lots of different values for
* <b>IMPORTANT: Use of this method may result in data being lost. ContextSets can contain lots of different values for
* one key.</b>
*
* @return an immutable map
* @deprecated because the resultant map may not contain all data in the ContextSet
*/
@Nonnull
@Deprecated
Map<String, String> toMap();
/**
@@ -225,6 +233,19 @@ public interface ContextSet {
*/
boolean has(@Nonnull String key, @Nonnull String value);
/**
* Check if thr set contains a given key mapped to a given value
*
* @param entry the entry to look for
* @return true if the set contains the KV pair
* @throws NullPointerException if the key or value is null
* @since 3.4
*/
default boolean has(@Nonnull Map.Entry<String, String> entry) {
Preconditions.checkNotNull(entry, "entry");
return has(entry.getKey(), entry.getValue());
}
/**
* Same as {@link #has(String, String)}, except ignores the case of the value.
*
@@ -235,6 +256,19 @@ public interface ContextSet {
*/
boolean hasIgnoreCase(@Nonnull String key, @Nonnull String value);
/**
* Same as {@link #has(Map.Entry)}, except ignores the case of the value.
*
* @param entry the entry to look for
* @return true if the set contains the KV pair
* @throws NullPointerException if the key or value is null
* @since 3.4
*/
default boolean hasIgnoreCase(@Nonnull Map.Entry<String, String> entry) {
Preconditions.checkNotNull(entry, "entry");
return hasIgnoreCase(entry.getKey(), entry.getValue());
}
/**
* Checks to see if all entries in this context set are also included in another set.
*
@@ -243,6 +277,18 @@ public interface ContextSet {
* @since 3.1
*/
default boolean isSatisfiedBy(@Nonnull ContextSet other) {
return isSatisfiedBy(other, true);
}
/**
* Checks to see if all entries in this context set are also included in another set.
*
* @param other the other set to check
* @param caseSensitive if the lookup should be case sensitive. see {@link #has(Map.Entry)} and {@link #hasIgnoreCase(Map.Entry)}.
* @return true if all entries in this set are also in the other set
* @since 3.4
*/
default boolean isSatisfiedBy(@Nonnull ContextSet other, boolean caseSensitive) {
Preconditions.checkNotNull(other, "other");
if (this.isEmpty()) {
// this is empty, so is therefore always satisfied.
@@ -256,8 +302,14 @@ public interface ContextSet {
} else {
// neither are empty, we need to compare the individual entries
for (Map.Entry<String, String> pair : toSet()) {
if (!other.has(pair.getKey(), pair.getValue())) {
return false;
if (caseSensitive) {
if (!other.has(pair)) {
return false;
}
} else {
if (!other.hasIgnoreCase(pair)) {
return false;
}
}
}
@@ -25,12 +25,10 @@
package me.lucko.luckperms.api.context;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.SetMultimap;
import java.util.Collection;
import java.util.Map;
@@ -38,9 +36,14 @@ import java.util.Set;
import javax.annotation.Nonnull;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* An immutable implementation of {@link ContextSet}.
*
* <p>On construction, all keys/values are {@link String#intern()}ed, in order to increase
* comparison speed.</p>
*
* @since 2.16
*/
public final class ImmutableContextSet implements ContextSet {
@@ -56,9 +59,10 @@ public final class ImmutableContextSet implements ContextSet {
*/
@Nonnull
public static ImmutableContextSet singleton(@Nonnull String key, @Nonnull String value) {
Preconditions.checkNotNull(key, "key");
Preconditions.checkNotNull(value, "value");
return new ImmutableContextSet(ImmutableSetMultimap.of(key.toLowerCase(), value));
return new ImmutableContextSet(ImmutableSetMultimap.of(
checkNotNull(key, "key").toLowerCase().intern(),
checkNotNull(value, "value").intern()
));
}
/**
@@ -74,30 +78,12 @@ public final class ImmutableContextSet implements ContextSet {
*/
@Nonnull
public static ImmutableContextSet of(@Nonnull String key1, @Nonnull String value1, @Nonnull String key2, @Nonnull String value2) {
Preconditions.checkNotNull(key1, "key1");
Preconditions.checkNotNull(value1, "value1");
Preconditions.checkNotNull(key2, "key2");
Preconditions.checkNotNull(value2, "value2");
return new ImmutableContextSet(ImmutableSetMultimap.of(key1.toLowerCase(), value1, key2.toLowerCase(), value2));
}
/**
* Creates an ImmutableContextSet from an existing map
*
* @param map the map to copy from
* @return a new ImmutableContextSet representing the pairs from the map
* @throws NullPointerException if the map is null
*/
@Nonnull
public static ImmutableContextSet fromMap(@Nonnull Map<String, String> map) {
Preconditions.checkNotNull(map, "map");
ImmutableSetMultimap.Builder<String, String> b = ImmutableSetMultimap.builder();
for (Map.Entry<String, String> e : map.entrySet()) {
b.put(e.getKey().toLowerCase(), e.getValue());
}
return new ImmutableContextSet(b.build());
return new ImmutableContextSet(ImmutableSetMultimap.of(
checkNotNull(key1, "key1").toLowerCase().intern(),
checkNotNull(value1, "value1").intern(),
checkNotNull(key2, "key2").toLowerCase().intern(),
checkNotNull(value2, "value2").intern()
));
}
/**
@@ -109,8 +95,26 @@ public final class ImmutableContextSet implements ContextSet {
*/
@Nonnull
public static ImmutableContextSet fromEntries(@Nonnull Iterable<? extends Map.Entry<String, String>> iterable) {
Preconditions.checkNotNull(iterable, "iterable");
return MutableContextSet.fromEntries(iterable).makeImmutable();
checkNotNull(iterable, "iterable");
ImmutableSetMultimap.Builder<String, String> b = ImmutableSetMultimap.builder();
for (Map.Entry<String, String> e : iterable) {
b.put(checkNotNull(e.getKey()).toLowerCase().intern(), checkNotNull(e.getValue()).intern());
}
return new ImmutableContextSet(b.build());
}
/**
* Creates an ImmutableContextSet from an existing map
*
* @param map the map to copy from
* @return a new ImmutableContextSet representing the pairs from the map
* @throws NullPointerException if the map is null
*/
@Nonnull
public static ImmutableContextSet fromMap(@Nonnull Map<String, String> map) {
return fromEntries(checkNotNull(map, "map").entrySet());
}
/**
@@ -122,8 +126,7 @@ public final class ImmutableContextSet implements ContextSet {
*/
@Nonnull
public static ImmutableContextSet fromMultimap(@Nonnull Multimap<String, String> multimap) {
Preconditions.checkNotNull(multimap, "multimap");
return MutableContextSet.fromMultimap(multimap).makeImmutable();
return fromEntries(checkNotNull(multimap, "multimap").entries());
}
/**
@@ -136,7 +139,7 @@ public final class ImmutableContextSet implements ContextSet {
*/
@Nonnull
public static ImmutableContextSet fromSet(@Nonnull ContextSet contextSet) {
return Preconditions.checkNotNull(contextSet, "contextSet").makeImmutable();
return checkNotNull(contextSet, "contextSet").makeImmutable();
}
/**
@@ -149,10 +152,10 @@ public final class ImmutableContextSet implements ContextSet {
return EMPTY;
}
private final SetMultimap<String, String> map;
private final ImmutableSetMultimap<String, String> map;
ImmutableContextSet(Multimap<String, String> contexts) {
this.map = ImmutableSetMultimap.copyOf(contexts);
ImmutableContextSet(ImmutableSetMultimap<String, String> contexts) {
this.map = contexts;
}
@Override
@@ -176,7 +179,7 @@ public final class ImmutableContextSet implements ContextSet {
@Override
@Nonnull
public Set<Map.Entry<String, String>> toSet() {
return ImmutableSet.copyOf(map.entries());
return map.entries();
}
@Override
@@ -197,36 +200,35 @@ public final class ImmutableContextSet implements ContextSet {
}
@Override
@Nonnull
public boolean containsKey(@Nonnull String key) {
Preconditions.checkNotNull(key, "key");
return map.containsKey(key);
return map.containsKey(checkNotNull(key, "key").toLowerCase().intern());
}
@Override
@Nonnull
public Set<String> getValues(@Nonnull String key) {
Preconditions.checkNotNull(key, "key");
Collection<String> c = map.get(key);
return c != null && !c.isEmpty() ? ImmutableSet.copyOf(c) : ImmutableSet.of();
Collection<String> values = map.get(checkNotNull(key, "key").toLowerCase().intern());
return values != null ? ImmutableSet.copyOf(values) : ImmutableSet.of();
}
@Override
@Nonnull
public boolean has(@Nonnull String key, @Nonnull String value) {
Preconditions.checkNotNull(key, "key");
Preconditions.checkNotNull(value, "value");
return map.containsEntry(key, value);
return map.containsEntry(checkNotNull(key, "key").toLowerCase().intern(), checkNotNull(value, "value").intern());
}
@Override
@Nonnull
public boolean hasIgnoreCase(@Nonnull String key, @Nonnull String value) {
Preconditions.checkNotNull(key, "key");
Preconditions.checkNotNull(value, "value");
value = checkNotNull(value, "value").intern();
Collection<String> values = map.get(checkNotNull(key, "key").toLowerCase().intern());
Collection<String> c = map.get(key);
if (c == null || c.isEmpty()) {
if (values == null || values.isEmpty()) {
return false;
}
for (String val : c) {
for (String val : values) {
if (val.equalsIgnoreCase(value)) {
return true;
}
@@ -250,6 +252,11 @@ public final class ImmutableContextSet implements ContextSet {
if (!(o instanceof ContextSet)) return false;
final ContextSet other = (ContextSet) o;
// saves on copying the multimap
if (other instanceof MutableContextSet) {
return other.equals(this);
}
final Multimap<String, String> thisContexts = this.toMultimap();
final Multimap<String, String> otherContexts = other.toMultimap();
return thisContexts.equals(otherContexts);
@@ -257,7 +264,7 @@ public final class ImmutableContextSet implements ContextSet {
@Override
public int hashCode() {
return 59 + (this.map == null ? 43 : this.map.hashCode());
return map.hashCode();
}
@Override
@@ -40,9 +40,14 @@ import java.util.Set;
import javax.annotation.Nonnull;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A mutable implementation of {@link ContextSet}.
*
* <p>On construction, all keys/values are {@link String#intern()}ed, in order to increase
* comparison speed.</p>
*
* @since 2.16
*/
public final class MutableContextSet implements ContextSet {
@@ -57,9 +62,9 @@ public final class MutableContextSet implements ContextSet {
*/
@Nonnull
public static MutableContextSet singleton(@Nonnull String key, @Nonnull String value) {
Preconditions.checkNotNull(key, "key");
Preconditions.checkNotNull(value, "value");
MutableContextSet set = new MutableContextSet();
checkNotNull(key, "key");
checkNotNull(value, "value");
MutableContextSet set = MutableContextSet.create();
set.add(key, value);
return set;
}
@@ -77,27 +82,13 @@ public final class MutableContextSet implements ContextSet {
*/
@Nonnull
public static MutableContextSet of(@Nonnull String key1, @Nonnull String value1, @Nonnull String key2, @Nonnull String value2) {
Preconditions.checkNotNull(key1, "key1");
Preconditions.checkNotNull(value1, "value1");
Preconditions.checkNotNull(key2, "key2");
Preconditions.checkNotNull(value2, "value2");
MutableContextSet ret = singleton(key1, value1);
ret.add(key2, value2);
return ret;
}
/**
* 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
*/
@Nonnull
public static MutableContextSet fromMap(@Nonnull Map<String, String> map) {
Preconditions.checkNotNull(map, "map");
MutableContextSet set = new MutableContextSet();
set.addAll(map);
checkNotNull(key1, "key1");
checkNotNull(value1, "value1");
checkNotNull(key2, "key2");
checkNotNull(value2, "value2");
MutableContextSet set = MutableContextSet.create();
set.add(key1, value1);
set.add(key2, value2);
return set;
}
@@ -110,12 +101,27 @@ public final class MutableContextSet implements ContextSet {
*/
@Nonnull
public static MutableContextSet fromEntries(@Nonnull Iterable<? extends Map.Entry<String, String>> iterable) {
Preconditions.checkNotNull(iterable, "iterable");
MutableContextSet set = new MutableContextSet();
checkNotNull(iterable, "iterable");
MutableContextSet set = MutableContextSet.create();
set.addAll(iterable);
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
*/
@Nonnull
public static MutableContextSet fromMap(@Nonnull Map<String, String> map) {
checkNotNull(map, "map");
MutableContextSet set = MutableContextSet.create();
set.addAll(map);
return set;
}
/**
* Creates a MutableContextSet from an existing multimap
*
@@ -125,8 +131,10 @@ public final class MutableContextSet implements ContextSet {
*/
@Nonnull
public static MutableContextSet fromMultimap(@Nonnull Multimap<String, String> multimap) {
Preconditions.checkNotNull(multimap, "multimap");
return fromEntries(multimap.entries());
checkNotNull(multimap, "multimap");
MutableContextSet set = MutableContextSet.create();
set.addAll(multimap);
return set;
}
/**
@@ -161,8 +169,8 @@ public final class MutableContextSet implements ContextSet {
this.map = Multimaps.synchronizedSetMultimap(HashMultimap.create());
}
private MutableContextSet(Multimap<String, String> contexts) {
this.map = Multimaps.synchronizedSetMultimap(HashMultimap.create(contexts));
private MutableContextSet(MutableContextSet other) {
this.map = Multimaps.synchronizedSetMultimap(HashMultimap.create(other.map));
}
@Override
@@ -170,21 +178,25 @@ public final class MutableContextSet implements ContextSet {
return false;
}
@Nonnull
@Override
public ImmutableContextSet makeImmutable() {
return new ImmutableContextSet(map);
return new ImmutableContextSet(ImmutableSetMultimap.copyOf(map));
}
@Nonnull
@Override
public MutableContextSet mutableCopy() {
return new MutableContextSet(map);
return new MutableContextSet(this);
}
@Nonnull
@Override
public Set<Map.Entry<String, String>> toSet() {
return ImmutableSet.copyOf(map.entries());
}
@Nonnull
@Override
public Map<String, String> toMap() {
ImmutableMap.Builder<String, String> m = ImmutableMap.builder();
@@ -195,57 +207,42 @@ public final class MutableContextSet implements ContextSet {
return m.build();
}
@Nonnull
@Override
public Multimap<String, String> toMultimap() {
return ImmutableSetMultimap.copyOf(map);
}
@Nonnull
@Override
public boolean containsKey(String key) {
if (key == null) {
throw new NullPointerException("key");
}
return map.containsKey(key);
public boolean containsKey(@Nonnull String key) {
return map.containsKey(checkNotNull(key, "key").toLowerCase().intern());
}
@Nonnull
@Override
public Set<String> getValues(String key) {
if (key == null) {
throw new NullPointerException("key");
}
Collection<String> c = map.get(key);
return c != null && !c.isEmpty() ? ImmutableSet.copyOf(c) : ImmutableSet.of();
public Set<String> getValues(@Nonnull String key) {
Collection<String> values = map.get(checkNotNull(key, "key").toLowerCase().intern());
return values != null ? ImmutableSet.copyOf(values) : ImmutableSet.of();
}
@Nonnull
@Override
public boolean has(String key, String value) {
if (key == null) {
throw new NullPointerException("key");
}
if (value == null) {
throw new NullPointerException("value");
}
return map.containsEntry(key, value);
public boolean has(@Nonnull String key, @Nonnull String value) {
return map.containsEntry(checkNotNull(key, "key").toLowerCase().intern(), checkNotNull(value, "value").intern());
}
@Nonnull
@Override
public boolean hasIgnoreCase(String key, String value) {
if (key == null) {
throw new NullPointerException("key");
}
if (value == null) {
throw new NullPointerException("value");
}
public boolean hasIgnoreCase(@Nonnull String key, @Nonnull String value) {
value = checkNotNull(value, "value").intern();
Collection<String> values = map.get(checkNotNull(key, "key").toLowerCase().intern());
Collection<String> c = map.get(key);
if (c == null || c.isEmpty()) {
if (values == null || values.isEmpty()) {
return false;
}
for (String val : c) {
for (String val : values) {
if (val.equalsIgnoreCase(value)) {
return true;
}
@@ -270,15 +267,8 @@ public final class MutableContextSet implements ContextSet {
* @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");
}
map.put(key.toLowerCase(), value);
public void add(@Nonnull String key, @Nonnull String value) {
map.put(checkNotNull(key, "key").toLowerCase().intern(), checkNotNull(value, "value").intern());
}
/**
@@ -287,12 +277,9 @@ public final class MutableContextSet implements ContextSet {
* @param entry the entry to add
* @throws NullPointerException if the entry is null
*/
public void add(Map.Entry<String, String> entry) {
if (entry == null) {
throw new NullPointerException("context");
}
map.put(entry.getKey().toLowerCase(), entry.getValue());
public void add(@Nonnull Map.Entry<String, String> entry) {
checkNotNull(entry, "entry");
add(entry.getKey(), entry.getValue());
}
/**
@@ -301,13 +288,9 @@ public final class MutableContextSet implements ContextSet {
* @param iterable an iterable of key value context pairs
* @throws NullPointerException if iterable is null
*/
public void addAll(Iterable<? extends Map.Entry<String, String>> iterable) {
if (iterable == null) {
throw new NullPointerException("iterable");
}
for (Map.Entry<String, String> e : iterable) {
this.map.put(e.getKey().toLowerCase(), e.getValue());
public void addAll(@Nonnull Iterable<? extends Map.Entry<String, String>> iterable) {
for (Map.Entry<String, String> e : checkNotNull(iterable, "iterable")) {
add(e);
}
}
@@ -317,11 +300,19 @@ public final class MutableContextSet implements ContextSet {
* @param map the map to add from
* @throws NullPointerException if the map is null
*/
public void addAll(Map<String, String> map) {
if (map == null) {
throw new NullPointerException("map");
}
addAll(map.entrySet());
public void addAll(@Nonnull Map<String, String> map) {
addAll(checkNotNull(map, "map").entrySet());
}
/**
* Adds the entries of a multimap to the set
*
* @param multimap the multimap to add from
* @throws NullPointerException if the map is null
* @since 3.4
*/
public void addAll(@Nonnull Multimap<String, String> multimap) {
addAll(checkNotNull(multimap, "multimap").entries());
}
/**
@@ -330,12 +321,14 @@ public final class MutableContextSet implements ContextSet {
* @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");
public void addAll(@Nonnull ContextSet contextSet) {
checkNotNull(contextSet, "contextSet");
if (contextSet instanceof MutableContextSet) {
MutableContextSet other = ((MutableContextSet) contextSet);
this.map.putAll(other.map);
} else {
addAll(contextSet.toMultimap());
}
this.map.putAll(contextSet.toMultimap());
}
/**
@@ -345,15 +338,12 @@ public final class MutableContextSet implements ContextSet {
* @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");
}
public void remove(@Nonnull String key, @Nonnull String value) {
String k = checkNotNull(key, "key").toLowerCase().intern();
String v = checkNotNull(value, "value").intern();
map.entries().removeIf(entry -> entry.getKey().equals(key) && entry.getValue().equals(value));
//noinspection StringEquality
map.entries().removeIf(entry -> entry.getKey() == k && entry.getValue() == v);
}
/**
@@ -363,15 +353,12 @@ public final class MutableContextSet implements ContextSet {
* @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");
}
public void removeIgnoreCase(@Nonnull String key, @Nonnull String value) {
String k = checkNotNull(key, "key").toLowerCase().intern();
String v = checkNotNull(value, "value").intern();
map.entries().removeIf(e -> e.getKey().equalsIgnoreCase(key) && e.getValue().equalsIgnoreCase(value));
//noinspection StringEquality
map.entries().removeIf(e -> e.getKey() == k && e.getValue().equalsIgnoreCase(v));
}
/**
@@ -380,12 +367,8 @@ public final class MutableContextSet implements ContextSet {
* @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");
}
map.removeAll(key.toLowerCase());
public void removeAll(@Nonnull String key) {
map.removeAll(checkNotNull(key, "key").toLowerCase());
}
/**
@@ -401,14 +384,20 @@ public final class MutableContextSet implements ContextSet {
if (!(o instanceof ContextSet)) return false;
final ContextSet other = (ContextSet) o;
final Multimap<String, String> thisContexts = this.toMultimap();
final Multimap<String, String> otherContexts = other.toMultimap();
return thisContexts == null ? otherContexts == null : thisContexts.equals(otherContexts);
final Multimap<String, String> otherContexts;
if (other instanceof MutableContextSet) {
otherContexts = ((MutableContextSet) other).map;
} else {
otherContexts = other.toMultimap();
}
return this.map.equals(otherContexts);
}
@Override
public int hashCode() {
return 59 + (this.map == null ? 43 : this.map.hashCode());
return map.hashCode();
}
@Override
@@ -0,0 +1,72 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.api.event.user;
import me.lucko.luckperms.api.User;
import me.lucko.luckperms.api.event.LuckPermsEvent;
import java.util.UUID;
import javax.annotation.Nonnull;
/**
* Called when LuckPerms has finished processing a certain Player's connection.
*
* <p>This event will always execute during the platforms async login/auth event.
* All handlers will be called instantly.</p>
*
* <p>This, among other things, allows you to wait until permission data is loaded
* for a User during the BungeeCord 'LoginEvent', as event priorities are ignored
* by the current implementation.</p>
*
* @since 3.4
*/
public interface UserLoginProcessEvent extends LuckPermsEvent {
/**
* Gets the UUID of the connection which was processed
*
* @return the uuid of the connection which was processed
*/
@Nonnull
UUID getUuid();
/**
* Gets the username of the connection which was processed
*
* @return the username of the connection which was processed
*/
@Nonnull
String getUsername();
/**
* Gets the resultant User instance which was loaded.
*
* @return the user instance
*/
User getUser();
}
@@ -26,8 +26,16 @@
package me.lucko.luckperms.exceptions;
/**
* Thrown when a permission holding object doesn't / already has a permission or isn't / is already is a member of a
* group
* Thrown when a certain membership state is / isn't met.
*
* For example, when:
* <p></p>
* <ul>
* <li>a permission holding object doesn't have a permission</li>
* <li>a permission holding object already has a permission</li>
* <li>a permission holding object is already a member of a group</li>
* <li>a permission holding object isn't already a member of a group</li>
* </ul>
*
* @since 2.7
*/
@@ -26,8 +26,15 @@
package me.lucko.luckperms.exceptions;
/**
* Thrown when a permission holding object already has a permission, is already a member of a group, or when a track
* already contains a group.
* Thrown when an object already has something.
*
* <p>For example, when:</p>
* <p></p>
* <ul>
* <li>a permission holding object already has a permission</li>
* <li>a permission holding object is already a member of a group</li>
* <li>a track already contains a group</li>
* </ul>
*/
public class ObjectAlreadyHasException extends MembershipException {
}
@@ -26,8 +26,15 @@
package me.lucko.luckperms.exceptions;
/**
* Thrown when a permission holding object does not already have a permission, is not already a member of a group,
* or when a track doesn't contain a group.
* Thrown when an object lacks something.
*
* <p>For example, when:</p>
* <p></p>
* <ul>
* <li>a permission holding object doesn't have a permission</li>
* <li>a permission holding object isn't already a member of a group</li>
* <li>a track doesn't contain a group</li>
* </ul>
*/
public class ObjectLacksException extends MembershipException {
}