/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * 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 com.jcwhatever.nucleus.internal.providers.permissions.bukkit; import com.jcwhatever.nucleus.internal.NucMsg; import com.jcwhatever.nucleus.utils.BatchTracker; import com.jcwhatever.nucleus.managed.reflection.Reflection; import com.jcwhatever.nucleus.utils.PreCon; import org.bukkit.Bukkit; import org.bukkit.permissions.Permission; import org.bukkit.plugin.PluginManager; import java.lang.reflect.Field; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * An extended implementation of {@link BatchTracker} that aids in modifying Bukkit permissions * without triggering costly recalculations. * * <p>Normally modifies permissions using the Bukkit API. Use {@link BatchTracker#start} to start * a batch operation which prevents recalculations until all batch operations are completed.</p> */ public final class FastPermissions extends BatchTracker { private Set<Permission> _toRecalculate; private Set<Permission> _dontRecalculate; private Map<String, Permission> _permissions; /** * Constructor. */ public FastPermissions() { try { loadPermissionsMap(); } catch (NoSuchFieldException | IllegalAccessException e) { NucMsg.warning("Failed to access Bukkits permissions map for fast permission insertion. " + "Normal insertion will be used instead."); } } /** * Add a permission without causing a permissions recalculation. * * <p>If there is at least 1 batch operation running, the permission will be added * without causing a permissions recalculation.</p> * * <p>If there was a problem loading Bukkits plugin manager's permission map via * reflection, the normal method via the Bukkit API is always used.</p> * * @param permission The permission to add. */ public void addPermission(Permission permission) { PreCon.notNull(permission); // make sure a batch operation is in progress. if (_permissions == null || !isRunning()) { if (Bukkit.getPluginManager().getPermission(permission.getName()) != null) return; Bukkit.getPluginManager().addPermission(permission); return; } if (_permissions.containsKey(permission.getName().toLowerCase())) return; loadSets(); _permissions.put(permission.getName().toLowerCase(), permission); // add permission to the collection of permissions that need to be recalculated if (!_dontRecalculate.contains(permission)) _toRecalculate.add(permission); } /** * Add a parent permission to a child permission. * * <p>If there is at least 1 batch operation running, the permission will be modified * without causing a permissions recalculation.</p> * * <p>If there was a problem loading Bukkits plugin manager's permission map via * reflection, the normal method via the Bukkit API is always used.</p> * * @param child The child permission to add the parent to. * @param parent The parent permission. * @param value The permission value. */ public void addParent(Permission child, Permission parent, boolean value) { PreCon.notNull(child); PreCon.notNull(parent); if (parent.getChildren().containsKey(child.getName().toLowerCase())) return; if (_permissions == null || !isRunning()) { child.addParent(parent, value); return; } loadSets(); parent.getChildren().put(child.getName().toLowerCase(), value); if (!_dontRecalculate.contains(parent)) _toRecalculate.add(parent); _toRecalculate.remove(child); _dontRecalculate.add(child); } @Override public int end() { int count = super.end(); if (count == 0) recalculate(); return count; } /* * Recalculate all permissions added or modified by this since the * last time recalculate was invoked. */ private void recalculate() { if (_toRecalculate == null) return; for (Permission permission : _toRecalculate) { Bukkit.getPluginManager().recalculatePermissionDefaults(permission); permission.recalculatePermissibles(); } _toRecalculate = null; _dontRecalculate = null; } private void loadSets() { if (_toRecalculate != null) return; _toRecalculate = new HashSet<>(200); _dontRecalculate = new HashSet<>(200); } private boolean loadPermissionsMap() throws NoSuchFieldException, IllegalAccessException { PluginManager pm = Bukkit.getPluginManager(); Field field = pm.getClass().getDeclaredField("permissions"); if (!Reflection.removeFinal(field)) return false; @SuppressWarnings("unchecked") Map<String, Permission> permissions = (Map<String, Permission>) field.get(pm); _permissions = permissions; return true; } }