Skip to content

Commit

Permalink
New Registration API (#6246)
Browse files Browse the repository at this point in the history
* 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 <apickledwalrus@gmail.com>

* Apply suggestions from code review

Co-authored-by: Patrick Miller <apickledwalrus@gmail.com>

* 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 <apickledwalrus@gmail.com>
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 <admin@moderocky.com>
Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com>
Co-authored-by: Efnilite <35348263+Efnilite@users.noreply.github.com>
  • Loading branch information
6 people authored Dec 29, 2024
1 parent 42ba3ea commit a0f3a5b
Show file tree
Hide file tree
Showing 39 changed files with 4,053 additions and 323 deletions.
352 changes: 213 additions & 139 deletions src/main/java/ch/njol/skript/Skript.java

Large diffs are not rendered by default.

119 changes: 92 additions & 27 deletions src/main/java/ch/njol/skript/SkriptAddon.java
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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 <b>must</b> exist!
Expand All @@ -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
Expand All @@ -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 <R extends Registry<?>> void storeRegistry(Class<R> 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 extends Registry<?>> R registry(Class<R> registryClass) {
return addon.registry(registryClass);
}

@Override
@ApiStatus.Experimental
public <R extends Registry<?>> R registry(Class<R> registryClass, Supplier<R> putIfAbsent) {
return addon.registry(registryClass, putIfAbsent);
}

@Override
@ApiStatus.Experimental
public SyntaxRegistry syntaxRegistry() {
return addon.syntaxRegistry();
}

@Override
@ApiStatus.Experimental
public Localizer localizer() {
return addon.localizer();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -36,6 +40,14 @@
*/
public abstract class PropertyCondition<T> extends Condition implements Checker<T> {

/**
* 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
*/
Expand Down Expand Up @@ -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 <i>fly</i> in <i>players can fly</i>
* @param type Must be plural, for example <i>players</i> in <i>players can fly</i>
* @param <E> The Condition type.
* @return The registered {@link SyntaxInfo}.
*/
@ApiStatus.Experimental
public static <E extends Condition> SyntaxInfo<E> register(SyntaxRegistry registry, Class<E> 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 <i>fly</i> in <i>players can fly</i>
* @param type Must be plural, for example <i>players</i> in <i>players can fly</i>
* @param <E> The Condition type.
* @return The registered {@link SyntaxInfo}.
*/
@ApiStatus.Experimental
public static <E extends Condition> SyntaxInfo<E> register(SyntaxRegistry registry, Class<E> 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<E> info = builder.build();
registry.register(SyntaxRegistry.CONDITION, info);
return info;
}

/**
* Registers a new property condition. The property type is set to {@link PropertyType#BE}.
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -47,6 +51,36 @@
*/
public class EventValueExpression<T> extends SimpleExpression<T> implements DefaultExpression<T> {

/**
* 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 <T> The return type.
* @param <E> The Expression type.
* @return The registered {@link SyntaxInfo}.
*/
@ApiStatus.Experimental
public static <E extends EventValueExpression<T>, T> SyntaxInfo.Expression<E, T> register(SyntaxRegistry registry, Class<E> expressionClass, Class<T> returnType, String pattern) {
SyntaxInfo.Expression<E, T> 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.
Expand Down
Loading

0 comments on commit a0f3a5b

Please sign in to comment.