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
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>luckperms</artifactId>
<groupId>me.lucko.luckperms</groupId>
<version>3.3-SNAPSHOT</version>
<version>3.4-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -31,18 +31,23 @@ import me.lucko.luckperms.api.LuckPermsApi;
import java.lang.reflect.Method;
public class ApiHandler {
private static Method REGISTER;
private static Method UNREGISTER;
private static final Method REGISTER;
private static final Method UNREGISTER;
static {
Method register = null;
Method unregister = null;
try {
REGISTER = LuckPerms.class.getDeclaredMethod("registerProvider", LuckPermsApi.class);
REGISTER.setAccessible(true);
register = LuckPerms.class.getDeclaredMethod("registerProvider", LuckPermsApi.class);
register.setAccessible(true);
UNREGISTER = LuckPerms.class.getDeclaredMethod("unregisterProvider");
UNREGISTER.setAccessible(true);
unregister = LuckPerms.class.getDeclaredMethod("unregisterProvider");
unregister.setAccessible(true);
} catch (Exception e) {
e.printStackTrace();
}
REGISTER = register;
UNREGISTER = unregister;
}
public static void registerProvider(LuckPermsApi luckPermsApi) {
@@ -88,7 +88,7 @@ public class ApiProvider implements LuckPermsApi {
@Override
public double getApiVersion() {
return 3.3;
return 3.4;
}
@Override
@@ -33,6 +33,7 @@ import me.lucko.luckperms.api.caching.PermissionData;
import me.lucko.luckperms.common.calculators.CalculatorFactory;
import me.lucko.luckperms.common.calculators.PermissionCalculator;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.verbose.CheckOrigin;
import java.util.Collections;
import java.util.Map;
@@ -93,6 +94,10 @@ public class PermissionCache implements PermissionData {
@Override
public Tristate getPermissionValue(@NonNull String permission) {
return calculator.getPermissionValue(permission);
return calculator.getPermissionValue(permission, CheckOrigin.API);
}
public Tristate getPermissionValue(@NonNull String permission, CheckOrigin origin) {
return calculator.getPermissionValue(permission, origin);
}
}
@@ -36,7 +36,6 @@ import com.google.common.collect.ImmutableSet;
import me.lucko.luckperms.api.ChatMetaType;
import me.lucko.luckperms.api.Contexts;
import me.lucko.luckperms.api.caching.MetaContexts;
import me.lucko.luckperms.api.caching.MetaData;
import me.lucko.luckperms.api.caching.PermissionData;
import me.lucko.luckperms.api.caching.UserData;
import me.lucko.luckperms.common.config.ConfigKeys;
@@ -68,17 +67,17 @@ public class UserCache implements UserData {
.build(new MetaCacheLoader());
@Override
public PermissionData getPermissionData(@NonNull Contexts contexts) {
public PermissionCache getPermissionData(@NonNull Contexts contexts) {
return permission.get(contexts);
}
@Override
public MetaData getMetaData(@NonNull MetaContexts contexts) {
public MetaCache getMetaData(@NonNull MetaContexts contexts) {
return meta.get(contexts);
}
@Override
public MetaData getMetaData(@NonNull Contexts contexts) {
public MetaCache getMetaData(@NonNull Contexts contexts) {
// just create a MetaContexts instance using the values in the config
return getMetaData(makeFromMetaContextsConfig(contexts, user.getPlugin()));
}
@@ -33,6 +33,7 @@ import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.processors.PermissionProcessor;
import me.lucko.luckperms.common.verbose.CheckOrigin;
import java.util.List;
import java.util.Map;
@@ -43,7 +44,7 @@ import java.util.Map;
@RequiredArgsConstructor
public class PermissionCalculator {
private final LuckPermsPlugin plugin;
private final String objectName;
private final PermissionCalculatorMetadata metadata;
private final List<PermissionProcessor> processors;
// caches lookup calls.
@@ -54,7 +55,7 @@ public class PermissionCalculator {
lookupCache.invalidateAll();
}
public Tristate getPermissionValue(String permission) {
public Tristate getPermissionValue(String permission, CheckOrigin origin) {
// convert the permission to lowercase, as all values in the backing map are also lowercase.
// this allows fast case insensitive lookups
@@ -64,7 +65,7 @@ public class PermissionCalculator {
Tristate result = lookupCache.get(permission);
// log this permission lookup to the verbose handler
plugin.getVerboseHandler().offerCheckData(objectName, permission, result);
plugin.getVerboseHandler().offerCheckData(origin, metadata.getObjectName(), metadata.getContext(), permission, result);
// return the result
return result;
@@ -0,0 +1,47 @@
/*
* 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.calculators;
import lombok.AllArgsConstructor;
import lombok.Getter;
import me.lucko.luckperms.api.context.ContextSet;
@Getter
@AllArgsConstructor(staticName = "of")
public class PermissionCalculatorMetadata {
/**
* The name of the object which owns the permission calculator
*/
private final String objectName;
/**
* The context the permission calculator works with
*/
private final ContextSet context;
}
@@ -173,7 +173,7 @@ public class PermissionInfo extends SharedSubCommand {
}
for (Node node : page) {
String s = "&3> " + (node.getValue() ? "&a" : "&c") + node.getPermission() + (console ? " &7(" + node.getValue() + "&7)" : "") + Util.getAppendableNodeContextString(node) + "\n";
String s = "&3> " + (node.getValuePrimitive() ? "&a" : "&c") + node.getPermission() + (console ? " &7(" + node.getValuePrimitive() + "&7)" : "") + Util.getAppendableNodeContextString(node) + "\n";
if (temp) {
s += "&2- expires in " + DateUtil.formatDateDiff(node.getExpiryUnixTime()) + "\n";
}
@@ -186,7 +186,7 @@ public class PermissionInfo extends SharedSubCommand {
private static Consumer<BuildableComponent.Builder<?, ?>> makeFancy(PermissionHolder holder, String label, Node node) {
HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy(TextUtils.joinNewline(
"¥3> " + (node.getValue() ? "¥a" : "¥c") + node.getPermission(),
"¥3> " + (node.getValuePrimitive() ? "¥a" : "¥c") + node.getPermission(),
" ",
"¥7Click to remove this node from " + holder.getFriendlyName()
), '¥'));
@@ -192,7 +192,7 @@ public class GroupListMembers extends SubCommand<Group> {
private static Consumer<BuildableComponent.Builder<? ,?>> makeFancy(String holderName, boolean group, String label, HeldPermission<?> perm) {
HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy(TextUtils.joinNewline(
"&3> " + (perm.asNode().getValue() ? "&a" : "&c") + perm.asNode().getGroupName(),
"&3> " + (perm.asNode().getValuePrimitive() ? "&a" : "&c") + perm.asNode().getGroupName(),
" ",
"&7Click to remove this parent from " + holderName
), Constants.FORMAT_CHAR));
@@ -61,7 +61,7 @@ public class LogNotify extends SubCommand<Log> {
// if they don't have the perm, they're not ignoring
// if set to false, ignore it, return false
return ret.map(Node::getValue).orElse(false);
return ret.map(Node::getValuePrimitive).orElse(false);
}
private static void setIgnoring(LuckPermsPlugin plugin, UUID uuid, boolean state) {
@@ -39,6 +39,7 @@ import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.utils.Predicates;
import me.lucko.luckperms.common.verbose.CheckOrigin;
import java.util.List;
import java.util.UUID;
@@ -67,7 +68,7 @@ public class CheckCommand extends SingleCommand {
return CommandResult.STATE_ERROR;
}
Tristate tristate = user.getUserData().getPermissionData(plugin.getContextForUser(user)).getPermissionValue(permission);
Tristate tristate = user.getUserData().getPermissionData(plugin.getContextForUser(user)).getPermissionValue(permission, CheckOrigin.INTERNAL);
Message.CHECK_RESULT.send(sender, user.getFriendlyName(), permission, Util.formatTristate(tristate));
return CommandResult.SUCCESS;
}
@@ -191,7 +191,7 @@ public class SearchCommand extends SingleCommand {
private static Consumer<BuildableComponent.Builder<?, ?>> makeFancy(String holderName, boolean group, String label, HeldPermission<?> perm) {
HoverEvent hoverEvent = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy(TextUtils.joinNewline(
"&3> " + (perm.asNode().getValue() ? "&a" : "&c") + perm.asNode().getPermission(),
"&3> " + (perm.asNode().getValuePrimitive() ? "&a" : "&c") + perm.asNode().getPermission(),
" ",
"&7Click to remove this node from " + holderName
), Constants.FORMAT_CHAR));
@@ -25,7 +25,7 @@
package me.lucko.luckperms.common.commands.impl.misc;
import me.lucko.luckperms.api.caching.PermissionData;
import me.lucko.luckperms.common.caching.PermissionCache;
import me.lucko.luckperms.common.commands.CommandException;
import me.lucko.luckperms.common.commands.CommandResult;
import me.lucko.luckperms.common.commands.abstraction.SingleCommand;
@@ -86,7 +86,7 @@ public class TreeCommand extends SingleCommand {
return CommandResult.STATE_ERROR;
}
PermissionData permissionData = user.getUserData().getPermissionData(plugin.getContextForUser(user));
PermissionCache permissionData = user.getUserData().getPermissionData(plugin.getContextForUser(user));
TreeView view = TreeViewBuilder.newBuilder().rootPosition(selection).maxLevels(maxLevel).build(plugin.getPermissionVault());
if (!view.hasData()) {
@@ -63,6 +63,7 @@ public class VerboseCommand extends SingleCommand {
return CommandResult.INVALID_ARGS;
}
boolean noTraces = args.remove("--notrace") || args.remove("--notraces") || args.remove("--slim") || args.remove("-s");
String mode = args.get(0).toLowerCase();
if (mode.equals("on") || mode.equals("true") || mode.equals("record")) {
@@ -108,7 +109,7 @@ public class VerboseCommand extends SingleCommand {
} else {
Message.VERBOSE_RECORDING_UPLOAD_START.send(sender);
String url = listener.uploadPasteData();
String url = listener.uploadPasteData(!noTraces);
if (url == null) {
url = "null";
}
@@ -99,7 +99,7 @@ public class UserDemote extends SubCommand<User> {
// Load applicable groups
Set<Node> nodes = user.getEnduringNodes().values().stream()
.filter(Node::isGroupNode)
.filter(Node::getValue)
.filter(Node::getValuePrimitive)
.filter(node -> node.getFullContexts().makeImmutable().equals(context.makeImmutable()))
.collect(Collectors.toSet());
@@ -97,7 +97,7 @@ public class UserPromote extends SubCommand<User> {
// Load applicable groups
Set<Node> nodes = user.getEnduringNodes().values().stream()
.filter(Node::isGroupNode)
.filter(Node::getValue)
.filter(Node::getValuePrimitive)
.filter(node -> node.getFullContexts().makeImmutable().equals(context.makeImmutable()))
.collect(Collectors.toSet());
@@ -27,20 +27,13 @@ package me.lucko.luckperms.common.contexts;
import me.lucko.luckperms.api.context.ImmutableContextSet;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
public class ContextSetComparator implements Comparator<ImmutableContextSet> {
private static final Comparator<Map.Entry<String, String>> STRING_ENTRY_COMPARATOR = (o1, o2) -> {
int ret = o1.getKey().compareTo(o2.getKey());
if (ret != 0) {
return ret;
}
return o1.getValue().compareTo(o2.getValue());
};
private static final ContextSetComparator INSTANCE = new ContextSetComparator();
public static Comparator<ImmutableContextSet> get() {
@@ -75,16 +68,15 @@ public class ContextSetComparator implements Comparator<ImmutableContextSet> {
return o1Size > o2Size ? 1 : -1;
}
// we *have* to maintain transitivity in this comparator. this may be expensive, but it's necessary, as these
// values are stored in a treemap.
// we *have* to maintain transitivity in this comparator. this may be expensive, but it's necessary, as this
// comparator is used in the PermissionHolder nodes treemap
// in order to have consistent ordering, we have to compare the content of the context sets by ordering the
// elements and then comparing which set is greater.
TreeSet<Map.Entry<String, String>> o1Map = new TreeSet<>(STRING_ENTRY_COMPARATOR);
TreeSet<Map.Entry<String, String>> o2Map = new TreeSet<>(STRING_ENTRY_COMPARATOR);
o1Map.addAll(o1.toMultimap().entries());
o2Map.addAll(o2.toMultimap().entries());
List<Map.Entry<String, String>> o1Map = new ArrayList<>(o1.toSet());
List<Map.Entry<String, String>> o2Map = new ArrayList<>(o2.toSet());
o1Map.sort(STRING_ENTRY_COMPARATOR);
o2Map.sort(STRING_ENTRY_COMPARATOR);
int o1MapSize = o1Map.size();
int o2MapSize = o2Map.size();
@@ -101,18 +93,33 @@ public class ContextSetComparator implements Comparator<ImmutableContextSet> {
Map.Entry<String, String> ent2 = it2.next();
// compare these values.
if (ent1.getKey().equals(ent2.getKey()) && ent1.getValue().equals(ent2.getValue())) {
//noinspection StringEquality - strings are intern'd
if (ent1.getKey() == ent2.getKey() && ent1.getValue() == ent2.getValue()) {
// identical entries. just move on
continue;
}
// these values are at the same position in the ordered sets.
// these entries are at the same position in the ordered sets.
// if ent1 is "greater" than ent2, then at this first position, o1 has a "greater" entry, and can therefore be considered
// a greater set.
// a greater set, and vice versa
return STRING_ENTRY_COMPARATOR.compare(ent1, ent2);
}
// shouldn't ever reach this point. ever.
// shouldn't ever reach this point.
return 0;
}
private static final Comparator<String> FAST_STRING_COMPARATOR = (o1, o2) -> {
//noinspection StringEquality
return o1 == o2 ? 0 : o1.compareTo(o2);
};
private static final Comparator<Map.Entry<String, String>> STRING_ENTRY_COMPARATOR = (o1, o2) -> {
int ret = FAST_STRING_COMPARATOR.compare(o1.getKey(), o2.getKey());
if (ret != 0) {
return ret;
}
return FAST_STRING_COMPARATOR.compare(o1.getValue(), o2.getValue());
};
}
@@ -64,6 +64,7 @@ import me.lucko.luckperms.common.event.impl.EventUserDataRecalculate;
import me.lucko.luckperms.common.event.impl.EventUserDemote;
import me.lucko.luckperms.common.event.impl.EventUserFirstLogin;
import me.lucko.luckperms.common.event.impl.EventUserLoad;
import me.lucko.luckperms.common.event.impl.EventUserLoginProcess;
import me.lucko.luckperms.common.event.impl.EventUserPromote;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.PermissionHolder;
@@ -79,158 +80,167 @@ import java.util.concurrent.atomic.AtomicBoolean;
public final class EventFactory {
private final LuckPermsEventBus eventBus;
private void fireEvent(LuckPermsEvent event) {
private void fireEventAsync(LuckPermsEvent event) {
eventBus.fireEventAsync(event);
}
private void fireEvent(LuckPermsEvent event) {
eventBus.fireEvent(event);
}
public void handleGroupCreate(Group group, CreationCause cause) {
EventGroupCreate event = new EventGroupCreate(group.getDelegate(), cause);
fireEvent(event);
fireEventAsync(event);
}
public void handleGroupDelete(Group group, DeletionCause cause) {
EventGroupDelete event = new EventGroupDelete(group.getName(), ImmutableSet.copyOf(group.getEnduringNodes().values()), cause);
fireEvent(event);
fireEventAsync(event);
}
public void handleGroupLoadAll() {
EventGroupLoadAll event = new EventGroupLoadAll();
fireEvent(event);
fireEventAsync(event);
}
public void handleGroupLoad(Group group) {
EventGroupLoad event = new EventGroupLoad(group.getDelegate());
fireEvent(event);
fireEventAsync(event);
}
public boolean handleLogBroadcast(boolean initialState, LogEntry entry, LogBroadcastEvent.Origin origin) {
AtomicBoolean cancel = new AtomicBoolean(initialState);
EventLogBroadcast event = new EventLogBroadcast(cancel, entry, origin);
eventBus.fireEvent(event);
fireEvent(event);
return cancel.get();
}
public boolean handleLogPublish(boolean initialState, LogEntry entry) {
AtomicBoolean cancel = new AtomicBoolean(initialState);
EventLogPublish event = new EventLogPublish(cancel, entry);
eventBus.fireEvent(event);
fireEvent(event);
return cancel.get();
}
public boolean handleLogNetworkPublish(boolean initialState, UUID id, LogEntry entry) {
AtomicBoolean cancel = new AtomicBoolean(initialState);
EventLogNetworkPublish event = new EventLogNetworkPublish(cancel, id, entry);
eventBus.fireEvent(event);
fireEvent(event);
return cancel.get();
}
public void handleLogReceive(UUID id, LogEntry entry) {
EventLogReceive event = new EventLogReceive(id, entry);
fireEvent(event);
fireEventAsync(event);
}
public void handleNodeAdd(Node node, PermissionHolder target, Collection<Node> before, Collection<Node> after) {
EventNodeAdd event = new EventNodeAdd(node, target.getDelegate(), ImmutableSet.copyOf(before), ImmutableSet.copyOf(after));
fireEvent(event);
fireEventAsync(event);
}
public void handleNodeClear(PermissionHolder target, Collection<Node> before, Collection<Node> after) {
EventNodeClear event = new EventNodeClear(target.getDelegate(), ImmutableSet.copyOf(before), ImmutableSet.copyOf(after));
fireEvent(event);
fireEventAsync(event);
}
public void handleNodeRemove(Node node, PermissionHolder target, Collection<Node> before, Collection<Node> after) {
EventNodeRemove event = new EventNodeRemove(node, target.getDelegate(), ImmutableSet.copyOf(before), ImmutableSet.copyOf(after));
fireEvent(event);
fireEventAsync(event);
}
public void handleConfigReload() {
EventConfigReload event = new EventConfigReload();
fireEvent(event);
fireEventAsync(event);
}
public void handlePostSync() {
EventPostSync event = new EventPostSync();
fireEvent(event);
fireEventAsync(event);
}
public boolean handleNetworkPreSync(boolean initialState, UUID id) {
AtomicBoolean cancel = new AtomicBoolean(initialState);
EventPreNetworkSync event = new EventPreNetworkSync(cancel, id);
eventBus.fireEvent(event);
fireEvent(event);
return cancel.get();
}
public boolean handlePreSync(boolean initialState) {
AtomicBoolean cancel = new AtomicBoolean(initialState);
EventPreSync event = new EventPreSync(cancel);
eventBus.fireEvent(event);
fireEvent(event);
return cancel.get();
}
public void handleTrackCreate(Track track, CreationCause cause) {
EventTrackCreate event = new EventTrackCreate(track.getDelegate(), cause);
fireEvent(event);
fireEventAsync(event);
}
public void handleTrackDelete(Track track, DeletionCause cause) {
EventTrackDelete event = new EventTrackDelete(track.getName(), ImmutableList.copyOf(track.getGroups()), cause);
fireEvent(event);
fireEventAsync(event);
}
public void handleTrackLoadAll() {
EventTrackLoadAll event = new EventTrackLoadAll();
fireEvent(event);
fireEventAsync(event);
}
public void handleTrackLoad(Track track) {
EventTrackLoad event = new EventTrackLoad(track.getDelegate());
fireEvent(event);
fireEventAsync(event);
}
public void handleTrackAddGroup(Track track, String group, List<String> before, List<String> after) {
EventTrackAddGroup event = new EventTrackAddGroup(group, track.getDelegate(), ImmutableList.copyOf(before), ImmutableList.copyOf(after));
fireEvent(event);
fireEventAsync(event);
}
public void handleTrackClear(Track track, List<String> before) {
EventTrackClear event = new EventTrackClear(track.getDelegate(), ImmutableList.copyOf(before), ImmutableList.of());
fireEvent(event);
fireEventAsync(event);
}
public void handleTrackRemoveGroup(Track track, String group, List<String> before, List<String> after) {
EventTrackRemoveGroup event = new EventTrackRemoveGroup(group, track.getDelegate(), ImmutableList.copyOf(before), ImmutableList.copyOf(after));
fireEvent(event);
fireEventAsync(event);
}
public void handleUserCacheLoad(User user, UserData data) {
EventUserCacheLoad event = new EventUserCacheLoad(user.getDelegate(), data);
fireEvent(event);
fireEventAsync(event);
}
public void handleUserDataRecalculate(User user, UserData data) {
EventUserDataRecalculate event = new EventUserDataRecalculate(user.getDelegate(), data);
fireEvent(event);
fireEventAsync(event);
}
public void handleUserFirstLogin(UUID uuid, String username) {
EventUserFirstLogin event = new EventUserFirstLogin(uuid, username);
fireEvent(event);
fireEventAsync(event);
}
public void handleUserLoad(User user) {
EventUserLoad event = new EventUserLoad(user.getDelegate());
fireEventAsync(event);
}
public void handleUserLoginProcess(UUID uuid, String username, User user) {
EventUserLoginProcess event = new EventUserLoginProcess(uuid, username, user.getDelegate());
fireEvent(event);
}
public void handleUserDemote(User user, Track track, String from, String to) {
EventUserDemote event = new EventUserDemote(track.getDelegate(), user.getDelegate(), from, to);
fireEvent(event);
fireEventAsync(event);
}
public void handleUserPromote(User user, Track track, String from, String to) {
EventUserPromote event = new EventUserPromote(track.getDelegate(), user.getDelegate(), from, to);
fireEvent(event);
fireEventAsync(event);
}
}
@@ -0,0 +1,47 @@
/*
* 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.event.impl;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;
import me.lucko.luckperms.api.User;
import me.lucko.luckperms.api.event.user.UserLoginProcessEvent;
import me.lucko.luckperms.common.event.AbstractEvent;
import java.util.UUID;
@Getter
@ToString
@AllArgsConstructor
public class EventUserLoginProcess extends AbstractEvent implements UserLoginProcessEvent {
private final UUID uuid;
private final String username;
private final User user;
}
@@ -57,7 +57,8 @@ public enum CommandSpec {
VERBOSE("Manage verbose permission checking", "/%s verbose <true|false> [filter]",
Arg.list(
Arg.create("on|record|off|paste", true, "whether to enable/disable logging, or to paste the logged output"),
Arg.create("filter", false, "the filter to match entries against")
Arg.create("filter", false, "the filter to match entries against"),
Arg.create("--slim", false, "add \"--slim\" to exclude trace data from the pasted output")
)
),
TREE("Generate a tree view of permissions", "/%s tree [selection] [max level] [player]",
@@ -57,7 +57,7 @@ public enum Message {
OP_DISABLED("&bThe vanilla OP system is disabled on this server.", false),
OP_DISABLED_SPONGE("&2Server Operator status has no effect when a permission plugin is installed. Please edit user data directly.", true),
LOG("&3LOG &3&l> {0}", true),
VERBOSE_LOG("&3VERBOSE &3&l> {0}", true),
VERBOSE_LOG("&3VB &3&l> {0}", true),
EXPORT_LOG("&3EXPORT &3&l> &f{0}", true),
EXPORT_LOG_PROGRESS("&3EXPORT &3&l> &7{0}", true),
@@ -39,35 +39,8 @@ import java.util.UUID;
@RequiredArgsConstructor
public class GenericUserManager extends AbstractManager<UserIdentifier, User> implements UserManager {
public static boolean giveDefaultIfNeeded(User user, boolean save, LuckPermsPlugin plugin) {
boolean hasGroup = false;
if (user.getPrimaryGroup().getStoredValue() != null && !user.getPrimaryGroup().getStoredValue().isEmpty()) {
for (Node node : user.getEnduringNodes().values()) {
if (node.hasSpecificContext()) {
continue;
}
if (node.isGroupNode()) {
hasGroup = true;
break;
}
}
}
if (hasGroup) {
return false;
}
user.getPrimaryGroup().setStoredValue("default");
user.setPermission(NodeFactory.make("group.default"));
if (save) {
plugin.getStorage().saveUser(user);
}
return true;
}
private final LuckPermsPlugin plugin;
@Override
public User getOrMake(UserIdentifier id) {
@@ -78,39 +51,6 @@ public class GenericUserManager extends AbstractManager<UserIdentifier, User> im
return ret;
}
/**
* Check whether the user's state indicates that they should be persisted to storage.
*
* @param user the user to check
* @return true if the user should be saved
*/
public static boolean shouldSave(User user) {
if (user.getEnduringNodes().size() != 1) {
return true;
}
for (Node node : user.getEnduringNodes().values()) {
// There's only one.
if (!node.isGroupNode()) {
return true;
}
if (node.isTemporary() || node.isServerSpecific() || node.isWorldSpecific()) {
return true;
}
if (!node.getGroupName().equalsIgnoreCase("default")) {
// The user's only node is not the default group one.
return true;
}
}
// Not in the default primary group
return !user.getPrimaryGroup().getStoredValue().equalsIgnoreCase("default");
}
private final LuckPermsPlugin plugin;
@Override
public User apply(UserIdentifier id) {
return !id.getUsername().isPresent() ?
@@ -140,9 +80,12 @@ public class GenericUserManager extends AbstractManager<UserIdentifier, User> im
}
@Override
public void cleanup(User user) {
public boolean cleanup(User user) {
if (!plugin.isPlayerOnline(plugin.getUuidCache().getExternalUUID(user.getUuid()))) {
unload(user);
return true;
} else {
return false;
}
}
@@ -177,4 +120,65 @@ public class GenericUserManager extends AbstractManager<UserIdentifier, User> im
});
});
}
public static boolean giveDefaultIfNeeded(User user, boolean save, LuckPermsPlugin plugin) {
boolean hasGroup = false;
if (user.getPrimaryGroup().getStoredValue() != null && !user.getPrimaryGroup().getStoredValue().isEmpty()) {
for (Node node : user.getEnduringNodes().values()) {
if (node.hasSpecificContext()) {
continue;
}
if (node.isGroupNode()) {
hasGroup = true;
break;
}
}
}
if (hasGroup) {
return false;
}
user.getPrimaryGroup().setStoredValue("default");
user.setPermission(NodeFactory.make("group.default"));
if (save) {
plugin.getStorage().saveUser(user);
}
return true;
}
/**
* Check whether the user's state indicates that they should be persisted to storage.
*
* @param user the user to check
* @return true if the user should be saved
*/
public static boolean shouldSave(User user) {
if (user.getEnduringNodes().size() != 1) {
return true;
}
for (Node node : user.getEnduringNodes().values()) {
// There's only one.
if (!node.isGroupNode()) {
return true;
}
if (node.isTemporary() || node.isServerSpecific() || node.isWorldSpecific()) {
return true;
}
if (!node.getGroupName().equalsIgnoreCase("default")) {
// The user's only node is not the default group one.
return true;
}
}
// Not in the default primary group
return !user.getPrimaryGroup().getStoredValue().equalsIgnoreCase("default");
}
}
@@ -61,7 +61,7 @@ public interface UserManager extends Manager<UserIdentifier, User> {
*
* @param user The user to be cleaned up
*/
void cleanup(User user);
boolean cleanup(User user);
/**
* Schedules a task to cleanup a user after a certain period of time, if they're not on the server anymore.
@@ -546,7 +546,7 @@ public abstract class PermissionHolder {
if (!n.isGroupNode()) continue;
String groupName = n.getGroupName();
if (!processedGroups.add(groupName) || excludedGroups.contains(groupName) || !n.getValue()) continue;
if (!processedGroups.add(groupName) || excludedGroups.contains(groupName) || !n.getValuePrimitive()) continue;
if (!((contexts.isApplyGlobalGroups() || n.isServerSpecific()) && (contexts.isApplyGlobalWorldGroups() || n.isWorldSpecific()))) {
continue;
@@ -624,7 +624,7 @@ public abstract class PermissionHolder {
if (!n.isGroupNode()) continue;
String groupName = n.getGroupName();
if (!processedGroups.add(groupName) || excludedGroups.contains(groupName) || !n.getValue()) continue;
if (!processedGroups.add(groupName) || excludedGroups.contains(groupName) || !n.getValuePrimitive()) continue;
Group g = plugin.getGroupManager().getIfLoaded(groupName);
if (g != null) {
@@ -717,11 +717,11 @@ public abstract class PermissionHolder {
for (Node node : entries) {
String perm = lowerCase ? node.getPermission().toLowerCase() : node.getPermission();
if (perms.putIfAbsent(perm, node.getValue()) == null) {
if (perms.putIfAbsent(perm, node.getValuePrimitive()) == null) {
if (applyShorthand) {
List<String> sh = node.resolveShorthand();
if (!sh.isEmpty()) {
sh.stream().map(s -> lowerCase ? s.toLowerCase() : s).forEach(s -> perms.putIfAbsent(s, node.getValue()));
sh.stream().map(s -> lowerCase ? s.toLowerCase() : s).forEach(s -> perms.putIfAbsent(s, node.getValuePrimitive()));
}
}
}
@@ -738,11 +738,11 @@ public abstract class PermissionHolder {
for (Node node : entries) {
String perm = lowerCase ? node.getPermission().toLowerCase() : node.getPermission();
if (perms.putIfAbsent(perm, node.getValue()) == null) {
if (perms.putIfAbsent(perm, node.getValuePrimitive()) == null) {
if (applyShorthand) {
List<String> sh = node.resolveShorthand();
if (!sh.isEmpty()) {
sh.stream().map(s -> lowerCase ? s.toLowerCase() : s).forEach(s -> perms.putIfAbsent(s, node.getValue()));
sh.stream().map(s -> lowerCase ? s.toLowerCase() : s).forEach(s -> perms.putIfAbsent(s, node.getValuePrimitive()));
}
}
}
@@ -770,7 +770,7 @@ public abstract class PermissionHolder {
List<Node> nodes = filterNodes(context.getContextSet());
for (Node node : nodes) {
if (!node.getValue()) continue;
if (!node.getValuePrimitive()) continue;
if (!node.isMeta() && !node.isPrefix() && !node.isSuffix()) continue;
if (!((contexts.isIncludeGlobal() || node.isServerSpecific()) && (contexts.isIncludeGlobalWorld() || node.isWorldSpecific()))) {
@@ -793,7 +793,7 @@ public abstract class PermissionHolder {
if (!n.isGroupNode()) continue;
String groupName = n.getGroupName();
if (!processedGroups.add(groupName) || excludedGroups.contains(groupName) || !n.getValue()) continue;
if (!processedGroups.add(groupName) || excludedGroups.contains(groupName) || !n.getValuePrimitive()) continue;
if (!((contexts.isApplyGlobalGroups() || n.isServerSpecific()) && (contexts.isApplyGlobalWorldGroups() || n.isWorldSpecific()))) {
continue;
@@ -834,7 +834,7 @@ public abstract class PermissionHolder {
List<Node> nodes = getOwnNodes();
for (Node node : nodes) {
if (!node.getValue()) continue;
if (!node.getValuePrimitive()) continue;
if (!node.isMeta() && !node.isPrefix() && !node.isSuffix()) continue;
accumulator.accumulateNode(ImmutableLocalizedNode.of(node, getObjectName()));
@@ -853,7 +853,7 @@ public abstract class PermissionHolder {
if (!n.isGroupNode()) continue;
String groupName = n.getGroupName();
if (!processedGroups.add(groupName) || excludedGroups.contains(groupName) || !n.getValue()) continue;
if (!processedGroups.add(groupName) || excludedGroups.contains(groupName) || !n.getValuePrimitive()) continue;
Group g = plugin.getGroupManager().getIfLoaded(groupName);
if (g != null) {
@@ -70,13 +70,7 @@ public class User extends PermissionHolder implements Identifiable<UserIdentifie
private final UserCache userData = new UserCache(this);
@Getter
private BufferedRequest<Void> refreshBuffer = new BufferedRequest<Void>(250L, 50L, r -> getPlugin().doAsync(r)) {
@Override
protected Void perform() {
refreshPermissions();
return null;
}
};
private BufferedRequest<Void> refreshBuffer = new UserRefreshBuffer(this);
@Getter
private final UserDelegate delegate = new UserDelegate(this);
@@ -214,4 +208,20 @@ public class User extends PermissionHolder implements Identifiable<UserIdentifie
public void cleanup() {
userData.cleanup();
}
private static final class UserRefreshBuffer extends BufferedRequest<Void> {
private final User user;
private UserRefreshBuffer(User user) {
super(250L, 50L, r -> user.getPlugin().doAsync(r));
this.user = user;
}
@Override
protected Void perform() {
user.refreshPermissions();
return null;
}
}
}
@@ -28,7 +28,6 @@ package me.lucko.luckperms.common.node;
import lombok.Getter;
import lombok.ToString;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
@@ -50,18 +49,31 @@ import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkState;
/**
* An immutable permission node
* An immutable implementation of {@link Node}.
*/
@ToString(of = {"permission", "value", "override", "server", "world", "expireAt", "contexts"})
public final class ImmutableNode implements Node {
private static final int NODE_SEPARATOR_CHAR = Character.getNumericValue('.');
private static final String[] PERMISSION_DELIMS = new String[]{"/", "-", "$", "(", ")", "=", ","};
private static final String[] SERVER_WORLD_DELIMS = new String[]{"/", "-"};
private static final Splitter META_SPLITTER = Splitter.on(PatternCache.compileDelimitedMatcher(".", "\\")).limit(2);
/*
* NODE STATE
*
* This are the actual node parameters, and are
* basically what this class wraps.
*/
@Getter
private final String permission;
@Getter
private final Boolean value;
private final boolean value;
@Getter
private boolean override;
@@ -80,12 +92,28 @@ public final class ImmutableNode implements Node {
@Getter
private final ImmutableContextSet fullContexts;
// Cached state
/*
* CACHED STATE
*
* These values are based upon the node state above, and are stored here
* to make node comparison and manipulation faster.
*
* This increases the memory footprint of this class by a bit, but it is
* worth it for the gain in speed.
*
* The methods on this class are called v. frequently.
*/
// these save on lots of instance creation when comparing nodes
// storing optionals as a field type is usually a bad idea, however, the
// #getServer and #getWorld methods are called frequently when comparing nodes.
// without caching these values, it creates quite a bit of object churn
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private final Optional<String> optServer;
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
private final Optional<String> optWorld;
private final int hashCode;
private final boolean isGroup;
private String groupName;
@@ -103,7 +131,7 @@ public final class ImmutableNode implements Node {
private final List<String> resolvedShorthand;
private final String serializedNode;
private String serializedNode = null;
/**
* Make an immutable node instance
@@ -116,7 +144,7 @@ public final class ImmutableNode implements Node {
* @param contexts any additional contexts applying to this node
*/
@SuppressWarnings("deprecation")
public ImmutableNode(String permission, boolean value, boolean override, long expireAt, String server, String world, ContextSet contexts) {
ImmutableNode(String permission, boolean value, boolean override, long expireAt, String server, String world, ContextSet contexts) {
if (permission == null || permission.equals("")) {
throw new IllegalArgumentException("Empty permission");
}
@@ -135,18 +163,20 @@ public final class ImmutableNode implements Node {
world = null;
}
this.permission = NodeFactory.unescapeDelimiters(permission, "/", "-", "$", "(", ")", "=", ",");
this.permission = NodeFactory.unescapeDelimiters(permission, PERMISSION_DELIMS).intern();
this.value = value;
this.override = override;
this.expireAt = expireAt;
this.server = NodeFactory.unescapeDelimiters(server, "/", "-");
this.world = NodeFactory.unescapeDelimiters(world, "/", "-");
this.server = internString(NodeFactory.unescapeDelimiters(server, SERVER_WORLD_DELIMS));
this.world = internString(NodeFactory.unescapeDelimiters(world, SERVER_WORLD_DELIMS));
this.contexts = contexts == null ? ContextSet.empty() : contexts.makeImmutable();
String lowerCasePermission = this.permission.toLowerCase();
// Setup state
isGroup = this.permission.toLowerCase().startsWith("group.");
isGroup = lowerCasePermission.startsWith("group.");
if (isGroup) {
groupName = this.permission.substring("group.".length()).toLowerCase();
groupName = lowerCasePermission.substring("group.".length()).intern();
}
isWildcard = this.permission.endsWith(".*");
@@ -154,38 +184,48 @@ public final class ImmutableNode implements Node {
isMeta = NodeFactory.isMetaNode(this.permission);
if (isMeta) {
List<String> metaPart = Splitter.on(PatternCache.compileDelimitedMatcher(".", "\\")).limit(2).splitToList(getPermission().substring("meta.".length()));
meta = Maps.immutableEntry(MetaUtils.unescapeCharacters(metaPart.get(0)), MetaUtils.unescapeCharacters(metaPart.get(1)));
List<String> metaPart = META_SPLITTER.splitToList(this.permission.substring("meta.".length()));
meta = Maps.immutableEntry(MetaUtils.unescapeCharacters(metaPart.get(0)).intern(), MetaUtils.unescapeCharacters(metaPart.get(1)).intern());
}
isPrefix = NodeFactory.isPrefixNode(this.permission);
if (isPrefix) {
List<String> prefixPart = Splitter.on(PatternCache.compileDelimitedMatcher(".", "\\")).limit(2).splitToList(getPermission().substring("prefix.".length()));
List<String> prefixPart = META_SPLITTER.splitToList(this.permission.substring("prefix.".length()));
Integer i = Integer.parseInt(prefixPart.get(0));
prefix = Maps.immutableEntry(i, MetaUtils.unescapeCharacters(prefixPart.get(1)));
prefix = Maps.immutableEntry(i, MetaUtils.unescapeCharacters(prefixPart.get(1)).intern());
}
isSuffix = NodeFactory.isSuffixNode(this.permission);
if (isSuffix) {
List<String> suffixPart = Splitter.on(PatternCache.compileDelimitedMatcher(".", "\\")).limit(2).splitToList(getPermission().substring("suffix.".length()));
List<String> suffixPart = META_SPLITTER.splitToList(this.permission.substring("suffix.".length()));
Integer i = Integer.parseInt(suffixPart.get(0));
suffix = Maps.immutableEntry(i, MetaUtils.unescapeCharacters(suffixPart.get(1)));
suffix = Maps.immutableEntry(i, MetaUtils.unescapeCharacters(suffixPart.get(1)).intern());
}
resolvedShorthand = ImmutableList.copyOf(ShorthandParser.parseShorthand(getPermission()));
serializedNode = calculateSerializedNode();
MutableContextSet fullContexts = this.contexts.mutableCopy();
if (isServerSpecific()) {
fullContexts.add("server", this.server);
}
if (isWorldSpecific()) {
fullContexts.add("world", this.world);
if (this.server != null || this.world != null) {
MutableContextSet fullContexts = this.contexts.mutableCopy();
if (this.server != null) {
fullContexts.add("server", this.server);
}
if (this.world != null) {
fullContexts.add("world", this.world);
}
this.fullContexts = fullContexts.makeImmutable();
} else {
this.fullContexts = this.contexts;
}
this.fullContexts = fullContexts.makeImmutable();
this.optServer = Optional.ofNullable(this.server);
this.optWorld = Optional.ofNullable(this.world);
this.hashCode = calculateHashCode();
}
@Override
public boolean getValuePrimitive() {
return value;
}
@Override
@@ -225,19 +265,19 @@ public final class ImmutableNode implements Node {
@Override
public long getExpiryUnixTime() {
Preconditions.checkState(isTemporary(), "Node does not have an expiry time.");
checkState(isTemporary(), "Node does not have an expiry time.");
return expireAt;
}
@Override
public Date getExpiry() {
Preconditions.checkState(isTemporary(), "Node does not have an expiry time.");
checkState(isTemporary(), "Node does not have an expiry time.");
return new Date(expireAt * 1000L);
}
@Override
public long getSecondsTilExpiry() {
Preconditions.checkState(isTemporary(), "Node does not have an expiry time.");
checkState(isTemporary(), "Node does not have an expiry time.");
return expireAt - DateUtil.unixSecondsNow();
}
@@ -253,7 +293,7 @@ public final class ImmutableNode implements Node {
@Override
public String getGroupName() {
Preconditions.checkState(isGroupNode(), "Node is not a group node");
checkState(isGroupNode(), "Node is not a group node");
return groupName;
}
@@ -274,7 +314,7 @@ public final class ImmutableNode implements Node {
@Override
public Map.Entry<String, String> getMeta() {
Preconditions.checkState(isMeta(), "Node is not a meta node");
checkState(isMeta(), "Node is not a meta node");
return meta;
}
@@ -285,7 +325,7 @@ public final class ImmutableNode implements Node {
@Override
public Map.Entry<Integer, String> getPrefix() {
Preconditions.checkState(isPrefix(), "Node is not a prefix node");
checkState(isPrefix(), "Node is not a prefix node");
return prefix;
}
@@ -296,7 +336,7 @@ public final class ImmutableNode implements Node {
@Override
public Map.Entry<Integer, String> getSuffix() {
Preconditions.checkState(isSuffix(), "Node is not a suffix node");
checkState(isSuffix(), "Node is not a suffix node");
return suffix;
}
@@ -395,7 +435,10 @@ public final class ImmutableNode implements Node {
}
@Override
public String toSerializedNode() {
public synchronized String toSerializedNode() {
if (serializedNode == null) {
serializedNode = calculateSerializedNode();
}
return serializedNode;
}
@@ -403,16 +446,11 @@ public final class ImmutableNode implements Node {
StringBuilder builder = new StringBuilder();
if (server != null) {
builder.append(NodeFactory.escapeDelimiters(server, "/", "-"));
if (world != null) {
builder.append("-").append(NodeFactory.escapeDelimiters(world, "/", "-"));
}
builder.append(NodeFactory.escapeDelimiters(server, SERVER_WORLD_DELIMS));
if (world != null) builder.append("-").append(NodeFactory.escapeDelimiters(world, SERVER_WORLD_DELIMS));
builder.append("/");
} else {
if (world != null) {
builder.append("global-").append(NodeFactory.escapeDelimiters(world, "/", "-")).append("/");
}
if (world != null) builder.append("global-").append(NodeFactory.escapeDelimiters(world, SERVER_WORLD_DELIMS)).append("/");
}
if (!contexts.isEmpty()) {
@@ -420,162 +458,110 @@ public final class ImmutableNode implements Node {
for (Map.Entry<String, String> entry : contexts.toSet()) {
builder.append(NodeFactory.escapeDelimiters(entry.getKey(), "=", "(", ")", ",")).append("=").append(NodeFactory.escapeDelimiters(entry.getValue(), "=", "(", ")", ",")).append(",");
}
builder.deleteCharAt(builder.length() - 1);
builder.append(")");
}
builder.append(NodeFactory.escapeDelimiters(permission, "/", "-", "$", "(", ")", "=", ","));
if (expireAt != 0L) {
builder.append("$").append(expireAt);
}
if (expireAt != 0L) builder.append("$").append(expireAt);
return builder.toString();
}
@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.equals(other.getPermission())) return false;
if (!this.getValue().equals(other.getValue())) return false;
if (this.permission != other.getPermission()) return false;
if (this.value != other.getValuePrimitive()) return false;
if (this.override != other.isOverride()) return false;
final String thisServer = this.getServer().orElse(null);
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.getWorld().orElse(null);
final String thisWorld = this.world;
final String otherWorld = other.getWorld().orElse(null);
if (thisWorld == null ? otherWorld != null : !thisWorld.equals(otherWorld)) return false;
final long thisExpireAt = this.isTemporary() ? this.getExpiryUnixTime() : 0L;
final long otherExpireAt = other.isTemporary() ? other.getExpiryUnixTime() : 0L;
return thisExpireAt == otherExpireAt && this.getContexts().equals(other.getContexts());
return this.expireAt == otherExpireAt && this.getContexts().equals(other.getContexts());
}
@Override
public int hashCode() {
return this.hashCode;
}
private int calculateHashCode() {
final int PRIME = 59;
int result = 1;
result = result * PRIME + this.permission.hashCode();
result = result * PRIME + Boolean.hashCode(this.value);
result = result * PRIME + (this.value ? 79 : 97);
result = result * PRIME + (this.override ? 79 : 97);
final String server = this.getServer().orElse(null);
result = result * PRIME + (server == null ? 43 : server.hashCode());
final String world = this.getWorld().orElse(null);
result = result * PRIME + (world == null ? 43 : world.hashCode());
result = result * PRIME + (this.server == null ? 43 : this.server.hashCode());
result = result * PRIME + (this.world == null ? 43 : this.world.hashCode());
result = result * PRIME + (int) (this.expireAt >>> 32 ^ this.expireAt);
result = result * PRIME + this.contexts.hashCode();
return result;
}
@SuppressWarnings("StringEquality")
@Override
public boolean equalsIgnoringValue(Node other) {
if (!other.getPermission().equalsIgnoreCase(this.getPermission())) {
return false;
}
if (this.permission != other.getPermission()) return false;
if (this.override != other.isOverride()) return false;
if (other.isTemporary() != this.isTemporary()) {
return false;
}
final String thisServer = this.server;
final String otherServer = other.getServer().orElse(null);
if (thisServer == null ? otherServer != null : !thisServer.equals(otherServer)) return false;
if (this.isTemporary()) {
if (other.getExpiryUnixTime() != this.getExpiryUnixTime()) {
return false;
}
}
final String thisWorld = this.world;
final String otherWorld = other.getWorld().orElse(null);
if (thisWorld == null ? otherWorld != null : !thisWorld.equals(otherWorld)) return false;
if (other.getServer().isPresent() == this.getServer().isPresent()) {
if (other.getServer().isPresent()) {
if (!other.getServer().get().equalsIgnoreCase(this.getServer().get())) {
return false;
}
}
} else {
return false;
}
if (other.getWorld().isPresent() == this.getWorld().isPresent()) {
if (other.getWorld().isPresent()) {
if (!other.getWorld().get().equalsIgnoreCase(this.getWorld().get())) {
return false;
}
}
} else {
return false;
}
return other.getContexts().equals(this.getContexts());
final long otherExpireAt = other.isTemporary() ? other.getExpiryUnixTime() : 0L;
return this.expireAt == otherExpireAt && this.getContexts().equals(other.getContexts());
}
@SuppressWarnings("StringEquality")
@Override
public boolean almostEquals(Node other) {
if (!other.getPermission().equalsIgnoreCase(this.getPermission())) {
return false;
}
if (this.permission != other.getPermission()) return false;
if (this.override != other.isOverride()) return false;
if (other.isTemporary() != this.isTemporary()) {
final String thisServer = this.server;
final String otherServer = other.getServer().orElse(null);
if (thisServer == null ? otherServer != null : !thisServer.equals(otherServer))
return false;
}
if (other.getServer().isPresent() == this.getServer().isPresent()) {
if (other.getServer().isPresent()) {
if (!other.getServer().get().equalsIgnoreCase(this.getServer().get())) {
return false;
}
}
} else {
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());
if (other.getWorld().isPresent() == this.getWorld().isPresent()) {
if (other.getWorld().isPresent()) {
if (!other.getWorld().get().equalsIgnoreCase(this.getWorld().get())) {
return false;
}
}
} else {
return false;
}
return other.getContexts().equals(this.getContexts());
}
@SuppressWarnings("StringEquality")
@Override
public boolean equalsIgnoringValueOrTemp(Node other) {
if (!other.getPermission().equalsIgnoreCase(this.getPermission())) {
return false;
}
if (this.permission != other.getPermission()) return false;
if (this.override != other.isOverride()) return false;
if (other.getServer().isPresent() == this.getServer().isPresent()) {
if (other.getServer().isPresent()) {
if (!other.getServer().get().equalsIgnoreCase(this.getServer().get())) {
return false;
}
}
} else {
final String thisServer = this.server;
final String otherServer = other.getServer().orElse(null);
if (thisServer == null ? otherServer != null : !thisServer.equals(otherServer))
return false;
}
if (other.getWorld().isPresent() == this.getWorld().isPresent()) {
if (other.getWorld().isPresent()) {
if (!other.getWorld().get().equalsIgnoreCase(this.getWorld().get())) {
return false;
}
}
} else {
return false;
}
return other.getContexts().equals(this.getContexts());
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());
}
@Override
@@ -603,39 +589,33 @@ public final class ImmutableNode implements Node {
}
for (String s : expandedThisStr) {
if (p.matcher(s).matches()) {
return true;
}
if (p.matcher(s).matches()) return true;
}
return false;
}
if (thisStr.toLowerCase().startsWith("r=") && applyRegex) {
Pattern p = PatternCache.compile(thisStr.substring(2));
if (p == null) {
return false;
}
if (p == null) return false;
for (String s : expandedStr) {
if (p.matcher(s).matches()) {
return true;
}
if (p.matcher(s).matches()) return true;
}
return false;
}
if (expandedStr.size() <= 1 && expandedThisStr.size() <= 1) {
return false;
}
if (expandedStr.size() <= 1 && expandedThisStr.size() <= 1) return false;
for (String t : expandedThisStr) {
for (String s : expandedStr) {
if (t.equalsIgnoreCase(s)) {
return true;
}
if (t.equalsIgnoreCase(s)) return true;
}
}
return false;
}
private static String internString(String s) {
return s == null ? null : s.intern();
}
}
@@ -82,7 +82,7 @@ class NodeBuilder implements Node.Builder {
NodeBuilder(Node other) {
this.permission = other.getPermission();
this.value = other.getValue();
this.value = other.getValuePrimitive();
this.override = other.isOverride();
this.server = other.getServer().orElse(null);
this.world = other.getWorld().orElse(null);
@@ -151,7 +151,7 @@ public class NodeFactory {
return appendContextToCommand(sb, node).toString();
}
if (node.getValue() && (node.isPrefix() || node.isSuffix())) {
if (node.getValuePrimitive() && (node.isPrefix() || node.isSuffix())) {
ChatMetaType type = node.isPrefix() ? ChatMetaType.PREFIX : ChatMetaType.SUFFIX;
String typeName = type.name().toLowerCase();
@@ -171,7 +171,7 @@ public class NodeFactory {
return appendContextToCommand(sb, node).toString();
}
if (node.getValue() && node.isMeta()) {
if (node.getValuePrimitive() && node.isMeta()) {
sb.append(node.isTemporary() ? (set ? "meta settemp " : "meta unsettemp ") : (set ? "meta set " : "meta unset "));
if (node.getMeta().getKey().contains(" ")) {
@@ -204,7 +204,7 @@ public class NodeFactory {
sb.append(node.getPermission());
}
if (set) {
sb.append(" ").append(node.getValue());
sb.append(" ").append(node.getValuePrimitive());
if (node.isTemporary()) {
sb.append(" ").append(node.getExpiryUnixTime());
@@ -33,6 +33,7 @@ import com.google.common.collect.Multimap;
import me.lucko.luckperms.api.HeldPermission;
import me.lucko.luckperms.api.Node;
import me.lucko.luckperms.api.context.ContextSet;
import java.util.Optional;
import java.util.OptionalLong;
@@ -55,7 +56,7 @@ public final class NodeHeldPermission<T> implements HeldPermission<T> {
@Override
public boolean getValue() {
return node.getValue();
return node.getValuePrimitive();
}
@Override
@@ -78,6 +79,11 @@ public final class NodeHeldPermission<T> implements HeldPermission<T> {
return node.getContexts().toMultimap();
}
@Override
public ContextSet getContexts() {
return node.getContexts();
}
@Override
public Node asNode() {
return node;
@@ -64,7 +64,7 @@ public final class NodeModel {
public static NodeModel fromNode(Node node) {
return NodeModel.of(
node.getPermission(),
node.getValue(),
node.getValuePrimitive(),
node.getServer().orElse("global"),
node.getWorld().orElse("global"),
node.isTemporary() ? node.getExpiryUnixTime() : 0L,
@@ -220,12 +220,14 @@ public interface LuckPermsPlugin {
/**
* Gets the name or "brand" of the running platform
*
* @return the server brand
*/
String getServerName();
/**
* Gets the version of the running platform
*
* @return the server version
*/
String getServerVersion();
@@ -57,7 +57,7 @@ public class ParentsByWeightHolder extends StoredHolder {
List<Group> groups = new ArrayList<>();
for (Node node : user.filterNodes(contextSet)) {
if (!node.getValue() || !node.isGroupNode()) {
if (!node.getValuePrimitive() || !node.isGroupNode()) {
continue;
}
@@ -755,7 +755,7 @@ public class MongoDBBacking extends AbstractBacking {
public static Map<String, Boolean> exportToLegacy(Iterable<Node> nodes) {
Map<String, Boolean> m = new HashMap<>();
for (Node node : nodes) {
m.put(node.toSerializedNode(), node.getValue());
m.put(node.toSerializedNode(), node.getValuePrimitive());
}
return m;
}
@@ -31,8 +31,9 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.api.caching.PermissionData;
import me.lucko.luckperms.common.caching.PermissionCache;
import me.lucko.luckperms.common.utils.PasteUtils;
import me.lucko.luckperms.common.verbose.CheckOrigin;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -194,7 +195,7 @@ public class TreeView {
* @return the url, or null
* @see PasteUtils#paste(String, List)
*/
public String uploadPasteData(String version, String username, PermissionData checker) {
public String uploadPasteData(String version, String username, PermissionCache checker) {
// only paste if there is actually data here
if (!hasData()) {
throw new IllegalStateException();
@@ -213,7 +214,7 @@ public class TreeView {
for (Map.Entry<String, String> e : ret) {
// lookup a permission value for the node
Tristate tristate = checker.getPermissionValue(e.getValue());
Tristate tristate = checker.getPermissionValue(e.getValue(), CheckOrigin.INTERNAL);
// append the data to the paste
builder.add(getTristateDiffPrefix(tristate) + e.getKey() + e.getValue());
@@ -41,7 +41,7 @@ import java.util.concurrent.CompletableFuture;
@UtilityClass
public class LoginHelper {
public static void loadUser(LuckPermsPlugin plugin, UUID u, String username, boolean joinUuidSave) {
public static User loadUser(LuckPermsPlugin plugin, UUID u, String username, boolean joinUuidSave) {
final long startTime = System.currentTimeMillis();
final UuidCache cache = plugin.getUuidCache();
@@ -97,6 +97,8 @@ public class LoginHelper {
if (time >= 1000) {
plugin.getLog().warn("Processing login for " + username + " took " + time + "ms.");
}
return user;
}
public static void refreshPlayer(LuckPermsPlugin plugin, UUID uuid) {
@@ -33,13 +33,18 @@ import net.kyori.text.serializer.ComponentSerializers;
import java.util.Arrays;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@SuppressWarnings("deprecation")
@UtilityClass
public class TextUtils {
public String joinNewline(String... strings) {
return Arrays.stream(strings).collect(Collectors.joining("\n"));
return joinNewline(Arrays.stream(strings));
}
public String joinNewline(Stream<String> strings) {
return strings.collect(Collectors.joining("\n"));
}
public TextComponent fromLegacy(String input, char character) {
@@ -29,6 +29,7 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.api.context.ImmutableContextSet;
/**
* Holds the data from a permission check
@@ -37,11 +38,26 @@ import me.lucko.luckperms.api.Tristate;
@AllArgsConstructor
public class CheckData {
/**
* The origin of the check
*/
private final CheckOrigin checkOrigin;
/**
* The name of the entity which was checked
*/
private final String checkTarget;
/**
* The contexts where the check took place
*/
private final ImmutableContextSet checkContext;
/**
* The stack trace when the check took place
*/
private final StackTraceElement[] checkTrace;
/**
* The permission which was checked for
*/
@@ -0,0 +1,60 @@
/*
* 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.verbose;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* Represents the origin of a permission check
*/
@Getter
@AllArgsConstructor
public enum CheckOrigin {
/**
* Indicates the check was caused by a 'hasPermission' check on the platform
*/
PLATFORM_PERMISSION_CHECK('C'),
/**
* Indicates the check was caused by a 'hasPermissionSet' type check on the platform
*/
PLATFORM_LOOKUP_CHECK('L'),
/**
* Indicates the check was caused by an API call
*/
API('A'),
/**
* Indicates the check was caused by a LuckPerms internal
*/
INTERNAL('I');
private final char code;
}
@@ -28,6 +28,7 @@ package me.lucko.luckperms.common.verbose;
import lombok.Setter;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.api.context.ContextSet;
import me.lucko.luckperms.common.commands.sender.Sender;
import java.util.Map;
@@ -70,18 +71,23 @@ public class VerboseHandler implements Runnable {
* <p>The check data is added to a queue to be processed later, to avoid blocking
* the main thread each time a permission check is made.</p>
*
* @param checkOrigin the origin of the check
* @param checkTarget the target of the permission check
* @param checkContext the contexts where the check occurred
* @param permission the permission which was checked for
* @param result the result of the permission check
*/
public void offerCheckData(String checkTarget, String permission, Tristate result) {
public void offerCheckData(CheckOrigin checkOrigin, String checkTarget, ContextSet checkContext, String permission, Tristate result) {
// don't bother even processing the check if there are no listeners registered
if (!listening) {
return;
}
//noinspection ThrowableNotThrown
StackTraceElement[] trace = new Exception().getStackTrace();
// add the check data to a queue to be processed later.
queue.offer(new CheckData(checkTarget, permission, result));
queue.offer(new CheckData(checkOrigin, checkTarget, checkContext.makeImmutable(), trace, permission, result));
}
/**
@@ -33,9 +33,15 @@ import com.google.common.collect.Maps;
import me.lucko.luckperms.api.Tristate;
import me.lucko.luckperms.common.commands.sender.Sender;
import me.lucko.luckperms.common.commands.utils.Util;
import me.lucko.luckperms.common.constants.Constants;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.utils.DateUtil;
import me.lucko.luckperms.common.utils.PasteUtils;
import me.lucko.luckperms.common.utils.TextUtils;
import net.kyori.text.TextComponent;
import net.kyori.text.event.HoverEvent;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -55,6 +61,9 @@ public class VerboseListener {
// how much data should we store before stopping.
private static final int DATA_TRUNCATION = 10000;
// how many traces should we add
private static final int TRACE_DATA_TRUNCATION = 250;
// the time when the listener was first registered
private final long startTime = System.currentTimeMillis();
@@ -98,7 +107,64 @@ public class VerboseListener {
}
if (notify) {
Message.VERBOSE_LOG.send(notifiedSender, "&a" + data.getCheckTarget() + "&7 -- &a" + data.getPermission() + "&7 -- " + getTristateColor(data.getResult()) + data.getResult().name().toLowerCase() + "");
StringBuilder msgContent = new StringBuilder();
msgContent.append("&a")
.append(data.getCheckTarget());
if (notifiedSender.isConsole()) {
msgContent.append("&7 - &8[&2")
.append(data.getCheckOrigin().getCode())
.append("&8]");
}
msgContent.append("&7 - &a")
.append(data.getPermission())
.append("&7 - ")
.append(getTristateColor(data.getResult()))
.append(data.getResult().name().toLowerCase());
if (notifiedSender.isConsole()) {
msgContent.append("&7 - ").append(Util.contextSetToString(data.getCheckContext()));
}
if (notifiedSender.isConsole()) {
Message.VERBOSE_LOG.send(notifiedSender, msgContent.toString());
} else {
TextComponent textComponent = TextUtils.fromLegacy(Message.VERBOSE_LOG.asString(notifiedSender.getPlatform().getLocaleManager(), msgContent.toString()));
List<String> hover = new ArrayList<>();
hover.add("&bOrigin: &2" + data.getCheckOrigin().name());
hover.add("&bContext: &r" + Util.contextSetToString(data.getCheckContext()));
hover.add("&bTrace: &r");
StackTraceElement[] checkTrace = data.getCheckTrace();
// how many lines have been printed
int count = 0;
// if we're printing elements yet
boolean printing = false;
for (StackTraceElement e : checkTrace) {
// start printing when we escape LP internals code
if (!printing && !e.getClassName().startsWith("me.lucko.luckperms.")) {
printing = true;
}
if (!printing) continue;
if (count >= 15) break;
hover.add("&7" + e.getClassName() + "." + e.getMethodName() + (e.getLineNumber() >= 0 ? ":" + e.getLineNumber() : ""));
count++;
}
if (checkTrace.length > 15) {
int remain = checkTrace.length - 15;
hover.add("&f... and " + remain + " more");
}
HoverEvent e = new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextUtils.fromLegacy(TextUtils.joinNewline(hover.stream()), Constants.FORMAT_CHAR));
TextComponent msg = textComponent.toBuilder().applyDeep(comp -> comp.hoverEvent(e)).build();
notifiedSender.sendMessage(msg);
}
}
}
@@ -108,7 +174,7 @@ public class VerboseListener {
* @return the url
* @see PasteUtils#paste(String, List)
*/
public String uploadPasteData() {
public String uploadPasteData(boolean showTraces) {
long now = System.currentTimeMillis();
String startDate = DATE_FORMAT.format(new Date(startTime));
String endDate = DATE_FORMAT.format(new Date(now));
@@ -132,9 +198,10 @@ public class VerboseListener {
.add("| Start Time | " + startDate + " |")
.add("| End Time | " + endDate + " |")
.add("| Duration | " + duration +" |")
.add("| Count | **" + matchedCounter.get() + "** / " + counter + " |")
.add("| Count | **" + matchedCounter.get() + "** / " + counter.get() + " |")
.add("| User | " + notifiedSender.getName() + " |")
.add("| Filter | " + filter + " |")
.add("| Include traces | " + showTraces + " |")
.add("");
if (matchedCounter.get() > results.size()) {
@@ -142,8 +209,14 @@ public class VerboseListener {
prettyOutput.add("");
}
if (showTraces && results.size() > TRACE_DATA_TRUNCATION) {
prettyOutput.add("**WARN:** Result set exceeded size of " + TRACE_DATA_TRUNCATION + ". The traced output below was truncated to " + TRACE_DATA_TRUNCATION + " entries. ");
prettyOutput.add("Either refine the query using a more specific filter, or disable tracing by adding '--slim' to the end of the paste command.");
prettyOutput.add("");
}
prettyOutput.add("### Output")
.add("Format: `<checked>` `<permission>` `<value>`")
.add("Format: `<checked>` `<origin>` `<permission>` `<value>`")
.add("")
.add("___")
.add("");
@@ -151,8 +224,49 @@ public class VerboseListener {
ImmutableList.Builder<String> csvOutput = ImmutableList.<String>builder()
.add("User,Permission,Result");
AtomicInteger printedCount = new AtomicInteger(0);
results.forEach(c -> {
prettyOutput.add("`" + c.getCheckTarget() + "` - " + c.getPermission() + " - **" + c.getResult().toString() + "** ");
if (!showTraces) {
prettyOutput.add("`" + c.getCheckTarget() + "` - " + c.getPermission() + " - " + getTristateSymbol(c.getResult()) + " ");
} else if (printedCount.incrementAndGet() > TRACE_DATA_TRUNCATION) {
prettyOutput.add("<br><code>" + c.getCheckTarget() + "</code> - " + c.getPermission() + " - " + getTristateSymbol(c.getResult()));
} else {
prettyOutput.add("<details><summary><code>" + c.getCheckTarget() + "</code> - " + c.getPermission() + " - " + getTristateSymbol(c.getResult()) + "</summary><p>");
prettyOutput.add("<br><b>Origin:</b> <code>" + c.getCheckOrigin().name() + "</code>");
prettyOutput.add("<br><b>Context:</b> <code>" + Util.stripColor(Util.contextSetToString(c.getCheckContext())) + "</code>");
prettyOutput.add("<br><b>Trace:</b><pre>");
// add trace
StackTraceElement[] checkTrace = c.getCheckTrace();
// how many lines have been printed
int count = 0;
// if we're printing elements yet
boolean printing = false;
for (StackTraceElement e : checkTrace) {
// start printing when we escape LP internals code
if (!printing && !e.getClassName().startsWith("me.lucko.luckperms.")) {
printing = true;
}
if (!printing) continue;
if (count >= 30) break;
prettyOutput.add(e.getClassName() + "." + e.getMethodName() + (e.getLineNumber() >= 0 ? ":" + e.getLineNumber() : ""));
count++;
}
if (checkTrace.length > 30) {
int remain = checkTrace.length - 30;
prettyOutput.add("... and " + remain + " more");
}
prettyOutput.add("</pre></p></details>");
}
csvOutput.add(escapeCommas(c.getCheckTarget()) + "," + escapeCommas(c.getPermission()) + "," + c.getResult().name().toLowerCase());
});
results.clear();
@@ -180,4 +294,15 @@ public class VerboseListener {
}
}
private static String getTristateSymbol(Tristate tristate) {
switch (tristate) {
case TRUE:
return "✔️";
case FALSE:
return "";
default:
return "";
}
}
}