Ensure caches are invalidated when Bukkit/Nukkit's Permission#getChildren map is modified (#1378)

This commit is contained in:
Luck
2019-01-13 16:58:31 +00:00
Unverified
parent 78a74510cf
commit 3c0d1ba7a1
12 changed files with 285 additions and 23 deletions
@@ -61,4 +61,9 @@ public class ChildProcessor extends AbstractPermissionProcessor implements Permi
}
this.childPermissions = builder;
}
@Override
public void invalidate() {
refresh();
}
}
@@ -95,6 +95,8 @@ public final class LPDefaultsMap implements Map<Boolean, Set<Permission>> {
private void invalidate(boolean op) {
getCache(op).invalidate();
this.plugin.getUserManager().invalidateAllPermissionCalculators();
this.plugin.getGroupManager().invalidateAllPermissionCalculators();
}
/**
@@ -176,6 +178,13 @@ public final class LPDefaultsMap implements Map<Boolean, Set<Permission>> {
invalidate(this.op);
return ret;
}
@Override
public boolean remove(@NonNull Object object) {
boolean ret = super.remove(object);
invalidate(this.op);
return ret;
}
}
}
@@ -37,6 +37,7 @@ import org.bukkit.plugin.PluginManager;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -49,7 +50,8 @@ import java.util.function.Function;
*
* This instance allows LuckPerms to intercept calls to
* {@link PluginManager#addPermission(Permission)} and record permissions in the
* {@link PermissionRegistry}.
* {@link PermissionRegistry}. It also lets us monitor changes to child permission
* relationships.
*
* It also allows us to pre-determine child permission relationships.
*
@@ -57,6 +59,17 @@ import java.util.function.Function;
*/
public final class LPPermissionMap extends ForwardingMap<String, Permission> {
private static final Field PERMISSION_CHILDREN_FIELD;
static {
try {
PERMISSION_CHILDREN_FIELD = Permission.class.getDeclaredField("children");
PERMISSION_CHILDREN_FIELD.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new ExceptionInInitializerError(e);
}
}
// Uses perm.getName().toLowerCase(java.util.Locale.ENGLISH); to determine the key
private final Map<String, Permission> delegate = new ConcurrentHashMap<>();
@@ -81,6 +94,8 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
private void update() {
this.trueChildPermissions.clear();
this.falseChildPermissions.clear();
this.plugin.getUserManager().invalidateAllPermissionCalculators();
this.plugin.getGroupManager().invalidateAllPermissionCalculators();
}
@Override
@@ -94,7 +109,7 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
Objects.requireNonNull(value, "value");
this.plugin.getPermissionRegistry().insert(key);
Permission ret = super.put(key, value);
Permission ret = super.put(key, inject(value));
update();
return ret;
}
@@ -106,32 +121,21 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
}
}
@Override
public Permission putIfAbsent(String key, Permission value) {
Objects.requireNonNull(key, "key");
Objects.requireNonNull(value, "value");
this.plugin.getPermissionRegistry().insert(key);
Permission ret = super.putIfAbsent(key, value);
update();
return ret;
}
// null-safe - the plugin manager uses hashmap
@Override
public Permission remove(@Nullable Object object) {
if (object == null) {
return null;
}
return super.remove(object);
return uninject(super.remove(object));
}
@Override
public boolean remove(Object key, Object value) {
return key != null && value != null && super.remove(key, value);
return key != null && value != null && super.remove(key, uninject(((Permission) value)));
}
// check for null
@Override
public boolean containsKey(@Nullable Object key) {
return key != null && super.containsKey(key);
@@ -186,4 +190,81 @@ public final class LPPermissionMap extends ForwardingMap<String, Permission> {
}
}
private Permission inject(Permission permission) {
if (permission == null) {
return null;
}
try {
//noinspection unchecked
Map<String, Boolean> children = (Map<String, Boolean>) PERMISSION_CHILDREN_FIELD.get(permission);
while (children instanceof PermissionNotifyingChildrenMap) {
children = ((PermissionNotifyingChildrenMap) children).delegate;
}
PermissionNotifyingChildrenMap notifyingChildren = new PermissionNotifyingChildrenMap(children);
PERMISSION_CHILDREN_FIELD.set(permission, notifyingChildren);
} catch (Exception e) {
e.printStackTrace();
}
return permission;
}
private Permission uninject(Permission permission) {
if (permission == null) {
return null;
}
try {
//noinspection unchecked
Map<String, Boolean> children = (Map<String, Boolean>) PERMISSION_CHILDREN_FIELD.get(permission);
while (children instanceof PermissionNotifyingChildrenMap) {
children = ((PermissionNotifyingChildrenMap) children).delegate;
}
PERMISSION_CHILDREN_FIELD.set(permission, children);
} catch (Exception e) {
e.printStackTrace();
}
return permission;
}
private final class PermissionNotifyingChildrenMap extends ForwardingMap<String, Boolean> {
private final Map<String, Boolean> delegate;
PermissionNotifyingChildrenMap(Map<String, Boolean> delegate) {
this.delegate = delegate;
}
@Override
protected Map<String, Boolean> delegate() {
return this.delegate;
}
@Override
public Boolean put(@NonNull String key, @NonNull Boolean value) {
Boolean ret = super.put(key, value);
LPPermissionMap.this.update();
return ret;
}
@Override
public void putAll(@NonNull Map<? extends String, ? extends Boolean> map) {
super.putAll(map);
LPPermissionMap.this.update();
}
@Override
public Boolean remove(@NonNull Object object) {
Boolean ret = super.remove(object);
LPPermissionMap.this.update();
return ret;
}
@Override
public void clear() {
super.clear();
LPPermissionMap.this.update();
}
}
}