Allow display names to be set in specific contexts (#963)

This commit is contained in:
Luck 2018-05-02 21:27:21 +01:00
parent 58bd7de66a
commit f556c75d94
No known key found for this signature in database
GPG Key ID: EFA9B3EC5FD90F8B
13 changed files with 236 additions and 72 deletions

View File

@ -30,6 +30,7 @@ import com.google.common.base.Preconditions;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.nodetype.NodeType;
import me.lucko.luckperms.api.nodetype.NodeTypeKey;
import me.lucko.luckperms.api.nodetype.types.DisplayNameType;
import me.lucko.luckperms.api.nodetype.types.InheritanceType;
import me.lucko.luckperms.api.nodetype.types.MetaType;
import me.lucko.luckperms.api.nodetype.types.PrefixType;
@ -95,6 +96,7 @@ import javax.annotation.concurrent.Immutable;
* <li>{@link SuffixType} - represents an assigned suffix</li>
* <li>{@link MetaType} - represents an assigned meta option</li>
* <li>{@link WeightType} - marks the weight of the object holding this node</li>
* <li>{@link DisplayNameType} - marks the display name of the object holding this node</li>
* </ul>
*
* <p>The core node state must be immutable in all implementations.</p>

View File

@ -0,0 +1,54 @@
/*
* 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.nodetype.types;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.nodetype.NodeType;
import me.lucko.luckperms.api.nodetype.NodeTypeKey;
import javax.annotation.Nonnull;
/**
* A sub-type of {@link Node} used to mark the display name of the node's holder.
*
* @since 4.2
*/
public interface DisplayNameType extends NodeType {
/**
* The key for this type.
*/
NodeTypeKey<DisplayNameType> KEY = new NodeTypeKey<DisplayNameType>(){};
/**
* Gets the display name.
*
* @return the display name
*/
@Nonnull
String getDisplayName();
}

View File

@ -26,63 +26,42 @@
package me.lucko.luckperms.common.buffers;
import java.util.Optional;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.annotation.Nonnull;
/**
* Thread-safe caching utility
* Simple one element cache implementation.
*
* @param <T> the type being stored
*/
public abstract class Cache<T> {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private T cached = null;
private volatile T value = null;
@Nonnull
protected abstract T supply();
public final T get() {
// try to just read from the cached value
this.lock.readLock().lock();
try {
if (this.cached != null) {
return this.cached;
T val = this.value;
// double checked locking
if (val == null) {
synchronized (this) {
val = this.value;
if (val == null) {
val = supply();
this.value = val;
}
}
} finally {
// we have to release the read lock, as it is not possible
// to acquire the write lock whilst holding a read lock
this.lock.readLock().unlock();
}
this.lock.writeLock().lock();
try {
// Since the lock was unlocked momentarily, we need
// to check again for a cached value
if (this.cached != null) {
return this.cached;
}
// call the supplier and set the cached value
this.cached = supply();
return this.cached;
} finally {
this.lock.writeLock().unlock();
}
return val;
}
public final Optional<T> getIfPresent() {
this.lock.readLock().lock();
try {
return Optional.ofNullable(this.cached);
} finally {
this.lock.readLock().unlock();
}
return Optional.ofNullable(this.value);
}
public final void invalidate() {
this.lock.writeLock().lock();
try {
this.cached = null;
} finally {
this.lock.writeLock().unlock();
}
this.value = null;
}
}

View File

@ -25,12 +25,16 @@
package me.lucko.luckperms.common.commands.group;
import me.lucko.luckperms.api.context.MutableContextSet;
import me.lucko.luckperms.api.nodetype.types.DisplayNameType;
import me.lucko.luckperms.common.actionlog.ExtendedLogEntry;
import me.lucko.luckperms.common.command.CommandResult;
import me.lucko.luckperms.common.command.abstraction.CommandException;
import me.lucko.luckperms.common.command.abstraction.SubCommand;
import me.lucko.luckperms.common.command.access.ArgumentPermissions;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.command.utils.ArgumentParser;
import me.lucko.luckperms.common.command.utils.MessageUtils;
import me.lucko.luckperms.common.command.utils.StorageAssistant;
import me.lucko.luckperms.common.locale.LocaleManager;
import me.lucko.luckperms.common.locale.command.CommandSpec;
@ -45,18 +49,20 @@ import java.util.List;
public class GroupSetDisplayName extends SubCommand<Group> {
public GroupSetDisplayName(LocaleManager locale) {
super(CommandSpec.GROUP_SET_DISPLAY_NAME.localize(locale), "setdisplayname", CommandPermission.GROUP_SET_DISPLAY_NAME, Predicates.not(1));
super(CommandSpec.GROUP_SET_DISPLAY_NAME.localize(locale), "setdisplayname", CommandPermission.GROUP_SET_DISPLAY_NAME, Predicates.is(0));
}
@Override
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Group group, List<String> args, String label) {
public CommandResult execute(LuckPermsPlugin plugin, Sender sender, Group group, List<String> args, String label) throws CommandException {
if (ArgumentPermissions.checkModifyPerms(plugin, sender, getPermission().get(), group)) {
Message.COMMAND_NO_PERMISSION.send(sender);
return CommandResult.NO_PERMISSION;
}
String name = ArgumentParser.parseString(0, args);
String previousName = group.getDisplayName().orElse(null);
MutableContextSet context = ArgumentParser.parseContext(1, args, plugin);
String previousName = group.getDisplayName(context).orElse(null);
if (previousName == null && name.equals(group.getName())) {
Message.GROUP_SET_DISPLAY_NAME_DOESNT_HAVE.send(sender, group.getName());
@ -74,25 +80,25 @@ public class GroupSetDisplayName extends SubCommand<Group> {
return CommandResult.STATE_ERROR;
}
group.removeIf(n -> n.getPermission().startsWith("displayname."));
group.removeIf(context, n -> n.getTypeData(DisplayNameType.KEY).isPresent());
if (name.equals(group.getName())) {
Message.GROUP_SET_DISPLAY_NAME_REMOVED.send(sender, group.getName());
Message.GROUP_SET_DISPLAY_NAME_REMOVED.send(sender, group.getName(), MessageUtils.contextSetToString(context));
ExtendedLogEntry.build().actor(sender).acted(group)
.action("setdisplayname", name)
.action("setdisplayname", name, context)
.build().submit(plugin, sender);
StorageAssistant.save(group, sender, plugin);
return CommandResult.SUCCESS;
}
group.setPermission(NodeFactory.builder("displayname." + name).build());
group.setPermission(NodeFactory.builder("displayname." + name).withExtraContext(context).build());
Message.GROUP_SET_DISPLAY_NAME.send(sender, name, group.getName());
Message.GROUP_SET_DISPLAY_NAME.send(sender, name, group.getName(), MessageUtils.contextSetToString(context));
ExtendedLogEntry.build().actor(sender).acted(group)
.action("setdisplayname", name)
.action("setdisplayname", name, context)
.build().submit(plugin, sender);
StorageAssistant.save(group, sender, plugin);

View File

@ -169,7 +169,8 @@ public enum CommandSpec {
),
GROUP_SET_DISPLAY_NAME("Set the groups display name",
Argument.list(
Argument.create("name", true, "the name to set")
Argument.create("name", true, "the name to set"),
Argument.create("context...", false, "the contexts to set the name in")
)
),
GROUP_RENAME("Rename the group",

View File

@ -364,8 +364,8 @@ public enum Message {
GROUP_SET_DISPLAY_NAME_DOESNT_HAVE("&b{}&a doesn't have a display name set.", true),
GROUP_SET_DISPLAY_NAME_ALREADY_HAS("&b{}&a already has a display name of &b{}&a.", true),
GROUP_SET_DISPLAY_NAME_ALREADY_IN_USE("&aThe display name &b{}&a is already being used by &b{}&a.", true),
GROUP_SET_DISPLAY_NAME("&aSet display name to &b{}&a for group &b{}&a.", true),
GROUP_SET_DISPLAY_NAME_REMOVED("&aRemoved display name for group &b{}&a.", true),
GROUP_SET_DISPLAY_NAME("&aSet display name to &b{}&a for group &b{}&a in context {}&a.", true),
GROUP_SET_DISPLAY_NAME_REMOVED("&aRemoved display name for group &b{}&a in context {}&a.", true),
TRACK_INFO(
"{PREFIX}&b&l> &bShowing Track: &f{}" + "\n" +

View File

@ -28,6 +28,8 @@ package me.lucko.luckperms.common.managers.group;
import me.lucko.luckperms.common.managers.AbstractManager;
import me.lucko.luckperms.common.model.Group;
import java.util.Optional;
public abstract class AbstractGroupManager<T extends Group> extends AbstractManager<String, Group, T> implements GroupManager<T> {
@Override
@ -40,14 +42,16 @@ public abstract class AbstractGroupManager<T extends Group> extends AbstractMana
// then try exact display name matches
for (T group : getAll().values()) {
if (group.getDisplayName().isPresent() && group.getDisplayName().get().equals(name)) {
Optional<String> displayName = group.getDisplayName();
if (displayName.isPresent() && displayName.get().equals(name)) {
return group;
}
}
// then try case insensitive name matches
for (T group : getAll().values()) {
if (group.getDisplayName().isPresent() && group.getDisplayName().get().equalsIgnoreCase(name)) {
Optional<String> displayName = group.getDisplayName();
if (displayName.isPresent() && displayName.get().equalsIgnoreCase(name)) {
return group;
}
}

View File

@ -0,0 +1,62 @@
/*
* 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.common.model;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.nodetype.types.DisplayNameType;
import me.lucko.luckperms.common.buffers.Cache;
import me.lucko.luckperms.common.config.ConfigKeys;
import java.util.Optional;
import javax.annotation.Nonnull;
/**
* Cache instance to supply the display name of a {@link Group}.
*/
public class DisplayNameCache extends Cache<Optional<String>> {
private final Group group;
public DisplayNameCache(Group group) {
this.group = group;
}
@Nonnull
@Override
protected Optional<String> supply() {
// query for a displayname node
for (Node n : this.group.getOwnNodes(this.group.getPlugin().getContextManager().getStaticContext())) {
Optional<DisplayNameType> displayName = n.getTypeData(DisplayNameType.KEY);
if (displayName.isPresent()) {
return Optional.of(displayName.get().getDisplayName());
}
}
// fallback to config
String name = this.group.getPlugin().getConfiguration().get(ConfigKeys.GROUP_NAME_REWRITES).get(this.group.getObjectName());
return name == null || name.equals(this.group.getObjectName()) ? Optional.empty() : Optional.of(name);
}
}

View File

@ -26,12 +26,12 @@
package me.lucko.luckperms.common.model;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.api.nodetype.types.DisplayNameType;
import me.lucko.luckperms.common.api.delegates.model.ApiGroup;
import me.lucko.luckperms.common.buffers.BufferedRequest;
import me.lucko.luckperms.common.buffers.Cache;
import me.lucko.luckperms.common.caching.GroupCachedData;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import java.util.Optional;
@ -47,11 +47,15 @@ public class Group extends PermissionHolder implements Identifiable<String> {
private final String name;
/**
* Caches the holders weight
* @see #getWeight()
* Caches the groups weight
*/
private final Cache<OptionalInt> weightCache = new WeightCache(this);
/**
* Caches the groups display name
*/
private final Cache<Optional<String>> displayNameCache = new DisplayNameCache(this);
/**
* The groups data cache instance
*/
@ -77,6 +81,7 @@ public class Group extends PermissionHolder implements Identifiable<String> {
@Override
protected void invalidateCache() {
this.weightCache.invalidate();
this.displayNameCache.invalidate();
super.invalidateCache();
}
@ -104,22 +109,17 @@ public class Group extends PermissionHolder implements Identifiable<String> {
}
public Optional<String> getDisplayName() {
String name = null;
for (Node n : enduringData().immutable().get(ImmutableContextSet.empty())) {
if (!n.getPermission().startsWith("displayname.")) {
continue;
return this.displayNameCache.get();
}
public Optional<String> getDisplayName(ContextSet contextSet) {
for (Node n : getData(NodeMapType.ENDURING).immutable().get(contextSet.makeImmutable())) {
Optional<DisplayNameType> displayName = n.getTypeData(DisplayNameType.KEY);
if (displayName.isPresent()) {
return Optional.of(displayName.get().getDisplayName());
}
name = n.getPermission().substring("displayname.".length());
break;
}
if (name != null) {
return Optional.of(name);
}
name = getPlugin().getConfiguration().get(ConfigKeys.GROUP_NAME_REWRITES).get(getObjectName());
return name == null || name.equals(getObjectName()) ? Optional.empty() : Optional.of(name);
return Optional.empty();
}
@Override

View File

@ -53,6 +53,7 @@ import java.util.TreeSet;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
@ -363,6 +364,7 @@ public final class NodeMap {
this.handle = handle;
}
@Nonnull
@Override
protected ImmutableSetMultimap<ImmutableContextSet, Node> supply() {
this.handle.lock.lock();

View File

@ -35,8 +35,10 @@ import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
import javax.annotation.Nonnull;
/**
* Cache instance to supply the weight of a {@link PermissionHolder}.
* Cache instance to supply the weight of a {@link Group}.
*/
public class WeightCache extends Cache<OptionalInt> {
private final Group group;
@ -45,6 +47,7 @@ public class WeightCache extends Cache<OptionalInt> {
this.group = group;
}
@Nonnull
@Override
protected OptionalInt supply() {
boolean seen = false;

View File

@ -30,6 +30,7 @@ import com.google.common.collect.ImmutableMap;
import me.lucko.luckperms.api.nodetype.NodeType;
import me.lucko.luckperms.api.nodetype.NodeTypeKey;
import me.lucko.luckperms.api.nodetype.types.DisplayNameType;
import me.lucko.luckperms.api.nodetype.types.InheritanceType;
import me.lucko.luckperms.api.nodetype.types.MetaType;
import me.lucko.luckperms.api.nodetype.types.PrefixType;
@ -51,12 +52,14 @@ public final class NodeTypes {
public static final String SUFFIX_KEY = "suffix";
public static final String META_KEY = "meta";
public static final String WEIGHT_KEY = "weight";
public static final String DISPLAY_NAME_KEY = "displayname";
public static final String GROUP_NODE_MARKER = "group.";
public static final String PREFIX_NODE_MARKER = PREFIX_KEY + ".";
public static final String SUFFIX_NODE_MARKER = SUFFIX_KEY + ".";
public static final String META_NODE_MARKER = META_KEY + ".";
public static final String WEIGHT_NODE_MARKER = WEIGHT_KEY + ".";
public static final String DISPLAY_NAME_NODE_MARKER = DISPLAY_NAME_KEY + ".";
// used to split prefix/suffix/meta nodes
private static final Splitter META_SPLITTER = Splitter.on(PatternCache.compileDelimiterPattern(".", "\\")).limit(2);
@ -89,6 +92,11 @@ public final class NodeTypes {
results.put(WeightType.KEY, type);
}
type = parseDisplayNameType(s);
if (type != null) {
results.put(DisplayNameType.KEY, type);
}
if (results.isEmpty()) {
return ImmutableMap.of();
}
@ -182,6 +190,14 @@ public final class NodeTypes {
}
}
private static DisplayNameType parseDisplayNameType(String s) {
if (!s.toLowerCase().startsWith(DISPLAY_NAME_NODE_MARKER)) {
return null;
}
return new DisplayName(s.substring(DISPLAY_NAME_NODE_MARKER.length()));
}
private static final class Inheritance implements InheritanceType {
private final String groupName;
@ -408,6 +424,38 @@ public final class NodeTypes {
}
}
private static final class DisplayName implements DisplayNameType {
private final String displayName;
private DisplayName(String displayName) {
this.displayName = displayName;
}
@Nonnull
@Override
public String getDisplayName() {
return this.displayName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DisplayName that = (DisplayName) o;
return Objects.equals(this.displayName, that.displayName);
}
@Override
public int hashCode() {
return Objects.hash(this.displayName);
}
@Override
public String toString() {
return "DisplayName{displayName='" + this.displayName + '\'' + '}';
}
}
private NodeTypes() {}
}

View File

@ -30,6 +30,8 @@ import me.lucko.luckperms.common.caching.handlers.StateListener;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.factory.NodeFactory;
import javax.annotation.Nonnull;
/**
* Abstract implementation of {@link StateListener} which caches all lookups.
*/
@ -37,6 +39,7 @@ public abstract class CachedPrimaryGroupHolder extends StoredHolder implements S
// cache lookups
private final Cache<String> cache = new Cache<String>() {
@Nonnull
@Override
protected String supply() {
return calculateValue();