{
@Override
public boolean init(Expression>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
- if (!getParser().isCurrentEvent(BlockPistonExtendEvent.class, BlockPistonRetractEvent.class)) {
- Skript.error("The moved blocks are only usable in piston extend and retract events", ErrorQuality.SEMANTIC_ERROR);
- return false;
- }
-
return true;
}
-
+
+ @Override
+ public Class extends Event>[] supportedEvents() {
+ return CollectionUtils.array(BlockPistonExtendEvent.class, BlockPistonRetractEvent.class);
+ }
+
@Override
@Nullable
protected Block[] get(Event e) {
diff --git a/src/main/java/ch/njol/skript/lang/EventRestrictedSyntax.java b/src/main/java/ch/njol/skript/lang/EventRestrictedSyntax.java
new file mode 100644
index 00000000000..901c23d6ece
--- /dev/null
+++ b/src/main/java/ch/njol/skript/lang/EventRestrictedSyntax.java
@@ -0,0 +1,25 @@
+package ch.njol.skript.lang;
+
+import ch.njol.util.Kleenean;
+import ch.njol.util.coll.CollectionUtils;
+import org.bukkit.event.Event;
+
+/**
+ * A syntax element that restricts the events it can be used in.
+ */
+public interface EventRestrictedSyntax {
+
+ /**
+ * Returns all supported events for this syntax element.
+ *
+ * Before {@link SyntaxElement#init(Expression[], int, Kleenean, SkriptParser.ParseResult)} is called, checks
+ * to see if the current event is supported by this syntax element.
+ * If it is not, an error will be printed and the syntax element will not be initialised.
+ *
+ *
+ * @return All supported event classes.
+ * @see CollectionUtils#array(Object[])
+ */
+ Class extends Event>[] supportedEvents();
+
+}
diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java
index 6deca5e4cc6..79d94bf6ebc 100644
--- a/src/main/java/ch/njol/skript/lang/SkriptParser.java
+++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java
@@ -31,19 +31,14 @@
import ch.njol.util.StringUtils;
import ch.njol.util.coll.CollectionUtils;
import com.google.common.primitives.Booleans;
+import org.bukkit.event.Event;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.lang.script.Script;
import org.skriptlang.skript.lang.script.ScriptWarning;
-import java.util.ArrayList;
-import java.util.Deque;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
+import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
@@ -225,7 +220,26 @@ public boolean hasTag(String tag) {
}
}
T element = info.getElementClass().newInstance();
- if (element.init(parseResult.exprs, patternIndex, getParser().getHasDelayBefore(), parseResult)) {
+
+ if (element instanceof EventRestrictedSyntax eventRestrictedSyntax) {
+ Class extends Event>[] supportedEvents = eventRestrictedSyntax.supportedEvents();
+ if (!getParser().isCurrentEvent(supportedEvents)) {
+ Iterator iterator = Arrays.stream(supportedEvents)
+ .map(it -> "the " + it.getSimpleName()
+ .replaceAll("([A-Z])", " $1")
+ .toLowerCase()
+ .trim())
+ .iterator();
+
+ String events = StringUtils.join(iterator, ", ", " or ");
+
+ Skript.error("'" + parseResult.expr + "' can only be used in " + events);
+ continue;
+ }
+ }
+
+ boolean success = element.init(parseResult.exprs, patternIndex, getParser().getHasDelayBefore(), parseResult);
+ if (success) {
log.printLog();
return element;
}
diff --git a/src/test/skript/tests/misc/supported events.sk b/src/test/skript/tests/misc/supported events.sk
new file mode 100644
index 00000000000..f0cfc2f8a16
--- /dev/null
+++ b/src/test/skript/tests/misc/supported events.sk
@@ -0,0 +1,5 @@
+test "supported events":
+ parse:
+ set {_x} to the exploded blocks
+
+ assert last parse logs contain "'the exploded blocks' can only be used in the entity explode event" with "supported events message did not get sent correctly"
From 27c4591429336e50ab0501cc25f8b84efa136204 Mon Sep 17 00:00:00 2001
From: Efnilite <35348263+Efnilite@users.noreply.github.com>
Date: Sun, 29 Dec 2024 22:19:55 +0100
Subject: [PATCH 07/19] Add unicode tag (#7312)
* init commit
* remove statics
---------
Co-authored-by: Moderocky
---
src/main/java/ch/njol/skript/Skript.java | 1 -
src/main/java/ch/njol/skript/util/Utils.java | 170 +++++++++---------
.../njol/skript/util/chat/ChatMessages.java | 13 +-
src/test/skript/tests/misc/unicode.sk | 6 +
4 files changed, 92 insertions(+), 98 deletions(-)
create mode 100644 src/test/skript/tests/misc/unicode.sk
diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java
index 3c245a78615..8bcbffa039b 100644
--- a/src/main/java/ch/njol/skript/Skript.java
+++ b/src/main/java/ch/njol/skript/Skript.java
@@ -209,7 +209,6 @@ public Skript() throws IllegalStateException {
/**
* Check minecraft version and assign it to minecraftVersion field
* This method is created to update MC version before onEnable method
- * To fix {@link Utils#HEX_SUPPORTED} being assigned before minecraftVersion is properly assigned
*/
public static void updateMinecraftVersion() {
String bukkitV = Bukkit.getBukkitVersion();
diff --git a/src/main/java/ch/njol/skript/util/Utils.java b/src/main/java/ch/njol/skript/util/Utils.java
index 7b54dd78ccf..dc2b253e158 100644
--- a/src/main/java/ch/njol/skript/util/Utils.java
+++ b/src/main/java/ch/njol/skript/util/Utils.java
@@ -1,5 +1,27 @@
package ch.njol.skript.util;
+import ch.njol.skript.Skript;
+import ch.njol.skript.effects.EffTeleport;
+import ch.njol.skript.localization.Language;
+import ch.njol.skript.localization.LanguageChangeListener;
+import ch.njol.skript.registrations.Classes;
+import ch.njol.util.*;
+import ch.njol.util.coll.CollectionUtils;
+import ch.njol.util.coll.iterator.EnumerationIterable;
+import com.google.common.collect.Iterables;
+import com.google.common.io.ByteArrayDataInput;
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+import net.md_5.bungee.api.ChatColor;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.bukkit.plugin.messaging.Messenger;
+import org.bukkit.plugin.messaging.PluginMessageListener;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
@@ -13,34 +35,6 @@
import java.util.regex.Pattern;
import java.util.stream.Stream;
-import org.bukkit.Bukkit;
-import org.bukkit.entity.Player;
-import org.bukkit.plugin.Plugin;
-import org.bukkit.plugin.java.JavaPlugin;
-import org.bukkit.plugin.messaging.Messenger;
-import org.bukkit.plugin.messaging.PluginMessageListener;
-
-import com.google.common.collect.Iterables;
-import com.google.common.io.ByteArrayDataInput;
-import com.google.common.io.ByteArrayDataOutput;
-import com.google.common.io.ByteStreams;
-
-import ch.njol.skript.Skript;
-import ch.njol.skript.effects.EffTeleport;
-import ch.njol.skript.localization.Language;
-import ch.njol.skript.localization.LanguageChangeListener;
-import ch.njol.skript.registrations.Classes;
-import ch.njol.util.Callback;
-import ch.njol.util.Checker;
-import ch.njol.util.NonNullPair;
-import ch.njol.util.Pair;
-import ch.njol.util.StringUtils;
-import ch.njol.util.coll.CollectionUtils;
-import ch.njol.util.coll.iterator.EnumerationIterable;
-import net.md_5.bungee.api.ChatColor;
-import org.jetbrains.annotations.Nullable;
-import org.jetbrains.annotations.NotNull;
-
/**
* Utility class.
*
@@ -572,9 +566,6 @@ public static CompletableFuture sendPluginMessage(Player pla
final static Map chat = new HashMap<>();
final static Map englishChat = new HashMap<>();
- public final static boolean HEX_SUPPORTED = Skript.isRunningMinecraft(1, 16);
- public final static boolean COPY_SUPPORTED = Skript.isRunningMinecraft(1, 15);
-
static {
Language.addListener(new LanguageChangeListener() {
@Override
@@ -601,43 +592,17 @@ public static String getChatStyle(final String s) {
return chat.get(s);
}
- private final static Pattern stylePattern = Pattern.compile("<([^<>]+)>");
-
/**
* Replaces <chat styles> in the message
*
* @param message
* @return message with localised chat styles converted to Minecraft's format
*/
- public static String replaceChatStyles(final String message) {
+ public static @NotNull String replaceChatStyles(String message) {
if (message.isEmpty())
return message;
- String m = StringUtils.replaceAll(Matcher.quoteReplacement("" + message.replace("<>", "")), stylePattern, new Callback() {
- @Override
- public String run(final Matcher m) {
- SkriptColor color = SkriptColor.fromName("" + m.group(1));
- if (color != null)
- return color.getFormattedChat();
- final String tag = m.group(1).toLowerCase(Locale.ENGLISH);
- final String f = chat.get(tag);
- if (f != null)
- return f;
- if (HEX_SUPPORTED && tag.startsWith("#")) { // Check for parsing hex colors
- ChatColor chatColor = parseHexColor(tag);
- if (chatColor != null)
- return chatColor.toString();
- }
- return "" + m.group();
- }
- });
- assert m != null;
- // Restore user input post-sanitization
- // Sometimes, the message has already been restored
- if (!message.equals(m)) {
- m = m.replace("\\$", "$").replace("\\\\", "\\");
- }
- m = ChatColor.translateAlternateColorCodes('&', "" + m);
- return "" + m;
+
+ return replaceChatStyle(message.replace("<>", ""));
}
/**
@@ -647,54 +612,81 @@ public String run(final Matcher m) {
* @param message
* @return message with english chat styles converted to Minecraft's format
*/
- public static String replaceEnglishChatStyles(final String message) {
+ public static @NotNull String replaceEnglishChatStyles(String message) {
if (message.isEmpty())
return message;
- String m = StringUtils.replaceAll(Matcher.quoteReplacement(message), stylePattern, new Callback() {
- @Override
- public String run(final Matcher m) {
- SkriptColor color = SkriptColor.fromName("" + m.group(1));
- if (color != null)
- return color.getFormattedChat();
- final String tag = m.group(1).toLowerCase(Locale.ENGLISH);
- final String f = englishChat.get(tag);
- if (f != null)
- return f;
- if (HEX_SUPPORTED && tag.startsWith("#")) { // Check for parsing hex colors
- ChatColor chatColor = parseHexColor(tag);
- if (chatColor != null)
- return chatColor.toString();
- }
- return "" + m.group();
+
+ return replaceChatStyle(message);
+ }
+
+ private final static Pattern STYLE_PATTERN = Pattern.compile("<([^<>]+)>");
+
+ private static @NotNull String replaceChatStyle(String message) {
+ String m = StringUtils.replaceAll(Matcher.quoteReplacement(message), STYLE_PATTERN, matcher -> {
+ SkriptColor color = SkriptColor.fromName(matcher.group(1));
+ if (color != null)
+ return color.getFormattedChat();
+
+ String tag = matcher.group(1).toLowerCase(Locale.ENGLISH);
+ String f = englishChat.get(tag);
+ if (f != null)
+ return f;
+
+ if (tag.startsWith("#")) {
+ ChatColor chatColor = parseHexColor(tag);
+ if (chatColor != null)
+ return chatColor.toString();
+ } else if (tag.startsWith("u:") || tag.startsWith("unicode:")) {
+ String character = parseUnicode(tag);
+ if (character != null)
+ return character;
}
+ return matcher.group();
});
- assert m != null;
+
// Restore user input post-sanitization
// Sometimes, the message has already been restored
if (!message.equals(m)) {
m = m.replace("\\$", "$").replace("\\\\", "\\");
}
- m = ChatColor.translateAlternateColorCodes('&', "" + m);
- return "" + m;
+
+ return ChatColor.translateAlternateColorCodes('&', m);
}
- private static final Pattern HEX_PATTERN = Pattern.compile("(?i)#{0,2}[0-9a-f]{6}");
+ private static final Pattern UNICODE_PATTERN = Pattern.compile("(?i)u(?:nicode)?:(?[0-9a-f]{4,})");
+
+ /**
+ * Tries to extract a Unicode character from the given string.
+ * @param string The string.
+ * @return The Unicode character, or null if it could not be parsed.
+ */
+ public static @Nullable String parseUnicode(String string) {
+ Matcher matcher = UNICODE_PATTERN.matcher(string);
+ if (!matcher.matches())
+ return null;
+
+ try {
+ return Character.toString(Integer.parseInt(matcher.group("code"), 16));
+ } catch (IllegalArgumentException ex) {
+ return null;
+ }
+ }
+
+ private static final Pattern HEX_PATTERN = Pattern.compile("(?i)#{0,2}(?[0-9a-f]{6})");
/**
* Tries to get a {@link ChatColor} from the given string.
- * @param hex The hex code to parse.
+ * @param string The string code to parse.
* @return The ChatColor, or null if it couldn't be parsed.
*/
- @SuppressWarnings("null")
- @Nullable
- public static ChatColor parseHexColor(String hex) {
- if (!HEX_SUPPORTED || !HEX_PATTERN.matcher(hex).matches()) // Proper hex code validation
+ public static @Nullable ChatColor parseHexColor(String string) {
+ Matcher matcher = HEX_PATTERN.matcher(string);
+ if (!matcher.matches())
return null;
- hex = hex.replace("#", "");
try {
- return ChatColor.of('#' + hex.substring(0, 6));
- } catch (IllegalArgumentException e) {
+ return ChatColor.of('#' + matcher.group("code"));
+ } catch (IllegalArgumentException ex) {
return null;
}
}
diff --git a/src/main/java/ch/njol/skript/util/chat/ChatMessages.java b/src/main/java/ch/njol/skript/util/chat/ChatMessages.java
index 5498f829179..b8bcd5d4ed9 100644
--- a/src/main/java/ch/njol/skript/util/chat/ChatMessages.java
+++ b/src/main/java/ch/njol/skript/util/chat/ChatMessages.java
@@ -77,8 +77,6 @@ public void onLanguageChange() {
Skript.debug("Parsing message style lang files");
for (SkriptChatCode code : SkriptChatCode.values()) {
assert code != null;
- if (code == SkriptChatCode.copy_to_clipboard && !Utils.COPY_SUPPORTED)
- continue;
registerChatCode(code);
}
@@ -213,7 +211,7 @@ else if (c2 == '>')
}
name = name.toLowerCase(Locale.ENGLISH); // Tags are case-insensitive
- boolean tryHex = Utils.HEX_SUPPORTED && name.startsWith("#");
+ boolean tryHex = name.startsWith("#");
ChatColor chatColor = null;
if (tryHex) {
chatColor = Utils.parseHexColor(name);
@@ -261,7 +259,7 @@ else if (c2 == '>')
char color = chars[i + 1];
- boolean tryHex = Utils.HEX_SUPPORTED && color == 'x';
+ boolean tryHex = color == 'x';
ChatColor chatColor = null;
if (tryHex && i + 14 < chars.length) { // Try to parse hex "&x&1&2&3&4&5&6"
chatColor = Utils.parseHexColor(msg.substring(i + 2, i + 14).replace("&", "").replace("§", ""));
@@ -424,7 +422,7 @@ public static List fromParsedString(String msg) {
char color = chars[i + 1];
- boolean tryHex = Utils.HEX_SUPPORTED && color == 'x';
+ boolean tryHex = color == 'x';
ChatColor chatColor = null;
if (tryHex && i + 14 < chars.length) { // Try to parse hex "&x&1&2&3&4&5&6"
chatColor = Utils.parseHexColor(msg.substring(i + 2, i + 14).replace("&", "").replace("§", ""));
@@ -582,9 +580,8 @@ public static String stripStyles(String text) {
builder.append(component.text);
}
String plain = builder.toString();
-
- if (Utils.HEX_SUPPORTED) // Strip '§x', '&x'
- plain = HEX_COLOR_PATTERN.matcher(plain).replaceAll("");
+
+ plain = HEX_COLOR_PATTERN.matcher(plain).replaceAll("");
result = ANY_COLOR_PATTERN.matcher(plain).replaceAll(""); // strips colors & or § (ex. &5)
} while (!previous.equals(result));
diff --git a/src/test/skript/tests/misc/unicode.sk b/src/test/skript/tests/misc/unicode.sk
new file mode 100644
index 00000000000..3a6e9ba7816
--- /dev/null
+++ b/src/test/skript/tests/misc/unicode.sk
@@ -0,0 +1,6 @@
+test "unicode":
+ assert "" is "§" with "single symbol did not get replaced"
+ assert "" is "§" with "single short symbol did not get replaced"
+
+ assert "aB" is "a🐛B" with "symbol did not get replaced"
+ assert "aB" is "a🐛B" with "short symbol did not get replaced"
From 42ba3ea2cea4045d389f2f853f2859995b32c5a5 Mon Sep 17 00:00:00 2001
From: Efnilite <35348263+Efnilite@users.noreply.github.com>
Date: Sun, 29 Dec 2024 22:21:52 +0100
Subject: [PATCH 08/19] Add event-block to command struct (#7320)
init commit
---
.../java/ch/njol/skript/classes/data/BukkitEventValues.java | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java
index f35f934789d..ab91438aaf2 100644
--- a/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java
+++ b/src/main/java/ch/njol/skript/classes/data/BukkitEventValues.java
@@ -4,6 +4,7 @@
import ch.njol.skript.aliases.ItemType;
import ch.njol.skript.bukkitutil.InventoryUtils;
import ch.njol.skript.command.CommandEvent;
+import ch.njol.skript.command.ScriptCommandEvent;
import ch.njol.skript.events.bukkit.ScriptEvent;
import ch.njol.skript.events.bukkit.SkriptStartEvent;
import ch.njol.skript.events.bukkit.SkriptStopEvent;
@@ -25,6 +26,7 @@
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.data.BlockData;
+import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.*;
import org.bukkit.event.block.*;
@@ -965,6 +967,8 @@ public World get(final CommandEvent e) {
return e.getSender() instanceof Player ? ((Player) e.getSender()).getWorld() : null;
}
}, 0);
+ EventValues.registerEventValue(CommandEvent.class, Block.class,
+ event -> event.getSender() instanceof BlockCommandSender sender ? sender.getBlock() : null);
// === ServerEvents ===
// Script load/unload event
From a0f3a5b586a02e36c2914c063643336903cd120b Mon Sep 17 00:00:00 2001
From: Patrick Miller
Date: Sun, 29 Dec 2024 16:51:36 -0500
Subject: [PATCH 09/19] New Registration API (#6246)
* First api design
* Rework and implement for expression
* Implement for all syntax elements
* Rename to Skript and other small refactors
* Registration closing
* Child key
* Cleanup
* Moving
* hashcode
* Oops
* TODO
* Move event pattern transformation to the correct place
* Moves key implementation away
* Apply suggestions from code review
Co-authored-by: Patrick Miller
* Apply suggestions from code review
Co-authored-by: Patrick Miller
* Requested changes
* Package rename
* Refactoring
* Deprecation
* Builders
* Use builders
* Certified license header moment
* Not too proud of this one
* Fix tests
* Refactoring
* Replace with shorter version
* Fix order
* Refactoring
* Cherry-pick docs-tool into api-rework
* Fix documentation
* Double tabbing
* Fix registrations
* Attempt 2
* Apply suggestions from code review
Co-authored-by: Patrick Miller
Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com>
* Allows updating of Skript instance.
* Fix error
* Deprecation
* Simpler keys
* SimpleSkriptRegistry
* Priority stuff
* Allow for multiple Skript instances
* Suppliers for syntax elements
* Fixes for event parsing changes
* Improve BukkitOrigin; Remove LegacyEventImpl
* Origin improvements
* Rework SyntaxInfo/Builder system
* Improve Registry and Key system
* Improve SyntaxRegister docs
* Improve SyntaxInfo implementations
* Add missing JavaDocs
* Require builders for creating SyntaxInfos
* Initial implementation of new Addon API
* Address UnderscoreTud's review
* Address Moderocky's review
* Improve compatibility implementation
* Remove Skript State system
* Minor Tweaks
* Localizer system draft
* Localizer Fixes
* Add support for property registration methods
* Add support for unregistering syntax infos
* First pass at SkriptAddon rework
This will not build. This is a design pivot to have SkriptAddon instances returned by registering an addon by name rather than implementing the interface.
* Implement child registries for addons
* Fix language and addon compatibility
* Fix building and tests
* Block registration of interfaces/abstract classes
They are permitted if a creation supplier is provided
* Origin reworks
Added a default SkriptAddon origin and pivoted internally to avoid using an "unknown" origin
* First pass at priority rework
* Add priority support to info builders
* Revert Structure priority changes
* Pivot to a relational priority system
* Replace ExpressionType with priority system
* Revert some unnecessary changes
* Add missing EventValueExpression registration api
* Rework Expression priorities to be for all SyntaxInfos
* Change Skript.createInstance to also return modifiable addon
* Add SyntaxPriority
An implementation of Priority that enables positioning itself around specific SyntaxElement classes.
* Move module loading out of registration
* Add missing PriorityImpl checks
* SyntaxPriority improvements and clarifications
* Limitations on SyntaxPriority
* Alternative module loading method
* Remove unnecessary SyntaxRegister interface
* Fix simple structure support
* Implement Registry interface
* Remove SyntaxPriority Implementation
It is too unstable in its current state. It may return in a future PR.
* Disconnect Event Info from Structure Info
For registration purposes, SkriptEvents are no longer tied to Structures (see StructEvent)
* Allow structures to be simple or section
* Improve SyntaxInfo parameter checks
* Add class loading utilities
* Replace ClassLoader Java 11 methods
* SkriptAddon: remove unnecessary annotations
* ClassLoader: add default loadClasses method
* Use builder method for Expression return type
* Remove license headers
* Prevent SkriptImpl from exposing its addons
* Use the same registry across all addons
Addon-specific registries may return but will need reworked
* Rename SkriptAddon#registry to SkriptAddon#syntaxRegistry
* Add ViewProvider interface
* Improve annotation usage and placement
* Remove deprecation annotations
We plan to merge this as experimental/preview API
* Add listening behavior to Event SyntaxInfo
Forgot about this...
* Add missing experimental annotations
* Rename BukkitInfos to BukkitSyntaxInfos
* Add unmodifiableView for Skript
With this change, I have also removed the weird NonNullPair from creating a default Skript.
* Add registry storage to SkriptAddon
* Add source requirement to SkriptAddon
* Remove source from Localizer
This can be obtained from the addon instance
* Fix old addon registration
* SyntaxInfo equals/hashCode improvements
* Implementation optimizations
* Fix ConditionType support
* Return the constructed info for static registration methods
* Builder interface
* Remove unncessary package infos
* Implement Buildable interface
Allows converting SyntaxInfos back into Builders
* Improve legacy collection methods
* Use a map for modern addon tracking
* Fix SkriptEventInfo Compatibility
* Allow Skript#getAddon to return Skript's addon instance
Fixes current test failures
* Tweak Preconditions check
* ExpressionInfo Builder: require returnType at creation
* Add 'clear' methods to builders
* AddonModule: add init phase
* Fix incorrect Expression Builder uses
---------
Co-authored-by: kiip1 <25848425+kiip1@users.noreply.github.com>
Co-authored-by: LimeGlass <16087552+TheLimeGlass@users.noreply.github.com>
Co-authored-by: Moderocky
Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com>
Co-authored-by: Efnilite <35348263+Efnilite@users.noreply.github.com>
---
src/main/java/ch/njol/skript/Skript.java | 352 +++++++++------
src/main/java/ch/njol/skript/SkriptAddon.java | 119 +++--
.../conditions/base/PropertyCondition.java | 56 ++-
.../base/EventValueExpression.java | 34 ++
.../expressions/base/PropertyExpression.java | 74 +++-
.../java/ch/njol/skript/lang/Condition.java | 27 +-
.../ch/njol/skript/lang/ExpressionType.java | 45 +-
.../java/ch/njol/skript/lang/SkriptEvent.java | 29 +-
.../ch/njol/skript/lang/SkriptEventInfo.java | 135 +++++-
.../njol/skript/lang/SyntaxElementInfo.java | 54 ++-
.../ch/njol/skript/localization/Language.java | 133 ++++--
src/main/java/ch/njol/skript/util/Utils.java | 105 ++---
.../java/org/skriptlang/skript/Skript.java | 56 +++
.../org/skriptlang/skript/SkriptImpl.java | 263 +++++++++++
.../skriptlang/skript/addon/AddonModule.java | 36 ++
.../skriptlang/skript/addon/SkriptAddon.java | 112 +++++
.../skript/addon/SkriptAddonImpl.java | 79 ++++
.../registration/BukkitRegistryKeys.java | 22 +
.../registration/BukkitSyntaxInfos.java | 353 +++++++++++++++
.../registration/BukkitSyntaxInfosImpl.java | 419 ++++++++++++++++++
.../skript/lang/structure/Structure.java | 10 +-
.../skript/lang/structure/StructureInfo.java | 24 +-
.../skript/localization/Localizer.java | 66 +++
.../skript/localization/LocalizerImpl.java | 74 ++++
.../registration/DefaultSyntaxInfos.java | 183 ++++++++
.../registration/DefaultSyntaxInfosImpl.java | 200 +++++++++
.../skript/registration/SyntaxInfo.java | 173 ++++++++
.../skript/registration/SyntaxInfoImpl.java | 198 +++++++++
.../skript/registration/SyntaxOrigin.java | 60 +++
.../skript/registration/SyntaxRegister.java | 41 ++
.../skript/registration/SyntaxRegistry.java | 158 +++++++
.../registration/SyntaxRegistryImpl.java | 156 +++++++
.../org/skriptlang/skript/util/Builder.java | 42 ++
.../skriptlang/skript/util/ClassLoader.java | 279 ++++++++++++
.../skriptlang/skript/util/ClassUtils.java | 19 +
.../org/skriptlang/skript/util/Priority.java | 55 +++
.../skriptlang/skript/util/PriorityImpl.java | 82 ++++
.../org/skriptlang/skript/util/Registry.java | 31 ++
.../skriptlang/skript/util/ViewProvider.java | 22 +
39 files changed, 4053 insertions(+), 323 deletions(-)
create mode 100644 src/main/java/org/skriptlang/skript/Skript.java
create mode 100644 src/main/java/org/skriptlang/skript/SkriptImpl.java
create mode 100644 src/main/java/org/skriptlang/skript/addon/AddonModule.java
create mode 100644 src/main/java/org/skriptlang/skript/addon/SkriptAddon.java
create mode 100644 src/main/java/org/skriptlang/skript/addon/SkriptAddonImpl.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/registration/BukkitRegistryKeys.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/registration/BukkitSyntaxInfos.java
create mode 100644 src/main/java/org/skriptlang/skript/bukkit/registration/BukkitSyntaxInfosImpl.java
create mode 100644 src/main/java/org/skriptlang/skript/localization/Localizer.java
create mode 100644 src/main/java/org/skriptlang/skript/localization/LocalizerImpl.java
create mode 100644 src/main/java/org/skriptlang/skript/registration/DefaultSyntaxInfos.java
create mode 100644 src/main/java/org/skriptlang/skript/registration/DefaultSyntaxInfosImpl.java
create mode 100644 src/main/java/org/skriptlang/skript/registration/SyntaxInfo.java
create mode 100644 src/main/java/org/skriptlang/skript/registration/SyntaxInfoImpl.java
create mode 100644 src/main/java/org/skriptlang/skript/registration/SyntaxOrigin.java
create mode 100644 src/main/java/org/skriptlang/skript/registration/SyntaxRegister.java
create mode 100644 src/main/java/org/skriptlang/skript/registration/SyntaxRegistry.java
create mode 100644 src/main/java/org/skriptlang/skript/registration/SyntaxRegistryImpl.java
create mode 100644 src/main/java/org/skriptlang/skript/util/Builder.java
create mode 100644 src/main/java/org/skriptlang/skript/util/ClassLoader.java
create mode 100644 src/main/java/org/skriptlang/skript/util/ClassUtils.java
create mode 100644 src/main/java/org/skriptlang/skript/util/Priority.java
create mode 100644 src/main/java/org/skriptlang/skript/util/PriorityImpl.java
create mode 100644 src/main/java/org/skriptlang/skript/util/Registry.java
create mode 100644 src/main/java/org/skriptlang/skript/util/ViewProvider.java
diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java
index 8bcbffa039b..33adc41a12b 100644
--- a/src/main/java/ch/njol/skript/Skript.java
+++ b/src/main/java/ch/njol/skript/Skript.java
@@ -62,7 +62,6 @@
import ch.njol.skript.variables.Variables;
import ch.njol.util.Closeable;
import ch.njol.util.Kleenean;
-import ch.njol.util.NullableChecker;
import ch.njol.util.StringUtils;
import ch.njol.util.coll.iterator.CheckedIterator;
import ch.njol.util.coll.iterator.EnumerationIterable;
@@ -87,11 +86,15 @@
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.java.JavaPlugin;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Unmodifiable;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
import org.junit.After;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
+import org.skriptlang.skript.bukkit.registration.BukkitRegistryKeys;
+import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos;
import org.junit.runner.notification.Failure;
import org.skriptlang.skript.bukkit.SkriptMetrics;
import org.skriptlang.skript.bukkit.breeding.BreedingModule;
@@ -109,6 +112,9 @@
import org.skriptlang.skript.lang.script.Script;
import org.skriptlang.skript.lang.structure.Structure;
import org.skriptlang.skript.lang.structure.StructureInfo;
+import org.skriptlang.skript.registration.SyntaxOrigin;
+import org.skriptlang.skript.registration.SyntaxRegistry;
+import org.skriptlang.skript.registration.SyntaxInfo;
import java.io.File;
import java.io.IOException;
@@ -143,6 +149,8 @@
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
@@ -181,14 +189,24 @@ public final class Skript extends JavaPlugin implements Listener {
@Nullable
private static Skript instance = null;
+ static org.skriptlang.skript.@UnknownNullability Skript skript = null;
+ private static org.skriptlang.skript.@UnknownNullability Skript unmodifiableSkript = null;
+
private static boolean disabled = false;
private static boolean partDisabled = false;
public static Skript getInstance() {
- final Skript i = instance;
- if (i == null)
+ if (instance == null)
throw new IllegalStateException();
- return i;
+ return instance;
+ }
+
+ @ApiStatus.Experimental
+ public static org.skriptlang.skript.Skript instance() {
+ if (unmodifiableSkript == null) {
+ throw new SkriptAPIException("Skript is still initializing");
+ }
+ return unmodifiableSkript;
}
/**
@@ -393,8 +411,6 @@ public void onEnable() {
} catch (Exception e) {
Skript.exception(e, "Update checker could not be initialized.");
}
- experimentRegistry = new ExperimentRegistry(this);
- Feature.registerAll(getAddonInstance(), experimentRegistry);
if (!getDataFolder().isDirectory())
getDataFolder().mkdirs();
@@ -469,9 +485,17 @@ public void onEnable() {
}
}
- // initialize the Skript addon instance
+ // initialize the modern Skript instance
+ skript = org.skriptlang.skript.Skript.of(getClass(), getName());
+ unmodifiableSkript = skript.unmodifiableView();
+ skript.localizer().setSourceDirectories("lang",
+ getDataFolder().getAbsolutePath() + "lang");
+ // initialize the old Skript SkriptAddon instance
getAddonInstance();
+ experimentRegistry = new ExperimentRegistry(this);
+ Feature.registerAll(getAddonInstance(), experimentRegistry);
+
// Load classes which are always safe to use
new JavaClasses(); // These may be needed in configuration
@@ -619,7 +643,6 @@ public void run() {
stopAcceptingRegistrations();
-
Documentation.generate(); // TODO move to test classes?
// Variable loading
@@ -1226,7 +1249,6 @@ private boolean isServerRunning() {
private void beforeDisable() {
partDisabled = true;
EvtSkript.onSkriptStop(); // TODO [code style] warn user about delays in Skript stop events
-
ScriptLoader.unloadScripts(ScriptLoader.getLoadedScripts());
}
@@ -1338,194 +1360,231 @@ public static void checkAcceptRegistrations() {
private static void stopAcceptingRegistrations() {
Converters.createChainedConverters();
-
acceptRegistrations = false;
-
Classes.onRegistrationsStop();
}
// ================ ADDONS ================
- private final static HashMap addons = new HashMap<>();
+ @Deprecated
+ private static final Set addons = new HashSet<>();
/**
* Registers an addon to Skript. This is currently not required for addons to work, but the returned {@link SkriptAddon} provides useful methods for registering syntax elements
* and adding new strings to Skript's localization system (e.g. the required "types.[type]" strings for registered classes).
- *
- * @param p The plugin
+ *
+ * @param plugin The plugin
*/
- public static SkriptAddon registerAddon(final JavaPlugin p) {
+ public static SkriptAddon registerAddon(JavaPlugin plugin) {
checkAcceptRegistrations();
- if (addons.containsKey(p.getName()))
- throw new IllegalArgumentException("The plugin " + p.getName() + " is already registered");
- final SkriptAddon addon = new SkriptAddon(p);
- addons.put(p.getName(), addon);
+ SkriptAddon addon = new SkriptAddon(plugin);
+ addons.add(addon);
return addon;
}
- @Nullable
- public static SkriptAddon getAddon(final JavaPlugin p) {
- return addons.get(p.getName());
+ public static @Nullable SkriptAddon getAddon(JavaPlugin plugin) {
+ if (plugin == Skript.getInstance()) {
+ return Skript.getAddonInstance();
+ }
+ for (SkriptAddon addon : getAddons()) {
+ if (addon.plugin == plugin) {
+ return addon;
+ }
+ }
+ return null;
}
- @Nullable
- public static SkriptAddon getAddon(final String name) {
- return addons.get(name);
+ public static @Nullable SkriptAddon getAddon(String name) {
+ if (name.equals(Skript.getInstance().getName())) {
+ return Skript.getAddonInstance();
+ }
+ for (SkriptAddon addon : getAddons()) {
+ if (addon.getName().equals(name)) {
+ return addon;
+ }
+ }
+ return null;
}
- @SuppressWarnings("null")
- public static Collection getAddons() {
- return Collections.unmodifiableCollection(addons.values());
+ public static @Unmodifiable Collection getAddons() {
+ Set addons = new HashSet<>(Skript.addons);
+ addons.addAll(instance().addons().stream()
+ .filter(addon -> addons.stream().noneMatch(oldAddon -> oldAddon.name().equals(addon.name())))
+ .map(SkriptAddon::fromModern)
+ .collect(Collectors.toSet())
+ );
+ return Collections.unmodifiableCollection(addons);
}
- @Nullable
- private static SkriptAddon addon;
+ @Deprecated
+ private static @Nullable SkriptAddon addon;
/**
* @return A {@link SkriptAddon} representing Skript.
*/
public static SkriptAddon getAddonInstance() {
if (addon == null) {
- addon = new SkriptAddon(Skript.getInstance());
- addon.setLanguageFileDirectory("lang");
+ addon = SkriptAddon.fromModern(instance());
}
return addon;
}
// ================ CONDITIONS & EFFECTS & SECTIONS ================
- private static final List> conditions = new ArrayList<>(50);
- private static final List> effects = new ArrayList<>(50);
- private static final List> statements = new ArrayList<>(100);
- private static final List> sections = new ArrayList<>(50);
+ private static final class BukkitOrigin implements SyntaxOrigin {
- public static Collection> getStatements() {
- return statements;
- }
+ private final String name;
- public static Collection> getEffects() {
- return effects;
- }
+ private BukkitOrigin(Plugin plugin) {
+ this.name = plugin.getName();
+ }
- public static Collection> getSections() {
- return sections;
- }
+ @Override
+ public String name() {
+ return name;
+ }
- // ================ CONDITIONS ================
- public static Collection> getConditions() {
- return conditions;
}
- private final static int[] conditionTypesStartIndices = new int[ConditionType.values().length];
+ private static SyntaxOrigin getSyntaxOrigin(JavaPlugin plugin) {
+ SkriptAddon addon = getAddon(plugin);
+ if (addon != null) {
+ return SyntaxOrigin.of(addon);
+ }
+ return new BukkitOrigin(plugin);
+ }
/**
- * registers a {@link Condition}.
- *
- * @param condition The condition's class
+ * Registers a {@link Condition}.
+ *
+ * @param conditionClass The condition's class
* @param patterns Skript patterns to match this condition
*/
- public static void registerCondition(Class condition, String... patterns) throws IllegalArgumentException {
- registerCondition(condition, ConditionType.COMBINED, patterns);
+ public static void registerCondition(Class conditionClass, String... patterns) throws IllegalArgumentException {
+ registerCondition(conditionClass, ConditionType.COMBINED, patterns);
}
/**
- * registers a {@link Condition}.
- *
- * @param condition The condition's class
- * @param type The conditions {@link ConditionType type}. This is used to determine in which order to try to parse conditions.
+ * Registers a {@link Condition}.
+ *
+ * @param conditionClass The condition's class
+ * @param type The type of condition which affects its priority in the parsing search
* @param patterns Skript patterns to match this condition
*/
- public static void registerCondition(Class condition, ConditionType type, String... patterns) throws IllegalArgumentException {
+ public static void registerCondition(Class conditionClass, ConditionType type, String... patterns) throws IllegalArgumentException {
checkAcceptRegistrations();
- String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName();
- final SyntaxElementInfo info = new SyntaxElementInfo<>(patterns, condition, originClassPath);
- conditions.add(conditionTypesStartIndices[type.ordinal()], info);
- statements.add(conditionTypesStartIndices[type.ordinal()], info);
- for (int i = type.ordinal(); i < ConditionType.values().length; i++)
- conditionTypesStartIndices[i]++;
+ skript.syntaxRegistry().register(SyntaxRegistry.CONDITION, SyntaxInfo.builder(conditionClass)
+ .priority(type.priority())
+ .origin(getSyntaxOrigin(JavaPlugin.getProvidingPlugin(conditionClass)))
+ .addPatterns(patterns)
+ .build()
+ );
}
/**
* Registers an {@link Effect}.
- *
- * @param effect The effect's class
+ *
+ * @param effectClass The effect's class
* @param patterns Skript patterns to match this effect
*/
- public static void registerEffect(final Class effect, final String... patterns) throws IllegalArgumentException {
+ public static void registerEffect(Class effectClass, String... patterns) throws IllegalArgumentException {
checkAcceptRegistrations();
- String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName();
- final SyntaxElementInfo info = new SyntaxElementInfo<>(patterns, effect, originClassPath);
- effects.add(info);
- statements.add(info);
+ skript.syntaxRegistry().register(SyntaxRegistry.EFFECT, SyntaxInfo.builder(effectClass)
+ .origin(getSyntaxOrigin(JavaPlugin.getProvidingPlugin(effectClass)))
+ .addPatterns(patterns)
+ .build()
+ );
}
/**
* Registers a {@link Section}.
*
- * @param section The section's class
+ * @param sectionClass The section's class
* @param patterns Skript patterns to match this section
* @see Section
*/
- public static void registerSection(Class section, String... patterns) throws IllegalArgumentException {
+ public static void registerSection(Class sectionClass, String... patterns) throws IllegalArgumentException {
checkAcceptRegistrations();
- String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName();
- SyntaxElementInfo info = new SyntaxElementInfo<>(patterns, section, originClassPath);
- sections.add(info);
+ skript.syntaxRegistry().register(SyntaxRegistry.SECTION, SyntaxInfo.builder(sectionClass)
+ .origin(getSyntaxOrigin(JavaPlugin.getProvidingPlugin(sectionClass)))
+ .addPatterns(patterns)
+ .build()
+ );
}
- // ================ EXPRESSIONS ================
+ public static @Unmodifiable Collection> getStatements() {
+ return instance().syntaxRegistry()
+ .syntaxes(SyntaxRegistry.STATEMENT).stream()
+ .map(SyntaxElementInfo::, Statement>fromModern)
+ .collect(Collectors.toUnmodifiableList());
+ }
+
+ public static @Unmodifiable Collection> getConditions() {
+ return instance().syntaxRegistry()
+ .syntaxes(SyntaxRegistry.CONDITION).stream()
+ .map(SyntaxElementInfo::, Condition>fromModern)
+ .collect(Collectors.toUnmodifiableList());
+ }
+
+ public static @Unmodifiable Collection> getEffects() {
+ return instance().syntaxRegistry()
+ .syntaxes(SyntaxRegistry.EFFECT).stream()
+ .map(SyntaxElementInfo::, Effect>fromModern)
+ .collect(Collectors.toUnmodifiableList());
+ }
- private final static List> expressions = new ArrayList<>(100);
+ public static @Unmodifiable Collection> getSections() {
+ return instance().syntaxRegistry()
+ .syntaxes(SyntaxRegistry.SECTION).stream()
+ .map(SyntaxElementInfo::, Section>fromModern)
+ .collect(Collectors.toUnmodifiableList());
+ }
- private final static int[] expressionTypesStartIndices = new int[ExpressionType.values().length];
+ // ================ EXPRESSIONS ================
/**
* Registers an expression.
- *
- * @param c The expression's class
+ *
+ * @param expressionType The expression's class
* @param returnType The superclass of all values returned by the expression
* @param type The expression's {@link ExpressionType type}. This is used to determine in which order to try to parse expressions.
* @param patterns Skript patterns that match this expression
* @throws IllegalArgumentException if returnType is not a normal class
*/
- public static , T> void registerExpression(final Class c, final Class returnType, final ExpressionType type, final String... patterns) throws IllegalArgumentException {
+ public static , T> void registerExpression(
+ Class expressionType, Class returnType, ExpressionType type, String... patterns
+ ) throws IllegalArgumentException {
checkAcceptRegistrations();
- if (returnType.isAnnotation() || returnType.isArray() || returnType.isPrimitive())
- throw new IllegalArgumentException("returnType must be a normal type");
- String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName();
- final ExpressionInfo info = new ExpressionInfo<>(patterns, returnType, c, originClassPath, type);
- expressions.add(expressionTypesStartIndices[type.ordinal()], info);
- for (int i = type.ordinal(); i < ExpressionType.values().length; i++) {
- expressionTypesStartIndices[i]++;
- }
+ skript.syntaxRegistry().register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(expressionType, returnType)
+ .priority(type.priority())
+ .origin(getSyntaxOrigin(JavaPlugin.getProvidingPlugin(expressionType)))
+ .addPatterns(patterns)
+ .build()
+ );
}
- @SuppressWarnings("null")
public static Iterator> getExpressions() {
- return expressions.iterator();
- }
-
- public static Iterator> getExpressions(final Class>... returnTypes) {
- return new CheckedIterator<>(getExpressions(), new NullableChecker>() {
- @Override
- public boolean check(final @Nullable ExpressionInfo, ?> i) {
- if (i == null || i.returnType == Object.class)
+ List> list = new ArrayList<>();
+ for (SyntaxInfo.Expression, ?> info : instance().syntaxRegistry().syntaxes(SyntaxRegistry.EXPRESSION))
+ list.add((ExpressionInfo, ?>) SyntaxElementInfo.fromModern(info));
+ return list.iterator();
+ }
+
+ public static Iterator> getExpressions(Class>... returnTypes) {
+ return new CheckedIterator<>(getExpressions(), info -> {
+ if (info == null || info.returnType == Object.class)
+ return true;
+ for (Class> returnType : returnTypes) {
+ assert returnType != null;
+ if (Converters.converterExists(info.returnType, returnType))
return true;
- for (final Class> returnType : returnTypes) {
- assert returnType != null;
- if (Converters.converterExists(i.returnType, returnType))
- return true;
- }
- return false;
}
+ return false;
});
}
// ================ EVENTS ================
- private static final List> events = new ArrayList<>(50);
- private static final List> structures = new ArrayList<>(10);
-
/**
* Registers an event.
*
@@ -1545,51 +1604,66 @@ public static SkriptEventInfo registerEvent(String na
* Registers an event.
*
* @param name The name of the event, used for error messages
- * @param c The event's class
+ * @param eventClass The event's class
* @param events The Bukkit events this event applies to
* @param patterns Skript patterns to match this event
* @return A SkriptEventInfo representing the registered event. Used to generate Skript's documentation.
*/
- public static SkriptEventInfo registerEvent(String name, Class c, Class extends Event>[] events, String... patterns) {
+ @SuppressWarnings("ConstantConditions") // caused by bad array annotations
+ public static SkriptEventInfo registerEvent(
+ String name, Class eventClass, Class extends Event>[] events, String... patterns
+ ) {
checkAcceptRegistrations();
- String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName();
-
- String[] transformedPatterns = new String[patterns.length];
for (int i = 0; i < patterns.length; i++)
- transformedPatterns[i] = SkriptEvent.fixPattern(patterns[i]);
-
- SkriptEventInfo r = new SkriptEventInfo<>(name, transformedPatterns, c, originClassPath, events);
- Skript.events.add(r);
- return r;
+ patterns[i] = BukkitSyntaxInfos.fixPattern(patterns[i]);
+ var legacy = new SkriptEventInfo.ModernSkriptEventInfo<>(name, patterns, eventClass, "", events);
+ skript.syntaxRegistry().register(BukkitRegistryKeys.EVENT, legacy);
+ return legacy;
}
- public static void registerStructure(Class c, String... patterns) {
+ public static void registerStructure(Class structureClass, String... patterns) {
checkAcceptRegistrations();
- String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName();
- StructureInfo structureInfo = new StructureInfo<>(patterns, c, originClassPath);
- structures.add(structureInfo);
+ skript.syntaxRegistry().register(SyntaxRegistry.STRUCTURE, SyntaxInfo.Structure.builder(structureClass)
+ .origin(getSyntaxOrigin(JavaPlugin.getProvidingPlugin(structureClass)))
+ .addPatterns(patterns)
+ .build()
+ );
}
- public static void registerSimpleStructure(Class c, String... patterns) {
+ public static void registerSimpleStructure(Class structureClass, String... patterns) {
checkAcceptRegistrations();
- String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName();
- StructureInfo structureInfo = new StructureInfo<>(patterns, c, originClassPath, true);
- structures.add(structureInfo);
+ skript.syntaxRegistry().register(SyntaxRegistry.STRUCTURE, SyntaxInfo.Structure.builder(structureClass)
+ .origin(getSyntaxOrigin(JavaPlugin.getProvidingPlugin(structureClass)))
+ .addPatterns(patterns)
+ .nodeType(SyntaxInfo.Structure.NodeType.SIMPLE)
+ .build()
+ );
}
- public static void registerStructure(Class c, EntryValidator entryValidator, String... patterns) {
+ public static void registerStructure(
+ Class structureClass, EntryValidator entryValidator, String... patterns
+ ) {
checkAcceptRegistrations();
- String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName();
- StructureInfo structureInfo = new StructureInfo<>(patterns, c, originClassPath, entryValidator);
- structures.add(structureInfo);
- }
-
- public static Collection> getEvents() {
- return events;
- }
-
- public static List> getStructures() {
- return structures;
+ skript.syntaxRegistry().register(SyntaxRegistry.STRUCTURE, SyntaxInfo.Structure.builder(structureClass)
+ .origin(getSyntaxOrigin(JavaPlugin.getProvidingPlugin(structureClass)))
+ .addPatterns(patterns)
+ .entryValidator(entryValidator)
+ .build()
+ );
+ }
+
+ public static @Unmodifiable Collection> getEvents() {
+ return instance().syntaxRegistry()
+ .syntaxes(BukkitRegistryKeys.EVENT).stream()
+ .map(SyntaxElementInfo::, SkriptEvent>fromModern)
+ .collect(Collectors.toUnmodifiableList());
+ }
+
+ public static @Unmodifiable List> getStructures() {
+ return instance().syntaxRegistry()
+ .syntaxes(SyntaxRegistry.STRUCTURE).stream()
+ .map(SyntaxElementInfo::, Structure>fromModern)
+ .collect(Collectors.toUnmodifiableList());
}
// ================ COMMANDS ================
diff --git a/src/main/java/ch/njol/skript/SkriptAddon.java b/src/main/java/ch/njol/skript/SkriptAddon.java
index 5d9d8aafbd8..f09bdcf4e5d 100644
--- a/src/main/java/ch/njol/skript/SkriptAddon.java
+++ b/src/main/java/ch/njol/skript/SkriptAddon.java
@@ -2,49 +2,60 @@
import java.io.File;
import java.io.IOException;
+import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.bukkit.Bukkit;
+import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.Nullable;
-import ch.njol.skript.localization.Language;
import ch.njol.skript.util.Utils;
import ch.njol.skript.util.Version;
+import org.jetbrains.annotations.ApiStatus;
+import org.skriptlang.skript.localization.Localizer;
+import org.skriptlang.skript.registration.SyntaxRegistry;
+import org.skriptlang.skript.util.Registry;
/**
* Utility class for Skript addons. Use {@link Skript#registerAddon(JavaPlugin)} to create a SkriptAddon instance for your plugin.
*/
-public final class SkriptAddon {
+public final class SkriptAddon implements org.skriptlang.skript.addon.SkriptAddon {
public final JavaPlugin plugin;
public final Version version;
private final String name;
+ private final org.skriptlang.skript.addon.SkriptAddon addon;
+
/**
* Package-private constructor. Use {@link Skript#registerAddon(JavaPlugin)} to get a SkriptAddon for your plugin.
- *
- * @param p
*/
- SkriptAddon(final JavaPlugin p) {
- plugin = p;
- name = "" + p.getName();
- Version v;
+ SkriptAddon(JavaPlugin plugin) {
+ this(plugin, Skript.skript.registerAddon(plugin.getClass(), plugin.getName()));
+ }
+
+ SkriptAddon(JavaPlugin plugin, org.skriptlang.skript.addon.SkriptAddon addon) {
+ this.addon = addon;
+ this.plugin = plugin;
+ this.name = plugin.getName();
+ Version version;
try {
- v = new Version("" + p.getDescription().getVersion());
- } catch (final IllegalArgumentException e) {
- final Matcher m = Pattern.compile("(\\d+)(?:\\.(\\d+)(?:\\.(\\d+))?)?").matcher(p.getDescription().getVersion());
+ version = new Version(plugin.getDescription().getVersion());
+ } catch (IllegalArgumentException e) {
+ final Matcher m = Pattern.compile("(\\d+)(?:\\.(\\d+)(?:\\.(\\d+))?)?").matcher(plugin.getDescription().getVersion());
if (!m.find())
- throw new IllegalArgumentException("The version of the plugin " + p.getName() + " does not contain any numbers: " + p.getDescription().getVersion());
- v = new Version(Utils.parseInt("" + m.group(1)), m.group(2) == null ? 0 : Utils.parseInt("" + m.group(2)), m.group(3) == null ? 0 : Utils.parseInt("" + m.group(3)));
- Skript.warning("The plugin " + p.getName() + " uses a non-standard version syntax: '" + p.getDescription().getVersion() + "'. Skript will use " + v + " instead.");
+ throw new IllegalArgumentException("The version of the plugin " + name + " does not contain any numbers: " + plugin.getDescription().getVersion());
+ version = new Version(Utils.parseInt(m.group(1)), m.group(2) == null ? 0 : Utils.parseInt(m.group(2)), m.group(3) == null ? 0 : Utils.parseInt(m.group(3)));
+ Skript.warning("The plugin " + name + " uses a non-standard version syntax: '" + plugin.getDescription().getVersion() + "'. Skript will use " + version + " instead.");
}
- version = v;
+ this.version = version;
}
@Override
public final String toString() {
- return name;
+ return getName();
}
public String getName() {
@@ -65,9 +76,6 @@ public SkriptAddon loadClasses(String basePackage, String... subPackages) throws
return this;
}
- @Nullable
- private String languageFileDirectory = null;
-
/**
* Makes Skript load language files from the specified directory, e.g. "lang" or "skript lang" if you have a lang folder yourself. Localised files will be read from the
* plugin's jar and the plugin's data folder, but the default English file is only taken from the jar and must exist!
@@ -76,19 +84,13 @@ public SkriptAddon loadClasses(String basePackage, String... subPackages) throws
* @return This SkriptAddon
*/
public SkriptAddon setLanguageFileDirectory(String directory) {
- if (languageFileDirectory != null)
- throw new IllegalStateException();
- directory = "" + directory.replace('\\', '/');
- if (directory.endsWith("/"))
- directory = "" + directory.substring(0, directory.length() - 1);
- languageFileDirectory = directory;
- Language.loadDefault(this);
+ localizer().setSourceDirectories(directory, plugin.getDataFolder().getAbsolutePath() + directory);
return this;
}
@Nullable
public String getLanguageFileDirectory() {
- return languageFileDirectory;
+ return localizer().languageFileDirectory();
}
@Nullable
@@ -108,4 +110,67 @@ public File getFile() {
return file;
}
+ //
+ // Modern SkriptAddon Compatibility
+ //
+
+ @ApiStatus.Experimental
+ static SkriptAddon fromModern(org.skriptlang.skript.addon.SkriptAddon addon) {
+ return new SkriptAddon(JavaPlugin.getProvidingPlugin(addon.source()), addon);
+ }
+
+ @Override
+ @ApiStatus.Experimental
+ public Class> source() {
+ return addon.source();
+ }
+
+ @Override
+ @ApiStatus.Experimental
+ public String name() {
+ return addon.name();
+ }
+
+ @Override
+ @ApiStatus.Experimental
+ public > void storeRegistry(Class registryClass, R registry) {
+ addon.storeRegistry(registryClass, registry);
+ }
+
+ @Override
+ @ApiStatus.Experimental
+ public void removeRegistry(Class extends Registry>> registryClass) {
+ addon.removeRegistry(registryClass);
+ }
+
+ @Override
+ @ApiStatus.Experimental
+ public boolean hasRegistry(Class extends Registry>> registryClass) {
+ return addon.hasRegistry(registryClass);
+ }
+
+ @Override
+ @ApiStatus.Experimental
+ public > R registry(Class registryClass) {
+ return addon.registry(registryClass);
+ }
+
+ @Override
+ @ApiStatus.Experimental
+ public > R registry(Class registryClass, Supplier putIfAbsent) {
+ return addon.registry(registryClass, putIfAbsent);
+ }
+
+ @Override
+ @ApiStatus.Experimental
+ public SyntaxRegistry syntaxRegistry() {
+ return addon.syntaxRegistry();
+ }
+
+ @Override
+ @ApiStatus.Experimental
+ public Localizer localizer() {
+ return addon.localizer();
+ }
+
}
diff --git a/src/main/java/ch/njol/skript/conditions/base/PropertyCondition.java b/src/main/java/ch/njol/skript/conditions/base/PropertyCondition.java
index b97f0d5fdf5..d539273f252 100644
--- a/src/main/java/ch/njol/skript/conditions/base/PropertyCondition.java
+++ b/src/main/java/ch/njol/skript/conditions/base/PropertyCondition.java
@@ -10,6 +10,10 @@
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.util.Checker;
import ch.njol.util.Kleenean;
+import org.jetbrains.annotations.ApiStatus;
+import org.skriptlang.skript.registration.SyntaxInfo;
+import org.skriptlang.skript.registration.SyntaxRegistry;
+import org.skriptlang.skript.util.Priority;
/**
* This class can be used for an easier writing of conditions that contain only one type in the pattern,
@@ -36,6 +40,14 @@
*/
public abstract class PropertyCondition extends Condition implements Checker {
+ /**
+ * A priority for {@link PropertyCondition}s.
+ * They will be registered before {@link SyntaxInfo#PATTERN_MATCHES_EVERYTHING} expressions
+ * but after {@link SyntaxInfo#COMBINED} expressions.
+ */
+ @ApiStatus.Experimental
+ public static final Priority DEFAULT_PRIORITY = Priority.before(SyntaxInfo.PATTERN_MATCHES_EVERYTHING);
+
/**
* See {@link PropertyCondition} for more info
*/
@@ -65,7 +77,47 @@ public enum PropertyType {
WILL
}
- private Expression extends T> expr;
+ /**
+ * @param registry The SyntaxRegistry to register with.
+ * @param condition The class to register
+ * @param property The property name, for example fly in players can fly
+ * @param type Must be plural, for example players in players can fly
+ * @param The Condition type.
+ * @return The registered {@link SyntaxInfo}.
+ */
+ @ApiStatus.Experimental
+ public static SyntaxInfo register(SyntaxRegistry registry, Class condition, String property, String type) {
+ return register(registry, condition, PropertyType.BE, property, type);
+ }
+
+ /**
+ * @param registry The SyntaxRegistry to register with.
+ * @param condition The class to register
+ * @param propertyType The property type, see {@link PropertyType}
+ * @param property The property name, for example fly in players can fly
+ * @param type Must be plural, for example players in players can fly
+ * @param The Condition type.
+ * @return The registered {@link SyntaxInfo}.
+ */
+ @ApiStatus.Experimental
+ public static SyntaxInfo register(SyntaxRegistry registry, Class condition, PropertyType propertyType, String property, String type) {
+ if (type.contains("%"))
+ throw new SkriptAPIException("The type argument must not contain any '%'s");
+ SyntaxInfo.Builder, E> builder = SyntaxInfo.builder(condition).priority(DEFAULT_PRIORITY);
+ switch (propertyType) {
+ case BE -> builder.addPatterns("%" + type + "% (is|are) " + property,
+ "%" + type + "% (isn't|is not|aren't|are not) " + property);
+ case CAN -> builder.addPatterns("%" + type + "% can " + property,
+ "%" + type + "% (can't|cannot|can not) " + property);
+ case HAVE -> builder.addPatterns("%" + type + "% (has|have) " + property,
+ "%" + type + "% (doesn't|does not|do not|don't) have " + property);
+ case WILL -> builder.addPatterns("%" + type + "% will " + property,
+ "%" + type + "% (will (not|neither)|won't) " + property);
+ }
+ SyntaxInfo info = builder.build();
+ registry.register(SyntaxRegistry.CONDITION, info);
+ return info;
+ }
/**
* Registers a new property condition. The property type is set to {@link PropertyType#BE}.
@@ -123,6 +175,8 @@ public static String[] getPatterns(PropertyType propertyType, String property, S
};
}
+ private Expression extends T> expr;
+
@Override
@SuppressWarnings("unchecked")
public boolean init(Expression>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
diff --git a/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java b/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java
index cb74c70ebe9..a421691ddf5 100644
--- a/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java
+++ b/src/main/java/ch/njol/skript/expressions/base/EventValueExpression.java
@@ -18,7 +18,11 @@
import ch.njol.skript.registrations.EventValues;
import ch.njol.skript.util.Utils;
import ch.njol.util.Kleenean;
+import org.skriptlang.skript.registration.SyntaxInfo;
+import org.skriptlang.skript.registration.SyntaxRegistry;
+import org.skriptlang.skript.util.Priority;
import org.bukkit.event.Event;
+import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.lang.converter.Converter;
@@ -47,6 +51,36 @@
*/
public class EventValueExpression extends SimpleExpression implements DefaultExpression {
+ /**
+ * A priority for {@link EventValueExpression}s.
+ * They will be registered before {@link SyntaxInfo#COMBINED} expressions
+ * but after {@link SyntaxInfo#SIMPLE} expressions.
+ */
+ @ApiStatus.Experimental
+ public static final Priority DEFAULT_PRIORITY = Priority.before(SyntaxInfo.COMBINED);
+
+ /**
+ * Registers an event value expression with the provided pattern.
+ * The syntax info will be forced to use the {@link #DEFAULT_PRIORITY} priority.
+ * This also adds '[the]' to the start of the pattern.
+ *
+ * @param registry The SyntaxRegistry to register with.
+ * @param expressionClass The EventValueExpression class being registered.
+ * @param returnType The class representing the expression's return type.
+ * @param pattern The pattern to match for creating this expression.
+ * @param The return type.
+ * @param The Expression type.
+ * @return The registered {@link SyntaxInfo}.
+ */
+ @ApiStatus.Experimental
+ public static , T> SyntaxInfo.Expression register(SyntaxRegistry registry, Class expressionClass, Class returnType, String pattern) {
+ SyntaxInfo.Expression info = SyntaxInfo.Expression.builder(expressionClass, returnType)
+ .priority(DEFAULT_PRIORITY)
+ .addPattern("[the] " + pattern)
+ .build();
+ registry.register(SyntaxRegistry.EXPRESSION, info);
+ return info;
+ }
/**
* Registers an expression as {@link ExpressionType#EVENT} with the provided pattern.
diff --git a/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java b/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java
index 71f5efa9fca..29a569a4c65 100644
--- a/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java
+++ b/src/main/java/ch/njol/skript/expressions/base/PropertyExpression.java
@@ -8,13 +8,17 @@
import ch.njol.skript.lang.util.SimpleExpression;
import ch.njol.util.Kleenean;
import com.google.common.base.Preconditions;
+import java.util.Arrays;
import org.bukkit.event.Event;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnknownNullability;
import org.skriptlang.skript.lang.converter.Converter;
import org.skriptlang.skript.lang.converter.Converters;
-
-import java.util.Arrays;
+import org.skriptlang.skript.registration.SyntaxInfo;
+import org.skriptlang.skript.registration.SyntaxRegistry;
+import org.skriptlang.skript.util.Priority;
/**
* Represents an expression which represents a property of another one. Remember to set the expression with {@link #setExpr(Expression)} in
@@ -27,18 +31,15 @@ public abstract class PropertyExpression extends SimpleExpression {
/**
* A helper method to get the property patterns given a property, type, and default expression parameter.
- *
- * @param property the property
- * @param fromType the type(s) that the property should apply to
- * @param defaultExpr whether the type(s) should be optional
- *
+ * @param property the property
+ * @param fromType the type(s) that the property should apply to
+ * @param defaultExpr whether the type(s) should be optional
* @return an array of strings representing the patterns of the given property and type(s)
* @throws IllegalArgumentException if property or fromType is null
*/
private static String[] patternsOf(String property, String fromType, boolean defaultExpr) {
- if (property == null || fromType == null)
- throw new IllegalArgumentException("'property' or 'fromType' was null.");
-
+ Preconditions.checkNotNull(property, "property must be present");
+ Preconditions.checkNotNull(fromType, "fromType must be present");
String types = defaultExpr ? "[of %" + fromType + "%]" : "of %" + fromType + "%";
return new String[]{"[the] " + property + " " + types, "%" + fromType + "%'[s] " + property};
}
@@ -71,6 +72,36 @@ public static String[] getDefaultPatterns(String property, String fromType) {
return patternsOf(property, fromType, true);
}
+ /**
+ * A priority for {@link PropertyExpression}s.
+ * They will be registered before {@link SyntaxInfo#PATTERN_MATCHES_EVERYTHING} expressions
+ * but after {@link SyntaxInfo#COMBINED} expressions.
+ */
+ @ApiStatus.Experimental
+ public static final Priority DEFAULT_PRIORITY = Priority.before(SyntaxInfo.PATTERN_MATCHES_EVERYTHING);
+
+ /**
+ * Registers an expression with the two default property patterns "property of %types%" and "%types%'[s] property"
+ *
+ * @param registry The SyntaxRegistry to register with.
+ * @param expressionClass The PropertyExpression class being registered.
+ * @param returnType The class representing the expression's return type.
+ * @param property The name of the property.
+ * @param fromType Should be plural to support multiple objects but doesn't have to be.
+ * @param The return type.
+ * @param The Expression type.
+ * @return The registered {@link SyntaxInfo}.
+ */
+ @ApiStatus.Experimental
+ public static , T> SyntaxInfo.Expression register(SyntaxRegistry registry, Class expressionClass, Class returnType, String property, String fromType) {
+ SyntaxInfo.Expression info = SyntaxInfo.Expression.builder(expressionClass, returnType)
+ .priority(DEFAULT_PRIORITY)
+ .addPatterns(getPatterns(property, fromType))
+ .build();
+ registry.register(SyntaxRegistry.EXPRESSION, info);
+ return info;
+ }
+
/**
* Registers an expression as {@link ExpressionType#PROPERTY} with the two default property patterns "property of %types%" and "%types%'[s] property"
*
@@ -83,6 +114,29 @@ public static void register(Class extends Expression> expressionClass,
Skript.registerExpression(expressionClass, type, ExpressionType.PROPERTY, getPatterns(property, fromType));
}
+ /**
+ * Registers an expression with the two default property patterns "property [of %types%]" and "%types%'[s] property"
+ * This method also makes the expression type optional to force a default expression on the property expression.
+ *
+ * @param registry The SyntaxRegistry to register with.
+ * @param expressionClass The PropertyExpression class being registered.
+ * @param returnType The class representing the expression's return type.
+ * @param property The name of the property.
+ * @param fromType Should be plural to support multiple objects but doesn't have to be.
+ * @param The return type.
+ * @param The Expression type.
+ * @return The registered {@link SyntaxInfo}.
+ */
+ @ApiStatus.Experimental
+ public static , T> SyntaxInfo.Expression registerDefault(SyntaxRegistry registry, Class expressionClass, Class returnType, String property, String fromType) {
+ SyntaxInfo.Expression info = SyntaxInfo.Expression.builder(expressionClass, returnType)
+ .priority(DEFAULT_PRIORITY)
+ .addPatterns(getDefaultPatterns(property, fromType))
+ .build();
+ registry.register(SyntaxRegistry.EXPRESSION, info);
+ return info;
+ }
+
/**
* Registers an expression as {@link ExpressionType#PROPERTY} with the two default property patterns "property [of %types%]" and "%types%'[s] property"
* This method also makes the expression type optional to force a default expression on the property expression.
diff --git a/src/main/java/ch/njol/skript/lang/Condition.java b/src/main/java/ch/njol/skript/lang/Condition.java
index ddb533a6379..31a1810d2b0 100644
--- a/src/main/java/ch/njol/skript/lang/Condition.java
+++ b/src/main/java/ch/njol/skript/lang/Condition.java
@@ -1,12 +1,16 @@
package ch.njol.skript.lang;
import ch.njol.skript.Skript;
+import ch.njol.skript.conditions.base.PropertyCondition;
import ch.njol.skript.lang.util.SimpleExpression;
import ch.njol.util.Checker;
import ch.njol.util.Kleenean;
import org.bukkit.event.Event;
+import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.lang.condition.Conditional;
+import org.skriptlang.skript.registration.SyntaxInfo;
+import org.skriptlang.skript.util.Priority;
import java.util.Iterator;
@@ -23,17 +27,34 @@ public enum ConditionType {
*
* @see #PROPERTY
*/
- COMBINED,
+ COMBINED(SyntaxInfo.COMBINED),
/**
* Property conditions, e.g. "%properties% is/are data value[s]"
*/
- PROPERTY,
+ PROPERTY(PropertyCondition.DEFAULT_PRIORITY),
/**
* Conditions whose pattern matches (almost) everything or should be last checked.
*/
- PATTERN_MATCHES_EVERYTHING;
+ PATTERN_MATCHES_EVERYTHING(SyntaxInfo.PATTERN_MATCHES_EVERYTHING);
+
+ @ApiStatus.Experimental
+ private final Priority priority;
+
+ @ApiStatus.Experimental
+ ConditionType(Priority priority) {
+ this.priority = priority;
+ }
+
+ /**
+ * @return The Priority equivalent of this ConditionType.
+ */
+ @ApiStatus.Experimental
+ public Priority priority() {
+ return this.priority;
+ }
+
}
private boolean negated;
diff --git a/src/main/java/ch/njol/skript/lang/ExpressionType.java b/src/main/java/ch/njol/skript/lang/ExpressionType.java
index 013f6ea435a..b47c538e884 100644
--- a/src/main/java/ch/njol/skript/lang/ExpressionType.java
+++ b/src/main/java/ch/njol/skript/lang/ExpressionType.java
@@ -2,6 +2,10 @@
import ch.njol.skript.expressions.base.EventValueExpression;
import ch.njol.skript.expressions.base.PropertyExpression;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Nullable;
+import org.skriptlang.skript.registration.SyntaxInfo;
+import org.skriptlang.skript.util.Priority;
/**
* Used to define in which order to parse expressions.
@@ -11,32 +15,63 @@ public enum ExpressionType {
/**
* Expressions that only match simple text, e.g. "[the] player"
*/
- SIMPLE,
+ SIMPLE(SyntaxInfo.SIMPLE),
/**
* Expressions that are related to the Event that are typically simple.
*
* @see EventValueExpression
*/
- EVENT,
+ EVENT(EventValueExpression.DEFAULT_PRIORITY),
/**
* Expressions that contain other expressions, e.g. "[the] distance between %location% and %location%"
*
* @see #PROPERTY
*/
- COMBINED,
+ COMBINED(SyntaxInfo.COMBINED),
/**
* Property expressions, e.g. "[the] data value[s] of %items%"/"%items%'[s] data value[s]"
*
* @see PropertyExpression
*/
- PROPERTY,
+ PROPERTY(PropertyExpression.DEFAULT_PRIORITY),
/**
* Expressions whose pattern matches (almost) everything. Typically when using regex. Example: "[the] [loop-]<.+>"
*/
- PATTERN_MATCHES_EVERYTHING;
+ PATTERN_MATCHES_EVERYTHING(SyntaxInfo.PATTERN_MATCHES_EVERYTHING);
+
+ @ApiStatus.Experimental
+ private final Priority priority;
+
+ @ApiStatus.Experimental
+ ExpressionType(Priority priority) {
+ this.priority = priority;
+ }
+
+ /**
+ * @return The Priority equivalent of this ExpressionType.
+ */
+ @ApiStatus.Experimental
+ public Priority priority() {
+ return priority;
+ }
+
+ @ApiStatus.Experimental
+ public static @Nullable ExpressionType fromModern(Priority priority) {
+ if (priority == SyntaxInfo.SIMPLE)
+ return ExpressionType.SIMPLE;
+ if (priority == EventValueExpression.DEFAULT_PRIORITY)
+ return ExpressionType.EVENT;
+ if (priority == SyntaxInfo.COMBINED)
+ return ExpressionType.COMBINED;
+ if (priority == PropertyExpression.DEFAULT_PRIORITY)
+ return ExpressionType.PROPERTY;
+ if (priority == SyntaxInfo.PATTERN_MATCHES_EVERYTHING)
+ return ExpressionType.PATTERN_MATCHES_EVERYTHING;
+ return null;
+ }
}
diff --git a/src/main/java/ch/njol/skript/lang/SkriptEvent.java b/src/main/java/ch/njol/skript/lang/SkriptEvent.java
index 4080fd923ff..c7eb245fb22 100644
--- a/src/main/java/ch/njol/skript/lang/SkriptEvent.java
+++ b/src/main/java/ch/njol/skript/lang/SkriptEvent.java
@@ -8,10 +8,11 @@
import ch.njol.skript.events.EvtClick;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.structures.StructEvent.EventData;
-import ch.njol.skript.util.Utils;
-import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.EventPriority;
+import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos;
+import ch.njol.skript.util.Utils;
+import org.bukkit.event.Cancellable;
import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.lang.entry.EntryContainer;
import org.skriptlang.skript.lang.script.Script;
@@ -238,29 +239,7 @@ public boolean canExecuteAsynchronously() {
* to be nullable.
*/
public static String fixPattern(String pattern) {
- char[] chars = pattern.toCharArray();
- StringBuilder stringBuilder = new StringBuilder();
-
- boolean inType = false;
- for (int i = 0; i < chars.length; i++) {
- char character = chars[i];
- stringBuilder.append(character);
-
- if (character == '%') {
- // toggle inType
- inType = !inType;
-
- // add the dash character if it's not already present
- // a type specification can have two prefix characters for modification
- if (inType && i + 2 < chars.length && chars[i + 1] != '-' && chars[i + 2] != '-')
- stringBuilder.append('-');
- } else if (character == '\\' && i + 1 < chars.length) {
- // Make sure we don't toggle inType for escape percentage signs
- stringBuilder.append(chars[i + 1]);
- i++;
- }
- }
- return stringBuilder.toString();
+ return BukkitSyntaxInfos.fixPattern(pattern);
}
@Nullable
diff --git a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java
index ea593cb94ce..c083adaf7ad 100644
--- a/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java
+++ b/src/main/java/ch/njol/skript/lang/SkriptEventInfo.java
@@ -1,16 +1,28 @@
package ch.njol.skript.lang;
+import ch.njol.skript.Skript;
import ch.njol.skript.SkriptAPIException;
import ch.njol.skript.SkriptConfig;
import ch.njol.skript.lang.SkriptEvent.ListeningBehavior;
+import ch.njol.skript.lang.SkriptEventInfo.ModernSkriptEventInfo;
import org.bukkit.event.Event;
import org.bukkit.event.player.PlayerInteractAtEntityEvent;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.Unmodifiable;
+import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos;
import org.skriptlang.skript.lang.structure.StructureInfo;
+import org.skriptlang.skript.registration.SyntaxInfo;
+import org.skriptlang.skript.registration.SyntaxOrigin;
+import org.skriptlang.skript.util.Priority;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+import java.util.List;
import java.util.Locale;
-public final class SkriptEventInfo extends StructureInfo {
+public sealed class SkriptEventInfo extends StructureInfo permits ModernSkriptEventInfo {
public Class extends Event>[] events;
public final String name;
@@ -182,4 +194,125 @@ public ListeningBehavior getListeningBehavior() {
return documentationID;
}
+ /*
+ * Registration API Compatibility
+ */
+
+ /**
+ * Internal wrapper class for providing compatibility with the new Registration API.
+ */
+ @ApiStatus.Internal
+ @ApiStatus.Experimental
+ public static final class ModernSkriptEventInfo
+ extends SkriptEventInfo
+ implements BukkitSyntaxInfos.Event {
+
+ private final SyntaxOrigin origin;
+
+ public ModernSkriptEventInfo(String name, String[] patterns, Class eventClass, String originClassPath, Class extends Event>[] events) {
+ super(name, patterns, eventClass, originClassPath, events);
+ origin = SyntaxOrigin.of(Skript.getAddon(JavaPlugin.getProvidingPlugin(eventClass)));
+ }
+
+ @Override
+ public Builder extends Builder, E>, E> builder() {
+ return BukkitSyntaxInfos.Event.builder(type(), name())
+ .origin(origin)
+ .addPatterns(patterns())
+ .priority(priority())
+ .listeningBehavior(listeningBehavior())
+ .since(since())
+ .documentationId(id())
+ .addDescription(description())
+ .addExamples(examples())
+ .addKeywords(keywords())
+ .addRequiredPlugins(requiredPlugins())
+ .addEvents(events());
+ }
+
+ @Override
+ public SyntaxOrigin origin() {
+ return origin;
+ }
+
+ @Override
+ public Class type() {
+ return getElementClass();
+ }
+
+ @Override
+ public E instance() {
+ try {
+ return type().getDeclaredConstructor().newInstance();
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
+ NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public @Unmodifiable Collection patterns() {
+ return List.of(getPatterns());
+ }
+
+ @Override
+ public Priority priority() {
+ return SyntaxInfo.COMBINED;
+ }
+
+ @Override
+ public ListeningBehavior listeningBehavior() {
+ return getListeningBehavior();
+ }
+
+ @Override
+ public String name() {
+ return getName();
+ }
+
+ @Override
+ public String id() {
+ return getId();
+ }
+
+ @Override
+ public @Nullable String since() {
+ return getSince();
+ }
+
+ @Override
+ public @Nullable String documentationId() {
+ return getDocumentationID();
+ }
+
+ @Override
+ public Collection description() {
+ String[] description = getDescription();
+ return description != null ? List.of(description) : List.of();
+ }
+
+ @Override
+ public Collection examples() {
+ String[] examples = getExamples();
+ return examples != null ? List.of(examples) : List.of();
+ }
+
+ @Override
+ public Collection keywords() {
+ String[] keywords = getKeywords();
+ return keywords != null ? List.of(keywords) : List.of();
+ }
+
+ @Override
+ public Collection requiredPlugins() {
+ String[] requiredPlugins = getRequiredPlugins();
+ return requiredPlugins != null ? List.of(requiredPlugins) : List.of();
+ }
+
+ @Override
+ public Collection> events() {
+ return List.of(events);
+ }
+ }
+
}
diff --git a/src/main/java/ch/njol/skript/lang/SyntaxElementInfo.java b/src/main/java/ch/njol/skript/lang/SyntaxElementInfo.java
index d639ae59e83..2724451d23d 100644
--- a/src/main/java/ch/njol/skript/lang/SyntaxElementInfo.java
+++ b/src/main/java/ch/njol/skript/lang/SyntaxElementInfo.java
@@ -1,12 +1,18 @@
package ch.njol.skript.lang;
+import org.bukkit.event.Event;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Contract;
+import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos;
+import org.skriptlang.skript.registration.SyntaxInfo;
+import org.skriptlang.skript.lang.structure.StructureInfo;
+
import ch.njol.skript.SkriptAPIException;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
- * @author Peter Güttinger
* @param the syntax element this info is for
*/
public class SyntaxElementInfo {
@@ -56,4 +62,50 @@ public String[] getPatterns() {
public String getOriginClassPath() {
return originClassPath;
}
+
+ @Contract("_ -> new")
+ @ApiStatus.Experimental
+ @SuppressWarnings("unchecked")
+ public static , E extends SyntaxElement> I fromModern(SyntaxInfo extends E> info) {
+ if (info instanceof BukkitSyntaxInfos.Event> event) {
+ // We must first go back to the raw input
+ String rawName = event.name().startsWith("On ")
+ ? event.name().substring(3)
+ : "*" + event.name();
+ SkriptEventInfo> eventInfo = new SkriptEventInfo<>(
+ rawName, event.patterns().toArray(new String[0]),
+ event.type(), event.origin().name(),
+ (Class extends Event>[]) event.events().toArray(new Class>[0]));
+ String since = event.since();
+ if (since != null)
+ eventInfo.since(since);
+ String documentationId = event.documentationId();
+ if (documentationId != null)
+ eventInfo.documentationID(documentationId);
+ eventInfo.listeningBehavior(event.listeningBehavior())
+ .description(event.description().toArray(new String[0]))
+ .examples(event.examples().toArray(new String[0]))
+ .keywords(event.keywords().toArray(new String[0]))
+ .requiredPlugins(event.requiredPlugins().toArray(new String[0]));
+
+ return (I) eventInfo;
+ } else if (info instanceof SyntaxInfo.Structure> structure) {
+ return (I) new StructureInfo<>(structure.patterns().toArray(new String[0]), structure.type(),
+ structure.origin().name(), structure.entryValidator(), structure.nodeType());
+ } else if (info instanceof SyntaxInfo.Expression, ?> expression) {
+ return (I) fromModernExpression(expression);
+ }
+
+ return (I) new SyntaxElementInfo<>(info.patterns().toArray(new String[0]), info.type(), info.origin().name());
+ }
+
+ @Contract("_ -> new")
+ @ApiStatus.Experimental
+ private static , R> ExpressionInfo fromModernExpression(SyntaxInfo.Expression info) {
+ return new ExpressionInfo<>(
+ info.patterns().toArray(new String[0]), info.returnType(),
+ info.type(), info.origin().name(), ExpressionType.fromModern(info.priority())
+ );
+ }
+
}
diff --git a/src/main/java/ch/njol/skript/localization/Language.java b/src/main/java/ch/njol/skript/localization/Language.java
index 95b917056d4..e31567c8377 100644
--- a/src/main/java/ch/njol/skript/localization/Language.java
+++ b/src/main/java/ch/njol/skript/localization/Language.java
@@ -1,11 +1,12 @@
package ch.njol.skript.localization;
import ch.njol.skript.Skript;
-import ch.njol.skript.SkriptAddon;
import ch.njol.skript.config.Config;
import ch.njol.skript.util.ExceptionUtils;
import ch.njol.skript.util.FileUtils;
import ch.njol.skript.util.Version;
+import org.skriptlang.skript.addon.SkriptAddon;
+import org.skriptlang.skript.localization.Localizer;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.Nullable;
@@ -46,7 +47,7 @@ public class Language {
@Nullable
private static HashMap localizedLanguage = null;
- private static final HashMap langVersion = new HashMap<>();
+ private static final HashMap langVersion = new HashMap<>();
public static String getName() {
return name;
@@ -165,46 +166,80 @@ public static boolean isInitialized() {
return !defaultLanguage.isEmpty();
}
+ @Nullable
+ private static String getSanitizedLanguageDirectory(SkriptAddon addon) {
+ Localizer localizer = addon.localizer();
+ if (localizer == null) {
+ return null;
+ }
+ String languageFileDirectory = localizer.languageFileDirectory();
+ if (languageFileDirectory == null) {
+ return null;
+ }
+ // sanitization
+ languageFileDirectory = languageFileDirectory.replace('\\', '/');
+ if (languageFileDirectory.startsWith("/")) {
+ languageFileDirectory = languageFileDirectory.substring(1);
+ }
+ if (languageFileDirectory.endsWith("/")) {
+ languageFileDirectory = languageFileDirectory.substring(0, languageFileDirectory.length() - 1);
+ }
+ return languageFileDirectory;
+ }
+
public static void loadDefault(SkriptAddon addon) {
- if (addon.getLanguageFileDirectory() == null)
+ String languageFileDirectory = getSanitizedLanguageDirectory(addon);
+ if (languageFileDirectory == null) {
return;
+ }
- InputStream defaultIs = addon.plugin.getResource(addon.getLanguageFileDirectory() + "/default.lang");
- InputStream englishIs = addon.plugin.getResource(addon.getLanguageFileDirectory() + "/english.lang");
+ Class> source = addon.source();
+ assert source != null; // getSanitizedLanguageDirectory call means source should not be null
+ try (
+ InputStream defaultIs = source.getResourceAsStream("/" + languageFileDirectory + "/default.lang");
+ InputStream englishIs = source.getResourceAsStream("/" + languageFileDirectory + "/english.lang")
+ ) {
- if (defaultIs == null) {
- if (englishIs == null) {
- throw new IllegalStateException(addon + " is missing the required default.lang file!");
- } else {
- defaultIs = englishIs;
- englishIs = null;
+ InputStream defaultLangIs = defaultIs;
+ InputStream englishLangIs = englishIs;
+ if (defaultLangIs == null) {
+ if (englishLangIs == null) {
+ throw new IllegalStateException(addon + " is missing the required default.lang file!");
+ } else {
+ defaultLangIs = englishLangIs;
+ englishLangIs = null;
+ }
}
- }
- Map def = load(defaultIs, "default", false);
- Map en = load(englishIs, "english", addon == Skript.getAddonInstance());
- String v = def.get("version");
- if (v == null)
- Skript.warning("Missing version in default.lang");
+ Map def = load(defaultLangIs, "default", false);
+ Map en = load(englishLangIs, "english", addon instanceof org.skriptlang.skript.Skript);
- langVersion.put(addon.plugin, v == null ? Skript.getVersion() : new Version(v));
- def.remove("version");
- defaultLanguage.putAll(def);
+ String v = def.get("version");
+ if (v == null)
+ Skript.warning("Missing version in default.lang");
- if (localizedLanguage == null)
- localizedLanguage = new HashMap<>();
- localizedLanguage.putAll(en);
+ langVersion.put(addon.name(), v == null ? Skript.getVersion() : new Version(v));
+ def.remove("version");
+ defaultLanguage.putAll(def);
- for (LanguageChangeListener l : listeners)
- l.onLanguageChange();
+ if (localizedLanguage == null)
+ localizedLanguage = new HashMap<>();
+ localizedLanguage.putAll(en);
+
+ for (LanguageChangeListener l : listeners)
+ l.onLanguageChange();
+
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
}
public static boolean load(String name) {
name = "" + name.toLowerCase(Locale.ENGLISH);
localizedLanguage = new HashMap<>();
- boolean exists = load(Skript.getAddonInstance(), name, true);
- for (SkriptAddon addon : Skript.getAddons()) {
+ boolean exists = load(Skript.instance(), name, true);
+ for (SkriptAddon addon : Skript.instance().addons()) {
exists |= load(addon, name, false);
}
if (!exists) {
@@ -225,20 +260,42 @@ public static boolean load(String name) {
}
private static boolean load(SkriptAddon addon, String name, boolean tryUpdate) {
- if (addon.getLanguageFileDirectory() == null)
+ String languageFileDirectory = getSanitizedLanguageDirectory(addon);
+ if (languageFileDirectory == null) {
return false;
+ }
+
+ Class> source = addon.source();
+
// Backwards addon compatibility
- if (name.equals("english") && addon.plugin.getResource(addon.getLanguageFileDirectory() + "/default.lang") == null)
- return true;
+ if (name.equals("english")) {
+ try (InputStream is = source.getResourceAsStream("/" + languageFileDirectory + "/default.lang")) {
+ if (is == null) {
+ return true;
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
- Map l = load(addon.plugin.getResource(addon.getLanguageFileDirectory() + "/" + name + ".lang"), name, tryUpdate);
- File file = new File(addon.plugin.getDataFolder(), addon.getLanguageFileDirectory() + File.separator + name + ".lang");
- try {
- if (file.exists())
- l.putAll(load(new FileInputStream(file), name, tryUpdate));
- } catch (FileNotFoundException e) {
- assert false;
+ Map l;
+ try (InputStream is = source.getResourceAsStream("/" + languageFileDirectory + "/" + name + ".lang")) {
+ l = load(is, name, tryUpdate);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
+
+ String dataFileDirectory = addon.localizer().dataFileDirectory();
+ if (dataFileDirectory != null) { // attempt to load language files from disk
+ File file = new File(dataFileDirectory, File.separator + name + ".lang");
+ try {
+ if (file.exists())
+ l.putAll(load(new FileInputStream(file), name, tryUpdate));
+ } catch (FileNotFoundException e) {
+ assert false;
+ }
+ }
+
if (l.isEmpty())
return false;
if (!l.containsKey("version")) {
@@ -246,7 +303,7 @@ private static boolean load(SkriptAddon addon, String name, boolean tryUpdate) {
} else {
try {
Version v = new Version("" + l.get("version"));
- Version lv = langVersion.get(addon.plugin);
+ Version lv = langVersion.get(addon.name());
assert lv != null; // set in loadDefault()
if (v.isSmallerThan(lv))
Skript.warning(addon + "'s language file " + name + ".lang is outdated, some messages will be english.");
diff --git a/src/main/java/ch/njol/skript/util/Utils.java b/src/main/java/ch/njol/skript/util/Utils.java
index dc2b253e158..d9704829b9b 100644
--- a/src/main/java/ch/njol/skript/util/Utils.java
+++ b/src/main/java/ch/njol/skript/util/Utils.java
@@ -1,27 +1,5 @@
package ch.njol.skript.util;
-import ch.njol.skript.Skript;
-import ch.njol.skript.effects.EffTeleport;
-import ch.njol.skript.localization.Language;
-import ch.njol.skript.localization.LanguageChangeListener;
-import ch.njol.skript.registrations.Classes;
-import ch.njol.util.*;
-import ch.njol.util.coll.CollectionUtils;
-import ch.njol.util.coll.iterator.EnumerationIterable;
-import com.google.common.collect.Iterables;
-import com.google.common.io.ByteArrayDataInput;
-import com.google.common.io.ByteArrayDataOutput;
-import com.google.common.io.ByteStreams;
-import net.md_5.bungee.api.ChatColor;
-import org.bukkit.Bukkit;
-import org.bukkit.entity.Player;
-import org.bukkit.plugin.Plugin;
-import org.bukkit.plugin.java.JavaPlugin;
-import org.bukkit.plugin.messaging.Messenger;
-import org.bukkit.plugin.messaging.PluginMessageListener;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
@@ -35,6 +13,35 @@
import java.util.regex.Pattern;
import java.util.stream.Stream;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.bukkit.plugin.messaging.Messenger;
+import org.bukkit.plugin.messaging.PluginMessageListener;
+
+import com.google.common.collect.Iterables;
+import com.google.common.io.ByteArrayDataInput;
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+
+import ch.njol.skript.Skript;
+import ch.njol.skript.effects.EffTeleport;
+import ch.njol.skript.localization.Language;
+import ch.njol.skript.localization.LanguageChangeListener;
+import ch.njol.skript.registrations.Classes;
+import ch.njol.util.Callback;
+import ch.njol.util.Checker;
+import ch.njol.util.NonNullPair;
+import ch.njol.util.Pair;
+import ch.njol.util.StringUtils;
+import ch.njol.util.coll.CollectionUtils;
+import ch.njol.util.coll.iterator.EnumerationIterable;
+import net.md_5.bungee.api.ChatColor;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.NotNull;
+import org.skriptlang.skript.util.ClassLoader;
+
/**
* Utility class.
*
@@ -192,49 +199,25 @@ public static Pair getAmount(String s) {
* as well. Use an empty array to load all subpackages of the base package.
* @throws IOException If some error occurred attempting to read the plugin's jar file.
* @return This SkriptAddon
+ * @deprecated Use {@link org.skriptlang.skript.util.ClassLoader}.
*/
+ @Deprecated
public static Class>[] getClasses(Plugin plugin, String basePackage, String... subPackages) throws IOException {
- assert subPackages != null;
- JarFile jar = new JarFile(getFile(plugin));
- for (int i = 0; i < subPackages.length; i++)
- subPackages[i] = subPackages[i].replace('.', '/') + "/";
- basePackage = basePackage.replace('.', '/') + "/";
List> classes = new ArrayList<>();
- try {
- List classNames = new ArrayList<>();
-
- for (JarEntry e : new EnumerationIterable<>(jar.entries())) {
- if (e.getName().startsWith(basePackage) && e.getName().endsWith(".class") && !e.getName().endsWith("package-info.class")) {
- boolean load = subPackages.length == 0;
- for (String sub : subPackages) {
- if (e.getName().startsWith(sub, basePackage.length())) {
- load = true;
- break;
- }
- }
-
- if (load)
- classNames.add(e.getName().replace('/', '.').substring(0, e.getName().length() - ".class".length()));
- }
- }
-
- classNames.sort(String::compareToIgnoreCase);
-
- for (String c : classNames) {
- try {
- classes.add(Class.forName(c, true, plugin.getClass().getClassLoader()));
- } catch (ClassNotFoundException | NoClassDefFoundError ex) {
- Skript.exception(ex, "Cannot load class " + c);
- } catch (ExceptionInInitializerError err) {
- Skript.exception(err.getCause(), "class " + c + " generated an exception while loading");
- }
- }
- } finally {
- try {
- jar.close();
- } catch (IOException e) {}
+ ClassLoader loader = ClassLoader.builder()
+ .basePackage(basePackage)
+ .addSubPackages(subPackages)
+ .deep(true)
+ .initialize(true)
+ .forEachClass(classes::add)
+ .build();
+ File jarFile = getFile(plugin);
+ if (jarFile != null) {
+ loader.loadClasses(plugin.getClass(), jarFile);
+ } else {
+ loader.loadClasses(plugin.getClass());
}
- return classes.toArray(new Class>[classes.size()]);
+ return classes.toArray(new Class[0]);
}
/**
diff --git a/src/main/java/org/skriptlang/skript/Skript.java b/src/main/java/org/skriptlang/skript/Skript.java
new file mode 100644
index 00000000000..25604cf5cbf
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/Skript.java
@@ -0,0 +1,56 @@
+package org.skriptlang.skript;
+
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.Unmodifiable;
+import org.skriptlang.skript.addon.SkriptAddon;
+
+import java.util.Collection;
+
+/**
+ * The main class for everything related to Skript.
+ */
+@ApiStatus.Experimental
+public interface Skript extends SkriptAddon {
+
+ /**
+ * Constructs a default implementation of a Skript.
+ * It makes use of the default implementations of required components.
+ * @param source The main class of the application creating this Skript.
+ * Typically, this can be the class invoking this method.
+ * @param name The name for the Skript to use.
+ * @return A Skript.
+ */
+ @Contract("_, _ -> new")
+ static Skript of(Class> source, String name) {
+ return new SkriptImpl(source, name);
+ }
+
+ /**
+ * Registers the provided addon with this Skript and loads the provided modules.
+ * @param source The main class of the application registering this addon.
+ * Typically, this can be the class invoking this method.
+ * @param name The name of the addon to register.
+ */
+ @Contract("_, _ -> new")
+ SkriptAddon registerAddon(Class> source, String name);
+
+ /**
+ * @return An unmodifiable snapshot of addons currently registered with this Skript.
+ */
+ @Unmodifiable Collection addons();
+
+ /**
+ * Constructs an unmodifiable view of this Skript.
+ * That is, the returned Skript will be unable to register new addons
+ * and the individual addons from {@link #addons()} will be unmodifiable.
+ * Additionally, it will return unmodifiable views of its inherited {@link SkriptAddon} components.
+ * @return An unmodifiable view of this Skript.
+ */
+ @Override
+ @Contract("-> new")
+ default Skript unmodifiableView() {
+ return new SkriptImpl.UnmodifiableSkript(this, SkriptAddon.super.unmodifiableView());
+ }
+
+}
diff --git a/src/main/java/org/skriptlang/skript/SkriptImpl.java b/src/main/java/org/skriptlang/skript/SkriptImpl.java
new file mode 100644
index 00000000000..03b96f503a5
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/SkriptImpl.java
@@ -0,0 +1,263 @@
+package org.skriptlang.skript;
+
+import ch.njol.skript.SkriptAPIException;
+import com.google.common.collect.ImmutableSet;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.Unmodifiable;
+import org.skriptlang.skript.addon.AddonModule;
+import org.skriptlang.skript.addon.SkriptAddon;
+import org.skriptlang.skript.localization.Localizer;
+import org.skriptlang.skript.registration.SyntaxRegistry;
+import org.skriptlang.skript.util.Registry;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+
+final class SkriptImpl implements Skript {
+
+ /**
+ * The addon instance backing this Skript.
+ */
+ private final SkriptAddon addon;
+
+ SkriptImpl(Class> source, String name) {
+ addon = new SkriptAddonImpl(this, source, name, Localizer.of(this));
+ storeRegistry(SyntaxRegistry.class, SyntaxRegistry.empty());
+ }
+
+ /*
+ * Registry Management
+ */
+
+ private static final Map, Registry>> registries = new ConcurrentHashMap<>();
+
+ @Override
+ public > void storeRegistry(Class registryClass, R registry) {
+ registries.put(registryClass, registry);
+ }
+
+ @Override
+ public void removeRegistry(Class extends Registry>> registryClass) {
+ registries.remove(registryClass);
+ }
+
+ @Override
+ public boolean hasRegistry(Class extends Registry>> registryClass) {
+ return registries.containsKey(registryClass);
+ }
+
+ @Override
+ public > R registry(Class registryClass) {
+ //noinspection unchecked
+ R registry = (R) registries.get(registryClass);
+ if (registry == null)
+ throw new NullPointerException("Registry not present for " + registryClass);
+ return registry;
+ }
+
+ @Override
+ public > R registry(Class registryClass, Supplier putIfAbsent) {
+ //noinspection unchecked
+ return (R) registries.computeIfAbsent(registryClass, key -> putIfAbsent.get());
+ }
+
+ /*
+ * SkriptAddon Management
+ */
+
+ private static final Map addons = new HashMap<>();
+
+ @Override
+ public SkriptAddon registerAddon(Class> source, String name) {
+ // make sure an addon is not already registered with this name
+ SkriptAddon existing = addons.get(name);
+ if (existing != null) {
+ throw new SkriptAPIException(
+ "An addon (provided by '" + existing.source().getName() + "') with the name '" + name + "' is already registered"
+ );
+ }
+
+ SkriptAddon addon = new SkriptAddonImpl(this, source, name, null);
+ addons.put(name, addon);
+ return addon;
+ }
+
+ @Override
+ public @Unmodifiable Collection addons() {
+ return ImmutableSet.copyOf(addons.values());
+ }
+
+ /*
+ * SkriptAddon Implementation
+ */
+
+ @Override
+ public Class> source() {
+ return addon.source();
+ }
+
+ @Override
+ public String name() {
+ return addon.name();
+ }
+
+ @Override
+ public SyntaxRegistry syntaxRegistry() {
+ return registry(SyntaxRegistry.class);
+ }
+
+ @Override
+ public Localizer localizer() {
+ return addon.localizer();
+ }
+
+ @Override
+ public void loadModules(AddonModule... modules) {
+ addon.loadModules(modules);
+ }
+
+ private static final class SkriptAddonImpl implements SkriptAddon {
+
+ private final Skript skript;
+ private final Class> source;
+ private final String name;
+ private final Localizer localizer;
+
+ SkriptAddonImpl(Skript skript, Class> source, String name, @Nullable Localizer localizer) {
+ this.skript = skript;
+ this.source = source;
+ this.name = name;
+ this.localizer = localizer == null ? Localizer.of(this) : localizer;
+ }
+
+ @Override
+ public Class> source() {
+ return source;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public > void storeRegistry(Class registryClass, R registry) {
+ skript.storeRegistry(registryClass, registry);
+ }
+
+ @Override
+ public void removeRegistry(Class extends Registry>> registryClass) {
+ skript.removeRegistry(registryClass);
+ }
+
+ @Override
+ public boolean hasRegistry(Class extends Registry>> registryClass) {
+ return skript.hasRegistry(registryClass);
+ }
+
+ @Override
+ public > R registry(Class registryClass) {
+ return skript.registry(registryClass);
+ }
+
+ @Override
+ public > R registry(Class registryClass, Supplier putIfAbsent) {
+ return skript.registry(registryClass, putIfAbsent);
+ }
+
+ @Override
+ public SyntaxRegistry syntaxRegistry() {
+ return skript.syntaxRegistry();
+ }
+
+ @Override
+ public Localizer localizer() {
+ return localizer;
+ }
+
+ }
+
+ /*
+ * ViewProvider Implementation
+ */
+
+ static final class UnmodifiableSkript implements Skript {
+
+ private final Skript skript;
+ private final SkriptAddon unmodifiableAddon;
+
+ UnmodifiableSkript(Skript skript, SkriptAddon unmodifiableAddon) {
+ this.skript = skript;
+ this.unmodifiableAddon = unmodifiableAddon;
+ }
+
+ @Override
+ public SkriptAddon registerAddon(Class> source, String name) {
+ throw new UnsupportedOperationException("Cannot register addons using an unmodifiable Skript");
+ }
+
+ @Override
+ public @Unmodifiable Collection addons() {
+ ImmutableSet.Builder addons = ImmutableSet.builder();
+ skript.addons().stream()
+ .map(SkriptAddon::unmodifiableView)
+ .forEach(addons::add);
+ return addons.build();
+ }
+
+ @Override
+ public Class> source() {
+ return skript.source();
+ }
+
+ @Override
+ public String name() {
+ return skript.name();
+ }
+
+ @Override
+ public > void storeRegistry(Class registryClass, R registry) {
+ unmodifiableAddon.storeRegistry(registryClass, registry);
+ }
+
+ @Override
+ public void removeRegistry(Class extends Registry>> registryClass) {
+ unmodifiableAddon.removeRegistry(registryClass);
+ }
+
+ @Override
+ public boolean hasRegistry(Class extends Registry>> registryClass) {
+ return unmodifiableAddon.hasRegistry(registryClass);
+ }
+
+ @Override
+ public > R registry(Class registryClass) {
+ return unmodifiableAddon.registry(registryClass);
+ }
+
+ @Override
+ public > R registry(Class registryClass, Supplier putIfAbsent) {
+ return unmodifiableAddon.registry(registryClass, putIfAbsent);
+ }
+
+ @Override
+ public SyntaxRegistry syntaxRegistry() {
+ return unmodifiableAddon.syntaxRegistry();
+ }
+
+ @Override
+ public Localizer localizer() {
+ return unmodifiableAddon.localizer();
+ }
+
+ @Override
+ public void loadModules(AddonModule... modules) {
+ unmodifiableAddon.loadModules(modules);
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/skriptlang/skript/addon/AddonModule.java b/src/main/java/org/skriptlang/skript/addon/AddonModule.java
new file mode 100644
index 00000000000..243d51b730b
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/addon/AddonModule.java
@@ -0,0 +1,36 @@
+package org.skriptlang.skript.addon;
+
+import org.jetbrains.annotations.ApiStatus;
+import org.skriptlang.skript.Skript;
+
+/**
+ * A module is a component of a {@link SkriptAddon} used for registering syntax and other {@link Skript} components.
+ *
+ * Modules have two loading phases: {@link #init(SkriptAddon)} followed by {@link #load(SkriptAddon)}.
+ *
+ * The init
phase should be used for loading components that are needed first or that may be used by other modules,
+ * such as class infos (think numeric types that are used everywhere).
+ *
+ * The load
phase should be used for loading components more specific to the module, such as syntax.
+ * @see SkriptAddon#loadModules(AddonModule...)
+ */
+@FunctionalInterface
+@ApiStatus.Experimental
+public interface AddonModule {
+
+ /**
+ * Used for loading the components of this module that are needed first or by other modules (e.g. class infos).
+ * This method will always be called before {@link #load(SkriptAddon)}.
+ * @param addon The addon this module belongs to.
+ * @see #load(SkriptAddon)
+ */
+ default void init(SkriptAddon addon) { }
+
+ /**
+ * Used for loading the components (e.g. syntax) of this module.
+ * @param addon The addon this module belongs to.
+ * @see #init(SkriptAddon)
+ */
+ void load(SkriptAddon addon);
+
+}
diff --git a/src/main/java/org/skriptlang/skript/addon/SkriptAddon.java b/src/main/java/org/skriptlang/skript/addon/SkriptAddon.java
new file mode 100644
index 00000000000..13e47180ff3
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/addon/SkriptAddon.java
@@ -0,0 +1,112 @@
+package org.skriptlang.skript.addon;
+
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.Contract;
+import org.skriptlang.skript.Skript;
+import org.skriptlang.skript.localization.Localizer;
+import org.skriptlang.skript.registration.SyntaxRegistry;
+import org.skriptlang.skript.util.Registry;
+import org.skriptlang.skript.util.ViewProvider;
+
+import java.util.function.Supplier;
+
+/**
+ * A Skript addon is an extension to Skript that expands its features.
+ * Typically, an addon instance may be obtained through {@link Skript#registerAddon(Class, String)}.
+ */
+@ApiStatus.Experimental
+public interface SkriptAddon extends ViewProvider {
+
+ /**
+ * @return A class from the application that registered this addon.
+ * Typically, this is the main class or the specific class in which registration occurred.
+ */
+ Class> source();
+
+ /**
+ * @return The name of this addon.
+ */
+ String name();
+
+ /**
+ * Stores a registry under registryClass
.
+ * If a registry is already stored under registryClass
, it will be replaced.
+ * @param registryClass The class (key) to store registry
under.
+ * @param registry The registry to store.
+ * @param The type of registry.
+ */
+ > void storeRegistry(Class registryClass, R registry);
+
+ /**
+ * Removes the registry stored under registryClass
.
+ * It is safe to call this method even if a registry is not stored under registryClass
.
+ * @param registryClass The class (key) that the registry to remove is under.
+ */
+ void removeRegistry(Class extends Registry>> registryClass);
+
+ /**
+ * Determines whether a registry has been stored under registryClass
.
+ * @param registryClass The class (key) to search for a registry under.
+ * @return Whether a registry is stored under registryClass
.
+ */
+ boolean hasRegistry(Class extends Registry>> registryClass);
+
+ /**
+ * Obtains the registry stored under registryClass
.
+ * This method will never return null, meaning it may be necessary to call {@link #hasRegistry(Class)}
+ * if you are not sure whether the registry you need exists.
+ * @param registryClass The class (key) that the registry is stored under.
+ * @return The registry stored under registryClass
.
+ * @param The type of registry.
+ */
+ > R registry(Class