comparator) {
+ this.comparator = comparator;
+ }
+
+
+ @Override
+ public final int compare(final CloudExpansion expansion1, final CloudExpansion expansion2) {
+ return comparator.compare(expansion1, expansion2);
+ }
+
+}
diff --git a/src/main/java/at/helpch/placeholderapi/configuration/PlaceholderAPIConfig.java b/src/main/java/at/helpch/placeholderapi/configuration/PlaceholderAPIConfig.java
new file mode 100644
index 0000000..a4b9199
--- /dev/null
+++ b/src/main/java/at/helpch/placeholderapi/configuration/PlaceholderAPIConfig.java
@@ -0,0 +1,29 @@
+/*
+ * This file is part of PlaceholderAPI
+ *
+ * PlaceholderAPI
+ * Copyright (c) 2015 - 2026 PlaceholderAPI Team
+ *
+ * PlaceholderAPI free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PlaceholderAPI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.helpch.placeholderapi.configuration;
+
+import com.google.gson.annotations.JsonAdapter;
+import org.jetbrains.annotations.NotNull;
+
+public record PlaceholderAPIConfig(boolean cloudEnabled, boolean debugMode, @NotNull ExpansionSort cloudSorting,
+ @NotNull BooleanValue booleanValue, @NotNull String dateFormat) {
+
+}
diff --git a/src/main/java/at/helpch/placeholderapi/events/ExpansionRegisterEvent.java b/src/main/java/at/helpch/placeholderapi/events/ExpansionRegisterEvent.java
new file mode 100644
index 0000000..c5d0505
--- /dev/null
+++ b/src/main/java/at/helpch/placeholderapi/events/ExpansionRegisterEvent.java
@@ -0,0 +1,89 @@
+/*
+ * This file is part of PlaceholderAPI
+ *
+ * PlaceholderAPI
+ * Copyright (c) 2015 - 2026 PlaceholderAPI Team
+ *
+ * PlaceholderAPI free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PlaceholderAPI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.helpch.placeholderapi.events;
+
+import at.helpch.placeholderapi.expansion.PlaceholderExpansion;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * This event indicates that a single {@link PlaceholderExpansion PlaceholderExpansion} has
+ * been registered in PlaceholderAPI.
+ *
+ * To know when all Expansions have been registered, use the
+ * {@link at.helpch.placeholderapi.events.ExpansionsLoadedEvent ExpansionsLoadedEvent} instead.
+ */
+public final class ExpansionRegisterEvent extends Event implements Cancellable {
+
+ @NotNull
+ private static final HandlerList HANDLERS = new HandlerList();
+ @NotNull
+ private final PlaceholderExpansion expansion;
+ private boolean cancelled;
+
+ public ExpansionRegisterEvent(@NotNull final PlaceholderExpansion expansion) {
+ this.expansion = expansion;
+ }
+
+ @NotNull
+ public static HandlerList getHandlerList() {
+ return HANDLERS;
+ }
+
+ /**
+ * The {@link PlaceholderExpansion PlaceholderExpansion} that was registered in PlaceholderAPI.
+ *
The PlaceholderExpansion will be available for use when the event
+ * {@link #isCancelled() was not cancelled}!
+ *
+ * @return Current instance of the registered {@link PlaceholderExpansion PlaceholderExpansion}
+ */
+ @NotNull
+ public PlaceholderExpansion getExpansion() {
+ return expansion;
+ }
+
+ /**
+ * Indicates if this event was cancelled or not.
+ *
A cancelled Event will result in the {@link #getExpansion() PlaceholderExpansion} NOT
+ * being added to PlaceholderAPI's internal list and will therefore be considered not registered
+ * anymore.
+ *
+ * @return Whether the event has been cancelled or not.
+ */
+ @Override
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean cancelled) {
+ this.cancelled = cancelled;
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return HANDLERS;
+ }
+
+}
diff --git a/src/main/java/at/helpch/placeholderapi/events/ExpansionUnregisterEvent.java b/src/main/java/at/helpch/placeholderapi/events/ExpansionUnregisterEvent.java
new file mode 100644
index 0000000..92aea32
--- /dev/null
+++ b/src/main/java/at/helpch/placeholderapi/events/ExpansionUnregisterEvent.java
@@ -0,0 +1,70 @@
+/*
+ * This file is part of PlaceholderAPI
+ *
+ * PlaceholderAPI
+ * Copyright (c) 2015 - 2026 PlaceholderAPI Team
+ *
+ * PlaceholderAPI free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PlaceholderAPI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.helpch.placeholderapi.events;
+
+import at.helpch.placeholderapi.expansion.PlaceholderExpansion;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * This event indicates that a {@link PlaceholderExpansion PlaceholderExpansion} has been
+ * unregistered by PlaceholderAPI.
+ *
+ *
Note that this event is triggered before the PlaceholderExpansion is completely
+ * removed.
+ *
This includes removing any Listeners, stopping active tasks and clearing the cache of
+ * the PlaceholderExpansion.
+ */
+public final class ExpansionUnregisterEvent extends Event {
+
+ @NotNull
+ private static final HandlerList HANDLERS = new HandlerList();
+
+ @NotNull
+ private final PlaceholderExpansion expansion;
+
+ public ExpansionUnregisterEvent(@NotNull final PlaceholderExpansion expansion) {
+ this.expansion = expansion;
+ }
+
+ @NotNull
+ public static HandlerList getHandlerList() {
+ return HANDLERS;
+ }
+
+ /**
+ * The {@link PlaceholderExpansion PlaceholderExpansion} that was unregistered.
+ *
+ * @return The {@link PlaceholderExpansion PlaceholderExpansion} instance.
+ */
+ @NotNull
+ public PlaceholderExpansion getExpansion() {
+ return expansion;
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return HANDLERS;
+ }
+
+}
diff --git a/src/main/java/at/helpch/placeholderapi/events/ExpansionsLoadedEvent.java b/src/main/java/at/helpch/placeholderapi/events/ExpansionsLoadedEvent.java
new file mode 100644
index 0000000..a63e2c5
--- /dev/null
+++ b/src/main/java/at/helpch/placeholderapi/events/ExpansionsLoadedEvent.java
@@ -0,0 +1,74 @@
+/*
+ * This file is part of PlaceholderAPI
+ *
+ * PlaceholderAPI
+ * Copyright (c) 2015 - 2026 PlaceholderAPI Team
+ *
+ * PlaceholderAPI free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PlaceholderAPI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.helpch.placeholderapi.events;
+
+
+import java.util.Collections;
+import java.util.List;
+
+import at.helpch.placeholderapi.expansion.PlaceholderExpansion;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * This event indicated that all {@link PlaceholderExpansion PlaceholderExpansions} have
+ * been registered in PlaceholderAPI and can now be used.
+ *
This even will also be triggered whenever PlaceholderAPI gets reloaded.
+ *
+ *
All PlaceholderExpansions, except for those loaded by plugins, are loaded
+ * after Spigot triggered its ServerLoadEvent (1.13+), or after PlaceholderAPI has been enabled.
+ */
+public class ExpansionsLoadedEvent extends Event {
+
+ private final List expansions;
+
+ public ExpansionsLoadedEvent(List expansions) {
+ this.expansions = Collections.unmodifiableList(expansions);
+ }
+
+ /**
+ * Returns a unmodifiable list of {@link PlaceholderExpansion PlaceholderExpansions} that
+ * have been registered by PlaceholderAPI.
+ *
+ * This list does not include manually registered PlaceholderExpansions.
+ *
+ * @return List of {@link PlaceholderExpansion registered PlaceholderExpansions}.
+ */
+ @NotNull
+ public final List getExpansions() {
+ return expansions;
+ }
+
+ @NotNull
+ private static final HandlerList HANDLERS = new HandlerList();
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return HANDLERS;
+ }
+
+ @NotNull
+ public static HandlerList getHandlerList() {
+ return HANDLERS;
+ }
+}
diff --git a/src/main/java/at/helpch/placeholderapi/events/PlaceholderHookUnloadEvent.java b/src/main/java/at/helpch/placeholderapi/events/PlaceholderHookUnloadEvent.java
new file mode 100644
index 0000000..5a86703
--- /dev/null
+++ b/src/main/java/at/helpch/placeholderapi/events/PlaceholderHookUnloadEvent.java
@@ -0,0 +1,70 @@
+/*
+ * This file is part of PlaceholderAPI
+ *
+ * PlaceholderAPI
+ * Copyright (c) 2015 - 2026 PlaceholderAPI Team
+ *
+ * PlaceholderAPI free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PlaceholderAPI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.helpch.placeholderapi.events;
+
+import at.helpch.placeholderapi.PlaceholderHook;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @deprecated This event is no longer used.
+ */
+@Deprecated
+public final class PlaceholderHookUnloadEvent extends Event {
+
+ @NotNull
+ private static final HandlerList HANDLERS = new HandlerList();
+
+
+ @NotNull
+ private final String plugin;
+ @NotNull
+ private final PlaceholderHook placeholderHook;
+
+ public PlaceholderHookUnloadEvent(@NotNull final String plugin,
+ @NotNull final PlaceholderHook placeholderHook) {
+ this.plugin = plugin;
+ this.placeholderHook = placeholderHook;
+ }
+
+ @NotNull
+ public static HandlerList getHandlerList() {
+ return HANDLERS;
+ }
+
+ @NotNull
+ public String getHookName() {
+ return plugin;
+ }
+
+ @NotNull
+ public PlaceholderHook getHook() {
+ return placeholderHook;
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return HANDLERS;
+ }
+
+}
diff --git a/src/main/java/at/helpch/placeholderapi/exceptions/NoDefaultCommandException.java b/src/main/java/at/helpch/placeholderapi/exceptions/NoDefaultCommandException.java
new file mode 100644
index 0000000..dc608e8
--- /dev/null
+++ b/src/main/java/at/helpch/placeholderapi/exceptions/NoDefaultCommandException.java
@@ -0,0 +1,29 @@
+/*
+ * This file is part of PlaceholderAPI
+ *
+ * PlaceholderAPI
+ * Copyright (c) 2015 - 2026 PlaceholderAPI Team
+ *
+ * PlaceholderAPI free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PlaceholderAPI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.helpch.placeholderapi.exceptions;
+
+public final class NoDefaultCommandException extends RuntimeException {
+
+ public NoDefaultCommandException(final String message) {
+ super(message);
+ }
+}
+
diff --git a/src/main/java/at/helpch/placeholderapi/expansion/Cacheable.java b/src/main/java/at/helpch/placeholderapi/expansion/Cacheable.java
new file mode 100644
index 0000000..26e8d0f
--- /dev/null
+++ b/src/main/java/at/helpch/placeholderapi/expansion/Cacheable.java
@@ -0,0 +1,38 @@
+/*
+ * This file is part of PlaceholderAPI
+ *
+ * PlaceholderAPI
+ * Copyright (c) 2015 - 2026 PlaceholderAPI Team
+ *
+ * PlaceholderAPI free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PlaceholderAPI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.helpch.placeholderapi.expansion;
+
+/**
+ * Classes implementing this interface will have a {@link #clear() clear void} that is called
+ * by PlaceholderAPI whenever the {@link at.helpch.placeholderapi.expansion.PlaceholderExpansion PlaceholderExpansion}
+ * is unregistered.
+ *
+ * This allows you to execute things such as clearing internal caches, saving data to files, etc.
+ *
+ * @author Ryan McCarthy
+ */
+public interface Cacheable {
+
+ /**
+ * Called when the implementing class is unregistered from PlaceholderAPI
+ */
+ void clear();
+}
diff --git a/src/main/java/at/helpch/placeholderapi/expansion/Cleanable.java b/src/main/java/at/helpch/placeholderapi/expansion/Cleanable.java
new file mode 100644
index 0000000..4311e20
--- /dev/null
+++ b/src/main/java/at/helpch/placeholderapi/expansion/Cleanable.java
@@ -0,0 +1,42 @@
+/*
+ * This file is part of PlaceholderAPI
+ *
+ * PlaceholderAPI
+ * Copyright (c) 2015 - 2026 PlaceholderAPI Team
+ *
+ * PlaceholderAPI free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PlaceholderAPI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.helpch.placeholderapi.expansion;
+
+import org.bukkit.entity.Player;
+
+/**
+ * Classes implementing this interface will have a {@link #cleanup(Player) cleanup void} that is
+ * called by PlaceholderAPI whenever a Player leaves the server.
+ *
+ *
This can be useful for cases where you keep data of the player in a cache or similar
+ * and want to free up space whenever they leave.
+ *
+ * @author Ryan McCarthy
+ */
+public interface Cleanable {
+
+ /**
+ * Called when a player leaves the server
+ *
+ * @param p (@link Player} who left the server
+ */
+ void cleanup(Player p);
+}
diff --git a/src/main/java/at/helpch/placeholderapi/expansion/Configurable.java b/src/main/java/at/helpch/placeholderapi/expansion/Configurable.java
new file mode 100644
index 0000000..5cec0b6
--- /dev/null
+++ b/src/main/java/at/helpch/placeholderapi/expansion/Configurable.java
@@ -0,0 +1,57 @@
+/*
+ * This file is part of PlaceholderAPI
+ *
+ * PlaceholderAPI
+ * Copyright (c) 2015 - 2026 PlaceholderAPI Team
+ *
+ * PlaceholderAPI free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PlaceholderAPI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.helpch.placeholderapi.expansion;
+
+import java.util.Map;
+
+/**
+ * Implementing this interface allows {@link at.helpch.placeholderapi.expansion.PlaceholderExpansion PlaceholderExpansions}
+ * to set a list of default configuration values through the {@link #getDefaults() getDefaults method}
+ * that should be added to the config.yml of PlaceholderAPI.
+ *
+ *
The entries will be added under {@code expansions} as their own section.
+ *
Example:
+ * returning a Map with key {@code foo} and value {@code bar} will result in the following config entry:
+ *
+ *
+ * expansions:
+ * myexpansion:
+ * foo: "bar"
+ *
+ *
+ * The configuration is set before the PlaceholderExpansion is registered!
+ *
+ * @author Ryan McCarthy
+ */
+public interface Configurable {
+
+ /**
+ * The map returned by this method will be used to set config options in PlaceholderAPI's config.yml.
+ *
+ *
The key and value pairs are set under a section named after your
+ * {@link at.helpch.placeholderapi.expansion.PlaceholderExpansion PlaceholderExpansion} in the
+ * {@code expansions} section of the config.
+ *
+ * @return Map of config path / values which need to be added / removed from the PlaceholderAPI
+ * config.yml file
+ */
+ Map getDefaults();
+}
diff --git a/src/main/java/at/helpch/placeholderapi/expansion/NMSVersion.java b/src/main/java/at/helpch/placeholderapi/expansion/NMSVersion.java
new file mode 100644
index 0000000..944c352
--- /dev/null
+++ b/src/main/java/at/helpch/placeholderapi/expansion/NMSVersion.java
@@ -0,0 +1,81 @@
+/*
+ * This file is part of PlaceholderAPI
+ *
+ * PlaceholderAPI
+ * Copyright (c) 2015 - 2026 PlaceholderAPI Team
+ *
+ * PlaceholderAPI free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PlaceholderAPI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.helpch.placeholderapi.expansion;
+
+public enum NMSVersion {
+
+ UNKNOWN("unknown"),
+ SPIGOT_1_7_R1("v1_7_R1"),
+ SPIGOT_1_7_R2("v1_7_R2"),
+ SPIGOT_1_7_R3("v1_7_R3"),
+ SPIGOT_1_7_R4("v1_7_R4"),
+ SPIGOT_1_8_R1("v1_8_R1"),
+ SPIGOT_1_8_R2("v1_8_R2"),
+ SPIGOT_1_8_R3("v1_8_R3"),
+ SPIGOT_1_9_R1("v1_9_R1"),
+ SPIGOT_1_9_R2("v1_9_R2"),
+ SPIGOT_1_10_R1("v1_10_R1"),
+ SPIGOT_1_11_R1("v1_11_R1"),
+ SPIGOT_1_12_R1("v1_12_R1"),
+ SPIGOT_1_13_R1("v1_13_R1"),
+ SPIGOT_1_13_R2("v1_13_R2"),
+ SPIGOT_1_14_R1("v1_14_R1"),
+ SPIGOT_1_15_R1("v1_15_R1"),
+ SPIGOT_1_16_R1("v1_16_R1"),
+ SPIGOT_1_16_R2("v1_16_R2"),
+ SPIGOT_1_16_R3("v1_16_R3"),
+ SPIGOT_1_17_R1("v1_17_R1"),
+ SPIGOT_1_18_R1("v1_18_R1"),
+ SPIGOT_1_19_R1("v1_19_R1"),
+ SPIGOT_1_19_R2("v1_19_R2"),
+ SPIGOT_1_19_R3("v1_19_R3"),
+ SPIGOT_1_20_R1("v1_20_R1"),
+ SPIGOT_1_20_R2("v1_20_R2"),
+ SPIGOT_1_20_R3("v1_20_R3"),
+ SPIGOT_1_20_R4("v1_20_R4"),
+ SPIGOT_1_21_R1("v1_21_R1"),
+ SPIGOT_1_21_R2("V1_21_R2"),
+ SPIGOT_1_21_R3("V1_21_R3"),
+ SPIGOT_1_21_R4("V1_21_R4"),
+ SPIGOT_1_21_R5("V1_21_R5"),
+ SPIGOT_1_21_R6("V1_21_R6");
+
+ private final String version;
+
+ NMSVersion(String version) {
+ this.version = version;
+ }
+
+ public static NMSVersion getVersion(String version) {
+ for (NMSVersion v : values()) {
+ if (v.getVersion().equalsIgnoreCase(version)) {
+ return v;
+ }
+ }
+
+ return NMSVersion.UNKNOWN;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+}
diff --git a/src/main/java/at/helpch/placeholderapi/expansion/PlaceholderExpansion.java b/src/main/java/at/helpch/placeholderapi/expansion/PlaceholderExpansion.java
new file mode 100644
index 0000000..ba04f97
--- /dev/null
+++ b/src/main/java/at/helpch/placeholderapi/expansion/PlaceholderExpansion.java
@@ -0,0 +1,479 @@
+/*
+ * This file is part of PlaceholderAPI
+ *
+ * PlaceholderAPI
+ * Copyright (c) 2015 - 2026 PlaceholderAPI Team
+ *
+ * PlaceholderAPI free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PlaceholderAPI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.helpch.placeholderapi.expansion;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.logging.Level;
+
+import at.helpch.placeholderapi.PlaceholderAPIPlugin;
+import at.helpch.placeholderapi.PlaceholderHook;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.ConfigurationSection;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Any class extending this will be able to get registered as a PlaceholderExpansion.
+ *
The registration either happens automatically when the jar file containing a
+ * class extending this one is located under the {@code PlaceholderAPI/expansions}
+ * directory or when the {@link #register()} method is called by said class.
+ */
+public abstract class PlaceholderExpansion extends PlaceholderHook {
+
+ /**
+ * The type is {@link Type#INTERNAL} by default.
+ * For external expansions, the type is updated on {@link at.helpch.placeholderapi.expansion.manager.LocalExpansionManager#register(Class) register}.
+ *
+ * @since 2.11.4
+ */
+ @ApiStatus.Internal
+ protected Type expansionType = Type.INTERNAL;
+
+ /**
+ * The placeholder identifier of this expansion. May not contain {@literal %},
+ * {@literal {}} or _
+ *
+ * @return placeholder identifier that is associated with this expansion
+ */
+ @NotNull
+ public abstract String getIdentifier();
+
+ /**
+ * The author of this expansion
+ *
+ * @return name of the author for this expansion
+ */
+ @NotNull
+ public abstract String getAuthor();
+
+ /**
+ * The version of this expansion
+ *
+ * @return current version of this expansion
+ */
+ @NotNull
+ public abstract String getVersion();
+
+ /**
+ * The name of this expansion
+ *
+ * @return {@link #getIdentifier()} by default, name of this expansion if specified
+ */
+ @NotNull
+ public String getName() {
+ return getIdentifier();
+ }
+
+ /**
+ * The name of the plugin that this expansion hooks into. by default will null
+ *
+ * @return plugin name that this expansion requires to function
+ */
+ @Nullable
+ public String getRequiredPlugin() {
+ return getPlugin();
+ }
+
+ /**
+ * The placeholders associated with this expansion
+ *
+ * @return placeholder list that this expansion provides
+ */
+ @NotNull
+ public List getPlaceholders() {
+ return Collections.emptyList();
+ }
+
+
+ /**
+ * Expansions that do not use the ecloud and instead register from the dependency should set this
+ * to true to ensure that your placeholder expansion is not unregistered when the papi reload
+ * command is used
+ *
+ * @return if this expansion should persist through placeholder reloads
+ */
+ public boolean persist() {
+ return false;
+ }
+
+
+ /**
+ * Check if this placeholder identifier has already been registered
+ *
+ * @return true if the identifier for this expansion is already registered
+ */
+ public final boolean isRegistered() {
+ return getPlaceholderAPI().getLocalExpansionManager().findExpansionByIdentifier(getIdentifier())
+ .map(it -> it.equals(this)).orElse(false);
+ }
+
+
+ /**
+ * If any requirements need to be checked before this expansion should register, you can check
+ * them here
+ *
+ * @return true if this hook meets all the requirements to register
+ */
+ public boolean canRegister() {
+ return getRequiredPlugin() == null
+ || Bukkit.getPluginManager().getPlugin(getRequiredPlugin()) != null;
+ }
+
+ /**
+ * Attempt to register this PlaceholderExpansion
+ *
+ * @return true if this expansion is now registered with PlaceholderAPI
+ */
+ public boolean register() {
+ return getPlaceholderAPI().getLocalExpansionManager().register(this);
+ }
+
+ /**
+ * Attempt to unregister this PlaceholderExpansion
+ *
+ * @return true if this expansion is now unregistered with PlaceholderAPI
+ */
+ public final boolean unregister() {
+ return getPlaceholderAPI().getLocalExpansionManager().unregister(this);
+ }
+
+
+ /**
+ * Quick getter for the {@link PlaceholderAPIPlugin} instance
+ *
+ * @return {@link PlaceholderAPIPlugin} instance
+ */
+ @NotNull
+ public final PlaceholderAPIPlugin getPlaceholderAPI() {
+ return PlaceholderAPIPlugin.getInstance();
+ }
+
+ /**
+ * Get the type of the expansion
+ *
+ * @return the type of the expansion
+ * @since 2.11.4
+ */
+ @ApiStatus.Internal
+ public Type getExpansionType() {
+ return expansionType;
+ }
+
+ /**
+ * Set the type of the expansion
+ *
+ * @param expansionType the new type
+ * @since 2.11.4
+ */
+ @ApiStatus.Internal
+ public void setExpansionType(Type expansionType) {
+ this.expansionType = expansionType;
+ }
+
+ // === Configuration ===
+
+ /**
+ * Gets the ConfigurationSection of the expansion located in the config.yml of PlaceholderAPI or
+ * null when not specified.
+ *
You may use the {@link Configurable} interface to define default values set
+ *
+ * @return ConfigurationSection that this expansion has.
+ */
+ @Nullable
+ public final ConfigurationSection getConfigSection() {
+ return getPlaceholderAPI().getConfig().getConfigurationSection("expansions." + getIdentifier());
+ }
+
+ /**
+ * Gets the ConfigurationSection relative to the {@link #getConfigSection() default one} set
+ * by the expansion or null when the default ConfigurationSection is null
+ *
+ * @param path The path to get the ConfigurationSection from. This is relative to the default section
+ * @return ConfigurationSection relative to the default section
+ */
+ @Nullable
+ public final ConfigurationSection getConfigSection(@NotNull final String path) {
+ final ConfigurationSection section = getConfigSection();
+ return section == null ? null : section.getConfigurationSection(path);
+ }
+
+ /**
+ * Gets the Object relative to the {@link #getConfigSection() default ConfigurationSection} set
+ * by the expansion or the provided Default Object, when the default ConfigurationSection is null
+ *
+ * @param path The path to get the Object from. This is relative to the default section
+ * @param def The default Object to return when the ConfigurationSection returns null
+ * @return Object from the provided path or the default one provided
+ */
+ @Nullable
+ @Contract("_, !null -> !null")
+ public final Object get(@NotNull final String path, final Object def) {
+ final ConfigurationSection section = getConfigSection();
+ return section == null ? def : section.get(path, def);
+ }
+
+ /**
+ * Gets the int relative to the {@link #getConfigSection() default ConfigurationSection} set
+ * by the expansion or the provided Default int, when the default ConfigurationSection is null
+ *
+ * @param path The path to get the int from. This is relative to the default section
+ * @param def The default int to return when the ConfigurationSection returns null
+ * @return int from the provided path or the default one provided
+ */
+ public final int getInt(@NotNull final String path, final int def) {
+ final ConfigurationSection section = getConfigSection();
+ return section == null ? def : section.getInt(path, def);
+ }
+
+ /**
+ * Gets the long relative to the {@link #getConfigSection() default ConfigurationSection} set
+ * by the expansion or the provided Default long, when the default ConfigurationSection is null
+ *
+ * @param path The path to get the long from. This is relative to the default section
+ * @param def The default long to return when the ConfigurationSection returns null
+ * @return long from the provided path or the default one provided
+ */
+ public final long getLong(@NotNull final String path, final long def) {
+ final ConfigurationSection section = getConfigSection();
+ return section == null ? def : section.getLong(path, def);
+ }
+
+ /**
+ * Gets the double relative to the {@link #getConfigSection() default ConfigurationSection} set
+ * by the expansion or the provided Default double, when the default ConfigurationSection is null
+ *
+ * @param path The path to get the double from. This is relative to the default section
+ * @param def The default double to return when the ConfigurationSection returns null
+ * @return double from the provided path or the default one provided
+ */
+ public final double getDouble(@NotNull final String path, final double def) {
+ final ConfigurationSection section = getConfigSection();
+ return section == null ? def : section.getDouble(path, def);
+ }
+
+ /**
+ * Gets the String relative to the {@link #getConfigSection() default ConfigurationSection} set
+ * by the expansion or the provided Default String, when the default ConfigurationSection is null
+ *
+ * @param path The path to get the String from. This is relative to the default section
+ * @param def The default String to return when the ConfigurationSection returns null. Can be null
+ * @return String from the provided path or the default one provided
+ */
+ @Nullable
+ @Contract("_, !null -> !null")
+ public final String getString(@NotNull final String path, @Nullable final String def) {
+ final ConfigurationSection section = getConfigSection();
+ return section == null ? def : section.getString(path, def);
+ }
+
+ /**
+ * Gets a String List relative to the {@link #getConfigSection() default ConfigurationSection} set
+ * by the expansion or an empty List, when the default ConfigurationSection is null
+ *
+ * @param path The path to get the String list from. This is relative to the default section
+ * @return String list from the provided path or an empty list
+ */
+ @NotNull
+ public final List getStringList(@NotNull final String path) {
+ final ConfigurationSection section = getConfigSection();
+ return section == null ? Collections.emptyList() : section.getStringList(path);
+ }
+
+ /**
+ * Gets the boolean relative to the {@link #getConfigSection() default ConfigurationSection} set
+ * by the expansion or the default boolean, when the default ConfigurationSection is null
+ *
+ * @param path The path to get the boolean from. This is relative to the default section
+ * @param def The default boolean to return when the ConfigurationSection is null
+ * @return boolean from the provided path or the default one provided
+ */
+ public final boolean getBoolean(@NotNull final String path, final boolean def) {
+ final ConfigurationSection section = getConfigSection();
+ return section == null ? def : section.getBoolean(path, def);
+ }
+
+ /**
+ * Whether the {@link #getConfigSection() default ConfigurationSection} contains the provided path
+ * or not. This will return {@code false} when either the default section is null, or doesn't
+ * contain the provided path
+ *
+ * @param path The path to check
+ * @return true when the default ConfigurationSection is not null and contains the path, false otherwise
+ */
+ public final boolean configurationContains(@NotNull final String path) {
+ final ConfigurationSection section = getConfigSection();
+ return section != null && section.contains(path);
+ }
+
+ /**
+ * Logs the provided message with the provided Level in the console.
+ *
The message will be prefixed with {@link #getName() [<expansion name>]}
+ *
+ * @param level The Level at which the message should be logged with
+ * @param msg The message to log
+ */
+ public void log(Level level, String msg) {
+ getPlaceholderAPI().getLogger().log(level, "[" + getName() + "] " + msg);
+ }
+
+ /**
+ * Logs the provided message and Throwable with the provided Level in the console.
+ *
The message will be prefixed with {@link #getName() [<expansion name>]}
+ *
+ * @param level The Level at which the message should be logged with
+ * @param msg The message to log
+ * @param throwable The Throwable to log
+ */
+ public void log(Level level, String msg, Throwable throwable) {
+ getPlaceholderAPI().getLogger().log(level, "[" + getName() + "] " + msg, throwable);
+ }
+
+ /**
+ * Logs the provided message with Level "info".
+ *
The message will be prefixed with {@link #getName() [<expansion name>]}
+ *
+ * @param msg The message to log
+ */
+ public void info(String msg) {
+ log(Level.INFO, msg);
+ }
+
+ /**
+ * Logs the provided message with Level "warning".
+ *
The message will be prefixed with {@link #getName() [<expansion name>]}
+ *
+ * @param msg The message to log
+ */
+ public void warning(String msg) {
+ log(Level.WARNING, msg);
+ }
+
+ /**
+ * Logs the provided message with Level "severe" (error).
+ *
The message will be prefixed with {@link #getName() [<expansion name>]}
+ *
+ * @param msg The message to log
+ */
+ public void severe(String msg) {
+ log(Level.SEVERE, msg);
+ }
+
+ /**
+ * Logs the provided message and Throwable with Level "severe" (error).
+ *
The message will be prefixed with {@link #getName() [<expansion name>]}
+ *
+ * @param msg The message to log
+ * @param throwable The Throwable to log
+ */
+ public void severe(String msg, Throwable throwable) {
+ log(Level.SEVERE, msg, throwable);
+ }
+
+
+ /**
+ * Whether the provided Object is an instance of this PlaceholderExpansion.
+ *
This method will perform the following checks in order:
+ *
+ * - Checks if Object equals the class. Returns true when equal and continues otherwise
+ * - Checks if the Object is an instance of a PlaceholderExpansion. Returns false if not
+ * - Checks if the Object's Identifier, Author and version equal the one of this class
+ *
+ *
+ * @param o The Object to check
+ * @return true or false depending on the above mentioned checks
+ */
+ @Override
+ public final boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof PlaceholderExpansion)) {
+ return false;
+ }
+
+ final PlaceholderExpansion expansion = (PlaceholderExpansion) o;
+
+ return getIdentifier().equals(expansion.getIdentifier()) &&
+ getAuthor().equals(expansion.getAuthor()) &&
+ getVersion().equals(expansion.getVersion());
+ }
+
+ /**
+ * Returns a String containing the Expansion's name, author and version
+ *
+ * @return String containing name, author and version of the expansion
+ */
+ @Override
+ public final String toString() {
+ return String.format("PlaceholderExpansion[name: '%s', author: '%s', version: '%s', type: '%s']", getName(),
+ getAuthor(), getVersion(), getExpansionType());
+ }
+
+ // === Deprecated API ===
+
+ /**
+ * @return The plugin name.
+ * @deprecated As of versions greater than 2.8.7, use {@link #getRequiredPlugin()}
+ */
+ @Deprecated
+ @ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
+ public String getPlugin() {
+ return null;
+ }
+
+ /**
+ * @return The description of the expansion.
+ * @deprecated As of versions greater than 2.8.7, use the expansion cloud to show a description
+ */
+ @Deprecated
+ @ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
+ public String getDescription() {
+ return null;
+ }
+
+ /**
+ * @return The link for the expansion.
+ * @deprecated As of versions greater than 2.8.7, use the expansion cloud to display a link
+ */
+ @Deprecated
+ @ApiStatus.ScheduledForRemoval(inVersion = "2.13.0")
+ public String getLink() {
+ return null;
+ }
+
+ public enum Type {
+
+ /**
+ * An expansion provided by a plugin is considered internal
+ */
+ INTERNAL,
+
+ /**
+ * An expansion loaded from the expansions folder is considered external
+ */
+ EXTERNAL
+
+ }
+
+}
diff --git a/src/main/java/at/helpch/placeholderapi/expansion/Relational.java b/src/main/java/at/helpch/placeholderapi/expansion/Relational.java
new file mode 100644
index 0000000..a28d059
--- /dev/null
+++ b/src/main/java/at/helpch/placeholderapi/expansion/Relational.java
@@ -0,0 +1,43 @@
+/*
+ * This file is part of PlaceholderAPI
+ *
+ * PlaceholderAPI
+ * Copyright (c) 2015 - 2026 PlaceholderAPI Team
+ *
+ * PlaceholderAPI free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PlaceholderAPI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.helpch.placeholderapi.expansion;
+
+import org.bukkit.entity.Player;
+
+/**
+ * Implementing this interface allows your {@link at.helpch.placeholderapi.expansion.PlaceholderExpansion PlaceholderExpansion}
+ * to be used as a relational placeholder expansion.
+ *
+ * Relational placeholders take two Players as input and are always prefixed with {@code rel_},
+ * so {@code %foo_bar%} becomes {@code %rel_foo_bar%}
+ */
+public interface Relational {
+
+ /**
+ * This method is called whenever a placeholder starting with {@code rel_} is called.
+ *
+ * @param one The first player used for the placeholder.
+ * @param two The second player used for the placeholder.
+ * @param identifier The text right after the expansion's name (%expansion_identifier%)
+ * @return Parsed String from the expansion.
+ */
+ String onPlaceholderRequest(Player one, Player two, String identifier);
+}
diff --git a/src/main/java/at/helpch/placeholderapi/expansion/Taskable.java b/src/main/java/at/helpch/placeholderapi/expansion/Taskable.java
new file mode 100644
index 0000000..ed2f815
--- /dev/null
+++ b/src/main/java/at/helpch/placeholderapi/expansion/Taskable.java
@@ -0,0 +1,43 @@
+/*
+ * This file is part of PlaceholderAPI
+ *
+ * PlaceholderAPI
+ * Copyright (c) 2015 - 2026 PlaceholderAPI Team
+ *
+ * PlaceholderAPI free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PlaceholderAPI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.helpch.placeholderapi.expansion;
+
+/**
+ * Implementing this interface adds the {@link #start() start} and {@link #stop() stop} void
+ * methods to your {@link at.helpch.placeholderapi.expansion.PlaceholderExpansion PlaceholderExpansion}.
+ *
+ *
This can be used to execute methods and tasks whenever the PlaceholderExpansion has been
+ * successfully (un)registered.
+ */
+public interface Taskable {
+
+ /**
+ * Called when the implementing class has successfully been registered to the placeholder map.
+ *
Tasks that need to be performed when this expansion is registered should go here
+ */
+ void start();
+
+ /**
+ * Called when the implementing class has been unregistered from PlaceholderAPI.
+ *
Tasks that need to be performed when this expansion has unregistered should go here
+ */
+ void stop();
+}
diff --git a/src/main/java/at/helpch/placeholderapi/expansion/cloud/CloudExpansion.java b/src/main/java/at/helpch/placeholderapi/expansion/cloud/CloudExpansion.java
new file mode 100644
index 0000000..d0c51ae
--- /dev/null
+++ b/src/main/java/at/helpch/placeholderapi/expansion/cloud/CloudExpansion.java
@@ -0,0 +1,207 @@
+/*
+ * This file is part of PlaceholderAPI
+ *
+ * PlaceholderAPI
+ * Copyright (c) 2015 - 2026 PlaceholderAPI Team
+ *
+ * PlaceholderAPI free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PlaceholderAPI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.helpch.placeholderapi.expansion.cloud;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import at.helpch.placeholderapi.util.TimeUtil;
+
+
+public class CloudExpansion {
+
+ private String name,
+ author,
+ latest_version,
+ description,
+ source_url,
+ dependency_url;
+
+ private boolean hasExpansion,
+ shouldUpdate;
+
+ private long last_update,
+ ratings_count;
+
+ private double average_rating;
+
+ private List placeholders;
+
+ private List versions;
+
+ public CloudExpansion() {
+ }
+
+ public String getTimeSinceLastUpdate() {
+ int time = (int) TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - getLastUpdate());
+ return TimeUtil.getTime(time);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+
+ public Version getVersion() {
+ return getLatestVersion() == null ? null : getVersion(getLatestVersion());
+ }
+
+ public Version getVersion(String version) {
+ return versions == null ? null : versions.stream()
+ .filter(v -> v.getVersion().equals(version))
+ .findFirst()
+ .orElse(null);
+ }
+
+ public List getAvailableVersions() {
+ return versions.stream().map(Version::getVersion).collect(Collectors.toList());
+ }
+
+ public String getLatestVersion() {
+ return latest_version;
+ }
+
+ public void setLatestVersion(String latest_version) {
+ this.latest_version = latest_version;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getSourceUrl() {
+ return source_url;
+ }
+
+ public void setSourceUrl(String source_url) {
+ this.source_url = source_url;
+ }
+
+ public String getDependencyUrl() {
+ return dependency_url;
+ }
+
+ public void setDependencyUrl(String dependency_url) {
+ this.dependency_url = dependency_url;
+ }
+
+ public boolean hasExpansion() {
+ return hasExpansion;
+ }
+
+ public void setHasExpansion(boolean hasExpansion) {
+ this.hasExpansion = hasExpansion;
+ }
+
+ public boolean shouldUpdate() {
+ return shouldUpdate;
+ }
+
+ public void setShouldUpdate(boolean shouldUpdate) {
+ this.shouldUpdate = shouldUpdate;
+ }
+
+ public long getLastUpdate() {
+ return last_update;
+ }
+
+ public void setLastUpdate(long last_update) {
+ this.last_update = last_update;
+ }
+
+ public long getRatingsCount() {
+ return ratings_count;
+ }
+
+ public double getAverage_rating() {
+ return average_rating;
+ }
+
+ public List getPlaceholders() {
+ return placeholders;
+ }
+
+ public void setPlaceholders(List placeholders) {
+ this.placeholders = placeholders;
+ }
+
+ public List getVersions() {
+ return versions;
+ }
+
+ public void setVersions(List versions) {
+ this.versions = versions;
+ }
+
+ public static class Version {
+
+ private String url, version, release_notes;
+ private boolean verified;
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getReleaseNotes() {
+ return release_notes;
+ }
+
+ public void setReleaseNotes(String release_notes) {
+ this.release_notes = release_notes;
+ }
+
+ public boolean isVerified() {
+ return verified;
+ }
+
+ public void setVerified(boolean verified) {
+ this.verified = verified;
+ }
+ }
+}
diff --git a/src/main/java/at/helpch/placeholderapi/expansion/manager/CloudExpansionManager.java b/src/main/java/at/helpch/placeholderapi/expansion/manager/CloudExpansionManager.java
new file mode 100644
index 0000000..f462662
--- /dev/null
+++ b/src/main/java/at/helpch/placeholderapi/expansion/manager/CloudExpansionManager.java
@@ -0,0 +1,278 @@
+/*
+ * This file is part of PlaceholderAPI
+ *
+ * PlaceholderAPI
+ * Copyright (c) 2015 - 2026 PlaceholderAPI Team
+ *
+ * PlaceholderAPI free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * PlaceholderAPI is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.helpch.placeholderapi.expansion.manager;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.Resources;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Function;
+import java.util.logging.Level;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+
+import at.helpch.placeholderapi.PlaceholderAPIPlugin;
+import at.helpch.placeholderapi.expansion.PlaceholderExpansion;
+import at.helpch.placeholderapi.expansion.cloud.CloudExpansion;
+import at.helpch.placeholderapi.util.Msg;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Unmodifiable;
+
+public final class CloudExpansionManager {
+
+ @NotNull
+ private static final String API_URL = "https://ecloud.placeholderapi.com/api/v3/";
+
+ @NotNull
+ private static final Gson GSON = new Gson();
+ @NotNull
+ private static final Type TYPE = new TypeToken