55import java .util .Map .Entry ;
66import java .util .Set ;
77import java .util .UUID ;
8- import java .util .concurrent .TimeUnit ;
98
109import org .bukkit .entity .Player ;
1110import org .bukkit .permissions .PermissionAttachment ;
@@ -70,7 +69,7 @@ public PlayerPermissionHandler(UUID uuid, PermissionAttachment attachment, Permi
7069 * @param duration How long the permission should last
7170 * @return this
7271 */
73- public PlayerPermissionHandler addExpiration (String perm , ParsedDuration duration ) {
72+ public synchronized PlayerPermissionHandler addExpiration (String perm , ParsedDuration duration ) {
7473 if (duration == null || duration .isEmpty ()) {
7574 return addPerm (perm );
7675 }
@@ -87,7 +86,7 @@ public PlayerPermissionHandler addExpiration(String perm, ParsedDuration duratio
8786 }
8887
8988 long delayMillis = duration .delayMillisFromNow ();
90- handler .getTimer (). schedule (() -> removePermission ( perm ), delayMillis , TimeUnit . MILLISECONDS );
89+ handler .scheduleExpiration ( this , perm , expireAt , delayMillis );
9190
9291 return this ;
9392 }
@@ -106,7 +105,7 @@ public PlayerPermissionHandler addExpiration(String perm, long seconds) {
106105 * @param duration Duration; empty means permanent
107106 * @return this
108107 */
109- public PlayerPermissionHandler addOfflinePerm (String perm , ParsedDuration duration ) {
108+ public synchronized PlayerPermissionHandler addOfflinePerm (String perm , ParsedDuration duration ) {
110109 if (permsToAdd == null ) {
111110 permsToAdd = new HashMap <>();
112111 }
@@ -128,7 +127,7 @@ public PlayerPermissionHandler addOfflinePerm(String perm, long seconds) {
128127 * @param perm Permission node
129128 * @return this
130129 */
131- public PlayerPermissionHandler addPerm (String perm ) {
130+ public synchronized PlayerPermissionHandler addPerm (String perm ) {
132131 persistentPermissions .add (perm );
133132
134133 if (attachment != null ) {
@@ -141,7 +140,7 @@ public PlayerPermissionHandler addPerm(String perm) {
141140 /**
142141 * Re-applies stored permissions after an attachment is created on login.
143142 */
144- public void onLogin (Player player ) {
143+ public synchronized void onLogin (Player player ) {
145144 if (attachment == null ) {
146145 return ;
147146 }
@@ -184,10 +183,43 @@ public void onLogout(Player player) {
184183 // intentionally empty
185184 }
186185
186+
187+ /** True only while this exact scheduled expiration still owns the timed grant. */
188+ synchronized boolean isExpirationCurrent (String perm , long expectedExpireAt ) {
189+ if (timedPermissions == null ) return false ;
190+ Long current = timedPermissions .get (perm );
191+ return current != null && current .longValue () == expectedExpireAt ;
192+ }
193+
194+ /**
195+ * Expire only the scheduled grant that still owns this permission. The
196+ * attachment flag is true only from the player's owning platform scheduler.
197+ */
198+ synchronized void expirePermission (String perm , long expectedExpireAt , boolean updateAttachment ) {
199+ if (timedPermissions == null ) return ;
200+ Long current = timedPermissions .get (perm );
201+ if (current == null || current .longValue () != expectedExpireAt ) return ;
202+ if (!timedPermissions .remove (perm , current )) return ;
203+ persistentPermissions .remove (perm );
204+ if (permsToAdd != null ) permsToAdd .remove (perm );
205+ if (updateAttachment && attachment != null ) {
206+ attachment .setPermission (perm , false );
207+ attachment .getPermissions ().remove (perm );
208+ }
209+ removeHandlerIfEmpty ();
210+ }
211+
212+ private void removeHandlerIfEmpty () {
213+ boolean noTracked = persistentPermissions .isEmpty ()
214+ && (timedPermissions == null || timedPermissions .isEmpty ())
215+ && (permsToAdd == null || permsToAdd .isEmpty ());
216+ if (noTracked && (attachment == null || attachment .getPermissions ().isEmpty ())) handler .removePermission (uuid );
217+ }
218+
187219 /**
188220 * Removes a permission from both internal tracking and the live attachment (if online).
189221 */
190- public void removePermission (String perm ) {
222+ public synchronized void removePermission (String perm ) {
191223 persistentPermissions .remove (perm );
192224 if (timedPermissions != null ) {
193225 timedPermissions .remove (perm );
@@ -201,13 +233,6 @@ public void removePermission(String perm) {
201233 attachment .getPermissions ().remove (perm );
202234 }
203235
204- // If nothing tracked and attachment has nothing, drop handler state entirely
205- boolean noTracked = persistentPermissions .isEmpty ()
206- && (timedPermissions == null || timedPermissions .isEmpty ())
207- && (permsToAdd == null || permsToAdd .isEmpty ());
208-
209- if (noTracked && attachment != null && attachment .getPermissions ().isEmpty ()) {
210- handler .removePermission (uuid );
211- }
236+ removeHandlerIfEmpty ();
212237 }
213238}
0 commit comments