Add node equality predicates, and provide way to determine hasPermission behaviour using them (#782)
This commit is contained in:
+27
-2
@@ -35,6 +35,7 @@ import me.lucko.luckperms.api.Contexts;
|
||||
import me.lucko.luckperms.api.DataMutateResult;
|
||||
import me.lucko.luckperms.api.LocalizedNode;
|
||||
import me.lucko.luckperms.api.Node;
|
||||
import me.lucko.luckperms.api.NodeEqualityPredicate;
|
||||
import me.lucko.luckperms.api.Tristate;
|
||||
import me.lucko.luckperms.api.caching.CachedData;
|
||||
import me.lucko.luckperms.api.context.ContextSet;
|
||||
@@ -158,18 +159,42 @@ public class ApiPermissionHolder implements me.lucko.luckperms.api.PermissionHol
|
||||
return ImmutableMap.copyOf(this.handle.exportNodesAndShorthand(contexts, lowerCase));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Tristate hasPermission(@Nonnull Node node, @Nonnull NodeEqualityPredicate equalityPredicate) {
|
||||
Objects.requireNonNull(node, "node");
|
||||
Objects.requireNonNull(equalityPredicate, "equalityPredicate");
|
||||
return this.handle.hasPermission(NodeMapType.ENDURING, node, equalityPredicate);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Tristate hasTransientPermission(@Nonnull Node node, @Nonnull NodeEqualityPredicate equalityPredicate) {
|
||||
Objects.requireNonNull(node, "node");
|
||||
Objects.requireNonNull(equalityPredicate, "equalityPredicate");
|
||||
return this.handle.hasPermission(NodeMapType.TRANSIENT, node, equalityPredicate);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Tristate inheritsPermission(@Nonnull Node node, @Nonnull NodeEqualityPredicate equalityPredicate) {
|
||||
Objects.requireNonNull(node, "node");
|
||||
Objects.requireNonNull(equalityPredicate, "equalityPredicate");
|
||||
return this.handle.inheritsPermission(node, equalityPredicate);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Tristate hasPermission(@Nonnull Node node) {
|
||||
Objects.requireNonNull(node, "node");
|
||||
return this.handle.hasPermission(node, NodeMapType.ENDURING);
|
||||
return this.handle.hasPermission(NodeMapType.ENDURING, node);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Tristate hasTransientPermission(@Nonnull Node node) {
|
||||
Objects.requireNonNull(node, "node");
|
||||
return this.handle.hasPermission(node, NodeMapType.TRANSIENT);
|
||||
return this.handle.hasPermission(NodeMapType.TRANSIENT, node);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
||||
+2
-1
@@ -25,6 +25,7 @@
|
||||
|
||||
package me.lucko.luckperms.common.commands.impl.generic.permission;
|
||||
|
||||
import me.lucko.luckperms.api.StandardNodeEquality;
|
||||
import me.lucko.luckperms.api.Tristate;
|
||||
import me.lucko.luckperms.api.context.MutableContextSet;
|
||||
import me.lucko.luckperms.common.commands.ArgumentPermissions;
|
||||
@@ -62,7 +63,7 @@ public class PermissionCheck extends SharedSubCommand {
|
||||
String node = ArgumentUtils.handleString(0, args);
|
||||
MutableContextSet context = ArgumentUtils.handleContext(1, args, plugin);
|
||||
|
||||
Tristate result = holder.hasPermission(NodeFactory.builder(node).withExtraContext(context).build());
|
||||
Tristate result = holder.hasPermission(NodeFactory.builder(node).withExtraContext(context).build(), StandardNodeEquality.IGNORE_VALUE_OR_IF_TEMPORARY);
|
||||
String s = CommandUtils.formatTristate(result);
|
||||
|
||||
Message.CHECK_PERMISSION.send(sender, holder.getFriendlyName(), node, s, CommandUtils.contextSetToString(context));
|
||||
|
||||
+2
-1
@@ -25,6 +25,7 @@
|
||||
|
||||
package me.lucko.luckperms.common.commands.impl.generic.permission;
|
||||
|
||||
import me.lucko.luckperms.api.StandardNodeEquality;
|
||||
import me.lucko.luckperms.api.context.MutableContextSet;
|
||||
import me.lucko.luckperms.common.commands.ArgumentPermissions;
|
||||
import me.lucko.luckperms.common.commands.CommandException;
|
||||
@@ -62,7 +63,7 @@ public class PermissionCheckInherits extends SharedSubCommand {
|
||||
String node = ArgumentUtils.handleString(0, args);
|
||||
MutableContextSet context = ArgumentUtils.handleContext(1, args, plugin);
|
||||
|
||||
InheritanceInfo result = holder.inheritsPermissionInfo(NodeFactory.builder(node).withExtraContext(context).build());
|
||||
InheritanceInfo result = holder.searchForInheritedMatch(NodeFactory.builder(node).withExtraContext(context).build(), StandardNodeEquality.IGNORE_VALUE_OR_IF_TEMPORARY);
|
||||
|
||||
String location = result.getLocation().orElse(null);
|
||||
if (location == null || location.equalsIgnoreCase(holder.getObjectName())) {
|
||||
|
||||
@@ -35,6 +35,8 @@ import me.lucko.luckperms.api.Contexts;
|
||||
import me.lucko.luckperms.api.DataMutateResult;
|
||||
import me.lucko.luckperms.api.LocalizedNode;
|
||||
import me.lucko.luckperms.api.Node;
|
||||
import me.lucko.luckperms.api.NodeEqualityPredicate;
|
||||
import me.lucko.luckperms.api.StandardNodeEquality;
|
||||
import me.lucko.luckperms.api.Tristate;
|
||||
import me.lucko.luckperms.api.context.ContextSet;
|
||||
import me.lucko.luckperms.api.context.ImmutableContextSet;
|
||||
@@ -578,44 +580,53 @@ public abstract class PermissionHolder {
|
||||
return transientWork || enduringWork;
|
||||
}
|
||||
|
||||
private Optional<Node> getAlmostEquals(Node node, NodeMapType type) {
|
||||
private Optional<Node> searchForMatch(NodeMapType type, Node node, NodeEqualityPredicate equalityPredicate) {
|
||||
for (Node n : getData(type).immutable().values()) {
|
||||
if (n.almostEquals(node)) {
|
||||
if (n.equals(node, equalityPredicate)) {
|
||||
return Optional.of(n);
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the holder has a permission node
|
||||
*
|
||||
* @param node the node to check
|
||||
* @param type which backing map to check
|
||||
* @return a tristate
|
||||
* @param node the node to check
|
||||
* @param equalityPredicate how to match
|
||||
* @return a tristate, returns undefined if no match
|
||||
*/
|
||||
public Tristate hasPermission(Node node, NodeMapType type) {
|
||||
public Tristate hasPermission(NodeMapType type, Node node, NodeEqualityPredicate equalityPredicate) {
|
||||
if (this.getType().isGroup() && node.isGroupNode() && node.getGroupName().equalsIgnoreCase(getObjectName())) {
|
||||
return Tristate.TRUE;
|
||||
}
|
||||
|
||||
return getAlmostEquals(node, type).map(Node::getTristate).orElse(Tristate.UNDEFINED);
|
||||
return searchForMatch(type, node, equalityPredicate).map(Node::getTristate).orElse(Tristate.UNDEFINED);
|
||||
}
|
||||
|
||||
public Tristate hasPermission(NodeMapType type, Node node) {
|
||||
return hasPermission(type, node, StandardNodeEquality.IGNORE_EXPIRY_TIME_AND_VALUE);
|
||||
}
|
||||
|
||||
public Tristate hasPermission(Node node, NodeEqualityPredicate equalityPredicate) {
|
||||
return hasPermission(NodeMapType.ENDURING, node, equalityPredicate);
|
||||
}
|
||||
|
||||
public Tristate hasPermission(Node node) {
|
||||
return hasPermission(node, NodeMapType.ENDURING);
|
||||
return hasPermission(NodeMapType.ENDURING, node, StandardNodeEquality.IGNORE_EXPIRY_TIME_AND_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the holder inherits a node
|
||||
*
|
||||
* @param node the node to check
|
||||
* @param equalityPredicate how to match
|
||||
* @return the result of the lookup
|
||||
*/
|
||||
public InheritanceInfo inheritsPermissionInfo(Node node) {
|
||||
public InheritanceInfo searchForInheritedMatch(Node node, NodeEqualityPredicate equalityPredicate) {
|
||||
for (LocalizedNode n : resolveInheritances()) {
|
||||
if (n.getNode().almostEquals(node)) {
|
||||
if (n.getNode().equals(node, equalityPredicate)) {
|
||||
return InheritanceInfo.of(n);
|
||||
}
|
||||
}
|
||||
@@ -627,10 +638,15 @@ public abstract class PermissionHolder {
|
||||
* Check if the holder inherits a node
|
||||
*
|
||||
* @param node the node to check
|
||||
* @param equalityPredicate how to match
|
||||
* @return the Tristate result
|
||||
*/
|
||||
public Tristate inheritsPermission(Node node, NodeEqualityPredicate equalityPredicate) {
|
||||
return searchForInheritedMatch(node, equalityPredicate).getResult();
|
||||
}
|
||||
|
||||
public Tristate inheritsPermission(Node node) {
|
||||
return inheritsPermissionInfo(node).getResult();
|
||||
return inheritsPermission(node, StandardNodeEquality.IGNORE_EXPIRY_TIME_AND_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -639,7 +655,7 @@ public abstract class PermissionHolder {
|
||||
* @param node the node to set
|
||||
*/
|
||||
public DataMutateResult setPermission(Node node) {
|
||||
if (hasPermission(node, NodeMapType.ENDURING) != Tristate.UNDEFINED) {
|
||||
if (hasPermission(NodeMapType.ENDURING, node) != Tristate.UNDEFINED) {
|
||||
return DataMutateResult.ALREADY_HAS;
|
||||
}
|
||||
|
||||
@@ -663,7 +679,7 @@ public abstract class PermissionHolder {
|
||||
if (node.isTemporary()) {
|
||||
if (modifier == TemporaryModifier.ACCUMULATE) {
|
||||
// Try to accumulate with an existing node
|
||||
Optional<Node> existing = getAlmostEquals(node, NodeMapType.ENDURING);
|
||||
Optional<Node> existing = searchForMatch(NodeMapType.ENDURING, node, StandardNodeEquality.IGNORE_EXPIRY_TIME_AND_VALUE);
|
||||
|
||||
// An existing node was found
|
||||
if (existing.isPresent()) {
|
||||
@@ -684,7 +700,7 @@ public abstract class PermissionHolder {
|
||||
|
||||
} else if (modifier == TemporaryModifier.REPLACE) {
|
||||
// Try to replace an existing node
|
||||
Optional<Node> existing = getAlmostEquals(node, NodeMapType.ENDURING);
|
||||
Optional<Node> existing = searchForMatch(NodeMapType.ENDURING, node, StandardNodeEquality.IGNORE_EXPIRY_TIME_AND_VALUE);
|
||||
|
||||
// An existing node was found
|
||||
if (existing.isPresent()) {
|
||||
@@ -717,7 +733,7 @@ public abstract class PermissionHolder {
|
||||
* @param node the node to set
|
||||
*/
|
||||
public DataMutateResult setTransientPermission(Node node) {
|
||||
if (hasPermission(node, NodeMapType.TRANSIENT) != Tristate.UNDEFINED) {
|
||||
if (hasPermission(NodeMapType.TRANSIENT, node) != Tristate.UNDEFINED) {
|
||||
return DataMutateResult.ALREADY_HAS;
|
||||
}
|
||||
|
||||
@@ -732,7 +748,7 @@ public abstract class PermissionHolder {
|
||||
* @param node the node to unset
|
||||
*/
|
||||
public DataMutateResult unsetPermission(Node node) {
|
||||
if (hasPermission(node, NodeMapType.ENDURING) == Tristate.UNDEFINED) {
|
||||
if (hasPermission(NodeMapType.ENDURING, node) == Tristate.UNDEFINED) {
|
||||
return DataMutateResult.LACKS;
|
||||
}
|
||||
|
||||
@@ -751,7 +767,7 @@ public abstract class PermissionHolder {
|
||||
* @param node the node to unset
|
||||
*/
|
||||
public DataMutateResult unsetTransientPermission(Node node) {
|
||||
if (hasPermission(node, NodeMapType.TRANSIENT) == Tristate.UNDEFINED) {
|
||||
if (hasPermission(NodeMapType.TRANSIENT, node) == Tristate.UNDEFINED) {
|
||||
return DataMutateResult.LACKS;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
package me.lucko.luckperms.common.node;
|
||||
|
||||
import me.lucko.luckperms.api.Node;
|
||||
import me.lucko.luckperms.api.StandardNodeEquality;
|
||||
import me.lucko.luckperms.api.Tristate;
|
||||
import me.lucko.luckperms.api.context.ContextSet;
|
||||
|
||||
@@ -224,18 +225,8 @@ public abstract class ForwardingNode implements Node {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equalsIgnoringValue(@Nonnull Node other) {
|
||||
return delegate().equalsIgnoringValue(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean almostEquals(@Nonnull Node other) {
|
||||
return delegate().almostEquals(other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equalsIgnoringValueOrTemp(@Nonnull Node other) {
|
||||
return delegate().equalsIgnoringValueOrTemp(other);
|
||||
public boolean standardEquals(Node other, StandardNodeEquality equalityPredicate) {
|
||||
return delegate().standardEquals(other, equalityPredicate);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.google.common.collect.ImmutableList;
|
||||
|
||||
import me.lucko.luckperms.api.Contexts;
|
||||
import me.lucko.luckperms.api.Node;
|
||||
import me.lucko.luckperms.api.StandardNodeEquality;
|
||||
import me.lucko.luckperms.api.context.ContextSet;
|
||||
import me.lucko.luckperms.api.context.ImmutableContextSet;
|
||||
import me.lucko.luckperms.api.context.MutableContextSet;
|
||||
@@ -320,27 +321,41 @@ public final class ImmutableNode implements Node {
|
||||
return this.resolvedShorthand;
|
||||
}
|
||||
|
||||
@SuppressWarnings("StringEquality")
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) return true;
|
||||
if (!(o instanceof Node)) return false;
|
||||
final Node other = (Node) o;
|
||||
|
||||
if (this.permission != other.getPermission()) return false;
|
||||
if (this.value != other.getValuePrimitive()) return false;
|
||||
if (this.override != other.isOverride()) return false;
|
||||
Node other = (Node) o;
|
||||
while (other instanceof ForwardingNode) {
|
||||
other = ((ForwardingNode) other).delegate();
|
||||
}
|
||||
return other instanceof ImmutableNode && Equality.EXACT.areEqual(this, (ImmutableNode) other);
|
||||
}
|
||||
|
||||
final String thisServer = this.server;
|
||||
final String otherServer = other.getServer().orElse(null);
|
||||
if (thisServer == null ? otherServer != null : !thisServer.equals(otherServer)) return false;
|
||||
|
||||
final String thisWorld = this.world;
|
||||
final String otherWorld = other.getWorld().orElse(null);
|
||||
if (thisWorld == null ? otherWorld != null : !thisWorld.equals(otherWorld)) return false;
|
||||
|
||||
final long otherExpireAt = other.isTemporary() ? other.getExpiryUnixTime() : 0L;
|
||||
return this.expireAt == otherExpireAt && this.getContexts().equals(other.getContexts());
|
||||
@Override
|
||||
public boolean standardEquals(Node o, StandardNodeEquality equalityPredicate) {
|
||||
while (o instanceof ForwardingNode) {
|
||||
o = ((ForwardingNode) o).delegate();
|
||||
}
|
||||
if (!(o instanceof ImmutableNode)) {
|
||||
return false;
|
||||
}
|
||||
ImmutableNode other = (ImmutableNode) o;
|
||||
switch (equalityPredicate) {
|
||||
case EXACT:
|
||||
return Equality.EXACT.areEqual(this, other);
|
||||
case IGNORE_VALUE:
|
||||
return Equality.IGNORE_VALUE.areEqual(this, other);
|
||||
case IGNORE_EXPIRY_TIME:
|
||||
return Equality.IGNORE_EXPIRY_TIME.areEqual(this, other);
|
||||
case IGNORE_EXPIRY_TIME_AND_VALUE:
|
||||
return Equality.IGNORE_EXPIRY_TIME_AND_VALUE.areEqual(this, other);
|
||||
case IGNORE_VALUE_OR_IF_TEMPORARY:
|
||||
return Equality.IGNORE_VALUE_OR_IF_TEMPORARY.areEqual(this, other);
|
||||
default:
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -362,57 +377,70 @@ public final class ImmutableNode implements Node {
|
||||
}
|
||||
|
||||
@SuppressWarnings("StringEquality")
|
||||
@Override
|
||||
public boolean equalsIgnoringValue(@Nonnull Node other) {
|
||||
if (this.permission != other.getPermission()) return false;
|
||||
if (this.override != other.isOverride()) return false;
|
||||
private enum Equality {
|
||||
EXACT {
|
||||
@Override
|
||||
public boolean areEqual(@Nonnull ImmutableNode o1, @Nonnull ImmutableNode o2) {
|
||||
return o1 == o2 ||
|
||||
o1.permission == o2.permission &&
|
||||
o1.value == o2.value &&
|
||||
o1.override == o2.override &&
|
||||
(o1.server == null ? o2.server == null : o1.server.equals(o2.server)) &&
|
||||
(o1.world == null ? o2.world == null : o1.world.equals(o2.world)) &&
|
||||
o1.expireAt == o2.expireAt &&
|
||||
o1.getContexts().equals(o2.getContexts());
|
||||
}
|
||||
},
|
||||
IGNORE_VALUE {
|
||||
@Override
|
||||
public boolean areEqual(@Nonnull ImmutableNode o1, @Nonnull ImmutableNode o2) {
|
||||
return o1 == o2 ||
|
||||
o1.permission == o2.permission &&
|
||||
o1.override == o2.override &&
|
||||
(o1.server == null ? o2.server == null : o1.server.equals(o2.server)) &&
|
||||
(o1.world == null ? o2.world == null : o1.world.equals(o2.world)) &&
|
||||
o1.expireAt == o2.expireAt &&
|
||||
o1.getContexts().equals(o2.getContexts());
|
||||
}
|
||||
},
|
||||
IGNORE_EXPIRY_TIME {
|
||||
@Override
|
||||
public boolean areEqual(@Nonnull ImmutableNode o1, @Nonnull ImmutableNode o2) {
|
||||
return o1 == o2 ||
|
||||
o1.permission == o2.permission &&
|
||||
o1.value == o2.value &&
|
||||
o1.override == o2.override &&
|
||||
(o1.server == null ? o2.server == null : o1.server.equals(o2.server)) &&
|
||||
(o1.world == null ? o2.world == null : o1.world.equals(o2.world)) &&
|
||||
o1.isTemporary() == o2.isTemporary() &&
|
||||
o1.getContexts().equals(o2.getContexts());
|
||||
}
|
||||
},
|
||||
IGNORE_EXPIRY_TIME_AND_VALUE {
|
||||
@Override
|
||||
public boolean areEqual(@Nonnull ImmutableNode o1, @Nonnull ImmutableNode o2) {
|
||||
return o1 == o2 ||
|
||||
o1.permission == o2.permission &&
|
||||
o1.override == o2.override &&
|
||||
(o1.server == null ? o2.server == null : o1.server.equals(o2.server)) &&
|
||||
(o1.world == null ? o2.world == null : o1.world.equals(o2.world)) &&
|
||||
o1.isTemporary() == o2.isTemporary() &&
|
||||
o1.getContexts().equals(o2.getContexts());
|
||||
}
|
||||
},
|
||||
IGNORE_VALUE_OR_IF_TEMPORARY {
|
||||
@Override
|
||||
public boolean areEqual(@Nonnull ImmutableNode o1, @Nonnull ImmutableNode o2) {
|
||||
return o1 == o2 ||
|
||||
o1.permission == o2.permission &&
|
||||
o1.override == o2.override &&
|
||||
(o1.server == null ? o2.server == null : o1.server.equals(o2.server)) &&
|
||||
(o1.world == null ? o2.world == null : o1.world.equals(o2.world)) &&
|
||||
o1.getContexts().equals(o2.getContexts());
|
||||
}
|
||||
};
|
||||
|
||||
final String thisServer = this.server;
|
||||
final String otherServer = other.getServer().orElse(null);
|
||||
if (thisServer == null ? otherServer != null : !thisServer.equals(otherServer)) return false;
|
||||
|
||||
final String thisWorld = this.world;
|
||||
final String otherWorld = other.getWorld().orElse(null);
|
||||
if (thisWorld == null ? otherWorld != null : !thisWorld.equals(otherWorld)) return false;
|
||||
|
||||
final long otherExpireAt = other.isTemporary() ? other.getExpiryUnixTime() : 0L;
|
||||
return this.expireAt == otherExpireAt && this.getContexts().equals(other.getContexts());
|
||||
}
|
||||
|
||||
@SuppressWarnings("StringEquality")
|
||||
@Override
|
||||
public boolean almostEquals(@Nonnull Node other) {
|
||||
if (this.permission != other.getPermission()) return false;
|
||||
if (this.override != other.isOverride()) return false;
|
||||
|
||||
final String thisServer = this.server;
|
||||
final String otherServer = other.getServer().orElse(null);
|
||||
if (thisServer == null ? otherServer != null : !thisServer.equals(otherServer))
|
||||
return false;
|
||||
|
||||
final String thisWorld = this.world;
|
||||
final String otherWorld = other.getWorld().orElse(null);
|
||||
return (thisWorld == null ? otherWorld == null : thisWorld.equals(otherWorld)) &&
|
||||
this.isTemporary() == other.isTemporary() &&
|
||||
this.getContexts().equals(other.getContexts());
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("StringEquality")
|
||||
@Override
|
||||
public boolean equalsIgnoringValueOrTemp(@Nonnull Node other) {
|
||||
if (this.permission != other.getPermission()) return false;
|
||||
if (this.override != other.isOverride()) return false;
|
||||
|
||||
final String thisServer = this.server;
|
||||
final String otherServer = other.getServer().orElse(null);
|
||||
if (thisServer == null ? otherServer != null : !thisServer.equals(otherServer))
|
||||
return false;
|
||||
|
||||
final String thisWorld = this.world;
|
||||
final String otherWorld = other.getWorld().orElse(null);
|
||||
return (thisWorld == null ? otherWorld == null : thisWorld.equals(otherWorld)) &&
|
||||
this.getContexts().equals(other.getContexts());
|
||||
public abstract boolean areEqual(@Nonnull ImmutableNode o1, @Nonnull ImmutableNode o2);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -443,6 +471,13 @@ public final class ImmutableNode implements Node {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ImmutableNode(permission=" + this.permission + ", value=" + this.value + ", override=" + this.override + ", server=" + this.getServer() + ", world=" + this.getWorld() + ", expireAt=" + this.expireAt + ", contexts=" + this.contexts + ")";
|
||||
return "ImmutableNode(" +
|
||||
"permission=" + this.permission + ", " +
|
||||
"value=" + this.value + ", " +
|
||||
"override=" + this.override + ", " +
|
||||
"server=" + this.getServer() + ", " +
|
||||
"world=" + this.getWorld() + ", " +
|
||||
"expireAt=" + this.expireAt + ", " +
|
||||
"contexts=" + this.contexts + ")";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user