001/* 002 * PlotSquared, a land and world management plugin for Minecraft. 003 * Copyright (C) IntellectualSites <https://intellectualsites.com> 004 * Copyright (C) IntellectualSites team and contributors 005 * 006 * This program is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program. If not, see <https://www.gnu.org/licenses/>. 018 */ 019package com.plotsquared.core.plot.flag; 020 021import com.google.common.base.Preconditions; 022import com.google.common.collect.ImmutableMap; 023import com.intellectualsites.annotations.NotPublic; 024import com.plotsquared.core.configuration.caption.CaptionUtility; 025import org.apache.logging.log4j.LogManager; 026import org.apache.logging.log4j.Logger; 027import org.checkerframework.checker.nullness.qual.NonNull; 028import org.checkerframework.checker.nullness.qual.Nullable; 029 030import java.util.Collection; 031import java.util.HashMap; 032import java.util.HashSet; 033import java.util.Locale; 034import java.util.Map; 035 036/** 037 * Container type for {@link PlotFlag plot flags}. 038 */ 039public class FlagContainer { 040 041 private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + FlagContainer.class.getSimpleName()); 042 043 private final Map<String, String> unknownFlags = new HashMap<>(); 044 private final Map<Class<?>, PlotFlag<?, ?>> flagMap = new HashMap<>(); 045 private final PlotFlagUpdateHandler plotFlagUpdateHandler; 046 private final Collection<PlotFlagUpdateHandler> updateSubscribers = new HashSet<>(); 047 private final PlotFlagUpdateHandler unknownsRef; 048 private FlagContainer parentContainer; 049 050 /** 051 * Construct a new flag container with an optional parent container and update handler. 052 * Default values are inherited from the parent container. At the top 053 * of the parent-child hierarchy must be the {@link GlobalFlagContainer} 054 * (or an equivalent top level flag container). 055 * 056 * @param parentContainer Parent container. The top level flag container should not have a parent, 057 * and can set this parameter to null. If this is not a top level 058 * flag container, the parent should not be null. 059 * @param plotFlagUpdateHandler Event handler that will be called whenever a plot flag is 060 * added, removed or updated in this flag container. 061 */ 062 public FlagContainer( 063 final @Nullable FlagContainer parentContainer, 064 @Nullable PlotFlagUpdateHandler plotFlagUpdateHandler 065 ) { 066 this.parentContainer = parentContainer; 067 this.plotFlagUpdateHandler = plotFlagUpdateHandler; 068 if (!(this instanceof GlobalFlagContainer)) { 069 this.unknownsRef = this::handleUnknowns; 070 GlobalFlagContainer.getInstance().subscribe(this.unknownsRef); 071 } else { 072 this.unknownsRef = null; 073 } 074 } 075 076 /** 077 * Construct a new flag container with an optional parent container. 078 * Default values are inherited from the parent container. At the top 079 * of the parent-child hierarchy must be the {@link GlobalFlagContainer} 080 * (or an equivalent top level flag container). 081 * 082 * @param parentContainer Parent container. The top level flag container should not have a parent, 083 * and can set this parameter to null. If this is not a top level 084 * flag container, the parent should not be null. 085 */ 086 public FlagContainer(final @Nullable FlagContainer parentContainer) { 087 this(parentContainer, null); 088 } 089 090 /** 091 * Cast a plot flag with wildcard parameters into a parametrized 092 * PlotFlag. This is an unsafe operation, and should only be performed 093 * if the generic parameters are known beforehand. 094 * 095 * @param flag Flag instance 096 * @param <V> Flag value type 097 * @param <T> Flag type 098 * @return Casted flag 099 */ 100 @SuppressWarnings("unchecked") 101 public static <V, T extends PlotFlag<V, ?>> T castUnsafe( 102 final PlotFlag<?, ?> flag 103 ) { 104 return (T) flag; 105 } 106 107 /** 108 * Return the parent container (if the container has a parent) 109 * 110 * @return Parent container, if it exists 111 */ 112 public @Nullable FlagContainer getParentContainer() { 113 return this.parentContainer; 114 } 115 116 public void setParentContainer(FlagContainer parentContainer) { 117 this.parentContainer = parentContainer; 118 } 119 120 @SuppressWarnings("unused") 121 protected Map<Class<?>, PlotFlag<?, ?>> getInternalPlotFlagMap() { 122 return this.flagMap; 123 } 124 125 /** 126 * Get an immutable view of the underlying flag map 127 * 128 * @return Immutable flag map 129 */ 130 public Map<Class<?>, PlotFlag<?, ?>> getFlagMap() { 131 return ImmutableMap.<Class<?>, PlotFlag<?, ?>>builder().putAll(this.flagMap).build(); 132 } 133 134 /** 135 * Add a flag to the container 136 * 137 * <p> 138 * Use {@link #addAll(Collection)} to add multiple flags. 139 * </p> 140 * 141 * @param flag Flag to add 142 * @param <T> flag type 143 * @param <V> flag value type 144 */ 145 public <V, T extends PlotFlag<V, ?>> void addFlag(final T flag) { 146 try { 147 Preconditions.checkState( 148 flag.getName().length() <= 64, 149 "flag name may not be more than 64 characters. Check: " + flag.getName() 150 ); 151 final PlotFlag<?, ?> oldInstance = this.flagMap.put(flag.getClass(), flag); 152 final PlotFlagUpdateType plotFlagUpdateType; 153 if (oldInstance != null) { 154 plotFlagUpdateType = PlotFlagUpdateType.FLAG_UPDATED; 155 } else { 156 plotFlagUpdateType = PlotFlagUpdateType.FLAG_ADDED; 157 } 158 if (this.plotFlagUpdateHandler != null) { 159 this.plotFlagUpdateHandler.handle(flag, plotFlagUpdateType); 160 } 161 this.updateSubscribers 162 .forEach(subscriber -> subscriber.handle(flag, plotFlagUpdateType)); 163 } catch (IllegalStateException e) { 164 LOGGER.info("Flag {} (class '{}') could not be added to the container because the " 165 + "flag name exceeded the allowed limit of 64 characters. Please tell the developer " 166 + "of the flag to fix this.", flag.getName(), flag.getClass().getName()); 167 e.printStackTrace(); 168 } 169 } 170 171 /** 172 * Remove a flag from the container 173 * 174 * @param flag Flag to remove 175 * @param <T> flag type 176 * @param <V> flag value type 177 * @return value of flag removed 178 */ 179 @SuppressWarnings("unchecked") 180 public <V, T extends PlotFlag<V, ?>> V removeFlag(final T flag) { 181 final Object value = this.flagMap.remove(flag.getClass()); 182 if (this.plotFlagUpdateHandler != null) { 183 this.plotFlagUpdateHandler.handle(flag, PlotFlagUpdateType.FLAG_REMOVED); 184 } 185 this.updateSubscribers 186 .forEach(subscriber -> subscriber.handle(flag, PlotFlagUpdateType.FLAG_REMOVED)); 187 if (value == null) { 188 return null; 189 } else { 190 return (V) value; 191 } 192 } 193 194 /** 195 * Add all flags to the container 196 * 197 * <p> 198 * Use {@link #addFlag(PlotFlag)} to add a single flag. 199 * </p> 200 * 201 * @param flags Flags to add 202 */ 203 public void addAll(final Collection<PlotFlag<?, ?>> flags) { 204 for (final PlotFlag<?, ?> flag : flags) { 205 this.addFlag(flag); 206 } 207 } 208 209 public void addAll(final FlagContainer container) { 210 this.addAll(container.flagMap.values()); 211 } 212 213 /** 214 * Clears the local flag map 215 */ 216 public void clearLocal() { 217 this.flagMap.clear(); 218 } 219 220 /** 221 * Get a collection of all recognized plot flags. Will by 222 * default use the values contained in {@link GlobalFlagContainer}. 223 * 224 * @return All recognized flag types 225 */ 226 public Collection<PlotFlag<?, ?>> getRecognizedPlotFlags() { 227 return this.getHighestClassContainer().getFlagMap().values(); 228 } 229 230 /** 231 * Recursively seek for the highest order flag container. 232 * This will by default return {@link GlobalFlagContainer}. 233 * 234 * @return Highest order class container. 235 */ 236 public final FlagContainer getHighestClassContainer() { 237 if (this.getParentContainer() != null) { 238 return this.getParentContainer(); 239 } 240 return this; 241 } 242 243 /** 244 * Has the same functionality as {@link #getFlag(Class)}, but 245 * with wildcard generic types. 246 * 247 * @param flagClass The {@link PlotFlag} class. 248 * @return the plot flag 249 */ 250 public PlotFlag<?, ?> getFlagErased(Class<?> flagClass) { 251 final PlotFlag<?, ?> flag = this.flagMap.get(flagClass); 252 if (flag != null) { 253 return flag; 254 } else { 255 if (getParentContainer() != null) { 256 return getParentContainer().getFlagErased(flagClass); 257 } 258 } 259 return null; 260 } 261 262 /** 263 * Query all levels of flag containers for a flag. This guarantees that a flag 264 * instance is returned, as long as it is registered in the 265 * {@link GlobalFlagContainer global flag container}. 266 * 267 * @param flagClass Flag class to query for 268 * @param <V> Flag value type 269 * @param <T> Flag type 270 * @return Flag instance 271 */ 272 public <V, T extends PlotFlag<V, ?>> T getFlag(final Class<? extends T> flagClass) { 273 final PlotFlag<?, ?> flag = this.flagMap.get(flagClass); 274 if (flag != null) { 275 return castUnsafe(flag); 276 } else { 277 if (getParentContainer() != null) { 278 return getParentContainer().getFlag(flagClass); 279 } 280 } 281 return null; 282 } 283 284 /** 285 * Check for flag existence in this flag container instance. 286 * 287 * @param flagClass Flag class to query for 288 * @param <V> Flag value type 289 * @param <T> Flag type 290 * @return The flag instance, if it exists in this container, else null. 291 */ 292 public @Nullable <V, T extends PlotFlag<V, ?>> T queryLocal(final Class<?> flagClass) { 293 final PlotFlag<?, ?> localFlag = this.flagMap.get(flagClass); 294 if (localFlag == null) { 295 return null; 296 } else { 297 return castUnsafe(localFlag); 298 } 299 } 300 301 /** 302 * Subscribe to flag updates in this particular flag container instance. 303 * Updates are: a flag being removed, a flag being added or a flag 304 * being updated. 305 * 306 * <p> 307 * Use {@link PlotFlagUpdateType} to see the update types available. 308 * </p> 309 * 310 * @param plotFlagUpdateHandler The update handler which will react to changes. 311 */ 312 public void subscribe(final @NonNull PlotFlagUpdateHandler plotFlagUpdateHandler) { 313 this.updateSubscribers.add(plotFlagUpdateHandler); 314 } 315 316 private void handleUnknowns( 317 final PlotFlag<?, ?> flag, 318 final PlotFlagUpdateType plotFlagUpdateType 319 ) { 320 if (plotFlagUpdateType != PlotFlagUpdateType.FLAG_REMOVED && this.unknownFlags 321 .containsKey(flag.getName())) { 322 String value = this.unknownFlags.remove(flag.getName()); 323 if (value != null) { 324 value = CaptionUtility.stripClickEvents(flag, value); 325 try { 326 this.addFlag(flag.parse(value)); 327 } catch (final Exception ignored) { 328 } 329 } 330 } 331 } 332 333 /** 334 * Register a flag key-value pair which cannot yet be associated with 335 * an existing flag instance (such as when third party flag values are 336 * loaded before the flag type has been registered). 337 * <p> 338 * These values will be registered in the flag container if the associated 339 * flag type is registered in the top level flag container. 340 * 341 * @param flagName Flag name 342 * @param value Flag value 343 */ 344 public void addUnknownFlag(final String flagName, final String value) { 345 this.unknownFlags.put(flagName.toLowerCase(Locale.ENGLISH), value); 346 } 347 348 /** 349 * Creates a cleanup hook that is meant to run once this FlagContainer isn't needed anymore. 350 * This is to prevent memory leaks. This method is not part of the API. 351 * 352 * @return a new Runnable that cleans up once the FlagContainer isn't needed anymore. 353 * @since 6.0.10 354 */ 355 @NotPublic 356 public Runnable createCleanupHook() { 357 return () -> GlobalFlagContainer.getInstance().unsubscribe(unknownsRef); 358 } 359 360 void unsubscribe(final @Nullable PlotFlagUpdateHandler updateHandler) { 361 if (updateHandler != null) { 362 this.updateSubscribers.remove(updateHandler); 363 } 364 } 365 366 @Override 367 public boolean equals(final Object o) { 368 if (this == o) { 369 return true; 370 } 371 if (o == null || getClass() != o.getClass()) { 372 return false; 373 } 374 final FlagContainer that = (FlagContainer) o; 375 return flagMap.equals(that.flagMap); 376 } 377 378 @Override 379 public int hashCode() { 380 return flagMap.hashCode(); 381 } 382 383 /** 384 * @deprecated This method is not meant to be invoked or overridden, with no replacement. 385 */ 386 @Deprecated(forRemoval = true, since = "6.6.0") 387 protected boolean canEqual(final Object other) { 388 return other instanceof FlagContainer; 389 } 390 391 /** 392 * Update event types used in {@link PlotFlagUpdateHandler}. 393 */ 394 public enum PlotFlagUpdateType { 395 /** 396 * A flag was added to a plot container 397 */ 398 FLAG_ADDED, 399 /** 400 * A flag was removed from a plot container 401 */ 402 FLAG_REMOVED, 403 /** 404 * A flag was already stored in this container, 405 * but a new instance has bow replaced it 406 */ 407 FLAG_UPDATED 408 } 409 410 411 /** 412 * Handler for update events in {@link FlagContainer flag containers}. 413 */ 414 @FunctionalInterface 415 public interface PlotFlagUpdateHandler { 416 417 /** 418 * Act on the flag update event 419 * 420 * @param plotFlag Plot flag 421 * @param type Update type 422 */ 423 void handle(PlotFlag<?, ?> plotFlag, PlotFlagUpdateType type); 424 425 } 426 427}