diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/calculator/ChildProcessor.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/calculator/ChildProcessor.java index 3b34141d..ad01a546 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/calculator/ChildProcessor.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/calculator/ChildProcessor.java @@ -61,4 +61,9 @@ public class ChildProcessor extends AbstractPermissionProcessor implements Permi } this.childPermissions = builder; } + + @Override + public void invalidate() { + refresh(); + } } diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/inject/server/LPDefaultsMap.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/inject/server/LPDefaultsMap.java index a5964e4c..0e05f559 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/inject/server/LPDefaultsMap.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/inject/server/LPDefaultsMap.java @@ -95,6 +95,8 @@ public final class LPDefaultsMap implements Map> { 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> { invalidate(this.op); return ret; } + + @Override + public boolean remove(@NonNull Object object) { + boolean ret = super.remove(object); + invalidate(this.op); + return ret; + } } } diff --git a/bukkit/src/main/java/me/lucko/luckperms/bukkit/inject/server/LPPermissionMap.java b/bukkit/src/main/java/me/lucko/luckperms/bukkit/inject/server/LPPermissionMap.java index d244a6ca..b0ca50f8 100644 --- a/bukkit/src/main/java/me/lucko/luckperms/bukkit/inject/server/LPPermissionMap.java +++ b/bukkit/src/main/java/me/lucko/luckperms/bukkit/inject/server/LPPermissionMap.java @@ -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 { + 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 delegate = new ConcurrentHashMap<>(); @@ -81,6 +94,8 @@ public final class LPPermissionMap extends ForwardingMap { 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 { 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 { } } - @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 { } } + private Permission inject(Permission permission) { + if (permission == null) { + return null; + } + + try { + //noinspection unchecked + Map children = (Map) 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 children = (Map) 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 { + private final Map delegate; + + PermissionNotifyingChildrenMap(Map delegate) { + this.delegate = delegate; + } + + @Override + protected Map 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 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(); + } + } + } diff --git a/common/src/main/java/me/lucko/luckperms/common/calculator/PermissionCalculator.java b/common/src/main/java/me/lucko/luckperms/common/calculator/PermissionCalculator.java index ec7e0142..30b3b7fa 100644 --- a/common/src/main/java/me/lucko/luckperms/common/calculator/PermissionCalculator.java +++ b/common/src/main/java/me/lucko/luckperms/common/calculator/PermissionCalculator.java @@ -130,6 +130,9 @@ public class PermissionCalculator implements Function { } public void invalidateCache() { + for (PermissionProcessor processor : this.processors) { + processor.invalidate(); + } this.lookupCache.clear(); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/calculator/processor/PermissionProcessor.java b/common/src/main/java/me/lucko/luckperms/common/calculator/processor/PermissionProcessor.java index ee184548..a91ae790 100644 --- a/common/src/main/java/me/lucko/luckperms/common/calculator/processor/PermissionProcessor.java +++ b/common/src/main/java/me/lucko/luckperms/common/calculator/processor/PermissionProcessor.java @@ -62,4 +62,11 @@ public interface PermissionProcessor { } + /** + * Called after the parent calculator has been invalidated + */ + default void invalidate() { + + } + } diff --git a/common/src/main/java/me/lucko/luckperms/common/model/manager/group/AbstractGroupManager.java b/common/src/main/java/me/lucko/luckperms/common/model/manager/group/AbstractGroupManager.java index b462fa3d..6e1bb951 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/manager/group/AbstractGroupManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/manager/group/AbstractGroupManager.java @@ -69,4 +69,9 @@ public abstract class AbstractGroupManager extends AbstractMana public void invalidateAllGroupCaches() { getAll().values().forEach(PermissionHolder::invalidateCachedData); } + + @Override + public void invalidateAllPermissionCalculators() { + getAll().values().forEach(p -> p.getCachedData().invalidatePermissionCalculators()); + } } \ No newline at end of file diff --git a/common/src/main/java/me/lucko/luckperms/common/model/manager/group/GroupManager.java b/common/src/main/java/me/lucko/luckperms/common/model/manager/group/GroupManager.java index 8d91608f..62ee024e 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/manager/group/GroupManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/manager/group/GroupManager.java @@ -25,6 +25,7 @@ package me.lucko.luckperms.common.model.manager.group; +import me.lucko.luckperms.common.calculator.PermissionCalculator; import me.lucko.luckperms.common.model.Group; import me.lucko.luckperms.common.model.manager.Manager; @@ -43,4 +44,9 @@ public interface GroupManager extends Manager */ void invalidateAllGroupCaches(); + /** + * Invalidates the {@link PermissionCalculator}s for *loaded* groups. + */ + void invalidateAllPermissionCalculators(); + } diff --git a/common/src/main/java/me/lucko/luckperms/common/model/manager/user/AbstractUserManager.java b/common/src/main/java/me/lucko/luckperms/common/model/manager/user/AbstractUserManager.java index 33ebeddc..1b21e71e 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/manager/user/AbstractUserManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/manager/user/AbstractUserManager.java @@ -162,6 +162,11 @@ public abstract class AbstractUserManager extends AbstractManage getAll().values().forEach(PermissionHolder::invalidateCachedData); } + @Override + public void invalidateAllPermissionCalculators() { + getAll().values().forEach(p -> p.getCachedData().invalidatePermissionCalculators()); + } + /** * Check whether the user's state indicates that they should be persisted to storage. * diff --git a/common/src/main/java/me/lucko/luckperms/common/model/manager/user/UserManager.java b/common/src/main/java/me/lucko/luckperms/common/model/manager/user/UserManager.java index 901d67b9..147dd1c2 100644 --- a/common/src/main/java/me/lucko/luckperms/common/model/manager/user/UserManager.java +++ b/common/src/main/java/me/lucko/luckperms/common/model/manager/user/UserManager.java @@ -25,6 +25,7 @@ package me.lucko.luckperms.common.model.manager.user; +import me.lucko.luckperms.common.calculator.PermissionCalculator; import me.lucko.luckperms.common.model.User; import me.lucko.luckperms.common.model.UserIdentifier; import me.lucko.luckperms.common.model.manager.Manager; @@ -89,4 +90,9 @@ public interface UserManager extends Manager> { diff --git a/nukkit/src/main/java/me/lucko/luckperms/nukkit/inject/server/LPPermissionMap.java b/nukkit/src/main/java/me/lucko/luckperms/nukkit/inject/server/LPPermissionMap.java index d9607f7d..bbca1be4 100644 --- a/nukkit/src/main/java/me/lucko/luckperms/nukkit/inject/server/LPPermissionMap.java +++ b/nukkit/src/main/java/me/lucko/luckperms/nukkit/inject/server/LPPermissionMap.java @@ -33,13 +33,16 @@ import me.lucko.luckperms.common.treeview.PermissionRegistry; import me.lucko.luckperms.common.util.LoadingMap; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import cn.nukkit.permission.Permission; import cn.nukkit.plugin.PluginManager; +import java.lang.reflect.Field; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; @@ -56,6 +59,17 @@ import java.util.function.Function; */ public final class LPPermissionMap extends ForwardingMap { + 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 delegate = new ConcurrentHashMap<>(); @@ -80,6 +94,8 @@ public final class LPPermissionMap extends ForwardingMap { private void update() { this.trueChildPermissions.clear(); this.falseChildPermissions.clear(); + this.plugin.getUserManager().invalidateAllPermissionCalculators(); + this.plugin.getGroupManager().invalidateAllPermissionCalculators(); } @Override @@ -89,8 +105,11 @@ public final class LPPermissionMap extends ForwardingMap { @Override public Permission put(@NonNull String key, @NonNull Permission value) { + Objects.requireNonNull(key, "key"); + 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; } @@ -103,11 +122,36 @@ public final class LPPermissionMap extends ForwardingMap { } @Override - public Permission putIfAbsent(String key, Permission value) { - this.plugin.getPermissionRegistry().insert(key); - Permission ret = super.putIfAbsent(key, value); - update(); - return ret; + public Permission remove(@Nullable Object object) { + if (object == null) { + return null; + } + return uninject(super.remove(object)); + } + + @Override + public boolean remove(Object key, Object 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); + } + + @Override + public boolean containsValue(@Nullable Object value) { + return value != null && super.containsValue(value); + } + + @Override + public Permission get(@Nullable Object key) { + if (key == null) { + return null; + } + return super.get(key); } private final class ChildPermissionResolver implements Function> { @@ -146,4 +190,81 @@ public final class LPPermissionMap extends ForwardingMap { } } + private Permission inject(Permission permission) { + if (permission == null) { + return null; + } + + try { + //noinspection unchecked + Map children = (Map) 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 children = (Map) 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 { + private final Map delegate; + + PermissionNotifyingChildrenMap(Map delegate) { + this.delegate = delegate; + } + + @Override + protected Map 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 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(); + } + } + }